pdf.js/src/core/fonts.js

3447 lines
121 KiB
JavaScript
Raw Normal View History

2012-09-01 07:48:21 +09:00
/* 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.
*/
2011-10-26 10:18:22 +09:00
'use strict';
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define('pdfjs/core/fonts', ['exports', 'pdfjs/shared/util',
'pdfjs/core/primitives', 'pdfjs/core/stream', 'pdfjs/core/glyphlist',
'pdfjs/core/font_renderer', 'pdfjs/core/encodings',
'pdfjs/core/standard_fonts', 'pdfjs/core/unicode',
2016-04-01 20:37:00 +09:00
'pdfjs/core/type1_parser', 'pdfjs/core/cff_parser'], factory);
} else if (typeof exports !== 'undefined') {
factory(exports, require('../shared/util.js'), require('./primitives.js'),
require('./stream.js'), require('./glyphlist.js'),
require('./font_renderer.js'), require('./encodings.js'),
require('./standard_fonts.js'), require('./unicode.js'),
require('./type1_parser.js'), require('./cff_parser.js'));
} else {
factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreGlyphList,
root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings,
root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode,
root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser);
}
}(this, function (exports, sharedUtil, corePrimitives, coreStream,
coreGlyphList, coreFontRenderer, coreEncodings,
coreStandardFonts, coreUnicode, coreType1Parser,
coreCFFParser) {
var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
var FontType = sharedUtil.FontType;
var assert = sharedUtil.assert;
var bytesToString = sharedUtil.bytesToString;
var error = sharedUtil.error;
var info = sharedUtil.info;
var isArray = sharedUtil.isArray;
var isInt = sharedUtil.isInt;
var isNum = sharedUtil.isNum;
var readUint32 = sharedUtil.readUint32;
var shadow = sharedUtil.shadow;
var string32 = sharedUtil.string32;
var warn = sharedUtil.warn;
var MissingDataException = sharedUtil.MissingDataException;
var isSpace = sharedUtil.isSpace;
var Stream = coreStream.Stream;
2016-01-22 05:47:48 +09:00
var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
var FontRendererFactory = coreFontRenderer.FontRendererFactory;
2016-01-22 06:18:46 +09:00
var StandardEncoding = coreEncodings.StandardEncoding;
var MacRomanEncoding = coreEncodings.MacRomanEncoding;
var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
var getEncoding = coreEncodings.getEncoding;
var getStdFontMap = coreStandardFonts.getStdFontMap;
var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
var getSupplementalGlyphMapForArialBlack =
coreStandardFonts.getSupplementalGlyphMapForArialBlack;
2016-01-22 07:10:42 +09:00
var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
2016-04-01 20:37:00 +09:00
var Type1Parser = coreType1Parser.Type1Parser;
var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
var CFFParser = coreCFFParser.CFFParser;
var CFFCompiler = coreCFFParser.CFFCompiler;
var CFF = coreCFFParser.CFF;
var CFFHeader = coreCFFParser.CFFHeader;
var CFFTopDict = coreCFFParser.CFFTopDict;
var CFFPrivateDict = coreCFFParser.CFFPrivateDict;
var CFFStrings = coreCFFParser.CFFStrings;
var CFFIndex = coreCFFParser.CFFIndex;
var CFFCharset = coreCFFParser.CFFCharset;
2011-09-05 21:35:03 +09:00
// Unicode Private Use Area
var PRIVATE_USE_OFFSET_START = 0xE000;
var PRIVATE_USE_OFFSET_END = 0xF8FF;
var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
2011-09-17 09:53:52 +09:00
// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
// except for Type 3 fonts
var PDF_GLYPH_SPACE_UNITS = 1000;
2011-09-05 21:35:03 +09:00
// Accented charactars are not displayed properly on Windows, using this flag
2013-02-27 03:00:20 +09:00
// to control analysis of seac charstrings.
var SEAC_ANALYSIS_ENABLED = false;
2012-01-28 09:53:05 +09:00
var FontFlags = {
FixedPitch: 1,
Serif: 2,
Symbolic: 4,
Script: 8,
Nonsymbolic: 32,
Italic: 64,
AllCap: 65536,
SmallCap: 131072,
ForceBold: 262144
};
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'];
2013-01-04 09:39:06 +09:00
function adjustWidths(properties) {
if (!properties.fontMatrix) {
return;
}
2013-01-04 09:39:06 +09:00
if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
return;
}
// adjusting width to fontMatrix scale
var scale = 0.001 / properties.fontMatrix[0];
var glyphsWidths = properties.widths;
for (var glyph in glyphsWidths) {
glyphsWidths[glyph] *= scale;
}
properties.defaultWidth *= scale;
}
function adjustToUnicode(properties, builtInEncoding) {
if (properties.hasIncludedToUnicodeMap) {
return; // The font dictionary has a `ToUnicode` entry.
}
if (properties.hasEncoding) {
return; // The font dictionary has an `Encoding` entry.
}
if (builtInEncoding === properties.defaultEncoding) {
return; // No point in trying to adjust `toUnicode` if the encodings match.
}
if (properties.toUnicode instanceof IdentityToUnicodeMap) {
return;
}
var toUnicode = [], glyphsUnicodeMap = getGlyphsUnicode();
for (var charCode in builtInEncoding) {
var glyphName = builtInEncoding[charCode];
var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
toUnicode[charCode] = String.fromCharCode(unicode);
}
}
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() {
2015-11-02 23:54:15 +09:00
function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
isSpace, isInFont) {
this.fontChar = fontChar;
this.unicode = unicode;
this.accent = accent;
this.width = width;
this.vmetric = vmetric;
this.operatorListId = operatorListId;
2015-11-02 23:54:15 +09:00
this.isSpace = isSpace;
this.isInFont = isInFont;
}
2015-11-02 23:54:15 +09:00
Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
vmetric, operatorListId, isSpace,
isInFont) {
return this.fontChar === fontChar &&
this.unicode === unicode &&
this.accent === accent &&
this.width === width &&
this.vmetric === vmetric &&
2015-11-02 23:54:15 +09:00
this.operatorListId === operatorListId &&
this.isSpace === isSpace &&
this.isInFont === isInFont;
};
return Glyph;
})();
2014-08-07 10:02:11 +09:00
var ToUnicodeMap = (function ToUnicodeMapClosure() {
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: function(callback) {
for (var charCode in this._map) {
callback(charCode, this._map[charCode].charCodeAt(0));
}
},
has: function(i) {
return this._map[i] !== undefined;
},
2014-08-07 10:02:11 +09:00
get: function(i) {
return this._map[i];
},
charCodeOf: function(v) {
return this._map.indexOf(v);
},
amend: function (map) {
for (var charCode in map) {
this._map[charCode] = map[charCode];
}
},
2014-08-07 10:02:11 +09:00
};
return ToUnicodeMap;
})();
var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
function IdentityToUnicodeMap(firstChar, lastChar) {
this.firstChar = firstChar;
this.lastChar = lastChar;
}
IdentityToUnicodeMap.prototype = {
get length() {
return (this.lastChar + 1) - this.firstChar;
},
forEach: function (callback) {
for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
callback(i, i);
}
},
has: function (i) {
return this.firstChar <= i && i <= this.lastChar;
},
get: function (i) {
if (this.firstChar <= i && i <= this.lastChar) {
return String.fromCharCode(i);
}
return undefined;
},
charCodeOf: function (v) {
return (isInt(v) && v >= this.firstChar && v <= this.lastChar) ? v : -1;
},
amend: function (map) {
error('Should not call amend()');
},
};
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;
}
}
}
function OpenTypeFileBuilder(sfnt) {
this.sfnt = sfnt;
this.tables = Object.create(null);
}
OpenTypeFileBuilder.getSearchParams =
function OpenTypeFileBuilder_getSearchParams(entriesCount, entrySize) {
2014-08-11 03:51:09 +09:00
var maxPower2 = 1, log2 = 0;
while ((maxPower2 ^ entriesCount) > maxPower2) {
maxPower2 <<= 1;
log2++;
}
var searchRange = maxPower2 * entrySize;
return {
range: searchRange,
2014-08-11 03:51:09 +09:00
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;
})();
2015-07-03 06:47:47 +09:00
// Problematic Unicode characters in the fonts that needs to be moved to avoid
// issues when they are painted on the canvas, e.g. complex-script shaping or
// control/whitespace characters. The ranges are listed in pairs: the first item
// is a code of the first problematic code, the second one is the next
// non-problematic code. The ranges must be in sorted order.
var ProblematicCharRanges = new Int32Array([
// Control characters.
0x0000, 0x0020,
0x007F, 0x00A1,
0x00AD, 0x00AE,
// Chars that is used in complex-script shaping.
0x0600, 0x0780,
0x08A0, 0x10A0,
0x1780, 0x1800,
0x1C00, 0x1C50,
2015-07-03 06:47:47 +09:00
// General punctuation chars.
0x2000, 0x2010,
0x2011, 0x2012,
0x2028, 0x2030,
0x205F, 0x2070,
0x25CC, 0x25CD,
0x3000, 0x3001,
2015-07-03 06:47:47 +09:00
// Chars that is used in complex-script shaping.
0xAA60, 0xAA80,
// Specials Unicode block.
0xFFF0, 0x10000
]);
if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')) {
/**
* Used to validate the entries in `ProblematicCharRanges`, and to ensure that
* its total number of characters does not exceed the PUA (Private Use Area)
* length.
* @returns {Object} An object with {number} `numChars`, {number} `puaLength`,
* and {number} `percentage` parameters.
*/
var checkProblematicCharRanges = function checkProblematicCharRanges() {
function printRange(limits) {
return '[' + limits.lower.toString('16').toUpperCase() + ', ' +
limits.upper.toString('16').toUpperCase() + ')';
}
var numRanges = ProblematicCharRanges.length;
if (numRanges % 2 !== 0) {
throw new Error('Char ranges must contain an even number of elements.');
}
var prevLimits, numChars = 0;
for (var i = 0; i < numRanges; i += 2) {
var limits = {
lower: ProblematicCharRanges[i],
upper: ProblematicCharRanges[i + 1],
};
if (!isInt(limits.lower) || !isInt(limits.upper)) {
throw new Error('Range endpoints must be integers: ' +
printRange(limits));
}
if (limits.lower < 0 || limits.upper < 0) {
throw new Error('Range endpoints must be non-negative: ' +
printRange(limits));
}
var range = limits.upper - limits.lower;
if (range < 1) {
throw new Error('Range must contain at least one element: ' +
printRange(limits));
}
if (prevLimits) {
if (limits.lower < prevLimits.lower) {
throw new Error('Ranges must be sorted in ascending order: ' +
printRange(limits) + ', ' + printRange(prevLimits));
}
if (limits.lower < prevLimits.upper) {
throw new Error('Ranges must not overlap: ' +
printRange(limits) + ', ' + printRange(prevLimits));
}
}
prevLimits = {
lower: limits.lower,
upper: limits.upper,
};
// The current range is OK.
numChars += range;
}
var puaLength = (PRIVATE_USE_OFFSET_END + 1) - PRIVATE_USE_OFFSET_START;
if (numChars > puaLength) {
throw new Error('Total number of chars must not exceed the PUA length.');
}
return {
numChars: numChars,
puaLength: puaLength,
percentage: 100 * (numChars / puaLength),
};
};
exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED;
exports.checkProblematicCharRanges = checkProblematicCharRanges;
}
/**
* '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:
2011-06-17 20:55:42 +09:00
* var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
* type1Font.bind();
2011-06-04 00:54:40 +09:00
*/
2011-12-09 07:18:43 +09:00
var Font = (function FontClosure() {
function Font(name, file, properties) {
var charCode, glyphName, unicode;
2012-09-13 09:31:04 +09:00
this.name = name;
this.loadedName = properties.loadedName;
this.isType3Font = properties.isType3Font;
2011-07-21 09:10:04 +09:00
this.sizes = [];
this.missingFile = false;
this.glyphCache = Object.create(null);
2011-09-09 04:37:35 +09:00
var names = name.split('+');
names = names.length > 1 ? names[1] : names[0];
names = names.split(/[-,_]/g)[0];
2012-01-28 09:53:05 +09:00
this.isSerifFont = !!(properties.flags & FontFlags.Serif);
this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
var type = properties.type;
var subtype = properties.subtype;
this.type = type;
2014-03-18 01:34:30 +09:00
this.fallbackName = (this.isMonospace ? 'monospace' :
(this.isSerifFont ? 'serif' : 'sans-serif'));
this.differences = properties.differences;
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite;
this.wideChars = properties.wideChars;
this.cMap = properties.cMap;
this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
2011-10-04 08:36:01 +09:00
this.fontMatrix = properties.fontMatrix;
this.bbox = properties.bbox;
this.toUnicode = properties.toUnicode;
this.toFontChar = [];
if (properties.type === 'Type3') {
2014-04-08 06:42:54 +09:00
for (charCode = 0; charCode < 256; charCode++) {
2014-03-18 01:34:30 +09:00
this.toFontChar[charCode] = (this.differences[charCode] ||
properties.defaultEncoding[charCode]);
}
this.fontType = FontType.TYPE3;
2011-10-04 08:36:01 +09:00
return;
}
2011-07-25 23:42:46 +09:00
this.cidEncoding = properties.cidEncoding;
2013-02-08 21:29:22 +09:00
this.vertical = properties.vertical;
if (this.vertical) {
this.vmetrics = properties.vmetrics;
this.defaultVMetrics = properties.defaultVMetrics;
}
2016-01-22 05:47:48 +09:00
var glyphsUnicodeMap;
if (!file || file.isEmpty) {
if (file) {
// Some bad PDF generators will include empty font files,
// attempting to recover by assuming that no file exists.
warn('Font file is empty in "' + name + '" (' + this.loadedName + ')');
}
this.missingFile = true;
2011-08-15 02:11:23 +09:00
// The file data is not specified. Trying to fix the font name
2011-08-14 22:40:22 +09:00
// to be used with the canvas.font.
2011-10-20 03:14:13 +09:00
var fontName = name.replace(/[,_]/g, '-');
var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
var isStandardFont = !!stdFontMap[fontName] ||
!!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
2011-10-20 03:14:13 +09:00
this.bold = (fontName.search(/bold/gi) !== -1);
this.italic = ((fontName.search(/oblique/gi) !== -1) ||
(fontName.search(/italic/gi) !== -1));
2011-09-01 11:06:33 +09:00
// 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);
2011-09-01 11:06:33 +09:00
2013-05-06 23:34:47 +09:00
// if at least one width is present, remeasure all chars when exists
this.remeasure = Object.keys(this.widths).length > 0;
2014-01-04 05:17:50 +09:00
if (isStandardFont && type === 'CIDFontType2' &&
properties.cidEncoding.indexOf('Identity-') === 0) {
var GlyphMapForStandardFonts = getGlyphMapForStandardFonts();
2014-01-04 05:17:50 +09:00
// Standard fonts might be embedded as CID font without glyph mapping.
// Building one based on GlyphMapForStandardFonts.
var map = [];
for (charCode in GlyphMapForStandardFonts) {
map[+charCode] = GlyphMapForStandardFonts[charCode];
}
if (/Arial-?Black/i.test(name)) {
var SupplementalGlyphMapForArialBlack =
getSupplementalGlyphMapForArialBlack();
for (charCode in SupplementalGlyphMapForArialBlack) {
map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
}
2014-01-04 05:17:50 +09:00
}
var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
if (!isIdentityUnicode) {
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
map[+charCode] = unicodeCharCode;
});
}
2014-01-04 05:17:50 +09:00
this.toFontChar = map;
2014-08-07 10:02:11 +09:00
this.toUnicode = new ToUnicodeMap(map);
2014-02-12 06:37:12 +09:00
} else if (/Symbol/i.test(fontName)) {
this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(),
properties.differences);
2014-09-01 10:22:24 +09:00
} else if (/Dingbats/i.test(fontName)) {
if (/Wingdings/i.test(name)) {
warn('Non-embedded Wingdings font, falling back to ZapfDingbats.');
2014-02-12 06:37:12 +09:00
}
this.toFontChar = buildToFontChar(ZapfDingbatsEncoding,
getDingbatsGlyphsUnicode(),
properties.differences);
} else if (isStandardFont) {
this.toFontChar = buildToFontChar(properties.defaultEncoding,
getGlyphsUnicode(),
properties.differences);
} else {
2016-01-22 05:47:48 +09:00
glyphsUnicodeMap = getGlyphsUnicode();
2014-08-07 10:02:11 +09:00
this.toUnicode.forEach(function(charCode, unicodeCharCode) {
if (!this.composite) {
glyphName = (properties.differences[charCode] ||
properties.defaultEncoding[charCode]);
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
if (unicode !== -1) {
unicodeCharCode = unicode;
}
}
this.toFontChar[charCode] = unicodeCharCode;
2014-08-07 10:02:11 +09:00
}.bind(this));
2014-01-04 05:17:50 +09:00
}
this.loadedName = fontName.split('-')[0];
2011-07-25 23:42:46 +09:00
this.loading = false;
this.fontType = getFontType(type, subtype);
2011-06-21 09:35:14 +09:00
return;
}
// Some fonts might use wrong font types for Type1C or CIDFontType0C
if (subtype === 'Type1C') {
if (type !== 'Type1' && type !== 'MMType1') {
// Some TrueType fonts by mistake claim Type1C
if (isTrueTypeFile(file)) {
subtype = 'TrueType';
} else {
type = 'Type1';
}
} else if (isOpenTypeFile(file)) {
// Sometimes the type/subtype can be a complete lie (see issue7598.pdf).
type = subtype = 'OpenType';
}
2014-03-18 01:34:30 +09:00
}
if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
type = 'CIDFontType0';
2014-03-18 01:34:30 +09:00
}
2012-12-11 07:59:23 +09:00
if (subtype === 'OpenType') {
type = 'OpenType';
}
// Some CIDFontType0C fonts by mistake claim CIDFontType0.
if (type === 'CIDFontType0') {
if (isType1File(file)) {
subtype = 'CIDFontType0';
} else if (isOpenTypeFile(file)) {
// Sometimes the type/subtype can be a complete lie (see issue6782.pdf).
type = subtype = 'OpenType';
} else {
subtype = 'CIDFontType0C';
}
}
var data;
switch (type) {
case 'MMType1':
info('MMType1 font (' + name + '), falling back to Type1.');
/* falls through */
2011-07-06 15:06:45 +09:00
case 'Type1':
case 'CIDFontType0':
2011-07-06 15:06:45 +09:00
this.mimetype = 'font/opentype';
var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
2012-03-11 12:37:22 +09:00
new CFFFont(file, properties) : new Type1Font(name, file, properties);
2013-01-04 09:39:06 +09:00
adjustWidths(properties);
2011-06-21 09:35:14 +09:00
// Wrap the CFF data inside an OTF font file
data = this.convert(name, cff, properties);
2011-06-21 09:35:14 +09:00
break;
2013-06-25 08:45:31 +09:00
case 'OpenType':
2011-07-06 15:06:45 +09:00
case 'TrueType':
case 'CIDFontType2':
this.mimetype = 'font/opentype';
// Repair the TrueType file. It is can be damaged in the point of
// view of the sanitizer
data = this.checkAndRepair(name, file, properties);
if (this.isOpenType) {
adjustWidths(properties);
type = 'OpenType';
}
break;
2011-06-21 09:35:14 +09:00
default:
2013-06-28 04:38:52 +09:00
error('Font ' + type + ' is not supported');
2011-06-21 09:35:14 +09:00
break;
}
2011-06-27 03:55:27 +09:00
this.data = data;
this.fontType = getFontType(type, subtype);
2013-01-04 09:39:06 +09:00
// Transfer some properties again that could change during font conversion
2011-10-04 08:36:01 +09:00
this.fontMatrix = properties.fontMatrix;
2013-01-04 09:39:06 +09:00
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.toUnicode = properties.toUnicode;
this.encoding = properties.baseEncoding;
2013-02-27 03:00:20 +09:00
this.seacMap = properties.seacMap;
2013-01-04 09:39:06 +09:00
2011-07-25 23:42:46 +09:00
this.loading = true;
}
Font.getFontID = (function () {
var ID = 1;
return function Font_getFontID() {
return String(ID++);
};
})();
function int16(b0, b1) {
return (b0 << 8) + b1;
}
function signedInt16(b0, b1) {
var value = (b0 << 8) + b1;
return value & (1 << 15) ? value - 0x10000 : value;
}
2011-07-02 14:54:28 +09:00
function int32(b0, b1, b2, b3) {
return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
}
2011-07-02 14:54:28 +09:00
2011-06-28 20:06:31 +09:00
function string16(value) {
2014-03-27 21:01:43 +09:00
return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
}
2011-12-07 13:13:14 +09:00
function safeString16(value) {
// clamp value to the 16-bit int range
2014-03-18 01:34:30 +09:00
value = (value > 0x7FFF ? 0x7FFF : (value < -0x8000 ? -0x8000 : value));
2014-03-27 21:01:43 +09:00
return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
}
function isTrueTypeFile(file) {
var header = file.peekBytes(4);
return readUint32(header, 0) === 0x00010000;
}
function isOpenTypeFile(file) {
var header = file.peekBytes(4);
return bytesToString(header) === 'OTTO';
}
function isType1File(file) {
var 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.
if (header[0] === 0x80 && header[1] === 0x01) { // pfb file header.
return true;
}
return false;
}
function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
var toFontChar = [], unicode;
for (var i = 0, ii = encoding.length; i < ii; i++) {
unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[i] = unicode;
}
}
for (var charCode in differences) {
unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
if (unicode !== -1) {
toFontChar[+charCode] = unicode;
}
}
return toFontChar;
}
/**
* Helper function for `adjustMapping`.
* @return {boolean}
*/
function isProblematicUnicodeLocation(code) {
2015-07-03 06:47:47 +09:00
// Using binary search to find a range start.
var i = 0, j = ProblematicCharRanges.length - 1;
while (i < j) {
var c = (i + j + 1) >> 1;
if (code < ProblematicCharRanges[c]) {
j = c - 1;
} else {
i = c;
}
}
2015-07-03 06:47:47 +09:00
// Even index means code in problematic range.
return !(i & 1);
}
/**
* Rebuilds the char code to glyph ID map by trying to replace the char codes
* with their unicode value. It also moves char codes that are in known
* problematic locations.
* @return {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, properties) {
var toUnicode = properties.toUnicode;
var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
2014-08-08 11:49:41 +09:00
var isIdentityUnicode =
properties.toUnicode instanceof IdentityToUnicodeMap;
var newMap = Object.create(null);
var toFontChar = [];
var usedFontCharCodes = [];
var nextAvailableFontCharCode = PRIVATE_USE_OFFSET_START;
for (var originalCharCode in charCodeToGlyphId) {
originalCharCode |= 0;
var glyphId = charCodeToGlyphId[originalCharCode];
var fontCharCode = originalCharCode;
// First try to map the value to a unicode position if a non identity map
// was created.
var hasUnicodeValue = false;
if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
hasUnicodeValue = true;
2014-10-04 02:35:49 +09:00
var unicode = toUnicode.get(fontCharCode);
// TODO: Try to map ligatures to the correct spot.
if (unicode.length === 1) {
fontCharCode = unicode.charCodeAt(0);
}
}
// Try to move control characters, special characters and already mapped
// characters to the private use area since they will not be drawn by
// canvas if left in their current position. Also, move characters if the
// font was symbolic and there is only an identity unicode map since the
// characters probably aren't in the correct position (fixes an issue
// with firefox and thuluthfont).
if ((usedFontCharCodes[fontCharCode] !== undefined ||
isProblematicUnicodeLocation(fontCharCode) ||
(isSymbolic && !hasUnicodeValue)) &&
nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
// Loop to try and find a free spot in the private use area.
do {
fontCharCode = nextAvailableFontCharCode++;
if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
fontCharCode = 0xF020;
nextAvailableFontCharCode = fontCharCode + 1;
}
} while (usedFontCharCodes[fontCharCode] !== undefined &&
nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END);
}
newMap[fontCharCode] = glyphId;
toFontChar[originalCharCode] = fontCharCode;
usedFontCharCodes[fontCharCode] = true;
}
return {
toFontChar: toFontChar,
2014-04-12 01:55:39 +09:00
charCodeToGlyphId: newMap,
nextAvailableFontCharCode: nextAvailableFontCharCode
};
}
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) {
// Remove an invalid glyph ID mappings to make OTS happy.
if (glyphs[charCode] >= numGlyphs) {
continue;
}
codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });
}
codes.sort(function fontGetRangesSort(a, b) {
return a.fontCharCode - b.fontCharCode;
});
// Split the sorted codes into ranges.
var ranges = [];
var length = codes.length;
Switch to using ESLint, instead of JSHint, for linting *Please note that most of the necessary code adjustments were made in PR 7890.* ESLint has a number of advantageous properties, compared to JSHint. Among those are: - The ability to find subtle bugs, thanks to more rules (e.g. PR 7881). - Much more customizable in general, and many rules allow fine-tuned behaviour rather than the just the on/off rules in JSHint. - Many more rules that can help developers avoid bugs, and a lot of rules that can be used to enforce a consistent coding style. The latter should be particularily useful for new contributors (and reduce the amount of stylistic review comments necessary). - The ability to easily specify exactly what rules to use/not to use, as opposed to JSHint which has a default set. *Note:* in future JSHint version some of the rules we depend on will be removed, according to warnings in http://jshint.com/docs/options/, so we wouldn't be able to update without losing lint coverage. - More easily disable one, or more, rules temporarily. In JSHint this requires using a numeric code, which isn't very user friendly, whereas in ESLint the rule name is simply used instead. By default there's no rules enabled in ESLint, but there are some default rule sets available. However, to prevent linting failures if we update ESLint in the future, it seemed easier to just explicitly specify what rules we want. Obviously this makes the ESLint config file somewhat bigger than the old JSHint config file, but given how rarely that one has been updated over the years I don't think that matters too much. I've tried, to the best of my ability, to ensure that we enable the same rules for ESLint that we had for JSHint. Furthermore, I've also enabled a number of rules that seemed to make sense, both to catch possible errors *and* various style guide violations. Despite the ESLint README claiming that it's slower that JSHint, https://github.com/eslint/eslint#how-does-eslint-performance-compare-to-jshint, locally this patch actually reduces the runtime for `gulp` lint (by approximately 20-25%). A couple of stylistic rules that would have been nice to enable, but where our code currently differs to much to make it feasible: - `comma-dangle`, controls trailing commas in Objects and Arrays (among others). - `object-curly-spacing`, controls spacing inside of Objects. - `spaced-comment`, used to enforce spaces after `//` and `/*. (This is made difficult by the fact that there's still some usage of the old preprocessor left.) Rules that I indend to look into possibly enabling in follow-ups, if it seems to make sense: `no-else-return`, `no-lonely-if`, `brace-style` with the `allowSingleLine` parameter removed. Useful links: - http://eslint.org/docs/user-guide/configuring - http://eslint.org/docs/rules/
2016-12-15 23:52:29 +09:00
for (var n = 0; n < length; ) { // eslint-disable-line space-in-parens
var start = codes[n].fontCharCode;
var codeIndices = [codes[n].glyphId];
++n;
var end = start;
while (n < length && end + 1 === codes[n].fontCharCode) {
codeIndices.push(codes[n].glyphId);
++end;
++n;
2014-03-18 01:34:30 +09:00
if (end === 0xFFFF) {
break;
}
}
ranges.push([start, end, codeIndices]);
}
return ranges;
}
function createCmapTable(glyphs, numGlyphs) {
var ranges = getRanges(glyphs, numGlyphs);
var numTables = ranges[ranges.length - 1][1] > 0xFFFF ? 2 : 1;
2011-07-06 15:06:45 +09:00
var cmap = '\x00\x00' + // version
2011-07-02 09:44:57 +09:00
string16(numTables) + // numTables
2011-07-06 15:06:45 +09:00
'\x00\x03' + // platformID
'\x00\x01' + // encodingID
string32(4 + numTables * 8); // start of the table record
2011-07-02 09:44:57 +09:00
2014-04-08 06:42:54 +09:00
var i, ii, j, jj;
for (i = ranges.length - 1; i >= 0; --i) {
if (ranges[i][0] <= 0xFFFF) { break; }
}
var bmpLength = i + 1;
2013-03-19 21:37:31 +09:00
if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
ranges[i][1] = 0xFFFE;
}
2013-07-02 01:25:46 +09:00
var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
2013-03-19 21:37:31 +09:00
var segCount = bmpLength + trailingRangesCount;
var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
// Fill up the 4 parallel arrays describing the segments.
2011-07-06 15:06:45 +09:00
var startCount = '';
var endCount = '';
var idDeltas = '';
var idRangeOffsets = '';
var glyphsIds = '';
var bias = 0;
2014-04-08 06:42:54 +09:00
var range, start, end, codes;
for (i = 0, ii = bmpLength; i < ii; i++) {
range = ranges[i];
start = range[0];
end = range[1];
2013-03-19 21:37:31 +09:00
startCount += string16(start);
endCount += string16(end);
2014-04-08 06:42:54 +09:00
codes = range[2];
2013-03-19 21:37:31 +09:00
var contiguous = true;
2014-04-08 06:42:54 +09:00
for (j = 1, jj = codes.length; j < jj; ++j) {
2013-03-19 21:37:31 +09:00
if (codes[j] !== codes[j - 1] + 1) {
contiguous = false;
break;
}
}
if (!contiguous) {
var offset = (segCount - i) * 2 + bias * 2;
bias += (end - start + 1);
idDeltas += string16(0);
idRangeOffsets += string16(offset);
2014-04-08 06:42:54 +09:00
for (j = 0, jj = codes.length; j < jj; ++j) {
2013-03-19 21:37:31 +09:00
glyphsIds += string16(codes[j]);
}
} else {
var startCode = codes[0];
2013-03-19 21:37:31 +09:00
idDeltas += string16((startCode - start) & 0xFFFF);
idRangeOffsets += string16(0);
}
}
2013-03-19 21:37:31 +09:00
if (trailingRangesCount > 0) {
endCount += '\xFF\xFF';
startCount += '\xFF\xFF';
idDeltas += '\x00\x01';
idRangeOffsets += '\x00\x00';
}
var format314 = '\x00\x00' + // language
string16(2 * segCount) +
string16(searchParams.range) +
string16(searchParams.entry) +
string16(searchParams.rangeShift) +
endCount + '\x00\x00' + startCount +
idDeltas + idRangeOffsets + glyphsIds;
var format31012 = '';
var header31012 = '';
if (numTables > 1) {
cmap += '\x00\x03' + // platformID
'\x00\x0A' + // encodingID
string32(4 + numTables * 8 +
4 + format314.length); // start of the table record
format31012 = '';
2014-04-08 06:42:54 +09:00
for (i = 0, ii = ranges.length; i < ii; i++) {
range = ranges[i];
start = range[0];
codes = range[2];
2013-03-19 21:37:31 +09:00
var code = codes[0];
2014-04-08 06:42:54 +09:00
for (j = 1, jj = codes.length; j < jj; ++j) {
2013-03-19 21:37:31 +09:00
if (codes[j] !== codes[j - 1] + 1) {
2014-04-08 06:42:54 +09:00
end = range[0] + j - 1;
2013-03-19 21:37:31 +09:00
format31012 += string32(start) + // startCharCode
string32(end) + // endCharCode
string32(code); // startGlyphID
start = end + 1;
code = codes[j];
}
}
2013-03-19 21:37:31 +09:00
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
}
2014-08-10 13:34:01 +09:00
return cmap + '\x00\x04' + // format
string16(format314.length + 4) + // length
format314 + header31012 + format31012;
}
function validateOS2Table(os2) {
var stream = new Stream(os2.data);
var version = stream.getUint16();
// TODO verify all OS/2 tables fields, but currently we validate only those
// that give us issues
stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges
var selection = stream.getUint16();
if (version < 4 && (selection & 0x0300)) {
return false;
}
var firstChar = stream.getUint16();
var lastChar = stream.getUint16();
if (firstChar > lastChar) {
return false;
}
2013-03-09 05:11:12 +09:00
stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap
var usWinAscent = stream.getUint16();
2013-03-09 05:11:12 +09:00
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) {
2011-10-20 03:14:13 +09:00
override = override || {
unitsPerEm: 0,
yMax: 0,
yMin: 0,
ascent: 0,
descent: 0
};
var ulUnicodeRange1 = 0;
var ulUnicodeRange2 = 0;
var ulUnicodeRange3 = 0;
var ulUnicodeRange4 = 0;
var firstCharIndex = null;
var lastCharIndex = 0;
if (charstrings) {
for (var code in charstrings) {
code |= 0;
2014-03-18 01:34:30 +09:00
if (firstCharIndex > code || !firstCharIndex) {
firstCharIndex = code;
2014-03-18 01:34:30 +09:00
}
if (lastCharIndex < code) {
lastCharIndex = code;
2014-03-18 01:34:30 +09:00
}
var 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 {
error('Unicode ranges Bits > 123 are reserved for internal usage');
}
}
} else {
// TODO
firstCharIndex = 0;
lastCharIndex = 255;
}
2013-01-05 02:41:24 +09:00
var bbox = properties.bbox || [0, 0, 0, 0];
2014-03-18 01:34:30 +09:00
var unitsPerEm = (override.unitsPerEm ||
1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0]);
2013-01-05 02:41:24 +09:00
// if the font units differ to the PDF glyph space units
// then scale up the values
2014-03-18 01:34:30 +09:00
var scale = (properties.ascentScaled ? 1.0 :
unitsPerEm / PDF_GLYPH_SPACE_UNITS);
2013-01-05 02:41:24 +09:00
2014-03-18 01:34:30 +09:00
var typoAscent = (override.ascent ||
Math.round(scale * (properties.ascent || bbox[3])));
var typoDescent = (override.descent ||
Math.round(scale * (properties.descent || bbox[1])));
2013-01-05 02:41:24 +09:00
if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
typoDescent = -typoDescent; // fixing incorrect descent
}
var winAscent = override.yMax || typoAscent;
var winDescent = -override.yMin || -typoDescent;
2011-07-06 15:06:45 +09:00
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)
2011-07-06 15:06:45 +09:00
'\x2A\x32\x31\x2A' + // achVendID
2011-06-30 18:48:43 +09:00
string16(properties.italicAngle ? 1 : 0) + // fsSelection
2011-07-06 15:06:45 +09:00
string16(firstCharIndex ||
properties.firstChar) + // usFirstCharIndex
string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
string16(typoAscent) + // sTypoAscender
string16(typoDescent) + // sTypoDescender
2011-07-06 15:06:45 +09:00
'\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
string16(winAscent) + // usWinAscent
string16(winDescent) + // usWinDescent
2011-07-06 15:06:45 +09:00
'\x00\x00\x00\x00' + // ulCodePageRange1 (Bits 0-31)
'\x00\x00\x00\x00' + // ulCodePageRange2 (Bits 32-63)
string16(properties.xHeight) + // sxHeight
2011-06-24 06:15:22 +09:00
string16(properties.capHeight) + // sCapHeight
string16(0) + // usDefaultChar
string16(firstCharIndex || properties.firstChar) + // usBreakChar
2011-07-06 15:06:45 +09:00
'\x00\x03'; // usMaxContext
}
2011-06-24 06:15:22 +09:00
function createPostTable(properties) {
2011-06-30 18:48:43 +09:00
var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16)));
2014-03-18 01:34:30 +09:00
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
}
2012-11-08 09:24:13 +09:00
function createNameTable(name, proto) {
if (!proto) {
proto = [[], []]; // no strings and unicode strings
}
var strings = [
2012-11-08 09:24:13 +09:00
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
var stringsUnicode = [];
2014-04-08 06:42:54 +09:00
var i, ii, j, jj, str;
for (i = 0, ii = strings.length; i < ii; i++) {
str = proto[1][i] || strings[i];
var strBufUnicode = [];
2014-04-08 06:42:54 +09:00
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'];
var namesRecordCount = strings.length * platforms.length;
var nameTable =
'\x00\x00' + // format
string16(namesRecordCount) + // Number of names Record
string16(namesRecordCount * 12 + 6); // Storage
// Build the name records field
var strOffset = 0;
2014-04-08 06:42:54 +09:00
for (i = 0, ii = platforms.length; i < ii; i++) {
var strs = names[i];
2014-04-08 06:42:54 +09:00
for (j = 0, jj = strs.length; j < jj; j++) {
str = strs[j];
var 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;
}
2011-12-09 07:18:43 +09:00
Font.prototype = {
name: null,
font: null,
mimetype: null,
2011-06-21 09:35:14 +09:00
encoding: null,
get renderer() {
var renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
return shadow(this, 'renderer', renderer);
},
2012-09-20 23:38:59 +09:00
exportData: function Font_exportData() {
// TODO remove enumerating of the properties, e.g. hardcode exact names.
2012-09-13 09:31:04 +09:00
var data = {};
for (var i in this) {
2014-03-18 01:34:30 +09:00
if (this.hasOwnProperty(i)) {
2012-09-13 09:31:04 +09:00
data[i] = this[i];
2014-03-18 01:34:30 +09:00
}
2012-09-13 09:31:04 +09:00
}
return data;
},
checkAndRepair: function Font_checkAndRepair(name, font, properties) {
function readTableEntry(file) {
2014-03-27 21:01:43 +09:00
var tag = bytesToString(file.getBytes(4));
var checksum = file.getInt32() >>> 0;
var offset = file.getInt32() >>> 0;
var length = file.getInt32() >>> 0;
// Read the table associated data
2011-06-22 04:11:59 +09:00
var previousPosition = file.pos;
file.pos = file.start ? file.start : 0;
file.skip(offset);
var data = file.getBytes(length);
2011-06-22 04:11:59 +09:00
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: tag,
checksum: checksum,
length: length,
offset: offset,
data: data
2011-07-06 15:06:45 +09:00
};
}
function readOpenTypeHeader(ttf) {
return {
2014-03-27 21:01:43 +09:00
version: bytesToString(ttf.getBytes(4)),
numTables: ttf.getUint16(),
searchRange: ttf.getUint16(),
entrySelector: ttf.getUint16(),
rangeShift: ttf.getUint16()
2011-07-06 15:06:45 +09:00
};
}
/**
* Read the appropriate subtable from the cmap according to 9.6.6.4 from
* PDF spec
*/
function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
if (!cmap) {
warn('No cmap table available.');
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
2014-04-08 06:42:54 +09:00
var segment;
var start = (font.start ? font.start : 0) + cmap.offset;
font.pos = start;
var version = font.getUint16();
var numTables = font.getUint16();
var potentialTable;
var 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 (var i = 0; i < numTables; i++) {
var platformId = font.getUint16();
var encodingId = font.getUint16();
var offset = font.getInt32() >>> 0;
var useTable = false;
if (platformId === 0 && encodingId === 0) {
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 &&
((!isSymbolicFont && hasEncoding) || !potentialTable)) {
useTable = true;
if (!isSymbolicFont) {
canBreak = true;
}
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
useTable = true;
canBreak = true;
}
if (useTable) {
potentialTable = {
platformId: platformId,
encodingId: encodingId,
offset: offset
};
}
if (canBreak) {
break;
}
}
if (potentialTable) {
font.pos = start + potentialTable.offset;
}
if (!potentialTable || font.peekByte() === -1) {
warn('Could not find a preferred cmap table.');
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
var format = font.getUint16();
var length = font.getUint16();
var language = font.getUint16();
2011-09-05 21:35:03 +09:00
var hasShortCmap = false;
var mappings = [];
2014-04-08 06:42:54 +09:00
var j, glyphId;
// TODO(mack): refactor this cmap subtable reading logic out
if (format === 0) {
2014-04-08 06:42:54 +09:00
for (j = 0; j < 256; j++) {
var index = font.getByte();
if (!index) {
continue;
2011-07-20 22:18:44 +09:00
}
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
var segCount = (font.getUint16() >> 1);
font.getBytes(6); // skipping range fields
var segIndex, segments = [];
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments.push({ end: font.getUint16() });
}
font.getUint16();
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments[segIndex].start = font.getUint16();
}
2011-09-09 04:37:35 +09:00
for (segIndex = 0; segIndex < segCount; segIndex++) {
segments[segIndex].delta = font.getUint16();
}
var offsetsCount = 0;
for (segIndex = 0; segIndex < segCount; segIndex++) {
2014-04-08 06:42:54 +09:00
segment = segments[segIndex];
var rangeOffset = font.getUint16();
if (!rangeOffset) {
segment.offsetIndex = -1;
continue;
}
var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
segment.offsetIndex = offsetIndex;
offsetsCount = Math.max(offsetsCount, offsetIndex +
2014-03-18 01:34:30 +09:00
segment.end - segment.start + 1);
}
var offsets = [];
2014-04-08 06:42:54 +09:00
for (j = 0; j < offsetsCount; j++) {
offsets.push(font.getUint16());
}
for (segIndex = 0; segIndex < segCount; segIndex++) {
2014-04-08 06:42:54 +09:00
segment = segments[segIndex];
start = segment.start;
var end = segment.end;
var delta = segment.delta;
offsetIndex = segment.offsetIndex;
2014-04-08 06:42:54 +09:00
for (j = start; j <= end; j++) {
if (j === 0xFFFF) {
continue;
}
2014-04-08 06:42:54 +09:00
glyphId = (offsetIndex < 0 ?
j : offsets[offsetIndex + j - start]);
glyphId = (glyphId + delta) & 0xFFFF;
if (glyphId === 0) {
continue;
}
mappings.push({
charCode: j,
glyphId: 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
var firstCode = font.getUint16();
var entryCount = font.getUint16();
2014-04-08 06:42:54 +09:00
for (j = 0; j < entryCount; j++) {
glyphId = font.getUint16();
var charCode = firstCode + j;
mappings.push({
charCode: charCode,
glyphId: glyphId
});
}
} else {
warn('cmap table has unsupported format: ' + format);
return {
platformId: -1,
encodingId: -1,
mappings: [],
hasShortCmap: false
};
}
// removing duplicate entries
mappings.sort(function (a, b) {
return a.charCode - b.charCode;
});
2014-04-08 06:42:54 +09:00
for (i = 1; i < mappings.length; i++) {
if (mappings[i - 1].charCode === mappings[i].charCode) {
mappings.splice(i, 1);
i--;
}
}
return {
platformId: potentialTable.platformId,
encodingId: potentialTable.encodingId,
mappings: mappings,
hasShortCmap: hasShortCmap
};
}
2011-09-09 23:37:56 +09:00
function sanitizeMetrics(font, header, metrics, numGlyphs) {
if (!header) {
if (metrics) {
metrics.data = null;
}
2011-09-09 23:37:56 +09:00
return;
}
font.pos = (font.start ? font.start : 0) + header.offset;
font.pos += header.length - 2;
var numOfMetrics = font.getUint16();
2011-09-13 02:42:55 +09:00
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;
}
2011-09-09 23:37:56 +09:00
var numOfSidebearings = numGlyphs - numOfMetrics;
var numMissing = numOfSidebearings -
((metrics.length - numOfMetrics * 4) >> 1);
2011-09-09 23:37:56 +09:00
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);
entries.set(metrics.data);
2014-08-10 13:34:01 +09:00
metrics.data = entries;
2011-09-09 23:37:56 +09:00
}
}
2011-09-09 23:37:56 +09:00
2013-03-18 22:06:59 +09:00
function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart,
hintsValid) {
if (sourceEnd - sourceStart <= 12) {
// glyph with data less than 12 is invalid one
return 0;
}
var glyf = source.subarray(sourceStart, sourceEnd);
var contoursCount = (glyf[0] << 8) | glyf[1];
if (contoursCount & 0x8000) {
// complex glyph, writing as is
dest.set(glyf, destStart);
return glyf.length;
}
2014-04-08 06:42:54 +09:00
var i, j = 10, flagsCount = 0;
for (i = 0; i < contoursCount; i++) {
var endPoint = (glyf[j] << 8) | glyf[j + 1];
flagsCount = endPoint + 1;
j += 2;
}
// skipping instructions
2013-03-18 22:06:59 +09:00
var instructionsStart = j;
var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
j += 2 + instructionsLength;
2013-03-18 22:06:59 +09:00
var instructionsEnd = j;
// validating flags
var coordinatesLength = 0;
2014-04-08 06:42:54 +09:00
for (i = 0; i < flagsCount; i++) {
var flag = glyf[j++];
if (flag & 0xC0) {
2013-11-14 04:45:59 +09:00
// reserved flags must be zero, cleaning up
glyf[j - 1] = flag & 0x3F;
}
var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) +
((flag & 4) ? 1 : (flag & 32) ? 0 : 2);
coordinatesLength += xyLength;
if (flag & 8) {
var repeat = glyf[j++];
i += repeat;
coordinatesLength += repeat * xyLength;
}
}
2013-11-03 07:07:13 +09:00
// glyph without coordinates will be rejected
if (coordinatesLength === 0) {
return 0;
}
var glyphDataLength = j + coordinatesLength;
if (glyphDataLength > glyf.length) {
// not enough data for coordinates
return 0;
}
2013-03-18 22:06:59 +09:00
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;
}
return glyphDataLength;
}
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);
return glyphDataLength;
}
// glyph data is fine
dest.set(glyf, destStart);
return glyf.length;
}
function sanitizeHead(head, numGlyphs, locaLength) {
var data = head.data;
// Validate version:
// Should always be 0x00010000
var 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;
}
var 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.
var 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 {
warn('Could not fix indexToLocFormat: ' + indexToLocFormat);
}
}
}
function sanitizeGlyphLocations(loca, glyf, numGlyphs,
2013-07-30 05:24:32 +09:00
isGlyphLocationsLong, hintsValid,
dupFirstEntry) {
var 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;
};
} else {
itemSize = 2;
itemDecode = function fontItemDecode(data, offset) {
2011-09-19 12:44:25 +09:00
return (data[offset] << 9) | (data[offset + 1] << 1);
};
itemEncode = function fontItemEncode(data, offset, value) {
2011-09-19 12:44:25 +09:00
data[offset] = (value >> 9) & 0xFF;
data[offset + 1] = (value >> 1) & 0xFF;
};
}
var locaData = loca.data;
2013-11-03 08:56:48 +09:00
var locaDataSize = itemSize * (1 + numGlyphs);
// is loca.data too short or long?
if (locaData.length !== locaDataSize) {
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);
var startOffset = itemDecode(locaData, 0);
var writeOffset = 0;
var missingGlyphData = Object.create(null);
itemEncode(locaData, 0, writeOffset);
2014-04-08 06:42:54 +09:00
var i, j;
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
var endOffset = itemDecode(locaData, j);
2014-03-12 04:16:27 +09:00
if (endOffset > oldGlyfDataLength &&
((oldGlyfDataLength + 3) & ~3) === endOffset) {
// Aspose breaks fonts by aligning the glyphs to the qword, but not
// the glyf table size, which makes last glyph out of range.
endOffset = oldGlyfDataLength;
}
if (endOffset > oldGlyfDataLength) {
// glyph end offset points outside glyf data, rejecting the glyph
itemEncode(locaData, j, writeOffset);
startOffset = endOffset;
continue;
}
2014-10-04 02:35:49 +09:00
if (startOffset === endOffset) {
missingGlyphData[i] = true;
}
var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
2013-03-18 22:06:59 +09:00
newGlyfData, writeOffset, hintsValid);
writeOffset += newLength;
itemEncode(locaData, j, writeOffset);
startOffset = endOffset;
}
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(
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
2014-04-08 06:42:54 +09:00
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
itemEncode(locaData, j, simpleGlyph.length);
2014-03-18 01:34:30 +09:00
}
glyf.data = simpleGlyph;
2014-10-04 02:35:49 +09:00
return missingGlyphData;
}
2013-07-30 05:24:32 +09:00
if (dupFirstEntry) {
var 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);
2013-11-03 08:56:48 +09:00
itemEncode(loca.data, locaData.length - itemSize,
2013-07-30 05:24:32 +09:00
writeOffset + firstEntryLength);
} else {
glyf.data = newGlyfData.subarray(0, writeOffset);
}
2014-10-04 02:35:49 +09:00
return missingGlyphData;
}
function readPostScriptTable(post, properties, maxpNumGlyphs) {
var start = (font.start ? font.start : 0) + post.offset;
font.pos = start;
var length = post.length, end = start + length;
var version = font.getInt32();
// skip rest to the tables
font.getBytes(28);
var glyphNames;
2012-11-08 02:03:08 +09:00
var valid = true;
2014-04-08 06:42:54 +09:00
var i;
switch (version) {
case 0x00010000:
glyphNames = MacStandardGlyphOrdering;
break;
case 0x00020000:
var numGlyphs = font.getUint16();
if (numGlyphs !== maxpNumGlyphs) {
valid = false;
break;
}
var glyphNameIndexes = [];
2014-04-08 06:42:54 +09:00
for (i = 0; i < numGlyphs; ++i) {
var index = font.getUint16();
2012-11-08 02:03:08 +09:00
if (index >= 32768) {
valid = false;
break;
}
glyphNameIndexes.push(index);
}
if (!valid) {
break;
}
var customNames = [];
var strBuf = [];
while (font.pos < end) {
var stringLength = font.getByte();
strBuf.length = stringLength;
2014-04-08 06:42:54 +09:00
for (i = 0; i < stringLength; ++i) {
strBuf[i] = String.fromCharCode(font.getByte());
2013-07-01 05:45:15 +09:00
}
customNames.push(strBuf.join(''));
}
glyphNames = [];
2014-04-08 06:42:54 +09:00
for (i = 0; i < numGlyphs; ++i) {
var 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);
2012-11-08 02:03:08 +09:00
valid = false;
if (properties.defaultEncoding) {
glyphNames = properties.defaultEncoding;
}
break;
}
properties.glyphNames = glyphNames;
2012-11-08 02:03:08 +09:00
return valid;
}
2012-11-08 09:24:13 +09:00
function readNameTable(nameTable) {
var start = (font.start ? font.start : 0) + nameTable.offset;
font.pos = start;
var names = [[], []];
var length = nameTable.length, end = start + length;
var format = font.getUint16();
2012-11-08 09:24:13 +09:00
var 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();
2012-11-08 09:24:13 +09:00
var records = [];
var NAME_RECORD_LENGTH = 12;
2014-04-08 06:42:54 +09:00
var i, ii;
for (i = 0; i < numRecords &&
2012-11-08 09:24:13 +09:00
font.pos + NAME_RECORD_LENGTH <= end; i++) {
var r = {
platform: font.getUint16(),
encoding: font.getUint16(),
language: font.getUint16(),
name: font.getUint16(),
length: font.getUint16(),
offset: font.getUint16()
2012-11-08 09:24:13 +09:00
};
// 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)) {
2012-11-08 09:24:13 +09:00
records.push(r);
}
}
2014-04-08 06:42:54 +09:00
for (i = 0, ii = records.length; i < ii; i++) {
2012-11-08 09:24:13 +09:00
var record = records[i];
if (record.length <= 0) {
continue; // Nothing to process, ignoring.
}
2012-11-08 09:24:13 +09:00
var pos = start + stringsStart + record.offset;
if (pos + record.length > end) {
continue; // outside of name table, ignoring
}
font.pos = pos;
var nameIndex = record.name;
if (record.encoding) {
// unicode
var str = '';
for (var j = 0, jj = record.length; j < jj; j += 2) {
str += String.fromCharCode(font.getUint16());
2012-11-08 09:24:13 +09:00
}
names[1][nameIndex] = str;
} else {
names[0][nameIndex] = bytesToString(font.getBytes(record.length));
}
}
return names;
}
var 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,
2013-03-23 18:08:18 +09:00
1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
2013-03-18 22:06:59 +09:00
0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2,
0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
-1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
2013-03-23 18:08:18 +09:00
-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,
2013-03-23 18:08:18 +09:00
-999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
// 0xC0-DF == -1 and 0xE0-FF == -2
function sanitizeTTProgram(table, ttContext) {
var data = table.data;
2014-04-08 06:42:54 +09:00
var i = 0, j, n, b, funcId, pc, lastEndf = 0, lastDeff = 0;
var stack = [];
2013-03-23 18:08:18 +09:00
var callstack = [];
var functionsCalled = [];
var tooComplexToFollowFunctions =
ttContext.tooComplexToFollowFunctions;
2013-03-23 18:08:18 +09:00
var inFDEF = false, ifLevel = 0, inELSE = 0;
for (var ii = data.length; i < ii;) {
var 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++];
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
i += n;
} else {
2014-04-08 06:42:54 +09:00
for (j = 0; j < n; j++) {
2013-03-23 18:08:18 +09:00
stack.push(data[i++]);
}
}
} else if (op === 0x41) { // NPUSHW - pushes n words
n = data[i++];
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
i += n * 2;
} else {
2014-04-08 06:42:54 +09:00
for (j = 0; j < n; j++) {
b = data[i++];
2013-03-23 18:08:18 +09:00
stack.push((b << 8) | data[i++]);
}
}
} else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
n = op - 0xB0 + 1;
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
i += n;
} else {
2014-04-08 06:42:54 +09:00
for (j = 0; j < n; j++) {
2013-03-23 18:08:18 +09:00
stack.push(data[i++]);
}
}
} else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
n = op - 0xB8 + 1;
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
i += n * 2;
} else {
2014-04-08 06:42:54 +09:00
for (j = 0; j < n; j++) {
b = data[i++];
2013-03-23 18:08:18 +09:00
stack.push((b << 8) | data[i++]);
}
}
} else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
2013-03-23 18:08:18 +09:00
if (!inFDEF && !inELSE) {
// collecting inforamtion about which functions are used
2014-04-08 06:42:54 +09:00
funcId = stack[stack.length - 1];
2013-03-23 18:08:18 +09:00
ttContext.functionsUsed[funcId] = true;
if (funcId in ttContext.functionsStackDeltas) {
stack.length += ttContext.functionsStackDeltas[funcId];
} else if (funcId in ttContext.functionsDefined &&
functionsCalled.indexOf(funcId) < 0) {
callstack.push({data: data, i: i, stackTop: stack.length - 1});
functionsCalled.push(funcId);
2014-04-08 06:42:54 +09:00
pc = ttContext.functionsDefined[funcId];
2013-10-31 00:54:19 +09:00
if (!pc) {
warn('TT: CALL non-existent function');
ttContext.hintsValid = false;
return;
}
2013-03-23 18:08:18 +09:00
data = pc.data;
i = pc.i;
}
}
} else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
warn('TT: nested FDEFs not allowed');
tooComplexToFollowFunctions = true;
}
2013-03-23 18:08:18 +09:00
inFDEF = true;
// collecting inforamtion about which functions are defined
lastDeff = i;
2014-04-08 06:42:54 +09:00
funcId = stack.pop();
2013-03-23 18:08:18 +09:00
ttContext.functionsDefined[funcId] = {data: data, i: i};
} else if (op === 0x2D) { // ENDF - end of function
2013-03-23 18:08:18 +09:00
if (inFDEF) {
inFDEF = false;
lastEndf = i;
} else {
2014-04-08 06:42:54 +09:00
pc = callstack.pop();
2013-10-31 00:54:19 +09:00
if (!pc) {
warn('TT: ENDF bad stack');
ttContext.hintsValid = false;
return;
}
2014-04-08 06:42:54 +09:00
funcId = functionsCalled.pop();
2013-03-23 18:08:18 +09:00
data = pc.data;
i = pc.i;
ttContext.functionsStackDeltas[funcId] =
stack.length - pc.stackTop;
}
} else if (op === 0x89) { // IDEF - instruction definition
2013-03-23 18:08:18 +09:00
if (inFDEF || inELSE) {
warn('TT: nested IDEFs not allowed');
tooComplexToFollowFunctions = true;
}
inFDEF = true;
// recording it as a function to track ENDF
lastDeff = i;
2013-03-23 18:08:18 +09:00
} 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) {
var offset = stack[stack.length - 1];
// only jumping forward to prevent infinite loop
2014-03-18 01:34:30 +09:00
if (offset > 0) {
i += offset - 1;
}
}
}
// Adjusting stack not extactly, but just enough to get function id
2013-03-23 18:08:18 +09:00
if (!inFDEF && !inELSE) {
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
if (op >= 0x71 && op <= 0x75) {
n = stack.pop();
if (n === 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--;
}
}
}
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
var content = [data];
if (i > data.length) {
content.push(new Uint8Array(i - data.length));
}
if (lastDeff > lastEndf) {
2013-03-23 18:08:18 +09:00
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]));
}
2013-03-23 18:08:18 +09:00
foldTTTable(table, content);
}
2013-03-18 22:06:59 +09:00
function checkInvalidFunctions(ttContext, maxFunctionDefs) {
if (ttContext.tooComplexToFollowFunctions) {
return;
}
if (ttContext.functionsDefined.length > maxFunctionDefs) {
warn('TT: more functions defined than expected');
ttContext.hintsValid = false;
return;
}
2013-03-18 22:06:59 +09:00
for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
if (j > maxFunctionDefs) {
warn('TT: invalid function id: ' + j);
ttContext.hintsValid = false;
return;
2013-03-23 18:08:18 +09:00
}
2013-03-18 22:06:59 +09:00
if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
warn('TT: undefined function: ' + j);
ttContext.hintsValid = false;
return;
}
}
2013-03-23 18:08:18 +09:00
}
function foldTTTable(table, content) {
if (content.length > 1) {
// concatenating the content items
var newLength = 0;
2014-04-08 06:42:54 +09:00
var 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;
2014-04-08 06:42:54 +09:00
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) {
var ttContext = {
functionsDefined: [],
functionsUsed: [],
2013-03-23 18:08:18 +09:00
functionsStackDeltas: [],
2013-03-18 22:06:59 +09:00
tooComplexToFollowFunctions: false,
hintsValid: true
};
2013-03-23 18:08:18 +09:00
if (fpgm) {
sanitizeTTProgram(fpgm, ttContext);
}
if (prep) {
sanitizeTTProgram(prep, ttContext);
}
if (fpgm) {
2013-03-18 22:06:59 +09:00
checkInvalidFunctions(ttContext, maxFunctionDefs);
}
if (cvt && (cvt.length & 1)) {
var cvtData = new Uint8Array(cvt.length + 1);
cvtData.set(cvt.data);
cvt.data = cvtData;
}
2013-03-18 22:06:59 +09:00
return ttContext.hintsValid;
}
// The following steps modify the original font data, making copy
font = new Stream(new Uint8Array(font.getBytes()));
2013-06-25 05:33:50 +09:00
var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
var header = readOpenTypeHeader(font);
var numTables = header.numTables;
2014-04-08 06:42:54 +09:00
var cff, cffFile;
var tables = Object.create(null);
tables['OS/2'] = null;
tables['cmap'] = null;
tables['head'] = null;
tables['hhea'] = null;
tables['hmtx'] = null;
tables['maxp'] = null;
tables['name'] = null;
tables['post'] = null;
var table;
for (var i = 0; i < numTables; i++) {
2014-04-08 06:42:54 +09:00
table = readTableEntry(font);
2013-06-25 05:33:50 +09:00
if (VALID_TABLES.indexOf(table.tag) < 0) {
continue; // skipping table if it's not a required or optional table
}
2013-11-02 07:13:31 +09:00
if (table.length === 0) {
continue; // skipping empty tables
}
2013-06-25 05:33:50 +09:00
tables[table.tag] = table;
}
2013-06-25 05:33:50 +09:00
var isTrueType = !tables['CFF '];
if (!isTrueType) {
// OpenType font
if ((header.version === 'OTTO' && !properties.composite) ||
!tables['head'] || !tables['hhea'] || !tables['maxp'] ||
!tables['post']) {
2013-06-25 08:45:31 +09:00
// no major tables: throwing everything at CFFFont
2014-04-08 06:42:54 +09:00
cffFile = new Stream(tables['CFF '].data);
cff = new CFFFont(cffFile, properties);
2013-06-25 08:45:31 +09:00
adjustWidths(properties);
2013-06-25 08:45:31 +09:00
return this.convert(name, cff, properties);
}
delete tables['glyf'];
delete tables['loca'];
delete tables['fpgm'];
delete tables['prep'];
2013-06-25 05:33:50 +09:00
delete tables['cvt '];
this.isOpenType = true;
2013-06-25 05:33:50 +09:00
} else {
if (!tables['loca']) {
error('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),
};
2013-06-25 05:33:50 +09:00
}
this.isOpenType = false;
2013-06-25 05:33:50 +09:00
}
if (!tables['maxp']) {
2013-06-25 08:45:31 +09:00
error('Required "maxp" table is not found');
}
font.pos = (font.start || 0) + tables['maxp'].offset;
var version = font.getInt32();
var numGlyphs = font.getUint16();
2013-03-18 22:06:59 +09:00
var maxFunctionDefs = 0;
if (version >= 0x00010000 && tables['maxp'].length >= 22) {
2013-11-03 08:16:24 +09:00
// maxZones can be invalid
font.pos += 8;
var maxZones = font.getUint16();
2013-11-03 08:16:24 +09:00
if (maxZones > 2) { // reset to 2 if font has invalid maxZones
tables['maxp'].data[14] = 0;
tables['maxp'].data[15] = 2;
2013-11-03 08:16:24 +09:00
}
font.pos += 4;
maxFunctionDefs = font.getUint16();
2013-03-18 22:06:59 +09:00
}
2013-07-30 05:24:32 +09:00
var dupFirstEntry = false;
if (properties.type === 'CIDFontType2' && properties.toUnicode &&
2014-08-07 10:02:11 +09:00
properties.toUnicode.get(0) > '\u0000') {
2013-07-30 05:24:32 +09:00
// oracle's defect (see 3427), duplicating first entry
dupFirstEntry = true;
numGlyphs++;
tables['maxp'].data[4] = numGlyphs >> 8;
tables['maxp'].data[5] = numGlyphs & 255;
2013-07-30 05:24:32 +09:00
}
var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'],
tables['cvt '], maxFunctionDefs);
2013-03-18 22:06:59 +09:00
if (!hintsValid) {
delete tables['fpgm'];
delete tables['prep'];
delete tables['cvt '];
2013-03-18 22:06:59 +09:00
}
2013-06-25 05:33:50 +09:00
// Ensure the hmtx table contains the advance width and
// sidebearings information for numGlyphs in the maxp table
sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
if (!tables['head']) {
2013-06-25 05:33:50 +09:00
error('Required "head" table is not found');
}
sanitizeHead(tables['head'], numGlyphs,
isTrueType ? tables['loca'].length : 0);
2013-06-25 05:33:50 +09:00
var missingGlyphs = Object.create(null);
2013-06-25 05:33:50 +09:00
if (isTrueType) {
var isGlyphLocationsLong = int16(tables['head'].data[50],
tables['head'].data[51]);
missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'],
numGlyphs, isGlyphLocationsLong,
hintsValid, dupFirstEntry);
}
if (!tables['hhea']) {
2013-06-25 08:45:31 +09:00
error('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;
}
2015-11-07 05:47:10 +09:00
// Extract some more font properties from the OpenType head and
// hhea tables; yMin and descent value are always negative.
var 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])
2015-11-07 05:47:10 +09:00
};
// 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']) {
var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
2012-11-08 02:03:08 +09:00
if (!valid) {
tables['post'] = null;
2012-11-08 02:03:08 +09:00
}
}
var charCodeToGlyphId = [], charCode;
var toUnicode = properties.toUnicode, widths = properties.widths;
var skipToUnicode = (toUnicode instanceof IdentityToUnicodeMap ||
toUnicode.length === 0x10000);
// Helper function to try to skip mapping of empty glyphs.
// Note: In some cases, just relying on the glyph data doesn't work,
// hence we also use a few heuristics to fix various PDF files.
function hasGlyph(glyphId, charCode, widthCode) {
if (!missingGlyphs[glyphId]) {
return true;
}
if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
return true;
}
if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
return true;
}
return false;
}
if (properties.composite) {
var cidToGidMap = properties.cidToGidMap || [];
var isCidToGidMapEmpty = cidToGidMap.length === 0;
properties.cMap.forEach(function(charCode, cid) {
assert(cid <= 0xffff, 'Max size of CID is 65,535');
var glyphId = -1;
if (isCidToGidMapEmpty) {
glyphId = cid;
} else if (cidToGidMap[cid] !== undefined) {
glyphId = cidToGidMap[cid];
2013-07-30 05:24:32 +09:00
}
2014-10-04 02:35:49 +09:00
if (glyphId >= 0 && glyphId < numGlyphs &&
hasGlyph(glyphId, charCode, cid)) {
charCodeToGlyphId[charCode] = glyphId;
}
});
if (dupFirstEntry && (isCidToGidMapEmpty || !charCodeToGlyphId[0])) {
// We don't duplicate the first entry in the `charCodeToGlyphId` map
// if the font has a `CIDToGIDMap` which has already mapped the first
// entry to a non-zero `glyphId` (fixes issue7544.pdf).
charCodeToGlyphId[0] = numGlyphs - 1;
2011-11-29 10:47:37 +09:00
}
} 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(tables['cmap'], font, this.isSymbolicFont,
properties.hasEncoding);
var cmapPlatformId = cmapTable.platformId;
var cmapEncodingId = cmapTable.encodingId;
var cmapMappings = cmapTable.mappings;
var cmapMappingsLength = cmapMappings.length;
// The spec seems to imply that if the font is symbolic the encoding
// should be ignored, this doesn't appear to work for 'preistabelle.pdf'
// where the the font is symbolic and it has an encoding.
if (properties.hasEncoding &&
(cmapPlatformId === 3 && cmapEncodingId === 1 ||
cmapPlatformId === 1 && cmapEncodingId === 0) ||
(cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
2016-01-22 06:18:46 +09:00
!!getEncoding(properties.baseEncodingName))) { // Temporary hack
// When no preferred cmap table was found and |baseEncodingName| is
// one of the predefined encodings, we seem to obtain a better
// |charCodeToGlyphId| map from the code below (fixes bug 1057544).
// TODO: Note that this is a hack which should be removed as soon as
// we have proper support for more exotic cmap tables.
var baseEncoding = [];
if (properties.baseEncodingName === 'MacRomanEncoding' ||
properties.baseEncodingName === 'WinAnsiEncoding') {
2016-01-22 06:18:46 +09:00
baseEncoding = getEncoding(properties.baseEncodingName);
}
2016-01-22 05:47:48 +09:00
var glyphsUnicodeMap = getGlyphsUnicode();
2014-04-08 06:42:54 +09:00
for (charCode = 0; charCode < 256; charCode++) {
var glyphName, standardGlyphName;
if (this.differences && charCode in this.differences) {
glyphName = this.differences[charCode];
} else if (charCode in baseEncoding &&
baseEncoding[charCode] !== '') {
glyphName = baseEncoding[charCode];
} else {
2016-01-22 06:18:46 +09:00
glyphName = StandardEncoding[charCode];
}
if (!glyphName) {
continue;
}
// Ensure that non-standard glyph names are resolved to valid ones.
standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
var unicodeOrCharCode, isUnicode = false;
if (cmapPlatformId === 3 && cmapEncodingId === 1) {
unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
isUnicode = true;
} else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
// TODO: the encoding needs to be updated with mac os table.
unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
}
var found = false;
2014-04-08 06:42:54 +09:00
for (i = 0; i < cmapMappingsLength; ++i) {
if (cmapMappings[i].charCode !== unicodeOrCharCode) {
continue;
}
var code = isUnicode ? charCode : unicodeOrCharCode;
if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
found = true;
break;
}
}
if (!found && properties.glyphNames) {
// Try to map using the post table.
var glyphId = properties.glyphNames.indexOf(glyphName);
// The post table ought to use the same kind of glyph names as the
// `differences` array, but check the standard ones as a fallback.
if (glyphId === -1 && standardGlyphName !== glyphName) {
glyphId = properties.glyphNames.indexOf(standardGlyphName);
}
if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
charCodeToGlyphId[charCode] = glyphId;
found = true;
}
}
if (!found) {
charCodeToGlyphId[charCode] = 0; // notdef
}
}
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
// Default Unicode semantics, use the charcodes as is.
for (i = 0; i < cmapMappingsLength; ++i) {
charCodeToGlyphId[cmapMappings[i].charCode] =
cmapMappings[i].glyphId;
}
} else {
// For (3, 0) cmap tables:
// The charcode key being stored in charCodeToGlyphId is the lower
// byte of the two-byte charcodes of the cmap table since according to
// the spec: 'each byte from the string shall be prepended with the
// high byte of the range [of charcodes in the cmap table], to form
// a two-byte character, which shall be used to select the
// associated glyph description from the subtable'.
//
// For (1, 0) cmap tables:
// 'single bytes from the string shall be used to look up the
// associated glyph descriptions from the subtable'. This means
// charcodes in the cmap will be single bytes, so no-op since
// glyph.charCode & 0xFF === glyph.charCode
2014-04-08 06:42:54 +09:00
for (i = 0; i < cmapMappingsLength; ++i) {
charCode = cmapMappings[i].charCode & 0xFF;
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
}
}
}
if (charCodeToGlyphId.length === 0) {
// defines at least one glyph
charCodeToGlyphId[0] = 0;
}
// Converting glyphs and ids into font's cmap table
var newMapping = adjustMapping(charCodeToGlyphId, properties);
this.toFontChar = newMapping.toFontChar;
tables['cmap'] = {
tag: 'cmap',
data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
};
2013-06-25 05:33:50 +09:00
if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
tables['OS/2'] = {
2012-04-07 07:52:57 +09:00
tag: 'OS/2',
2014-08-10 13:34:01 +09:00
data: createOS2Table(properties, newMapping.charCodeToGlyphId,
2015-11-07 05:47:10 +09:00
metricsOverride)
2013-06-25 05:33:50 +09:00
};
2012-04-07 07:52:57 +09:00
}
// Rewrite the 'post' table if needed
if (!tables['post']) {
tables['post'] = {
tag: 'post',
2014-08-10 13:34:01 +09:00
data: createPostTable(properties)
2013-06-25 05:33:50 +09:00
};
}
2013-06-25 08:45:31 +09:00
if (!isTrueType) {
try {
// Trying to repair CFF file
2014-04-08 06:42:54 +09:00
cffFile = new Stream(tables['CFF '].data);
var parser = new CFFParser(cffFile, properties,
SEAC_ANALYSIS_ENABLED);
2014-04-08 06:42:54 +09:00
cff = parser.parse();
2013-06-25 08:45:31 +09:00
var compiler = new CFFCompiler(cff);
tables['CFF '].data = compiler.compile();
} catch (e) {
warn('Failed to compile font ' + properties.loadedName);
}
}
2012-11-08 09:24:13 +09:00
// Re-creating 'name' table
if (!tables['name']) {
tables['name'] = {
tag: 'name',
2014-08-10 13:34:01 +09:00
data: createNameTable(this.name)
2013-06-25 05:33:50 +09:00
};
2012-11-08 09:24:13 +09:00
} else {
// ... using existing 'name' table as prototype
var namePrototype = readNameTable(tables['name']);
tables['name'].data = createNameTable(name, namePrototype);
}
var builder = new OpenTypeFileBuilder(header.version);
for (var tableTag in tables) {
builder.addTable(tableTag, tables[tableTag].data);
}
return builder.toArray();
},
convert: function Font_convert(fontName, font, properties) {
2014-02-25 10:39:51 +09:00
// TODO: Check the charstring widths to determine this.
properties.fixedPitch = false;
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);
}
var mapping = font.getGlyphMapping(properties);
var newMapping = adjustMapping(mapping, properties);
this.toFontChar = newMapping.toFontChar;
var numGlyphs = font.numGlyphs;
function getCharCodes(charCodeToGlyphId, glyphId) {
var charCodes = null;
2014-04-12 01:55:39 +09:00
for (var charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
if (!charCodes) {
charCodes = [];
}
charCodes.push(charCode | 0);
2014-04-12 01:55:39 +09:00
}
}
return charCodes;
}
function createCharCode(charCodeToGlyphId, glyphId) {
for (var charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
return charCode | 0;
}
2014-04-12 01:55:39 +09:00
}
newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] =
glyphId;
return newMapping.nextAvailableFontCharCode++;
2014-04-12 01:55:39 +09:00
}
2013-02-27 03:00:20 +09:00
var seacs = font.seacs;
if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
2013-02-27 03:00:20 +09:00
var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
var charset = font.getCharset();
var seacMap = Object.create(null);
for (var glyphId in seacs) {
glyphId |= 0;
var seac = seacs[glyphId];
2016-01-22 06:18:46 +09:00
var baseGlyphName = StandardEncoding[seac[2]];
var accentGlyphName = StandardEncoding[seac[3]];
var baseGlyphId = charset.indexOf(baseGlyphName);
var accentGlyphId = charset.indexOf(accentGlyphName);
if (baseGlyphId < 0 || accentGlyphId < 0) {
2013-02-27 03:00:20 +09:00
continue;
}
var accentOffset = {
x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
};
2014-04-12 01:55:39 +09:00
var charCodes = getCharCodes(mapping, glyphId);
if (!charCodes) {
2014-04-12 01:55:39 +09:00
// There's no point in mapping it if the char code was never mapped
// to begin with.
continue;
}
for (var i = 0, ii = charCodes.length; i < ii; i++) {
var 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(charCodeToGlyphId,
baseGlyphId);
var accentFontCharCode = createCharCode(charCodeToGlyphId,
accentGlyphId);
seacMap[charCode] = {
baseFontCharCode: baseFontCharCode,
accentFontCharCode: accentFontCharCode,
accentOffset: accentOffset
};
}
2013-02-27 03:00:20 +09:00
}
properties.seacMap = seacMap;
}
2013-01-04 09:39:06 +09:00
var unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
var builder = new OpenTypeFileBuilder('\x4F\x54\x54\x4F');
// PostScript Font Program
builder.addTable('CFF ', font.data);
// OS/2 and Windows Specific metrics
2014-08-10 13:34:01 +09:00
builder.addTable('OS/2', createOS2Table(properties,
newMapping.charCodeToGlyphId));
// Character to glyphs mapping
builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId,
numGlyphs));
// Font header
2014-08-10 13:34:01 +09:00
builder.addTable('head',
2014-03-18 01:34:30 +09:00
'\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
2011-07-03 08:17:28 +09:00
// Horizontal header
2014-08-10 13:34:01 +09:00
builder.addTable('hhea',
2014-03-18 01:34:30 +09:00
'\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
2011-07-03 08:17:28 +09:00
// Horizontal metrics
builder.addTable('hmtx', (function fontFieldsHmtx() {
var charstrings = font.charstrings;
2014-08-15 06:11:09 +09:00
var cffWidths = font.cff ? font.cff.widths : null;
2011-07-06 15:06:45 +09:00
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
for (var i = 1, ii = numGlyphs; i < ii; i++) {
2014-08-15 06:11:09 +09:00
var width = 0;
if (charstrings) {
var 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);
}
2014-08-10 13:34:01 +09:00
return hmtx;
})());
2011-07-03 08:17:28 +09:00
// Maximum profile
2014-08-10 13:34:01 +09:00
builder.addTable('maxp',
2014-03-18 01:34:30 +09:00
'\x00\x00\x50\x00' + // Version number
string16(numGlyphs)); // Num of glyphs
2011-07-03 08:17:28 +09:00
// Naming tables
2014-08-10 13:34:01 +09:00
builder.addTable('name', createNameTable(fontName));
2011-07-03 08:17:28 +09:00
2016-07-17 21:33:41 +09:00
// PostScript information
2014-08-10 13:34:01 +09:00
builder.addTable('post', createPostTable(properties));
return builder.toArray();
},
get spaceWidth() {
2012-09-22 18:18:26 +09:00
if ('_shadowWidth' in this) {
return this._shadowWidth;
}
// 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];
// if possible, getting width by glyph name
if (glyphName in this.widths) {
width = this.widths[glyphName];
break;
}
2016-01-22 05:47:48 +09:00
var glyphsUnicodeMap = getGlyphsUnicode();
var glyphUnicode = glyphsUnicodeMap[glyphName];
// finding the charcode via unicodeToCID map
var charcode = 0;
if (this.composite) {
if (this.cMap.contains(glyphUnicode)) {
charcode = this.cMap.lookup(glyphUnicode);
}
}
// ... via toUnicode map
if (!charcode && this.toUnicode) {
2014-08-07 10:02:11 +09:00
charcode = this.toUnicode.charCodeOf(glyphUnicode);
2014-03-18 01:34:30 +09:00
}
// setting it to unicode if negative or undefined
2014-03-18 01:34:30 +09:00
if (charcode <= 0) {
charcode = glyphUnicode;
2014-03-18 01:34:30 +09:00
}
// trying to get width via charcode
width = this.widths[charcode];
2014-03-18 01:34:30 +09:00
if (width) {
break; // the non-zero width found
2014-03-18 01:34:30 +09:00
}
}
2013-01-04 09:39:06 +09:00
width = width || this.defaultWidth;
2012-09-22 18:18:26 +09:00
// Do not shadow the property here. See discussion:
// https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
this._shadowWidth = width;
return width;
},
2015-11-02 23:54:15 +09:00
charToGlyph: function Font_charToGlyph(charcode, isSpace) {
var fontCharCode, width, operatorListId;
2012-04-09 00:57:55 +09:00
var widthCode = charcode;
if (this.cMap && this.cMap.contains(charcode)) {
widthCode = this.cMap.lookup(charcode);
}
2014-04-08 06:42:54 +09:00
width = this.widths[widthCode];
width = isNum(width) ? width : this.defaultWidth;
var vmetric = this.vmetrics && this.vmetrics[widthCode];
2011-11-25 00:38:09 +09:00
2014-08-07 10:02:11 +09:00
var unicode = this.toUnicode.get(charcode) || charcode;
if (typeof unicode === 'number') {
unicode = String.fromCharCode(unicode);
2013-02-27 03:00:20 +09:00
}
2011-11-25 00:38:09 +09:00
var 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) {
fontCharCode = mapSpecialUnicodeValues(fontCharCode);
}
if (this.isType3Font) {
// Font char code in this case is actually a glyph name.
operatorListId = fontCharCode;
}
2013-02-27 03:00:20 +09:00
var accent = null;
if (this.seacMap && this.seacMap[charcode]) {
isInFont = true;
var seac = this.seacMap[charcode];
fontCharCode = seac.baseFontCharCode;
2013-02-27 03:00:20 +09:00
accent = {
fontChar: String.fromCharCode(seac.accentFontCharCode),
2013-02-27 03:00:20 +09:00
offset: seac.accentOffset
};
}
var fontChar = String.fromCharCode(fontCharCode);
var 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: function Font_charsToGlyphs(chars) {
var charsCache = this.charsCache;
2014-04-08 06:42:54 +09:00
var glyphs, glyph, charcode;
// if we translated this string before, just grab it from the cache
if (charsCache) {
2011-09-16 09:26:32 +09:00
glyphs = charsCache[chars];
2014-03-18 01:34:30 +09:00
if (glyphs) {
2011-09-16 09:26:32 +09:00
return glyphs;
2014-03-18 01:34:30 +09:00
}
}
// lazily create the translation cache
2014-03-18 01:34:30 +09:00
if (!charsCache) {
charsCache = this.charsCache = Object.create(null);
2014-03-18 01:34:30 +09:00
}
2011-09-16 09:26:32 +09:00
glyphs = [];
var charsCacheKey = chars;
2014-04-08 06:42:54 +09:00
var 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);
while (i < chars.length) {
this.cMap.readCharCode(chars, i, c);
charcode = c.charcode;
var length = c.length;
i += length;
2015-11-02 23:54:15 +09:00
// Space is char with code 0x20 and length 1 in multiple-byte codes.
var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
glyph = this.charToGlyph(charcode, isSpace);
2011-09-16 09:26:32 +09:00
glyphs.push(glyph);
}
} else {
2014-04-08 06:42:54 +09:00
for (i = 0, ii = chars.length; i < ii; ++i) {
charcode = chars.charCodeAt(i);
2015-11-02 23:54:15 +09:00
glyph = this.charToGlyph(charcode, charcode === 0x20);
2011-09-16 09:26:32 +09:00
glyphs.push(glyph);
}
}
// Enter the translated string into the cache
return (charsCache[charsCacheKey] = glyphs);
}
};
2011-12-09 07:18:43 +09:00
return Font;
})();
var ErrorFont = (function ErrorFontClosure() {
function ErrorFont(error) {
this.error = error;
this.loadedName = 'g_font_error';
this.loading = false;
}
ErrorFont.prototype = {
charsToGlyphs: function ErrorFont_charsToGlyphs() {
return [];
},
exportData: function ErrorFont_exportData() {
return {error: this.error};
}
};
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);
2014-04-08 06:42:54 +09:00
var glyphId, charCode, baseEncoding;
var isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
2014-04-08 06:42:54 +09:00
if (properties.baseEncodingName) {
// If a valid base encoding name was used, the mapping is initialized with
// that.
2016-01-22 06:18:46 +09:00
baseEncoding = getEncoding(properties.baseEncodingName);
2014-04-08 06:42:54 +09:00
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.
2014-04-08 06:42:54 +09:00
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.
2016-01-22 06:18:46 +09:00
baseEncoding = StandardEncoding;
2014-04-08 06:42:54 +09:00
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) {
2014-04-08 06:42:54 +09:00
for (charCode in differences) {
var glyphName = differences[charCode];
2014-04-08 06:42:54 +09:00
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;
}
2012-03-11 12:12:33 +09:00
// 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 && isSpace(streamBytes[i])) {
i++;
}
found = true;
break;
}
i++;
}
return {
found: 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,
};
}
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];
}
2013-10-31 23:10:08 +09:00
2016-07-17 21:33:41 +09:00
// Get the data block containing glyphs and subrs information
var headerBlock = getHeaderBlock(file, headerBlockLength);
headerBlockLength = headerBlock.length;
2016-04-01 20:37:00 +09:00
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];
}
2013-10-31 23:10:08 +09:00
// Decrypt the data blocks and retrieve it's content
var eexecBlock = getEexecBlock(file, eexecBlockLength);
eexecBlockLength = eexecBlock.length;
2016-04-01 20:37:00 +09:00
var eexecBlockParser = new Type1Parser(eexecBlock.stream, true,
SEAC_ANALYSIS_ENABLED);
var data = eexecBlockParser.extractFontProgram();
for (var info in data.properties) {
properties[info] = data.properties[info];
}
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);
}
2011-06-11 10:25:58 +09:00
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;
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;
}
}
2013-02-01 06:13:36 +09:00
}
2011-06-11 10:25:58 +09:00
return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
},
2011-06-11 10:25:58 +09:00
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;
}
2013-02-27 03:00:20 +09:00
}
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]);
2014-03-18 01:34:30 +09:00
}
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 = [0];
var i, ii;
for (i = 0; i < count; i++) {
var index = CFFStandardStrings.indexOf(charstrings[i].glyphName);
// TODO: Insert the string and correctly map it. Previously it was
// thought mapping names that aren't in the standard strings to .notdef
// was fine, however in issue818 when mapping them all to .notdef the
// adieresis glyph no longer worked.
if (index === -1) {
index = 0;
}
charsetArray.push((index >> 8) & 0xff, index & 0xff);
}
cff.charset = new CFFCharset(false, 0, [], charsetArray);
var charStringsIndex = new CFFIndex();
charStringsIndex.add([0x8B, 0x0E]); // .notdef
for (i = 0; i < count; i++) {
var glyph = glyphs[i];
// If the CharString outline is empty, replace it with .notdef to
// prevent OTS from rejecting the font (fixes bug1252420.pdf).
if (glyph.length === 0) {
charStringsIndex.add([0x8B, 0x0E]); // .notdef
continue;
}
charStringsIndex.add(glyph);
}
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 (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;
})();
2012-03-11 12:37:22 +09:00
var CFFFont = (function CFFFontClosure() {
function CFFFont(file, properties) {
2011-07-21 07:00:16 +09:00
this.properties = properties;
var parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
this.cff = parser.parse();
var compiler = new CFFCompiler(this.cff);
this.seacs = this.cff.seacs;
2012-03-11 12:12:33 +09:00
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;
}
2011-09-12 05:04:17 +09:00
}
2012-03-11 12:37:22 +09:00
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;
2014-04-08 06:42:54 +09:00
var glyphId;
if (properties.composite) {
charCodeToGlyphId = Object.create(null);
if (cff.isCIDFont) {
// If the font is actually a CID font then we should use the charset
// to map CIDs to GIDs.
2014-04-08 06:42:54 +09:00
for (glyphId = 0; glyphId < charsets.length; glyphId++) {
var cid = charsets[glyphId];
var 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.
2014-04-08 06:42:54 +09:00
for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
charCodeToGlyphId[glyphId] = glyphId;
}
2011-10-20 03:14:13 +09:00
}
return charCodeToGlyphId;
2011-10-20 03:14:13 +09:00
}
var encoding = cff.encoding ? cff.encoding.encoding : null;
charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
return charCodeToGlyphId;
2012-03-11 12:12:33 +09:00
}
};
2012-03-11 12:37:22 +09:00
return CFFFont;
2012-03-11 12:12:33 +09:00
})();
2013-02-27 03:00:20 +09:00
// Workaround for seac on Windows.
(function checkSeacSupport() {
if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
2013-02-27 03:00:20 +09:00
SEAC_ANALYSIS_ENABLED = true;
}
})();
// Workaround for Private Use Area characters in Chrome on Windows
// http://code.google.com/p/chromium/issues/detail?id=122465
// https://github.com/mozilla/pdf.js/issues/1689
(function checkChromeWindows() {
if (typeof navigator !== 'undefined' &&
/Windows.*Chrome/.test(navigator.userAgent)) {
SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
}
})();
exports.ErrorFont = ErrorFont;
exports.Font = Font;
exports.FontFlags = FontFlags;
exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
exports.ToUnicodeMap = ToUnicodeMap;
exports.getFontType = getFontType;
}));