1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "distancefieldmodelworker.h"
30
31#include "distancefieldmodel.h"
32#include <qendian.h>
33#include <QtGui/private/qdistancefield_p.h>
34
35QT_BEGIN_NAMESPACE
36
37# pragma pack(1)
38struct MaxpHeader
39{
40 quint32 version;
41 quint16 numGlyphs;
42};
43
44struct CmapHeader {
45 quint16 version;
46 quint16 numTables;
47};
48
49struct CmapEncodingRecord {
50 quint16 platformId;
51 quint16 encodingId;
52 quint32 offset;
53};
54
55struct CmapSubtable
56{
57 quint16 format;
58 quint16 length;
59 quint16 language;
60};
61
62struct CmapSubtable0 : public CmapSubtable
63{
64 quint8 glyphIdArray[256];
65};
66
67struct CmapSubtable4 : public CmapSubtable
68{
69 quint16 segCountX2;
70 quint16 searchRange;
71 quint16 entrySelector;
72 quint16 rangeShift;
73};
74
75struct CmapSubtable6 : public CmapSubtable
76{
77 quint16 firstCode;
78 quint16 entryCount;
79};
80
81struct CmapSubtable10
82{
83 quint32 format;
84 quint32 length;
85 quint32 language;
86 quint32 startCharCode;
87 quint32 numChars;
88};
89
90struct CmapSubtable12
91{
92 quint16 format;
93 quint16 reserved;
94 quint32 length;
95 quint32 language;
96 quint32 numGroups;
97};
98
99struct SequentialMapGroup
100{
101 quint32 startCharCode;
102 quint32 endCharCode;
103 quint32 glyphIndex;
104};
105
106# pragma pack()
107
108DistanceFieldModelWorker::DistanceFieldModelWorker(QObject *parent)
109 : QObject(parent)
110 , m_glyphCount(0)
111 , m_nextGlyphId(0)
112 , m_doubleGlyphResolution(false)
113{
114}
115
116template <typename T>
117static void readCmapSubtable(DistanceFieldModelWorker *worker, const QByteArray &cmap, quint32 tableOffset, quint16 format)
118{
119 if (uint(cmap.size()) < tableOffset + sizeof(T)) {
120 emit worker->error(errorString: QObject::tr(s: "End of file when reading subtable of format '%1'").arg(a: format));
121 return;
122 }
123
124 const T *subtable = reinterpret_cast<const T *>(cmap.constData() + tableOffset);
125 quint16 length = qFromBigEndian(subtable->length);
126 if (uint(cmap.size()) < tableOffset + length) {
127 emit worker->error(errorString: QObject::tr(s: "Corrupt data found when reading subtable of format '%1'. Table offset: %2. Length: %3. Cmap length: %4.")
128 .arg(a: format).arg(a: tableOffset).arg(a: length).arg(a: cmap.size()));
129 return;
130 }
131
132 const void *end = cmap.constData() + tableOffset + length;
133 worker->readCmapSubtable(subtable, end);
134}
135
136void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable0 *subtable, const void *end)
137{
138 Q_UNUSED(end); // Already checked for length
139 for (int i = 0; i < 256; ++i)
140 m_cmapping[glyph_t(subtable->glyphIdArray[i])] = i;
141}
142
143void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable4 *subtable, const void *end)
144{
145 quint16 segCount = qFromBigEndian(source: subtable->segCountX2) / 2;
146 const quint16 *endCodes = reinterpret_cast<const quint16 *>(subtable + 1);
147 const quint16 *startCodes = endCodes + segCount + 1; // endCodes[segCount] + reservedPad
148 const qint16 *idDeltas = reinterpret_cast<const qint16 *>(startCodes + segCount);
149 const quint16 *idRangeOffsets = reinterpret_cast<const quint16 *>(idDeltas + segCount);
150 const quint16 *glyphIdArray = idRangeOffsets + segCount;
151 if (glyphIdArray > end) {
152 emit error(errorString: tr(s: "End of cmap table reached when parsing subtable format '4'"));
153 return;
154 }
155
156 for (int i = 0; i < segCount - 1; ++i) { // Last entry in arrays is the sentinel
157 quint16 startCode = qFromBigEndian(source: startCodes[i]);
158 quint16 endCode = qFromBigEndian(source: endCodes[i]);
159 quint16 rangeOffset = qFromBigEndian(source: idRangeOffsets[i]);
160
161 for (quint16 c = startCode; c <= endCode; ++c) {
162 if (rangeOffset != 0) {
163 const quint16 *glyphIndex = (idRangeOffsets + i) + (c - startCode) + rangeOffset / 2;
164 if (glyphIndex + 1 > end) {
165 emit error(errorString: tr(s: "End of cmap, subtable format '4', reached when fetching character '%1' in range [%2, %3]").arg(a: c).arg(a: startCode).arg(a: endCode));
166 return;
167 }
168
169 m_cmapping[glyph_t(qFromBigEndian(source: *glyphIndex))] = quint32(c);
170 } else {
171 quint16 idDelta = qFromBigEndian(source: idDeltas[i]);
172 m_cmapping[glyph_t((idDelta + c) % 65536)] = quint32(c);
173 }
174 }
175
176 }
177}
178
179void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable6 *subtable, const void *end)
180{
181 quint16 entryCount = qFromBigEndian(source: subtable->entryCount);
182 const quint16 *glyphIndexes = reinterpret_cast<const quint16 *>(subtable + 1);
183 if (glyphIndexes + entryCount > end) {
184 emit error(errorString: tr(s: "End of cmap reached while parsing subtable format '6'"));
185 return;
186 }
187
188 quint16 firstCode = qFromBigEndian(source: subtable->firstCode);
189 for (quint16 i = 0; i < entryCount; ++i)
190 m_cmapping[glyph_t(qFromBigEndian(source: glyphIndexes[i]))] = firstCode + i;
191}
192
193void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable10 *subtable, const void *end)
194{
195 quint32 numChars = qFromBigEndian(source: subtable->numChars);
196 const quint16 *glyphs = reinterpret_cast<const quint16 *>(subtable + 1);
197 if (glyphs + numChars > end) {
198 emit error(errorString: tr(s: "End of cmap reached while parsing subtable of format '10'"));
199 return;
200 }
201
202 quint32 startCharCode = qFromBigEndian(source: subtable->startCharCode);
203 for (quint32 i = 0; i < numChars; ++i)
204 m_cmapping[glyph_t(qFromBigEndian(source: glyphs[i]))] = startCharCode + i;
205}
206
207void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable12 *subtable, const void *end)
208{
209 quint32 numGroups = qFromBigEndian(source: subtable->numGroups);
210 const SequentialMapGroup *sequentialMapGroups = reinterpret_cast<const SequentialMapGroup *>(subtable + 1);
211 if (sequentialMapGroups + numGroups > end) {
212 emit error(errorString: tr(s: "End of cmap reached while parsing subtable of format '12'"));
213 return;
214 }
215
216 for (quint32 i = 0; i < numGroups; ++i) {
217 quint32 startCharCode = qFromBigEndian(source: sequentialMapGroups[i].startCharCode);
218 quint32 endCharCode = qFromBigEndian(source: sequentialMapGroups[i].endCharCode);
219 quint32 startGlyphIndex = qFromBigEndian(source: sequentialMapGroups[i].glyphIndex);
220
221 for (quint32 j = 0; j < endCharCode - startCharCode + 1; ++j)
222 m_cmapping[glyph_t(startGlyphIndex + j)] = startCharCode + j;
223 }
224}
225
226void DistanceFieldModelWorker::readCmap()
227{
228 if (m_font.isValid()) {
229 QByteArray cmap = m_font.fontTable(tagName: "cmap");
230 if (uint(cmap.size()) < sizeof(CmapHeader)) {
231 emit error(errorString: tr(s: "Invalid cmap table. No header."));
232 return;
233 }
234
235 const CmapHeader *header = reinterpret_cast<const CmapHeader *>(cmap.constData());
236 quint16 numTables = qFromBigEndian(source: header->numTables);
237
238 if (uint(cmap.size()) < sizeof(CmapHeader) + numTables * sizeof(CmapEncodingRecord)) {
239 emit error(errorString: tr(s: "Invalid cmap table. No space for %1 encoding records.").arg(a: numTables));
240 return;
241 }
242
243 // Support the same encodings as macOS (and same order of prefernece), since this should
244 // cover most fonts
245 static quint32 encodingPreferenceOrder[] =
246 {
247 quint32(0) << 16 | 4, // Unicode 2.0 +
248 quint32(0) << 16 | 3, // Unicode 2.0 BMP
249 quint32(0) << 16 | 1, // Unicode 1.1
250 quint32(3) << 16 | 10, // Windows, UCS-4
251 quint32(3) << 16 | 1, // Windows, UCS-2
252 quint32(0)
253 };
254
255 QHash<quint32, const CmapEncodingRecord *> encodingRecords;
256 {
257 const CmapEncodingRecord *encodingRecord = reinterpret_cast<const CmapEncodingRecord *>(cmap.constData() + sizeof(CmapHeader));
258 while (numTables-- > 0) {
259 quint32 encoding = quint32(qFromBigEndian(source: encodingRecord->platformId)) << 16 | qFromBigEndian(source: encodingRecord->encodingId);
260 encodingRecords[encoding] = encodingRecord++;
261 }
262 }
263
264 // Find the first subtable we support in order of preference
265 for (int i = 0; encodingPreferenceOrder[i] != 0; ++i) {
266 const CmapEncodingRecord *encodingRecord = encodingRecords.value(akey: encodingPreferenceOrder[i], adefaultValue: nullptr);
267 if (encodingRecord != nullptr) {
268 quint32 offset = qFromBigEndian(source: encodingRecord->offset);
269 if (uint(cmap.size()) < offset + sizeof(quint16)) {
270 emit error(errorString: tr(s: "Invalid offset '%1' in cmap").arg(a: offset));
271 return;
272 }
273
274 quint16 format = qFromBigEndian(source: *reinterpret_cast<const quint16 *>(cmap.constData() + offset));
275 switch (format) {
276 case 0:
277 ::readCmapSubtable<CmapSubtable0>(worker: this, cmap, tableOffset: offset, format);
278 return;
279 case 4:
280 ::readCmapSubtable<CmapSubtable4>(worker: this, cmap, tableOffset: offset, format);
281 return;
282 case 6:
283 ::readCmapSubtable<CmapSubtable6>(worker: this, cmap, tableOffset: offset, format);
284 return;
285 case 10:
286 ::readCmapSubtable<CmapSubtable10>(worker: this, cmap, tableOffset: offset, format);
287 return;
288 case 12:
289 ::readCmapSubtable<CmapSubtable12>(worker: this, cmap, tableOffset: offset, format);
290 return;
291 default:
292 qWarning() << tr(s: "Unsupported cmap subtable format '%1'").arg(a: format);
293 };
294 }
295 }
296
297 emit error(errorString: tr(s: "No suitable cmap subtable found"));
298 }
299}
300
301void DistanceFieldModelWorker::readGlyphCount()
302{
303 m_nextGlyphId = 0;
304 m_glyphCount = 0;
305 if (m_font.isValid()) {
306 QByteArray maxp = m_font.fontTable(tagName: "maxp");
307 if (uint(maxp.size()) >= sizeof(MaxpHeader)) {
308 const MaxpHeader *header = reinterpret_cast<const MaxpHeader *>(maxp.constData());
309 m_glyphCount = qFromBigEndian(source: header->numGlyphs);
310 }
311 }
312
313 m_doubleGlyphResolution = qt_fontHasNarrowOutlines(f: m_font) && m_glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
314}
315
316void DistanceFieldModelWorker::loadFont(const QString &fileName)
317{
318 m_font = QRawFont(fileName, 64);
319 if (!m_font.isValid())
320 emit error(errorString: tr(s: "File '%1' is not a valid font file.").arg(a: fileName));
321
322 readGlyphCount();
323 readCmap();
324
325 qreal pixelSize = QT_DISTANCEFIELD_BASEFONTSIZE(narrowOutlineFont: m_doubleGlyphResolution) * QT_DISTANCEFIELD_SCALE(narrowOutlineFont: m_doubleGlyphResolution);
326 m_font.setPixelSize(pixelSize);
327
328 emit fontLoaded(glyphCount: m_glyphCount,
329 doubleResolution: m_doubleGlyphResolution,
330 pixelSize);
331}
332
333void DistanceFieldModelWorker::generateOneDistanceField()
334{
335 Q_ASSERT(m_nextGlyphId <= m_glyphCount);
336
337 if (m_nextGlyphId == m_glyphCount) {
338 emit fontGenerated();
339 return;
340 }
341
342 QPainterPath path = m_font.pathForGlyph(glyphIndex: m_nextGlyphId);
343 QDistanceField distanceField(path, m_nextGlyphId, m_doubleGlyphResolution);
344 emit distanceFieldGenerated(distanceField: distanceField.toImage(format: QImage::Format_Alpha8),
345 path,
346 glyphId: m_nextGlyphId,
347 cmapAssignment: m_cmapping.value(akey: m_nextGlyphId));
348
349 m_nextGlyphId++;
350}
351
352QT_END_NAMESPACE
353

source code of qttools/src/distancefieldgenerator/distancefieldmodelworker.cpp