fontbakery: Add check: Grade axis doesn't have any reflow

Observed behaviour

The grade (GRAD) axis should not change any advanceWidth or kerning data across its design space.

Expected behaviour

We should have a FB check for that.

Resources and exact process needed to replicate

@madig wrote this script for @chrissimpkins to report advance width issues. Kerning is a good 2nd step after this check ships.

# flake8: noqa

from pathlib import Path

from ufoLib2 import Font


TARGET_DIR = Path("source/")
DEFAULT = {
    "Family-opsz17-wght380-GRAD-50.ufo": "Family-opsz17-wght380-GRAD0.ufo",
    "Family-opsz17-wght380-GRAD200.ufo": "Family-opsz17-wght380-GRAD0.ufo",
    "Family-opsz18-wght380-GRAD-50.ufo": "Family-opsz18-wght380-GRAD0.ufo",
    "Family-opsz18-wght380-GRAD200.ufo": "Family-opsz18-wght380-GRAD0.ufo",
    "FamilyItalic-opsz17-wght380-GRAD-50.ufo": "FamilyItalic-opsz17-wght380-GRAD0.ufo",
    "FamilyItalic-opsz17-wght380-GRAD200.ufo": "FamilyItalic-opsz17-wght380-GRAD0.ufo",
    "FamilyItalic-opsz18-wght380-GRAD-50.ufo": "FamilyItalic-opsz18-wght380-GRAD0.ufo",
    "FamilyItalic-opsz18-wght380-GRAD200.ufo": "FamilyItalic-opsz18-wght380-GRAD0.ufo",
}

comparison_ufos = {}

for ufo_path, comparison_ufo_path in DEFAULT.items():
    ufo = Font.open(TARGET_DIR / ufo_path)
    if comparison_ufo_path in comparison_ufos:
        comparison_ufo = comparison_ufos[comparison_ufo_path]
    else:
        comparison_ufo = Font.open(TARGET_DIR / comparison_ufo_path)
        comparison_ufos[comparison_ufo_path] = comparison_ufo

    mismatches = [
        glyph.name for glyph in ufo if glyph.width != comparison_ufo[glyph.name].width
    ]
    if mismatches:
        print(f"UFO {ufo_path} width mismatches compared to {comparison_ufo_path}:")
        print("\n".join(f"  {x}" for x in mismatches))

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 19 (11 by maintainers)

Most upvoted comments

i checked varLib.iup code, and the last four phantom points are treated specially (see iup_delta and iup_contour functions), in the sense that if deltas are None, they will be always inferred as (0, 0). so I think you’re safe to ignore IUP for those 4

You don’t need a shaping engine for this. Just check the gvar table. There’ll be a phantom point at the end of each glyph which shows the delta to the horizontal advance.

Cosimo says:

a value of None means it's an inferred delta
it doesn't mean it going to be inferred as 0,0
that depends on the varLib.iup algorithm
tuplevariations have a calcInferredDeltas() method, check the instancer to see how that works

Also: kerning deltas need to be checked.

from fontTools.ttLib import TTFont
from fontTools.ttLib.tables._g_v_a_r import table__g_v_a_r

font = TTFont("secretproject.ttf")
gvar: table__g_v_a_r = font["gvar"]
for glyph, deltas in gvar.variations.items():
    for delta in deltas:
        if "GRAD" not in delta.axes:
            continue
        if any(c is not None and c != (0, 0) for c in delta.coordinates[-4:]):
            print(glyph)
            break

@simoncozens like this?