Skip to content

Conversation

@danielwe
Copy link
Contributor

@danielwe danielwe commented Sep 8, 2025

This PR will probably need a rebase on the final outcome of #8550 and #8552 #8580, but I'm putting it out here so folks can begin taking a look if they want.

This is a rewrite of the code that applies scaling and alignment constraints. The main intention is to further improve how Nerd Font icons are matched to the primary font. This PR aligns the calculations more closely with how the Nerd Font font-patcher script works, except in two cases where we can easily do something unambiguously better (one because of what's arguably a bug in the script, and one because we do multi-cell alignment with knowledge of the pixel-rounded cell grid).

A goal of the rewrite is to make the scaling and alignment calculations as clear and easy to follow as possible.

I'll lead with some screenshots. First the status quo, then this PR.
Screenshot 2025-09-07 at 17 23 51Screenshot 2025-09-07 at 17 20 39

Relevant specs: macOS; 1920x1080; Ghostty config:

font-family = "CommitMono"
font-size = "15"
adjust-cell-height = "+20%"

Points to note

  • Icons are generally larger, making better use of the available space.
  • Icons are aligned nearly a pixel lower, better matching the text. This is because alignment is now calculated from face metrics/bearings, not the pixel-rounded cell. (See more below.)
  • Relative sizes are better matched. Note especially that tall and narrow icons, like the git branch symbol and icons depicting sheets of paper, look conspicuously small in the status quo. With this PR, they're better matched to other icons.
  • Look at the letter Z icon I use as prompt character for zsh. It's tiny in the status quo, but properly sized with this PR. This demonstrates the most important and clear-cut improvement we make over font-patcher. (See more below.)
  • Icons wider than a single cell are now left-aligned rather than centered across two cells. I think this is preferable and makes better use of space in most relevant contexts.
    • Consider a Neovim bufferline showing the buffer title as a filetype icon followed by the file name. Padding on the left would be a waste of space, but having that extra space on the right can improve legibility.
    • In listings, such as in the screenshots, columns look tidier when their left edges are straight rather than ragged.
    • This is how font-patcher does alignment, and thus what Nerd Font users and UI designers expect.

Implementation details

I won't get too deep in the weeds here; see the code and comments. In brief:

  • size_horizontal and size_vertical are combined to a single size, which can be .none, .stretch, .fit, .cover or .fit_cover1. The latter implements the pa rule from font-patcher, except it works better for icons that are small before scaling, like the letter Z prompt in the screenshots. In short, it preserves aspect ratio while clamping the size such that the icon .covers at least one cell and .fits within the available space. See code comments and Scale non-mono icons to be at least as large as mono ryanoasis/nerd-fonts#1926 for details.
  • An alignment mode .center1 is added, implementing the centering rule from font-patcher that I explained/defended above. In short, we center the icon in the first cell, even it's allowed to span multiple cells. For icons wider than a single cell, the lower bound that prevents them from protruding to the left kicks in and turns this into left-alignment. We keep the regular .center rule around for use with emojis, et cetera.
  • Scaling and alignment calculations only use the unrounded face metrics and bearings. This ensures that pixel rounding of the cell and baseline, and adjust-cell-{width,height}, don't affect scaling or relative alignment; the icons are always scaled and aligned to the face. (The one place we need to use cell metrics in the calculations is when we use cell_width to obtain the inter-cell padding needed to correctly center or right-align a glyph across two cells.)
    • We can do this with impunity because we're blessed with sprite glyphs in place of the "icons" that are actually box drawing and block graphics characters 🙌

Guide

The meat of the changes is 100 % in src/font/face.zig and src/font/nerd_font_codegen.py. Changes to other files only amount to a) adding/changing some struct fields to get numbers to where they need to be (see src/font/Metrics.zig), and b) collateral updates to make otherwise unchanged code and tests work with/take advantage of the modified structs.

Most files should have a clear and friendly diff. The exception is the bottom half of src/font/face.zig, where the diff is meaningless and the new code should just be reviewed on its own merits. This is the part where the constrain function is rewritten and refactored. Scarred by countless hours perusing font-patcher, I tried hard to make the math and logic easy to follow here. I hope I have succeeded 🤞

@danielwe danielwe requested review from a team as code owners September 8, 2025 02:41
@danielwe danielwe force-pushed the icon_scaling_alignment branch from d1f52fd to 62b36d4 Compare September 8, 2025 02:44
@danielwe danielwe changed the title Rewrite constraint code for improved icon scaling/alignment feat(font): Rewrite constraint code for improved icon scaling/alignment Sep 8, 2025
@danielwe danielwe force-pushed the icon_scaling_alignment branch from 62b36d4 to c686c00 Compare September 8, 2025 05:29
@00-kat 00-kat added the font Issue within the font stack (typically src/font) label Sep 9, 2025
@danielwe
Copy link
Contributor Author

danielwe commented Sep 18, 2025

Realized due to discord thread that this PR's revised horizontal alignment has an additional benefit: If people specify a Nerd Font Mono in their config because they want strictly monospace icons, this PR makes it so that most1 icons behave the way they want. Currently, these icons retain their monospace size but are centered across two cells, often resulting in unbalanced whitespace as in this screenshot from the discord thread:
image
With this PR, these icons will sit more to the left, only occupying only their "own" cell, like the user expects.

EDIT: Encountered another concern about icon alignment in the wild, https://www.reddit.com/r/Ghostty/comments/1nnuyci/icons_not_centered_properly/, which would be addressed by this PR.

Screenshot 2025-09-22 at 22 43 18

Footnotes

  1. This applies to icons with the pa scaling rule, i.e., those that are scaled down to fit but not up, such that when loaded from a Nerd Font Mono where they've already been shrunk to monospace size, they stay that way. Icons with the pa! rule will be stretched back up to two cells, but they are a minority. (Symbols with the xy stretch rule will also be stretched, but they are mostly irrelevant to this discussion, as they tend to be block drawing characters, not icons.)

@danielwe
Copy link
Contributor Author

danielwe commented Sep 23, 2025

This should also fix most of the concerns in #8822 and #8942

@danielwe danielwe changed the title feat(font): Rewrite constraint code for improved icon scaling/alignment fix(font): Rewrite constraint code for improved icon scaling/alignment Sep 24, 2025
Copy link
Contributor

@mitchellh mitchellh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this is really good work. Sorry for the delay. I went through all the changes and I don't see any issues with this on its face.

The only minor issue is we're again comparing floats directly, but hopefully this doesn't cause issues in the future. If it does, its easy to know what's going on (and the values should be CLOSE so we can fix it then).

@mitchellh mitchellh force-pushed the icon_scaling_alignment branch from c686c00 to e3ebdc7 Compare September 29, 2025 19:09
@mitchellh mitchellh merged commit 25dab0e into ghostty-org:main Sep 29, 2025
11 checks passed
@github-actions github-actions bot added this to the 1.2.1 milestone Sep 29, 2025
@danielwe danielwe deleted the icon_scaling_alignment branch September 29, 2025 20:40
mitchellh added a commit that referenced this pull request Oct 3, 2025
Seems like there needs to be a general, easy-to-use solution for
approximate equality testing of containers holding floats (see, e.g.,
#8563 (review)).
How's this?
mitchellh added a commit that referenced this pull request Oct 3, 2025
…ent (#8990)

Follow-up to #8563, which broke scaling without alignment. This change
recovers the behavior from before #8563, such that a scaled group is
clamped to the constraint width and height if necessary, and otherwise,
scaling does not shift the center of the group bounding box.

As a part of this change, horizontal alignment was rewritten to assume
the face is flush with the left edge of the cell. The cell-to-face
offset in the rendering code is then applied regardless of the value of
`align_horizontal`. This both simplifies the code and improves
consistency, as it ensures that the offset is the same for all
non-bitmap glyphs (rounded in FreeType, not rounded in CoreText). It's
the right thing to do following the align-to-face changes in #8563.
mitchellh added a commit that referenced this pull request Oct 3, 2025
This is my final set of fixes to the font patcher/icon scaling code. It
builds on #8563 and there's not much reason to pay attention here until
that one has been reviewed (the unique changes in this PR only touch the
two `nerd_font_*` files; the other 8 files in the diff are just #8563).
However, I wanted to make sure the full set of changes/fixes I propose
are out in the open, such that any substantial edits by maintainers
(like in #7953) can take into account the full context.

I think this and the related patches should be considered fixes, not
features, so I hope they can be considered for a 1.2.x release.

This PR fixes some bugs in the extraction of scale and alignment rules
from the `font_patcher` script. Roughly in order of importance:

* Nerd fonts apply an offset to some codepoint ranges when extracting
glyphs from their original font (e.g., Font Awesome) and placing them in
a Nerd Font. Rules are specified in terms of the former codepoints, but
must be applied to the latter. This offset was previously not taken into
account, so rules were applied to the wrong glyphs, and some glyphs that
should have rules didn't get any.
* Previously, the rules from every single patch set was included, but
the embedded Symbols Only font doesn't contain all of them. Most
importantly, there's a legacy patch set that only exists for historical
reasons and is never used anymore, which was overwriting some other
rules because of overlapping codepoint ranges. Also, the Symbols Only
font contains no box drawing characters, so those rules should not be
included. With this PR, irrelevant patch sets are filtered out.
* Some patch sets specify overlapping codepoint ranges, though in
reality the original fonts don't actually cover the full ranges and the
overlaps just imply that they're filling each other's gaps. During font
patching, the presence/absence of a glyph at each codepoint in the
original font takes care of the ambiguity. Since we don't have that
information, we need to hardcode which patch set "wins" for each case
(it's not always the latest set in the list). Luckily, there are only
two cases.
* Many glyphs belong to scale groups that should be scaled and aligned
as a unit. However, in `font_patcher`, the scale group is _not_ used for
_horizontal_ alignment, _unless_ the entire scale group has a single
advance width (remember, the original symbol fonts are not monospace).
This PR implements this rule by only setting `relative_width` and
`relative_x` if the group is monospace.

There are some additional tweaks to ensure that each codepoint actually
gets the rule it's supposed to when it belongs to multiple scale groups
or patch sets, and to avoid setting rules for codepoints that don't
exist in the embedded font.
mitchellh added a commit that referenced this pull request Oct 6, 2025
This is my final set of fixes to the font patcher/icon scaling code. It
builds on #8563 and there's not much reason to pay attention here until
that one has been reviewed (the unique changes in this PR only touch the
two `nerd_font_*` files; the other 8 files in the diff are just #8563).
However, I wanted to make sure the full set of changes/fixes I propose
are out in the open, such that any substantial edits by maintainers
(like in #7953) can take into account the full context.

I think this and the related patches should be considered fixes, not
features, so I hope they can be considered for a 1.2.x release.

This PR fixes some bugs in the extraction of scale and alignment rules
from the `font_patcher` script. Roughly in order of importance:

* Nerd fonts apply an offset to some codepoint ranges when extracting
glyphs from their original font (e.g., Font Awesome) and placing them in
a Nerd Font. Rules are specified in terms of the former codepoints, but
must be applied to the latter. This offset was previously not taken into
account, so rules were applied to the wrong glyphs, and some glyphs that
should have rules didn't get any.
* Previously, the rules from every single patch set was included, but
the embedded Symbols Only font doesn't contain all of them. Most
importantly, there's a legacy patch set that only exists for historical
reasons and is never used anymore, which was overwriting some other
rules because of overlapping codepoint ranges. Also, the Symbols Only
font contains no box drawing characters, so those rules should not be
included. With this PR, irrelevant patch sets are filtered out.
* Some patch sets specify overlapping codepoint ranges, though in
reality the original fonts don't actually cover the full ranges and the
overlaps just imply that they're filling each other's gaps. During font
patching, the presence/absence of a glyph at each codepoint in the
original font takes care of the ambiguity. Since we don't have that
information, we need to hardcode which patch set "wins" for each case
(it's not always the latest set in the list). Luckily, there are only
two cases.
* Many glyphs belong to scale groups that should be scaled and aligned
as a unit. However, in `font_patcher`, the scale group is _not_ used for
_horizontal_ alignment, _unless_ the entire scale group has a single
advance width (remember, the original symbol fonts are not monospace).
This PR implements this rule by only setting `relative_width` and
`relative_x` if the group is monospace.

There are some additional tweaks to ensure that each codepoint actually
gets the rule it's supposed to when it belongs to multiple scale groups
or patch sets, and to avoid setting rules for codepoints that don't
exist in the embedded font.
mitchellh added a commit that referenced this pull request Oct 6, 2025
…ent (#8990)

Follow-up to #8563, which broke scaling without alignment. This change
recovers the behavior from before #8563, such that a scaled group is
clamped to the constraint width and height if necessary, and otherwise,
scaling does not shift the center of the group bounding box.

As a part of this change, horizontal alignment was rewritten to assume
the face is flush with the left edge of the cell. The cell-to-face
offset in the rendering code is then applied regardless of the value of
`align_horizontal`. This both simplifies the code and improves
consistency, as it ensures that the offset is the same for all
non-bitmap glyphs (rounded in FreeType, not rounded in CoreText). It's
the right thing to do following the align-to-face changes in #8563.
mitchellh added a commit that referenced this pull request Oct 8, 2025
Seems like there needs to be a general, easy-to-use solution for
approximate equality testing of containers holding floats (see, e.g.,
#8563 (review)).
How's this?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

font Issue within the font stack (typically src/font)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants