diff --git a/src/font.js b/src/font.js index a4ca07f3..09d2dddc 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 */ @@ -87,11 +88,45 @@ function Font(options) { this.ascender = options.ascender; this.descender = options.descender; this.createdTimestamp = options.createdTimestamp; + this.italicAngle = options.italicAngle || 0; + this.weightClass = options.weightClass || 0; + + let selection = 0; + 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]; + } + 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) }); } @@ -562,6 +597,7 @@ Font.prototype.download = function(fileName) { fs.writeFileSync(fileName, buffer); } }; + /** * @private */ @@ -578,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 d8ac530c..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: 0}, + {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/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..fe2e1f74 100644 --- a/src/tables/sfnt.js +++ b/src/tables/sfnt.js @@ -207,6 +207,15 @@ function fontToSfntTable(font) { globals.ascender = font.ascender; globals.descender = font.descender; + // macStyle bits must agree with the fsSelection bits + let macStyle = 0; + if (font.weightClass >= 600) { + 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) unitsPerEm: font.unitsPerEm, @@ -215,6 +224,7 @@ function fontToSfntTable(font) { xMax: globals.xMax, yMax: globals.yMax, lowestRecPPEM: 3, + macStyle: macStyle, createdTimestamp: font.createdTimestamp }); @@ -225,7 +235,7 @@ function fontToSfntTable(font) { minLeftSideBearing: globals.minLeftSideBearing, minRightSideBearing: globals.minRightSideBearing, xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), - numberOfHMetrics: font.glyphs.length + numberOfHMetrics: font.glyphs.length, }); const maxpTable = maxp.make(font.glyphs.length); @@ -325,7 +335,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, diff --git a/test/font.js b/test/font.js index b114d5a3..d3ac06af 100644 --- a/test/font.js +++ b/test/font.js @@ -45,6 +45,65 @@ 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('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', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: 0, + weightClass: 600, + fsSelection: false, + }); + assert.equal(weightClassFont.tables.os2.fsSelection, 32); + const weightClassHeadTable = weightClassFont.toTables().tables.find(table => table.tableName === 'head'); + assert.equal(weightClassHeadTable.macStyle, font.macStyleValues.BOLD); + + let italicAngleFont = new Font({ + familyName: 'MyFont', + styleName: 'Medium', + unitsPerEm: 1000, + ascender: 800, + descender: 0, + italicAngle: -13, + fsSelection: false, + }); + assert.equal(italicAngleFont.tables.os2.fsSelection, 1); + const italicAngleHeadTable = italicAngleFont.toTables().tables.find(table => table.tableName === 'head'); + 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'); assert.equal(os2.achVendID, 'TEST');