Subversion Repositories Scribus

Compare Revisions

Ignore whitespace Rev 24469 → Rev 24468

/trunk/Scribus/scribus/fonts/sfnt.cpp
1,28 → 1,24
//
// sfnt.cpp
// Scribus
//
// Created by Andreas Vox on 18.04.15.
//
//
 
#include "fonts/sfnt.h"
#include "fonts/sfnt_format.h"
 
#include FT_TRUETYPE_TABLES_H
#include FT_TRUETYPE_TAGS_H
#include FT_TRUETYPE_IDS_H
 
#include <QDebug>
#include <QScopedPointer>
 
#include "scconfig.h"
 
//
// sfnt.cpp
// Scribus
//
// Created by Andreas Vox on 18.04.15.
//
//
 
#include "fonts/sfnt.h"
#include "fonts/sfnt_format.h"
 
#include FT_TRUETYPE_TABLES_H
#include FT_TRUETYPE_TAGS_H
#include FT_TRUETYPE_IDS_H
 
#include <QDebug>
#include <QScopedPointer>
 
#include <harfbuzz/hb.h>
#ifdef HAVE_HARFBUZZ_SUBSET
#include <harfbuzz/hb-subset.h>
#endif
 
#include <harfbuzz/hb-subset.h>
 
struct HbBlobDeleter
{
static void cleanup(void *pointer)
41,7 → 37,6
}
};
 
#ifdef HAVE_HARFBUZZ_SUBSET
struct HbSubsetInputDeleter
{
static void cleanup(void *pointer)
49,1198 → 44,1197
if (pointer)
hb_subset_input_destroy((hb_subset_input_t*) pointer);
}
};
#endif
 
namespace sfnt {
uchar byte(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0];
}
 
quint32 word(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0] << 24 | pp[1] << 16 | pp[2] << 8 | pp[3];
}
void putWord(QByteArray & bb, uint pos, quint32 val)
{
unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos;
*pp++ = (val >> 24) & 0xFF;
*pp++ = (val >> 16) & 0xFF;
*pp++ = (val >> 8) & 0xFF;
*pp++ = (val) & 0xFF;
}
void appendWord(QByteArray & bb, quint32 val)
{
uint pos = bb.size();
bb.resize(pos + 4);
putWord(bb, pos, val);
}
quint16 word16(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0] << 8 | pp[1];
}
void putWord16(QByteArray & bb, uint pos, quint16 val)
{
unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos;
*pp++ = (val >> 8) & 0xFF;
*pp++ = (val) & 0xFF;
}
void appendWord16(QByteArray & bb, quint16 val)
{
uint pos = bb.size();
bb.resize(pos + 2);
putWord16(bb, pos, val);
}
QByteArray tag(const QByteArray& bb, uint pos)
{
return QByteArray::fromRawData(bb.constData() + pos, 4);
}
QByteArray tag(uint word)
{
QByteArray result;
result.resize(4);
result[0] = (word >> 24) & 0xFF;
result[1] = (word >> 16) & 0xFF;
result[2] = (word >> 8) & 0xFF;
result[3] = (word) & 0xFF;
return result;
}
bool copy(QByteArray & dst, uint to, const QByteArray & src, uint from, uint len)
{
if (!dst.data())
return false;
if (!src.data())
return false;
if (to + len > static_cast<uint>(dst.size()))
return false;
if (from + len > static_cast<uint>(src.size()))
return false;
memcpy(dst.data() + to, src.data() + from, len);
return true;
}
 
const uint post_format10_names_count = 258;
 
static const char* post_format10_names[] = {
".notdef",
".null",
"nonmarkingreturn",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quotesingle",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
"question",
"at",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"bracketleft",
"backslash",
"bracketright",
"asciicircum",
"underscore",
"grave",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"braceleft",
"bar",
"braceright",
"asciitilde",
"Adieresis",
"Aring",
"Ccedilla",
"Eacute",
"Ntilde",
"Odieresis",
"Udieresis",
"aacute",
"agrave",
"acircumflex",
"adieresis",
"atilde",
"aring",
"ccedilla",
"eacute",
"egrave",
"ecircumflex",
"edieresis",
"iacute",
"igrave",
"icircumflex",
"idieresis",
"ntilde",
"oacute",
"ograve",
"ocircumflex",
"odieresis",
"otilde",
"uacute",
"ugrave",
"ucircumflex",
"udieresis",
"dagger",
"degree",
"cent",
"sterling",
"section",
"bullet",
"paragraph",
"germandbls",
"registered",
"copyright",
"trademark",
"acute",
"dieresis",
"notequal",
"AE",
"Oslash",
"infinity",
"plusminus",
"lessequal",
"greaterequal",
"yen",
"mu",
"partialdiff",
"summation",
"product",
"pi",
"integral",
"ordfeminine",
"ordmasculine",
"Omega",
"ae",
"oslash",
"questiondown",
"exclamdown",
"logicalnot",
"radical",
"florin",
"approxequal",
"Delta",
"guillemotleft",
"guillemotright",
"ellipsis",
"nonbreakingspace",
"Agrave",
"Atilde",
"Otilde",
"OE",
"oe",
"endash",
"emdash",
"quotedblleft",
"quotedblright",
"quoteleft",
"quoteright",
"divide",
"lozenge",
"ydieresis",
"Ydieresis",
"fraction",
"currency",
"guilsinglleft",
"guilsinglright",
"fi",
"fl",
"daggerdbl",
"periodcentered",
"quotesinglbase",
"quotedblbase",
"perthousand",
"Acircumflex",
"Ecircumflex",
"Aacute",
"Edieresis",
"Egrave",
"Iacute",
"Icircumflex",
"Idieresis",
"Igrave",
"Oacute",
"Ocircumflex",
"apple",
"Ograve",
"Uacute",
"Ucircumflex",
"Ugrave",
"dotlessi",
"circumflex",
"tilde",
"macron",
"breve",
"dotaccent",
"ring",
"cedilla",
"hungarumlaut",
"ogonek",
"caron",
"Lslash",
"lslash",
"Scaron",
"scaron",
"Zcaron",
"zcaron",
"brokenbar",
"Eth",
"eth",
"Yacute",
"yacute",
"Thorn",
"thorn",
"minus",
"multiply",
"onesuperior",
"twosuperior",
"threesuperior",
"onehalf",
"onequarter",
"threequarters",
"franc",
"Gbreve",
"gbreve",
"Idotaccent",
"Scedilla",
"scedilla",
"Cacute",
"cacute",
"Ccaron",
"ccaron",
"dcroat"
};
 
bool PostTable::usable() const
{
return m_usable;
}
 
void PostTable::setUsable(bool usable)
{
m_usable = usable;
}
 
QString PostTable::errorMsg() const
{
return m_errorMsg;
}
 
void PostTable::setErrorMsg(const QString& errorMsg)
{
m_errorMsg = errorMsg;
}
 
uint PostTable::numberOfGlyphs() const
{
if (m_names.length() > 0)
return m_names.length();
return post_format10_names_count;
}
 
QString PostTable::nameFor(uint glyph) const
{
if (glyph < (uint) m_names.length())
return m_names[glyph];
if (glyph < sfnt::post_format10_names_count)
return post_format10_names[glyph];
return ".notdef";
}
 
void PostTable::readFrom(FT_Face face)
{
QByteArray postData;
FT_ULong size = 0;
int error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, nullptr, &size );
//qDebug() << "load post" << error << size;
if (error || size == 0)
{
m_errorMsg = "no post table";
m_usable = false;
return;
}
postData.resize(size);
error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, reinterpret_cast<FT_Byte*>(postData.data()), &size );
if (error)
{
m_errorMsg = "can't load post table";
m_usable = false;
return;
}
 
switch (sfnt::word(postData, ttf_post_format))
{
case sfnt::post_format10:
m_usable = true;
m_names.clear();
return;
case sfnt::post_format20:
break;
case sfnt::post_format30:
m_errorMsg = QString("post table has no glyph names");
m_usable = false;
return;
case sfnt::post_format25:
case sfnt::post_format40:
default:
m_errorMsg = QString("unsupported post format %1").arg(sfnt::word(postData,0));
m_usable = false;
return;
 
}
QMap<QString,uint> usedNames;
QList<QByteArray> pascalStrings;
 
uint nrOfGlyphs = sfnt::word16(postData, ttf_post_header_length);
uint stringPos = ttf_post_header_length + 2 + 2 * nrOfGlyphs;
while (stringPos < (uint) postData.length())
{
int strLen = byte(postData, stringPos);
++stringPos;
pascalStrings.append(postData.mid(stringPos, strLen));
stringPos += strLen;
}
uint pos = ttf_post_header_length + 2;
for (uint gid = 0; gid < nrOfGlyphs; ++gid)
{
uint nameIndex = sfnt::word16(postData, pos);
pos += 2;
QString name;
if (nameIndex < sfnt::post_format10_names_count)
name = sfnt::post_format10_names[nameIndex];
else if (nameIndex < pascalStrings.length() + sfnt::post_format10_names_count)
name = pascalStrings[nameIndex - sfnt::post_format10_names_count];
else {
m_usable = false;
m_errorMsg = QString("missing name %1 for glyph %2").arg(nameIndex).arg(gid);
return;
}
if (name != ".notdef" && name[0] != QChar(0) && usedNames.contains(name))
{
m_usable = false;
m_errorMsg = QString("duplicate name %1 used for glyphs %2 and %3").arg(name).arg(gid).arg(usedNames[name]);
return;
}
usedNames[name] = gid;
m_names.append(name);
}
m_errorMsg = "";
m_usable = true;
}
 
