Skip to content

Commit

Permalink
Merge pull request opentypejs#235 from brawer/master
Browse files Browse the repository at this point in the history
Encode variation adjustment deltas
  • Loading branch information
fpirsch authored Jan 17, 2017
2 parents bce1539 + 599954e commit 7d2dcf1
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,122 @@ sizeOf.MACSTRING = function(str, encoding) {
}
};

// 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.
// The values should be objects containing name / type / value.
/**
Expand Down
60 changes: 60 additions & 0 deletions test/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,64 @@ describe('types.js', function() {
assert.equal(hex(encode.LITERAL([0xff, 0x23, 0xA7])), 'FF 23 A7');
assert.equal(sizeOf.LITERAL([0xff, 0x23, 0xA7]), 3);
});

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');
});
});

0 comments on commit 7d2dcf1

Please sign in to comment.