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
35 QT_BEGIN_NAMESPACE
36
37 # pragma pack(1)
38 struct MaxpHeader
39 {
40 quint32 version;
41 quint16 numGlyphs;
42 };
43
44 struct CmapHeader {
45 quint16 version;
46 quint16 numTables;
47 };
48
49 struct CmapEncodingRecord {
50 quint16 platformId;
51 quint16 encodingId;
52 quint32 offset;
53 };
54
55 struct CmapSubtable
56 {
57 quint16 format;
58 quint16 length;
59 quint16 language;
60 };
61
62 struct CmapSubtable0 : public CmapSubtable
63 {
64 quint8 glyphIdArray[256];
65 };
66
67 struct CmapSubtable4 : public CmapSubtable
68 {
69 quint16 segCountX2;
70 quint16 searchRange;
71 quint16 entrySelector;
72 quint16 rangeShift;
73 };
74
75 struct CmapSubtable6 : public CmapSubtable
76 {
77 quint16 firstCode;
78 quint16 entryCount;
79 };
80
81 struct CmapSubtable10
82 {
83 quint32 format;
84 quint32 length;
85 quint32 language;
86 quint32 startCharCode;
87 quint32 numChars;
88 };
89
90 struct CmapSubtable12
91 {
92 quint16 format;
93 quint16 reserved;
94 quint32 length;
95 quint32 language;
96 quint32 numGroups;
97 };
98
99 struct SequentialMapGroup
100 {
101 quint32 startCharCode;
102 quint32 endCharCode;
103 quint32 glyphIndex;
104 };
105
106 # pragma pack()
107
DistanceFieldModelWorker(QObject * parent)108 DistanceFieldModelWorker::DistanceFieldModelWorker(QObject *parent)
109 : QObject(parent)
110 , m_glyphCount(0)
111 , m_nextGlyphId(0)
112 , m_doubleGlyphResolution(false)
113 {
114 }
115
116 template <typename T>
readCmapSubtable(DistanceFieldModelWorker * worker,const QByteArray & cmap,quint32 tableOffset,quint16 format)117 static void readCmapSubtable(DistanceFieldModelWorker *worker, const QByteArray &cmap, quint32 tableOffset, quint16 format)
118 {
119 if (uint(cmap.size()) < tableOffset + sizeof(T)) {
120 emit worker->error(QObject::tr("End of file when reading subtable of format '%1'").arg(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(QObject::tr("Corrupt data found when reading subtable of format '%1'. Table offset: %2. Length: %3. Cmap length: %4.")
128 .arg(format).arg(tableOffset).arg(length).arg(cmap.size()));
129 return;
130 }
131
132 const void *end = cmap.constData() + tableOffset + length;
133 worker->readCmapSubtable(subtable, end);
134 }
135
readCmapSubtable(const CmapSubtable0 * subtable,const void * end)136 void 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
readCmapSubtable(const CmapSubtable4 * subtable,const void * end)143 void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable4 *subtable, const void *end)
144 {
145 quint16 segCount = qFromBigEndian(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(tr("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(startCodes[i]);
158 quint16 endCode = qFromBigEndian(endCodes[i]);
159 quint16 rangeOffset = qFromBigEndian(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(tr("End of cmap, subtable format '4', reached when fetching character '%1' in range [%2, %3]").arg(c).arg(startCode).arg(endCode));
166 return;
167 }
168
169 m_cmapping[glyph_t(qFromBigEndian(*glyphIndex))] = quint32(c);
170 } else {
171 quint16 idDelta = qFromBigEndian(idDeltas[i]);
172 m_cmapping[glyph_t((idDelta + c) % 65536)] = quint32(c);
173 }
174 }
175
176 }
177 }
178
readCmapSubtable(const CmapSubtable6 * subtable,const void * end)179 void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable6 *subtable, const void *end)
180 {
181 quint16 entryCount = qFromBigEndian(subtable->entryCount);
182 const quint16 *glyphIndexes = reinterpret_cast<const quint16 *>(subtable + 1);
183 if (glyphIndexes + entryCount > end) {
184 emit error(tr("End of cmap reached while parsing subtable format '6'"));
185 return;
186 }
187
188 quint16 firstCode = qFromBigEndian(subtable->firstCode);
189 for (quint16 i = 0; i < entryCount; ++i)
190 m_cmapping[glyph_t(qFromBigEndian(glyphIndexes[i]))] = firstCode + i;
191 }
192
readCmapSubtable(const CmapSubtable10 * subtable,const void * end)193 void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable10 *subtable, const void *end)
194 {
195 quint32 numChars = qFromBigEndian(subtable->numChars);
196 const quint16 *glyphs = reinterpret_cast<const quint16 *>(subtable + 1);
197 if (glyphs + numChars > end) {
198 emit error(tr("End of cmap reached while parsing subtable of format '10'"));
199 return;
200 }
201
202 quint32 startCharCode = qFromBigEndian(subtable->startCharCode);
203 for (quint32 i = 0; i < numChars; ++i)
204 m_cmapping[glyph_t(qFromBigEndian(glyphs[i]))] = startCharCode + i;
205 }
206
readCmapSubtable(const CmapSubtable12 * subtable,const void * end)207 void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable12 *subtable, const void *end)
208 {
209 quint32 numGroups = qFromBigEndian(subtable->numGroups);
210 const SequentialMapGroup *sequentialMapGroups = reinterpret_cast<const SequentialMapGroup *>(subtable + 1);
211 if (sequentialMapGroups + numGroups > end) {
212 emit error(tr("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(sequentialMapGroups[i].startCharCode);
218 quint32 endCharCode = qFromBigEndian(sequentialMapGroups[i].endCharCode);
219 quint32 startGlyphIndex = qFromBigEndian(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
readCmap()226 void DistanceFieldModelWorker::readCmap()
227 {
228 if (m_font.isValid()) {
229 QByteArray cmap = m_font.fontTable("cmap");
230 if (uint(cmap.size()) < sizeof(CmapHeader)) {
231 emit error(tr("Invalid cmap table. No header."));
232 return;
233 }
234
235 const CmapHeader *header = reinterpret_cast<const CmapHeader *>(cmap.constData());
236 quint16 numTables = qFromBigEndian(header->numTables);
237
238 if (uint(cmap.size()) < sizeof(CmapHeader) + numTables * sizeof(CmapEncodingRecord)) {
239 emit error(tr("Invalid cmap table. No space for %1 encoding records.").arg(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(encodingRecord->platformId)) << 16 | qFromBigEndian(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(encodingPreferenceOrder[i], nullptr);
267 if (encodingRecord != nullptr) {
268 quint32 offset = qFromBigEndian(encodingRecord->offset);
269 if (uint(cmap.size()) < offset + sizeof(quint16)) {
270 emit error(tr("Invalid offset '%1' in cmap").arg(offset));
271 return;
272 }
273
274 quint16 format = qFromBigEndian(*reinterpret_cast<const quint16 *>(cmap.constData() + offset));
275 switch (format) {
276 case 0:
277 ::readCmapSubtable<CmapSubtable0>(this, cmap, offset, format);
278 return;
279 case 4:
280 ::readCmapSubtable<CmapSubtable4>(this, cmap, offset, format);
281 return;
282 case 6:
283 ::readCmapSubtable<CmapSubtable6>(this, cmap, offset, format);
284 return;
285 case 10:
286 ::readCmapSubtable<CmapSubtable10>(this, cmap, offset, format);
287 return;
288 case 12:
289 ::readCmapSubtable<CmapSubtable12>(this, cmap, offset, format);
290 return;
291 default:
292 qWarning() << tr("Unsupported cmap subtable format '%1'").arg(format);
293 };
294 }
295 }
296
297 emit error(tr("No suitable cmap subtable found"));
298 }
299 }
300
readGlyphCount()301 void DistanceFieldModelWorker::readGlyphCount()
302 {
303 m_nextGlyphId = 0;
304 m_glyphCount = 0;
305 if (m_font.isValid()) {
306 QByteArray maxp = m_font.fontTable("maxp");
307 if (uint(maxp.size()) >= sizeof(MaxpHeader)) {
308 const MaxpHeader *header = reinterpret_cast<const MaxpHeader *>(maxp.constData());
309 m_glyphCount = qFromBigEndian(header->numGlyphs);
310 }
311 }
312
313 m_doubleGlyphResolution = qt_fontHasNarrowOutlines(m_font) && m_glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
314 }
315
loadFont(const QString & fileName)316 void DistanceFieldModelWorker::loadFont(const QString &fileName)
317 {
318 m_font = QRawFont(fileName, 64);
319 if (!m_font.isValid())
320 emit error(tr("File '%1' is not a valid font file.").arg(fileName));
321
322 readGlyphCount();
323 readCmap();
324
325 qreal pixelSize = QT_DISTANCEFIELD_BASEFONTSIZE(m_doubleGlyphResolution) * QT_DISTANCEFIELD_SCALE(m_doubleGlyphResolution);
326 m_font.setPixelSize(pixelSize);
327
328 emit fontLoaded(m_glyphCount,
329 m_doubleGlyphResolution,
330 pixelSize);
331 }
332
generateOneDistanceField()333 void 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(m_nextGlyphId);
343 QDistanceField distanceField(path, m_nextGlyphId, m_doubleGlyphResolution);
344 emit distanceFieldGenerated(distanceField.toImage(QImage::Format_Alpha8),
345 path,
346 m_nextGlyphId,
347 m_cmapping.value(m_nextGlyphId));
348
349 m_nextGlyphId++;
350 }
351
352 QT_END_NAMESPACE
353