Forschen

Skalierbare Icons in TrueType-Schriften


Entwickelt man gerade eine Anwendung, in der eine Vielzahl verschiedener Piktogramme benötigt werden, stellt sich die Frage wie man diese möglichst einfach und platzsparend unterbringen kann. Kommt dann noch die Anforderung hinzu, dass die kleinen Bildchen nach Bedarf einfärbbar sein sollen und auf den heutzutage verfügbaren Endgeräten überall eine gestochen scharfe Anzeige erwartet wird, kann diese Aufgabe mit herkömmlichen Pixel-Bildern schnell knifflig werden.

Ein erster Ansatz könnte sein, die Icons als skalierbare SVGs in der Anwendung abzulegen. Allerdings ist in vielen Fällen die SVG-Darstellung ineffizient, sodass die Anzeige-Performance darunter leidet. Ebenso kann die Verwendung von SVGs dazu führen, dass die Größe der Applikation unnötig zunimmt. An dieser Stelle wird oft zu so genannten Icon-Fonts gegriffen - also Schriftarten, die anstelle “normaler” Zeichen die gewünschten Icons enthalten. Gegenüber klassischen Pixelbildern und SVG ergeben sich hier einige Vorteile:

  • skalierbar
  • schnelles Rendering
  • nachträglich einfärbbar
  • kompakt (ein Beispiel aus einem Projekt: ~800 Icons, PNGs > 30MB, SVGs 3.2MB, TTF 0.2MB)

Natürlich ist die Verwendung einer Icon-Font nicht nur mit Vorteilen verbunden. Je nach Anforderung können sie auch ungeeignet sein - hier ein paar Gründe:

  • einfarbig (dies gilt zumindest für die klassische Standard-Schrift)
  • Quell-SVGs müssen unter Umständen “optimiert” werden

Entscheidet man sich für Icon-Fonts, stellt sich unverzüglich die Frage: wie bekomme ich meine meist als SVG vorliegenden Grafiken in eine TrueType-Schrift? Editoren für Schriften gibt es, allerdings sind die für den Schrift-Laien eher schwieriges Terrain. Aus diesem Grund haben wir ein kleines Skript geschrieben, welches diese Aufgabe für C++-Applikationen übernimmt:

Dieses sammelt alle SVGs innerhalb eines angegebenen Verzeichnisses und erstellt daraus eine TTF-Datei. Weiterhin kann es für “Plain”-C++- und Qt-/Qml-Anwendungen passenden Code erstellen, mit dem sich die Icons dann komfortabel über den ursprünglichen Dateinamen (ohne Dateiendung .svg) nutzen lassen.

Nachfolgend zeigen wir anhand eines Qt-/Qml-Beispiels für “Entwicklungs-Interessierte” wie das Skript eingesetzt werden kann.


Installation

Da das in Python geschriebene Skript Abhängigkeiten zu nativen Bibliotheken hat (insbesondere FontForge), empfehlen wir den Weg über ein kleines Docker-Image zu gehen. Benötigt wird dazu lediglich Docker oder Podman. Auf einem Unix System kann einfach ein Skript heruntergeladen werden, welches die notwendigen Schritte durchführt:

curl https://raw.githubusercontent.com/gonicus/svg-to-ttf/master/svg-to-ttf --output svg-to-ttf
chmod +x svg-to-ttf
mv svg-to-ttf ~/.local/bin
hash -r

Wir verschieben das Skript an eine Stelle im PATH; in unserem Beispiel nach ´~/.local/bin´. Führt man das Skript das erste Mal aus, so bezieht es zunächst die notwendigen Daten für das Docker-Image.

svg-to-ttf --help

Ausgabe:

usage: svg-to-ttf [-h] [--font-name FONT_NAME] [--copyright COPYRIGHT] [--output OUTPUT] [--strip-bounding-rect] [--qt] [--qml-namespace QML_NAMESPACE] [--qml-element QML_ELEMENT] source

positional arguments:
  source

optional arguments:
  -h, --help            show this help message and exit
  --font-name FONT_NAME
                        name of the generated font
  --copyright COPYRIGHT
                        copyright notice placed inside the generated TTF file
  --output OUTPUT       path where generated files are placed
  --strip-bounding-rect
                        path where generated files are placed
  --qt                  whether to build Qt/QML style output files
  --qml-namespace QML_NAMESPACE
                        name of the QML namespace used in your .pro file
  --qml-element QML_ELEMENT
                        name of the QML icon element for this font


Als Quellcode

Alternativ kann das Skript natürlich auch direkt gestartet werden; dazu das Repository mittels git auschecken:

git clone https://github.com/gonicus/svg-to-ttf.git
cd svg-to-ttf

Für einen erfolgreichen Start müssen die folgenden Programmpakete installiert sein:

  • Python 3
  • Beautifulsoup 4
  • Fontforge bindings for Python 3

Das Skript kann dann direkt in src/svg-to-ttf aufgerufen und auch von dort an eine Stelle im PATH kopiert werden.


Beispiel mit Qt/Qml

