fix(font): Additional scale group tweaks (#9152)

Of course #9142 would require a minor follow-up!

* Scale groups can cut across patch sets, but not across fonts. We had
some scale group mixing between Font Awesome and the weather symbols,
which is removed by this PR.[^cp_table_full]
* There's one case where a scale group includes a glyph that's not part
of any patch sets, just for padding out the group bounding box.
Previously, an unrelated glyph from a different font would be pulled in.
Now we use an appropriate stand-in. (See code comment for details.)
* I noticed overlaps weren't being split between each side of the
bounding box, they were added to both sides, resulting in twice as much
padding as specified.

Screenshots showing the extra vertical padding for progress bar elements
due to the second bullet point:

**Before**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 54"
src="https://github.com/user-attachments/assets/cf288cce-86d3-46fd-ae86-18e5c274b0e4"
/>

**After**
<img width="191" height="42" alt="Screenshot 2025-10-11 at 15 33 20"
src="https://github.com/user-attachments/assets/7ac799c7-bf50-4e65-a74a-f8a2c42d2441"
/>

[^cp_table_full]: Forming and using the merged `cp_table_full` table
should have been a red flag. Such a table doesn't make sense, it would
be a one-to-many map. You need the names of the original fonts to
disambiguate.
pull/9160/head
Daniel Wennberg 2025-10-11 19:48:08 -07:00 committed by GitHub
parent aa0c68ee5e
commit 2e34f4e0e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 1374 additions and 207 deletions

File diff suppressed because it is too large Load Diff

View File

@ -282,12 +282,12 @@ def emit_zig_entry_multikey(codepoints: list[int], attr: PatchSetAttributeEntry)
# `overlap` and `ypadding` are mutually exclusive, # `overlap` and `ypadding` are mutually exclusive,
# this is asserted in the nerd fonts patcher itself. # this is asserted in the nerd fonts patcher itself.
if overlap: if overlap:
pad = -overlap pad = -overlap / 2
s += f" .pad_left = {pad},\n" s += f" .pad_left = {pad},\n"
s += f" .pad_right = {pad},\n" s += f" .pad_right = {pad},\n"
# In the nerd fonts patcher, overlap values # In the nerd fonts patcher, overlap values
# are capped at 0.01 in the vertical direction. # are capped at 0.01 in the vertical direction.
v_pad = -min(0.01, overlap) v_pad = -min(0.01, overlap) / 2
s += f" .pad_top = {v_pad},\n" s += f" .pad_top = {v_pad},\n"
s += f" .pad_bottom = {v_pad},\n" s += f" .pad_bottom = {v_pad},\n"
elif y_padding: elif y_padding:
@ -314,7 +314,7 @@ def generate_codepoint_tables(
return nerd_font_codepoint_tables.cp_tables return nerd_font_codepoint_tables.cp_tables
cp_tables: dict[str, dict[int, int]] = {} cp_tables: dict[str, dict[int, int]] = {}
cp_table_full: dict[int, int] = {} cp_nerdfont_used: set[int] = set()
cmap = nerd_font.getBestCmap() cmap = nerd_font.getBestCmap()
for entry in patch_sets: for entry in patch_sets:
patch_set_name = entry["Name"] patch_set_name = entry["Name"]
@ -381,12 +381,12 @@ def generate_codepoint_tables(
raise ValueError( raise ValueError(
f"Missing codepoint in Symbols Only Font: {hex(cp_nerdfont)} in patch set '{patch_set_name}'" f"Missing codepoint in Symbols Only Font: {hex(cp_nerdfont)} in patch set '{patch_set_name}'"
) )
elif cp_nerdfont in cp_table_full.values(): elif cp_nerdfont in cp_nerdfont_used:
raise ValueError( raise ValueError(
f"Overlap for codepoint {hex(cp_nerdfont)} in patch set '{patch_set_name}'" f"Overlap for codepoint {hex(cp_nerdfont)} in patch set '{patch_set_name}'"
) )
cp_tables[patch_set_name][cp_original] = cp_nerdfont cp_tables[patch_set_name][cp_original] = cp_nerdfont
cp_table_full |= cp_tables[patch_set_name] cp_nerdfont_used.add(cp_nerdfont)
# Store the table and corresponding Nerd Fonts version together in a module. # Store the table and corresponding Nerd Fonts version together in a module.
with open("nerd_font_codepoint_tables.py", "w") as f: with open("nerd_font_codepoint_tables.py", "w") as f:
@ -419,9 +419,6 @@ def generate_zig_switch_arms(
cmap = nerd_font.getBestCmap() cmap = nerd_font.getBestCmap()
glyphs = nerd_font.getGlyphSet() glyphs = nerd_font.getGlyphSet()
cp_tables = generate_codepoint_tables(patch_sets, nerd_font, nf_version) cp_tables = generate_codepoint_tables(patch_sets, nerd_font, nf_version)
cp_table_full: dict[int, int] = {}
for cp_table in cp_tables.values():
cp_table_full |= cp_table
entries: dict[int, PatchSetAttributeEntry] = {} entries: dict[int, PatchSetAttributeEntry] = {}
for entry in patch_sets: for entry in patch_sets:
@ -454,9 +451,37 @@ def generate_zig_switch_arms(
individual_bounds: dict[int, tuple[int, int, int, int]] = {} individual_bounds: dict[int, tuple[int, int, int, int]] = {}
individual_advances: set[float] = set() individual_advances: set[float] = set()
for cp_original in group: for cp_original in group:
# Scale groups may cut across patch sets, so we need to use if cp_original not in cp_table:
# the full lookup table here # There is one special case where a scale group includes
cp_nerdfont = cp_table_full[cp_original] # a glyph from the original font that's not in any patch
# set, and hence not in the Symbols Only font. The point
# of this glyph is to add extra vertical padding to a
# stretched (^xy) scale group, which means that its
# scaled and aligned position would span the line height
# plus overlap. Thus, we can use any other stretched
# glyph with overlap as stand-in to get the vertical
# bounds, such as as 0xE0B0 (powerline left hard
# divider). We don't worry about the horizontal bounds,
# as they by design should not affect the group's
# bounding box.
if (
patch_set_name == "Progress Indicators"
and cp_original == 0xEDFF
):
glyph = glyphs[cmap[0xE0B0]]
bounds = BoundsPen(glyphSet=glyphs)
glyph.draw(bounds)
yMin = min(bounds.bounds[1], yMin)
yMax = max(bounds.bounds[3], yMax)
else:
# Other cases are due to lazily specified scale
# groups with gaps in the codepoint range.
print(
f"Info: Skipping scale group codepoint {hex(cp_original)}, which does not exist in patch set '{patch_set_name}'"
)
continue
cp_nerdfont = cp_table[cp_original]
glyph = glyphs[cmap[cp_nerdfont]] glyph = glyphs[cmap[cp_nerdfont]]
individual_advances.add(glyph.width) individual_advances.add(glyph.width)
bounds = BoundsPen(glyphSet=glyphs) bounds = BoundsPen(glyphSet=glyphs)
@ -472,7 +497,9 @@ def generate_zig_switch_arms(
len(individual_advances) == 1 len(individual_advances) == 1
) )
for cp_original in group: for cp_original in group:
cp_nerdfont = cp_table_full[cp_original] if cp_original not in cp_table:
continue
cp_nerdfont = cp_table[cp_original]
if ( if (
# Scale groups may cut across patch sets, but we're only # Scale groups may cut across patch sets, but we're only
# updating a single patch set at a time, so we skip # updating a single patch set at a time, so we skip