int copyTable(QByteArray& ttf, uint destDirEntry, uint pos, const QByteArray& source, uint dirEntry)
{
FT_ULong tag = word(source, dirEntry + ttf_TableRecord_tag);
uint checksum = word(source, dirEntry + ttf_TableRecord_checkSum);
uint tableStart = word(source, dirEntry + ttf_TableRecord_offset);
uint tableSize = word(source, dirEntry + ttf_TableRecord_length);
if (!copy(ttf, pos, source, tableStart, tableSize))
return -1;
putWord(ttf, destDirEntry + ttf_TableRecord_tag, tag);
putWord(ttf, destDirEntry + ttf_TableRecord_checkSum, checksum);
putWord(ttf, destDirEntry + ttf_TableRecord_offset, pos);
putWord(ttf, destDirEntry + ttf_TableRecord_length, tableSize);
return tableSize;
}
 
QByteArray extractFace(const QByteArray& coll, int faceIndex)
{
QByteArray result;
 
const int numFonts = word(coll, ttc_numFonts);
if (faceIndex >= static_cast<int>(numFonts))
{
return result;
}
 
uint faceOffset = sfnt::word(coll, ttc_OffsetTables + 4 * faceIndex);
uint nTables = sfnt::word16(coll, faceOffset + ttf_numtables);
 
//qDebug() << QObject::tr("extracting face %1 from font %2 (offset=%3, nTables=%4)").arg(faceIndex).arg("collection").arg(faceOffset).arg(nTables);
 
uint headerLength = ttf_TableRecords + ttf_TableRecord_Size * nTables;
 
uint tableLengths = 0;
// sum table lengths incl padding
for (uint i=0; i < nTables; ++i)
{
tableLengths += sfnt::word(coll, faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i + ttf_TableRecord_length);
tableLengths = (tableLengths+3) & ~3;
}
result.resize(headerLength + tableLengths);
if (!result.data())
{
result.resize(0);
return result;
}
 
// write header
// sDebug(QObject::tr("memcpy header: %1 %2 %3").arg(0).arg(faceOffset).arg(headerLength));
if (!copy(result, 0, coll, faceOffset, headerLength))
{
result.resize(0);
return result;
}
uint pos = headerLength;
for (uint i=0; i < nTables; ++i)
{
uint sourceDirEntry = faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i;
uint destDirEntry = ttf_TableRecords + ttf_TableRecord_Size * i;
 
int tableSize = copyTable(result, destDirEntry, pos, coll, sourceDirEntry);
if (tableSize < 0)
{
result.resize(0);
return result;
}
pos += tableSize;
 
// pad
while ((pos & 3) != 0)
result.data()[pos++] = '\0';
}
return result;
}
 
uint getTableDirEntry(const QByteArray& ttf, const QByteArray& ttfTag)
{
uint nTables = word16(ttf, ttf_numtables);
uint pos = ttf_TableRecords;
for (uint i=0; i < nTables; ++i)
{
if (ttfTag == tag(word(ttf, pos + ttf_TableRecord_tag)))
{
return pos;
}
pos += ttf_TableRecord_Size;
}
return 0;
}
 
QByteArray getTable(const QByteArray& ttf, const QByteArray& ttfTag)
{
uint pos = getTableDirEntry(ttf, ttfTag);
if (pos <= 0)
return QByteArray();
 
uint offset = word(ttf, pos + ttf_TableRecord_offset);
uint length = word(ttf, pos + ttf_TableRecord_length);
return QByteArray::fromRawData(ttf.constData() + offset, length);
}
 
QByteArray createTableDir(const QList<QByteArray>& tags)
{
QByteArray result;
uint numTables = tags.length();
uint tableRecordsSize = numTables * ttf_TableRecord_Size;
result.resize(ttf_TableRecords + tableRecordsSize);
uint entrySelector = 0;
uint searchRange = ttf_TableRecord_Size;
while (2*searchRange < tableRecordsSize)
{
searchRange *= 2;
++entrySelector;
}
uint rangeShift = tableRecordsSize - searchRange;
putWord(result, ttf_sfnt_version, 0x00010000);
putWord16(result, ttf_numtables, numTables);
putWord16(result, ttf_searchRange, searchRange);
putWord16(result, ttf_entrySelector, entrySelector);
putWord16(result, ttf_rangeShift, rangeShift);
for (uint i = 0; i < numTables; ++i)
{
copy(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_tag, tags[i], 0, 4);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_checkSum, 0);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_offset, 0);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size +ttf_TableRecord_length, 0);
}
return result;
}
 
quint32 calcTableChecksum(QByteArray& table)
{
quint32 Sum = 0L;
for (int pos = 0; pos < table.length(); pos += 4)
Sum += word(table, pos);
return Sum;
}
 
void writeTable(QByteArray& ttf, const QByteArray& tag, QByteArray& table)
{
//qDebug() << "writing table" << tag << table.size() << "@" << ttf.size();
uint length = table.size();
while (table.size() & 0x3)
table.append('\0');
uint offset = ttf.size();
uint checksum = calcTableChecksum(table);
uint pos = getTableDirEntry(ttf, tag);
putWord(ttf, pos + ttf_TableRecord_checkSum, checksum);
putWord(ttf, pos + ttf_TableRecord_offset, offset);
putWord(ttf, pos + ttf_TableRecord_length, length);
ttf.append(table);
}
 
bool hasLongLocaFormat(const QByteArray& ttf)
{
const QByteArray head = getTable(ttf, "head");
uint idxToLocFormat = word16(head, ttf_head_indexToLocFormat);
// qDebug() << "loca format:" << (void*)idxToLocFormat;
return idxToLocFormat == 1;
}
 
QList<quint32> readLoca(const QByteArray& ttf)
{
QList<quint32> result;
const QByteArray loca = getTable(ttf, "loca");
result.reserve(loca.length());
if (hasLongLocaFormat(ttf))
{
for (int i = 0; i < loca.length(); i+=4)
result.append(word(loca, i));
}
else
{
for (int i = 0; i < loca.length(); i+=2)
result.append(word16(loca, i) * 2);
}
return result;
}
 
QByteArray writeLoca(const QList<uint>& loca, bool longFormat)
{
QByteArray result;
if (longFormat)
{
for (int i=0; i < loca.length(); ++i)
appendWord(result, loca[i]);
}
else
{
for (int i=0; i < loca.length(); ++i)
appendWord16(result, loca[i] / 2);
}
return result;
}
 
QList<std::pair<qint16,quint16> > readHmtx(const QByteArray& ttf)
{
QList<std::pair<qint16,quint16> > result;
const QByteArray hhea = getTable(ttf, "hhea");
const QByteArray hmtx = getTable(ttf, "hmtx");
uint endOfLongHorMetrics = 4 * word16(hhea, ttf_hhea_numOfLongHorMetrics);
qint16 advance;
quint16 leftSideBearing;
uint pos = 0;
while (pos < endOfLongHorMetrics)
{
advance = word16(hmtx, pos);
leftSideBearing = word16(hmtx, pos+2);
//qDebug() << pos << "hmtx" << advance << leftSideBearing;
result.append(std::pair<qint16,quint16>(advance, leftSideBearing));
pos += 4;
}
while (pos < (uint) hmtx.length())
{
leftSideBearing = word16(hmtx, pos);
//qDebug() << pos << "hmtx =" << advance << leftSideBearing;
result.append(std::pair<qint16,quint16>(advance, leftSideBearing));
pos += 2;
}
return result;
}
 
QByteArray writeHmtx(const QList<std::pair<qint16,quint16> >& longHorMetrics)
{
QByteArray result;
QList<std::pair<qint16,quint16> >::const_iterator it;
//int i = 0;
for (it = longHorMetrics.cbegin(); it < longHorMetrics.cend(); ++it)
{
//qDebug() << "hmtx" << i++ << it->first << it->second;
appendWord16(result, it->first);
appendWord16(result, it->second);
}
return result;
}
 
