blob: c6cb8233c02c33a8b52214216418c46b29f90b80 [file] [log] [blame] [edit]
//========================================================================
//
// FoFiTrueType.cc
//
// Copyright 1999-2003 Glyph & Cog, LLC
//
//========================================================================
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2006 Takashi Iwai <tiwai@suse.de>
// Copyright (C) 2007 Koji Otani <sho@bbr.jp>
// Copyright (C) 2007 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2008, 2009, 2012, 2014-2022, 2024 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2008 Tomas Are Haavet <tomasare@gmail.com>
// Copyright (C) 2012 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
// Copyright (C) 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2014 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2015 Aleksei Volkov <Aleksei Volkov>
// Copyright (C) 2015, 2016 William Bader <williambader@hotmail.com>
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2022 Zachary Travis <ztravis@everlaw.com>
// Copyright (C) 2022, 2024 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <algorithm>
#include <array>
#include "goo/GooLikely.h"
#include "goo/GooString.h"
#include "goo/GooCheckedOps.h"
#include "FoFiType1C.h"
#include "FoFiTrueType.h"
#include "poppler/Error.h"
//
// Terminology
// -----------
//
// character code = number used as an element of a text string
//
// character name = glyph name = name for a particular glyph within a
// font
//
// glyph index = GID = position (within some internal table in the font)
// where the instructions to draw a particular glyph are
// stored
//
// Type 1 fonts
// ------------
//
// Type 1 fonts contain:
//
// Encoding: array of glyph names, maps char codes to glyph names
//
// Encoding[charCode] = charName
//
// CharStrings: dictionary of instructions, keyed by character names,
// maps character name to glyph data
//
// CharStrings[charName] = glyphData
//
// TrueType fonts
// --------------
//
// TrueType fonts contain:
//
// 'cmap' table: mapping from character code to glyph index; there may
// be multiple cmaps in a TrueType font
//
// cmap[charCode] = gid
//
// 'post' table: mapping from glyph index to glyph name
//
// post[gid] = glyphName
//
// Type 42 fonts
// -------------
//
// Type 42 fonts contain:
//
// Encoding: array of glyph names, maps char codes to glyph names
//
// Encoding[charCode] = charName
//
// CharStrings: dictionary of glyph indexes, keyed by character names,
// maps character name to glyph index
//
// CharStrings[charName] = gid
//
//------------------------------------------------------------------------
#define ttcfTag 0x74746366
//------------------------------------------------------------------------
struct TrueTypeTable
{
unsigned int tag = 0;
unsigned int checksum = 0;
int offset = 0;
int origOffset = 0;
int len = 0;
};
struct TrueTypeCmap
{
int platform = 0;
int encoding = 0;
int offset = 0;
int len = 0;
int fmt = 0;
};
struct TrueTypeLoca
{
int idx = 0;
int origOffset = 0;
int newOffset = 0;
int len = 0;
};
#define cmapTag 0x636d6170
#define glyfTag 0x676c7966
#define headTag 0x68656164
#define hheaTag 0x68686561
#define hmtxTag 0x686d7478
#define locaTag 0x6c6f6361
#define nameTag 0x6e616d65
#define os2Tag 0x4f532f32
#define postTag 0x706f7374
#define vrt2Tag 0x76727432
#define vertTag 0x76657274
struct cmpTrueTypeLocaOffsetFunctor
{
bool operator()(const TrueTypeLoca loca1, const TrueTypeLoca loca2)
{
if (loca1.origOffset == loca2.origOffset) {
return loca1.idx < loca2.idx;
}
return loca1.origOffset < loca2.origOffset;
}
};
struct cmpTrueTypeLocaIdxFunctor
{
bool operator()(const TrueTypeLoca loca1, const TrueTypeLoca loca2) { return loca1.idx < loca2.idx; }
};
struct cmpTrueTypeTableTagFunctor
{
bool operator()(const TrueTypeTable &tab1, const TrueTypeTable &tab2) { return tab1.tag < tab2.tag; }
};
//------------------------------------------------------------------------
struct T42Table
{
const char *tag; // 4-byte tag
bool required; // required by the TrueType spec?
};
// TrueType tables to be embedded in Type 42 fonts.
// NB: the table names must be in alphabetical order here.
#define nT42Tables 11
static const T42Table t42Tables[nT42Tables] = { { "cvt ", true }, { "fpgm", true }, { "glyf", true }, { "head", true }, { "hhea", true }, { "hmtx", true },
{ "loca", true }, { "maxp", true }, { "prep", true }, { "vhea", false }, { "vmtx", false } };
#define t42HeadTable 3
#define t42LocaTable 6
#define t42GlyfTable 2
#define t42VheaTable 9
#define t42VmtxTable 10
//------------------------------------------------------------------------
// Glyph names in some arbitrary standard order that Apple uses for
// their TrueType fonts.
static const char *macGlyphNames[258] = { ".notdef",
"null",
"CR",
"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",
"increment",
"guillemotleft",
"guillemotright",
"ellipsis",
"nbspace",
"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",
"applelogo",
"Ograve",
"Uacute",
"Ucircumflex",
"Ugrave",
"dotlessi",
"circumflex",
"tilde",
"overscore",
"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",
"Idot",
"Scedilla",
"scedilla",
"Cacute",
"cacute",
"Ccaron",
"ccaron",
"dmacron" };
//------------------------------------------------------------------------
// FoFiTrueType
//------------------------------------------------------------------------
std::unique_ptr<FoFiTrueType> FoFiTrueType::make(const unsigned char *fileA, int lenA, int faceIndexA)
{
// Cannot use std::make_unique, because the constructor is private
auto ff = new FoFiTrueType(fileA, lenA, false, faceIndexA);
if (!ff->parsedOk) {
delete ff;
return nullptr;
}
return std::unique_ptr<FoFiTrueType>(ff);
}
std::unique_ptr<FoFiTrueType> FoFiTrueType::load(const char *fileName, int faceIndexA)
{
char *fileA;
int lenA;
if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
return nullptr;
}
// Cannot use std::make_unique, because the constructor is private
auto ff = new FoFiTrueType((unsigned char *)fileA, lenA, true, faceIndexA);
if (!ff->parsedOk) {
delete ff;
return nullptr;
}
return std::unique_ptr<FoFiTrueType>(ff);
}
FoFiTrueType::FoFiTrueType(const unsigned char *fileA, int lenA, bool freeFileDataA, int faceIndexA) : FoFiBase(fileA, lenA, freeFileDataA)
{
parsedOk = false;
faceIndex = faceIndexA;
gsubFeatureTable = 0;
gsubLookupList = 0;
parse();
}
FoFiTrueType::~FoFiTrueType() = default;
int FoFiTrueType::getNumCmaps() const
{
return cmaps.size();
}
int FoFiTrueType::getCmapPlatform(int i) const
{
return cmaps[i].platform;
}
int FoFiTrueType::getCmapEncoding(int i) const
{
return cmaps[i].encoding;
}
int FoFiTrueType::findCmap(int platform, int encoding) const
{
for (int i = 0; i < (int)cmaps.size(); ++i) {
if (cmaps[i].platform == platform && cmaps[i].encoding == encoding) {
return i;
}
}
return -1;
}
int FoFiTrueType::mapCodeToGID(int i, unsigned int c) const
{
int gid;
unsigned int segCnt, segEnd, segStart, segDelta, segOffset;
unsigned int cmapFirst, cmapLen;
int pos, a, b, m;
unsigned int high, low, idx;
bool ok;
if (i < 0 || i >= (int)cmaps.size()) {
return 0;
}
ok = true;
pos = cmaps[i].offset;
switch (cmaps[i].fmt) {
case 0:
if (c + 6 >= (unsigned int)cmaps[i].len) {
return 0;
}
gid = getU8(cmaps[i].offset + 6 + c, &ok);
break;
case 2:
high = c >> 8;
low = c & 0xFFU;
idx = getU16BE(pos + 6 + high * 2, &ok);
segStart = getU16BE(pos + 6 + 512 + idx, &ok);
segCnt = getU16BE(pos + 6 + 512 + idx + 2, &ok);
segDelta = getS16BE(pos + 6 + 512 + idx + 4, &ok);
segOffset = getU16BE(pos + 6 + 512 + idx + 6, &ok);
if (low < segStart || low >= segStart + segCnt) {
gid = 0;
} else {
int val = getU16BE(pos + 6 + 512 + idx + 6 + segOffset + (low - segStart) * 2, &ok);
gid = val == 0 ? 0 : (val + segDelta) & 0xFFFFU;
}
break;
case 4:
segCnt = getU16BE(pos + 6, &ok) / 2;
a = -1;
b = segCnt - 1;
segEnd = getU16BE(pos + 14 + 2 * b, &ok);
if (c > segEnd) {
// malformed font -- the TrueType spec requires the last segEnd
// to be 0xffff
return 0;
}
// invariant: seg[a].end < code <= seg[b].end
while (b - a > 1 && ok) {
m = (a + b) / 2;
segEnd = getU16BE(pos + 14 + 2 * m, &ok);
if (segEnd < c) {
a = m;
} else {
b = m;
}
}
segStart = getU16BE(pos + 16 + 2 * segCnt + 2 * b, &ok);
segDelta = getU16BE(pos + 16 + 4 * segCnt + 2 * b, &ok);
segOffset = getU16BE(pos + 16 + 6 * segCnt + 2 * b, &ok);
if (c < segStart) {
return 0;
}
if (segOffset == 0) {
gid = (c + segDelta) & 0xffff;
} else {
gid = getU16BE(pos + 16 + 6 * segCnt + 2 * b + segOffset + 2 * (c - segStart), &ok);
if (gid != 0) {
gid = (gid + segDelta) & 0xffff;
}
}
break;
case 6:
cmapFirst = getU16BE(pos + 6, &ok);
cmapLen = getU16BE(pos + 8, &ok);
if (c < cmapFirst || c >= cmapFirst + cmapLen) {
return 0;
}
gid = getU16BE(pos + 10 + 2 * (c - cmapFirst), &ok);
break;
case 12:
case 13:
segCnt = getU32BE(pos + 12, &ok);
a = -1;
b = segCnt - 1;
segEnd = getU32BE(pos + 16 + 12 * b + 4, &ok);
if (c > segEnd) {
return 0;
}
// invariant: seg[a].end < code <= seg[b].end
while (b - a > 1 && ok) {
m = (a + b) / 2;
segEnd = getU32BE(pos + 16 + 12 * m + 4, &ok);
if (segEnd < c) {
a = m;
} else {
b = m;
}
}
segStart = getU32BE(pos + 16 + 12 * b, &ok);
segDelta = getU32BE(pos + 16 + 12 * b + 8, &ok);
if (c < segStart) {
return 0;
}
// In format 12, the glyph codes increment through
// each segment; in format 13 the same glyph code is used
// for an entire segment.
gid = segDelta + (cmaps[i].fmt == 12 ? (c - segStart) : 0);
break;
default:
return 0;
}
if (!ok) {
return 0;
}
return gid;
}
int FoFiTrueType::mapNameToGID(const char *name) const
{
const auto gid = nameToGID.find(name);
if (gid == nameToGID.end()) {
return 0;
}
return gid->second;
}
bool FoFiTrueType::getCFFBlock(char **start, int *length) const
{
if (!openTypeCFF || tables.empty()) {
return false;
}
int i = seekTable("CFF ");
if (i < 0 || !checkRegion(tables[i].offset, tables[i].len)) {
return false;
}
*start = (char *)file + tables[i].offset;
*length = tables[i].len;
return true;
}
int *FoFiTrueType::getCIDToGIDMap(int *nCIDs) const
{
char *start;
int length;
FoFiType1C *ff;
int *map;
*nCIDs = 0;
if (!getCFFBlock(&start, &length)) {
return nullptr;
}
if (!(ff = FoFiType1C::make((unsigned char *)start, length))) {
return nullptr;
}
map = ff->getCIDToGIDMap(nCIDs);
delete ff;
return map;
}
int FoFiTrueType::getEmbeddingRights() const
{
int i, fsType;
bool ok;
if ((i = seekTable("OS/2")) < 0) {
return 4;
}
ok = true;
fsType = getU16BE(tables[i].offset + 8, &ok);
if (!ok) {
return 4;
}
if (fsType & 0x0008) {
return 2;
}
if (fsType & 0x0004) {
return 1;
}
if (fsType & 0x0002) {
return 0;
}
return 3;
}
void FoFiTrueType::getFontMatrix(double *mat) const
{
char *start;
int length;
FoFiType1C *ff;
if (!getCFFBlock(&start, &length)) {
return;
}
if (!(ff = FoFiType1C::make((unsigned char *)start, length))) {
return;
}
ff->getFontMatrix(mat);
delete ff;
}
void FoFiTrueType::convertToType42(const char *psName, char **encoding, int *codeToGID, FoFiOutputFunc outputFunc, void *outputStream) const
{
int maxUsedGlyph;
bool ok;
if (openTypeCFF) {
return;
}
// write the header
ok = true;
std::string buf = GooString::format("%!PS-TrueTypeFont-{0:2g}\n", (double)getS32BE(0, &ok) / 65536.0);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
// begin the font dictionary
(*outputFunc)(outputStream, "10 dict begin\n", 14);
(*outputFunc)(outputStream, "/FontName /", 11);
(*outputFunc)(outputStream, psName, strlen(psName));
(*outputFunc)(outputStream, " def\n", 5);
(*outputFunc)(outputStream, "/FontType 42 def\n", 17);
(*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
buf = GooString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n", bbox[0], bbox[1], bbox[2], bbox[3]);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
// write the guts of the dictionary
cvtEncoding(encoding, outputFunc, outputStream);
cvtCharStrings(encoding, codeToGID, outputFunc, outputStream);
cvtSfnts(outputFunc, outputStream, nullptr, false, &maxUsedGlyph);
// end the dictionary and define the font
(*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
}
void FoFiTrueType::convertToType1(const char *psName, const char **newEncoding, bool ascii, FoFiOutputFunc outputFunc, void *outputStream) const
{
char *start;
int length;
FoFiType1C *ff;
if (!getCFFBlock(&start, &length)) {
return;
}
if (!(ff = FoFiType1C::make((unsigned char *)start, length))) {
return;
}
ff->convertToType1(psName, newEncoding, ascii, outputFunc, outputStream);
delete ff;
}
void FoFiTrueType::convertToCIDType2(const char *psName, const int *cidMap, int nCIDs, bool needVerticalMetrics, FoFiOutputFunc outputFunc, void *outputStream) const
{
int cid, maxUsedGlyph;
bool ok;
int i, j, k;
if (openTypeCFF) {
return;
}
// write the header
ok = true;
std::string buf = GooString::format("%!PS-TrueTypeFont-{0:2g}\n", (double)getS32BE(0, &ok) / 65536.0);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
// begin the font dictionary
(*outputFunc)(outputStream, "20 dict begin\n", 14);
(*outputFunc)(outputStream, "/CIDFontName /", 14);
(*outputFunc)(outputStream, psName, strlen(psName));
(*outputFunc)(outputStream, " def\n", 5);
(*outputFunc)(outputStream, "/CIDFontType 2 def\n", 19);
(*outputFunc)(outputStream, "/FontType 42 def\n", 17);
(*outputFunc)(outputStream, "/CIDSystemInfo 3 dict dup begin\n", 32);
(*outputFunc)(outputStream, " /Registry (Adobe) def\n", 24);
(*outputFunc)(outputStream, " /Ordering (Identity) def\n", 27);
(*outputFunc)(outputStream, " /Supplement 0 def\n", 20);
(*outputFunc)(outputStream, " end def\n", 10);
(*outputFunc)(outputStream, "/GDBytes 2 def\n", 15);
if (cidMap) {
buf = GooString::format("/CIDCount {0:d} def\n", nCIDs);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
if (nCIDs > 32767) {
(*outputFunc)(outputStream, "/CIDMap [", 9);
for (i = 0; i < nCIDs; i += 32768 - 16) {
(*outputFunc)(outputStream, "<\n", 2);
for (j = 0; j < 32768 - 16 && i + j < nCIDs; j += 16) {
(*outputFunc)(outputStream, " ", 2);
for (k = 0; k < 16 && i + j + k < nCIDs; ++k) {
cid = cidMap[i + j + k];
buf = GooString::format("{0:02x}{1:02x}", (cid >> 8) & 0xff, cid & 0xff);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "\n", 1);
}
(*outputFunc)(outputStream, " >", 3);
}
(*outputFunc)(outputStream, "\n", 1);
(*outputFunc)(outputStream, "] def\n", 6);
} else {
(*outputFunc)(outputStream, "/CIDMap <\n", 10);
for (i = 0; i < nCIDs; i += 16) {
(*outputFunc)(outputStream, " ", 2);
for (j = 0; j < 16 && i + j < nCIDs; ++j) {
cid = cidMap[i + j];
buf = GooString::format("{0:02x}{1:02x}", (cid >> 8) & 0xff, cid & 0xff);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "\n", 1);
}
(*outputFunc)(outputStream, "> def\n", 6);
}
} else {
// direct mapping - just fill the string(s) with s[i]=i
buf = GooString::format("/CIDCount {0:d} def\n", nGlyphs);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
if (nGlyphs > 32767) {
(*outputFunc)(outputStream, "/CIDMap [\n", 10);
for (i = 0; i < nGlyphs; i += 32767) {
j = nGlyphs - i < 32767 ? nGlyphs - i : 32767;
buf = GooString::format(" {0:d} string 0 1 {1:d} {{\n", 2 * j, j - 1);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
buf = GooString::format(" 2 copy dup 2 mul exch {0:d} add -8 bitshift put\n", i);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
buf = GooString::format(" 1 index exch dup 2 mul 1 add exch {0:d} add"
" 255 and put\n",
i);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, " } for\n", 8);
}
(*outputFunc)(outputStream, "] def\n", 6);
} else {
buf = GooString::format("/CIDMap {0:d} string\n", 2 * nGlyphs);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
buf = GooString::format(" 0 1 {0:d} {{\n", nGlyphs - 1);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, " 2 copy dup 2 mul exch -8 bitshift put\n", 42);
(*outputFunc)(outputStream, " 1 index exch dup 2 mul 1 add exch 255 and put\n", 50);
(*outputFunc)(outputStream, " } for\n", 8);
(*outputFunc)(outputStream, "def\n", 4);
}
}
(*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
buf = GooString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n", bbox[0], bbox[1], bbox[2], bbox[3]);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
(*outputFunc)(outputStream, "/Encoding [] readonly def\n", 26);
(*outputFunc)(outputStream, "/CharStrings 1 dict dup begin\n", 30);
(*outputFunc)(outputStream, " /.notdef 0 def\n", 17);
(*outputFunc)(outputStream, " end readonly def\n", 19);
// write the guts of the dictionary
cvtSfnts(outputFunc, outputStream, nullptr, needVerticalMetrics, &maxUsedGlyph);
// end the dictionary and define the font
(*outputFunc)(outputStream, "CIDFontName currentdict end /CIDFont defineresource pop\n", 56);
}
void FoFiTrueType::convertToCIDType0(const char *psName, int *cidMap, int nCIDs, FoFiOutputFunc outputFunc, void *outputStream) const
{
char *start;
int length;
FoFiType1C *ff;
if (!getCFFBlock(&start, &length)) {
return;
}
if (!(ff = FoFiType1C::make((unsigned char *)start, length))) {
return;
}
ff->convertToCIDType0(psName, cidMap, nCIDs, outputFunc, outputStream);
delete ff;
}
void FoFiTrueType::convertToType0(const char *psName, int *cidMap, int nCIDs, bool needVerticalMetrics, int *maxValidGlyph, FoFiOutputFunc outputFunc, void *outputStream) const
{
GooString *sfntsName;
int maxUsedGlyph, n, i, j;
*maxValidGlyph = -1;
if (openTypeCFF) {
return;
}
// write the Type 42 sfnts array
sfntsName = (new GooString(psName))->append("_sfnts");
cvtSfnts(outputFunc, outputStream, sfntsName, needVerticalMetrics, &maxUsedGlyph);
delete sfntsName;
// write the descendant Type 42 fonts
// (The following is a kludge: nGlyphs is the glyph count from the
// maxp table; maxUsedGlyph is the max glyph number that has a
// non-zero-length description, from the loca table. The problem is
// that some TrueType font subsets fail to change the glyph count,
// i.e., nGlyphs is much larger than maxUsedGlyph+1, which results
// in an unnecessarily huge Type 0 font. But some other PDF files
// have fonts with only zero or one used glyph, and a content stream
// that refers to one of the unused glyphs -- this results in PS
// errors if we simply use maxUsedGlyph+1 for the Type 0 font. So
// we compromise by always defining at least 256 glyphs.)
// Some fonts have a large nGlyphs but maxUsedGlyph of 0.
// These fonts might reference any glyph.
// Return the last written glyph number in maxValidGlyph.
// PSOutputDev::drawString() can use maxValidGlyph to avoid
// referencing zero-length glyphs that we trimmed.
// This allows pdftops to avoid writing huge files while still
// handling the rare PDF that uses a zero-length glyph.
if (cidMap) {
n = nCIDs;
} else if (nGlyphs > maxUsedGlyph + 256) {
if (maxUsedGlyph <= 255) {
n = 256;
} else {
n = maxUsedGlyph + 1;
}
} else {
n = nGlyphs;
}
*maxValidGlyph = n - 1;
for (i = 0; i < n; i += 256) {
(*outputFunc)(outputStream, "10 dict begin\n", 14);
(*outputFunc)(outputStream, "/FontName /", 11);
(*outputFunc)(outputStream, psName, strlen(psName));
std::string buf = GooString::format("_{0:02x} def\n", i >> 8);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, "/FontType 42 def\n", 17);
(*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
buf = GooString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n", bbox[0], bbox[1], bbox[2], bbox[3]);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
(*outputFunc)(outputStream, "/sfnts ", 7);
(*outputFunc)(outputStream, psName, strlen(psName));
(*outputFunc)(outputStream, "_sfnts def\n", 11);
(*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
for (j = 0; j < 256 && i + j < n; ++j) {
buf = GooString::format("dup {0:d} /c{1:02x} put\n", j, j);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "readonly def\n", 13);
(*outputFunc)(outputStream, "/CharStrings 257 dict dup begin\n", 32);
(*outputFunc)(outputStream, "/.notdef 0 def\n", 15);
for (j = 0; j < 256 && i + j < n; ++j) {
buf = GooString::format("/c{0:02x} {1:d} def\n", j, cidMap ? cidMap[i + j] : i + j);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "end readonly def\n", 17);
(*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
}
// write the Type 0 parent font
(*outputFunc)(outputStream, "16 dict begin\n", 14);
(*outputFunc)(outputStream, "/FontName /", 11);
(*outputFunc)(outputStream, psName, strlen(psName));
(*outputFunc)(outputStream, " def\n", 5);
(*outputFunc)(outputStream, "/FontType 0 def\n", 16);
(*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
(*outputFunc)(outputStream, "/FMapType 2 def\n", 16);
(*outputFunc)(outputStream, "/Encoding [\n", 12);
for (i = 0; i < n; i += 256) {
const std::string buf = GooString::format("{0:d}\n", i >> 8);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "] def\n", 6);
(*outputFunc)(outputStream, "/FDepVector [\n", 14);
for (i = 0; i < n; i += 256) {
(*outputFunc)(outputStream, "/", 1);
(*outputFunc)(outputStream, psName, strlen(psName));
const std::string buf = GooString::format("_{0:02x} findfont\n", i >> 8);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
(*outputFunc)(outputStream, "] def\n", 6);
(*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
}
void FoFiTrueType::convertToType0(const char *psName, int *cidMap, int nCIDs, FoFiOutputFunc outputFunc, void *outputStream) const
{
char *start;
int length;
FoFiType1C *ff;
if (!getCFFBlock(&start, &length)) {
return;
}
if (!(ff = FoFiType1C::make((unsigned char *)start, length))) {
return;
}
ff->convertToType0(psName, cidMap, nCIDs, outputFunc, outputStream);
delete ff;
}
void FoFiTrueType::cvtEncoding(char **encoding, FoFiOutputFunc outputFunc, void *outputStream) const
{
const char *name;
int i;
(*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
if (encoding) {
for (i = 0; i < 256; ++i) {
if (!(name = encoding[i])) {
name = ".notdef";
}
const std::string buf = GooString::format("dup {0:d} /", i);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
(*outputFunc)(outputStream, name, strlen(name));
(*outputFunc)(outputStream, " put\n", 5);
}
} else {
for (i = 0; i < 256; ++i) {
const std::string buf = GooString::format("dup {0:d} /c{1:02x} put\n", i, i);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
}
(*outputFunc)(outputStream, "readonly def\n", 13);
}
void FoFiTrueType::cvtCharStrings(char **encoding, const int *codeToGID, FoFiOutputFunc outputFunc, void *outputStream) const
{
const char *name;
char buf2[16];
int i, k;
// always define '.notdef'
(*outputFunc)(outputStream, "/CharStrings 256 dict dup begin\n", 32);
(*outputFunc)(outputStream, "/.notdef 0 def\n", 15);
// if there's no 'cmap' table, punt
if (cmaps.empty()) {
goto err;
}
// map char name to glyph index:
// 1. use encoding to map name to char code
// 2. use codeToGID to map char code to glyph index
// N.B. We do this in reverse order because font subsets can have
// weird encodings that use the same character name twice, and
// the first definition is probably the one we want.
k = 0; // make gcc happy
for (i = 255; i >= 0; --i) {
if (encoding) {
name = encoding[i];
} else {
sprintf(buf2, "c%02x", i);
name = buf2;
}
if (name && (strcmp(name, ".notdef") != 0)) {
k = codeToGID[i];
// note: Distiller (maybe Adobe's PS interpreter in general)
// doesn't like TrueType fonts that have CharStrings entries
// which point to nonexistent glyphs, hence the (k < nGlyphs)
// test
if (k > 0 && k < nGlyphs) {
(*outputFunc)(outputStream, "/", 1);
(*outputFunc)(outputStream, name, strlen(name));
const std::string buf = GooString::format(" {0:d} def\n", k);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
}
}
err:
(*outputFunc)(outputStream, "end readonly def\n", 17);
}
void FoFiTrueType::cvtSfnts(FoFiOutputFunc outputFunc, void *outputStream, const GooString *name, bool needVerticalMetrics, int *maxUsedGlyph) const
{
std::array<unsigned char, 54> headData;
std::vector<unsigned char> locaData;
TrueTypeTable newTables[nT42Tables];
unsigned char tableDir[12 + nT42Tables * 16];
bool ok;
unsigned int checksum;
int nNewTables;
int glyfTableLen, length, glyfPos, j, k;
std::array<unsigned char, 36> vheaTab = {
0, 1, 0, 0, // table version number
0, 0, // ascent
0, 0, // descent
0, 0, // reserved
0, 0, // max advance height
0, 0, // min top side bearing
0, 0, // min bottom side bearing
0, 0, // y max extent
0, 0, // caret slope rise
0, 1, // caret slope run
0, 0, // caret offset
0, 0, // reserved
0, 0, // reserved
0, 0, // reserved
0, 0, // reserved
0, 0, // metric data format
0, 1 // number of advance heights in vmtx table
};
std::vector<unsigned char> vmtxTab;
bool needVhea, needVmtx;
int advance;
*maxUsedGlyph = -1;
// construct the 'head' table, zero out the font checksum
int i = seekTable("head");
if (i < 0 || i >= (int)tables.size()) {
return;
}
int pos = tables[i].offset;
if (!checkRegion(pos, 54)) {
return;
}
memcpy(headData.data(), file + pos, 54);
headData[8] = headData[9] = headData[10] = headData[11] = (unsigned char)0;
// check for a bogus loca format field in the 'head' table
// (I've encountered fonts with loca format set to 0x0100 instead of 0x0001)
if (locaFmt != 0 && locaFmt != 1) {
headData[50] = 0;
headData[51] = 1;
}
// read the original 'loca' table, pad entries out to 4 bytes, and
// sort it into proper order -- some (non-compliant) fonts have
// out-of-order loca tables; in order to correctly handle the case
// where (compliant) fonts have empty entries in the middle of the
// table, cmpTrueTypeLocaOffset uses offset as its primary sort key,
// and idx as its secondary key (ensuring that adjacent entries with
// the same pos value remain in the same order)
std::vector<TrueTypeLoca> locaTable { size_t(nGlyphs + 1) };
i = seekTable("loca");
pos = tables[i].offset;
i = seekTable("glyf");
glyfTableLen = tables[i].len;
ok = true;
for (i = 0; i <= nGlyphs; ++i) {
locaTable[i].idx = i;
if (locaFmt) {
locaTable[i].origOffset = (int)getU32BE(pos + i * 4, &ok);
} else {
locaTable[i].origOffset = 2 * getU16BE(pos + i * 2, &ok);
}
if (locaTable[i].origOffset > glyfTableLen) {
locaTable[i].origOffset = glyfTableLen;
}
}
std::ranges::sort(locaTable, cmpTrueTypeLocaOffsetFunctor());
for (i = 0; i < nGlyphs; ++i) {
locaTable[i].len = locaTable[i + 1].origOffset - locaTable[i].origOffset;
}
locaTable[nGlyphs].len = 0;
std::ranges::sort(locaTable, cmpTrueTypeLocaIdxFunctor());
pos = 0;
for (i = 0; i <= nGlyphs; ++i) {
locaTable[i].newOffset = pos;
int newPos;
if (unlikely(checkedAdd(pos, locaTable[i].len, &newPos))) {
ok = false;
} else {
pos = newPos;
if (pos & 3) {
pos += 4 - (pos & 3);
}
}
if (locaTable[i].len > 0) {
*maxUsedGlyph = i;
}
}
// construct the new 'loca' table
locaData.resize((nGlyphs + 1) * (locaFmt ? 4 : 2));
for (i = 0; i <= nGlyphs; ++i) {
pos = locaTable[i].newOffset;
if (locaFmt) {
locaData[4 * i] = (unsigned char)(pos >> 24);
locaData[4 * i + 1] = (unsigned char)(pos >> 16);
locaData[4 * i + 2] = (unsigned char)(pos >> 8);
locaData[4 * i + 3] = (unsigned char)pos;
} else {
locaData[2 * i] = (unsigned char)(pos >> 9);
locaData[2 * i + 1] = (unsigned char)(pos >> 1);
}
}
// count the number of tables
nNewTables = 0;
for (i = 0; i < nT42Tables; ++i) {
if (t42Tables[i].required || seekTable(t42Tables[i].tag) >= 0) {
++nNewTables;
}
}
advance = 0; // make gcc happy
if (needVerticalMetrics) {
needVhea = seekTable("vhea") < 0;
needVmtx = seekTable("vmtx") < 0;
if (needVhea || needVmtx) {
i = seekTable("head");
advance = getU16BE(tables[i].offset + 18, &ok); // units per em
if (needVhea) {
++nNewTables;
}
if (needVmtx) {
++nNewTables;
}
}
}
// construct the new table headers, including table checksums
// (pad each table out to a multiple of 4 bytes)
pos = 12 + nNewTables * 16;
k = 0;
for (i = 0; i < nT42Tables; ++i) {
length = -1;
checksum = 0; // make gcc happy
if (i == t42HeadTable) {
length = 54;
checksum = computeTableChecksum(headData);
} else if (i == t42LocaTable) {
length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
checksum = computeTableChecksum(locaData);
} else if (i == t42GlyfTable) {
length = 0;
checksum = 0;
glyfPos = tables[seekTable("glyf")].offset;
for (j = 0; j < nGlyphs; ++j) {
length += locaTable[j].len;
if (length & 3) {
length += 4 - (length & 3);
}
if (checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
checksum += computeTableChecksum(std::span(file + glyfPos + locaTable[j].origOffset, locaTable[j].len));
}
}
} else {
if ((j = seekTable(t42Tables[i].tag)) >= 0) {
length = tables[j].len;
if (checkRegion(tables[j].offset, length)) {
checksum = computeTableChecksum(std::span(file + tables[j].offset, length));
}
} else if (needVerticalMetrics && i == t42VheaTable) {
vheaTab[10] = advance / 256; // max advance height
vheaTab[11] = advance % 256;
length = vheaTab.size();
checksum = computeTableChecksum(vheaTab);
} else if (needVerticalMetrics && i == t42VmtxTable) {
length = 4 + (nGlyphs - 1) * 2;
vmtxTab.resize(length, 0);
vmtxTab[0] = advance / 256;
vmtxTab[1] = advance % 256;
checksum = computeTableChecksum(vmtxTab);
} else if (t42Tables[i].required) {
//~ error(-1, "Embedded TrueType font is missing a required table ('%s')",
//~ t42Tables[i].tag);
length = 0;
checksum = 0;
}
}
if (length >= 0) {
newTables[k].tag = ((t42Tables[i].tag[0] & 0xff) << 24) | ((t42Tables[i].tag[1] & 0xff) << 16) | ((t42Tables[i].tag[2] & 0xff) << 8) | (t42Tables[i].tag[3] & 0xff);
newTables[k].checksum = checksum;
newTables[k].offset = pos;
newTables[k].len = length;
int newPos;
if (unlikely(checkedAdd(pos, length, &newPos))) {
ok = false;
} else {
pos = newPos;
if (pos & 3) {
pos += 4 - (length & 3);
}
}
++k;
}
}
if (unlikely(k < nNewTables)) {
error(errSyntaxWarning, -1, "unexpected number of tables");
nNewTables = k;
}
// construct the table directory
tableDir[0] = 0x00; // sfnt version
tableDir[1] = 0x01;
tableDir[2] = 0x00;
tableDir[3] = 0x00;
tableDir[4] = 0; // numTables
tableDir[5] = nNewTables;
tableDir[6] = 0; // searchRange
tableDir[7] = (unsigned char)128;
tableDir[8] = 0; // entrySelector
tableDir[9] = 3;
tableDir[10] = 0; // rangeShift
tableDir[11] = (unsigned char)(16 * nNewTables - 128);
pos = 12;
for (i = 0; i < nNewTables; ++i) {
tableDir[pos] = (unsigned char)(newTables[i].tag >> 24);
tableDir[pos + 1] = (unsigned char)(newTables[i].tag >> 16);
tableDir[pos + 2] = (unsigned char)(newTables[i].tag >> 8);
tableDir[pos + 3] = (unsigned char)newTables[i].tag;
tableDir[pos + 4] = (unsigned char)(newTables[i].checksum >> 24);
tableDir[pos + 5] = (unsigned char)(newTables[i].checksum >> 16);
tableDir[pos + 6] = (unsigned char)(newTables[i].checksum >> 8);
tableDir[pos + 7] = (unsigned char)newTables[i].checksum;
tableDir[pos + 8] = (unsigned char)(newTables[i].offset >> 24);
tableDir[pos + 9] = (unsigned char)(newTables[i].offset >> 16);
tableDir[pos + 10] = (unsigned char)(newTables[i].offset >> 8);
tableDir[pos + 11] = (unsigned char)newTables[i].offset;
tableDir[pos + 12] = (unsigned char)(newTables[i].len >> 24);
tableDir[pos + 13] = (unsigned char)(newTables[i].len >> 16);
tableDir[pos + 14] = (unsigned char)(newTables[i].len >> 8);
tableDir[pos + 15] = (unsigned char)newTables[i].len;
pos += 16;
}
// compute the font checksum and store it in the head table
checksum = computeTableChecksum(std::span(tableDir, 12 + nNewTables * 16));
for (i = 0; i < nNewTables; ++i) {
checksum += newTables[i].checksum;
}
checksum = 0xb1b0afba - checksum; // because the TrueType spec says so
headData[8] = (unsigned char)(checksum >> 24);
headData[9] = (unsigned char)(checksum >> 16);
headData[10] = (unsigned char)(checksum >> 8);
headData[11] = (unsigned char)checksum;
// start the sfnts array
if (name) {
(*outputFunc)(outputStream, "/", 1);
(*outputFunc)(outputStream, name->c_str(), name->getLength());
(*outputFunc)(outputStream, " [\n", 3);
} else {
(*outputFunc)(outputStream, "/sfnts [\n", 9);
}
// write the table directory
dumpString(std::span(tableDir, 12 + nNewTables * 16), outputFunc, outputStream);
// write the tables
for (i = 0; i < nNewTables; ++i) {
if (i == t42HeadTable) {
dumpString(headData, outputFunc, outputStream);
} else if (i == t42LocaTable) {
length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
dumpString(locaData, outputFunc, outputStream);
} else if (i == t42GlyfTable) {
glyfPos = tables[seekTable("glyf")].offset;
for (j = 0; j < nGlyphs; ++j) {
if (locaTable[j].len > 0 && checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
dumpString(std::span(file + glyfPos + locaTable[j].origOffset, locaTable[j].len), outputFunc, outputStream);
}
}
} else {
// length == 0 means the table is missing and the error was
// already reported during the construction of the table
// headers
if ((length = newTables[i].len) > 0) {
if ((j = seekTable(t42Tables[i].tag)) >= 0 && checkRegion(tables[j].offset, tables[j].len)) {
dumpString(std::span(file + tables[j].offset, tables[j].len), outputFunc, outputStream);
} else if (needVerticalMetrics && i == t42VheaTable) {
if (unlikely(length > (int)sizeof(vheaTab))) {
error(errSyntaxWarning, -1, "length bigger than vheaTab size");
length = sizeof(vheaTab);
}
dumpString(vheaTab, outputFunc, outputStream);
} else if (needVerticalMetrics && i == t42VmtxTable) {
if (unlikely(length > (int)vmtxTab.size())) {
error(errSyntaxWarning, -1, "length bigger than vmtxTab size");
}
dumpString(vmtxTab, outputFunc, outputStream);
}
}
}
}
// end the sfnts array
(*outputFunc)(outputStream, "] def\n", 6);
}
void FoFiTrueType::dumpString(std::span<const unsigned char> s, FoFiOutputFunc outputFunc, void *outputStream)
{
(*outputFunc)(outputStream, "<", 1);
for (int i = 0; i < (int)s.size(); i += 32) {
for (int j = 0; j < 32 && i + j < (int)s.size(); ++j) {
const std::string buf = GooString::format("{0:02x}", s[i + j] & 0xff);
(*outputFunc)(outputStream, buf.c_str(), buf.size());
}
if (i % (65536 - 32) == 65536 - 64) {
(*outputFunc)(outputStream, ">\n<", 3);
} else if (i + 32 < (int)s.size()) {
(*outputFunc)(outputStream, "\n", 1);
}
}
if (s.size() & 3) {
int pad = 4 - (s.size() & 3);
for (int i = 0; i < pad; ++i) {
(*outputFunc)(outputStream, "00", 2);
}
}
// add an extra zero byte because the Adobe Type 42 spec says so
(*outputFunc)(outputStream, "00>\n", 4);
}
unsigned int FoFiTrueType::computeTableChecksum(std::span<const unsigned char> data)
{
unsigned checksum = 0;
for (size_t i = 0; i + 3 < data.size(); i += 4) {
unsigned int word = ((data[i] & 0xff) << 24) + ((data[i + 1] & 0xff) << 16) + ((data[i + 2] & 0xff) << 8) + (data[i + 3] & 0xff);
checksum += word;
}
if (data.size() & 3) {
unsigned int word = 0;
int i = data.size() & ~3;
switch (data.size() & 3) {
case 3:
word |= (data[i + 2] & 0xff) << 8;
// fallthrough
case 2:
word |= (data[i + 1] & 0xff) << 16;
// fallthrough
case 1:
word |= (data[i] & 0xff) << 24;
break;
}
checksum += word;
}
return checksum;
}
void FoFiTrueType::parse()
{
unsigned int topTag;
int pos, ver, i, j;
parsedOk = true;
// look for a collection (TTC)
topTag = getU32BE(0, &parsedOk);
if (!parsedOk) {
return;
}
if (topTag == ttcfTag) {
/* TTC font */
int dircount;
dircount = getU32BE(8, &parsedOk);
if (!parsedOk) {
return;
}
if (!dircount) {
parsedOk = false;
return;
}
if (faceIndex >= dircount) {
faceIndex = 0;
}
pos = getU32BE(12 + faceIndex * 4, &parsedOk);
if (!parsedOk) {
return;
}
} else {
pos = 0;
}
// check the sfnt version
ver = getU32BE(pos, &parsedOk);
if (!parsedOk) {
return;
}
openTypeCFF = ver == 0x4f54544f; // 'OTTO'
// read the table directory
int nTables = getU16BE(pos + 4, &parsedOk);
if (!parsedOk) {
return;
}
tables.resize(nTables);
pos += 12;
j = 0;
for (i = 0; i < nTables; ++i) {
tables[j].tag = getU32BE(pos, &parsedOk);
tables[j].checksum = getU32BE(pos + 4, &parsedOk);
tables[j].offset = (int)getU32BE(pos + 8, &parsedOk);
tables[j].len = (int)getU32BE(pos + 12, &parsedOk);
if (unlikely((tables[j].offset < 0) || (tables[j].len < 0) || (tables[j].offset < INT_MAX - tables[j].len) || (tables[j].len > INT_MAX - tables[j].offset)
|| (tables[j].offset + tables[j].len >= tables[j].offset && tables[j].offset + tables[j].len <= len))) {
// ignore any bogus entries in the table directory
++j;
}
pos += 16;
}
if (nTables != j) {
nTables = j;
tables.resize(nTables);
}
if (!parsedOk || tables.empty()) {
parsedOk = false;
return;
}
// check for tables that are required by both the TrueType spec and
// the Type 42 spec
if (seekTable("head") < 0 || seekTable("hhea") < 0 || seekTable("maxp") < 0 || (!openTypeCFF && seekTable("loca") < 0) || (!openTypeCFF && seekTable("glyf") < 0) || (openTypeCFF && (seekTable("CFF ") < 0 && seekTable("CFF2") < 0))) {
parsedOk = false;
return;
}
// read the cmaps
if ((i = seekTable("cmap")) >= 0) {
pos = tables[i].offset + 2;
int nCmaps = getU16BE(pos, &parsedOk);
pos += 2;
if (!parsedOk) {
return;
}
cmaps.resize(nCmaps);
for (j = 0; j < nCmaps; ++j) {
cmaps[j].platform = getU16BE(pos, &parsedOk);
cmaps[j].encoding = getU16BE(pos + 2, &parsedOk);
cmaps[j].offset = tables[i].offset + getU32BE(pos + 4, &parsedOk);
pos += 8;
cmaps[j].fmt = getU16BE(cmaps[j].offset, &parsedOk);
cmaps[j].len = getU16BE(cmaps[j].offset + 2, &parsedOk);
}
if (!parsedOk) {
cmaps.clear();
return;
}
} else {
cmaps.clear();
}
// get the number of glyphs from the maxp table
i = seekTable("maxp");
nGlyphs = getU16BE(tables[i].offset + 4, &parsedOk);
if (!parsedOk) {
return;
}
// get the bbox and loca table format from the head table
i = seekTable("head");
bbox[0] = getS16BE(tables[i].offset + 36, &parsedOk);
bbox[1] = getS16BE(tables[i].offset + 38, &parsedOk);
bbox[2] = getS16BE(tables[i].offset + 40, &parsedOk);
bbox[3] = getS16BE(tables[i].offset + 42, &parsedOk);
locaFmt = getS16BE(tables[i].offset + 50, &parsedOk);
if (!parsedOk) {
return;
}
// read the post table
readPostTable();
}
void FoFiTrueType::readPostTable()
{
std::string name;
int tablePos, postFmt, stringIdx, stringPos;
bool ok;
int i, j, n, m;
ok = true;
if ((i = seekTable("post")) < 0) {
return;
}
tablePos = tables[i].offset;
postFmt = getU32BE(tablePos, &ok);
if (!ok) {
goto err;
}
if (postFmt == 0x00010000) {
nameToGID.reserve(258);
for (i = 0; i < 258; ++i) {
nameToGID.emplace(macGlyphNames[i], i);
}
} else if (postFmt == 0x00020000) {
nameToGID.reserve(258);
n = getU16BE(tablePos + 32, &ok);
if (!ok) {
goto err;
}
if (n > nGlyphs) {
n = nGlyphs;
}
stringIdx = 0;
stringPos = tablePos + 34 + 2 * n;
for (i = 0; i < n; ++i) {
ok = true;
j = getU16BE(tablePos + 34 + 2 * i, &ok);
if (j < 258) {
nameToGID[macGlyphNames[j]] = i;
} else {
j -= 258;
if (j != stringIdx) {
for (stringIdx = 0, stringPos = tablePos + 34 + 2 * n; stringIdx < j; ++stringIdx, stringPos += 1 + getU8(stringPos, &ok)) {
;
}
if (!ok) {
continue;
}
}
m = getU8(stringPos, &ok);
if (!ok || !checkRegion(stringPos + 1, m)) {
continue;
}
name.assign((char *)&file[stringPos + 1], m);
nameToGID[name] = i;
++stringIdx;
stringPos += 1 + m;
}
}
} else if (postFmt == 0x00028000) {
nameToGID.reserve(258);
for (i = 0; i < nGlyphs; ++i) {
j = getU8(tablePos + 32 + i, &ok);
if (!ok) {
continue;
}
if (j < 258) {
nameToGID[macGlyphNames[j]] = i;
}
}
}
return;
err:
nameToGID.clear();
}
int FoFiTrueType::seekTable(const char *tag) const
{
unsigned int tagI;
tagI = ((tag[0] & 0xff) << 24) | ((tag[1] & 0xff) << 16) | ((tag[2] & 0xff) << 8) | (tag[3] & 0xff);
for (int i = 0; i < (int)tables.size(); ++i) {
if (tables[i].tag == tagI) {
return i;
}
}
return -1;
}
unsigned int FoFiTrueType::charToTag(const char *tagName)
{
int n = strlen(tagName);
unsigned int tag = 0;
int i;
if (n > 4) {
n = 4;
}
for (i = 0; i < n; i++) {
tag <<= 8;
tag |= tagName[i] & 0xff;
}
for (; i < 4; i++) {
tag <<= 8;
tag |= ' ';
}
return tag;
}
/*
setup GSUB table data
Only supporting vertical text substitution.
*/
int FoFiTrueType::setupGSUB(const char *scriptName)
{
return setupGSUB(scriptName, nullptr);
}
/*
setup GSUB table data
Only supporting vertical text substitution.
*/
int FoFiTrueType::setupGSUB(const char *scriptName, const char *languageName)
{
unsigned int gsubTable;
unsigned int i;
unsigned int scriptList, featureList;
unsigned int scriptCount;
unsigned int tag;
unsigned int scriptTable = 0;
unsigned int langSys;
unsigned int featureCount;
unsigned int featureIndex;
unsigned int ftable = 0;
unsigned int llist;
unsigned int scriptTag;
int x;
unsigned int pos;
if (scriptName == nullptr) {
gsubFeatureTable = 0;
return 0;
}
scriptTag = charToTag(scriptName);
/* read GSUB Header */
if ((x = seekTable("GSUB")) < 0) {
return 0; /* GSUB table not found */
}
gsubTable = tables[x].offset;
pos = gsubTable + 4;
scriptList = getU16BE(pos, &parsedOk);
pos += 2;
featureList = getU16BE(pos, &parsedOk);
pos += 2;
llist = getU16BE(pos, &parsedOk);
gsubLookupList = llist + gsubTable; /* change to offset from top of file */
/* read script list table */
pos = gsubTable + scriptList;
scriptCount = getU16BE(pos, &parsedOk);
pos += 2;
/* find script */
for (i = 0; i < scriptCount; i++) {
tag = getU32BE(pos, &parsedOk);
pos += 4;
scriptTable = getU16BE(pos, &parsedOk);
pos += 2;
if (tag == scriptTag) {
/* found */
break;
}
}
if (i >= scriptCount) {
/* not found */
return 0;
}
/* read script table */
/* use default language system */
pos = gsubTable + scriptList + scriptTable;
langSys = 0;
if (languageName) {
unsigned int langTag = charToTag(languageName);
unsigned int langCount = getU16BE(pos + 2, &parsedOk);
for (i = 0; i < langCount && langSys == 0; i++) {
tag = getU32BE(pos + 4 + i * (4 + 2), &parsedOk);
if (tag == langTag) {
langSys = getU16BE(pos + 4 + i * (4 + 2) + 4, &parsedOk);
}
}
}
if (langSys == 0) {
/* default language system */
langSys = getU16BE(pos, &parsedOk);
}
/* read LangSys table */
if (langSys == 0) {
/* no default LangSys */
return 0;
}
pos = gsubTable + scriptList + scriptTable + langSys + 2;
featureIndex = getU16BE(pos, &parsedOk); /* ReqFeatureIndex */
pos += 2;
if (featureIndex != 0xffff) {
unsigned int tpos;
/* read feature record */
tpos = gsubTable + featureList;
featureCount = getU16BE(tpos, &parsedOk);
tpos = gsubTable + featureList + 2 + featureIndex * (4 + 2);
tag = getU32BE(tpos, &parsedOk);
tpos += 4;
if (tag == vrt2Tag) {
/* vrt2 is preferred, overwrite vert */
ftable = getU16BE(tpos, &parsedOk);
/* convert to offset from file top */
gsubFeatureTable = ftable + gsubTable + featureList;
return 0;
} else if (tag == vertTag) {
ftable = getU16BE(tpos, &parsedOk);
}
}
featureCount = getU16BE(pos, &parsedOk);
pos += 2;
/* find 'vrt2' or 'vert' feature */
for (i = 0; i < featureCount; i++) {
unsigned int oldPos;
featureIndex = getU16BE(pos, &parsedOk);
pos += 2;
oldPos = pos; /* save position */
/* read feature record */
pos = gsubTable + featureList + 2 + featureIndex * (4 + 2);
tag = getU32BE(pos, &parsedOk);
pos += 4;
if (tag == vrt2Tag) {
/* vrt2 is preferred, overwrite vert */
ftable = getU16BE(pos, &parsedOk);
break;
} else if (ftable == 0 && tag == vertTag) {
ftable = getU16BE(pos, &parsedOk);
}
pos = oldPos; /* restore old position */
}
if (ftable == 0) {
/* vert nor vrt2 are not found */
return 0;
}
/* convert to offset from file top */
gsubFeatureTable = ftable + gsubTable + featureList;
return 0;
}
unsigned int FoFiTrueType::doMapToVertGID(unsigned int orgGID)
{
unsigned int lookupCount;
unsigned int lookupListIndex;
unsigned int i;
unsigned int gid = 0;
unsigned int pos;
pos = gsubFeatureTable + 2;
lookupCount = getU16BE(pos, &parsedOk);
pos += 2;
for (i = 0; i < lookupCount; i++) {
lookupListIndex = getU16BE(pos, &parsedOk);
pos += 2;
if ((gid = scanLookupList(lookupListIndex, orgGID)) != 0) {
break;
}
}
return gid;
}
unsigned int FoFiTrueType::mapToVertGID(unsigned int orgGID)
{
unsigned int mapped;
if (gsubFeatureTable == 0) {
return orgGID;
}
if ((mapped = doMapToVertGID(orgGID)) != 0) {
return mapped;
}
return orgGID;
}
unsigned int FoFiTrueType::scanLookupList(unsigned int listIndex, unsigned int orgGID)
{
unsigned int lookupTable;
unsigned int subTableCount;
unsigned int subTable;
unsigned int i;
unsigned int gid = 0;
unsigned int pos;
if (gsubLookupList == 0) {
return 0; /* no lookup list */
}
pos = gsubLookupList + 2 + listIndex * 2;
lookupTable = getU16BE(pos, &parsedOk);
/* read lookup table */
pos = gsubLookupList + lookupTable + 4;
subTableCount = getU16BE(pos, &parsedOk);
pos += 2;
;
for (i = 0; i < subTableCount; i++) {
subTable = getU16BE(pos, &parsedOk);
pos += 2;
if ((gid = scanLookupSubTable(gsubLookupList + lookupTable + subTable, orgGID)) != 0) {
break;
}
}
return gid;
}
unsigned int FoFiTrueType::scanLookupSubTable(unsigned int subTable, unsigned int orgGID)
{
unsigned int format;
unsigned int coverage;
int delta;
int glyphCount;
unsigned int substitute;
unsigned int gid = 0;
int coverageIndex;
int pos;
pos = subTable;
format = getU16BE(pos, &parsedOk);
pos += 2;
coverage = getU16BE(pos, &parsedOk);
pos += 2;
if ((coverageIndex = checkGIDInCoverage(subTable + coverage, orgGID)) >= 0) {
switch (format) {
case 1:
/* format 1 */
delta = getS16BE(pos, &parsedOk);
pos += 2;
gid = orgGID + delta;
break;
case 2:
/* format 2 */
glyphCount = getS16BE(pos, &parsedOk);
pos += 2;
if (glyphCount > coverageIndex) {
pos += coverageIndex * 2;
substitute = getU16BE(pos, &parsedOk);
gid = substitute;
}
break;
default:
/* unknown format */
break;
}
}
return gid;
}
int FoFiTrueType::checkGIDInCoverage(unsigned int coverage, unsigned int orgGID)
{
int index = -1;
unsigned int format;
unsigned int count;
unsigned int i;
unsigned int pos;
pos = coverage;
format = getU16BE(pos, &parsedOk);
pos += 2;
switch (format) {
case 1:
count = getU16BE(pos, &parsedOk);
pos += 2;
// In some poor CJK fonts, key GIDs are not sorted,
// thus we cannot finish checking even when the range
// including orgGID seems to have already passed.
for (i = 0; i < count; i++) {
unsigned int gid;
gid = getU16BE(pos, &parsedOk);
pos += 2;
if (gid == orgGID) {
/* found */
index = i;
break;
}
}
break;
case 2:
count = getU16BE(pos, &parsedOk);
pos += 2;
for (i = 0; i < count; i++) {
unsigned int startGID, endGID;
unsigned int startIndex;
startGID = getU16BE(pos, &parsedOk);
pos += 2;
endGID = getU16BE(pos, &parsedOk);
pos += 2;
startIndex = getU16BE(pos, &parsedOk);
pos += 2;
// In some poor CJK fonts, key GIDs are not sorted,
// thus we cannot finish checking even when the range
// including orgGID seems to have already passed.
if (startGID <= orgGID && orgGID <= endGID) {
/* found */
index = startIndex + orgGID - startGID;
break;
}
}
break;
default:
break;
}
return index;
}