opentypejs / opentype.js

Read and write OpenType fonts using JavaScript.
https://opentype.js.org/
MIT License
4.38k stars 468 forks source link

Inconsistent glyph sizes when adding glyphs from different fonts #405

Closed p3k closed 1 year ago

p3k commented 4 years ago

I am trying to rewrite a Python script in Node, replacing the fontforge bindings with OpenTypeJS.

The script takes a bunch of font files and copies the desired glyphs into a subset font, creating the necessary font formats and a LESS file.

Expected Behavior

The resulting subset font should contain all glyphs in the same size.

image

Current Behavior

It works well for some fonts but as soon as a certain font file is included, those glyphs are added with smaller sizes (approx. 50%).

image

Possible Solution

I just might be missing a basic information or step to achieve the consistent glyph size. Alternatively, it might be necessary to patch the code so glyph sizes are correctly saved. (I don’t know much or enough about font tables so any assistance is appreciated.)

Steps to Reproduce (for bugs)

  1. Prepare two source fonts, entypo.ttf and fa-solid-900.ttf
  2. Create a subset font with OpenTypeJS and add two glyphs, one from each source font
  3. Compare the glyphs in the subset font with the corresponding glyphs in the source fonts

Here is a script for creating the subset font:

#!/usr/bin/env node

const fs = require('fs');
const opentype = require('opentype.js');

const glyphs = [];
const entypoFont = opentype.loadSync('entypo.ttf');
const faFont = opentype.loadSync('fa-solid-900.ttf');

const addGlyph = glyph => {
    glyph.unicode = 0xe001 + glyphs.length;
    glyph.unicodes = [glyph.unicode];
    glyph.name = 'uni' + glyph.unicode.toString(16);
    glyphs.push(glyph);
};

addGlyph(entypoFont.glyphs.get(25));
addGlyph(faFont.glyphs.get(185));

const subsetFont = new opentype.Font({
    familyName: 'Test',
    styleName: 'Regular',
    unitsPerEm: 1000,
    ascender: 800,
    descender: -200,
    glyphs: glyphs
});

fs.writeFileSync('test.ttf', Buffer.from(subsetFont.toArrayBuffer()));

And this is a visual comparison of the result (left) with the source glyphs:

image image image

Your Environment

p3k commented 4 years ago

bump

fizfaz commented 4 years ago

The fa-solid-900.ttf font has a unitsPerEm of 512, entypo.ttf unitsPerEm of 1000. (See opentype spec for an explanation of this parameter). I guess you would have to scale the glyphs accordingly. Does this help?

p3k commented 4 years ago

thanks @fizfaz for the suggestion. so how do i scale glyphs? is this a process supported by opentypejs?

fizfaz commented 4 years ago

Unfortunately my knowledge of opentype.js is very limited. Here I tried to just move the coordinates of the commands used in a glyph, which makes it bigger but not sure if this is enough.

image

#!/usr/bin/env node

const fs = require('fs');
const opentype = require('opentype.js');

const glyphs = [];
const entypoFont = opentype.loadSync('entypo.ttf');
const faFont = opentype.loadSync('fa-solid-900.ttf');

const addGlyph = glyph => {
 glyph.unicode = 0xe001 + glyphs.length;
 glyph.unicodes = [glyph.unicode];
 glyph.name = 'uni' + glyph.unicode.toString(16);

 // handle different unitsPerEm:
 if (glyph.path.unitsPerEm != 1000) {
    const scale = 1000/glyph.path.unitsPerEm; 
    glyph.path.commands.forEach( c => {
        c.x = Math.round(c.x * scale);
        c.y = Math.round(c.y * scale);
        if (c.x1) c.x1 = Math.round(c.x1 * scale);
        if (c.x2) c.x2 = Math.round(c.x2 * scale);
        if (c.y1) c.y1 = Math.round(c.y1 * scale);
        if (c.y2) c.y2 = Math.round(c.y2 * scale);
    });

    glyph.path.unitsPerEm = 1000;
 }

 glyphs.push(glyph);
};

addGlyph(entypoFont.glyphs.get(25));
addGlyph(faFont.glyphs.get(185));

const subsetFont = new opentype.Font({
 familyName: 'Test',
 styleName: 'Regular',
 unitsPerEm: 1000,
 ascender: 800,
 descender: -200,
 glyphs: glyphs
});

fs.writeFileSync('test.ttf', Buffer.from(subsetFont.toArrayBuffer()));
p3k commented 4 years ago

thanks a lot @fizfaz the scaling works great and the glyphs look more consistent now!

i notice though that some glyphs seem to be off-centered now (e.g. a spinning loading icon made of various glyphs now wobbles unhappily instead of rotating around its circular center).

others are displayed too close to the next glyph. are there some other parameters in the glyph’s path / commands i need to scale / offset accordingly?

p3k commented 4 years ago

seems the spacing and centering issues can be ironed simply by scaling some more properties accordingly:

if (glyph.advanceWidth) glyph.advanceWidth *= scale;
if (glyph.leftSideBearing) glyph.leftSideBearing *= scale;
if (glyph.rightSideBearing) glyph.rightSideBearing *= scale;

if (glyph.xMin) glyph.xMin *= scale;
if (glyph.xMax) glyph.xMax *= scale;
if (glyph.yMin) glyph.yMin *= scale;
if (glyph.yMax) glyph.yMax *= scale;

as this solves the issue for me it could be closed. however, if someone of the opentypejs experts still would want to chime in – e.g. with a better suggestion – i leave it open for now.

Connum commented 1 year ago

Opentype.js simply reads and writes the font data, so I'd say that any editing like scaling is out of scope. Might be added ad some point in the far future, but right how we don't even have all the parts of the spec implemented.