The grade (GRAD) axis should not change any advanceWidth or kerning data across its design space.
We should have a FB check for that.
@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))
Apache License 2.0. Gtg if any of the source is helpful.
this code-snippet references some files. Can you provide them as well (under a free license) so that we can refactor this into a fontbakery check?
I mean... to use the files in the code-test for the proposed new check
"The grade (GRAD) axis should not change any advanceWidth or kerning data across its design space."
@davelab6 has this happened in a real font project? If so, can you point me to it so that we have real-world samples of the problem?
Sorry those aren't available to distribute under a free license but we can make a simple test case font. A positive test case includes an advance width that changes across masters that do not differ by anything other than GRAD.
e.g.,
This assumes that GRAD should never change advance width. I think that this is a correct assumption based on how it is defined.
cool, thanks.
the code snippet deals with UFOs. Could this be checked on a VF ttf intead?
FontBakery can check UFOs, but we typically implement checks on TTFonts
It may be possible to do it with wizardry that I do not possess using fonttools. I'd definitely be interested to know how to do that.
We discussed use of uharfbuzz shaper output to do this at the binary level. That should be straightforward but adds a dependency.
but adds a dependency.
Though I think that Simon is working on the addition of shaping regression testing here? Perhaps uharfbuzz based?
if a new dependency is needed for something important/useful and it is a simple python package, then I have no worries adding it. Installation of python dependencies is trivial both locally as well as on our continuous integration setup.
I think @simoncozens was using something custom on his shaping check, that's why we have not merged that one yet. But he's also planning to make that custom code into a python package, so that will solve the dependency problem in that case.
If Simon's dependency will be coming in soon, it may be best to see if he feels that we can implement this on his shaping lib.
I spoke with David Berlow about this today. He told me that, given fixed values on all other design axes, there should be a constant adv width as GRAD changes. This is a valid assumption and the way that GRAD is intended to work. I think that this could land in a universally applicable test profile for testing on any project that includes a GRAD axis. He did raise the issue of avar on GRAD 馃く . But I don't think that this changes the test. For a given glyph with all other design axes at fixed values, adv width should be a constant value over the entire axis so it doesn't matter how GRAD bends off of a line from min to max. That only influences the "rate" of things that do change.
Here's a test font. Only includes numeral one (U+0031) with different widths across the three GRAD masters (GRAD -50, 0, 200). Source and designspace file included in case you need to expand further for the tests.
You can build with:
$ fontmake -m BadGrades.designspace -o variable
Simon wrote a python shaping engine 馃槀
You don鈥檛 need a shaping engine for this. Just check the gvar table. There鈥檒l be a phantom point at the end of each glyph which shows the delta to the horizontal advance.
This assumes that GRAD should never change advance width. I think that this is a correct assumption based on how it is defined.
Out of curiosity _where_ is this axis defined?
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?
That's what I would do. I presume you're posting it because you've tested it and it works. :-) You could also check HVAR.
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.
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
Most helpful comment
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