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.
|
|
|
|
*/
|
2015-11-22 01:32:47 +09:00
|
|
|
/* globals PDFJS */
|
2011-06-12 10:00:45 +09:00
|
|
|
|
2011-10-26 10:18:22 +09:00
|
|
|
'use strict';
|
|
|
|
|
2015-11-22 01:32:47 +09:00
|
|
|
(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/parser',
|
|
|
|
'pdfjs/core/cmap', 'pdfjs/core/glyphlist', 'pdfjs/core/charsets',
|
2016-01-22 06:52:24 +09:00
|
|
|
'pdfjs/core/font_renderer', 'pdfjs/core/encodings',
|
2016-01-22 07:10:42 +09:00
|
|
|
'pdfjs/core/standard_fonts', 'pdfjs/core/unicode'], factory);
|
2015-11-22 01:32:47 +09:00
|
|
|
} else if (typeof exports !== 'undefined') {
|
|
|
|
factory(exports, require('../shared/util.js'), require('./primitives.js'),
|
|
|
|
require('./stream.js'), require('./parser.js'), require('./cmap.js'),
|
|
|
|
require('./glyphlist.js'), require('./charsets.js'),
|
2016-01-22 06:52:24 +09:00
|
|
|
require('./font_renderer.js'), require('./encodings.js'),
|
2016-01-22 07:10:42 +09:00
|
|
|
require('./standard_fonts'), require('./unicode.js'));
|
2015-11-22 01:32:47 +09:00
|
|
|
} else {
|
|
|
|
factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
|
|
|
|
root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser,
|
|
|
|
root.pdfjsCoreCMap, root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets,
|
2016-01-22 06:52:24 +09:00
|
|
|
root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings,
|
2016-01-22 07:10:42 +09:00
|
|
|
root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode);
|
2015-11-22 01:32:47 +09:00
|
|
|
}
|
|
|
|
}(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser,
|
2016-01-22 06:18:46 +09:00
|
|
|
coreCMap, coreGlyphList, coreCharsets, coreFontRenderer,
|
2016-01-22 07:10:42 +09:00
|
|
|
coreEncodings, coreStandardFonts, coreUnicode) {
|
2015-11-22 01:32:47 +09:00
|
|
|
|
|
|
|
var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
|
|
|
|
var FontType = sharedUtil.FontType;
|
|
|
|
var Util = sharedUtil.Util;
|
|
|
|
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 stringToBytes = sharedUtil.stringToBytes;
|
|
|
|
var string32 = sharedUtil.string32;
|
|
|
|
var warn = sharedUtil.warn;
|
|
|
|
var Name = corePrimitives.Name;
|
|
|
|
var Stream = coreStream.Stream;
|
|
|
|
var Lexer = coreParser.Lexer;
|
|
|
|
var CMapFactory = coreCMap.CMapFactory;
|
|
|
|
var IdentityCMap = coreCMap.IdentityCMap;
|
2016-01-22 05:47:48 +09:00
|
|
|
var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
|
|
|
|
var getDingbatsGlyphsUnicode = coreGlyphList.getDingbatsGlyphsUnicode;
|
2015-11-22 01:32:47 +09:00
|
|
|
var ISOAdobeCharset = coreCharsets.ISOAdobeCharset;
|
|
|
|
var ExpertCharset = coreCharsets.ExpertCharset;
|
|
|
|
var ExpertSubsetCharset = coreCharsets.ExpertSubsetCharset;
|
|
|
|
var FontRendererFactory = coreFontRenderer.FontRendererFactory;
|
2016-01-22 06:18:46 +09:00
|
|
|
var WinAnsiEncoding = coreEncodings.WinAnsiEncoding;
|
|
|
|
var StandardEncoding = coreEncodings.StandardEncoding;
|
|
|
|
var MacRomanEncoding = coreEncodings.MacRomanEncoding;
|
|
|
|
var SymbolSetEncoding = coreEncodings.SymbolSetEncoding;
|
|
|
|
var ZapfDingbatsEncoding = coreEncodings.ZapfDingbatsEncoding;
|
|
|
|
var ExpertEncoding = coreEncodings.ExpertEncoding;
|
|
|
|
var getEncoding = coreEncodings.getEncoding;
|
2016-01-22 06:52:24 +09:00
|
|
|
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;
|
2016-03-08 04:56:15 +09:00
|
|
|
var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
|
2015-11-22 01:32:47 +09:00
|
|
|
|
2011-09-05 21:35:03 +09:00
|
|
|
// Unicode Private Use Area
|
2014-02-12 03:27:09 +09:00
|
|
|
var PRIVATE_USE_OFFSET_START = 0xE000;
|
|
|
|
var PRIVATE_USE_OFFSET_END = 0xF8FF;
|
|
|
|
var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
|
2011-10-03 18:16:18 +09:00
|
|
|
|
2011-09-17 09:53:52 +09:00
|
|
|
// PDF Glyph Space Units are one Thousandth of a TextSpace Unit
|
|
|
|
// except for Type 3 fonts
|
2012-11-10 06:34:11 +09:00
|
|
|
var PDF_GLYPH_SPACE_UNITS = 1000;
|
2011-09-05 21:35:03 +09:00
|
|
|
|
2013-01-10 10:33:59 +09:00
|
|
|
// Hinting is currently disabled due to unknown problems on windows
|
|
|
|
// in tracemonkey and various other pdfs with type1 fonts.
|
2012-11-10 06:34:11 +09:00
|
|
|
var HINTING_ENABLED = false;
|
2011-09-11 03:00:27 +09:00
|
|
|
|
2013-02-27 03:00:20 +09:00
|
|
|
// Accented charactars are not displayed properly on windows, using this flag
|
|
|
|
// to control analysis of seac charstrings.
|
|
|
|
var SEAC_ANALYSIS_ENABLED = false;
|
|
|
|
|
2016-01-06 02:53:31 +09:00
|
|
|
// Maximum subroutine call depth of type 2 chartrings. Matches OTS.
|
|
|
|
var MAX_SUBR_NESTING = 10;
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
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) {
|
2015-12-08 06:30:09 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-06-16 23:52:04 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-13 21:56:12 +09:00
|
|
|
var Glyph = (function GlyphClosure() {
|
2015-11-02 23:54:15 +09:00
|
|
|
function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
|
2016-02-25 03:48:02 +09:00
|
|
|
isSpace, isInFont) {
|
2014-03-13 21:56:12 +09:00
|
|
|
this.fontChar = fontChar;
|
|
|
|
this.unicode = unicode;
|
|
|
|
this.accent = accent;
|
|
|
|
this.width = width;
|
|
|
|
this.vmetric = vmetric;
|
2014-05-20 06:27:54 +09:00
|
|
|
this.operatorListId = operatorListId;
|
2015-11-02 23:54:15 +09:00
|
|
|
this.isSpace = isSpace;
|
2016-02-25 03:48:02 +09:00
|
|
|
this.isInFont = isInFont;
|
2014-03-13 21:56:12 +09:00
|
|
|
}
|
|
|
|
|
2015-11-02 23:54:15 +09:00
|
|
|
Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
|
2016-02-25 03:48:02 +09:00
|
|
|
vmetric, operatorListId, isSpace,
|
|
|
|
isInFont) {
|
2014-03-13 21:56:12 +09:00
|
|
|
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 &&
|
2016-02-25 03:48:02 +09:00
|
|
|
this.isSpace === isSpace &&
|
|
|
|
this.isInFont === isInFont;
|
2014-03-13 21:56:12 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-01-02 22:21:56 +09:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return ToUnicodeMap;
|
|
|
|
})();
|
|
|
|
|
2014-08-08 10:29:53 +09:00
|
|
|
var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
|
|
|
|
function IdentityToUnicodeMap(firstChar, lastChar) {
|
|
|
|
this.firstChar = firstChar;
|
|
|
|
this.lastChar = lastChar;
|
|
|
|
}
|
|
|
|
|
|
|
|
IdentityToUnicodeMap.prototype = {
|
|
|
|
get length() {
|
2015-02-09 18:05:55 +09:00
|
|
|
return (this.lastChar + 1) - this.firstChar;
|
2014-08-08 10:29:53 +09:00
|
|
|
},
|
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
forEach: function (callback) {
|
2014-08-08 10:29:53 +09:00
|
|
|
for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
|
|
|
|
callback(i, i);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-01-02 22:21:56 +09:00
|
|
|
has: function (i) {
|
|
|
|
return this.firstChar <= i && i <= this.lastChar;
|
|
|
|
},
|
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
get: function (i) {
|
2014-08-08 10:29:53 +09:00
|
|
|
if (this.firstChar <= i && i <= this.lastChar) {
|
|
|
|
return String.fromCharCode(i);
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
},
|
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
charCodeOf: function (v) {
|
2015-12-05 20:22:09 +09:00
|
|
|
return (isInt(v) && v >= this.firstChar && v <= this.lastChar) ? v : -1;
|
2014-08-08 10:29:53 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return IdentityToUnicodeMap;
|
|
|
|
})();
|
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
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;
|
2014-08-10 08:18:41 +09:00
|
|
|
return {
|
|
|
|
range: searchRange,
|
2014-08-11 03:51:09 +09:00
|
|
|
entry: log2,
|
2014-08-10 08:18:41 +09:00
|
|
|
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 = (file[j] << 24) + (file[j + 1] << 16) +
|
|
|
|
(file[j + 2] << 8) + file[j + 3];
|
|
|
|
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,
|
|
|
|
// General punctuation chars.
|
|
|
|
0x2000, 0x2010,
|
|
|
|
0x2011, 0x2012,
|
|
|
|
0x2028, 0x2030,
|
|
|
|
0x205F, 0x2070,
|
|
|
|
0x25CC, 0x25CD,
|
|
|
|
// Chars that is used in complex-script shaping.
|
|
|
|
0xAA60, 0xAA80,
|
|
|
|
// Specials Unicode block.
|
|
|
|
0xFFF0, 0x10000
|
|
|
|
]);
|
|
|
|
|
2011-06-10 08:20:00 +09:00
|
|
|
/**
|
2011-06-13 08:38:05 +09:00
|
|
|
* '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);
|
2011-06-13 08:38:05 +09:00
|
|
|
* 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) {
|
2016-03-08 04:56:15 +09:00
|
|
|
var charCode, glyphName, unicode, fontChar;
|
2012-09-13 09:31:04 +09:00
|
|
|
|
2011-06-21 13:49:59 +09:00
|
|
|
this.name = name;
|
2012-09-14 00:09:46 +09:00
|
|
|
this.loadedName = properties.loadedName;
|
2014-05-20 06:27:54 +09:00
|
|
|
this.isType3Font = properties.isType3Font;
|
2011-07-21 09:10:04 +09:00
|
|
|
this.sizes = [];
|
2016-02-25 03:48:02 +09:00
|
|
|
this.missingFile = false;
|
2011-06-20 07:46:58 +09:00
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
this.glyphCache = Object.create(null);
|
2014-03-13 21:56:12 +09:00
|
|
|
|
2011-09-09 04:37:35 +09:00
|
|
|
var names = name.split('+');
|
2011-09-07 07:29:08 +09:00
|
|
|
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);
|
2012-09-15 02:58:33 +09:00
|
|
|
this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
|
2011-09-07 07:29:08 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
var type = properties.type;
|
2014-06-16 23:52:04 +09:00
|
|
|
var subtype = properties.subtype;
|
2011-10-29 10:38:31 +09:00
|
|
|
this.type = type;
|
|
|
|
|
2014-03-18 01:34:30 +09:00
|
|
|
this.fallbackName = (this.isMonospace ? 'monospace' :
|
|
|
|
(this.isSerifFont ? 'serif' : 'sans-serif'));
|
2011-10-29 10:38:31 +09:00
|
|
|
|
|
|
|
this.differences = properties.differences;
|
|
|
|
this.widths = properties.widths;
|
|
|
|
this.defaultWidth = properties.defaultWidth;
|
|
|
|
this.composite = properties.composite;
|
2012-04-24 07:44:51 +09:00
|
|
|
this.wideChars = properties.wideChars;
|
2014-02-12 03:27:09 +09:00
|
|
|
this.cMap = properties.cMap;
|
2014-01-09 04:50:52 +09:00
|
|
|
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;
|
2015-04-03 22:49:06 +09:00
|
|
|
this.bbox = properties.bbox;
|
2014-02-12 03:27:09 +09:00
|
|
|
|
2016-02-29 01:20:29 +09:00
|
|
|
this.toUnicode = properties.toUnicode;
|
2014-02-12 03:27:09 +09:00
|
|
|
|
|
|
|
this.toFontChar = [];
|
|
|
|
|
2014-08-02 03:57:43 +09:00
|
|
|
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]);
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
2014-06-16 23:52:04 +09:00
|
|
|
this.fontType = FontType.TYPE3;
|
2011-10-04 08:36:01 +09:00
|
|
|
return;
|
2011-12-05 04:51:12 +09:00
|
|
|
}
|
2011-07-25 23:42:46 +09:00
|
|
|
|
2013-01-15 23:20:58 +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;
|
2014-05-18 07:57:06 +09:00
|
|
|
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 + ')');
|
|
|
|
}
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
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, '-');
|
2016-01-22 06:52:24 +09:00
|
|
|
var stdFontMap = getStdFontMap(), nonStdFontMap = getNonStdFontMap();
|
2014-12-12 01:29:26 +09:00
|
|
|
var isStandardFont = !!stdFontMap[fontName] ||
|
2015-01-13 23:36:48 +09:00
|
|
|
!!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
|
2012-01-17 09:10:48 +09:00
|
|
|
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
|
2011-10-20 03:14:13 +09:00
|
|
|
|
2014-08-02 03:57:43 +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
|
2011-09-09 08:35:37 +09:00
|
|
|
// name ArialBlack for example will be replaced by Helvetica.
|
2014-08-02 03:57:43 +09:00
|
|
|
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) {
|
2016-01-22 06:52:24 +09:00
|
|
|
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 = [];
|
2015-07-02 05:16:52 +09:00
|
|
|
for (charCode in GlyphMapForStandardFonts) {
|
|
|
|
map[+charCode] = GlyphMapForStandardFonts[charCode];
|
|
|
|
}
|
|
|
|
if (/ArialBlack/i.test(name)) {
|
2016-01-22 06:52:24 +09:00
|
|
|
var SupplementalGlyphMapForArialBlack =
|
|
|
|
getSupplementalGlyphMapForArialBlack();
|
2015-07-02 05:16:52 +09:00
|
|
|
for (charCode in SupplementalGlyphMapForArialBlack) {
|
|
|
|
map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
|
|
|
|
}
|
2014-01-04 05:17:50 +09:00
|
|
|
}
|
2014-09-27 20:14:25 +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)) {
|
2016-01-22 06:18:46 +09:00
|
|
|
var symbols = SymbolSetEncoding;
|
2016-01-22 05:47:48 +09:00
|
|
|
glyphsUnicodeMap = getGlyphsUnicode();
|
2014-04-08 06:42:54 +09:00
|
|
|
for (charCode in symbols) {
|
2016-01-22 05:47:48 +09:00
|
|
|
fontChar = glyphsUnicodeMap[symbols[charCode]];
|
2014-09-01 10:22:24 +09:00
|
|
|
if (!fontChar) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.toFontChar[charCode] = fontChar;
|
|
|
|
}
|
2014-08-31 21:03:25 +09:00
|
|
|
for (charCode in properties.differences) {
|
2016-01-22 05:47:48 +09:00
|
|
|
fontChar = glyphsUnicodeMap[properties.differences[charCode]];
|
2014-08-31 21:03:25 +09:00
|
|
|
if (!fontChar) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.toFontChar[charCode] = fontChar;
|
|
|
|
}
|
2014-09-01 10:22:24 +09:00
|
|
|
} else if (/Dingbats/i.test(fontName)) {
|
2016-01-22 05:47:48 +09:00
|
|
|
glyphsUnicodeMap = getDingbatsGlyphsUnicode();
|
Add basic support for non-embedded Wingdings fonts
This is a tentative patch that adds *very* basic support for non-embedded Wingdings fonts (a Windows version of Dingbats), by falling back to the ZapfDingbats encoding. Obviously this approach will not work perfectly, but in my opinion it seems to work reasonably well in pratice.
Instead of this very simple patch, another option would be to try and include more complete glyph data for Wingdings, e.g. a Unicode map and glyph widths, similar to what was done for ZapfDingbats.
However there is, in my opinion, one important difference between Wingdings and ZapfDingbats: ZapfDingbats is part of the 14 standard fonts, which in previous versions of the PDF specification was assumed to be available in PDF readers. To improve compatibility with older files, it thus makes sense for us to include data for ZapfDingbats.
However Wingdings has never been a standard font in PDF files, hence PDF files using it *should* thus contain all the necessary font data.
Given the above, I thus believe that it should be OK to fall back to ZapfDingbats for now. If non-embedded Wingdings fonts turns out to be *a lot* more common, then we can revisit this later.
Fixes 4301 completely.
Fixes 4837 almost completely. With this patch the bullets are displayed correctly, but the arrows are not of the correct type.
Fixes `artofwar.pdf`, pages 14 and 15.
2014-11-01 21:08:26 +09:00
|
|
|
if (/Wingdings/i.test(name)) {
|
|
|
|
warn('Wingdings font without embedded font file, ' +
|
|
|
|
'falling back to the ZapfDingbats encoding.');
|
|
|
|
}
|
2016-01-22 06:18:46 +09:00
|
|
|
var dingbats = ZapfDingbatsEncoding;
|
2014-09-01 10:22:24 +09:00
|
|
|
for (charCode in dingbats) {
|
2016-01-22 05:47:48 +09:00
|
|
|
fontChar = glyphsUnicodeMap[dingbats[charCode]];
|
2014-09-01 10:22:24 +09:00
|
|
|
if (!fontChar) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.toFontChar[charCode] = fontChar;
|
|
|
|
}
|
|
|
|
for (charCode in properties.differences) {
|
2016-01-22 05:47:48 +09:00
|
|
|
fontChar = glyphsUnicodeMap[properties.differences[charCode]];
|
2014-02-12 06:37:12 +09:00
|
|
|
if (!fontChar) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
this.toFontChar[charCode] = fontChar;
|
|
|
|
}
|
2014-02-26 04:28:17 +09:00
|
|
|
} else if (isStandardFont) {
|
2016-01-22 05:47:48 +09:00
|
|
|
glyphsUnicodeMap = getGlyphsUnicode();
|
2014-04-08 06:42:54 +09:00
|
|
|
for (charCode in properties.defaultEncoding) {
|
2014-06-15 05:51:13 +09:00
|
|
|
glyphName = (properties.differences[charCode] ||
|
|
|
|
properties.defaultEncoding[charCode]);
|
2016-03-08 04:56:15 +09:00
|
|
|
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
|
|
|
|
if (unicode !== -1) {
|
|
|
|
this.toFontChar[charCode] = unicode;
|
|
|
|
}
|
2014-02-26 04:28:17 +09:00
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
} 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) {
|
2016-03-08 04:56:15 +09:00
|
|
|
if (!this.composite) {
|
2014-06-15 05:51:13 +09:00
|
|
|
glyphName = (properties.differences[charCode] ||
|
|
|
|
properties.defaultEncoding[charCode]);
|
2016-03-08 04:56:15 +09:00
|
|
|
unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
|
|
|
|
if (unicode !== -1) {
|
|
|
|
unicodeCharCode = unicode;
|
|
|
|
}
|
2014-06-15 05:51:13 +09:00
|
|
|
}
|
|
|
|
this.toFontChar[charCode] = unicodeCharCode;
|
2014-08-07 10:02:11 +09:00
|
|
|
}.bind(this));
|
2014-01-04 05:17:50 +09:00
|
|
|
}
|
2011-10-09 01:15:18 +09:00
|
|
|
this.loadedName = fontName.split('-')[0];
|
2011-07-25 23:42:46 +09:00
|
|
|
this.loading = false;
|
2014-06-16 23:52:04 +09:00
|
|
|
this.fontType = getFontType(type, subtype);
|
2011-06-21 09:35:14 +09:00
|
|
|
return;
|
|
|
|
}
|
2011-06-13 08:38:05 +09:00
|
|
|
|
2012-08-30 06:11:56 +09:00
|
|
|
// Some fonts might use wrong font types for Type1C or CIDFontType0C
|
2014-08-02 03:57:43 +09:00
|
|
|
if (subtype === 'Type1C' && (type !== 'Type1' && type !== 'MMType1')) {
|
2014-04-25 01:48:18 +09:00
|
|
|
// Some TrueType fonts by mistake claim Type1C
|
|
|
|
if (isTrueTypeFile(file)) {
|
|
|
|
subtype = 'TrueType';
|
|
|
|
} else {
|
|
|
|
type = 'Type1';
|
|
|
|
}
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2014-08-02 03:57:43 +09:00
|
|
|
if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
|
2012-08-30 06:11:56 +09:00
|
|
|
type = 'CIDFontType0';
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-12-11 07:59:23 +09:00
|
|
|
if (subtype === 'OpenType') {
|
|
|
|
type = 'OpenType';
|
|
|
|
}
|
2015-02-24 00:01:08 +09:00
|
|
|
// Some CIDFontType0C fonts by mistake claim CIDFontType0.
|
|
|
|
if (type === 'CIDFontType0') {
|
2016-01-06 10:07:21 +09:00
|
|
|
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';
|
|
|
|
}
|
2015-02-24 00:01:08 +09:00
|
|
|
}
|
2012-08-30 06:11:56 +09:00
|
|
|
|
2011-06-24 18:47:22 +09:00
|
|
|
var data;
|
2011-09-22 20:39:28 +09:00
|
|
|
switch (type) {
|
2014-09-18 23:10:46 +09:00
|
|
|
case 'MMType1':
|
|
|
|
info('MMType1 font (' + name + '), falling back to Type1.');
|
|
|
|
/* falls through */
|
2011-07-06 15:06:45 +09:00
|
|
|
case 'Type1':
|
2011-07-12 11:58:09 +09:00
|
|
|
case 'CIDFontType0':
|
2011-07-06 15:06:45 +09:00
|
|
|
this.mimetype = 'font/opentype';
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2014-06-16 23:52:04 +09:00
|
|
|
var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
|
2012-03-11 12:37:22 +09:00
|
|
|
new CFFFont(file, properties) : new Type1Font(name, file, properties);
|
2011-06-14 11:35:46 +09:00
|
|
|
|
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
|
2011-06-24 18:47:22 +09:00
|
|
|
data = this.convert(name, cff, properties);
|
2011-06-21 09:35:14 +09:00
|
|
|
break;
|
2011-06-13 08:38:05 +09:00
|
|
|
|
2013-06-25 08:45:31 +09:00
|
|
|
case 'OpenType':
|
2011-07-06 15:06:45 +09:00
|
|
|
case 'TrueType':
|
2011-07-12 11:58:09 +09:00
|
|
|
case 'CIDFontType2':
|
2011-07-12 01:41:47 +09:00
|
|
|
this.mimetype = 'font/opentype';
|
|
|
|
|
2011-08-26 22:30:26 +09:00
|
|
|
// Repair the TrueType file. It is can be damaged in the point of
|
2011-07-12 01:41:47 +09:00
|
|
|
// view of the sanitizer
|
|
|
|
data = this.checkAndRepair(name, file, properties);
|
2014-06-16 23:52:04 +09:00
|
|
|
if (this.isOpenType) {
|
2015-12-08 06:30:09 +09:00
|
|
|
adjustWidths(properties);
|
|
|
|
|
2014-06-16 23:52:04 +09:00
|
|
|
type = 'OpenType';
|
|
|
|
}
|
2011-07-12 01:41:47 +09:00
|
|
|
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-16 08:30:47 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
|
2011-06-27 03:55:27 +09:00
|
|
|
this.data = data;
|
2014-06-16 23:52:04 +09:00
|
|
|
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;
|
2011-10-29 10:38:31 +09:00
|
|
|
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;
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-14 11:35:46 +09:00
|
|
|
|
2014-03-04 02:44:45 +09:00
|
|
|
Font.getFontID = (function () {
|
|
|
|
var ID = 1;
|
|
|
|
return function Font_getFontID() {
|
|
|
|
return String(ID++);
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2014-03-12 13:09:49 +09:00
|
|
|
function int16(b0, b1) {
|
|
|
|
return (b0 << 8) + b1;
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2016-03-08 04:56:15 +09:00
|
|
|
|
2016-03-03 11:10:15 +09:00
|
|
|
function signedInt16(b0, b1) {
|
|
|
|
var value = (b0 << 8) + b1;
|
|
|
|
return value & (1 << 15) ? value - 0x10000 : value;
|
|
|
|
}
|
2011-07-02 14:54:28 +09:00
|
|
|
|
2014-03-12 13:09:49 +09:00
|
|
|
function int32(b0, b1, b2, b3) {
|
|
|
|
return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
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);
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
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);
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2014-04-25 01:48:18 +09:00
|
|
|
function isTrueTypeFile(file) {
|
|
|
|
var header = file.peekBytes(4);
|
|
|
|
return readUint32(header, 0) === 0x00010000;
|
|
|
|
}
|
|
|
|
|
2016-01-06 10:07:21 +09:00
|
|
|
function isOpenTypeFile(file) {
|
|
|
|
var header = file.peekBytes(4);
|
|
|
|
return bytesToString(header) === 'OTTO';
|
|
|
|
}
|
|
|
|
|
2015-02-24 00:01:08 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-04-09 19:56:29 +09:00
|
|
|
/**
|
|
|
|
* 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-05-11 04:48:17 +09:00
|
|
|
}
|
2015-07-03 06:47:47 +09:00
|
|
|
// Even index means code in problematic range.
|
|
|
|
return !(i & 1);
|
2015-04-09 19:56:29 +09:00
|
|
|
}
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
/**
|
|
|
|
* 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;
|
2014-02-12 03:27:09 +09:00
|
|
|
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.
|
2015-01-02 22:21:56 +09:00
|
|
|
if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
|
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);
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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).
|
2014-06-26 07:03:36 +09:00
|
|
|
if ((usedFontCharCodes[fontCharCode] !== undefined ||
|
2015-04-09 19:56:29 +09:00
|
|
|
isProblematicUnicodeLocation(fontCharCode) ||
|
2014-02-12 03:27:09 +09:00
|
|
|
(isSymbolic && isIdentityUnicode)) &&
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-06-26 07:03:36 +09:00
|
|
|
} while (usedFontCharCodes[fontCharCode] !== undefined &&
|
2014-02-12 03:27:09 +09:00
|
|
|
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
|
2014-02-12 03:27:09 +09:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-12-03 09:47:20 +09:00
|
|
|
function getRanges(glyphs, numGlyphs) {
|
2011-06-21 13:49:59 +09:00
|
|
|
// Array.sort() sorts by characters, not numerically, so convert to an
|
|
|
|
// array of characters.
|
|
|
|
var codes = [];
|
2014-02-12 03:27:09 +09:00
|
|
|
for (var charCode in glyphs) {
|
2015-12-03 09:47:20 +09:00
|
|
|
// Remove an invalid glyph ID mappings to make OTS happy.
|
|
|
|
if (glyphs[charCode] >= numGlyphs) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
codes.push({ fontCharCode: charCode | 0, glyphId: glyphs[charCode] });
|
|
|
|
}
|
2011-09-24 02:44:48 +09:00
|
|
|
codes.sort(function fontGetRangesSort(a, b) {
|
2014-02-12 03:27:09 +09:00
|
|
|
return a.fontCharCode - b.fontCharCode;
|
2011-09-18 07:13:22 +09:00
|
|
|
});
|
2011-06-21 13:49:59 +09:00
|
|
|
|
|
|
|
// Split the sorted codes into ranges.
|
|
|
|
var ranges = [];
|
2014-02-12 03:27:09 +09:00
|
|
|
var length = codes.length;
|
2011-06-21 13:49:59 +09:00
|
|
|
for (var n = 0; n < length; ) {
|
2014-02-12 03:27:09 +09:00
|
|
|
var start = codes[n].fontCharCode;
|
|
|
|
var codeIndices = [codes[n].glyphId];
|
2011-09-18 07:13:22 +09:00
|
|
|
++n;
|
2011-06-21 13:49:59 +09:00
|
|
|
var end = start;
|
2014-08-02 03:57:43 +09:00
|
|
|
while (n < length && end + 1 === codes[n].fontCharCode) {
|
2014-02-12 03:27:09 +09:00
|
|
|
codeIndices.push(codes[n].glyphId);
|
2011-06-21 13:49:59 +09:00
|
|
|
++end;
|
|
|
|
++n;
|
2014-03-18 01:34:30 +09:00
|
|
|
if (end === 0xFFFF) {
|
|
|
|
break;
|
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
}
|
2011-11-28 11:43:23 +09:00
|
|
|
ranges.push([start, end, codeIndices]);
|
2011-06-21 13:49:59 +09:00
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-06-21 13:49:59 +09:00
|
|
|
return ranges;
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2015-12-03 09:47:20 +09:00
|
|
|
function createCmapTable(glyphs, numGlyphs) {
|
|
|
|
var ranges = getRanges(glyphs, numGlyphs);
|
2013-02-05 06:20:33 +09:00
|
|
|
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
|
2011-07-02 12:46:50 +09:00
|
|
|
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) {
|
2013-02-05 06:20:33 +09:00
|
|
|
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;
|
2014-08-10 08:18:41 +09:00
|
|
|
var searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
|
2011-06-21 13:49:59 +09:00
|
|
|
|
|
|
|
// 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 = '';
|
2011-06-21 13:49:59 +09:00
|
|
|
var bias = 0;
|
2014-04-17 04:31:16 +09:00
|
|
|
|
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) {
|
2011-09-18 07:13:22 +09:00
|
|
|
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];
|
2011-09-18 07:13:22 +09:00
|
|
|
|
2013-03-19 21:37:31 +09:00
|
|
|
idDeltas += string16((startCode - start) & 0xFFFF);
|
2011-09-18 07:13:22 +09:00
|
|
|
idRangeOffsets += string16(0);
|
|
|
|
}
|
|
|
|
}
|
2011-07-20 21:29:07 +09:00
|
|
|
|
2013-03-19 21:37:31 +09:00
|
|
|
if (trailingRangesCount > 0) {
|
|
|
|
endCount += '\xFF\xFF';
|
|
|
|
startCount += '\xFF\xFF';
|
|
|
|
idDeltas += '\x00\x01';
|
|
|
|
idRangeOffsets += '\x00\x00';
|
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2011-07-10 13:12:11 +09:00
|
|
|
var format314 = '\x00\x00' + // language
|
2014-08-10 08:18:41 +09:00
|
|
|
string16(2 * segCount) +
|
|
|
|
string16(searchParams.range) +
|
|
|
|
string16(searchParams.entry) +
|
|
|
|
string16(searchParams.rangeShift) +
|
2011-07-10 13:12:11 +09:00
|
|
|
endCount + '\x00\x00' + startCount +
|
|
|
|
idDeltas + idRangeOffsets + glyphsIds;
|
|
|
|
|
2013-02-05 06:20:33 +09:00
|
|
|
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-02-05 06:20:33 +09:00
|
|
|
}
|
|
|
|
}
|
2013-03-19 21:37:31 +09:00
|
|
|
format31012 += string32(start) + // startCharCode
|
|
|
|
string32(range[1]) + // endCharCode
|
|
|
|
string32(code); // startGlyphID
|
2013-02-05 06:20:33 +09:00
|
|
|
}
|
|
|
|
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;
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2012-11-08 02:11:36 +09:00
|
|
|
function validateOS2Table(os2) {
|
|
|
|
var stream = new Stream(os2.data);
|
2014-03-12 13:09:49 +09:00
|
|
|
var version = stream.getUint16();
|
2012-11-08 02:11:36 +09:00
|
|
|
// 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
|
2014-03-12 13:09:49 +09:00
|
|
|
var selection = stream.getUint16();
|
2012-11-08 02:11:36 +09:00
|
|
|
if (version < 4 && (selection & 0x0300)) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-03-12 13:09:49 +09:00
|
|
|
var firstChar = stream.getUint16();
|
|
|
|
var lastChar = stream.getUint16();
|
2012-11-08 02:11:36 +09:00
|
|
|
if (firstChar > lastChar) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-03-09 05:11:12 +09:00
|
|
|
stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap
|
2014-03-12 13:09:49 +09:00
|
|
|
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
|
2012-11-08 02:11:36 +09:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
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
|
|
|
|
};
|
2011-09-17 07:55:06 +09:00
|
|
|
|
2011-06-30 09:52:47 +09:00
|
|
|
var ulUnicodeRange1 = 0;
|
|
|
|
var ulUnicodeRange2 = 0;
|
|
|
|
var ulUnicodeRange3 = 0;
|
|
|
|
var ulUnicodeRange4 = 0;
|
|
|
|
|
2011-08-30 07:56:02 +09:00
|
|
|
var firstCharIndex = null;
|
|
|
|
var lastCharIndex = 0;
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
if (charstrings) {
|
2014-02-12 03:27:09 +09:00
|
|
|
for (var code in charstrings) {
|
|
|
|
code |= 0;
|
2014-03-18 01:34:30 +09:00
|
|
|
if (firstCharIndex > code || !firstCharIndex) {
|
2011-10-29 10:38:31 +09:00
|
|
|
firstCharIndex = code;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
|
|
|
if (lastCharIndex < code) {
|
2011-10-29 10:38:31 +09:00
|
|
|
lastCharIndex = code;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +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');
|
|
|
|
}
|
2011-06-30 09:52:47 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
// TODO
|
|
|
|
firstCharIndex = 0;
|
|
|
|
lastCharIndex = 255;
|
2011-06-30 09:52:47 +09:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
2011-09-17 07:55:06 +09:00
|
|
|
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
|
2011-06-30 09:52:47 +09:00
|
|
|
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
|
2011-06-30 12:38:10 +09:00
|
|
|
string16(lastCharIndex || properties.lastChar) + // usLastCharIndex
|
2011-09-16 21:50:11 +09:00
|
|
|
string16(typoAscent) + // sTypoAscender
|
|
|
|
string16(typoDescent) + // sTypoDescender
|
2011-07-06 15:06:45 +09:00
|
|
|
'\x00\x64' + // sTypoLineGap (7%-10% of the unitsPerEM value)
|
2011-09-16 21:50:11 +09:00
|
|
|
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
|
2011-06-30 10:01:46 +09:00
|
|
|
string16(0) + // usDefaultChar
|
2011-06-30 12:38:10 +09:00
|
|
|
string16(firstCharIndex || properties.firstChar) + // usBreakChar
|
2011-07-06 15:06:45 +09:00
|
|
|
'\x00\x03'; // usMaxContext
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-24 05:11:16 +09:00
|
|
|
|
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
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2012-11-08 09:24:13 +09:00
|
|
|
function createNameTable(name, proto) {
|
|
|
|
if (!proto) {
|
|
|
|
proto = [[], []]; // no strings and unicode strings
|
|
|
|
}
|
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
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
|
2011-07-13 17:31:34 +09:00
|
|
|
];
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
// 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];
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2014-02-28 08:23:56 +09:00
|
|
|
var strBufUnicode = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0, jj = str.length; j < jj; j++) {
|
2014-02-28 08:23:56 +09:00
|
|
|
strBufUnicode.push(string16(str.charCodeAt(j)));
|
|
|
|
}
|
|
|
|
stringsUnicode.push(strBufUnicode.join(''));
|
2011-07-13 17:31:34 +09:00
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
var names = [strings, stringsUnicode];
|
|
|
|
var platforms = ['\x00\x01', '\x00\x03'];
|
|
|
|
var encodings = ['\x00\x00', '\x00\x01'];
|
|
|
|
var languages = ['\x00\x00', '\x04\x09'];
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
var namesRecordCount = strings.length * platforms.length;
|
|
|
|
var nameTable =
|
|
|
|
'\x00\x00' + // format
|
|
|
|
string16(namesRecordCount) + // Number of names Record
|
|
|
|
string16(namesRecordCount * 12 + 6); // Storage
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
// 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++) {
|
2011-07-13 17:31:34 +09:00
|
|
|
var strs = names[i];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0, jj = strs.length; j < jj; j++) {
|
|
|
|
str = strs[j];
|
2011-07-13 17:31:34 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-13 17:31:34 +09:00
|
|
|
nameTable += strings.join('') + stringsUnicode.join('');
|
|
|
|
return nameTable;
|
|
|
|
}
|
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
Font.prototype = {
|
2011-06-20 07:46:58 +09:00
|
|
|
name: null,
|
|
|
|
font: null,
|
|
|
|
mimetype: null,
|
2011-06-21 09:35:14 +09:00
|
|
|
encoding: null,
|
2013-08-20 08:33:20 +09:00
|
|
|
get renderer() {
|
|
|
|
var renderer = FontRendererFactory.create(this);
|
|
|
|
return shadow(this, 'renderer', renderer);
|
|
|
|
},
|
2011-06-20 07:46:58 +09:00
|
|
|
|
2012-09-20 23:38:59 +09:00
|
|
|
exportData: function Font_exportData() {
|
2016-01-28 02:04:13 +09:00
|
|
|
// 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;
|
|
|
|
},
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
checkAndRepair: function Font_checkAndRepair(name, font, properties) {
|
2011-06-21 13:49:59 +09:00
|
|
|
function readTableEntry(file) {
|
2014-03-27 21:01:43 +09:00
|
|
|
var tag = bytesToString(file.getBytes(4));
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2014-04-17 04:31:16 +09:00
|
|
|
var checksum = file.getInt32();
|
|
|
|
var offset = file.getInt32() >>> 0;
|
|
|
|
var length = file.getInt32() >>> 0;
|
2011-06-21 13:49:59 +09:00
|
|
|
|
|
|
|
// 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);
|
2011-06-21 13:49:59 +09:00
|
|
|
var data = file.getBytes(length);
|
2011-06-22 04:11:59 +09:00
|
|
|
file.pos = previousPosition;
|
2011-06-21 13:49:59 +09:00
|
|
|
|
2014-08-02 03:57:43 +09:00
|
|
|
if (tag === 'head') {
|
2011-08-12 03:41:03 +09:00
|
|
|
// clearing checksum adjustment
|
|
|
|
data[8] = data[9] = data[10] = data[11] = 0;
|
2011-09-16 07:23:22 +09:00
|
|
|
data[17] |= 0x20; //Set font optimized for cleartype flag
|
|
|
|
}
|
2011-07-11 06:47:09 +09:00
|
|
|
|
2011-06-21 13:49:59 +09:00
|
|
|
return {
|
|
|
|
tag: tag,
|
|
|
|
checksum: checksum,
|
2011-07-15 23:02:32 +09:00
|
|
|
length: length,
|
|
|
|
offset: offset,
|
2011-06-21 13:49:59 +09:00
|
|
|
data: data
|
2011-07-06 15:06:45 +09:00
|
|
|
};
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-14 11:35:46 +09:00
|
|
|
|
2011-06-21 13:49:59 +09:00
|
|
|
function readOpenTypeHeader(ttf) {
|
|
|
|
return {
|
2014-03-27 21:01:43 +09:00
|
|
|
version: bytesToString(ttf.getBytes(4)),
|
2014-03-12 13:09:49 +09:00
|
|
|
numTables: ttf.getUint16(),
|
|
|
|
searchRange: ttf.getUint16(),
|
|
|
|
entrySelector: ttf.getUint16(),
|
|
|
|
rangeShift: ttf.getUint16()
|
2011-07-06 15:06:45 +09:00
|
|
|
};
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-10 09:07:41 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
/**
|
|
|
|
* Read the appropriate subtable from the cmap according to 9.6.6.4 from
|
|
|
|
* PDF spec
|
|
|
|
*/
|
2015-09-07 00:16:31 +09:00
|
|
|
function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
|
2015-11-08 21:18:23 +09:00
|
|
|
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;
|
2011-07-15 23:02:32 +09:00
|
|
|
var start = (font.start ? font.start : 0) + cmap.offset;
|
2011-07-15 21:58:14 +09:00
|
|
|
font.pos = start;
|
2011-07-02 07:19:24 +09:00
|
|
|
|
2014-03-12 13:09:49 +09:00
|
|
|
var version = font.getUint16();
|
|
|
|
var numTables = font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
|
|
|
|
var potentialTable;
|
2014-02-12 03:27:09 +09:00
|
|
|
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.
|
2013-01-31 03:01:32 +09:00
|
|
|
for (var i = 0; i < numTables; i++) {
|
2014-03-12 13:09:49 +09:00
|
|
|
var platformId = font.getUint16();
|
|
|
|
var encodingId = font.getUint16();
|
2014-04-17 04:31:16 +09:00
|
|
|
var offset = font.getInt32() >>> 0;
|
2013-01-31 03:01:32 +09:00
|
|
|
var useTable = false;
|
|
|
|
|
2015-01-11 22:54:12 +09:00
|
|
|
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) {
|
2013-01-31 03:01:32 +09:00
|
|
|
useTable = true;
|
2014-02-12 03:27:09 +09:00
|
|
|
// Continue the loop since there still may be a higher priority
|
|
|
|
// table.
|
2015-02-19 21:58:03 +09:00
|
|
|
} else if (platformId === 3 && encodingId === 1 &&
|
2015-09-07 00:16:31 +09:00
|
|
|
((!isSymbolicFont && hasEncoding) || !potentialTable)) {
|
2013-01-31 03:01:32 +09:00
|
|
|
useTable = true;
|
2015-02-05 23:25:23 +09:00
|
|
|
if (!isSymbolicFont) {
|
|
|
|
canBreak = true;
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
|
2013-01-31 03:01:32 +09:00
|
|
|
useTable = true;
|
2014-02-12 03:27:09 +09:00
|
|
|
canBreak = true;
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2011-07-15 21:58:14 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
if (useTable) {
|
|
|
|
potentialTable = {
|
|
|
|
platformId: platformId,
|
|
|
|
encodingId: encodingId,
|
2014-02-12 03:27:09 +09:00
|
|
|
offset: offset
|
2013-01-31 03:01:32 +09:00
|
|
|
};
|
|
|
|
}
|
|
|
|
if (canBreak) {
|
|
|
|
break;
|
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
}
|
2011-06-22 11:41:31 +09:00
|
|
|
|
2015-04-02 22:31:21 +09:00
|
|
|
if (potentialTable) {
|
|
|
|
font.pos = start + potentialTable.offset;
|
|
|
|
}
|
|
|
|
if (!potentialTable || font.peekByte() === -1) {
|
2014-02-12 03:27:09 +09:00
|
|
|
warn('Could not find a preferred cmap table.');
|
2014-05-24 18:29:52 +09:00
|
|
|
return {
|
|
|
|
platformId: -1,
|
|
|
|
encodingId: -1,
|
|
|
|
mappings: [],
|
|
|
|
hasShortCmap: false
|
|
|
|
};
|
2011-09-06 22:12:33 +09:00
|
|
|
}
|
|
|
|
|
2014-03-12 13:09:49 +09:00
|
|
|
var format = font.getUint16();
|
|
|
|
var length = font.getUint16();
|
|
|
|
var language = font.getUint16();
|
2011-09-05 21:35:03 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
var hasShortCmap = false;
|
|
|
|
var mappings = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
var j, glyphId;
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
// 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++) {
|
2013-01-31 03:01:32 +09:00
|
|
|
var index = font.getByte();
|
|
|
|
if (!index) {
|
|
|
|
continue;
|
2011-07-20 22:18:44 +09:00
|
|
|
}
|
2013-01-31 03:01:32 +09:00
|
|
|
mappings.push({
|
2014-02-12 03:27:09 +09:00
|
|
|
charCode: j,
|
2013-01-31 03:01:32 +09:00
|
|
|
glyphId: index
|
|
|
|
});
|
|
|
|
}
|
|
|
|
hasShortCmap = true;
|
|
|
|
} else if (format === 4) {
|
|
|
|
// re-creating the table in format 4 since the encoding
|
|
|
|
// might be changed
|
2014-03-12 13:09:49 +09:00
|
|
|
var segCount = (font.getUint16() >> 1);
|
2013-01-31 03:01:32 +09:00
|
|
|
font.getBytes(6); // skipping range fields
|
|
|
|
var segIndex, segments = [];
|
|
|
|
for (segIndex = 0; segIndex < segCount; segIndex++) {
|
2014-03-12 13:09:49 +09:00
|
|
|
segments.push({ end: font.getUint16() });
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2014-03-12 13:09:49 +09:00
|
|
|
font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
for (segIndex = 0; segIndex < segCount; segIndex++) {
|
2014-03-12 13:09:49 +09:00
|
|
|
segments[segIndex].start = font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2011-09-09 04:37:35 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
for (segIndex = 0; segIndex < segCount; segIndex++) {
|
2014-03-12 13:09:49 +09:00
|
|
|
segments[segIndex].delta = font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
var offsetsCount = 0;
|
|
|
|
for (segIndex = 0; segIndex < segCount; segIndex++) {
|
2014-04-08 06:42:54 +09:00
|
|
|
segment = segments[segIndex];
|
2014-03-12 13:09:49 +09:00
|
|
|
var rangeOffset = font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
if (!rangeOffset) {
|
|
|
|
segment.offsetIndex = -1;
|
|
|
|
continue;
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
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);
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
var offsets = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0; j < offsetsCount; j++) {
|
2014-03-12 13:09:49 +09:00
|
|
|
offsets.push(font.getUint16());
|
2013-01-31 03:01:32 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
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;
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = start; j <= end; j++) {
|
2014-08-02 03:57:43 +09:00
|
|
|
if (j === 0xFFFF) {
|
2013-01-31 03:01:32 +09:00
|
|
|
continue;
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
glyphId = (offsetIndex < 0 ?
|
|
|
|
j : offsets[offsetIndex + j - start]);
|
2013-01-31 03:01:32 +09:00
|
|
|
glyphId = (glyphId + delta) & 0xFFFF;
|
|
|
|
if (glyphId === 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
mappings.push({
|
2014-02-12 03:27:09 +09:00
|
|
|
charCode: j,
|
2013-01-31 03:01:32 +09:00
|
|
|
glyphId: glyphId
|
|
|
|
});
|
2011-09-08 10:16:33 +09:00
|
|
|
}
|
2011-06-22 11:41:31 +09:00
|
|
|
}
|
2014-08-02 03:57:43 +09:00
|
|
|
} else if (format === 6) {
|
2013-01-31 03:01:32 +09:00
|
|
|
// 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
|
2014-03-12 13:09:49 +09:00
|
|
|
var firstCode = font.getUint16();
|
|
|
|
var entryCount = font.getUint16();
|
2013-01-31 03:01:32 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
for (j = 0; j < entryCount; j++) {
|
|
|
|
glyphId = font.getUint16();
|
2014-02-12 03:27:09 +09:00
|
|
|
var charCode = firstCode + j;
|
2013-01-31 03:01:32 +09:00
|
|
|
|
|
|
|
mappings.push({
|
2014-02-12 03:27:09 +09:00
|
|
|
charCode: charCode,
|
2013-01-31 03:01:32 +09:00
|
|
|
glyphId: glyphId
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
2015-09-01 20:31:02 +09:00
|
|
|
warn('cmap table has unsupported format: ' + format);
|
|
|
|
return {
|
|
|
|
platformId: -1,
|
|
|
|
encodingId: -1,
|
|
|
|
mappings: [],
|
|
|
|
hasShortCmap: false
|
|
|
|
};
|
2011-06-22 11:41:31 +09:00
|
|
|
}
|
2013-01-31 03:01:32 +09:00
|
|
|
|
2013-11-02 06:30:28 +09:00
|
|
|
// removing duplicate entries
|
|
|
|
mappings.sort(function (a, b) {
|
2014-02-12 03:27:09 +09:00
|
|
|
return a.charCode - b.charCode;
|
2013-11-02 06:30:28 +09:00
|
|
|
});
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 1; i < mappings.length; i++) {
|
2014-02-12 03:27:09 +09:00
|
|
|
if (mappings[i - 1].charCode === mappings[i].charCode) {
|
2013-11-02 06:30:28 +09:00
|
|
|
mappings.splice(i, 1);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-31 03:01:32 +09:00
|
|
|
return {
|
|
|
|
platformId: potentialTable.platformId,
|
|
|
|
encodingId: potentialTable.encodingId,
|
|
|
|
mappings: mappings,
|
|
|
|
hasShortCmap: hasShortCmap
|
|
|
|
};
|
2013-02-01 08:31:41 +09:00
|
|
|
}
|
2011-06-22 11:41:31 +09:00
|
|
|
|
2011-09-09 23:37:56 +09:00
|
|
|
function sanitizeMetrics(font, header, metrics, numGlyphs) {
|
2012-11-08 02:32:55 +09:00
|
|
|
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;
|
2014-03-12 13:09:49 +09:00
|
|
|
var numOfMetrics = font.getUint16();
|
2011-09-13 02:42:55 +09:00
|
|
|
|
2013-01-23 03:46:54 +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 -
|
2013-01-23 03:46:54 +09:00
|
|
|
((metrics.length - numOfMetrics * 4) >> 1);
|
|
|
|
|
2011-09-09 23:37:56 +09:00
|
|
|
if (numMissing > 0) {
|
2014-08-20 20:54:35 +09:00
|
|
|
// 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
|
|
|
}
|
2013-02-01 08:31:41 +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) {
|
2012-02-13 12:11:44 +09:00
|
|
|
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++) {
|
2012-02-13 12:11:44 +09:00
|
|
|
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;
|
2012-02-13 12:11:44 +09:00
|
|
|
var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
|
|
|
|
j += 2 + instructionsLength;
|
2013-03-18 22:06:59 +09:00
|
|
|
var instructionsEnd = j;
|
2012-02-13 12:11:44 +09:00
|
|
|
// validating flags
|
|
|
|
var coordinatesLength = 0;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < flagsCount; i++) {
|
2012-02-13 12:11:44 +09:00
|
|
|
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;
|
2012-02-13 12:11:44 +09:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2012-02-13 12:11:44 +09:00
|
|
|
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;
|
|
|
|
}
|
2012-02-13 12:11:44 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-01-23 03:46:54 +09:00
|
|
|
function sanitizeHead(head, numGlyphs, locaLength) {
|
2013-01-19 08:06:12 +09:00
|
|
|
var data = head.data;
|
|
|
|
|
|
|
|
// Validate version:
|
|
|
|
// Should always be 0x00010000
|
2014-03-12 13:09:49 +09:00
|
|
|
var version = int32(data[0], data[1], data[2], data[3]);
|
2013-01-19 08:06:12 +09:00
|
|
|
if (version >> 16 !== 1) {
|
2013-01-23 03:46:54 +09:00
|
|
|
info('Attempting to fix invalid version in head table: ' + version);
|
2013-01-19 08:06:12 +09:00
|
|
|
data[0] = 0;
|
|
|
|
data[1] = 1;
|
|
|
|
data[2] = 0;
|
|
|
|
data[3] = 0;
|
|
|
|
}
|
2013-01-23 03:46:54 +09:00
|
|
|
|
2014-03-12 13:09:49 +09:00
|
|
|
var indexToLocFormat = int16(data[50], data[51]);
|
2013-01-23 03:46:54 +09:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2013-01-19 08:06:12 +09:00
|
|
|
}
|
|
|
|
|
2011-09-19 11:25:05 +09:00
|
|
|
function sanitizeGlyphLocations(loca, glyf, numGlyphs,
|
2013-07-30 05:24:32 +09:00
|
|
|
isGlyphLocationsLong, hintsValid,
|
|
|
|
dupFirstEntry) {
|
2011-09-19 11:25:05 +09:00
|
|
|
var itemSize, itemDecode, itemEncode;
|
|
|
|
if (isGlyphLocationsLong) {
|
|
|
|
itemSize = 4;
|
2011-09-24 02:44:48 +09:00
|
|
|
itemDecode = function fontItemDecodeLong(data, offset) {
|
2011-09-19 11:25:05 +09:00
|
|
|
return (data[offset] << 24) | (data[offset + 1] << 16) |
|
|
|
|
(data[offset + 2] << 8) | data[offset + 3];
|
|
|
|
};
|
2011-09-24 02:44:48 +09:00
|
|
|
itemEncode = function fontItemEncodeLong(data, offset, value) {
|
2011-09-19 11:25:05 +09:00
|
|
|
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;
|
2011-09-24 02:44:48 +09:00
|
|
|
itemDecode = function fontItemDecode(data, offset) {
|
2011-09-19 12:44:25 +09:00
|
|
|
return (data[offset] << 9) | (data[offset + 1] << 1);
|
2011-09-19 11:25:05 +09:00
|
|
|
};
|
2011-09-24 02:44:48 +09:00
|
|
|
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;
|
2011-09-19 11:25:05 +09:00
|
|
|
};
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2012-02-13 12:11:44 +09:00
|
|
|
// removing the invalid glyphs
|
|
|
|
var oldGlyfData = glyf.data;
|
2012-02-20 11:12:57 +09:00
|
|
|
var oldGlyfDataLength = oldGlyfData.length;
|
|
|
|
var newGlyfData = new Uint8Array(oldGlyfDataLength);
|
2011-09-19 11:25:05 +09:00
|
|
|
var startOffset = itemDecode(locaData, 0);
|
2012-02-13 12:11:44 +09:00
|
|
|
var writeOffset = 0;
|
2016-01-28 02:04:13 +09:00
|
|
|
var missingGlyphData = Object.create(null);
|
2012-02-13 12:11:44 +09:00
|
|
|
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) {
|
2012-02-13 12:11:44 +09:00
|
|
|
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;
|
|
|
|
}
|
2012-02-20 11:12:57 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-02-13 12:11:44 +09:00
|
|
|
var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
|
2013-03-18 22:06:59 +09:00
|
|
|
newGlyfData, writeOffset, hintsValid);
|
2012-02-13 12:11:44 +09:00
|
|
|
writeOffset += newLength;
|
|
|
|
itemEncode(locaData, j, writeOffset);
|
|
|
|
startOffset = endOffset;
|
2011-09-19 11:25:05 +09:00
|
|
|
}
|
2012-02-19 06:01:53 +09:00
|
|
|
|
2013-02-01 08:31:41 +09:00
|
|
|
if (writeOffset === 0) {
|
2012-02-19 06:01:53 +09:00
|
|
|
// 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) {
|
2012-02-19 06:01:53 +09:00
|
|
|
itemEncode(locaData, j, simpleGlyph.length);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-02-19 06:01:53 +09:00
|
|
|
glyf.data = simpleGlyph;
|
2014-10-04 02:35:49 +09:00
|
|
|
return missingGlyphData;
|
2012-02-19 06:01:53 +09:00
|
|
|
}
|
|
|
|
|
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;
|
2011-09-19 11:25:05 +09:00
|
|
|
}
|
|
|
|
|
2012-11-15 07:17:06 +09:00
|
|
|
function readPostScriptTable(post, properties, maxpNumGlyphs) {
|
2011-10-29 10:38:31 +09:00
|
|
|
var start = (font.start ? font.start : 0) + post.offset;
|
|
|
|
font.pos = start;
|
|
|
|
|
|
|
|
var length = post.length, end = start + length;
|
2014-04-17 04:31:16 +09:00
|
|
|
var version = font.getInt32();
|
2011-10-29 10:38:31 +09:00
|
|
|
// 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;
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
switch (version) {
|
|
|
|
case 0x00010000:
|
|
|
|
glyphNames = MacStandardGlyphOrdering;
|
|
|
|
break;
|
|
|
|
case 0x00020000:
|
2014-03-12 13:09:49 +09:00
|
|
|
var numGlyphs = font.getUint16();
|
2014-08-02 03:57:43 +09:00
|
|
|
if (numGlyphs !== maxpNumGlyphs) {
|
2012-11-15 07:17:06 +09:00
|
|
|
valid = false;
|
|
|
|
break;
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
var glyphNameIndexes = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < numGlyphs; ++i) {
|
2014-03-12 13:09:49 +09:00
|
|
|
var index = font.getUint16();
|
2012-11-08 02:03:08 +09:00
|
|
|
if (index >= 32768) {
|
|
|
|
valid = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
glyphNameIndexes.push(index);
|
|
|
|
}
|
|
|
|
if (!valid) {
|
|
|
|
break;
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
var customNames = [];
|
2014-07-24 14:17:56 +09:00
|
|
|
var strBuf = [];
|
2011-10-29 10:38:31 +09:00
|
|
|
while (font.pos < end) {
|
|
|
|
var stringLength = font.getByte();
|
2014-07-24 14:17:56 +09:00
|
|
|
strBuf.length = stringLength;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < stringLength; ++i) {
|
2014-07-24 14:17:56 +09:00
|
|
|
strBuf[i] = String.fromCharCode(font.getByte());
|
2013-07-01 05:45:15 +09:00
|
|
|
}
|
2014-07-24 14:17:56 +09:00
|
|
|
customNames.push(strBuf.join(''));
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
glyphNames = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < numGlyphs; ++i) {
|
2011-10-29 10:38:31 +09:00
|
|
|
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;
|
2015-04-02 23:26:14 +09:00
|
|
|
if (properties.defaultEncoding) {
|
|
|
|
glyphNames = properties.defaultEncoding;
|
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
properties.glyphNames = glyphNames;
|
2012-11-08 02:03:08 +09:00
|
|
|
return valid;
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
|
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;
|
2014-03-12 13:09:49 +09:00
|
|
|
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;
|
|
|
|
}
|
2014-03-12 13:09:49 +09:00
|
|
|
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 = {
|
2014-03-12 13:09:49 +09:00
|
|
|
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
|
2014-08-02 03:57:43 +09:00
|
|
|
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];
|
2016-03-02 05:39:33 +09:00
|
|
|
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) {
|
2014-03-12 13:09:49 +09:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-11-08 02:32:55 +09:00
|
|
|
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,
|
2012-11-08 02:32:55 +09:00
|
|
|
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,
|
2012-11-08 02:32:55 +09:00
|
|
|
-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];
|
2012-11-08 02:32:55 +09:00
|
|
|
// 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;
|
2012-11-08 02:32:55 +09:00
|
|
|
var stack = [];
|
2013-03-23 18:08:18 +09:00
|
|
|
var callstack = [];
|
|
|
|
var functionsCalled = [];
|
2012-11-08 02:32:55 +09:00
|
|
|
var tooComplexToFollowFunctions =
|
|
|
|
ttContext.tooComplexToFollowFunctions;
|
2013-03-23 18:08:18 +09:00
|
|
|
var inFDEF = false, ifLevel = 0, inELSE = 0;
|
2012-11-08 02:32:55 +09:00
|
|
|
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++]);
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
} 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++]);
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
} 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++]);
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
} 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++]);
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
} else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
|
2013-03-23 18:08:18 +09:00
|
|
|
if (inFDEF || inELSE) {
|
|
|
|
warn('TT: nested FDEFs not allowed');
|
2012-11-08 02:32:55 +09:00
|
|
|
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};
|
2012-11-08 02:32:55 +09:00
|
|
|
} 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;
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
} 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;
|
2012-11-08 02:32:55 +09:00
|
|
|
// 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
|
2014-03-13 10:19:04 +09:00
|
|
|
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;
|
|
|
|
}
|
2014-03-13 10:19:04 +09:00
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
// 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--;
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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');
|
2012-11-08 02:32:55 +09:00
|
|
|
// 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;
|
|
|
|
}
|
2014-03-03 06:40:48 +09:00
|
|
|
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;
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
}
|
2013-03-23 18:08:18 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function foldTTTable(table, content) {
|
2012-11-08 02:32:55 +09:00
|
|
|
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++) {
|
2012-11-08 02:32:55 +09:00
|
|
|
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++) {
|
2012-11-08 02:32:55 +09:00
|
|
|
result.set(content[j], pos);
|
|
|
|
pos += content[j].length;
|
|
|
|
}
|
|
|
|
table.data = result;
|
|
|
|
table.length = newLength;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-09 02:38:36 +09:00
|
|
|
function sanitizeTTPrograms(fpgm, prep, cvt) {
|
2012-11-08 02:32:55 +09:00
|
|
|
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
|
2012-11-08 02:32:55 +09:00
|
|
|
};
|
2013-03-23 18:08:18 +09:00
|
|
|
if (fpgm) {
|
|
|
|
sanitizeTTProgram(fpgm, ttContext);
|
|
|
|
}
|
2012-11-08 02:32:55 +09:00
|
|
|
if (prep) {
|
|
|
|
sanitizeTTProgram(prep, ttContext);
|
|
|
|
}
|
|
|
|
if (fpgm) {
|
2013-03-18 22:06:59 +09:00
|
|
|
checkInvalidFunctions(ttContext, maxFunctionDefs);
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
2013-11-09 02:38:36 +09:00
|
|
|
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;
|
2012-11-08 02:32:55 +09:00
|
|
|
}
|
|
|
|
|
2013-05-16 22:56:22 +09:00
|
|
|
// 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 '];
|
2011-06-21 11:30:28 +09:00
|
|
|
|
2011-06-21 13:49:59 +09:00
|
|
|
var header = readOpenTypeHeader(font);
|
|
|
|
var numTables = header.numTables;
|
2014-04-08 06:42:54 +09:00
|
|
|
var cff, cffFile;
|
2011-06-12 10:00:45 +09:00
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
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;
|
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
var table;
|
2011-06-21 13:49:59 +09:00
|
|
|
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
|
2011-06-21 13:49:59 +09:00
|
|
|
}
|
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;
|
2011-06-13 09:30:16 +09:00
|
|
|
}
|
2011-06-01 03:22:05 +09:00
|
|
|
|
2013-06-25 05:33:50 +09:00
|
|
|
var isTrueType = !tables['CFF '];
|
|
|
|
if (!isTrueType) {
|
|
|
|
// OpenType font
|
2015-07-25 19:26:36 +09:00
|
|
|
if ((header.version === 'OTTO' && properties.type !== 'CIDFontType2') ||
|
2016-01-28 02:04:13 +09:00
|
|
|
!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
|
|
|
|
2015-12-08 06:30:09 +09:00
|
|
|
adjustWidths(properties);
|
|
|
|
|
2013-06-25 08:45:31 +09:00
|
|
|
return this.convert(name, cff, properties);
|
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
delete tables['glyf'];
|
|
|
|
delete tables['loca'];
|
|
|
|
delete tables['fpgm'];
|
|
|
|
delete tables['prep'];
|
2013-06-25 05:33:50 +09:00
|
|
|
delete tables['cvt '];
|
2014-06-16 23:52:04 +09:00
|
|
|
this.isOpenType = true;
|
2013-06-25 05:33:50 +09:00
|
|
|
} else {
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['loca']) {
|
2016-01-09 19:50:48 +09:00
|
|
|
error('Required "loca" table is not found');
|
|
|
|
}
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['glyf']) {
|
2016-01-09 19:50:48 +09:00
|
|
|
warn('Required "glyf" table is not found -- trying to recover.');
|
|
|
|
// Note: We use `sanitizeGlyphLocations` to add dummy glyf data below.
|
2016-01-28 02:04:13 +09:00
|
|
|
tables['glyf'] = {
|
2016-01-09 19:50:48 +09:00
|
|
|
tag: 'glyf',
|
|
|
|
data: new Uint8Array(0),
|
|
|
|
};
|
2013-06-25 05:33:50 +09:00
|
|
|
}
|
2014-06-16 23:52:04 +09:00
|
|
|
this.isOpenType = false;
|
2013-06-25 05:33:50 +09:00
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['maxp']) {
|
2013-06-25 08:45:31 +09:00
|
|
|
error('Required "maxp" table is not found');
|
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
font.pos = (font.start || 0) + tables['maxp'].offset;
|
2014-04-17 04:31:16 +09:00
|
|
|
var version = font.getInt32();
|
2014-03-12 13:09:49 +09:00
|
|
|
var numGlyphs = font.getUint16();
|
2013-03-18 22:06:59 +09:00
|
|
|
var maxFunctionDefs = 0;
|
2016-01-28 02:04:13 +09:00
|
|
|
if (version >= 0x00010000 && tables['maxp'].length >= 22) {
|
2013-11-03 08:16:24 +09:00
|
|
|
// maxZones can be invalid
|
|
|
|
font.pos += 8;
|
2014-03-12 13:09:49 +09:00
|
|
|
var maxZones = font.getUint16();
|
2013-11-03 08:16:24 +09:00
|
|
|
if (maxZones > 2) { // reset to 2 if font has invalid maxZones
|
2016-01-28 02:04:13 +09:00
|
|
|
tables['maxp'].data[14] = 0;
|
|
|
|
tables['maxp'].data[15] = 2;
|
2013-11-03 08:16:24 +09:00
|
|
|
}
|
|
|
|
font.pos += 4;
|
2014-03-12 13:09:49 +09:00
|
|
|
maxFunctionDefs = font.getUint16();
|
2013-03-18 22:06:59 +09:00
|
|
|
}
|
|
|
|
|
2013-07-30 05:24:32 +09:00
|
|
|
var dupFirstEntry = false;
|
2014-08-02 03:57:43 +09:00
|
|
|
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++;
|
2016-01-28 02:04:13 +09:00
|
|
|
tables['maxp'].data[4] = numGlyphs >> 8;
|
|
|
|
tables['maxp'].data[5] = numGlyphs & 255;
|
2013-07-30 05:24:32 +09:00
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
var hintsValid = sanitizeTTPrograms(tables['fpgm'], tables['prep'],
|
2013-11-09 02:38:36 +09:00
|
|
|
tables['cvt '], maxFunctionDefs);
|
2013-03-18 22:06:59 +09:00
|
|
|
if (!hintsValid) {
|
2016-01-28 02:04:13 +09:00
|
|
|
delete tables['fpgm'];
|
|
|
|
delete tables['prep'];
|
2013-11-09 02:38:36 +09:00
|
|
|
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
|
2016-01-28 02:04:13 +09:00
|
|
|
sanitizeMetrics(font, tables['hhea'], tables['hmtx'], numGlyphs);
|
2012-11-08 02:32:55 +09:00
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['head']) {
|
2013-06-25 05:33:50 +09:00
|
|
|
error('Required "head" table is not found');
|
2013-01-19 08:06:12 +09:00
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
sanitizeHead(tables['head'], numGlyphs,
|
|
|
|
isTrueType ? tables['loca'].length : 0);
|
2013-06-25 05:33:50 +09:00
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
var missingGlyphs = Object.create(null);
|
2013-06-25 05:33:50 +09:00
|
|
|
if (isTrueType) {
|
2016-01-28 02:04:13 +09:00
|
|
|
var isGlyphLocationsLong = int16(tables['head'].data[50],
|
|
|
|
tables['head'].data[51]);
|
|
|
|
missingGlyphs = sanitizeGlyphLocations(tables['loca'], tables['glyf'],
|
2015-01-02 22:21:56 +09:00
|
|
|
numGlyphs, isGlyphLocationsLong,
|
|
|
|
hintsValid, dupFirstEntry);
|
2011-09-19 11:25:05 +09:00
|
|
|
}
|
|
|
|
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['hhea']) {
|
2013-06-25 08:45:31 +09:00
|
|
|
error('Required "hhea" table is not found');
|
|
|
|
}
|
2012-03-14 08:59:16 +09:00
|
|
|
|
2011-08-01 12:56:41 +09:00
|
|
|
// Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
|
|
|
|
// Sometimes it's 0. That needs to be fixed
|
2016-01-28 02:04:13 +09:00
|
|
|
if (tables['hhea'].data[10] === 0 && tables['hhea'].data[11] === 0) {
|
|
|
|
tables['hhea'].data[10] = 0xFF;
|
|
|
|
tables['hhea'].data[11] = 0xFF;
|
2011-08-01 12:56:41 +09:00
|
|
|
}
|
2011-06-13 09:30:16 +09:00
|
|
|
|
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 = {
|
2016-01-28 02:04:13 +09:00
|
|
|
unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
|
|
|
|
yMax: int16(tables['head'].data[42], tables['head'].data[43]),
|
2016-03-03 11:10:15 +09:00
|
|
|
yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
|
2016-01-28 02:04:13 +09:00
|
|
|
ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
|
2016-03-03 11:10:15 +09:00
|
|
|
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;
|
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
// The 'post' table has glyphs names.
|
2016-01-28 02:04:13 +09:00
|
|
|
if (tables['post']) {
|
|
|
|
var valid = readPostScriptTable(tables['post'], properties, numGlyphs);
|
2012-11-08 02:03:08 +09:00
|
|
|
if (!valid) {
|
2016-01-28 02:04:13 +09:00
|
|
|
tables['post'] = null;
|
2012-11-08 02:03:08 +09:00
|
|
|
}
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
|
|
|
|
2015-04-30 19:40:54 +09:00
|
|
|
var charCodeToGlyphId = [], charCode;
|
|
|
|
var toUnicode = properties.toUnicode, widths = properties.widths;
|
2015-06-19 04:53:15 +09:00
|
|
|
var skipToUnicode = (toUnicode instanceof IdentityToUnicodeMap ||
|
|
|
|
toUnicode.length === 0x10000);
|
2015-02-07 08:13:41 +09:00
|
|
|
|
2015-06-19 04:53:15 +09:00
|
|
|
// 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.
|
2015-04-30 19:40:54 +09:00
|
|
|
function hasGlyph(glyphId, charCode, widthCode) {
|
2015-02-07 08:13:41 +09:00
|
|
|
if (!missingGlyphs[glyphId]) {
|
|
|
|
return true;
|
|
|
|
}
|
2015-06-19 04:53:15 +09:00
|
|
|
if (!skipToUnicode && charCode >= 0 && toUnicode.has(charCode)) {
|
2015-04-30 19:40:54 +09:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
|
2015-02-07 08:13:41 +09:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-08 04:56:15 +09:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
warn('Unable to recover a standard glyph name for: ' + name);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-26 03:56:43 +09:00
|
|
|
if (properties.type === 'CIDFontType2') {
|
2011-11-29 12:54:07 +09:00
|
|
|
var cidToGidMap = properties.cidToGidMap || [];
|
2015-01-02 22:21:56 +09:00
|
|
|
var isCidToGidMapEmpty = cidToGidMap.length === 0;
|
|
|
|
|
2014-07-30 12:30:16 +09:00
|
|
|
properties.cMap.forEach(function(charCode, cid) {
|
2014-08-01 15:46:37 +09:00
|
|
|
assert(cid <= 0xffff, 'Max size of CID is 65,535');
|
2014-02-12 03:27:09 +09:00
|
|
|
var glyphId = -1;
|
2015-01-02 22:21:56 +09:00
|
|
|
if (isCidToGidMapEmpty) {
|
2016-03-02 10:05:33 +09:00
|
|
|
glyphId = cid;
|
2014-06-26 03:56:43 +09:00
|
|
|
} else if (cidToGidMap[cid] !== undefined) {
|
2014-02-12 03:27:09 +09:00
|
|
|
glyphId = cidToGidMap[cid];
|
2013-07-30 05:24:32 +09:00
|
|
|
}
|
2014-10-04 02:35:49 +09:00
|
|
|
|
2015-01-02 22:21:56 +09:00
|
|
|
if (glyphId >= 0 && glyphId < numGlyphs &&
|
2015-04-30 19:40:54 +09:00
|
|
|
hasGlyph(glyphId, charCode, cid)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
2011-11-28 11:43:23 +09:00
|
|
|
}
|
2014-07-30 12:30:16 +09:00
|
|
|
});
|
2014-02-12 03:27:09 +09:00
|
|
|
if (dupFirstEntry) {
|
|
|
|
charCodeToGlyphId[0] = numGlyphs - 1;
|
2011-11-29 10:47:37 +09:00
|
|
|
}
|
2011-07-15 21:58:14 +09:00
|
|
|
} else {
|
2013-01-31 03:01:32 +09:00
|
|
|
// Most of the following logic in this code branch is based on the
|
|
|
|
// 9.6.6.4 of the PDF spec.
|
2015-09-07 00:16:31 +09:00
|
|
|
var hasEncoding =
|
|
|
|
properties.differences.length > 0 || !!properties.baseEncodingName;
|
|
|
|
var cmapTable =
|
2016-01-28 02:04:13 +09:00
|
|
|
readCmapTable(tables['cmap'], font, this.isSymbolicFont, hasEncoding);
|
2013-01-31 03:01:32 +09:00
|
|
|
var cmapPlatformId = cmapTable.platformId;
|
|
|
|
var cmapEncodingId = cmapTable.encodingId;
|
|
|
|
var cmapMappings = cmapTable.mappings;
|
|
|
|
var cmapMappingsLength = cmapMappings.length;
|
2014-02-12 03:27:09 +09:00
|
|
|
|
|
|
|
// 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 (hasEncoding &&
|
|
|
|
(cmapPlatformId === 3 && cmapEncodingId === 1 ||
|
2014-08-23 07:03:13 +09:00
|
|
|
cmapPlatformId === 1 && cmapEncodingId === 0) ||
|
|
|
|
(cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
|
2016-01-22 06:18:46 +09:00
|
|
|
!!getEncoding(properties.baseEncodingName))) { // Temporary hack
|
2014-08-23 07:03:13 +09:00
|
|
|
// 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.
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var baseEncoding = [];
|
|
|
|
if (properties.baseEncodingName === 'MacRomanEncoding' ||
|
|
|
|
properties.baseEncodingName === 'WinAnsiEncoding') {
|
2016-01-22 06:18:46 +09:00
|
|
|
baseEncoding = getEncoding(properties.baseEncodingName);
|
2011-11-28 11:43:23 +09:00
|
|
|
}
|
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++) {
|
2016-03-08 04:56:15 +09:00
|
|
|
var glyphName, standardGlyphName;
|
2014-02-12 03:27:09 +09:00
|
|
|
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];
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
if (!glyphName) {
|
2013-01-31 03:01:32 +09:00
|
|
|
continue;
|
2012-01-11 13:01:24 +09:00
|
|
|
}
|
2016-03-08 04:56:15 +09:00
|
|
|
// Ensure that non-standard glyph names are resolved to valid ones.
|
|
|
|
standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
|
|
|
|
|
2015-08-09 19:31:05 +09:00
|
|
|
var unicodeOrCharCode, isUnicode = false;
|
2014-02-12 03:27:09 +09:00
|
|
|
if (cmapPlatformId === 3 && cmapEncodingId === 1) {
|
2016-03-08 04:56:15 +09:00
|
|
|
unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
|
2015-08-09 19:31:05 +09:00
|
|
|
isUnicode = true;
|
2014-02-12 03:27:09 +09:00
|
|
|
} else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
|
|
|
|
// TODO: the encoding needs to be updated with mac os table.
|
2016-03-08 04:56:15 +09:00
|
|
|
unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
|
2012-03-01 13:11:32 +09:00
|
|
|
}
|
2013-01-31 03:01:32 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var found = false;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < cmapMappingsLength; ++i) {
|
2015-08-09 19:31:05 +09:00
|
|
|
if (cmapMappings[i].charCode !== unicodeOrCharCode) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
var code = isUnicode ? charCode : unicodeOrCharCode;
|
|
|
|
if (hasGlyph(cmapMappings[i].glyphId, code, -1)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
2012-03-18 13:05:22 +09:00
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
if (!found && properties.glyphNames) {
|
2015-06-07 01:00:14 +09:00
|
|
|
// Try to map using the post table.
|
2014-07-30 12:30:16 +09:00
|
|
|
var glyphId = properties.glyphNames.indexOf(glyphName);
|
2016-03-08 04:56:15 +09:00
|
|
|
// 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);
|
|
|
|
}
|
2015-04-30 19:40:54 +09:00
|
|
|
if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
2016-01-18 06:03:21 +09:00
|
|
|
found = true;
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
2016-01-18 06:03:21 +09:00
|
|
|
if (!found) {
|
|
|
|
charCodeToGlyphId[charCode] = 0; // notdef
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
2015-01-11 22:54:12 +09:00
|
|
|
} 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;
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
} 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;
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
|
2012-02-21 08:19:12 +09:00
|
|
|
}
|
|
|
|
}
|
2012-03-26 04:15:40 +09:00
|
|
|
}
|
2011-11-28 11:43:23 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
if (charCodeToGlyphId.length === 0) {
|
2012-10-16 00:01:18 +09:00
|
|
|
// defines at least one glyph
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[0] = 0;
|
2012-10-16 00:01:18 +09:00
|
|
|
}
|
|
|
|
|
2012-03-26 04:15:40 +09:00
|
|
|
// Converting glyphs and ids into font's cmap table
|
2014-02-12 03:27:09 +09:00
|
|
|
var newMapping = adjustMapping(charCodeToGlyphId, properties);
|
|
|
|
this.toFontChar = newMapping.toFontChar;
|
2016-01-28 02:04:13 +09:00
|
|
|
tables['cmap'] = {
|
2014-02-12 03:27:09 +09:00
|
|
|
tag: 'cmap',
|
2015-12-03 09:47:20 +09:00
|
|
|
data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphs)
|
2014-02-12 03:27:09 +09:00
|
|
|
};
|
2011-07-13 17:31:34 +09:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2011-07-15 21:58:14 +09:00
|
|
|
// Rewrite the 'post' table if needed
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['post']) {
|
|
|
|
tables['post'] = {
|
2011-07-15 21:58:14 +09:00
|
|
|
tag: 'post',
|
2014-08-10 13:34:01 +09:00
|
|
|
data: createPostTable(properties)
|
2013-06-25 05:33:50 +09:00
|
|
|
};
|
2011-07-15 21:58:14 +09:00
|
|
|
}
|
2011-06-01 18:52:10 +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);
|
2013-06-25 08:45:31 +09:00
|
|
|
var parser = new CFFParser(cffFile, properties);
|
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);
|
|
|
|
}
|
2011-07-15 21:58:14 +09:00
|
|
|
}
|
2011-06-01 18:52:10 +09:00
|
|
|
|
2012-11-08 09:24:13 +09:00
|
|
|
// Re-creating 'name' table
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!tables['name']) {
|
|
|
|
tables['name'] = {
|
2011-07-15 21:58:14 +09:00
|
|
|
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
|
2016-01-28 02:04:13 +09:00
|
|
|
var namePrototype = readNameTable(tables['name']);
|
|
|
|
tables['name'].data = createNameTable(name, namePrototype);
|
2011-07-15 21:58:14 +09:00
|
|
|
}
|
2011-06-13 10:35:56 +09:00
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
var builder = new OpenTypeFileBuilder(header.version);
|
|
|
|
for (var tableTag in tables) {
|
|
|
|
builder.addTable(tableTag, tables[tableTag].data);
|
2011-07-15 21:58:14 +09:00
|
|
|
}
|
2014-08-10 08:18:41 +09:00
|
|
|
return builder.toArray();
|
2011-06-21 13:49:59 +09:00
|
|
|
},
|
2011-06-21 11:30:28 +09:00
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
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;
|
2011-07-23 01:57:26 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var mapping = font.getGlyphMapping(properties);
|
|
|
|
var newMapping = adjustMapping(mapping, properties);
|
|
|
|
this.toFontChar = newMapping.toFontChar;
|
|
|
|
var numGlyphs = font.numGlyphs;
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2014-06-24 03:55:51 +09:00
|
|
|
function getCharCodes(charCodeToGlyphId, glyphId) {
|
|
|
|
var charCodes = null;
|
2014-04-12 01:55:39 +09:00
|
|
|
for (var charCode in charCodeToGlyphId) {
|
|
|
|
if (glyphId === charCodeToGlyphId[charCode]) {
|
2014-06-24 03:55:51 +09:00
|
|
|
if (!charCodes) {
|
|
|
|
charCodes = [];
|
|
|
|
}
|
|
|
|
charCodes.push(charCode | 0);
|
2014-04-12 01:55:39 +09:00
|
|
|
}
|
|
|
|
}
|
2014-06-24 03:55:51 +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
|
|
|
}
|
2014-06-24 03:55:51 +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;
|
2014-02-12 03:27:09 +09:00
|
|
|
if (SEAC_ANALYSIS_ENABLED && seacs && seacs.length) {
|
2013-02-27 03:00:20 +09:00
|
|
|
var matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
|
2014-02-12 03:27:09 +09:00
|
|
|
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]];
|
2014-02-12 03:27:09 +09:00
|
|
|
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
|
|
|
|
2014-06-24 03:55:51 +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;
|
|
|
|
}
|
2014-06-24 03:55:51 +09:00
|
|
|
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];
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
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));
|
2014-08-10 08:18:41 +09:00
|
|
|
// Character to glyphs mapping
|
2015-12-03 09:47:20 +09:00
|
|
|
builder.addTable('cmap', createCmapTable(newMapping.charCodeToGlyphId,
|
|
|
|
numGlyphs));
|
2014-08-10 08:18:41 +09:00
|
|
|
// 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
|
|
|
|
2014-08-10 08:18:41 +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
|
2014-04-13 03:25:13 +09:00
|
|
|
string16(numGlyphs)); // Number of HMetrics
|
2011-07-03 08:17:28 +09:00
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
// Horizontal metrics
|
|
|
|
builder.addTable('hmtx', (function fontFieldsHmtx() {
|
2014-02-12 03:27:09 +09:00
|
|
|
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
|
2014-04-13 03:25:13 +09:00
|
|
|
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);
|
|
|
|
}
|
2011-11-28 11:43:23 +09:00
|
|
|
hmtx += string16(width) + string16(0);
|
|
|
|
}
|
2014-08-10 13:34:01 +09:00
|
|
|
return hmtx;
|
2014-08-10 08:18:41 +09:00
|
|
|
})());
|
2011-07-03 08:17:28 +09:00
|
|
|
|
2014-08-10 08:18:41 +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
|
2014-04-13 03:25:13 +09:00
|
|
|
string16(numGlyphs)); // Num of glyphs
|
2011-07-03 08:17:28 +09:00
|
|
|
|
2014-08-10 08:18:41 +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
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
// PostScript informations
|
2014-08-10 13:34:01 +09:00
|
|
|
builder.addTable('post', createPostTable(properties));
|
2011-06-04 00:48:32 +09:00
|
|
|
|
2014-08-10 08:18:41 +09:00
|
|
|
return builder.toArray();
|
2011-06-21 13:49:59 +09:00
|
|
|
},
|
|
|
|
|
2011-12-13 12:32:20 +09:00
|
|
|
get spaceWidth() {
|
2012-09-22 18:18:26 +09:00
|
|
|
if ('_shadowWidth' in this) {
|
2012-09-16 23:46:13 +09:00
|
|
|
return this._shadowWidth;
|
|
|
|
}
|
|
|
|
|
2011-12-13 12:32:20 +09:00
|
|
|
// trying to estimate space character width
|
2011-12-14 09:28:02 +09:00
|
|
|
var possibleSpaceReplacements = ['space', 'minus', 'one', 'i'];
|
2011-12-13 12:32:20 +09:00
|
|
|
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];
|
2011-12-13 12:32:20 +09:00
|
|
|
// finding the charcode via unicodeToCID map
|
|
|
|
var charcode = 0;
|
2014-02-12 03:27:09 +09:00
|
|
|
if (this.composite) {
|
2014-07-30 12:30:16 +09:00
|
|
|
if (this.cMap.contains(glyphUnicode)) {
|
2014-08-01 15:46:37 +09:00
|
|
|
charcode = this.cMap.lookup(glyphUnicode);
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
2011-12-13 12:32:20 +09:00
|
|
|
// ... via toUnicode map
|
2015-12-05 20:22:09 +09:00
|
|
|
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
|
|
|
}
|
2011-12-13 12:32:20 +09:00
|
|
|
// setting it to unicode if negative or undefined
|
2014-03-18 01:34:30 +09:00
|
|
|
if (charcode <= 0) {
|
2011-12-13 12:32:20 +09:00
|
|
|
charcode = glyphUnicode;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-12-13 12:32:20 +09:00
|
|
|
// trying to get width via charcode
|
|
|
|
width = this.widths[charcode];
|
2014-03-18 01:34:30 +09:00
|
|
|
if (width) {
|
2011-12-13 12:32:20 +09:00
|
|
|
break; // the non-zero width found
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-12-13 12:32:20 +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
|
2012-09-16 23:46:13 +09:00
|
|
|
this._shadowWidth = width;
|
|
|
|
return width;
|
2011-12-13 12:32:20 +09:00
|
|
|
},
|
|
|
|
|
2015-11-02 23:54:15 +09:00
|
|
|
charToGlyph: function Font_charToGlyph(charcode, isSpace) {
|
2014-05-20 06:27:54 +09:00
|
|
|
var fontCharCode, width, operatorListId;
|
2012-04-09 00:57:55 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var widthCode = charcode;
|
2014-07-30 12:30:16 +09:00
|
|
|
if (this.cMap && this.cMap.contains(charcode)) {
|
2014-08-01 15:46:37 +09:00
|
|
|
widthCode = this.cMap.lookup(charcode);
|
2011-10-29 10:38:31 +09:00
|
|
|
}
|
2014-04-08 06:42:54 +09:00
|
|
|
width = this.widths[widthCode];
|
2014-02-12 03:27:09 +09:00
|
|
|
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;
|
2014-03-13 21:56:12 +09:00
|
|
|
if (typeof unicode === 'number') {
|
|
|
|
unicode = String.fromCharCode(unicode);
|
2013-02-27 03:00:20 +09:00
|
|
|
}
|
2011-11-25 00:38:09 +09:00
|
|
|
|
2016-02-25 03:48:02 +09:00
|
|
|
var isInFont = charcode in this.toFontChar;
|
2014-02-12 03:27:09 +09:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2014-05-20 06:27:54 +09:00
|
|
|
if (this.isType3Font) {
|
2014-02-12 03:27:09 +09:00
|
|
|
// Font char code in this case is actually a glyph name.
|
2014-05-20 06:27:54 +09:00
|
|
|
operatorListId = fontCharCode;
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
2011-11-30 13:06:00 +09:00
|
|
|
|
2013-02-27 03:00:20 +09:00
|
|
|
var accent = null;
|
2014-02-12 03:27:09 +09:00
|
|
|
if (this.seacMap && this.seacMap[charcode]) {
|
2016-02-25 03:48:02 +09:00
|
|
|
isInFont = true;
|
2014-02-12 03:27:09 +09:00
|
|
|
var seac = this.seacMap[charcode];
|
|
|
|
fontCharCode = seac.baseFontCharCode;
|
2013-02-27 03:00:20 +09:00
|
|
|
accent = {
|
2014-02-12 03:27:09 +09:00
|
|
|
fontChar: String.fromCharCode(seac.accentFontCharCode),
|
2013-02-27 03:00:20 +09:00
|
|
|
offset: seac.accentOffset
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2014-03-13 21:56:12 +09:00
|
|
|
var fontChar = String.fromCharCode(fontCharCode);
|
|
|
|
|
|
|
|
var glyph = this.glyphCache[charcode];
|
|
|
|
if (!glyph ||
|
|
|
|
!glyph.matchesForCache(fontChar, unicode, accent, width, vmetric,
|
2016-02-25 03:48:02 +09:00
|
|
|
operatorListId, isSpace, isInFont)) {
|
2014-03-13 21:56:12 +09:00
|
|
|
glyph = new Glyph(fontChar, unicode, accent, width, vmetric,
|
2016-02-25 03:48:02 +09:00
|
|
|
operatorListId, isSpace, isInFont);
|
2014-03-13 21:56:12 +09:00
|
|
|
this.glyphCache[charcode] = glyph;
|
|
|
|
}
|
|
|
|
return glyph;
|
2011-10-29 10:38:31 +09:00
|
|
|
},
|
|
|
|
|
2012-04-05 05:43:26 +09:00
|
|
|
charsToGlyphs: function Font_charsToGlyphs(chars) {
|
2011-07-09 08:36:50 +09:00
|
|
|
var charsCache = this.charsCache;
|
2014-04-08 06:42:54 +09:00
|
|
|
var glyphs, glyph, charcode;
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-09 08:36:50 +09:00
|
|
|
// 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
|
|
|
}
|
2011-07-09 08:36:50 +09:00
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-07-09 08:36:50 +09:00
|
|
|
// lazily create the translation cache
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!charsCache) {
|
2011-07-09 08:36:50 +09:00
|
|
|
charsCache = this.charsCache = Object.create(null);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2011-09-16 09:26:32 +09:00
|
|
|
glyphs = [];
|
2013-01-15 23:20:58 +09:00
|
|
|
var charsCacheKey = chars;
|
2014-04-08 06:42:54 +09:00
|
|
|
var i = 0, ii;
|
2013-01-15 23:20:58 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
if (this.cMap) {
|
2011-08-12 03:41:03 +09:00
|
|
|
// composite fonts have multi-byte strings convert the string from
|
2011-08-19 22:04:34 +09:00
|
|
|
// single-byte to multi-byte
|
2016-01-28 02:04:13 +09:00
|
|
|
var c = Object.create(null);
|
2013-09-26 02:32:04 +09:00
|
|
|
while (i < chars.length) {
|
2014-08-11 14:27:04 +09:00
|
|
|
this.cMap.readCharCode(chars, i, c);
|
|
|
|
charcode = c.charcode;
|
|
|
|
var length = c.length;
|
2013-09-26 02:32:04 +09:00
|
|
|
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);
|
2011-07-10 14:12:42 +09:00
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
} 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);
|
2011-07-09 08:36:50 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enter the translated string into the cache
|
2013-01-15 23:20:58 +09:00
|
|
|
return (charsCache[charsCacheKey] = glyphs);
|
2011-06-13 09:30:16 +09:00
|
|
|
}
|
2011-06-20 07:46:58 +09:00
|
|
|
};
|
2011-06-04 00:48:32 +09:00
|
|
|
|
2011-12-09 07:18:43 +09:00
|
|
|
return Font;
|
2011-06-20 07:46:58 +09:00
|
|
|
})();
|
2011-06-04 00:48:32 +09:00
|
|
|
|
2012-08-30 06:11:56 +09:00
|
|
|
var ErrorFont = (function ErrorFontClosure() {
|
|
|
|
function ErrorFont(error) {
|
|
|
|
this.error = error;
|
2014-05-20 06:27:54 +09:00
|
|
|
this.loadedName = 'g_font_error';
|
|
|
|
this.loading = false;
|
2012-08-30 06:11:56 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
ErrorFont.prototype = {
|
|
|
|
charsToGlyphs: function ErrorFont_charsToGlyphs() {
|
|
|
|
return [];
|
2012-10-16 01:48:45 +09:00
|
|
|
},
|
|
|
|
exportData: function ErrorFont_exportData() {
|
|
|
|
return {error: this.error};
|
2012-08-30 06:11:56 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return ErrorFont;
|
|
|
|
})();
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
/**
|
|
|
|
* 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} 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;
|
|
|
|
|
2014-02-12 03:27:09 +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]);
|
2014-02-12 03:27:09 +09:00
|
|
|
if (glyphId >= 0) {
|
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
2014-10-04 04:11:20 +09:00
|
|
|
} else {
|
|
|
|
charCodeToGlyphId[charCode] = 0; // notdef
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!!(properties.flags & FontFlags.Symbolic)) {
|
|
|
|
// 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) {
|
2014-02-12 03:27:09 +09:00
|
|
|
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]);
|
2014-02-12 03:27:09 +09:00
|
|
|
if (glyphId >= 0) {
|
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
2014-10-04 04:11:20 +09:00
|
|
|
} else {
|
|
|
|
charCodeToGlyphId[charCode] = 0; // notdef
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lastly, merge in the differences.
|
|
|
|
var differences = properties.differences;
|
|
|
|
if (differences) {
|
2014-04-08 06:42:54 +09:00
|
|
|
for (charCode in differences) {
|
2014-02-12 03:27:09 +09:00
|
|
|
var glyphName = differences[charCode];
|
2014-04-08 06:42:54 +09:00
|
|
|
glyphId = glyphNames.indexOf(glyphName);
|
2014-02-12 03:27:09 +09:00
|
|
|
if (glyphId >= 0) {
|
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
2014-10-04 04:11:20 +09:00
|
|
|
} else {
|
|
|
|
charCodeToGlyphId[charCode] = 0; // notdef
|
2014-02-12 03:27:09 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return charCodeToGlyphId;
|
|
|
|
}
|
|
|
|
|
2013-01-10 10:33:59 +09:00
|
|
|
/*
|
|
|
|
* CharStrings are encoded following the the CharString Encoding sequence
|
|
|
|
* describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
|
|
|
|
* The value in a byte indicates a command, a number, or subsequent bytes
|
|
|
|
* that are to be interpreted in a special way.
|
|
|
|
*
|
|
|
|
* CharString Number Encoding:
|
|
|
|
* A CharString byte containing the values from 32 through 255 inclusive
|
|
|
|
* indicate an integer. These values are decoded in four ranges.
|
|
|
|
*
|
|
|
|
* 1. A CharString byte containing a value, v, between 32 and 246 inclusive,
|
|
|
|
* indicate the integer v - 139. Thus, the integer values from -107 through
|
|
|
|
* 107 inclusive may be encoded in single byte.
|
|
|
|
*
|
|
|
|
* 2. A CharString byte containing a value, v, between 247 and 250 inclusive,
|
|
|
|
* indicates an integer involving the next byte, w, according to the formula:
|
|
|
|
* [(v - 247) x 256] + w + 108
|
|
|
|
*
|
|
|
|
* 3. A CharString byte containing a value, v, between 251 and 254 inclusive,
|
|
|
|
* indicates an integer involving the next byte, w, according to the formula:
|
|
|
|
* -[(v - 251) * 256] - w - 108
|
|
|
|
*
|
|
|
|
* 4. A CharString containing the value 255 indicates that the next 4 bytes
|
|
|
|
* are a two complement signed integer. The first of these bytes contains the
|
|
|
|
* highest order bits, the second byte contains the next higher order bits
|
|
|
|
* and the fourth byte contain the lowest order bits.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* CharString Command Encoding:
|
|
|
|
* CharStrings commands are encoded in 1 or 2 bytes.
|
|
|
|
*
|
|
|
|
* Single byte commands are encoded in 1 byte that contains a value between
|
|
|
|
* 0 and 31 inclusive.
|
|
|
|
* If a command byte contains the value 12, then the value in the next byte
|
|
|
|
* indicates a command. This "escape" mechanism allows many extra commands
|
|
|
|
* to be encoded and this encoding technique helps to minimize the length of
|
|
|
|
* the charStrings.
|
|
|
|
*/
|
|
|
|
var Type1CharString = (function Type1CharStringClosure() {
|
|
|
|
var COMMAND_MAP = {
|
|
|
|
'hstem': [1],
|
|
|
|
'vstem': [3],
|
|
|
|
'vmoveto': [4],
|
|
|
|
'rlineto': [5],
|
|
|
|
'hlineto': [6],
|
|
|
|
'vlineto': [7],
|
|
|
|
'rrcurveto': [8],
|
|
|
|
'callsubr': [10],
|
|
|
|
'flex': [12, 35],
|
|
|
|
'drop' : [12, 18],
|
|
|
|
'endchar': [14],
|
|
|
|
'rmoveto': [21],
|
|
|
|
'hmoveto': [22],
|
|
|
|
'vhcurveto': [30],
|
|
|
|
'hvcurveto': [31]
|
|
|
|
};
|
|
|
|
|
|
|
|
function Type1CharString() {
|
|
|
|
this.width = 0;
|
|
|
|
this.lsb = 0;
|
|
|
|
this.flexing = false;
|
|
|
|
this.output = [];
|
|
|
|
this.stack = [];
|
2012-08-12 01:57:42 +09:00
|
|
|
}
|
|
|
|
|
2013-01-10 10:33:59 +09:00
|
|
|
Type1CharString.prototype = {
|
|
|
|
convert: function Type1CharString_convert(encoded, subrs) {
|
|
|
|
var count = encoded.length;
|
|
|
|
var error = false;
|
2014-04-08 06:42:54 +09:00
|
|
|
var wx, sbx, subrNumber;
|
2013-01-10 10:33:59 +09:00
|
|
|
for (var i = 0; i < count; i++) {
|
|
|
|
var value = encoded[i];
|
|
|
|
if (value < 32) {
|
|
|
|
if (value === 12) {
|
|
|
|
value = (value << 8) + encoded[++i];
|
|
|
|
}
|
|
|
|
switch (value) {
|
|
|
|
case 1: // hstem
|
|
|
|
if (!HINTING_ENABLED) {
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.hstem);
|
|
|
|
break;
|
|
|
|
case 3: // vstem
|
|
|
|
if (!HINTING_ENABLED) {
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.vstem);
|
|
|
|
break;
|
|
|
|
case 4: // vmoveto
|
|
|
|
if (this.flexing) {
|
|
|
|
if (this.stack.length < 1) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Add the dx for flex and but also swap the values so they are
|
|
|
|
// the right order.
|
|
|
|
var dy = this.stack.pop();
|
|
|
|
this.stack.push(0, dy);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
error = this.executeCommand(1, COMMAND_MAP.vmoveto);
|
|
|
|
break;
|
|
|
|
case 5: // rlineto
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.rlineto);
|
|
|
|
break;
|
|
|
|
case 6: // hlineto
|
|
|
|
error = this.executeCommand(1, COMMAND_MAP.hlineto);
|
|
|
|
break;
|
|
|
|
case 7: // vlineto
|
|
|
|
error = this.executeCommand(1, COMMAND_MAP.vlineto);
|
|
|
|
break;
|
|
|
|
case 8: // rrcurveto
|
|
|
|
error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
|
|
|
|
break;
|
|
|
|
case 9: // closepath
|
|
|
|
// closepath is a Type1 command that does not take argument and is
|
|
|
|
// useless in Type2 and it can simply be ignored.
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
case 10: // callsubr
|
|
|
|
if (this.stack.length < 1) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
2014-04-08 06:42:54 +09:00
|
|
|
subrNumber = this.stack.pop();
|
2013-01-10 10:33:59 +09:00
|
|
|
error = this.convert(subrs[subrNumber], subrs);
|
|
|
|
break;
|
|
|
|
case 11: // return
|
|
|
|
return error;
|
|
|
|
case 13: // hsbw
|
|
|
|
if (this.stack.length < 2) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// To convert to type2 we have to move the width value to the
|
|
|
|
// first part of the charstring and then use hmoveto with lsb.
|
2014-04-08 06:42:54 +09:00
|
|
|
wx = this.stack.pop();
|
|
|
|
sbx = this.stack.pop();
|
2013-01-10 10:33:59 +09:00
|
|
|
this.lsb = sbx;
|
|
|
|
this.width = wx;
|
2014-08-14 11:15:40 +09:00
|
|
|
this.stack.push(wx, sbx);
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.hmoveto);
|
2013-01-10 10:33:59 +09:00
|
|
|
break;
|
|
|
|
case 14: // endchar
|
|
|
|
this.output.push(COMMAND_MAP.endchar[0]);
|
|
|
|
break;
|
|
|
|
case 21: // rmoveto
|
|
|
|
if (this.flexing) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.rmoveto);
|
|
|
|
break;
|
|
|
|
case 22: // hmoveto
|
|
|
|
if (this.flexing) {
|
|
|
|
// Add the dy for flex.
|
|
|
|
this.stack.push(0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
error = this.executeCommand(1, COMMAND_MAP.hmoveto);
|
|
|
|
break;
|
|
|
|
case 30: // vhcurveto
|
|
|
|
error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
|
|
|
|
break;
|
|
|
|
case 31: // hvcurveto
|
|
|
|
error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 0: // dotsection
|
|
|
|
// dotsection is a Type1 command to specify some hinting feature
|
|
|
|
// for dots that do not take a parameter and it can safely be
|
|
|
|
// ignored for Type2.
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 1: // vstem3
|
|
|
|
if (!HINTING_ENABLED) {
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// [vh]stem3 are Type1 only and Type2 supports [vh]stem with
|
|
|
|
// multiple parameters, so instead of returning [vh]stem3 take a
|
|
|
|
// shortcut and return [vhstem] instead.
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.vstem);
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 2: // hstem3
|
|
|
|
if (!HINTING_ENABLED) {
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// See vstem3.
|
|
|
|
error = this.executeCommand(2, COMMAND_MAP.hstem);
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 6: // seac
|
|
|
|
// seac is like type 2's special endchar but it doesn't use the
|
|
|
|
// first argument asb, so remove it.
|
2013-02-27 03:00:20 +09:00
|
|
|
if (SEAC_ANALYSIS_ENABLED) {
|
|
|
|
this.seac = this.stack.splice(-4, 4);
|
|
|
|
error = this.executeCommand(0, COMMAND_MAP.endchar);
|
|
|
|
} else {
|
|
|
|
error = this.executeCommand(4, COMMAND_MAP.endchar);
|
|
|
|
}
|
2013-01-10 10:33:59 +09:00
|
|
|
break;
|
|
|
|
case (12 << 8) + 7: // sbw
|
|
|
|
if (this.stack.length < 4) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// To convert to type2 we have to move the width value to the
|
|
|
|
// first part of the charstring and then use rmoveto with
|
|
|
|
// (dx, dy). The height argument will not be used for vmtx and
|
|
|
|
// vhea tables reconstruction -- ignoring it.
|
|
|
|
var wy = this.stack.pop();
|
2014-04-08 06:42:54 +09:00
|
|
|
wx = this.stack.pop();
|
2013-01-10 10:33:59 +09:00
|
|
|
var sby = this.stack.pop();
|
2014-04-08 06:42:54 +09:00
|
|
|
sbx = this.stack.pop();
|
2013-01-10 10:33:59 +09:00
|
|
|
this.lsb = sbx;
|
|
|
|
this.width = wx;
|
2014-08-14 11:15:40 +09:00
|
|
|
this.stack.push(wx, sbx, sby);
|
|
|
|
error = this.executeCommand(3, COMMAND_MAP.rmoveto);
|
2013-01-10 10:33:59 +09:00
|
|
|
break;
|
|
|
|
case (12 << 8) + 12: // div
|
|
|
|
if (this.stack.length < 2) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var num2 = this.stack.pop();
|
|
|
|
var num1 = this.stack.pop();
|
|
|
|
this.stack.push(num1 / num2);
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 16: // callothersubr
|
|
|
|
if (this.stack.length < 2) {
|
|
|
|
error = true;
|
|
|
|
break;
|
|
|
|
}
|
2014-04-08 06:42:54 +09:00
|
|
|
subrNumber = this.stack.pop();
|
2013-01-10 10:33:59 +09:00
|
|
|
var numArgs = this.stack.pop();
|
|
|
|
if (subrNumber === 0 && numArgs === 3) {
|
|
|
|
var flexArgs = this.stack.splice(this.stack.length - 17, 17);
|
|
|
|
this.stack.push(
|
|
|
|
flexArgs[2] + flexArgs[0], // bcp1x + rpx
|
|
|
|
flexArgs[3] + flexArgs[1], // bcp1y + rpy
|
|
|
|
flexArgs[4], // bcp2x
|
|
|
|
flexArgs[5], // bcp2y
|
|
|
|
flexArgs[6], // p2x
|
|
|
|
flexArgs[7], // p2y
|
|
|
|
flexArgs[8], // bcp3x
|
|
|
|
flexArgs[9], // bcp3y
|
|
|
|
flexArgs[10], // bcp4x
|
|
|
|
flexArgs[11], // bcp4y
|
|
|
|
flexArgs[12], // p3x
|
|
|
|
flexArgs[13], // p3y
|
|
|
|
flexArgs[14] // flexDepth
|
|
|
|
// 15 = finalx unused by flex
|
|
|
|
// 16 = finaly unused by flex
|
|
|
|
);
|
|
|
|
error = this.executeCommand(13, COMMAND_MAP.flex, true);
|
|
|
|
this.flexing = false;
|
|
|
|
this.stack.push(flexArgs[15], flexArgs[16]);
|
|
|
|
} else if (subrNumber === 1 && numArgs === 0) {
|
|
|
|
this.flexing = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 17: // pop
|
|
|
|
// Ignore this since it is only used with othersubr.
|
|
|
|
break;
|
|
|
|
case (12 << 8) + 33: // setcurrentpoint
|
|
|
|
// Ignore for now.
|
|
|
|
this.stack = [];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
warn('Unknown type 1 charstring command of "' + value + '"');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (error) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
} else if (value <= 246) {
|
|
|
|
value = value - 139;
|
|
|
|
} else if (value <= 250) {
|
|
|
|
value = ((value - 247) * 256) + encoded[++i] + 108;
|
|
|
|
} else if (value <= 254) {
|
|
|
|
value = -((value - 251) * 256) - encoded[++i] - 108;
|
|
|
|
} else {
|
|
|
|
value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 |
|
|
|
|
(encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
|
|
|
|
}
|
|
|
|
this.stack.push(value);
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
},
|
|
|
|
|
|
|
|
executeCommand: function(howManyArgs, command, keepStack) {
|
|
|
|
var stackLength = this.stack.length;
|
|
|
|
if (howManyArgs > stackLength) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var start = stackLength - howManyArgs;
|
|
|
|
for (var i = start; i < stackLength; i++) {
|
|
|
|
var value = this.stack[i];
|
|
|
|
if (value === (value | 0)) { // int
|
|
|
|
this.output.push(28, (value >> 8) & 0xff, value & 0xff);
|
|
|
|
} else { // fixed point
|
|
|
|
value = (65536 * value) | 0;
|
|
|
|
this.output.push(255,
|
|
|
|
(value >> 24) & 0xFF,
|
|
|
|
(value >> 16) & 0xFF,
|
|
|
|
(value >> 8) & 0xFF,
|
|
|
|
value & 0xFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.output.push.apply(this.output, command);
|
|
|
|
if (keepStack) {
|
|
|
|
this.stack.splice(start, howManyArgs);
|
|
|
|
} else {
|
2014-03-13 12:49:46 +09:00
|
|
|
this.stack.length = 0;
|
2013-01-10 10:33:59 +09:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return Type1CharString;
|
2012-08-12 01:57:42 +09:00
|
|
|
})();
|
|
|
|
|
2011-08-26 02:20:19 +09:00
|
|
|
/*
|
2011-06-17 20:55:42 +09:00
|
|
|
* Type1Parser encapsulate the needed code for parsing a Type1 font
|
2011-07-06 15:06:45 +09:00
|
|
|
* program. Some of its logic depends on the Type2 charstrings
|
|
|
|
* structure.
|
2013-03-28 09:15:44 +09:00
|
|
|
* Note: this doesn't really parse the font since that would require evaluation
|
|
|
|
* of PostScript, but it is possible in most cases to extract what we need
|
|
|
|
* without a full parse.
|
2011-06-13 09:30:16 +09:00
|
|
|
*/
|
2013-03-28 09:15:44 +09:00
|
|
|
var Type1Parser = (function Type1ParserClosure() {
|
2011-06-13 09:30:16 +09:00
|
|
|
/*
|
|
|
|
* Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence
|
|
|
|
* of Plaintext Bytes. The function took a key as a parameter which can be
|
|
|
|
* for decrypting the eexec block of for decoding charStrings.
|
|
|
|
*/
|
2012-11-10 06:34:11 +09:00
|
|
|
var EEXEC_ENCRYPT_KEY = 55665;
|
|
|
|
var CHAR_STRS_ENCRYPT_KEY = 4330;
|
2011-06-10 08:20:00 +09:00
|
|
|
|
2014-04-17 21:52:33 +09:00
|
|
|
function isHexDigit(code) {
|
|
|
|
return code >= 48 && code <= 57 || // '0'-'9'
|
|
|
|
code >= 65 && code <= 70 || // 'A'-'F'
|
|
|
|
code >= 97 && code <= 102; // 'a'-'f'
|
|
|
|
}
|
2011-06-04 00:48:32 +09:00
|
|
|
|
2014-04-17 21:52:33 +09:00
|
|
|
function decrypt(data, key, discardNumber) {
|
2016-01-29 06:25:52 +09:00
|
|
|
if (discardNumber >= data.length) {
|
|
|
|
return new Uint8Array(0);
|
|
|
|
}
|
|
|
|
var r = key | 0, c1 = 52845, c2 = 22719, i, j;
|
|
|
|
for (i = 0; i < discardNumber; i++) {
|
|
|
|
r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1);
|
|
|
|
}
|
|
|
|
var count = data.length - discardNumber;
|
2014-04-17 21:52:33 +09:00
|
|
|
var decrypted = new Uint8Array(count);
|
2016-01-29 06:25:52 +09:00
|
|
|
for (i = discardNumber, j = 0; j < count; i++, j++) {
|
2014-04-17 21:52:33 +09:00
|
|
|
var value = data[i];
|
2016-01-29 06:25:52 +09:00
|
|
|
decrypted[j] = value ^ (r >> 8);
|
2011-06-13 09:30:16 +09:00
|
|
|
r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
|
|
|
|
}
|
2016-01-29 06:25:52 +09:00
|
|
|
return decrypted;
|
2014-04-17 21:52:33 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
function decryptAscii(data, key, discardNumber) {
|
|
|
|
var r = key | 0, c1 = 52845, c2 = 22719;
|
|
|
|
var count = data.length, maybeLength = count >>> 1;
|
|
|
|
var decrypted = new Uint8Array(maybeLength);
|
|
|
|
var i, j;
|
|
|
|
for (i = 0, j = 0; i < count; i++) {
|
|
|
|
var digit1 = data[i];
|
|
|
|
if (!isHexDigit(digit1)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
var digit2;
|
|
|
|
while (i < count && !isHexDigit(digit2 = data[i])) {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
if (i < count) {
|
|
|
|
var value = parseInt(String.fromCharCode(digit1, digit2), 16);
|
|
|
|
decrypted[j++] = value ^ (r >> 8);
|
|
|
|
r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Array.prototype.slice.call(decrypted, discardNumber, j);
|
2011-09-12 05:04:17 +09:00
|
|
|
}
|
2011-06-04 00:48:32 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
function isSpecial(c) {
|
2013-07-01 05:45:15 +09:00
|
|
|
return c === 0x2F || // '/'
|
|
|
|
c === 0x5B || c === 0x5D || // '[', ']'
|
|
|
|
c === 0x7B || c === 0x7D || // '{', '}'
|
|
|
|
c === 0x28 || c === 0x29; // '(', ')'
|
2011-09-12 05:04:17 +09:00
|
|
|
}
|
2011-07-01 18:28:22 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
function Type1Parser(stream, encrypted) {
|
|
|
|
if (encrypted) {
|
2014-04-17 21:52:33 +09:00
|
|
|
var data = stream.getBytes();
|
|
|
|
var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) &&
|
|
|
|
isHexDigit(data[2]) && isHexDigit(data[3]));
|
|
|
|
stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) :
|
|
|
|
decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
|
2013-03-28 09:15:44 +09:00
|
|
|
}
|
|
|
|
this.stream = stream;
|
2013-07-01 05:45:15 +09:00
|
|
|
this.nextChar();
|
2011-09-12 05:04:17 +09:00
|
|
|
}
|
2011-07-10 13:12:11 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
Type1Parser.prototype = {
|
|
|
|
readNumberArray: function Type1Parser_readNumberArray() {
|
|
|
|
this.getToken(); // read '[' or '{' (arrays can start with either)
|
|
|
|
var array = [];
|
|
|
|
while (true) {
|
|
|
|
var token = this.getToken();
|
|
|
|
if (token === null || token === ']' || token === '}') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
array.push(parseFloat(token || 0));
|
|
|
|
}
|
|
|
|
return array;
|
|
|
|
},
|
2013-01-09 03:44:39 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
readNumber: function Type1Parser_readNumber() {
|
|
|
|
var token = this.getToken();
|
|
|
|
return parseFloat(token || 0);
|
|
|
|
},
|
2013-01-09 03:44:39 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
readInt: function Type1Parser_readInt() {
|
|
|
|
// Use '| 0' to prevent setting a double into length such as the double
|
|
|
|
// does not flow into the loop variable.
|
|
|
|
var token = this.getToken();
|
|
|
|
return parseInt(token || 0, 10) | 0;
|
|
|
|
},
|
2013-01-09 03:44:39 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
readBoolean: function Type1Parser_readBoolean() {
|
|
|
|
var token = this.getToken();
|
2013-01-09 03:44:39 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
// Use 1 and 0 since that's what type2 charstrings use.
|
|
|
|
return token === 'true' ? 1 : 0;
|
|
|
|
},
|
2011-09-06 05:09:40 +09:00
|
|
|
|
2013-07-01 05:45:15 +09:00
|
|
|
nextChar : function Type1_nextChar() {
|
|
|
|
return (this.currentChar = this.stream.getByte());
|
|
|
|
},
|
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
getToken: function Type1Parser_getToken() {
|
|
|
|
// Eat whitespace and comments.
|
|
|
|
var comment = false;
|
2013-07-01 05:45:15 +09:00
|
|
|
var ch = this.currentChar;
|
2013-03-28 09:15:44 +09:00
|
|
|
while (true) {
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch === -1) {
|
2013-03-28 09:15:44 +09:00
|
|
|
return null;
|
2013-07-01 05:45:15 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
|
|
|
|
if (comment) {
|
2013-07-01 05:45:15 +09:00
|
|
|
if (ch === 0x0A || ch === 0x0D) {
|
2013-03-28 09:15:44 +09:00
|
|
|
comment = false;
|
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
} else if (ch === 0x25) { // '%'
|
2013-03-28 09:15:44 +09:00
|
|
|
comment = true;
|
|
|
|
} else if (!Lexer.isSpace(ch)) {
|
|
|
|
break;
|
2011-09-02 02:50:04 +09:00
|
|
|
}
|
2013-07-01 05:45:15 +09:00
|
|
|
ch = this.nextChar();
|
2011-07-01 18:28:22 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
if (isSpecial(ch)) {
|
2013-07-01 05:45:15 +09:00
|
|
|
this.nextChar();
|
|
|
|
return String.fromCharCode(ch);
|
2013-03-28 09:15:44 +09:00
|
|
|
}
|
|
|
|
var token = '';
|
|
|
|
do {
|
2013-07-01 05:45:15 +09:00
|
|
|
token += String.fromCharCode(ch);
|
|
|
|
ch = this.nextChar();
|
|
|
|
} while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch));
|
2013-03-28 09:15:44 +09:00
|
|
|
return token;
|
|
|
|
},
|
2011-08-17 02:25:02 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
/*
|
|
|
|
* Returns an object containing a Subrs array and a CharStrings
|
|
|
|
* array extracted from and eexec encrypted block of data
|
|
|
|
*/
|
|
|
|
extractFontProgram: function Type1Parser_extractFontProgram() {
|
|
|
|
var stream = this.stream;
|
|
|
|
|
|
|
|
var subrs = [], charstrings = [];
|
2016-01-28 02:04:13 +09:00
|
|
|
var privateData = Object.create(null);
|
|
|
|
privateData['lenIV'] = 4;
|
2013-03-28 09:15:44 +09:00
|
|
|
var program = {
|
|
|
|
subrs: [],
|
|
|
|
charstrings: [],
|
|
|
|
properties: {
|
2016-01-28 02:04:13 +09:00
|
|
|
'privateData': privateData
|
2013-03-28 09:15:44 +09:00
|
|
|
}
|
2011-08-25 09:29:22 +09:00
|
|
|
};
|
2014-04-08 06:42:54 +09:00
|
|
|
var token, length, data, lenIV, encoded;
|
2013-03-28 09:15:44 +09:00
|
|
|
while ((token = this.getToken()) !== null) {
|
|
|
|
if (token !== '/') {
|
|
|
|
continue;
|
2011-07-01 12:16:27 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
token = this.getToken();
|
|
|
|
switch (token) {
|
|
|
|
case 'CharStrings':
|
|
|
|
// The number immediately following CharStrings must be greater or
|
|
|
|
// equal to the number of CharStrings.
|
|
|
|
this.getToken();
|
|
|
|
this.getToken(); // read in 'dict'
|
|
|
|
this.getToken(); // read in 'dup'
|
|
|
|
this.getToken(); // read in 'begin'
|
|
|
|
while(true) {
|
|
|
|
token = this.getToken();
|
|
|
|
if (token === null || token === 'end') {
|
|
|
|
break;
|
2011-08-17 02:25:02 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
|
|
|
|
if (token !== '/') {
|
|
|
|
continue;
|
2013-01-04 06:29:55 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
var glyph = this.getToken();
|
2014-04-08 06:42:54 +09:00
|
|
|
length = this.readInt();
|
2013-03-28 09:15:44 +09:00
|
|
|
this.getToken(); // read in 'RD' or '-|'
|
2014-04-08 06:42:54 +09:00
|
|
|
data = stream.makeSubStream(stream.pos, length);
|
|
|
|
lenIV = program.properties.privateData['lenIV'];
|
|
|
|
encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
|
2013-03-28 09:15:44 +09:00
|
|
|
// Skip past the required space and binary data.
|
2013-07-01 05:45:15 +09:00
|
|
|
stream.skip(length);
|
|
|
|
this.nextChar();
|
2013-03-28 09:15:44 +09:00
|
|
|
token = this.getToken(); // read in 'ND' or '|-'
|
|
|
|
if (token === 'noaccess') {
|
|
|
|
this.getToken(); // read in 'def'
|
|
|
|
}
|
|
|
|
charstrings.push({
|
|
|
|
glyph: glyph,
|
|
|
|
encoded: encoded
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'Subrs':
|
|
|
|
var num = this.readInt();
|
|
|
|
this.getToken(); // read in 'array'
|
2013-05-17 01:21:47 +09:00
|
|
|
while ((token = this.getToken()) === 'dup') {
|
2013-03-28 09:15:44 +09:00
|
|
|
var index = this.readInt();
|
2014-04-08 06:42:54 +09:00
|
|
|
length = this.readInt();
|
2013-03-28 09:15:44 +09:00
|
|
|
this.getToken(); // read in 'RD' or '-|'
|
2014-04-08 06:42:54 +09:00
|
|
|
data = stream.makeSubStream(stream.pos, length);
|
|
|
|
lenIV = program.properties.privateData['lenIV'];
|
|
|
|
encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV);
|
2013-03-28 09:15:44 +09:00
|
|
|
// Skip past the required space and binary data.
|
2013-07-01 05:45:15 +09:00
|
|
|
stream.skip(length);
|
|
|
|
this.nextChar();
|
2013-03-28 09:15:44 +09:00
|
|
|
token = this.getToken(); // read in 'NP' or '|'
|
|
|
|
if (token === 'noaccess') {
|
|
|
|
this.getToken(); // read in 'put'
|
|
|
|
}
|
|
|
|
subrs[index] = encoded;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'BlueValues':
|
|
|
|
case 'OtherBlues':
|
|
|
|
case 'FamilyBlues':
|
|
|
|
case 'FamilyOtherBlues':
|
|
|
|
var blueArray = this.readNumberArray();
|
|
|
|
// *Blue* values may contain invalid data: disables reading of
|
|
|
|
// those values when hinting is disabled.
|
|
|
|
if (blueArray.length > 0 && (blueArray.length % 2) === 0 &&
|
|
|
|
HINTING_ENABLED) {
|
|
|
|
program.properties.privateData[token] = blueArray;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'StemSnapH':
|
|
|
|
case 'StemSnapV':
|
|
|
|
program.properties.privateData[token] = this.readNumberArray();
|
|
|
|
break;
|
|
|
|
case 'StdHW':
|
|
|
|
case 'StdVW':
|
|
|
|
program.properties.privateData[token] =
|
|
|
|
this.readNumberArray()[0];
|
|
|
|
break;
|
|
|
|
case 'BlueShift':
|
|
|
|
case 'lenIV':
|
|
|
|
case 'BlueFuzz':
|
|
|
|
case 'BlueScale':
|
|
|
|
case 'LanguageGroup':
|
|
|
|
case 'ExpansionFactor':
|
|
|
|
program.properties.privateData[token] = this.readNumber();
|
|
|
|
break;
|
|
|
|
case 'ForceBold':
|
|
|
|
program.properties.privateData[token] = this.readBoolean();
|
|
|
|
break;
|
2011-07-01 12:16:27 +09:00
|
|
|
}
|
2011-06-13 09:30:16 +09:00
|
|
|
}
|
2011-07-01 12:16:27 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
for (var i = 0; i < charstrings.length; i++) {
|
2014-04-08 06:42:54 +09:00
|
|
|
glyph = charstrings[i].glyph;
|
|
|
|
encoded = charstrings[i].encoded;
|
2013-03-28 09:15:44 +09:00
|
|
|
var charString = new Type1CharString();
|
|
|
|
var error = charString.convert(encoded, subrs);
|
|
|
|
var output = charString.output;
|
|
|
|
if (error) {
|
|
|
|
// It seems when FreeType encounters an error while evaluating a glyph
|
|
|
|
// that it completely ignores the glyph so we'll mimic that behaviour
|
|
|
|
// here and put an endchar to make the validator happy.
|
|
|
|
output = [14];
|
|
|
|
}
|
|
|
|
program.charstrings.push({
|
2014-02-12 03:27:09 +09:00
|
|
|
glyphName: glyph,
|
|
|
|
charstring: output,
|
|
|
|
width: charString.width,
|
2013-03-28 09:15:44 +09:00
|
|
|
lsb: charString.lsb,
|
2014-02-12 03:27:09 +09:00
|
|
|
seac: charString.seac
|
2013-03-28 09:15:44 +09:00
|
|
|
});
|
2013-01-10 10:33:59 +09:00
|
|
|
}
|
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
return program;
|
|
|
|
},
|
2011-07-01 14:16:56 +09:00
|
|
|
|
2013-03-28 09:15:44 +09:00
|
|
|
extractFontHeader: function Type1Parser_extractFontHeader(properties) {
|
|
|
|
var token;
|
|
|
|
while ((token = this.getToken()) !== null) {
|
|
|
|
if (token !== '/') {
|
|
|
|
continue;
|
2011-08-25 09:29:22 +09:00
|
|
|
}
|
2013-03-28 09:15:44 +09:00
|
|
|
token = this.getToken();
|
2011-07-01 14:16:56 +09:00
|
|
|
switch (token) {
|
2013-03-28 09:15:44 +09:00
|
|
|
case 'FontMatrix':
|
|
|
|
var matrix = this.readNumberArray();
|
2011-10-04 08:36:01 +09:00
|
|
|
properties.fontMatrix = matrix;
|
2011-08-25 09:29:22 +09:00
|
|
|
break;
|
2013-03-28 09:15:44 +09:00
|
|
|
case 'Encoding':
|
|
|
|
var encodingArg = this.getToken();
|
2011-10-29 10:38:31 +09:00
|
|
|
var encoding;
|
|
|
|
if (!/^\d+$/.test(encodingArg)) {
|
|
|
|
// encoding name is specified
|
2016-01-22 06:18:46 +09:00
|
|
|
encoding = getEncoding(encodingArg);
|
2011-10-29 10:38:31 +09:00
|
|
|
} else {
|
|
|
|
encoding = [];
|
2013-03-28 09:15:44 +09:00
|
|
|
var size = parseInt(encodingArg, 10) | 0;
|
|
|
|
this.getToken(); // read in 'array'
|
2011-09-10 21:50:46 +09:00
|
|
|
|
2011-10-29 10:38:31 +09:00
|
|
|
for (var j = 0; j < size; j++) {
|
2014-04-08 06:42:54 +09:00
|
|
|
token = this.getToken();
|
2013-11-02 01:33:30 +09:00
|
|
|
// skipping till first dup or def (e.g. ignoring for statement)
|
|
|
|
while (token !== 'dup' && token !== 'def') {
|
|
|
|
token = this.getToken();
|
|
|
|
if (token === null) {
|
|
|
|
return; // invalid header
|
|
|
|
}
|
2011-08-30 07:56:02 +09:00
|
|
|
}
|
2013-11-02 01:33:30 +09:00
|
|
|
if (token === 'def') {
|
|
|
|
break; // read all array data
|
|
|
|
}
|
|
|
|
var index = this.readInt();
|
|
|
|
this.getToken(); // read in '/'
|
|
|
|
var glyph = this.getToken();
|
|
|
|
encoding[index] = glyph;
|
|
|
|
this.getToken(); // read the in 'put'
|
2011-08-25 09:29:22 +09:00
|
|
|
}
|
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
properties.builtInEncoding = encoding;
|
2011-07-01 14:16:56 +09:00
|
|
|
break;
|
2013-11-02 01:33:30 +09:00
|
|
|
case 'FontBBox':
|
|
|
|
var fontBBox = this.readNumberArray();
|
|
|
|
// adjusting ascent/descent
|
|
|
|
properties.ascent = fontBBox[3];
|
|
|
|
properties.descent = fontBBox[1];
|
|
|
|
properties.ascentScaled = true;
|
|
|
|
break;
|
2011-07-01 14:16:56 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2013-03-28 09:15:44 +09:00
|
|
|
|
|
|
|
return Type1Parser;
|
|
|
|
})();
|
2011-06-11 10:25:58 +09:00
|
|
|
|
2011-06-21 11:30:28 +09:00
|
|
|
/**
|
2011-09-09 04:37:35 +09:00
|
|
|
* The CFF class takes a Type1 file and wrap it into a
|
2011-09-06 05:09:40 +09:00
|
|
|
* 'Compact Font Format' which itself embed Type2 charstrings.
|
2011-06-21 11:30:28 +09:00
|
|
|
*/
|
2012-03-11 12:12:33 +09:00
|
|
|
var CFFStandardStrings = [
|
2011-07-06 15:06:45 +09:00
|
|
|
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
|
|
|
|
'ampersand', 'quoteright', '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', 'quoteleft', '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', 'exclamdown', 'cent',
|
|
|
|
'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
|
|
|
|
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
|
|
|
|
'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl',
|
|
|
|
'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase',
|
|
|
|
'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 'questiondown',
|
|
|
|
'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
|
|
|
|
'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash',
|
|
|
|
'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
|
|
|
|
'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
|
|
|
|
'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
|
|
|
|
'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
|
|
|
|
'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
|
|
|
|
'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
|
|
|
|
'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
|
|
|
|
'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
|
|
|
|
'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
|
|
|
|
'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
|
|
|
|
'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde',
|
|
|
|
'ccedilla', 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute',
|
|
|
|
'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex',
|
|
|
|
'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex',
|
|
|
|
'udieresis', 'ugrave', 'yacute', 'ydieresis', 'zcaron', 'exclamsmall',
|
|
|
|
'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall',
|
2014-03-14 22:11:16 +09:00
|
|
|
'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
|
2011-07-06 15:06:45 +09:00
|
|
|
'onedotenleader', 'zerooldstyle', 'oneoldstyle', 'twooldstyle',
|
|
|
|
'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
|
|
|
|
'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior',
|
|
|
|
'threequartersemdash', 'periodsuperior', 'questionsmall', 'asuperior',
|
|
|
|
'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior',
|
|
|
|
'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior',
|
|
|
|
'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 'parenrightinferior',
|
|
|
|
'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall',
|
|
|
|
'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall',
|
|
|
|
'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
|
|
|
|
'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
|
|
|
|
'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah',
|
|
|
|
'Tildesmall', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall',
|
|
|
|
'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall',
|
|
|
|
'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior',
|
|
|
|
'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 'questiondownsmall', 'oneeighth',
|
|
|
|
'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds',
|
|
|
|
'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior',
|
|
|
|
'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
|
|
|
|
'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior',
|
|
|
|
'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior',
|
|
|
|
'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior',
|
|
|
|
'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall',
|
|
|
|
'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall',
|
|
|
|
'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall',
|
|
|
|
'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall',
|
|
|
|
'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall',
|
|
|
|
'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall',
|
|
|
|
'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall',
|
|
|
|
'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', '001.003',
|
|
|
|
'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold'
|
2011-06-18 08:07:09 +09:00
|
|
|
];
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
// Type1Font is also a CIDFontType0.
|
|
|
|
var Type1Font = function Type1Font(name, file, properties) {
|
2013-10-31 23:10:08 +09:00
|
|
|
// Some bad generators embed pfb file as is, we have to strip 6-byte headers.
|
|
|
|
// 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);
|
2014-08-02 03:57:43 +09:00
|
|
|
var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
|
2013-10-31 23:10:08 +09:00
|
|
|
if (pfbHeaderPresent) {
|
|
|
|
file.skip(PFB_HEADER_SIZE);
|
|
|
|
headerBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
|
|
|
|
(pfbHeader[3] << 8) | pfbHeader[2];
|
|
|
|
}
|
|
|
|
|
2011-06-16 06:02:30 +09:00
|
|
|
// Get the data block containing glyphs and subrs informations
|
2013-10-31 23:10:08 +09:00
|
|
|
var headerBlock = new Stream(file.getBytes(headerBlockLength));
|
2013-03-28 09:15:44 +09:00
|
|
|
var headerBlockParser = new Type1Parser(headerBlock);
|
|
|
|
headerBlockParser.extractFontHeader(properties);
|
2011-06-14 06:23:13 +09:00
|
|
|
|
2013-10-31 23:10:08 +09:00
|
|
|
if (pfbHeaderPresent) {
|
|
|
|
pfbHeader = file.getBytes(PFB_HEADER_SIZE);
|
|
|
|
eexecBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
|
|
|
|
(pfbHeader[3] << 8) | pfbHeader[2];
|
|
|
|
}
|
|
|
|
|
2011-06-24 06:15:22 +09:00
|
|
|
// Decrypt the data blocks and retrieve it's content
|
2013-10-31 23:10:08 +09:00
|
|
|
var eexecBlock = new Stream(file.getBytes(eexecBlockLength));
|
2013-03-28 09:15:44 +09:00
|
|
|
var eexecBlockParser = new Type1Parser(eexecBlock, true);
|
|
|
|
var data = eexecBlockParser.extractFontProgram();
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var info in data.properties) {
|
2011-07-01 18:28:22 +09:00
|
|
|
properties[info] = data.properties[info];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-06-16 06:02:30 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var charstrings = data.charstrings;
|
2011-07-01 07:44:11 +09:00
|
|
|
var type2Charstrings = this.getType2Charstrings(charstrings);
|
|
|
|
var subrs = this.getType2Subrs(data.subrs);
|
|
|
|
|
|
|
|
this.charstrings = charstrings;
|
2011-07-06 15:06:45 +09:00
|
|
|
this.data = this.wrap(name, type2Charstrings, this.charstrings,
|
|
|
|
subrs, properties);
|
2013-02-27 03:00:20 +09:00
|
|
|
this.seacs = this.getSeacs(data.charstrings);
|
2011-06-13 09:30:16 +09:00
|
|
|
};
|
2011-06-11 10:25:58 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
Type1Font.prototype = {
|
2014-02-12 03:27:09 +09:00
|
|
|
get numGlyphs() {
|
2014-04-13 03:25:13 +09:00
|
|
|
return this.charstrings.length + 1;
|
2014-02-12 03:27:09 +09:00
|
|
|
},
|
|
|
|
|
|
|
|
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;
|
2014-04-08 06:42:54 +09:00
|
|
|
var glyphNames = ['.notdef'], glyphId;
|
|
|
|
for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
|
2014-02-12 03:27:09 +09:00
|
|
|
glyphNames.push(charstrings[glyphId].glyphName);
|
|
|
|
}
|
|
|
|
var encoding = properties.builtInEncoding;
|
|
|
|
if (encoding) {
|
2016-01-28 02:04:13 +09:00
|
|
|
var builtInEncoding = Object.create(null);
|
2014-02-12 03:27:09 +09:00
|
|
|
for (var charCode in encoding) {
|
2014-04-08 06:42:54 +09:00
|
|
|
glyphId = glyphNames.indexOf(encoding[charCode]);
|
2014-02-12 03:27:09 +09:00
|
|
|
if (glyphId >= 0) {
|
|
|
|
builtInEncoding[charCode] = glyphId;
|
|
|
|
}
|
2013-02-01 06:13:36 +09:00
|
|
|
}
|
2011-07-06 15:06:45 +09:00
|
|
|
}
|
2011-06-11 10:25:58 +09:00
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
|
2011-06-13 09:30:16 +09:00
|
|
|
},
|
2011-06-11 10:25:58 +09:00
|
|
|
|
2013-02-27 03:00:20 +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) {
|
2014-02-12 03:27:09 +09:00
|
|
|
// Offset by 1 for .notdef
|
|
|
|
seacMap[i + 1] = charstring.seac;
|
2013-02-27 03:00:20 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return seacMap;
|
|
|
|
},
|
|
|
|
|
2012-04-05 13:37:04 +09:00
|
|
|
getType2Charstrings: function Type1Font_getType2Charstrings(
|
2013-01-10 10:33:59 +09:00
|
|
|
type1Charstrings) {
|
2011-07-01 07:44:11 +09:00
|
|
|
var type2Charstrings = [];
|
2013-01-10 10:33:59 +09:00
|
|
|
for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
|
|
|
|
type2Charstrings.push(type1Charstrings[i].charstring);
|
|
|
|
}
|
2011-07-01 07:44:11 +09:00
|
|
|
return type2Charstrings;
|
|
|
|
},
|
|
|
|
|
2012-04-05 06:14:47 +09:00
|
|
|
getType2Subrs: function Type1Font_getType2Subrs(type1Subrs) {
|
2011-07-01 07:44:11 +09:00
|
|
|
var bias = 0;
|
|
|
|
var count = type1Subrs.length;
|
2014-03-18 01:34:30 +09:00
|
|
|
if (count < 1133) {
|
2011-07-01 07:44:11 +09:00
|
|
|
bias = 107;
|
2014-03-18 01:34:30 +09:00
|
|
|
} else if (count < 33769) {
|
2011-07-01 07:44:11 +09:00
|
|
|
bias = 1131;
|
2014-03-18 01:34:30 +09:00
|
|
|
} else {
|
2011-07-01 07:44:11 +09:00
|
|
|
bias = 32768;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-07-01 07:44:11 +09:00
|
|
|
|
|
|
|
// Add a bunch of empty subrs to deal with the Type2 bias
|
|
|
|
var type2Subrs = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
var i;
|
|
|
|
for (i = 0; i < bias; i++) {
|
2011-07-01 07:44:11 +09:00
|
|
|
type2Subrs.push([0x0B]);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-07-01 07:44:11 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < count; i++) {
|
2013-01-10 10:33:59 +09:00
|
|
|
type2Subrs.push(type1Subrs[i]);
|
2011-08-17 02:25:02 +09:00
|
|
|
}
|
2011-07-01 07:44:11 +09:00
|
|
|
|
|
|
|
return type2Subrs;
|
|
|
|
},
|
|
|
|
|
2012-04-05 06:14:47 +09:00
|
|
|
wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
|
2012-12-22 07:11:58 +09:00
|
|
|
var cff = new CFF();
|
|
|
|
cff.header = new CFFHeader(1, 0, 4, 4);
|
|
|
|
|
|
|
|
cff.names = [name];
|
|
|
|
|
|
|
|
var topDict = new CFFTopDict();
|
2013-01-31 01:15:08 +09:00
|
|
|
// 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);
|
2012-12-22 07:11:58 +09:00
|
|
|
topDict.setByName('Encoding', null); // placeholder
|
2013-01-04 09:39:06 +09:00
|
|
|
topDict.setByName('FontMatrix', properties.fontMatrix);
|
2012-12-22 07:11:58 +09:00
|
|
|
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];
|
2014-04-08 06:42:54 +09:00
|
|
|
var i, ii;
|
|
|
|
for (i = 0; i < count; i++) {
|
2014-02-12 03:27:09 +09:00
|
|
|
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.
|
2014-08-02 03:57:43 +09:00
|
|
|
if (index === -1) {
|
2012-12-22 07:11:58 +09:00
|
|
|
index = 0;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-12-22 07:11:58 +09:00
|
|
|
charsetArray.push((index >> 8) & 0xff, index & 0xff);
|
2012-12-21 04:14:10 +09:00
|
|
|
}
|
2012-12-22 07:11:58 +09:00
|
|
|
cff.charset = new CFFCharset(false, 0, [], charsetArray);
|
2012-12-21 04:14:10 +09:00
|
|
|
|
2012-12-22 07:11:58 +09:00
|
|
|
var charStringsIndex = new CFFIndex();
|
|
|
|
charStringsIndex.add([0x8B, 0x0E]); // .notdef
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < count; i++) {
|
2012-12-22 07:11:58 +09:00
|
|
|
charStringsIndex.add(glyphs[i]);
|
|
|
|
}
|
|
|
|
cff.charStrings = charStringsIndex;
|
|
|
|
|
|
|
|
var privateDict = new CFFPrivateDict();
|
|
|
|
privateDict.setByName('Subrs', null); // placeholder
|
|
|
|
var fields = [
|
|
|
|
'BlueValues',
|
|
|
|
'OtherBlues',
|
|
|
|
'FamilyBlues',
|
|
|
|
'FamilyOtherBlues',
|
|
|
|
'StemSnapH',
|
|
|
|
'StemSnapV',
|
|
|
|
'BlueShift',
|
|
|
|
'BlueFuzz',
|
|
|
|
'BlueScale',
|
|
|
|
'LanguageGroup',
|
2013-01-09 03:44:39 +09:00
|
|
|
'ExpansionFactor',
|
|
|
|
'ForceBold',
|
|
|
|
'StdHW',
|
|
|
|
'StdVW'
|
2012-12-22 07:11:58 +09:00
|
|
|
];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = fields.length; i < ii; i++) {
|
2012-12-22 07:11:58 +09:00
|
|
|
var field = fields[i];
|
2016-01-28 02:04:13 +09:00
|
|
|
if (!(field in properties.privateData)) {
|
2012-12-22 07:11:58 +09:00
|
|
|
continue;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2013-03-29 07:05:03 +09:00
|
|
|
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);
|
2012-12-22 07:11:58 +09:00
|
|
|
}
|
|
|
|
cff.topDict.privateDict = privateDict;
|
2011-06-12 07:54:47 +09:00
|
|
|
|
2012-12-22 07:11:58 +09:00
|
|
|
var subrIndex = new CFFIndex();
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = subrs.length; i < ii; i++) {
|
2012-12-22 07:11:58 +09:00
|
|
|
subrIndex.add(subrs[i]);
|
2011-06-16 16:34:43 +09:00
|
|
|
}
|
2012-12-22 07:11:58 +09:00
|
|
|
privateDict.subrsIndex = subrIndex;
|
2011-06-12 05:08:30 +09:00
|
|
|
|
2012-12-22 07:11:58 +09:00
|
|
|
var compiler = new CFFCompiler(cff);
|
|
|
|
return compiler.compile();
|
2011-06-10 13:12:59 +09:00
|
|
|
}
|
2011-06-10 15:40:28 +09:00
|
|
|
};
|
|
|
|
|
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;
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
var parser = new CFFParser(file, properties);
|
2013-05-02 08:42:25 +09:00
|
|
|
this.cff = parser.parse();
|
|
|
|
var compiler = new CFFCompiler(this.cff);
|
2014-02-12 03:27:09 +09:00
|
|
|
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
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
|
2012-03-11 12:37:22 +09:00
|
|
|
CFFFont.prototype = {
|
2014-02-12 03:27:09 +09:00
|
|
|
get numGlyphs() {
|
|
|
|
return this.cff.charStrings.count;
|
2011-07-16 00:58:09 +09:00
|
|
|
},
|
2014-02-12 03:27:09 +09:00
|
|
|
getCharset: function CFFFont_getCharset() {
|
|
|
|
return this.cff.charset.charset;
|
2011-07-16 00:58:09 +09:00
|
|
|
},
|
2014-02-12 03:27:09 +09:00
|
|
|
getGlyphMapping: function CFFFont_getGlyphMapping() {
|
2013-05-02 08:42:25 +09:00
|
|
|
var cff = this.cff;
|
2014-04-10 01:17:50 +09:00
|
|
|
var properties = this.properties;
|
2013-05-02 08:42:25 +09:00
|
|
|
var charsets = cff.charset.charset;
|
2014-04-10 01:17:50 +09:00
|
|
|
var charCodeToGlyphId;
|
2014-04-08 06:42:54 +09:00
|
|
|
var glyphId;
|
2014-02-12 03:27:09 +09:00
|
|
|
|
2014-04-10 01:17:50 +09:00
|
|
|
if (properties.composite) {
|
|
|
|
charCodeToGlyphId = Object.create(null);
|
|
|
|
if (cff.isCIDFont) {
|
2013-10-01 13:40:28 +09:00
|
|
|
// 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++) {
|
2014-08-01 15:46:37 +09:00
|
|
|
var cid = charsets[glyphId];
|
|
|
|
var charCode = properties.cMap.charCodeOf(cid);
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[charCode] = glyphId;
|
|
|
|
}
|
2013-05-02 08:42:25 +09:00
|
|
|
} else {
|
2013-10-01 13:40:28 +09:00
|
|
|
// 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++) {
|
2014-02-12 03:27:09 +09:00
|
|
|
charCodeToGlyphId[glyphId] = glyphId;
|
2013-08-23 02:12:16 +09:00
|
|
|
}
|
2011-10-20 03:14:13 +09:00
|
|
|
}
|
2014-02-12 03:27:09 +09:00
|
|
|
return charCodeToGlyphId;
|
2011-10-20 03:14:13 +09:00
|
|
|
}
|
|
|
|
|
2014-02-12 03:27:09 +09:00
|
|
|
var encoding = cff.encoding ? cff.encoding.encoding : null;
|
2014-04-10 01:17:50 +09:00
|
|
|
charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
|
|
|
|
return charCodeToGlyphId;
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
};
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2012-03-11 12:37:22 +09:00
|
|
|
return CFFFont;
|
2012-03-11 12:12:33 +09:00
|
|
|
})();
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
var CFFParser = (function CFFParserClosure() {
|
2012-10-15 12:21:41 +09:00
|
|
|
var CharstringValidationData = [
|
|
|
|
null,
|
2014-08-15 06:11:09 +09:00
|
|
|
{ id: 'hstem', min: 2, stackClearing: true, stem: true },
|
2012-10-15 12:21:41 +09:00
|
|
|
null,
|
2014-08-15 06:11:09 +09:00
|
|
|
{ id: 'vstem', min: 2, stackClearing: true, stem: true },
|
|
|
|
{ id: 'vmoveto', min: 1, stackClearing: true },
|
2012-10-15 12:21:41 +09:00
|
|
|
{ id: 'rlineto', min: 2, resetStack: true },
|
|
|
|
{ id: 'hlineto', min: 1, resetStack: true },
|
|
|
|
{ id: 'vlineto', min: 1, resetStack: true },
|
|
|
|
{ id: 'rrcurveto', min: 6, resetStack: true },
|
|
|
|
null,
|
|
|
|
{ id: 'callsubr', min: 1, undefStack: true },
|
2013-01-26 22:08:45 +09:00
|
|
|
{ id: 'return', min: 0, undefStack: true },
|
2012-10-15 12:21:41 +09:00
|
|
|
null, // 12
|
|
|
|
null,
|
2014-08-15 06:11:09 +09:00
|
|
|
{ id: 'endchar', min: 0, stackClearing: true },
|
2012-10-15 12:21:41 +09:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
2014-08-15 06:11:09 +09:00
|
|
|
{ id: 'hstemhm', min: 2, stackClearing: true, stem: true },
|
|
|
|
{ id: 'hintmask', min: 0, stackClearing: true },
|
|
|
|
{ id: 'cntrmask', min: 0, stackClearing: true },
|
|
|
|
{ id: 'rmoveto', min: 2, stackClearing: true },
|
|
|
|
{ id: 'hmoveto', min: 1, stackClearing: true },
|
|
|
|
{ id: 'vstemhm', min: 2, stackClearing: true, stem: true },
|
2012-10-15 12:21:41 +09:00
|
|
|
{ id: 'rcurveline', min: 8, resetStack: true },
|
|
|
|
{ id: 'rlinecurve', min: 8, resetStack: true },
|
|
|
|
{ id: 'vvcurveto', min: 4, resetStack: true },
|
|
|
|
{ id: 'hhcurveto', min: 4, resetStack: true },
|
|
|
|
null, // shortint
|
|
|
|
{ id: 'callgsubr', min: 1, undefStack: true },
|
|
|
|
{ id: 'vhcurveto', min: 4, resetStack: true },
|
|
|
|
{ id: 'hvcurveto', min: 4, resetStack: true }
|
|
|
|
];
|
|
|
|
var CharstringValidationData12 = [
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
{ id: 'and', min: 2, stackDelta: -1 },
|
|
|
|
{ id: 'or', min: 2, stackDelta: -1 },
|
2013-01-26 22:08:45 +09:00
|
|
|
{ id: 'not', min: 1, stackDelta: 0 },
|
2012-10-15 12:21:41 +09:00
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
{ id: 'abs', min: 1, stackDelta: 0 },
|
2013-02-27 03:00:20 +09:00
|
|
|
{ id: 'add', min: 2, stackDelta: -1,
|
|
|
|
stackFn: function stack_div(stack, index) {
|
|
|
|
stack[index - 2] = stack[index - 2] + stack[index - 1];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ id: 'sub', min: 2, stackDelta: -1,
|
|
|
|
stackFn: function stack_div(stack, index) {
|
|
|
|
stack[index - 2] = stack[index - 2] - stack[index - 1];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ id: 'div', min: 2, stackDelta: -1,
|
|
|
|
stackFn: function stack_div(stack, index) {
|
|
|
|
stack[index - 2] = stack[index - 2] / stack[index - 1];
|
|
|
|
}
|
|
|
|
},
|
2012-10-15 12:21:41 +09:00
|
|
|
null,
|
2013-02-27 03:00:20 +09:00
|
|
|
{ id: 'neg', min: 1, stackDelta: 0,
|
|
|
|
stackFn: function stack_div(stack, index) {
|
|
|
|
stack[index - 1] = -stack[index - 1];
|
|
|
|
}
|
|
|
|
},
|
2012-10-15 12:21:41 +09:00
|
|
|
{ id: 'eq', min: 2, stackDelta: -1 },
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
{ id: 'drop', min: 1, stackDelta: -1 },
|
|
|
|
null,
|
|
|
|
{ id: 'put', min: 2, stackDelta: -2 },
|
|
|
|
{ id: 'get', min: 1, stackDelta: 0 },
|
|
|
|
{ id: 'ifelse', min: 4, stackDelta: -3 },
|
|
|
|
{ id: 'random', min: 0, stackDelta: 1 },
|
2013-02-27 03:00:20 +09:00
|
|
|
{ id: 'mul', min: 2, stackDelta: -1,
|
|
|
|
stackFn: function stack_div(stack, index) {
|
|
|
|
stack[index - 2] = stack[index - 2] * stack[index - 1];
|
|
|
|
}
|
|
|
|
},
|
2012-10-15 12:21:41 +09:00
|
|
|
null,
|
|
|
|
{ id: 'sqrt', min: 1, stackDelta: 0 },
|
|
|
|
{ id: 'dup', min: 1, stackDelta: 1 },
|
|
|
|
{ id: 'exch', min: 2, stackDelta: 0 },
|
|
|
|
{ id: 'index', min: 2, stackDelta: 0 },
|
|
|
|
{ id: 'roll', min: 3, stackDelta: -2 },
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
null,
|
|
|
|
{ id: 'hflex', min: 7, resetStack: true },
|
|
|
|
{ id: 'flex', min: 13, resetStack: true },
|
|
|
|
{ id: 'hflex1', min: 9, resetStack: true },
|
|
|
|
{ id: 'flex1', min: 11, resetStack: true }
|
|
|
|
];
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
function CFFParser(file, properties) {
|
|
|
|
this.bytes = file.getBytes();
|
|
|
|
this.properties = properties;
|
|
|
|
}
|
|
|
|
CFFParser.prototype = {
|
2013-01-19 04:05:38 +09:00
|
|
|
parse: function CFFParser_parse() {
|
2012-03-11 12:12:33 +09:00
|
|
|
var properties = this.properties;
|
2012-03-11 12:37:22 +09:00
|
|
|
var cff = new CFF();
|
2012-03-11 12:12:33 +09:00
|
|
|
this.cff = cff;
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
// The first five sections must be in order, all the others are reached
|
|
|
|
// via offsets contained in one of the below.
|
|
|
|
var header = this.parseHeader();
|
|
|
|
var nameIndex = this.parseIndex(header.endPos);
|
|
|
|
var topDictIndex = this.parseIndex(nameIndex.endPos);
|
|
|
|
var stringIndex = this.parseIndex(topDictIndex.endPos);
|
|
|
|
var globalSubrIndex = this.parseIndex(stringIndex.endPos);
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
var topDictParsed = this.parseDict(topDictIndex.obj.get(0));
|
|
|
|
var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
|
2011-10-29 10:38:31 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
cff.header = header.obj;
|
|
|
|
cff.names = this.parseNameIndex(nameIndex.obj);
|
|
|
|
cff.strings = this.parseStringIndex(stringIndex.obj);
|
|
|
|
cff.topDict = topDict;
|
|
|
|
cff.globalSubrIndex = globalSubrIndex.obj;
|
2011-07-19 07:49:32 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
this.parsePrivateDict(cff.topDict);
|
2011-07-19 07:49:32 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
cff.isCIDFont = topDict.hasName('ROS');
|
2011-07-19 00:24:57 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
var charStringOffset = topDict.getByName('CharStrings');
|
2016-01-06 02:53:31 +09:00
|
|
|
var charStringIndex = this.parseIndex(charStringOffset).obj;
|
2011-07-19 00:24:57 +09:00
|
|
|
|
2012-08-30 00:31:59 +09:00
|
|
|
var fontMatrix = topDict.getByName('FontMatrix');
|
|
|
|
if (fontMatrix) {
|
2013-01-04 09:39:06 +09:00
|
|
|
properties.fontMatrix = fontMatrix;
|
2012-08-30 00:31:59 +09:00
|
|
|
}
|
|
|
|
|
2012-08-30 02:19:09 +09:00
|
|
|
var fontBBox = topDict.getByName('FontBBox');
|
|
|
|
if (fontBBox) {
|
|
|
|
// adjusting ascent/descent
|
|
|
|
properties.ascent = fontBBox[3];
|
|
|
|
properties.descent = fontBBox[1];
|
2013-01-05 02:41:24 +09:00
|
|
|
properties.ascentScaled = true;
|
2012-08-30 02:19:09 +09:00
|
|
|
}
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
var charset, encoding;
|
|
|
|
if (cff.isCIDFont) {
|
|
|
|
var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj;
|
|
|
|
for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
|
|
|
|
var dictRaw = fdArrayIndex.get(i);
|
|
|
|
var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw),
|
|
|
|
cff.strings);
|
|
|
|
this.parsePrivateDict(fontDict);
|
|
|
|
cff.fdArray.push(fontDict);
|
2012-02-21 08:49:45 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
// cid fonts don't have an encoding
|
|
|
|
encoding = null;
|
|
|
|
charset = this.parseCharsets(topDict.getByName('charset'),
|
2016-01-06 02:53:31 +09:00
|
|
|
charStringIndex.count, cff.strings, true);
|
2012-03-11 12:12:33 +09:00
|
|
|
cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'),
|
2016-01-06 02:53:31 +09:00
|
|
|
charStringIndex.count);
|
2012-03-11 12:12:33 +09:00
|
|
|
} else {
|
|
|
|
charset = this.parseCharsets(topDict.getByName('charset'),
|
2016-01-06 02:53:31 +09:00
|
|
|
charStringIndex.count, cff.strings, false);
|
2012-03-11 12:12:33 +09:00
|
|
|
encoding = this.parseEncoding(topDict.getByName('Encoding'),
|
|
|
|
properties,
|
|
|
|
cff.strings, charset.charset);
|
2012-02-20 15:12:22 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
cff.charset = charset;
|
|
|
|
cff.encoding = encoding;
|
2011-07-19 00:24:57 +09:00
|
|
|
|
2016-01-06 02:53:31 +09:00
|
|
|
var charStringsAndSeacs = this.parseCharStrings(
|
|
|
|
charStringIndex,
|
|
|
|
topDict.privateDict.subrsIndex,
|
|
|
|
globalSubrIndex.obj,
|
|
|
|
cff.fdSelect,
|
|
|
|
cff.fdArray);
|
|
|
|
cff.charStrings = charStringsAndSeacs.charStrings;
|
|
|
|
cff.seacs = charStringsAndSeacs.seacs;
|
|
|
|
cff.widths = charStringsAndSeacs.widths;
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
return cff;
|
2011-07-19 00:24:57 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseHeader: function CFFParser_parseHeader() {
|
2011-07-16 00:58:09 +09:00
|
|
|
var bytes = this.bytes;
|
2014-03-13 20:33:42 +09:00
|
|
|
var bytesLength = bytes.length;
|
2011-07-16 00:58:09 +09:00
|
|
|
var offset = 0;
|
|
|
|
|
2014-03-13 20:33:42 +09:00
|
|
|
// Prevent an infinite loop, by checking that the offset is within the
|
|
|
|
// bounds of the bytes array. Necessary in empty, or invalid, font files.
|
|
|
|
while (offset < bytesLength && bytes[offset] !== 1) {
|
2011-07-16 00:58:09 +09:00
|
|
|
++offset;
|
2014-03-13 20:33:42 +09:00
|
|
|
}
|
|
|
|
if (offset >= bytesLength) {
|
|
|
|
error('Invalid CFF header');
|
|
|
|
} else if (offset !== 0) {
|
2012-05-15 09:19:09 +09:00
|
|
|
info('cff data is shifted');
|
2011-07-16 00:58:09 +09:00
|
|
|
bytes = bytes.subarray(offset);
|
|
|
|
this.bytes = bytes;
|
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var major = bytes[0];
|
|
|
|
var minor = bytes[1];
|
|
|
|
var hdrSize = bytes[2];
|
|
|
|
var offSize = bytes[3];
|
|
|
|
var header = new CFFHeader(major, minor, hdrSize, offSize);
|
2014-03-18 01:34:30 +09:00
|
|
|
return { obj: header, endPos: hdrSize };
|
2011-07-16 00:58:09 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseDict: function CFFParser_parseDict(dict) {
|
2011-07-16 00:58:09 +09:00
|
|
|
var pos = 0;
|
|
|
|
|
|
|
|
function parseOperand() {
|
|
|
|
var value = dict[pos++];
|
|
|
|
if (value === 30) {
|
2011-07-19 00:24:57 +09:00
|
|
|
return parseFloatOperand(pos);
|
2011-07-16 00:58:09 +09:00
|
|
|
} else if (value === 28) {
|
2011-07-19 00:24:57 +09:00
|
|
|
value = dict[pos++];
|
2012-08-30 02:58:12 +09:00
|
|
|
value = ((value << 24) | (dict[pos++] << 16)) >> 16;
|
2011-07-19 00:24:57 +09:00
|
|
|
return value;
|
2011-07-16 00:58:09 +09:00
|
|
|
} else if (value === 29) {
|
2011-07-19 00:24:57 +09:00
|
|
|
value = dict[pos++];
|
2011-07-16 00:58:09 +09:00
|
|
|
value = (value << 8) | dict[pos++];
|
|
|
|
value = (value << 8) | dict[pos++];
|
|
|
|
value = (value << 8) | dict[pos++];
|
|
|
|
return value;
|
2012-03-11 12:12:33 +09:00
|
|
|
} else if (value >= 32 && value <= 246) {
|
2011-07-16 00:58:09 +09:00
|
|
|
return value - 139;
|
2012-03-11 12:12:33 +09:00
|
|
|
} else if (value >= 247 && value <= 250) {
|
2011-07-16 00:58:09 +09:00
|
|
|
return ((value - 247) * 256) + dict[pos++] + 108;
|
2012-03-11 12:12:33 +09:00
|
|
|
} else if (value >= 251 && value <= 254) {
|
2011-07-16 00:58:09 +09:00
|
|
|
return -((value - 251) * 256) - dict[pos++] - 108;
|
|
|
|
} else {
|
2011-09-11 22:23:35 +09:00
|
|
|
error('255 is not a valid DICT command');
|
2011-07-16 00:58:09 +09:00
|
|
|
}
|
2011-08-31 07:37:39 +09:00
|
|
|
return -1;
|
2011-09-12 05:04:17 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
|
2011-07-19 00:24:57 +09:00
|
|
|
function parseFloatOperand() {
|
2011-08-09 05:13:32 +09:00
|
|
|
var str = '';
|
2011-07-16 00:58:09 +09:00
|
|
|
var eof = 15;
|
|
|
|
var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8',
|
|
|
|
'9', '.', 'E', 'E-', null, '-'];
|
|
|
|
var length = dict.length;
|
|
|
|
while (pos < length) {
|
|
|
|
var b = dict[pos++];
|
|
|
|
var b1 = b >> 4;
|
|
|
|
var b2 = b & 15;
|
|
|
|
|
2014-08-02 03:57:43 +09:00
|
|
|
if (b1 === eof) {
|
2011-07-16 00:58:09 +09:00
|
|
|
break;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
str += lookup[b1];
|
|
|
|
|
2014-08-02 03:57:43 +09:00
|
|
|
if (b2 === eof) {
|
2011-07-16 00:58:09 +09:00
|
|
|
break;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
str += lookup[b2];
|
|
|
|
}
|
|
|
|
return parseFloat(str);
|
2011-09-12 05:04:17 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
|
|
|
|
var operands = [];
|
|
|
|
var entries = [];
|
2011-08-09 05:13:32 +09:00
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
pos = 0;
|
2011-07-16 00:58:09 +09:00
|
|
|
var end = dict.length;
|
|
|
|
while (pos < end) {
|
|
|
|
var b = dict[pos];
|
|
|
|
if (b <= 21) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (b === 12) {
|
2012-03-11 12:12:33 +09:00
|
|
|
b = (b << 8) | dict[++pos];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
entries.push([b, operands]);
|
|
|
|
operands = [];
|
|
|
|
++pos;
|
|
|
|
} else {
|
|
|
|
operands.push(parseOperand());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return entries;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseIndex: function CFFParser_parseIndex(pos) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var cffIndex = new CFFIndex();
|
2011-07-16 00:58:09 +09:00
|
|
|
var bytes = this.bytes;
|
2012-03-11 12:12:33 +09:00
|
|
|
var count = (bytes[pos++] << 8) | bytes[pos++];
|
2011-09-12 05:04:17 +09:00
|
|
|
var offsets = [];
|
|
|
|
var end = pos;
|
2014-04-08 06:42:54 +09:00
|
|
|
var i, ii;
|
2011-09-12 05:04:17 +09:00
|
|
|
|
2013-02-01 08:31:41 +09:00
|
|
|
if (count !== 0) {
|
2011-07-16 00:58:09 +09:00
|
|
|
var offsetSize = bytes[pos++];
|
2011-07-19 00:24:57 +09:00
|
|
|
// add 1 for offset to determine size of last object
|
2011-07-16 00:58:09 +09:00
|
|
|
var startPos = pos + ((count + 1) * offsetSize) - 1;
|
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = count + 1; i < ii; ++i) {
|
2011-07-16 00:58:09 +09:00
|
|
|
var offset = 0;
|
|
|
|
for (var j = 0; j < offsetSize; ++j) {
|
|
|
|
offset <<= 8;
|
|
|
|
offset += bytes[pos++];
|
|
|
|
}
|
|
|
|
offsets.push(startPos + offset);
|
|
|
|
}
|
2011-09-12 05:04:17 +09:00
|
|
|
end = offsets[count];
|
2011-07-16 00:58:09 +09:00
|
|
|
}
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = offsets.length - 1; i < ii; ++i) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var offsetStart = offsets[i];
|
|
|
|
var offsetEnd = offsets[i + 1];
|
|
|
|
cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
|
|
|
|
}
|
|
|
|
return {obj: cffIndex, endPos: end};
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseNameIndex: function CFFParser_parseNameIndex(index) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var names = [];
|
|
|
|
for (var i = 0, ii = index.count; i < ii; ++i) {
|
|
|
|
var name = index.get(i);
|
|
|
|
// OTS doesn't allow names to be over 127 characters.
|
|
|
|
var length = Math.min(name.length, 127);
|
2012-03-22 22:15:27 +09:00
|
|
|
var data = [];
|
2012-03-11 12:12:33 +09:00
|
|
|
// OTS also only permits certain characters in the name.
|
|
|
|
for (var j = 0; j < length; ++j) {
|
|
|
|
var c = name[j];
|
|
|
|
if (j === 0 && c === 0) {
|
|
|
|
data[j] = c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ ||
|
|
|
|
c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ ||
|
|
|
|
c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ ||
|
2014-04-18 00:50:06 +09:00
|
|
|
c === 47 /* / */ || c === 37 /* % */ || c === 35 /* # */) {
|
2012-03-11 12:12:33 +09:00
|
|
|
data[j] = 95;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
data[j] = c;
|
|
|
|
}
|
2014-03-27 21:01:43 +09:00
|
|
|
names.push(bytesToString(data));
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
return names;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseStringIndex: function CFFParser_parseStringIndex(index) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var strings = new CFFStrings();
|
|
|
|
for (var i = 0, ii = index.count; i < ii; ++i) {
|
|
|
|
var data = index.get(i);
|
2014-03-27 21:01:43 +09:00
|
|
|
strings.add(bytesToString(data));
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
return strings;
|
|
|
|
},
|
2013-02-01 08:31:41 +09:00
|
|
|
createDict: function CFFParser_createDict(Type, dict, strings) {
|
|
|
|
var cffDict = new Type(strings);
|
2012-03-11 12:12:33 +09:00
|
|
|
for (var i = 0, ii = dict.length; i < ii; ++i) {
|
|
|
|
var pair = dict[i];
|
|
|
|
var key = pair[0];
|
|
|
|
var value = pair[1];
|
|
|
|
cffDict.setByKey(key, value);
|
|
|
|
}
|
|
|
|
return cffDict;
|
|
|
|
},
|
2016-01-06 02:53:31 +09:00
|
|
|
parseCharString: function CFFParser_parseCharString(state, data,
|
|
|
|
localSubrIndex,
|
|
|
|
globalSubrIndex) {
|
|
|
|
if (state.callDepth > MAX_SUBR_NESTING) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var stackSize = state.stackSize;
|
|
|
|
var stack = state.stack;
|
|
|
|
|
|
|
|
var length = data.length;
|
|
|
|
|
|
|
|
for (var j = 0; j < length;) {
|
|
|
|
var value = data[j++];
|
|
|
|
var validationCommand = null;
|
|
|
|
if (value === 12) {
|
|
|
|
var q = data[j++];
|
|
|
|
if (q === 0) {
|
|
|
|
// The CFF specification state that the 'dotsection' command
|
|
|
|
// (12, 0) is deprecated and treated as a no-op, but all Type2
|
|
|
|
// charstrings processors should support them. Unfortunately
|
|
|
|
// the font sanitizer don't. As a workaround the sequence (12, 0)
|
|
|
|
// is replaced by a useless (0, hmoveto).
|
|
|
|
data[j - 2] = 139;
|
|
|
|
data[j - 1] = 22;
|
|
|
|
stackSize = 0;
|
|
|
|
} else {
|
|
|
|
validationCommand = CharstringValidationData12[q];
|
|
|
|
}
|
|
|
|
} else if (value === 28) { // number (16 bit)
|
|
|
|
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16;
|
|
|
|
j += 2;
|
|
|
|
stackSize++;
|
|
|
|
} else if (value === 14) {
|
|
|
|
if (stackSize >= 4) {
|
|
|
|
stackSize -= 4;
|
|
|
|
if (SEAC_ANALYSIS_ENABLED) {
|
|
|
|
state.seac = stack.slice(stackSize, stackSize + 4);
|
|
|
|
return false;
|
2012-10-15 12:21:41 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
}
|
|
|
|
validationCommand = CharstringValidationData[value];
|
|
|
|
} else if (value >= 32 && value <= 246) { // number
|
|
|
|
stack[stackSize] = value - 139;
|
|
|
|
stackSize++;
|
|
|
|
} else if (value >= 247 && value <= 254) { // number (+1 bytes)
|
|
|
|
stack[stackSize] = (value < 251 ?
|
|
|
|
((value - 247) << 8) + data[j] + 108 :
|
|
|
|
-((value - 251) << 8) - data[j] - 108);
|
|
|
|
j++;
|
|
|
|
stackSize++;
|
|
|
|
} else if (value === 255) { // number (32 bit)
|
|
|
|
stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16) |
|
|
|
|
(data[j + 2] << 8) | data[j + 3]) / 65536;
|
|
|
|
j += 4;
|
|
|
|
stackSize++;
|
|
|
|
} else if (value === 19 || value === 20) {
|
|
|
|
state.hints += stackSize >> 1;
|
|
|
|
// skipping right amount of hints flag data
|
|
|
|
j += (state.hints + 7) >> 3;
|
|
|
|
stackSize %= 2;
|
|
|
|
validationCommand = CharstringValidationData[value];
|
|
|
|
} else if (value === 10 || value === 29) {
|
|
|
|
var subrsIndex;
|
|
|
|
if (value === 10) {
|
|
|
|
subrsIndex = localSubrIndex;
|
2012-10-15 12:21:41 +09:00
|
|
|
} else {
|
2016-01-06 02:53:31 +09:00
|
|
|
subrsIndex = globalSubrIndex;
|
|
|
|
}
|
|
|
|
if (!subrsIndex) {
|
2012-10-15 12:21:41 +09:00
|
|
|
validationCommand = CharstringValidationData[value];
|
2016-01-06 02:53:31 +09:00
|
|
|
warn('Missing subrsIndex for ' + validationCommand.id);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var bias = 32768;
|
|
|
|
if (subrsIndex.count < 1240) {
|
|
|
|
bias = 107;
|
|
|
|
} else if (subrsIndex.count < 33900) {
|
|
|
|
bias = 1131;
|
|
|
|
}
|
|
|
|
var subrNumber = stack[--stackSize] + bias;
|
|
|
|
if (subrNumber < 0 || subrNumber >= subrsIndex.count) {
|
|
|
|
validationCommand = CharstringValidationData[value];
|
|
|
|
warn('Out of bounds subrIndex for ' + validationCommand.id);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
state.stackSize = stackSize;
|
|
|
|
state.callDepth++;
|
|
|
|
var valid = this.parseCharString(state, subrsIndex.get(subrNumber),
|
|
|
|
localSubrIndex, globalSubrIndex);
|
|
|
|
if (!valid) {
|
|
|
|
return false;
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
state.callDepth--;
|
|
|
|
stackSize = state.stackSize;
|
|
|
|
continue;
|
|
|
|
} else if (value === 11) {
|
|
|
|
state.stackSize = stackSize;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
validationCommand = CharstringValidationData[value];
|
|
|
|
}
|
|
|
|
if (validationCommand) {
|
|
|
|
if (validationCommand.stem) {
|
|
|
|
state.hints += stackSize >> 1;
|
|
|
|
}
|
|
|
|
if ('min' in validationCommand) {
|
|
|
|
if (!state.undefStack && stackSize < validationCommand.min) {
|
|
|
|
warn('Not enough parameters for ' + validationCommand.id +
|
|
|
|
'; actual: ' + stackSize +
|
|
|
|
', expected: ' + validationCommand.min);
|
|
|
|
return false;
|
2013-01-26 22:08:45 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
}
|
|
|
|
if (state.firstStackClearing && validationCommand.stackClearing) {
|
|
|
|
state.firstStackClearing = false;
|
|
|
|
// the optional character width can be found before the first
|
|
|
|
// stack-clearing command arguments
|
|
|
|
stackSize -= validationCommand.min;
|
|
|
|
if (stackSize >= 2 && validationCommand.stem) {
|
|
|
|
// there are even amount of arguments for stem commands
|
|
|
|
stackSize %= 2;
|
|
|
|
} else if (stackSize > 1) {
|
|
|
|
warn('Found too many parameters for stack-clearing command');
|
2012-10-15 12:21:41 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
if (stackSize > 0 && stack[stackSize - 1] >= 0) {
|
|
|
|
state.width = stack[stackSize - 1];
|
2014-08-15 06:11:09 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
}
|
|
|
|
if ('stackDelta' in validationCommand) {
|
|
|
|
if ('stackFn' in validationCommand) {
|
|
|
|
validationCommand.stackFn(stack, stackSize);
|
2012-10-15 12:21:41 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
stackSize += validationCommand.stackDelta;
|
|
|
|
} else if (validationCommand.stackClearing) {
|
|
|
|
stackSize = 0;
|
|
|
|
} else if (validationCommand.resetStack) {
|
|
|
|
stackSize = 0;
|
|
|
|
state.undefStack = false;
|
|
|
|
} else if (validationCommand.undefStack) {
|
|
|
|
stackSize = 0;
|
|
|
|
state.undefStack = true;
|
|
|
|
state.firstStackClearing = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
state.stackSize = stackSize;
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
parseCharStrings: function CFFParser_parseCharStrings(charStrings,
|
|
|
|
localSubrIndex,
|
|
|
|
globalSubrIndex,
|
|
|
|
fdSelect,
|
|
|
|
fdArray) {
|
|
|
|
var seacs = [];
|
|
|
|
var widths = [];
|
|
|
|
var count = charStrings.count;
|
|
|
|
for (var i = 0; i < count; i++) {
|
|
|
|
var charstring = charStrings.get(i);
|
|
|
|
var state = {
|
|
|
|
callDepth: 0,
|
|
|
|
stackSize: 0,
|
|
|
|
stack: [],
|
|
|
|
undefStack: true,
|
|
|
|
hints: 0,
|
|
|
|
firstStackClearing: true,
|
|
|
|
seac: null,
|
|
|
|
width: null
|
|
|
|
};
|
|
|
|
var valid = true;
|
|
|
|
var localSubrToUse = null;
|
|
|
|
if (fdSelect && fdArray.length) {
|
|
|
|
var fdIndex = fdSelect.getFDIndex(i);
|
|
|
|
if (fdIndex === -1) {
|
|
|
|
warn('Glyph index is not in fd select.');
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
if (fdIndex >= fdArray.length) {
|
|
|
|
warn('Invalid fd index for glyph index.');
|
|
|
|
valid = false;
|
|
|
|
}
|
|
|
|
if (valid) {
|
|
|
|
localSubrToUse = fdArray[fdIndex].privateDict.subrsIndex;
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
} else if (localSubrIndex) {
|
|
|
|
localSubrToUse = localSubrIndex;
|
|
|
|
}
|
|
|
|
if (valid) {
|
|
|
|
valid = this.parseCharString(state, charstring, localSubrToUse,
|
|
|
|
globalSubrIndex);
|
|
|
|
}
|
|
|
|
if (state.width !== null) {
|
|
|
|
widths[i] = state.width;
|
|
|
|
}
|
|
|
|
if (state.seac !== null) {
|
|
|
|
seacs[i] = state.seac;
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
2012-10-15 12:21:41 +09:00
|
|
|
if (!valid) {
|
|
|
|
// resetting invalid charstring to single 'endchar'
|
|
|
|
charStrings.set(i, new Uint8Array([14]));
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
}
|
2014-08-15 06:11:09 +09:00
|
|
|
return { charStrings: charStrings, seacs: seacs, widths: widths };
|
2012-03-11 12:12:33 +09:00
|
|
|
},
|
2013-03-09 04:07:36 +09:00
|
|
|
emptyPrivateDictionary:
|
|
|
|
function CFFParser_emptyPrivateDictionary(parentDict) {
|
|
|
|
var privateDict = this.createDict(CFFPrivateDict, [],
|
|
|
|
parentDict.strings);
|
|
|
|
parentDict.setByKey(18, [0, 0]);
|
|
|
|
parentDict.privateDict = privateDict;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parsePrivateDict: function CFFParser_parsePrivateDict(parentDict) {
|
2012-03-11 12:12:33 +09:00
|
|
|
// no private dict, do nothing
|
2013-03-09 04:07:36 +09:00
|
|
|
if (!parentDict.hasName('Private')) {
|
|
|
|
this.emptyPrivateDictionary(parentDict);
|
2012-03-11 12:12:33 +09:00
|
|
|
return;
|
2013-03-09 04:07:36 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var privateOffset = parentDict.getByName('Private');
|
|
|
|
// make sure the params are formatted correctly
|
|
|
|
if (!isArray(privateOffset) || privateOffset.length !== 2) {
|
|
|
|
parentDict.removeByName('Private');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var size = privateOffset[0];
|
|
|
|
var offset = privateOffset[1];
|
|
|
|
// remove empty dicts or ones that refer to invalid location
|
|
|
|
if (size === 0 || offset >= this.bytes.length) {
|
2013-03-09 04:07:36 +09:00
|
|
|
this.emptyPrivateDictionary(parentDict);
|
2012-03-11 12:12:33 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var privateDictEnd = offset + size;
|
|
|
|
var dictData = this.bytes.subarray(offset, privateDictEnd);
|
|
|
|
var dict = this.parseDict(dictData);
|
|
|
|
var privateDict = this.createDict(CFFPrivateDict, dict,
|
|
|
|
parentDict.strings);
|
|
|
|
parentDict.privateDict = privateDict;
|
|
|
|
|
|
|
|
// Parse the Subrs index also since it's relative to the private dict.
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!privateDict.getByName('Subrs')) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var subrsOffset = privateDict.getByName('Subrs');
|
|
|
|
var relativeOffset = offset + subrsOffset;
|
|
|
|
// Validate the offset.
|
|
|
|
if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
|
2013-03-09 04:07:36 +09:00
|
|
|
this.emptyPrivateDictionary(parentDict);
|
2012-03-11 12:12:33 +09:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
var subrsIndex = this.parseIndex(relativeOffset);
|
|
|
|
privateDict.subrsIndex = subrsIndex.obj;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) {
|
2013-02-01 08:31:41 +09:00
|
|
|
if (pos === 0) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE,
|
2012-03-22 08:03:17 +09:00
|
|
|
ISOAdobeCharset);
|
2014-08-02 03:57:43 +09:00
|
|
|
} else if (pos === 1) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT,
|
2012-03-22 08:03:17 +09:00
|
|
|
ExpertCharset);
|
2014-08-02 03:57:43 +09:00
|
|
|
} else if (pos === 2) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET,
|
2012-03-22 08:03:17 +09:00
|
|
|
ExpertSubsetCharset);
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
var bytes = this.bytes;
|
|
|
|
var start = pos;
|
|
|
|
var format = bytes[pos++];
|
|
|
|
var charset = ['.notdef'];
|
2014-04-08 06:42:54 +09:00
|
|
|
var id, count, i;
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
// subtract 1 for the .notdef glyph
|
|
|
|
length -= 1;
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
case 0:
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < length; i++) {
|
|
|
|
id = (bytes[pos++] << 8) | bytes[pos++];
|
2012-03-11 12:12:33 +09:00
|
|
|
charset.push(cid ? id : strings.get(id));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
while (charset.length <= length) {
|
2014-04-08 06:42:54 +09:00
|
|
|
id = (bytes[pos++] << 8) | bytes[pos++];
|
|
|
|
count = bytes[pos++];
|
|
|
|
for (i = 0; i <= count; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
charset.push(cid ? id++ : strings.get(id++));
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
while (charset.length <= length) {
|
2014-04-08 06:42:54 +09:00
|
|
|
id = (bytes[pos++] << 8) | bytes[pos++];
|
|
|
|
count = (bytes[pos++] << 8) | bytes[pos++];
|
|
|
|
for (i = 0; i <= count; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
charset.push(cid ? id++ : strings.get(id++));
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
error('Unknown charset format');
|
|
|
|
}
|
|
|
|
// Raw won't be needed if we actually compile the charset.
|
|
|
|
var end = pos;
|
|
|
|
var raw = bytes.subarray(start, end);
|
|
|
|
|
|
|
|
return new CFFCharset(false, format, charset, raw);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseEncoding: function CFFParser_parseEncoding(pos,
|
|
|
|
properties,
|
|
|
|
strings,
|
|
|
|
charset) {
|
2016-01-28 02:04:13 +09:00
|
|
|
var encoding = Object.create(null);
|
2012-03-11 12:12:33 +09:00
|
|
|
var bytes = this.bytes;
|
|
|
|
var predefined = false;
|
|
|
|
var hasSupplement = false;
|
2014-04-08 06:42:54 +09:00
|
|
|
var format, i, ii;
|
2012-03-11 12:12:33 +09:00
|
|
|
var raw = null;
|
|
|
|
|
|
|
|
function readSupplement() {
|
|
|
|
var supplementsCount = bytes[pos++];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < supplementsCount; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var code = bytes[pos++];
|
|
|
|
var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
|
2013-08-23 02:12:16 +09:00
|
|
|
encoding[code] = charset.indexOf(strings.get(sid));
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-02 03:57:43 +09:00
|
|
|
if (pos === 0 || pos === 1) {
|
2012-03-11 12:12:33 +09:00
|
|
|
predefined = true;
|
|
|
|
format = pos;
|
2016-01-22 06:18:46 +09:00
|
|
|
var baseEncoding = pos ? ExpertEncoding : StandardEncoding;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = charset.length; i < ii; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var index = baseEncoding.indexOf(charset[i]);
|
2014-08-02 03:57:43 +09:00
|
|
|
if (index !== -1) {
|
2012-12-09 14:04:37 +09:00
|
|
|
encoding[index] = i;
|
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var dataStart = pos;
|
2014-04-08 06:42:54 +09:00
|
|
|
format = bytes[pos++];
|
2012-03-11 12:12:33 +09:00
|
|
|
switch (format & 0x7f) {
|
|
|
|
case 0:
|
|
|
|
var glyphsCount = bytes[pos++];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 1; i <= glyphsCount; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
encoding[bytes[pos++]] = i;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
var rangesCount = bytes[pos++];
|
|
|
|
var gid = 1;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < rangesCount; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var start = bytes[pos++];
|
|
|
|
var left = bytes[pos++];
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var j = start; j <= start + left; j++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
encoding[j] = gid++;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
error('Unknow encoding format: ' + format + ' in CFF');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var dataEnd = pos;
|
|
|
|
if (format & 0x80) {
|
|
|
|
// The font sanitizer does not support CFF encoding with a
|
|
|
|
// supplement, since the encoding is not really used to map
|
|
|
|
// between gid to glyph, let's overwrite what is declared in
|
|
|
|
// the top dictionary to let the sanitizer think the font use
|
|
|
|
// StandardEncoding, that's a lie but that's ok.
|
|
|
|
bytes[dataStart] &= 0x7f;
|
|
|
|
readSupplement();
|
|
|
|
hasSupplement = true;
|
|
|
|
}
|
|
|
|
raw = bytes.subarray(dataStart, dataEnd);
|
|
|
|
}
|
|
|
|
format = format & 0x7f;
|
|
|
|
return new CFFEncoding(predefined, format, encoding, raw);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
parseFDSelect: function CFFParser_parseFDSelect(pos, length) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var start = pos;
|
|
|
|
var bytes = this.bytes;
|
|
|
|
var format = bytes[pos++];
|
|
|
|
var fdSelect = [];
|
2014-04-08 06:42:54 +09:00
|
|
|
var i;
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
switch (format) {
|
|
|
|
case 0:
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < length; ++i) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var id = bytes[pos++];
|
|
|
|
fdSelect.push(id);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
var rangesCount = (bytes[pos++] << 8) | bytes[pos++];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < rangesCount; ++i) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var first = (bytes[pos++] << 8) | bytes[pos++];
|
|
|
|
var fdIndex = bytes[pos++];
|
|
|
|
var next = (bytes[pos] << 8) | bytes[pos + 1];
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var j = first; j < next; ++j) {
|
2012-03-11 12:12:33 +09:00
|
|
|
fdSelect.push(fdIndex);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
// Advance past the sentinel(next).
|
|
|
|
pos += 2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
error('Unknown fdselect format ' + format);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
var end = pos;
|
|
|
|
return new CFFFDSelect(fdSelect, bytes.subarray(start, end));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return CFFParser;
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Compact Font Format
|
2012-03-11 12:37:22 +09:00
|
|
|
var CFF = (function CFFClosure() {
|
|
|
|
function CFF() {
|
2012-03-11 12:12:33 +09:00
|
|
|
this.header = null;
|
|
|
|
this.names = [];
|
|
|
|
this.topDict = null;
|
|
|
|
this.strings = new CFFStrings();
|
|
|
|
this.globalSubrIndex = null;
|
|
|
|
|
|
|
|
// The following could really be per font, but since we only have one font
|
|
|
|
// store them here.
|
|
|
|
this.encoding = null;
|
|
|
|
this.charset = null;
|
|
|
|
this.charStrings = null;
|
|
|
|
this.fdArray = [];
|
|
|
|
this.fdSelect = null;
|
|
|
|
|
|
|
|
this.isCIDFont = false;
|
|
|
|
}
|
2012-03-11 12:37:22 +09:00
|
|
|
return CFF;
|
2012-03-11 12:12:33 +09:00
|
|
|
})();
|
|
|
|
|
2012-03-20 02:41:13 +09:00
|
|
|
var CFFHeader = (function CFFHeaderClosure() {
|
2012-03-11 12:12:33 +09:00
|
|
|
function CFFHeader(major, minor, hdrSize, offSize) {
|
|
|
|
this.major = major;
|
|
|
|
this.minor = minor;
|
|
|
|
this.hdrSize = hdrSize;
|
|
|
|
this.offSize = offSize;
|
|
|
|
}
|
|
|
|
return CFFHeader;
|
|
|
|
})();
|
|
|
|
|
2012-03-20 02:41:13 +09:00
|
|
|
var CFFStrings = (function CFFStringsClosure() {
|
2012-03-11 12:12:33 +09:00
|
|
|
function CFFStrings() {
|
|
|
|
this.strings = [];
|
|
|
|
}
|
|
|
|
CFFStrings.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
get: function CFFStrings_get(index) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (index >= 0 && index <= 390) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return CFFStandardStrings[index];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
|
|
|
if (index - 391 <= this.strings.length) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.strings[index - 391];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return CFFStandardStrings[0];
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
add: function CFFStrings_add(value) {
|
2012-03-11 12:12:33 +09:00
|
|
|
this.strings.push(value);
|
|
|
|
},
|
|
|
|
get count() {
|
|
|
|
return this.strings.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return CFFStrings;
|
|
|
|
})();
|
|
|
|
|
2012-03-20 02:41:13 +09:00
|
|
|
var CFFIndex = (function CFFIndexClosure() {
|
2012-03-11 12:12:33 +09:00
|
|
|
function CFFIndex() {
|
|
|
|
this.objects = [];
|
|
|
|
this.length = 0;
|
|
|
|
}
|
|
|
|
CFFIndex.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
add: function CFFIndex_add(data) {
|
2012-03-11 12:12:33 +09:00
|
|
|
this.length += data.length;
|
|
|
|
this.objects.push(data);
|
|
|
|
},
|
2012-10-15 12:21:41 +09:00
|
|
|
set: function CFFIndex_set(index, data) {
|
|
|
|
this.length += data.length - this.objects[index].length;
|
|
|
|
this.objects[index] = data;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
get: function CFFIndex_get(index) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.objects[index];
|
|
|
|
},
|
|
|
|
get count() {
|
|
|
|
return this.objects.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return CFFIndex;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFDict = (function CFFDictClosure() {
|
|
|
|
function CFFDict(tables, strings) {
|
|
|
|
this.keyToNameMap = tables.keyToNameMap;
|
|
|
|
this.nameToKeyMap = tables.nameToKeyMap;
|
|
|
|
this.defaults = tables.defaults;
|
|
|
|
this.types = tables.types;
|
|
|
|
this.opcodes = tables.opcodes;
|
|
|
|
this.order = tables.order;
|
|
|
|
this.strings = strings;
|
2016-01-28 02:04:13 +09:00
|
|
|
this.values = Object.create(null);
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
CFFDict.prototype = {
|
|
|
|
// value should always be an array
|
2012-04-05 05:43:26 +09:00
|
|
|
setByKey: function CFFDict_setByKey(key, value) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!(key in this.keyToNameMap)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return false;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
// ignore empty values
|
2014-03-18 01:34:30 +09:00
|
|
|
if (value.length === 0) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return true;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var type = this.types[key];
|
|
|
|
// remove the array wrapping these types of values
|
2014-03-18 01:34:30 +09:00
|
|
|
if (type === 'num' || type === 'sid' || type === 'offset') {
|
2012-03-11 12:12:33 +09:00
|
|
|
value = value[0];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
this.values[key] = value;
|
|
|
|
return true;
|
|
|
|
},
|
2012-12-22 07:11:58 +09:00
|
|
|
setByName: function CFFDict_setByName(name, value) {
|
|
|
|
if (!(name in this.nameToKeyMap)) {
|
|
|
|
error('Invalid dictionary name "' + name + '"');
|
|
|
|
}
|
|
|
|
this.values[this.nameToKeyMap[name]] = value;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
hasName: function CFFDict_hasName(name) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.nameToKeyMap[name] in this.values;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
getByName: function CFFDict_getByName(name) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!(name in this.nameToKeyMap)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
error('Invalid dictionary name "' + name + '"');
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var key = this.nameToKeyMap[name];
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!(key in this.values)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.defaults[key];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.values[key];
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
removeByName: function CFFDict_removeByName(name) {
|
2012-03-11 12:12:33 +09:00
|
|
|
delete this.values[this.nameToKeyMap[name]];
|
|
|
|
}
|
|
|
|
};
|
2012-04-05 05:43:26 +09:00
|
|
|
CFFDict.createTables = function CFFDict_createTables(layout) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var tables = {
|
|
|
|
keyToNameMap: {},
|
|
|
|
nameToKeyMap: {},
|
|
|
|
defaults: {},
|
|
|
|
types: {},
|
|
|
|
opcodes: {},
|
|
|
|
order: []
|
|
|
|
};
|
|
|
|
for (var i = 0, ii = layout.length; i < ii; ++i) {
|
|
|
|
var entry = layout[i];
|
|
|
|
var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
|
|
|
|
tables.keyToNameMap[key] = entry[1];
|
|
|
|
tables.nameToKeyMap[entry[1]] = key;
|
|
|
|
tables.types[key] = entry[2];
|
|
|
|
tables.defaults[key] = entry[3];
|
|
|
|
tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]];
|
|
|
|
tables.order.push(key);
|
|
|
|
}
|
|
|
|
return tables;
|
|
|
|
};
|
|
|
|
return CFFDict;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFTopDict = (function CFFTopDictClosure() {
|
|
|
|
var layout = [
|
|
|
|
[[12, 30], 'ROS', ['sid', 'sid', 'num'], null],
|
|
|
|
[[12, 20], 'SyntheticBase', 'num', null],
|
|
|
|
[0, 'version', 'sid', null],
|
|
|
|
[1, 'Notice', 'sid', null],
|
|
|
|
[[12, 0], 'Copyright', 'sid', null],
|
|
|
|
[2, 'FullName', 'sid', null],
|
|
|
|
[3, 'FamilyName', 'sid', null],
|
|
|
|
[4, 'Weight', 'sid', null],
|
|
|
|
[[12, 1], 'isFixedPitch', 'num', 0],
|
|
|
|
[[12, 2], 'ItalicAngle', 'num', 0],
|
|
|
|
[[12, 3], 'UnderlinePosition', 'num', -100],
|
|
|
|
[[12, 4], 'UnderlineThickness', 'num', 50],
|
|
|
|
[[12, 5], 'PaintType', 'num', 0],
|
|
|
|
[[12, 6], 'CharstringType', 'num', 2],
|
2012-03-12 10:21:38 +09:00
|
|
|
[[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'],
|
2013-02-01 08:31:41 +09:00
|
|
|
[0.001, 0, 0, 0.001, 0, 0]],
|
2012-03-11 12:12:33 +09:00
|
|
|
[13, 'UniqueID', 'num', null],
|
|
|
|
[5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]],
|
|
|
|
[[12, 8], 'StrokeWidth', 'num', 0],
|
|
|
|
[14, 'XUID', 'array', null],
|
|
|
|
[15, 'charset', 'offset', 0],
|
|
|
|
[16, 'Encoding', 'offset', 0],
|
|
|
|
[17, 'CharStrings', 'offset', 0],
|
|
|
|
[18, 'Private', ['offset', 'offset'], null],
|
|
|
|
[[12, 21], 'PostScript', 'sid', null],
|
|
|
|
[[12, 22], 'BaseFontName', 'sid', null],
|
|
|
|
[[12, 23], 'BaseFontBlend', 'delta', null],
|
|
|
|
[[12, 31], 'CIDFontVersion', 'num', 0],
|
|
|
|
[[12, 32], 'CIDFontRevision', 'num', 0],
|
|
|
|
[[12, 33], 'CIDFontType', 'num', 0],
|
|
|
|
[[12, 34], 'CIDCount', 'num', 8720],
|
|
|
|
[[12, 35], 'UIDBase', 'num', null],
|
2013-01-19 04:05:38 +09:00
|
|
|
// XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes
|
|
|
|
// before FDArray.
|
2012-03-11 12:12:33 +09:00
|
|
|
[[12, 37], 'FDSelect', 'offset', null],
|
2013-01-19 04:05:38 +09:00
|
|
|
[[12, 36], 'FDArray', 'offset', null],
|
|
|
|
[[12, 38], 'FontName', 'sid', null]
|
|
|
|
];
|
2012-03-11 12:12:33 +09:00
|
|
|
var tables = null;
|
|
|
|
function CFFTopDict(strings) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (tables === null) {
|
2012-03-11 12:12:33 +09:00
|
|
|
tables = CFFDict.createTables(layout);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
CFFDict.call(this, tables, strings);
|
|
|
|
this.privateDict = null;
|
|
|
|
}
|
|
|
|
CFFTopDict.prototype = Object.create(CFFDict.prototype);
|
|
|
|
return CFFTopDict;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFPrivateDict = (function CFFPrivateDictClosure() {
|
|
|
|
var layout = [
|
|
|
|
[6, 'BlueValues', 'delta', null],
|
|
|
|
[7, 'OtherBlues', 'delta', null],
|
|
|
|
[8, 'FamilyBlues', 'delta', null],
|
|
|
|
[9, 'FamilyOtherBlues', 'delta', null],
|
|
|
|
[[12, 9], 'BlueScale', 'num', 0.039625],
|
|
|
|
[[12, 10], 'BlueShift', 'num', 7],
|
|
|
|
[[12, 11], 'BlueFuzz', 'num', 1],
|
|
|
|
[10, 'StdHW', 'num', null],
|
|
|
|
[11, 'StdVW', 'num', null],
|
|
|
|
[[12, 12], 'StemSnapH', 'delta', null],
|
|
|
|
[[12, 13], 'StemSnapV', 'delta', null],
|
|
|
|
[[12, 14], 'ForceBold', 'num', 0],
|
|
|
|
[[12, 17], 'LanguageGroup', 'num', 0],
|
|
|
|
[[12, 18], 'ExpansionFactor', 'num', 0.06],
|
|
|
|
[[12, 19], 'initialRandomSeed', 'num', 0],
|
|
|
|
[20, 'defaultWidthX', 'num', 0],
|
2012-05-18 07:54:24 +09:00
|
|
|
[21, 'nominalWidthX', 'num', 0],
|
|
|
|
[19, 'Subrs', 'offset', null]
|
2012-03-11 12:12:33 +09:00
|
|
|
];
|
|
|
|
var tables = null;
|
|
|
|
function CFFPrivateDict(strings) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (tables === null) {
|
2012-03-11 12:12:33 +09:00
|
|
|
tables = CFFDict.createTables(layout);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
CFFDict.call(this, tables, strings);
|
|
|
|
this.subrsIndex = null;
|
|
|
|
}
|
|
|
|
CFFPrivateDict.prototype = Object.create(CFFDict.prototype);
|
|
|
|
return CFFPrivateDict;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFCharsetPredefinedTypes = {
|
|
|
|
ISO_ADOBE: 0,
|
|
|
|
EXPERT: 1,
|
|
|
|
EXPERT_SUBSET: 2
|
|
|
|
};
|
|
|
|
var CFFCharset = (function CFFCharsetClosure() {
|
|
|
|
function CFFCharset(predefined, format, charset, raw) {
|
|
|
|
this.predefined = predefined;
|
|
|
|
this.format = format;
|
|
|
|
this.charset = charset;
|
|
|
|
this.raw = raw;
|
|
|
|
}
|
|
|
|
return CFFCharset;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFEncoding = (function CFFEncodingClosure() {
|
|
|
|
function CFFEncoding(predefined, format, encoding, raw) {
|
|
|
|
this.predefined = predefined;
|
|
|
|
this.format = format;
|
|
|
|
this.encoding = encoding;
|
|
|
|
this.raw = raw;
|
|
|
|
}
|
|
|
|
return CFFEncoding;
|
|
|
|
})();
|
|
|
|
|
|
|
|
var CFFFDSelect = (function CFFFDSelectClosure() {
|
|
|
|
function CFFFDSelect(fdSelect, raw) {
|
|
|
|
this.fdSelect = fdSelect;
|
|
|
|
this.raw = raw;
|
|
|
|
}
|
2016-01-06 02:53:31 +09:00
|
|
|
CFFFDSelect.prototype = {
|
|
|
|
getFDIndex: function CFFFDSelect_get(glyphIndex) {
|
|
|
|
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return this.fdSelect[glyphIndex];
|
|
|
|
}
|
|
|
|
};
|
2012-03-11 12:12:33 +09:00
|
|
|
return CFFFDSelect;
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Helper class to keep track of where an offset is within the data and helps
|
|
|
|
// filling in that offset once it's known.
|
2012-03-27 20:27:49 +09:00
|
|
|
var CFFOffsetTracker = (function CFFOffsetTrackerClosure() {
|
2012-03-11 12:12:33 +09:00
|
|
|
function CFFOffsetTracker() {
|
2016-01-28 02:04:13 +09:00
|
|
|
this.offsets = Object.create(null);
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
CFFOffsetTracker.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
isTracking: function CFFOffsetTracker_isTracking(key) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return key in this.offsets;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
track: function CFFOffsetTracker_track(key, location) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (key in this.offsets) {
|
2012-03-11 12:12:33 +09:00
|
|
|
error('Already tracking location of ' + key);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
this.offsets[key] = location;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
offset: function CFFOffsetTracker_offset(value) {
|
2012-03-11 12:12:33 +09:00
|
|
|
for (var key in this.offsets) {
|
|
|
|
this.offsets[key] += value;
|
|
|
|
}
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
setEntryLocation: function CFFOffsetTracker_setEntryLocation(key,
|
|
|
|
values,
|
|
|
|
output) {
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!(key in this.offsets)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
error('Not tracking location of ' + key);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var data = output.data;
|
|
|
|
var dataOffset = this.offsets[key];
|
|
|
|
var size = 5;
|
|
|
|
for (var i = 0, ii = values.length; i < ii; ++i) {
|
|
|
|
var offset0 = i * size + dataOffset;
|
|
|
|
var offset1 = offset0 + 1;
|
|
|
|
var offset2 = offset0 + 2;
|
|
|
|
var offset3 = offset0 + 3;
|
|
|
|
var offset4 = offset0 + 4;
|
|
|
|
// It's easy to screw up offsets so perform this sanity check.
|
|
|
|
if (data[offset0] !== 0x1d || data[offset1] !== 0 ||
|
2014-03-18 01:34:30 +09:00
|
|
|
data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
|
2012-03-11 12:12:33 +09:00
|
|
|
error('writing to an offset that is not empty');
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var value = values[i];
|
|
|
|
data[offset0] = 0x1d;
|
|
|
|
data[offset1] = (value >> 24) & 0xFF;
|
|
|
|
data[offset2] = (value >> 16) & 0xFF;
|
|
|
|
data[offset3] = (value >> 8) & 0xFF;
|
|
|
|
data[offset4] = value & 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
return CFFOffsetTracker;
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Takes a CFF and converts it to the binary representation.
|
|
|
|
var CFFCompiler = (function CFFCompilerClosure() {
|
|
|
|
function CFFCompiler(cff) {
|
|
|
|
this.cff = cff;
|
|
|
|
}
|
|
|
|
CFFCompiler.prototype = {
|
2012-04-05 05:43:26 +09:00
|
|
|
compile: function CFFCompiler_compile() {
|
2012-03-11 12:12:33 +09:00
|
|
|
var cff = this.cff;
|
|
|
|
var output = {
|
|
|
|
data: [],
|
|
|
|
length: 0,
|
2012-04-05 05:43:26 +09:00
|
|
|
add: function CFFCompiler_add(data) {
|
2012-03-11 12:12:33 +09:00
|
|
|
this.data = this.data.concat(data);
|
|
|
|
this.length = this.data.length;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Compile the five entries that must be in order.
|
|
|
|
var header = this.compileHeader(cff.header);
|
|
|
|
output.add(header);
|
|
|
|
|
|
|
|
var nameIndex = this.compileNameIndex(cff.names);
|
|
|
|
output.add(nameIndex);
|
|
|
|
|
2013-04-16 08:14:07 +09:00
|
|
|
if (cff.isCIDFont) {
|
|
|
|
// The spec is unclear on how font matrices should relate to each other
|
|
|
|
// when there is one in the main top dict and the sub top dicts.
|
|
|
|
// Windows handles this differently than linux and osx so we have to
|
|
|
|
// normalize to work on all.
|
|
|
|
// Rules based off of some mailing list discussions:
|
|
|
|
// - If main font has a matrix and subfont doesn't, use the main matrix.
|
|
|
|
// - If no main font matrix and there is a subfont matrix, use the
|
|
|
|
// subfont matrix.
|
|
|
|
// - If both have matrices, concat together.
|
|
|
|
// - If neither have matrices, use default.
|
|
|
|
// To make this work on all platforms we move the top matrix into each
|
|
|
|
// sub top dict and concat if necessary.
|
|
|
|
if (cff.topDict.hasName('FontMatrix')) {
|
|
|
|
var base = cff.topDict.getByName('FontMatrix');
|
|
|
|
cff.topDict.removeByName('FontMatrix');
|
|
|
|
for (var i = 0, ii = cff.fdArray.length; i < ii; i++) {
|
|
|
|
var subDict = cff.fdArray[i];
|
|
|
|
var matrix = base.slice(0);
|
|
|
|
if (subDict.hasName('FontMatrix')) {
|
|
|
|
matrix = Util.transform(matrix, subDict.getByName('FontMatrix'));
|
|
|
|
}
|
|
|
|
subDict.setByName('FontMatrix', matrix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-19 04:05:38 +09:00
|
|
|
var compiled = this.compileTopDicts([cff.topDict],
|
|
|
|
output.length,
|
|
|
|
cff.isCIDFont);
|
2012-03-11 12:12:33 +09:00
|
|
|
output.add(compiled.output);
|
|
|
|
var topDictTracker = compiled.trackers[0];
|
|
|
|
|
|
|
|
var stringIndex = this.compileStringIndex(cff.strings.strings);
|
|
|
|
output.add(stringIndex);
|
|
|
|
|
|
|
|
var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
|
|
|
|
output.add(globalSubrIndex);
|
|
|
|
|
|
|
|
// Now start on the other entries that have no specfic order.
|
|
|
|
if (cff.encoding && cff.topDict.hasName('Encoding')) {
|
|
|
|
if (cff.encoding.predefined) {
|
|
|
|
topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
|
|
|
|
output);
|
|
|
|
} else {
|
|
|
|
var encoding = this.compileEncoding(cff.encoding);
|
|
|
|
topDictTracker.setEntryLocation('Encoding', [output.length], output);
|
|
|
|
output.add(encoding);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cff.charset && cff.topDict.hasName('charset')) {
|
|
|
|
if (cff.charset.predefined) {
|
|
|
|
topDictTracker.setEntryLocation('charset', [cff.charset.format],
|
|
|
|
output);
|
|
|
|
} else {
|
|
|
|
var charset = this.compileCharset(cff.charset);
|
|
|
|
topDictTracker.setEntryLocation('charset', [output.length], output);
|
|
|
|
output.add(charset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var charStrings = this.compileCharStrings(cff.charStrings);
|
|
|
|
topDictTracker.setEntryLocation('CharStrings', [output.length], output);
|
|
|
|
output.add(charStrings);
|
|
|
|
|
|
|
|
if (cff.isCIDFont) {
|
2012-03-30 07:37:40 +09:00
|
|
|
// For some reason FDSelect must be in front of FDArray on windows. OSX
|
|
|
|
// and linux don't seem to care.
|
|
|
|
topDictTracker.setEntryLocation('FDSelect', [output.length], output);
|
|
|
|
var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
|
|
|
|
output.add(fdSelect);
|
2013-01-19 04:05:38 +09:00
|
|
|
// It is unclear if the sub font dictionary can have CID related
|
|
|
|
// dictionary keys, but the sanitizer doesn't like them so remove them.
|
2014-04-08 06:42:54 +09:00
|
|
|
compiled = this.compileTopDicts(cff.fdArray, output.length, true);
|
2012-03-11 12:12:33 +09:00
|
|
|
topDictTracker.setEntryLocation('FDArray', [output.length], output);
|
|
|
|
output.add(compiled.output);
|
|
|
|
var fontDictTrackers = compiled.trackers;
|
|
|
|
|
|
|
|
this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
|
|
|
|
|
2013-02-23 20:46:20 +09:00
|
|
|
// If the font data ends with INDEX whose object data is zero-length,
|
|
|
|
// the sanitizer will bail out. Add a dummy byte to avoid that.
|
|
|
|
output.add([0]);
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
return output.data;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
encodeNumber: function CFFCompiler_encodeNumber(value) {
|
2014-08-02 03:57:43 +09:00
|
|
|
if (parseFloat(value) === parseInt(value, 10) && !isNaN(value)) { // isInt
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.encodeInteger(value);
|
2014-03-18 01:34:30 +09:00
|
|
|
} else {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.encodeFloat(value);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
},
|
2012-12-27 12:29:28 +09:00
|
|
|
encodeFloat: function CFFCompiler_encodeFloat(num) {
|
|
|
|
var value = num.toString();
|
2013-05-04 05:13:22 +09:00
|
|
|
|
|
|
|
// rounding inaccurate doubles
|
|
|
|
var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
|
|
|
|
if (m) {
|
|
|
|
var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
|
|
|
|
value = (Math.round(num * epsilon) / epsilon).toString();
|
|
|
|
}
|
|
|
|
|
2012-12-27 12:29:28 +09:00
|
|
|
var nibbles = '';
|
2014-04-08 06:42:54 +09:00
|
|
|
var i, ii;
|
|
|
|
for (i = 0, ii = value.length; i < ii; ++i) {
|
2012-12-27 12:29:28 +09:00
|
|
|
var a = value[i];
|
|
|
|
if (a === 'e') {
|
|
|
|
nibbles += value[++i] === '-' ? 'c' : 'b';
|
2012-03-11 12:12:33 +09:00
|
|
|
} else if (a === '.') {
|
2012-12-27 12:29:28 +09:00
|
|
|
nibbles += 'a';
|
2012-03-11 12:12:33 +09:00
|
|
|
} else if (a === '-') {
|
2012-12-27 12:29:28 +09:00
|
|
|
nibbles += 'e';
|
2012-03-11 12:12:33 +09:00
|
|
|
} else {
|
2012-12-27 12:29:28 +09:00
|
|
|
nibbles += a;
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
}
|
2012-12-27 12:29:28 +09:00
|
|
|
nibbles += (nibbles.length & 1) ? 'f' : 'ff';
|
2012-03-11 12:12:33 +09:00
|
|
|
var out = [30];
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0, ii = nibbles.length; i < ii; i += 2) {
|
2012-12-27 12:29:28 +09:00
|
|
|
out.push(parseInt(nibbles.substr(i, 2), 16));
|
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return out;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
encodeInteger: function CFFCompiler_encodeInteger(value) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var code;
|
|
|
|
if (value >= -107 && value <= 107) {
|
|
|
|
code = [value + 139];
|
|
|
|
} else if (value >= 108 && value <= 1131) {
|
|
|
|
value = [value - 108];
|
|
|
|
code = [(value >> 8) + 247, value & 0xFF];
|
|
|
|
} else if (value >= -1131 && value <= -108) {
|
|
|
|
value = -value - 108;
|
|
|
|
code = [(value >> 8) + 251, value & 0xFF];
|
|
|
|
} else if (value >= -32768 && value <= 32767) {
|
|
|
|
code = [0x1c, (value >> 8) & 0xFF, value & 0xFF];
|
|
|
|
} else {
|
|
|
|
code = [0x1d,
|
|
|
|
(value >> 24) & 0xFF,
|
|
|
|
(value >> 16) & 0xFF,
|
|
|
|
(value >> 8) & 0xFF,
|
|
|
|
value & 0xFF];
|
|
|
|
}
|
|
|
|
return code;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileHeader: function CFFCompiler_compileHeader(header) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return [
|
|
|
|
header.major,
|
|
|
|
header.minor,
|
|
|
|
header.hdrSize,
|
|
|
|
header.offSize
|
|
|
|
];
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileNameIndex: function CFFCompiler_compileNameIndex(names) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var nameIndex = new CFFIndex();
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var i = 0, ii = names.length; i < ii; ++i) {
|
2014-08-10 13:34:01 +09:00
|
|
|
nameIndex.add(stringToBytes(names[i]));
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileIndex(nameIndex);
|
|
|
|
},
|
2013-01-19 04:05:38 +09:00
|
|
|
compileTopDicts: function CFFCompiler_compileTopDicts(dicts,
|
|
|
|
length,
|
|
|
|
removeCidKeys) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var fontDictTrackers = [];
|
|
|
|
var fdArrayIndex = new CFFIndex();
|
|
|
|
for (var i = 0, ii = dicts.length; i < ii; ++i) {
|
|
|
|
var fontDict = dicts[i];
|
2013-01-19 04:05:38 +09:00
|
|
|
if (removeCidKeys) {
|
|
|
|
fontDict.removeByName('CIDFontVersion');
|
|
|
|
fontDict.removeByName('CIDFontRevision');
|
|
|
|
fontDict.removeByName('CIDFontType');
|
|
|
|
fontDict.removeByName('CIDCount');
|
|
|
|
fontDict.removeByName('UIDBase');
|
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var fontDictTracker = new CFFOffsetTracker();
|
|
|
|
var fontDictData = this.compileDict(fontDict, fontDictTracker);
|
|
|
|
fontDictTrackers.push(fontDictTracker);
|
|
|
|
fdArrayIndex.add(fontDictData);
|
|
|
|
fontDictTracker.offset(length);
|
|
|
|
}
|
|
|
|
fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
|
2011-07-16 00:58:09 +09:00
|
|
|
return {
|
2012-03-11 12:12:33 +09:00
|
|
|
trackers: fontDictTrackers,
|
|
|
|
output: fdArrayIndex
|
2011-08-09 05:13:32 +09:00
|
|
|
};
|
2012-03-11 12:12:33 +09:00
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compilePrivateDicts: function CFFCompiler_compilePrivateDicts(dicts,
|
|
|
|
trackers,
|
|
|
|
output) {
|
2012-03-11 12:12:33 +09:00
|
|
|
for (var i = 0, ii = dicts.length; i < ii; ++i) {
|
|
|
|
var fontDict = dicts[i];
|
2013-03-09 04:07:36 +09:00
|
|
|
assert(fontDict.privateDict && fontDict.hasName('Private'),
|
|
|
|
'There must be an private dictionary.');
|
2012-03-11 12:12:33 +09:00
|
|
|
var privateDict = fontDict.privateDict;
|
|
|
|
var privateDictTracker = new CFFOffsetTracker();
|
|
|
|
var privateDictData = this.compileDict(privateDict, privateDictTracker);
|
|
|
|
|
2013-03-09 04:07:36 +09:00
|
|
|
var outputLength = output.length;
|
|
|
|
privateDictTracker.offset(outputLength);
|
|
|
|
if (!privateDictData.length) {
|
|
|
|
// The private dictionary was empty, set the output length to zero to
|
|
|
|
// ensure the offset length isn't out of bounds in the eyes of the
|
|
|
|
// sanitizer.
|
|
|
|
outputLength = 0;
|
|
|
|
}
|
|
|
|
|
2012-03-11 12:12:33 +09:00
|
|
|
trackers[i].setEntryLocation('Private',
|
2013-03-09 04:07:36 +09:00
|
|
|
[privateDictData.length, outputLength],
|
2012-03-11 12:12:33 +09:00
|
|
|
output);
|
|
|
|
output.add(privateDictData);
|
|
|
|
|
|
|
|
if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
|
|
|
|
var subrs = this.compileIndex(privateDict.subrsIndex);
|
|
|
|
privateDictTracker.setEntryLocation('Subrs', [privateDictData.length],
|
|
|
|
output);
|
|
|
|
output.add(subrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileDict: function CFFCompiler_compileDict(dict, offsetTracker) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var out = [];
|
|
|
|
// The dictionary keys must be in a certain order.
|
|
|
|
var order = dict.order;
|
|
|
|
for (var i = 0; i < order.length; ++i) {
|
|
|
|
var key = order[i];
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!(key in dict.values)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
continue;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
var values = dict.values[key];
|
|
|
|
var types = dict.types[key];
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!isArray(types)) {
|
|
|
|
types = [types];
|
|
|
|
}
|
|
|
|
if (!isArray(values)) {
|
|
|
|
values = [values];
|
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
// Remove any empty dict values.
|
2014-03-18 01:34:30 +09:00
|
|
|
if (values.length === 0) {
|
2012-03-11 12:12:33 +09:00
|
|
|
continue;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
for (var j = 0, jj = types.length; j < jj; ++j) {
|
|
|
|
var type = types[j];
|
|
|
|
var value = values[j];
|
|
|
|
switch (type) {
|
|
|
|
case 'num':
|
|
|
|
case 'sid':
|
|
|
|
out = out.concat(this.encodeNumber(value));
|
|
|
|
break;
|
|
|
|
case 'offset':
|
|
|
|
// For offsets we just insert a 32bit integer so we don't have to
|
|
|
|
// deal with figuring out the length of the offset when it gets
|
|
|
|
// replaced later on by the compiler.
|
|
|
|
var name = dict.keyToNameMap[key];
|
|
|
|
// Some offsets have the offset and the length, so just record the
|
|
|
|
// position of the first one.
|
2014-03-18 01:34:30 +09:00
|
|
|
if (!offsetTracker.isTracking(name)) {
|
2012-03-11 12:12:33 +09:00
|
|
|
offsetTracker.track(name, out.length);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
out = out.concat([0x1d, 0, 0, 0, 0]);
|
|
|
|
break;
|
|
|
|
case 'array':
|
|
|
|
case 'delta':
|
|
|
|
out = out.concat(this.encodeNumber(value));
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var k = 1, kk = values.length; k < kk; ++k) {
|
2012-03-11 12:12:33 +09:00
|
|
|
out = out.concat(this.encodeNumber(values[k]));
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
error('Unknown data type of ' + type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out = out.concat(dict.opcodes[key]);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileStringIndex: function CFFCompiler_compileStringIndex(strings) {
|
2012-03-11 12:12:33 +09:00
|
|
|
var stringIndex = new CFFIndex();
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var i = 0, ii = strings.length; i < ii; ++i) {
|
2014-08-10 13:34:01 +09:00
|
|
|
stringIndex.add(stringToBytes(strings[i]));
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileIndex(stringIndex);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileGlobalSubrIndex: function CFFCompiler_compileGlobalSubrIndex() {
|
2012-03-11 12:12:33 +09:00
|
|
|
var globalSubrIndex = this.cff.globalSubrIndex;
|
|
|
|
this.out.writeByteArray(this.compileIndex(globalSubrIndex));
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileCharStrings: function CFFCompiler_compileCharStrings(charStrings) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileIndex(charStrings);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileCharset: function CFFCompiler_compileCharset(charset) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileTypedArray(charset.raw);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileEncoding: function CFFCompiler_compileEncoding(encoding) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileTypedArray(encoding.raw);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileFDSelect: function CFFCompiler_compileFDSelect(fdSelect) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return this.compileTypedArray(fdSelect);
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileTypedArray: function CFFCompiler_compileTypedArray(data) {
|
2012-03-22 22:15:27 +09:00
|
|
|
var out = [];
|
2014-03-18 01:34:30 +09:00
|
|
|
for (var i = 0, ii = data.length; i < ii; ++i) {
|
2012-03-11 12:12:33 +09:00
|
|
|
out[i] = data[i];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
return out;
|
|
|
|
},
|
2012-04-05 05:43:26 +09:00
|
|
|
compileIndex: function CFFCompiler_compileIndex(index, trackers) {
|
2012-03-11 12:12:33 +09:00
|
|
|
trackers = trackers || [];
|
|
|
|
var objects = index.objects;
|
|
|
|
// First 2 bytes contains the number of objects contained into this index
|
|
|
|
var count = objects.length;
|
|
|
|
|
|
|
|
// If there is no object, just create an index. This technically
|
|
|
|
// should just be [0, 0] but OTS has an issue with that.
|
2014-03-18 01:34:30 +09:00
|
|
|
if (count === 0) {
|
2012-03-11 12:12:33 +09:00
|
|
|
return [0, 0, 0];
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
var data = [(count >> 8) & 0xFF, count & 0xff];
|
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
var lastOffset = 1, i;
|
|
|
|
for (i = 0; i < count; ++i) {
|
2012-03-11 12:12:33 +09:00
|
|
|
lastOffset += objects[i].length;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
var offsetSize;
|
2014-03-18 01:34:30 +09:00
|
|
|
if (lastOffset < 0x100) {
|
2012-03-11 12:12:33 +09:00
|
|
|
offsetSize = 1;
|
2014-03-18 01:34:30 +09:00
|
|
|
} else if (lastOffset < 0x10000) {
|
2012-03-11 12:12:33 +09:00
|
|
|
offsetSize = 2;
|
2014-03-18 01:34:30 +09:00
|
|
|
} else if (lastOffset < 0x1000000) {
|
2012-03-11 12:12:33 +09:00
|
|
|
offsetSize = 3;
|
2014-03-18 01:34:30 +09:00
|
|
|
} else {
|
2012-03-11 12:12:33 +09:00
|
|
|
offsetSize = 4;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
|
|
|
|
// Next byte contains the offset size use to reference object in the file
|
|
|
|
data.push(offsetSize);
|
|
|
|
|
|
|
|
// Add another offset after this one because we need a new offset
|
|
|
|
var relativeOffset = 1;
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < count + 1; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
if (offsetSize === 1) {
|
|
|
|
data.push(relativeOffset & 0xFF);
|
|
|
|
} else if (offsetSize === 2) {
|
|
|
|
data.push((relativeOffset >> 8) & 0xFF,
|
|
|
|
relativeOffset & 0xFF);
|
|
|
|
} else if (offsetSize === 3) {
|
|
|
|
data.push((relativeOffset >> 16) & 0xFF,
|
|
|
|
(relativeOffset >> 8) & 0xFF,
|
|
|
|
relativeOffset & 0xFF);
|
|
|
|
} else {
|
|
|
|
data.push((relativeOffset >>> 24) & 0xFF,
|
|
|
|
(relativeOffset >> 16) & 0xFF,
|
|
|
|
(relativeOffset >> 8) & 0xFF,
|
|
|
|
relativeOffset & 0xFF);
|
|
|
|
}
|
|
|
|
|
2014-03-18 01:34:30 +09:00
|
|
|
if (objects[i]) {
|
2012-03-11 12:12:33 +09:00
|
|
|
relativeOffset += objects[i].length;
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
|
2014-04-08 06:42:54 +09:00
|
|
|
for (i = 0; i < count; i++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
// Notify the tracker where the object will be offset in the data.
|
2014-03-18 01:34:30 +09:00
|
|
|
if (trackers[i]) {
|
2012-03-11 12:12:33 +09:00
|
|
|
trackers[i].offset(data.length);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
|
|
|
for (var j = 0, jj = objects[i].length; j < jj; j++) {
|
2012-03-11 12:12:33 +09:00
|
|
|
data.push(objects[i][j]);
|
2014-03-18 01:34:30 +09:00
|
|
|
}
|
2012-03-11 12:12:33 +09:00
|
|
|
}
|
|
|
|
return data;
|
2011-08-09 05:13:32 +09:00
|
|
|
}
|
2011-07-16 00:58:09 +09:00
|
|
|
};
|
2012-03-11 12:12:33 +09:00
|
|
|
return CFFCompiler;
|
2011-07-16 00:58:09 +09:00
|
|
|
})();
|
2011-10-28 03:51:10 +09:00
|
|
|
|
2015-11-22 01:32:47 +09:00
|
|
|
function _enableSeacAnalysis(enabled) {
|
|
|
|
exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED = enabled;
|
|
|
|
}
|
|
|
|
|
2013-02-27 03:00:20 +09:00
|
|
|
// Workaround for seac on Windows.
|
|
|
|
(function checkSeacSupport() {
|
2015-11-22 01:32:47 +09:00
|
|
|
if (typeof navigator !== 'undefined' && /Windows/.test(navigator.userAgent)) {
|
2013-02-27 03:00:20 +09:00
|
|
|
SEAC_ANALYSIS_ENABLED = true;
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
2012-11-17 05:01:33 +09:00
|
|
|
// 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() {
|
2015-11-22 01:32:47 +09:00
|
|
|
if (typeof navigator !== 'undefined' &&
|
|
|
|
/Windows.*Chrome/.test(navigator.userAgent)) {
|
2014-02-12 03:27:09 +09:00
|
|
|
SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = true;
|
2012-11-17 05:01:33 +09:00
|
|
|
}
|
|
|
|
})();
|
2015-11-22 01:32:47 +09:00
|
|
|
|
|
|
|
exports.SEAC_ANALYSIS_ENABLED = SEAC_ANALYSIS_ENABLED;
|
|
|
|
exports.CFFCompiler = CFFCompiler;
|
|
|
|
exports.CFFIndex = CFFIndex;
|
|
|
|
exports.CFFParser = CFFParser;
|
|
|
|
exports.CFFStrings = CFFStrings;
|
|
|
|
exports.ErrorFont = ErrorFont;
|
|
|
|
exports.FontFlags = FontFlags;
|
|
|
|
exports.Font = Font;
|
|
|
|
exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
|
|
|
|
exports.ToUnicodeMap = ToUnicodeMap;
|
|
|
|
exports.Type1Parser = Type1Parser;
|
|
|
|
exports.getFontType = getFontType;
|
|
|
|
exports._enableSeacAnalysis = _enableSeacAnalysis;
|
|
|
|
|
|
|
|
// TODO refactor to remove cyclic dependency on font_renderer.js
|
|
|
|
coreFontRenderer._setCoreFonts(exports);
|
|
|
|
}));
|