opentype.js
Advanced tools
Comparing version 0.4.9 to 0.4.10
{ | ||
"name": "opentype.js", | ||
"version": "0.4.9", | ||
"version": "0.4.10", | ||
"main": "dist/opentype.js", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -38,3 +38,3 @@ # Contributing | ||
7. Check if tests pass (currently this just runs the code through JSHint) | ||
7. Check if tests pass | ||
@@ -41,0 +41,0 @@ npm test |
{ | ||
"name": "opentype.js", | ||
"description": "OpenType font parser", | ||
"version": "0.4.9", | ||
"version": "0.4.10", | ||
"author": { | ||
@@ -29,3 +29,3 @@ "name": "Frederik De Bleser", | ||
"start": "mkdirp build && parallelshell \"npm run watch\" \"node ./bin/server.js\"", | ||
"test": "jshint . && jscs .", | ||
"test": "mocha --recursive && jshint . && jscs .", | ||
"browserify": "browserify src/opentype.js --bare --standalone opentype > dist/opentype.js", | ||
@@ -40,2 +40,3 @@ "uglify": "browserify src/opentype.js --bare --standalone opentype -g uglifyify > dist/opentype.min.js", | ||
"mkdirp": "^0.5.1", | ||
"mocha": "^2.2.5", | ||
"parallelshell": "^1.1.1", | ||
@@ -42,0 +43,0 @@ "rimraf": "^2.4.0", |
@@ -80,3 +80,8 @@ opentype.js | ||
### Loading a font synchronously (Node.js) | ||
Use `opentype.loadSync(url)` to load a font from a file and return a `Font` object. | ||
Throws an error if the font could not be parsed. This only works in Node.js. | ||
var font = opentype.loadSync('fonts/Roboto-Black.ttf'); | ||
### Writing a font | ||
@@ -95,3 +100,3 @@ Once you have a `Font` object (either by using `opentype.load` or by creating a new one from scratch) you can write it | ||
notdefPath.lineTo(100, 700); | ||
// more drawing instructions.... | ||
// more drawing instructions.... | ||
var notdefGlyph = new opentype.Glyph({ | ||
@@ -103,3 +108,3 @@ name: '.notdef', | ||
}); | ||
var aPath = new opentype.Path(); | ||
@@ -129,2 +134,4 @@ aPath.moveTo(100, 0); | ||
* `unitsPerEm`: X/Y coordinates in fonts are stored as integers. This value determines the size of the grid. Common values are 2048 and 4096. | ||
* `ascender`: Distance from baseline of highest ascender. In font units, not pixels. | ||
* `descender`: Distance from baseline of lowest descender. In font units, not pixels. | ||
@@ -180,3 +187,3 @@ #### `Font.getPath(text, x, y, fontSize, options)` | ||
* `xMin`, `yMin`, `xMax`, `yMax`: The bounding box of the glyph. | ||
* `path`: The raw, unscaled path of the glyph. | ||
* `path`: The raw, unscaled path of the glyph. | ||
@@ -220,2 +227,11 @@ ##### `Glyph.getPath(x, y, fontSize)` | ||
##### `Path.toPathData(decimalPlaces)` | ||
Convert the Path to a string of path data instructions. | ||
See http://www.w3.org/TR/SVG/paths.html#PathData | ||
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) | ||
##### `Path.toSVG(decimalPlaces)` | ||
Convert the path to a SVG <path> element, as a string. | ||
* `decimalPlaces`: The amount of decimal places for floating-point values. (default: 2) | ||
#### Path commands | ||
@@ -231,3 +247,2 @@ * **Move To**: Move to a new position. This creates a new contour. Example: `{type: 'M', x: 100, y: 200}` | ||
* Support for ligatures and contextual alternates. | ||
* Support for SVG paths. | ||
@@ -234,0 +249,0 @@ Thanks |
@@ -0,1 +1,9 @@ | ||
0.4.10 (July 30, 2015) | ||
====================== | ||
* Add loadSync method for Node.js. | ||
* Unit tests for basic types and tables. | ||
* Implement MACSTRING codec. | ||
* Support multilingual names. | ||
* Handle names of font variation axes and instances. | ||
0.4.9 (June 23, 2015) | ||
@@ -2,0 +10,0 @@ ===================== |
@@ -17,18 +17,20 @@ // The Font object | ||
// OS X will complain if the names are empty, so we put a single space everywhere by default. | ||
this.familyName = options.familyName || ' '; | ||
this.styleName = options.styleName || ' '; | ||
this.designer = options.designer || ' '; | ||
this.designerURL = options.designerURL || ' '; | ||
this.manufacturer = options.manufacturer || ' '; | ||
this.manufacturerURL = options.manufacturerURL || ' '; | ||
this.license = options.license || ' '; | ||
this.licenseURL = options.licenseURL || ' '; | ||
this.version = options.version || 'Version 0.1'; | ||
this.description = options.description || ' '; | ||
this.copyright = options.copyright || ' '; | ||
this.trademark = options.trademark || ' '; | ||
this.names = { | ||
fontFamily: {en: options.familyName || ' '}, | ||
fontSubfamily: {en: options.styleName || ' '}, | ||
designer: {en: options.designer || ' '}, | ||
designerURL: {en: options.designerURL || ' '}, | ||
manufacturer: {en: options.manufacturer || ' '}, | ||
manufacturerURL: {en: options.manufacturerURL || ' '}, | ||
license: {en: options.license || ' '}, | ||
licenseURL: {en: options.licenseURL || ' '}, | ||
version: {en: options.version || 'Version 0.1'}, | ||
description: {en: options.description || ' '}, | ||
copyright: {en: options.copyright || ' '}, | ||
trademark: {en: options.trademark || ' '} | ||
}; | ||
this.unitsPerEm = options.unitsPerEm || 1000; | ||
this.ascender = options.ascender; | ||
this.descender = options.descender; | ||
this.supported = true; | ||
this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. | ||
this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); | ||
@@ -117,6 +119,2 @@ this.encoding = new encoding.DefaultEncoding(this); | ||
Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { | ||
if (!this.supported) { | ||
return; | ||
} | ||
x = x !== undefined ? x : 0; | ||
@@ -210,2 +208,9 @@ y = y !== undefined ? y : 0; | ||
Font.prototype.getEnglishName = function(name) { | ||
var translations = this.names[name]; | ||
if (translations) { | ||
return translations.en; | ||
} | ||
}; | ||
// Validate | ||
@@ -222,12 +227,14 @@ Font.prototype.validate = function() { | ||
function assertStringAttribute(attrName) { | ||
assert(_this[attrName] && _this[attrName].trim().length > 0, 'No ' + attrName + ' specified.'); | ||
function assertNamePresent(name) { | ||
var englishName = _this.getEnglishName(name); | ||
assert(englishName && englishName.trim().length > 0, | ||
'No English ' + name + ' specified.'); | ||
} | ||
// Identification information | ||
assertStringAttribute('familyName'); | ||
assertStringAttribute('weightName'); | ||
assertStringAttribute('manufacturer'); | ||
assertStringAttribute('copyright'); | ||
assertStringAttribute('version'); | ||
assertNamePresent('fontFamily'); | ||
assertNamePresent('weightName'); | ||
assertNamePresent('manufacturer'); | ||
assertNamePresent('copyright'); | ||
assertNamePresent('version'); | ||
@@ -258,3 +265,5 @@ // Dimension information | ||
Font.prototype.download = function() { | ||
var fileName = this.familyName.replace(/\s/g, '') + '-' + this.styleName + '.otf'; | ||
var familyName = this.getEnglishName('fontFamily'); | ||
var styleName = this.getEnglishName('fontSubfamily'); | ||
var fileName = familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; | ||
var buffer = this.toBuffer(); | ||
@@ -261,0 +270,0 @@ |
@@ -18,2 +18,3 @@ // opentype.js | ||
var cff = require('./tables/cff'); | ||
var fvar = require('./tables/fvar'); | ||
var glyf = require('./tables/glyf'); | ||
@@ -25,2 +26,3 @@ var gpos = require('./tables/gpos'); | ||
var kern = require('./tables/kern'); | ||
var ltag = require('./tables/ltag'); | ||
var loca = require('./tables/loca'); | ||
@@ -74,12 +76,15 @@ var maxp = require('./tables/maxp'); | ||
// Parse the OpenType file data (as an ArrayBuffer) and return a Font object. | ||
// If the file could not be parsed (most likely because it contains Postscript outlines) | ||
// we return an empty Font object with the `supported` flag set to `false`. | ||
// Throws an error if the font could not be parsed. | ||
function parseBuffer(buffer) { | ||
var indexToLocFormat; | ||
var ltagTable; | ||
var cffOffset; | ||
var fvarOffset; | ||
var glyfOffset; | ||
var gposOffset; | ||
var hmtxOffset; | ||
var glyfOffset; | ||
var kernOffset; | ||
var locaOffset; | ||
var cffOffset; | ||
var kernOffset; | ||
var gposOffset; | ||
var nameOffset; | ||
@@ -116,7 +121,6 @@ // OpenType fonts use big endian byte ordering. | ||
font.encoding = new encoding.CmapEncoding(font.tables.cmap); | ||
if (!font.encoding) { | ||
font.supported = false; | ||
} | ||
break; | ||
case 'fvar': | ||
fvarOffset = offset; | ||
break; | ||
case 'head': | ||
@@ -136,2 +140,5 @@ font.tables.head = head.parse(data, offset); | ||
break; | ||
case 'ltag': | ||
ltagTable = ltag.parse(data, offset); | ||
break; | ||
case 'maxp': | ||
@@ -142,5 +149,3 @@ font.tables.maxp = maxp.parse(data, offset); | ||
case 'name': | ||
font.tables.name = _name.parse(data, offset); | ||
font.familyName = font.tables.name.fontFamily; | ||
font.styleName = font.tables.name.fontSubfamily; | ||
nameOffset = offset; | ||
break; | ||
@@ -173,2 +178,5 @@ case 'OS/2': | ||
font.tables.name = _name.parse(data, nameOffset, ltagTable); | ||
font.names = font.tables.name; | ||
if (glyfOffset && locaOffset) { | ||
@@ -184,17 +192,19 @@ var shortVersion = indexToLocFormat === 0; | ||
} else { | ||
font.supported = false; | ||
throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); | ||
} | ||
if (font.supported) { | ||
if (kernOffset) { | ||
font.kerningPairs = kern.parse(data, kernOffset); | ||
} else { | ||
font.kerningPairs = {}; | ||
} | ||
if (kernOffset) { | ||
font.kerningPairs = kern.parse(data, kernOffset); | ||
} else { | ||
font.kerningPairs = {}; | ||
} | ||
if (gposOffset) { | ||
gpos.parse(data, gposOffset, font); | ||
} | ||
if (gposOffset) { | ||
gpos.parse(data, gposOffset, font); | ||
} | ||
if (fvarOffset) { | ||
font.tables.fvar = fvar.parse(data, fvarOffset, font.names); | ||
} | ||
return font; | ||
@@ -218,6 +228,2 @@ } | ||
var font = parseBuffer(arrayBuffer); | ||
if (!font.supported) { | ||
return callback('Font is not supported (is this a Postscript font?)'); | ||
} | ||
return callback(null, font); | ||
@@ -227,2 +233,10 @@ }); | ||
// Syncronously load the font from a URL or file. | ||
// When done, return the font object or throw an error. | ||
function loadSync(url) { | ||
var fs = require('fs'); | ||
var buffer = fs.readFileSync(url); | ||
return parseBuffer(toArrayBuffer(buffer)); | ||
} | ||
exports._parse = parse; | ||
@@ -234,1 +248,2 @@ exports.Font = _font.Font; | ||
exports.load = load; | ||
exports.loadSync = loadSync; |
@@ -6,3 +6,5 @@ // The `name` naming table. | ||
var encode = require('../types').encode; | ||
var types = require('../types'); | ||
var decode = types.decode; | ||
var encode = types.encode; | ||
var parse = require('../parse'); | ||
@@ -38,12 +40,602 @@ var table = require('../table'); | ||
// Parse the naming `name` table | ||
// Only Windows Unicode English names are supported. | ||
// Format 1 additional fields are not supported | ||
function parseNameTable(data, start) { | ||
var macLanguages = { | ||
0: 'en', | ||
1: 'fr', | ||
2: 'de', | ||
3: 'it', | ||
4: 'nl', | ||
5: 'sv', | ||
6: 'es', | ||
7: 'da', | ||
8: 'pt', | ||
9: 'no', | ||
10: 'he', | ||
11: 'ja', | ||
12: 'ar', | ||
13: 'fi', | ||
14: 'el', | ||
15: 'is', | ||
16: 'mt', | ||
17: 'tr', | ||
18: 'hr', | ||
19: 'zh-Hant', | ||
20: 'ur', | ||
21: 'hi', | ||
22: 'th', | ||
23: 'ko', | ||
24: 'lt', | ||
25: 'pl', | ||
26: 'hu', | ||
27: 'es', | ||
28: 'lv', | ||
29: 'se', | ||
30: 'fo', | ||
31: 'fa', | ||
32: 'ru', | ||
33: 'zh', | ||
34: 'nl-BE', | ||
35: 'ga', | ||
36: 'sq', | ||
37: 'ro', | ||
38: 'cz', | ||
39: 'sk', | ||
40: 'si', | ||
41: 'yi', | ||
42: 'sr', | ||
43: 'mk', | ||
44: 'bg', | ||
45: 'uk', | ||
46: 'be', | ||
47: 'uz', | ||
48: 'kk', | ||
49: 'az-Cyrl', | ||
50: 'az-Arab', | ||
51: 'hy', | ||
52: 'ka', | ||
53: 'mo', | ||
54: 'ky', | ||
55: 'tg', | ||
56: 'tk', | ||
57: 'mn-CN', | ||
58: 'mn', | ||
59: 'ps', | ||
60: 'ks', | ||
61: 'ku', | ||
62: 'sd', | ||
63: 'bo', | ||
64: 'ne', | ||
65: 'sa', | ||
66: 'mr', | ||
67: 'bn', | ||
68: 'as', | ||
69: 'gu', | ||
70: 'pa', | ||
71: 'or', | ||
72: 'ml', | ||
73: 'kn', | ||
74: 'ta', | ||
75: 'te', | ||
76: 'si', | ||
77: 'my', | ||
78: 'km', | ||
79: 'lo', | ||
80: 'vi', | ||
81: 'id', | ||
82: 'tl', | ||
83: 'ms', | ||
84: 'ms-Arab', | ||
85: 'am', | ||
86: 'ti', | ||
87: 'om', | ||
88: 'so', | ||
89: 'sw', | ||
90: 'rw', | ||
91: 'rn', | ||
92: 'ny', | ||
93: 'mg', | ||
94: 'eo', | ||
128: 'cy', | ||
129: 'eu', | ||
130: 'ca', | ||
131: 'la', | ||
132: 'qu', | ||
133: 'gn', | ||
134: 'ay', | ||
135: 'tt', | ||
136: 'ug', | ||
137: 'dz', | ||
138: 'jv', | ||
139: 'su', | ||
140: 'gl', | ||
141: 'af', | ||
142: 'br', | ||
143: 'iu', | ||
144: 'gd', | ||
145: 'gv', | ||
146: 'ga', | ||
147: 'to', | ||
148: 'el-polyton', | ||
149: 'kl', | ||
150: 'az', | ||
151: 'nn' | ||
}; | ||
// MacOS language ID → MacOS script ID | ||
// | ||
// Note that the script ID is not sufficient to determine what encoding | ||
// to use in TrueType files. For some languages, MacOS used a modification | ||
// of a mainstream script. For example, an Icelandic name would be stored | ||
// with smRoman in the TrueType naming table, but the actual encoding | ||
// is a special Icelandic version of the normal Macintosh Roman encoding. | ||
// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal | ||
// Syllables but MacOS had run out of available script codes, so this was | ||
// done as a (pretty radical) "modification" of Ethiopic. | ||
// | ||
// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt | ||
var macLanguageToScript = { | ||
0: 0, // langEnglish → smRoman | ||
1: 0, // langFrench → smRoman | ||
2: 0, // langGerman → smRoman | ||
3: 0, // langItalian → smRoman | ||
4: 0, // langDutch → smRoman | ||
5: 0, // langSwedish → smRoman | ||
6: 0, // langSpanish → smRoman | ||
7: 0, // langDanish → smRoman | ||
8: 0, // langPortuguese → smRoman | ||
9: 0, // langNorwegian → smRoman | ||
10: 5, // langHebrew → smHebrew | ||
11: 1, // langJapanese → smJapanese | ||
12: 4, // langArabic → smArabic | ||
13: 0, // langFinnish → smRoman | ||
14: 6, // langGreek → smGreek | ||
15: 0, // langIcelandic → smRoman (modified) | ||
16: 0, // langMaltese → smRoman | ||
17: 0, // langTurkish → smRoman (modified) | ||
18: 0, // langCroatian → smRoman (modified) | ||
19: 2, // langTradChinese → smTradChinese | ||
20: 4, // langUrdu → smArabic | ||
21: 9, // langHindi → smDevanagari | ||
22: 21, // langThai → smThai | ||
23: 3, // langKorean → smKorean | ||
24: 29, // langLithuanian → smCentralEuroRoman | ||
25: 29, // langPolish → smCentralEuroRoman | ||
26: 29, // langHungarian → smCentralEuroRoman | ||
27: 29, // langEstonian → smCentralEuroRoman | ||
28: 29, // langLatvian → smCentralEuroRoman | ||
29: 0, // langSami → smRoman | ||
30: 0, // langFaroese → smRoman (modified) | ||
31: 4, // langFarsi → smArabic (modified) | ||
32: 7, // langRussian → smCyrillic | ||
33: 25, // langSimpChinese → smSimpChinese | ||
34: 0, // langFlemish → smRoman | ||
35: 0, // langIrishGaelic → smRoman (modified) | ||
36: 0, // langAlbanian → smRoman | ||
37: 0, // langRomanian → smRoman (modified) | ||
38: 29, // langCzech → smCentralEuroRoman | ||
39: 29, // langSlovak → smCentralEuroRoman | ||
40: 0, // langSlovenian → smRoman (modified) | ||
41: 5, // langYiddish → smHebrew | ||
42: 7, // langSerbian → smCyrillic | ||
43: 7, // langMacedonian → smCyrillic | ||
44: 7, // langBulgarian → smCyrillic | ||
45: 7, // langUkrainian → smCyrillic (modified) | ||
46: 7, // langByelorussian → smCyrillic | ||
47: 7, // langUzbek → smCyrillic | ||
48: 7, // langKazakh → smCyrillic | ||
49: 7, // langAzerbaijani → smCyrillic | ||
50: 4, // langAzerbaijanAr → smArabic | ||
51: 24, // langArmenian → smArmenian | ||
52: 23, // langGeorgian → smGeorgian | ||
53: 7, // langMoldavian → smCyrillic | ||
54: 7, // langKirghiz → smCyrillic | ||
55: 7, // langTajiki → smCyrillic | ||
56: 7, // langTurkmen → smCyrillic | ||
57: 27, // langMongolian → smMongolian | ||
58: 7, // langMongolianCyr → smCyrillic | ||
59: 4, // langPashto → smArabic | ||
60: 4, // langKurdish → smArabic | ||
61: 4, // langKashmiri → smArabic | ||
62: 4, // langSindhi → smArabic | ||
63: 26, // langTibetan → smTibetan | ||
64: 9, // langNepali → smDevanagari | ||
65: 9, // langSanskrit → smDevanagari | ||
66: 9, // langMarathi → smDevanagari | ||
67: 13, // langBengali → smBengali | ||
68: 13, // langAssamese → smBengali | ||
69: 11, // langGujarati → smGujarati | ||
70: 10, // langPunjabi → smGurmukhi | ||
71: 12, // langOriya → smOriya | ||
72: 17, // langMalayalam → smMalayalam | ||
73: 16, // langKannada → smKannada | ||
74: 14, // langTamil → smTamil | ||
75: 15, // langTelugu → smTelugu | ||
76: 18, // langSinhalese → smSinhalese | ||
77: 19, // langBurmese → smBurmese | ||
78: 20, // langKhmer → smKhmer | ||
79: 22, // langLao → smLao | ||
80: 30, // langVietnamese → smVietnamese | ||
81: 0, // langIndonesian → smRoman | ||
82: 0, // langTagalog → smRoman | ||
83: 0, // langMalayRoman → smRoman | ||
84: 4, // langMalayArabic → smArabic | ||
85: 28, // langAmharic → smEthiopic | ||
86: 28, // langTigrinya → smEthiopic | ||
87: 28, // langOromo → smEthiopic | ||
88: 0, // langSomali → smRoman | ||
89: 0, // langSwahili → smRoman | ||
90: 0, // langKinyarwanda → smRoman | ||
91: 0, // langRundi → smRoman | ||
92: 0, // langNyanja → smRoman | ||
93: 0, // langMalagasy → smRoman | ||
94: 0, // langEsperanto → smRoman | ||
128: 0, // langWelsh → smRoman (modified) | ||
129: 0, // langBasque → smRoman | ||
130: 0, // langCatalan → smRoman | ||
131: 0, // langLatin → smRoman | ||
132: 0, // langQuechua → smRoman | ||
133: 0, // langGuarani → smRoman | ||
134: 0, // langAymara → smRoman | ||
135: 7, // langTatar → smCyrillic | ||
136: 4, // langUighur → smArabic | ||
137: 26, // langDzongkha → smTibetan | ||
138: 0, // langJavaneseRom → smRoman | ||
139: 0, // langSundaneseRom → smRoman | ||
140: 0, // langGalician → smRoman | ||
141: 0, // langAfrikaans → smRoman | ||
142: 0, // langBreton → smRoman (modified) | ||
143: 28, // langInuktitut → smEthiopic (modified) | ||
144: 0, // langScottishGaelic → smRoman (modified) | ||
145: 0, // langManxGaelic → smRoman (modified) | ||
146: 0, // langIrishGaelicScript → smRoman (modified) | ||
147: 0, // langTongan → smRoman | ||
148: 6, // langGreekAncient → smRoman | ||
149: 0, // langGreenlandic → smRoman | ||
150: 0, // langAzerbaijanRoman → smRoman | ||
151: 0 // langNynorsk → smRoman | ||
}; | ||
// While Microsoft indicates a region/country for all its language | ||
// IDs, we omit the region code if it's equal to the "most likely | ||
// region subtag" according to Unicode CLDR. For scripts, we omit | ||
// the subtag if it is equal to the Suppress-Script entry in the | ||
// IANA language subtag registry for IETF BCP 47. | ||
// | ||
// For example, Microsoft states that its language code 0x041A is | ||
// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' | ||
// and not 'hr-HR' because Croatia is the default country for Croatian, | ||
// according to Unicode CLDR. As another example, Microsoft states | ||
// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform | ||
// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script | ||
// for the Croatian language, according to IANA. | ||
// | ||
// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html | ||
// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry | ||
var windowsLanguages = { | ||
0x0436: 'af', | ||
0x041C: 'sq', | ||
0x0484: 'gsw', | ||
0x045E: 'am', | ||
0x1401: 'ar-DZ', | ||
0x3C01: 'ar-BH', | ||
0x0C01: 'ar', | ||
0x0801: 'ar-IQ', | ||
0x2C01: 'ar-JO', | ||
0x3401: 'ar-KW', | ||
0x3001: 'ar-LB', | ||
0x1001: 'ar-LY', | ||
0x1801: 'ary', | ||
0x2001: 'ar-OM', | ||
0x4001: 'ar-QA', | ||
0x0401: 'ar-SA', | ||
0x2801: 'ar-SY', | ||
0x1C01: 'aeb', | ||
0x3801: 'ar-AE', | ||
0x2401: 'ar-YE', | ||
0x042B: 'hy', | ||
0x044D: 'as', | ||
0x082C: 'az-Cyrl', | ||
0x042C: 'az', | ||
0x046D: 'ba', | ||
0x042D: 'eu', | ||
0x0423: 'be', | ||
0x0845: 'bn', | ||
0x0445: 'bn-IN', | ||
0x201A: 'bs-Cyrl', | ||
0x141A: 'bs', | ||
0x047E: 'br', | ||
0x0402: 'bg', | ||
0x0403: 'ca', | ||
0x0C04: 'zh-HK', | ||
0x1404: 'zh-MO', | ||
0x0804: 'zh', | ||
0x1004: 'zh-SG', | ||
0x0404: 'zh-TW', | ||
0x0483: 'co', | ||
0x041A: 'hr', | ||
0x101A: 'hr-BA', | ||
0x0405: 'cs', | ||
0x0406: 'da', | ||
0x048C: 'prs', | ||
0x0465: 'dv', | ||
0x0813: 'nl-BE', | ||
0x0413: 'nl', | ||
0x0C09: 'en-AU', | ||
0x2809: 'en-BZ', | ||
0x1009: 'en-CA', | ||
0x2409: 'en-029', | ||
0x4009: 'en-IN', | ||
0x1809: 'en-IE', | ||
0x2009: 'en-JM', | ||
0x4409: 'en-MY', | ||
0x1409: 'en-NZ', | ||
0x3409: 'en-PH', | ||
0x4809: 'en-SG', | ||
0x1C09: 'en-ZA', | ||
0x2C09: 'en-TT', | ||
0x0809: 'en-GB', | ||
0x0409: 'en', | ||
0x3009: 'en-ZW', | ||
0x0425: 'et', | ||
0x0438: 'fo', | ||
0x0464: 'fil', | ||
0x040B: 'fi', | ||
0x080C: 'fr-BE', | ||
0x0C0C: 'fr-CA', | ||
0x040C: 'fr', | ||
0x140C: 'fr-LU', | ||
0x180C: 'fr-MC', | ||
0x100C: 'fr-CH', | ||
0x0462: 'fy', | ||
0x0456: 'gl', | ||
0x0437: 'ka', | ||
0x0C07: 'de-AT', | ||
0x0407: 'de', | ||
0x1407: 'de-LI', | ||
0x1007: 'de-LU', | ||
0x0807: 'de-CH', | ||
0x0408: 'el', | ||
0x046F: 'kl', | ||
0x0447: 'gu', | ||
0x0468: 'ha', | ||
0x040D: 'he', | ||
0x0439: 'hi', | ||
0x040E: 'hu', | ||
0x040F: 'is', | ||
0x0470: 'ig', | ||
0x0421: 'id', | ||
0x045D: 'iu', | ||
0x085D: 'iu-Latn', | ||
0x083C: 'ga', | ||
0x0434: 'xh', | ||
0x0435: 'zu', | ||
0x0410: 'it', | ||
0x0810: 'it-CH', | ||
0x0411: 'ja', | ||
0x044B: 'kn', | ||
0x043F: 'kk', | ||
0x0453: 'km', | ||
0x0486: 'quc', | ||
0x0487: 'rw', | ||
0x0441: 'sw', | ||
0x0457: 'kok', | ||
0x0412: 'ko', | ||
0x0440: 'ky', | ||
0x0454: 'lo', | ||
0x0426: 'lv', | ||
0x0427: 'lt', | ||
0x082E: 'dsb', | ||
0x046E: 'lb', | ||
0x042F: 'mk', | ||
0x083E: 'ms-BN', | ||
0x043E: 'ms', | ||
0x044C: 'ml', | ||
0x043A: 'mt', | ||
0x0481: 'mi', | ||
0x047A: 'arn', | ||
0x044E: 'mr', | ||
0x047C: 'moh', | ||
0x0450: 'mn', | ||
0x0850: 'mn-CN', | ||
0x0461: 'ne', | ||
0x0414: 'nb', | ||
0x0814: 'nn', | ||
0x0482: 'oc', | ||
0x0448: 'or', | ||
0x0463: 'ps', | ||
0x0415: 'pl', | ||
0x0416: 'pt', | ||
0x0816: 'pt-PT', | ||
0x0446: 'pa', | ||
0x046B: 'qu-BO', | ||
0x086B: 'qu-EC', | ||
0x0C6B: 'qu', | ||
0x0418: 'ro', | ||
0x0417: 'rm', | ||
0x0419: 'ru', | ||
0x243B: 'smn', | ||
0x103B: 'smj-NO', | ||
0x143B: 'smj', | ||
0x0C3B: 'se-FI', | ||
0x043B: 'se', | ||
0x083B: 'se-SE', | ||
0x203B: 'sms', | ||
0x183B: 'sma-NO', | ||
0x1C3B: 'sms', | ||
0x044F: 'sa', | ||
0x1C1A: 'sr-Cyrl-BA', | ||
0x0C1A: 'sr', | ||
0x181A: 'sr-Latn-BA', | ||
0x081A: 'sr-Latn', | ||
0x046C: 'nso', | ||
0x0432: 'tn', | ||
0x045B: 'si', | ||
0x041B: 'sk', | ||
0x0424: 'sl', | ||
0x2C0A: 'es-AR', | ||
0x400A: 'es-BO', | ||
0x340A: 'es-CL', | ||
0x240A: 'es-CO', | ||
0x140A: 'es-CR', | ||
0x1C0A: 'es-DO', | ||
0x300A: 'es-EC', | ||
0x440A: 'es-SV', | ||
0x100A: 'es-GT', | ||
0x480A: 'es-HN', | ||
0x080A: 'es-MX', | ||
0x4C0A: 'es-NI', | ||
0x180A: 'es-PA', | ||
0x3C0A: 'es-PY', | ||
0x280A: 'es-PE', | ||
0x500A: 'es-PR', | ||
// Microsoft has defined two different language codes for | ||
// “Spanish with modern sorting” and “Spanish with traditional | ||
// sorting”. This makes sense for collation APIs, and it would be | ||
// possible to express this in BCP 47 language tags via Unicode | ||
// extensions (eg., es-u-co-trad is Spanish with traditional | ||
// sorting). However, for storing names in fonts, the distinction | ||
// does not make sense, so we give “es” in both cases. | ||
0x0C0A: 'es', | ||
0x040A: 'es', | ||
0x540A: 'es-US', | ||
0x380A: 'es-UY', | ||
0x200A: 'es-VE', | ||
0x081D: 'sv-FI', | ||
0x041D: 'sv', | ||
0x045A: 'syr', | ||
0x0428: 'tg', | ||
0x085F: 'tzm', | ||
0x0449: 'ta', | ||
0x0444: 'tt', | ||
0x044A: 'te', | ||
0x041E: 'th', | ||
0x0451: 'bo', | ||
0x041F: 'tr', | ||
0x0442: 'tk', | ||
0x0480: 'ug', | ||
0x0422: 'uk', | ||
0x042E: 'hsb', | ||
0x0420: 'ur', | ||
0x0843: 'uz-Cyrl', | ||
0x0443: 'uz', | ||
0x042A: 'vi', | ||
0x0452: 'cy', | ||
0x0488: 'wo', | ||
0x0485: 'sah', | ||
0x0478: 'ii', | ||
0x046A: 'yo' | ||
}; | ||
// Returns a IETF BCP 47 language code, for example 'zh-Hant' | ||
// for 'Chinese in the traditional script'. | ||
function getLanguageCode(platformID, languageID, ltag) { | ||
switch (platformID) { | ||
case 0: // Unicode | ||
if (languageID === 0xFFFF) { | ||
return 'und'; | ||
} else if (ltag) { | ||
return ltag[languageID]; | ||
} | ||
break; | ||
case 1: // Macintosh | ||
return macLanguages[languageID]; | ||
case 3: // Windows | ||
return windowsLanguages[languageID]; | ||
} | ||
return undefined; | ||
} | ||
var utf16 = 'utf-16'; | ||
// MacOS script ID → encoding. This table stores the default case, | ||
// which can be overridden by macLanguageEncodings. | ||
var macScriptEncodings = { | ||
0: 'macintosh', // smRoman | ||
1: 'x-mac-japanese', // smJapanese | ||
2: 'x-mac-chinesetrad', // smTradChinese | ||
3: 'x-mac-korean', // smKorean | ||
6: 'x-mac-greek', // smGreek | ||
7: 'x-mac-cyrillic', // smCyrillic | ||
9: 'x-mac-devanagai', // smDevanagari | ||
10: 'x-mac-gurmukhi', // smGurmukhi | ||
11: 'x-mac-gujarati', // smGujarati | ||
12: 'x-mac-oriya', // smOriya | ||
13: 'x-mac-bengali', // smBengali | ||
14: 'x-mac-tamil', // smTamil | ||
15: 'x-mac-telugu', // smTelugu | ||
16: 'x-mac-kannada', // smKannada | ||
17: 'x-mac-malayalam', // smMalayalam | ||
18: 'x-mac-sinhalese', // smSinhalese | ||
19: 'x-mac-burmese', // smBurmese | ||
20: 'x-mac-khmer', // smKhmer | ||
21: 'x-mac-thai', // smThai | ||
22: 'x-mac-lao', // smLao | ||
23: 'x-mac-georgian', // smGeorgian | ||
24: 'x-mac-armenian', // smArmenian | ||
25: 'x-mac-chinesesimp', // smSimpChinese | ||
26: 'x-mac-tibetan', // smTibetan | ||
27: 'x-mac-mongolian', // smMongolian | ||
28: 'x-mac-ethiopic', // smEthiopic | ||
29: 'x-mac-ce', // smCentralEuroRoman | ||
30: 'x-mac-vietnamese', // smVietnamese | ||
31: 'x-mac-extarabic' // smExtArabic | ||
}; | ||
// MacOS language ID → encoding. This table stores the exceptional | ||
// cases, which override macScriptEncodings. For writing MacOS naming | ||
// tables, we need to emit a MacOS script ID. Therefore, we cannot | ||
// merge macScriptEncodings into macLanguageEncodings. | ||
// | ||
// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt | ||
var macLanguageEncodings = { | ||
15: 'x-mac-icelandic', // langIcelandic | ||
17: 'x-mac-turkish', // langTurkish | ||
18: 'x-mac-croatian', // langCroatian | ||
24: 'x-mac-ce', // langLithuanian | ||
25: 'x-mac-ce', // langPolish | ||
26: 'x-mac-ce', // langHungarian | ||
27: 'x-mac-ce', // langEstonian | ||
28: 'x-mac-ce', // langLatvian | ||
30: 'x-mac-icelandic', // langFaroese | ||
37: 'x-mac-romanian', // langRomanian | ||
38: 'x-mac-ce', // langCzech | ||
39: 'x-mac-ce', // langSlovak | ||
40: 'x-mac-ce', // langSlovenian | ||
143: 'x-mac-inuit', // langInuktitut | ||
146: 'x-mac-gaelic' // langIrishGaelicScript | ||
}; | ||
function getEncoding(platformID, encodingID, languageID) { | ||
switch (platformID) { | ||
case 0: // Unicode | ||
return utf16; | ||
case 1: // Apple Macintosh | ||
return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; | ||
case 3: // Microsoft Windows | ||
if (encodingID === 1 || encodingID === 10) { | ||
return utf16; | ||
} | ||
break; | ||
} | ||
return undefined; | ||
} | ||
// Parse the naming `name` table. | ||
// FIXME: Format 1 additional fields are not supported yet. | ||
// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. | ||
function parseNameTable(data, start, ltag) { | ||
var name = {}; | ||
var p = new parse.Parser(data, start); | ||
name.format = p.parseUShort(); | ||
var format = p.parseUShort(); | ||
var count = p.parseUShort(); | ||
var stringOffset = p.offset + p.parseUShort(); | ||
var unknownCount = 0; | ||
for (var i = 0; i < count; i++) { | ||
@@ -54,29 +646,30 @@ var platformID = p.parseUShort(); | ||
var nameID = p.parseUShort(); | ||
var property = nameTableNames[nameID]; | ||
var property = nameTableNames[nameID] || nameID; | ||
var byteLength = p.parseUShort(); | ||
var offset = p.parseUShort(); | ||
// platformID - encodingID - languageID standard combinations : | ||
// 1 - 0 - 0 : Macintosh, Roman, English | ||
// 3 - 1 - 0x409 : Windows, Unicode BMP (UCS-2), en-US | ||
if (platformID === 3 && encodingID === 1 && languageID === 0x409) { | ||
var codePoints = []; | ||
var length = byteLength / 2; | ||
for (var j = 0; j < length; j++, offset += 2) { | ||
codePoints[j] = parse.getShort(data, stringOffset + offset); | ||
var language = getLanguageCode(platformID, languageID, ltag); | ||
var encoding = getEncoding(platformID, encodingID, languageID); | ||
if (encoding !== undefined && language !== undefined) { | ||
var text; | ||
if (encoding === utf16) { | ||
text = decode.UTF16(data, stringOffset + offset, byteLength); | ||
} else { | ||
text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); | ||
} | ||
var str = String.fromCharCode.apply(null, codePoints); | ||
if (property) { | ||
name[property] = str; | ||
if (text) { | ||
var translations = name[property]; | ||
if (translations === undefined) { | ||
translations = name[property] = {}; | ||
} | ||
translations[language] = text; | ||
} | ||
else { | ||
unknownCount++; | ||
name['unknown' + unknownCount] = str; | ||
} | ||
} | ||
} | ||
if (name.format === 1) { | ||
name.langTagCount = p.parseUShort(); | ||
var langTagCount = 0; | ||
if (format === 1) { | ||
// FIXME: Also handle Microsoft's 'name' table 1. | ||
langTagCount = p.parseUShort(); | ||
} | ||
@@ -87,2 +680,13 @@ | ||
// {23: 'foo'} → {'foo': 23} | ||
// ['bar', 'baz'] → {'bar': 0, 'baz': 1} | ||
function reverseDict(dict) { | ||
var result = {}; | ||
for (var key in dict) { | ||
result[dict[key]] = parseInt(key); | ||
} | ||
return result; | ||
} | ||
function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { | ||
@@ -99,56 +703,129 @@ return new table.Table('NameRecord', [ | ||
function addMacintoshNameRecord(t, recordID, s, offset) { | ||
// Macintosh, Roman, English | ||
var stringBytes = encode.STRING(s); | ||
t.records.push(makeNameRecord(1, 0, 0, recordID, stringBytes.length, offset)); | ||
t.strings.push(stringBytes); | ||
offset += stringBytes.length; | ||
return offset; | ||
// Finds the position of needle in haystack, or -1 if not there. | ||
// Like String.indexOf(), but for arrays. | ||
function findSubArray(needle, haystack) { | ||
var needleLength = needle.length; | ||
var limit = haystack.length - needleLength + 1; | ||
loop: | ||
for (var pos = 0; pos < limit; pos++) { | ||
for (; pos < limit; pos++) { | ||
for (var k = 0; k < needleLength; k++) { | ||
if (haystack[pos + k] !== needle[k]) { | ||
continue loop; | ||
} | ||
} | ||
return pos; | ||
} | ||
} | ||
return -1; | ||
} | ||
function addWindowsNameRecord(t, recordID, s, offset) { | ||
// Windows, Unicode BMP (UCS-2), US English | ||
var utf16Bytes = encode.UTF16(s); | ||
t.records.push(makeNameRecord(3, 1, 0x0409, recordID, utf16Bytes.length, offset)); | ||
t.strings.push(utf16Bytes); | ||
offset += utf16Bytes.length; | ||
function addStringToPool(s, pool) { | ||
var offset = findSubArray(s, pool); | ||
if (offset < 0) { | ||
offset = pool.length; | ||
for (var i = 0, len = s.length; i < len; ++i) { | ||
pool.push(s[i]); | ||
} | ||
} | ||
return offset; | ||
} | ||
function makeNameTable(options) { | ||
var t = new table.Table('name', [ | ||
{name: 'format', type: 'USHORT', value: 0}, | ||
{name: 'count', type: 'USHORT', value: 0}, | ||
{name: 'stringOffset', type: 'USHORT', value: 0} | ||
]); | ||
t.records = []; | ||
t.strings = []; | ||
var offset = 0; | ||
var i; | ||
var s; | ||
// Add Macintosh records first | ||
for (i = 0; i < nameTableNames.length; i += 1) { | ||
if (options[nameTableNames[i]] !== undefined) { | ||
s = options[nameTableNames[i]]; | ||
offset = addMacintoshNameRecord(t, i, s, offset); | ||
function makeNameTable(names, ltag) { | ||
var nameID; | ||
var nameIDs = []; | ||
var namesWithNumericKeys = {}; | ||
var nameTableIds = reverseDict(nameTableNames); | ||
for (var key in names) { | ||
var id = nameTableIds[key]; | ||
if (id === undefined) { | ||
id = key; | ||
} | ||
nameID = parseInt(id); | ||
namesWithNumericKeys[nameID] = names[key]; | ||
nameIDs.push(nameID); | ||
} | ||
// Then add Windows records | ||
for (i = 0; i < nameTableNames.length; i += 1) { | ||
if (options[nameTableNames[i]] !== undefined) { | ||
s = options[nameTableNames[i]]; | ||
offset = addWindowsNameRecord(t, i, s, offset); | ||
var macLanguageIds = reverseDict(macLanguages); | ||
var windowsLanguageIds = reverseDict(windowsLanguages); | ||
var nameRecords = []; | ||
var stringPool = []; | ||
for (var i = 0; i < nameIDs.length; i++) { | ||
nameID = nameIDs[i]; | ||
var translations = namesWithNumericKeys[nameID]; | ||
for (var lang in translations) { | ||
var text = translations[lang]; | ||
// For MacOS, we try to emit the name in the form that was introduced | ||
// in the initial version of the TrueType spec (in the late 1980s). | ||
// However, this can fail for various reasons: the requested BCP 47 | ||
// language code might not have an old-style Mac equivalent; | ||
// we might not have a codec for the needed character encoding; | ||
// or the name might contain characters that cannot be expressed | ||
// in the old-style Macintosh encoding. In case of failure, we emit | ||
// the name in a more modern fashion (Unicode encoding with BCP 47 | ||
// language tags) that is recognized by MacOS 10.5, released in 2009. | ||
// If fonts were only read by operating systems, we could simply | ||
// emit all names in the modern form; this would be much easier. | ||
// However, there are many applications and libraries that read | ||
// 'name' tables directly, and these will usually only recognize | ||
// the ancient form (silently skipping the unrecognized names). | ||
var macPlatform = 1; // Macintosh | ||
var macLanguage = macLanguageIds[lang]; | ||
var macScript = macLanguageToScript[macLanguage]; | ||
var macEncoding = getEncoding(macPlatform, macScript, macLanguage); | ||
var macName = encode.MACSTRING(text, macEncoding); | ||
if (macName === undefined) { | ||
macPlatform = 0; // Unicode | ||
macLanguage = ltag.indexOf(lang); | ||
if (macLanguage < 0) { | ||
macLanguage = ltag.length; | ||
ltag.push(lang); | ||
} | ||
macScript = 4; // Unicode 2.0 and later | ||
macName = encode.UTF16(text); | ||
} | ||
var macNameOffset = addStringToPool(macName, stringPool); | ||
nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, | ||
nameID, macName.length, macNameOffset)); | ||
var winLanguage = windowsLanguageIds[lang]; | ||
if (winLanguage !== undefined) { | ||
var winName = encode.UTF16(text); | ||
var winNameOffset = addStringToPool(winName, stringPool); | ||
nameRecords.push(makeNameRecord(3, 1, winLanguage, | ||
nameID, winName.length, winNameOffset)); | ||
} | ||
} | ||
} | ||
t.count = t.records.length; | ||
t.stringOffset = 6 + t.count * 12; | ||
for (i = 0; i < t.records.length; i += 1) { | ||
t.fields.push({name: 'record_' + i, type: 'TABLE', value: t.records[i]}); | ||
} | ||
nameRecords.sort(function(a, b) { | ||
return ((a.platformID - b.platformID) || | ||
(a.encodingID - b.encodingID) || | ||
(a.languageID - b.languageID) || | ||
(a.nameID - b.nameID)); | ||
}); | ||
for (i = 0; i < t.strings.length; i += 1) { | ||
t.fields.push({name: 'string_' + i, type: 'LITERAL', value: t.strings[i]}); | ||
var t = new table.Table('name', [ | ||
{name: 'format', type: 'USHORT', value: 0}, | ||
{name: 'count', type: 'USHORT', value: nameRecords.length}, | ||
{name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} | ||
]); | ||
for (var r = 0; r < nameRecords.length; r++) { | ||
t.fields.push({name: 'record_' + r, type: 'TABLE', value: nameRecords[r]}); | ||
} | ||
t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); | ||
return t; | ||
@@ -155,0 +832,0 @@ } |
@@ -17,2 +17,3 @@ // The `sfnt` wrapper provides organization for the tables in the font. | ||
var hmtx = require('./hmtx'); | ||
var ltag = require('./ltag'); | ||
var maxp = require('./maxp'); | ||
@@ -243,34 +244,50 @@ var _name = require('./name'); | ||
var fullName = font.familyName + ' ' + font.styleName; | ||
var postScriptName = font.familyName.replace(/\s/g, '') + '-' + font.styleName; | ||
var nameTable = _name.make({ | ||
copyright: font.copyright, | ||
fontFamily: font.familyName, | ||
fontSubfamily: font.styleName, | ||
uniqueID: font.manufacturer + ':' + fullName, | ||
fullName: fullName, | ||
version: font.version, | ||
postScriptName: postScriptName, | ||
trademark: font.trademark, | ||
manufacturer: font.manufacturer, | ||
designer: font.designer, | ||
description: font.description, | ||
manufacturerURL: font.manufacturerURL, | ||
designerURL: font.designerURL, | ||
license: font.license, | ||
licenseURL: font.licenseURL, | ||
preferredFamily: font.familyName, | ||
preferredSubfamily: font.styleName | ||
}); | ||
var englishFamilyName = font.getEnglishName('fontFamily'); | ||
var englishStyleName = font.getEnglishName('fontSubfamily'); | ||
var englishFullName = englishFamilyName + ' ' + englishStyleName; | ||
var postScriptName = font.getEnglishName('postScriptName'); | ||
if (!postScriptName) { | ||
postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; | ||
} | ||
var names = {}; | ||
for (var n in font.names) { | ||
names[n] = font.names[n]; | ||
} | ||
if (!names.uniqueID) { | ||
names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; | ||
} | ||
if (!names.postScriptName) { | ||
names.postScriptName = {en: postScriptName}; | ||
} | ||
if (!names.preferredFamily) { | ||
names.preferredFamily = font.names.fontFamily; | ||
} | ||
if (!names.preferredSubfamily) { | ||
names.preferredSubfamily = font.names.fontSubfamily; | ||
} | ||
var languageTags = []; | ||
var nameTable = _name.make(names, languageTags); | ||
var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); | ||
var postTable = post.make(); | ||
var cffTable = cff.make(font.glyphs, { | ||
version: font.version, | ||
fullName: fullName, | ||
familyName: font.familyName, | ||
weightName: font.styleName, | ||
version: font.getEnglishName('version'), | ||
fullName: englishFullName, | ||
familyName: englishFamilyName, | ||
weightName: englishStyleName, | ||
postScriptName: postScriptName, | ||
unitsPerEm: font.unitsPerEm | ||
}); | ||
// Order the tables according to the the OpenType specification 1.4. | ||
// The order does not matter because makeSfntTable() will sort them. | ||
var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; | ||
if (ltagTable) { | ||
tables.push(ltagTable); | ||
} | ||
@@ -277,0 +294,0 @@ var sfntTable = makeSfntTable(tables); |
219
src/types.js
@@ -39,3 +39,3 @@ // Data types used in the OpenType font file. | ||
sizeOf.BYTE = constant(1); | ||
sizeOf.CHAR = constant(1); | ||
@@ -169,5 +169,5 @@ // Convert an ASCII string to a list of bytes. | ||
sizeOf.NUMBER16 = constant(2); | ||
sizeOf.NUMBER16 = constant(3); | ||
// Convert a signed number between -(2^31) and +(2^31-1) to a four-byte value. | ||
// Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. | ||
// This is useful if you want to be sure you always use four bytes, | ||
@@ -179,3 +179,3 @@ // at the expense of wasting a few bytes for smaller numbers. | ||
sizeOf.NUMBER32 = constant(4); | ||
sizeOf.NUMBER32 = constant(5); | ||
@@ -228,8 +228,19 @@ encode.REAL = function(v) { | ||
// Convert a ASCII string to a list of UTF16 bytes. | ||
decode.UTF16 = function(data, offset, numBytes) { | ||
var codePoints = []; | ||
var numChars = numBytes / 2; | ||
for (var j = 0; j < numChars; j++, offset += 2) { | ||
codePoints[j] = data.getUint16(offset); | ||
} | ||
return String.fromCharCode.apply(null, codePoints); | ||
}; | ||
// Convert a JavaScript string to UTF16-BE. | ||
encode.UTF16 = function(v) { | ||
var b = []; | ||
for (var i = 0; i < v.length; i += 1) { | ||
b.push(0); | ||
b.push(v.charCodeAt(i)); | ||
var codepoint = v.charCodeAt(i); | ||
b.push((codepoint >> 8) & 0xFF); | ||
b.push(codepoint & 0xFF); | ||
} | ||
@@ -244,2 +255,163 @@ | ||
// Data for converting old eight-bit Macintosh encodings to Unicode. | ||
// This representation is optimized for decoding; encoding is slower | ||
// and needs more memory. The assumption is that all opentype.js users | ||
// want to open fonts, but saving a font will be comperatively rare | ||
// so it can be more expensive. Keyed by IANA character set name. | ||
// | ||
// Python script for generating these strings: | ||
// | ||
// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) | ||
// print(s.encode('utf-8')) | ||
var eightBitMacEncodings = { | ||
'x-mac-croatian': // Python: 'mac_croatian' | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + | ||
'¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', | ||
'x-mac-cyrillic': // Python: 'mac_cyrillic' | ||
'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + | ||
'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', | ||
'x-mac-gaelic': | ||
// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + | ||
'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', | ||
'x-mac-greek': // Python: 'mac_greek' | ||
'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + | ||
'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', | ||
'x-mac-icelandic': // Python: 'mac_iceland' | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + | ||
'¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', | ||
'x-mac-inuit': | ||
// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT | ||
'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + | ||
'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', | ||
'x-mac-ce': // Python: 'mac_latin2' | ||
'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + | ||
'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', | ||
macintosh: // Python: 'mac_roman' | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + | ||
'¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', | ||
'x-mac-romanian': // Python: 'mac_romanian' | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + | ||
'¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', | ||
'x-mac-turkish': // Python: 'mac_turkish' | ||
'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + | ||
'¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' | ||
}; | ||
// Decodes an old-style Macintosh string. Returns either a Unicode JavaScript | ||
// string, or 'undefined' if the encoding is unsupported. For example, we do | ||
// not support Chinese, Japanese or Korean because these would need large | ||
// mapping tables. | ||
decode.MACSTRING = function(dataView, offset, dataLength, encoding) { | ||
var table = eightBitMacEncodings[encoding]; | ||
if (table === undefined) { | ||
return undefined; | ||
} | ||
var result = ''; | ||
for (var i = 0; i < dataLength; i++) { | ||
var c = dataView.getUint8(offset + i); | ||
// In all eight-bit Mac encodings, the characters 0x00..0x7F are | ||
// mapped to U+0000..U+007F; we only need to look up the others. | ||
if (c <= 0x7F) { | ||
result += String.fromCharCode(c); | ||
} else { | ||
result += table[c & 0x7F]; | ||
} | ||
} | ||
return result; | ||
}; | ||
// Helper function for encode.MACSTRING. Returns a dictionary for mapping | ||
// Unicode character codes to their 8-bit MacOS equivalent. This table | ||
// is not exactly a super cheap data structure, but we do not care because | ||
// encoding Macintosh strings is only rarely needed in typical applications. | ||
var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); | ||
var macEncodingCacheKeys; | ||
var getMacEncodingTable = function(encoding) { | ||
// Since we use encoding as a cache key for WeakMap, it has to be | ||
// a String object and not a literal. And at least on NodeJS 2.10.1, | ||
// WeakMap requires that the same String instance is passed for cache hits. | ||
if (!macEncodingCacheKeys) { | ||
macEncodingCacheKeys = {}; | ||
for (var e in eightBitMacEncodings) { | ||
/*jshint -W053 */ // Suppress "Do not use String as a constructor." | ||
macEncodingCacheKeys[e] = new String(e); | ||
} | ||
} | ||
var cacheKey = macEncodingCacheKeys[encoding]; | ||
if (cacheKey === undefined) { | ||
return undefined; | ||
} | ||
// We can't do "if (cache.has(key)) {return cache.get(key)}" here: | ||
// since garbage collection may run at any time, it could also kick in | ||
// between the calls to cache.has() and cache.get(). In that case, | ||
// we would return 'undefined' even though we do support the encoding. | ||
if (macEncodingTableCache) { | ||
var cachedTable = macEncodingTableCache.get(cacheKey); | ||
if (cachedTable !== undefined) { | ||
return cachedTable; | ||
} | ||
} | ||
var decodingTable = eightBitMacEncodings[encoding]; | ||
if (decodingTable === undefined) { | ||
return undefined; | ||
} | ||
var encodingTable = {}; | ||
for (var i = 0; i < decodingTable.length; i++) { | ||
encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; | ||
} | ||
if (macEncodingTableCache) { | ||
macEncodingTableCache.set(cacheKey, encodingTable); | ||
} | ||
return encodingTable; | ||
}; | ||
// Encodes an old-style Macintosh string. Returns a byte array upon success. | ||
// If the requested encoding is unsupported, or if the input string contains | ||
// a character that cannot be expressed in the encoding, the function returns | ||
// 'undefined'. | ||
encode.MACSTRING = function(str, encoding) { | ||
var table = getMacEncodingTable(encoding); | ||
if (table === undefined) { | ||
return undefined; | ||
} | ||
var result = []; | ||
for (var i = 0; i < str.length; i++) { | ||
var c = str.charCodeAt(i); | ||
// In all eight-bit Mac encodings, the characters 0x00..0x7F are | ||
// mapped to U+0000..U+007F; we only need to look up the others. | ||
if (c >= 0x80) { | ||
c = table[c]; | ||
if (c === undefined) { | ||
// str contains a Unicode character that cannot be encoded | ||
// in the requested encoding. | ||
return undefined; | ||
} | ||
} | ||
result.push(c); | ||
} | ||
return result; | ||
}; | ||
sizeOf.MACSTRING = function(str, encoding) { | ||
var b = encode.MACSTRING(str, encoding); | ||
if (b !== undefined) { | ||
return b.length; | ||
} else { | ||
return 0; | ||
} | ||
}; | ||
// Convert a list of values to a CFF INDEX structure. | ||
@@ -354,4 +526,8 @@ // The values should be objects containing name / type / value. | ||
encode.CHARSTRING = function(ops) { | ||
if (wmm && wmm.has(ops)) { | ||
return wmm.get(ops); | ||
// See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". | ||
if (wmm) { | ||
var cachedValue = wmm.get(ops); | ||
if (cachedValue !== undefined) { | ||
return cachedValue; | ||
} | ||
} | ||
@@ -387,2 +563,8 @@ | ||
sizeOf.OBJECT = function(v) { | ||
var sizeOfFunction = sizeOf[v.type]; | ||
check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); | ||
return sizeOfFunction(v.value); | ||
}; | ||
// Convert a table object to bytes. | ||
@@ -411,2 +593,21 @@ // A table contains a list of fields containing the metadata (name, type and default value). | ||
sizeOf.TABLE = function(table) { | ||
var numBytes = 0; | ||
var length = table.fields.length; | ||
for (var i = 0; i < length; i += 1) { | ||
var field = table.fields[i]; | ||
var sizeOfFunction = sizeOf[field.type]; | ||
check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type); | ||
var value = table[field.name]; | ||
if (value === undefined) { | ||
value = field.value; | ||
} | ||
numBytes += sizeOfFunction(value); | ||
} | ||
return numBytes; | ||
}; | ||
// Merge in a list of bytes. | ||
@@ -413,0 +614,0 @@ encode.LITERAL = function(v) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
568668
61
10974
253
9
12