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