QMap<uint, uint> readCMap(const QByteArray& ttf)
{
QMap<uint,uint> result;
const QByteArray cmaps = getTable(ttf, "cmap");
uint numSubtables = word16(cmaps, ttf_cmap_numberSubtables);
uint startOfUnicodeTable = 0;
uint startOfSymbolTable = 0;
uint unicodeFormat = 0, symbolFormat = 0;
uint pos = ttf_cmap_encodings;
for (uint i = 0; i < numSubtables; ++i)
{
uint platform = word16(cmaps, pos + ttf_cmap_encoding_platformID);
uint encoding = word16(cmaps, pos + ttf_cmap_encoding_platformSpecificID);
uint offset = word(cmaps, pos + ttf_cmap_encoding_offset);
uint format = word16(cmaps, offset + ttf_cmapx_format);
pos += ttf_cmap_encoding_Size;
if (format < 4 || format > 12)
continue;
if (platform == 0 || (platform == 3 && encoding == 1))
{
startOfUnicodeTable = offset;
unicodeFormat = format;
break;
}
if (platform == 3 && encoding == 0) // MS Symbol cmap
{
startOfSymbolTable = offset;
symbolFormat = format;
continue;
}
format = 1; // no such format
}
// If no unicode cmap was found, fallback on ms symbol cmap
uint format = unicodeFormat;
if ((unicodeFormat <= 0) && (symbolFormat > 0))
{
startOfUnicodeTable = startOfSymbolTable;
format = symbolFormat;
}
//qDebug() << "reading cmap format" << format;
switch (format)
{
case 4:
{
uint segCount2 = word16(cmaps, startOfUnicodeTable + ttf_cmap4_segCountX2);
uint endCodes = startOfUnicodeTable + ttf_cmap4_EndCodes;
uint startCodes = endCodes + segCount2 + ttf_cmap4_StartCodes_EndCodes;
uint idDeltas = startCodes + segCount2 + ttf_cmap4_IdDeltas_StartCodes;
uint idRangeOffsets = idDeltas + segCount2 + ttf_cmap4_IdRangeOffsets_IdDeltas;
//uint glyphIndexArray = idRangeOffsets + segCount2 + ttf_cmap4_GlyphIds_IdRangeOffsets;
 
for (uint seg = 0; seg < segCount2; seg+=2)
{
uint start = word16(cmaps, startCodes + seg);
uint end = word16(cmaps, endCodes + seg);
uint idDelta = word16(cmaps, idDeltas + seg);
uint idRangeOffset = word16(cmaps, idRangeOffsets + seg);
for (uint c = start; c <= end; ++c)
{
quint16 glyph;
if (idRangeOffset > 0)
{
uint glyphIndexAdress = idRangeOffset + 2*(c-start) + (idRangeOffsets + seg);
glyph = word16(cmaps, glyphIndexAdress);
if (glyph != 0)
glyph += idDelta;
}
else
{
glyph = c + idDelta;
}
if (!result.contains(c))
{
// search would always find the one in the segment with the lower endcode, i.e. earlier segment
if (c < 256 || glyph == 0)
//qDebug() << "(" << QChar(c) << "," << glyph << ")";
result[c] = glyph;
}
else
{
// nothing to do. No idea if fonts with overlapping cmap4 segments exist, though.
}
}
}
break;
}
case 6:
{
uint firstCode = word16(cmaps, startOfUnicodeTable + ttf_cmap6_firstCode);
uint count = word16(cmaps, startOfUnicodeTable + ttf_cmap6_entryCount);
pos = word16(cmaps, startOfUnicodeTable + ttf_cmap6_glyphIndexArray);
for (uint i = 0; i < count; ++i)
{
result[firstCode + i] = word16(cmaps, pos);
pos += 2;
}
break;
}
case 12:
{
uint nGroups = word(cmaps, startOfUnicodeTable + ttf_cmap12_nGroups);
pos = startOfUnicodeTable + ttf_cmap12_Groups;
for (uint grp = 0; grp < nGroups; ++grp)
{
uint start = word(cmaps, pos + ttf_cmap12_Group_startCharCode);
uint end = word(cmaps, pos + ttf_cmap12_Group_endCharCode);
uint gid = word(cmaps, pos + ttf_cmap12_Group_startGlyphCode);
for (uint c = start; c <= end; ++c)
{
result[c] = gid;
++gid;
}
pos += ttf_cmap12_Group_Size;
}
break;
}
default:
{
qDebug() << "unsupported cmap format" << format;
break;
}
}
return result;
}
 
QByteArray writeCMap(const QMap<uint, uint>& cmap)
{
// we always write only one table: platform=3(MS), encoding=1(Unicode 16bit)
QByteArray result;
appendWord16(result, 0); // version
appendWord16(result, 1); // number of subtables
appendWord16(result, 3); // platformID Microsoft
appendWord16(result, 1); // encodingID UnicodeBMP
appendWord(result, result.size() + 4); // offset
// find the segments
QList<uint> chars;
QMap<uint, uint>::ConstIterator cit;
//qDebug() << "writing cmap";
bool cmapHasData = false;
for (cit = cmap.cbegin(); cit != cmap.cend(); ++cit)
{
uint ch = cit.key();
if (!QChar::requiresSurrogates(ch) && cit.value() != 0)
{
//qDebug() << "(" << QChar(cit.key()) << "," << cit.value() << ")";
chars.append(ch);
cmapHasData = true;
}
// qDebug() << QChar(ch) << QChar::requiresSurrogates(ch) << cit.value();
}
std::sort(chars.begin(), chars.end());
QList<quint16> startCodes;
QList<quint16> endCodes;
QList<quint16> idDeltas;
QList<quint16> rangeOffsets;
if (cmapHasData)
{
uint pos = 0;
do {
quint16 start = chars[pos];
quint16 delta = cmap[start] - start;
quint16 rangeOffset = 0;
quint16 end = start;
quint16 next;
++pos;
while (pos < (uint) chars.length() && (next = chars[pos]) == end+1)
{
end = next;
if (delta != (quint16)(cmap[chars[pos]] - next))
{
rangeOffset = 1; // will be changed later
}
++pos;
}
startCodes.append(start);
endCodes.append(end);
idDeltas.append(delta);
rangeOffsets.append(rangeOffset);
}
while (pos < (uint) chars.length());
}
startCodes.append(0xFFFF);
endCodes.append(0xFFFF);
idDeltas.append(1); // makes gid 0
rangeOffsets.append(0);
// write the tables
uint startOfTable = result.size();
result.resize(startOfTable + ttf_cmap4_EndCodes);
 
uint segCount = endCodes.length();
uint segCountX2 = 2 * segCount;
uint entrySelector = 0;
uint searchRange = 2;
while (searchRange <= segCount)
{
++entrySelector;
searchRange *= 2;
}
putWord16(result, startOfTable + ttf_cmapx_format, 4);
/* ttf_cmap4_length is set later */
putWord16(result, startOfTable + ttf_cmap4_language, 0);
putWord16(result, startOfTable + ttf_cmap4_segCountX2, segCountX2);
putWord16(result, startOfTable + ttf_cmap4_searchRange, searchRange);
putWord16(result, startOfTable + ttf_cmap4_entrySelector, entrySelector);
putWord16(result, startOfTable + ttf_cmap4_rangeShift, segCountX2 - searchRange);
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, endCodes[i]);
};
appendWord16(result, 0); // reservedPad
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, startCodes[i]);
};
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, idDeltas[i]);
};
uint startOfIdRangeOffsetTable = result.size();
uint startOfGlyphIndexArray = startOfIdRangeOffsetTable + segCountX2;
result.resize(startOfGlyphIndexArray);
for (uint i = 0; i < segCount; ++i)
{
uint idRangeOffsetAddress = startOfIdRangeOffsetTable + 2*i;
if (rangeOffsets[i] == 0)
{
//quint16 dbg = startCodes[i] + idDeltas[i];
//qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "+" << idDeltas[i] << "-->" << dbg;
putWord16(result, idRangeOffsetAddress, 0);
}
else
{
quint16 idRangeOffset = result.size() - idRangeOffsetAddress;
putWord16(result, idRangeOffsetAddress, idRangeOffset);
 
//qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "@" << idRangeOffset << "+" << idDeltas[i];
 
uint startCode = startCodes[i];
uint segLength = (endCodes[i]-startCode+1);
for (uint offset = 0; offset < segLength; ++offset)
{
quint16 glyph = cmap[startCode + offset];
if (glyph != 0)
{
glyph -= idDeltas[i];
}
appendWord16(result, glyph);
}
}
};
putWord16(result, startOfTable + ttf_cmap4_length, result.size() - startOfTable);
return result;
}
QList<uint> copyGlyphComponents(QByteArray& destGlyf, const QByteArray& srcGlyf, uint srcOffset,
QMap<uint,uint>& newForOldGid, uint& nextFreeGid)
{
QList<uint> result;
uint destStart = destGlyf.size();
destGlyf.resize(destStart + ttf_glyf_headerSize);
copy(destGlyf, destStart, srcGlyf, srcOffset, ttf_glyf_headerSize);
uint pos = srcOffset + ttf_glyf_headerSize;
bool haveInstructions = false;
uint flags = 0;
do {
/* flags */
flags = word16(srcGlyf, pos);
pos += 2;
haveInstructions |= ((flags & ttf_glyf_ComponentFlag_WE_HAVE_INSTRUCTIONS) != 0);
appendWord16(destGlyf, flags);
/* glyphindex */
uint glyphIndex = word16(srcGlyf, pos);
pos += 2;
if (!newForOldGid.contains(glyphIndex))
{
result.append(glyphIndex);
newForOldGid[glyphIndex] = nextFreeGid++;
}
glyphIndex = newForOldGid[glyphIndex];
appendWord16(destGlyf, glyphIndex);
/* args */
if ( flags & ttf_glyf_ComponentFlag_ARG_1_AND_2_ARE_WORDS) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg2
pos += 2;
}
else {
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1and2
pos += 2;
}
if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_SCALE ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale
pos += 2;
} else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_AN_X_AND_Y_SCALE ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale
pos += 2;
} else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_TWO_BY_TWO ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale01
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale10
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale
pos += 2;
}
} while ( flags & ttf_glyf_ComponentFlag_MORE_COMPONENTS );
 
if (haveInstructions)
{
uint numInstr = word16(srcGlyf, pos);
appendWord16(destGlyf, numInstr);
pos += 2;
uint destPos = destGlyf.size();
destGlyf.resize(destPos + numInstr);
copy(destGlyf, destPos, srcGlyf, pos, numInstr);
}
return result;
}
 
