Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check glyph hashes more thoroughly #814

Merged
merged 12 commits into from
May 13, 2024
28 changes: 16 additions & 12 deletions Lib/ufo2ft/instructionCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import array
import logging
from functools import partial
from typing import TYPE_CHECKING, Optional

from fontTools import ttLib
from fontTools.misc.fixedTools import floatToFixedToFloat
from fontTools.pens.hashPointPen import HashPointPen
from fontTools.pens.roundingPen import RoundingPointPen
from fontTools.ttLib import newTable
from fontTools.ttLib.tables._g_l_y_f import (
OVERLAP_COMPOUND,
Expand Down Expand Up @@ -42,27 +45,28 @@ def __init__(
self.autoUseMyMetrics = lambda ttGlyph, glyphName: None

def _check_glyph_hash(
self, glyphName: str, ttglyph: TTGlyph, glyph_hash: Optional[str]
self, glyph: Glyph, ttglyph: TTGlyph, stored_hash: Optional[str]
) -> bool:
"""Check if the supplied glyph hash from the ufo matches the current outlines."""
if glyph_hash is None:
"""Check if the supplied stored glyph hash from the ufo matches the TTGlyph."""
if stored_hash is None:
# The glyph hash is required
logger.error(
f"Glyph hash missing, glyph '{glyphName}' will have "
f"Glyph hash missing, glyph '{glyph.name}' will have "
"no instructions in font."
)
return False

# Check the glyph hash against the TTGlyph that is being built

ttwidth = self.otf["hmtx"][glyphName][0]
ttwidth = self.otf["hmtx"][glyph.name][0]
hash_pen = HashPointPen(ttwidth, self.otf.getGlyphSet())
ttglyph.drawPoints(hash_pen, self.otf["glyf"])
round_pen = RoundingPointPen(
hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)
)
ttglyph.drawPoints(round_pen, self.otf["glyf"])

if glyph_hash != hash_pen.hash:
if stored_hash != hash_pen.hash:
logger.error(
f"The stored hash for glyph '{glyphName}' does not match the TrueType "
"output glyph. Glyph will have no instructions in the font."
f"The stored hash for glyph '{glyph.name}' does not match the "
"TrueType output glyph. Glyph will have no instructions in the font."
)
return False
return True
Expand Down Expand Up @@ -132,7 +136,7 @@ def _compile_tt_glyph_program(
) -> None:
self._check_tt_data_format(ttdata, f"glyph '{glyph.name}'")
glyph_hash = ttdata.get("id", None)
if not self._check_glyph_hash(glyph.name, ttglyph, glyph_hash):
if not self._check_glyph_hash(glyph, ttglyph, glyph_hash):
return

# Compile the glyph program
Expand Down
1 change: 0 additions & 1 deletion tests/infoCompiler_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def testufo(FontClass):


class InfoCompilerTest:

def test_head(self, testttf, testufo):
info = {"versionMajor": 5, "versionMinor": 6}
compiler = InfoCompiler(testttf, testufo, info)
Expand Down
18 changes: 12 additions & 6 deletions tests/instructionCompiler_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import logging
from functools import partial

import pytest
from fontTools.cu2qu.ufo import font_to_quadratic
from fontTools.misc.fixedTools import floatToFixedToFloat
from fontTools.pens.hashPointPen import HashPointPen
from fontTools.pens.roundingPen import RoundingPointPen
from fontTools.ttLib.tables._g_l_y_f import (
OVERLAP_COMPOUND,
ROUND_XY_TO_GRID,
Expand Down Expand Up @@ -40,7 +43,10 @@ def expect_maxp(

def get_hash_ufo(glyph, ufo):
hash_pen = HashPointPen(glyph.width, ufo)
glyph.drawPoints(hash_pen)
round_pen = RoundingPointPen(
hash_pen, transformRoundFunc=partial(floatToFixedToFloat, precisionBits=14)
)
glyph.drawPoints(round_pen)
return hash_pen.hash


Expand Down Expand Up @@ -89,15 +95,15 @@ def test_check_glyph_hash_match(self, quaduforeversed, quadfont):
ttglyph = quadfont["glyf"]["a"]

ic = InstructionCompiler(quaduforeversed, quadfont)
result = ic._check_glyph_hash(glyph.name, ttglyph, ufo_hash)
result = ic._check_glyph_hash(glyph, ttglyph, ufo_hash)
assert result

def test_check_glyph_hash_missing(self, quaduforeversed, quadfont):
glyph = quaduforeversed["a"]

ic = InstructionCompiler(quaduforeversed, quadfont)
result = ic._check_glyph_hash(
glyph.name,
glyph,
quadfont["glyf"]["a"],
None,
)
Expand All @@ -113,7 +119,7 @@ def test_check_glyph_hash_mismatch(self, testufo, quadfont):

ic = InstructionCompiler(testufo, quadfont)
result = ic._check_glyph_hash(
glyph.name,
glyph,
ttglyph,
ufo_hash,
)
Expand All @@ -129,7 +135,7 @@ def test_check_glyph_hash_mismatch_composite(self, testufo, quadfont):

ic = InstructionCompiler(testufo, quadfont)
result = ic._check_glyph_hash(
glyph.name,
glyph,
ttglyph,
ufo_hash,
)
Expand All @@ -146,7 +152,7 @@ def test_check_glyph_hash_mismatch_width(self, quaduforeversed, quadfont):

ic = InstructionCompiler(quaduforeversed, quadfont)
result = ic._check_glyph_hash(
glyph.name,
glyph,
ttglyph,
ufo_hash,
)
Expand Down
Loading