Socket
Socket
Sign inDemoInstall

opentype.js

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opentype.js - npm Package Compare versions

Comparing version 0.6.9 to 0.7.0

.github/ISSUE_TEMPLATE.md

5

bin/server.js

@@ -40,3 +40,6 @@ #!/usr/bin/env node

var contentType = CONTENT_TYPES[path.extname(filePath)] || 'text/plain';
res.writeHead(200, {'Content-Type': contentType});
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': 'max-age=0'
});
res.end(data);

@@ -43,0 +46,0 @@ }

2

bower.json
{
"name": "opentype.js",
"version": "0.6.9",
"version": "0.7.0",
"main": "dist/opentype.js",

@@ -5,0 +5,0 @@ "keywords": [

@@ -18,3 +18,3 @@ # Contributing

git clone git://github.com/nodebox/opentype.js.git
`git clone git://github.com/nodebox/opentype.js.git`

@@ -27,8 +27,8 @@ 3. Create a new branch for your feature. For example: `git checkout -b my-awesome-feature`.

cd opentype.js
npm install
`cd opentype.js`
`npm install`
5. Start the development server. This watches file changes and compiles and serves the page at http://localhost:8080/
npm start
`npm start`

@@ -42,7 +42,7 @@ Note that the compiled file ends up in the `build` folder, not the `dist` folder! The development server

npm test
`npm test`
8. Commit your changes
git add --all && git commit
`git add --all && git commit`

@@ -55,3 +55,3 @@ 9. Submit your pull request -- and thanks in advance!

npm run dist
`npm run dist`

@@ -58,0 +58,0 @@ This compiles the source and places `opentype.js` and `opentype.min.js` in the `dist` folder.

@@ -49,5 +49,6 @@ /**

* @param {string} s
* @param {Object=} options
* @return {opentype.Glyph[]}
*/
opentype.Font.prototype.stringToGlyphs = function(s) {};
opentype.Font.prototype.stringToGlyphs = function(s, options) {};

@@ -182,4 +183,5 @@ /**

* Initiate a download of the OpenType font.
* @param {string=} fileName
*/
opentype.Font.prototype.download = function() {};
opentype.Font.prototype.download = function(fileName) {};

@@ -203,2 +205,8 @@ // A Glyph is an individual mark that often corresponds to a character.

/**
* Calculate the minimum bounding box for this glyph.
* @return {opentype.BoundingBox}
*/
opentype.Glyph.prototype.getBoundingBox = function() {};
/**
* Convert the glyph to a Path we can draw on a drawing context.

@@ -330,3 +338,3 @@ * @param {number} [x=0] - Horizontal position of the beginning of the text.

* Add the given path or list of commands to the commands of this path.
* @param {Array}
* @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands.
*/

@@ -336,2 +344,8 @@ opentype.Path.prototype.extend = function(pathOrCommands) {};

/**
* Calculate the bounding box of the path.
* @returns {opentype.BoundingBox}
*/
opentype.Path.prototype.getBoundingBox = function() {};
/**
* @param {CanvasRenderingContext2D} ctx - A 2D drawing context.

@@ -354,2 +368,9 @@ */

/**
* Convert the path to a DOM element.
* @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
* @return {SVGPathElement}
*/
opentype.Path.prototype.toDOMElement = function(decimalPlaces) {};
/**
* @constructor

@@ -452,3 +473,3 @@ */

*/
Substitution.prototype.createDefaultTable = function() {};
opentype.Substitution.prototype.createDefaultTable = function() {};

@@ -591,2 +612,7 @@ /**

/**
* @constructor
*/
opentype.BoundingBox = function() {};
/**
* @param {string} url - The URL of the font to load.

@@ -593,0 +619,0 @@ * @param {Function} callback - The callback.

{
"name": "opentype.js",
"description": "OpenType font parser",
"version": "0.6.9",
"version": "0.7.0",
"author": {

@@ -6,0 +6,0 @@ "name": "Frederik De Bleser",

@@ -38,2 +38,3 @@ opentype.js

* Support for ligatures.
* Support for TrueType font hinting.
* Very efficient.

@@ -151,2 +152,3 @@ * Runs in the browser and node.js.

Currently only ligature features "liga" and "rlig" are supported (default: true).
* `hinting`: if true uses TrueType font hinting if available (default: false).

@@ -164,2 +166,5 @@ _Note: there is also `Font.getPaths` with the same arguments which returns a list of Paths._

* `kerning`: if true takes kerning information into account (default: true)
* `features`: an object with [OpenType feature tags](https://www.microsoft.com/typography/otspec/featuretags.htm) as keys, and a boolean value to enable each feature.
Currently only ligature features "liga" and "rlig" are supported (default: true).
* `hinting`: if true uses TrueType font hinting if available (default: false).

@@ -186,2 +191,14 @@ #### `Font.drawPoints(ctx, text, x, y, fontSize, options)`

#### `Font.getAdvanceWidth(text, fontSize, options)`
Returns the advance width of a text.
This is something different than Path.getBoundingBox() as for example a
suffixed whitespace increases the advancewidth but not the bounding box
or an overhanging letter like a calligraphic 'f' might have a quite larger
bounding box than its advance width.
This corresponds to canvas2dContext.measureText(text).width
* `fontSize`: Size of the text in pixels (default: 72).
* `options`: See Font.getPath
#### The Glyph object

@@ -188,0 +205,0 @@ A Glyph is an individual mark that often corresponds to a character. Some glyphs, such as ligatures, are a combination of many characters. Glyphs are the basic building blocks of a font.

@@ -0,1 +1,12 @@

0.7.0 (Apr 25, 2017)
====================
* Add font hinting (thanks @axkibe!)
* Add support for CID-keyed fonts, thanks to @tshinnic.
* TrueType fonts with signature 'true' or 'typ1' are also supported.
* Fixing rounding issues.
* Add GSUB and kern output in font-inspector.
* Add font loading error callback.
* Dev server turns browser caching off.
* Add encoding support for variation adjustment deltas (thanks @brawer!).
0.6.9 (Jan 17, 2017)

@@ -2,0 +13,0 @@ ====================

@@ -266,3 +266,7 @@ // Glyph encoding

if (font.cffEncoding) {
glyph.name = font.cffEncoding.charset[i];
if (font.isCIDFont) {
glyph.name = 'cid' + i;
} else {
glyph.name = font.cffEncoding.charset[i];
}
} else if (font.glyphNames.names) {

@@ -269,0 +273,0 @@ glyph.name = font.glyphNames.glyphIndexToName(i);

@@ -11,2 +11,3 @@ // The Font object

var util = require('./util');
var HintingTrueType = require('./hintingtt');

@@ -94,2 +95,11 @@ /**

this.tables = this.tables || {};
Object.defineProperty(this, 'hinting', {
get: function() {
if (this._hinting) return this._hinting;
if (this.outlinesFormat === 'truetype') {
return (this._hinting = new HintingTrueType(this));
}
}
});
}

@@ -275,3 +285,3 @@

var glyph = glyphs[i];
callback(glyph, x, y, fontSize, options);
callback.call(this, glyph, x, y, fontSize, options);
if (glyph.advanceWidth) {

@@ -292,2 +302,3 @@ x += glyph.advanceWidth * fontScale;

}
return x;
};

@@ -307,6 +318,5 @@

this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) {
var glyphPath = glyph.getPath(gX, gY, gFontSize);
var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this);
fullPath.extend(glyphPath);
});
return fullPath;

@@ -335,2 +345,21 @@ };

/**
* Returns the advance width of a text.
*
* This is something different than Path.getBoundingBox() as for example a
* suffixed whitespace increases the advancewidth but not the bounding box
* or an overhanging letter like a calligraphic 'f' might have a quite larger
* bounding box than its advance width.
*
* This corresponds to canvas2dContext.measureText(text).width
*
* @param {string} text - The text to create.
* @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`.
* @param {GlyphRenderOptions=} options
* @return advance width
*/
Font.prototype.getAdvanceWidth = function(text, fontSize, options) {
return this.forEachGlyph(text, 0, 0, fontSize, options, function() {});
};
/**
* Draw the text on the given drawing context.

@@ -337,0 +366,0 @@ * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas.

@@ -8,2 +8,3 @@ // The Glyph object

var path = require('./path');
var glyf = require('./tables/glyf');

@@ -122,15 +123,37 @@ function getPathDefinition(glyph, path) {

* @param {Object=} options - xScale, yScale to strech the glyph.
* @param {opentype.Font} if hinting is to be used, the font
* @return {opentype.Path}
*/
Glyph.prototype.getPath = function(x, y, fontSize, options) {
Glyph.prototype.getPath = function(x, y, fontSize, options, font) {
x = x !== undefined ? x : 0;
y = y !== undefined ? y : 0;
options = options !== undefined ? options : {xScale: 1.0, yScale: 1.0};
fontSize = fontSize !== undefined ? fontSize : 72;
var scale = 1 / this.path.unitsPerEm * fontSize;
var xScale = options.xScale * scale;
var yScale = options.yScale * scale;
var commands;
var hPoints;
if (!options) options = { };
var xScale = options.xScale;
var yScale = options.yScale;
if (options.hinting && font && font.hinting) {
// in case of hinting, the hinting engine takes care
// of scaling the points (not the path) before hinting.
hPoints = this.path && font.hinting.exec(this, fontSize);
// in case the hinting engine failed hPoints is undefined
// and thus reverts to plain rending
}
if (hPoints) {
commands = glyf.getPath(hPoints).commands;
x = Math.round(x);
y = Math.round(y);
// TODO in case of hinting xyScaling is not yet supported
xScale = yScale = 1;
} else {
commands = this.path.commands;
var scale = 1 / this.path.unitsPerEm * fontSize;
if (xScale === undefined) xScale = scale;
if (yScale === undefined) yScale = scale;
}
var p = new path.Path();
var commands = this.path.commands;
for (var i = 0; i < commands.length; i += 1) {

@@ -137,0 +160,0 @@ var cmd = commands[i];

@@ -78,2 +78,6 @@ // opentype.js

request.onerror = function () {
callback('Font could not be loaded');
};
request.send();

@@ -184,3 +188,3 @@ }

var signature = parse.getTag(data, 0);
if (signature === String.fromCharCode(0, 1, 0, 0)) {
if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') {
font.outlinesFormat = 'truetype';

@@ -219,2 +223,3 @@ numTables = parse.getUShort(data, 4);

var metaTableEntry;
var p;

@@ -230,5 +235,15 @@ for (var i = 0; i < numTables; i += 1) {

break;
case 'cvt ' :
table = uncompressTable(data, tableEntry);
p = new parse.Parser(table.data, table.offset);
font.tables.cvt = p.parseShortList(tableEntry.length / 2);
break;
case 'fvar':
fvarTableEntry = tableEntry;
break;
case 'fpgm' :
table = uncompressTable(data, tableEntry);
p = new parse.Parser(table.data, table.offset);
font.tables.fpgm = p.parseByteList(tableEntry.length);
break;
case 'head':

@@ -271,2 +286,7 @@ table = uncompressTable(data, tableEntry);

break;
case 'prep' :
table = uncompressTable(data, tableEntry);
p = new parse.Parser(table.data, table.offset);
font.tables.prep = p.parseByteList(tableEntry.length);
break;
case 'glyf':

@@ -273,0 +293,0 @@ glyfTableEntry = tableEntry;

@@ -200,3 +200,3 @@ // Parsing utility functions

// Parse a list of 16 bit integers. The length of the list can be read on the stream
// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream
// or provided as an argument.

@@ -218,2 +218,29 @@ Parser.prototype.parseOffset16List =

// Parses a list of 16 bit signed integers.
Parser.prototype.parseShortList = function(count) {
var list = new Array(count);
var dataView = this.data;
var offset = this.offset + this.relativeOffset;
for (var i = 0; i < count; i++) {
list[i] = dataView.getInt16(offset);
offset += 2;
}
this.relativeOffset += count * 2;
return list;
};
// Parses a list of bytes.
Parser.prototype.parseByteList = function(count) {
var list = new Array(count);
var dataView = this.data;
var offset = this.offset + this.relativeOffset;
for (var i = 0; i < count; i++) {
list[i] = dataView.getUint8(offset++);
}
this.relativeOffset += count;
return list;
};
/**

@@ -220,0 +247,0 @@ * Parse a list of items.

@@ -307,2 +307,7 @@ // Geometric objects

/**
* Convert the path to a DOM element.
* @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values
* @return {SVGPathElement}
*/
Path.prototype.toDOMElement = function(decimalPlaces) {

@@ -309,0 +314,0 @@ var temporaryPath = this.toPathData(decimalPlaces);

@@ -35,2 +35,17 @@ // The `CFF` table contains the glyph outlines in PostScript format.

// Subroutines are encoded using the negative half of the number space.
// See type 2 chapter 4.7 "Subroutine operators".
function calcCFFSubroutineBias(subrs) {
var bias;
if (subrs.length < 1240) {
bias = 107;
} else if (subrs.length < 33900) {
bias = 1131;
} else {
bias = 32768;
}
return bias;
}
// Parse a `CFF` INDEX array.

@@ -212,2 +227,3 @@ // An index array consists of a list of offsets, then a list of objects at those offsets.

var newDict = {};
var value;

@@ -218,12 +234,28 @@ // Because we also want to include missing values, we start out from the meta list

var m = meta[i];
var value = dict[m.op];
if (value === undefined) {
value = m.value !== undefined ? m.value : null;
}
if (m.type === 'SID') {
value = getCFFString(strings, value);
if (Array.isArray(m.type)) {
var values = [];
values.length = m.type.length;
for (var j = 0; j < m.type.length; j++) {
value = dict[m.op] !== undefined ? dict[m.op][j] : undefined;
if (value === undefined) {
value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null;
}
if (m.type[j] === 'SID') {
value = getCFFString(strings, value);
}
values[j] = value;
}
newDict[m.name] = values;
} else {
value = dict[m.op];
if (value === undefined) {
value = m.value !== undefined ? m.value : null;
}
if (m.type === 'SID') {
value = getCFFString(strings, value);
}
newDict[m.name] = value;
}
newDict[m.name] = value;
}

@@ -267,3 +299,12 @@

{name: 'charStrings', op: 17, type: 'offset', value: 0},
{name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}
{name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]},
{name: 'ros', op: 1230, type: ['SID', 'SID', 'number']},
{name: 'cidFontVersion', op: 1231, type: 'number', value: 0},
{name: 'cidFontRevision', op: 1232, type: 'number', value: 0},
{name: 'cidFontType', op: 1233, type: 'number', value: 0},
{name: 'cidCount', op: 1234, type: 'number', value: 8720},
{name: 'uidBase', op: 1235, type: 'number'},
{name: 'fdArray', op: 1236, type: 'offset'},
{name: 'fdSelect', op: 1237, type: 'offset'},
{name: 'fontName', op: 1238, type: 'SID'}
];

@@ -290,2 +331,43 @@

// Returns a list of "Top DICT"s found using an INDEX list.
// Used to read both the usual high-level Top DICTs and also the FDArray
// discovered inside CID-keyed fonts. When a Top DICT has a reference to
// a Private DICT that is read and saved into the Top DICT.
//
// In addition to the expected/optional values as outlined in TOP_DICT_META
// the following values might be saved into the Top DICT.
//
// _subrs [] array of local CFF subroutines from Private DICT
// _subrsBias bias value computed from number of subroutines
// (see calcCFFSubroutineBias() and parseCFFCharstring())
// _defaultWidthX default widths for CFF characters
// _nominalWidthX bias added to width embedded within glyph description
//
// _privateDict saved copy of parsed Private DICT from Top DICT
function gatherCFFTopDicts(data, start, cffIndex, strings) {
var topDictArray = [];
for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) {
var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer);
var topDict = parseCFFTopDict(topDictData, strings);
topDict._subrs = [];
topDict._subrsBias = 0;
var privateSize = topDict.private[0];
var privateOffset = topDict.private[1];
if (privateSize !== 0 && privateOffset !== 0) {
var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings);
topDict._defaultWidthX = privateDict.defaultWidthX;
topDict._nominalWidthX = privateDict.nominalWidthX;
if (privateDict.subrs !== 0) {
var subrOffset = privateOffset + privateDict.subrs;
var subrIndex = parseCFFIndex(data, subrOffset + start);
topDict._subrs = subrIndex.objects;
topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs);
}
topDict._privateDict = privateDict;
}
topDictArray.push(topDict);
}
return topDictArray;
}
// Parse the CFF charset table, which contains internal names for all the glyphs.

@@ -379,6 +461,23 @@ // This function will return a list of glyph names.

var haveWidth = false;
var width = font.defaultWidthX;
var open = false;
var x = 0;
var y = 0;
var subrs;
var subrsBias;
var defaultWidthX;
var nominalWidthX;
if (font.isCIDFont) {
var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index];
var fdDict = font.tables.cff.topDict._fdArray[fdIndex];
subrs = fdDict._subrs;
subrsBias = fdDict._subrsBias;
defaultWidthX = fdDict._defaultWidthX;
nominalWidthX = fdDict._nominalWidthX;
} else {
subrs = font.tables.cff.topDict._subrs;
subrsBias = font.tables.cff.topDict._subrsBias;
defaultWidthX = font.tables.cff.topDict._defaultWidthX;
nominalWidthX = font.tables.cff.topDict._nominalWidthX;
}
var width = defaultWidthX;