QList<uint> copyGlyph(QList<uint>& destLoca, QByteArray& destGlyf, uint destGid,
const QList<uint>& srcLoca, const QByteArray& srcGlyf, uint srcGid,
QMap<uint,uint>& newForOldGid, uint& nextFreeGid)
{
QList<uint> compositeElements;
uint glyphStart = srcLoca[srcGid];
uint glyphLength = srcLoca[srcGid+1] - glyphStart;
destLoca.append(destGlyf.size());
//int i = 0;
if (glyphLength > 0)
{
quint16 nrOfContours = word16(srcGlyf, glyphStart);
if (nrOfContours <= ttf_glyf_Max_numberOfContours)
{
// simple glyph
uint destStart = destGlyf.size();
//qDebug() << i++ << ":" << nrOfContours << "contours" << glyphStart << "-->" << destStart << "/" << glyphLength;
destGlyf.resize(destStart + glyphLength);
copy(destGlyf, destStart, srcGlyf, glyphStart, glyphLength);
}
else
{
compositeElements.append(copyGlyphComponents(destGlyf, srcGlyf, glyphStart, newForOldGid, nextFreeGid));
//qDebug() << i++ << ":" << srcGid << "composite glyph brought" << compositeElements.size() << "more glyphs";
}
// Data must be aligned at least on 2 byte boundary, however
// a 4-byte alignment is recommended by TTF specs for reasons
// related to CPU and efficiency
int targetSize = (destGlyf.size() + 3) & ~3;
if (destGlyf.size() < targetSize)
{
destGlyf.reserve(targetSize);
while (destGlyf.size() < targetSize)
destGlyf.append('\0');
}
}
 
return compositeElements;
}
QByteArray subsetFace(const QByteArray& ttf, QList<uint>& glyphs, QMap<uint, uint>& glyphMap)
{
QMap<QByteArray,QByteArray> tables;
// qDebug() << "loca table:" << (void*) oldLoca[0] << (void*) oldLoca[1] << (void*) oldLoca[2] << (void*) oldLoca[3] << (void*) oldLoca[4] << (void*) oldLoca[5] << (void*) oldLoca[6] << (void*) oldLoca[7];
 
QMap<uint, uint> newForOldGid;
if (glyphs.length() == 0)
{
tables["loca"] = getTable(ttf, "loca");
tables["glyf"] = getTable(ttf, "glyf");
}
else
{
QList<uint> oldLoca = readLoca(ttf);
const QByteArray oldGlyf = getTable(ttf, "glyf");
 
QList<quint32> newLoca;
QByteArray newGlyf;
glyphs.removeAll(0);
glyphs.prepend(0);
 
for (int i = 0; i < glyphs.length(); ++i)
{
uint oldGid = glyphs[i];
newForOldGid[oldGid] = i;
}
uint nextFreeGid = glyphs.length();
for (int i = 0; i < glyphs.length(); ++i)
{
uint oldGid = glyphs[i];
glyphs.append(copyGlyph(newLoca, newGlyf, i,
oldLoca, oldGlyf, oldGid,
newForOldGid, nextFreeGid));
}
newLoca.append(newGlyf.length());
tables["loca"] = writeLoca(newLoca, hasLongLocaFormat(ttf));
tables["glyf"] = newGlyf;
}
QMap<uint,uint> cmap = readCMap(ttf);
QMap<uint,uint>::iterator it;
uint firstChar = 0xFFFFFFFF;
uint lastChar = 0;
for (it = cmap.begin(); it != cmap.end(); ++it)
{
if (glyphs.length() > 0 && !glyphs.contains(it.value()))
{
it.value() = 0;
}
else if (it.value() != 0)
{
if (glyphs.length() > 0)
{
//qDebug() << "MAP" << QChar(it.key()) << it.value() << "-->" << newForOldGid[it.value()];
it.value() = newForOldGid[it.value()];
}
if (it.key() < firstChar)
firstChar = it.key();
else if (it.key() > lastChar)
lastChar = it.key();
}
}
tables["cmap"] = writeCMap(cmap);
QByteArray os2 = getTable(ttf, "OS/2");
if (os2.length() > ttf_os2_usLastCharIndex)
{
// TODO: adapt unicode ranges
putWord16(os2, ttf_os2_usFirstCharIndex, firstChar < 0xFFFF ? firstChar : 0xFFFF);
putWord16(os2, ttf_os2_usLastCharIndex, lastChar < 0xFFFF ? lastChar : 0xFFFF);
tables["OS/2"] = os2;
}
if (glyphs.length() > 0)
{
QList<std::pair<qint16, quint16> > oldHmtx = readHmtx(ttf);
QList<std::pair<qint16, quint16> > newHmtx;
newHmtx.reserve(glyphs.length() + 1);
newHmtx.append(std::pair<qint16, quint16>(1234, 123));
for (int i = 1; i < glyphs.length(); ++i)
newHmtx.append(newHmtx[0]);
QMap<uint,uint>::const_iterator iter;
for (iter = newForOldGid.cbegin(); iter != newForOldGid.cend(); ++iter)
{
//qDebug() << "hmtx" << iter.key() << " -> " << iter.value() << "=" << oldHmtx[iter.key()].first;
newHmtx[iter.value()] = oldHmtx[iter.key()];
}
tables["hmtx"] = writeHmtx(newHmtx);
}
else
{
tables["hmtx"] = getTable(ttf, "hmtx");
}
QByteArray maxp = getTable(ttf, "maxp");
if (glyphs.length() > 0)
{
putWord16(maxp, ttf_maxp_numGlyphs, glyphs.length());
}
tables["maxp"] = maxp;
QByteArray hhea = getTable(ttf, "hhea");
if (glyphs.length() > 0)
{
putWord16(hhea, ttf_hhea_numOfLongHorMetrics, glyphs.length());
}
tables["hhea"] = hhea;
QByteArray post = getTable(ttf, "post");
if (word(post, ttf_post_format) != post_format30)
{
putWord(post, ttf_post_format, post_format30);
post.truncate(ttf_post_header_length);
}
tables["post"] = post;
// TODO: kern table
QByteArray name = getTable(ttf, "name");
if (name.length() > 0)
tables["name"] = name;
QByteArray prep = getTable(ttf, "prep");
if (prep.length() > 0)
tables["prep"] = prep;
QByteArray cvt = getTable(ttf, "cvt ");
if (cvt.length() > 0)
tables["cvt "] = cvt;
 
QByteArray fpgm = getTable(ttf, "fpgm");
if (fpgm.length() > 0)
tables["fpgm"] = fpgm;
 
QByteArray head = getTable(ttf, "head");
putWord(head, ttf_head_checkSumAdjustment, 0);
tables["head"] = head;
 
QByteArray font = createTableDir(tables.keys());
QMap<QByteArray,QByteArray>::iterator tableP;
for (tableP = tables.begin(); tableP != tables.end(); ++tableP)
{
writeTable(font, tableP.key(), tableP.value());
}
uint checkSumAdjustment = 0xB1B0AFBA - calcTableChecksum(font);
uint headTable = getTableDirEntry(font, "head");
headTable = word(font, headTable + ttf_TableRecord_offset);
putWord(font, headTable + ttf_head_checkSumAdjustment, checkSumAdjustment);
};
 
namespace sfnt {
uchar byte(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0];
}
 
quint32 word(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0] << 24 | pp[1] << 16 | pp[2] << 8 | pp[3];
}
void putWord(QByteArray & bb, uint pos, quint32 val)
{
unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos;
*pp++ = (val >> 24) & 0xFF;
*pp++ = (val >> 16) & 0xFF;
*pp++ = (val >> 8) & 0xFF;
*pp++ = (val) & 0xFF;
}
void appendWord(QByteArray & bb, quint32 val)
{
uint pos = bb.size();
bb.resize(pos + 4);
putWord(bb, pos, val);
}
quint16 word16(const QByteArray & bb, uint pos)
{
const unsigned char * pp = reinterpret_cast<const unsigned char*>(bb.data()) + pos;
return pp[0] << 8 | pp[1];
}
void putWord16(QByteArray & bb, uint pos, quint16 val)
{
unsigned char * pp = reinterpret_cast<unsigned char*>(bb.data()) + pos;
*pp++ = (val >> 8) & 0xFF;
*pp++ = (val) & 0xFF;
}
void appendWord16(QByteArray & bb, quint16 val)
{
uint pos = bb.size();
bb.resize(pos + 2);
putWord16(bb, pos, val);
}
QByteArray tag(const QByteArray& bb, uint pos)
{
return QByteArray::fromRawData(bb.constData() + pos, 4);
}
QByteArray tag(uint word)
{
QByteArray result;
result.resize(4);
result[0] = (word >> 24) & 0xFF;
result[1] = (word >> 16) & 0xFF;
result[2] = (word >> 8) & 0xFF;
result[3] = (word) & 0xFF;
return result;
}
bool copy(QByteArray & dst, uint to, const QByteArray & src, uint from, uint len)
{
if (!dst.data())
return false;
if (!src.data())
return false;
if (to + len > static_cast<uint>(dst.size()))
return false;
if (from + len > static_cast<uint>(src.size()))
return false;
memcpy(dst.data() + to, src.data() + from, len);
return true;
}
 
const uint post_format10_names_count = 258;
 
static const char* post_format10_names[] = {
".notdef",
".null",
"nonmarkingreturn",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quotesingle",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
"question",
"at",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"bracketleft",
"backslash",
"bracketright",
"asciicircum",
"underscore",
"grave",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"braceleft",
"bar",
"braceright",
"asciitilde",
"Adieresis",
"Aring",
"Ccedilla",
"Eacute",
"Ntilde",
"Odieresis",
"Udieresis",
"aacute",
"agrave",
"acircumflex",
"adieresis",
"atilde",
"aring",
"ccedilla",
"eacute",
"egrave",
"ecircumflex",
"edieresis",
"iacute",
"igrave",
"icircumflex",
"idieresis",
"ntilde",
"oacute",
"ograve",
"ocircumflex",
"odieresis",
"otilde",
"uacute",
"ugrave",
"ucircumflex",
"udieresis",
"dagger",
"degree",
"cent",
"sterling",
"section",
"bullet",
"paragraph",
"germandbls",
"registered",
"copyright",
"trademark",
"acute",
"dieresis",
"notequal",
"AE",
"Oslash",
"infinity",
"plusminus",
"lessequal",
"greaterequal",
"yen",
"mu",
"partialdiff",
"summation",
"product",
"pi",
"integral",
"ordfeminine",
"ordmasculine",
"Omega",
"ae",
"oslash",
"questiondown",
"exclamdown",
"logicalnot",
"radical",
"florin",
"approxequal",
"Delta",
"guillemotleft",
"guillemotright",
"ellipsis",
"nonbreakingspace",
"Agrave",
"Atilde",
"Otilde",
"OE",
"oe",
"endash",
"emdash",
"quotedblleft",
"quotedblright",
"quoteleft",
"quoteright",
"divide",
"lozenge",
"ydieresis",
"Ydieresis",
"fraction",
"currency",
"guilsinglleft",
"guilsinglright",
"fi",
"fl",
"daggerdbl",
"periodcentered",
"quotesinglbase",
"quotedblbase",
"perthousand",
"Acircumflex",
"Ecircumflex",
"Aacute",
"Edieresis",
"Egrave",
"Iacute",
"Icircumflex",
"Idieresis",
"Igrave",
"Oacute",
"Ocircumflex",
"apple",
"Ograve",
"Uacute",
"Ucircumflex",
"Ugrave",
"dotlessi",
"circumflex",
"tilde",
"macron",
"breve",
"dotaccent",
"ring",
"cedilla",
"hungarumlaut",
"ogonek",
"caron",
"Lslash",
"lslash",
"Scaron",
"scaron",
"Zcaron",
"zcaron",
"brokenbar",
"Eth",
"eth",
"Yacute",
"yacute",
"Thorn",
"thorn",
"minus",
"multiply",
"onesuperior",
"twosuperior",
"threesuperior",
"onehalf",
"onequarter",
"threequarters",
"franc",
"Gbreve",
"gbreve",
"Idotaccent",
"Scedilla",
"scedilla",
"Cacute",
"cacute",
"Ccaron",
"ccaron",
"dcroat"
};
 
bool PostTable::usable() const
{
return m_usable;
}
 
void PostTable::setUsable(bool usable)
{
m_usable = usable;
}
 
QString PostTable::errorMsg() const
{
return m_errorMsg;
}
 
void PostTable::setErrorMsg(const QString& errorMsg)
{
m_errorMsg = errorMsg;
}
 
uint PostTable::numberOfGlyphs() const
{
if (m_names.length() > 0)
return m_names.length();
return post_format10_names_count;
}
 
QString PostTable::nameFor(uint glyph) const
{
if (glyph < (uint) m_names.length())
return m_names[glyph];
if (glyph < sfnt::post_format10_names_count)
return post_format10_names[glyph];
return ".notdef";
}
 
void PostTable::readFrom(FT_Face face)
{
QByteArray postData;
FT_ULong size = 0;
int error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, nullptr, &size );
//qDebug() << "load post" << error << size;
if (error || size == 0)
{
m_errorMsg = "no post table";
m_usable = false;
return;
}
postData.resize(size);
error = FT_Load_Sfnt_Table ( face, TTAG_post , 0, reinterpret_cast<FT_Byte*>(postData.data()), &size );
if (error)
{
m_errorMsg = "can't load post table";
m_usable = false;
return;
}
 
switch (sfnt::word(postData, ttf_post_format))
{
case sfnt::post_format10:
m_usable = true;
m_names.clear();
return;
case sfnt::post_format20:
break;
case sfnt::post_format30:
m_errorMsg = QString("post table has no glyph names");
m_usable = false;
return;
case sfnt::post_format25:
case sfnt::post_format40:
default:
m_errorMsg = QString("unsupported post format %1").arg(sfnt::word(postData,0));
m_usable = false;
return;
 
}
QMap<QString,uint> usedNames;
QList<QByteArray> pascalStrings;
 
uint nrOfGlyphs = sfnt::word16(postData, ttf_post_header_length);
uint stringPos = ttf_post_header_length + 2 + 2 * nrOfGlyphs;
while (stringPos < (uint) postData.length())
{
int strLen = byte(postData, stringPos);
++stringPos;
pascalStrings.append(postData.mid(stringPos, strLen));
stringPos += strLen;
}
uint pos = ttf_post_header_length + 2;
for (uint gid = 0; gid < nrOfGlyphs; ++gid)
{
uint nameIndex = sfnt::word16(postData, pos);
pos += 2;
QString name;
if (nameIndex < sfnt::post_format10_names_count)
name = sfnt::post_format10_names[nameIndex];
else if (nameIndex < pascalStrings.length() + sfnt::post_format10_names_count)
name = pascalStrings[nameIndex - sfnt::post_format10_names_count];
else {
m_usable = false;
m_errorMsg = QString("missing name %1 for glyph %2").arg(nameIndex).arg(gid);
return;
}
if (name != ".notdef" && name[0] != QChar(0) && usedNames.contains(name))
{
m_usable = false;
m_errorMsg = QString("duplicate name %1 used for glyphs %2 and %3").arg(name).arg(gid).arg(usedNames[name]);
return;
}
usedNames[name] = gid;
m_names.append(name);
}
m_errorMsg = "";
m_usable = true;
}
 
int copyTable(QByteArray& ttf, uint destDirEntry, uint pos, const QByteArray& source, uint dirEntry)
{
FT_ULong tag = word(source, dirEntry + ttf_TableRecord_tag);
uint checksum = word(source, dirEntry + ttf_TableRecord_checkSum);
uint tableStart = word(source, dirEntry + ttf_TableRecord_offset);
uint tableSize = word(source, dirEntry + ttf_TableRecord_length);
if (!copy(ttf, pos, source, tableStart, tableSize))
return -1;
putWord(ttf, destDirEntry + ttf_TableRecord_tag, tag);
putWord(ttf, destDirEntry + ttf_TableRecord_checkSum, checksum);
putWord(ttf, destDirEntry + ttf_TableRecord_offset, pos);
putWord(ttf, destDirEntry + ttf_TableRecord_length, tableSize);
return tableSize;
}
 
QByteArray extractFace(const QByteArray& coll, int faceIndex)
{
QByteArray result;
 
const int numFonts = word(coll, ttc_numFonts);
if (faceIndex >= static_cast<int>(numFonts))
{
return result;
}
 
uint faceOffset = sfnt::word(coll, ttc_OffsetTables + 4 * faceIndex);
uint nTables = sfnt::word16(coll, faceOffset + ttf_numtables);
 
//qDebug() << QObject::tr("extracting face %1 from font %2 (offset=%3, nTables=%4)").arg(faceIndex).arg("collection").arg(faceOffset).arg(nTables);
 
uint headerLength = ttf_TableRecords + ttf_TableRecord_Size * nTables;
 
uint tableLengths = 0;
// sum table lengths incl padding
for (uint i=0; i < nTables; ++i)
{
tableLengths += sfnt::word(coll, faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i + ttf_TableRecord_length);
tableLengths = (tableLengths+3) & ~3;
}
result.resize(headerLength + tableLengths);
if (!result.data())
{
result.resize(0);
return result;
}
 
// write header
// sDebug(QObject::tr("memcpy header: %1 %2 %3").arg(0).arg(faceOffset).arg(headerLength));
if (!copy(result, 0, coll, faceOffset, headerLength))
{
result.resize(0);
return result;
}
uint pos = headerLength;
for (uint i=0; i < nTables; ++i)
{
uint sourceDirEntry = faceOffset + ttf_TableRecords + ttf_TableRecord_Size * i;
uint destDirEntry = ttf_TableRecords + ttf_TableRecord_Size * i;
 
int tableSize = copyTable(result, destDirEntry, pos, coll, sourceDirEntry);
if (tableSize < 0)
{
result.resize(0);
return result;
}
pos += tableSize;
 
// pad
while ((pos & 3) != 0)
result.data()[pos++] = '\0';
}
return result;
}
 
uint getTableDirEntry(const QByteArray& ttf, const QByteArray& ttfTag)
{
uint nTables = word16(ttf, ttf_numtables);
uint pos = ttf_TableRecords;
for (uint i=0; i < nTables; ++i)
{
if (ttfTag == tag(word(ttf, pos + ttf_TableRecord_tag)))
{
return pos;
}
pos += ttf_TableRecord_Size;
}
return 0;
}
 
QByteArray getTable(const QByteArray& ttf, const QByteArray& ttfTag)
{
uint pos = getTableDirEntry(ttf, ttfTag);
if (pos <= 0)
return QByteArray();
 
uint offset = word(ttf, pos + ttf_TableRecord_offset);
uint length = word(ttf, pos + ttf_TableRecord_length);
return QByteArray::fromRawData(ttf.constData() + offset, length);
}
 
QByteArray createTableDir(const QList<QByteArray>& tags)
{
QByteArray result;
uint numTables = tags.length();
uint tableRecordsSize = numTables * ttf_TableRecord_Size;
result.resize(ttf_TableRecords + tableRecordsSize);
uint entrySelector = 0;
uint searchRange = ttf_TableRecord_Size;
while (2*searchRange < tableRecordsSize)
{
searchRange *= 2;
++entrySelector;
}
uint rangeShift = tableRecordsSize - searchRange;
putWord(result, ttf_sfnt_version, 0x00010000);
putWord16(result, ttf_numtables, numTables);
putWord16(result, ttf_searchRange, searchRange);
putWord16(result, ttf_entrySelector, entrySelector);
putWord16(result, ttf_rangeShift, rangeShift);
for (uint i = 0; i < numTables; ++i)
{
copy(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_tag, tags[i], 0, 4);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_checkSum, 0);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size + ttf_TableRecord_offset, 0);
putWord(result, ttf_TableRecords + i*ttf_TableRecord_Size +ttf_TableRecord_length, 0);
}
return result;
}
 
quint32 calcTableChecksum(QByteArray& table)
{
quint32 Sum = 0L;
for (int pos = 0; pos < table.length(); pos += 4)
Sum += word(table, pos);
return Sum;
}
 
void writeTable(QByteArray& ttf, const QByteArray& tag, QByteArray& table)
{
//qDebug() << "writing table" << tag << table.size() << "@" << ttf.size();
uint length = table.size();
while (table.size() & 0x3)
table.append('\0');
uint offset = ttf.size();
uint checksum = calcTableChecksum(table);
uint pos = getTableDirEntry(ttf, tag);
putWord(ttf, pos + ttf_TableRecord_checkSum, checksum);
putWord(ttf, pos + ttf_TableRecord_offset, offset);
putWord(ttf, pos + ttf_TableRecord_length, length);
ttf.append(table);
}
 
bool hasLongLocaFormat(const QByteArray& ttf)
{
const QByteArray head = getTable(ttf, "head");
uint idxToLocFormat = word16(head, ttf_head_indexToLocFormat);
// qDebug() << "loca format:" << (void*)idxToLocFormat;
return idxToLocFormat == 1;
}
 
QList<quint32> readLoca(const QByteArray& ttf)
{
QList<quint32> result;
const QByteArray loca = getTable(ttf, "loca");
result.reserve(loca.length());
if (hasLongLocaFormat(ttf))
{
for (int i = 0; i < loca.length(); i+=4)
result.append(word(loca, i));
}
else
{
for (int i = 0; i < loca.length(); i+=2)
result.append(word16(loca, i) * 2);
}
return result;
}
 
QByteArray writeLoca(const QList<uint>& loca, bool longFormat)
{
QByteArray result;
if (longFormat)
{
for (int i=0; i < loca.length(); ++i)
appendWord(result, loca[i]);
}
else
{
for (int i=0; i < loca.length(); ++i)
appendWord16(result, loca[i] / 2);
}
return result;
}
 
QList<std::pair<qint16,quint16> > readHmtx(const QByteArray& ttf)
{
QList<std::pair<qint16,quint16> > result;
const QByteArray hhea = getTable(ttf, "hhea");
const QByteArray hmtx = getTable(ttf, "hmtx");
uint endOfLongHorMetrics = 4 * word16(hhea, ttf_hhea_numOfLongHorMetrics);
qint16 advance;
quint16 leftSideBearing;
uint pos = 0;
while (pos < endOfLongHorMetrics)
{
advance = word16(hmtx, pos);
leftSideBearing = word16(hmtx, pos+2);
//qDebug() << pos << "hmtx" << advance << leftSideBearing;
result.append(std::pair<qint16,quint16>(advance, leftSideBearing));
pos += 4;
}
while (pos < (uint) hmtx.length())
{
leftSideBearing = word16(hmtx, pos);
//qDebug() << pos << "hmtx =" << advance << leftSideBearing;
result.append(std::pair<qint16,quint16>(advance, leftSideBearing));
pos += 2;
}
return result;
}
 
QByteArray writeHmtx(const QList<std::pair<qint16,quint16> >& longHorMetrics)
{
QByteArray result;
QList<std::pair<qint16,quint16> >::const_iterator it;
//int i = 0;
for (it = longHorMetrics.cbegin(); it < longHorMetrics.cend(); ++it)
{
//qDebug() << "hmtx" << i++ << it->first << it->second;
appendWord16(result, it->first);
appendWord16(result, it->second);
}
return result;
}
 
QMap<uint, uint> readCMap(const QByteArray& ttf)
{
QMap<uint,uint> result;
const QByteArray cmaps = getTable(ttf, "cmap");
uint numSubtables = word16(cmaps, ttf_cmap_numberSubtables);
uint startOfUnicodeTable = 0;
uint startOfSymbolTable = 0;
uint unicodeFormat = 0, symbolFormat = 0;
uint pos = ttf_cmap_encodings;
for (uint i = 0; i < numSubtables; ++i)
{
uint platform = word16(cmaps, pos + ttf_cmap_encoding_platformID);
uint encoding = word16(cmaps, pos + ttf_cmap_encoding_platformSpecificID);
uint offset = word(cmaps, pos + ttf_cmap_encoding_offset);
uint format = word16(cmaps, offset + ttf_cmapx_format);
pos += ttf_cmap_encoding_Size;
if (format < 4 || format > 12)
continue;
if (platform == 0 || (platform == 3 && encoding == 1))
{
startOfUnicodeTable = offset;
unicodeFormat = format;
break;
}
if (platform == 3 && encoding == 0) // MS Symbol cmap
{
startOfSymbolTable = offset;
symbolFormat = format;
continue;
}
format = 1; // no such format
}
// If no unicode cmap was found, fallback on ms symbol cmap
uint format = unicodeFormat;
if ((unicodeFormat <= 0) && (symbolFormat > 0))
{
startOfUnicodeTable = startOfSymbolTable;
format = symbolFormat;
}
//qDebug() << "reading cmap format" << format;
switch (format)
{
case 4:
{
uint segCount2 = word16(cmaps, startOfUnicodeTable + ttf_cmap4_segCountX2);
uint endCodes = startOfUnicodeTable + ttf_cmap4_EndCodes;
uint startCodes = endCodes + segCount2 + ttf_cmap4_StartCodes_EndCodes;
uint idDeltas = startCodes + segCount2 + ttf_cmap4_IdDeltas_StartCodes;
uint idRangeOffsets = idDeltas + segCount2 + ttf_cmap4_IdRangeOffsets_IdDeltas;
//uint glyphIndexArray = idRangeOffsets + segCount2 + ttf_cmap4_GlyphIds_IdRangeOffsets;
 
for (uint seg = 0; seg < segCount2; seg+=2)
{
uint start = word16(cmaps, startCodes + seg);
uint end = word16(cmaps, endCodes + seg);
uint idDelta = word16(cmaps, idDeltas + seg);
uint idRangeOffset = word16(cmaps, idRangeOffsets + seg);
for (uint c = start; c <= end; ++c)
{
quint16 glyph;
if (idRangeOffset > 0)
{
uint glyphIndexAdress = idRangeOffset + 2*(c-start) + (idRangeOffsets + seg);
glyph = word16(cmaps, glyphIndexAdress);
if (glyph != 0)
glyph += idDelta;
}
else
{
glyph = c + idDelta;
}
if (!result.contains(c))
{
// search would always find the one in the segment with the lower endcode, i.e. earlier segment
if (c < 256 || glyph == 0)
//qDebug() << "(" << QChar(c) << "," << glyph << ")";
result[c] = glyph;
}
else
{
// nothing to do. No idea if fonts with overlapping cmap4 segments exist, though.
}
}
}
break;
}
case 6:
{
uint firstCode = word16(cmaps, startOfUnicodeTable + ttf_cmap6_firstCode);
uint count = word16(cmaps, startOfUnicodeTable + ttf_cmap6_entryCount);
pos = word16(cmaps, startOfUnicodeTable + ttf_cmap6_glyphIndexArray);
for (uint i = 0; i < count; ++i)
{
result[firstCode + i] = word16(cmaps, pos);
pos += 2;
}
break;
}
case 12:
{
uint nGroups = word(cmaps, startOfUnicodeTable + ttf_cmap12_nGroups);
pos = startOfUnicodeTable + ttf_cmap12_Groups;
for (uint grp = 0; grp < nGroups; ++grp)
{
uint start = word(cmaps, pos + ttf_cmap12_Group_startCharCode);
uint end = word(cmaps, pos + ttf_cmap12_Group_endCharCode);
uint gid = word(cmaps, pos + ttf_cmap12_Group_startGlyphCode);
for (uint c = start; c <= end; ++c)
{
result[c] = gid;
++gid;
}
pos += ttf_cmap12_Group_Size;
}
break;
}
default:
{
qDebug() << "unsupported cmap format" << format;
break;
}
}
return result;
}
 
QByteArray writeCMap(const QMap<uint, uint>& cmap)
{
// we always write only one table: platform=3(MS), encoding=1(Unicode 16bit)
QByteArray result;
appendWord16(result, 0); // version
appendWord16(result, 1); // number of subtables
appendWord16(result, 3); // platformID Microsoft
appendWord16(result, 1); // encodingID UnicodeBMP
appendWord(result, result.size() + 4); // offset
// find the segments
QList<uint> chars;
QMap<uint, uint>::ConstIterator cit;
//qDebug() << "writing cmap";
bool cmapHasData = false;
for (cit = cmap.cbegin(); cit != cmap.cend(); ++cit)
{
uint ch = cit.key();
if (!QChar::requiresSurrogates(ch) && cit.value() != 0)
{
//qDebug() << "(" << QChar(cit.key()) << "," << cit.value() << ")";
chars.append(ch);
cmapHasData = true;
}
// qDebug() << QChar(ch) << QChar::requiresSurrogates(ch) << cit.value();
}
std::sort(chars.begin(), chars.end());
QList<quint16> startCodes;
QList<quint16> endCodes;
QList<quint16> idDeltas;
QList<quint16> rangeOffsets;
if (cmapHasData)
{
uint pos = 0;
do {
quint16 start = chars[pos];
quint16 delta = cmap[start] - start;
quint16 rangeOffset = 0;
quint16 end = start;
quint16 next;
++pos;
while (pos < (uint) chars.length() && (next = chars[pos]) == end+1)
{
end = next;
if (delta != (quint16)(cmap[chars[pos]] - next))
{
rangeOffset = 1; // will be changed later
}
++pos;
}
startCodes.append(start);
endCodes.append(end);
idDeltas.append(delta);
rangeOffsets.append(rangeOffset);
}
while (pos < (uint) chars.length());
}
startCodes.append(0xFFFF);
endCodes.append(0xFFFF);
idDeltas.append(1); // makes gid 0
rangeOffsets.append(0);
// write the tables
uint startOfTable = result.size();
result.resize(startOfTable + ttf_cmap4_EndCodes);
 
uint segCount = endCodes.length();
uint segCountX2 = 2 * segCount;
uint entrySelector = 0;
uint searchRange = 2;
while (searchRange <= segCount)
{
++entrySelector;
searchRange *= 2;
}
putWord16(result, startOfTable + ttf_cmapx_format, 4);
/* ttf_cmap4_length is set later */
putWord16(result, startOfTable + ttf_cmap4_language, 0);
putWord16(result, startOfTable + ttf_cmap4_segCountX2, segCountX2);
putWord16(result, startOfTable + ttf_cmap4_searchRange, searchRange);
putWord16(result, startOfTable + ttf_cmap4_entrySelector, entrySelector);
putWord16(result, startOfTable + ttf_cmap4_rangeShift, segCountX2 - searchRange);
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, endCodes[i]);
};
appendWord16(result, 0); // reservedPad
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, startCodes[i]);
};
for (uint i = 0; i < segCount; ++i)
{
appendWord16(result, idDeltas[i]);
};
uint startOfIdRangeOffsetTable = result.size();
uint startOfGlyphIndexArray = startOfIdRangeOffsetTable + segCountX2;
result.resize(startOfGlyphIndexArray);
for (uint i = 0; i < segCount; ++i)
{
uint idRangeOffsetAddress = startOfIdRangeOffsetTable + 2*i;
if (rangeOffsets[i] == 0)
{
//quint16 dbg = startCodes[i] + idDeltas[i];
//qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "+" << idDeltas[i] << "-->" << dbg;
putWord16(result, idRangeOffsetAddress, 0);
}
else
{
quint16 idRangeOffset = result.size() - idRangeOffsetAddress;
putWord16(result, idRangeOffsetAddress, idRangeOffset);
 
//qDebug() << QChar(startCodes[i]) << "-" << QChar(endCodes[i]) << "/" << (endCodes[i]-startCodes[i]+1) << "@" << idRangeOffset << "+" << idDeltas[i];
 
uint startCode = startCodes[i];
uint segLength = (endCodes[i]-startCode+1);
for (uint offset = 0; offset < segLength; ++offset)
{
quint16 glyph = cmap[startCode + offset];
if (glyph != 0)
{
glyph -= idDeltas[i];
}
appendWord16(result, glyph);
}
}
};
putWord16(result, startOfTable + ttf_cmap4_length, result.size() - startOfTable);
return result;
}
QList<uint> copyGlyphComponents(QByteArray& destGlyf, const QByteArray& srcGlyf, uint srcOffset,
QMap<uint,uint>& newForOldGid, uint& nextFreeGid)
{
QList<uint> result;
uint destStart = destGlyf.size();
destGlyf.resize(destStart + ttf_glyf_headerSize);
copy(destGlyf, destStart, srcGlyf, srcOffset, ttf_glyf_headerSize);
uint pos = srcOffset + ttf_glyf_headerSize;
bool haveInstructions = false;
uint flags = 0;
do {
/* flags */
flags = word16(srcGlyf, pos);
pos += 2;
haveInstructions |= ((flags & ttf_glyf_ComponentFlag_WE_HAVE_INSTRUCTIONS) != 0);
appendWord16(destGlyf, flags);
/* glyphindex */
uint glyphIndex = word16(srcGlyf, pos);
pos += 2;
if (!newForOldGid.contains(glyphIndex))
{
result.append(glyphIndex);
newForOldGid[glyphIndex] = nextFreeGid++;
}
glyphIndex = newForOldGid[glyphIndex];
appendWord16(destGlyf, glyphIndex);
/* args */
if ( flags & ttf_glyf_ComponentFlag_ARG_1_AND_2_ARE_WORDS) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg2
pos += 2;
}
else {
appendWord16(destGlyf, word16(srcGlyf, pos)); // arg1and2
pos += 2;
}
if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_SCALE ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale
pos += 2;
} else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_AN_X_AND_Y_SCALE ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale
pos += 2;
} else if ( flags & ttf_glyf_ComponentFlag_WE_HAVE_A_TWO_BY_TWO ) {
appendWord16(destGlyf, word16(srcGlyf, pos)); // xscale
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale01
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // scale10
pos += 2;
appendWord16(destGlyf, word16(srcGlyf, pos)); // yscale
pos += 2;
}
} while ( flags & ttf_glyf_ComponentFlag_MORE_COMPONENTS );
 
if (haveInstructions)
{
uint numInstr = word16(srcGlyf, pos);
appendWord16(destGlyf, numInstr);
pos += 2;
uint destPos = destGlyf.size();
destGlyf.resize(destPos + numInstr);
copy(destGlyf, destPos, srcGlyf, pos, numInstr);
}
return result;
}
 
QList<uint> copyGlyph(QList<uint>& destLoca, QByteArray& destGlyf, uint destGid,
const QList<uint>& srcLoca, const QByteArray& srcGlyf, uint srcGid,
QMap<uint,uint>& newForOldGid, uint& nextFreeGid)
{
QList<uint> compositeElements;
uint glyphStart = srcLoca[srcGid];
uint glyphLength = srcLoca[srcGid+1] - glyphStart;
destLoca.append(destGlyf.size());
//int i = 0;
if (glyphLength > 0)
{
quint16 nrOfContours = word16(srcGlyf, glyphStart);
if (nrOfContours <= ttf_glyf_Max_numberOfContours)
{
// simple glyph
uint destStart = destGlyf.size();
//qDebug() << i++ << ":" << nrOfContours << "contours" << glyphStart << "-->" << destStart << "/" << glyphLength;
destGlyf.resize(destStart + glyphLength);
copy(destGlyf, destStart, srcGlyf, glyphStart, glyphLength);
}
else
{
compositeElements.append(copyGlyphComponents(destGlyf, srcGlyf, glyphStart, newForOldGid, nextFreeGid));
//qDebug() << i++ << ":" << srcGid << "composite glyph brought" << compositeElements.size() << "more glyphs";
}
// Data must be aligned at least on 2 byte boundary, however
// a 4-byte alignment is recommended by TTF specs for reasons
// related to CPU and efficiency
int targetSize = (destGlyf.size() + 3) & ~3;
if (destGlyf.size() < targetSize)
{
destGlyf.reserve(targetSize);
while (destGlyf.size() < targetSize)
destGlyf.append('\0');
}
}
 
return compositeElements;
}
QByteArray subsetFace(const QByteArray& ttf, QList<uint>& glyphs, QMap<uint, uint>& glyphMap)
{
QMap<QByteArray,QByteArray> tables;
// qDebug() << "loca table:" << (void*) oldLoca[0] << (void*) oldLoca[1] << (void*) oldLoca[2] << (void*) oldLoca[3] << (void*) oldLoca[4] << (void*) oldLoca[5] << (void*) oldLoca[6] << (void*) oldLoca[7];
 
QMap<uint, uint> newForOldGid;
if (glyphs.length() == 0)
{
tables["loca"] = getTable(ttf, "loca");
tables["glyf"] = getTable(ttf, "glyf");
}
else
{
QList<uint> oldLoca = readLoca(ttf);
const QByteArray oldGlyf = getTable(ttf, "glyf");
 
QList<quint32> newLoca;
QByteArray newGlyf;
glyphs.removeAll(0);
glyphs.prepend(0);
 
for (int i = 0; i < glyphs.length(); ++i)
{
uint oldGid = glyphs[i];
newForOldGid[oldGid] = i;
}
uint nextFreeGid = glyphs.length();
for (int i = 0; i < glyphs.length(); ++i)
{
uint oldGid = glyphs[i];
glyphs.append(copyGlyph(newLoca, newGlyf, i,
oldLoca, oldGlyf, oldGid,
newForOldGid, nextFreeGid));
}
newLoca.append(newGlyf.length());
tables["loca"] = writeLoca(newLoca, hasLongLocaFormat(ttf));
tables["glyf"] = newGlyf;
}
QMap<uint,uint> cmap = readCMap(ttf);
QMap<uint,uint>::iterator it;
uint firstChar = 0xFFFFFFFF;
uint lastChar = 0;
for (it = cmap.begin(); it != cmap.end(); ++it)
{
if (glyphs.length() > 0 && !glyphs.contains(it.value()))
{
it.value() = 0;
}
else if (it.value() != 0)
{
if (glyphs.length() > 0)
{
//qDebug() << "MAP" << QChar(it.key()) << it.value() << "-->" << newForOldGid[it.value()];
it.value() = newForOldGid[it.value()];
}
if (it.key() < firstChar)
firstChar = it.key();
else if (it.key() > lastChar)
lastChar = it.key();
}
}
tables["cmap"] = writeCMap(cmap);
QByteArray os2 = getTable(ttf, "OS/2");
if (os2.length() > ttf_os2_usLastCharIndex)
{
// TODO: adapt unicode ranges
putWord16(os2, ttf_os2_usFirstCharIndex, firstChar < 0xFFFF ? firstChar : 0xFFFF);
putWord16(os2, ttf_os2_usLastCharIndex, lastChar < 0xFFFF ? lastChar : 0xFFFF);
tables["OS/2"] = os2;
}
if (glyphs.length() > 0)
{
QList<std::pair<qint16, quint16> > oldHmtx = readHmtx(ttf);
QList<std::pair<qint16, quint16> > newHmtx;
newHmtx.reserve(glyphs.length() + 1);
newHmtx.append(std::pair<qint16, quint16>(1234, 123));
for (int i = 1; i < glyphs.length(); ++i)
newHmtx.append(newHmtx[0]);
QMap<uint,uint>::const_iterator iter;
for (iter = newForOldGid.cbegin(); iter != newForOldGid.cend(); ++iter)
{
//qDebug() << "hmtx" << iter.key() << " -> " << iter.value() << "=" << oldHmtx[iter.key()].first;
newHmtx[iter.value()] = oldHmtx[iter.key()];
}
tables["hmtx"] = writeHmtx(newHmtx);
}
else
{
tables["hmtx"] = getTable(ttf, "hmtx");
}
QByteArray maxp = getTable(ttf, "maxp");
if (glyphs.length() > 0)
{
putWord16(maxp, ttf_maxp_numGlyphs, glyphs.length());
}
tables["maxp"] = maxp;
QByteArray hhea = getTable(ttf, "hhea");
if (glyphs.length() > 0)
{
putWord16(hhea, ttf_hhea_numOfLongHorMetrics, glyphs.length());
}
tables["hhea"] = hhea;
QByteArray post = getTable(ttf, "post");
if (word(post, ttf_post_format) != post_format30)
{
putWord(post, ttf_post_format, post_format30);
post.truncate(ttf_post_header_length);
}
tables["post"] = post;
// TODO: kern table
QByteArray name = getTable(ttf, "name");
if (name.length() > 0)
tables["name"] = name;
QByteArray prep = getTable(ttf, "prep");
if (prep.length() > 0)
tables["prep"] = prep;
QByteArray cvt = getTable(ttf, "cvt ");
if (cvt.length() > 0)
tables["cvt "] = cvt;
 
QByteArray fpgm = getTable(ttf, "fpgm");
if (fpgm.length() > 0)
tables["fpgm"] = fpgm;
 
QByteArray head = getTable(ttf, "head");
putWord(head, ttf_head_checkSumAdjustment, 0);
tables["head"] = head;
 
QByteArray font = createTableDir(tables.keys());
QMap<QByteArray,QByteArray>::iterator tableP;
for (tableP = tables.begin(); tableP != tables.end(); ++tableP)
{
writeTable(font, tableP.key(), tableP.value());
}
uint checkSumAdjustment = 0xB1B0AFBA - calcTableChecksum(font);
uint headTable = getTableDirEntry(font, "head");
headTable = word(font, headTable + ttf_TableRecord_offset);
putWord(font, headTable + ttf_head_checkSumAdjustment, checkSumAdjustment);
glyphMap.clear();
for (int i = 0; i < glyphs.length(); ++i)
glyphMap[glyphs[i]] = i;
// done!
return font;
}
 
QByteArray subsetFaceWithHB(const QByteArray& cff, QList<uint> cids, int faceIndex, QMap<uint, uint>& glyphMap)
{
glyphMap[glyphs[i]] = i;
// done!
return font;
}
 
QByteArray subsetFaceWithHB(const QByteArray& cff, QList<uint> cids, int faceIndex, QMap<uint, uint>& glyphMap)
{
glyphMap.clear();
 
#ifdef HAVE_HARFBUZZ_SUBSET
#if HB_VERSION_ATLEAST(2, 0, 0)
QScopedPointer<hb_blob_t, HbBlobDeleter> hbBlob(hb_blob_create(cff.data(), cff.length(), HB_MEMORY_MODE_READONLY, nullptr, nullptr));
if (hbBlob.isNull())
return QByteArray();
1278,19 → 1272,19
for (int i = 0; i < cids.length(); ++i)
glyphMap[cids[i]] = cids[i];
}
return subset;
#else
return QByteArray();
#endif
}
 
bool canSubsetOpenTypeFonts()
{
#ifdef HAVE_HARFBUZZ_SUBSET
return true;
#else
return false;
#endif
}
} // namespace sfnt
return subset;
#else
return QByteArray();
#endif
}
 
bool canSubsetOpenTypeFonts()
{
#if HB_VERSION_ATLEAST(2, 0, 0)
return true;
#else
return false;
#endif
}
} // namespace sfnt
/trunk/Scribus/scribus/CMakeLists.txt
28,7 → 28,6
${CMAKE_CURRENT_SOURCE_DIR}
${FREETYPE_INCLUDE_DIRS}
${HARFBUZZ_INCLUDE_DIRS}
${HARFBUZZ_SUBSET_INCLUDE_DIRS}
)
 
include(api/CMakeLists.txt)
1144,7 → 1143,6
${CMAKE_CURRENT_BINARY_DIR}/third_party/rtf-qt
${CMAKE_CURRENT_BINARY_DIR}/third_party/zip
${HARFBUZZ_LIBRARY_DIRS}
${HARFBUZZ_SUBSET_LIBRARY_DIRS}
${ICU_LIBRARY_DIRS}
)
 
1318,7 → 1316,6
${CUPS_LIBRARIES}
${FREETYPE_LIBRARIES}
${HARFBUZZ_LIBRARIES}
${HARFBUZZ_SUBSET_LIBRARIES}
${ICU_LIBRARIES}
${LIBXML2_LIBRARIES}
${TIFF_LIBRARIES}
/trunk/Scribus/win32/msvc2015/win-config.h
43,9 → 43,6
/* Defines if your system has the libfontconfig library */
#undef HAVE_FONTCONFIG
 
/* Defines if your system has the harfbuzz-subset library */
#define HAVE_HARFBUZZ_SUBSET 1
 
/* Define if you have libz */
#define HAVE_LIBZ 1
 
/trunk/Scribus/win32/msvc2017/win-config.h
43,9 → 43,6
/* Defines if your system has the libfontconfig library */
#undef HAVE_FONTCONFIG
 
/* Defines if your system has the harfbuzz-subset library */
#define HAVE_HARFBUZZ_SUBSET 1
 
/* Define if you have libz */
#define HAVE_LIBZ 1
 
/trunk/Scribus/win32/msvc2019/win-config.h
43,9 → 43,6
/* Defines if your system has the libfontconfig library */
#undef HAVE_FONTCONFIG
 
/* Defines if your system has the harfbuzz-subset library */
#define HAVE_HARFBUZZ_SUBSET 1
 
/* Define if you have libz */
#define HAVE_LIBZ 1
 
/trunk/Scribus/CMakeLists_Dependencies.cmake
298,9 → 298,3
# CTL support
pkg_check_modules(HARFBUZZ REQUIRED harfbuzz>=0.9.42 harfbuzz-icu)
pkg_check_modules(ICU REQUIRED icu-uc)
 
# OpenType subsetting support
pkg_check_modules(HARFBUZZ_SUBSET harfbuzz-subset>=2.0.0)
if (HARFBUZZ_SUBSET_FOUND)
set (HAVE_HARFBUZZ_SUBSET ON)
endif()
/trunk/Scribus/config.h.cmake
23,7 → 23,6
#cmakedefine HAVE_TIFF 1
#cmakedefine HAVE_LIBZ 1
#cmakedefine HAVE_FONTCONFIG 1
#cmakedefine HAVE_HARFBUZZ_SUBSET 1
#cmakedefine HAVE_PODOFO 1
#cmakedefine HAVE_POPPLER 1
#cmakedefine HAVE_OSG 1