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 |
39 | { |
40 | quint32 ; |
41 | quint16 ; |
42 | }; |
43 | |
44 | struct { |
45 | quint16 ; |
46 | quint16 ; |
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 | |
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> |
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(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 | |
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 | |
143 | void 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 | |
179 | void 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 | |
193 | void 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 | |
207 | void 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 | |
226 | void 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 * = 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 | |
301 | void 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 * = 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 | |
316 | void 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 | |
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(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 | |
352 | QT_END_NAMESPACE |
353 | |