@@ -401,3 +500,3 @@ function newContour(x, y) {

if (hasWidthArg && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
width = stack.shift() + nominalWidthX;
}

@@ -437,3 +536,3 @@

if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
width = stack.shift() + nominalWidthX;
haveWidth = true;

@@ -492,4 +591,4 @@ }

case 10: // callsubr
codeIndex = stack.pop() + font.subrsBias;
subrCode = font.subrs[codeIndex];
codeIndex = stack.pop() + subrsBias;
subrCode = subrs[codeIndex];
if (subrCode) {

@@ -518,4 +617,4 @@ parse(subrCode);

c4y = c3y + stack.shift(); // dy5
x = c4x + stack.shift(); // dx6
y = c4y + stack.shift(); // dy6
x = c4x + stack.shift(); // dx6
y = c4y + stack.shift(); // dy6
stack.shift(); // flex depth

@@ -585,3 +684,3 @@ p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);

if (stack.length > 0 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
width = stack.shift() + nominalWidthX;
haveWidth = true;

@@ -606,3 +705,3 @@ }

if (stack.length > 2 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
width = stack.shift() + nominalWidthX;
haveWidth = true;

@@ -617,3 +716,3 @@ }

if (stack.length > 1 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
width = stack.shift() + nominalWidthX;
haveWidth = true;

@@ -781,15 +880,45 @@ }

