Revision control
++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++
++ #include "distancefieldmodelworker.h"
++
++ #include "distancefieldmodel.h"
++ #include <qendian.h>
++ #include <QtGui/private/qdistancefield_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ # pragma pack(1)
++ struct MaxpHeader
++ {
++ quint32 version;
++ quint16 numGlyphs;
++ };
++
++ struct CmapHeader {
++ quint16 version;
++ quint16 numTables;
++ };
++
++ struct CmapEncodingRecord {
++ quint16 platformId;
++ quint16 encodingId;
++ quint32 offset;
++ };
++
++ struct CmapSubtable
++ {
++ quint16 format;
++ quint16 length;
++ quint16 language;
++ };
++
++ struct CmapSubtable0 : public CmapSubtable
++ {
++ quint8 glyphIdArray[256];
++ };
++
++ struct CmapSubtable4 : public CmapSubtable
++ {
++ quint16 segCountX2;
++ quint16 searchRange;
++ quint16 entrySelector;
++ quint16 rangeShift;
++ };
++
++ struct CmapSubtable6 : public CmapSubtable
++ {
++ quint16 firstCode;
++ quint16 entryCount;
++ };
++
++ struct CmapSubtable10
++ {
++ quint32 format;
++ quint32 length;
++ quint32 language;
++ quint32 startCharCode;
++ quint32 numChars;
++ };
++
++ struct CmapSubtable12
++ {
++ quint16 format;
++ quint16 reserved;
++ quint32 length;
++ quint32 language;
++ quint32 numGroups;
++ };
++
++ struct SequentialMapGroup
++ {
++ quint32 startCharCode;
++ quint32 endCharCode;
++ quint32 glyphIndex;
++ };
++
++ # pragma pack()
++
++ DistanceFieldModelWorker::DistanceFieldModelWorker(QObject *parent)
++ : QObject(parent)
++ , m_glyphCount(0)
++ , m_nextGlyphId(0)
++ , m_doubleGlyphResolution(false)
++ {
++ }
++
++ template <typename T>
++ static void readCmapSubtable(DistanceFieldModelWorker *worker, const QByteArray &cmap, quint32 tableOffset, quint16 format)
++ {
++ if (uint(cmap.size()) < tableOffset + sizeof(T)) {
++ emit worker->error(QObject::tr("End of file when reading subtable of format '%1'").arg(format));
++ return;
++ }
++
++ const T *subtable = reinterpret_cast<const T *>(cmap.constData() + tableOffset);
++ quint16 length = qFromBigEndian(subtable->length);
++ if (uint(cmap.size()) < tableOffset + length) {
++ emit worker->error(QObject::tr("Corrupt data found when reading subtable of format '%1'. Table offset: %2. Length: %3. Cmap length: %4.")
++ .arg(format).arg(tableOffset).arg(length).arg(cmap.size()));
++ return;
++ }
++
++ const void *end = cmap.constData() + tableOffset + length;
++ worker->readCmapSubtable(subtable, end);
++ }
++
++ void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable0 *subtable, const void *end)
++ {
++ Q_UNUSED(end); // Already checked for length
++ for (int i = 0; i < 256; ++i)
++ m_cmapping[glyph_t(subtable->glyphIdArray[i])] = i;
++ }
++
++ void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable4 *subtable, const void *end)
++ {
++ quint16 segCount = qFromBigEndian(subtable->segCountX2) / 2;
++ const quint16 *endCodes = reinterpret_cast<const quint16 *>(subtable + 1);
++ const quint16 *startCodes = endCodes + segCount + 1; // endCodes[segCount] + reservedPad
++ const qint16 *idDeltas = reinterpret_cast<const qint16 *>(startCodes + segCount);
++ const quint16 *idRangeOffsets = reinterpret_cast<const quint16 *>(idDeltas + segCount);
++ const quint16 *glyphIdArray = idRangeOffsets + segCount;
++ if (glyphIdArray > end) {
++ emit error(tr("End of cmap table reached when parsing subtable format '4'"));
++ return;
++ }
++
++ for (int i = 0; i < segCount - 1; ++i) { // Last entry in arrays is the sentinel
++ quint16 startCode = qFromBigEndian(startCodes[i]);
++ quint16 endCode = qFromBigEndian(endCodes[i]);
++ quint16 rangeOffset = qFromBigEndian(idRangeOffsets[i]);
++
++ for (quint16 c = startCode; c <= endCode; ++c) {
++ if (rangeOffset != 0) {
++ const quint16 *glyphIndex = (idRangeOffsets + i) + (c - startCode) + rangeOffset / 2;
++ if (glyphIndex + 1 > end) {
++ emit error(tr("End of cmap, subtable format '4', reached when fetching character '%1' in range [%2, %3]").arg(c).arg(startCode).arg(endCode));
++ return;
++ }
++
++ m_cmapping[glyph_t(qFromBigEndian(*glyphIndex))] = quint32(c);
++ } else {
++ quint16 idDelta = qFromBigEndian(idDeltas[i]);
++ m_cmapping[glyph_t((idDelta + c) % 65536)] = quint32(c);
++ }
++ }
++
++ }
++ }
++
++ void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable6 *subtable, const void *end)
++ {
++ quint16 entryCount = qFromBigEndian(subtable->entryCount);
++ const quint16 *glyphIndexes = reinterpret_cast<const quint16 *>(subtable + 1);
++ if (glyphIndexes + entryCount > end) {
++ emit error(tr("End of cmap reached while parsing subtable format '6'"));
++ return;
++ }
++
++ quint16 firstCode = qFromBigEndian(subtable->firstCode);
++ for (quint16 i = 0; i < entryCount; ++i)
++ m_cmapping[glyph_t(qFromBigEndian(glyphIndexes[i]))] = firstCode + i;
++ }
++
++ void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable10 *subtable, const void *end)
++ {
++ quint32 numChars = qFromBigEndian(subtable->numChars);
++ const quint16 *glyphs = reinterpret_cast<const quint16 *>(subtable + 1);
++ if (glyphs + numChars > end) {
++ emit error(tr("End of cmap reached while parsing subtable of format '10'"));
++ return;
++ }
++
++ quint32 startCharCode = qFromBigEndian(subtable->startCharCode);
++ for (quint32 i = 0; i < numChars; ++i)
++ m_cmapping[glyph_t(qFromBigEndian(glyphs[i]))] = startCharCode + i;
++ }
++
++ void DistanceFieldModelWorker::readCmapSubtable(const CmapSubtable12 *subtable, const void *end)
++ {
++ quint32 numGroups = qFromBigEndian(subtable->numGroups);
++ const SequentialMapGroup *sequentialMapGroups = reinterpret_cast<const SequentialMapGroup *>(subtable + 1);
++ if (sequentialMapGroups + numGroups > end) {
++ emit error(tr("End of cmap reached while parsing subtable of format '12'"));
++ return;
++ }
++
++ for (quint32 i = 0; i < numGroups; ++i) {
++ quint32 startCharCode = qFromBigEndian(sequentialMapGroups[i].startCharCode);
++ quint32 endCharCode = qFromBigEndian(sequentialMapGroups[i].endCharCode);
++ quint32 startGlyphIndex = qFromBigEndian(sequentialMapGroups[i].glyphIndex);
++
++ for (quint32 j = 0; j < endCharCode - startCharCode + 1; ++j)
++ m_cmapping[glyph_t(startGlyphIndex + j)] = startCharCode + j;
++ }
++ }
++
++ void DistanceFieldModelWorker::readCmap()
++ {
++ if (m_font.isValid()) {
++ QByteArray cmap = m_font.fontTable("cmap");
++ if (uint(cmap.size()) < sizeof(CmapHeader)) {
++ emit error(tr("Invalid cmap table. No header."));
++ return;
++ }
++
++ const CmapHeader *header = reinterpret_cast<const CmapHeader *>(cmap.constData());
++ quint16 numTables = qFromBigEndian(header->numTables);
++
++ if (uint(cmap.size()) < sizeof(CmapHeader) + numTables * sizeof(CmapEncodingRecord)) {
++ emit error(tr("Invalid cmap table. No space for %1 encoding records.").arg(numTables));
++ return;
++ }
++
++ // Support the same encodings as macOS (and same order of prefernece), since this should
++ // cover most fonts
++ static quint32 encodingPreferenceOrder[] =
++ {
++ quint32(0) << 16 | 4, // Unicode 2.0 +
++ quint32(0) << 16 | 3, // Unicode 2.0 BMP
++ quint32(0) << 16 | 1, // Unicode 1.1
++ quint32(3) << 16 | 10, // Windows, UCS-4
++ quint32(3) << 16 | 1, // Windows, UCS-2
++ quint32(0)
++ };
++
++ QHash<quint32, const CmapEncodingRecord *> encodingRecords;
++ {
++ const CmapEncodingRecord *encodingRecord = reinterpret_cast<const CmapEncodingRecord *>(cmap.constData() + sizeof(CmapHeader));
++ while (numTables-- > 0) {
++ quint32 encoding = quint32(qFromBigEndian(encodingRecord->platformId)) << 16 | qFromBigEndian(encodingRecord->encodingId);
++ encodingRecords[encoding] = encodingRecord++;
++ }
++ }
++
++ // Find the first subtable we support in order of preference
++ for (int i = 0; encodingPreferenceOrder[i] != 0; ++i) {
++ const CmapEncodingRecord *encodingRecord = encodingRecords.value(encodingPreferenceOrder[i], nullptr);
++ if (encodingRecord != nullptr) {
++ quint32 offset = qFromBigEndian(encodingRecord->offset);
++ if (uint(cmap.size()) < offset + sizeof(quint16)) {
++ emit error(tr("Invalid offset '%1' in cmap").arg(offset));
++ return;
++ }
++
++ quint16 format = qFromBigEndian(*reinterpret_cast<const quint16 *>(cmap.constData() + offset));
++ switch (format) {
++ case 0:
++ ::readCmapSubtable<CmapSubtable0>(this, cmap, offset, format);
++ return;
++ case 4:
++ ::readCmapSubtable<CmapSubtable4>(this, cmap, offset, format);
++ return;
++ case 6:
++ ::readCmapSubtable<CmapSubtable6>(this, cmap, offset, format);
++ return;
++ case 10:
++ ::readCmapSubtable<CmapSubtable10>(this, cmap, offset, format);
++ return;
++ case 12:
++ ::readCmapSubtable<CmapSubtable12>(this, cmap, offset, format);
++ return;
++ default:
++ qWarning() << tr("Unsupported cmap subtable format '%1'").arg(format);
++ };
++ }
++ }
++
++ emit error(tr("No suitable cmap subtable found"));
++ }
++ }
++
++ void DistanceFieldModelWorker::readGlyphCount()
++ {
++ m_nextGlyphId = 0;
++ m_glyphCount = 0;
++ if (m_font.isValid()) {
++ QByteArray maxp = m_font.fontTable("maxp");
++ if (uint(maxp.size()) >= sizeof(MaxpHeader)) {
++ const MaxpHeader *header = reinterpret_cast<const MaxpHeader *>(maxp.constData());
++ m_glyphCount = qFromBigEndian(header->numGlyphs);
++ }
++ }
++
++ m_doubleGlyphResolution = qt_fontHasNarrowOutlines(m_font) && m_glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
++ }
++
++ void DistanceFieldModelWorker::loadFont(const QString &fileName)
++ {
++ m_font = QRawFont(fileName, 64);
++ if (!m_font.isValid())
++ emit error(tr("File '%1' is not a valid font file.").arg(fileName));
++
++ readGlyphCount();
++ readCmap();
++
++ qreal pixelSize = QT_DISTANCEFIELD_BASEFONTSIZE(m_doubleGlyphResolution) * QT_DISTANCEFIELD_SCALE(m_doubleGlyphResolution);
++ m_font.setPixelSize(pixelSize);
++
++ emit fontLoaded(m_glyphCount,
++ m_doubleGlyphResolution,
++ pixelSize);
++ }
++
++ void DistanceFieldModelWorker::generateOneDistanceField()
++ {
++ Q_ASSERT(m_nextGlyphId <= m_glyphCount);
++
++ if (m_nextGlyphId == m_glyphCount) {
++ emit fontGenerated();
++ return;
++ }
++
++ QPainterPath path = m_font.pathForGlyph(m_nextGlyphId);
++ QDistanceField distanceField(path, m_nextGlyphId, m_doubleGlyphResolution);
++ emit distanceFieldGenerated(distanceField.toImage(QImage::Format_Alpha8),
++ path,
++ m_nextGlyphId,
++ m_cmapping.value(m_nextGlyphId));
++
++ m_nextGlyphId++;
++ }
++
++ QT_END_NAMESPACE