Skip to content

Instantly share code, notes, and snippets.

@tshinnic
Created February 7, 2015 00:01
Show Gist options
  • Save tshinnic/64f70567ed8d4cdc2af8 to your computer and use it in GitHub Desktop.
Save tshinnic/64f70567ed8d4cdc2af8 to your computer and use it in GitHub Desktop.
opentype.js 2-byte decoding and add operator/operand decode tracing
Combine 1- and 2 byte operator decoding, add trace
With the implementation of the flex operator variations 2-byte operators
are now "mainstreamed". Realign the operator code decoding so as to
have just one switch+case clause for all operators.
Additionally, add a debug trace while decoding operators and operands.
This has been useful elsewhere when hints were decoded badly, and useful
here recently to show that the flex operators were being exercised and
failing in a font file not tested before.
Dumping of operand decoding is provided for, but disabled. The operator
decoding will dump the stack as seen before the operator is executed, e.g.
. Glyph# 66
. on operator 29 callgsubr stack: (1) [-26]
. on operator 10 callsubr stack: (3) [17, -3, -5]
. on operator 11 return stack: (7) [17, -3, 14, 223, 35, 387, -20]
. on operator 11 return stack: (7) [17, -3, 14, 223, 35, 387, -20]
. on operator 1 hstem stack: (7) [17, -3, 14, 223, 35, 387, -20]
. on operator 3 vstem stack: (2) [-26, 689]
. on operator 29 callgsubr stack: (1) [-107]
. on operator 21 rmoveto stack: (2) [82, 91]
. on operator 8 rrcurveto stack: (6) [-17, -43, -17, -30, -40, -5]
The use of the 'dbg' variable to turn on debugging for just some glyphs is
clumsy and could be improved on.
These changes should be in two different patches.
diff --git a/src/tables/cff.js b/src/tables/cff.js
index 18d2cc9..7d1a087 100644
--- a/src/tables/cff.js
+++ b/src/tables/cff.js
@@ -327,17 +327,38 @@ function parseCFFEncoding(data, start, charset) {
return new encoding.CffEncoding(enc, charset);
}
+
+var CFFType2OperatorsByCode = {
+ 1: 'hstem', 3: 'vstem', 4: 'vmoveto', 5: 'rlineto',
+ 6: 'hlineto', 7: 'vlineto', 8: 'rrcurveto', 10: 'callsubr',
+ 11: 'return', 14: 'endchar', 18: 'hstemhm', 19: 'hintmask',
+ 20: 'cntrmask', 21: 'rmoveto', 22: 'hmoveto', 23: 'vstemhm',
+ 24: 'rcurveline', 25: 'rlinecurve', 26: 'vvcurveto', 27: 'hhcurveto',
+ 28: 'shortint', 29: 'callgsubr', 30: 'vhcurveto', 31: 'hvcurveto',
+ 1203: 'and', 1204: 'or', 1205: 'not', 1209: 'abs',
+ 1210: 'add', 1211: 'sub', 1212: 'div', 1214: 'neg',
+ 1215: 'eq', 1218: 'drop', 1220: 'put', 1221: 'get',
+ 1222: 'ifelse', 1223: 'random', 1224: 'mul', 1226: 'sqrt',
+ 1227: 'dup', 1228: 'exch', 1229: 'index', 1230: 'roll',
+ 1234: 'hflex', 1235: 'flex', 1236: 'hflex1', 1237: 'flex1',
+ 1299: '???'
+};
+
+
// Take in charstring code and return a Glyph object.
// The encoding is described in the Type 2 Charstring Format
// https://www.microsoft.com/typography/OTSPEC/charstr2.htm
function parseCFFCharstring(code, font, index) {
var p, glyph, stack, nStems, haveWidth, width, x, y, c1x, c1y, c2x, c2y, v;
+ var jpx, jpy, c3x, c3y, c4x, c4y, fd;
p = new path.Path();
stack = [];
nStems = 0;
haveWidth = false;
width = font.defaultWidthX;
x = y = 0;
+ var dbg = (index === 66 || index === 67);
+ if ( dbg ) console.log(' Glyph# %d', index);
function parseStems() {
var hasWidthArg;
@@ -358,6 +379,23 @@ function parseCFFCharstring(code, font, index) {
while (i < code.length) {
v = code[i];
i += 1;
+ if (v === 12) {
+ if (i < code.length) {
+ v = 1200 + code[i];
+ i += 1;
+ } else
+ v = 1299;
+ }
+ if ( dbg ) {
+ if ( v in CFFType2OperatorsByCode ) {
+ console.log(' on operator %d %s stack: (%d) ',
+ v, CFFType2OperatorsByCode[v], stack.length, stack);
+ } else {
+ //console.log(' on operand %d stack: (%d) ',
+ // v, stack.length, stack);
+ }
+ }
+
switch (v) {
case 1: // hstem
parseStems();
@@ -422,89 +460,80 @@ function parseCFFCharstring(code, font, index) {
break;
case 11: // return
return;
- case 12: // flex operators
- v = code[i];
- i += 1;
- var jpx, jpy, c3x, c3y, c4x, c4y, fd;
- switch (v) {
- case 35: // flex
- // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y + stack.shift(); // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = jpy + stack.shift(); // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
- x = c4x + stack.shift(); // dx6
- y = c4y + stack.shift(); // dy6
- fd = stack.shift(); // flex depth
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 34: // hflex
- // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
- c1x = x + stack.shift(); // dx1
- c1y = y; // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y; // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = c2y; // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = y; // dy5
- x = c4x + stack.shift(); // dx6
- // y = y; // dy6
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 36: // hflex1
- // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y; // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = c2y; // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
- x = c4x + stack.shift(); // dx6
- // y = y; // dy6
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- case 37: // flex1
- // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
- c1x = x + stack.shift(); // dx1
- c1y = y + stack.shift(); // dy1
- c2x = c1x + stack.shift(); // dx2
- c2y = c1y + stack.shift(); // dy2
- jpx = c2x + stack.shift(); // dx3
- jpy = c2y + stack.shift(); // dy3
- c3x = jpx + stack.shift(); // dx4
- c3y = jpy + stack.shift(); // dy4
- c4x = c3x + stack.shift(); // dx5
- c4y = c3y + stack.shift(); // dy5
-
- if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
- x = c4x + stack.shift(); // d6
- } else {
- y = c4y + stack.shift(); // d6
- }
- p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
- p.curveTo(c3x, c3y, c4x, c4y, x, y);
- break;
- default:
- console.log('Glyph ' + index + ': unknown operator ' + 1200 + v);
- stack.length = 0;
+ case 1235: // flex
+ // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |-
+ c1x = x + stack.shift(); // dx1
+ c1y = y + stack.shift(); // dy1
+ c2x = c1x + stack.shift(); // dx2
+ c2y = c1y + stack.shift(); // dy2
+ jpx = c2x + stack.shift(); // dx3
+ jpy = c2y + stack.shift(); // dy3
+ c3x = jpx + stack.shift(); // dx4
+ c3y = jpy + stack.shift(); // dy4
+ c4x = c3x + stack.shift(); // dx5
+ c4y = c3y + stack.shift(); // dy5
+ x = c4x + stack.shift(); // dx6
+ y = c4y + stack.shift(); // dy6
+ fd = stack.shift(); // flex depth
+ p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
+ p.curveTo(c3x, c3y, c4x, c4y, x, y);
+ break;
+ case 1234: // hflex
+ // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |-
+ c1x = x + stack.shift(); // dx1
+ c1y = y; // dy1
+ c2x = c1x + stack.shift(); // dx2
+ c2y = c1y + stack.shift(); // dy2
+ jpx = c2x + stack.shift(); // dx3
+ jpy = c2y; // dy3
+ c3x = jpx + stack.shift(); // dx4
+ c3y = c2y; // dy4
+ c4x = c3x + stack.shift(); // dx5
+ c4y = y; // dy5
+ x = c4x + stack.shift(); // dx6
+ // y = y; // dy6
+ p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
+ p.curveTo(c3x, c3y, c4x, c4y, x, y);
+ break;
+ case 1236: // hflex1
+ // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |-
+ c1x = x + stack.shift(); // dx1
+ c1y = y + stack.shift(); // dy1
+ c2x = c1x + stack.shift(); // dx2
+ c2y = c1y + stack.shift(); // dy2
+ jpx = c2x + stack.shift(); // dx3
+ jpy = c2y; // dy3
+ c3x = jpx + stack.shift(); // dx4
+ c3y = c2y; // dy4
+ c4x = c3x + stack.shift(); // dx5
+ c4y = c3y + stack.shift(); // dy5
+ x = c4x + stack.shift(); // dx6
+ // y = y; // dy6
+ p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
+ p.curveTo(c3x, c3y, c4x, c4y, x, y);
+ break;
+ case 1237: // flex1
+ // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |-
+ c1x = x + stack.shift(); // dx1
+ c1y = y + stack.shift(); // dy1
+ c2x = c1x + stack.shift(); // dx2
+ c2y = c1y + stack.shift(); // dy2
+ jpx = c2x + stack.shift(); // dx3
+ jpy = c2y + stack.shift(); // dy3
+ c3x = jpx + stack.shift(); // dx4
+ c3y = jpy + stack.shift(); // dy4
+ c4x = c3x + stack.shift(); // dx5
+ c4y = c3y + stack.shift(); // dy5
+
+ if (Math.abs(c4x - x) > Math.abs(c4y - y)) {
+ x = c4x + stack.shift(); // d6
+ } else {
+ y = c4y + stack.shift(); // d6
}
+ p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy);
+ p.curveTo(c3x, c3y, c4x, c4y, x, y);
break;
+
case 14: // endchar
if (stack.length > 0 && !haveWidth) {
width = stack.shift() + font.nominalWidthX;
@@ -654,6 +683,7 @@ function parseCFFCharstring(code, font, index) {
default:
if (v < 32) {
console.log('Glyph ' + index + ': unknown operator ' + v);
+ stack.length = 0;
} else if (v < 247) {
stack.push(v - 139);
} else if (v < 251) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment