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

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