From 0428d1dee1cbe893994a728db793cc9503d440fd Mon Sep 17 00:00:00 2001 From: Leonardo Pessoa Date: Fri, 21 Jul 2023 11:43:43 -0300 Subject: [PATCH 01/12] Fixed metadata that enables fonts to be recognised as a family I've found out that a few fields that might be passed to opentype.js were not being used while producing the font. I have been able to pinpoint those fields and where they should be applied to produce fonts that are recognised as a family. Tested only with a couple of fonts but changes were regonised by Windows and other apps. An issue regarding the fields hhea.caretSlopeRise and hhea.caretSlopeRun for italic and oblique fonts is still pending review but does not prevent font usage or recognition as a family (caret slope had a weird inclination while testing on Word but not on Affinity Designer). These same changes were applied to the copy of the library used by Glyphr Studio (v2) where the tests were conducted. --- src/font.js | 28 +++++++++++++++++++++++++++- src/tables/head.js | 2 +- src/tables/post.js | 6 +++--- src/tables/sfnt.js | 13 +++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/font.js b/src/font.js index 6e02c189..e357239f 100644 --- a/src/font.js +++ b/src/font.js @@ -86,12 +86,38 @@ function Font(options) { this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; + this.slope = options.slope; + this.italicAngle = options.italicAngle; this.createdTimestamp = options.createdTimestamp; + + var selection = 0; + if (this.italicAngle < 0) { + selection |= this.fsSelectionValues.ITALIC; + } else if (this.italicAngle > 0) { + selection |= this.fsSelectionValues.OBLIQUE; + } + if (this.weightClass >= 600) { + selection |= this.fsSelectionValues.BOLD; + } + if (selection == 0) { + selection = this.fsSelectionValues.REGULAR; + } + this.tables = Object.assign(options.tables, { os2: Object.assign({ usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + bFamilyType: options.panose[0] || 0, + bSerifStyle: options.panose[1] || 0, + bWeight: options.panose[2] || 0, + bProportion: options.panose[3] || 0, + bContrast: options.panose[4] || 0, + bStrokeVariation: options.panose[5] || 0, + bArmStyle: options.panose[6] || 0, + bLetterform: options.panose[7] || 0, + bMidline: options.panose[8] || 0, + bXHeight: options.panose[9] || 0, + fsSelection: selection, }, options.tables.os2) }); } diff --git a/src/tables/head.js b/src/tables/head.js index d8ac530c..e29b9295 100644 --- a/src/tables/head.js +++ b/src/tables/head.js @@ -52,7 +52,7 @@ function makeHeadTable(options) { {name: 'yMin', type: 'SHORT', value: 0}, {name: 'xMax', type: 'SHORT', value: 0}, {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: options.macStyle}, {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, {name: 'fontDirectionHint', type: 'SHORT', value: 2}, {name: 'indexToLocFormat', type: 'SHORT', value: 0}, diff --git a/src/tables/post.js b/src/tables/post.js index c140e1e5..8e243fbb 100644 --- a/src/tables/post.js +++ b/src/tables/post.js @@ -50,9 +50,9 @@ function parsePostTable(data, start) { return post; } -function makePostTable(postTable) { +function makePostTable(font) { const { - italicAngle = 0, + italicAngle = Math.round((font.italicAngle || 0) * 0x10000), underlinePosition = 0, underlineThickness = 0, isFixedPitch = 0, @@ -60,7 +60,7 @@ function makePostTable(postTable) { maxMemType42 = 0, minMemType1 = 0, maxMemType1 = 0 - } = postTable || {}; + } = font.tables.post || {}; return new table.Table('post', [ { name: 'version', type: 'FIXED', value: 0x00030000 }, { name: 'italicAngle', type: 'FIXED', value: italicAngle }, diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 41171291..54e83bee 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -206,6 +206,13 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; + var macStyle = 0; + if (font.italicAngle < 0) { + macStyle |= 2; + } + if (font.weightClass >= 600) { + macStyle |= 1; + } const headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) unitsPerEm: font.unitsPerEm, @@ -214,6 +221,7 @@ function fontToSfntTable(font) { xMax: globals.xMax, yMax: globals.yMax, lowestRecPPEM: 3, + macStyle: macStyle, createdTimestamp: font.createdTimestamp }); @@ -224,7 +232,8 @@ function fontToSfntTable(font) { minLeftSideBearing: globals.minLeftSideBearing, minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length + numberOfHMetrics: font.glyphs.length, + slope: font.slope, }); const maxpTable = maxp.make(font.glyphs.length); @@ -323,7 +332,7 @@ function fontToSfntTable(font) { const nameTable = _name.make(names, languageTags); const ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - const postTable = post.make(font.tables.post); + const postTable = post.make(font); const cffTable = cff.make(font.glyphs, { version: font.getEnglishName('version'), fullName: englishFullName, From e324ce6b88a430489a83b9c75218213547708af7 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Wed, 4 Oct 2023 10:54:42 -0700 Subject: [PATCH 02/12] Providing options so tests pass --- src/font.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/font.js b/src/font.js index e357239f..0115b574 100644 --- a/src/font.js +++ b/src/font.js @@ -102,6 +102,12 @@ function Font(options) { if (selection == 0) { selection = this.fsSelectionValues.REGULAR; } + if (options.fsSelection) { + selection = options.fsSelection; + } + if (!options.panose || !Array.isArray(options.panose)) { + options.panose = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + } this.tables = Object.assign(options.tables, { os2: Object.assign({ From 1f1383affb8cf0fb9eecaf53b3cd31027d5e5335 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Wed, 4 Oct 2023 11:21:10 -0700 Subject: [PATCH 03/12] Update font.js --- src/font.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/font.js b/src/font.js index e357239f..0115b574 100644 --- a/src/font.js +++ b/src/font.js @@ -102,6 +102,12 @@ function Font(options) { if (selection == 0) { selection = this.fsSelectionValues.REGULAR; } + if (options.fsSelection) { + selection = options.fsSelection; + } + if (!options.panose || !Array.isArray(options.panose)) { + options.panose = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + } this.tables = Object.assign(options.tables, { os2: Object.assign({ From 5a320e552246e7d947ec730fdad63a580efd71d5 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Thu, 26 Oct 2023 14:02:09 -0700 Subject: [PATCH 04/12] let instead of var --- src/font.js | 4 ++-- src/tables/sfnt.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/font.js b/src/font.js index 0115b574..f955c69d 100644 --- a/src/font.js +++ b/src/font.js @@ -86,11 +86,11 @@ function Font(options) { this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; this.slope = options.slope; this.italicAngle = options.italicAngle; - this.createdTimestamp = options.createdTimestamp; - var selection = 0; + let selection = 0; if (this.italicAngle < 0) { selection |= this.fsSelectionValues.ITALIC; } else if (this.italicAngle > 0) { diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 9e7aac4c..1c2ccd6e 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -207,13 +207,14 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; - var macStyle = 0; + let macStyle = 0; if (font.italicAngle < 0) { macStyle |= 2; } if (font.weightClass >= 600) { macStyle |= 1; } + const headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) unitsPerEm: font.unitsPerEm, @@ -234,7 +235,7 @@ function fontToSfntTable(font) { minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), numberOfHMetrics: font.glyphs.length, - slope: font.slope, + slope: font.slope }); const maxpTable = maxp.make(font.glyphs.length); From 067bbe3915ec5e0e2c5fc6cf439ef4bfd4f63aca Mon Sep 17 00:00:00 2001 From: Leonardo Pessoa Date: Fri, 21 Jul 2023 11:43:43 -0300 Subject: [PATCH 05/12] Fixed metadata that enables fonts to be recognised as a family I've found out that a few fields that might be passed to opentype.js were not being used while producing the font. I have been able to pinpoint those fields and where they should be applied to produce fonts that are recognised as a family. Tested only with a couple of fonts but changes were regonised by Windows and other apps. An issue regarding the fields hhea.caretSlopeRise and hhea.caretSlopeRun for italic and oblique fonts is still pending review but does not prevent font usage or recognition as a family (caret slope had a weird inclination while testing on Word but not on Affinity Designer). These same changes were applied to the copy of the library used by Glyphr Studio (v2) where the tests were conducted. --- src/font.js | 28 +++++++++++++++++++++++++++- src/tables/head.js | 2 +- src/tables/post.js | 6 +++--- src/tables/sfnt.js | 13 +++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/font.js b/src/font.js index 6e02c189..e357239f 100644 --- a/src/font.js +++ b/src/font.js @@ -86,12 +86,38 @@ function Font(options) { this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; + this.slope = options.slope; + this.italicAngle = options.italicAngle; this.createdTimestamp = options.createdTimestamp; + + var selection = 0; + if (this.italicAngle < 0) { + selection |= this.fsSelectionValues.ITALIC; + } else if (this.italicAngle > 0) { + selection |= this.fsSelectionValues.OBLIQUE; + } + if (this.weightClass >= 600) { + selection |= this.fsSelectionValues.BOLD; + } + if (selection == 0) { + selection = this.fsSelectionValues.REGULAR; + } + this.tables = Object.assign(options.tables, { os2: Object.assign({ usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, - fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + bFamilyType: options.panose[0] || 0, + bSerifStyle: options.panose[1] || 0, + bWeight: options.panose[2] || 0, + bProportion: options.panose[3] || 0, + bContrast: options.panose[4] || 0, + bStrokeVariation: options.panose[5] || 0, + bArmStyle: options.panose[6] || 0, + bLetterform: options.panose[7] || 0, + bMidline: options.panose[8] || 0, + bXHeight: options.panose[9] || 0, + fsSelection: selection, }, options.tables.os2) }); } diff --git a/src/tables/head.js b/src/tables/head.js index d8ac530c..e29b9295 100644 --- a/src/tables/head.js +++ b/src/tables/head.js @@ -52,7 +52,7 @@ function makeHeadTable(options) { {name: 'yMin', type: 'SHORT', value: 0}, {name: 'xMax', type: 'SHORT', value: 0}, {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: options.macStyle}, {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, {name: 'fontDirectionHint', type: 'SHORT', value: 2}, {name: 'indexToLocFormat', type: 'SHORT', value: 0}, diff --git a/src/tables/post.js b/src/tables/post.js index c140e1e5..8e243fbb 100644 --- a/src/tables/post.js +++ b/src/tables/post.js @@ -50,9 +50,9 @@ function parsePostTable(data, start) { return post; } -function makePostTable(postTable) { +function makePostTable(font) { const { - italicAngle = 0, + italicAngle = Math.round((font.italicAngle || 0) * 0x10000), underlinePosition = 0, underlineThickness = 0, isFixedPitch = 0, @@ -60,7 +60,7 @@ function makePostTable(postTable) { maxMemType42 = 0, minMemType1 = 0, maxMemType1 = 0 - } = postTable || {}; + } = font.tables.post || {}; return new table.Table('post', [ { name: 'version', type: 'FIXED', value: 0x00030000 }, { name: 'italicAngle', type: 'FIXED', value: italicAngle }, diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index d0f86dca..9e7aac4c 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -207,6 +207,13 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; + var macStyle = 0; + if (font.italicAngle < 0) { + macStyle |= 2; + } + if (font.weightClass >= 600) { + macStyle |= 1; + } const headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) unitsPerEm: font.unitsPerEm, @@ -215,6 +222,7 @@ function fontToSfntTable(font) { xMax: globals.xMax, yMax: globals.yMax, lowestRecPPEM: 3, + macStyle: macStyle, createdTimestamp: font.createdTimestamp }); @@ -225,7 +233,8 @@ function fontToSfntTable(font) { minLeftSideBearing: globals.minLeftSideBearing, minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length + numberOfHMetrics: font.glyphs.length, + slope: font.slope, }); const maxpTable = maxp.make(font.glyphs.length); @@ -325,7 +334,7 @@ function fontToSfntTable(font) { const nameTable = _name.make(names, languageTags); const ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); - const postTable = post.make(font.tables.post); + const postTable = post.make(font); const cffTable = cff.make(font.glyphs, { version: font.getEnglishName('version'), fullName: englishFullName, From 0be81c0e96acef144313d5676f8c194835e42909 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Wed, 4 Oct 2023 10:54:42 -0700 Subject: [PATCH 06/12] Providing options so tests pass --- src/font.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/font.js b/src/font.js index e357239f..0115b574 100644 --- a/src/font.js +++ b/src/font.js @@ -102,6 +102,12 @@ function Font(options) { if (selection == 0) { selection = this.fsSelectionValues.REGULAR; } + if (options.fsSelection) { + selection = options.fsSelection; + } + if (!options.panose || !Array.isArray(options.panose)) { + options.panose = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + } this.tables = Object.assign(options.tables, { os2: Object.assign({ From 6f8f85591429bf39332eab4e91286d3f457ad4dd Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Thu, 26 Oct 2023 14:02:09 -0700 Subject: [PATCH 07/12] let instead of var --- src/font.js | 4 ++-- src/tables/sfnt.js | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/font.js b/src/font.js index 0115b574..f955c69d 100644 --- a/src/font.js +++ b/src/font.js @@ -86,11 +86,11 @@ function Font(options) { this.unitsPerEm = options.unitsPerEm || 1000; this.ascender = options.ascender; this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; this.slope = options.slope; this.italicAngle = options.italicAngle; - this.createdTimestamp = options.createdTimestamp; - var selection = 0; + let selection = 0; if (this.italicAngle < 0) { selection |= this.fsSelectionValues.ITALIC; } else if (this.italicAngle > 0) { diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 9e7aac4c..1c2ccd6e 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -207,13 +207,14 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; - var macStyle = 0; + let macStyle = 0; if (font.italicAngle < 0) { macStyle |= 2; } if (font.weightClass >= 600) { macStyle |= 1; } + const headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) unitsPerEm: font.unitsPerEm, @@ -234,7 +235,7 @@ function fontToSfntTable(font) { minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), numberOfHMetrics: font.glyphs.length, - slope: font.slope, + slope: font.slope }); const maxpTable = maxp.make(font.glyphs.length); From 0f23a2441fa21005a28cab253bb61077c5ab94c2 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Mon, 6 Nov 2023 10:29:14 -0800 Subject: [PATCH 08/12] Check for fsSelection before calculating default values --- src/font.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/font.js b/src/font.js index f955c69d..aa55d076 100644 --- a/src/font.js +++ b/src/font.js @@ -91,20 +91,22 @@ function Font(options) { this.italicAngle = options.italicAngle; let selection = 0; - if (this.italicAngle < 0) { - selection |= this.fsSelectionValues.ITALIC; - } else if (this.italicAngle > 0) { - selection |= this.fsSelectionValues.OBLIQUE; - } - if (this.weightClass >= 600) { - selection |= this.fsSelectionValues.BOLD; - } - if (selection == 0) { - selection = this.fsSelectionValues.REGULAR; - } if (options.fsSelection) { selection = options.fsSelection; + } else { + if (this.italicAngle < 0) { + selection |= this.fsSelectionValues.ITALIC; + } else if (this.italicAngle > 0) { + selection |= this.fsSelectionValues.OBLIQUE; + } + if (this.weightClass >= 600) { + selection |= this.fsSelectionValues.BOLD; + } + if (selection == 0) { + selection = this.fsSelectionValues.REGULAR; + } } + if (!options.panose || !Array.isArray(options.panose)) { options.panose = [0, 0, 0, 0, 0, 0, 0, 0, 0]; } From e5ec6568731473f3ba9bd163970de66f081af07d Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Mon, 6 Nov 2023 10:30:16 -0800 Subject: [PATCH 09/12] New tests for Panose and calculated fsSelection --- test/font.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/font.js b/test/font.js index 626b634b..5bc5635f 100644 --- a/test/font.js +++ b/test/font.js @@ -45,6 +45,42 @@ describe('font.js', function() { it('tables definition can override defaults values', function() { assert.equal(font.tables.os2.fsSelection, 42); }); + it('panose has default fallback', function() { + assert.equal(font.tables.os2.bFamilyType, 0); + assert.equal(font.tables.os2.bSerifStyle, 0); + assert.equal(font.tables.os2.bWeight, 0); + assert.equal(font.tables.os2.bProportion, 0); + assert.equal(font.tables.os2.bContrast, 0); + assert.equal(font.tables.os2.bStrokeVariation, 0); + assert.equal(font.tables.os2.bArmStyle, 0); + assert.equal(font.tables.os2.bLetterform, 0); + assert.equal(font.tables.os2.bMidline, 0); + assert.equal(font.tables.os2.bXHeight, 0); + }); + it('fsSelection is calcluated if no value is provided', function () { + let weightClassFont = new Font({ + familyName: 'MyFont', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: 0, + weightClass: 600, + fsSelection: false, + }); + assert.equal(weightClassFont.tables.os2.fsSelection, 64); + // assert.equal(weightClassFont.tables.head.macStyle, 64); + let italicAngleFont = new Font({ + familyName: 'MyFont', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: 0, + italicAngle: 13, + fsSelection: false, + }); + assert.equal(italicAngleFont.tables.os2.fsSelection, 512); + // assert.equal(italicAngleFont.tables.head.macStyle, 512); + }); it('tables definition shall be serialized', function() { const os2 = font.toTables().tables.find(table => table.tableName === 'OS/2'); assert.equal(os2.achVendID, 'TEST'); From e4f8ee73f4101863c8f83f6c850f11f8e2b6c527 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Mon, 6 Nov 2023 12:34:38 -0800 Subject: [PATCH 10/12] macStyle is calculated to match fsSelection calculated value (with updated tests) --- src/font.js | 6 ++++-- src/tables/sfnt.js | 10 ++++++++-- test/font.js | 13 +++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/font.js b/src/font.js index aa55d076..2b23c267 100644 --- a/src/font.js +++ b/src/font.js @@ -52,7 +52,8 @@ function createDefaultNamesInfo(options) { * @property {Number} ascender * @property {Number} descender * @property {Number} createdTimestamp - * @property {string=} weightClass + * @property {Number} weightClass + * @property {Number} italicAngle * @property {string=} widthClass * @property {string=} fsSelection */ @@ -88,7 +89,8 @@ function Font(options) { this.descender = options.descender; this.createdTimestamp = options.createdTimestamp; this.slope = options.slope; - this.italicAngle = options.italicAngle; + this.italicAngle = options.italicAngle || 0; + this.weightClass = options.weightClass; let selection = 0; if (options.fsSelection) { diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 1c2ccd6e..6793880f 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -207,12 +207,18 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; + // macStyle bits must agree with the fsSelection bits let macStyle = 0; if (font.italicAngle < 0) { - macStyle |= 2; + macStyle |= font.fsSelectionValues.ITALIC; + } else if (font.italicAngle > 0) { + macStyle |= font.fsSelectionValues.OBLIQUE; } if (font.weightClass >= 600) { - macStyle |= 1; + macStyle |= font.fsSelectionValues.BOLD; + } + if (macStyle == 0) { + macStyle = font.fsSelectionValues.REGULAR; } const headTable = head.make({ diff --git a/test/font.js b/test/font.js index 5bc5635f..1560ee59 100644 --- a/test/font.js +++ b/test/font.js @@ -57,7 +57,7 @@ describe('font.js', function() { assert.equal(font.tables.os2.bMidline, 0); assert.equal(font.tables.os2.bXHeight, 0); }); - it('fsSelection is calcluated if no value is provided', function () { + it('fsSelection and macStyle are calcluated if no fsSelection value is provided', function() { let weightClassFont = new Font({ familyName: 'MyFont', styleName: 'Medium', @@ -67,19 +67,20 @@ describe('font.js', function() { weightClass: 600, fsSelection: false, }); - assert.equal(weightClassFont.tables.os2.fsSelection, 64); - // assert.equal(weightClassFont.tables.head.macStyle, 64); + assert.equal(weightClassFont.tables.os2.fsSelection, 32); + assert.equal(weightClassFont.toTables().tables[0].macStyle, 32); + let italicAngleFont = new Font({ familyName: 'MyFont', styleName: 'Medium', unitsPerEm: 1000, ascender: 800, descender: 0, - italicAngle: 13, + italicAngle: -13, fsSelection: false, }); - assert.equal(italicAngleFont.tables.os2.fsSelection, 512); - // assert.equal(italicAngleFont.tables.head.macStyle, 512); + assert.equal(italicAngleFont.tables.os2.fsSelection, 1); + assert.equal(italicAngleFont.toTables().tables[0].macStyle, 1); }); it('tables definition shall be serialized', function() { const os2 = font.toTables().tables.find(table => table.tableName === 'OS/2'); From 81b2508a8fef4b3737bac66609215666bf23615a Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Mon, 6 Nov 2023 12:49:58 -0800 Subject: [PATCH 11/12] Updated tests for Panose and head table --- test/font.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/font.js b/test/font.js index 7379b34a..744f43ba 100644 --- a/test/font.js +++ b/test/font.js @@ -57,6 +57,26 @@ describe('font.js', function() { assert.equal(font.tables.os2.bMidline, 0); assert.equal(font.tables.os2.bXHeight, 0); }); + it('panose values are set correctly', function () { + let panoseFont = new Font({ + familyName: 'MyFont', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: 0, + panose: [0, 1,2,3,4,5,6,7,8,9], + }); + assert.equal(panoseFont.tables.os2.bFamilyType, 0); + assert.equal(panoseFont.tables.os2.bSerifStyle, 1); + assert.equal(panoseFont.tables.os2.bWeight, 2); + assert.equal(panoseFont.tables.os2.bProportion, 3); + assert.equal(panoseFont.tables.os2.bContrast, 4); + assert.equal(panoseFont.tables.os2.bStrokeVariation, 5); + assert.equal(panoseFont.tables.os2.bArmStyle, 6); + assert.equal(panoseFont.tables.os2.bLetterform, 7); + assert.equal(panoseFont.tables.os2.bMidline, 8); + assert.equal(panoseFont.tables.os2.bXHeight, 9); + }); it('fsSelection and macStyle are calcluated if no fsSelection value is provided', function() { let weightClassFont = new Font({ familyName: 'MyFont', @@ -68,7 +88,8 @@ describe('font.js', function() { fsSelection: false, }); assert.equal(weightClassFont.tables.os2.fsSelection, 32); - assert.equal(weightClassFont.toTables().tables[0].macStyle, 32); + const weightClassHeadTable = weightClassFont.toTables().tables.find(table => table.tableName === 'head'); + assert.equal(weightClassHeadTable.macStyle, 32); let italicAngleFont = new Font({ familyName: 'MyFont', @@ -80,7 +101,8 @@ describe('font.js', function() { fsSelection: false, }); assert.equal(italicAngleFont.tables.os2.fsSelection, 1); - assert.equal(italicAngleFont.toTables().tables[0].macStyle, 1); + const italicAngleHeadTable = italicAngleFont.toTables().tables.find(table => table.tableName === 'head'); + assert.equal(italicAngleHeadTable.macStyle, 1); }); it('tables definition shall be serialized', function() { const os2 = font.toTables().tables.find(table => table.tableName === 'OS/2'); From 51e8b86a141da64b12e95515097a59929144c142 Mon Sep 17 00:00:00 2001 From: Matt LaGrandeur Date: Tue, 7 Nov 2023 13:53:29 -0800 Subject: [PATCH 12/12] Added bit names for macStyle, and use them for calculating macStyle --- src/font.js | 17 +++++++++++++++-- src/tables/head.js | 4 ++-- src/tables/sfnt.js | 14 ++++---------- test/font.js | 6 +++--- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/font.js b/src/font.js index 1a8a0727..09d2dddc 100644 --- a/src/font.js +++ b/src/font.js @@ -88,9 +88,8 @@ function Font(options) { this.ascender = options.ascender; this.descender = options.descender; this.createdTimestamp = options.createdTimestamp; - this.slope = options.slope; this.italicAngle = options.italicAngle || 0; - this.weightClass = options.weightClass; + this.weightClass = options.weightClass || 0; let selection = 0; if (options.fsSelection) { @@ -598,6 +597,7 @@ Font.prototype.download = function(fileName) { fs.writeFileSync(fileName, buffer); } }; + /** * @private */ @@ -614,6 +614,19 @@ Font.prototype.fsSelectionValues = { OBLIQUE: 0x200 //512 }; +/** + * @private + */ +Font.prototype.macStyleValues = { + BOLD: 0x001, //1 + ITALIC: 0x002, //2 + UNDERLINE: 0x004, //4 + OUTLINED: 0x008, //8 + SHADOW: 0x010, //16 + CONDENSED: 0x020, //32 + EXTENDED: 0x040, //64 +}; + /** * @private */ diff --git a/src/tables/head.js b/src/tables/head.js index e29b9295..832c60d1 100644 --- a/src/tables/head.js +++ b/src/tables/head.js @@ -34,7 +34,7 @@ function makeHeadTable(options) { // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 const timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; let createdTimestamp = timestamp; - + let macStyle = options.macStyle || 0; if (options.createdTimestamp) { createdTimestamp = options.createdTimestamp + 2082844800; } @@ -52,7 +52,7 @@ function makeHeadTable(options) { {name: 'yMin', type: 'SHORT', value: 0}, {name: 'xMax', type: 'SHORT', value: 0}, {name: 'yMax', type: 'SHORT', value: 0}, - {name: 'macStyle', type: 'USHORT', value: options.macStyle}, + {name: 'macStyle', type: 'USHORT', value: macStyle}, {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, {name: 'fontDirectionHint', type: 'SHORT', value: 2}, {name: 'indexToLocFormat', type: 'SHORT', value: 0}, diff --git a/src/tables/sfnt.js b/src/tables/sfnt.js index 6793880f..fe2e1f74 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -209,17 +209,12 @@ function fontToSfntTable(font) { // macStyle bits must agree with the fsSelection bits let macStyle = 0; - if (font.italicAngle < 0) { - macStyle |= font.fsSelectionValues.ITALIC; - } else if (font.italicAngle > 0) { - macStyle |= font.fsSelectionValues.OBLIQUE; - } if (font.weightClass >= 600) { - macStyle |= font.fsSelectionValues.BOLD; - } - if (macStyle == 0) { - macStyle = font.fsSelectionValues.REGULAR; + macStyle |= font.macStyleValues.BOLD; } + if (font.italicAngle < 0) { + macStyle |= font.macStyleValues.ITALIC; + } const headTable = head.make({ flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) @@ -241,7 +236,6 @@ function fontToSfntTable(font) { minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), numberOfHMetrics: font.glyphs.length, - slope: font.slope }); const maxpTable = maxp.make(font.glyphs.length); diff --git a/test/font.js b/test/font.js index 744f43ba..d3ac06af 100644 --- a/test/font.js +++ b/test/font.js @@ -64,7 +64,7 @@ describe('font.js', function() { unitsPerEm: 1000, ascender: 800, descender: 0, - panose: [0, 1,2,3,4,5,6,7,8,9], + panose: [0,1,2,3,4,5,6,7,8,9], }); assert.equal(panoseFont.tables.os2.bFamilyType, 0); assert.equal(panoseFont.tables.os2.bSerifStyle, 1); @@ -89,7 +89,7 @@ describe('font.js', function() { }); assert.equal(weightClassFont.tables.os2.fsSelection, 32); const weightClassHeadTable = weightClassFont.toTables().tables.find(table => table.tableName === 'head'); - assert.equal(weightClassHeadTable.macStyle, 32); + assert.equal(weightClassHeadTable.macStyle, font.macStyleValues.BOLD); let italicAngleFont = new Font({ familyName: 'MyFont', @@ -102,7 +102,7 @@ describe('font.js', function() { }); assert.equal(italicAngleFont.tables.os2.fsSelection, 1); const italicAngleHeadTable = italicAngleFont.toTables().tables.find(table => table.tableName === 'head'); - assert.equal(italicAngleHeadTable.macStyle, 1); + assert.equal(italicAngleHeadTable.macStyle, font.macStyleValues.ITALIC); }); it('tables definition shall be serialized', function() { const os2 = font.toTables().tables.find(table => table.tableName === 'OS/2');