// Subroutines are encoded using the negative half of the number space.
// See type 2 chapter 4.7 "Subroutine operators".
function calcCFFSubroutineBias(subrs) {
var bias;
if (subrs.length < 1240) {
bias = 107;
} else if (subrs.length < 33900) {
bias = 1131;
function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) {
var fdSelect = [];
var fdIndex;
var parser = new parse.Parser(data, start);
var format = parser.parseCard8();
if (format === 0) {
// Simple list of nGlyphs elements
for (var iGid = 0; iGid < nGlyphs; iGid++) {
fdIndex = parser.parseCard8();
if (fdIndex >= fdArrayCount) {
throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
}
fdSelect.push(fdIndex);
}
} else if (format === 3) {
// Ranges
var nRanges = parser.parseCard16();
var first = parser.parseCard16();
if (first !== 0) {
throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first);
}
var next;
for (var iRange = 0; iRange < nRanges; iRange++) {
fdIndex = parser.parseCard8();
next = parser.parseCard16();
if (fdIndex >= fdArrayCount) {
throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')');
}
if (next > nGlyphs) {
throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next);
}
for (; first < next; first++) {
fdSelect.push(fdIndex);
}
first = next;
}
if (next !== nGlyphs) {
throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next);
}
} else {
bias = 32768;
throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format);
}
return bias;
return fdSelect;
}

