Commit graph

19 commits

Author SHA1 Message Date
Danila Poyarkov a2fc235131
fix(text): render variable font styles at their named instances (#773)
* fix(text): render variable font styles at their named instances

Installed variable fonts such as SF Pro list every named instance, but
font-kit loads each one at the default weight, so the desktop loader
rejected Medium and Bold and the canvas fell back to a substitute. Even
when a variable face was loaded, CanvasKit drew it at its default axes:
Medium rendered as Regular and Bold as a synthetic bold.

The desktop loader now falls back to a variable face whose wght axis
covers the requested weight. The renderer applies the coordinates of the
named instance matching the style, or the clamped weight when none
matches, beneath any explicit font variations on the text.

* fix(text): validate variable font tables and leave loading to the host

Check name-table records and string ranges, the fvar header size, and
axis and instance record sizes, so a malformed font falls back to the
style weight instead of throwing while text is shaped.

Drop the desktop loader's variable-face fallback; system font discovery
moves to fontique, which lists variable faces with their weight axes.
2026-09-26 11:25:01 +04:00
Danila Poyarkov d4f34abdb2
fix(fonts): explain PingFang substitution and draw its CJK text (#781)
* fix(text): request script fallbacks for substituted text

When a text's font could not be loaded and the default family
substituted for it, font readiness returned before checking glyph
coverage. That check is what requests CJK and Arabic fallbacks, so text
such as Chinese in an unavailable PingFang SC drew missing glyphs unless
another layer happened to request the fallback first.

Substituted text now observes glyph coverage too. It waits while a
fallback loads and stays visible when none is available.

* fix(fonts): explain installed fonts with unsupported outlines

On macOS 15 and later PingFang ships only `hvgl` outlines, which neither
font-kit nor CanvasKit can read. The desktop loader spent over a second
parsing the collection per style, and the font banner showed PingFang as
substituted with no explanation.

The loader now reads the family's table directories first and returns a
structured unsupported-format error. The font manager records it per
face, document font status exposes it as `reason`, and the banner shows
it inline with the full explanation in a tooltip. The resolver reports
progress after each failed candidate so the banner updates before web
font lookups finish.

* fix(fonts): keep the unsupported-format reason after failed retries

A later host attempt that returns no font no longer clears the reason; only a loaded face does.
2026-09-26 11:14:30 +04:00
Danila Poyarkov 45c401cbf5
fix(text): preserve cached font coverage and source offsets 2026-09-02 09:26:59 +03:00
Danila Poyarkov 3caf5c99d9 fix: resolve lint warnings 2026-09-02 01:49:37 +03:00
Danila Poyarkov 0f64cecc49
fix(text): finalize font readiness and label shaping (#593)
* fix(text): prepare browser fonts atomically

- Fetch approved Fontsource resources directly in browsers with bounded responses and retryable provider failures
- Limit page font resolution concurrency and retain live Inter substitution paragraphs after baked glyph invalidation
- Shape frame, section, and component labels through a bounded native Paragraph cache
- Cover real Geist, Geist Mono, and Roboto Mono browser loading plus substitution and cache lifecycle behavior

* test(canvas): cover substitution and label shaping

- Capture baked missing-font text before editing, live Inter substitution on first input, and visible undo output
- Assert text-picture and derived-glyph invalidation with finalized substituted readiness
- Cover shaped frame, rotated frame, section, component, component-set, ellipsis, kerning, and zoom label presentation

* fix(text): preserve same-origin fetches during font resolution

- Route same-origin application and fixture requests through native fetch while Unifont temporarily proxies global HTTP requests
- Keep the external provider HTTPS allowlist enforced for cross-origin font resources
- Cover the production race discovered while loading gold-preview.fig during provider initialization

* fix(text): preserve substituted path glyphs

- Keep imported derived curved glyphs for text-on-path layers when exact fonts finalize as substituted
- Cover substituted path rendering through the runtime renderer and refresh the corrected visual oracle
- Update shaped Inter measurement badges and merged typography panel snapshots after visual inspection
- Search virtualized font catalogs explicitly and restore the canvas screenshot helper used by broad E2E coverage

* fix(text): use browser font transport without desktop warning

- Keep the browser provider implementation aligned with current settings behavior after splitting from preparation

* test(text): inject same-origin browser font context

- Keep the transport test deterministic outside a Window global

* fix(text): preserve final browser font transport hardening

- Carry the same-origin large-document bypass and bounded cross-origin provider checks from the preserved integration snapshot

* fix(text): initialize font transport outside Window contexts

- Keep unit and headless module imports safe while browsers use their current origin

* test(text): satisfy async visual fixture contract

- Return from the resolver setup continuation after requesting render

* test(canvas): include paragraph cache lifecycle

* fix(text): cancel queued browser font loads

- Race serialized provider work against preparation cancellation\n- Release queue slots after cancelled waiters without disturbing active requests\n- Reuse one section-title paragraph cache entry for measurement and drawing

* fix(text): close font queue cancellation races

- Release reserved proxy queue slots when cancellation lands after queue acquisition\n- Skip paragraph work for zero-width section labels

* fix(text): abort active provider resolution

- Race active provider resolution against its preparation signal and restore the temporary fetch proxy promptly\n- Forward cancellation through proxied provider requests\n- Align the renderer font-readiness facade with substituted text
2026-08-30 14:33:32 +03:00
Danila Poyarkov 6f5638380a
feat(app): prepare documents atomically per tab (#592)
* feat(app): show atomic document loading progress

- Preserve the existing full-canvas pencil loader while adding phase, detail, accessible status, and honest determinate progress
- Keep one generation-safe load owner across FIG decoding, graph preparation, page population, fonts, fallbacks, layout, viewport fitting, and first-render fade
- Prevent nested page setup and viewport cleanup from revealing partially prepared documents
- Cover obsolete sessions, font-resolution ownership, and staged loader UI

* refactor(app): scope editor preparation per tab

- Replace the shared loading boolean with one reactive preparation snapshot and one imperative controller per editor store
- Keep Core page work progress-only and inject canvas suspension from the app boundary
- Route FIG, storage, recovery, DOM import, and page switching through reusable tab-local preparation handles
- Abort only the closing tab's operation and cover generation safety, multi-tab isolation, progress UI, and disposal

* fix(editor): commit prepared pages atomically

- Prepare population, fonts, fallbacks, and layout without changing the visible page
- Reject cancelled and stale prepared pages before committing viewport, selection, and page events
- Keep the preparation overlay until the committed scene version is presented
- Cover call order, cancellation, stale generations, and presentation acknowledgement

* fix(app): stage imported documents before commit

- Prepare imported graphs in an isolated Core editor before replacing the live document
- Share font loading while keeping live selection, graph, renderers, and history untouched during staging
- Preserve the previous graph when staging is cancelled or fails and remove the duplicate pre-font layout pass

* refactor(app): namespace preparation UI

- Move canvas and tab preparation presentations into focused subfolders with concise component names
- Share progress and phase presentation helpers across preparation surfaces
- Show tab-local preparation status without covering the active canvas for background work

* fix(app): cancel preparation work at source

- Publish typed per-store preparation lifecycle events with explicit completion, cancellation, and failure outcomes
- Propagate tab-local AbortSignals through FIG parsing, population workers, and browser font downloads
- Keep cancellable font requests outside shared in-flight caches while retaining globally completed font registrations
- Stop FIG manifest previews from replacing the live graph before atomic document commit

* fix(app): cancel storage and DOM preparation

- Propagate preparation signals through S3 downloads, byte progress, local-cache boundaries, and DOM/CSS conversion checkpoints
- Reuse merged diagnostics and localized toasts for document, storage, and presentation failures
- Replace manual font concurrency and presentation timers with es-toolkit limitAsync and withTimeout
- Guard stalled first presentation and fix the merged recovery dialog title bindings

* fix(app): stage reload and font retry

- Prepare reload graphs in isolation and preserve the current document on read, decode, font, or layout failure
- Restore page and viewport state only after atomic graph commit with cancellable reload reads
- Run font Retry as a tab-local preparation with cache reset, final layout, picture invalidation, and presentation acknowledgement
- Keep completed document pixels visible while Retry reports activity in the tab

* fix(app): enforce exclusive preparation outcomes

- Complete document, storage, recovery, and DOM preparations only after successful commit
- Keep failed and cancelled handles terminal so lifecycle events cannot report contradictory outcomes
- Preserve external AbortError identity across storage timeouts and cancel streamed readers without returning partial bytes
- Cover credential-free pre-abort, mid-stream cancellation, progress cutoff, and terminal outcome exclusivity

* feat(diagnostics): record preparation outcomes

- Persist completed, cancelled, and failed preparation lifecycles through the validated diagnostics recorder
- Store only operation kind, outcome, cancellation or failure category, terminal phase, and coarse duration bucket
- Exclude document subjects, font families, storage identities, URLs, raw durations, messages, and stack traces

* chore(app): keep browser font tests with typography split

- Remove the browser font transport test inherited from a mixed cancellation commit; the source and coverage remain on the typography branch and safety snapshot

* test(vue): assert injected render suspension

- Exercise shouldSuspendRender instead of removed Core loading state\n- Preserve the contract that rendering resumes without a version change

* test(app): complete atomic preparation contracts

- Acknowledge first presentation in headless file-open tests\n- Assert the cancellable font-loading signature at the Tauri fallback boundary

* fix(app): preserve preparation cancellation

- Stage imported graphs before mutating live tabs and propagate aborts through page, DOM, font, and storage work\n- Use the accessible progress primitive and clamp determinate values\n- Cover fallback-font cancellation and yield pending-open test polling to the task queue

* test(text): await fallback font request cancellation

Start the mocked remote font request before aborting so the test proves that the active request receives the preparation signal.
2026-08-30 12:21:49 +03:00
Danila Poyarkov 1eb1f38c78 feat(text): surface missing font substitutions
- Track loaded font provenance and aggregate page-level face fidelity, including synthesized styles and active Inter substitutions
- Add a localized warning banner with retry and affected-layer actions
- Expose get_font_status through the Figma API and MCP tool registry
2026-08-13 18:33:10 +03:00
Danila Poyarkov 72dee43e44 fix(text): share fonts across canvas layers
- Register loaded faces with both scene and overlay renderers\n- Keep provider registration state isolated per CanvasKit provider
2026-07-20 22:19:27 +03:00
Danila Poyarkov cf857dbf80 fix(text): preserve local font rendering (#395)
- Reject text pictures captured before the current font generation
- Keep text visible when CanvasKit must synthesize a missing italic face
2026-07-20 14:07:47 +03:00
Danila Poyarkov 9919d8710a fix(text): verify visible international font rendering
- Register CanvasKit faces under their source family names so CJK and Arabic glyphs paint instead of only shaping metrics
- Add deterministic CJK, Arabic, mixed-script, first-paint, and interactive fallback image oracles
- Cover typed and tool-created text parity with a repository-owned Noto CJK subset

Fixes #324
Fixes #291
2026-07-17 16:19:18 +03:00
Danila Poyarkov 47fe43c3ec feat(text): harden font resolution lifecycle
- Gate ingest rendering until required faces and script fallbacks resolve
- Key CanvasKit artifacts by font registration generation and invalidate affected nodes
- Load character-aware remote subsets with cumulative coverage and exact render aliases
- Use BCP-47 language hints for shaping and CJK fallback order
- Keep baked .fig glyphs as an exhaustion-only fallback
2026-07-17 14:52:35 +03:00
Danila Poyarkov 4b3d97ce0d feat(text): add demand-driven font resolver
- Resolve exact faces through registered, local, cache, and remote sources with deduplicated state transitions\n- Use CanvasKit notdef glyphs to request script fallbacks and repaint after resolution\n- Make CJK coverage analysis codepoint-safe across supplementary characters and style runs
2026-07-17 13:00:09 +03:00
Danila Poyarkov 2c171139c1 fix(text): prefer script-specific CJK fallbacks 2026-07-01 22:19:14 +03:00
Danila Poyarkov 1ffc61cf50 fix(text): bundle Inter ExtraBold font 2026-05-18 00:59:05 +03:00
Danila Poyarkov 5d71e810b6 fix(text): preserve requested font weights 2026-05-17 20:40:39 +03:00
Danila Poyarkov b46722a50b fix(text): prefer matching local font slant 2026-05-17 20:33:13 +03:00
Danila Poyarkov 3534a4a919 fix(canvas): render exact font faces 2026-05-17 13:40:56 +03:00
Danila Poyarkov a710841392 test(engine): align io tests with source tree 2026-05-16 11:59:02 +03:00
Danila Poyarkov 9616094766 refactor(text): group derived text helpers 2026-05-16 11:56:35 +03:00