Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/font.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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,
Copy link

@Finii Finii Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces names for the individual Panose values.
I think this is problematic.

When one sees a property bProportion one might assume it has something to do with the font proportion. But the Panose values are ... a mess. The meaning of a certain value depends on the 1st Panose value.

For example with the 4th Panose value panose[3] contains

  • Proportion for Latin Text
  • Spacing for Latin Script and Latin Picture
  • Aspect for Latin Decorative

So I would not call it bProportion as that is an oversimplification. I believe it is best to keep just the array and let interested uses decode it by for example this excellent compilation (by Alan Bernard Hughes) that I usually use for reference:

image
https://forum.high-logic.com/postedfiles/Panose.pdf

Edit: Fix wrong name


Addendum:

The names chosen above are from Panose-1 which was rather simple. But when it has been expanded to be Panose-2 the values became multiple meaning depending on Family Kind.

Just looked it up again in Haralambous: Fonts & Encodings, O'Reilly (2007)

panose

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)
});
}
Expand Down Expand Up @@ -562,6 +597,7 @@ Font.prototype.download = function(fileName) {
fs.writeFileSync(fileName, buffer);
}
};

/**
* @private
*/
Expand All @@ -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
*/
Expand Down
4 changes: 2 additions & 2 deletions src/tables/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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},
Expand Down
6 changes: 3 additions & 3 deletions src/tables/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,17 @@ 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,
minMemType42 = 0,
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 },
Expand Down
14 changes: 12 additions & 2 deletions src/tables/sfnt.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -215,6 +224,7 @@ function fontToSfntTable(font) {
xMax: globals.xMax,
yMax: globals.yMax,
lowestRecPPEM: 3,
macStyle: macStyle,
createdTimestamp: font.createdTimestamp
});

Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
59 changes: 59 additions & 0 deletions test/font.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down