@@ -808,6 +937,33 @@

var topDictData = new DataView(new Uint8Array(topDictIndex.objects[0]).buffer);
var topDict = parseCFFTopDict(topDictData, stringIndex.objects);
var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects);
if (topDictArray.length !== 1) {
throw new Error('CFF table has too many fonts in \'FontSet\' - ' + 'count of fonts NameIndex.length = ' + topDictArray.length);
}
var topDict = topDictArray[0];
font.tables.cff.topDict = topDict;
if (topDict._privateDict) {
font.defaultWidthX = topDict._privateDict.defaultWidthX;
font.nominalWidthX = topDict._privateDict.nominalWidthX;
}
if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) {
font.isCIDFont = true;
}
if (font.isCIDFont) {
var fdArrayOffset = topDict.fdArray;
var fdSelectOffset = topDict.fdSelect;
if (fdArrayOffset === 0 || fdSelectOffset === 0) {
throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing');
}
fdArrayOffset += start;
var fdArrayIndex = parseCFFIndex(data, fdArrayOffset);
var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects);
topDict._fdArray = fdArray;
fdSelectOffset += start;
topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length);
}
var privateDictOffset = start + topDict['private'][1];

@@ -814,0 +970,0 @@ var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict['private'][0], stringIndex.objects);

