Fix SMP ligature filter and Extension-wrapped GPOS kern dispatch
Two related bugs in fontconvert_sdcard.py:
1. SMP filter for ligatures was post-pack, which only checked the
already-masked low half (always ≤ 0xFFFF), didn't check `lc`, and
couldn't catch SMP cps that already had bits truncated by the
mask. Filter at source instead — inside extract_ligatures_fonttools
when building the `filtered` dict — so any seq with an SMP
component or any lig_cp > 0xFFFF is dropped before packing. The
chained 3+ char path is naturally covered because
`intermediate_cp = filtered[prefix].lig_cp` reads from the same
already-filtered table.
2. GPOS kern dispatch checked `actual.LookupType` after unwrapping an
Extension subtable. Format-specific subtables (PairPosFormat1/2)
don't carry `LookupType`, so this fails for any Extension-wrapped
PairPos lookup — i.e. how every modern font (Inter, Source Serif,
Bitter, …) ships kern. Use the effective type instead:
effective_type = getattr(st, "ExtensionLookupType", lookup.LookupType)
so non-Extension lookups read the outer type and Extension lookups
read the inner ExtensionLookupType, and Type-2 PairPos lookups
actually reach _extract_pairpos_subtable in either case.
This commit is contained in:
@@ -385,14 +385,19 @@ def extract_kerning_fonttools(font_path, codepoints, ppem):
|
|||||||
lookup = gpos.LookupList.Lookup[li]
|
lookup = gpos.LookupList.Lookup[li]
|
||||||
for st in lookup.SubTable:
|
for st in lookup.SubTable:
|
||||||
actual = st
|
actual = st
|
||||||
# Unwrap Extension (lookup type 9) wrappers
|
# Unwrap Extension (lookup type 9) wrappers. After unwrapping,
|
||||||
|
# `lookup.LookupType` is still 9 and the unwrapped subtable
|
||||||
|
# carries `Format` rather than `LookupType`, so the *effective*
|
||||||
|
# type for the dispatch below comes from `st.ExtensionLookupType`.
|
||||||
if lookup.LookupType == 9 and hasattr(st, 'ExtSubTable'):
|
if lookup.LookupType == 9 and hasattr(st, 'ExtSubTable'):
|
||||||
actual = st.ExtSubTable
|
actual = st.ExtSubTable
|
||||||
|
effective_type = getattr(st, 'ExtensionLookupType', lookup.LookupType)
|
||||||
if hasattr(actual, 'Format'):
|
if hasattr(actual, 'Format'):
|
||||||
if actual.LookupType == 2:
|
if effective_type == 2:
|
||||||
_extract_pairpos_subtable(actual, glyph_to_cp, raw_kern)
|
_extract_pairpos_subtable(actual, glyph_to_cp, raw_kern)
|
||||||
else:
|
else:
|
||||||
print(f" Debug: skipping unsupported GPOS kern lookupType={actual.LookupType} (Format={actual.Format})",
|
print(f" Debug: skipping unsupported GPOS kern lookupType="
|
||||||
|
f"{effective_type} (outer={lookup.LookupType}, Format={actual.Format})",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
font.close()
|
font.close()
|
||||||
@@ -552,7 +557,13 @@ def extract_ligatures_fonttools(font_path, codepoints):
|
|||||||
codepoints_set = set(codepoints)
|
codepoints_set = set(codepoints)
|
||||||
filtered = {}
|
filtered = {}
|
||||||
for seq, lig_cp in raw_ligatures.items():
|
for seq, lig_cp in raw_ligatures.items():
|
||||||
if lig_cp not in codepoints_set:
|
if lig_cp not in codepoints_set or lig_cp > 0xFFFF:
|
||||||
|
continue
|
||||||
|
# The on-disk format packs each ligature component as a uint16. Drop
|
||||||
|
# any seq with an SMP component here so the chained 3+ char path —
|
||||||
|
# which uses `intermediate_cp = filtered[prefix].lig_cp` — also stays
|
||||||
|
# 16-bit safe by construction.
|
||||||
|
if any(cp > 0xFFFF for cp in seq):
|
||||||
continue
|
continue
|
||||||
if all(cp in codepoints_set for cp in seq):
|
if all(cp in codepoints_set for cp in seq):
|
||||||
filtered[seq] = lig_cp
|
filtered[seq] = lig_cp
|
||||||
@@ -727,9 +738,10 @@ def rasterize_font_style(fontfile, size, intervals, style_id=0, force_autohint=F
|
|||||||
print(f" [{style_label}] Kerning classes: {kern_left_class_count} left, {kern_right_class_count} right, "
|
print(f" [{style_label}] Kerning classes: {kern_left_class_count} left, {kern_right_class_count} right, "
|
||||||
f"{matrix_size + entries_size} bytes", file=sys.stderr)
|
f"{matrix_size + entries_size} bytes", file=sys.stderr)
|
||||||
|
|
||||||
|
# SMP codepoints in ligature inputs / outputs are filtered inside
|
||||||
|
# extract_ligatures_fonttools (see the codepoints_set filter), so every
|
||||||
|
# entry returned here is already 16-bit safe.
|
||||||
ligature_pairs = extract_ligatures_fonttools(fontfile, all_cps)
|
ligature_pairs = extract_ligatures_fonttools(fontfile, all_cps)
|
||||||
# SMP codepoints overflow the uint32 packed-pair encoding; drop them.
|
|
||||||
ligature_pairs = [(pk, lc) for pk, lc in ligature_pairs if (pk >> 16) <= 0xFFFF and (pk & 0xFFFF) <= 0xFFFF]
|
|
||||||
if len(ligature_pairs) > 255:
|
if len(ligature_pairs) > 255:
|
||||||
print(f" [{style_label}] WARNING: {len(ligature_pairs)} ligature pairs exceeds uint8_t max (255), truncating",
|
print(f" [{style_label}] WARNING: {len(ligature_pairs)} ligature pairs exceeds uint8_t max (255), truncating",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|||||||
Reference in New Issue
Block a user