Skip to content

Commit 328368d

Browse files
committed
WIP: CFF2 writing
1 parent 636d687 commit 328368d

File tree

3 files changed

+221
-169
lines changed

3 files changed

+221
-169
lines changed

src/glyph.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ function getPathDefinition(glyph, path) {
2121

2222
set: function(p) {
2323
_path = p;
24+
// remove the subrs/gsubrs
25+
// @TODO: In the future we'll need an algorithm that finds
26+
// candidates for sub routines and adds them to the index
27+
delete glyph.subrs;
28+
delete glyph.gsubrs;
2429
}
2530
};
2631
}

src/tables/cff.js

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ const TOP_DICT_META_CFF2 = [
390390
op: 1207,
391391
type: ['real', 'real', 'real', 'real', 'real', 'real'],
392392
// 1/unitsPerEm 0 0 1/unitsPerEm 0 0
393-
// value: [0.001, 0, 0, 0.001, 0, 0]
393+
value: [0.001, 0, 0, 0.001, 0, 0]
394394
},
395395
];
396396

@@ -645,6 +645,9 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
645645
let vsindex = 0;
646646
let vstore = [];
647647
let blendVector;
648+
const usedOps = [];
649+
const glyphSubrs= [];
650+
const glyphGSubrs= [];
648651

649652
const cffTable = font.tables.cff2 || font.tables.cff;
650653
defaultWidthX = cffTable.topDict._defaultWidthX;
@@ -704,7 +707,7 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
704707
haveWidth = true;
705708
}
706709

707-
function parse(code) {
710+
function parse(code, fromSubr = false) {
708711
let b1;
709712
let b2;
710713
let b3;
@@ -721,6 +724,7 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
721724
let i = 0;
722725
while (i < code.length) {
723726
let v = code[i];
727+
!fromSubr && v < 32 && usedOps.push(v);
724728
i += 1;
725729
switch (v) {
726730
case 1: // hstem
@@ -838,10 +842,12 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
838842
case 10: // callsubr
839843
console.log('callsubr');
840844
codeIndex = stack.pop() + subrsBias;
845+
glyphSubrs.push(codeIndex);
846+
glyphGSubrs.push(null);
841847
subrCode = subrs[codeIndex];
842848
console.log({subrsBias, codeIndex, subrCode});
843849
if (subrCode) {
844-
parse(subrCode);
850+
parse(subrCode, true);
845851
}
846852

847853
break;
@@ -1317,10 +1323,12 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
13171323
case 29: // callgsubr
13181324
console.log('callgsubr');
13191325
codeIndex = stack.pop() + font.gsubrsBias;
1326+
glyphSubrs.push(null);
1327+
glyphGSubrs.push(codeIndex);
13201328
subrCode = font.gsubrs[codeIndex];
13211329
console.log({gsubrBias: font.gsubrsBias, codeIndex, subrCode});
13221330
if (subrCode) {
1323-
parse(subrCode);
1331+
parse(subrCode, true);
13241332
}
13251333

13261334
break;
@@ -1464,6 +1472,15 @@ function parseCFFCharstring(font, glyph, code, version, coords) {
14641472
glyph.advanceWidth = width;
14651473
}
14661474

1475+
1476+
// glyph only consists of (global) subroutines
1477+
if(usedOps.filter(o => o === 10 || o === 29).length === glyphSubrs.filter(s => s!==null).length + glyphGSubrs.filter(s => s!==null).length) {
1478+
glyph.subrs = glyphSubrs;
1479+
glyph.gsubrs = glyphGSubrs;
1480+
console.log('#########');
1481+
console.log(glyph);
1482+
}
1483+
14671484
return p;
14681485
}
14691486

@@ -1778,11 +1795,40 @@ function makeCharsets(glyphNames, strings) {
17781795
return t;
17791796
}
17801797

1781-
function glyphToOps(glyph, version) {
1798+
function glyphToOps(glyph, version, font) {
17821799
// @TODO: write existing blend data if we already have a CFF2 font
17831800
// @TODO: if we have a gvar table, we'll need to convert its data to CFF2 blend data
17841801
const ops = [];
17851802
const path = glyph.path;
1803+
1804+
// @TODO: Right now we only make use of (global) sub routines if the whole glyph is made up of them
1805+
// and they are already defined on the glyph. In the future we'll need an algorithm that finds
1806+
// candidates for sub routines and extracts them from the glyphs, replacing the actual commands
1807+
if(glyph.subrs && glyph.gsubrs && glyph.subrs.length === glyph.gsubrs.length) {
1808+
const cffTable = font.tables[version < 2 ? 'cff' : 'cff2'];
1809+
const fdIndex = cffTable.topDict._fdSelect ? cffTable.topDict._fdSelect[glyph.index] : 0;
1810+
const fdDict = cffTable.topDict._fdArray[fdIndex];
1811+
for(let i = 0; i < glyph.subrs.length; i++) {
1812+
let v = glyph.subrs[i];
1813+
let name = 'subr';
1814+
let op = 10;
1815+
if(v === null) {
1816+
v = glyph.gsubrs[i];
1817+
name = 'gsubr';
1818+
op = 29;
1819+
if (v === null) {
1820+
throw Error(`Inconsistend subr/gsubr values on glyph ${glyph.index}`);
1821+
}
1822+
v -= font.gsubrsBias;
1823+
} else {
1824+
v -= fdDict._subrsBias;
1825+
}
1826+
ops.push({name: `${name}Index`, type: 'NUMBER', value: v});
1827+
ops.push({name, type: 'OP', value: op});
1828+
}
1829+
return ops;
1830+
}
1831+
17861832
if ( version < 2 ) {
17871833
ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth});
17881834
}
@@ -1882,7 +1928,7 @@ function makeCharStringsIndex(glyphs, version) {
18821928

18831929
for (let i = 0; i < glyphs.length; i += 1) {
18841930
const glyph = glyphs.get(i);
1885-
const ops = glyphToOps(glyph, version);
1931+
const ops = glyphToOps(glyph, version, glyphs.font);
18861932
t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops});
18871933
}
18881934

0 commit comments

Comments
 (0)