@@ -47,4 +47,5 @@ // The `glyf` table describes the glyphs in TrueType outline format.

var flag;
var i;
if (glyph.numberOfContours > 0) {
var i;
// This glyph is not a composite.

@@ -172,2 +173,10 @@ var endPointIndices = glyph.endPointIndices = [];

}
if (flags & 0x100) {
// We have instructions
glyph.instructionLength = p.parseUShort();
glyph.instructions = [];
for (i = 0; i < glyph.instructionLength; i += 1) {
glyph.instructions.push(p.parseByte());
}
}
}

@@ -217,57 +226,45 @@ }

var contours = getContours(points);
for (var i = 0; i < contours.length; i += 1) {
var contour = contours[i];
var firstPt = contour[0];
var lastPt = contour[contour.length - 1];
var curvePt;
var realFirstPoint;
if (firstPt.onCurve) {
curvePt = null;
// The first point will be consumed by the moveTo command,
// so skip it in the loop.
realFirstPoint = true;
for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) {
var contour = contours[contourIndex];
var prev = null;
var curr = contour[contour.length - 1];
var next = contour[0];
if (curr.onCurve) {
p.moveTo(curr.x, curr.y);
} else {
if (lastPt.onCurve) {
// If the first point is off-curve and the last point is on-curve,
// start at the last point.
firstPt = lastPt;
if (next.onCurve) {
p.moveTo(next.x, next.y);
} else {
// If both first and last points are off-curve, start at their middle.
firstPt = { x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2 };
var start = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 };
p.moveTo(start.x, start.y);
}
curvePt = firstPt;
// The first point is synthesized, so don't skip the real first point.
realFirstPoint = false;
}
p.moveTo(firstPt.x, firstPt.y);
for (var i = 0; i < contour.length; ++i) {
prev = curr;
curr = next;
next = contour[(i + 1) % contour.length];
for (var j = realFirstPoint ? 1 : 0; j < contour.length; j += 1) {
var pt = contour[j];
var prevPt = j === 0 ? firstPt : contour[j - 1];
if (prevPt.onCurve && pt.onCurve) {
if (curr.onCurve) {
// This is a straight line.
p.lineTo(pt.x, pt.y);
} else if (prevPt.onCurve && !pt.onCurve) {
curvePt = pt;
} else if (!prevPt.onCurve && !pt.onCurve) {
var midPt = { x: (prevPt.x + pt.x) / 2, y: (prevPt.y + pt.y) / 2 };
p.quadraticCurveTo(prevPt.x, prevPt.y, midPt.x, midPt.y);
curvePt = pt;
} else if (!prevPt.onCurve && pt.onCurve) {
// Previous point off-curve, this point on-curve.
p.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y);
curvePt = null;
p.lineTo(curr.x, curr.y);
} else {
throw new Error('Invalid state.');
}
}
var prev2 = prev;
var next2 = next;
if (firstPt !== lastPt) {
// Connect the last and first points
if (curvePt) {
p.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y);
} else {
p.lineTo(firstPt.x, firstPt.y);
if (!prev.onCurve) {
prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 };
p.lineTo(prev2.x, prev2.y);
}
if (!next.onCurve) {
next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 };
}
p.lineTo(prev2.x, prev2.y);
p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y);
}

