Skip to content

Commit 90bf641

Browse files
Connumyne
authored andcommitted
Add START gvar and avar table parsing
1 parent 234a535 commit 90bf641

File tree

14 files changed

+651
-52
lines changed

14 files changed

+651
-52
lines changed

src/opentype.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import cpal from './tables/cpal.js';
1414
import colr from './tables/colr.js';
1515
import cmap from './tables/cmap.js';
1616
import cff from './tables/cff.js';
17+
import stat from './tables/stat.js';
1718
import fvar from './tables/fvar.js';
19+
import gvar from './tables/gvar.js';
20+
import avar from './tables/avar.js';
1821
import glyf from './tables/glyf.js';
1922
import gdef from './tables/gdef.js';
2023
import gpos from './tables/gpos.js';
@@ -212,6 +215,9 @@ function parseBuffer(buffer, opt={}) {
212215

213216
let cffTableEntry;
214217
let fvarTableEntry;
218+
let statTableEntry;
219+
let gvarTableEntry;
220+
let avarTableEntry;
215221
let glyfTableEntry;
216222
let gdefTableEntry;
217223
let gposTableEntry;
@@ -227,6 +233,9 @@ function parseBuffer(buffer, opt={}) {
227233
const tableEntry = tableEntries[i];
228234
let table;
229235
switch (tableEntry.tag) {
236+
case 'avar':
237+
avarTableEntry = tableEntry;
238+
break;
230239
case 'cmap':
231240
table = uncompressTable(data, tableEntry);
232241
font.tables.cmap = cmap.parse(table.data, table.offset);
@@ -240,6 +249,12 @@ function parseBuffer(buffer, opt={}) {
240249
case 'fvar':
241250
fvarTableEntry = tableEntry;
242251
break;
252+
case 'STAT':
253+
statTableEntry = tableEntry;
254+
break;
255+
case 'gvar':
256+
gvarTableEntry = tableEntry;
257+
break;
243258
case 'fpgm' :
244259
table = uncompressTable(data, tableEntry);
245260
p = new parse.Parser(table.data, table.offset);
@@ -371,6 +386,30 @@ function parseBuffer(buffer, opt={}) {
371386
font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names);
372387
}
373388

389+
if (statTableEntry) {
390+
const statTable = uncompressTable(data, statTableEntry);
391+
font.tables.stat = stat.parse(statTable.data, statTable.offset, font.tables.fvar);
392+
}
393+
394+
if (gvarTableEntry) {
395+
if (!fvarTableEntry) {
396+
console.warn('This font provides a gvar table, but no fvar table, which is required for variable fonts.');
397+
}
398+
if (!glyfTableEntry) {
399+
console.warn('This font provides a gvar table, but no glyf table. Glyph variation only works with TrueType outlines.');
400+
}
401+
const gvarTable = uncompressTable(data, gvarTableEntry);
402+
font.tables.gvar = gvar.parse(gvarTable.data, gvarTable.offset, font.names);
403+
}
404+
405+
if (avarTableEntry) {
406+
if (!fvarTableEntry) {
407+
console.warn('This font provides an avar table, but no fvar table, which is required for variable fonts.');
408+
}
409+
const avarTable = uncompressTable(data, avarTableEntry);
410+
font.tables.avar = avar.parse(avarTable.data, avarTable.offset, font.tables.fvar);
411+
}
412+
374413
if (metaTableEntry) {
375414
const metaTable = uncompressTable(data, metaTableEntry);
376415
font.tables.meta = meta.parse(metaTable.data, metaTable.offset);

src/parse.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort;
550550
Parser.uShortList = Parser.prototype.parseUShortList;
551551
Parser.uLong = Parser.offset32 = Parser.prototype.parseULong;
552552
Parser.uLongList = Parser.prototype.parseULongList;
553+
Parser.fixed = Parser.prototype.parseFixed;
553554
Parser.struct = Parser.prototype.parseStruct;
554555
Parser.coverage = Parser.prototype.parseCoverage;
555556
Parser.classDef = Parser.prototype.parseClassDef;

src/substitution.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import check from './check.js';
55
import Layout from './layout.js';
6+
import { arraysEqual } from './util.js';
67

78
/**
89
* @exports opentype.Substitution
@@ -15,16 +16,6 @@ function Substitution(font) {
1516
Layout.call(this, font, 'gsub');
1617
}
1718

18-
// Check if 2 arrays of primitives are equal.
19-
function arraysEqual(ar1, ar2) {
20-
const n = ar1.length;
21-
if (n !== ar2.length) { return false; }
22-
for (let i = 0; i < n; i++) {
23-
if (ar1[i] !== ar2[i]) { return false; }
24-
}
25-
return true;
26-
}
27-
2819
// Find the first subtable of a lookup table in a particular format.
2920
function getSubstFormat(lookupTable, format, defaultSubtable) {
3021
const subtables = lookupTable.subtables;

src/tables/avar.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// The `avar` table stores information on how to modify a variation along a variation axis
2+
// https://learn.microsoft.com/en-us/typography/opentype/spec/avar
3+
4+
import check from '../check.js';
5+
import { Parser } from '../parse.js';
6+
import table from '../table.js';
7+
8+
function makeAvarAxisValueMap(n, axisValueMap) {
9+
return new table.Record('axisValueMap_' + n, [
10+
{name: 'fromCoordinate_' + n, type: 'F2DOT14', value: axisValueMap.fromCoordinate},
11+
{name: 'toCoordinate_' + n, type: 'F2DOT14', value: axisValueMap.toCoordinate}
12+
]);
13+
}
14+
15+
function makeAvarSegmentMap(n, axis) {
16+
const returnTable = new table.Record('segmentMap_' + n, [
17+
{name: 'positionMapCount_' + n, type: 'USHORT', value: axis.axisValueMaps.length}
18+
]);
19+
20+
let axisValueMaps = [];
21+
for (let i = 0; i < axis.axisValueMaps.length; i++) {
22+
const valueMap = makeAvarAxisValueMap(`${n}_${i}`, axis.axisValueMaps[i]);
23+
axisValueMaps = axisValueMaps.concat(valueMap.fields);
24+
}
25+
26+
returnTable.fields = returnTable.fields.concat(axisValueMaps);
27+
28+
return returnTable;
29+
}
30+
31+
function makeAvarTable(avar, fvar) {
32+
check.argument(avar.axisSegmentMaps.length === fvar.axes.length, 'avar axis count must correspond to fvar axis count');
33+
34+
const result = new table.Table('avar', [
35+
{name: 'majorVersion', type: 'USHORT', value: 1},
36+
{name: 'minorVersion', type: 'USHORT', value: 0},
37+
{name: 'reserved', type: 'USHORT', value: 0},
38+
{name: 'axisCount', type: 'USHORT', value: avar.axisSegmentMaps.length},
39+
]);
40+
41+
for (let i = 0; i < avar.axisSegmentMaps.length; i++) {
42+
const axisRecord = makeAvarSegmentMap(i, avar.axisSegmentMaps[i]);
43+
result.fields = result.fields.concat(axisRecord.fields);
44+
}
45+
46+
return result;
47+
}
48+
49+
function parseAvarTable(data, start, fvar) {
50+
if (!start) {
51+
start = 0;
52+
}
53+
54+
const p = new Parser(data, start);
55+
const tableVersionMajor = p.parseUShort();
56+
const tableVersionMinor = p.parseUShort();
57+
58+
if (tableVersionMajor !== 1) {
59+
console.warn(`Unsupported avar table version ${tableVersionMajor}.${tableVersionMinor}`);
60+
}
61+
62+
p.skip('uShort', 1); // reserved
63+
const axisCount = p.parseUShort();
64+
65+
check.argument(axisCount === fvar.axes.length, 'avar axis count must correspond to fvar axis count');
66+
67+
const axisSegmentMaps = [];
68+
for (let i = 0; i < axisCount; i++) {
69+
const axisValueMaps = [];
70+
const positionMapCount = p.parseUShort();
71+
for (let j = 0; j < positionMapCount; j++) {
72+
const fromCoordinate = p.parseF2Dot14();
73+
const toCoordinate = p.parseF2Dot14();
74+
axisValueMaps.push({
75+
fromCoordinate,
76+
toCoordinate
77+
});
78+
}
79+
axisSegmentMaps.push({
80+
axisValueMaps
81+
});
82+
}
83+
84+
return {
85+
version: [tableVersionMajor, tableVersionMinor],
86+
axisSegmentMaps
87+
};
88+
}
89+
90+
export default { make: makeAvarTable, parse: parseAvarTable };

src/tables/fvar.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@
44
import check from '../check.js';
55
import parse from '../parse.js';
66
import table from '../table.js';
7+
import { objectsEqual } from '../util.js';
78

89
function addName(name, names) {
9-
const nameString = JSON.stringify(name);
1010
let nameID = 256;
11-
for (let nameKey in names) {
12-
let n = parseInt(nameKey);
13-
if (!n || n < 256) {
14-
continue;
15-
}
16-
17-
if (JSON.stringify(names[nameKey]) === nameString) {
18-
return n;
19-
}
20-
21-
if (nameID <= n) {
22-
nameID = n + 1;
11+
for (let platform in names) {
12+
for (let nameKey in names[platform]) {
13+
let n = parseInt(nameKey);
14+
if (!n || n < 256) {
15+
continue;
16+
}
17+
18+
if (objectsEqual(names[platform][nameKey], name)) {
19+
return n;
20+
}
21+
22+
if (nameID <= n) {
23+
nameID = n + 1;
24+
}
2325
}
26+
names[platform][nameID] = name;
2427
}
2528

26-
names[nameID] = name;
2729
return nameID;
2830
}
2931

@@ -47,7 +49,7 @@ function parseFvarAxis(data, start, names) {
4749
axis.defaultValue = p.parseFixed();
4850
axis.maxValue = p.parseFixed();
4951
p.skip('uShort', 1); // reserved for flags; no values defined
50-
axis.name = names[p.parseUShort()] || {};
52+
axis.name = (names.macintosh || names.windows || names.unicode)[p.parseUShort()] || {};
5153
return axis;
5254
}
5355

@@ -73,7 +75,7 @@ function makeFvarInstance(n, inst, axes, names) {
7375
function parseFvarInstance(data, start, axes, names) {
7476
const inst = {};
7577
const p = new parse.Parser(data, start);
76-
inst.name = names[p.parseUShort()] || {};
78+
inst.name = (names.macintosh || names.windows || names.unicode)[p.parseUShort()] || {};
7779
p.skip('uShort', 1); // reserved for flags; no values defined
7880

7981
inst.coordinates = {};

src/tables/gvar.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// The `gvar` table stores information on how to modify glyf outlines across the variation space
2+
// https://learn.microsoft.com/en-us/typography/opentype/spec/gvar
3+
4+
// import check from '../check';
5+
// import parse from '../parse';
6+
// import table from '../table';
7+
8+
function makeGvarTable() {
9+
console.warn('Writing of gvar tables is not yet supported.');
10+
}
11+
12+
function parseGvarTable(/*data, start, names*/) {
13+
console.warn('Parsing of gvar tables is not yet supported.');
14+
}
15+
16+
export default { make: makeGvarTable, parse: parseGvarTable };

src/tables/sfnt.js

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import gsub from './gsub.js';
2121
import meta from './meta.js';
2222
import colr from './colr.js';
2323
import cpal from './cpal.js';
24+
import fvar from './fvar.js';
25+
import stat from './stat.js';
26+
import avar from './avar.js';
2427

2528
function log2(v) {
2629
return Math.log(v) / Math.log(2) | 0;
@@ -313,6 +316,9 @@ function fontToSfntTable(font) {
313316
names.windows.preferredSubfamily = fontNamesWindows.fontSubFamily || fontNamesUnicode.fontSubFamily || fontNamesMacintosh.fontSubFamily;
314317
}
315318

319+
// we have to handle fvar before name, because it may modify name IDs
320+
const fvarTable = font.tables.fvar ? fvar.make(font.tables.fvar, font.names) : undefined;
321+
316322
const languageTags = [];
317323
const nameTable = _name.make(names, languageTags);
318324
const ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined);
@@ -335,16 +341,32 @@ function fontToSfntTable(font) {
335341
if (ltagTable) {
336342
tables.push(ltagTable);
337343
}
344+
338345
// Optional tables
339-
if (font.tables.gsub) {
340-
tables.push(gsub.make(font.tables.gsub));
341-
}
342-
if (font.tables.cpal) {
343-
tables.push(cpal.make(font.tables.cpal));
346+
const optionalTables = {
347+
gsub,
348+
cpal,
349+
colr,
350+
stat,
351+
avar
352+
};
353+
354+
const optionalTableArgs = {
355+
avar: [font.tables.fvar]
356+
};
357+
358+
// fvar table is already handled above
359+
if (fvarTable) {
360+
tables.push(fvarTable);
344361
}
345-
if (font.tables.colr) {
346-
tables.push(colr.make(font.tables.colr));
362+
363+
for (let tableName in optionalTables) {
364+
const table = font.tables[tableName];
365+
if (table) {
366+
tables.push(optionalTables[tableName].make.call(font, table, ...(optionalTableArgs[tableName] || [])));
367+
}
347368
}
369+
348370
if (metaTable) {
349371
tables.push(metaTable);
350372
}

0 commit comments

Comments
 (0)