Commit Graph
28 Commits
Author SHA1 Message Date
skylar taylor-barrick 09c7f60567 PDFW2: integrate current main with shared PDF writing 2026-10-14 21:00:04 -06:00
Brandon Thomas 54d3f19b5b Merge PR #738 from xmaks82: Fonts: falling back to the default face no longer deadlocks 2026-10-14 21:59:38 -04:00
skylar taylor-barrick 0cbe475f13 fix(pdf): reconcile writer with notes and handout masters (PDFW)
Preserve notes and handout master export, visible text and native parallel
compression while propagating shared font errors through each export path.
Keep the dependency check accurate for native and WASM feature resolution.

PR #708.
2026-10-14 19:44:21 -06:00
Магда М.Г. 8c3404fc87 Fonts: falling back to the default face no longer deadlocks
cascade's last fallback searched the loaded faces and, finding none that
draws the cluster, called `first` while the search's read guard was still
alive (a temporary in the match arm lives to the end of the `let`).
`first` looks up the default family, which loads it through add_fonts,
and add_fonts takes the faces' write lock even when there is nothing to
add: the thread waited on itself, and every other thread then waited on
the loading lock it held.

It shows when a run's chain is empty and nothing loaded draws the
character before the default family has been looked up, so it depends on
which test (or document) gets there first: deckcraft-text's tests hung in
4 of 10 parallel runs here (all threads parked in system_fallback /
try_family, one in add_fonts' write), and in 0 of 15 with this change.

The search now finishes before `first` runs. New test
falling_back_to_the_default_face_does_not_deadlock reaches that path on a
fresh database; it times out on main and passes with the fix.
2026-10-14 20:35:43 +02:00
Brandon Thomas a0d97fa931 Merge PR #644 from NikitkaRa: Startup: survive damaged settings, font folders, recovery folders and small texture limits
# Conflicts:
#	crates/engine/src/recovery.rs
#	crates/fonts/src/fontdb.rs
#	crates/fonts/src/lib.rs
#	crates/ui-egui/src/lib.rs
2026-10-14 14:24:45 -04:00
Brandon Thomas 20cf13a801 Merge PR #491 from NikitkaRa: Fonts: find fonts where Linux and Flatpak keep them; add and rescan commands (#277)
# Conflicts:
#	crates/engine/src/tests.rs
2026-10-14 13:08:45 -04:00
Brandon Thomas 78b6c2982f Merge PR #439 from NikitkaRa: Pptx: read and write embedded fonts
# Conflicts:
#	crates/pptx/tests/roundtrip.rs
2026-10-14 13:08:43 -04:00
Brandon Thomas 973abff543 Merge PR #351 from dotCooCoo: Fonts: the font scan leaves out faces with no outline tables 2026-10-14 12:57:23 -04:00
Brandon Thomas 2578a92e96 Merge PR #80 from LloydNicholson: Fonts: a lookup finds an installed family whichever thread loads it first 2026-10-14 12:57:18 -04:00
Nikita RakitinandCursor c583480010 Startup: survive damaged settings, font folders, recovery folders and small texture limits
- ui.json / prefs.json are read through a new settings module: regular files only, 4 MiB
  cap, parse errors logged, values sanitized (a saved zoom of 1e30 aborted start-up,
  1e4 panicked in egui); writes are atomic.
- The canvas and the control protocol clamp stray zooms; the status bar shows the zoom in use.
- The canvas texture is cut to the GPU's max texture side (zoom 400% on a 2048 limit panicked).
- The system font scan opens only regular files of sane size (a FIFO named *.ttf in ~/.fonts
  hung start-up), with depth and entry caps.
- AutoRecover skips non-regular entries and reopens at most 32 per start, newest first.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-14 03:27:57 +03:00
skylar taylor-barrick 7d5dd85209 UI-FONTS: continue fallback selection past ineligible faces 2026-10-13 13:39:33 -06:00
Nikita RakitinandCursor eec67560df Fonts: find fonts where Linux and Flatpak keep them; add and rescan commands (#277)
The font menu lists every family the scan of the system font folders finds,
but on Linux the scan only looked in /usr/share/fonts, /usr/local/share/fonts,
~/.fonts and ~/.local/share/fonts. Fonts under XDG data dirs, in folders
fontconfig is configured with (NixOS, Guix, custom setups) and, inside
Flatpak, the host's fonts never showed.

- system_font_dirs adds $XDG_DATA_HOME/fonts and each $XDG_DATA_DIRS/fonts,
  Flatpak's /run/host/{fonts,local-fonts,user-fonts}, and the <dir> entries
  of /etc/fonts/fonts.conf, local.conf and conf.d; duplicates are dropped.
- font.add {data} adds a font from its bytes for the session; font.rescan
  catalogs the installed fonts again, and the font menu has a
  "Rescan Installed Fonts" item for fonts installed while DeckCraft runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 21:16:23 +03:00
NikitaandCursor 82dcd86c88 Pptx: read and write embedded fonts
A deck that embeds a TrueType or OpenType face, including one wrapped as Embedded OpenType, now draws with that face and saves it back. MicroType Express subsets stay in the file and still draw in a stand-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-13 18:22:48 +03:00
Brandon ThomasandClaude Opus 5.5 6912b5df76 Fonts: a letter with combining marks the run's font lacks takes a face that has them all
"Z̴̡̛͓a̸̧̘l̵̩g̶̞o̸̤" in an Aptos run drew its marks as missing-glyph boxes: no face in the chain
covered the whole cluster and the fallback for its base letter neither, so the run's own font was
kept. PowerPoint draws the marks stacked. A loaded plain face that covers the whole cluster is
now taken before falling back to the run's font. w09-scripts s1 8.40 -> 7.86.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon ThomasandClaude Opus 5.5 bab3854484 Text: strikethrough at the font's strikeout position and thickness
The strikethrough was drawn 0.3 em above the baseline, 0.05 em thick, for every font. PowerPoint
uses the font's own strikeout metrics (OS/2): Arial 96 pt strikes rows 347-351, 24.8 px above the
baseline (530/2048 em) and 4.8 px thick; DeckCraft drew 343-347. Faces now carry their strikeout
metrics (0.26/0.05 em when a font has none) and the line matches PowerPoint's rows exactly.
w14-xml s11 4.06 -> 3.24.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon ThomasandClaude Opus 5.5 3d22ca642d Fonts: only steer a symbol away from the colour font when that is the face it would get
The previous change sent every text-presentation pictograph to a symbol font, which also moved
bullets such as "▪" (Wingdings substitutes) from the face they had to a wider one: necc-
presentation-0 s13 18.78 -> 19.72. Now a pictograph is redirected to a plain face only when its
fallback would be the colour emoji font; keycaps and U+FE0E sequences still take a plain face.
necc s12-s14 back to 26.38 / 18.78 / 20.91; w14-xml s7 (pictographs) 9.79 -> 7.49 overall.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon ThomasandClaude Opus 5.5 232501a9f3 Fonts: emoji presentation as PowerPoint draws it; keycaps and text pictographs in a symbol font
Observed on PowerPoint's export of pictographs in Aptos, Arial and Segoe UI:
- characters that default to emoji presentation draw in colour without a selector: ⭐ ✅ ☔ ⌚ ⏩
  and the other BMP Emoji_Presentation characters (DeckCraft only knew the supplementary-plane
  ones and drew ⭐ ✅ as text);
- text-presentation pictographs (⚠ ♥ ✔ ☎ ⬆ ➡ ▶ ⤴) draw as text even where the colour font is the
  first installed font that has them: they now come from a plain symbol font, symbol families
  first, never the colour one; and a symbol after an emoji no longer inherits its colour face;
- keycap sequences ("1️⃣", "#⃣") draw as text, an outlined key, not the colour keycap.

w14-xml s7 (pictographs) 9.79 -> 6.16, w09-scripts s1 (emoji) 11.56 -> 10.27.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
Brandon ThomasandClaude Opus 5.5 08bc46c0ea Text: underline styles (double, heavy, dotted, dashed, wavy) at the font's underline position
Every underline was a solid line 0.06 em thick at 0.12 em below the baseline, whatever a:rPr/@u
asked for. Underlines now sit at the font's own underline position and thickness (post table),
where PowerPoint draws them (Arial 96 pt: 10 px below the baseline, 7 px thick), and each style
is drawn as measured on PowerPoint's export: dbl two lines 0.55 thick spanning one thickness above
to the single line's bottom; heavy from one thickness above to 0.55 below; dotted dots and gaps one
thickness long; dashes three long with gaps of two; wavy a thin zigzag every 3.9 thicknesses. The
"words" style underlines spaces too, as PowerPoint draws it. Dashes and waves are new Deco line
kinds the renderer fills or strokes (warped text draws their boxes).

Underline slides: w14-xml s10 11.49 -> 10.43, s11 6.64 -> 4.06; w06-charfmt s3 9.66 -> 9.51,
s4 7.63 -> 7.44.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 06:48:43 -04:00
dotCooCoo 180cb4f36f Fonts: the font scan leaves out faces with no outline tables
The font scan also listed faces with none of the tables skrifa reads
glyph outlines from (glyf, CFF, CFF2, VARC): faces whose glyphs are
bitmaps only or are stored as outlines in Apple's hvgl table.
FontDb::outline, which the renderer reads glyph outlines from, returns
an empty path for every glyph of such a face. On macOS 27.0.1 the scan
lists "GB18030 Bitmap" (/System/Library/Fonts/Supplemental/NISC18030.ttf),
which stores its glyphs as bitmaps (bdat) and has no color glyph
tables, so the renderer draws none of the 27,544 characters it maps.

file_face_names now leaves out a face whose table directory has none of
those tables, as vectorcraft's font scan does (storytold/vectorcraft#781).
2026-10-12 21:43:39 -07:00
Brandon ThomasandClaude Opus 5.5 85366b1a62 Fonts: Office's downloaded fonts are used; opening a file says which fonts are missing (#326)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 20:34:54 -04:00
lloydandClaude Opus 5.5 cc8e2eeb4b Merge storytold/main into fix-flaky-flipped-click-test
Conflict in crates/fonts/src/lib.rs: both sides added tests at the same
place; all three are kept. #322 independently serialised font loading in
try_family (loaded_families). This branch's loading lock and batched
add_fonts stay for the system-fallback path, but load_family no longer
skips a family with some face loaded: a lookup by a Windows family name
loads only that style's file, and the family's other files must load
later (windows_family_names_find_their_faces).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:21:02 +02:00
Brandon ThomasandClaude Opus 5.5 55a53f8ecb Fonts: missing fonts draw as PowerPoint draws them, and Windows family names resolve (#322)
* Text: a missing font is drawn in Calibri, as PowerPoint does

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fonts: Windows family names find their faces; PowerPoint's twins for missing fonts

"Segoe UI Semibold", "Arial Black" and the like are the faces' Windows (GDI) family
names; a family's installed files now load together; Liberation Sans and Serif draw as
Arial and Times New Roman when missing, as PowerPoint draws them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fonts: Office cloud fonts (Aptos…) keep their open substitute

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 19:05:48 -04:00
Brandon ThomasandClaude Opus 5.5 ba56742fa9 Emoji: drawn in colour, with families, skin tones and flags joined (#278)
* Emoji: colour glyphs, and emoji take the colour emoji font

Emoji were drawn as black outlines from a symbol font, with ZWJ sequences
(families, flags) falling apart into their parts. Now:

- an emoji cluster (emoji presentation, U+FE0F, ZWJ between pictographs,
  skin tones, regional indicators, keycaps) takes an installed colour font
  (COLR, CBDT, sbix or SVG tables) before the document's fonts, so its
  sequences ligate as in PowerPoint; U+FE0E keeps a plain face;
- COLR v0 and v1 glyphs are painted in their CPAL colours (solid fills,
  linear, radial and sweep gradients, transforms, composite modes), with
  palette index 0xFFFF in the text colour.

A tiny COLRv0 test font (two squares, CC0, regenerated by
make_colr_font.py) backs the render test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Render: lock skrifa for colour glyphs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:48:51 -04:00
Brandon ThomasandClaude Opus 5.5 2e68504e7c Text: single-spaced lines are 1.2 times the font size apart, as in PowerPoint (#266)
* Text: single-spaced lines are 1.2 times the font size apart, as in PowerPoint

PowerPoint spaces single-spaced lines 1.2 x the font size apart in every
font (measured on twenty Latin, CJK, Thai and Indic fonts at 18 and 40 pt
with PowerPoint's slide export) and puts the baseline where the font's
Windows ascent/descent split it. DeckCraft used each font's own ascent plus
descent, so Calibri lines were 15% too tight and Segoe UI ones too loose.

FontFace gains line_split (usWinAscent / (usWinAscent + usWinDescent));
line_metrics uses it with LINE_PITCH = 1.2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Text: tidy the line pitch test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Fonts: line_split guard reads plainly (clippy)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Text: note that the 1.2 line pitch was measured on one Windows install

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 17:42:53 -04:00
lloydandClaude Opus 5.5 074e090f05 Fonts: a lookup finds an installed family whichever thread loads it first
Two threads resolving a family nobody had loaded yet raced. The one that
lost the race saw add_font add nothing and fell back to another face, and
a lookup made while a family was being added saw only part of it (Arial
Bold Italic loads first, so "Regular" came back bold italic). The same
text was then laid out in different fonts depending on timing, which made
clicks_in_flipped_shapes_hit_the_text_where_it_is_drawn flaky when the
theme font isn't bundled.

Loading installed fonts now happens under one lock and rechecks once it
holds it, and a family's faces are added in one step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:50:21 +02:00
4b09f35c1d feat(text, fonts): UAX #9 BiDi and Arabic complex script typography engine (#4)
* Sprint 1: bidi and Arabic typography engine

fonts: ISO 15924 script and a:latin/a:ea/a:cs slot classification;
shape_range shapes an item with the whole paragraph as HarfRust
pre/post context, explicit script/language/direction, glyphs kept in
visual order; per-grapheme-cluster fallback (slot face, theme script
font, OS cascade families, bundled craft-fonts face); GDEF ligature
carets.

text: UAX #9 levels for the whole paragraph (a:pPr/@rtl), itemization
by level, script, face and shaping style (paint-only changes no longer
split words), logical line breaking at cluster boundaries, per-line
L1/L2 reordering (L4 mirroring by the shaper); RTL start edge and
bullets, justLow kashida; caret Affinity, logical/visual movement,
bidi hit testing and selection as the union of visual boxes.

model/pptx: theme a:font script fonts and algn="justLow" round-trip.
engine: format.rtl (Right-to-Left Text Direction), justifyLow align.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fonts: the bundled script face comes before installed fonts; no Microsoft family names

Arabic, Hebrew, Thai and Devanagari clusters try the craft-fonts face first, so every machine
draws the same, then openly licensed or OS-native installed families, then any installed face.
Installed Microsoft faces are still used through that scan, but not named (AGENTS.md §1.1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Tayeb <tayeb@192.168.11.179>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-09 10:48:26 -04:00
Brandon ThomasandClaude Opus 5.5 ae9d525bce Rename SlideCraft to DeckCraft: crates, apps, binaries, app id, file extension (.deckcraft; .slidecraft files still open), packaging, docs; ROADMAP alpha and parity estimates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 01:46:40 -04:00
Brandon ThomasandClaude Opus 5.5 6078b68e97 M0: workspace, model, geometry, fonts, text, renderer, engine, MCP and CLI
Clean-room PowerPoint-class presentation engine in pure Rust:
- geom: units, transforms and 150+ re-derived preset shapes with adjust handles
- color: theme slots and colour transforms
- model: presentations, masters, 11 layouts, placeholder inheritance, themes,
  text, tables, charts, transitions, animations, text edit operations
- fonts: font DB with craft-fonts, system fonts and open substitutes
- text: shaping, line breaking, bullets/numbering, alignment, autofit, caret hit tests
- render: vello_cpu slides with fills, gradients, pictures, effects, tables, charts
- format: native .slidecraft (zip + JSON)
- engine: ~200 commands, undo/redo, clipboard, pointer tools, keys, sample deck
- mcp: MCP server (headless or bridged to the app); slidecraft-cli

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:27:20 -04:00