blob: b76f3b7071e4f13eba6da2c15c4451c03b3e24ee [file] [log] [blame]
/****************************************************************************
**
** 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 <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