Skip to content

Typography improvements #2

Open
irvingpop wants to merge 9 commits into
albertnahas:mainfrom
irvingpop:main
Open

Typography improvements #2
irvingpop wants to merge 9 commits into
albertnahas:mainfrom
irvingpop:main

Conversation

@irvingpop

Copy link
Copy Markdown

hey @albertnahas thank you for this awesome project! I wanted to use it for my upcoming app release, but I ran into some issues getting the text to display just how I wanted it. Please accept the following contribution with some improvements, I added testing and took care not to break any existing functionality.

Headline fixes

  • Letter-spacing default → 0. The fontSize × 0.08 default was pushing captions past the canvas edge whenever Inter (or any properly-kerned font) was installed. SVG text now also uses font-kerning="normal" and text rendering="geometricPrecision".
  • Multi-line titleH bug. titleH was hardcoded to fontSize × 2 regardless of line count, so two-line titles shoved the device frame off-canvas. Now computed from actual line count.
  • Multi-line subtitles. Subtitles previously rendered as a single <text> element regardless of newlines in the input. Now they render multi-line via the same tspan path as titles, with layout that flows in the right direction for textPosition=top vs bottom.
  • Forgiving newline parsing. Both titles and subtitles accept real \n/\r\n (config files, programmatic callers) and the literal backslash-n that bash passes from --title "Foo\nBar".

New knobs (all behind opt-in flags, defaults preserve existing behavior)

  • Per-text typography: --title-weight, --subtitle-weight, --title-spacing, --subtitle-spacing, --title-color, --subtitle-color, --title-line-height
  • --font-family with an fc-match-based availability check — warns once when the default Inter stack falls back, throws when an explicit --font-family isn't installed (silently skips on systems without fc-match)
  • --auto-fit-title — shrinks title size to a floor, then falls back to balanced word-wrap

Tests + distribution

  • Vitest unit tests covering firstFamily, estimateTextWidth, computeAutoFit, splitLines, and schema defaults
  • Pixel-diff golden image tests (8 scenarios) at a 0.05% tolerance — regenerable via UPDATE_GOLDENS=1
  • "prepare": "npm run build" so consumers can npx github:<fork>#<ref> without a committed dist/
  • README updated with a typography section, expanded flag reference, and updated examples

All changes are upstream-friendly: no renames, no removed flags, no changed defaults except the strict letter-spacing bug fix.

irvingpop and others added 9 commits April 26, 2026 18:41
Add seven new fields to frameOptionsSchema with backward-compatible
defaults: titleWeight (800), subtitleWeight (500), titleSpacing (0),
subtitleSpacing (0), fontFamily (Inter stack), titleLineHeight (1.15),
autoFitTitle (false).

Wire them through as CLI flags on the frame subcommand:
--title-size, --subtitle-size, --title-weight, --subtitle-weight,
--title-spacing, --subtitle-spacing, --title-color, --subtitle-color,
--font-family, --title-line-height, --auto-fit-title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New src/core/fonts.ts exposes checkFont() and firstFamily().
checkFont() uses execFileSync('fc-match') to verify whether the first
family in a font stack is installed. Returns null on systems without
fc-match (Windows, minimal Linux) so callers can skip gracefully.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… multi-line

Phase 1 — letter-spacing fix:
- Default titleSpacing and subtitleSpacing to 0 (removes the 1.2.1
  overflow bug where letter-spacing = fontSize * 0.08 caused captions
  to exceed canvas width with Inter installed)
- Add font-kerning="normal" text-rendering="geometricPrecision" to all
  <text> elements in buildDeviceTextSvg and buildTextSvg

Phase 2 — wire new typography opts:
- buildDeviceTextSvg and buildTextSvg now use opts.titleWeight,
  opts.subtitleWeight, opts.fontFamily, opts.titleSpacing,
  opts.subtitleSpacing, opts.titleLineHeight instead of hardcoded values
- buildTextSvg gains multi-line support via renderMultilineText

Phase 3a — multi-line title height fix:
- titleH was hardcoded to fontSize * 2 regardless of line count,
  pushing the device off-canvas for multi-line titles; now computed
  as lines.length * fontSize * lineHeight + fontSize * 0.5

Phase 3b — --auto-fit-title:
- computeAutoFit() shrinks titleSize in 0.005 steps down to a 0.04
  floor, then falls back to word-boundary wrap if still too wide
- estimateTextWidth() heuristic: avg char ratio interpolated linearly
  from 0.50 (weight 400) to 0.55 (weight 800)

Phase 4 — font check integration:
- frameScreenshot() calls checkFont() on the resolved fontFamily;
  throws if an explicitly-passed --font-family is unavailable, warns
  once (deduped) if the default Inter stack falls back

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add "prepare": "npm run build" so that npx github:irvingpop/appshots#<ref>
auto-builds dist/ on install without needing a committed dist/ directory.

Bump version 1.2.1 → 1.3.0 to reflect the new typography flags,
auto-fit, and multi-line fixes added in this release.

Add test scripts: test, test:watch, test:goldens, test:update-goldens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unit tests (tests/unit.test.ts — 21 tests):
- firstFamily(): quote stripping, single/multi-family stacks
- estimateTextWidth(): weight interpolation math at 400/600/800
- computeAutoFit(): short title passthrough, font shrink, word wrap
  at floor, explicit \n passthrough, padding sensitivity, floor clamp
- frameOptionsSchema: Phase 1+2 defaults (spacing=0, weights, lineHeight,
  fontFamily, autoFitTitle)

Golden image tests (tests/golden.test.ts — 5 tests):
- Device frame with title+subtitle at bottom (baseline path)
- Auto-fit long title with text at top
- Multi-line title via literal \n
- No-device-frame path (buildTextSvg)
- Custom typography flags (titleWeight, titleSize, titleSpacing)

Goldens live in tests/goldens/ and are compared with pixelmatch at a
0.05% pixel-diff threshold. Regenerate with:
  UPDATE_GOLDENS=1 npm run test:update-goldens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add typography example to Quick Start (--title-weight, --title-size,
  --auto-fit-title, multi-line \n)
- Expand CLI reference table with all 11 new frame flags
- Add new typography fields to the config file example
- Add Typography section covering multi-line titles, auto-fit behavior,
  and font requirements
- Update programmatic API example to show new options

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subtitles now render multi-line via the same tspan path as titles, with
layout that adjusts automatically: in textPosition=bottom the subtitle
flows upward and pushes the title further up; in top it flows downward;
the no-device-frame path recomputes subtitleH from line count so the
screenshot area shrinks instead of getting clipped.

A shared splitLines() helper accepts real \n, \r\n, and the literal
backslash-n / \r\n sequences bash passes from --subtitle "Foo\nBar",
so config files and programmatic callers work as well as the CLI.

Subtitle line-height is hardcoded to 1.3 — no new flag.

Adds 3 new golden tests (multi-line subtitle bottom, multi-line both
top via real \n, no-frame multi-line subtitle) and unit tests for
splitLines covering all four newline forms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #1. The hardcoded phoneScale ratios in getFrameConfig (0.775 phones,
0.88 tablets) produce inconsistent canvas-fill rhythm across devices when
framing the same project. Adds an optional override at the schema, CLI, and
config-file level — default behavior is unchanged when the flag is unset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat: add --phone-scale flag to override per-device phoneScale
@irvingpop

Copy link
Copy Markdown
Author

@albertnahas any feedback?

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