From a783c7ca79956af848724de922a31f72ea18bcbe Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 11:47:56 +0200 Subject: [PATCH 01/18] Move the `OpenTypeFileBuilder` from `src/core/fonts.js` and into its own file --- src/core/fonts.js | 145 +------------------------- src/core/opentype_file_builder.js | 164 ++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 144 deletions(-) create mode 100644 src/core/opentype_file_builder.js diff --git a/src/core/fonts.js b/src/core/fonts.js index 111de58db..3da8eabb2 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -66,6 +66,7 @@ import { } from "./core_utils.js"; import { FontRendererFactory } from "./font_renderer.js"; import { IdentityCMap } from "./cmap.js"; +import { OpenTypeFileBuilder } from "./opentype_file_builder.js"; import { Stream } from "./stream.js"; import { Type1Parser } from "./type1_parser.js"; @@ -408,150 +409,6 @@ var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { return IdentityToUnicodeMap; })(); -var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { - function writeInt16(dest, offset, num) { - dest[offset] = (num >> 8) & 0xff; - dest[offset + 1] = num & 0xff; - } - - function writeInt32(dest, offset, num) { - dest[offset] = (num >> 24) & 0xff; - dest[offset + 1] = (num >> 16) & 0xff; - dest[offset + 2] = (num >> 8) & 0xff; - dest[offset + 3] = num & 0xff; - } - - function writeData(dest, offset, data) { - var i, ii; - if (data instanceof Uint8Array) { - dest.set(data, offset); - } else if (typeof data === "string") { - for (i = 0, ii = data.length; i < ii; i++) { - dest[offset++] = data.charCodeAt(i) & 0xff; - } - } else { - // treating everything else as array - for (i = 0, ii = data.length; i < ii; i++) { - dest[offset++] = data[i] & 0xff; - } - } - } - - // eslint-disable-next-line no-shadow - function OpenTypeFileBuilder(sfnt) { - this.sfnt = sfnt; - this.tables = Object.create(null); - } - - OpenTypeFileBuilder.getSearchParams = function OpenTypeFileBuilder_getSearchParams( - entriesCount, - entrySize - ) { - var maxPower2 = 1, - log2 = 0; - while ((maxPower2 ^ entriesCount) > maxPower2) { - maxPower2 <<= 1; - log2++; - } - var searchRange = maxPower2 * entrySize; - return { - range: searchRange, - entry: log2, - rangeShift: entrySize * entriesCount - searchRange, - }; - }; - - var OTF_HEADER_SIZE = 12; - var OTF_TABLE_ENTRY_SIZE = 16; - - OpenTypeFileBuilder.prototype = { - toArray: function OpenTypeFileBuilder_toArray() { - var sfnt = this.sfnt; - - // Tables needs to be written by ascendant alphabetic order - var tables = this.tables; - var tablesNames = Object.keys(tables); - tablesNames.sort(); - var numTables = tablesNames.length; - - var i, j, jj, table, tableName; - // layout the tables data - var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; - var tableOffsets = [offset]; - for (i = 0; i < numTables; i++) { - table = tables[tablesNames[i]]; - var paddedLength = ((table.length + 3) & ~3) >>> 0; - offset += paddedLength; - tableOffsets.push(offset); - } - - var file = new Uint8Array(offset); - // write the table data first (mostly for checksum) - for (i = 0; i < numTables; i++) { - table = tables[tablesNames[i]]; - writeData(file, tableOffsets[i], table); - } - - // sfnt version (4 bytes) - if (sfnt === "true") { - // Windows hates the Mac TrueType sfnt version number - sfnt = string32(0x00010000); - } - file[0] = sfnt.charCodeAt(0) & 0xff; - file[1] = sfnt.charCodeAt(1) & 0xff; - file[2] = sfnt.charCodeAt(2) & 0xff; - file[3] = sfnt.charCodeAt(3) & 0xff; - - // numTables (2 bytes) - writeInt16(file, 4, numTables); - - var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); - - // searchRange (2 bytes) - writeInt16(file, 6, searchParams.range); - // entrySelector (2 bytes) - writeInt16(file, 8, searchParams.entry); - // rangeShift (2 bytes) - writeInt16(file, 10, searchParams.rangeShift); - - offset = OTF_HEADER_SIZE; - // writing table entries - for (i = 0; i < numTables; i++) { - tableName = tablesNames[i]; - file[offset] = tableName.charCodeAt(0) & 0xff; - file[offset + 1] = tableName.charCodeAt(1) & 0xff; - file[offset + 2] = tableName.charCodeAt(2) & 0xff; - file[offset + 3] = tableName.charCodeAt(3) & 0xff; - - // checksum - var checksum = 0; - for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { - var quad = readUint32(file, j); - checksum = (checksum + quad) >>> 0; - } - writeInt32(file, offset + 4, checksum); - - // offset - writeInt32(file, offset + 8, tableOffsets[i]); - // length - writeInt32(file, offset + 12, tables[tableName].length); - - offset += OTF_TABLE_ENTRY_SIZE; - } - return file; - }, - - addTable: function OpenTypeFileBuilder_addTable(tag, data) { - if (tag in this.tables) { - throw new Error("Table " + tag + " already exists"); - } - this.tables[tag] = data; - }, - }; - - return OpenTypeFileBuilder; -})(); - /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). diff --git a/src/core/opentype_file_builder.js b/src/core/opentype_file_builder.js new file mode 100644 index 000000000..95cdcd5bf --- /dev/null +++ b/src/core/opentype_file_builder.js @@ -0,0 +1,164 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-var */ + +import { readUint32 } from "./core_utils.js"; +import { string32 } from "../shared/util.js"; + +var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { + function writeInt16(dest, offset, num) { + dest[offset] = (num >> 8) & 0xff; + dest[offset + 1] = num & 0xff; + } + + function writeInt32(dest, offset, num) { + dest[offset] = (num >> 24) & 0xff; + dest[offset + 1] = (num >> 16) & 0xff; + dest[offset + 2] = (num >> 8) & 0xff; + dest[offset + 3] = num & 0xff; + } + + function writeData(dest, offset, data) { + var i, ii; + if (data instanceof Uint8Array) { + dest.set(data, offset); + } else if (typeof data === "string") { + for (i = 0, ii = data.length; i < ii; i++) { + dest[offset++] = data.charCodeAt(i) & 0xff; + } + } else { + // treating everything else as array + for (i = 0, ii = data.length; i < ii; i++) { + dest[offset++] = data[i] & 0xff; + } + } + } + + // eslint-disable-next-line no-shadow + function OpenTypeFileBuilder(sfnt) { + this.sfnt = sfnt; + this.tables = Object.create(null); + } + + OpenTypeFileBuilder.getSearchParams = function OpenTypeFileBuilder_getSearchParams( + entriesCount, + entrySize + ) { + var maxPower2 = 1, + log2 = 0; + while ((maxPower2 ^ entriesCount) > maxPower2) { + maxPower2 <<= 1; + log2++; + } + var searchRange = maxPower2 * entrySize; + return { + range: searchRange, + entry: log2, + rangeShift: entrySize * entriesCount - searchRange, + }; + }; + + var OTF_HEADER_SIZE = 12; + var OTF_TABLE_ENTRY_SIZE = 16; + + OpenTypeFileBuilder.prototype = { + toArray: function OpenTypeFileBuilder_toArray() { + var sfnt = this.sfnt; + + // Tables needs to be written by ascendant alphabetic order + var tables = this.tables; + var tablesNames = Object.keys(tables); + tablesNames.sort(); + var numTables = tablesNames.length; + + var i, j, jj, table, tableName; + // layout the tables data + var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; + var tableOffsets = [offset]; + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + var paddedLength = ((table.length + 3) & ~3) >>> 0; + offset += paddedLength; + tableOffsets.push(offset); + } + + var file = new Uint8Array(offset); + // write the table data first (mostly for checksum) + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + writeData(file, tableOffsets[i], table); + } + + // sfnt version (4 bytes) + if (sfnt === "true") { + // Windows hates the Mac TrueType sfnt version number + sfnt = string32(0x00010000); + } + file[0] = sfnt.charCodeAt(0) & 0xff; + file[1] = sfnt.charCodeAt(1) & 0xff; + file[2] = sfnt.charCodeAt(2) & 0xff; + file[3] = sfnt.charCodeAt(3) & 0xff; + + // numTables (2 bytes) + writeInt16(file, 4, numTables); + + var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); + + // searchRange (2 bytes) + writeInt16(file, 6, searchParams.range); + // entrySelector (2 bytes) + writeInt16(file, 8, searchParams.entry); + // rangeShift (2 bytes) + writeInt16(file, 10, searchParams.rangeShift); + + offset = OTF_HEADER_SIZE; + // writing table entries + for (i = 0; i < numTables; i++) { + tableName = tablesNames[i]; + file[offset] = tableName.charCodeAt(0) & 0xff; + file[offset + 1] = tableName.charCodeAt(1) & 0xff; + file[offset + 2] = tableName.charCodeAt(2) & 0xff; + file[offset + 3] = tableName.charCodeAt(3) & 0xff; + + // checksum + var checksum = 0; + for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { + var quad = readUint32(file, j); + checksum = (checksum + quad) >>> 0; + } + writeInt32(file, offset + 4, checksum); + + // offset + writeInt32(file, offset + 8, tableOffsets[i]); + // length + writeInt32(file, offset + 12, tables[tableName].length); + + offset += OTF_TABLE_ENTRY_SIZE; + } + return file; + }, + + addTable: function OpenTypeFileBuilder_addTable(tag, data) { + if (tag in this.tables) { + throw new Error("Table " + tag + " already exists"); + } + this.tables[tag] = data; + }, + }; + + return OpenTypeFileBuilder; +})(); + +export { OpenTypeFileBuilder }; From 1808b2dc96cc09af035e38ef3273c914b499f66f Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 11:50:06 +0200 Subject: [PATCH 02/18] Enable the `no-var` rule in the `src/core/opentype_file_builder.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/opentype_file_builder.js | 37 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/core/opentype_file_builder.js b/src/core/opentype_file_builder.js index 95cdcd5bf..1cf0ac811 100644 --- a/src/core/opentype_file_builder.js +++ b/src/core/opentype_file_builder.js @@ -12,12 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { readUint32 } from "./core_utils.js"; import { string32 } from "../shared/util.js"; -var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { +const OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { function writeInt16(dest, offset, num) { dest[offset] = (num >> 8) & 0xff; dest[offset + 1] = num & 0xff; @@ -31,7 +30,7 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { } function writeData(dest, offset, data) { - var i, ii; + let i, ii; if (data instanceof Uint8Array) { dest.set(data, offset); } else if (typeof data === "string") { @@ -56,13 +55,13 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { entriesCount, entrySize ) { - var maxPower2 = 1, + let maxPower2 = 1, log2 = 0; while ((maxPower2 ^ entriesCount) > maxPower2) { maxPower2 <<= 1; log2++; } - var searchRange = maxPower2 * entrySize; + const searchRange = maxPower2 * entrySize; return { range: searchRange, entry: log2, @@ -70,31 +69,31 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { }; }; - var OTF_HEADER_SIZE = 12; - var OTF_TABLE_ENTRY_SIZE = 16; + const OTF_HEADER_SIZE = 12; + const OTF_TABLE_ENTRY_SIZE = 16; OpenTypeFileBuilder.prototype = { toArray: function OpenTypeFileBuilder_toArray() { - var sfnt = this.sfnt; + let sfnt = this.sfnt; // Tables needs to be written by ascendant alphabetic order - var tables = this.tables; - var tablesNames = Object.keys(tables); + const tables = this.tables; + const tablesNames = Object.keys(tables); tablesNames.sort(); - var numTables = tablesNames.length; + const numTables = tablesNames.length; - var i, j, jj, table, tableName; + let i, j, jj, table, tableName; // layout the tables data - var offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; - var tableOffsets = [offset]; + let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; + const tableOffsets = [offset]; for (i = 0; i < numTables; i++) { table = tables[tablesNames[i]]; - var paddedLength = ((table.length + 3) & ~3) >>> 0; + const paddedLength = ((table.length + 3) & ~3) >>> 0; offset += paddedLength; tableOffsets.push(offset); } - var file = new Uint8Array(offset); + const file = new Uint8Array(offset); // write the table data first (mostly for checksum) for (i = 0; i < numTables; i++) { table = tables[tablesNames[i]]; @@ -114,7 +113,7 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { // numTables (2 bytes) writeInt16(file, 4, numTables); - var searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); + const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); // searchRange (2 bytes) writeInt16(file, 6, searchParams.range); @@ -133,9 +132,9 @@ var OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { file[offset + 3] = tableName.charCodeAt(3) & 0xff; // checksum - var checksum = 0; + let checksum = 0; for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { - var quad = readUint32(file, j); + const quad = readUint32(file, j); checksum = (checksum + quad) >>> 0; } writeInt32(file, offset + 4, checksum); From 8c1d1a58f7b8dacfea5fc77a50183b1a7868c0ff Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 11:54:05 +0200 Subject: [PATCH 03/18] Convert `src/core/opentype_file_builder.js` to use standard classes --- src/core/opentype_file_builder.js | 219 ++++++++++++++---------------- 1 file changed, 105 insertions(+), 114 deletions(-) diff --git a/src/core/opentype_file_builder.js b/src/core/opentype_file_builder.js index 1cf0ac811..9405ed40d 100644 --- a/src/core/opentype_file_builder.js +++ b/src/core/opentype_file_builder.js @@ -16,45 +16,43 @@ import { readUint32 } from "./core_utils.js"; import { string32 } from "../shared/util.js"; -const OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { - function writeInt16(dest, offset, num) { - dest[offset] = (num >> 8) & 0xff; - dest[offset + 1] = num & 0xff; - } +function writeInt16(dest, offset, num) { + dest[offset] = (num >> 8) & 0xff; + dest[offset + 1] = num & 0xff; +} - function writeInt32(dest, offset, num) { - dest[offset] = (num >> 24) & 0xff; - dest[offset + 1] = (num >> 16) & 0xff; - dest[offset + 2] = (num >> 8) & 0xff; - dest[offset + 3] = num & 0xff; - } +function writeInt32(dest, offset, num) { + dest[offset] = (num >> 24) & 0xff; + dest[offset + 1] = (num >> 16) & 0xff; + dest[offset + 2] = (num >> 8) & 0xff; + dest[offset + 3] = num & 0xff; +} - function writeData(dest, offset, data) { - let i, ii; - if (data instanceof Uint8Array) { - dest.set(data, offset); - } else if (typeof data === "string") { - for (i = 0, ii = data.length; i < ii; i++) { - dest[offset++] = data.charCodeAt(i) & 0xff; - } - } else { - // treating everything else as array - for (i = 0, ii = data.length; i < ii; i++) { - dest[offset++] = data[i] & 0xff; - } +function writeData(dest, offset, data) { + if (data instanceof Uint8Array) { + dest.set(data, offset); + } else if (typeof data === "string") { + for (let i = 0, ii = data.length; i < ii; i++) { + dest[offset++] = data.charCodeAt(i) & 0xff; + } + } else { + // treating everything else as array + for (let i = 0, ii = data.length; i < ii; i++) { + dest[offset++] = data[i] & 0xff; } } +} - // eslint-disable-next-line no-shadow - function OpenTypeFileBuilder(sfnt) { +const OTF_HEADER_SIZE = 12; +const OTF_TABLE_ENTRY_SIZE = 16; + +class OpenTypeFileBuilder { + constructor(sfnt) { this.sfnt = sfnt; this.tables = Object.create(null); } - OpenTypeFileBuilder.getSearchParams = function OpenTypeFileBuilder_getSearchParams( - entriesCount, - entrySize - ) { + static getSearchParams(entriesCount, entrySize) { let maxPower2 = 1, log2 = 0; while ((maxPower2 ^ entriesCount) > maxPower2) { @@ -67,97 +65,90 @@ const OpenTypeFileBuilder = (function OpenTypeFileBuilderClosure() { entry: log2, rangeShift: entrySize * entriesCount - searchRange, }; - }; + } - const OTF_HEADER_SIZE = 12; - const OTF_TABLE_ENTRY_SIZE = 16; + toArray() { + let sfnt = this.sfnt; - OpenTypeFileBuilder.prototype = { - toArray: function OpenTypeFileBuilder_toArray() { - let sfnt = this.sfnt; + // Tables needs to be written by ascendant alphabetic order + const tables = this.tables; + const tablesNames = Object.keys(tables); + tablesNames.sort(); + const numTables = tablesNames.length; - // Tables needs to be written by ascendant alphabetic order - const tables = this.tables; - const tablesNames = Object.keys(tables); - tablesNames.sort(); - const numTables = tablesNames.length; + let i, j, jj, table, tableName; + // layout the tables data + let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; + const tableOffsets = [offset]; + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + const paddedLength = ((table.length + 3) & ~3) >>> 0; + offset += paddedLength; + tableOffsets.push(offset); + } - let i, j, jj, table, tableName; - // layout the tables data - let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE; - const tableOffsets = [offset]; - for (i = 0; i < numTables; i++) { - table = tables[tablesNames[i]]; - const paddedLength = ((table.length + 3) & ~3) >>> 0; - offset += paddedLength; - tableOffsets.push(offset); + const file = new Uint8Array(offset); + // write the table data first (mostly for checksum) + for (i = 0; i < numTables; i++) { + table = tables[tablesNames[i]]; + writeData(file, tableOffsets[i], table); + } + + // sfnt version (4 bytes) + if (sfnt === "true") { + // Windows hates the Mac TrueType sfnt version number + sfnt = string32(0x00010000); + } + file[0] = sfnt.charCodeAt(0) & 0xff; + file[1] = sfnt.charCodeAt(1) & 0xff; + file[2] = sfnt.charCodeAt(2) & 0xff; + file[3] = sfnt.charCodeAt(3) & 0xff; + + // numTables (2 bytes) + writeInt16(file, 4, numTables); + + const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); + + // searchRange (2 bytes) + writeInt16(file, 6, searchParams.range); + // entrySelector (2 bytes) + writeInt16(file, 8, searchParams.entry); + // rangeShift (2 bytes) + writeInt16(file, 10, searchParams.rangeShift); + + offset = OTF_HEADER_SIZE; + // writing table entries + for (i = 0; i < numTables; i++) { + tableName = tablesNames[i]; + file[offset] = tableName.charCodeAt(0) & 0xff; + file[offset + 1] = tableName.charCodeAt(1) & 0xff; + file[offset + 2] = tableName.charCodeAt(2) & 0xff; + file[offset + 3] = tableName.charCodeAt(3) & 0xff; + + // checksum + let checksum = 0; + for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { + const quad = readUint32(file, j); + checksum = (checksum + quad) >>> 0; } + writeInt32(file, offset + 4, checksum); - const file = new Uint8Array(offset); - // write the table data first (mostly for checksum) - for (i = 0; i < numTables; i++) { - table = tables[tablesNames[i]]; - writeData(file, tableOffsets[i], table); - } + // offset + writeInt32(file, offset + 8, tableOffsets[i]); + // length + writeInt32(file, offset + 12, tables[tableName].length); - // sfnt version (4 bytes) - if (sfnt === "true") { - // Windows hates the Mac TrueType sfnt version number - sfnt = string32(0x00010000); - } - file[0] = sfnt.charCodeAt(0) & 0xff; - file[1] = sfnt.charCodeAt(1) & 0xff; - file[2] = sfnt.charCodeAt(2) & 0xff; - file[3] = sfnt.charCodeAt(3) & 0xff; + offset += OTF_TABLE_ENTRY_SIZE; + } + return file; + } - // numTables (2 bytes) - writeInt16(file, 4, numTables); - - const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16); - - // searchRange (2 bytes) - writeInt16(file, 6, searchParams.range); - // entrySelector (2 bytes) - writeInt16(file, 8, searchParams.entry); - // rangeShift (2 bytes) - writeInt16(file, 10, searchParams.rangeShift); - - offset = OTF_HEADER_SIZE; - // writing table entries - for (i = 0; i < numTables; i++) { - tableName = tablesNames[i]; - file[offset] = tableName.charCodeAt(0) & 0xff; - file[offset + 1] = tableName.charCodeAt(1) & 0xff; - file[offset + 2] = tableName.charCodeAt(2) & 0xff; - file[offset + 3] = tableName.charCodeAt(3) & 0xff; - - // checksum - let checksum = 0; - for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) { - const quad = readUint32(file, j); - checksum = (checksum + quad) >>> 0; - } - writeInt32(file, offset + 4, checksum); - - // offset - writeInt32(file, offset + 8, tableOffsets[i]); - // length - writeInt32(file, offset + 12, tables[tableName].length); - - offset += OTF_TABLE_ENTRY_SIZE; - } - return file; - }, - - addTable: function OpenTypeFileBuilder_addTable(tag, data) { - if (tag in this.tables) { - throw new Error("Table " + tag + " already exists"); - } - this.tables[tag] = data; - }, - }; - - return OpenTypeFileBuilder; -})(); + addTable(tag, data) { + if (tag in this.tables) { + throw new Error("Table " + tag + " already exists"); + } + this.tables[tag] = data; + } +} export { OpenTypeFileBuilder }; From 6912bb5e0acf4bc1239075af0151b64c68e01ee8 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 12:04:34 +0200 Subject: [PATCH 04/18] Move the `IdentityToUnicodeMap`/`ToUnicodeMap` from `src/core/fonts.js` and into its own file --- src/core/evaluator.js | 10 +--- src/core/fonts.js | 107 +-------------------------------- src/core/to_unicode_map.js | 114 ++++++++++++++++++++++++++++++++++++ test/font/font_fpgm_spec.js | 3 +- test/font/font_os2_spec.js | 3 +- test/font/font_post_spec.js | 3 +- 6 files changed, 124 insertions(+), 116 deletions(-) create mode 100644 src/core/to_unicode_map.js diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 5c67ce3c6..18d7923ec 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -47,14 +47,7 @@ import { Ref, RefSet, } from "./primitives.js"; -import { - ErrorFont, - Font, - FontFlags, - getFontType, - IdentityToUnicodeMap, - ToUnicodeMap, -} from "./fonts.js"; +import { ErrorFont, Font, FontFlags, getFontType } from "./fonts.js"; import { getEncoding, MacRomanEncoding, @@ -74,6 +67,7 @@ import { getSymbolsFonts, } from "./standard_fonts.js"; import { getTilingPatternIR, Pattern } from "./pattern.js"; +import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; import { isPDFFunction, PDFFunctionFactory } from "./function.js"; import { Lexer, Parser } from "./parser.js"; import { diff --git a/src/core/fonts.js b/src/core/fonts.js index 3da8eabb2..b0992820c 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -24,7 +24,6 @@ import { isNum, shadow, string32, - unreachable, warn, } from "../shared/util.js"; import { @@ -59,6 +58,7 @@ import { getUnicodeRangeFor, mapSpecialUnicodeValues, } from "./unicode.js"; +import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; import { isWhiteSpace, MissingDataException, @@ -314,101 +314,6 @@ var Glyph = (function GlyphClosure() { return Glyph; })(); -var ToUnicodeMap = (function ToUnicodeMapClosure() { - // eslint-disable-next-line no-shadow - function ToUnicodeMap(cmap = []) { - // The elements of this._map can be integers or strings, depending on how - // `cmap` was created. - this._map = cmap; - } - - ToUnicodeMap.prototype = { - get length() { - return this._map.length; - }, - - forEach(callback) { - for (var charCode in this._map) { - callback(charCode, this._map[charCode].charCodeAt(0)); - } - }, - - has(i) { - return this._map[i] !== undefined; - }, - - get(i) { - return this._map[i]; - }, - - charCodeOf(value) { - // `Array.prototype.indexOf` is *extremely* inefficient for arrays which - // are both very sparse and very large (see issue8372.pdf). - const map = this._map; - if (map.length <= 0x10000) { - return map.indexOf(value); - } - for (const charCode in map) { - if (map[charCode] === value) { - return charCode | 0; - } - } - return -1; - }, - - amend(map) { - for (var charCode in map) { - this._map[charCode] = map[charCode]; - } - }, - }; - - return ToUnicodeMap; -})(); - -var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { - // eslint-disable-next-line no-shadow - function IdentityToUnicodeMap(firstChar, lastChar) { - this.firstChar = firstChar; - this.lastChar = lastChar; - } - - IdentityToUnicodeMap.prototype = { - get length() { - return this.lastChar + 1 - this.firstChar; - }, - - forEach(callback) { - for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) { - callback(i, i); - } - }, - - has(i) { - return this.firstChar <= i && i <= this.lastChar; - }, - - get(i) { - if (this.firstChar <= i && i <= this.lastChar) { - return String.fromCharCode(i); - } - return undefined; - }, - - charCodeOf(v) { - return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar - ? v - : -1; - }, - - amend(map) { - unreachable("Should not call amend()"); - }, - }; - - return IdentityToUnicodeMap; -})(); - /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -3949,12 +3854,4 @@ var CFFFont = (function CFFFontClosure() { return CFFFont; })(); -export { - ErrorFont, - Font, - FontFlags, - getFontType, - IdentityToUnicodeMap, - SEAC_ANALYSIS_ENABLED, - ToUnicodeMap, -}; +export { ErrorFont, Font, FontFlags, getFontType, SEAC_ANALYSIS_ENABLED }; diff --git a/src/core/to_unicode_map.js b/src/core/to_unicode_map.js new file mode 100644 index 000000000..36f84b4f9 --- /dev/null +++ b/src/core/to_unicode_map.js @@ -0,0 +1,114 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-var */ + +import { unreachable } from "../shared/util.js"; + +var ToUnicodeMap = (function ToUnicodeMapClosure() { + // eslint-disable-next-line no-shadow + function ToUnicodeMap(cmap = []) { + // The elements of this._map can be integers or strings, depending on how + // `cmap` was created. + this._map = cmap; + } + + ToUnicodeMap.prototype = { + get length() { + return this._map.length; + }, + + forEach(callback) { + for (var charCode in this._map) { + callback(charCode, this._map[charCode].charCodeAt(0)); + } + }, + + has(i) { + return this._map[i] !== undefined; + }, + + get(i) { + return this._map[i]; + }, + + charCodeOf(value) { + // `Array.prototype.indexOf` is *extremely* inefficient for arrays which + // are both very sparse and very large (see issue8372.pdf). + const map = this._map; + if (map.length <= 0x10000) { + return map.indexOf(value); + } + for (const charCode in map) { + if (map[charCode] === value) { + return charCode | 0; + } + } + return -1; + }, + + amend(map) { + for (var charCode in map) { + this._map[charCode] = map[charCode]; + } + }, + }; + + return ToUnicodeMap; +})(); + +var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { + // eslint-disable-next-line no-shadow + function IdentityToUnicodeMap(firstChar, lastChar) { + this.firstChar = firstChar; + this.lastChar = lastChar; + } + + IdentityToUnicodeMap.prototype = { + get length() { + return this.lastChar + 1 - this.firstChar; + }, + + forEach(callback) { + for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) { + callback(i, i); + } + }, + + has(i) { + return this.firstChar <= i && i <= this.lastChar; + }, + + get(i) { + if (this.firstChar <= i && i <= this.lastChar) { + return String.fromCharCode(i); + } + return undefined; + }, + + charCodeOf(v) { + return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar + ? v + : -1; + }, + + amend(map) { + unreachable("Should not call amend()"); + }, + }; + + return IdentityToUnicodeMap; +})(); + +export { IdentityToUnicodeMap, ToUnicodeMap }; diff --git a/test/font/font_fpgm_spec.js b/test/font/font_fpgm_spec.js index cf2698738..7c748ed47 100644 --- a/test/font/font_fpgm_spec.js +++ b/test/font/font_fpgm_spec.js @@ -1,8 +1,9 @@ import { decodeFontData, ttx, verifyTtxOutput } from "./fontutils.js"; -import { Font, ToUnicodeMap } from "../../src/core/fonts.js"; import { CMapFactory } from "../../src/core/cmap.js"; +import { Font } from "../../src/core/fonts.js"; import { Name } from "../../src/core/primitives.js"; import { Stream } from "../../src/core/stream.js"; +import { ToUnicodeMap } from "../../src/core/to_unicode_map.js"; describe("font_fpgm", function () { const font2324 = decodeFontData( diff --git a/test/font/font_os2_spec.js b/test/font/font_os2_spec.js index eef69ed06..b344bc90b 100644 --- a/test/font/font_os2_spec.js +++ b/test/font/font_os2_spec.js @@ -1,8 +1,9 @@ import { decodeFontData, ttx, verifyTtxOutput } from "./fontutils.js"; -import { Font, ToUnicodeMap } from "../../src/core/fonts.js"; import { CMapFactory } from "../../src/core/cmap.js"; +import { Font } from "../../src/core/fonts.js"; import { Name } from "../../src/core/primitives.js"; import { Stream } from "../../src/core/stream.js"; +import { ToUnicodeMap } from "../../src/core/to_unicode_map.js"; describe("font_post", function () { const font2154 = decodeFontData( diff --git a/test/font/font_post_spec.js b/test/font/font_post_spec.js index 5b29e5147..6cb92705a 100644 --- a/test/font/font_post_spec.js +++ b/test/font/font_post_spec.js @@ -1,8 +1,9 @@ import { decodeFontData, ttx, verifyTtxOutput } from "./fontutils.js"; -import { Font, ToUnicodeMap } from "../../src/core/fonts.js"; import { CMapFactory } from "../../src/core/cmap.js"; +import { Font } from "../../src/core/fonts.js"; import { Name } from "../../src/core/primitives.js"; import { Stream } from "../../src/core/stream.js"; +import { ToUnicodeMap } from "../../src/core/to_unicode_map.js"; describe("font_post", function () { const font2109 = decodeFontData( From 33ea6b1131fecf1fab7889c1b724fdb644ee9e5a Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 12:06:32 +0200 Subject: [PATCH 05/18] Enable the `no-var` rule in the `src/core/to_unicode_map.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/to_unicode_map.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/to_unicode_map.js b/src/core/to_unicode_map.js index 36f84b4f9..9ae91b9fc 100644 --- a/src/core/to_unicode_map.js +++ b/src/core/to_unicode_map.js @@ -12,11 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { unreachable } from "../shared/util.js"; -var ToUnicodeMap = (function ToUnicodeMapClosure() { +const ToUnicodeMap = (function ToUnicodeMapClosure() { // eslint-disable-next-line no-shadow function ToUnicodeMap(cmap = []) { // The elements of this._map can be integers or strings, depending on how @@ -30,7 +29,7 @@ var ToUnicodeMap = (function ToUnicodeMapClosure() { }, forEach(callback) { - for (var charCode in this._map) { + for (const charCode in this._map) { callback(charCode, this._map[charCode].charCodeAt(0)); } }, @@ -59,7 +58,7 @@ var ToUnicodeMap = (function ToUnicodeMapClosure() { }, amend(map) { - for (var charCode in map) { + for (const charCode in map) { this._map[charCode] = map[charCode]; } }, @@ -68,7 +67,7 @@ var ToUnicodeMap = (function ToUnicodeMapClosure() { return ToUnicodeMap; })(); -var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { +const IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { // eslint-disable-next-line no-shadow function IdentityToUnicodeMap(firstChar, lastChar) { this.firstChar = firstChar; @@ -81,7 +80,7 @@ var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { }, forEach(callback) { - for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) { + for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) { callback(i, i); } }, From 22539b52fa2ac1afe7a341455d2bb6bbf0ac3365 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 12:08:18 +0200 Subject: [PATCH 06/18] Convert `src/core/to_unicode_map.js` to use standard classes --- src/core/to_unicode_map.js | 142 +++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 76 deletions(-) diff --git a/src/core/to_unicode_map.js b/src/core/to_unicode_map.js index 9ae91b9fc..24bd8fe7d 100644 --- a/src/core/to_unicode_map.js +++ b/src/core/to_unicode_map.js @@ -15,99 +15,89 @@ import { unreachable } from "../shared/util.js"; -const ToUnicodeMap = (function ToUnicodeMapClosure() { - // eslint-disable-next-line no-shadow - function ToUnicodeMap(cmap = []) { +class ToUnicodeMap { + constructor(cmap = []) { // The elements of this._map can be integers or strings, depending on how // `cmap` was created. this._map = cmap; } - ToUnicodeMap.prototype = { - get length() { - return this._map.length; - }, + get length() { + return this._map.length; + } - forEach(callback) { - for (const charCode in this._map) { - callback(charCode, this._map[charCode].charCodeAt(0)); + forEach(callback) { + for (const charCode in this._map) { + callback(charCode, this._map[charCode].charCodeAt(0)); + } + } + + has(i) { + return this._map[i] !== undefined; + } + + get(i) { + return this._map[i]; + } + + charCodeOf(value) { + // `Array.prototype.indexOf` is *extremely* inefficient for arrays which + // are both very sparse and very large (see issue8372.pdf). + const map = this._map; + if (map.length <= 0x10000) { + return map.indexOf(value); + } + for (const charCode in map) { + if (map[charCode] === value) { + return charCode | 0; } - }, + } + return -1; + } - has(i) { - return this._map[i] !== undefined; - }, + amend(map) { + for (const charCode in map) { + this._map[charCode] = map[charCode]; + } + } +} - get(i) { - return this._map[i]; - }, - - charCodeOf(value) { - // `Array.prototype.indexOf` is *extremely* inefficient for arrays which - // are both very sparse and very large (see issue8372.pdf). - const map = this._map; - if (map.length <= 0x10000) { - return map.indexOf(value); - } - for (const charCode in map) { - if (map[charCode] === value) { - return charCode | 0; - } - } - return -1; - }, - - amend(map) { - for (const charCode in map) { - this._map[charCode] = map[charCode]; - } - }, - }; - - return ToUnicodeMap; -})(); - -const IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() { - // eslint-disable-next-line no-shadow - function IdentityToUnicodeMap(firstChar, lastChar) { +class IdentityToUnicodeMap { + constructor(firstChar, lastChar) { this.firstChar = firstChar; this.lastChar = lastChar; } - IdentityToUnicodeMap.prototype = { - get length() { - return this.lastChar + 1 - this.firstChar; - }, + get length() { + return this.lastChar + 1 - this.firstChar; + } - forEach(callback) { - for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) { - callback(i, i); - } - }, + forEach(callback) { + for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) { + callback(i, i); + } + } - has(i) { - return this.firstChar <= i && i <= this.lastChar; - }, + has(i) { + return this.firstChar <= i && i <= this.lastChar; + } - get(i) { - if (this.firstChar <= i && i <= this.lastChar) { - return String.fromCharCode(i); - } - return undefined; - }, + get(i) { + if (this.firstChar <= i && i <= this.lastChar) { + return String.fromCharCode(i); + } + return undefined; + } - charCodeOf(v) { - return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar - ? v - : -1; - }, + charCodeOf(v) { + return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar + ? v + : -1; + } - amend(map) { - unreachable("Should not call amend()"); - }, - }; - - return IdentityToUnicodeMap; -})(); + amend(map) { + unreachable("Should not call amend()"); + } +} export { IdentityToUnicodeMap, ToUnicodeMap }; From 77b258440b9a6b65f1477fbf151cb4edb28514a4 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:11:01 +0200 Subject: [PATCH 07/18] Move some constants and helper functions `from src/core/fonts.js` and into their own file - `FontFlags`, is used in both `src/core/fonts.js` and `src/core/evaluator.js`. - `getFontType`, same as the above. - `MacStandardGlyphOrdering`, is a fairly large data-structure and `src/core/fonts.js` is already a *very* large file. - `recoverGlyphName`, a dependency of `type1FontGlyphMapping`; please see below. - `SEAC_ANALYSIS_ENABLED`, is used by both `Type1Font`, `CFFFont`, and unit-tests; please see below. - `type1FontGlyphMapping`, is used by both `Type1Font` and `CFFFont` which a later patch will move to their own files. --- src/core/evaluator.js | 3 +- src/core/fonts.js | 185 ++---------------------------- src/core/fonts_utils.js | 204 +++++++++++++++++++++++++++++++++ test/unit/cff_parser_spec.js | 2 +- test/unit/type1_parser_spec.js | 2 +- 5 files changed, 217 insertions(+), 179 deletions(-) create mode 100644 src/core/fonts_utils.js diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 18d7923ec..16f902728 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -47,7 +47,8 @@ import { Ref, RefSet, } from "./primitives.js"; -import { ErrorFont, Font, FontFlags, getFontType } from "./fonts.js"; +import { ErrorFont, Font } from "./fonts.js"; +import { FontFlags, getFontType } from "./fonts_utils.js"; import { getEncoding, MacRomanEncoding, diff --git a/src/core/fonts.js b/src/core/fonts.js index b0992820c..28d2ea97c 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -38,6 +38,14 @@ import { CFFStrings, CFFTopDict, } from "./cff_parser.js"; +import { + FontFlags, + getFontType, + MacStandardGlyphOrdering, + recoverGlyphName, + SEAC_ANALYSIS_ENABLED, + type1FontGlyphMapping, +} from "./fonts_utils.js"; import { getDingbatsGlyphsUnicode, getGlyphsUnicode } from "./glyphlist.js"; import { getEncoding, @@ -80,15 +88,6 @@ const PRIVATE_USE_AREAS = [ // except for Type 3 fonts var PDF_GLYPH_SPACE_UNITS = 1000; -// Accented characters have issues on Windows and Linux. When this flag is -// enabled glyphs that use seac and seac style endchar operators are truncated -// and we instead just store the glyph id's of the base glyph and its accent to -// be drawn individually. -// Linux (freetype) requires that when a seac style endchar is used -// that the charset must be a predefined one, however we build a -// custom one. Windows just refuses to draw glyphs with seac operators. -var SEAC_ANALYSIS_ENABLED = true; - const EXPORT_DATA_PROPERTIES = [ "ascent", "bbox", @@ -130,57 +129,6 @@ const EXPORT_DATA_EXTRA_PROPERTIES = [ "widths", ]; -var FontFlags = { - FixedPitch: 1, - Serif: 2, - Symbolic: 4, - Script: 8, - Nonsymbolic: 32, - Italic: 64, - AllCap: 65536, - SmallCap: 131072, - ForceBold: 262144, -}; - -// prettier-ignore -var MacStandardGlyphOrdering = [ - ".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"]; - function adjustWidths(properties) { if (!properties.fontMatrix) { return; @@ -225,48 +173,6 @@ function adjustToUnicode(properties, builtInEncoding) { properties.toUnicode.amend(toUnicode); } -function getFontType(type, subtype) { - switch (type) { - case "Type1": - return subtype === "Type1C" ? FontType.TYPE1C : FontType.TYPE1; - case "CIDFontType0": - return subtype === "CIDFontType0C" - ? FontType.CIDFONTTYPE0C - : FontType.CIDFONTTYPE0; - case "OpenType": - return FontType.OPENTYPE; - case "TrueType": - return FontType.TRUETYPE; - case "CIDFontType2": - return FontType.CIDFONTTYPE2; - case "MMType1": - return FontType.MMTYPE1; - case "Type0": - return FontType.TYPE0; - default: - return FontType.UNKNOWN; - } -} - -// Some bad PDF generators, e.g. Scribus PDF, include glyph names -// in a 'uniXXXX' format -- attempting to recover proper ones. -function recoverGlyphName(name, glyphsUnicodeMap) { - if (glyphsUnicodeMap[name] !== undefined) { - return name; - } - // The glyph name is non-standard, trying to recover. - var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); - if (unicode !== -1) { - for (var key in glyphsUnicodeMap) { - if (glyphsUnicodeMap[key] === unicode) { - return key; - } - } - } - info("Unable to recover a standard glyph name for: " + name); - return name; -} - var Glyph = (function GlyphClosure() { // eslint-disable-next-line no-shadow function Glyph( @@ -3282,79 +3188,6 @@ var ErrorFont = (function ErrorFontClosure() { return ErrorFont; })(); -/** - * Shared logic for building a char code to glyph id mapping for Type1 and - * simple CFF fonts. See section 9.6.6.2 of the spec. - * @param {Object} properties Font properties object. - * @param {Object} builtInEncoding The encoding contained within the actual font - * data. - * @param {Array} glyphNames Array of glyph names where the index is the - * glyph ID. - * @returns {Object} A char code to glyph ID map. - */ -function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { - var charCodeToGlyphId = Object.create(null); - var glyphId, charCode, baseEncoding; - var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); - - if (properties.baseEncodingName) { - // If a valid base encoding name was used, the mapping is initialized with - // that. - baseEncoding = getEncoding(properties.baseEncodingName); - for (charCode = 0; charCode < baseEncoding.length; charCode++) { - glyphId = glyphNames.indexOf(baseEncoding[charCode]); - if (glyphId >= 0) { - charCodeToGlyphId[charCode] = glyphId; - } else { - charCodeToGlyphId[charCode] = 0; // notdef - } - } - } else if (isSymbolicFont) { - // For a symbolic font the encoding should be the fonts built-in encoding. - for (charCode in builtInEncoding) { - charCodeToGlyphId[charCode] = builtInEncoding[charCode]; - } - } else { - // For non-symbolic fonts that don't have a base encoding the standard - // encoding should be used. - baseEncoding = StandardEncoding; - for (charCode = 0; charCode < baseEncoding.length; charCode++) { - glyphId = glyphNames.indexOf(baseEncoding[charCode]); - if (glyphId >= 0) { - charCodeToGlyphId[charCode] = glyphId; - } else { - charCodeToGlyphId[charCode] = 0; // notdef - } - } - } - - // Lastly, merge in the differences. - var differences = properties.differences, - glyphsUnicodeMap; - if (differences) { - for (charCode in differences) { - var glyphName = differences[charCode]; - glyphId = glyphNames.indexOf(glyphName); - - if (glyphId === -1) { - if (!glyphsUnicodeMap) { - glyphsUnicodeMap = getGlyphsUnicode(); - } - var standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); - if (standardGlyphName !== glyphName) { - glyphId = glyphNames.indexOf(standardGlyphName); - } - } - if (glyphId >= 0) { - charCodeToGlyphId[charCode] = glyphId; - } else { - charCodeToGlyphId[charCode] = 0; // notdef - } - } - } - return charCodeToGlyphId; -} - // Type1Font is also a CIDFontType0. var Type1Font = (function Type1FontClosure() { function findBlock(streamBytes, signature, startIndex) { @@ -3854,4 +3687,4 @@ var CFFFont = (function CFFFontClosure() { return CFFFont; })(); -export { ErrorFont, Font, FontFlags, getFontType, SEAC_ANALYSIS_ENABLED }; +export { ErrorFont, Font }; diff --git a/src/core/fonts_utils.js b/src/core/fonts_utils.js new file mode 100644 index 000000000..6d051106d --- /dev/null +++ b/src/core/fonts_utils.js @@ -0,0 +1,204 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-var */ + +import { FontType, info } from "../shared/util.js"; +import { getEncoding, StandardEncoding } from "./encodings.js"; +import { getGlyphsUnicode } from "./glyphlist.js"; +import { getUnicodeForGlyph } from "./unicode.js"; + +// Accented characters have issues on Windows and Linux. When this flag is +// enabled glyphs that use seac and seac style endchar operators are truncated +// and we instead just store the glyph id's of the base glyph and its accent to +// be drawn individually. +// Linux (freetype) requires that when a seac style endchar is used +// that the charset must be a predefined one, however we build a +// custom one. Windows just refuses to draw glyphs with seac operators. +var SEAC_ANALYSIS_ENABLED = true; + +var FontFlags = { + FixedPitch: 1, + Serif: 2, + Symbolic: 4, + Script: 8, + Nonsymbolic: 32, + Italic: 64, + AllCap: 65536, + SmallCap: 131072, + ForceBold: 262144, +}; + +// prettier-ignore +var MacStandardGlyphOrdering = [ + ".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"]; + +function getFontType(type, subtype) { + switch (type) { + case "Type1": + return subtype === "Type1C" ? FontType.TYPE1C : FontType.TYPE1; + case "CIDFontType0": + return subtype === "CIDFontType0C" + ? FontType.CIDFONTTYPE0C + : FontType.CIDFONTTYPE0; + case "OpenType": + return FontType.OPENTYPE; + case "TrueType": + return FontType.TRUETYPE; + case "CIDFontType2": + return FontType.CIDFONTTYPE2; + case "MMType1": + return FontType.MMTYPE1; + case "Type0": + return FontType.TYPE0; + default: + return FontType.UNKNOWN; + } +} + +// Some bad PDF generators, e.g. Scribus PDF, include glyph names +// in a 'uniXXXX' format -- attempting to recover proper ones. +function recoverGlyphName(name, glyphsUnicodeMap) { + if (glyphsUnicodeMap[name] !== undefined) { + return name; + } + // The glyph name is non-standard, trying to recover. + var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); + if (unicode !== -1) { + for (var key in glyphsUnicodeMap) { + if (glyphsUnicodeMap[key] === unicode) { + return key; + } + } + } + info("Unable to recover a standard glyph name for: " + name); + return name; +} + +/** + * Shared logic for building a char code to glyph id mapping for Type1 and + * simple CFF fonts. See section 9.6.6.2 of the spec. + * @param {Object} properties Font properties object. + * @param {Object} builtInEncoding The encoding contained within the actual font + * data. + * @param {Array} glyphNames Array of glyph names where the index is the + * glyph ID. + * @returns {Object} A char code to glyph ID map. + */ +function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { + var charCodeToGlyphId = Object.create(null); + var glyphId, charCode, baseEncoding; + var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); + + if (properties.baseEncodingName) { + // If a valid base encoding name was used, the mapping is initialized with + // that. + baseEncoding = getEncoding(properties.baseEncodingName); + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); + if (glyphId >= 0) { + charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef + } + } + } else if (isSymbolicFont) { + // For a symbolic font the encoding should be the fonts built-in encoding. + for (charCode in builtInEncoding) { + charCodeToGlyphId[charCode] = builtInEncoding[charCode]; + } + } else { + // For non-symbolic fonts that don't have a base encoding the standard + // encoding should be used. + baseEncoding = StandardEncoding; + for (charCode = 0; charCode < baseEncoding.length; charCode++) { + glyphId = glyphNames.indexOf(baseEncoding[charCode]); + if (glyphId >= 0) { + charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef + } + } + } + + // Lastly, merge in the differences. + var differences = properties.differences, + glyphsUnicodeMap; + if (differences) { + for (charCode in differences) { + var glyphName = differences[charCode]; + glyphId = glyphNames.indexOf(glyphName); + + if (glyphId === -1) { + if (!glyphsUnicodeMap) { + glyphsUnicodeMap = getGlyphsUnicode(); + } + var standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); + if (standardGlyphName !== glyphName) { + glyphId = glyphNames.indexOf(standardGlyphName); + } + } + if (glyphId >= 0) { + charCodeToGlyphId[charCode] = glyphId; + } else { + charCodeToGlyphId[charCode] = 0; // notdef + } + } + } + return charCodeToGlyphId; +} + +export { + FontFlags, + getFontType, + MacStandardGlyphOrdering, + recoverGlyphName, + SEAC_ANALYSIS_ENABLED, + type1FontGlyphMapping, +}; diff --git a/test/unit/cff_parser_spec.js b/test/unit/cff_parser_spec.js index 6eaa0a0b8..43a979269 100644 --- a/test/unit/cff_parser_spec.js +++ b/test/unit/cff_parser_spec.js @@ -20,7 +20,7 @@ import { CFFParser, CFFStrings, } from "../../src/core/cff_parser.js"; -import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts.js"; +import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts_utils.js"; import { Stream } from "../../src/core/stream.js"; describe("CFFParser", function () { diff --git a/test/unit/type1_parser_spec.js b/test/unit/type1_parser_spec.js index db02bc0e8..00929a33c 100644 --- a/test/unit/type1_parser_spec.js +++ b/test/unit/type1_parser_spec.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts.js"; +import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts_utils.js"; import { StringStream } from "../../src/core/stream.js"; import { Type1Parser } from "../../src/core/type1_parser.js"; From d4606712f2fcdd79e2f1dc874537c3f36cb5ccbc Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:19:13 +0200 Subject: [PATCH 08/18] Enable the no-var rule in the `src/core/fonts_utils.js` file These changes were made *mostly* automatically, using `gulp lint --fix`, with the following manual changes: ```diff diff --git a/src/core/fonts_utils.js b/src/core/fonts_utils.js index f88ce4a8c..c4b3f3808 100644 --- a/src/core/fonts_utils.js +++ b/src/core/fonts_utils.js @@ -167,8 +167,8 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { } // Lastly, merge in the differences. - let differences = properties.differences, - glyphsUnicodeMap; + const differences = properties.differences; + let glyphsUnicodeMap; if (differences) { for (charCode in differences) { const glyphName = differences[charCode]; ``` --- src/core/fonts_utils.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/core/fonts_utils.js b/src/core/fonts_utils.js index 6d051106d..c4b3f3808 100644 --- a/src/core/fonts_utils.js +++ b/src/core/fonts_utils.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { FontType, info } from "../shared/util.js"; import { getEncoding, StandardEncoding } from "./encodings.js"; @@ -26,9 +25,9 @@ import { getUnicodeForGlyph } from "./unicode.js"; // Linux (freetype) requires that when a seac style endchar is used // that the charset must be a predefined one, however we build a // custom one. Windows just refuses to draw glyphs with seac operators. -var SEAC_ANALYSIS_ENABLED = true; +const SEAC_ANALYSIS_ENABLED = true; -var FontFlags = { +const FontFlags = { FixedPitch: 1, Serif: 2, Symbolic: 4, @@ -41,7 +40,7 @@ var FontFlags = { }; // prettier-ignore -var MacStandardGlyphOrdering = [ +const MacStandardGlyphOrdering = [ ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", @@ -109,9 +108,9 @@ function recoverGlyphName(name, glyphsUnicodeMap) { return name; } // The glyph name is non-standard, trying to recover. - var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); + const unicode = getUnicodeForGlyph(name, glyphsUnicodeMap); if (unicode !== -1) { - for (var key in glyphsUnicodeMap) { + for (const key in glyphsUnicodeMap) { if (glyphsUnicodeMap[key] === unicode) { return key; } @@ -132,9 +131,9 @@ function recoverGlyphName(name, glyphsUnicodeMap) { * @returns {Object} A char code to glyph ID map. */ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { - var charCodeToGlyphId = Object.create(null); - var glyphId, charCode, baseEncoding; - var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); + const charCodeToGlyphId = Object.create(null); + let glyphId, charCode, baseEncoding; + const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); if (properties.baseEncodingName) { // If a valid base encoding name was used, the mapping is initialized with @@ -168,18 +167,18 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { } // Lastly, merge in the differences. - var differences = properties.differences, - glyphsUnicodeMap; + const differences = properties.differences; + let glyphsUnicodeMap; if (differences) { for (charCode in differences) { - var glyphName = differences[charCode]; + const glyphName = differences[charCode]; glyphId = glyphNames.indexOf(glyphName); if (glyphId === -1) { if (!glyphsUnicodeMap) { glyphsUnicodeMap = getGlyphsUnicode(); } - var standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); + const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); if (standardGlyphName !== glyphName) { glyphId = glyphNames.indexOf(standardGlyphName); } From d5d73e31685d034921b4d1c7f229660a43033902 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:36:12 +0200 Subject: [PATCH 09/18] Move the `CFFFont` from `src/core/fonts.js` and into its own file --- src/core/cff_font.js | 116 +++++++++++++++++++++++++++++++++++++++++++ src/core/fonts.js | 96 +---------------------------------- 2 files changed, 117 insertions(+), 95 deletions(-) create mode 100644 src/core/cff_font.js diff --git a/src/core/cff_font.js b/src/core/cff_font.js new file mode 100644 index 000000000..fbd78b802 --- /dev/null +++ b/src/core/cff_font.js @@ -0,0 +1,116 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-var */ + +import { CFFCompiler, CFFParser } from "./cff_parser.js"; +import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; +import { warn } from "../shared/util.js"; + +var CFFFont = (function CFFFontClosure() { + // eslint-disable-next-line no-shadow + function CFFFont(file, properties) { + this.properties = properties; + + var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); + this.cff = parser.parse(); + this.cff.duplicateFirstGlyph(); + var compiler = new CFFCompiler(this.cff); + this.seacs = this.cff.seacs; + try { + this.data = compiler.compile(); + } catch (e) { + warn("Failed to compile font " + properties.loadedName); + // There may have just been an issue with the compiler, set the data + // anyway and hope the font loaded. + this.data = file; + } + this._createBuiltInEncoding(); + } + + CFFFont.prototype = { + get numGlyphs() { + return this.cff.charStrings.count; + }, + getCharset: function CFFFont_getCharset() { + return this.cff.charset.charset; + }, + getGlyphMapping: function CFFFont_getGlyphMapping() { + var cff = this.cff; + var properties = this.properties; + var charsets = cff.charset.charset; + var charCodeToGlyphId; + var glyphId; + + if (properties.composite) { + charCodeToGlyphId = Object.create(null); + let charCode; + if (cff.isCIDFont) { + // If the font is actually a CID font then we should use the charset + // to map CIDs to GIDs. + for (glyphId = 0; glyphId < charsets.length; glyphId++) { + var cid = charsets[glyphId]; + charCode = properties.cMap.charCodeOf(cid); + charCodeToGlyphId[charCode] = glyphId; + } + } else { + // If it is NOT actually a CID font then CIDs should be mapped + // directly to GIDs. + for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { + charCode = properties.cMap.charCodeOf(glyphId); + charCodeToGlyphId[charCode] = glyphId; + } + } + return charCodeToGlyphId; + } + + var encoding = cff.encoding ? cff.encoding.encoding : null; + charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); + return charCodeToGlyphId; + }, + hasGlyphId: function CFFFont_hasGlyphID(id) { + return this.cff.hasGlyphId(id); + }, + + /** + * @private + */ + _createBuiltInEncoding() { + const { charset, encoding } = this.cff; + if (!charset || !encoding) { + return; + } + const charsets = charset.charset, + encodings = encoding.encoding; + const map = []; + + for (const charCode in encodings) { + const glyphId = encodings[charCode]; + if (glyphId >= 0) { + const glyphName = charsets[glyphId]; + if (glyphName) { + map[charCode] = glyphName; + } + } + } + if (map.length > 0) { + this.properties.builtInEncoding = map; + } + }, + }; + + return CFFFont; +})(); + +export { CFFFont }; diff --git a/src/core/fonts.js b/src/core/fonts.js index 28d2ea97c..a641ac33f 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -72,6 +72,7 @@ import { MissingDataException, readUint32, } from "./core_utils.js"; +import { CFFFont } from "./cff_font.js"; import { FontRendererFactory } from "./font_renderer.js"; import { IdentityCMap } from "./cmap.js"; import { OpenTypeFileBuilder } from "./opentype_file_builder.js"; @@ -3592,99 +3593,4 @@ var Type1Font = (function Type1FontClosure() { return Type1Font; })(); -var CFFFont = (function CFFFontClosure() { - // eslint-disable-next-line no-shadow - function CFFFont(file, properties) { - this.properties = properties; - - var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); - this.cff = parser.parse(); - this.cff.duplicateFirstGlyph(); - var compiler = new CFFCompiler(this.cff); - this.seacs = this.cff.seacs; - try { - this.data = compiler.compile(); - } catch (e) { - warn("Failed to compile font " + properties.loadedName); - // There may have just been an issue with the compiler, set the data - // anyway and hope the font loaded. - this.data = file; - } - this._createBuiltInEncoding(); - } - - CFFFont.prototype = { - get numGlyphs() { - return this.cff.charStrings.count; - }, - getCharset: function CFFFont_getCharset() { - return this.cff.charset.charset; - }, - getGlyphMapping: function CFFFont_getGlyphMapping() { - var cff = this.cff; - var properties = this.properties; - var charsets = cff.charset.charset; - var charCodeToGlyphId; - var glyphId; - - if (properties.composite) { - charCodeToGlyphId = Object.create(null); - let charCode; - if (cff.isCIDFont) { - // If the font is actually a CID font then we should use the charset - // to map CIDs to GIDs. - for (glyphId = 0; glyphId < charsets.length; glyphId++) { - var cid = charsets[glyphId]; - charCode = properties.cMap.charCodeOf(cid); - charCodeToGlyphId[charCode] = glyphId; - } - } else { - // If it is NOT actually a CID font then CIDs should be mapped - // directly to GIDs. - for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { - charCode = properties.cMap.charCodeOf(glyphId); - charCodeToGlyphId[charCode] = glyphId; - } - } - return charCodeToGlyphId; - } - - var encoding = cff.encoding ? cff.encoding.encoding : null; - charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); - return charCodeToGlyphId; - }, - hasGlyphId: function CFFFont_hasGlyphID(id) { - return this.cff.hasGlyphId(id); - }, - - /** - * @private - */ - _createBuiltInEncoding() { - const { charset, encoding } = this.cff; - if (!charset || !encoding) { - return; - } - const charsets = charset.charset, - encodings = encoding.encoding; - const map = []; - - for (const charCode in encodings) { - const glyphId = encodings[charCode]; - if (glyphId >= 0) { - const glyphName = charsets[glyphId]; - if (glyphName) { - map[charCode] = glyphName; - } - } - } - if (map.length > 0) { - this.properties.builtInEncoding = map; - } - }, - }; - - return CFFFont; -})(); - export { ErrorFont, Font }; From 542ee0d7984d1737203182b96f566733f88439f2 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:40:21 +0200 Subject: [PATCH 10/18] Enable the `no-var` rule in the ` src/core/cff_font.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/cff_font.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/core/cff_font.js b/src/core/cff_font.js index fbd78b802..4fc8e6146 100644 --- a/src/core/cff_font.js +++ b/src/core/cff_font.js @@ -12,21 +12,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { CFFCompiler, CFFParser } from "./cff_parser.js"; import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; import { warn } from "../shared/util.js"; -var CFFFont = (function CFFFontClosure() { +const CFFFont = (function CFFFontClosure() { // eslint-disable-next-line no-shadow function CFFFont(file, properties) { this.properties = properties; - var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); + const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); this.cff = parser.parse(); this.cff.duplicateFirstGlyph(); - var compiler = new CFFCompiler(this.cff); + const compiler = new CFFCompiler(this.cff); this.seacs = this.cff.seacs; try { this.data = compiler.compile(); @@ -47,11 +46,11 @@ var CFFFont = (function CFFFontClosure() { return this.cff.charset.charset; }, getGlyphMapping: function CFFFont_getGlyphMapping() { - var cff = this.cff; - var properties = this.properties; - var charsets = cff.charset.charset; - var charCodeToGlyphId; - var glyphId; + const cff = this.cff; + const properties = this.properties; + const charsets = cff.charset.charset; + let charCodeToGlyphId; + let glyphId; if (properties.composite) { charCodeToGlyphId = Object.create(null); @@ -60,7 +59,7 @@ var CFFFont = (function CFFFontClosure() { // If the font is actually a CID font then we should use the charset // to map CIDs to GIDs. for (glyphId = 0; glyphId < charsets.length; glyphId++) { - var cid = charsets[glyphId]; + const cid = charsets[glyphId]; charCode = properties.cMap.charCodeOf(cid); charCodeToGlyphId[charCode] = glyphId; } @@ -75,7 +74,7 @@ var CFFFont = (function CFFFontClosure() { return charCodeToGlyphId; } - var encoding = cff.encoding ? cff.encoding.encoding : null; + const encoding = cff.encoding ? cff.encoding.encoding : null; charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); return charCodeToGlyphId; }, From e803584fe7aef105fd0c137742825f070134a84b Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:41:45 +0200 Subject: [PATCH 11/18] Convert `src/core/cff_font.js` to use standard classes --- src/core/cff_font.js | 134 +++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 68 deletions(-) diff --git a/src/core/cff_font.js b/src/core/cff_font.js index 4fc8e6146..e7caa8afa 100644 --- a/src/core/cff_font.js +++ b/src/core/cff_font.js @@ -17,9 +17,8 @@ import { CFFCompiler, CFFParser } from "./cff_parser.js"; import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; import { warn } from "../shared/util.js"; -const CFFFont = (function CFFFontClosure() { - // eslint-disable-next-line no-shadow - function CFFFont(file, properties) { +class CFFFont { + constructor(file, properties) { this.properties = properties; const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED); @@ -38,78 +37,77 @@ const CFFFont = (function CFFFontClosure() { this._createBuiltInEncoding(); } - CFFFont.prototype = { - get numGlyphs() { - return this.cff.charStrings.count; - }, - getCharset: function CFFFont_getCharset() { - return this.cff.charset.charset; - }, - getGlyphMapping: function CFFFont_getGlyphMapping() { - const cff = this.cff; - const properties = this.properties; - const charsets = cff.charset.charset; - let charCodeToGlyphId; - let glyphId; + get numGlyphs() { + return this.cff.charStrings.count; + } - if (properties.composite) { - charCodeToGlyphId = Object.create(null); - let charCode; - if (cff.isCIDFont) { - // If the font is actually a CID font then we should use the charset - // to map CIDs to GIDs. - for (glyphId = 0; glyphId < charsets.length; glyphId++) { - const cid = charsets[glyphId]; - charCode = properties.cMap.charCodeOf(cid); - charCodeToGlyphId[charCode] = glyphId; - } - } else { - // If it is NOT actually a CID font then CIDs should be mapped - // directly to GIDs. - for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { - charCode = properties.cMap.charCodeOf(glyphId); - charCodeToGlyphId[charCode] = glyphId; - } + getCharset() { + return this.cff.charset.charset; + } + + getGlyphMapping() { + const cff = this.cff; + const properties = this.properties; + const charsets = cff.charset.charset; + let charCodeToGlyphId; + let glyphId; + + if (properties.composite) { + charCodeToGlyphId = Object.create(null); + let charCode; + if (cff.isCIDFont) { + // If the font is actually a CID font then we should use the charset + // to map CIDs to GIDs. + for (glyphId = 0; glyphId < charsets.length; glyphId++) { + const cid = charsets[glyphId]; + charCode = properties.cMap.charCodeOf(cid); + charCodeToGlyphId[charCode] = glyphId; + } + } else { + // If it is NOT actually a CID font then CIDs should be mapped + // directly to GIDs. + for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) { + charCode = properties.cMap.charCodeOf(glyphId); + charCodeToGlyphId[charCode] = glyphId; } - return charCodeToGlyphId; } - - const encoding = cff.encoding ? cff.encoding.encoding : null; - charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); return charCodeToGlyphId; - }, - hasGlyphId: function CFFFont_hasGlyphID(id) { - return this.cff.hasGlyphId(id); - }, + } - /** - * @private - */ - _createBuiltInEncoding() { - const { charset, encoding } = this.cff; - if (!charset || !encoding) { - return; - } - const charsets = charset.charset, - encodings = encoding.encoding; - const map = []; + const encoding = cff.encoding ? cff.encoding.encoding : null; + charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets); + return charCodeToGlyphId; + } - for (const charCode in encodings) { - const glyphId = encodings[charCode]; - if (glyphId >= 0) { - const glyphName = charsets[glyphId]; - if (glyphName) { - map[charCode] = glyphName; - } + hasGlyphId(id) { + return this.cff.hasGlyphId(id); + } + + /** + * @private + */ + _createBuiltInEncoding() { + const { charset, encoding } = this.cff; + if (!charset || !encoding) { + return; + } + const charsets = charset.charset, + encodings = encoding.encoding; + const map = []; + + for (const charCode in encodings) { + const glyphId = encodings[charCode]; + if (glyphId >= 0) { + const glyphName = charsets[glyphId]; + if (glyphName) { + map[charCode] = glyphName; } } - if (map.length > 0) { - this.properties.builtInEncoding = map; - } - }, - }; - - return CFFFont; -})(); + } + if (map.length > 0) { + this.properties.builtInEncoding = map; + } + } +} export { CFFFont }; From ff85bcfc0ed67c776f76aa4f2ff66118160873ba Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:46:49 +0200 Subject: [PATCH 12/18] Move the `Type1Font` from `src/core/fonts.js` and into its own file --- src/core/fonts.js | 426 +-------------------------------------- src/core/type1_font.js | 438 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+), 423 deletions(-) create mode 100644 src/core/type1_font.js diff --git a/src/core/fonts.js b/src/core/fonts.js index a641ac33f..a2f175fa3 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -26,25 +26,13 @@ import { string32, warn, } from "../shared/util.js"; -import { - CFF, - CFFCharset, - CFFCompiler, - CFFHeader, - CFFIndex, - CFFParser, - CFFPrivateDict, - CFFStandardStrings, - CFFStrings, - CFFTopDict, -} from "./cff_parser.js"; +import { CFFCompiler, CFFParser } from "./cff_parser.js"; import { FontFlags, getFontType, MacStandardGlyphOrdering, recoverGlyphName, SEAC_ANALYSIS_ENABLED, - type1FontGlyphMapping, } from "./fonts_utils.js"; import { getDingbatsGlyphsUnicode, getGlyphsUnicode } from "./glyphlist.js"; import { @@ -67,17 +55,13 @@ import { mapSpecialUnicodeValues, } from "./unicode.js"; import { IdentityToUnicodeMap, ToUnicodeMap } from "./to_unicode_map.js"; -import { - isWhiteSpace, - MissingDataException, - readUint32, -} from "./core_utils.js"; import { CFFFont } from "./cff_font.js"; import { FontRendererFactory } from "./font_renderer.js"; import { IdentityCMap } from "./cmap.js"; import { OpenTypeFileBuilder } from "./opentype_file_builder.js"; +import { readUint32 } from "./core_utils.js"; import { Stream } from "./stream.js"; -import { Type1Parser } from "./type1_parser.js"; +import { Type1Font } from "./type1_font.js"; // Unicode Private Use Areas: const PRIVATE_USE_AREAS = [ @@ -3189,408 +3173,4 @@ var ErrorFont = (function ErrorFontClosure() { return ErrorFont; })(); -// Type1Font is also a CIDFontType0. -var Type1Font = (function Type1FontClosure() { - function findBlock(streamBytes, signature, startIndex) { - var streamBytesLength = streamBytes.length; - var signatureLength = signature.length; - var scanLength = streamBytesLength - signatureLength; - - var i = startIndex, - j, - found = false; - while (i < scanLength) { - j = 0; - while (j < signatureLength && streamBytes[i + j] === signature[j]) { - j++; - } - if (j >= signatureLength) { - // `signature` found, skip over whitespace. - i += j; - while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { - i++; - } - found = true; - break; - } - i++; - } - return { - found, - length: i, - }; - } - - function getHeaderBlock(stream, suggestedLength) { - var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; - - var streamStartPos = stream.pos; // Save the initial stream position. - var headerBytes, headerBytesLength, block; - try { - headerBytes = stream.getBytes(suggestedLength); - headerBytesLength = headerBytes.length; - } catch (ex) { - if (ex instanceof MissingDataException) { - throw ex; - } - // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array - // cannot hold the result of `getBytes`, and fallback to simply checking - // the entire stream (fixes issue3928.pdf). - } - - if (headerBytesLength === suggestedLength) { - // Most of the time `suggestedLength` is correct, so to speed things up we - // initially only check the last few bytes to see if the header was found. - // Otherwise we (potentially) check the entire stream to prevent errors in - // `Type1Parser` (fixes issue5686.pdf). - block = findBlock( - headerBytes, - EEXEC_SIGNATURE, - suggestedLength - 2 * EEXEC_SIGNATURE.length - ); - - if (block.found && block.length === suggestedLength) { - return { - stream: new Stream(headerBytes), - length: suggestedLength, - }; - } - } - warn('Invalid "Length1" property in Type1 font -- trying to recover.'); - stream.pos = streamStartPos; // Reset the stream position. - - var SCAN_BLOCK_LENGTH = 2048; - var actualLength; - while (true) { - var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); - block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); - - if (block.length === 0) { - break; - } - stream.pos += block.length; // Update the stream position. - - if (block.found) { - actualLength = stream.pos - streamStartPos; - break; - } - } - stream.pos = streamStartPos; // Reset the stream position. - - if (actualLength) { - return { - stream: new Stream(stream.getBytes(actualLength)), - length: actualLength, - }; - } - warn('Unable to recover "Length1" property in Type1 font -- using as is.'); - return { - stream: new Stream(stream.getBytes(suggestedLength)), - length: suggestedLength, - }; - } - - function getEexecBlock(stream, suggestedLength) { - // We should ideally parse the eexec block to ensure that `suggestedLength` - // is correct, so we don't truncate the block data if it's too small. - // However, this would also require checking if the fixed-content portion - // exists (using the 'Length3' property), and ensuring that it's valid. - // - // Given that `suggestedLength` almost always is correct, all the validation - // would require a great deal of unnecessary parsing for most fonts. - // To save time, we always fetch the entire stream instead, which also avoid - // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`). - // - // NOTE: This means that the function can include the fixed-content portion - // in the returned eexec block. In practice this does *not* seem to matter, - // since `Type1Parser_extractFontProgram` will skip over any non-commands. - var eexecBytes = stream.getBytes(); - return { - stream: new Stream(eexecBytes), - length: eexecBytes.length, - }; - } - - // eslint-disable-next-line no-shadow - function Type1Font(name, file, properties) { - // Some bad generators embed pfb file as is, we have to strip 6-byte header. - // Also, length1 and length2 might be off by 6 bytes as well. - // http://www.math.ubc.ca/~cass/piscript/type1.pdf - var PFB_HEADER_SIZE = 6; - var headerBlockLength = properties.length1; - var eexecBlockLength = properties.length2; - var pfbHeader = file.peekBytes(PFB_HEADER_SIZE); - var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; - if (pfbHeaderPresent) { - file.skip(PFB_HEADER_SIZE); - headerBlockLength = - (pfbHeader[5] << 24) | - (pfbHeader[4] << 16) | - (pfbHeader[3] << 8) | - pfbHeader[2]; - } - - // Get the data block containing glyphs and subrs information - var headerBlock = getHeaderBlock(file, headerBlockLength); - var headerBlockParser = new Type1Parser( - headerBlock.stream, - false, - SEAC_ANALYSIS_ENABLED - ); - headerBlockParser.extractFontHeader(properties); - - if (pfbHeaderPresent) { - pfbHeader = file.getBytes(PFB_HEADER_SIZE); - eexecBlockLength = - (pfbHeader[5] << 24) | - (pfbHeader[4] << 16) | - (pfbHeader[3] << 8) | - pfbHeader[2]; - } - - // Decrypt the data blocks and retrieve it's content - var eexecBlock = getEexecBlock(file, eexecBlockLength); - var eexecBlockParser = new Type1Parser( - eexecBlock.stream, - true, - SEAC_ANALYSIS_ENABLED - ); - var data = eexecBlockParser.extractFontProgram(properties); - for (const key in data.properties) { - properties[key] = data.properties[key]; - } - - var charstrings = data.charstrings; - var type2Charstrings = this.getType2Charstrings(charstrings); - var subrs = this.getType2Subrs(data.subrs); - - this.charstrings = charstrings; - this.data = this.wrap( - name, - type2Charstrings, - this.charstrings, - subrs, - properties - ); - this.seacs = this.getSeacs(data.charstrings); - } - - Type1Font.prototype = { - get numGlyphs() { - return this.charstrings.length + 1; - }, - - getCharset: function Type1Font_getCharset() { - var charset = [".notdef"]; - var charstrings = this.charstrings; - for (var glyphId = 0; glyphId < charstrings.length; glyphId++) { - charset.push(charstrings[glyphId].glyphName); - } - return charset; - }, - - getGlyphMapping: function Type1Font_getGlyphMapping(properties) { - var charstrings = this.charstrings; - - if (properties.composite) { - const charCodeToGlyphId = Object.create(null); - // Map CIDs directly to GIDs. - for ( - let glyphId = 0, charstringsLen = charstrings.length; - glyphId < charstringsLen; - glyphId++ - ) { - const charCode = properties.cMap.charCodeOf(glyphId); - // Add 1 because glyph 0 is duplicated. - charCodeToGlyphId[charCode] = glyphId + 1; - } - return charCodeToGlyphId; - } - - var glyphNames = [".notdef"], - glyphId; - for (glyphId = 0; glyphId < charstrings.length; glyphId++) { - glyphNames.push(charstrings[glyphId].glyphName); - } - var encoding = properties.builtInEncoding; - if (encoding) { - var builtInEncoding = Object.create(null); - for (var charCode in encoding) { - glyphId = glyphNames.indexOf(encoding[charCode]); - if (glyphId >= 0) { - builtInEncoding[charCode] = glyphId; - } - } - } - - return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); - }, - - hasGlyphId: function Type1Font_hasGlyphID(id) { - if (id < 0 || id >= this.numGlyphs) { - return false; - } - if (id === 0) { - // notdef is always defined. - return true; - } - var glyph = this.charstrings[id - 1]; - return glyph.charstring.length > 0; - }, - - getSeacs: function Type1Font_getSeacs(charstrings) { - var i, ii; - var seacMap = []; - for (i = 0, ii = charstrings.length; i < ii; i++) { - var charstring = charstrings[i]; - if (charstring.seac) { - // Offset by 1 for .notdef - seacMap[i + 1] = charstring.seac; - } - } - return seacMap; - }, - - getType2Charstrings: function Type1Font_getType2Charstrings( - type1Charstrings - ) { - var type2Charstrings = []; - for (var i = 0, ii = type1Charstrings.length; i < ii; i++) { - type2Charstrings.push(type1Charstrings[i].charstring); - } - return type2Charstrings; - }, - - getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { - var bias = 0; - var count = type1Subrs.length; - if (count < 1133) { - bias = 107; - } else if (count < 33769) { - bias = 1131; - } else { - bias = 32768; - } - - // Add a bunch of empty subrs to deal with the Type2 bias - var type2Subrs = []; - var i; - for (i = 0; i < bias; i++) { - type2Subrs.push([0x0b]); - } - - for (i = 0; i < count; i++) { - type2Subrs.push(type1Subrs[i]); - } - - return type2Subrs; - }, - - wrap: function Type1Font_wrap( - name, - glyphs, - charstrings, - subrs, - properties - ) { - var cff = new CFF(); - cff.header = new CFFHeader(1, 0, 4, 4); - - cff.names = [name]; - - var topDict = new CFFTopDict(); - // CFF strings IDs 0...390 are predefined names, so refering - // to entries in our own String INDEX starts at SID 391. - topDict.setByName("version", 391); - topDict.setByName("Notice", 392); - topDict.setByName("FullName", 393); - topDict.setByName("FamilyName", 394); - topDict.setByName("Weight", 395); - topDict.setByName("Encoding", null); // placeholder - topDict.setByName("FontMatrix", properties.fontMatrix); - topDict.setByName("FontBBox", properties.bbox); - topDict.setByName("charset", null); // placeholder - topDict.setByName("CharStrings", null); // placeholder - topDict.setByName("Private", null); // placeholder - cff.topDict = topDict; - - var strings = new CFFStrings(); - strings.add("Version 0.11"); // Version - strings.add("See original notice"); // Notice - strings.add(name); // FullName - strings.add(name); // FamilyName - strings.add("Medium"); // Weight - cff.strings = strings; - - cff.globalSubrIndex = new CFFIndex(); - - var count = glyphs.length; - var charsetArray = [".notdef"]; - var i, ii; - for (i = 0; i < count; i++) { - const glyphName = charstrings[i].glyphName; - const index = CFFStandardStrings.indexOf(glyphName); - if (index === -1) { - strings.add(glyphName); - } - charsetArray.push(glyphName); - } - cff.charset = new CFFCharset(false, 0, charsetArray); - - var charStringsIndex = new CFFIndex(); - charStringsIndex.add([0x8b, 0x0e]); // .notdef - for (i = 0; i < count; i++) { - charStringsIndex.add(glyphs[i]); - } - cff.charStrings = charStringsIndex; - - var privateDict = new CFFPrivateDict(); - privateDict.setByName("Subrs", null); // placeholder - var fields = [ - "BlueValues", - "OtherBlues", - "FamilyBlues", - "FamilyOtherBlues", - "StemSnapH", - "StemSnapV", - "BlueShift", - "BlueFuzz", - "BlueScale", - "LanguageGroup", - "ExpansionFactor", - "ForceBold", - "StdHW", - "StdVW", - ]; - for (i = 0, ii = fields.length; i < ii; i++) { - var field = fields[i]; - if (!(field in properties.privateData)) { - continue; - } - var value = properties.privateData[field]; - if (Array.isArray(value)) { - // All of the private dictionary array data in CFF must be stored as - // "delta-encoded" numbers. - for (var j = value.length - 1; j > 0; j--) { - value[j] -= value[j - 1]; // ... difference from previous value - } - } - privateDict.setByName(field, value); - } - cff.topDict.privateDict = privateDict; - - var subrIndex = new CFFIndex(); - for (i = 0, ii = subrs.length; i < ii; i++) { - subrIndex.add(subrs[i]); - } - privateDict.subrsIndex = subrIndex; - - var compiler = new CFFCompiler(cff); - return compiler.compile(); - }, - }; - - return Type1Font; -})(); - export { ErrorFont, Font }; diff --git a/src/core/type1_font.js b/src/core/type1_font.js new file mode 100644 index 000000000..5cdecae6c --- /dev/null +++ b/src/core/type1_font.js @@ -0,0 +1,438 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* eslint-disable no-var */ + +import { + CFF, + CFFCharset, + CFFCompiler, + CFFHeader, + CFFIndex, + CFFPrivateDict, + CFFStandardStrings, + CFFStrings, + CFFTopDict, +} from "./cff_parser.js"; +import { isWhiteSpace, MissingDataException } from "./core_utils.js"; +import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; +import { Stream } from "./stream.js"; +import { Type1Parser } from "./type1_parser.js"; +import { warn } from "../shared/util.js"; + +// Type1Font is also a CIDFontType0. +var Type1Font = (function Type1FontClosure() { + function findBlock(streamBytes, signature, startIndex) { + var streamBytesLength = streamBytes.length; + var signatureLength = signature.length; + var scanLength = streamBytesLength - signatureLength; + + var i = startIndex, + j, + found = false; + while (i < scanLength) { + j = 0; + while (j < signatureLength && streamBytes[i + j] === signature[j]) { + j++; + } + if (j >= signatureLength) { + // `signature` found, skip over whitespace. + i += j; + while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { + i++; + } + found = true; + break; + } + i++; + } + return { + found, + length: i, + }; + } + + function getHeaderBlock(stream, suggestedLength) { + var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; + + var streamStartPos = stream.pos; // Save the initial stream position. + var headerBytes, headerBytesLength, block; + try { + headerBytes = stream.getBytes(suggestedLength); + headerBytesLength = headerBytes.length; + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array + // cannot hold the result of `getBytes`, and fallback to simply checking + // the entire stream (fixes issue3928.pdf). + } + + if (headerBytesLength === suggestedLength) { + // Most of the time `suggestedLength` is correct, so to speed things up we + // initially only check the last few bytes to see if the header was found. + // Otherwise we (potentially) check the entire stream to prevent errors in + // `Type1Parser` (fixes issue5686.pdf). + block = findBlock( + headerBytes, + EEXEC_SIGNATURE, + suggestedLength - 2 * EEXEC_SIGNATURE.length + ); + + if (block.found && block.length === suggestedLength) { + return { + stream: new Stream(headerBytes), + length: suggestedLength, + }; + } + } + warn('Invalid "Length1" property in Type1 font -- trying to recover.'); + stream.pos = streamStartPos; // Reset the stream position. + + var SCAN_BLOCK_LENGTH = 2048; + var actualLength; + while (true) { + var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); + block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); + + if (block.length === 0) { + break; + } + stream.pos += block.length; // Update the stream position. + + if (block.found) { + actualLength = stream.pos - streamStartPos; + break; + } + } + stream.pos = streamStartPos; // Reset the stream position. + + if (actualLength) { + return { + stream: new Stream(stream.getBytes(actualLength)), + length: actualLength, + }; + } + warn('Unable to recover "Length1" property in Type1 font -- using as is.'); + return { + stream: new Stream(stream.getBytes(suggestedLength)), + length: suggestedLength, + }; + } + + function getEexecBlock(stream, suggestedLength) { + // We should ideally parse the eexec block to ensure that `suggestedLength` + // is correct, so we don't truncate the block data if it's too small. + // However, this would also require checking if the fixed-content portion + // exists (using the 'Length3' property), and ensuring that it's valid. + // + // Given that `suggestedLength` almost always is correct, all the validation + // would require a great deal of unnecessary parsing for most fonts. + // To save time, we always fetch the entire stream instead, which also avoid + // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`). + // + // NOTE: This means that the function can include the fixed-content portion + // in the returned eexec block. In practice this does *not* seem to matter, + // since `Type1Parser_extractFontProgram` will skip over any non-commands. + var eexecBytes = stream.getBytes(); + return { + stream: new Stream(eexecBytes), + length: eexecBytes.length, + }; + } + + // eslint-disable-next-line no-shadow + function Type1Font(name, file, properties) { + // Some bad generators embed pfb file as is, we have to strip 6-byte header. + // Also, length1 and length2 might be off by 6 bytes as well. + // http://www.math.ubc.ca/~cass/piscript/type1.pdf + var PFB_HEADER_SIZE = 6; + var headerBlockLength = properties.length1; + var eexecBlockLength = properties.length2; + var pfbHeader = file.peekBytes(PFB_HEADER_SIZE); + var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; + if (pfbHeaderPresent) { + file.skip(PFB_HEADER_SIZE); + headerBlockLength = + (pfbHeader[5] << 24) | + (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | + pfbHeader[2]; + } + + // Get the data block containing glyphs and subrs information + var headerBlock = getHeaderBlock(file, headerBlockLength); + var headerBlockParser = new Type1Parser( + headerBlock.stream, + false, + SEAC_ANALYSIS_ENABLED + ); + headerBlockParser.extractFontHeader(properties); + + if (pfbHeaderPresent) { + pfbHeader = file.getBytes(PFB_HEADER_SIZE); + eexecBlockLength = + (pfbHeader[5] << 24) | + (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | + pfbHeader[2]; + } + + // Decrypt the data blocks and retrieve it's content + var eexecBlock = getEexecBlock(file, eexecBlockLength); + var eexecBlockParser = new Type1Parser( + eexecBlock.stream, + true, + SEAC_ANALYSIS_ENABLED + ); + var data = eexecBlockParser.extractFontProgram(properties); + for (const key in data.properties) { + properties[key] = data.properties[key]; + } + + var charstrings = data.charstrings; + var type2Charstrings = this.getType2Charstrings(charstrings); + var subrs = this.getType2Subrs(data.subrs); + + this.charstrings = charstrings; + this.data = this.wrap( + name, + type2Charstrings, + this.charstrings, + subrs, + properties + ); + this.seacs = this.getSeacs(data.charstrings); + } + + Type1Font.prototype = { + get numGlyphs() { + return this.charstrings.length + 1; + }, + + getCharset: function Type1Font_getCharset() { + var charset = [".notdef"]; + var charstrings = this.charstrings; + for (var glyphId = 0; glyphId < charstrings.length; glyphId++) { + charset.push(charstrings[glyphId].glyphName); + } + return charset; + }, + + getGlyphMapping: function Type1Font_getGlyphMapping(properties) { + var charstrings = this.charstrings; + + if (properties.composite) { + const charCodeToGlyphId = Object.create(null); + // Map CIDs directly to GIDs. + for ( + let glyphId = 0, charstringsLen = charstrings.length; + glyphId < charstringsLen; + glyphId++ + ) { + const charCode = properties.cMap.charCodeOf(glyphId); + // Add 1 because glyph 0 is duplicated. + charCodeToGlyphId[charCode] = glyphId + 1; + } + return charCodeToGlyphId; + } + + var glyphNames = [".notdef"], + glyphId; + for (glyphId = 0; glyphId < charstrings.length; glyphId++) { + glyphNames.push(charstrings[glyphId].glyphName); + } + var encoding = properties.builtInEncoding; + if (encoding) { + var builtInEncoding = Object.create(null); + for (var charCode in encoding) { + glyphId = glyphNames.indexOf(encoding[charCode]); + if (glyphId >= 0) { + builtInEncoding[charCode] = glyphId; + } + } + } + + return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); + }, + + hasGlyphId: function Type1Font_hasGlyphID(id) { + if (id < 0 || id >= this.numGlyphs) { + return false; + } + if (id === 0) { + // notdef is always defined. + return true; + } + var glyph = this.charstrings[id - 1]; + return glyph.charstring.length > 0; + }, + + getSeacs: function Type1Font_getSeacs(charstrings) { + var i, ii; + var seacMap = []; + for (i = 0, ii = charstrings.length; i < ii; i++) { + var charstring = charstrings[i]; + if (charstring.seac) { + // Offset by 1 for .notdef + seacMap[i + 1] = charstring.seac; + } + } + return seacMap; + }, + + getType2Charstrings: function Type1Font_getType2Charstrings( + type1Charstrings + ) { + var type2Charstrings = []; + for (var i = 0, ii = type1Charstrings.length; i < ii; i++) { + type2Charstrings.push(type1Charstrings[i].charstring); + } + return type2Charstrings; + }, + + getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { + var bias = 0; + var count = type1Subrs.length; + if (count < 1133) { + bias = 107; + } else if (count < 33769) { + bias = 1131; + } else { + bias = 32768; + } + + // Add a bunch of empty subrs to deal with the Type2 bias + var type2Subrs = []; + var i; + for (i = 0; i < bias; i++) { + type2Subrs.push([0x0b]); + } + + for (i = 0; i < count; i++) { + type2Subrs.push(type1Subrs[i]); + } + + return type2Subrs; + }, + + wrap: function Type1Font_wrap( + name, + glyphs, + charstrings, + subrs, + properties + ) { + var cff = new CFF(); + cff.header = new CFFHeader(1, 0, 4, 4); + + cff.names = [name]; + + var topDict = new CFFTopDict(); + // CFF strings IDs 0...390 are predefined names, so refering + // to entries in our own String INDEX starts at SID 391. + topDict.setByName("version", 391); + topDict.setByName("Notice", 392); + topDict.setByName("FullName", 393); + topDict.setByName("FamilyName", 394); + topDict.setByName("Weight", 395); + topDict.setByName("Encoding", null); // placeholder + topDict.setByName("FontMatrix", properties.fontMatrix); + topDict.setByName("FontBBox", properties.bbox); + topDict.setByName("charset", null); // placeholder + topDict.setByName("CharStrings", null); // placeholder + topDict.setByName("Private", null); // placeholder + cff.topDict = topDict; + + var strings = new CFFStrings(); + strings.add("Version 0.11"); // Version + strings.add("See original notice"); // Notice + strings.add(name); // FullName + strings.add(name); // FamilyName + strings.add("Medium"); // Weight + cff.strings = strings; + + cff.globalSubrIndex = new CFFIndex(); + + var count = glyphs.length; + var charsetArray = [".notdef"]; + var i, ii; + for (i = 0; i < count; i++) { + const glyphName = charstrings[i].glyphName; + const index = CFFStandardStrings.indexOf(glyphName); + if (index === -1) { + strings.add(glyphName); + } + charsetArray.push(glyphName); + } + cff.charset = new CFFCharset(false, 0, charsetArray); + + var charStringsIndex = new CFFIndex(); + charStringsIndex.add([0x8b, 0x0e]); // .notdef + for (i = 0; i < count; i++) { + charStringsIndex.add(glyphs[i]); + } + cff.charStrings = charStringsIndex; + + var privateDict = new CFFPrivateDict(); + privateDict.setByName("Subrs", null); // placeholder + var fields = [ + "BlueValues", + "OtherBlues", + "FamilyBlues", + "FamilyOtherBlues", + "StemSnapH", + "StemSnapV", + "BlueShift", + "BlueFuzz", + "BlueScale", + "LanguageGroup", + "ExpansionFactor", + "ForceBold", + "StdHW", + "StdVW", + ]; + for (i = 0, ii = fields.length; i < ii; i++) { + var field = fields[i]; + if (!(field in properties.privateData)) { + continue; + } + var value = properties.privateData[field]; + if (Array.isArray(value)) { + // All of the private dictionary array data in CFF must be stored as + // "delta-encoded" numbers. + for (var j = value.length - 1; j > 0; j--) { + value[j] -= value[j - 1]; // ... difference from previous value + } + } + privateDict.setByName(field, value); + } + cff.topDict.privateDict = privateDict; + + var subrIndex = new CFFIndex(); + for (i = 0, ii = subrs.length; i < ii; i++) { + subrIndex.add(subrs[i]); + } + privateDict.subrsIndex = subrIndex; + + var compiler = new CFFCompiler(cff); + return compiler.compile(); + }, + }; + + return Type1Font; +})(); + +export { Type1Font }; From 4bd69556abcc9b9e61b25756ab38f53c29c38922 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:48:39 +0200 Subject: [PATCH 13/18] Enable the `no-var` rule in the `src/core/type1_font.js` file These changes were made *mostly* automatically, using `gulp lint --fix`, with the following manual changes: ```diff diff --git a/src/core/type1_font.js b/src/core/type1_font.js index 50a3e49e6..55a2005fb 100644 --- a/src/core/type1_font.js +++ b/src/core/type1_font.js @@ -38,10 +38,9 @@ const Type1Font = (function Type1FontClosure() { const scanLength = streamBytesLength - signatureLength; let i = startIndex, - j, found = false; while (i < scanLength) { - j = 0; + let j = 0; while (j < signatureLength && streamBytes[i + j] === signature[j]) { j++; } @@ -248,14 +247,14 @@ const Type1Font = (function Type1FontClosure() { return charCodeToGlyphId; } - let glyphNames = [".notdef"], - glyphId; + const glyphNames = [".notdef"]; + let builtInEncoding, glyphId; for (glyphId = 0; glyphId < charstrings.length; glyphId++) { glyphNames.push(charstrings[glyphId].glyphName); } const encoding = properties.builtInEncoding; if (encoding) { - var builtInEncoding = Object.create(null); + builtInEncoding = Object.create(null); for (const charCode in encoding) { glyphId = glyphNames.indexOf(encoding[charCode]); if (glyphId >= 0 ``` --- src/core/type1_font.js | 120 ++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/src/core/type1_font.js b/src/core/type1_font.js index 5cdecae6c..55a2005fb 100644 --- a/src/core/type1_font.js +++ b/src/core/type1_font.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { CFF, @@ -32,17 +31,16 @@ import { Type1Parser } from "./type1_parser.js"; import { warn } from "../shared/util.js"; // Type1Font is also a CIDFontType0. -var Type1Font = (function Type1FontClosure() { +const Type1Font = (function Type1FontClosure() { function findBlock(streamBytes, signature, startIndex) { - var streamBytesLength = streamBytes.length; - var signatureLength = signature.length; - var scanLength = streamBytesLength - signatureLength; + const streamBytesLength = streamBytes.length; + const signatureLength = signature.length; + const scanLength = streamBytesLength - signatureLength; - var i = startIndex, - j, + let i = startIndex, found = false; while (i < scanLength) { - j = 0; + let j = 0; while (j < signatureLength && streamBytes[i + j] === signature[j]) { j++; } @@ -64,10 +62,10 @@ var Type1Font = (function Type1FontClosure() { } function getHeaderBlock(stream, suggestedLength) { - var EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; + const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; - var streamStartPos = stream.pos; // Save the initial stream position. - var headerBytes, headerBytesLength, block; + const streamStartPos = stream.pos; // Save the initial stream position. + let headerBytes, headerBytesLength, block; try { headerBytes = stream.getBytes(suggestedLength); headerBytesLength = headerBytes.length; @@ -101,10 +99,10 @@ var Type1Font = (function Type1FontClosure() { warn('Invalid "Length1" property in Type1 font -- trying to recover.'); stream.pos = streamStartPos; // Reset the stream position. - var SCAN_BLOCK_LENGTH = 2048; - var actualLength; + const SCAN_BLOCK_LENGTH = 2048; + let actualLength; while (true) { - var scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); + const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); if (block.length === 0) { @@ -146,7 +144,7 @@ var Type1Font = (function Type1FontClosure() { // NOTE: This means that the function can include the fixed-content portion // in the returned eexec block. In practice this does *not* seem to matter, // since `Type1Parser_extractFontProgram` will skip over any non-commands. - var eexecBytes = stream.getBytes(); + const eexecBytes = stream.getBytes(); return { stream: new Stream(eexecBytes), length: eexecBytes.length, @@ -158,11 +156,11 @@ var Type1Font = (function Type1FontClosure() { // Some bad generators embed pfb file as is, we have to strip 6-byte header. // Also, length1 and length2 might be off by 6 bytes as well. // http://www.math.ubc.ca/~cass/piscript/type1.pdf - var PFB_HEADER_SIZE = 6; - var headerBlockLength = properties.length1; - var eexecBlockLength = properties.length2; - var pfbHeader = file.peekBytes(PFB_HEADER_SIZE); - var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; + const PFB_HEADER_SIZE = 6; + let headerBlockLength = properties.length1; + let eexecBlockLength = properties.length2; + let pfbHeader = file.peekBytes(PFB_HEADER_SIZE); + const pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01; if (pfbHeaderPresent) { file.skip(PFB_HEADER_SIZE); headerBlockLength = @@ -173,8 +171,8 @@ var Type1Font = (function Type1FontClosure() { } // Get the data block containing glyphs and subrs information - var headerBlock = getHeaderBlock(file, headerBlockLength); - var headerBlockParser = new Type1Parser( + const headerBlock = getHeaderBlock(file, headerBlockLength); + const headerBlockParser = new Type1Parser( headerBlock.stream, false, SEAC_ANALYSIS_ENABLED @@ -191,20 +189,20 @@ var Type1Font = (function Type1FontClosure() { } // Decrypt the data blocks and retrieve it's content - var eexecBlock = getEexecBlock(file, eexecBlockLength); - var eexecBlockParser = new Type1Parser( + const eexecBlock = getEexecBlock(file, eexecBlockLength); + const eexecBlockParser = new Type1Parser( eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED ); - var data = eexecBlockParser.extractFontProgram(properties); + const data = eexecBlockParser.extractFontProgram(properties); for (const key in data.properties) { properties[key] = data.properties[key]; } - var charstrings = data.charstrings; - var type2Charstrings = this.getType2Charstrings(charstrings); - var subrs = this.getType2Subrs(data.subrs); + const charstrings = data.charstrings; + const type2Charstrings = this.getType2Charstrings(charstrings); + const subrs = this.getType2Subrs(data.subrs); this.charstrings = charstrings; this.data = this.wrap( @@ -223,16 +221,16 @@ var Type1Font = (function Type1FontClosure() { }, getCharset: function Type1Font_getCharset() { - var charset = [".notdef"]; - var charstrings = this.charstrings; - for (var glyphId = 0; glyphId < charstrings.length; glyphId++) { + const charset = [".notdef"]; + const charstrings = this.charstrings; + for (let glyphId = 0; glyphId < charstrings.length; glyphId++) { charset.push(charstrings[glyphId].glyphName); } return charset; }, getGlyphMapping: function Type1Font_getGlyphMapping(properties) { - var charstrings = this.charstrings; + const charstrings = this.charstrings; if (properties.composite) { const charCodeToGlyphId = Object.create(null); @@ -249,15 +247,15 @@ var Type1Font = (function Type1FontClosure() { return charCodeToGlyphId; } - var glyphNames = [".notdef"], - glyphId; + const glyphNames = [".notdef"]; + let builtInEncoding, glyphId; for (glyphId = 0; glyphId < charstrings.length; glyphId++) { glyphNames.push(charstrings[glyphId].glyphName); } - var encoding = properties.builtInEncoding; + const encoding = properties.builtInEncoding; if (encoding) { - var builtInEncoding = Object.create(null); - for (var charCode in encoding) { + builtInEncoding = Object.create(null); + for (const charCode in encoding) { glyphId = glyphNames.indexOf(encoding[charCode]); if (glyphId >= 0) { builtInEncoding[charCode] = glyphId; @@ -276,15 +274,15 @@ var Type1Font = (function Type1FontClosure() { // notdef is always defined. return true; } - var glyph = this.charstrings[id - 1]; + const glyph = this.charstrings[id - 1]; return glyph.charstring.length > 0; }, getSeacs: function Type1Font_getSeacs(charstrings) { - var i, ii; - var seacMap = []; + let i, ii; + const seacMap = []; for (i = 0, ii = charstrings.length; i < ii; i++) { - var charstring = charstrings[i]; + const charstring = charstrings[i]; if (charstring.seac) { // Offset by 1 for .notdef seacMap[i + 1] = charstring.seac; @@ -296,16 +294,16 @@ var Type1Font = (function Type1FontClosure() { getType2Charstrings: function Type1Font_getType2Charstrings( type1Charstrings ) { - var type2Charstrings = []; - for (var i = 0, ii = type1Charstrings.length; i < ii; i++) { + const type2Charstrings = []; + for (let i = 0, ii = type1Charstrings.length; i < ii; i++) { type2Charstrings.push(type1Charstrings[i].charstring); } return type2Charstrings; }, getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { - var bias = 0; - var count = type1Subrs.length; + let bias = 0; + const count = type1Subrs.length; if (count < 1133) { bias = 107; } else if (count < 33769) { @@ -315,8 +313,8 @@ var Type1Font = (function Type1FontClosure() { } // Add a bunch of empty subrs to deal with the Type2 bias - var type2Subrs = []; - var i; + const type2Subrs = []; + let i; for (i = 0; i < bias; i++) { type2Subrs.push([0x0b]); } @@ -335,12 +333,12 @@ var Type1Font = (function Type1FontClosure() { subrs, properties ) { - var cff = new CFF(); + const cff = new CFF(); cff.header = new CFFHeader(1, 0, 4, 4); cff.names = [name]; - var topDict = new CFFTopDict(); + const topDict = new CFFTopDict(); // CFF strings IDs 0...390 are predefined names, so refering // to entries in our own String INDEX starts at SID 391. topDict.setByName("version", 391); @@ -356,7 +354,7 @@ var Type1Font = (function Type1FontClosure() { topDict.setByName("Private", null); // placeholder cff.topDict = topDict; - var strings = new CFFStrings(); + const strings = new CFFStrings(); strings.add("Version 0.11"); // Version strings.add("See original notice"); // Notice strings.add(name); // FullName @@ -366,9 +364,9 @@ var Type1Font = (function Type1FontClosure() { cff.globalSubrIndex = new CFFIndex(); - var count = glyphs.length; - var charsetArray = [".notdef"]; - var i, ii; + const count = glyphs.length; + const charsetArray = [".notdef"]; + let i, ii; for (i = 0; i < count; i++) { const glyphName = charstrings[i].glyphName; const index = CFFStandardStrings.indexOf(glyphName); @@ -379,16 +377,16 @@ var Type1Font = (function Type1FontClosure() { } cff.charset = new CFFCharset(false, 0, charsetArray); - var charStringsIndex = new CFFIndex(); + const charStringsIndex = new CFFIndex(); charStringsIndex.add([0x8b, 0x0e]); // .notdef for (i = 0; i < count; i++) { charStringsIndex.add(glyphs[i]); } cff.charStrings = charStringsIndex; - var privateDict = new CFFPrivateDict(); + const privateDict = new CFFPrivateDict(); privateDict.setByName("Subrs", null); // placeholder - var fields = [ + const fields = [ "BlueValues", "OtherBlues", "FamilyBlues", @@ -405,15 +403,15 @@ var Type1Font = (function Type1FontClosure() { "StdVW", ]; for (i = 0, ii = fields.length; i < ii; i++) { - var field = fields[i]; + const field = fields[i]; if (!(field in properties.privateData)) { continue; } - var value = properties.privateData[field]; + const value = properties.privateData[field]; if (Array.isArray(value)) { // All of the private dictionary array data in CFF must be stored as // "delta-encoded" numbers. - for (var j = value.length - 1; j > 0; j--) { + for (let j = value.length - 1; j > 0; j--) { value[j] -= value[j - 1]; // ... difference from previous value } } @@ -421,13 +419,13 @@ var Type1Font = (function Type1FontClosure() { } cff.topDict.privateDict = privateDict; - var subrIndex = new CFFIndex(); + const subrIndex = new CFFIndex(); for (i = 0, ii = subrs.length; i < ii; i++) { subrIndex.add(subrs[i]); } privateDict.subrsIndex = subrIndex; - var compiler = new CFFCompiler(cff); + const compiler = new CFFCompiler(cff); return compiler.compile(); }, }; From f64b7922b359df6e0643d5a93fe9d684d8d03b3a Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:54:45 +0200 Subject: [PATCH 14/18] Convert `src/core/type1_font.js` to use standard classes --- src/core/type1_font.js | 616 ++++++++++++++++++++--------------------- 1 file changed, 302 insertions(+), 314 deletions(-) diff --git a/src/core/type1_font.js b/src/core/type1_font.js index 55a2005fb..bcb46bc82 100644 --- a/src/core/type1_font.js +++ b/src/core/type1_font.js @@ -30,129 +30,130 @@ import { Stream } from "./stream.js"; import { Type1Parser } from "./type1_parser.js"; import { warn } from "../shared/util.js"; -// Type1Font is also a CIDFontType0. -const Type1Font = (function Type1FontClosure() { - function findBlock(streamBytes, signature, startIndex) { - const streamBytesLength = streamBytes.length; - const signatureLength = signature.length; - const scanLength = streamBytesLength - signatureLength; +function findBlock(streamBytes, signature, startIndex) { + const streamBytesLength = streamBytes.length; + const signatureLength = signature.length; + const scanLength = streamBytesLength - signatureLength; - let i = startIndex, - found = false; - while (i < scanLength) { - let j = 0; - while (j < signatureLength && streamBytes[i + j] === signature[j]) { - j++; - } - if (j >= signatureLength) { - // `signature` found, skip over whitespace. - i += j; - while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { - i++; - } - found = true; - break; - } - i++; + let i = startIndex, + found = false; + while (i < scanLength) { + let j = 0; + while (j < signatureLength && streamBytes[i + j] === signature[j]) { + j++; } - return { - found, - length: i, - }; + if (j >= signatureLength) { + // `signature` found, skip over whitespace. + i += j; + while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) { + i++; + } + found = true; + break; + } + i++; + } + return { + found, + length: i, + }; +} + +function getHeaderBlock(stream, suggestedLength) { + const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; + + const streamStartPos = stream.pos; // Save the initial stream position. + let headerBytes, headerBytesLength, block; + try { + headerBytes = stream.getBytes(suggestedLength); + headerBytesLength = headerBytes.length; + } catch (ex) { + if (ex instanceof MissingDataException) { + throw ex; + } + // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array + // cannot hold the result of `getBytes`, and fallback to simply checking + // the entire stream (fixes issue3928.pdf). } - function getHeaderBlock(stream, suggestedLength) { - const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63]; + if (headerBytesLength === suggestedLength) { + // Most of the time `suggestedLength` is correct, so to speed things up we + // initially only check the last few bytes to see if the header was found. + // Otherwise we (potentially) check the entire stream to prevent errors in + // `Type1Parser` (fixes issue5686.pdf). + block = findBlock( + headerBytes, + EEXEC_SIGNATURE, + suggestedLength - 2 * EEXEC_SIGNATURE.length + ); - const streamStartPos = stream.pos; // Save the initial stream position. - let headerBytes, headerBytesLength, block; - try { - headerBytes = stream.getBytes(suggestedLength); - headerBytesLength = headerBytes.length; - } catch (ex) { - if (ex instanceof MissingDataException) { - throw ex; - } - // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array - // cannot hold the result of `getBytes`, and fallback to simply checking - // the entire stream (fixes issue3928.pdf). - } - - if (headerBytesLength === suggestedLength) { - // Most of the time `suggestedLength` is correct, so to speed things up we - // initially only check the last few bytes to see if the header was found. - // Otherwise we (potentially) check the entire stream to prevent errors in - // `Type1Parser` (fixes issue5686.pdf). - block = findBlock( - headerBytes, - EEXEC_SIGNATURE, - suggestedLength - 2 * EEXEC_SIGNATURE.length - ); - - if (block.found && block.length === suggestedLength) { - return { - stream: new Stream(headerBytes), - length: suggestedLength, - }; - } - } - warn('Invalid "Length1" property in Type1 font -- trying to recover.'); - stream.pos = streamStartPos; // Reset the stream position. - - const SCAN_BLOCK_LENGTH = 2048; - let actualLength; - while (true) { - const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); - block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); - - if (block.length === 0) { - break; - } - stream.pos += block.length; // Update the stream position. - - if (block.found) { - actualLength = stream.pos - streamStartPos; - break; - } - } - stream.pos = streamStartPos; // Reset the stream position. - - if (actualLength) { + if (block.found && block.length === suggestedLength) { return { - stream: new Stream(stream.getBytes(actualLength)), - length: actualLength, + stream: new Stream(headerBytes), + length: suggestedLength, }; } - warn('Unable to recover "Length1" property in Type1 font -- using as is.'); + } + warn('Invalid "Length1" property in Type1 font -- trying to recover.'); + stream.pos = streamStartPos; // Reset the stream position. + + const SCAN_BLOCK_LENGTH = 2048; + let actualLength; + while (true) { + const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH); + block = findBlock(scanBytes, EEXEC_SIGNATURE, 0); + + if (block.length === 0) { + break; + } + stream.pos += block.length; // Update the stream position. + + if (block.found) { + actualLength = stream.pos - streamStartPos; + break; + } + } + stream.pos = streamStartPos; // Reset the stream position. + + if (actualLength) { return { - stream: new Stream(stream.getBytes(suggestedLength)), - length: suggestedLength, + stream: new Stream(stream.getBytes(actualLength)), + length: actualLength, }; } + warn('Unable to recover "Length1" property in Type1 font -- using as is.'); + return { + stream: new Stream(stream.getBytes(suggestedLength)), + length: suggestedLength, + }; +} - function getEexecBlock(stream, suggestedLength) { - // We should ideally parse the eexec block to ensure that `suggestedLength` - // is correct, so we don't truncate the block data if it's too small. - // However, this would also require checking if the fixed-content portion - // exists (using the 'Length3' property), and ensuring that it's valid. - // - // Given that `suggestedLength` almost always is correct, all the validation - // would require a great deal of unnecessary parsing for most fonts. - // To save time, we always fetch the entire stream instead, which also avoid - // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`). - // - // NOTE: This means that the function can include the fixed-content portion - // in the returned eexec block. In practice this does *not* seem to matter, - // since `Type1Parser_extractFontProgram` will skip over any non-commands. - const eexecBytes = stream.getBytes(); - return { - stream: new Stream(eexecBytes), - length: eexecBytes.length, - }; - } +function getEexecBlock(stream, suggestedLength) { + // We should ideally parse the eexec block to ensure that `suggestedLength` + // is correct, so we don't truncate the block data if it's too small. + // However, this would also require checking if the fixed-content portion + // exists (using the 'Length3' property), and ensuring that it's valid. + // + // Given that `suggestedLength` almost always is correct, all the validation + // would require a great deal of unnecessary parsing for most fonts. + // To save time, we always fetch the entire stream instead, which also avoid + // issues if `suggestedLength` is huge (see comment in `getHeaderBlock`). + // + // NOTE: This means that the function can include the fixed-content portion + // in the returned eexec block. In practice this does *not* seem to matter, + // since `Type1Parser_extractFontProgram` will skip over any non-commands. + const eexecBytes = stream.getBytes(); + return { + stream: new Stream(eexecBytes), + length: eexecBytes.length, + }; +} - // eslint-disable-next-line no-shadow - function Type1Font(name, file, properties) { +/** + * Type1Font is also a CIDFontType0. + */ +class Type1Font { + constructor(name, file, properties) { // Some bad generators embed pfb file as is, we have to strip 6-byte header. // Also, length1 and length2 might be off by 6 bytes as well. // http://www.math.ubc.ca/~cass/piscript/type1.pdf @@ -215,222 +216,209 @@ const Type1Font = (function Type1FontClosure() { this.seacs = this.getSeacs(data.charstrings); } - Type1Font.prototype = { - get numGlyphs() { - return this.charstrings.length + 1; - }, + get numGlyphs() { + return this.charstrings.length + 1; + } - getCharset: function Type1Font_getCharset() { - const charset = [".notdef"]; - const charstrings = this.charstrings; - for (let glyphId = 0; glyphId < charstrings.length; glyphId++) { - charset.push(charstrings[glyphId].glyphName); + getCharset() { + const charset = [".notdef"]; + const charstrings = this.charstrings; + for (let glyphId = 0; glyphId < charstrings.length; glyphId++) { + charset.push(charstrings[glyphId].glyphName); + } + return charset; + } + + getGlyphMapping(properties) { + const charstrings = this.charstrings; + + if (properties.composite) { + const charCodeToGlyphId = Object.create(null); + // Map CIDs directly to GIDs. + for ( + let glyphId = 0, charstringsLen = charstrings.length; + glyphId < charstringsLen; + glyphId++ + ) { + const charCode = properties.cMap.charCodeOf(glyphId); + // Add 1 because glyph 0 is duplicated. + charCodeToGlyphId[charCode] = glyphId + 1; } - return charset; - }, + return charCodeToGlyphId; + } - getGlyphMapping: function Type1Font_getGlyphMapping(properties) { - const charstrings = this.charstrings; - - if (properties.composite) { - const charCodeToGlyphId = Object.create(null); - // Map CIDs directly to GIDs. - for ( - let glyphId = 0, charstringsLen = charstrings.length; - glyphId < charstringsLen; - glyphId++ - ) { - const charCode = properties.cMap.charCodeOf(glyphId); - // Add 1 because glyph 0 is duplicated. - charCodeToGlyphId[charCode] = glyphId + 1; - } - return charCodeToGlyphId; - } - - const glyphNames = [".notdef"]; - let builtInEncoding, glyphId; - for (glyphId = 0; glyphId < charstrings.length; glyphId++) { - glyphNames.push(charstrings[glyphId].glyphName); - } - const encoding = properties.builtInEncoding; - if (encoding) { - builtInEncoding = Object.create(null); - for (const charCode in encoding) { - glyphId = glyphNames.indexOf(encoding[charCode]); - if (glyphId >= 0) { - builtInEncoding[charCode] = glyphId; - } + const glyphNames = [".notdef"]; + let builtInEncoding, glyphId; + for (glyphId = 0; glyphId < charstrings.length; glyphId++) { + glyphNames.push(charstrings[glyphId].glyphName); + } + const encoding = properties.builtInEncoding; + if (encoding) { + builtInEncoding = Object.create(null); + for (const charCode in encoding) { + glyphId = glyphNames.indexOf(encoding[charCode]); + if (glyphId >= 0) { + builtInEncoding[charCode] = glyphId; } } + } - return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); - }, + return type1FontGlyphMapping(properties, builtInEncoding, glyphNames); + } - hasGlyphId: function Type1Font_hasGlyphID(id) { - if (id < 0 || id >= this.numGlyphs) { - return false; + hasGlyphId(id) { + if (id < 0 || id >= this.numGlyphs) { + return false; + } + if (id === 0) { + // notdef is always defined. + return true; + } + const glyph = this.charstrings[id - 1]; + return glyph.charstring.length > 0; + } + + getSeacs(charstrings) { + const seacMap = []; + for (let i = 0, ii = charstrings.length; i < ii; i++) { + const charstring = charstrings[i]; + if (charstring.seac) { + // Offset by 1 for .notdef + seacMap[i + 1] = charstring.seac; } - if (id === 0) { - // notdef is always defined. - return true; - } - const glyph = this.charstrings[id - 1]; - return glyph.charstring.length > 0; - }, + } + return seacMap; + } - getSeacs: function Type1Font_getSeacs(charstrings) { - let i, ii; - const seacMap = []; - for (i = 0, ii = charstrings.length; i < ii; i++) { - const charstring = charstrings[i]; - if (charstring.seac) { - // Offset by 1 for .notdef - seacMap[i + 1] = charstring.seac; + getType2Charstrings(type1Charstrings) { + const type2Charstrings = []; + for (let i = 0, ii = type1Charstrings.length; i < ii; i++) { + type2Charstrings.push(type1Charstrings[i].charstring); + } + return type2Charstrings; + } + + getType2Subrs(type1Subrs) { + let bias = 0; + const count = type1Subrs.length; + if (count < 1133) { + bias = 107; + } else if (count < 33769) { + bias = 1131; + } else { + bias = 32768; + } + + // Add a bunch of empty subrs to deal with the Type2 bias + const type2Subrs = []; + let i; + for (i = 0; i < bias; i++) { + type2Subrs.push([0x0b]); + } + + for (i = 0; i < count; i++) { + type2Subrs.push(type1Subrs[i]); + } + + return type2Subrs; + } + + wrap(name, glyphs, charstrings, subrs, properties) { + const cff = new CFF(); + cff.header = new CFFHeader(1, 0, 4, 4); + + cff.names = [name]; + + const topDict = new CFFTopDict(); + // CFF strings IDs 0...390 are predefined names, so refering + // to entries in our own String INDEX starts at SID 391. + topDict.setByName("version", 391); + topDict.setByName("Notice", 392); + topDict.setByName("FullName", 393); + topDict.setByName("FamilyName", 394); + topDict.setByName("Weight", 395); + topDict.setByName("Encoding", null); // placeholder + topDict.setByName("FontMatrix", properties.fontMatrix); + topDict.setByName("FontBBox", properties.bbox); + topDict.setByName("charset", null); // placeholder + topDict.setByName("CharStrings", null); // placeholder + topDict.setByName("Private", null); // placeholder + cff.topDict = topDict; + + const strings = new CFFStrings(); + strings.add("Version 0.11"); // Version + strings.add("See original notice"); // Notice + strings.add(name); // FullName + strings.add(name); // FamilyName + strings.add("Medium"); // Weight + cff.strings = strings; + + cff.globalSubrIndex = new CFFIndex(); + + const count = glyphs.length; + const charsetArray = [".notdef"]; + let i, ii; + for (i = 0; i < count; i++) { + const glyphName = charstrings[i].glyphName; + const index = CFFStandardStrings.indexOf(glyphName); + if (index === -1) { + strings.add(glyphName); + } + charsetArray.push(glyphName); + } + cff.charset = new CFFCharset(false, 0, charsetArray); + + const charStringsIndex = new CFFIndex(); + charStringsIndex.add([0x8b, 0x0e]); // .notdef + for (i = 0; i < count; i++) { + charStringsIndex.add(glyphs[i]); + } + cff.charStrings = charStringsIndex; + + const privateDict = new CFFPrivateDict(); + privateDict.setByName("Subrs", null); // placeholder + const fields = [ + "BlueValues", + "OtherBlues", + "FamilyBlues", + "FamilyOtherBlues", + "StemSnapH", + "StemSnapV", + "BlueShift", + "BlueFuzz", + "BlueScale", + "LanguageGroup", + "ExpansionFactor", + "ForceBold", + "StdHW", + "StdVW", + ]; + for (i = 0, ii = fields.length; i < ii; i++) { + const field = fields[i]; + if (!(field in properties.privateData)) { + continue; + } + const value = properties.privateData[field]; + if (Array.isArray(value)) { + // All of the private dictionary array data in CFF must be stored as + // "delta-encoded" numbers. + for (let j = value.length - 1; j > 0; j--) { + value[j] -= value[j - 1]; // ... difference from previous value } } - return seacMap; - }, + privateDict.setByName(field, value); + } + cff.topDict.privateDict = privateDict; - getType2Charstrings: function Type1Font_getType2Charstrings( - type1Charstrings - ) { - const type2Charstrings = []; - for (let i = 0, ii = type1Charstrings.length; i < ii; i++) { - type2Charstrings.push(type1Charstrings[i].charstring); - } - return type2Charstrings; - }, + const subrIndex = new CFFIndex(); + for (i = 0, ii = subrs.length; i < ii; i++) { + subrIndex.add(subrs[i]); + } + privateDict.subrsIndex = subrIndex; - getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) { - let bias = 0; - const count = type1Subrs.length; - if (count < 1133) { - bias = 107; - } else if (count < 33769) { - bias = 1131; - } else { - bias = 32768; - } - - // Add a bunch of empty subrs to deal with the Type2 bias - const type2Subrs = []; - let i; - for (i = 0; i < bias; i++) { - type2Subrs.push([0x0b]); - } - - for (i = 0; i < count; i++) { - type2Subrs.push(type1Subrs[i]); - } - - return type2Subrs; - }, - - wrap: function Type1Font_wrap( - name, - glyphs, - charstrings, - subrs, - properties - ) { - const cff = new CFF(); - cff.header = new CFFHeader(1, 0, 4, 4); - - cff.names = [name]; - - const topDict = new CFFTopDict(); - // CFF strings IDs 0...390 are predefined names, so refering - // to entries in our own String INDEX starts at SID 391. - topDict.setByName("version", 391); - topDict.setByName("Notice", 392); - topDict.setByName("FullName", 393); - topDict.setByName("FamilyName", 394); - topDict.setByName("Weight", 395); - topDict.setByName("Encoding", null); // placeholder - topDict.setByName("FontMatrix", properties.fontMatrix); - topDict.setByName("FontBBox", properties.bbox); - topDict.setByName("charset", null); // placeholder - topDict.setByName("CharStrings", null); // placeholder - topDict.setByName("Private", null); // placeholder - cff.topDict = topDict; - - const strings = new CFFStrings(); - strings.add("Version 0.11"); // Version - strings.add("See original notice"); // Notice - strings.add(name); // FullName - strings.add(name); // FamilyName - strings.add("Medium"); // Weight - cff.strings = strings; - - cff.globalSubrIndex = new CFFIndex(); - - const count = glyphs.length; - const charsetArray = [".notdef"]; - let i, ii; - for (i = 0; i < count; i++) { - const glyphName = charstrings[i].glyphName; - const index = CFFStandardStrings.indexOf(glyphName); - if (index === -1) { - strings.add(glyphName); - } - charsetArray.push(glyphName); - } - cff.charset = new CFFCharset(false, 0, charsetArray); - - const charStringsIndex = new CFFIndex(); - charStringsIndex.add([0x8b, 0x0e]); // .notdef - for (i = 0; i < count; i++) { - charStringsIndex.add(glyphs[i]); - } - cff.charStrings = charStringsIndex; - - const privateDict = new CFFPrivateDict(); - privateDict.setByName("Subrs", null); // placeholder - const fields = [ - "BlueValues", - "OtherBlues", - "FamilyBlues", - "FamilyOtherBlues", - "StemSnapH", - "StemSnapV", - "BlueShift", - "BlueFuzz", - "BlueScale", - "LanguageGroup", - "ExpansionFactor", - "ForceBold", - "StdHW", - "StdVW", - ]; - for (i = 0, ii = fields.length; i < ii; i++) { - const field = fields[i]; - if (!(field in properties.privateData)) { - continue; - } - const value = properties.privateData[field]; - if (Array.isArray(value)) { - // All of the private dictionary array data in CFF must be stored as - // "delta-encoded" numbers. - for (let j = value.length - 1; j > 0; j--) { - value[j] -= value[j - 1]; // ... difference from previous value - } - } - privateDict.setByName(field, value); - } - cff.topDict.privateDict = privateDict; - - const subrIndex = new CFFIndex(); - for (i = 0, ii = subrs.length; i < ii; i++) { - subrIndex.add(subrs[i]); - } - privateDict.subrsIndex = subrIndex; - - const compiler = new CFFCompiler(cff); - return compiler.compile(); - }, - }; - - return Type1Font; -})(); + const compiler = new CFFCompiler(cff); + return compiler.compile(); + } +} export { Type1Font }; From b9cd080c01775965601ff79404625f7858498199 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 16:58:28 +0200 Subject: [PATCH 15/18] Enable the `no-var` rule in the `src/core/fonts.js` file These changes were made automatically, using `gulp lint --fix`. Given the large size of this patch, the manual fixes are done separately in the next commit. --- src/core/fonts.js | 570 +++++++++++++++++++++++----------------------- 1 file changed, 285 insertions(+), 285 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index a2f175fa3..f130de348 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { assert, @@ -71,7 +70,7 @@ const PRIVATE_USE_AREAS = [ // PDF Glyph Space Units are one Thousandth of a TextSpace Unit // except for Type 3 fonts -var PDF_GLYPH_SPACE_UNITS = 1000; +const PDF_GLYPH_SPACE_UNITS = 1000; const EXPORT_DATA_PROPERTIES = [ "ascent", @@ -122,9 +121,9 @@ function adjustWidths(properties) { return; } // adjusting width to fontMatrix scale - var scale = 0.001 / properties.fontMatrix[0]; - var glyphsWidths = properties.widths; - for (var glyph in glyphsWidths) { + const scale = 0.001 / properties.fontMatrix[0]; + const glyphsWidths = properties.widths; + for (const glyph in glyphsWidths) { glyphsWidths[glyph] *= scale; } properties.defaultWidth *= scale; @@ -140,17 +139,17 @@ function adjustToUnicode(properties, builtInEncoding) { if (properties.toUnicode instanceof IdentityToUnicodeMap) { return; } - var toUnicode = [], + const toUnicode = [], glyphsUnicodeMap = getGlyphsUnicode(); - for (var charCode in builtInEncoding) { + for (const charCode in builtInEncoding) { if ( properties.hasEncoding && properties.differences[charCode] !== undefined ) { continue; // The font dictionary has an `Encoding`/`Differences` entry. } - var glyphName = builtInEncoding[charCode]; - var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); + const glyphName = builtInEncoding[charCode]; + const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); if (unicode !== -1) { toUnicode[charCode] = String.fromCharCode(unicode); } @@ -158,7 +157,7 @@ function adjustToUnicode(properties, builtInEncoding) { properties.toUnicode.amend(toUnicode); } -var Glyph = (function GlyphClosure() { +const Glyph = (function GlyphClosure() { // eslint-disable-next-line no-shadow function Glyph( fontChar, @@ -213,10 +212,10 @@ var Glyph = (function GlyphClosure() { * var type1Font = new Font("MyFontName", binaryFile, propertiesObject); * type1Font.bind(); */ -var Font = (function FontClosure() { +const Font = (function FontClosure() { // eslint-disable-next-line no-shadow function Font(name, file, properties) { - var charCode; + let charCode; this.name = name; this.loadedName = properties.loadedName; @@ -230,8 +229,8 @@ var Font = (function FontClosure() { this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); this.isMonospace = !!(properties.flags & FontFlags.FixedPitch); - var type = properties.type; - var subtype = properties.subtype; + let type = properties.type; + let subtype = properties.subtype; this.type = type; this.subtype = subtype; @@ -364,7 +363,7 @@ var Font = (function FontClosure() { } function signedInt16(b0, b1) { - var value = (b0 << 8) + b1; + const value = (b0 << 8) + b1; return value & (1 << 15) ? value - 0x10000 : value; } @@ -387,7 +386,7 @@ var Font = (function FontClosure() { } function isTrueTypeFile(file) { - var header = file.peekBytes(4); + const header = file.peekBytes(4); return ( readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true" ); @@ -399,12 +398,12 @@ var Font = (function FontClosure() { } function isOpenTypeFile(file) { - var header = file.peekBytes(4); + const header = file.peekBytes(4); return bytesToString(header) === "OTTO"; } function isType1File(file) { - var header = file.peekBytes(2); + const header = file.peekBytes(2); // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21). if (header[0] === 0x25 && header[1] === 0x21) { return true; @@ -475,15 +474,15 @@ var Font = (function FontClosure() { } function buildToFontChar(encoding, glyphsUnicodeMap, differences) { - var toFontChar = [], + let toFontChar = [], unicode; - for (var i = 0, ii = encoding.length; i < ii; i++) { + for (let i = 0, ii = encoding.length; i < ii; i++) { unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); if (unicode !== -1) { toFontChar[i] = unicode; } } - for (var charCode in differences) { + for (const charCode in differences) { unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap); if (unicode !== -1) { toFontChar[+charCode] = unicode; @@ -504,14 +503,14 @@ var Font = (function FontClosure() { * 'charCodeToGlyphId' - maps the new font char codes to glyph ids */ function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) { - var newMap = Object.create(null); - var toFontChar = []; - var privateUseAreaIndex = 0; - var nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; - var privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; - for (var originalCharCode in charCodeToGlyphId) { + const newMap = Object.create(null); + const toFontChar = []; + let privateUseAreaIndex = 0; + let nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; + let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; + for (let originalCharCode in charCodeToGlyphId) { originalCharCode |= 0; - var glyphId = charCodeToGlyphId[originalCharCode]; + let glyphId = charCodeToGlyphId[originalCharCode]; // For missing glyphs don't create the mappings so the glyph isn't // drawn. if (!hasGlyph(glyphId)) { @@ -526,7 +525,7 @@ var Font = (function FontClosure() { nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; } - var fontCharCode = nextAvailableFontCharCode++; + const fontCharCode = nextAvailableFontCharCode++; if (glyphId === 0) { glyphId = newGlyphZeroId; } @@ -544,8 +543,8 @@ var Font = (function FontClosure() { function getRanges(glyphs, numGlyphs) { // Array.sort() sorts by characters, not numerically, so convert to an // array of characters. - var codes = []; - for (var charCode in glyphs) { + const codes = []; + for (const charCode in glyphs) { // Remove an invalid glyph ID mappings to make OTS happy. if (glyphs[charCode] >= numGlyphs) { continue; @@ -562,13 +561,13 @@ var Font = (function FontClosure() { }); // Split the sorted codes into ranges. - var ranges = []; - var length = codes.length; - for (var n = 0; n < length; ) { - var start = codes[n].fontCharCode; - var codeIndices = [codes[n].glyphId]; + const ranges = []; + const length = codes.length; + for (let n = 0; n < length; ) { + const start = codes[n].fontCharCode; + const codeIndices = [codes[n].glyphId]; ++n; - var end = start; + let end = start; while (n < length && end + 1 === codes[n].fontCharCode) { codeIndices.push(codes[n].glyphId); ++end; @@ -584,39 +583,39 @@ var Font = (function FontClosure() { } function createCmapTable(glyphs, numGlyphs) { - var ranges = getRanges(glyphs, numGlyphs); - var numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1; - var cmap = + const ranges = getRanges(glyphs, numGlyphs); + const numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1; + let cmap = "\x00\x00" + // version string16(numTables) + // numTables "\x00\x03" + // platformID "\x00\x01" + // encodingID string32(4 + numTables * 8); // start of the table record - var i, ii, j, jj; + let i, ii, j, jj; for (i = ranges.length - 1; i >= 0; --i) { if (ranges[i][0] <= 0xffff) { break; } } - var bmpLength = i + 1; + const bmpLength = i + 1; if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) { ranges[i][1] = 0xfffe; } - var trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; - var segCount = bmpLength + trailingRangesCount; - var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); + const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; + const segCount = bmpLength + trailingRangesCount; + const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); // Fill up the 4 parallel arrays describing the segments. - var startCount = ""; - var endCount = ""; - var idDeltas = ""; - var idRangeOffsets = ""; - var glyphsIds = ""; - var bias = 0; + let startCount = ""; + let endCount = ""; + let idDeltas = ""; + let idRangeOffsets = ""; + let glyphsIds = ""; + let bias = 0; - var range, start, end, codes; + let range, start, end, codes; for (i = 0, ii = bmpLength; i < ii; i++) { range = ranges[i]; start = range[0]; @@ -624,7 +623,7 @@ var Font = (function FontClosure() { startCount += string16(start); endCount += string16(end); codes = range[2]; - var contiguous = true; + let contiguous = true; for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { contiguous = false; @@ -632,7 +631,7 @@ var Font = (function FontClosure() { } } if (!contiguous) { - var offset = (segCount - i) * 2 + bias * 2; + const offset = (segCount - i) * 2 + bias * 2; bias += end - start + 1; idDeltas += string16(0); @@ -642,7 +641,7 @@ var Font = (function FontClosure() { glyphsIds += string16(codes[j]); } } else { - var startCode = codes[0]; + const startCode = codes[0]; idDeltas += string16((startCode - start) & 0xffff); idRangeOffsets += string16(0); @@ -656,7 +655,7 @@ var Font = (function FontClosure() { idRangeOffsets += "\x00\x00"; } - var format314 = + const format314 = "\x00\x00" + // language string16(2 * segCount) + string16(searchParams.range) + @@ -669,8 +668,8 @@ var Font = (function FontClosure() { idRangeOffsets + glyphsIds; - var format31012 = ""; - var header31012 = ""; + let format31012 = ""; + let header31012 = ""; if (numTables > 1) { cmap += "\x00\x03" + // platformID @@ -681,7 +680,7 @@ var Font = (function FontClosure() { range = ranges[i]; start = range[0]; codes = range[2]; - var code = codes[0]; + let code = codes[0]; for (j = 1, jj = codes.length; j < jj; ++j) { if (codes[j] !== codes[j - 1] + 1) { end = range[0] + j - 1; @@ -718,21 +717,21 @@ var Font = (function FontClosure() { function validateOS2Table(os2, file) { file.pos = (file.start || 0) + os2.offset; - var version = file.getUint16(); + const version = file.getUint16(); // TODO verify all OS/2 tables fields, but currently we validate only those // that give us issues file.skip(60); // skipping type, misc sizes, panose, unicode ranges - var selection = file.getUint16(); + const selection = file.getUint16(); if (version < 4 && selection & 0x0300) { return false; } - var firstChar = file.getUint16(); - var lastChar = file.getUint16(); + const firstChar = file.getUint16(); + const lastChar = file.getUint16(); if (firstChar > lastChar) { return false; } file.skip(6); // skipping sTypoAscender/Descender/LineGap - var usWinAscent = file.getUint16(); + const usWinAscent = file.getUint16(); if (usWinAscent === 0) { // makes font unreadable by windows return false; @@ -752,16 +751,16 @@ var Font = (function FontClosure() { descent: 0, }; - var ulUnicodeRange1 = 0; - var ulUnicodeRange2 = 0; - var ulUnicodeRange3 = 0; - var ulUnicodeRange4 = 0; + let ulUnicodeRange1 = 0; + let ulUnicodeRange2 = 0; + let ulUnicodeRange3 = 0; + let ulUnicodeRange4 = 0; - var firstCharIndex = null; - var lastCharIndex = 0; + let firstCharIndex = null; + let lastCharIndex = 0; if (charstrings) { - for (var code in charstrings) { + for (let code in charstrings) { code |= 0; if (firstCharIndex > code || !firstCharIndex) { firstCharIndex = code; @@ -770,7 +769,7 @@ var Font = (function FontClosure() { lastCharIndex = code; } - var position = getUnicodeRangeFor(code); + const position = getUnicodeRangeFor(code); if (position < 32) { ulUnicodeRange1 |= 1 << position; } else if (position < 64) { @@ -796,26 +795,26 @@ var Font = (function FontClosure() { lastCharIndex = 255; } - var bbox = properties.bbox || [0, 0, 0, 0]; - var unitsPerEm = + const bbox = properties.bbox || [0, 0, 0, 0]; + const unitsPerEm = override.unitsPerEm || 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; // if the font units differ to the PDF glyph space units // then scale up the values - var scale = properties.ascentScaled + const scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS; - var typoAscent = + const typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3])); - var typoDescent = + let typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1])); if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { typoDescent = -typoDescent; // fixing incorrect descent } - var winAscent = override.yMax || typoAscent; - var winDescent = -override.yMin || -typoDescent; + const winAscent = override.yMax || typoAscent; + const winDescent = -override.yMin || -typoDescent; return ( "\x00\x03" + // version @@ -861,7 +860,7 @@ var Font = (function FontClosure() { } function createPostTable(properties) { - var angle = Math.floor(properties.italicAngle * 2 ** 16); + const angle = Math.floor(properties.italicAngle * 2 ** 16); return ( "\x00\x03\x00\x00" + // Version number string32(angle) + // italicAngle @@ -880,7 +879,7 @@ var Font = (function FontClosure() { proto = [[], []]; // no strings and unicode strings } - var strings = [ + const strings = [ proto[0][0] || "Original licence", // 0.Copyright proto[0][1] || name, // 1.Font family proto[0][2] || "Unknown", // 2.Font subfamily (font weight) @@ -895,36 +894,36 @@ var Font = (function FontClosure() { // Mac want 1-byte per character strings while Windows want // 2-bytes per character, so duplicate the names table - var stringsUnicode = []; - var i, ii, j, jj, str; + const stringsUnicode = []; + let i, ii, j, jj, str; for (i = 0, ii = strings.length; i < ii; i++) { str = proto[1][i] || strings[i]; - var strBufUnicode = []; + const strBufUnicode = []; for (j = 0, jj = str.length; j < jj; j++) { strBufUnicode.push(string16(str.charCodeAt(j))); } stringsUnicode.push(strBufUnicode.join("")); } - var names = [strings, stringsUnicode]; - var platforms = ["\x00\x01", "\x00\x03"]; - var encodings = ["\x00\x00", "\x00\x01"]; - var languages = ["\x00\x00", "\x04\x09"]; + const names = [strings, stringsUnicode]; + const platforms = ["\x00\x01", "\x00\x03"]; + const encodings = ["\x00\x00", "\x00\x01"]; + const languages = ["\x00\x00", "\x04\x09"]; - var namesRecordCount = strings.length * platforms.length; - var nameTable = + const namesRecordCount = strings.length * platforms.length; + let nameTable = "\x00\x00" + // format string16(namesRecordCount) + // Number of names Record string16(namesRecordCount * 12 + 6); // Storage // Build the name records field - var strOffset = 0; + let strOffset = 0; for (i = 0, ii = platforms.length; i < ii; i++) { - var strs = names[i]; + const strs = names[i]; for (j = 0, jj = strs.length; j < jj; j++) { str = strs[j]; - var nameRecord = + const nameRecord = platforms[i] + // platform ID encodings[i] + // encoding ID languages[i] + // language ID @@ -947,7 +946,7 @@ var Font = (function FontClosure() { disableFontFace: false, get renderer() { - var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); + const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); return shadow(this, "renderer", renderer); }, @@ -972,11 +971,11 @@ var Font = (function FontClosure() { this.missingFile = true; // The file data is not specified. Trying to fix the font name // to be used with the canvas.font. - var name = this.name; - var type = this.type; - var subtype = this.subtype; + const name = this.name; + const type = this.type; + const subtype = this.subtype; let fontName = name.replace(/[,_]/g, "-").replace(/\s/g, ""); - var stdFontMap = getStdFontMap(), + const stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap(); const isStandardFont = !!stdFontMap[fontName]; const isMappedToStandardFont = !!( @@ -1014,7 +1013,7 @@ var Font = (function FontClosure() { map[+charCode] = GlyphMapForStandardFonts[charCode]; } if (/Arial-?Black/i.test(name)) { - var SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack(); + const SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack(); for (const charCode in SupplementalGlyphMapForArialBlack) { map[+charCode] = SupplementalGlyphMapForArialBlack[charCode]; } @@ -1035,7 +1034,8 @@ var Font = (function FontClosure() { } } - var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap; + const isIdentityUnicode = + this.toUnicode instanceof IdentityToUnicodeMap; if (!isIdentityUnicode) { this.toUnicode.forEach(function (charCode, unicodeCharCode) { map[+charCode] = unicodeCharCode; @@ -1069,7 +1069,7 @@ var Font = (function FontClosure() { const map = []; this.toUnicode.forEach((charCode, unicodeCharCode) => { if (!this.composite) { - var glyphName = + const glyphName = this.differences[charCode] || this.defaultEncoding[charCode]; const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); if (unicode !== -1) { @@ -1139,17 +1139,17 @@ var Font = (function FontClosure() { } function readTableEntry(file) { - var tag = file.getString(4); + const tag = file.getString(4); - var checksum = file.getInt32() >>> 0; - var offset = file.getInt32() >>> 0; - var length = file.getInt32() >>> 0; + const checksum = file.getInt32() >>> 0; + const offset = file.getInt32() >>> 0; + const length = file.getInt32() >>> 0; // Read the table associated data - var previousPosition = file.pos; + const previousPosition = file.pos; file.pos = file.start ? file.start : 0; file.skip(offset); - var data = file.getBytes(length); + const data = file.getBytes(length); file.pos = previousPosition; if (tag === "head") { @@ -1284,15 +1284,15 @@ var Font = (function FontClosure() { hasShortCmap: false, }; } - var segment; - var start = (file.start ? file.start : 0) + cmap.offset; + let segment; + let start = (file.start ? file.start : 0) + cmap.offset; file.pos = start; file.skip(2); // version - var numTables = file.getUint16(); + const numTables = file.getUint16(); - var potentialTable; - var canBreak = false; + let potentialTable; + let canBreak = false; // There's an order of preference in terms of which cmap subtable to // use: // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table @@ -1300,10 +1300,10 @@ var Font = (function FontClosure() { // The following takes advantage of the fact that the tables are sorted // to work. for (var i = 0; i < numTables; i++) { - var platformId = file.getUint16(); - var encodingId = file.getUint16(); - var offset = file.getInt32() >>> 0; - var useTable = false; + const platformId = file.getUint16(); + const encodingId = file.getUint16(); + const offset = file.getInt32() >>> 0; + let useTable = false; // Sometimes there are multiple of the same type of table. Default // to choosing the first table and skip the rest. @@ -1367,17 +1367,17 @@ var Font = (function FontClosure() { }; } - var format = file.getUint16(); + const format = file.getUint16(); file.skip(2 + 2); // length + language - var hasShortCmap = false; - var mappings = []; - var j, glyphId; + let hasShortCmap = false; + const mappings = []; + let j, glyphId; // TODO(mack): refactor this cmap subtable reading logic out if (format === 0) { for (j = 0; j < 256; j++) { - var index = file.getByte(); + const index = file.getByte(); if (!index) { continue; } @@ -1390,9 +1390,9 @@ var Font = (function FontClosure() { } else if (format === 4) { // re-creating the table in format 4 since the encoding // might be changed - var segCount = file.getUint16() >> 1; + const segCount = file.getUint16() >> 1; file.skip(6); // skipping range fields - var segIndex, + let segIndex, segments = []; for (segIndex = 0; segIndex < segCount; segIndex++) { segments.push({ end: file.getUint16() }); @@ -1406,10 +1406,10 @@ var Font = (function FontClosure() { segments[segIndex].delta = file.getUint16(); } - var offsetsCount = 0; + let offsetsCount = 0; for (segIndex = 0; segIndex < segCount; segIndex++) { segment = segments[segIndex]; - var rangeOffset = file.getUint16(); + const rangeOffset = file.getUint16(); if (!rangeOffset) { segment.offsetIndex = -1; continue; @@ -1423,7 +1423,7 @@ var Font = (function FontClosure() { ); } - var offsets = []; + const offsets = []; for (j = 0; j < offsetsCount; j++) { offsets.push(file.getUint16()); } @@ -1431,8 +1431,8 @@ var Font = (function FontClosure() { for (segIndex = 0; segIndex < segCount; segIndex++) { segment = segments[segIndex]; start = segment.start; - var end = segment.end; - var delta = segment.delta; + const end = segment.end; + const delta = segment.delta; offsetIndex = segment.offsetIndex; for (j = start; j <= end; j++) { @@ -1454,12 +1454,12 @@ var Font = (function FontClosure() { // table. (This looks weird, so I can have missed something), this // works on Linux but seems to fails on Mac so let's rewrite the // cmap table to a 3-1-4 style - var firstCode = file.getUint16(); - var entryCount = file.getUint16(); + const firstCode = file.getUint16(); + const entryCount = file.getUint16(); for (j = 0; j < entryCount; j++) { glyphId = file.getUint16(); - var charCode = firstCode + j; + const charCode = firstCode + j; mappings.push({ charCode, @@ -1523,7 +1523,7 @@ var Font = (function FontClosure() { file.pos += 2; // caret_offset file.pos += 8; // reserved file.pos += 2; // format - var numOfMetrics = file.getUint16(); + let numOfMetrics = file.getUint16(); if (numOfMetrics > numGlyphs) { info( @@ -1540,15 +1540,15 @@ var Font = (function FontClosure() { header.data[35] = numOfMetrics & 0x00ff; } - var numOfSidebearings = numGlyphs - numOfMetrics; - var numMissing = + const numOfSidebearings = numGlyphs - numOfMetrics; + const numMissing = numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1); if (numMissing > 0) { // For each missing glyph, we set both the width and lsb to 0 (zero). // Since we need to add two properties for each glyph, this explains // the use of |numMissing * 2| when initializing the typed array. - var entries = new Uint8Array(metrics.length + numMissing * 2); + const entries = new Uint8Array(metrics.length + numMissing * 2); entries.set(metrics.data); if (dupFirstEntry) { // Set the sidebearing value of the duplicated glyph. @@ -1567,7 +1567,7 @@ var Font = (function FontClosure() { destStart, hintsValid ) { - var glyphProfile = { + const glyphProfile = { length: 0, sizeOfInstructions: 0, }; @@ -1575,8 +1575,8 @@ var Font = (function FontClosure() { // glyph with data less than 12 is invalid one return glyphProfile; } - var glyf = source.subarray(sourceStart, sourceEnd); - var contoursCount = signedInt16(glyf[0], glyf[1]); + const glyf = source.subarray(sourceStart, sourceEnd); + let contoursCount = signedInt16(glyf[0], glyf[1]); if (contoursCount < 0) { // OTS doesn't like contour count to be less than -1. contoursCount = -1; @@ -1587,24 +1587,24 @@ var Font = (function FontClosure() { return glyphProfile; } - var i, + let i, j = 10, flagsCount = 0; for (i = 0; i < contoursCount; i++) { - var endPoint = (glyf[j] << 8) | glyf[j + 1]; + const endPoint = (glyf[j] << 8) | glyf[j + 1]; flagsCount = endPoint + 1; j += 2; } // skipping instructions - var instructionsStart = j; - var instructionsLength = (glyf[j] << 8) | glyf[j + 1]; + const instructionsStart = j; + const instructionsLength = (glyf[j] << 8) | glyf[j + 1]; glyphProfile.sizeOfInstructions = instructionsLength; j += 2 + instructionsLength; - var instructionsEnd = j; + const instructionsEnd = j; // validating flags - var coordinatesLength = 0; + let coordinatesLength = 0; for (i = 0; i < flagsCount; i++) { - var flag = glyf[j++]; + const flag = glyf[j++]; if (flag & 0xc0) { // reserved flags must be zero, cleaning up glyf[j - 1] = flag & 0x3f; @@ -1624,7 +1624,7 @@ var Font = (function FontClosure() { const xyLength = xLength + yLength; coordinatesLength += xyLength; if (flag & 8) { - var repeat = glyf[j++]; + const repeat = glyf[j++]; i += repeat; coordinatesLength += repeat * xyLength; } @@ -1633,7 +1633,7 @@ var Font = (function FontClosure() { if (coordinatesLength === 0) { return glyphProfile; } - var glyphDataLength = j + coordinatesLength; + let glyphDataLength = j + coordinatesLength; if (glyphDataLength > glyf.length) { // not enough data for coordinates return glyphProfile; @@ -1666,11 +1666,11 @@ var Font = (function FontClosure() { } function sanitizeHead(head, numGlyphs, locaLength) { - var data = head.data; + const data = head.data; // Validate version: // Should always be 0x00010000 - var version = int32(data[0], data[1], data[2], data[3]); + const version = int32(data[0], data[1], data[2], data[3]); if (version >> 16 !== 1) { info("Attempting to fix invalid version in head table: " + version); data[0] = 0; @@ -1679,7 +1679,7 @@ var Font = (function FontClosure() { data[3] = 0; } - var indexToLocFormat = int16(data[50], data[51]); + const indexToLocFormat = int16(data[50], data[51]); if (indexToLocFormat < 0 || indexToLocFormat > 1) { info( "Attempting to fix invalid indexToLocFormat in head table: " + @@ -1696,7 +1696,7 @@ var Font = (function FontClosure() { // size of each offset in the loca table, and thus figure out the // appropriate value for indexToLocFormat. - var numGlyphsPlusOne = numGlyphs + 1; + const numGlyphsPlusOne = numGlyphs + 1; if (locaLength === numGlyphsPlusOne << 1) { // 0x0000 indicates the loca table consists of short offsets data[50] = 0; @@ -1722,7 +1722,7 @@ var Font = (function FontClosure() { dupFirstEntry, maxSizeOfInstructions ) { - var itemSize, itemDecode, itemEncode; + let itemSize, itemDecode, itemEncode; if (isGlyphLocationsLong) { itemSize = 4; itemDecode = function fontItemDecodeLong(data, offset) { @@ -1750,23 +1750,23 @@ var Font = (function FontClosure() { }; } // The first glyph is duplicated. - var numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; - var locaDataSize = itemSize * (1 + numGlyphsOut); + const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; + const locaDataSize = itemSize * (1 + numGlyphsOut); // Resize loca table to account for duplicated glyph. - var locaData = new Uint8Array(locaDataSize); + const locaData = new Uint8Array(locaDataSize); locaData.set(loca.data.subarray(0, locaDataSize)); loca.data = locaData; // removing the invalid glyphs - var oldGlyfData = glyf.data; - var oldGlyfDataLength = oldGlyfData.length; - var newGlyfData = new Uint8Array(oldGlyfDataLength); + const oldGlyfData = glyf.data; + const oldGlyfDataLength = oldGlyfData.length; + const newGlyfData = new Uint8Array(oldGlyfDataLength); // The spec says the offsets should be in ascending order, however // this is not true for some fonts or they use the offset of 0 to mark a // glyph as missing. OTS requires the offsets to be in order and not to // be zero, so we must sort and rebuild the loca table and potentially // re-arrange the glyf data. - var i, j; + let i, j; const locaEntries = []; // There are numGlyphs + 1 loca table entries. for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) { @@ -1794,11 +1794,11 @@ var Font = (function FontClosure() { return a.index - b.index; }); - var missingGlyphs = Object.create(null); - var writeOffset = 0; + const missingGlyphs = Object.create(null); + let writeOffset = 0; itemEncode(locaData, 0, writeOffset); for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { - var glyphProfile = sanitizeGlyph( + const glyphProfile = sanitizeGlyph( oldGlyfData, locaEntries[i].offset, locaEntries[i].endOffset, @@ -1806,7 +1806,7 @@ var Font = (function FontClosure() { writeOffset, hintsValid ); - var newLength = glyphProfile.length; + const newLength = glyphProfile.length; if (newLength === 0) { missingGlyphs[i] = true; } @@ -1820,7 +1820,7 @@ var Font = (function FontClosure() { if (writeOffset === 0) { // glyf table cannot be empty -- redoing the glyf and loca tables // to have single glyph with one point - var simpleGlyph = new Uint8Array([ + const simpleGlyph = new Uint8Array([ 0, 1, 0, @@ -1846,7 +1846,7 @@ var Font = (function FontClosure() { // Browsers will not display a glyph at position 0. Typically glyph 0 // is notdef, but a number of fonts put a valid glyph there so it must // be duplicated and appended. - var firstEntryLength = itemDecode(locaData, itemSize); + const firstEntryLength = itemDecode(locaData, itemSize); if (newGlyfData.length > firstEntryLength + writeOffset) { glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset); } else { @@ -1869,18 +1869,18 @@ var Font = (function FontClosure() { } function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) { - var start = (font.start ? font.start : 0) + post.offset; + const start = (font.start ? font.start : 0) + post.offset; font.pos = start; - var length = post.length, + const length = post.length, end = start + length; - var version = font.getInt32(); + const version = font.getInt32(); // skip rest to the tables font.skip(28); - var glyphNames; - var valid = true; - var i; + let glyphNames; + let valid = true; + let i; switch (version) { case 0x00010000: @@ -1894,7 +1894,7 @@ var Font = (function FontClosure() { } var glyphNameIndexes = []; for (i = 0; i < numGlyphs; ++i) { - var index = font.getUint16(); + const index = font.getUint16(); if (index >= 32768) { valid = false; break; @@ -1907,7 +1907,7 @@ var Font = (function FontClosure() { var customNames = []; var strBuf = []; while (font.pos < end) { - var stringLength = font.getByte(); + const stringLength = font.getByte(); strBuf.length = stringLength; for (i = 0; i < stringLength; ++i) { strBuf[i] = String.fromCharCode(font.getByte()); @@ -1916,7 +1916,7 @@ var Font = (function FontClosure() { } glyphNames = []; for (i = 0; i < numGlyphs; ++i) { - var j = glyphNameIndexes[i]; + const j = glyphNameIndexes[i]; if (j < 258) { glyphNames.push(MacStandardGlyphOrdering[j]); continue; @@ -1939,30 +1939,30 @@ var Font = (function FontClosure() { } function readNameTable(nameTable) { - var start = (font.start ? font.start : 0) + nameTable.offset; + const start = (font.start ? font.start : 0) + nameTable.offset; font.pos = start; - var names = [[], []]; - var length = nameTable.length, + const names = [[], []]; + const length = nameTable.length, end = start + length; - var format = font.getUint16(); - var FORMAT_0_HEADER_LENGTH = 6; + const format = font.getUint16(); + const FORMAT_0_HEADER_LENGTH = 6; if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { // unsupported name table format or table "too" small return names; } - var numRecords = font.getUint16(); - var stringsStart = font.getUint16(); - var records = []; - var NAME_RECORD_LENGTH = 12; - var i, ii; + const numRecords = font.getUint16(); + const stringsStart = font.getUint16(); + const records = []; + const NAME_RECORD_LENGTH = 12; + let i, ii; for ( i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++ ) { - var r = { + const r = { platform: font.getUint16(), encoding: font.getUint16(), language: font.getUint16(), @@ -1979,20 +1979,20 @@ var Font = (function FontClosure() { } } for (i = 0, ii = records.length; i < ii; i++) { - var record = records[i]; + const record = records[i]; if (record.length <= 0) { continue; // Nothing to process, ignoring. } - var pos = start + stringsStart + record.offset; + const pos = start + stringsStart + record.offset; if (pos + record.length > end) { continue; // outside of name table, ignoring } font.pos = pos; - var nameIndex = record.name; + const nameIndex = record.name; if (record.encoding) { // unicode - var str = ""; - for (var j = 0, jj = record.length; j < jj; j += 2) { + let str = ""; + for (let j = 0, jj = record.length; j < jj; j += 2) { str += String.fromCharCode(font.getUint16()); } names[1][nameIndex] = str; @@ -2004,7 +2004,7 @@ var Font = (function FontClosure() { } // prettier-ignore - var TTOpsStackDeltas = [ + const TTOpsStackDeltas = [ 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, @@ -2017,8 +2017,8 @@ var Font = (function FontClosure() { // 0xC0-DF == -1 and 0xE0-FF == -2 function sanitizeTTProgram(table, ttContext) { - var data = table.data; - var i = 0, + let data = table.data; + let i = 0, j, n, b, @@ -2026,15 +2026,15 @@ var Font = (function FontClosure() { pc, lastEndf = 0, lastDeff = 0; - var stack = []; - var callstack = []; - var functionsCalled = []; - var tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; - var inFDEF = false, + const stack = []; + const callstack = []; + const functionsCalled = []; + let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; + let inFDEF = false, ifLevel = 0, inELSE = 0; - for (var ii = data.length; i < ii; ) { - var op = data[i++]; + for (let ii = data.length; i < ii; ) { + const op = data[i++]; // The TrueType instruction set docs can be found at // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html if (op === 0x40) { @@ -2167,7 +2167,7 @@ var Font = (function FontClosure() { } else if (op === 0x1c) { // JMPR if (!inFDEF && !inELSE) { - var offset = stack[stack.length - 1]; + const offset = stack[stack.length - 1]; // only jumping forward to prevent infinite loop if (offset > 0) { i += offset - 1; @@ -2201,7 +2201,7 @@ var Font = (function FontClosure() { } } ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; - var content = [data]; + const content = [data]; if (i > data.length) { content.push(new Uint8Array(i - data.length)); } @@ -2223,7 +2223,7 @@ var Font = (function FontClosure() { ttContext.hintsValid = false; return; } - for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { + for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { if (j > maxFunctionDefs) { warn("TT: invalid function id: " + j); ttContext.hintsValid = false; @@ -2240,14 +2240,14 @@ var Font = (function FontClosure() { function foldTTTable(table, content) { if (content.length > 1) { // concatenating the content items - var newLength = 0; - var j, jj; + let newLength = 0; + let j, jj; for (j = 0, jj = content.length; j < jj; j++) { newLength += content[j].length; } newLength = (newLength + 3) & ~3; - var result = new Uint8Array(newLength); - var pos = 0; + const result = new Uint8Array(newLength); + let pos = 0; for (j = 0, jj = content.length; j < jj; j++) { result.set(content[j], pos); pos += content[j].length; @@ -2258,7 +2258,7 @@ var Font = (function FontClosure() { } function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) { - var ttContext = { + const ttContext = { functionsDefined: [], functionsUsed: [], functionsStackDeltas: [], @@ -2275,7 +2275,7 @@ var Font = (function FontClosure() { checkInvalidFunctions(ttContext, maxFunctionDefs); } if (cvt && cvt.length & 1) { - var cvtData = new Uint8Array(cvt.length + 1); + const cvtData = new Uint8Array(cvt.length + 1); cvtData.set(cvt.data); cvt.data = cvtData; } @@ -2296,7 +2296,7 @@ var Font = (function FontClosure() { } let cff, cffFile; - var isTrueType = !tables["CFF "]; + const isTrueType = !tables["CFF "]; if (!isTrueType) { const isComposite = properties.composite && @@ -2345,7 +2345,7 @@ var Font = (function FontClosure() { } font.pos = (font.start || 0) + tables.maxp.offset; - var version = font.getInt32(); + const version = font.getInt32(); const numGlyphs = font.getUint16(); // Glyph 0 is duplicated and appended. let numGlyphsOut = numGlyphs + 1; @@ -2355,12 +2355,12 @@ var Font = (function FontClosure() { numGlyphsOut = numGlyphs; warn("Not enough space in glyfs to duplicate first glyph."); } - var maxFunctionDefs = 0; - var maxSizeOfInstructions = 0; + let maxFunctionDefs = 0; + let maxSizeOfInstructions = 0; if (version >= 0x00010000 && tables.maxp.length >= 22) { // maxZones can be invalid font.pos += 8; - var maxZones = font.getUint16(); + const maxZones = font.getUint16(); if (maxZones > 2) { // reset to 2 if font has invalid maxZones tables.maxp.data[14] = 0; @@ -2375,7 +2375,7 @@ var Font = (function FontClosure() { tables.maxp.data[4] = numGlyphsOut >> 8; tables.maxp.data[5] = numGlyphsOut & 255; - var hintsValid = sanitizeTTPrograms( + const hintsValid = sanitizeTTPrograms( tables.fpgm, tables.prep, tables["cvt "], @@ -2403,13 +2403,13 @@ var Font = (function FontClosure() { sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); - var missingGlyphs = Object.create(null); + let missingGlyphs = Object.create(null); if (isTrueType) { - var isGlyphLocationsLong = int16( + const isGlyphLocationsLong = int16( tables.head.data[50], tables.head.data[51] ); - var glyphsInfo = sanitizeGlyphLocations( + const glyphsInfo = sanitizeGlyphLocations( tables.loca, tables.glyf, numGlyphs, @@ -2440,7 +2440,7 @@ var Font = (function FontClosure() { // Extract some more font properties from the OpenType head and // hhea tables; yMin and descent value are always negative. - var metricsOverride = { + const metricsOverride = { unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), yMax: int16(tables.head.data[42], tables.head.data[43]), yMin: signedInt16(tables.head.data[38], tables.head.data[39]), @@ -2471,14 +2471,14 @@ var Font = (function FontClosure() { } if (properties.composite) { - var cidToGidMap = properties.cidToGidMap || []; - var isCidToGidMapEmpty = cidToGidMap.length === 0; + const cidToGidMap = properties.cidToGidMap || []; + const isCidToGidMapEmpty = cidToGidMap.length === 0; properties.cMap.forEach(function (charCode, cid) { if (cid > 0xffff) { throw new FormatError("Max size of CID is 65,535"); } - var glyphId = -1; + let glyphId = -1; if (isCidToGidMapEmpty) { glyphId = cid; } else if (cidToGidMap[cid] !== undefined) { @@ -2492,16 +2492,16 @@ var Font = (function FontClosure() { } else { // Most of the following logic in this code branch is based on the // 9.6.6.4 of the PDF spec. - var cmapTable = readCmapTable( + const cmapTable = readCmapTable( tables.cmap, font, this.isSymbolicFont, properties.hasEncoding ); - var cmapPlatformId = cmapTable.platformId; - var cmapEncodingId = cmapTable.encodingId; - var cmapMappings = cmapTable.mappings; - var cmapMappingsLength = cmapMappings.length; + const cmapPlatformId = cmapTable.platformId; + const cmapEncodingId = cmapTable.encodingId; + const cmapMappings = cmapTable.mappings; + const cmapMappingsLength = cmapMappings.length; let baseEncoding = []; if ( properties.hasEncoding && @@ -2520,7 +2520,7 @@ var Font = (function FontClosure() { ((cmapPlatformId === 3 && cmapEncodingId === 1) || (cmapPlatformId === 1 && cmapEncodingId === 0)) ) { - var glyphsUnicodeMap = getGlyphsUnicode(); + const glyphsUnicodeMap = getGlyphsUnicode(); for (let charCode = 0; charCode < 256; charCode++) { var glyphName, standardGlyphName; if (this.differences && charCode in this.differences) { @@ -2625,7 +2625,7 @@ var Font = (function FontClosure() { // view (e.g. with Xfa) so nothing must be moved in the private use area. if (!properties.cssFontInfo) { // Converting glyphs and ids into font's cmap table - var newMapping = adjustMapping( + const newMapping = adjustMapping( charCodeToGlyphId, hasGlyph, glyphZeroId @@ -2652,14 +2652,14 @@ var Font = (function FontClosure() { try { // Trying to repair CFF file cffFile = new Stream(tables["CFF "].data); - var parser = new CFFParser( + const parser = new CFFParser( cffFile, properties, SEAC_ANALYSIS_ENABLED ); cff = parser.parse(); cff.duplicateFirstGlyph(); - var compiler = new CFFCompiler(cff); + const compiler = new CFFCompiler(cff); tables["CFF "].data = compiler.compile(); } catch (e) { warn("Failed to compile font " + properties.loadedName); @@ -2674,12 +2674,12 @@ var Font = (function FontClosure() { }; } else { // ... using existing 'name' table as prototype - var namePrototype = readNameTable(tables.name); + const namePrototype = readNameTable(tables.name); tables.name.data = createNameTable(name, namePrototype); } - var builder = new OpenTypeFileBuilder(header.version); - for (var tableTag in tables) { + const builder = new OpenTypeFileBuilder(header.version); + for (const tableTag in tables) { builder.addTable(tableTag, tables[tableTag].data); } return builder.toArray(); @@ -2702,18 +2702,18 @@ var Font = (function FontClosure() { if (font instanceof CFFFont) { glyphZeroId = font.numGlyphs - 1; } - var mapping = font.getGlyphMapping(properties); - var newMapping = adjustMapping( + const mapping = font.getGlyphMapping(properties); + const newMapping = adjustMapping( mapping, font.hasGlyphId.bind(font), glyphZeroId ); this.toFontChar = newMapping.toFontChar; - var numGlyphs = font.numGlyphs; + const numGlyphs = font.numGlyphs; function getCharCodes(charCodeToGlyphId, glyphId) { - var charCodes = null; - for (var charCode in charCodeToGlyphId) { + let charCodes = null; + for (const charCode in charCodeToGlyphId) { if (glyphId === charCodeToGlyphId[charCode]) { if (!charCodes) { charCodes = []; @@ -2725,7 +2725,7 @@ var Font = (function FontClosure() { } function createCharCode(charCodeToGlyphId, glyphId) { - for (var charCode in charCodeToGlyphId) { + for (const charCode in charCodeToGlyphId) { if (glyphId === charCodeToGlyphId[charCode]) { return charCode | 0; } @@ -2736,42 +2736,42 @@ var Font = (function FontClosure() { return newMapping.nextAvailableFontCharCode++; } - var seacs = font.seacs; + const seacs = font.seacs; if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) { - var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; - var charset = font.getCharset(); - var seacMap = Object.create(null); - for (var glyphId in seacs) { + const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; + const charset = font.getCharset(); + const seacMap = Object.create(null); + for (let glyphId in seacs) { glyphId |= 0; - var seac = seacs[glyphId]; - var baseGlyphName = StandardEncoding[seac[2]]; - var accentGlyphName = StandardEncoding[seac[3]]; - var baseGlyphId = charset.indexOf(baseGlyphName); - var accentGlyphId = charset.indexOf(accentGlyphName); + const seac = seacs[glyphId]; + const baseGlyphName = StandardEncoding[seac[2]]; + const accentGlyphName = StandardEncoding[seac[3]]; + const baseGlyphId = charset.indexOf(baseGlyphName); + const accentGlyphId = charset.indexOf(accentGlyphName); if (baseGlyphId < 0 || accentGlyphId < 0) { continue; } - var accentOffset = { + const accentOffset = { x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5], }; - var charCodes = getCharCodes(mapping, glyphId); + const charCodes = getCharCodes(mapping, glyphId); if (!charCodes) { // There's no point in mapping it if the char code was never mapped // to begin with. continue; } for (let i = 0, ii = charCodes.length; i < ii; i++) { - var charCode = charCodes[i]; + const charCode = charCodes[i]; // Find a fontCharCode that maps to the base and accent glyphs. // If one doesn't exists, create it. - var charCodeToGlyphId = newMapping.charCodeToGlyphId; - var baseFontCharCode = createCharCode( + const charCodeToGlyphId = newMapping.charCodeToGlyphId; + const baseFontCharCode = createCharCode( charCodeToGlyphId, baseGlyphId ); - var accentFontCharCode = createCharCode( + const accentFontCharCode = createCharCode( charCodeToGlyphId, accentGlyphId ); @@ -2785,9 +2785,9 @@ var Font = (function FontClosure() { properties.seacMap = seacMap; } - var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; + const unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; - var builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); + const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); // PostScript Font Program builder.addTable("CFF ", font.data); // OS/2 and Windows Specific metrics @@ -2848,13 +2848,13 @@ var Font = (function FontClosure() { builder.addTable( "hmtx", (function fontFieldsHmtx() { - var charstrings = font.charstrings; - var cffWidths = font.cff ? font.cff.widths : null; - var hmtx = "\x00\x00\x00\x00"; // Fake .notdef + const charstrings = font.charstrings; + const cffWidths = font.cff ? font.cff.widths : null; + let hmtx = "\x00\x00\x00\x00"; // Fake .notdef for (let i = 1, ii = numGlyphs; i < ii; i++) { - var width = 0; + let width = 0; if (charstrings) { - var charstring = charstrings[i - 1]; + const charstring = charstrings[i - 1]; width = "width" in charstring ? charstring.width : 0; } else if (cffWidths) { width = Math.ceil(cffWidths[i] || 0); @@ -2882,19 +2882,19 @@ var Font = (function FontClosure() { get spaceWidth() { // trying to estimate space character width - var possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; - var width; - for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) { - var glyphName = possibleSpaceReplacements[i]; + const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; + let width; + for (let i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) { + const glyphName = possibleSpaceReplacements[i]; // if possible, getting width by glyph name if (glyphName in this.widths) { width = this.widths[glyphName]; break; } - var glyphsUnicodeMap = getGlyphsUnicode(); - var glyphUnicode = glyphsUnicodeMap[glyphName]; + const glyphsUnicodeMap = getGlyphsUnicode(); + const glyphUnicode = glyphsUnicodeMap[glyphName]; // finding the charcode via unicodeToCID map - var charcode = 0; + let charcode = 0; if (this.composite && this.cMap.contains(glyphUnicode)) { charcode = this.cMap.lookup(glyphUnicode); } @@ -2920,15 +2920,15 @@ var Font = (function FontClosure() { * @private */ _charToGlyph(charcode, isSpace = false) { - var fontCharCode, width, operatorListId; + let fontCharCode, width, operatorListId; - var widthCode = charcode; + let widthCode = charcode; if (this.cMap && this.cMap.contains(charcode)) { widthCode = this.cMap.lookup(charcode); } width = this.widths[widthCode]; width = isNum(width) ? width : this.defaultWidth; - var vmetric = this.vmetrics && this.vmetrics[widthCode]; + const vmetric = this.vmetrics && this.vmetrics[widthCode]; let unicode = this.toUnicode.get(charcode) || @@ -2938,7 +2938,7 @@ var Font = (function FontClosure() { unicode = String.fromCharCode(unicode); } - var isInFont = charcode in this.toFontChar; + let isInFont = charcode in this.toFontChar; // First try the toFontChar map, if it's not there then try falling // back to the char code. fontCharCode = this.toFontChar[charcode] || charcode; @@ -2961,10 +2961,10 @@ var Font = (function FontClosure() { operatorListId = fontCharCode; } - var accent = null; + let accent = null; if (this.seacMap && this.seacMap[charcode]) { isInFont = true; - var seac = this.seacMap[charcode]; + const seac = this.seacMap[charcode]; fontCharCode = seac.baseFontCharCode; accent = { fontChar: String.fromCodePoint(seac.accentFontCharCode), @@ -2981,7 +2981,7 @@ var Font = (function FontClosure() { } } - var glyph = this.glyphCache[charcode]; + let glyph = this.glyphCache[charcode]; if ( !glyph || !glyph.matchesForCache( @@ -3011,8 +3011,8 @@ var Font = (function FontClosure() { }, charsToGlyphs: function Font_charsToGlyphs(chars) { - var charsCache = this.charsCache; - var glyphs, glyph, charcode; + let charsCache = this.charsCache; + let glyphs, glyph, charcode; // if we translated this string before, just grab it from the cache if (charsCache) { @@ -3028,21 +3028,21 @@ var Font = (function FontClosure() { } glyphs = []; - var charsCacheKey = chars; - var i = 0, + const charsCacheKey = chars; + let i = 0, ii; if (this.cMap) { // composite fonts have multi-byte strings convert the string from // single-byte to multi-byte - var c = Object.create(null); + const c = Object.create(null); while (i < chars.length) { this.cMap.readCharCode(chars, i, c); charcode = c.charcode; - var length = c.length; + const length = c.length; i += length; // Space is char with code 0x20 and length 1 in multiple-byte codes. - var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20; + const isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20; glyph = this._charToGlyph(charcode, isSpace); glyphs.push(glyph); } @@ -3150,7 +3150,7 @@ var Font = (function FontClosure() { return Font; })(); -var ErrorFont = (function ErrorFontClosure() { +const ErrorFont = (function ErrorFontClosure() { // eslint-disable-next-line no-shadow function ErrorFont(error) { this.error = error; From cadc20d8b98e753f014999cb2627eee1b513f5a3 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 17:40:08 +0200 Subject: [PATCH 16/18] Fix the remaining `no-var` failures, which couldn't be handled automatically, in the `src/core/fonts.js` file --- src/core/fonts.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index f130de348..89b269fc0 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -297,8 +297,8 @@ const Font = (function FontClosure() { ); } + let data; try { - var data; switch (type) { case "MMType1": info("MMType1 font (" + name + "), falling back to Type1."); @@ -307,7 +307,7 @@ const Font = (function FontClosure() { case "CIDFontType0": this.mimetype = "font/opentype"; - var cff = + const cff = subtype === "Type1C" || subtype === "CIDFontType0C" ? new CFFFont(file, properties) : new Type1Font(name, file, properties); @@ -474,8 +474,8 @@ const Font = (function FontClosure() { } function buildToFontChar(encoding, glyphsUnicodeMap, differences) { - let toFontChar = [], - unicode; + const toFontChar = []; + let unicode; for (let i = 0, ii = encoding.length; i < ii; i++) { unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); if (unicode !== -1) { @@ -1299,7 +1299,7 @@ const Font = (function FontClosure() { // - symbolic fonts the preference is a 3,0 table then a 1,0 table // The following takes advantage of the fact that the tables are sorted // to work. - for (var i = 0; i < numTables; i++) { + for (let i = 0; i < numTables; i++) { const platformId = file.getUint16(); const encodingId = file.getUint16(); const offset = file.getInt32() >>> 0; @@ -1392,8 +1392,8 @@ const Font = (function FontClosure() { // might be changed const segCount = file.getUint16() >> 1; file.skip(6); // skipping range fields - let segIndex, - segments = []; + const segments = []; + let segIndex; for (segIndex = 0; segIndex < segCount; segIndex++) { segments.push({ end: file.getUint16() }); } @@ -1406,7 +1406,8 @@ const Font = (function FontClosure() { segments[segIndex].delta = file.getUint16(); } - let offsetsCount = 0; + let offsetsCount = 0, + offsetIndex; for (segIndex = 0; segIndex < segCount; segIndex++) { segment = segments[segIndex]; const rangeOffset = file.getUint16(); @@ -1415,7 +1416,7 @@ const Font = (function FontClosure() { continue; } - var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); + offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); segment.offsetIndex = offsetIndex; offsetsCount = Math.max( offsetsCount, @@ -1480,7 +1481,7 @@ const Font = (function FontClosure() { mappings.sort(function (a, b) { return a.charCode - b.charCode; }); - for (i = 1; i < mappings.length; i++) { + for (let i = 1; i < mappings.length; i++) { if (mappings[i - 1].charCode === mappings[i].charCode) { mappings.splice(i, 1); i--; @@ -1887,12 +1888,12 @@ const Font = (function FontClosure() { glyphNames = MacStandardGlyphOrdering; break; case 0x00020000: - var numGlyphs = font.getUint16(); + const numGlyphs = font.getUint16(); if (numGlyphs !== maxpNumGlyphs) { valid = false; break; } - var glyphNameIndexes = []; + const glyphNameIndexes = []; for (i = 0; i < numGlyphs; ++i) { const index = font.getUint16(); if (index >= 32768) { @@ -1904,8 +1905,8 @@ const Font = (function FontClosure() { if (!valid) { break; } - var customNames = []; - var strBuf = []; + const customNames = [], + strBuf = []; while (font.pos < end) { const stringLength = font.getByte(); strBuf.length = stringLength; @@ -2522,7 +2523,7 @@ const Font = (function FontClosure() { ) { const glyphsUnicodeMap = getGlyphsUnicode(); for (let charCode = 0; charCode < 256; charCode++) { - var glyphName, standardGlyphName; + let glyphName; if (this.differences && charCode in this.differences) { glyphName = this.differences[charCode]; } else if ( @@ -2537,9 +2538,12 @@ const Font = (function FontClosure() { continue; } // Ensure that non-standard glyph names are resolved to valid ones. - standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap); + const standardGlyphName = recoverGlyphName( + glyphName, + glyphsUnicodeMap + ); - var unicodeOrCharCode; + let unicodeOrCharCode; if (cmapPlatformId === 3 && cmapEncodingId === 1) { unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName]; } else if (cmapPlatformId === 1 && cmapEncodingId === 0) { @@ -2595,7 +2599,7 @@ const Font = (function FontClosure() { if (charCodeToGlyphId[i] !== undefined) { continue; } - glyphName = this.differences[i] || baseEncoding[i]; + const glyphName = this.differences[i] || baseEncoding[i]; if (!glyphName) { continue; } From b487edd05daf056bea58f0a064c25f335f0a1452 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 17:42:48 +0200 Subject: [PATCH 17/18] Convert `src/core/fonts.js` to use standard classes Obviously the `Font`-class is still *very* large, given particularly how TrueType fonts are handled, however this patch-series at least improves things by moving a number of functions/classes into their own files. As a follow-up it might make sense to try and re-factor/extract the TrueType parsing into its own file, since all of this code is quite old, however that's probably best left for another time. For e.g. `gulp mozcentral`, the *built* `pdf.worker.js` files decreases from `1 620 332` to `1 617 466` bytes with this patch-series. --- src/core/fonts.js | 5253 ++++++++++++++++++++++----------------------- 1 file changed, 2609 insertions(+), 2644 deletions(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index 89b269fc0..c7d788ed4 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -157,9 +157,8 @@ function adjustToUnicode(properties, builtInEncoding) { properties.toUnicode.amend(toUnicode); } -const Glyph = (function GlyphClosure() { - // eslint-disable-next-line no-shadow - function Glyph( +class Glyph { + constructor( fontChar, unicode, accent, @@ -179,7 +178,7 @@ const Glyph = (function GlyphClosure() { this.isInFont = isInFont; } - Glyph.prototype.matchesForCache = function ( + matchesForCache( fontChar, unicode, accent, @@ -199,25 +198,605 @@ const Glyph = (function GlyphClosure() { this.isSpace === isSpace && this.isInFont === isInFont ); + } +} + +function int16(b0, b1) { + return (b0 << 8) + b1; +} + +function writeSignedInt16(bytes, index, value) { + bytes[index + 1] = value; + bytes[index] = value >>> 8; +} + +function signedInt16(b0, b1) { + const value = (b0 << 8) + b1; + return value & (1 << 15) ? value - 0x10000 : value; +} + +function int32(b0, b1, b2, b3) { + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; +} + +function string16(value) { + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); +} + +function safeString16(value) { + // clamp value to the 16-bit int range + if (value > 0x7fff) { + value = 0x7fff; + } else if (value < -0x8000) { + value = -0x8000; + } + return String.fromCharCode((value >> 8) & 0xff, value & 0xff); +} + +function isTrueTypeFile(file) { + const header = file.peekBytes(4); + return ( + readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true" + ); +} + +function isTrueTypeCollectionFile(file) { + const header = file.peekBytes(4); + return bytesToString(header) === "ttcf"; +} + +function isOpenTypeFile(file) { + const header = file.peekBytes(4); + return bytesToString(header) === "OTTO"; +} + +function isType1File(file) { + const header = file.peekBytes(2); + // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21). + if (header[0] === 0x25 && header[1] === 0x21) { + return true; + } + // ... obviously some fonts violate that part of the specification, + // please refer to the comment in |Type1Font| below (pfb file header). + if (header[0] === 0x80 && header[1] === 0x01) { + return true; + } + return false; +} + +/** + * Compared to other font formats, the header in CFF files is not constant + * but contains version numbers. To reduce the possibility of misclassifying + * font files as CFF, it's recommended to check for other font formats first. + */ +function isCFFFile(file) { + const header = file.peekBytes(4); + if ( + /* major version, [1, 255] */ header[0] >= 1 && + /* minor version, [0, 255]; header[1] */ + /* header size, [0, 255]; header[2] */ + /* offset(0) size, [1, 4] */ header[3] >= 1 && + header[3] <= 4 + ) { + return true; + } + return false; +} + +function getFontFileType(file, { type, subtype, composite }) { + let fileType, fileSubtype; + + if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) { + if (composite) { + fileType = "CIDFontType2"; + } else { + fileType = "TrueType"; + } + } else if (isOpenTypeFile(file)) { + if (composite) { + fileType = "CIDFontType2"; + } else { + fileType = "OpenType"; + } + } else if (isType1File(file)) { + if (composite) { + fileType = "CIDFontType0"; + } else { + fileType = type === "MMType1" ? "MMType1" : "Type1"; + } + } else if (isCFFFile(file)) { + if (composite) { + fileType = "CIDFontType0"; + fileSubtype = "CIDFontType0C"; + } else { + fileType = type === "MMType1" ? "MMType1" : "Type1"; + fileSubtype = "Type1C"; + } + } else { + warn("getFontFileType: Unable to detect correct font file Type/Subtype."); + fileType = type; + fileSubtype = subtype; + } + + return [fileType, fileSubtype]; +} + +function buildToFontChar(encoding, glyphsUnicodeMap, differences) { + const toFontChar = []; + let unicode; + for (let i = 0, ii = encoding.length; i < ii; i++) { + unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); + if (unicode !== -1) { + toFontChar[i] = unicode; + } + } + for (const charCode in differences) { + unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap); + if (unicode !== -1) { + toFontChar[+charCode] = unicode; + } + } + return toFontChar; +} + +/** + * Rebuilds the char code to glyph ID map by moving all char codes to the + * private use area. This is done to avoid issues with various problematic + * unicode areas where either a glyph won't be drawn or is deformed by a + * shaper. + * @returns {Object} Two properties: + * 'toFontChar' - maps original char codes(the value that will be read + * from commands such as show text) to the char codes that will be used in the + * font that we build + * 'charCodeToGlyphId' - maps the new font char codes to glyph ids + */ +function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) { + const newMap = Object.create(null); + const toFontChar = []; + let privateUseAreaIndex = 0; + let nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; + let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; + for (let originalCharCode in charCodeToGlyphId) { + originalCharCode |= 0; + let glyphId = charCodeToGlyphId[originalCharCode]; + // For missing glyphs don't create the mappings so the glyph isn't + // drawn. + if (!hasGlyph(glyphId)) { + continue; + } + if (nextAvailableFontCharCode > privateUseOffetEnd) { + privateUseAreaIndex++; + if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) { + warn("Ran out of space in font private use area."); + break; + } + nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; + privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; + } + const fontCharCode = nextAvailableFontCharCode++; + if (glyphId === 0) { + glyphId = newGlyphZeroId; + } + + newMap[fontCharCode] = glyphId; + toFontChar[originalCharCode] = fontCharCode; + } + return { + toFontChar, + charCodeToGlyphId: newMap, + nextAvailableFontCharCode, + }; +} + +function getRanges(glyphs, numGlyphs) { + // Array.sort() sorts by characters, not numerically, so convert to an + // array of characters. + const codes = []; + for (const charCode in glyphs) { + // Remove an invalid glyph ID mappings to make OTS happy. + if (glyphs[charCode] >= numGlyphs) { + continue; + } + codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] }); + } + // Some fonts have zero glyphs and are used only for text selection, but + // there needs to be at least one to build a valid cmap table. + if (codes.length === 0) { + codes.push({ fontCharCode: 0, glyphId: 0 }); + } + codes.sort(function fontGetRangesSort(a, b) { + return a.fontCharCode - b.fontCharCode; + }); + + // Split the sorted codes into ranges. + const ranges = []; + const length = codes.length; + for (let n = 0; n < length; ) { + const start = codes[n].fontCharCode; + const codeIndices = [codes[n].glyphId]; + ++n; + let end = start; + while (n < length && end + 1 === codes[n].fontCharCode) { + codeIndices.push(codes[n].glyphId); + ++end; + ++n; + if (end === 0xffff) { + break; + } + } + ranges.push([start, end, codeIndices]); + } + + return ranges; +} + +function createCmapTable(glyphs, numGlyphs) { + const ranges = getRanges(glyphs, numGlyphs); + const numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1; + let cmap = + "\x00\x00" + // version + string16(numTables) + // numTables + "\x00\x03" + // platformID + "\x00\x01" + // encodingID + string32(4 + numTables * 8); // start of the table record + + let i, ii, j, jj; + for (i = ranges.length - 1; i >= 0; --i) { + if (ranges[i][0] <= 0xffff) { + break; + } + } + const bmpLength = i + 1; + + if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) { + ranges[i][1] = 0xfffe; + } + const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; + const segCount = bmpLength + trailingRangesCount; + const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); + + // Fill up the 4 parallel arrays describing the segments. + let startCount = ""; + let endCount = ""; + let idDeltas = ""; + let idRangeOffsets = ""; + let glyphsIds = ""; + let bias = 0; + + let range, start, end, codes; + for (i = 0, ii = bmpLength; i < ii; i++) { + range = ranges[i]; + start = range[0]; + end = range[1]; + startCount += string16(start); + endCount += string16(end); + codes = range[2]; + let contiguous = true; + for (j = 1, jj = codes.length; j < jj; ++j) { + if (codes[j] !== codes[j - 1] + 1) { + contiguous = false; + break; + } + } + if (!contiguous) { + const offset = (segCount - i) * 2 + bias * 2; + bias += end - start + 1; + + idDeltas += string16(0); + idRangeOffsets += string16(offset); + + for (j = 0, jj = codes.length; j < jj; ++j) { + glyphsIds += string16(codes[j]); + } + } else { + const startCode = codes[0]; + + idDeltas += string16((startCode - start) & 0xffff); + idRangeOffsets += string16(0); + } + } + + if (trailingRangesCount > 0) { + endCount += "\xFF\xFF"; + startCount += "\xFF\xFF"; + idDeltas += "\x00\x01"; + idRangeOffsets += "\x00\x00"; + } + + const format314 = + "\x00\x00" + // language + string16(2 * segCount) + + string16(searchParams.range) + + string16(searchParams.entry) + + string16(searchParams.rangeShift) + + endCount + + "\x00\x00" + + startCount + + idDeltas + + idRangeOffsets + + glyphsIds; + + let format31012 = ""; + let header31012 = ""; + if (numTables > 1) { + cmap += + "\x00\x03" + // platformID + "\x00\x0A" + // encodingID + string32(4 + numTables * 8 + 4 + format314.length); // start of the table record + format31012 = ""; + for (i = 0, ii = ranges.length; i < ii; i++) { + range = ranges[i]; + start = range[0]; + codes = range[2]; + let code = codes[0]; + for (j = 1, jj = codes.length; j < jj; ++j) { + if (codes[j] !== codes[j - 1] + 1) { + end = range[0] + j - 1; + format31012 += + string32(start) + // startCharCode + string32(end) + // endCharCode + string32(code); // startGlyphID + start = end + 1; + code = codes[j]; + } + } + format31012 += + string32(start) + // startCharCode + string32(range[1]) + // endCharCode + string32(code); // startGlyphID + } + header31012 = + "\x00\x0C" + // format + "\x00\x00" + // reserved + string32(format31012.length + 16) + // length + "\x00\x00\x00\x00" + // language + string32(format31012.length / 12); // nGroups + } + + return ( + cmap + + "\x00\x04" + // format + string16(format314.length + 4) + // length + format314 + + header31012 + + format31012 + ); +} + +function validateOS2Table(os2, file) { + file.pos = (file.start || 0) + os2.offset; + const version = file.getUint16(); + // TODO verify all OS/2 tables fields, but currently we validate only those + // that give us issues + file.skip(60); // skipping type, misc sizes, panose, unicode ranges + const selection = file.getUint16(); + if (version < 4 && selection & 0x0300) { + return false; + } + const firstChar = file.getUint16(); + const lastChar = file.getUint16(); + if (firstChar > lastChar) { + return false; + } + file.skip(6); // skipping sTypoAscender/Descender/LineGap + const usWinAscent = file.getUint16(); + if (usWinAscent === 0) { + // makes font unreadable by windows + return false; + } + + // OS/2 appears to be valid, resetting some fields + os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0 + return true; +} + +function createOS2Table(properties, charstrings, override) { + override = override || { + unitsPerEm: 0, + yMax: 0, + yMin: 0, + ascent: 0, + descent: 0, }; - return Glyph; -})(); + let ulUnicodeRange1 = 0; + let ulUnicodeRange2 = 0; + let ulUnicodeRange3 = 0; + let ulUnicodeRange4 = 0; + + let firstCharIndex = null; + let lastCharIndex = 0; + + if (charstrings) { + for (let code in charstrings) { + code |= 0; + if (firstCharIndex > code || !firstCharIndex) { + firstCharIndex = code; + } + if (lastCharIndex < code) { + lastCharIndex = code; + } + + const position = getUnicodeRangeFor(code); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << (position - 32); + } else if (position < 96) { + ulUnicodeRange3 |= 1 << (position - 64); + } else if (position < 123) { + ulUnicodeRange4 |= 1 << (position - 96); + } else { + throw new FormatError( + "Unicode ranges Bits > 123 are reserved for internal usage" + ); + } + } + if (lastCharIndex > 0xffff) { + // OS2 only supports a 16 bit int. The spec says if supplementary + // characters are used the field should just be set to 0xFFFF. + lastCharIndex = 0xffff; + } + } else { + // TODO + firstCharIndex = 0; + lastCharIndex = 255; + } + + const bbox = properties.bbox || [0, 0, 0, 0]; + const unitsPerEm = + override.unitsPerEm || + 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; + + // if the font units differ to the PDF glyph space units + // then scale up the values + const scale = properties.ascentScaled + ? 1.0 + : unitsPerEm / PDF_GLYPH_SPACE_UNITS; + + const typoAscent = + override.ascent || Math.round(scale * (properties.ascent || bbox[3])); + let typoDescent = + override.descent || Math.round(scale * (properties.descent || bbox[1])); + if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { + typoDescent = -typoDescent; // fixing incorrect descent + } + const winAscent = override.yMax || typoAscent; + const winDescent = -override.yMin || -typoDescent; + + return ( + "\x00\x03" + // version + "\x02\x24" + // xAvgCharWidth + "\x01\xF4" + // usWeightClass + "\x00\x05" + // usWidthClass + "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) + "\x02\x8A" + // ySubscriptXSize + "\x02\xBB" + // ySubscriptYSize + "\x00\x00" + // ySubscriptXOffset + "\x00\x8C" + // ySubscriptYOffset + "\x02\x8A" + // ySuperScriptXSize + "\x02\xBB" + // ySuperScriptYSize + "\x00\x00" + // ySuperScriptXOffset + "\x01\xDF" + // ySuperScriptYOffset + "\x00\x31" + // yStrikeOutSize + "\x01\x02" + // yStrikeOutPosition + "\x00\x00" + // sFamilyClass + "\x00\x00\x06" + + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + + "\x00\x00\x00\x00\x00\x00" + // Panose + string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31) + string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63) + string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95) + string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127) + "\x2A\x32\x31\x2A" + // achVendID + string16(properties.italicAngle ? 1 : 0) + // fsSelection + string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex + string16(lastCharIndex || properties.lastChar) + // usLastCharIndex + string16(typoAscent) + // sTypoAscender + string16(typoDescent) + // sTypoDescender + "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value) + string16(winAscent) + // usWinAscent + string16(winDescent) + // usWinDescent + "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31) + "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63) + string16(properties.xHeight) + // sxHeight + string16(properties.capHeight) + // sCapHeight + string16(0) + // usDefaultChar + string16(firstCharIndex || properties.firstChar) + // usBreakChar + "\x00\x03" + ); // usMaxContext +} + +function createPostTable(properties) { + const angle = Math.floor(properties.italicAngle * 2 ** 16); + return ( + "\x00\x03\x00\x00" + // Version number + string32(angle) + // italicAngle + "\x00\x00" + // underlinePosition + "\x00\x00" + // underlineThickness + string32(properties.fixedPitch) + // isFixedPitch + "\x00\x00\x00\x00" + // minMemType42 + "\x00\x00\x00\x00" + // maxMemType42 + "\x00\x00\x00\x00" + // minMemType1 + "\x00\x00\x00\x00" + ); // maxMemType1 +} + +function createNameTable(name, proto) { + if (!proto) { + proto = [[], []]; // no strings and unicode strings + } + + const strings = [ + proto[0][0] || "Original licence", // 0.Copyright + proto[0][1] || name, // 1.Font family + proto[0][2] || "Unknown", // 2.Font subfamily (font weight) + proto[0][3] || "uniqueID", // 3.Unique ID + proto[0][4] || name, // 4.Full font name + proto[0][5] || "Version 0.11", // 5.Version + proto[0][6] || "", // 6.Postscript name + proto[0][7] || "Unknown", // 7.Trademark + proto[0][8] || "Unknown", // 8.Manufacturer + proto[0][9] || "Unknown", // 9.Designer + ]; + + // Mac want 1-byte per character strings while Windows want + // 2-bytes per character, so duplicate the names table + const stringsUnicode = []; + let i, ii, j, jj, str; + for (i = 0, ii = strings.length; i < ii; i++) { + str = proto[1][i] || strings[i]; + + const strBufUnicode = []; + for (j = 0, jj = str.length; j < jj; j++) { + strBufUnicode.push(string16(str.charCodeAt(j))); + } + stringsUnicode.push(strBufUnicode.join("")); + } + + const names = [strings, stringsUnicode]; + const platforms = ["\x00\x01", "\x00\x03"]; + const encodings = ["\x00\x00", "\x00\x01"]; + const languages = ["\x00\x00", "\x04\x09"]; + + const namesRecordCount = strings.length * platforms.length; + let nameTable = + "\x00\x00" + // format + string16(namesRecordCount) + // Number of names Record + string16(namesRecordCount * 12 + 6); // Storage + + // Build the name records field + let strOffset = 0; + for (i = 0, ii = platforms.length; i < ii; i++) { + const strs = names[i]; + for (j = 0, jj = strs.length; j < jj; j++) { + str = strs[j]; + const nameRecord = + platforms[i] + // platform ID + encodings[i] + // encoding ID + languages[i] + // language ID + string16(j) + // name ID + string16(str.length) + + string16(strOffset); + nameTable += nameRecord; + strOffset += str.length; + } + } + + nameTable += strings.join("") + stringsUnicode.join(""); + return nameTable; +} /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). - * - * For example to read a Type1 font and to attach it to the document: - * var type1Font = new Font("MyFontName", binaryFile, propertiesObject); - * type1Font.bind(); */ -const Font = (function FontClosure() { - // eslint-disable-next-line no-shadow - function Font(name, file, properties) { - let charCode; - +class Font { + constructor(name, file, properties) { this.name = name; + this.mimetype = null; + this.disableFontFace = false; + this.loadedName = properties.loadedName; this.isType3Font = properties.isType3Font; this.missingFile = false; @@ -260,7 +839,7 @@ const Font = (function FontClosure() { this.toFontChar = []; if (properties.type === "Type3") { - for (charCode = 0; charCode < 256; charCode++) { + for (let charCode = 0; charCode < 256; charCode++) { this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode]; } @@ -353,1659 +932,1055 @@ const Font = (function FontClosure() { this.seacMap = properties.seacMap; } - function int16(b0, b1) { - return (b0 << 8) + b1; + get renderer() { + const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); + return shadow(this, "renderer", renderer); } - function writeSignedInt16(bytes, index, value) { - bytes[index + 1] = value; - bytes[index] = value >>> 8; - } + exportData(extraProperties = false) { + const exportDataProperties = extraProperties + ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] + : EXPORT_DATA_PROPERTIES; - function signedInt16(b0, b1) { - const value = (b0 << 8) + b1; - return value & (1 << 15) ? value - 0x10000 : value; - } - - function int32(b0, b1, b2, b3) { - return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; - } - - function string16(value) { - return String.fromCharCode((value >> 8) & 0xff, value & 0xff); - } - - function safeString16(value) { - // clamp value to the 16-bit int range - if (value > 0x7fff) { - value = 0x7fff; - } else if (value < -0x8000) { - value = -0x8000; + const data = Object.create(null); + let property, value; + for (property of exportDataProperties) { + value = this[property]; + // Ignore properties that haven't been explicitly set. + if (value !== undefined) { + data[property] = value; + } } - return String.fromCharCode((value >> 8) & 0xff, value & 0xff); + return data; } - function isTrueTypeFile(file) { - const header = file.peekBytes(4); - return ( - readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true" + fallbackToSystemFont(properties) { + this.missingFile = true; + // The file data is not specified. Trying to fix the font name + // to be used with the canvas.font. + const name = this.name; + const type = this.type; + const subtype = this.subtype; + let fontName = name.replace(/[,_]/g, "-").replace(/\s/g, ""); + const stdFontMap = getStdFontMap(), + nonStdFontMap = getNonStdFontMap(); + const isStandardFont = !!stdFontMap[fontName]; + const isMappedToStandardFont = !!( + nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]] ); - } - function isTrueTypeCollectionFile(file) { - const header = file.peekBytes(4); - return bytesToString(header) === "ttcf"; - } + fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; + this.bold = fontName.search(/bold/gi) !== -1; + this.italic = + fontName.search(/oblique/gi) !== -1 || fontName.search(/italic/gi) !== -1; - function isOpenTypeFile(file) { - const header = file.peekBytes(4); - return bytesToString(header) === "OTTO"; - } + // Use 'name' instead of 'fontName' here because the original + // name ArialBlack for example will be replaced by Helvetica. + this.black = name.search(/Black/g) !== -1; - function isType1File(file) { - const header = file.peekBytes(2); - // All Type1 font programs must begin with the comment '%!' (0x25 + 0x21). - if (header[0] === 0x25 && header[1] === 0x21) { - return true; - } - // ... obviously some fonts violate that part of the specification, - // please refer to the comment in |Type1Font| below (pfb file header). - if (header[0] === 0x80 && header[1] === 0x01) { - return true; - } - return false; - } + // Use 'name' instead of 'fontName' here because the original + // name ArialNarrow for example will be replaced by Helvetica. + const isNarrow = name.search(/Narrow/g) !== -1; - /** - * Compared to other font formats, the header in CFF files is not constant - * but contains version numbers. To reduce the possibility of misclassifying - * font files as CFF, it's recommended to check for other font formats first. - */ - function isCFFFile(file) { - const header = file.peekBytes(4); + // if at least one width is present, remeasure all chars when exists + this.remeasure = + (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0; if ( - /* major version, [1, 255] */ header[0] >= 1 && - /* minor version, [0, 255]; header[1] */ - /* header size, [0, 255]; header[2] */ - /* offset(0) size, [1, 4] */ header[3] >= 1 && - header[3] <= 4 + (isStandardFont || isMappedToStandardFont) && + type === "CIDFontType2" && + this.cidEncoding.startsWith("Identity-") ) { - return true; - } - return false; - } - - function getFontFileType(file, { type, subtype, composite }) { - let fileType, fileSubtype; - - if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) { - if (composite) { - fileType = "CIDFontType2"; - } else { - fileType = "TrueType"; + const GlyphMapForStandardFonts = getGlyphMapForStandardFonts(), + cidToGidMap = properties.cidToGidMap; + // Standard fonts might be embedded as CID font without glyph mapping. + // Building one based on GlyphMapForStandardFonts. + const map = []; + for (const charCode in GlyphMapForStandardFonts) { + map[+charCode] = GlyphMapForStandardFonts[charCode]; } - } else if (isOpenTypeFile(file)) { - if (composite) { - fileType = "CIDFontType2"; - } else { - fileType = "OpenType"; - } - } else if (isType1File(file)) { - if (composite) { - fileType = "CIDFontType0"; - } else { - fileType = type === "MMType1" ? "MMType1" : "Type1"; - } - } else if (isCFFFile(file)) { - if (composite) { - fileType = "CIDFontType0"; - fileSubtype = "CIDFontType0C"; - } else { - fileType = type === "MMType1" ? "MMType1" : "Type1"; - fileSubtype = "Type1C"; - } - } else { - warn("getFontFileType: Unable to detect correct font file Type/Subtype."); - fileType = type; - fileSubtype = subtype; - } - - return [fileType, fileSubtype]; - } - - function buildToFontChar(encoding, glyphsUnicodeMap, differences) { - const toFontChar = []; - let unicode; - for (let i = 0, ii = encoding.length; i < ii; i++) { - unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap); - if (unicode !== -1) { - toFontChar[i] = unicode; - } - } - for (const charCode in differences) { - unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap); - if (unicode !== -1) { - toFontChar[+charCode] = unicode; - } - } - return toFontChar; - } - - /** - * Rebuilds the char code to glyph ID map by moving all char codes to the - * private use area. This is done to avoid issues with various problematic - * unicode areas where either a glyph won't be drawn or is deformed by a - * shaper. - * @returns {Object} Two properties: - * 'toFontChar' - maps original char codes(the value that will be read - * from commands such as show text) to the char codes that will be used in the - * font that we build - * 'charCodeToGlyphId' - maps the new font char codes to glyph ids - */ - function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) { - const newMap = Object.create(null); - const toFontChar = []; - let privateUseAreaIndex = 0; - let nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; - let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; - for (let originalCharCode in charCodeToGlyphId) { - originalCharCode |= 0; - let glyphId = charCodeToGlyphId[originalCharCode]; - // For missing glyphs don't create the mappings so the glyph isn't - // drawn. - if (!hasGlyph(glyphId)) { - continue; - } - if (nextAvailableFontCharCode > privateUseOffetEnd) { - privateUseAreaIndex++; - if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) { - warn("Ran out of space in font private use area."); - break; + if (/Arial-?Black/i.test(name)) { + const SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack(); + for (const charCode in SupplementalGlyphMapForArialBlack) { + map[+charCode] = SupplementalGlyphMapForArialBlack[charCode]; } - nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0]; - privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1]; - } - const fontCharCode = nextAvailableFontCharCode++; - if (glyphId === 0) { - glyphId = newGlyphZeroId; - } - - newMap[fontCharCode] = glyphId; - toFontChar[originalCharCode] = fontCharCode; - } - return { - toFontChar, - charCodeToGlyphId: newMap, - nextAvailableFontCharCode, - }; - } - - function getRanges(glyphs, numGlyphs) { - // Array.sort() sorts by characters, not numerically, so convert to an - // array of characters. - const codes = []; - for (const charCode in glyphs) { - // Remove an invalid glyph ID mappings to make OTS happy. - if (glyphs[charCode] >= numGlyphs) { - continue; - } - codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] }); - } - // Some fonts have zero glyphs and are used only for text selection, but - // there needs to be at least one to build a valid cmap table. - if (codes.length === 0) { - codes.push({ fontCharCode: 0, glyphId: 0 }); - } - codes.sort(function fontGetRangesSort(a, b) { - return a.fontCharCode - b.fontCharCode; - }); - - // Split the sorted codes into ranges. - const ranges = []; - const length = codes.length; - for (let n = 0; n < length; ) { - const start = codes[n].fontCharCode; - const codeIndices = [codes[n].glyphId]; - ++n; - let end = start; - while (n < length && end + 1 === codes[n].fontCharCode) { - codeIndices.push(codes[n].glyphId); - ++end; - ++n; - if (end === 0xffff) { - break; + } else if (/Calibri/i.test(name)) { + const SupplementalGlyphMapForCalibri = getSupplementalGlyphMapForCalibri(); + for (const charCode in SupplementalGlyphMapForCalibri) { + map[+charCode] = SupplementalGlyphMapForCalibri[charCode]; } } - ranges.push([start, end, codeIndices]); - } - - return ranges; - } - - function createCmapTable(glyphs, numGlyphs) { - const ranges = getRanges(glyphs, numGlyphs); - const numTables = ranges[ranges.length - 1][1] > 0xffff ? 2 : 1; - let cmap = - "\x00\x00" + // version - string16(numTables) + // numTables - "\x00\x03" + // platformID - "\x00\x01" + // encodingID - string32(4 + numTables * 8); // start of the table record - - let i, ii, j, jj; - for (i = ranges.length - 1; i >= 0; --i) { - if (ranges[i][0] <= 0xffff) { - break; - } - } - const bmpLength = i + 1; - - if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) { - ranges[i][1] = 0xfffe; - } - const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0; - const segCount = bmpLength + trailingRangesCount; - const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2); - - // Fill up the 4 parallel arrays describing the segments. - let startCount = ""; - let endCount = ""; - let idDeltas = ""; - let idRangeOffsets = ""; - let glyphsIds = ""; - let bias = 0; - - let range, start, end, codes; - for (i = 0, ii = bmpLength; i < ii; i++) { - range = ranges[i]; - start = range[0]; - end = range[1]; - startCount += string16(start); - endCount += string16(end); - codes = range[2]; - let contiguous = true; - for (j = 1, jj = codes.length; j < jj; ++j) { - if (codes[j] !== codes[j - 1] + 1) { - contiguous = false; - break; - } - } - if (!contiguous) { - const offset = (segCount - i) * 2 + bias * 2; - bias += end - start + 1; - - idDeltas += string16(0); - idRangeOffsets += string16(offset); - - for (j = 0, jj = codes.length; j < jj; ++j) { - glyphsIds += string16(codes[j]); - } - } else { - const startCode = codes[0]; - - idDeltas += string16((startCode - start) & 0xffff); - idRangeOffsets += string16(0); - } - } - - if (trailingRangesCount > 0) { - endCount += "\xFF\xFF"; - startCount += "\xFF\xFF"; - idDeltas += "\x00\x01"; - idRangeOffsets += "\x00\x00"; - } - - const format314 = - "\x00\x00" + // language - string16(2 * segCount) + - string16(searchParams.range) + - string16(searchParams.entry) + - string16(searchParams.rangeShift) + - endCount + - "\x00\x00" + - startCount + - idDeltas + - idRangeOffsets + - glyphsIds; - - let format31012 = ""; - let header31012 = ""; - if (numTables > 1) { - cmap += - "\x00\x03" + // platformID - "\x00\x0A" + // encodingID - string32(4 + numTables * 8 + 4 + format314.length); // start of the table record - format31012 = ""; - for (i = 0, ii = ranges.length; i < ii; i++) { - range = ranges[i]; - start = range[0]; - codes = range[2]; - let code = codes[0]; - for (j = 1, jj = codes.length; j < jj; ++j) { - if (codes[j] !== codes[j - 1] + 1) { - end = range[0] + j - 1; - format31012 += - string32(start) + // startCharCode - string32(end) + // endCharCode - string32(code); // startGlyphID - start = end + 1; - code = codes[j]; + // Always update the glyph mapping with the `cidToGidMap` when it exists + // (fixes issue12418_reduced.pdf). + if (cidToGidMap) { + for (const charCode in map) { + const cid = map[charCode]; + if (cidToGidMap[cid] !== undefined) { + map[+charCode] = cidToGidMap[cid]; } } - format31012 += - string32(start) + // startCharCode - string32(range[1]) + // endCharCode - string32(code); // startGlyphID } - header31012 = - "\x00\x0C" + // format - "\x00\x00" + // reserved - string32(format31012.length + 16) + // length - "\x00\x00\x00\x00" + // language - string32(format31012.length / 12); // nGroups - } - return ( - cmap + - "\x00\x04" + // format - string16(format314.length + 4) + // length - format314 + - header31012 + - format31012 - ); - } - - function validateOS2Table(os2, file) { - file.pos = (file.start || 0) + os2.offset; - const version = file.getUint16(); - // TODO verify all OS/2 tables fields, but currently we validate only those - // that give us issues - file.skip(60); // skipping type, misc sizes, panose, unicode ranges - const selection = file.getUint16(); - if (version < 4 && selection & 0x0300) { - return false; - } - const firstChar = file.getUint16(); - const lastChar = file.getUint16(); - if (firstChar > lastChar) { - return false; - } - file.skip(6); // skipping sTypoAscender/Descender/LineGap - const usWinAscent = file.getUint16(); - if (usWinAscent === 0) { - // makes font unreadable by windows - return false; - } - - // OS/2 appears to be valid, resetting some fields - os2.data[8] = os2.data[9] = 0; // IE rejects fonts if fsType != 0 - return true; - } - - function createOS2Table(properties, charstrings, override) { - override = override || { - unitsPerEm: 0, - yMax: 0, - yMin: 0, - ascent: 0, - descent: 0, - }; - - let ulUnicodeRange1 = 0; - let ulUnicodeRange2 = 0; - let ulUnicodeRange3 = 0; - let ulUnicodeRange4 = 0; - - let firstCharIndex = null; - let lastCharIndex = 0; - - if (charstrings) { - for (let code in charstrings) { - code |= 0; - if (firstCharIndex > code || !firstCharIndex) { - firstCharIndex = code; - } - if (lastCharIndex < code) { - lastCharIndex = code; - } - - const position = getUnicodeRangeFor(code); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << (position - 32); - } else if (position < 96) { - ulUnicodeRange3 |= 1 << (position - 64); - } else if (position < 123) { - ulUnicodeRange4 |= 1 << (position - 96); - } else { - throw new FormatError( - "Unicode ranges Bits > 123 are reserved for internal usage" - ); - } - } - if (lastCharIndex > 0xffff) { - // OS2 only supports a 16 bit int. The spec says if supplementary - // characters are used the field should just be set to 0xFFFF. - lastCharIndex = 0xffff; - } - } else { - // TODO - firstCharIndex = 0; - lastCharIndex = 255; - } - - const bbox = properties.bbox || [0, 0, 0, 0]; - const unitsPerEm = - override.unitsPerEm || - 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; - - // if the font units differ to the PDF glyph space units - // then scale up the values - const scale = properties.ascentScaled - ? 1.0 - : unitsPerEm / PDF_GLYPH_SPACE_UNITS; - - const typoAscent = - override.ascent || Math.round(scale * (properties.ascent || bbox[3])); - let typoDescent = - override.descent || Math.round(scale * (properties.descent || bbox[1])); - if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) { - typoDescent = -typoDescent; // fixing incorrect descent - } - const winAscent = override.yMax || typoAscent; - const winDescent = -override.yMin || -typoDescent; - - return ( - "\x00\x03" + // version - "\x02\x24" + // xAvgCharWidth - "\x01\xF4" + // usWeightClass - "\x00\x05" + // usWidthClass - "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) - "\x02\x8A" + // ySubscriptXSize - "\x02\xBB" + // ySubscriptYSize - "\x00\x00" + // ySubscriptXOffset - "\x00\x8C" + // ySubscriptYOffset - "\x02\x8A" + // ySuperScriptXSize - "\x02\xBB" + // ySuperScriptYSize - "\x00\x00" + // ySuperScriptXOffset - "\x01\xDF" + // ySuperScriptYOffset - "\x00\x31" + // yStrikeOutSize - "\x01\x02" + // yStrikeOutPosition - "\x00\x00" + // sFamilyClass - "\x00\x00\x06" + - String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + - "\x00\x00\x00\x00\x00\x00" + // Panose - string32(ulUnicodeRange1) + // ulUnicodeRange1 (Bits 0-31) - string32(ulUnicodeRange2) + // ulUnicodeRange2 (Bits 32-63) - string32(ulUnicodeRange3) + // ulUnicodeRange3 (Bits 64-95) - string32(ulUnicodeRange4) + // ulUnicodeRange4 (Bits 96-127) - "\x2A\x32\x31\x2A" + // achVendID - string16(properties.italicAngle ? 1 : 0) + // fsSelection - string16(firstCharIndex || properties.firstChar) + // usFirstCharIndex - string16(lastCharIndex || properties.lastChar) + // usLastCharIndex - string16(typoAscent) + // sTypoAscender - string16(typoDescent) + // sTypoDescender - "\x00\x64" + // sTypoLineGap (7%-10% of the unitsPerEM value) - string16(winAscent) + // usWinAscent - string16(winDescent) + // usWinDescent - "\x00\x00\x00\x00" + // ulCodePageRange1 (Bits 0-31) - "\x00\x00\x00\x00" + // ulCodePageRange2 (Bits 32-63) - string16(properties.xHeight) + // sxHeight - string16(properties.capHeight) + // sCapHeight - string16(0) + // usDefaultChar - string16(firstCharIndex || properties.firstChar) + // usBreakChar - "\x00\x03" - ); // usMaxContext - } - - function createPostTable(properties) { - const angle = Math.floor(properties.italicAngle * 2 ** 16); - return ( - "\x00\x03\x00\x00" + // Version number - string32(angle) + // italicAngle - "\x00\x00" + // underlinePosition - "\x00\x00" + // underlineThickness - string32(properties.fixedPitch) + // isFixedPitch - "\x00\x00\x00\x00" + // minMemType42 - "\x00\x00\x00\x00" + // maxMemType42 - "\x00\x00\x00\x00" + // minMemType1 - "\x00\x00\x00\x00" - ); // maxMemType1 - } - - function createNameTable(name, proto) { - if (!proto) { - proto = [[], []]; // no strings and unicode strings - } - - const strings = [ - proto[0][0] || "Original licence", // 0.Copyright - proto[0][1] || name, // 1.Font family - proto[0][2] || "Unknown", // 2.Font subfamily (font weight) - proto[0][3] || "uniqueID", // 3.Unique ID - proto[0][4] || name, // 4.Full font name - proto[0][5] || "Version 0.11", // 5.Version - proto[0][6] || "", // 6.Postscript name - proto[0][7] || "Unknown", // 7.Trademark - proto[0][8] || "Unknown", // 8.Manufacturer - proto[0][9] || "Unknown", // 9.Designer - ]; - - // Mac want 1-byte per character strings while Windows want - // 2-bytes per character, so duplicate the names table - const stringsUnicode = []; - let i, ii, j, jj, str; - for (i = 0, ii = strings.length; i < ii; i++) { - str = proto[1][i] || strings[i]; - - const strBufUnicode = []; - for (j = 0, jj = str.length; j < jj; j++) { - strBufUnicode.push(string16(str.charCodeAt(j))); - } - stringsUnicode.push(strBufUnicode.join("")); - } - - const names = [strings, stringsUnicode]; - const platforms = ["\x00\x01", "\x00\x03"]; - const encodings = ["\x00\x00", "\x00\x01"]; - const languages = ["\x00\x00", "\x04\x09"]; - - const namesRecordCount = strings.length * platforms.length; - let nameTable = - "\x00\x00" + // format - string16(namesRecordCount) + // Number of names Record - string16(namesRecordCount * 12 + 6); // Storage - - // Build the name records field - let strOffset = 0; - for (i = 0, ii = platforms.length; i < ii; i++) { - const strs = names[i]; - for (j = 0, jj = strs.length; j < jj; j++) { - str = strs[j]; - const nameRecord = - platforms[i] + // platform ID - encodings[i] + // encoding ID - languages[i] + // language ID - string16(j) + // name ID - string16(str.length) + - string16(strOffset); - nameTable += nameRecord; - strOffset += str.length; - } - } - - nameTable += strings.join("") + stringsUnicode.join(""); - return nameTable; - } - - Font.prototype = { - name: null, - font: null, - mimetype: null, - disableFontFace: false, - - get renderer() { - const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED); - return shadow(this, "renderer", renderer); - }, - - exportData(extraProperties = false) { - const exportDataProperties = extraProperties - ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] - : EXPORT_DATA_PROPERTIES; - - const data = Object.create(null); - let property, value; - for (property of exportDataProperties) { - value = this[property]; - // Ignore properties that haven't been explicitly set. - if (value !== undefined) { - data[property] = value; - } - } - return data; - }, - - fallbackToSystemFont(properties) { - this.missingFile = true; - // The file data is not specified. Trying to fix the font name - // to be used with the canvas.font. - const name = this.name; - const type = this.type; - const subtype = this.subtype; - let fontName = name.replace(/[,_]/g, "-").replace(/\s/g, ""); - const stdFontMap = getStdFontMap(), - nonStdFontMap = getNonStdFontMap(); - const isStandardFont = !!stdFontMap[fontName]; - const isMappedToStandardFont = !!( - nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]] - ); - - fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; - this.bold = fontName.search(/bold/gi) !== -1; - this.italic = - fontName.search(/oblique/gi) !== -1 || - fontName.search(/italic/gi) !== -1; - - // Use 'name' instead of 'fontName' here because the original - // name ArialBlack for example will be replaced by Helvetica. - this.black = name.search(/Black/g) !== -1; - - // Use 'name' instead of 'fontName' here because the original - // name ArialNarrow for example will be replaced by Helvetica. - const isNarrow = name.search(/Narrow/g) !== -1; - - // if at least one width is present, remeasure all chars when exists - this.remeasure = - (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0; - if ( - (isStandardFont || isMappedToStandardFont) && - type === "CIDFontType2" && - this.cidEncoding.startsWith("Identity-") - ) { - const GlyphMapForStandardFonts = getGlyphMapForStandardFonts(), - cidToGidMap = properties.cidToGidMap; - // Standard fonts might be embedded as CID font without glyph mapping. - // Building one based on GlyphMapForStandardFonts. - const map = []; - for (const charCode in GlyphMapForStandardFonts) { - map[+charCode] = GlyphMapForStandardFonts[charCode]; - } - if (/Arial-?Black/i.test(name)) { - const SupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBlack(); - for (const charCode in SupplementalGlyphMapForArialBlack) { - map[+charCode] = SupplementalGlyphMapForArialBlack[charCode]; - } - } else if (/Calibri/i.test(name)) { - const SupplementalGlyphMapForCalibri = getSupplementalGlyphMapForCalibri(); - for (const charCode in SupplementalGlyphMapForCalibri) { - map[+charCode] = SupplementalGlyphMapForCalibri[charCode]; - } - } - // Always update the glyph mapping with the `cidToGidMap` when it exists - // (fixes issue12418_reduced.pdf). - if (cidToGidMap) { - for (const charCode in map) { - const cid = map[charCode]; - if (cidToGidMap[cid] !== undefined) { - map[+charCode] = cidToGidMap[cid]; - } - } - } - - const isIdentityUnicode = - this.toUnicode instanceof IdentityToUnicodeMap; - if (!isIdentityUnicode) { - this.toUnicode.forEach(function (charCode, unicodeCharCode) { - map[+charCode] = unicodeCharCode; - }); - } - this.toFontChar = map; - this.toUnicode = new ToUnicodeMap(map); - } else if (/Symbol/i.test(fontName)) { - this.toFontChar = buildToFontChar( - SymbolSetEncoding, - getGlyphsUnicode(), - this.differences - ); - } else if (/Dingbats/i.test(fontName)) { - if (/Wingdings/i.test(name)) { - warn("Non-embedded Wingdings font, falling back to ZapfDingbats."); - } - this.toFontChar = buildToFontChar( - ZapfDingbatsEncoding, - getDingbatsGlyphsUnicode(), - this.differences - ); - } else if (isStandardFont) { - this.toFontChar = buildToFontChar( - this.defaultEncoding, - getGlyphsUnicode(), - this.differences - ); - } else { - const glyphsUnicodeMap = getGlyphsUnicode(); - const map = []; - this.toUnicode.forEach((charCode, unicodeCharCode) => { - if (!this.composite) { - const glyphName = - this.differences[charCode] || this.defaultEncoding[charCode]; - const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); - if (unicode !== -1) { - unicodeCharCode = unicode; - } - } + const isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap; + if (!isIdentityUnicode) { + this.toUnicode.forEach(function (charCode, unicodeCharCode) { map[+charCode] = unicodeCharCode; }); + } + this.toFontChar = map; + this.toUnicode = new ToUnicodeMap(map); + } else if (/Symbol/i.test(fontName)) { + this.toFontChar = buildToFontChar( + SymbolSetEncoding, + getGlyphsUnicode(), + this.differences + ); + } else if (/Dingbats/i.test(fontName)) { + if (/Wingdings/i.test(name)) { + warn("Non-embedded Wingdings font, falling back to ZapfDingbats."); + } + this.toFontChar = buildToFontChar( + ZapfDingbatsEncoding, + getDingbatsGlyphsUnicode(), + this.differences + ); + } else if (isStandardFont) { + this.toFontChar = buildToFontChar( + this.defaultEncoding, + getGlyphsUnicode(), + this.differences + ); + } else { + const glyphsUnicodeMap = getGlyphsUnicode(); + const map = []; + this.toUnicode.forEach((charCode, unicodeCharCode) => { + if (!this.composite) { + const glyphName = + this.differences[charCode] || this.defaultEncoding[charCode]; + const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap); + if (unicode !== -1) { + unicodeCharCode = unicode; + } + } + map[+charCode] = unicodeCharCode; + }); - // Attempt to improve the glyph mapping for (some) composite fonts that - // appear to lack meaningful ToUnicode data. - if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) { - if (/Verdana/i.test(name)) { - // Fixes issue11242_reduced.pdf - const GlyphMapForStandardFonts = getGlyphMapForStandardFonts(); - for (const charCode in GlyphMapForStandardFonts) { - map[+charCode] = GlyphMapForStandardFonts[charCode]; + // Attempt to improve the glyph mapping for (some) composite fonts that + // appear to lack meaningful ToUnicode data. + if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) { + if (/Verdana/i.test(name)) { + // Fixes issue11242_reduced.pdf + const GlyphMapForStandardFonts = getGlyphMapForStandardFonts(); + for (const charCode in GlyphMapForStandardFonts) { + map[+charCode] = GlyphMapForStandardFonts[charCode]; + } + } + } + this.toFontChar = map; + } + this.loadedName = fontName.split("-")[0]; + this.fontType = getFontType(type, subtype); + } + + 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) { + const 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++) { + const table = readTableEntry(file); + if (!VALID_TABLES.includes(table.tag)) { + 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) { + const tag = file.getString(4); + + const checksum = file.getInt32() >>> 0; + const offset = file.getInt32() >>> 0; + const length = file.getInt32() >>> 0; + + // Read the table associated data + const previousPosition = file.pos; + file.pos = file.start ? file.start : 0; + file.skip(offset); + const data = file.getBytes(length); + file.pos = previousPosition; + + if (tag === "head") { + // clearing checksum adjustment + data[8] = data[9] = data[10] = data[11] = 0; + data[17] |= 0x20; // Set font optimized for cleartype flag. + } + + return { + tag, + checksum, + length, + offset, + data, + }; + } + + function readOpenTypeHeader(ttf) { + return { + version: ttf.getString(4), + numTables: ttf.getUint16(), + searchRange: ttf.getUint16(), + entrySelector: ttf.getUint16(), + rangeShift: ttf.getUint16(), + }; + } + + function readTrueTypeCollectionHeader(ttc) { + const ttcTag = ttc.getString(4); + assert(ttcTag === "ttcf", "Must be a TrueType Collection font."); + + const majorVersion = ttc.getUint16(); + const minorVersion = ttc.getUint16(); + const numFonts = ttc.getInt32() >>> 0; + const offsetTable = []; + for (let i = 0; i < numFonts; i++) { + offsetTable.push(ttc.getInt32() >>> 0); + } + + const 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) { + const { numFonts, offsetTable } = readTrueTypeCollectionHeader(ttc); + const fontNameParts = fontName.split("+"); + let fallbackData; + + for (let i = 0; i < numFonts; i++) { + ttc.pos = (ttc.start || 0) + offsetTable[i]; + const potentialHeader = readOpenTypeHeader(ttc); + const potentialTables = readTables(ttc, potentialHeader.numTables); + + if (!potentialTables.name) { + throw new FormatError( + 'TrueType Collection font must contain a "name" table.' + ); + } + const 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++) { + const nameEntry = + nameTable[j][k] && nameTable[j][k].replace(/\s/g, ""); + if (!nameEntry) { + continue; } - } - } - this.toFontChar = map; - } - this.loadedName = fontName.split("-")[0]; - this.fontType = getFontType(type, subtype); - }, - - 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) { - const 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++) { - const table = readTableEntry(file); - if (!VALID_TABLES.includes(table.tag)) { - 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) { - const tag = file.getString(4); - - const checksum = file.getInt32() >>> 0; - const offset = file.getInt32() >>> 0; - const length = file.getInt32() >>> 0; - - // Read the table associated data - const previousPosition = file.pos; - file.pos = file.start ? file.start : 0; - file.skip(offset); - const data = file.getBytes(length); - file.pos = previousPosition; - - if (tag === "head") { - // clearing checksum adjustment - data[8] = data[9] = data[10] = data[11] = 0; - data[17] |= 0x20; // Set font optimized for cleartype flag. - } - - return { - tag, - checksum, - length, - offset, - data, - }; - } - - function readOpenTypeHeader(ttf) { - return { - version: ttf.getString(4), - numTables: ttf.getUint16(), - searchRange: ttf.getUint16(), - entrySelector: ttf.getUint16(), - rangeShift: ttf.getUint16(), - }; - } - - function readTrueTypeCollectionHeader(ttc) { - const ttcTag = ttc.getString(4); - assert(ttcTag === "ttcf", "Must be a TrueType Collection font."); - - const majorVersion = ttc.getUint16(); - const minorVersion = ttc.getUint16(); - const numFonts = ttc.getInt32() >>> 0; - const offsetTable = []; - for (let i = 0; i < numFonts; i++) { - offsetTable.push(ttc.getInt32() >>> 0); - } - - const 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) { - const { numFonts, offsetTable } = readTrueTypeCollectionHeader(ttc); - const fontNameParts = fontName.split("+"); - let fallbackData; - - for (let i = 0; i < numFonts; i++) { - ttc.pos = (ttc.start || 0) + offsetTable[i]; - const potentialHeader = readOpenTypeHeader(ttc); - const potentialTables = readTables(ttc, potentialHeader.numTables); - - if (!potentialTables.name) { - throw new FormatError( - 'TrueType Collection font must contain a "name" table.' - ); - } - const 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++) { - const nameEntry = - nameTable[j][k] && nameTable[j][k].replace(/\s/g, ""); - if (!nameEntry) { - continue; - } - if (nameEntry === fontName) { - return { + if (nameEntry === fontName) { + return { + header: potentialHeader, + tables: potentialTables, + }; + } + if (fontNameParts.length < 2) { + continue; + } + for (const part of fontNameParts) { + if (nameEntry === part) { + fallbackData = { + name: part, header: potentialHeader, tables: potentialTables, }; } - if (fontNameParts.length < 2) { - continue; - } - for (const part of fontNameParts) { - if (nameEntry === part) { - fallbackData = { - name: part, - header: potentialHeader, - tables: potentialTables, - }; - } - } } } } - if (fallbackData) { - warn( - `TrueType Collection does not contain "${fontName}" font, ` + - `falling back to "${fallbackData.name}" font instead.` - ); - return { - header: fallbackData.header, - tables: fallbackData.tables, + } + if (fallbackData) { + warn( + `TrueType Collection does not contain "${fontName}" font, ` + + `falling back to "${fallbackData.name}" font instead.` + ); + return { + header: fallbackData.header, + tables: fallbackData.tables, + }; + } + 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 + */ + function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) { + if (!cmap) { + warn("No cmap table available."); + return { + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, + }; + } + let segment; + let start = (file.start ? file.start : 0) + cmap.offset; + file.pos = start; + + file.skip(2); // version + const numTables = file.getUint16(); + + let potentialTable; + let canBreak = false; + // There's an order of preference in terms of which cmap subtable to + // use: + // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table + // - symbolic fonts the preference is a 3,0 table then a 1,0 table + // The following takes advantage of the fact that the tables are sorted + // to work. + for (let i = 0; i < numTables; i++) { + const platformId = file.getUint16(); + const encodingId = file.getUint16(); + const offset = file.getInt32() >>> 0; + let useTable = false; + + // Sometimes there are multiple of the same type of table. Default + // to choosing the first table and skip the rest. + if ( + potentialTable && + potentialTable.platformId === platformId && + potentialTable.encodingId === encodingId + ) { + continue; + } + + if ( + platformId === 0 && + (encodingId === /* Unicode Default */ 0 || + encodingId === /* Unicode 1.1 */ 1 || + encodingId === /* Unicode BMP */ 3) + ) { + useTable = true; + // Continue the loop since there still may be a higher priority + // table. + } else if (platformId === 1 && encodingId === 0) { + useTable = true; + // Continue the loop since there still may be a higher priority + // table. + } else if ( + platformId === 3 && + encodingId === 1 && + (hasEncoding || !potentialTable) + ) { + useTable = true; + if (!isSymbolicFont) { + canBreak = true; + } + } else if (isSymbolicFont && platformId === 3 && encodingId === 0) { + useTable = true; + canBreak = true; + } + + if (useTable) { + potentialTable = { + platformId, + encodingId, + offset, }; } - throw new FormatError( - `TrueType Collection does not contain "${fontName}" font.` - ); + if (canBreak) { + break; + } } - /** - * Read the appropriate subtable from the cmap according to 9.6.6.4 from - * PDF spec - */ - function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) { - if (!cmap) { - warn("No cmap table available."); - return { - platformId: -1, - encodingId: -1, - mappings: [], - hasShortCmap: false, - }; + if (potentialTable) { + file.pos = start + potentialTable.offset; + } + if (!potentialTable || file.peekByte() === -1) { + warn("Could not find a preferred cmap table."); + return { + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, + }; + } + + const format = file.getUint16(); + file.skip(2 + 2); // length + language + + let hasShortCmap = false; + const mappings = []; + let j, glyphId; + + // TODO(mack): refactor this cmap subtable reading logic out + if (format === 0) { + for (j = 0; j < 256; j++) { + const index = file.getByte(); + if (!index) { + continue; + } + mappings.push({ + charCode: j, + glyphId: index, + }); + } + hasShortCmap = true; + } else if (format === 4) { + // re-creating the table in format 4 since the encoding + // might be changed + const segCount = file.getUint16() >> 1; + file.skip(6); // skipping range fields + const segments = []; + let segIndex; + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments.push({ end: file.getUint16() }); + } + file.skip(2); + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].start = file.getUint16(); } - let segment; - let start = (file.start ? file.start : 0) + cmap.offset; - file.pos = start; - file.skip(2); // version - const numTables = file.getUint16(); + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].delta = file.getUint16(); + } - let potentialTable; - let canBreak = false; - // There's an order of preference in terms of which cmap subtable to - // use: - // - non-symbolic fonts the preference is a 3,1 table then a 1,0 table - // - symbolic fonts the preference is a 3,0 table then a 1,0 table - // The following takes advantage of the fact that the tables are sorted - // to work. - for (let i = 0; i < numTables; i++) { - const platformId = file.getUint16(); - const encodingId = file.getUint16(); - const offset = file.getInt32() >>> 0; - let useTable = false; - - // Sometimes there are multiple of the same type of table. Default - // to choosing the first table and skip the rest. - if ( - potentialTable && - potentialTable.platformId === platformId && - potentialTable.encodingId === encodingId - ) { + let offsetsCount = 0, + offsetIndex; + for (segIndex = 0; segIndex < segCount; segIndex++) { + segment = segments[segIndex]; + const rangeOffset = file.getUint16(); + if (!rangeOffset) { + segment.offsetIndex = -1; continue; } - if ( - platformId === 0 && - (encodingId === /* Unicode Default */ 0 || - encodingId === /* Unicode 1.1 */ 1 || - encodingId === /* Unicode BMP */ 3) - ) { - useTable = true; - // Continue the loop since there still may be a higher priority - // table. - } else if (platformId === 1 && encodingId === 0) { - useTable = true; - // Continue the loop since there still may be a higher priority - // table. - } else if ( - platformId === 3 && - encodingId === 1 && - (hasEncoding || !potentialTable) - ) { - useTable = true; - if (!isSymbolicFont) { - canBreak = true; - } - } else if (isSymbolicFont && platformId === 3 && encodingId === 0) { - useTable = true; - canBreak = true; - } - - if (useTable) { - potentialTable = { - platformId, - encodingId, - offset, - }; - } - if (canBreak) { - break; - } + offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); + segment.offsetIndex = offsetIndex; + offsetsCount = Math.max( + offsetsCount, + offsetIndex + segment.end - segment.start + 1 + ); } - if (potentialTable) { - file.pos = start + potentialTable.offset; - } - if (!potentialTable || file.peekByte() === -1) { - warn("Could not find a preferred cmap table."); - return { - platformId: -1, - encodingId: -1, - mappings: [], - hasShortCmap: false, - }; + const offsets = []; + for (j = 0; j < offsetsCount; j++) { + offsets.push(file.getUint16()); } - const format = file.getUint16(); - file.skip(2 + 2); // length + language + for (segIndex = 0; segIndex < segCount; segIndex++) { + segment = segments[segIndex]; + start = segment.start; + const end = segment.end; + const delta = segment.delta; + offsetIndex = segment.offsetIndex; - let hasShortCmap = false; - const mappings = []; - let j, glyphId; - - // TODO(mack): refactor this cmap subtable reading logic out - if (format === 0) { - for (j = 0; j < 256; j++) { - const index = file.getByte(); - if (!index) { + for (j = start; j <= end; j++) { + if (j === 0xffff) { continue; } + + glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start]; + glyphId = (glyphId + delta) & 0xffff; mappings.push({ charCode: j, - glyphId: index, - }); - } - hasShortCmap = true; - } else if (format === 4) { - // re-creating the table in format 4 since the encoding - // might be changed - const segCount = file.getUint16() >> 1; - file.skip(6); // skipping range fields - const segments = []; - let segIndex; - for (segIndex = 0; segIndex < segCount; segIndex++) { - segments.push({ end: file.getUint16() }); - } - file.skip(2); - for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].start = file.getUint16(); - } - - for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].delta = file.getUint16(); - } - - let offsetsCount = 0, - offsetIndex; - for (segIndex = 0; segIndex < segCount; segIndex++) { - segment = segments[segIndex]; - const rangeOffset = file.getUint16(); - if (!rangeOffset) { - segment.offsetIndex = -1; - continue; - } - - offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); - segment.offsetIndex = offsetIndex; - offsetsCount = Math.max( - offsetsCount, - offsetIndex + segment.end - segment.start + 1 - ); - } - - const offsets = []; - for (j = 0; j < offsetsCount; j++) { - offsets.push(file.getUint16()); - } - - for (segIndex = 0; segIndex < segCount; segIndex++) { - segment = segments[segIndex]; - start = segment.start; - const end = segment.end; - const delta = segment.delta; - offsetIndex = segment.offsetIndex; - - for (j = start; j <= end; j++) { - if (j === 0xffff) { - continue; - } - - glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start]; - glyphId = (glyphId + delta) & 0xffff; - mappings.push({ - charCode: j, - glyphId, - }); - } - } - } else if (format === 6) { - // Format 6 is a 2-bytes dense mapping, which means the font data - // lives glue together even if they are pretty far in the unicode - // table. (This looks weird, so I can have missed something), this - // works on Linux but seems to fails on Mac so let's rewrite the - // cmap table to a 3-1-4 style - const firstCode = file.getUint16(); - const entryCount = file.getUint16(); - - for (j = 0; j < entryCount; j++) { - glyphId = file.getUint16(); - const charCode = firstCode + j; - - mappings.push({ - charCode, glyphId, }); } - } else { - warn("cmap table has unsupported format: " + format); - return { - platformId: -1, - encodingId: -1, - mappings: [], - hasShortCmap: false, - }; } + } else if (format === 6) { + // Format 6 is a 2-bytes dense mapping, which means the font data + // lives glue together even if they are pretty far in the unicode + // table. (This looks weird, so I can have missed something), this + // works on Linux but seems to fails on Mac so let's rewrite the + // cmap table to a 3-1-4 style + const firstCode = file.getUint16(); + const entryCount = file.getUint16(); - // removing duplicate entries - mappings.sort(function (a, b) { - return a.charCode - b.charCode; - }); - for (let i = 1; i < mappings.length; i++) { - if (mappings[i - 1].charCode === mappings[i].charCode) { - mappings.splice(i, 1); - i--; - } + for (j = 0; j < entryCount; j++) { + glyphId = file.getUint16(); + const charCode = firstCode + j; + + mappings.push({ + charCode, + glyphId, + }); } - + } else { + warn("cmap table has unsupported format: " + format); return { - platformId: potentialTable.platformId, - encodingId: potentialTable.encodingId, - mappings, - hasShortCmap, + platformId: -1, + encodingId: -1, + mappings: [], + hasShortCmap: false, }; } - function sanitizeMetrics( - file, - header, - metrics, - numGlyphs, - dupFirstEntry - ) { - if (!header) { - if (metrics) { - metrics.data = null; - } - return; - } - - file.pos = (file.start ? file.start : 0) + header.offset; - file.pos += 4; // version - file.pos += 2; // ascent - file.pos += 2; // descent - file.pos += 2; // linegap - file.pos += 2; // adv_width_max - file.pos += 2; // min_sb1 - file.pos += 2; // min_sb2 - file.pos += 2; // max_extent - file.pos += 2; // caret_slope_rise - file.pos += 2; // caret_slope_run - file.pos += 2; // caret_offset - file.pos += 8; // reserved - file.pos += 2; // format - let numOfMetrics = file.getUint16(); - - if (numOfMetrics > numGlyphs) { - info( - "The numOfMetrics (" + - numOfMetrics + - ") should not be " + - "greater than the numGlyphs (" + - numGlyphs + - ")" - ); - // Reduce numOfMetrics if it is greater than numGlyphs - numOfMetrics = numGlyphs; - header.data[34] = (numOfMetrics & 0xff00) >> 8; - header.data[35] = numOfMetrics & 0x00ff; - } - - const numOfSidebearings = numGlyphs - numOfMetrics; - const numMissing = - numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1); - - if (numMissing > 0) { - // For each missing glyph, we set both the width and lsb to 0 (zero). - // Since we need to add two properties for each glyph, this explains - // the use of |numMissing * 2| when initializing the typed array. - const entries = new Uint8Array(metrics.length + numMissing * 2); - entries.set(metrics.data); - if (dupFirstEntry) { - // Set the sidebearing value of the duplicated glyph. - entries[metrics.length] = metrics.data[2]; - entries[metrics.length + 1] = metrics.data[3]; - } - metrics.data = entries; + // removing duplicate entries + mappings.sort(function (a, b) { + return a.charCode - b.charCode; + }); + for (let i = 1; i < mappings.length; i++) { + if (mappings[i - 1].charCode === mappings[i].charCode) { + mappings.splice(i, 1); + i--; } } - function sanitizeGlyph( - source, - sourceStart, - sourceEnd, - dest, - destStart, - hintsValid - ) { - const glyphProfile = { - length: 0, - sizeOfInstructions: 0, - }; - if (sourceEnd - sourceStart <= 12) { - // glyph with data less than 12 is invalid one - return glyphProfile; - } - const glyf = source.subarray(sourceStart, sourceEnd); - let contoursCount = signedInt16(glyf[0], glyf[1]); - if (contoursCount < 0) { - // OTS doesn't like contour count to be less than -1. - contoursCount = -1; - writeSignedInt16(glyf, 0, contoursCount); - // complex glyph, writing as is - dest.set(glyf, destStart); - glyphProfile.length = glyf.length; - return glyphProfile; - } + return { + platformId: potentialTable.platformId, + encodingId: potentialTable.encodingId, + mappings, + hasShortCmap, + }; + } - let i, - j = 10, - flagsCount = 0; - for (i = 0; i < contoursCount; i++) { - const endPoint = (glyf[j] << 8) | glyf[j + 1]; - flagsCount = endPoint + 1; - j += 2; + function sanitizeMetrics(file, header, metrics, numGlyphs, dupFirstEntry) { + if (!header) { + if (metrics) { + metrics.data = null; } - // skipping instructions - const instructionsStart = j; - const instructionsLength = (glyf[j] << 8) | glyf[j + 1]; - glyphProfile.sizeOfInstructions = instructionsLength; - j += 2 + instructionsLength; - const instructionsEnd = j; - // validating flags - let coordinatesLength = 0; - for (i = 0; i < flagsCount; i++) { - const flag = glyf[j++]; - if (flag & 0xc0) { - // reserved flags must be zero, cleaning up - glyf[j - 1] = flag & 0x3f; - } - let xLength = 2; - if (flag & 2) { - xLength = 1; - } else if (flag & 16) { - xLength = 0; - } - let yLength = 2; - if (flag & 4) { - yLength = 1; - } else if (flag & 32) { - yLength = 0; - } - const xyLength = xLength + yLength; - coordinatesLength += xyLength; - if (flag & 8) { - const repeat = glyf[j++]; - i += repeat; - coordinatesLength += repeat * xyLength; - } + return; + } + + file.pos = (file.start ? file.start : 0) + header.offset; + file.pos += 4; // version + file.pos += 2; // ascent + file.pos += 2; // descent + file.pos += 2; // linegap + file.pos += 2; // adv_width_max + file.pos += 2; // min_sb1 + file.pos += 2; // min_sb2 + file.pos += 2; // max_extent + file.pos += 2; // caret_slope_rise + file.pos += 2; // caret_slope_run + file.pos += 2; // caret_offset + file.pos += 8; // reserved + file.pos += 2; // format + let numOfMetrics = file.getUint16(); + + if (numOfMetrics > numGlyphs) { + info( + "The numOfMetrics (" + + numOfMetrics + + ") should not be " + + "greater than the numGlyphs (" + + numGlyphs + + ")" + ); + // Reduce numOfMetrics if it is greater than numGlyphs + numOfMetrics = numGlyphs; + header.data[34] = (numOfMetrics & 0xff00) >> 8; + header.data[35] = numOfMetrics & 0x00ff; + } + + const numOfSidebearings = numGlyphs - numOfMetrics; + const numMissing = + numOfSidebearings - ((metrics.length - numOfMetrics * 4) >> 1); + + if (numMissing > 0) { + // For each missing glyph, we set both the width and lsb to 0 (zero). + // Since we need to add two properties for each glyph, this explains + // the use of |numMissing * 2| when initializing the typed array. + const entries = new Uint8Array(metrics.length + numMissing * 2); + entries.set(metrics.data); + if (dupFirstEntry) { + // Set the sidebearing value of the duplicated glyph. + entries[metrics.length] = metrics.data[2]; + entries[metrics.length + 1] = metrics.data[3]; } - // glyph without coordinates will be rejected - if (coordinatesLength === 0) { - return glyphProfile; - } - let glyphDataLength = j + coordinatesLength; - if (glyphDataLength > glyf.length) { - // not enough data for coordinates - return glyphProfile; - } - if (!hintsValid && instructionsLength > 0) { - dest.set(glyf.subarray(0, instructionsStart), destStart); - dest.set([0, 0], destStart + instructionsStart); - dest.set( - glyf.subarray(instructionsEnd, glyphDataLength), - destStart + instructionsStart + 2 - ); - glyphDataLength -= instructionsLength; - if (glyf.length - glyphDataLength > 3) { - glyphDataLength = (glyphDataLength + 3) & ~3; - } - glyphProfile.length = glyphDataLength; - return glyphProfile; - } - if (glyf.length - glyphDataLength > 3) { - // truncating and aligning to 4 bytes the long glyph data - glyphDataLength = (glyphDataLength + 3) & ~3; - dest.set(glyf.subarray(0, glyphDataLength), destStart); - glyphProfile.length = glyphDataLength; - return glyphProfile; - } - // glyph data is fine + metrics.data = entries; + } + } + + function sanitizeGlyph( + source, + sourceStart, + sourceEnd, + dest, + destStart, + hintsValid + ) { + const glyphProfile = { + length: 0, + sizeOfInstructions: 0, + }; + if (sourceEnd - sourceStart <= 12) { + // glyph with data less than 12 is invalid one + return glyphProfile; + } + const glyf = source.subarray(sourceStart, sourceEnd); + let contoursCount = signedInt16(glyf[0], glyf[1]); + if (contoursCount < 0) { + // OTS doesn't like contour count to be less than -1. + contoursCount = -1; + writeSignedInt16(glyf, 0, contoursCount); + // complex glyph, writing as is dest.set(glyf, destStart); glyphProfile.length = glyf.length; return glyphProfile; } - function sanitizeHead(head, numGlyphs, locaLength) { - const data = head.data; - - // Validate version: - // Should always be 0x00010000 - const version = int32(data[0], data[1], data[2], data[3]); - if (version >> 16 !== 1) { - info("Attempting to fix invalid version in head table: " + version); - data[0] = 0; - data[1] = 1; - data[2] = 0; - data[3] = 0; + let i, + j = 10, + flagsCount = 0; + for (i = 0; i < contoursCount; i++) { + const endPoint = (glyf[j] << 8) | glyf[j + 1]; + flagsCount = endPoint + 1; + j += 2; + } + // skipping instructions + const instructionsStart = j; + const instructionsLength = (glyf[j] << 8) | glyf[j + 1]; + glyphProfile.sizeOfInstructions = instructionsLength; + j += 2 + instructionsLength; + const instructionsEnd = j; + // validating flags + let coordinatesLength = 0; + for (i = 0; i < flagsCount; i++) { + const flag = glyf[j++]; + if (flag & 0xc0) { + // reserved flags must be zero, cleaning up + glyf[j - 1] = flag & 0x3f; } - - const indexToLocFormat = int16(data[50], data[51]); - if (indexToLocFormat < 0 || indexToLocFormat > 1) { - info( - "Attempting to fix invalid indexToLocFormat in head table: " + - indexToLocFormat - ); - - // The value of indexToLocFormat should be 0 if the loca table - // consists of short offsets, and should be 1 if the loca table - // consists of long offsets. - // - // The number of entries in the loca table should be numGlyphs + 1. - // - // Using this information, we can work backwards to deduce if the - // size of each offset in the loca table, and thus figure out the - // appropriate value for indexToLocFormat. - - const numGlyphsPlusOne = numGlyphs + 1; - if (locaLength === numGlyphsPlusOne << 1) { - // 0x0000 indicates the loca table consists of short offsets - data[50] = 0; - data[51] = 0; - } else if (locaLength === numGlyphsPlusOne << 2) { - // 0x0001 indicates the loca table consists of long offsets - data[50] = 0; - data[51] = 1; - } else { - throw new FormatError( - "Could not fix indexToLocFormat: " + indexToLocFormat - ); - } + let xLength = 2; + if (flag & 2) { + xLength = 1; + } else if (flag & 16) { + xLength = 0; + } + let yLength = 2; + if (flag & 4) { + yLength = 1; + } else if (flag & 32) { + yLength = 0; + } + const xyLength = xLength + yLength; + coordinatesLength += xyLength; + if (flag & 8) { + const repeat = glyf[j++]; + i += repeat; + coordinatesLength += repeat * xyLength; } } + // glyph without coordinates will be rejected + if (coordinatesLength === 0) { + return glyphProfile; + } + let glyphDataLength = j + coordinatesLength; + if (glyphDataLength > glyf.length) { + // not enough data for coordinates + return glyphProfile; + } + if (!hintsValid && instructionsLength > 0) { + dest.set(glyf.subarray(0, instructionsStart), destStart); + dest.set([0, 0], destStart + instructionsStart); + dest.set( + glyf.subarray(instructionsEnd, glyphDataLength), + destStart + instructionsStart + 2 + ); + glyphDataLength -= instructionsLength; + if (glyf.length - glyphDataLength > 3) { + glyphDataLength = (glyphDataLength + 3) & ~3; + } + glyphProfile.length = glyphDataLength; + return glyphProfile; + } + if (glyf.length - glyphDataLength > 3) { + // truncating and aligning to 4 bytes the long glyph data + glyphDataLength = (glyphDataLength + 3) & ~3; + dest.set(glyf.subarray(0, glyphDataLength), destStart); + glyphProfile.length = glyphDataLength; + return glyphProfile; + } + // glyph data is fine + dest.set(glyf, destStart); + glyphProfile.length = glyf.length; + return glyphProfile; + } - function sanitizeGlyphLocations( - loca, - glyf, - numGlyphs, - isGlyphLocationsLong, - hintsValid, - dupFirstEntry, - maxSizeOfInstructions - ) { - let itemSize, itemDecode, itemEncode; - if (isGlyphLocationsLong) { - itemSize = 4; - itemDecode = function fontItemDecodeLong(data, offset) { - return ( - (data[offset] << 24) | - (data[offset + 1] << 16) | - (data[offset + 2] << 8) | - data[offset + 3] - ); - }; - itemEncode = function fontItemEncodeLong(data, offset, value) { - data[offset] = (value >>> 24) & 0xff; - data[offset + 1] = (value >> 16) & 0xff; - data[offset + 2] = (value >> 8) & 0xff; - data[offset + 3] = value & 0xff; - }; + function sanitizeHead(head, numGlyphs, locaLength) { + const data = head.data; + + // Validate version: + // Should always be 0x00010000 + const version = int32(data[0], data[1], data[2], data[3]); + if (version >> 16 !== 1) { + info("Attempting to fix invalid version in head table: " + version); + data[0] = 0; + data[1] = 1; + data[2] = 0; + data[3] = 0; + } + + const indexToLocFormat = int16(data[50], data[51]); + if (indexToLocFormat < 0 || indexToLocFormat > 1) { + info( + "Attempting to fix invalid indexToLocFormat in head table: " + + indexToLocFormat + ); + + // The value of indexToLocFormat should be 0 if the loca table + // consists of short offsets, and should be 1 if the loca table + // consists of long offsets. + // + // The number of entries in the loca table should be numGlyphs + 1. + // + // Using this information, we can work backwards to deduce if the + // size of each offset in the loca table, and thus figure out the + // appropriate value for indexToLocFormat. + + const numGlyphsPlusOne = numGlyphs + 1; + if (locaLength === numGlyphsPlusOne << 1) { + // 0x0000 indicates the loca table consists of short offsets + data[50] = 0; + data[51] = 0; + } else if (locaLength === numGlyphsPlusOne << 2) { + // 0x0001 indicates the loca table consists of long offsets + data[50] = 0; + data[51] = 1; } else { - itemSize = 2; - itemDecode = function fontItemDecode(data, offset) { - return (data[offset] << 9) | (data[offset + 1] << 1); - }; - itemEncode = function fontItemEncode(data, offset, value) { - data[offset] = (value >> 9) & 0xff; - data[offset + 1] = (value >> 1) & 0xff; - }; - } - // The first glyph is duplicated. - const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; - const locaDataSize = itemSize * (1 + numGlyphsOut); - // Resize loca table to account for duplicated glyph. - const locaData = new Uint8Array(locaDataSize); - locaData.set(loca.data.subarray(0, locaDataSize)); - loca.data = locaData; - // removing the invalid glyphs - const oldGlyfData = glyf.data; - const oldGlyfDataLength = oldGlyfData.length; - const newGlyfData = new Uint8Array(oldGlyfDataLength); - - // The spec says the offsets should be in ascending order, however - // this is not true for some fonts or they use the offset of 0 to mark a - // glyph as missing. OTS requires the offsets to be in order and not to - // be zero, so we must sort and rebuild the loca table and potentially - // re-arrange the glyf data. - let i, j; - const locaEntries = []; - // There are numGlyphs + 1 loca table entries. - for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) { - let offset = itemDecode(locaData, j); - if (offset > oldGlyfDataLength) { - offset = oldGlyfDataLength; - } - locaEntries.push({ - index: i, - offset, - endOffset: 0, - }); - } - locaEntries.sort((a, b) => { - return a.offset - b.offset; - }); - // Now the offsets are sorted, calculate the end offset of each glyph. - // The last loca entry's endOffset is not calculated since it's the end - // of the data and will be stored on the previous entry's endOffset. - for (i = 0; i < numGlyphs; i++) { - locaEntries[i].endOffset = locaEntries[i + 1].offset; - } - // Re-sort so glyphs aren't out of order. - locaEntries.sort((a, b) => { - return a.index - b.index; - }); - - const missingGlyphs = Object.create(null); - let writeOffset = 0; - itemEncode(locaData, 0, writeOffset); - for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { - const glyphProfile = sanitizeGlyph( - oldGlyfData, - locaEntries[i].offset, - locaEntries[i].endOffset, - newGlyfData, - writeOffset, - hintsValid + throw new FormatError( + "Could not fix indexToLocFormat: " + indexToLocFormat ); - const newLength = glyphProfile.length; - if (newLength === 0) { - missingGlyphs[i] = true; - } - if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) { - maxSizeOfInstructions = glyphProfile.sizeOfInstructions; - } - writeOffset += newLength; - itemEncode(locaData, j, writeOffset); } + } + } - if (writeOffset === 0) { - // glyf table cannot be empty -- redoing the glyf and loca tables - // to have single glyph with one point - const simpleGlyph = new Uint8Array([ - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 49, - 0, - ]); - for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) { - itemEncode(locaData, j, simpleGlyph.length); - } - glyf.data = simpleGlyph; - } else if (dupFirstEntry) { - // Browsers will not display a glyph at position 0. Typically glyph 0 - // is notdef, but a number of fonts put a valid glyph there so it must - // be duplicated and appended. - const firstEntryLength = itemDecode(locaData, itemSize); - if (newGlyfData.length > firstEntryLength + writeOffset) { - glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset); - } else { - glyf.data = new Uint8Array(firstEntryLength + writeOffset); - glyf.data.set(newGlyfData.subarray(0, writeOffset)); - } - glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset); - itemEncode( - loca.data, - locaData.length - itemSize, - writeOffset + firstEntryLength + function sanitizeGlyphLocations( + loca, + glyf, + numGlyphs, + isGlyphLocationsLong, + hintsValid, + dupFirstEntry, + maxSizeOfInstructions + ) { + let itemSize, itemDecode, itemEncode; + if (isGlyphLocationsLong) { + itemSize = 4; + itemDecode = function fontItemDecodeLong(data, offset) { + return ( + (data[offset] << 24) | + (data[offset + 1] << 16) | + (data[offset + 2] << 8) | + data[offset + 3] ); - } else { - glyf.data = newGlyfData.subarray(0, writeOffset); - } - return { - missingGlyphs, - maxSizeOfInstructions, + }; + itemEncode = function fontItemEncodeLong(data, offset, value) { + data[offset] = (value >>> 24) & 0xff; + data[offset + 1] = (value >> 16) & 0xff; + data[offset + 2] = (value >> 8) & 0xff; + data[offset + 3] = value & 0xff; + }; + } else { + itemSize = 2; + itemDecode = function fontItemDecode(data, offset) { + return (data[offset] << 9) | (data[offset + 1] << 1); + }; + itemEncode = function fontItemEncode(data, offset, value) { + data[offset] = (value >> 9) & 0xff; + data[offset + 1] = (value >> 1) & 0xff; }; } + // The first glyph is duplicated. + const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs; + const locaDataSize = itemSize * (1 + numGlyphsOut); + // Resize loca table to account for duplicated glyph. + const locaData = new Uint8Array(locaDataSize); + locaData.set(loca.data.subarray(0, locaDataSize)); + loca.data = locaData; + // removing the invalid glyphs + const oldGlyfData = glyf.data; + const oldGlyfDataLength = oldGlyfData.length; + const newGlyfData = new Uint8Array(oldGlyfDataLength); - function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) { - const start = (font.start ? font.start : 0) + post.offset; - font.pos = start; + // The spec says the offsets should be in ascending order, however + // this is not true for some fonts or they use the offset of 0 to mark a + // glyph as missing. OTS requires the offsets to be in order and not to + // be zero, so we must sort and rebuild the loca table and potentially + // re-arrange the glyf data. + let i, j; + const locaEntries = []; + // There are numGlyphs + 1 loca table entries. + for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) { + let offset = itemDecode(locaData, j); + if (offset > oldGlyfDataLength) { + offset = oldGlyfDataLength; + } + locaEntries.push({ + index: i, + offset, + endOffset: 0, + }); + } + locaEntries.sort((a, b) => { + return a.offset - b.offset; + }); + // Now the offsets are sorted, calculate the end offset of each glyph. + // The last loca entry's endOffset is not calculated since it's the end + // of the data and will be stored on the previous entry's endOffset. + for (i = 0; i < numGlyphs; i++) { + locaEntries[i].endOffset = locaEntries[i + 1].offset; + } + // Re-sort so glyphs aren't out of order. + locaEntries.sort((a, b) => { + return a.index - b.index; + }); - const length = post.length, - end = start + length; - const version = font.getInt32(); - // skip rest to the tables - font.skip(28); + const missingGlyphs = Object.create(null); + let writeOffset = 0; + itemEncode(locaData, 0, writeOffset); + for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { + const glyphProfile = sanitizeGlyph( + oldGlyfData, + locaEntries[i].offset, + locaEntries[i].endOffset, + newGlyfData, + writeOffset, + hintsValid + ); + const newLength = glyphProfile.length; + if (newLength === 0) { + missingGlyphs[i] = true; + } + if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) { + maxSizeOfInstructions = glyphProfile.sizeOfInstructions; + } + writeOffset += newLength; + itemEncode(locaData, j, writeOffset); + } - let glyphNames; - let valid = true; - let i; + if (writeOffset === 0) { + // glyf table cannot be empty -- redoing the glyf and loca tables + // to have single glyph with one point + const simpleGlyph = new Uint8Array([ + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 49, + 0, + ]); + for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) { + itemEncode(locaData, j, simpleGlyph.length); + } + glyf.data = simpleGlyph; + } else if (dupFirstEntry) { + // Browsers will not display a glyph at position 0. Typically glyph 0 + // is notdef, but a number of fonts put a valid glyph there so it must + // be duplicated and appended. + const firstEntryLength = itemDecode(locaData, itemSize); + if (newGlyfData.length > firstEntryLength + writeOffset) { + glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset); + } else { + glyf.data = new Uint8Array(firstEntryLength + writeOffset); + glyf.data.set(newGlyfData.subarray(0, writeOffset)); + } + glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset); + itemEncode( + loca.data, + locaData.length - itemSize, + writeOffset + firstEntryLength + ); + } else { + glyf.data = newGlyfData.subarray(0, writeOffset); + } + return { + missingGlyphs, + maxSizeOfInstructions, + }; + } - switch (version) { - case 0x00010000: - glyphNames = MacStandardGlyphOrdering; + function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) { + const start = (font.start ? font.start : 0) + post.offset; + font.pos = start; + + const length = post.length, + end = start + length; + const version = font.getInt32(); + // skip rest to the tables + font.skip(28); + + let glyphNames; + let valid = true; + let i; + + switch (version) { + case 0x00010000: + glyphNames = MacStandardGlyphOrdering; + break; + case 0x00020000: + const numGlyphs = font.getUint16(); + if (numGlyphs !== maxpNumGlyphs) { + valid = false; break; - case 0x00020000: - const numGlyphs = font.getUint16(); - if (numGlyphs !== maxpNumGlyphs) { + } + const glyphNameIndexes = []; + for (i = 0; i < numGlyphs; ++i) { + const index = font.getUint16(); + if (index >= 32768) { valid = false; break; } - const glyphNameIndexes = []; - for (i = 0; i < numGlyphs; ++i) { - const index = font.getUint16(); - if (index >= 32768) { - valid = false; - break; - } - glyphNameIndexes.push(index); - } - if (!valid) { - break; - } - const customNames = [], - strBuf = []; - while (font.pos < end) { - const stringLength = font.getByte(); - strBuf.length = stringLength; - for (i = 0; i < stringLength; ++i) { - strBuf[i] = String.fromCharCode(font.getByte()); - } - customNames.push(strBuf.join("")); - } - glyphNames = []; - for (i = 0; i < numGlyphs; ++i) { - const j = glyphNameIndexes[i]; - if (j < 258) { - glyphNames.push(MacStandardGlyphOrdering[j]); - continue; - } - glyphNames.push(customNames[j - 258]); - } + glyphNameIndexes.push(index); + } + if (!valid) { break; - case 0x00030000: - break; - default: - warn("Unknown/unsupported post table version " + version); - valid = false; - if (propertiesObj.defaultEncoding) { - glyphNames = propertiesObj.defaultEncoding; + } + const customNames = [], + strBuf = []; + while (font.pos < end) { + const stringLength = font.getByte(); + strBuf.length = stringLength; + for (i = 0; i < stringLength; ++i) { + strBuf[i] = String.fromCharCode(font.getByte()); } - break; - } - propertiesObj.glyphNames = glyphNames; - return valid; + customNames.push(strBuf.join("")); + } + glyphNames = []; + for (i = 0; i < numGlyphs; ++i) { + const j = glyphNameIndexes[i]; + if (j < 258) { + glyphNames.push(MacStandardGlyphOrdering[j]); + continue; + } + glyphNames.push(customNames[j - 258]); + } + break; + case 0x00030000: + break; + default: + warn("Unknown/unsupported post table version " + version); + valid = false; + if (propertiesObj.defaultEncoding) { + glyphNames = propertiesObj.defaultEncoding; + } + break; } + propertiesObj.glyphNames = glyphNames; + return valid; + } - function readNameTable(nameTable) { - const start = (font.start ? font.start : 0) + nameTable.offset; - font.pos = start; + function readNameTable(nameTable) { + const start = (font.start ? font.start : 0) + nameTable.offset; + font.pos = start; - const names = [[], []]; - const length = nameTable.length, - end = start + length; - const format = font.getUint16(); - const FORMAT_0_HEADER_LENGTH = 6; - if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { - // unsupported name table format or table "too" small - return names; - } - const numRecords = font.getUint16(); - const stringsStart = font.getUint16(); - const records = []; - const NAME_RECORD_LENGTH = 12; - let i, ii; - - for ( - i = 0; - i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; - i++ - ) { - const r = { - platform: font.getUint16(), - encoding: font.getUint16(), - language: font.getUint16(), - name: font.getUint16(), - length: font.getUint16(), - offset: font.getUint16(), - }; - // using only Macintosh and Windows platform/encoding names - if ( - (r.platform === 1 && r.encoding === 0 && r.language === 0) || - (r.platform === 3 && r.encoding === 1 && r.language === 0x409) - ) { - records.push(r); - } - } - for (i = 0, ii = records.length; i < ii; i++) { - const record = records[i]; - if (record.length <= 0) { - continue; // Nothing to process, ignoring. - } - const pos = start + stringsStart + record.offset; - if (pos + record.length > end) { - continue; // outside of name table, ignoring - } - font.pos = pos; - const nameIndex = record.name; - if (record.encoding) { - // unicode - let str = ""; - for (let j = 0, jj = record.length; j < jj; j += 2) { - str += String.fromCharCode(font.getUint16()); - } - names[1][nameIndex] = str; - } else { - names[0][nameIndex] = font.getString(record.length); - } - } + const names = [[], []]; + const length = nameTable.length, + end = start + length; + const format = font.getUint16(); + const FORMAT_0_HEADER_LENGTH = 6; + if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { + // unsupported name table format or table "too" small return names; } + const numRecords = font.getUint16(); + const stringsStart = font.getUint16(); + const records = []; + const NAME_RECORD_LENGTH = 12; + let i, ii; - // prettier-ignore - const TTOpsStackDeltas = [ + for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) { + const r = { + platform: font.getUint16(), + encoding: font.getUint16(), + language: font.getUint16(), + name: font.getUint16(), + length: font.getUint16(), + offset: font.getUint16(), + }; + // using only Macintosh and Windows platform/encoding names + if ( + (r.platform === 1 && r.encoding === 0 && r.language === 0) || + (r.platform === 3 && r.encoding === 1 && r.language === 0x409) + ) { + records.push(r); + } + } + for (i = 0, ii = records.length; i < ii; i++) { + const record = records[i]; + if (record.length <= 0) { + continue; // Nothing to process, ignoring. + } + const pos = start + stringsStart + record.offset; + if (pos + record.length > end) { + continue; // outside of name table, ignoring + } + font.pos = pos; + const nameIndex = record.name; + if (record.encoding) { + // unicode + let str = ""; + for (let j = 0, jj = record.length; j < jj; j += 2) { + str += String.fromCharCode(font.getUint16()); + } + names[1][nameIndex] = str; + } else { + names[0][nameIndex] = font.getString(record.length); + } + } + return names; + } + + // prettier-ignore + const TTOpsStackDeltas = [ 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, @@ -2015,1166 +1990,1156 @@ const Font = (function FontClosure() { -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2]; - // 0xC0-DF == -1 and 0xE0-FF == -2 + // 0xC0-DF == -1 and 0xE0-FF == -2 - function sanitizeTTProgram(table, ttContext) { - let data = table.data; - let i = 0, - j, - n, - b, - funcId, - pc, - lastEndf = 0, - lastDeff = 0; - const stack = []; - const callstack = []; - const functionsCalled = []; - let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; - let inFDEF = false, - ifLevel = 0, - inELSE = 0; - for (let ii = data.length; i < ii; ) { - const op = data[i++]; - // The TrueType instruction set docs can be found at - // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html - if (op === 0x40) { - // NPUSHB - pushes n bytes - n = data[i++]; - if (inFDEF || inELSE) { - i += n; - } else { - for (j = 0; j < n; j++) { - stack.push(data[i++]); - } - } - } else if (op === 0x41) { - // NPUSHW - pushes n words - n = data[i++]; - if (inFDEF || inELSE) { - i += n * 2; - } else { - for (j = 0; j < n; j++) { - b = data[i++]; - stack.push((b << 8) | data[i++]); - } - } - } else if ((op & 0xf8) === 0xb0) { - // PUSHB - pushes bytes - n = op - 0xb0 + 1; - if (inFDEF || inELSE) { - i += n; - } else { - for (j = 0; j < n; j++) { - stack.push(data[i++]); - } - } - } else if ((op & 0xf8) === 0xb8) { - // PUSHW - pushes words - n = op - 0xb8 + 1; - if (inFDEF || inELSE) { - i += n * 2; - } else { - for (j = 0; j < n; j++) { - b = data[i++]; - stack.push((b << 8) | data[i++]); - } - } - } else if (op === 0x2b && !tooComplexToFollowFunctions) { - // CALL - if (!inFDEF && !inELSE) { - // collecting information about which functions are used - funcId = stack[stack.length - 1]; - if (isNaN(funcId)) { - info("TT: CALL empty stack (or invalid entry)."); - } else { - ttContext.functionsUsed[funcId] = true; - if (funcId in ttContext.functionsStackDeltas) { - const newStackLength = - stack.length + ttContext.functionsStackDeltas[funcId]; - if (newStackLength < 0) { - warn("TT: CALL invalid functions stack delta."); - ttContext.hintsValid = false; - return; - } - stack.length = newStackLength; - } else if ( - funcId in ttContext.functionsDefined && - !functionsCalled.includes(funcId) - ) { - callstack.push({ data, i, stackTop: stack.length - 1 }); - functionsCalled.push(funcId); - pc = ttContext.functionsDefined[funcId]; - if (!pc) { - warn("TT: CALL non-existent function"); - ttContext.hintsValid = false; - return; - } - data = pc.data; - i = pc.i; - } - } - } - } else if (op === 0x2c && !tooComplexToFollowFunctions) { - // FDEF - if (inFDEF || inELSE) { - warn("TT: nested FDEFs not allowed"); - tooComplexToFollowFunctions = true; - } - inFDEF = true; - // collecting information about which functions are defined - lastDeff = i; - funcId = stack.pop(); - ttContext.functionsDefined[funcId] = { data, i }; - } else if (op === 0x2d) { - // ENDF - end of function - if (inFDEF) { - inFDEF = false; - lastEndf = i; - } else { - pc = callstack.pop(); - if (!pc) { - warn("TT: ENDF bad stack"); - ttContext.hintsValid = false; - return; - } - funcId = functionsCalled.pop(); - data = pc.data; - i = pc.i; - ttContext.functionsStackDeltas[funcId] = - stack.length - pc.stackTop; - } - } else if (op === 0x89) { - // IDEF - instruction definition - if (inFDEF || inELSE) { - warn("TT: nested IDEFs not allowed"); - tooComplexToFollowFunctions = true; - } - inFDEF = true; - // recording it as a function to track ENDF - lastDeff = i; - } else if (op === 0x58) { - // IF - ++ifLevel; - } else if (op === 0x1b) { - // ELSE - inELSE = ifLevel; - } else if (op === 0x59) { - // EIF - if (inELSE === ifLevel) { - inELSE = 0; - } - --ifLevel; - } else if (op === 0x1c) { - // JMPR - if (!inFDEF && !inELSE) { - const offset = stack[stack.length - 1]; - // only jumping forward to prevent infinite loop - if (offset > 0) { - i += offset - 1; - } + function sanitizeTTProgram(table, ttContext) { + let data = table.data; + let i = 0, + j, + n, + b, + funcId, + pc, + lastEndf = 0, + lastDeff = 0; + const stack = []; + const callstack = []; + const functionsCalled = []; + let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; + let inFDEF = false, + ifLevel = 0, + inELSE = 0; + for (let ii = data.length; i < ii; ) { + const op = data[i++]; + // The TrueType instruction set docs can be found at + // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html + if (op === 0x40) { + // NPUSHB - pushes n bytes + n = data[i++]; + if (inFDEF || inELSE) { + i += n; + } else { + for (j = 0; j < n; j++) { + stack.push(data[i++]); } } - // Adjusting stack not extactly, but just enough to get function id + } else if (op === 0x41) { + // NPUSHW - pushes n words + n = data[i++]; + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (j = 0; j < n; j++) { + b = data[i++]; + stack.push((b << 8) | data[i++]); + } + } + } else if ((op & 0xf8) === 0xb0) { + // PUSHB - pushes bytes + n = op - 0xb0 + 1; + if (inFDEF || inELSE) { + i += n; + } else { + for (j = 0; j < n; j++) { + stack.push(data[i++]); + } + } + } else if ((op & 0xf8) === 0xb8) { + // PUSHW - pushes words + n = op - 0xb8 + 1; + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (j = 0; j < n; j++) { + b = data[i++]; + stack.push((b << 8) | data[i++]); + } + } + } else if (op === 0x2b && !tooComplexToFollowFunctions) { + // CALL if (!inFDEF && !inELSE) { - let stackDelta = 0; - if (op <= 0x8e) { - stackDelta = TTOpsStackDeltas[op]; - } else if (op >= 0xc0 && op <= 0xdf) { - stackDelta = -1; - } else if (op >= 0xe0) { - stackDelta = -2; - } - if (op >= 0x71 && op <= 0x75) { - n = stack.pop(); - if (!isNaN(n)) { - stackDelta = -n * 2; + // collecting information about which functions are used + funcId = stack[stack.length - 1]; + if (isNaN(funcId)) { + info("TT: CALL empty stack (or invalid entry)."); + } else { + ttContext.functionsUsed[funcId] = true; + if (funcId in ttContext.functionsStackDeltas) { + const newStackLength = + stack.length + ttContext.functionsStackDeltas[funcId]; + if (newStackLength < 0) { + warn("TT: CALL invalid functions stack delta."); + ttContext.hintsValid = false; + return; + } + stack.length = newStackLength; + } else if ( + funcId in ttContext.functionsDefined && + !functionsCalled.includes(funcId) + ) { + callstack.push({ data, i, stackTop: stack.length - 1 }); + functionsCalled.push(funcId); + pc = ttContext.functionsDefined[funcId]; + if (!pc) { + warn("TT: CALL non-existent function"); + ttContext.hintsValid = false; + return; + } + data = pc.data; + i = pc.i; } } - while (stackDelta < 0 && stack.length > 0) { - stack.pop(); - stackDelta++; + } + } else if (op === 0x2c && !tooComplexToFollowFunctions) { + // FDEF + if (inFDEF || inELSE) { + warn("TT: nested FDEFs not allowed"); + tooComplexToFollowFunctions = true; + } + inFDEF = true; + // collecting information about which functions are defined + lastDeff = i; + funcId = stack.pop(); + ttContext.functionsDefined[funcId] = { data, i }; + } else if (op === 0x2d) { + // ENDF - end of function + if (inFDEF) { + inFDEF = false; + lastEndf = i; + } else { + pc = callstack.pop(); + if (!pc) { + warn("TT: ENDF bad stack"); + ttContext.hintsValid = false; + return; } - while (stackDelta > 0) { - stack.push(NaN); // pushing any number into stack - stackDelta--; + funcId = functionsCalled.pop(); + data = pc.data; + i = pc.i; + ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop; + } + } else if (op === 0x89) { + // IDEF - instruction definition + if (inFDEF || inELSE) { + warn("TT: nested IDEFs not allowed"); + tooComplexToFollowFunctions = true; + } + inFDEF = true; + // recording it as a function to track ENDF + lastDeff = i; + } else if (op === 0x58) { + // IF + ++ifLevel; + } else if (op === 0x1b) { + // ELSE + inELSE = ifLevel; + } else if (op === 0x59) { + // EIF + if (inELSE === ifLevel) { + inELSE = 0; + } + --ifLevel; + } else if (op === 0x1c) { + // JMPR + if (!inFDEF && !inELSE) { + const offset = stack[stack.length - 1]; + // only jumping forward to prevent infinite loop + if (offset > 0) { + i += offset - 1; } } } - ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; - const content = [data]; - if (i > data.length) { - content.push(new Uint8Array(i - data.length)); + // Adjusting stack not extactly, but just enough to get function id + if (!inFDEF && !inELSE) { + let stackDelta = 0; + if (op <= 0x8e) { + stackDelta = TTOpsStackDeltas[op]; + } else if (op >= 0xc0 && op <= 0xdf) { + stackDelta = -1; + } else if (op >= 0xe0) { + stackDelta = -2; + } + if (op >= 0x71 && op <= 0x75) { + n = stack.pop(); + if (!isNaN(n)) { + stackDelta = -n * 2; + } + } + while (stackDelta < 0 && stack.length > 0) { + stack.pop(); + stackDelta++; + } + while (stackDelta > 0) { + stack.push(NaN); // pushing any number into stack + stackDelta--; + } } - if (lastDeff > lastEndf) { - warn("TT: complementing a missing function tail"); - // new function definition started, but not finished - // complete function by [CLEAR, ENDF] - content.push(new Uint8Array([0x22, 0x2d])); - } - foldTTTable(table, content); } + ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; + const content = [data]; + if (i > data.length) { + content.push(new Uint8Array(i - data.length)); + } + if (lastDeff > lastEndf) { + warn("TT: complementing a missing function tail"); + // new function definition started, but not finished + // complete function by [CLEAR, ENDF] + content.push(new Uint8Array([0x22, 0x2d])); + } + foldTTTable(table, content); + } - function checkInvalidFunctions(ttContext, maxFunctionDefs) { - if (ttContext.tooComplexToFollowFunctions) { - return; - } - if (ttContext.functionsDefined.length > maxFunctionDefs) { - warn("TT: more functions defined than expected"); + function checkInvalidFunctions(ttContext, maxFunctionDefs) { + if (ttContext.tooComplexToFollowFunctions) { + return; + } + if (ttContext.functionsDefined.length > maxFunctionDefs) { + warn("TT: more functions defined than expected"); + ttContext.hintsValid = false; + return; + } + for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { + if (j > maxFunctionDefs) { + warn("TT: invalid function id: " + j); ttContext.hintsValid = false; return; } - for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { - if (j > maxFunctionDefs) { - warn("TT: invalid function id: " + j); - ttContext.hintsValid = false; - return; - } - if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) { - warn("TT: undefined function: " + j); - ttContext.hintsValid = false; - return; - } + if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) { + warn("TT: undefined function: " + j); + ttContext.hintsValid = false; + return; } } + } - function foldTTTable(table, content) { - if (content.length > 1) { - // concatenating the content items - let newLength = 0; - let j, jj; - for (j = 0, jj = content.length; j < jj; j++) { - newLength += content[j].length; - } - newLength = (newLength + 3) & ~3; - const result = new Uint8Array(newLength); - let pos = 0; - for (j = 0, jj = content.length; j < jj; j++) { - result.set(content[j], pos); - pos += content[j].length; - } - table.data = result; - table.length = newLength; + function foldTTTable(table, content) { + if (content.length > 1) { + // concatenating the content items + let newLength = 0; + let j, jj; + for (j = 0, jj = content.length; j < jj; j++) { + newLength += content[j].length; } + newLength = (newLength + 3) & ~3; + const result = new Uint8Array(newLength); + let pos = 0; + for (j = 0, jj = content.length; j < jj; j++) { + result.set(content[j], pos); + pos += content[j].length; + } + table.data = result; + table.length = newLength; + } + } + + function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) { + const ttContext = { + functionsDefined: [], + functionsUsed: [], + functionsStackDeltas: [], + tooComplexToFollowFunctions: false, + hintsValid: true, + }; + if (fpgm) { + sanitizeTTProgram(fpgm, ttContext); + } + if (prep) { + sanitizeTTProgram(prep, ttContext); + } + if (fpgm) { + checkInvalidFunctions(ttContext, maxFunctionDefs); + } + if (cvt && cvt.length & 1) { + const cvtData = new Uint8Array(cvt.length + 1); + cvtData.set(cvt.data); + cvt.data = cvtData; + } + return ttContext.hintsValid; + } + + // The following steps modify the original font data, making copy + font = new Stream(new Uint8Array(font.getBytes())); + + let header, tables; + if (isTrueTypeCollectionFile(font)) { + const ttcData = readTrueTypeCollectionData(font, this.name); + header = ttcData.header; + tables = ttcData.tables; + } else { + header = readOpenTypeHeader(font); + tables = readTables(font, header.numTables); + } + let cff, cffFile; + + const isTrueType = !tables["CFF "]; + if (!isTrueType) { + const isComposite = + properties.composite && + ((properties.cidToGidMap || []).length > 0 || + !(properties.cMap instanceof IdentityCMap)); + // OpenType font (skip composite fonts with non-default glyph mapping). + if ( + (header.version === "OTTO" && !isComposite) || + !tables.head || + !tables.hhea || + !tables.maxp || + !tables.post + ) { + // No major tables: throwing everything at `CFFFont`. + cffFile = new Stream(tables["CFF "].data); + cff = new CFFFont(cffFile, properties); + + adjustWidths(properties); + + return this.convert(name, cff, properties); } - function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) { - const ttContext = { - functionsDefined: [], - functionsUsed: [], - functionsStackDeltas: [], - tooComplexToFollowFunctions: false, - hintsValid: true, + delete tables.glyf; + delete tables.loca; + delete tables.fpgm; + delete tables.prep; + delete tables["cvt "]; + this.isOpenType = true; + } else { + if (!tables.loca) { + throw new FormatError('Required "loca" table is not found'); + } + if (!tables.glyf) { + warn('Required "glyf" table is not found -- trying to recover.'); + // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below. + tables.glyf = { + tag: "glyf", + data: new Uint8Array(0), }; - if (fpgm) { - sanitizeTTProgram(fpgm, ttContext); - } - if (prep) { - sanitizeTTProgram(prep, ttContext); - } - if (fpgm) { - checkInvalidFunctions(ttContext, maxFunctionDefs); - } - if (cvt && cvt.length & 1) { - const cvtData = new Uint8Array(cvt.length + 1); - cvtData.set(cvt.data); - cvt.data = cvtData; - } - return ttContext.hintsValid; } + this.isOpenType = false; + } - // The following steps modify the original font data, making copy - font = new Stream(new Uint8Array(font.getBytes())); + if (!tables.maxp) { + throw new FormatError('Required "maxp" table is not found'); + } - let header, tables; - if (isTrueTypeCollectionFile(font)) { - const ttcData = readTrueTypeCollectionData(font, this.name); - header = ttcData.header; - tables = ttcData.tables; - } else { - header = readOpenTypeHeader(font); - tables = readTables(font, header.numTables); + font.pos = (font.start || 0) + tables.maxp.offset; + const version = font.getInt32(); + const numGlyphs = font.getUint16(); + // Glyph 0 is duplicated and appended. + let numGlyphsOut = numGlyphs + 1; + let dupFirstEntry = true; + if (numGlyphsOut > 0xffff) { + dupFirstEntry = false; + numGlyphsOut = numGlyphs; + warn("Not enough space in glyfs to duplicate first glyph."); + } + let maxFunctionDefs = 0; + let maxSizeOfInstructions = 0; + if (version >= 0x00010000 && tables.maxp.length >= 22) { + // maxZones can be invalid + font.pos += 8; + const maxZones = font.getUint16(); + if (maxZones > 2) { + // reset to 2 if font has invalid maxZones + tables.maxp.data[14] = 0; + tables.maxp.data[15] = 2; } - let cff, cffFile; + font.pos += 4; + maxFunctionDefs = font.getUint16(); + font.pos += 4; + maxSizeOfInstructions = font.getUint16(); + } - const isTrueType = !tables["CFF "]; - if (!isTrueType) { - const isComposite = - properties.composite && - ((properties.cidToGidMap || []).length > 0 || - !(properties.cMap instanceof IdentityCMap)); - // OpenType font (skip composite fonts with non-default glyph mapping). - if ( - (header.version === "OTTO" && !isComposite) || - !tables.head || - !tables.hhea || - !tables.maxp || - !tables.post - ) { - // No major tables: throwing everything at `CFFFont`. - cffFile = new Stream(tables["CFF "].data); - cff = new CFFFont(cffFile, properties); + tables.maxp.data[4] = numGlyphsOut >> 8; + tables.maxp.data[5] = numGlyphsOut & 255; - adjustWidths(properties); + const hintsValid = sanitizeTTPrograms( + tables.fpgm, + tables.prep, + tables["cvt "], + maxFunctionDefs + ); + if (!hintsValid) { + delete tables.fpgm; + delete tables.prep; + delete tables["cvt "]; + } - return this.convert(name, cff, properties); - } + // Ensure the hmtx table contains the advance width and + // sidebearings information for numGlyphs in the maxp table + sanitizeMetrics( + font, + tables.hhea, + tables.hmtx, + numGlyphsOut, + dupFirstEntry + ); - delete tables.glyf; - delete tables.loca; - delete tables.fpgm; - delete tables.prep; - delete tables["cvt "]; - this.isOpenType = true; - } else { - if (!tables.loca) { - throw new FormatError('Required "loca" table is not found'); - } - if (!tables.glyf) { - warn('Required "glyf" table is not found -- trying to recover.'); - // Note: We use `sanitizeGlyphLocations` to add dummy glyf data below. - tables.glyf = { - tag: "glyf", - data: new Uint8Array(0), - }; - } - this.isOpenType = false; - } + if (!tables.head) { + throw new FormatError('Required "head" table is not found'); + } - if (!tables.maxp) { - throw new FormatError('Required "maxp" table is not found'); - } + sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); - font.pos = (font.start || 0) + tables.maxp.offset; - const version = font.getInt32(); - const numGlyphs = font.getUint16(); - // Glyph 0 is duplicated and appended. - let numGlyphsOut = numGlyphs + 1; - let dupFirstEntry = true; - if (numGlyphsOut > 0xffff) { - dupFirstEntry = false; - numGlyphsOut = numGlyphs; - warn("Not enough space in glyfs to duplicate first glyph."); - } - let maxFunctionDefs = 0; - let maxSizeOfInstructions = 0; + let missingGlyphs = Object.create(null); + if (isTrueType) { + const isGlyphLocationsLong = int16( + tables.head.data[50], + tables.head.data[51] + ); + const glyphsInfo = sanitizeGlyphLocations( + tables.loca, + tables.glyf, + numGlyphs, + isGlyphLocationsLong, + hintsValid, + dupFirstEntry, + maxSizeOfInstructions + ); + missingGlyphs = glyphsInfo.missingGlyphs; + + // Some fonts have incorrect maxSizeOfInstructions values, so we use + // the computed value instead. if (version >= 0x00010000 && tables.maxp.length >= 22) { - // maxZones can be invalid - font.pos += 8; - const maxZones = font.getUint16(); - if (maxZones > 2) { - // reset to 2 if font has invalid maxZones - tables.maxp.data[14] = 0; - tables.maxp.data[15] = 2; + tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8; + tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255; + } + } + if (!tables.hhea) { + throw new FormatError('Required "hhea" table is not found'); + } + + // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth + // Sometimes it's 0. That needs to be fixed + if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) { + tables.hhea.data[10] = 0xff; + tables.hhea.data[11] = 0xff; + } + + // Extract some more font properties from the OpenType head and + // hhea tables; yMin and descent value are always negative. + const metricsOverride = { + unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), + yMax: int16(tables.head.data[42], tables.head.data[43]), + yMin: signedInt16(tables.head.data[38], tables.head.data[39]), + ascent: int16(tables.hhea.data[4], tables.hhea.data[5]), + descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]), + }; + + // PDF FontDescriptor metrics lie -- using data from actual font. + this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm; + this.descent = metricsOverride.descent / metricsOverride.unitsPerEm; + + // The 'post' table has glyphs names. + if (tables.post) { + readPostScriptTable(tables.post, properties, numGlyphs); + } + + // The original 'post' table is not needed, replace it. + tables.post = { + tag: "post", + data: createPostTable(properties), + }; + + const charCodeToGlyphId = []; + + // Helper function to try to skip mapping of empty glyphs. + function hasGlyph(glyphId) { + return !missingGlyphs[glyphId]; + } + + if (properties.composite) { + const cidToGidMap = properties.cidToGidMap || []; + const isCidToGidMapEmpty = cidToGidMap.length === 0; + + properties.cMap.forEach(function (charCode, cid) { + if (cid > 0xffff) { + throw new FormatError("Max size of CID is 65,535"); + } + let glyphId = -1; + if (isCidToGidMapEmpty) { + glyphId = cid; + } else if (cidToGidMap[cid] !== undefined) { + glyphId = cidToGidMap[cid]; } - font.pos += 4; - maxFunctionDefs = font.getUint16(); - font.pos += 4; - maxSizeOfInstructions = font.getUint16(); - } - tables.maxp.data[4] = numGlyphsOut >> 8; - tables.maxp.data[5] = numGlyphsOut & 255; - - const hintsValid = sanitizeTTPrograms( - tables.fpgm, - tables.prep, - tables["cvt "], - maxFunctionDefs - ); - if (!hintsValid) { - delete tables.fpgm; - delete tables.prep; - delete tables["cvt "]; - } - - // Ensure the hmtx table contains the advance width and - // sidebearings information for numGlyphs in the maxp table - sanitizeMetrics( + if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) { + charCodeToGlyphId[charCode] = glyphId; + } + }); + } else { + // Most of the following logic in this code branch is based on the + // 9.6.6.4 of the PDF spec. + const cmapTable = readCmapTable( + tables.cmap, font, - tables.hhea, - tables.hmtx, - numGlyphsOut, - dupFirstEntry + this.isSymbolicFont, + properties.hasEncoding ); - - if (!tables.head) { - throw new FormatError('Required "head" table is not found'); + const cmapPlatformId = cmapTable.platformId; + const cmapEncodingId = cmapTable.encodingId; + const cmapMappings = cmapTable.mappings; + const cmapMappingsLength = cmapMappings.length; + let baseEncoding = []; + if ( + properties.hasEncoding && + (properties.baseEncodingName === "MacRomanEncoding" || + properties.baseEncodingName === "WinAnsiEncoding") + ) { + baseEncoding = getEncoding(properties.baseEncodingName); } - sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); - - let missingGlyphs = Object.create(null); - if (isTrueType) { - const isGlyphLocationsLong = int16( - tables.head.data[50], - tables.head.data[51] - ); - const glyphsInfo = sanitizeGlyphLocations( - tables.loca, - tables.glyf, - numGlyphs, - isGlyphLocationsLong, - hintsValid, - dupFirstEntry, - maxSizeOfInstructions - ); - missingGlyphs = glyphsInfo.missingGlyphs; - - // Some fonts have incorrect maxSizeOfInstructions values, so we use - // the computed value instead. - if (version >= 0x00010000 && tables.maxp.length >= 22) { - tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8; - tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255; - } - } - if (!tables.hhea) { - throw new FormatError('Required "hhea" table is not found'); - } - - // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth - // Sometimes it's 0. That needs to be fixed - if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) { - tables.hhea.data[10] = 0xff; - tables.hhea.data[11] = 0xff; - } - - // Extract some more font properties from the OpenType head and - // hhea tables; yMin and descent value are always negative. - const metricsOverride = { - unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), - yMax: int16(tables.head.data[42], tables.head.data[43]), - yMin: signedInt16(tables.head.data[38], tables.head.data[39]), - ascent: int16(tables.hhea.data[4], tables.hhea.data[5]), - descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]), - }; - - // PDF FontDescriptor metrics lie -- using data from actual font. - this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm; - this.descent = metricsOverride.descent / metricsOverride.unitsPerEm; - - // The 'post' table has glyphs names. - if (tables.post) { - readPostScriptTable(tables.post, properties, numGlyphs); - } - - // The original 'post' table is not needed, replace it. - tables.post = { - tag: "post", - data: createPostTable(properties), - }; - - const charCodeToGlyphId = []; - - // Helper function to try to skip mapping of empty glyphs. - function hasGlyph(glyphId) { - return !missingGlyphs[glyphId]; - } - - if (properties.composite) { - const cidToGidMap = properties.cidToGidMap || []; - const isCidToGidMapEmpty = cidToGidMap.length === 0; - - properties.cMap.forEach(function (charCode, cid) { - if (cid > 0xffff) { - throw new FormatError("Max size of CID is 65,535"); + // If the font has an encoding and is not symbolic then follow the + // rules in section 9.6.6.4 of the spec on how to map 3,1 and 1,0 + // cmaps. + if ( + properties.hasEncoding && + !this.isSymbolicFont && + ((cmapPlatformId === 3 && cmapEncodingId === 1) || + (cmapPlatformId === 1 && cmapEncodingId === 0)) + ) { + const glyphsUnicodeMap = getGlyphsUnicode(); + for (let charCode = 0; charCode < 256; charCode++) { + let glyphName; + if (this.differences && charCode in this.differences) { + glyphName = this.differences[charCode]; + } else if ( + charCode in baseEncoding && + baseEncoding[charCode] !== "" + ) { + glyphName = baseEncoding[charCode]; + } else { + glyphName = StandardEncoding[charCode]; } - let glyphId = -1; - if (isCidToGidMapEmpty) { - glyphId = cid; - } else if (cidToGidMap[cid] !== undefined) { - glyphId = cidToGidMap[cid]; + if (!glyphName) { + continue; + } + // Ensure that non-standard glyph names are resolved to valid ones. + const standardGlyphName = recoverGlyphName( + glyphName, + glyphsUnicodeMap + ); + + let unicodeOrCharCode; + if (cmapPlatformId === 3 && cmapEncodingId === 1) { + unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName]; + } else if (cmapPlatformId === 1 && cmapEncodingId === 0) { + // TODO: the encoding needs to be updated with mac os table. + unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName); } - if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) { - charCodeToGlyphId[charCode] = glyphId; - } - }); - } else { - // Most of the following logic in this code branch is based on the - // 9.6.6.4 of the PDF spec. - const cmapTable = readCmapTable( - tables.cmap, - font, - this.isSymbolicFont, - properties.hasEncoding - ); - const cmapPlatformId = cmapTable.platformId; - const cmapEncodingId = cmapTable.encodingId; - const cmapMappings = cmapTable.mappings; - const cmapMappingsLength = cmapMappings.length; - let baseEncoding = []; - if ( - properties.hasEncoding && - (properties.baseEncodingName === "MacRomanEncoding" || - properties.baseEncodingName === "WinAnsiEncoding") - ) { - baseEncoding = getEncoding(properties.baseEncodingName); - } - - // If the font has an encoding and is not symbolic then follow the - // rules in section 9.6.6.4 of the spec on how to map 3,1 and 1,0 - // cmaps. - if ( - properties.hasEncoding && - !this.isSymbolicFont && - ((cmapPlatformId === 3 && cmapEncodingId === 1) || - (cmapPlatformId === 1 && cmapEncodingId === 0)) - ) { - const glyphsUnicodeMap = getGlyphsUnicode(); - for (let charCode = 0; charCode < 256; charCode++) { - let glyphName; - if (this.differences && charCode in this.differences) { - glyphName = this.differences[charCode]; - } else if ( - charCode in baseEncoding && - baseEncoding[charCode] !== "" - ) { - glyphName = baseEncoding[charCode]; - } else { - glyphName = StandardEncoding[charCode]; - } - if (!glyphName) { + for (let i = 0; i < cmapMappingsLength; ++i) { + if (cmapMappings[i].charCode !== unicodeOrCharCode) { continue; } - // Ensure that non-standard glyph names are resolved to valid ones. - const standardGlyphName = recoverGlyphName( - glyphName, - glyphsUnicodeMap - ); - - let unicodeOrCharCode; - if (cmapPlatformId === 3 && cmapEncodingId === 1) { - unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName]; - } else if (cmapPlatformId === 1 && cmapEncodingId === 0) { - // TODO: the encoding needs to be updated with mac os table. - unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName); - } - - for (let i = 0; i < cmapMappingsLength; ++i) { - if (cmapMappings[i].charCode !== unicodeOrCharCode) { - continue; - } - charCodeToGlyphId[charCode] = cmapMappings[i].glyphId; - break; - } - } - } else if (cmapPlatformId === 0) { - // Default Unicode semantics, use the charcodes as is. - for (let i = 0; i < cmapMappingsLength; ++i) { - charCodeToGlyphId[cmapMappings[i].charCode] = - cmapMappings[i].glyphId; - } - } else { - // When there is only a (1, 0) cmap table, the char code is a single - // byte and it is used directly as the char code. - - // When a (3, 0) cmap table is present, it is used instead but the - // spec has special rules for char codes in the range of 0xF000 to - // 0xF0FF and it says the (3, 0) table should map the values from - // the (1, 0) table by prepending 0xF0 to the char codes. To reverse - // this, the upper bits of the char code are cleared, but only for the - // 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 (let i = 0; i < cmapMappingsLength; ++i) { - let charCode = cmapMappings[i].charCode; - if ( - cmapPlatformId === 3 && - charCode >= 0xf000 && - charCode <= 0xf0ff - ) { - charCode &= 0xff; - } charCodeToGlyphId[charCode] = cmapMappings[i].glyphId; + break; } } - - // Last, try to map any missing charcodes using the post table. - if ( - properties.glyphNames && - (baseEncoding.length || this.differences.length) - ) { - for (let i = 0; i < 256; ++i) { - if (charCodeToGlyphId[i] !== undefined) { - continue; - } - const glyphName = this.differences[i] || baseEncoding[i]; - if (!glyphName) { - continue; - } - const glyphId = properties.glyphNames.indexOf(glyphName); - if (glyphId > 0 && hasGlyph(glyphId)) { - charCodeToGlyphId[i] = glyphId; - } - } + } else if (cmapPlatformId === 0) { + // Default Unicode semantics, use the charcodes as is. + for (let i = 0; i < cmapMappingsLength; ++i) { + charCodeToGlyphId[cmapMappings[i].charCode] = cmapMappings[i].glyphId; } - } - - if (charCodeToGlyphId.length === 0) { - // defines at least one glyph - charCodeToGlyphId[0] = 0; - } - - // Typically glyph 0 is duplicated and the mapping must be updated, but if - // there isn't enough room to duplicate, the glyph id is left the same. In - // this case, glyph 0 may not work correctly, but that is better than - // having the whole font fail. - let glyphZeroId = numGlyphsOut - 1; - if (!dupFirstEntry) { - glyphZeroId = 0; - } - - // When `cssFontInfo` is set, the font is used to render text in the HTML - // view (e.g. with Xfa) so nothing must be moved in the private use area. - if (!properties.cssFontInfo) { - // Converting glyphs and ids into font's cmap table - const newMapping = adjustMapping( - charCodeToGlyphId, - hasGlyph, - glyphZeroId - ); - this.toFontChar = newMapping.toFontChar; - tables.cmap = { - tag: "cmap", - data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut), - }; - - if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { - tables["OS/2"] = { - tag: "OS/2", - data: createOS2Table( - properties, - newMapping.charCodeToGlyphId, - metricsOverride - ), - }; - } - } - - if (!isTrueType) { - try { - // Trying to repair CFF file - cffFile = new Stream(tables["CFF "].data); - const parser = new CFFParser( - cffFile, - properties, - SEAC_ANALYSIS_ENABLED - ); - cff = parser.parse(); - cff.duplicateFirstGlyph(); - const compiler = new CFFCompiler(cff); - tables["CFF "].data = compiler.compile(); - } catch (e) { - warn("Failed to compile font " + properties.loadedName); - } - } - - // Re-creating 'name' table - if (!tables.name) { - tables.name = { - tag: "name", - data: createNameTable(this.name), - }; } else { - // ... using existing 'name' table as prototype - const namePrototype = readNameTable(tables.name); - tables.name.data = createNameTable(name, namePrototype); + // When there is only a (1, 0) cmap table, the char code is a single + // byte and it is used directly as the char code. + + // When a (3, 0) cmap table is present, it is used instead but the + // spec has special rules for char codes in the range of 0xF000 to + // 0xF0FF and it says the (3, 0) table should map the values from + // the (1, 0) table by prepending 0xF0 to the char codes. To reverse + // this, the upper bits of the char code are cleared, but only for the + // 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 (let i = 0; i < cmapMappingsLength; ++i) { + let charCode = cmapMappings[i].charCode; + if ( + cmapPlatformId === 3 && + charCode >= 0xf000 && + charCode <= 0xf0ff + ) { + charCode &= 0xff; + } + charCodeToGlyphId[charCode] = cmapMappings[i].glyphId; + } } - const builder = new OpenTypeFileBuilder(header.version); - for (const tableTag in tables) { - builder.addTable(tableTag, tables[tableTag].data); + // Last, try to map any missing charcodes using the post table. + if ( + properties.glyphNames && + (baseEncoding.length || this.differences.length) + ) { + for (let i = 0; i < 256; ++i) { + if (charCodeToGlyphId[i] !== undefined) { + continue; + } + const glyphName = this.differences[i] || baseEncoding[i]; + if (!glyphName) { + continue; + } + const glyphId = properties.glyphNames.indexOf(glyphName); + if (glyphId > 0 && hasGlyph(glyphId)) { + charCodeToGlyphId[i] = glyphId; + } + } } - return builder.toArray(); - }, + } - convert: function Font_convert(fontName, font, properties) { - // TODO: Check the charstring widths to determine this. - properties.fixedPitch = false; + if (charCodeToGlyphId.length === 0) { + // defines at least one glyph + charCodeToGlyphId[0] = 0; + } - if (properties.builtInEncoding) { - // For Type1 fonts that do not include either `ToUnicode` or `Encoding` - // data, attempt to use the `builtInEncoding` to improve text selection. - adjustToUnicode(properties, properties.builtInEncoding); - } + // Typically glyph 0 is duplicated and the mapping must be updated, but if + // there isn't enough room to duplicate, the glyph id is left the same. In + // this case, glyph 0 may not work correctly, but that is better than + // having the whole font fail. + let glyphZeroId = numGlyphsOut - 1; + if (!dupFirstEntry) { + glyphZeroId = 0; + } - // Type 1 fonts have a notdef inserted at the beginning, so glyph 0 - // becomes glyph 1. In a CFF font glyph 0 is appended to the end of the - // char strings. - let glyphZeroId = 1; - if (font instanceof CFFFont) { - glyphZeroId = font.numGlyphs - 1; - } - const mapping = font.getGlyphMapping(properties); + // When `cssFontInfo` is set, the font is used to render text in the HTML + // view (e.g. with Xfa) so nothing must be moved in the private use area. + if (!properties.cssFontInfo) { + // Converting glyphs and ids into font's cmap table const newMapping = adjustMapping( - mapping, - font.hasGlyphId.bind(font), + charCodeToGlyphId, + hasGlyph, glyphZeroId ); this.toFontChar = newMapping.toFontChar; - const numGlyphs = font.numGlyphs; + tables.cmap = { + tag: "cmap", + data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut), + }; - function getCharCodes(charCodeToGlyphId, glyphId) { - let charCodes = null; - for (const charCode in charCodeToGlyphId) { - if (glyphId === charCodeToGlyphId[charCode]) { - if (!charCodes) { - charCodes = []; - } - charCodes.push(charCode | 0); - } - } - return charCodes; - } - - function createCharCode(charCodeToGlyphId, glyphId) { - for (const charCode in charCodeToGlyphId) { - if (glyphId === charCodeToGlyphId[charCode]) { - return charCode | 0; - } - } - newMapping.charCodeToGlyphId[ - newMapping.nextAvailableFontCharCode - ] = glyphId; - return newMapping.nextAvailableFontCharCode++; - } - - const seacs = font.seacs; - if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) { - const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; - const charset = font.getCharset(); - const seacMap = Object.create(null); - for (let glyphId in seacs) { - glyphId |= 0; - const seac = seacs[glyphId]; - const baseGlyphName = StandardEncoding[seac[2]]; - const accentGlyphName = StandardEncoding[seac[3]]; - const baseGlyphId = charset.indexOf(baseGlyphName); - const accentGlyphId = charset.indexOf(accentGlyphName); - if (baseGlyphId < 0 || accentGlyphId < 0) { - continue; - } - const accentOffset = { - x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], - y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5], - }; - - const charCodes = getCharCodes(mapping, glyphId); - if (!charCodes) { - // There's no point in mapping it if the char code was never mapped - // to begin with. - continue; - } - for (let i = 0, ii = charCodes.length; i < ii; i++) { - const charCode = charCodes[i]; - // Find a fontCharCode that maps to the base and accent glyphs. - // If one doesn't exists, create it. - const charCodeToGlyphId = newMapping.charCodeToGlyphId; - const baseFontCharCode = createCharCode( - charCodeToGlyphId, - baseGlyphId - ); - const accentFontCharCode = createCharCode( - charCodeToGlyphId, - accentGlyphId - ); - seacMap[charCode] = { - baseFontCharCode, - accentFontCharCode, - accentOffset, - }; - } - } - properties.seacMap = seacMap; - } - - const unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; - - const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); - // PostScript Font Program - builder.addTable("CFF ", font.data); - // OS/2 and Windows Specific metrics - builder.addTable( - "OS/2", - createOS2Table(properties, newMapping.charCodeToGlyphId) - ); - // Character to glyphs mapping - builder.addTable( - "cmap", - createCmapTable(newMapping.charCodeToGlyphId, numGlyphs) - ); - // Font header - builder.addTable( - "head", - "\x00\x01\x00\x00" + // Version number - "\x00\x00\x10\x00" + // fontRevision - "\x00\x00\x00\x00" + // checksumAdjustement - "\x5F\x0F\x3C\xF5" + // magicNumber - "\x00\x00" + // Flags - safeString16(unitsPerEm) + // unitsPerEM - "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date - "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date - "\x00\x00" + // xMin - safeString16(properties.descent) + // yMin - "\x0F\xFF" + // xMax - safeString16(properties.ascent) + // yMax - string16(properties.italicAngle ? 2 : 0) + // macStyle - "\x00\x11" + // lowestRecPPEM - "\x00\x00" + // fontDirectionHint - "\x00\x00" + // indexToLocFormat - "\x00\x00" - ); // glyphDataFormat - - // Horizontal header - builder.addTable( - "hhea", - "\x00\x01\x00\x00" + // Version number - safeString16(properties.ascent) + // Typographic Ascent - safeString16(properties.descent) + // Typographic Descent - "\x00\x00" + // Line Gap - "\xFF\xFF" + // advanceWidthMax - "\x00\x00" + // minLeftSidebearing - "\x00\x00" + // minRightSidebearing - "\x00\x00" + // xMaxExtent - safeString16(properties.capHeight) + // caretSlopeRise - safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun - "\x00\x00" + // caretOffset - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // metricDataFormat - string16(numGlyphs) - ); // Number of HMetrics - - // Horizontal metrics - builder.addTable( - "hmtx", - (function fontFieldsHmtx() { - const charstrings = font.charstrings; - const cffWidths = font.cff ? font.cff.widths : null; - let hmtx = "\x00\x00\x00\x00"; // Fake .notdef - for (let i = 1, ii = numGlyphs; i < ii; i++) { - let width = 0; - if (charstrings) { - const charstring = charstrings[i - 1]; - width = "width" in charstring ? charstring.width : 0; - } else if (cffWidths) { - width = Math.ceil(cffWidths[i] || 0); - } - hmtx += string16(width) + string16(0); - } - return hmtx; - })() - ); - - // Maximum profile - builder.addTable( - "maxp", - "\x00\x00\x50\x00" + string16(numGlyphs) // Version number - ); // Num of glyphs - - // Naming tables - builder.addTable("name", createNameTable(fontName)); - - // PostScript information - builder.addTable("post", createPostTable(properties)); - - return builder.toArray(); - }, - - get spaceWidth() { - // trying to estimate space character width - const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; - let width; - for (let i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) { - const glyphName = possibleSpaceReplacements[i]; - // if possible, getting width by glyph name - if (glyphName in this.widths) { - width = this.widths[glyphName]; - break; - } - const glyphsUnicodeMap = getGlyphsUnicode(); - const glyphUnicode = glyphsUnicodeMap[glyphName]; - // finding the charcode via unicodeToCID map - let charcode = 0; - if (this.composite && this.cMap.contains(glyphUnicode)) { - charcode = this.cMap.lookup(glyphUnicode); - } - // ... via toUnicode map - if (!charcode && this.toUnicode) { - charcode = this.toUnicode.charCodeOf(glyphUnicode); - } - // setting it to unicode if negative or undefined - if (charcode <= 0) { - charcode = glyphUnicode; - } - // trying to get width via charcode - width = this.widths[charcode]; - if (width) { - break; // the non-zero width found - } - } - width = width || this.defaultWidth; - return shadow(this, "spaceWidth", width); - }, - - /** - * @private - */ - _charToGlyph(charcode, isSpace = false) { - let fontCharCode, width, operatorListId; - - let widthCode = charcode; - if (this.cMap && this.cMap.contains(charcode)) { - widthCode = this.cMap.lookup(charcode); - } - width = this.widths[widthCode]; - width = isNum(width) ? width : this.defaultWidth; - const vmetric = this.vmetrics && this.vmetrics[widthCode]; - - let unicode = - this.toUnicode.get(charcode) || - this.fallbackToUnicode.get(charcode) || - charcode; - if (typeof unicode === "number") { - unicode = String.fromCharCode(unicode); - } - - let isInFont = charcode in this.toFontChar; - // First try the toFontChar map, if it's not there then try falling - // back to the char code. - fontCharCode = this.toFontChar[charcode] || charcode; - if (this.missingFile) { - const glyphName = - this.differences[charcode] || this.defaultEncoding[charcode]; - if ( - (glyphName === ".notdef" || glyphName === "") && - this.type === "Type1" - ) { - // .notdef glyphs should be invisible in non-embedded Type1 fonts, so - // replace them with spaces. - fontCharCode = 0x20; - } - fontCharCode = mapSpecialUnicodeValues(fontCharCode); - } - - if (this.isType3Font) { - // Font char code in this case is actually a glyph name. - operatorListId = fontCharCode; - } - - let accent = null; - if (this.seacMap && this.seacMap[charcode]) { - isInFont = true; - const seac = this.seacMap[charcode]; - fontCharCode = seac.baseFontCharCode; - accent = { - fontChar: String.fromCodePoint(seac.accentFontCharCode), - offset: seac.accentOffset, + if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { + tables["OS/2"] = { + tag: "OS/2", + data: createOS2Table( + properties, + newMapping.charCodeToGlyphId, + metricsOverride + ), }; } + } - let fontChar = ""; - if (typeof fontCharCode === "number") { - if (fontCharCode <= 0x10ffff) { - fontChar = String.fromCodePoint(fontCharCode); - } else { - warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`); - } - } - - let glyph = this.glyphCache[charcode]; - if ( - !glyph || - !glyph.matchesForCache( - fontChar, - unicode, - accent, - width, - vmetric, - operatorListId, - isSpace, - isInFont - ) - ) { - glyph = new Glyph( - fontChar, - unicode, - accent, - width, - vmetric, - operatorListId, - isSpace, - isInFont + if (!isTrueType) { + try { + // Trying to repair CFF file + cffFile = new Stream(tables["CFF "].data); + const parser = new CFFParser( + cffFile, + properties, + SEAC_ANALYSIS_ENABLED ); - this.glyphCache[charcode] = glyph; + cff = parser.parse(); + cff.duplicateFirstGlyph(); + const compiler = new CFFCompiler(cff); + tables["CFF "].data = compiler.compile(); + } catch (e) { + warn("Failed to compile font " + properties.loadedName); } - return glyph; - }, + } - charsToGlyphs: function Font_charsToGlyphs(chars) { - let charsCache = this.charsCache; - let glyphs, glyph, charcode; + // Re-creating 'name' table + if (!tables.name) { + tables.name = { + tag: "name", + data: createNameTable(this.name), + }; + } else { + // ... using existing 'name' table as prototype + const namePrototype = readNameTable(tables.name); + tables.name.data = createNameTable(name, namePrototype); + } - // if we translated this string before, just grab it from the cache - if (charsCache) { - glyphs = charsCache[chars]; - if (glyphs) { - return glyphs; - } - } + const builder = new OpenTypeFileBuilder(header.version); + for (const tableTag in tables) { + builder.addTable(tableTag, tables[tableTag].data); + } + return builder.toArray(); + } - // lazily create the translation cache - if (!charsCache) { - charsCache = this.charsCache = Object.create(null); - } + convert(fontName, font, properties) { + // TODO: Check the charstring widths to determine this. + properties.fixedPitch = false; - glyphs = []; - const charsCacheKey = chars; - let i = 0, - ii; + if (properties.builtInEncoding) { + // For Type1 fonts that do not include either `ToUnicode` or `Encoding` + // data, attempt to use the `builtInEncoding` to improve text selection. + adjustToUnicode(properties, properties.builtInEncoding); + } - if (this.cMap) { - // composite fonts have multi-byte strings convert the string from - // single-byte to multi-byte - const c = Object.create(null); - while (i < chars.length) { - this.cMap.readCharCode(chars, i, c); - charcode = c.charcode; - const length = c.length; - i += length; - // Space is char with code 0x20 and length 1 in multiple-byte codes. - const isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20; - glyph = this._charToGlyph(charcode, isSpace); - glyphs.push(glyph); - } - } else { - for (i = 0, ii = chars.length; i < ii; ++i) { - charcode = chars.charCodeAt(i); - glyph = this._charToGlyph(charcode, charcode === 0x20); - glyphs.push(glyph); - } - } + // Type 1 fonts have a notdef inserted at the beginning, so glyph 0 + // becomes glyph 1. In a CFF font glyph 0 is appended to the end of the + // char strings. + let glyphZeroId = 1; + if (font instanceof CFFFont) { + glyphZeroId = font.numGlyphs - 1; + } + const mapping = font.getGlyphMapping(properties); + const newMapping = adjustMapping( + mapping, + font.hasGlyphId.bind(font), + glyphZeroId + ); + this.toFontChar = newMapping.toFontChar; + const numGlyphs = font.numGlyphs; - // Enter the translated string into the cache - return (charsCache[charsCacheKey] = glyphs); - }, - - /** - * Chars can have different sizes (depends on the encoding). - * @param {String} a string encoded with font encoding. - * @returns {Array>} the positions of each char in the string. - */ - getCharPositions(chars) { - // This function doesn't use a cache because - // it's called only when saving or printing. - const positions = []; - - if (this.cMap) { - const c = Object.create(null); - let i = 0; - while (i < chars.length) { - this.cMap.readCharCode(chars, i, c); - const length = c.length; - positions.push([i, i + length]); - i += length; - } - } else { - for (let i = 0, ii = chars.length; i < ii; ++i) { - positions.push([i, i + 1]); - } - } - - return positions; - }, - - get glyphCacheValues() { - return Object.values(this.glyphCache); - }, - - /** - * Encode a js string using font encoding. - * The resulting array contains an encoded string at even positions - * (can be empty) and a non-encoded one at odd positions. - * @param {String} a js string. - * @returns {Array} an array of encoded strings or non-encoded ones. - */ - encodeString(str) { - const buffers = []; - const currentBuf = []; - - // buffers will contain: encoded, non-encoded, encoded, ... - // currentBuf is pushed in buffers each time there is a change. - // So when buffers.length is odd then the last string is an encoded one - // and currentBuf contains non-encoded chars. - const hasCurrentBufErrors = () => buffers.length % 2 === 1; - - for (let i = 0, ii = str.length; i < ii; i++) { - const unicode = str.codePointAt(i); - if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) { - // unicode is represented by two uint16 - i++; - } - if (this.toUnicode) { - const char = String.fromCodePoint(unicode); - const charCode = this.toUnicode.charCodeOf(char); - if (charCode !== -1) { - if (hasCurrentBufErrors()) { - buffers.push(currentBuf.join("")); - currentBuf.length = 0; - } - const charCodeLength = this.cMap - ? this.cMap.getCharCodeLength(charCode) - : 1; - for (let j = charCodeLength - 1; j >= 0; j--) { - currentBuf.push( - String.fromCharCode((charCode >> (8 * j)) & 0xff) - ); - } - continue; + function getCharCodes(charCodeToGlyphId, glyphId) { + let charCodes = null; + for (const charCode in charCodeToGlyphId) { + if (glyphId === charCodeToGlyphId[charCode]) { + if (!charCodes) { + charCodes = []; } + charCodes.push(charCode | 0); } + } + return charCodes; + } - // unicode can't be encoded - if (!hasCurrentBufErrors()) { - buffers.push(currentBuf.join("")); - currentBuf.length = 0; + function createCharCode(charCodeToGlyphId, glyphId) { + for (const charCode in charCodeToGlyphId) { + if (glyphId === charCodeToGlyphId[charCode]) { + return charCode | 0; + } + } + newMapping.charCodeToGlyphId[ + newMapping.nextAvailableFontCharCode + ] = glyphId; + return newMapping.nextAvailableFontCharCode++; + } + + const seacs = font.seacs; + if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) { + const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX; + const charset = font.getCharset(); + const seacMap = Object.create(null); + for (let glyphId in seacs) { + glyphId |= 0; + const seac = seacs[glyphId]; + const baseGlyphName = StandardEncoding[seac[2]]; + const accentGlyphName = StandardEncoding[seac[3]]; + const baseGlyphId = charset.indexOf(baseGlyphName); + const accentGlyphId = charset.indexOf(accentGlyphName); + if (baseGlyphId < 0 || accentGlyphId < 0) { + continue; + } + const accentOffset = { + x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4], + y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5], + }; + + const charCodes = getCharCodes(mapping, glyphId); + if (!charCodes) { + // There's no point in mapping it if the char code was never mapped + // to begin with. + continue; + } + for (let i = 0, ii = charCodes.length; i < ii; i++) { + const charCode = charCodes[i]; + // Find a fontCharCode that maps to the base and accent glyphs. + // If one doesn't exists, create it. + const charCodeToGlyphId = newMapping.charCodeToGlyphId; + const baseFontCharCode = createCharCode( + charCodeToGlyphId, + baseGlyphId + ); + const accentFontCharCode = createCharCode( + charCodeToGlyphId, + accentGlyphId + ); + seacMap[charCode] = { + baseFontCharCode, + accentFontCharCode, + accentOffset, + }; + } + } + properties.seacMap = seacMap; + } + + const unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]; + + const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F"); + // PostScript Font Program + builder.addTable("CFF ", font.data); + // OS/2 and Windows Specific metrics + builder.addTable( + "OS/2", + createOS2Table(properties, newMapping.charCodeToGlyphId) + ); + // Character to glyphs mapping + builder.addTable( + "cmap", + createCmapTable(newMapping.charCodeToGlyphId, numGlyphs) + ); + // Font header + builder.addTable( + "head", + "\x00\x01\x00\x00" + // Version number + "\x00\x00\x10\x00" + // fontRevision + "\x00\x00\x00\x00" + // checksumAdjustement + "\x5F\x0F\x3C\xF5" + // magicNumber + "\x00\x00" + // Flags + safeString16(unitsPerEm) + // unitsPerEM + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date + "\x00\x00" + // xMin + safeString16(properties.descent) + // yMin + "\x0F\xFF" + // xMax + safeString16(properties.ascent) + // yMax + string16(properties.italicAngle ? 2 : 0) + // macStyle + "\x00\x11" + // lowestRecPPEM + "\x00\x00" + // fontDirectionHint + "\x00\x00" + // indexToLocFormat + "\x00\x00" + ); // glyphDataFormat + + // Horizontal header + builder.addTable( + "hhea", + "\x00\x01\x00\x00" + // Version number + safeString16(properties.ascent) + // Typographic Ascent + safeString16(properties.descent) + // Typographic Descent + "\x00\x00" + // Line Gap + "\xFF\xFF" + // advanceWidthMax + "\x00\x00" + // minLeftSidebearing + "\x00\x00" + // minRightSidebearing + "\x00\x00" + // xMaxExtent + safeString16(properties.capHeight) + // caretSlopeRise + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun + "\x00\x00" + // caretOffset + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // metricDataFormat + string16(numGlyphs) + ); // Number of HMetrics + + // Horizontal metrics + builder.addTable( + "hmtx", + (function fontFieldsHmtx() { + const charstrings = font.charstrings; + const cffWidths = font.cff ? font.cff.widths : null; + let hmtx = "\x00\x00\x00\x00"; // Fake .notdef + for (let i = 1, ii = numGlyphs; i < ii; i++) { + let width = 0; + if (charstrings) { + const charstring = charstrings[i - 1]; + width = "width" in charstring ? charstring.width : 0; + } else if (cffWidths) { + width = Math.ceil(cffWidths[i] || 0); + } + hmtx += string16(width) + string16(0); + } + return hmtx; + })() + ); + + // Maximum profile + builder.addTable( + "maxp", + "\x00\x00\x50\x00" + string16(numGlyphs) // Version number + ); // Num of glyphs + + // Naming tables + builder.addTable("name", createNameTable(fontName)); + + // PostScript information + builder.addTable("post", createPostTable(properties)); + + return builder.toArray(); + } + + get spaceWidth() { + // trying to estimate space character width + const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"]; + let width; + for (let i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) { + const glyphName = possibleSpaceReplacements[i]; + // if possible, getting width by glyph name + if (glyphName in this.widths) { + width = this.widths[glyphName]; + break; + } + const glyphsUnicodeMap = getGlyphsUnicode(); + const glyphUnicode = glyphsUnicodeMap[glyphName]; + // finding the charcode via unicodeToCID map + let charcode = 0; + if (this.composite && this.cMap.contains(glyphUnicode)) { + charcode = this.cMap.lookup(glyphUnicode); + } + // ... via toUnicode map + if (!charcode && this.toUnicode) { + charcode = this.toUnicode.charCodeOf(glyphUnicode); + } + // setting it to unicode if negative or undefined + if (charcode <= 0) { + charcode = glyphUnicode; + } + // trying to get width via charcode + width = this.widths[charcode]; + if (width) { + break; // the non-zero width found + } + } + width = width || this.defaultWidth; + return shadow(this, "spaceWidth", width); + } + + /** + * @private + */ + _charToGlyph(charcode, isSpace = false) { + let fontCharCode, width, operatorListId; + + let widthCode = charcode; + if (this.cMap && this.cMap.contains(charcode)) { + widthCode = this.cMap.lookup(charcode); + } + width = this.widths[widthCode]; + width = isNum(width) ? width : this.defaultWidth; + const vmetric = this.vmetrics && this.vmetrics[widthCode]; + + let unicode = + this.toUnicode.get(charcode) || + this.fallbackToUnicode.get(charcode) || + charcode; + if (typeof unicode === "number") { + unicode = String.fromCharCode(unicode); + } + + let isInFont = charcode in this.toFontChar; + // First try the toFontChar map, if it's not there then try falling + // back to the char code. + fontCharCode = this.toFontChar[charcode] || charcode; + if (this.missingFile) { + const glyphName = + this.differences[charcode] || this.defaultEncoding[charcode]; + if ( + (glyphName === ".notdef" || glyphName === "") && + this.type === "Type1" + ) { + // .notdef glyphs should be invisible in non-embedded Type1 fonts, so + // replace them with spaces. + fontCharCode = 0x20; + } + fontCharCode = mapSpecialUnicodeValues(fontCharCode); + } + + if (this.isType3Font) { + // Font char code in this case is actually a glyph name. + operatorListId = fontCharCode; + } + + let accent = null; + if (this.seacMap && this.seacMap[charcode]) { + isInFont = true; + const seac = this.seacMap[charcode]; + fontCharCode = seac.baseFontCharCode; + accent = { + fontChar: String.fromCodePoint(seac.accentFontCharCode), + offset: seac.accentOffset, + }; + } + + let fontChar = ""; + if (typeof fontCharCode === "number") { + if (fontCharCode <= 0x10ffff) { + fontChar = String.fromCodePoint(fontCharCode); + } else { + warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`); + } + } + + let glyph = this.glyphCache[charcode]; + if ( + !glyph || + !glyph.matchesForCache( + fontChar, + unicode, + accent, + width, + vmetric, + operatorListId, + isSpace, + isInFont + ) + ) { + glyph = new Glyph( + fontChar, + unicode, + accent, + width, + vmetric, + operatorListId, + isSpace, + isInFont + ); + this.glyphCache[charcode] = glyph; + } + return glyph; + } + + charsToGlyphs(chars) { + let charsCache = this.charsCache; + let glyphs, glyph, charcode; + + // if we translated this string before, just grab it from the cache + if (charsCache) { + glyphs = charsCache[chars]; + if (glyphs) { + return glyphs; + } + } + + // lazily create the translation cache + if (!charsCache) { + charsCache = this.charsCache = Object.create(null); + } + + glyphs = []; + const charsCacheKey = chars; + let i = 0, + ii; + + if (this.cMap) { + // composite fonts have multi-byte strings convert the string from + // single-byte to multi-byte + const c = Object.create(null); + while (i < chars.length) { + this.cMap.readCharCode(chars, i, c); + charcode = c.charcode; + const length = c.length; + i += length; + // Space is char with code 0x20 and length 1 in multiple-byte codes. + const isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20; + glyph = this._charToGlyph(charcode, isSpace); + glyphs.push(glyph); + } + } else { + for (i = 0, ii = chars.length; i < ii; ++i) { + charcode = chars.charCodeAt(i); + glyph = this._charToGlyph(charcode, charcode === 0x20); + glyphs.push(glyph); + } + } + + // Enter the translated string into the cache + return (charsCache[charsCacheKey] = glyphs); + } + + /** + * Chars can have different sizes (depends on the encoding). + * @param {String} a string encoded with font encoding. + * @returns {Array>} the positions of each char in the string. + */ + getCharPositions(chars) { + // This function doesn't use a cache because + // it's called only when saving or printing. + const positions = []; + + if (this.cMap) { + const c = Object.create(null); + let i = 0; + while (i < chars.length) { + this.cMap.readCharCode(chars, i, c); + const length = c.length; + positions.push([i, i + length]); + i += length; + } + } else { + for (let i = 0, ii = chars.length; i < ii; ++i) { + positions.push([i, i + 1]); + } + } + + return positions; + } + + get glyphCacheValues() { + return Object.values(this.glyphCache); + } + + /** + * Encode a js string using font encoding. + * The resulting array contains an encoded string at even positions + * (can be empty) and a non-encoded one at odd positions. + * @param {String} a js string. + * @returns {Array} an array of encoded strings or non-encoded ones. + */ + encodeString(str) { + const buffers = []; + const currentBuf = []; + + // buffers will contain: encoded, non-encoded, encoded, ... + // currentBuf is pushed in buffers each time there is a change. + // So when buffers.length is odd then the last string is an encoded one + // and currentBuf contains non-encoded chars. + const hasCurrentBufErrors = () => buffers.length % 2 === 1; + + for (let i = 0, ii = str.length; i < ii; i++) { + const unicode = str.codePointAt(i); + if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) { + // unicode is represented by two uint16 + i++; + } + if (this.toUnicode) { + const char = String.fromCodePoint(unicode); + const charCode = this.toUnicode.charCodeOf(char); + if (charCode !== -1) { + if (hasCurrentBufErrors()) { + buffers.push(currentBuf.join("")); + currentBuf.length = 0; + } + const charCodeLength = this.cMap + ? this.cMap.getCharCodeLength(charCode) + : 1; + for (let j = charCodeLength - 1; j >= 0; j--) { + currentBuf.push(String.fromCharCode((charCode >> (8 * j)) & 0xff)); + } + continue; } - currentBuf.push(String.fromCodePoint(unicode)); } - buffers.push(currentBuf.join("")); + // unicode can't be encoded + if (!hasCurrentBufErrors()) { + buffers.push(currentBuf.join("")); + currentBuf.length = 0; + } + currentBuf.push(String.fromCodePoint(unicode)); + } - return buffers; - }, - }; + buffers.push(currentBuf.join("")); - return Font; -})(); + return buffers; + } +} -const ErrorFont = (function ErrorFontClosure() { - // eslint-disable-next-line no-shadow - function ErrorFont(error) { +class ErrorFont { + constructor(error) { this.error = error; this.loadedName = "g_font_error"; this.missingFile = true; } - ErrorFont.prototype = { - charsToGlyphs: function ErrorFont_charsToGlyphs() { - return []; - }, - encodeString: function ErrorFont_encodeString(chars) { - return [chars]; - }, - exportData(extraProperties = false) { - return { error: this.error }; - }, - }; + charsToGlyphs() { + return []; + } - return ErrorFont; -})(); + encodeString(chars) { + return [chars]; + } + + exportData(extraProperties = false) { + return { error: this.error }; + } +} export { ErrorFont, Font }; From 5e5daca407e42562c08fba62364f1593c697c9e7 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sun, 2 May 2021 21:23:12 +0200 Subject: [PATCH 18/18] Remove unnecessary `MissingDataException` check from `getHeaderBlock` It shouldn't be possible for the `getBytes`-call to throw a `MissingDataException`, since all resources are loaded *before* e.g. font-parsing ever starts; see https://github.com/mozilla/pdf.js/blob/f0817015bd0b5b3dd498427df85c564e9eb08603/src/core/object_loader.js#L111-L126 Furthermore, even if we'd *somehow* re-throw a `MissingDataException` here that still won't help considering where the `Type1Font`-instance is created. Note how in the `Font`-constructor we simply catch any errors and fallback to a standard font, which means that a `MissingDataException` would just lead to rendering errors anyway; see https://github.com/mozilla/pdf.js/blob/f0817015bd0b5b3dd498427df85c564e9eb08603/src/core/fonts.js#L648-L691 All-in-all, it's not possible for a `MissingDataException` to be thrown in `getHeaderBlock` and this code-path can thus be removed. --- src/core/type1_font.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/type1_font.js b/src/core/type1_font.js index bcb46bc82..bde53e9a1 100644 --- a/src/core/type1_font.js +++ b/src/core/type1_font.js @@ -24,8 +24,8 @@ import { CFFStrings, CFFTopDict, } from "./cff_parser.js"; -import { isWhiteSpace, MissingDataException } from "./core_utils.js"; import { SEAC_ANALYSIS_ENABLED, type1FontGlyphMapping } from "./fonts_utils.js"; +import { isWhiteSpace } from "./core_utils.js"; import { Stream } from "./stream.js"; import { Type1Parser } from "./type1_parser.js"; import { warn } from "../shared/util.js"; @@ -68,9 +68,6 @@ function getHeaderBlock(stream, suggestedLength) { headerBytes = stream.getBytes(suggestedLength); headerBytesLength = headerBytes.length; } catch (ex) { - if (ex instanceof MissingDataException) { - throw ex; - } // Ignore errors if the `suggestedLength` is huge enough that a Uint8Array // cannot hold the result of `getBytes`, and fallback to simply checking // the entire stream (fixes issue3928.pdf).