@@ -338,2 +335,4 @@ }

exports.getPath = getPath;
exports.parse = parseGlyfTable;

@@ -586,2 +586,118 @@ // Data types used in the OpenType font file.

// Helper for encode.VARDELTAS
function isByteEncodable(value) {
return value >= -128 && value <= 127;
}
// Helper for encode.VARDELTAS
function encodeVarDeltaRunAsZeroes(deltas, pos, result) {
var runLength = 0;
var numDeltas = deltas.length;
while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) {
++pos;
++runLength;
}
result.push(0x80 | (runLength - 1));
return pos;
}
// Helper for encode.VARDELTAS
function encodeVarDeltaRunAsBytes(deltas, offset, result) {
var runLength = 0;
var numDeltas = deltas.length;
var pos = offset;
while (pos < numDeltas && runLength < 64) {
var value = deltas[pos];
if (!isByteEncodable(value)) {
break;
}
// Within a byte-encoded run of deltas, a single zero is best
// stored literally as 0x00 value. However, if we have two or
// more zeroes in a sequence, it is better to start a new run.
// Fore example, the sequence of deltas [15, 15, 0, 15, 15]
// becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero
// within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F)
// when starting a new run.
if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) {
break;
}
++pos;
++runLength;
}
result.push(runLength - 1);
for (var i = offset; i < pos; ++i) {
result.push((deltas[i] + 256) & 0xff);
}
return pos;
}
// Helper for encode.VARDELTAS
function encodeVarDeltaRunAsWords(deltas, offset, result) {
var runLength = 0;
var numDeltas = deltas.length;
var pos = offset;
while (pos < numDeltas && runLength < 64) {
var value = deltas[pos];
// Within a word-encoded run of deltas, it is easiest to start
// a new run (with a different encoding) whenever we encounter
// a zero value. For example, the sequence [0x6666, 0, 0x7777]
// needs 7 bytes when storing the zero inside the current run
// (42 66 66 00 00 77 77), and equally 7 bytes when starting a
// new run (40 66 66 80 40 77 77).
if (value === 0) {
break;
}
// Within a word-encoded run of deltas, a single value in the
// range (-128..127) should be encoded within the current run
// because it is more compact. For example, the sequence
// [0x6666, 2, 0x7777] becomes 7 bytes when storing the value
// literally (42 66 66 00 02 77 77), but 8 bytes when starting
// a new run (40 66 66 00 02 40 77 77).
if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) {
break;
}
++pos;
++runLength;
}
result.push(0x40 | (runLength - 1));
for (var i = offset; i < pos; ++i) {
var val = deltas[i];
result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff);
}
return pos;
}
/**
* Encode a list of variation adjustment deltas.
*
* Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables.
* They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted
* when generating instances of variation fonts.
*
* @see https://www.microsoft.com/typography/otspec/gvar.htm
* @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
* @param {Array}
* @return {Array}
*/
encode.VARDELTAS = function(deltas) {
var pos = 0;
var result = [];
while (pos < deltas.length) {
var value = deltas[pos];
if (value === 0) {
pos = encodeVarDeltaRunAsZeroes(deltas, pos, result);
} else if (value >= -128 && value <= 127) {
pos = encodeVarDeltaRunAsBytes(deltas, pos, result);
} else {
pos = encodeVarDeltaRunAsWords(deltas, pos, result);
}
}
return result;
};
// Convert a list of values to a CFF INDEX structure.

