summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2018-06-18 12:20:25 +0200
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2018-08-22 10:23:47 +0000
commitbabc227fa25733c1562eccd9623fd8a5f6bec688 (patch)
tree42bf43a762da8b2e564bc217c659a3f47bcf32a0
parentbb3e078e2ed79e763b16c8c7f872b4ebfc576cb6 (diff)
Introduce distancefieldgenerator
A tool that allows you to pick prepare .qdf files which are pregenerated distance fields in binary form that can be loaded by Qt to improve startup time. We currently only support cmaps subtable formats 0, 4, 6, 10 and 12, as these are either A. the most commonly used (4 and 12) or B. simple to implement. The tool writes a .ttf file which is a copy of the original file, but with the addition of a "qtdf" table that follows sfnt conventions. This way, the file will work as a normal font file, as well, which means that glyphs that are not in the pregenerated cache can easily be generated at runtime. Task-number: QTBUG-69356 Change-Id: Ib99c2d62f65e65973a60da4b1aa632b7ed3b2794 Reviewed-by: Lars Knoll <lars.knoll@qt.io>
-rw-r--r--src/distancefieldgenerator/distancefieldgenerator.pro19
-rw-r--r--src/distancefieldgenerator/distancefieldmodel.cpp202
-rw-r--r--src/distancefieldgenerator/distancefieldmodel.h237
-rw-r--r--src/distancefieldgenerator/distancefieldmodelworker.cpp352
-rw-r--r--src/distancefieldgenerator/distancefieldmodelworker.h80
-rw-r--r--src/distancefieldgenerator/doc/distancefieldgenerator.qdocconf27
-rw-r--r--src/distancefieldgenerator/doc/images/distancefieldgenerator.pngbin0 -> 68344 bytes
-rw-r--r--src/distancefieldgenerator/doc/src/distancefieldgenerator-manual.qdoc131
-rw-r--r--src/distancefieldgenerator/main.cpp52
-rw-r--r--src/distancefieldgenerator/mainwindow.cpp729
-rw-r--r--src/distancefieldgenerator/mainwindow.h83
-rw-r--r--src/distancefieldgenerator/mainwindow.ui193
-rw-r--r--src/src.pro2
13 files changed, 2107 insertions, 0 deletions
diff --git a/src/distancefieldgenerator/distancefieldgenerator.pro b/src/distancefieldgenerator/distancefieldgenerator.pro
new file mode 100644
index 000000000..42f8e246c
--- /dev/null
+++ b/src/distancefieldgenerator/distancefieldgenerator.pro
@@ -0,0 +1,19 @@
+QT += gui widgets gui-private core-private quick-private
+
+SOURCES += \
+ main.cpp \
+ mainwindow.cpp \
+ distancefieldmodel.cpp \
+ distancefieldmodelworker.cpp
+
+DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII
+DEFINES += QT_NO_FOREACH
+
+FORMS += \
+ mainwindow.ui
+
+HEADERS += \
+ mainwindow.h \
+ distancefieldmodel.h \
+ distancefieldmodelworker.h
+
diff --git a/src/distancefieldgenerator/distancefieldmodel.cpp b/src/distancefieldgenerator/distancefieldmodel.cpp
new file mode 100644
index 000000000..aebd86140
--- /dev/null
+++ b/src/distancefieldgenerator/distancefieldmodel.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "distancefieldmodel.h"
+#include "distancefieldmodelworker.h"
+
+#include <QThread>
+#include <QMetaEnum>
+
+QT_BEGIN_NAMESPACE
+
+DistanceFieldModel::DistanceFieldModel(QObject *parent)
+ : QAbstractListModel(parent)
+ , m_glyphCount(0)
+{
+ int index = metaObject()->indexOfEnumerator("UnicodeRange");
+ Q_ASSERT(index >= 0);
+
+ m_rangeEnum = metaObject()->enumerator(index);
+
+ m_workerThread.reset(new QThread);
+
+ m_worker = new DistanceFieldModelWorker;
+ m_worker->moveToThread(m_workerThread.data());
+ connect(m_workerThread.data(), &QThread::finished,
+ m_worker, &QObject::deleteLater);
+
+ connect(m_worker, &DistanceFieldModelWorker::fontLoaded,
+ this, &DistanceFieldModel::startGeneration);
+ connect(m_worker, &DistanceFieldModelWorker::fontLoaded,
+ this, &DistanceFieldModel::reserveSpace);
+ connect(m_worker, &DistanceFieldModelWorker::distanceFieldGenerated,
+ this, &DistanceFieldModel::addDistanceField);
+ connect(m_worker, &DistanceFieldModelWorker::fontGenerated,
+ this, &DistanceFieldModel::stopGeneration);
+ connect(m_worker, &DistanceFieldModelWorker::distanceFieldGenerated,
+ this, &DistanceFieldModel::distanceFieldGenerated);
+ connect(m_worker, &DistanceFieldModelWorker::error,
+ this, &DistanceFieldModel::error);
+
+ m_workerThread->start();
+}
+
+DistanceFieldModel::~DistanceFieldModel()
+{
+ m_workerThread->quit();
+ m_workerThread->wait();
+}
+
+QVariant DistanceFieldModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ Q_UNUSED(section);
+ Q_UNUSED(orientation);
+ Q_UNUSED(role);
+ return QVariant();
+}
+
+int DistanceFieldModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+ else
+ return m_glyphCount;
+}
+
+QVariant DistanceFieldModel::data(const QModelIndex &index, int role) const
+{
+ static QPixmap defaultImage;
+ if (defaultImage.isNull()) {
+ defaultImage = QPixmap(64, 64);
+ defaultImage.fill(Qt::white);
+ }
+
+ if (!index.isValid())
+ return QVariant();
+
+ if (role == Qt::DecorationRole) {
+ if (index.row() < m_distanceFields.size()) {
+ return QPixmap::fromImage(m_distanceFields.at(index.row()).scaled(64, 64));
+ } else {
+ return defaultImage;
+ }
+
+ }
+
+ return QVariant();
+}
+
+void DistanceFieldModel::setFont(const QString &fileName)
+{
+ QMetaObject::invokeMethod(m_worker,
+ [this, fileName] { m_worker->loadFont(fileName); },
+ Qt::QueuedConnection);
+}
+
+void DistanceFieldModel::reserveSpace(quint16 glyphCount,
+ bool doubleResolution,
+ qreal pixelSize)
+{
+ beginResetModel();
+ m_glyphsPerUnicodeRange.clear();
+ m_distanceFields.clear();
+ m_glyphCount = glyphCount;
+ if (glyphCount > 0)
+ m_distanceFields.reserve(glyphCount);
+ endResetModel();
+
+ m_doubleGlyphResolution = doubleResolution;
+ m_pixelSize = pixelSize;
+
+ QMetaObject::invokeMethod(m_worker,
+ [this] { m_worker->generateOneDistanceField(); },
+ Qt::QueuedConnection);
+}
+
+DistanceFieldModel::UnicodeRange DistanceFieldModel::unicodeRangeForUcs4(quint32 ucs4) const
+{
+ int index = metaObject()->indexOfEnumerator("UnicodeRange");
+ Q_ASSERT(index >= 0);
+
+ QMetaEnum range = metaObject()->enumerator(index);
+ for (int i = 0; i < range.keyCount() - 1; ++i) {
+ int rangeStart = range.value(i);
+ int rangeEnd = range.value(i + 1);
+ if (quint32(rangeStart) <= ucs4 && quint32(rangeEnd) >= ucs4)
+ return UnicodeRange(rangeStart);
+ }
+
+ return Other;
+}
+
+QList<DistanceFieldModel::UnicodeRange> DistanceFieldModel::unicodeRanges() const
+{
+ return m_glyphsPerUnicodeRange.uniqueKeys();
+}
+
+QList<glyph_t> DistanceFieldModel::glyphIndexesForUnicodeRange(UnicodeRange range) const
+{
+ return m_glyphsPerUnicodeRange.values(range);
+}
+
+QString DistanceFieldModel::nameForUnicodeRange(UnicodeRange range) const
+{
+ return QString::fromLatin1(m_rangeEnum.valueToKey(int(range)));
+}
+
+void DistanceFieldModel::addDistanceField(const QImage &distanceField,
+ const QPainterPath &path,
+ glyph_t glyphId,
+ quint32 ucs4)
+{
+ if (glyphId >= quint16(m_distanceFields.size()))
+ m_distanceFields.resize(glyphId + 1);
+ m_distanceFields[glyphId] = distanceField;
+ if (glyphId >= quint16(m_paths.size()))
+ m_paths.resize(glyphId + 1);
+ m_paths[glyphId] = path;
+
+ if (ucs4 != 0) {
+ UnicodeRange range = unicodeRangeForUcs4(ucs4);
+ m_glyphsPerUnicodeRange.insertMulti(range, glyphId);
+ m_glyphsPerUcs4.insert(ucs4, glyphId);
+ }
+
+ emit dataChanged(createIndex(glyphId, 0), createIndex(glyphId, 0));
+
+ QMetaObject::invokeMethod(m_worker,
+ [this] { m_worker->generateOneDistanceField(); },
+ Qt::QueuedConnection);
+}
+
+glyph_t DistanceFieldModel::glyphIndexForUcs4(quint32 ucs4) const
+{
+ return m_glyphsPerUcs4.value(ucs4);
+}
+
+QT_END_NAMESPACE
diff --git a/src/distancefieldgenerator/distancefieldmodel.h b/src/distancefieldgenerator/distancefieldmodel.h
new file mode 100644
index 000000000..b866e5e6a
--- /dev/null
+++ b/src/distancefieldgenerator/distancefieldmodel.h
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DISTANCEFIELDMODEL_H
+#define DISTANCEFIELDMODEL_H
+
+#include <QAbstractListModel>
+#include <QRawFont>
+#include <QtGui/private/qtextengine_p.h>
+#include <QMultiHash>
+#include <QScopedPointer>
+#include <QMetaEnum>
+#include <QThread>
+
+QT_BEGIN_NAMESPACE
+
+class QThread;
+class DistanceFieldModelWorker;
+class DistanceFieldModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ enum UnicodeRange {
+ Other = 0x00,
+ BasicLatin = 0x20,
+ Latin1Supplement = 0xa0,
+ LatinExtendedA = 0x100,
+ LatinExtendedB = 0x180,
+ IPAExtensions = 0x250,
+ SpacingModifierLetters = 0x2b0,
+ CombiningDiacriticalMarks = 0x300,
+ GreekAndCoptic = 0x370,
+ Cyrillic = 0x400,
+ CyrillicSupplementary = 0x500,
+ Armenian = 0x530,
+ Hebrew = 0x590,
+ Arabic = 0x600,
+ Syriac = 0x700,
+ Thaana = 0x780,
+ Devanagari = 0x900,
+ Bengali = 0x980,
+ Gurmukhi = 0xa00,
+ Gujarati = 0xa80,
+ Oriya = 0xb00,
+ Tamil = 0xb80,
+ Telugu = 0xc00,
+ Kannada = 0xc80,
+ Malayalam = 0xd00,
+ Sinhala = 0xd80,
+ Thai = 0xe00,
+ Lao = 0xe80,
+ Tibetan = 0xf00,
+ Myanmar = 0x1000,
+ Georgian = 0x10a0,
+ HangulJamo = 0x1100,
+ Ethiopic = 0x1200,
+ Cherokee = 0x13a0,
+ UnifiedCanadianAboriginalSyllabics = 0x1400,
+ Ogham = 0x1680,
+ Runic = 0x16a0,
+ Tagalog = 0x1700,
+ Hanunoo = 0x1720,
+ Buhid = 0x1740,
+ Tagbanwa = 0x1760,
+ Khmer = 0x1780,
+ Mongolian = 0x1800,
+ Limbu = 0x1900,
+ TaiLe = 0x1950,
+ KhmerSymbols = 0x19e0,
+ PhoneticExtensions = 0x1d00,
+ LatinExtendedAdditional = 0x1e00,
+ GreekExtended = 0x1f00,
+ GeneralPunctuation = 0x2000,
+ SuperscriptsAndSubscripts = 0x2070,
+ CurrencySymbols = 0x20a0,
+ CombiningDiacriticalMarksForSymbols = 0x20d0,
+ LetterlikeSymbols = 0x2100,
+ NumberForms = 0x2150,
+ Arrows = 0x2190,
+ MathematicalOperators = 0x2200,
+ MiscellaneousTechnical = 0x2300,
+ ControlPictures = 0x2400,
+ OpticalCharacterRecognition = 0x2440,
+ EnclosedAlphanumerics = 0x2460,
+ BoxDrawing = 0x2500,
+ BlockElements = 0x2580,
+ GeometricShapes = 0x25a0,
+ MiscellaneousSymbols = 0x2600,
+ Dingbats = 0x2700,
+ MiscellaneousMathematicalSymbolsA = 0x27c0,
+ SupplementalArrowsA = 0x27f0,
+ BraillePatterns = 0x2800,
+ SupplementalArrowsB = 0x2900,
+ MiscellaneousMathematicalSymbolsB = 0x2980,
+ SupplementalMathematicalOperators = 0x2a00,
+ MiscellaneousSymbolsAndArrows = 0x2b00,
+ CJKRadicalsSupplement = 0x2e80,
+ KangxiRadicals = 0x2f00,
+ IdeographicDescriptionCharacters = 0x2ff0,
+ CJKSymbolsAndPunctuation = 0x3000,
+ Hiragana = 0x3040,
+ Katakana = 0x30a0,
+ Bopomofo = 0x3100,
+ HangulCompatibilityJamo = 0x3130,
+ Kanbun = 0x3190,
+ BopomofoExtended = 0x31a0,
+ KatakanaPhoneticExtensions = 0x31f0,
+ EnclosedCJKLettersAndMonths = 0x3200,
+ CJKCompatibility = 0x3300,
+ CJKUnifiedIdeographsExtensionA = 0x3400,
+ YijingHexagramSymbols = 0x4dc0,
+ CJKUnifiedIdeographs = 0x4e00,
+ YiSyllables = 0xa000,
+ YiRadicals = 0xa490,
+ HangulSyllables = 0xac00,
+ HighSurrogates = 0xd800,
+ HighPrivateUseSurrogates = 0xdb80,
+ LowSurrogates = 0xdc00,
+ PrivateUseArea = 0xe000,
+ CJKCompatibilityIdeographs = 0xf900,
+ AlphabeticPresentationForms = 0xfb00,
+ ArabicPresentationFormsA = 0xfb50,
+ VariationSelectors = 0xfe00,
+ CombiningHalfMarks = 0xfe20,
+ CJKCompatibilityForms = 0xfe30,
+ SmallFormVariants = 0xfe50,
+ ArabicPresentationFormsB = 0xfe70,
+ HalfwidthAndFullwidthForms = 0xff00,
+ Specials = 0xfff0,
+ LinearBSyllabary = 0x10000,
+ LinearBIdeograms = 0x10080,
+ AegeanNumbers = 0x10100,
+ OldItalic = 0x10300,
+ Gothic = 0x10330,
+ Ugaritic = 0x10380,
+ Deseret = 0x10400,
+ Shavian = 0x10450,
+ Osmanya = 0x10480,
+ CypriotSyllabary = 0x10800,
+ ByzantineMusicalSymbols = 0x1d000,
+ MusicalSymbols = 0x1d100,
+ TaiXuanJingSymbols = 0x1d300,
+ MathematicalAlphanumericSymbols = 0x1d400,
+ CJKUnifiedIdeographsExtensionB = 0x20000,
+ CJKCompatibilityIdeographsSupplement = 0x2f800,
+ Tags = 0xe0000,
+ End = 0xe007f
+ };
+ Q_ENUM(UnicodeRange)
+
+ explicit DistanceFieldModel(QObject *parent = nullptr);
+ ~DistanceFieldModel() override;
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ void setFont(const QString &fileName);
+
+ QList<UnicodeRange> unicodeRanges() const;
+ QList<glyph_t> glyphIndexesForUnicodeRange(UnicodeRange range) const;
+ QString nameForUnicodeRange(UnicodeRange range) const;
+ glyph_t glyphIndexForUcs4(quint32 ucs4) const;
+
+ QImage distanceField(int row) const
+ {
+ return m_distanceFields.at(row);
+ }
+
+ QPainterPath path(int row) const
+ {
+ return m_paths.at(row);
+ }
+
+ qreal pixelSize() const { return m_pixelSize; }
+ bool doubleGlyphResolution() const { return m_doubleGlyphResolution; }
+
+signals:
+ void startGeneration(quint16 glyphCount);
+ void stopGeneration();
+ void distanceFieldGenerated();
+ void error(const QString &errorString);
+
+private slots:
+ void addDistanceField(const QImage &distanceField,
+ const QPainterPath &path,
+ glyph_t glyphId,
+ quint32 ucs4);
+ void reserveSpace(quint16 glyphCount,
+ bool doubleResolution,
+ qreal pixelSize);
+
+private:
+ UnicodeRange unicodeRangeForUcs4(quint32 ucs4) const;
+
+ QRawFont m_font;
+ DistanceFieldModelWorker *m_worker;
+ QScopedPointer<QThread> m_workerThread;
+ quint16 m_glyphCount;
+ QVector<QImage> m_distanceFields;
+ QVector<QPainterPath> m_paths;
+ QMultiHash<UnicodeRange, glyph_t> m_glyphsPerUnicodeRange;
+ QHash<quint32, glyph_t> m_glyphsPerUcs4;
+ bool m_doubleGlyphResolution;
+ qreal m_pixelSize;
+ QMetaEnum m_rangeEnum;
+};
+
+QT_END_NAMESPACE
+
+#endif // DISTANCEFIELDMODEL_H
diff --git a/src/distancefieldgenerator/distancefieldmodelworker.cpp b/src/distancefieldgenerator/distancefieldmodelworker.cpp
new file mode 100644
index 000000000..373cb05ab
--- /dev/null
+++ b/src/distancefieldgenerator/distancefieldmodelworker.cpp
@@ -0,0 +1,352 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "distancefieldmodelworker.h"
+
+#include "distancefieldmodel.h"
+#include <qendian.h>
+#include <QtGui/private/qdistancefield_p.h>
+
+QT_BEGIN_NAMESPACE
+
+# pragma pack(1)
+struct MaxpHeader
+{
+ quint32 version;
+ quint16 numGlyphs;
+};
+
+struct CmapHeader {
+ quint16 version;
+ quint16 numTables;
+};
+
+struct CmapEncodingRecord {
+ quint16 platformId;
+ quint16 encodingId;
+ quint32 offset;
+};
+
+struct CmapSubtable
+{
+ quint16 format;
+ quint16 length;
+ quint16 language;
+};
+
+struct CmapSubtable0 : public CmapSubtable
+{
+ quint8 glyphIdArray[256];
+};
+
+struct CmapSubtable4 : public CmapSubtable
+{
+ quint16 segCountX2;
+ quint16 searchRange;
+ quint16 entrySelector;
+ quint16 rangeShift;
+};
+
+struct CmapSubtable6 : public CmapSubtable
+{
+ quint16 firstCode;
+ quint16 entryCount;
+};
+
+struct CmapSubtable10
+{
+ quint32 format;
+ quint32 length;
+ quint32 language;
+ quint32 startCharCode;
+ quint32 numChars;
+};
+
+struct CmapSubtable12
+{
+ quint16 format;
+ quint16 reserved;
+ quint32 length;
+ quint32 language;
+ quint32 numGroups;
+};
+
+struct SequentialMapGroup
+{
+ quint32 startCharCode;
+ quint32 endCharCode;
+ quint32 glyphIndex;
+};
+
+# pragma pack()
+
+DistanceFieldModelWorker::DistanceFieldModelWorker(QObject *parent)
+ : QObject(parent)
+ , m_glyphCount(0)
+ , m_nextGlyphId(0)
+ , m_doubleGlyphResolution(false)
+{
+}
+
+template <typename T>
+static void readCmapSubtable(DistanceFieldModelWorker *worker, const QByteArray &cmap, quint32 tableOffset, quint16 format)
+{
+ if (uint(cmap.size()) < tableOffset + sizeof(T)) {
+ emit worker->error(QObject::tr("End of file when reading subtable of format '%1'").arg(format));
+ return;
+ }
+
+ const T *subtable = reinterpret_cast<const T *>(cmap.constData() + tableOffset);
+ quint16 length = qFromBigEndian(subtable->length);
+ if (uint(cmap.size()) < tableOffset + length) {
+ emit worker->error(QObject::tr("Corrupt data found when reading subtable of format '%1'. Table offset: %2. Length: %3. Cmap length: %4.")
+ .arg(format).arg(tableOffset).arg(length).arg(cmap.size()));
+ return;
+ }
+
+ const void *end = cmap.constData() + tableOffset + subtable->length;
+ worker->readCmapSubtable(subtable, end);
+}
+
+void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable0 *subtable, const void *end)
+{
+ Q_UNUSED(end); // Already checked for length
+ for (int i = 0; i < 256; ++i)
+ m_cmapping[glyph_t(subtable->glyphIdArray[i])] = i;
+}
+
+void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable4 *subtable, const void *end)
+{
+ quint16 segCount = qFromBigEndian(subtable->segCountX2) / 2;
+ const quint16 *endCodes = reinterpret_cast<const quint16 *>(subtable + 1);
+ const quint16 *startCodes = endCodes + segCount + 1; // endCodes[segCount] + reservedPad
+ const qint16 *idDeltas = reinterpret_cast<const qint16 *>(startCodes + segCount);
+ const quint16 *idRangeOffsets = reinterpret_cast<const quint16 *>(idDeltas + segCount);
+ const quint16 *glyphIdArray = idRangeOffsets + segCount;
+ if (glyphIdArray >= end) {
+ emit error(tr("End of cmap table reached when parsing subtable format '4'"));
+ return;
+ }
+
+ for (int i = 0; i < segCount - 1; ++i) { // Last entry in arrays is the sentinel
+ quint16 startCode = qFromBigEndian(startCodes[i]);
+ quint16 endCode = qFromBigEndian(endCodes[i]);
+ quint16 rangeOffset = qFromBigEndian(idRangeOffsets[i]);
+
+ for (quint16 c = startCode; c <= endCode; ++c) {
+ if (rangeOffset != 0) {
+ const quint16 *glyphIndex = (idRangeOffsets + i) + (c - startCode) + rangeOffset / 2;
+ if (glyphIndex + 1 > end) {
+ emit error(tr("End of cmap, subtable format '4', reached when fetching character '%1' in range [%2, %3]").arg(c).arg(startCode).arg(endCode));
+ return;
+ }
+
+ m_cmapping[glyph_t(qFromBigEndian(*glyphIndex))] = quint32(c);
+ } else {
+ quint16 idDelta = qFromBigEndian(idDeltas[i]);
+ m_cmapping[glyph_t((idDelta + c) % 65536)] = quint32(c);
+ }
+ }
+
+ }
+}
+
+void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable6 *subtable, const void *end)
+{
+ quint16 entryCount = qFromBigEndian(subtable->entryCount);
+ const quint16 *glyphIndexes = reinterpret_cast<const quint16 *>(subtable + 1);
+ if (glyphIndexes + entryCount > end) {
+ emit error(tr("End of cmap reached while parsing subtable format '6'"));
+ return;
+ }
+
+ quint16 firstCode = qFromBigEndian(subtable->firstCode);
+ for (quint16 i = 0; i < entryCount; ++i)
+ m_cmapping[glyph_t(qFromBigEndian(glyphIndexes[i]))] = firstCode + i;
+}
+
+void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable10 *subtable, const void *end)
+{
+ quint32 numChars = qFromBigEndian(subtable->numChars);
+ const quint16 *glyphs = reinterpret_cast<const quint16 *>(subtable + 1);
+ if (glyphs + numChars > end) {
+ emit error(tr("End of cmap reached while parsing subtable of format '10'"));
+ return;
+ }
+
+ quint32 startCharCode = qFromBigEndian(subtable->startCharCode);
+ for (quint32 i = 0; i < numChars; ++i)
+ m_cmapping[glyph_t(qFromBigEndian(glyphs[i]))] = startCharCode + i;
+}
+
+void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable12 *subtable, const void *end)
+{
+ quint32 numGroups = qFromBigEndian(subtable->numGroups);
+ const SequentialMapGroup *sequentialMapGroups = reinterpret_cast<const SequentialMapGroup *>(subtable + 1);
+ if (sequentialMapGroups + numGroups > end) {
+ emit error(tr("End of cmap reached while parsing subtable of format '12'"));
+ return;
+ }
+
+ for (quint32 i = 0; i < numGroups; ++i) {
+ quint32 startCharCode = qFromBigEndian(sequentialMapGroups[i].startCharCode);
+ quint32 endCharCode = qFromBigEndian(sequentialMapGroups[i].endCharCode);
+ quint32 startGlyphIndex = qFromBigEndian(sequentialMapGroups[i].glyphIndex);
+
+ for (quint32 j = 0; j < endCharCode - startCharCode + 1; ++j)
+ m_cmapping[glyph_t(startGlyphIndex + j)] = startCharCode + j;
+ }
+}
+
+void DistanceFieldModelWorker::readCmap()
+{
+ if (m_font.isValid()) {
+ QByteArray cmap = m_font.fontTable("cmap");
+ if (uint(cmap.size()) < sizeof(CmapHeader)) {
+ emit error(tr("Invalid cmap table. No header."));
+ return;
+ }
+
+ const CmapHeader *header = reinterpret_cast<const CmapHeader *>(cmap.constData());
+ quint16 numTables = qFromBigEndian(header->numTables);
+
+ if (uint(cmap.size()) < sizeof(CmapHeader) + numTables * sizeof(CmapEncodingRecord)) {
+ emit error(tr("Invalid cmap table. No space for %1 encoding records.").arg(numTables));
+ return;
+ }
+
+ // Support the same encodings as macOS (and same order of prefernece), since this should
+ // cover most fonts
+ static quint32 encodingPreferenceOrder[] =
+ {
+ quint32(0) << 16 | 4, // Unicode 2.0 +
+ quint32(0) << 16 | 3, // Unicode 2.0 BMP
+ quint32(0) << 16 | 1, // Unicode 1.1
+ quint32(3) << 16 | 10, // Windows, UCS-4
+ quint32(3) << 16 | 1, // Windows, UCS-2
+ quint32(0)
+ };
+
+ QHash<quint32, const CmapEncodingRecord *> encodingRecords;
+ {
+ const CmapEncodingRecord *encodingRecord = reinterpret_cast<const CmapEncodingRecord *>(cmap.constData() + sizeof(CmapHeader));
+ while (numTables-- > 0) {
+ quint32 encoding = quint32(qFromBigEndian(encodingRecord->platformId)) << 16 | qFromBigEndian(encodingRecord->encodingId);
+ encodingRecords[encoding] = encodingRecord++;
+ }
+ }
+
+ // Find the first subtable we support in order of preference
+ for (int i = 0; encodingPreferenceOrder[i] != 0; ++i) {
+ const CmapEncodingRecord *encodingRecord = encodingRecords.value(encodingPreferenceOrder[i], nullptr);
+ if (encodingRecord != nullptr) {
+ quint32 offset = qFromBigEndian(encodingRecord->offset);
+ if (uint(cmap.size()) < offset + sizeof(quint16)) {
+ emit error(tr("Invalid offset '%1' in cmap").arg(offset));
+ return;
+ }
+
+ quint16 format = qFromBigEndian(*reinterpret_cast<const quint16 *>(cmap.constData() + offset));
+ switch (format) {
+ case 0:
+ ::readCmapSubtable<CmapSubtable0>(this, cmap, offset, format);
+ return;
+ case 4:
+ ::readCmapSubtable<CmapSubtable4>(this, cmap, offset, format);
+ return;
+ case 6:
+ ::readCmapSubtable<CmapSubtable6>(this, cmap, offset, format);
+ return;
+ case 10:
+ ::readCmapSubtable<CmapSubtable10>(this, cmap, offset, format);
+ return;
+ case 12:
+ ::readCmapSubtable<CmapSubtable12>(this, cmap, offset, format);
+ return;
+ default:
+ qWarning() << tr("Unsupported cmap subtable format '%1'").arg(format);
+ };
+ }
+ }
+
+ emit error(tr("No suitable cmap subtable found"));
+ }
+}
+
+void DistanceFieldModelWorker::readGlyphCount()
+{
+ m_nextGlyphId = 0;
+ m_glyphCount = 0;
+ if (m_font.isValid()) {
+ QByteArray maxp = m_font.fontTable("maxp");
+ if (uint(maxp.size()) >= sizeof(MaxpHeader)) {
+ const MaxpHeader *header = reinterpret_cast<const MaxpHeader *>(maxp.constData());
+ m_glyphCount = qFromBigEndian(header->numGlyphs);
+ }
+ }
+
+ m_doubleGlyphResolution = qt_fontHasNarrowOutlines(m_font) && m_glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
+}
+
+void DistanceFieldModelWorker::loadFont(const QString &fileName)
+{
+ m_font = QRawFont(fileName, 64);
+ if (!m_font.isValid())
+ emit error(tr("File '%1' is not a valid font file.").arg(fileName));
+
+ readGlyphCount();
+ readCmap();
+
+ qreal pixelSize = QT_DISTANCEFIELD_BASEFONTSIZE(m_doubleGlyphResolution) * QT_DISTANCEFIELD_SCALE(m_doubleGlyphResolution);
+ m_font.setPixelSize(pixelSize);
+
+ emit fontLoaded(m_glyphCount,
+ m_doubleGlyphResolution,
+ pixelSize);
+}
+
+void DistanceFieldModelWorker::generateOneDistanceField()
+{
+ Q_ASSERT(m_nextGlyphId <= m_glyphCount);
+
+ if (m_nextGlyphId == m_glyphCount) {
+ emit fontGenerated();
+ return;
+ }
+
+ QPainterPath path = m_font.pathForGlyph(m_nextGlyphId);
+ QDistanceField distanceField(path, m_nextGlyphId, m_doubleGlyphResolution);
+ emit distanceFieldGenerated(distanceField.toImage(QImage::Format_Alpha8),
+ path,
+ m_nextGlyphId,
+ m_cmapping.value(m_nextGlyphId));
+
+ m_nextGlyphId++;
+}
+
+QT_END_NAMESPACE
diff --git a/src/distancefieldgenerator/distancefieldmodelworker.h b/src/distancefieldgenerator/distancefieldmodelworker.h
new file mode 100644
index 000000000..85b82f096
--- /dev/null
+++ b/src/distancefieldgenerator/distancefieldmodelworker.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef DISTANCEFIELDMODELWORKER_H
+#define DISTANCEFIELDMODELWORKER_H
+
+#include <QObject>
+#include <QRawFont>
+#include <QtGui/private/qtextengine_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct CmapSubtable0;
+struct CmapSubtable4;
+struct CmapSubtable6;
+struct CmapSubtable10;
+struct CmapSubtable12;
+class DistanceFieldModelWorker : public QObject
+{
+ Q_OBJECT
+public:
+ explicit DistanceFieldModelWorker(QObject *parent = nullptr);
+
+ Q_INVOKABLE void generateOneDistanceField();
+ Q_INVOKABLE void loadFont(const QString &fileName);
+
+ void readCmapSubtable(const CmapSubtable0 *subtable, const void *end);
+ void readCmapSubtable(const CmapSubtable4 *subtable, const void *end);
+ void readCmapSubtable(const CmapSubtable6 *subtable, const void *end);
+ void readCmapSubtable(const CmapSubtable10 *subtable, const void *end);
+ void readCmapSubtable(const CmapSubtable12 *subtable, const void *end);
+
+signals:
+ void fontLoaded(quint16 glyphCount, bool doubleResolution, qreal pixelSize);
+ void fontGenerated();
+ void distanceFieldGenerated(const QImage &distanceField,
+ const QPainterPath &path,
+ glyph_t glyphId,
+ quint32 cmapAssignment);
+ void error(const QString &errorString);
+
+private:
+ void readGlyphCount();
+ void readCmap();
+
+ QRawFont m_font;
+ quint16 m_glyphCount;
+ quint16 m_nextGlyphId;
+ bool m_doubleGlyphResolution;
+ QHash<glyph_t, quint32> m_cmapping;
+};
+
+QT_END_NAMESPACE
+
+#endif // DISTANCEFIELDMODELWORKER_H
diff --git a/src/distancefieldgenerator/doc/distancefieldgenerator.qdocconf b/src/distancefieldgenerator/doc/distancefieldgenerator.qdocconf
new file mode 100644
index 000000000..62282238c
--- /dev/null
+++ b/src/distancefieldgenerator/doc/distancefieldgenerator.qdocconf
@@ -0,0 +1,27 @@
+include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf)
+
+project = QtDistanceFieldGenerator
+description = Qt Distance Field Generator Manual
+
+qhp.projects = QtDistanceFieldGenerator
+
+qhp.QtDistanceFieldGenerator.file = qtdistancefieldgenerator.qhp
+qhp.QtDistanceFieldGenerator.namespace = org.qt-project.qtdistancefieldgenerator.$QT_VERSION_TAG
+qhp.QtDistanceFieldGenerator.virtualFolder = qtdistancefieldgenerator
+qhp.QtDistanceFieldGenerator.indexTitle = Qt Distance Field Generator Manual
+
+qhp.QtDistanceFieldGenerator.filterAttributes = qt $QT_VERSION tools qtdistancefieldgenerator
+qhp.QtDistanceFieldGenerator.customFilters.QtDistanceFieldGenerator.name = Qt Distance Field Generator Manual
+qhp.QtDistanceFieldGenerator.customFilters.QtDistanceFieldGenerator.filterAttributes = qt tools qtdistancefieldgenerator
+qhp.QtDistanceFieldGenerator.subprojects = manual
+qhp.QtDistanceFieldGenerator.subprojects.manual.title = Manual
+qhp.QtDistanceFieldGenerator.subprojects.manual.indexTitle = Qt Distance Field Generator Manual
+qhp.QtDistanceFieldGenerator.subprojects.manual.type = manual
+
+language = Cpp
+sourcedirs = ..
+imagedirs = images
+
+depends += qtdoc qtqml qtquick qtcore qtgui qmake
+
+navigation.landingpage = "Qt Distance Field Generator Manual"
diff --git a/src/distancefieldgenerator/doc/images/distancefieldgenerator.png b/src/distancefieldgenerator/doc/images/distancefieldgenerator.png
new file mode 100644
index 000000000..f6b39ce88
--- /dev/null
+++ b/src/distancefieldgenerator/doc/images/distancefieldgenerator.png
Binary files differ
diff --git a/src/distancefieldgenerator/doc/src/distancefieldgenerator-manual.qdoc b/src/distancefieldgenerator/doc/src/distancefieldgenerator-manual.qdoc
new file mode 100644
index 000000000..68a37ddbc
--- /dev/null
+++ b/src/distancefieldgenerator/doc/src/distancefieldgenerator-manual.qdoc
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \page qtdistancefieldgenerator-index.html
+ \title Qt Distance Field Generator Manual
+ \ingroup qttools
+
+ If the user interface of an application has a lot of text, it
+ may cause a small, but noticeable, performance impact the first
+ time it is displayed to the user. This is especially true if the
+ text is rendered in multiple different fonts or use a large amount
+ of distinct characters (common for instance in writing systems such
+ as Hanzi, written Chinese).
+
+ The reason is that in order to render the text efficiently later,
+ Qt will spend some time creating graphical assets for each of the
+ glyphs that will later be reused. This happens the first time a
+ glyph is displayed in the scene.
+
+ For advanced users who want to optimize startup performance, it is
+ possible to pregenerate this font cache, as long as Text.QtRendering
+ is the rendering type in use. The Qt Distance Field Generator tool can
+ be used to pregenerate the cache, either for all glyphs in the fonts,
+ or just a selection that are known to be displayed during a critical
+ phase.
+
+ \note This is a tool that may be used by advanced users
+ to streamline their application in ways that cannot be done
+ automatically by Qt. For most common use cases, the default behavior
+ in Qt will be sufficient.
+
+ \image distancefieldgenerator.png "Qt Distance Field Generator UI"
+
+ \section1 General Usage
+
+ Use the Qt Distance Field Generator in the following way:
+
+ \list
+ \li Load a font file with \gui File > \gui Open font. If the font
+ file is large, there will be some waiting as it reads the fonts
+ and generates the distance fields.
+ \li Once this is done, select the glyphs that you want to save in
+ the pregenerated cache. This should ideally be the glyphs that
+ are used by your application during a performance-critical phase,
+ so that doing the generation ahead of time will give a visible
+ impact on performance.
+ \li Finally, click \gui Save to save the new font file.
+ \endlist
+
+ \note In order to modify a font in this way, you will need to make sure
+ its license does not prohibit it.
+
+ \section1 Selecting Glyphs
+
+ Glyphs can be selected in multiple ways. The simplest way is to click
+ the grid of glyphs to select a particular glyph. You can cancel the
+ selection by clicking on the glyph again.
+
+ In addition, you can use the list of Unicode ranges to select all glyphs
+ matching the characters in a certain range.
+
+ If you want to make sure you pregenerate the glyphs for a specific string
+ from your user interface, you can use the \gui Select > \gui Select string
+ function.
+
+ \note Both of the two latter selection methods base the results
+ on the CMAP table in the font and will not do any shaping.
+
+ \section1 Using the File
+
+ Once you have prepared a file, the next step is to load it in your application.
+ The saved file is a copy of the original font file, and can thus be used in
+ the same ways as any other font file. In addition, it has a special font table
+ which is recognized by Qt and used to prepopulate the glyph cache when the
+ font is used in Qt Quick.
+
+ You can, for instance, load the font using a \c FontLoader in your application
+ code. When it is used to display text in a \c Text element with \c renderType
+ set to \c Text.QtRendering (the default), then the pregenerated cache will be
+ loaded and used.
+
+ \section1 Measuring performance
+
+ For analyzing the impact of distance field generation on your application, you
+ can set the \c QT_LOGGING_RULES environment variable to
+ \c "qt.scenegraph.time.glyph=true".
+
+ When using normal fonts with no built-in cache, you will give output similar to
+ this:
+
+ \code
+ qt.scenegraph.time.glyph: distancefield: 50 glyphs prepared in 16ms, rendering=15, upload=1
+ \endcode
+
+ If you have pregenerated all the glyphs in use, the output will instead read something like
+ this:
+
+ \code
+ qt.scenegraph.time.glyph: distancefield: 50 pre-generated glyphs loaded in 2ms
+ \endcode
+
+ In this case, the time used to prepare the distance fields used to render in the application
+ has been reduced from one full frame (16 ms) to 2 ms. You can also use the output to verify
+ that all the glyphs in use are being loaded from the cache and to identify problematic phases
+ in your application's life cycle, performance-wise.
+*/
diff --git a/src/distancefieldgenerator/main.cpp b/src/distancefieldgenerator/main.cpp
new file mode 100644
index 000000000..c7b5cab13
--- /dev/null
+++ b/src/distancefieldgenerator/main.cpp
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QCommandLineParser>
+#include <QFile>
+#include <QFileInfo>
+#include <QRawFont>
+
+QT_USE_NAMESPACE
+
+int main(int argc, char **argv)
+{
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QApplication app(argc, argv);
+ app.setOrganizationName(QStringLiteral("QtProject"));
+ app.setApplicationName(QStringLiteral("Qt Distance Field Generator"));
+ app.setApplicationVersion(QStringLiteral(QT_VERSION_STR));
+
+ MainWindow mainWindow;
+ mainWindow.showMaximized();
+
+ return app.exec();
+}
+
diff --git a/src/distancefieldgenerator/mainwindow.cpp b/src/distancefieldgenerator/mainwindow.cpp
new file mode 100644
index 000000000..7108e387c
--- /dev/null
+++ b/src/distancefieldgenerator/mainwindow.cpp
@@ -0,0 +1,729 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include "distancefieldmodel.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qdatastream.h>
+#include <QtCore/qmath.h>
+#include <QtCore/qendian.h>
+#include <QtCore/qbuffer.h>
+#include <QtGui/qrawfont.h>
+#include <QtWidgets/qmessagebox.h>
+#include <QtWidgets/qlabel.h>
+#include <QtWidgets/qprogressbar.h>
+#include <QtWidgets/qfiledialog.h>
+#include <QtWidgets/qinputdialog.h>
+
+#include <QtCore/private/qunicodetables_p.h>
+#include <QtGui/private/qdistancefield_p.h>
+#include <QtQuick/private/qsgareaallocator_p.h>
+#include <QtQuick/private/qsgadaptationlayer_p.h>
+
+QT_BEGIN_NAMESPACE
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent)
+ , ui(new Ui::MainWindow)
+ , m_settings(qApp->organizationName(), qApp->applicationName())
+ , m_model(nullptr)
+ , m_statusBarLabel(nullptr)
+ , m_statusBarProgressBar(nullptr)
+{
+ ui->setupUi(this);
+
+ m_statusBarLabel = new QLabel(this);
+ m_statusBarLabel->setText(tr("Ready"));
+ ui->statusbar->addPermanentWidget(m_statusBarLabel);
+
+ m_statusBarProgressBar = new QProgressBar(this);
+ ui->statusbar->addPermanentWidget(m_statusBarProgressBar);
+ m_statusBarProgressBar->setVisible(false);
+
+ if (m_settings.contains(QStringLiteral("fontDirectory")))
+ m_fontDir = m_settings.value(QStringLiteral("fontDirectory")).toString();
+ else
+ m_fontDir = QDir::currentPath();
+
+ qRegisterMetaType<glyph_t>("glyph_t");
+ qRegisterMetaType<QPainterPath>("QPainterPath");
+
+ setupConnections();
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::setupConnections()
+{
+ connect(ui->action_Open, &QAction::triggered, this, &MainWindow::openFont);
+ connect(ui->actionE_xit, &QAction::triggered, qApp, &QApplication::quit);
+ connect(ui->action_Save, &QAction::triggered, this, &MainWindow::save);
+ connect(ui->action_Save_as, &QAction::triggered, this, &MainWindow::saveAs);
+ connect(ui->tbSave, &QToolButton::clicked, this, &MainWindow::save);
+ connect(ui->tbSelectAll, &QToolButton::clicked, this, &MainWindow::selectAll);
+ connect(ui->actionSelect_all, &QAction::triggered, this, &MainWindow::selectAll);
+ connect(ui->actionSelect_string, &QAction::triggered, this, &MainWindow::selectString);
+ connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
+}
+
+void MainWindow::saveAs()
+{
+ QString fileName = QFileDialog::getSaveFileName(this,
+ tr("Save distance field-enriched file"),
+ m_fontDir,
+ tr("Font files (*.ttf *.otf);;All files (*)"));
+ if (!fileName.isEmpty()) {
+ m_fileName = fileName;
+ m_fontDir = QFileInfo(m_fileName).absolutePath();
+ m_settings.setValue(QStringLiteral("fontDirectory"), m_fontDir);
+ save();
+ }
+}
+
+
+# pragma pack(1)
+struct FontDirectoryHeader
+{
+ quint32 sfntVersion;
+ quint16 numTables;
+ quint16 searchRange;
+ quint16 entrySelector;
+ quint16 rangeShift;
+};
+
+struct TableRecord
+{
+ quint32 tag;
+ quint32 checkSum;
+ quint32 offset;
+ quint32 length;
+};
+
+struct QtdfHeader
+{
+ quint8 majorVersion;
+ quint8 minorVersion;
+ quint16 pixelSize;
+ quint32 textureSize;
+ quint8 flags;
+ quint8 padding;
+ quint32 numGlyphs;
+};
+
+struct QtdfGlyphRecord
+{
+ quint32 glyphIndex;
+ quint32 textureOffsetX;
+ quint32 textureOffsetY;
+ quint32 textureWidth;
+ quint32 textureHeight;
+ quint32 xMargin;
+ quint32 yMargin;
+ qint32 boundingRectX;
+ qint32 boundingRectY;
+ quint32 boundingRectWidth;
+ quint32 boundingRectHeight;
+ quint16 textureIndex;
+};
+
+struct QtdfTextureRecord
+{
+ quint32 allocatedX;
+ quint32 allocatedY;
+ quint32 allocatedWidth;
+ quint32 allocatedHeight;
+ quint8 padding;
+};
+
+struct Head
+{
+ quint16 majorVersion;
+ quint16 minorVersion;
+ quint32 fontRevision;
+ quint32 checkSumAdjustment;
+};
+# pragma pack()
+
+#define PAD_BUFFER(buffer, size) \
+ { \
+ int paddingNeed = size % 4; \
+ if (paddingNeed > 0) { \
+ const char padding[3] = { 0, 0, 0 }; \
+ buffer.write(padding, 4 - paddingNeed); \
+ } \
+ }
+
+#define ALIGN_OFFSET(offset) \
+ { \
+ int paddingNeed = offset % 4; \
+ if (paddingNeed > 0) \
+ offset += 4 - paddingNeed; \
+ }
+
+#define TO_FIXED_POINT(value) \
+ ((int)(value*qreal(65536)))
+
+void MainWindow::save()
+{
+ QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
+ if (list.isEmpty()) {
+ QMessageBox::warning(this,
+ tr("Nothing to save"),
+ tr("No glyphs selected for saving."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ if (m_fileName.isEmpty()) {
+ saveAs();
+ return;
+ }
+
+ QFile inFile(m_fontFile);
+ if (!inFile.open(QIODevice::ReadOnly)) {
+ QMessageBox::warning(this,
+ tr("Can't read original font"),
+ tr("Cannot open '%s' for reading. The original font file must remain in place until the new file has been saved.").arg(m_fontFile),
+ QMessageBox::Ok);
+ return;
+ }
+
+ QByteArray output;
+ quint32 headOffset = 0;
+
+ {
+ QBuffer outBuffer(&output);
+ outBuffer.open(QIODevice::WriteOnly);
+
+ uchar *inData = inFile.map(0, inFile.size());
+ if (inData == nullptr) {
+ QMessageBox::warning(this,
+ tr("Can't map input file"),
+ tr("Unable to memory map input file '%s'.").arg(m_fontFile));
+ return;
+ }
+
+ uchar *end = inData + inFile.size();
+ if (inData + sizeof(FontDirectoryHeader) > end) {
+ QMessageBox::warning(this,
+ tr("Can't read font directory"),
+ tr("Input file seems to be invalid or corrupt."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ FontDirectoryHeader fontDirectoryHeader;
+ memcpy(&fontDirectoryHeader, inData, sizeof(FontDirectoryHeader));
+ quint16 numTables = qFromBigEndian(fontDirectoryHeader.numTables) + 1;
+ fontDirectoryHeader.numTables = qToBigEndian(numTables);
+ {
+ quint16 searchRange = qFromBigEndian(fontDirectoryHeader.searchRange);
+ if (searchRange / 16 < numTables) {
+ quint16 pot = (searchRange / 16) * 2;
+ searchRange = pot * 16;
+ fontDirectoryHeader.searchRange = qToBigEndian(searchRange);
+ fontDirectoryHeader.rangeShift = qToBigEndian(numTables * 16 - searchRange);
+
+ quint16 entrySelector = 0;
+ while (pot > 1) {
+ pot >>= 1;
+ entrySelector++;
+ }
+ fontDirectoryHeader.entrySelector = qToBigEndian(entrySelector);
+ }
+ }
+
+ outBuffer.write(reinterpret_cast<char *>(&fontDirectoryHeader),
+ sizeof(FontDirectoryHeader));
+
+ QVarLengthArray<QPair<quint32, quint32>> offsetLengthPairs;
+ offsetLengthPairs.reserve(numTables - 1);
+
+ // Copy the offset table, updating offsets
+ TableRecord *offsetTable = reinterpret_cast<TableRecord *>(inData + sizeof(FontDirectoryHeader));
+ quint32 currentOffset = sizeof(FontDirectoryHeader) + sizeof(TableRecord) * numTables;
+ for (int i = 0; i < numTables - 1; ++i) {
+ ALIGN_OFFSET(currentOffset)
+
+ quint32 originalOffset = qFromBigEndian(offsetTable->offset);
+ quint32 length = qFromBigEndian(offsetTable->length);
+ offsetLengthPairs.append(qMakePair(originalOffset, length));
+ if (offsetTable->tag == qToBigEndian(MAKE_TAG('h', 'e', 'a', 'd')))
+ headOffset = currentOffset;
+
+ TableRecord newTableRecord;
+ memcpy(&newTableRecord, offsetTable, sizeof(TableRecord));
+ newTableRecord.offset = qToBigEndian(currentOffset);
+ outBuffer.write(reinterpret_cast<char *>(&newTableRecord), sizeof(TableRecord));
+
+ offsetTable++;
+ currentOffset += length;
+ }
+
+ if (headOffset == 0) {
+ QMessageBox::warning(this,
+ tr("Invalid font file"),
+ tr("Font file does not have 'head' table."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ QByteArray qtdf = createSfntTable();
+ if (qtdf.isEmpty())
+ return;
+
+ {
+ ALIGN_OFFSET(currentOffset)
+
+ TableRecord qtdfRecord;
+ qtdfRecord.offset = qToBigEndian(currentOffset);
+ qtdfRecord.length = qToBigEndian(qtdf.length());
+ qtdfRecord.tag = qToBigEndian(MAKE_TAG('q', 't', 'd', 'f'));
+ quint32 checkSum = 0;
+ const quint32 *start = reinterpret_cast<const quint32 *>(qtdf.constData());
+ const quint32 *end = reinterpret_cast<const quint32 *>(qtdf.constData() + qtdf.length());
+ while (start < end)
+ checkSum += *(start++);
+ qtdfRecord.checkSum = qToBigEndian(checkSum);
+
+ outBuffer.write(reinterpret_cast<char *>(&qtdfRecord),
+ sizeof(TableRecord));
+ }
+
+ // Copy all font tables
+ for (const QPair<quint32, quint32> &offsetLengthPair : offsetLengthPairs) {
+ PAD_BUFFER(outBuffer, output.size())
+ outBuffer.write(reinterpret_cast<char *>(inData + offsetLengthPair.first),
+ offsetLengthPair.second);
+ }
+
+ PAD_BUFFER(outBuffer, output.size())
+ outBuffer.write(qtdf);
+ }
+
+ // Clear 'head' checksum and calculate new check sum adjustment
+ Head *head = reinterpret_cast<Head *>(output.data() + headOffset);
+ head->checkSumAdjustment = 0;
+
+ quint32 checkSum = 0;
+ const quint32 *start = reinterpret_cast<const quint32 *>(output.constData());
+ const quint32 *end = reinterpret_cast<const quint32 *>(output.constData() + output.length());
+ while (start < end)
+ checkSum += *(start++);
+
+ head->checkSumAdjustment = qToBigEndian(0xB1B0AFBA - checkSum);
+
+ QFile outFile(m_fileName);
+ if (!outFile.open(QIODevice::WriteOnly)) {
+ QMessageBox::warning(this,
+ tr("Can't write to file"),
+ tr("Cannot open the file '%s' for writing").arg(m_fileName),
+ QMessageBox::Ok);
+ return;
+ }
+
+ outFile.write(output);
+}
+
+QByteArray MainWindow::createSfntTable()
+{
+ QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
+ Q_ASSERT(!list.isEmpty());
+
+ QByteArray ret;
+ {
+ QBuffer buffer(&ret);
+ buffer.open(QIODevice::WriteOnly);
+
+ QtdfHeader header;
+ header.majorVersion = 5;
+ header.minorVersion = 12;
+ header.pixelSize = qToBigEndian(quint16(qRound(m_model->pixelSize())));
+
+ quint32 textureSize = ui->sbMaximumTextureSize->value();
+ header.textureSize = qToBigEndian(textureSize);
+
+ const quint8 padding = 2;
+ header.padding = padding;
+ header.flags = m_model->doubleGlyphResolution() ? 1 : 0;
+ header.numGlyphs = qToBigEndian(quint32(list.size()));
+ buffer.write(reinterpret_cast<char *>(&header),
+ sizeof(QtdfHeader));
+
+ // Maximum height allocator to find optimal number of textures
+ QRect allocatedArea;
+ QVector<QRect> allocatedAreaPerTexture;
+
+ struct GlyphData {
+ QSGDistanceFieldGlyphCache::TexCoord texCoord;
+ QRectF boundingRect;
+ QSize glyphSize;
+ int textureIndex;
+ };
+ QVector<GlyphData> glyphDatas;
+ glyphDatas.resize(m_model->rowCount());
+
+ int textureCount = 0;
+
+ {
+ qreal scaleFactor = qreal(1) / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution());
+ QTransform scaleDown;
+ scaleDown.scale(scaleFactor, scaleFactor);
+
+ const int radius = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution())
+ / QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution());
+
+ {
+ bool foundOptimalSize = false;
+ while (!foundOptimalSize) {
+ allocatedAreaPerTexture.clear();
+
+ QSGAreaAllocator allocator(QSize(textureSize, textureSize * (++textureCount)));
+
+ int i;
+ for (i = 0; i < list.size(); ++i) {
+ int glyphIndex = list.at(i).row();
+ GlyphData &glyphData = glyphDatas[glyphIndex];
+
+ QPainterPath path = m_model->path(glyphIndex);
+ glyphData.boundingRect = scaleDown.mapRect(path.boundingRect());
+ int glyphWidth = qCeil(glyphData.boundingRect.width()) + radius * 2;
+ int glyphHeight = qCeil(glyphData.boundingRect.height()) + radius * 2;
+ glyphData.glyphSize = QSize(glyphWidth + padding * 2, glyphHeight + padding * 2);
+
+ if (glyphData.glyphSize.width() > qint32(textureSize)
+ || glyphData.glyphSize.height() > qint32(textureSize)) {
+ QMessageBox::warning(this,
+ tr("Glyph too large for texture"),
+ tr("Glyph %1 is too large to fit in texture of size %2.")
+ .arg(glyphIndex).arg(textureSize));
+ return QByteArray();
+ }
+
+ QRect rect = allocator.allocate(glyphData.glyphSize);
+ if (rect.isNull())
+ break;
+
+ glyphData.textureIndex = rect.y() / textureSize;
+ if (glyphData.textureIndex >= allocatedAreaPerTexture.size())
+ allocatedAreaPerTexture.resize(glyphData.textureIndex + 1);
+ allocatedAreaPerTexture[glyphData.textureIndex] |= QRect(rect.x(),
+ rect.y() % textureSize,
+ rect.width(),
+ rect.height());
+
+ allocatedArea |= rect;
+
+ glyphData.texCoord.xMargin = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution()));
+ glyphData.texCoord.yMargin = QT_DISTANCEFIELD_RADIUS(m_model->doubleGlyphResolution()) / qreal(QT_DISTANCEFIELD_SCALE(m_model->doubleGlyphResolution()));
+ glyphData.texCoord.x = rect.x() + padding;
+ glyphData.texCoord.y = rect.y() % textureSize + padding;
+ glyphData.texCoord.width = glyphData.boundingRect.width();
+ glyphData.texCoord.height = glyphData.boundingRect.height();
+
+ glyphDatas.append(glyphData);
+ }
+
+ foundOptimalSize = i == list.size();
+ if (foundOptimalSize)
+ buffer.write(allocator.serialize());
+ }
+ }
+ }
+
+ QVector<QDistanceField> textures;
+ textures.resize(textureCount);
+
+ for (int textureIndex = 0; textureIndex < textureCount; ++textureIndex) {
+ textures[textureIndex] = QDistanceField(allocatedAreaPerTexture.at(textureIndex).width(),
+ allocatedAreaPerTexture.at(textureIndex).height());
+
+ QRect rect = allocatedAreaPerTexture.at(textureIndex);
+
+ QtdfTextureRecord record;
+ record.allocatedX = qToBigEndian(rect.x());
+ record.allocatedY = qToBigEndian(rect.y());
+ record.allocatedWidth = qToBigEndian(rect.width());
+ record.allocatedHeight = qToBigEndian(rect.height());
+ record.padding = padding;
+ buffer.write(reinterpret_cast<char *>(&record),
+ sizeof(QtdfTextureRecord));
+ }
+
+ {
+ for (int i = 0; i < list.size(); ++i) {
+ int glyphIndex = list.at(i).row();
+ QImage image = m_model->distanceField(glyphIndex);
+
+ const GlyphData &glyphData = glyphDatas.at(glyphIndex);
+
+ QtdfGlyphRecord glyphRecord;
+ glyphRecord.glyphIndex = qToBigEndian(glyphIndex);
+ glyphRecord.textureOffsetX = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.x));
+ glyphRecord.textureOffsetY = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.y));
+ glyphRecord.textureWidth = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.width));
+ glyphRecord.textureHeight = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.height));
+ glyphRecord.xMargin = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.xMargin));
+ glyphRecord.yMargin = qToBigEndian(TO_FIXED_POINT(glyphData.texCoord.yMargin));
+ glyphRecord.boundingRectX = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.x()));
+ glyphRecord.boundingRectY = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.y()));
+ glyphRecord.boundingRectWidth = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.width()));
+ glyphRecord.boundingRectHeight = qToBigEndian(TO_FIXED_POINT(glyphData.boundingRect.height()));
+ glyphRecord.textureIndex = qToBigEndian(glyphData.textureIndex);
+ buffer.write(reinterpret_cast<char *>(&glyphRecord), sizeof(QtdfGlyphRecord));
+
+ int expectedWidth = qCeil(glyphData.texCoord.width + glyphData.texCoord.xMargin * 2);
+ image = image.copy(-padding, -padding,
+ expectedWidth + padding * 2,
+ image.height() + padding * 2);
+
+ uchar *inBits = image.scanLine(0);
+ uchar *outBits = textures[glyphData.textureIndex].scanLine(int(glyphData.texCoord.y) - padding)
+ + int(glyphData.texCoord.x) - padding;
+ for (int y = 0; y < image.height(); ++y) {
+ memcpy(outBits, inBits, image.width());
+ inBits += image.bytesPerLine();
+ outBits += textures[glyphData.textureIndex].width();
+ }
+ }
+ }
+
+ for (int i = 0; i < textures.size(); ++i) {
+ const QDistanceField &texture = textures.at(i);
+ const QRect &allocatedArea = allocatedAreaPerTexture.at(i);
+ buffer.write(reinterpret_cast<const char *>(texture.constBits()),
+ allocatedArea.width() * allocatedArea.height());
+ }
+
+ PAD_BUFFER(buffer, ret.size())
+ }
+
+ return ret;
+}
+
+void MainWindow::writeFile()
+{
+ Q_ASSERT(!m_fileName.isEmpty());
+
+ QFile file(m_fileName);
+ if (file.open(QIODevice::WriteOnly)) {
+
+ } else {
+ QMessageBox::warning(this,
+ tr("Can't open file for writing"),
+ tr("Unable to open file '%1' for writing").arg(m_fileName),
+ QMessageBox::Ok);
+ }
+}
+
+void MainWindow::openFont()
+{
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open font file"),
+ m_fontDir,
+ tr("Fonts (*.ttf *.otf);;All files (*)"));
+ if (!fileName.isEmpty()) {
+ m_fileName.clear();
+ m_fontFile = fileName;
+ m_fontDir = QFileInfo(fileName).absolutePath();
+ m_settings.setValue(QStringLiteral("fontDirectory"), m_fontDir);
+
+ if (m_model == nullptr) {
+ m_model = new DistanceFieldModel(this);
+ connect(m_model, &DistanceFieldModel::startGeneration, this, &MainWindow::startProgressBar);
+ connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::stopProgressBar);
+ connect(m_model, &DistanceFieldModel::distanceFieldGenerated, this, &MainWindow::updateProgressBar);
+ connect(m_model, &DistanceFieldModel::stopGeneration, this, &MainWindow::populateUnicodeRanges);
+ connect(m_model, &DistanceFieldModel::error, this, &MainWindow::displayError);
+
+ ui->lvGlyphs->setModel(m_model);
+
+ connect(ui->lvGlyphs->selectionModel(),
+ &QItemSelectionModel::selectionChanged,
+ this,
+ &MainWindow::updateSelection);
+ }
+
+ ui->lwUnicodeRanges->clear();
+ ui->lwUnicodeRanges->setDisabled(true);
+ ui->action_Save->setDisabled(true);
+ ui->action_Save_as->setDisabled(true);
+ ui->tbSave->setDisabled(true);
+ ui->action_Open->setDisabled(true);
+ m_model->setFont(fileName);
+ }
+}
+
+void MainWindow::updateProgressBar()
+{
+ m_statusBarProgressBar->setValue(m_statusBarProgressBar->value() + 1);
+ updateSelection();
+}
+
+void MainWindow::startProgressBar(quint16 glyphCount)
+{
+ ui->action_Open->setDisabled(false);
+ m_statusBarLabel->setText(tr("Generating"));
+ m_statusBarProgressBar->setMaximum(glyphCount);
+ m_statusBarProgressBar->setMinimum(0);
+ m_statusBarProgressBar->setValue(0);
+ m_statusBarProgressBar->setVisible(true);
+}
+
+void MainWindow::stopProgressBar()
+{
+ m_statusBarLabel->setText(tr("Ready"));
+ m_statusBarProgressBar->setVisible(false);
+}
+
+void MainWindow::selectAll()
+{
+ QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
+ if (list.size() == ui->lvGlyphs->model()->rowCount())
+ ui->lvGlyphs->clearSelection();
+ else
+ ui->lvGlyphs->selectAll();
+}
+
+void MainWindow::updateSelection()
+{
+ QModelIndexList list = ui->lvGlyphs->selectionModel()->selectedIndexes();
+ QString label;
+ if (list.size() == ui->lvGlyphs->model()->rowCount())
+ label = tr("Deselect &all");
+ else
+ label = tr("Select &all");
+
+ ui->tbSelectAll->setText(label);
+ ui->actionSelect_all->setText(label);
+
+ if (m_model != nullptr && ui->lwUnicodeRanges->count() > 0) {
+ // Ignore selection changes until we are done
+ disconnect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
+
+ QSet<int> selectedGlyphIndexes;
+ for (const QModelIndex &modelIndex : list)
+ selectedGlyphIndexes.insert(modelIndex.row());
+
+ QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
+ std::sort(unicodeRanges.begin(), unicodeRanges.end());
+
+ Q_ASSERT(ui->lwUnicodeRanges->count() == unicodeRanges.size());
+ for (int i = 0; i < unicodeRanges.size(); ++i) {
+ DistanceFieldModel::UnicodeRange unicodeRange = unicodeRanges.at(i);
+ QListWidgetItem *item = ui->lwUnicodeRanges->item(i);
+
+ QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange);
+ Q_ASSERT(!glyphIndexes.isEmpty());
+
+ item->setSelected(true);
+ for (glyph_t glyphIndex : glyphIndexes) {
+ if (!selectedGlyphIndexes.contains(glyphIndex)) {
+ item->setSelected(false);
+ break;
+ }
+ }
+ }
+
+ connect(ui->lwUnicodeRanges, &QListWidget::itemSelectionChanged, this, &MainWindow::updateUnicodeRanges);
+ }
+}
+
+void MainWindow::updateUnicodeRanges()
+{
+ if (m_model == nullptr)
+ return;
+
+ disconnect(ui->lvGlyphs->selectionModel(),
+ &QItemSelectionModel::selectionChanged,
+ this,
+ &MainWindow::updateSelection);
+
+ for (int i = 0; i < ui->lwUnicodeRanges->count(); ++i) {
+ QListWidgetItem *item = ui->lwUnicodeRanges->item(i);
+ DistanceFieldModel::UnicodeRange unicodeRange = item->data(Qt::UserRole).value<DistanceFieldModel::UnicodeRange>();
+ QList<glyph_t> glyphIndexes = m_model->glyphIndexesForUnicodeRange(unicodeRange);
+ for (glyph_t glyphIndex : glyphIndexes) {
+ QModelIndex index = m_model->index(glyphIndex);
+ ui->lvGlyphs->selectionModel()->select(index, item->isSelected()
+ ? QItemSelectionModel::Select
+ : QItemSelectionModel::Deselect);
+ }
+ }
+
+ connect(ui->lvGlyphs->selectionModel(),
+ &QItemSelectionModel::selectionChanged,
+ this,
+ &MainWindow::updateSelection);
+}
+
+void MainWindow::populateUnicodeRanges()
+{
+ QList<DistanceFieldModel::UnicodeRange> unicodeRanges = m_model->unicodeRanges();
+ std::sort(unicodeRanges.begin(), unicodeRanges.end());
+
+ for (DistanceFieldModel::UnicodeRange unicodeRange : unicodeRanges) {
+ QString name = m_model->nameForUnicodeRange(unicodeRange);
+ QListWidgetItem *item = new QListWidgetItem(name, ui->lwUnicodeRanges);
+ item->setData(Qt::UserRole, unicodeRange);
+ }
+
+ ui->lwUnicodeRanges->setDisabled(false);
+ ui->action_Save->setDisabled(false);
+ ui->action_Save_as->setDisabled(false);
+ ui->tbSave->setDisabled(false);
+}
+
+void MainWindow::displayError(const QString &errorString)
+{
+ QMessageBox::warning(this, tr("Error when parsing font file"), errorString, QMessageBox::Ok);
+}
+
+void MainWindow::selectString()
+{
+ QString s = QInputDialog::getText(this,
+ tr("Select glyphs for string"),
+ tr("String to parse:"));
+ if (!s.isEmpty()) {
+ QVector<uint> ucs4String = s.toUcs4();
+ for (uint ucs4 : ucs4String) {
+ glyph_t glyph = m_model->glyphIndexForUcs4(ucs4);
+ if (glyph != 0) {
+ ui->lvGlyphs->selectionModel()->select(m_model->index(glyph),
+ QItemSelectionModel::Select);
+ }
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/distancefieldgenerator/mainwindow.h b/src/distancefieldgenerator/mainwindow.h
new file mode 100644
index 000000000..dc209d558
--- /dev/null
+++ b/src/distancefieldgenerator/mainwindow.h
@@ -0,0 +1,83 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QSettings>
+
+QT_BEGIN_NAMESPACE
+
+namespace Ui {
+class MainWindow;
+}
+
+class QLabel;
+class QProgressBar;
+class DistanceFieldModel;
+class QListWidgetItem;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+private slots:
+ void openFont();
+ void startProgressBar(quint16 glyphCount);
+ void stopProgressBar();
+ void updateProgressBar();
+ void selectAll();
+ void updateSelection();
+ void updateUnicodeRanges();
+ void populateUnicodeRanges();
+ void save();
+ void saveAs();
+ void displayError(const QString &errorString);
+ void selectString();
+
+private:
+ void setupConnections();
+ void writeFile();
+ QByteArray createSfntTable();
+
+ Ui::MainWindow *ui;
+ QString m_fontDir;
+ QString m_fontFile;
+ QSettings m_settings;
+ DistanceFieldModel *m_model;
+ QLabel *m_statusBarLabel;
+ QProgressBar *m_statusBarProgressBar;
+ QString m_fileName;
+};
+
+QT_END_NAMESPACE
+
+#endif // MAINWINDOW_H
diff --git a/src/distancefieldgenerator/mainwindow.ui b/src/distancefieldgenerator/mainwindow.ui
new file mode 100644
index 000000000..d1f58e537
--- /dev/null
+++ b/src/distancefieldgenerator/mainwindow.ui
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Qt Distance Field Generator</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QListView" name="lvGlyphs">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::MultiSelection</enum>
+ </property>
+ <property name="flow">
+ <enum>QListView::LeftToRight</enum>
+ </property>
+ <property name="isWrapping" stdset="0">
+ <bool>true</bool>
+ </property>
+ <property name="gridSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="viewMode">
+ <enum>QListView::ListMode</enum>
+ </property>
+ <property name="uniformItemSizes">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QListWidget" name="lwUnicodeRanges">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::MultiSelection</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string/>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QToolButton" name="tbSelectAll">
+ <property name="text">
+ <string>Select &amp;all</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tbSave">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Maximum texture size:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="sbMaximumTextureSize">
+ <property name="minimum">
+ <number>64</number>
+ </property>
+ <property name="maximum">
+ <number>2147483647</number>
+ </property>
+ <property name="singleStep">
+ <number>64</number>
+ </property>
+ <property name="value">
+ <number>2048</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>19</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menu_File">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <addaction name="action_Open"/>
+ <addaction name="action_Save"/>
+ <addaction name="action_Save_as"/>
+ <addaction name="separator"/>
+ <addaction name="actionE_xit"/>
+ </widget>
+ <widget class="QMenu" name="menu_Select">
+ <property name="title">
+ <string>&amp;Select</string>
+ </property>
+ <addaction name="actionSelect_all"/>
+ <addaction name="actionSelect_string"/>
+ </widget>
+ <addaction name="menu_File"/>
+ <addaction name="menu_Select"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="action_Open">
+ <property name="text">
+ <string>&amp;Open font</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="action_Save_as">
+ <property name="text">
+ <string>Save &amp;as...</string>
+ </property>
+ </action>
+ <action name="action_Save">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="actionE_xit">
+ <property name="text">
+ <string>E&amp;xit</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionSelect_all">
+ <property name="text">
+ <string>Select &amp;all</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+A</string>
+ </property>
+ </action>
+ <action name="actionSelect_string">
+ <property name="text">
+ <string>Select &amp;string</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/src.pro b/src/src.pro
index 0783b7932..b6f6dcb1f 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -8,6 +8,8 @@ qtHaveModule(widgets) {
pixeltool \
designer
+ qtConfig(thread): SUBDIRS += distancefieldgenerator
+
linguist.depends = designer
}
}