/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "distancefieldmodelworker.h" #include "distancefieldmodel.h" #include #include 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 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(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(subtable + 1); const quint16 *startCodes = endCodes + segCount + 1; // endCodes[segCount] + reservedPad const qint16 *idDeltas = reinterpret_cast(startCodes + segCount); const quint16 *idRangeOffsets = reinterpret_cast(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(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(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(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(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 encodingRecords; { const CmapEncodingRecord *encodingRecord = reinterpret_cast(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(cmap.constData() + offset)); switch (format) { case 0: ::readCmapSubtable(this, cmap, offset, format); return; case 4: ::readCmapSubtable(this, cmap, offset, format); return; case 6: ::readCmapSubtable(this, cmap, offset, format); return; case 10: ::readCmapSubtable(this, cmap, offset, format); return; case 12: ::readCmapSubtable(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(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