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:
Hier der Link: svg-to-ttf
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
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.