Schauen wir uns eine einfache Qt-/Qml-Anwendung an (≥5.15), die ein Icon aus den Font-Awesome Icons enthalten soll. Diese Icons sind zwar bereits als TTF verfügbar, allerdings beinhaltet das Repository auch die SVG-Quellen, sodass wir etwas zum “Spielen” haben.

Als ersten Schritt legen wir ein Projekt-Verzeichnis an und wechseln dort hinein:

mkdir demo
cd demo

Nun laden wir aus besagtem Projekt eine Auswahl von SVGs herunter und entpacken diese direkt in das Verzeichnis regular:

curl -L https://github.com/FortAwesome/Font-Awesome/archive/5.15.0.tar.gz | tar xvz --strip-components=2 Font-Awesome-5.15.0/svgs/regular

Ist dies geglückt, können wir die in regular enthaltenen SVGs mittels svg-to-ttf in eine Schriftart für unser Qt-/Qml-Projekt zusammenführen:

svg-to-ttf --qml-namespace Demo --qt regular

Die Ausgabe sollte so ähnlich aussehen:

✓ ./IconFont.ttf has been generated
✓ ./IconFont.h has been generated
✓ ./IconFont.cpp has been generated
✓ ./Icon.qml has been generated

Gut. Nun haben wir eine TTF-Datei, eine einfache IconFontResolver-Klasse (die im Prinzip ein Zuweisung von Datei nach Zeichen-Index enthält) und ein neues Qml-Element mit dem Namen Icon. Zeit, ein qmake-basiertes Projekt mit diesen Dateien aufzubauen:


Projekt-Datei

Die Projektdatei soll demo.pro heißen, im eben angelegten Verzeichnis liegen und den folgenden Inhalt haben:

!versionAtLeast(QT_VERSION, 5.15):error("Use at least Qt version 5.15")

QT += quick gui

CONFIG += c++11 qmltypes

SOURCES += \
        IconFont.cpp \
        main.cpp

RESOURCES += qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =

# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =

QML_IMPORT_NAME = Demo
QML_IMPORT_MAJOR_VERSION = 1

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    IconFont.h

Hier ist anzumerken, dass QML_IMPORT_NAME mit der Angabe von --qml-namespace übereinstimmen muss, da die im ersten Schritt erzeugten Dateien ansonsten nicht passen.


Anwendungs-Vorlage

Das Hauptprogramm ist eine unmodifizierte, vom QtCreator für Qml-Anwendungen erzeugte main.cpp mit folgendem Inhalt:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}


Haupt-Ansicht

Die Anwendung soll zu Demonstrationszwecken ein einziges Fenster mit einem Icon aus unserer eben generierten Schriftart und ein wenig Text anzeigen. Dazu ist die folgende main.qml zu erzeugen:

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr('Icon Font Demo')

    Row {
        anchors.centerIn: parent
        height: Math.max(demoIcon.height, demoLabel.height)
        spacing: 16

        Icon {
            id: demoIcon
            iconPath: 'check-circle'
            size: 32
            color: 'green'
            anchors.verticalCenter: parent.verticalCenter
        }

        Text {
            id: demoLabel
            font.pixelSize: 32
            text: qsTr('This looks awesome!')
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}


Ressourcen

Damit Qt die nicht direkt kompilierten Dateien wie etwa die Qml-Dateien oder die Schrift-Datei findet, muss eine qml.qrc angelegt werden:

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
        <file>Icon.qml</file>
        <file>IconFont.ttf</file>
    </qresource>
</RCC>


Erstellen des Programms

Um Quellen und Dateien die während der Programmerstellung anfallen besser zu trennen, legen wir in der Regel ein spezielles build-Verzeichnis an und wechseln dort hinein:

mkdir build
cd build

Nun führen wir qmake aus der lokalen Qt-Installation aus und starten den Compiler-Prozess:

qmake ..
make

Das Demo-Programm kann nun gestartet werden:

./demo
Summit

Nebenbemerkung für Qt: wenn wirklich viele Icons in die Applikation eingebunden werden und viele davon quasi zeitgleich angezeigt werden müssen, kann es je nach Hardware zu leichten Rucklern kommen. Dies hängt damit zusammen, dass Qt die Schriftzeichen zur Laufzeit zur Nutzung “vorbereitet”. Eine Möglichkeit dies zu optimieren, ist die Vorgenerierung eines Distanzfeldes für die betroffene Schriftart.



Fazit

Die Verwendung einer Schriftart als Icon-Container bietet sich an, wenn in einer Anwendung viele Icons in einer einzigen, frei gewählten Farbe angezeigt werden sollen. Vorteil dabei ist die kompakte Speicherung, das effiziente Rendering und die Unabhängigkeit von Pixel-Dichten auf verschiedenen Bildschirmen.

×
×

Nehmen Sie Kontakt zu uns auf!

Mit * gekennzeichnete Felder sind Pflichtfelder.

Wir haben Ihre Kontaktanfrage erhalten und melden uns kurzfristig bei Ihnen!

×

Ich möchte digital unabhängig werden!

Vielen Dank für Ihre Mitteilung!