Skip to content

Add font selection to the text tool with a default in Settings - #186

Open
wangtuchuan wants to merge 1 commit into
realskyrin:mainfrom
wangtuchuan:feat/text-font-picker
Open

wangtuchuan wants to merge 1 commit into
realskyrin:mainfrom
wangtuchuan:feat/text-font-picker

Conversation

@wangtuchuan

Copy link
Copy Markdown

Problem

Text annotations are locked to the bold system font. There is no way to pick a typeface for a label, and no way to set one as the default.

What this adds

Editor — a font button at the front of the text sub-toolbar, followed by a separator, then the existing size slider / swatches / checkboxes unchanged. It opens a menu with "System default" on top and every installed font family below, each title previewed in its own typeface, current choice checked.

  • Picking a font while a text annotation is selected applies to it through the atomic mutation path, so it is undoable.
  • An active text field re-fonts live and re-fits.
  • Selecting an existing text annotation seeds the button with that annotation's font, like size and colour already do.

Settings — a "default text font" row in the capture pane, right after the beautify defaults (the pane that already owns editor-side defaults). Same title / hint / small NSPopUpButton row geometry as its neighbours, plain titles so the closed popup matches the adjacent ones, refreshed on language change.

Model

TextAnnotation gains fontName: String? holding a font family. nil means the existing bold system font, so every existing call site and the Agent CLI behave exactly as before.

All drawing, measuring, editing and re-editing paths now resolve through a single entry point, TextAnnotation.font(named:size:): the family's bold variant when it has one, its regular face otherwise, and bold system font when the family is missing. No hard-coded NSFont.systemFont(..., weight: .bold) remains on the text path.

withFontName(_:) anchors the cap line the same way withFontSize does, so switching fonts does not make the text jump vertically. annotationsEqualEnough compares fontName, so undo snapshots do not miss a font change.

Persistence

One stored value, Defaults.textFontName. Settings edits it; the editor writes it too, consistent with the existing remember-last-used text options (lastTextFontSize, lastTextStroke, …). A family that is no longer installed reads back as nil, so an uninstalled font can never wedge the tool.

FontCatalog is the single provider of the family list for both surfaces (hidden .-prefixed families filtered, sorted by localized name, lists and preview titles cached).

A note on HUDPopupButton colours

The button fills with AdaptiveChrome.subtleFill (hover/pressed: selectedFill) and strokes with AdaptiveChrome.border, without withAlphaComponent — the same way MoreOptionsButton and ToolButton do it.

This matters: those adaptive colours carry their own alpha (subtleFill is black @0.08 in light mode), and withAlphaComponent replaces alpha rather than scaling it. An early revision wrote subtleFill.withAlphaComponent(0.72) and got a near-opaque black button with dark label text in light mode. A comment at the draw site records this.

HUDCheckboxButton uses that same formula today — its unchecked box renders as a dark grey block in light mode. That is existing behaviour and is left alone here to keep this PR scoped; happy to send a follow-up.

Verification

  • bash scripts/compile-check.sh clean, git diff --check clean
  • swift test: 219 tests, 0 failures. New TextAnnotationFontTests (11 cases): nil / empty / unknown family fall back to bold system font; an installed family resolves to itself at the right size; withFontName preserves other fields and round-trips; withFontSize preserves fontName; Defaults normalizes an uninstalled family to nil; FontCatalog filters hidden families.
  • The whole sub-toolbar was rendered offscreen under .aqua and .darkAqua with a long CJK family name to check contrast, truncation and chevron direction in both appearances, then checked on device.
  • Strings added to all eight Localizable.strings, none ending in punctuation.

🤖 Generated with Claude Code

Text annotations were locked to the bold system font. Add a font picker
to the text sub-toolbar and a "default text font" row in Settings.

- `TextAnnotation` gains an optional `fontName` (font family). nil keeps
  the existing bold system font, so old behaviour is unchanged. All
  drawing, measuring and editing paths resolve through one entry point,
  `TextAnnotation.font(named:size:)`: bold variant of the family when it
  has one, regular otherwise, system bold when the family is missing.
- `withFontName` anchors the cap line the same way `withFontSize` does,
  so switching fonts does not make the text jump. Changing the font of a
  selected annotation goes through the atomic mutation path and is
  undoable; an active text field re-fonts live.
- The sub-toolbar gets a `HUDPopupButton` ahead of the size slider. It
  uses `AdaptiveChrome.subtleFill` / `selectedFill` / `border` directly,
  like the other HUD buttons: those colours carry their own alpha, and
  `withAlphaComponent` replaces rather than scales it.
- `FontCatalog` is the single source of the family list for both the
  editor menu (titles previewed in their own font) and the Settings
  popup (plain titles, matching neighbouring popups). Lists are cached.
- `Defaults.textFontName` is one stored value: Settings edits it and the
  editor writes it too, matching the existing remember-last-used text
  options. A family that is no longer installed reads back as nil.
- Localized strings added to all eight languages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant