//
//  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"

#include <harfbuzz/hb.h>
#ifdef HAVE_HARFBUZZ_SUBSET
#include <harfbuzz/hb-subset.h>
#endif

struct HbBlobDeleter
{
	static void cleanup(void *pointer)
	{
		if (pointer)
			hb_blob_destroy((hb_blob_t*) pointer);
	}
};

struct HbFaceDeleter
{
	static void cleanup(void *pointer)
	{
		if (pointer)
			hb_face_destroy((hb_face_t*) pointer);
	}
};

#ifdef HAVE_HARFBUZZ_SUBSET
struct HbSubsetInputDeleter
{
	static void cleanup(void *pointer)
	{
		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);
		
		glyphMap.clear();
		for (int i = 0; i < glyphs.length(); ++i)
			glyphMap[glyphs[i]] = i;
		// done!
		
		return font;
	}

	QByteArray subsetFaceWithHB(const QByteArray& fontData, QList<uint> cids, int faceIndex, QMap<uint, uint>& glyphMap)
	{
		glyphMap.clear();

#ifdef HAVE_HARFBUZZ_SUBSET
		QScopedPointer<hb_blob_t, HbBlobDeleter> hbBlob(hb_blob_create(fontData.data(), fontData.length(), HB_MEMORY_MODE_READONLY, nullptr, nullptr));
		if (hbBlob.isNull())
			return QByteArray();

		QScopedPointer<hb_face_t, HbFaceDeleter> hbFullFace(hb_face_create(hbBlob.get(), faceIndex));
		if (hbFullFace.isNull())
			return QByteArray();

		QScopedPointer<hb_subset_input_t, HbSubsetInputDeleter> hbSubsetInput(hb_subset_input_create_or_fail());
		hb_set_t* glyphSet = hb_subset_input_glyph_set(hbSubsetInput.get());
		if (glyphSet == nullptr)
			return QByteArray();

		for (int i = 0; i < cids.count(); ++i)
			hb_set_add(glyphSet, cids.at(i));

#if HB_VERSION_ATLEAST(2, 9, 0)
		uint32_t subsetFlags = (uint32_t) hb_subset_input_get_flags(hbSubsetInput.get());
		subsetFlags |= HB_SUBSET_FLAGS_RETAIN_GIDS;
		subsetFlags &= ~HB_SUBSET_FLAGS_NO_HINTING;
		subsetFlags |= HB_SUBSET_FLAGS_NAME_LEGACY;
		hb_subset_input_set_flags(hbSubsetInput.get(), subsetFlags);
#else
		hb_subset_input_set_retain_gids(hbSubsetInput.get(), true);
		hb_subset_input_set_drop_hints(hbSubsetInput.get(), false);
#if HB_VERSION_ATLEAST(2, 6, 5)
		hb_subset_input_set_name_legacy(hbSubsetInput.get(), true);
#endif
#endif

#if HB_VERSION_ATLEAST(2, 9, 0)
		QScopedPointer<hb_face_t, HbFaceDeleter> hbSubsetFace(hb_subset_or_fail(hbFullFace.get(), hbSubsetInput.get()));
		if (hbSubsetFace.isNull())
			return QByteArray();
#else
		QScopedPointer<hb_face_t, HbFaceDeleter> hbSubsetFace(hb_subset(hbFullFace.get(), hbSubsetInput.get()));
		if (hbSubsetFace.isNull())
			return QByteArray();
#endif

		QScopedPointer<hb_blob_t, HbBlobDeleter> hbSubsetBlob(hb_face_reference_blob(hbSubsetFace.get()));
		if (hbSubsetBlob.isNull())
			return QByteArray();

		unsigned int length;
		const char* subsetData = hb_blob_get_data(hbSubsetBlob.get(), &length);

		QByteArray subset(subsetData, length);
		if (!subset.isEmpty())
		{
			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
