Add support for TrueType Collection fonts (issue 9262)

The specification can be found at https://www.microsoft.com/typography/otspec/otff.htm, under the "Font Collections" heading.

Fixes 9262.
This commit is contained in:
Jonas Jenwald 2017-12-15 00:23:56 +01:00
parent a3b2456c7a
commit d6c028b946
4 changed files with 117 additions and 32 deletions

View File

@ -14,8 +14,9 @@
*/
import {
bytesToString, FONT_IDENTITY_MATRIX, FontType, FormatError, info, isNum,
isSpace, MissingDataException, readUint32, shadow, string32, unreachable, warn
assert, bytesToString, FONT_IDENTITY_MATRIX, FontType, FormatError, info,
isNum, isSpace, MissingDataException, readUint32, shadow, string32,
unreachable, warn
} from '../shared/util';
import {
CFF, CFFCharset, CFFCompiler, CFFHeader, CFFIndex, CFFParser, CFFPrivateDict,
@ -681,6 +682,11 @@ var Font = (function FontClosure() {
return readUint32(header, 0) === 0x00010000;
}
function isTrueTypeCollectionFile(file) {
let header = file.peekBytes(4);
return bytesToString(header) === 'ttcf';
}
function isOpenTypeFile(file) {
var header = file.peekBytes(4);
return bytesToString(header) === 'OTTO';
@ -1294,6 +1300,33 @@ var Font = (function FontClosure() {
},
checkAndRepair: function Font_checkAndRepair(name, font, properties) {
const VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
function readTables(file, numTables) {
let tables = Object.create(null);
tables['OS/2'] = null;
tables['cmap'] = null;
tables['head'] = null;
tables['hhea'] = null;
tables['hmtx'] = null;
tables['maxp'] = null;
tables['name'] = null;
tables['post'] = null;
for (let i = 0; i < numTables; i++) {
let table = readTableEntry(font);
if (VALID_TABLES.indexOf(table.tag) < 0) {
continue; // skipping table if it's not a required or optional table
}
if (table.length === 0) {
continue; // skipping empty tables
}
tables[table.tag] = table;
}
return tables;
}
function readTableEntry(file) {
var tag = bytesToString(file.getBytes(4));
@ -1333,6 +1366,68 @@ var Font = (function FontClosure() {
};
}
function readTrueTypeCollectionHeader(ttc) {
let ttcTag = bytesToString(ttc.getBytes(4));
assert(ttcTag === 'ttcf', 'Must be a TrueType Collection font.');
let majorVersion = ttc.getUint16();
let minorVersion = ttc.getUint16();
let numFonts = ttc.getInt32() >>> 0;
let offsetTable = [];
for (let i = 0; i < numFonts; i++) {
offsetTable.push(ttc.getInt32() >>> 0);
}
let header = {
ttcTag,
majorVersion,
minorVersion,
numFonts,
offsetTable,
};
switch (majorVersion) {
case 1:
return header;
case 2:
header.dsigTag = ttc.getInt32() >>> 0;
header.dsigLength = ttc.getInt32() >>> 0;
header.dsigOffset = ttc.getInt32() >>> 0;
return header;
}
throw new FormatError(
`Invalid TrueType Collection majorVersion: ${majorVersion}.`);
}
function readTrueTypeCollectionData(ttc, fontName) {
let { numFonts, offsetTable, } = readTrueTypeCollectionHeader(ttc);
for (let i = 0; i < numFonts; i++) {
ttc.pos = (ttc.start || 0) + offsetTable[i];
let potentialHeader = readOpenTypeHeader(ttc);
let potentialTables = readTables(ttc, potentialHeader.numTables);
if (!potentialTables['name']) {
throw new FormatError(
'TrueType Collection font must contain a "name" table.');
}
let nameTable = readNameTable(potentialTables['name']);
for (let j = 0, jj = nameTable.length; j < jj; j++) {
for (let k = 0, kk = nameTable[j].length; k < kk; k++) {
let nameEntry = nameTable[j][k];
if (nameEntry && nameEntry.replace(/\s/g, '') === fontName) {
return {
header: potentialHeader,
tables: potentialTables,
};
}
}
}
}
throw new FormatError(
`TrueType Collection does not contain "${fontName}" font.`);
}
/**
* Read the appropriate subtable from the cmap according to 9.6.6.4 from
* PDF spec
@ -2189,34 +2284,16 @@ var Font = (function FontClosure() {
// The following steps modify the original font data, making copy
font = new Stream(new Uint8Array(font.getBytes()));
var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
var header = readOpenTypeHeader(font);
var numTables = header.numTables;
var cff, cffFile;
var tables = Object.create(null);
tables['OS/2'] = null;
tables['cmap'] = null;
tables['head'] = null;
tables['hhea'] = null;
tables['hmtx'] = null;
tables['maxp'] = null;
tables['name'] = null;
tables['post'] = null;
var table;
for (var i = 0; i < numTables; i++) {
table = readTableEntry(font);
if (VALID_TABLES.indexOf(table.tag) < 0) {
continue; // skipping table if it's not a required or optional table
}
if (table.length === 0) {
continue; // skipping empty tables
}
tables[table.tag] = table;
let header, tables;
if (isTrueTypeCollectionFile(font)) {
let ttcData = readTrueTypeCollectionData(font, this.name);
header = ttcData.header;
tables = ttcData.tables;
} else {
header = readOpenTypeHeader(font);
tables = readTables(font, header.numTables);
}
let cff, cffFile;
var isTrueType = !tables['CFF '];
if (!isTrueType) {
@ -2444,7 +2521,7 @@ var Font = (function FontClosure() {
}
var found = false;
for (i = 0; i < cmapMappingsLength; ++i) {
for (let i = 0; i < cmapMappingsLength; ++i) {
if (cmapMappings[i].charCode !== unicodeOrCharCode) {
continue;
}
@ -2467,7 +2544,7 @@ var Font = (function FontClosure() {
}
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
// Default Unicode semantics, use the charcodes as is.
for (i = 0; i < cmapMappingsLength; ++i) {
for (let i = 0; i < cmapMappingsLength; ++i) {
charCodeToGlyphId[cmapMappings[i].charCode] =
cmapMappings[i].glyphId;
}
@ -2483,7 +2560,7 @@ var Font = (function FontClosure() {
// special range since some PDFs have char codes outside of this range
// (e.g. 0x2013) which when masked would overwrite other values in the
// cmap.
for (i = 0; i < cmapMappingsLength; ++i) {
for (let i = 0; i < cmapMappingsLength; ++i) {
charCode = cmapMappings[i].charCode;
if (cmapPlatformId === 3 &&
charCode >= 0xF000 && charCode <= 0xF0FF) {

View File

@ -65,6 +65,7 @@
!issue8823.pdf
!issue9084.pdf
!issue9105_reduced.pdf
!issue9262_reduced.pdf
!issue9291.pdf
!bad-PageLabels.pdf
!decodeACSuccessive.pdf

Binary file not shown.

View File

@ -734,6 +734,13 @@
"lastPage": 1,
"type": "eq"
},
{ "id": "issue9262",
"file": "pdfs/issue9262_reduced.pdf",
"md5": "5347ce2d7b3866625c22e115fd90e0de",
"rounds": 1,
"link": false,
"type": "eq"
},
{ "id": "issue8707",
"file": "pdfs/issue8707.pdf",
"md5": "d3dc670adde9ec9fb82c974027033029",