QIconでSVGを表示する

# はじめに

Qt widgetでアイコンを表すクラスQIconは、**SVGをサポートしています**。

[QIcon Class](http://doc.qt.io/qt-5/qicon.html#details)によれば、

> Note: Since Qt 4.2, an icon engine that supports SVG is included.

早速やっていきましょう。


# なぜSVGを使うのか

* ベクター画像のため、サイズに依存しない高品質な描画が可能(特に高解像度ディスプレイで効果を発揮する)
* **アイコンにおいてもDRY[^1]を推進できる**(かもしれない)

前者はよく言われていますが、今回は後者の利点に着目してみます。

[^1]: Don't repeat yourself; 同じことを繰り返さない

## アイコンにおけるDRY

わかりやすさの点、あるいはデザインの省力化の点から、しばしばアイコンのデザインは流用されます。例えば、ある要素のアイコンに`+`や`x`を重ねて"追加"、"削除"を表現したり、同じデザインのアイコンを回転させて方向を表現したりします。

ソースコードにおけるコピー&ペーストと同様、このような使い回しはデザインの変更を困難にします :no_good: 

SVGは`<use>`要素によって他のSVGファイルから図形を取り込むことができます。また、色やグラデーションといったスタイルに名前を付けて取り込むこともできます。

SVGをアイコンに使えば、これらの機能を活用してDRYをやっていくことができます :ok_woman:

# SVGをQIconに表示する

以下のコードで`icon.svg`をアクションのアイコンとしてツールバーに表示できます。

```cpp
#include <QApplication>
#include <QWidget>
#include <QToolBar>
#include <QAction>
#include <QIcon>
#include <QMainWindow>
#include <memory>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    auto window = std::make_unique<QMainWindow>();

    QToolBar* toolbar = window->addToolBar("Tools");
    toolbar->addAction(new QAction(QIcon("icon.svg"), "action", window.get()));

    window->show();
    return app.exec();
}
```

# use要素を使う

[QIcon Class](http://doc.qt.io/qt-5/qicon.html#details)には書いていないですが、SVGの描画には[Qt SVG](https://doc.qt.io/qt-5/qtsvg-index.html)が使われているはずです。

[Rendering SVG Files](https://doc.qt.io/qt-5/svgrendering.html)を見ると、Qt SVGは[SVG 1.2 Tiny](https://www.w3.org/TR/SVGMobile12/index.html)をサポートしていると書かれています。

> Qt supports the static features of [SVG 1.2 Tiny](https://www.w3.org/TR/SVGMobile12/index.html). ECMA scripts and DOM manipulation are currently not supported.

[SVG 1.2 Tiny](https://www.w3.org/TR/SVGMobile12/index.html)仕様の[14.1.4 Reference restrictions](https://www.w3.org/TR/SVGMobile12/linking.html)を見ると、SVG 1.2 Tinyで`<use>`は使える上に、外部ファイルのリンクも可能なようです。

Qt SVGは実装しているのでしょうか？

結論から言うと、同一ファイル内の要素は指定できますが、別のSVGファイルにある要素は指定できませんでした[^2]。

[^2]: *static features*ではないということでしょうか？

つまり、`xlink:href="#id"`のような指定は有効ですが、`xlink:href="external.svg#id"`のような指定は(SVG 1.2 Tinyとしては有効のはずだが)動かないという事です。

## ソースを見てみよう

[use要素のハンドラー](https://github.com/qt/qtsvg/blob/5.11/src/svg/qsvghandler.cpp#L3338)を見てみると……

```cpp:qsvghandler.cpp
static QSvgNode *createUseNode(QSvgNode *parent,
                               const QXmlStreamAttributes &attributes,
                               QSvgHandler *handler)
{
    QString linkId = attributes.value(QLatin1String("xlink:href")).toString().remove(0, 1);
    const QStringRef xStr = attributes.value(QLatin1String("x"));
    const QStringRef yStr = attributes.value(QLatin1String("y"));
    QSvgStructureNode *group = 0;
    /*略*/
}
```
1行目からして明らかに`#id`の形式しか想定していないですね。
QtでアイコンのDRYをやっていくのは現状では難しいようです:no_good:

しかし、`image`要素の方はちゃんと外部ファイルを読み込むように実装してあるので、同じようにSVGを再帰的にロードすれば動かせるような気がします[^3]。

[^3]: 循環参照を排除したりといった例外処理は必要

```cpp:qsvghandler.cpp
static QSvgNode *createImageNode(QSvgNode *parent,
                                 const QXmlStreamAttributes &attributes,
                                 QSvgHandler *handler)
{
    /*略*/
    QStringRef filename = attributes.value(QLatin1String("xlink:href"));
    /*略*/
    if (filename.startsWith(QLatin1String("data"))) {
        /*略*/
    } else
        image = QImage(filename.toString());
    /*略*/
}
```

# まとめ

* QIconはSVGを扱えます
* SVG 1.2 Tinyを**部分的に**サポートしています
* 外部SVGファイルをリンクすることはできませんでした[^4]
    * もしかしたら意外と簡単に実装できるかも

[^4]: コードから判断すると、`use`だけでなく全体的に外部のSVGを使うことはできない模様