Skip to content

Commit 3ce8902

Browse files
mattlagLmpessoaConnum
authored
Fixed metadata that enables fonts to be recognised as a family (tested!) (#630)
* Fixed metadata that enables fonts to be recognised as a family --------- Co-authored-by: Leonardo Pessoa <[email protected]> Co-authored-by: Constantin Groß <[email protected]>
1 parent 96d133f commit 3ce8902

File tree

5 files changed

+127
-9
lines changed

5 files changed

+127
-9
lines changed

src/font.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ function createDefaultNamesInfo(options) {
5252
* @property {Number} ascender
5353
* @property {Number} descender
5454
* @property {Number} createdTimestamp
55-
* @property {string=} weightClass
55+
* @property {Number} weightClass
56+
* @property {Number} italicAngle
5657
* @property {string=} widthClass
5758
* @property {string=} fsSelection
5859
*/
@@ -87,11 +88,45 @@ function Font(options) {
8788
this.ascender = options.ascender;
8889
this.descender = options.descender;
8990
this.createdTimestamp = options.createdTimestamp;
91+
this.italicAngle = options.italicAngle || 0;
92+
this.weightClass = options.weightClass || 0;
93+
94+
let selection = 0;
95+
if (options.fsSelection) {
96+
selection = options.fsSelection;
97+
} else {
98+
if (this.italicAngle < 0) {
99+
selection |= this.fsSelectionValues.ITALIC;
100+
} else if (this.italicAngle > 0) {
101+
selection |= this.fsSelectionValues.OBLIQUE;
102+
}
103+
if (this.weightClass >= 600) {
104+
selection |= this.fsSelectionValues.BOLD;
105+
}
106+
if (selection == 0) {
107+
selection = this.fsSelectionValues.REGULAR;
108+
}
109+
}
110+
111+
if (!options.panose || !Array.isArray(options.panose)) {
112+
options.panose = [0, 0, 0, 0, 0, 0, 0, 0, 0];
113+
}
114+
90115
this.tables = Object.assign(options.tables, {
91116
os2: Object.assign({
92117
usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM,
93118
usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM,
94-
fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR,
119+
bFamilyType: options.panose[0] || 0,
120+
bSerifStyle: options.panose[1] || 0,
121+
bWeight: options.panose[2] || 0,
122+
bProportion: options.panose[3] || 0,
123+
bContrast: options.panose[4] || 0,
124+
bStrokeVariation: options.panose[5] || 0,
125+
bArmStyle: options.panose[6] || 0,
126+
bLetterform: options.panose[7] || 0,
127+
bMidline: options.panose[8] || 0,
128+
bXHeight: options.panose[9] || 0,
129+
fsSelection: selection,
95130
}, options.tables.os2)
96131
});
97132
}
@@ -562,6 +597,7 @@ Font.prototype.download = function(fileName) {
562597
fs.writeFileSync(fileName, buffer);
563598
}
564599
};
600+
565601
/**
566602
* @private
567603
*/
@@ -578,6 +614,19 @@ Font.prototype.fsSelectionValues = {
578614
OBLIQUE: 0x200 //512
579615
};
580616

617+
/**
618+
* @private
619+
*/
620+
Font.prototype.macStyleValues = {
621+
BOLD: 0x001, //1
622+
ITALIC: 0x002, //2
623+
UNDERLINE: 0x004, //4
624+
OUTLINED: 0x008, //8
625+
SHADOW: 0x010, //16
626+
CONDENSED: 0x020, //32
627+
EXTENDED: 0x040, //64
628+
};
629+
581630
/**
582631
* @private
583632
*/

src/tables/head.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function makeHeadTable(options) {
3434
// Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970
3535
const timestamp = Math.round(new Date().getTime() / 1000) + 2082844800;
3636
let createdTimestamp = timestamp;
37-
37+
let macStyle = options.macStyle || 0;
3838
if (options.createdTimestamp) {
3939
createdTimestamp = options.createdTimestamp + 2082844800;
4040
}
@@ -52,7 +52,7 @@ function makeHeadTable(options) {
5252
{name: 'yMin', type: 'SHORT', value: 0},
5353
{name: 'xMax', type: 'SHORT', value: 0},
5454
{name: 'yMax', type: 'SHORT', value: 0},
55-
{name: 'macStyle', type: 'USHORT', value: 0},
55+
{name: 'macStyle', type: 'USHORT', value: macStyle},
5656
{name: 'lowestRecPPEM', type: 'USHORT', value: 0},
5757
{name: 'fontDirectionHint', type: 'SHORT', value: 2},
5858
{name: 'indexToLocFormat', type: 'SHORT', value: 0},

src/tables/post.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ function parsePostTable(data, start) {
5050
return post;
5151
}
5252

53-
function makePostTable(postTable) {
53+
function makePostTable(font) {
5454
const {
55-
italicAngle = 0,
55+
italicAngle = Math.round((font.italicAngle || 0) * 0x10000),
5656
underlinePosition = 0,
5757
underlineThickness = 0,
5858
isFixedPitch = 0,
5959
minMemType42 = 0,
6060
maxMemType42 = 0,
6161
minMemType1 = 0,
6262
maxMemType1 = 0
63-
} = postTable || {};
63+
} = font.tables.post || {};
6464
return new table.Table('post', [
6565
{ name: 'version', type: 'FIXED', value: 0x00030000 },
6666
{ name: 'italicAngle', type: 'FIXED', value: italicAngle },

src/tables/sfnt.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ function fontToSfntTable(font) {
207207
globals.ascender = font.ascender;
208208
globals.descender = font.descender;
209209

210+
// macStyle bits must agree with the fsSelection bits
211+
let macStyle = 0;
212+
if (font.weightClass >= 600) {
213+
macStyle |= font.macStyleValues.BOLD;
214+
}
215+
if (font.italicAngle < 0) {
216+
macStyle |= font.macStyleValues.ITALIC;
217+
}
218+
210219
const headTable = head.make({
211220
flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0)
212221
unitsPerEm: font.unitsPerEm,
@@ -215,6 +224,7 @@ function fontToSfntTable(font) {
215224
xMax: globals.xMax,
216225
yMax: globals.yMax,
217226
lowestRecPPEM: 3,
227+
macStyle: macStyle,
218228
createdTimestamp: font.createdTimestamp
219229
});
220230

@@ -225,7 +235,7 @@ function fontToSfntTable(font) {
225235
minLeftSideBearing: globals.minLeftSideBearing,
226236
minRightSideBearing: globals.minRightSideBearing,
227237
xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin),
228-
numberOfHMetrics: font.glyphs.length
238+
numberOfHMetrics: font.glyphs.length,
229239
});
230240

231241
const maxpTable = maxp.make(font.glyphs.length);
@@ -325,7 +335,7 @@ function fontToSfntTable(font) {
325335
const nameTable = _name.make(names, languageTags);
326336
const ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined);
327337

328-
const postTable = post.make(font.tables.post);
338+
const postTable = post.make(font);
329339
const cffTable = cff.make(font.glyphs, {
330340
version: font.getEnglishName('version'),
331341
fullName: englishFullName,

test/font.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,65 @@ describe('font.js', function() {
4545
it('tables definition can override defaults values', function() {
4646
assert.equal(font.tables.os2.fsSelection, 42);
4747
});
48+
it('panose has default fallback', function() {
49+
assert.equal(font.tables.os2.bFamilyType, 0);
50+
assert.equal(font.tables.os2.bSerifStyle, 0);
51+
assert.equal(font.tables.os2.bWeight, 0);
52+
assert.equal(font.tables.os2.bProportion, 0);
53+
assert.equal(font.tables.os2.bContrast, 0);
54+
assert.equal(font.tables.os2.bStrokeVariation, 0);
55+
assert.equal(font.tables.os2.bArmStyle, 0);
56+
assert.equal(font.tables.os2.bLetterform, 0);
57+
assert.equal(font.tables.os2.bMidline, 0);
58+
assert.equal(font.tables.os2.bXHeight, 0);
59+
});
60+
it('panose values are set correctly', function () {
61+
let panoseFont = new Font({
62+
familyName: 'MyFont',
63+
styleName: 'Medium',
64+
unitsPerEm: 1000,
65+
ascender: 800,
66+
descender: 0,
67+
panose: [0,1,2,3,4,5,6,7,8,9],
68+
});
69+
assert.equal(panoseFont.tables.os2.bFamilyType, 0);
70+
assert.equal(panoseFont.tables.os2.bSerifStyle, 1);
71+
assert.equal(panoseFont.tables.os2.bWeight, 2);
72+
assert.equal(panoseFont.tables.os2.bProportion, 3);
73+
assert.equal(panoseFont.tables.os2.bContrast, 4);
74+
assert.equal(panoseFont.tables.os2.bStrokeVariation, 5);
75+
assert.equal(panoseFont.tables.os2.bArmStyle, 6);
76+
assert.equal(panoseFont.tables.os2.bLetterform, 7);
77+
assert.equal(panoseFont.tables.os2.bMidline, 8);
78+
assert.equal(panoseFont.tables.os2.bXHeight, 9);
79+
});
80+
it('fsSelection and macStyle are calcluated if no fsSelection value is provided', function() {
81+
let weightClassFont = new Font({
82+
familyName: 'MyFont',
83+
styleName: 'Medium',
84+
unitsPerEm: 1000,
85+
ascender: 800,
86+
descender: 0,
87+
weightClass: 600,
88+
fsSelection: false,
89+
});
90+
assert.equal(weightClassFont.tables.os2.fsSelection, 32);
91+
const weightClassHeadTable = weightClassFont.toTables().tables.find(table => table.tableName === 'head');
92+
assert.equal(weightClassHeadTable.macStyle, font.macStyleValues.BOLD);
93+
94+
let italicAngleFont = new Font({
95+
familyName: 'MyFont',
96+
styleName: 'Medium',
97+
unitsPerEm: 1000,
98+
ascender: 800,
99+
descender: 0,
100+
italicAngle: -13,
101+
fsSelection: false,
102+
});
103+
assert.equal(italicAngleFont.tables.os2.fsSelection, 1);
104+
const italicAngleHeadTable = italicAngleFont.toTables().tables.find(table => table.tableName === 'head');
105+
assert.equal(italicAngleHeadTable.macStyle, font.macStyleValues.ITALIC);
106+
});
48107
it('tables definition shall be serialized', function() {
49108
const os2 = font.toTables().tables.find(table => table.tableName === 'OS/2');
50109
assert.equal(os2.achVendID, 'TEST');

0 commit comments

Comments
 (0)