Merge pull request #9282 from Snuffleupagus/TrueType-Collection
Add support for TrueType Collection fonts (issue 9262)
This commit is contained in:
commit
3925aab010
@ -14,8 +14,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bytesToString, FONT_IDENTITY_MATRIX, FontType, FormatError, info, isNum,
|
assert, bytesToString, FONT_IDENTITY_MATRIX, FontType, FormatError, info,
|
||||||
isSpace, MissingDataException, readUint32, shadow, string32, unreachable, warn
|
isNum, isSpace, MissingDataException, readUint32, shadow, string32,
|
||||||
|
unreachable, warn
|
||||||
} from '../shared/util';
|
} from '../shared/util';
|
||||||
import {
|
import {
|
||||||
CFF, CFFCharset, CFFCompiler, CFFHeader, CFFIndex, CFFParser, CFFPrivateDict,
|
CFF, CFFCharset, CFFCompiler, CFFHeader, CFFIndex, CFFParser, CFFPrivateDict,
|
||||||
@ -681,6 +682,11 @@ var Font = (function FontClosure() {
|
|||||||
return readUint32(header, 0) === 0x00010000;
|
return readUint32(header, 0) === 0x00010000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTrueTypeCollectionFile(file) {
|
||||||
|
let header = file.peekBytes(4);
|
||||||
|
return bytesToString(header) === 'ttcf';
|
||||||
|
}
|
||||||
|
|
||||||
function isOpenTypeFile(file) {
|
function isOpenTypeFile(file) {
|
||||||
var header = file.peekBytes(4);
|
var header = file.peekBytes(4);
|
||||||
return bytesToString(header) === 'OTTO';
|
return bytesToString(header) === 'OTTO';
|
||||||
@ -1294,6 +1300,33 @@ var Font = (function FontClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
checkAndRepair: function Font_checkAndRepair(name, font, properties) {
|
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) {
|
function readTableEntry(file) {
|
||||||
var tag = bytesToString(file.getBytes(4));
|
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
|
* Read the appropriate subtable from the cmap according to 9.6.6.4 from
|
||||||
* PDF spec
|
* PDF spec
|
||||||
@ -2189,34 +2284,16 @@ var Font = (function FontClosure() {
|
|||||||
// The following steps modify the original font data, making copy
|
// The following steps modify the original font data, making copy
|
||||||
font = new Stream(new Uint8Array(font.getBytes()));
|
font = new Stream(new Uint8Array(font.getBytes()));
|
||||||
|
|
||||||
var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
|
let header, tables;
|
||||||
'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
|
if (isTrueTypeCollectionFile(font)) {
|
||||||
|
let ttcData = readTrueTypeCollectionData(font, this.name);
|
||||||
var header = readOpenTypeHeader(font);
|
header = ttcData.header;
|
||||||
var numTables = header.numTables;
|
tables = ttcData.tables;
|
||||||
var cff, cffFile;
|
} else {
|
||||||
|
header = readOpenTypeHeader(font);
|
||||||
var tables = Object.create(null);
|
tables = readTables(font, header.numTables);
|
||||||
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 cff, cffFile;
|
||||||
|
|
||||||
var isTrueType = !tables['CFF '];
|
var isTrueType = !tables['CFF '];
|
||||||
if (!isTrueType) {
|
if (!isTrueType) {
|
||||||
@ -2444,7 +2521,7 @@ var Font = (function FontClosure() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var found = false;
|
var found = false;
|
||||||
for (i = 0; i < cmapMappingsLength; ++i) {
|
for (let i = 0; i < cmapMappingsLength; ++i) {
|
||||||
if (cmapMappings[i].charCode !== unicodeOrCharCode) {
|
if (cmapMappings[i].charCode !== unicodeOrCharCode) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -2467,7 +2544,7 @@ var Font = (function FontClosure() {
|
|||||||
}
|
}
|
||||||
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
|
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
|
||||||
// Default Unicode semantics, use the charcodes as is.
|
// 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] =
|
charCodeToGlyphId[cmapMappings[i].charCode] =
|
||||||
cmapMappings[i].glyphId;
|
cmapMappings[i].glyphId;
|
||||||
}
|
}
|
||||||
@ -2483,7 +2560,7 @@ var Font = (function FontClosure() {
|
|||||||
// special range since some PDFs have char codes outside of this range
|
// special range since some PDFs have char codes outside of this range
|
||||||
// (e.g. 0x2013) which when masked would overwrite other values in the
|
// (e.g. 0x2013) which when masked would overwrite other values in the
|
||||||
// cmap.
|
// cmap.
|
||||||
for (i = 0; i < cmapMappingsLength; ++i) {
|
for (let i = 0; i < cmapMappingsLength; ++i) {
|
||||||
charCode = cmapMappings[i].charCode;
|
charCode = cmapMappings[i].charCode;
|
||||||
if (cmapPlatformId === 3 &&
|
if (cmapPlatformId === 3 &&
|
||||||
charCode >= 0xF000 && charCode <= 0xF0FF) {
|
charCode >= 0xF000 && charCode <= 0xF0FF) {
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -65,6 +65,7 @@
|
|||||||
!issue8823.pdf
|
!issue8823.pdf
|
||||||
!issue9084.pdf
|
!issue9084.pdf
|
||||||
!issue9105_reduced.pdf
|
!issue9105_reduced.pdf
|
||||||
|
!issue9262_reduced.pdf
|
||||||
!issue9291.pdf
|
!issue9291.pdf
|
||||||
!bad-PageLabels.pdf
|
!bad-PageLabels.pdf
|
||||||
!decodeACSuccessive.pdf
|
!decodeACSuccessive.pdf
|
||||||
|
BIN
test/pdfs/issue9262_reduced.pdf
Normal file
BIN
test/pdfs/issue9262_reduced.pdf
Normal file
Binary file not shown.
@ -734,6 +734,13 @@
|
|||||||
"lastPage": 1,
|
"lastPage": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "issue9262",
|
||||||
|
"file": "pdfs/issue9262_reduced.pdf",
|
||||||
|
"md5": "5347ce2d7b3866625c22e115fd90e0de",
|
||||||
|
"rounds": 1,
|
||||||
|
"link": false,
|
||||||
|
"type": "eq"
|
||||||
|
},
|
||||||
{ "id": "issue8707",
|
{ "id": "issue8707",
|
||||||
"file": "pdfs/issue8707.pdf",
|
"file": "pdfs/issue8707.pdf",
|
||||||
"md5": "d3dc670adde9ec9fb82c974027033029",
|
"md5": "d3dc670adde9ec9fb82c974027033029",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user