@@ -588,0 +704,0 @@ // The values should be objects containing name / type / value.

@@ -32,2 +32,18 @@ 'use strict';

it('can load a CID-keyed font', function() {
var font = opentype.loadSync('./fonts/FDArrayTest257.otf');
assert.deepEqual(font.names.fontFamily, {en: 'FDArray Test 257'});
assert.deepEqual(font.tables.cff.topDict.ros, ['Adobe', 'Identity', 0]);
assert.equal(font.tables.cff.topDict._fdArray.length, 256);
assert.equal(font.tables.cff.topDict._fdSelect[0], 0);
assert.equal(font.tables.cff.topDict._fdSelect[42], 41);
assert.equal(font.tables.cff.topDict._fdSelect[256], 255);
assert.equal(font.unitsPerEm, 1000);
assert.equal(font.glyphs.length, 257);
var aGlyph = font.glyphs.get(2);
assert.equal(aGlyph.name, 'cid2');
assert.equal(aGlyph.unicode, 1);
assert.equal(aGlyph.path.commands.length, 24);
});
it('can load a WOFF/CFF font', function() {

@@ -34,0 +50,0 @@ var font = opentype.loadSync('./fonts/FiraSansMedium.woff');

@@ -548,2 +548,62 @@ 'use strict';

});
it('can encode VARDELTAS', function() {
var e = function(deltas) { return hex(encode.VARDELTAS(deltas)); };
assert.equal(e([]), '');
// zeroes
assert.equal(e([0]), '80');
assert.equal(e(new Array(64).fill(0)), 'BF');
assert.equal(e(new Array(65).fill(0)), 'BF 80');
assert.equal(e(new Array(100).fill(0)), 'BF A3');
assert.equal(e(new Array(256).fill(0)), 'BF BF BF BF');
// bytes
assert.equal(e([1]), '00 01');
assert.equal(e([1, 2, 3, 127, -128, -1, -2]), '06 01 02 03 7F 80 FF FE');
assert.equal(e(new Array(64).fill(127)),
'3F ' + (new Array(64).fill('7F')).join(' '));
assert.equal(e(new Array(65).fill(127)),
'3F ' + (new Array(64).fill('7F')).join(' ') + ' 00 7F');
// words
assert.equal(e([0x6666]), '40 66 66');
assert.equal(e([0x6666, 32767, -1, -32768]), '43 66 66 7F FF FF FF 80 00');
assert.equal(e(new Array(64).fill(0x1122)),
'7F ' + (new Array(64).fill('11 22')).join(' '));
assert.equal(e(new Array(65).fill(0x1122)),
'7F ' + (new Array(64).fill('11 22')).join(' ') + ' 40 11 22');
// bytes, zeroes
assert.equal(e([1, 0]), '01 01 00');
assert.equal(e([1, 0, 0]), '00 01 81');
// bytes, zeroes, bytes:
// a single zero is more compact when encoded within the bytes run
assert.equal(e([127, 127, 0, 127, 127]), '04 7F 7F 00 7F 7F');
// multiple zeroes are more compact when encoded into their own run
assert.equal(e([127, 127, 0, 0, 127, 127]), '01 7F 7F 81 01 7F 7F');
assert.equal(e([127, 127, 0, 0, 0, 127, 127]), '01 7F 7F 82 01 7F 7F');
assert.equal(e([127, 127, 0, 0, 0, 0, 127, 127]), '01 7F 7F 83 01 7F 7F');
// words, zeroes
assert.equal(e([0x6789, 0]), '40 67 89 80');
assert.equal(e([0x6666, 0, 0]), '40 66 66 81');
// words, zeroes, bytes
assert.equal(e([0x6666, 0, 1, 2, 3]), '40 66 66 80 02 01 02 03');
assert.equal(e([0x6666, 0, 0, 1, 2, 3]), '40 66 66 81 02 01 02 03');
assert.equal(e([0x6666, 0, 0, 0, 1, 2, 3]), '40 66 66 82 02 01 02 03');
// words, zeroes, words
assert.equal(e([0x6666, 0, 0x7777]), '40 66 66 80 40 77 77');
assert.equal(e([0x6666, 0, 0, 0x7777]), '40 66 66 81 40 77 77');
assert.equal(e([0x6666, 0, 0, 0, 0x7777]), '40 66 66 82 40 77 77');
// words, bytes, words:
// a single byte-encodable word is more compact when encoded within the words run
assert.equal(e([0x6666, 2, 0x7777]), '42 66 66 00 02 77 77');
// multiple byte-encodable words are more compated when forming their own run
assert.equal(e([0x6666, 2, 2, 0x7777]), '40 66 66 01 02 02 40 77 77');
});
});

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc