Add variable font weight axis support#65
Conversation
|
Nice! Agreed that weight only is good to start; I suggest doing all axes for instance selection (so Kablammo or Recursive can pick a precis style desires) and tying optical size to font-size, as a middle step between this and motion typography. Also STAT table support to select named styles from a drop-down, per axis, as demonstrated in lorp.github.io/samsa - would be another great waypoint. Poppins and Skis are available as variable fonts btw |
|
Thanks! Agreed all of those are good follow-ups — wanted to keep this PR scoped to just weight so it’s easy to review. Happy to look at STAT/named-instance support as a next PR if that’s useful direction-wise. Also noted re: Poppins/Skia — makes sense, the bundled files here are static, but good to know variable versions exist. |
…ontWeight, palmier-io#92 wordsPerCaption - Add transcriptionLanguage field to Timeline (skip_serializing_if None) - Add fontWeight (f64, default 400) to TextStyle - Add wordsPerCaption param to add_captions tool input - Add language param to inspect_media, get_transcript, add_captions tools - Update tool descriptions per upstream - Port all corresponding tests
a069601 to
5ebccc1
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5ebccc1. Configure here.
Adds fontWeight (400 regular / 700 bold, TextStyle field from PR palmier-io#65) to the per-entry text styling parsed by cmd_add_texts, so bold titles can be created directly. Schema updated; test now asserts weight 700 applies.
…ontWeight set_clip_properties had grown to 12 positional Option args and still could not set three TextStyle fields the model supports: font_weight (PR palmier-io#65) and background/border fills (Issue palmier-io#18). - Replaced the positional signature with a ClipPropertyUpdate struct (all callers updated; test call sites are now readable). - Added font_weight, background, border handling; the executor parses fontWeight plus background/border objects ({enabled, color, padding, cornerRadius}) via a shared parse_text_fill helper. - Reconciled the previously-orphaned mutation::validate_set_clip_properties, which had drifted to old field names (text/textColor/textAlignment/ textBackground/textBorder, trimStart/trimEnd), to the executor + Swift convention (content/color/alignment/background/border, trimStartFrame/ trimEndFrame). Its Issue palmier-io#18 / MUT-010 / MUT-012 tests updated to match. - Schema documents the full text-clip field set. New executor tests cover fontWeight, background (with padding/cornerRadius), border, and invalid-fill-colour rejection. Closes the set_clip_properties half of the flagged text-styling gap.
…ier-io#65 compat bug Following the adversarial audit of the FCPXML keyframe/title output (which confirmed the keyframe time axis and position top-left→centre coupling are faithful): - Title border now emits glyph strokeColor/strokeWidth (0.04*fontSize when border.enabled), closing a documented deferral. 1 test. - Regression test: a keyframed clip starting at frame 100 emits clip-relative keyframe times (0s, 30/30s), not absolute — while the clip offset stays absolute (100/30s). Locks the verified axis. Also records a real finding the audit surfaced: palmier-io#65 TextStyle DIVERGES from the current Swift on-disk format. Swift stores isBold/isItalic (bools, bold-default, fontName-inferred); Rust stores font_weight (f64, default 400) and reads neither — so Swift-authored bold/italic text loads as regular in Rust and Rust's fontWeight is dropped by Swift. Documented in the audit + porting tables as a careful high-stakes data-model fix (serde bridge + is_italic field), not rushed here. The FCPXML face is correct for Rust-authored data.
…r-io#254) Extracted append_opacity_blend (static or keyframed <adjust-blend>) and shared it between asset-clips and titles, so animated text opacity now round-trips too (titles previously got static opacity only). 1 title-keyframe test; the asset-clip opacity tests validate the refactor. FCPXML palmier-io#254 geometry is now comprehensive for the Resolve target: static + keyframed transform/crop/opacity, volume (incl. collapsed-pair partner gain), enabled, source timecode, relink-by-filename, and text titles (font/color/ alignment/border-stroke/transform/opacity). Remaining: FCP-target value encoding (needs a target-selection decision), no-explicit-transform auto-fit, and the palmier-io#65 TextStyle isBold/isItalic on-disk-format compat fix.
…/isItalic) The Rust TextStyle stored font_weight (f64) while current Swift (9a3ae50) stores isBold/isItalic bools — so Swift-authored bold/italic text loaded as regular in Rust and Rust's fontWeight was dropped by Swift (bidirectional data loss on the .palmier format). Implemented a `#[serde(from/into)]` TextStyleWire bridge: - LOAD accepts EITHER fontWeight (Rust) OR isBold (Swift → 700/400); fontWeight wins when both present. isItalic loads into a new TextStyle.is_italic field. - SAVE writes BOTH fontWeight AND isBold/isItalic, so a .palmier written by either app round-trips bold + italic. Additive — no key removed, so existing Rust readers are unaffected (the full-project round-trip test still passes). - FCPXML title fontFace now reflects bold AND italic (Bold/Italic/Bold Italic/ Regular), mirroring Swift fontFaceFallback. is_italic ripples to the (few) full TextStyle literals; Default = false. 3 new compat tests (Swift keys load, both-key precedence, dual-write round-trip) + the existing palmier-io#65 fontWeight test + full-project serde round-trip. Surfaced 2026-07-05 by the adversarial FCPXML title audit.
The adversarial audit of the palmier-io#65 serde bridge confirmed it is faithful (no data loss/corruption — every field verified both directions, backward-compat additive, no panics), and flagged one gap: fmt_009_timeline_round_trip_ preserves_all_fields asserted only 5/13 TextStyle fields despite its name, so a FUTURE dropped field would slip through. Now also asserts color, is_italic, shadow (offset/blur), background, and border — so a wire-bridge regression that drops a field fails this round-trip test.
…r-io#169 palmier-io#67 palmier-io#164) palmier-io#45 arrow/line shapes: compositor rasterize_line_or_arrow — endpoints normalized 0..1 of the box (documented assumption; shapes are Rust-native, no Swift rasterizer), horizontal-centre default, Arrow barbs scaled to stroke width. 3 golden tests. palmier-io#65 variable-font wght: ab_glyph 0.2.32 supports the wght axis (VariableFont::set_variation) — applied in render_text (no-op on static faces). Wired the bundled variable families (Inter/Geist/GeistMono/DMSans/ Caveat/PlayfairDisplay/SpaceGrotesk) into font_for so the axis is reachable, matching Swift BundledFonts. 2 tests. palmier-io#169 viewer guides: completed the Guides dropdown UI over the existing palmier-io#167 overlay/state — guide_menu_rows + ⊞ button toggle + toggle_guide, mutually exclusive with the settings menus. Pure-function test. palmier-io#67 project-card Duplicate: card menu entry (accessible-gated) → host-side plan_duplicate + recursive package copy → "(Copy).palmier" + recents register + Home cards refresh. Menu + fs-copy tests. Hub save_now() absent (data slice palmier-io#211) — Home-transition autosave deferred. palmier-io#164 shortcut parity: added [ / ] trim aliases and Shift+Backspace ripple delete (menu.rs route table 44->47, global_shortcuts !input bindings, app_root RippleDeleteSelection handler). V/C/A/Shift+A/Esc deferred — no tool-mode/deselect concept yet (gap list in tasks.md). Gates (exit 0): render_core lib 263, app_shell_gpui desktop-app lib 407, cargo check --bin fronda.
palmier-io#211 autosave, palmier-io#138 HDR export, palmier-io#176 duplicate_clips (56→57), palmier-io#284 aspect labels, palmier-io#45 arrow/line shapes, palmier-io#65 wght variable-font axis, palmier-io#169 viewer guides, palmier-io#67 project-card duplicate, palmier-io#164 shortcut parity (+ cross-slice autosave/aspect wiring). Deferred (feature-absent, documented): V/C/A tool shortcuts, HDR runtime verify (no libx265 in CI).

Closes #50.
Adds a
fontWeightproperty toTextStyleand a Weight slider in the text inspector's Typography section, applied via CoreText'swghtvariation axis inresolvedFont.BundledFonts.swiftalready handled variable font registration correctly (dedups by family, per the existing comments) — the actual gap was downstream: the font picker andTextStyleonly ever carried a font name, so nothing could select or apply a font's weight range even though it loaded correctly.The slider is disabled (and visually dimmed) for fonts with no
wghtaxis, detected viaCTFontCopyVariationAxes— so static fonts like Poppins or Skia don't show a control that silently does nothing.Scoped to static weight selection, addressing #50. Full animatable motion typography (keyframing weight/width over time) would be a separate, larger follow-up.
Tested locally and verified that: — confirmed the slider visibly changes rendered weight on bundled variable fonts (Geist, Inter, SpaceGrotesk, etc.), correctly stays disabled on static fonts (Poppins, Skia, Anton), and that existing saved project files without a
fontWeightfield still load correctly.Note
Low Risk
Localized text styling and rendering changes with safe fallbacks; minor layout risk from removing explicit word-wrap on paragraph style.
Overview
Adds
fontWeight(default 400) toTextStylewith backward-compatible decoding, and a Weight scrubber in the text inspector Typography section that previews/commits through the existing text-style editor and refits clip bounds like font size.Rendering now applies weight via CoreText’s
wghtvariation axis inresolvedFont, with NaN/infinity clamping and a safe fallback when variation resolution fails.fontSupportsWeightAxisusesCTFontCopyVariationAxesso the slider is disabled and dimmed for fonts without a weight axis.paragraphStyleno longer setslineBreakModeto word wrapping. A small unit test coversfontWeightdefault and JSON round-trip.Reviewed by Cursor Bugbot for commit 84363a3. Bugbot is set up for automated code reviews on this repo. Configure here.