Commit Graph
295 Commits
Author SHA1 Message Date
65c9b2e5ec speech: launch Windows PowerShell by full path for Read Aloud (#792)
Read Aloud on Windows started `powershell` by name, so the program search
decided which executable ran. Start
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe instead
(C:\Windows when SystemRoot is unset), as PdfCraft already does for
rundll32. The command line and stdin handling are unchanged.

Adds speech::tests::powershell_is_launched_from_the_system_folder.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 01:16:07 -04:00
9d7a972d0b #771: a page total in the footer doesn't make the body lay out again (#773)
foot_top_for measured the footer with total tracking still on, so a
NUMPAGES/SECTIONPAGES footer counted as a page total shown in the body and
every layout ran a second full pass. Footer totals are filled in after
pagination, like the header's, so the measurement now turns tracking off
the way body_top_for does.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 01:12:26 -04:00
a520fb362d Track Changes: show deletions in balloons when 'Show revisions in balloons' is on - balloons now work (#734)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 01:08:26 -04:00
Debarghaya MitraandBrandon Thomas 9a58b5ba33 Web: add wordcraft.ico as favicon via Trunk (#717)
Registers assets/app-icon/wordcraft.ico in apps/wordcraft-web/index.html
using Trunk's rel=icon asset pipeline.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 01:04:33 -04:00
37318985b5 Layout: multiple line spacing adds its leading below the text, like Word (#719)
Benchmarked against docxide-pdf's 390 Word-referenced fixtures, the
largest systematic gap was the vertical position of text: WordCraft put
the baseline one descent above the bottom of the line box, so all the
extra leading of 1.15 or double spacing went above the text (1.8pt at
1.15-spaced 12pt). Word keeps the text one ascent below the line top and
puts the extra leading below it; below single spacing it shrinks the
whole box.

The hhea line gap now counts toward the ascent instead of being split
half above, half below. Line heights stay the same, but every descent
is half a line gap smaller, so text in exact, at-least and grid-snapped
lines (which keep the descent below the baseline or centre the glyph
box) sits up to half a line gap lower: 0.25pt for 12pt Times New Roman.
On the suite that brings those lines closer to Word as well (exact:
5 fixtures better, 1 worse; at-least: 5 better, none worse).

On the suite: mean Jaccard 29.2 -> 33.5%, SSIM 49.4 -> 50.3%, median
baseline offset from Word +0.68 -> -0.06pt, documents within 1pt
35 -> 51%.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:58:37 -04:00
MarekSpodBiedryandBrandon Thomas 92d6165d4c Let dialogs be dragged by their title bar instead of pinning them to the screen centre (#732) (#748)
* Let dialogs be dragged by their title bar instead of pinning them to the screen centre (#732)

* rustfmt after merging main

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:54:25 -04:00
1490d4e594 Dark theme: readable text on the accent (#655) (#704)
* #657: input borders reach 3:1 contrast in both themes

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

* #655: dark-theme accent controls use dark on_accent text (6:1)

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:51:53 -04:00
MarekSpodBiedryandBrandon Thomas cad4138e7e Backspace still erases while Shift is held (#707) (#747)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:50:14 -04:00
MarekSpodBiedryandBrandon Thomas 4fb9c2cb2a Ink: draw the live pen stroke in the dark-page colour while drawing, not only after lifting the pen (#730) (#746)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:48:02 -04:00
05679497ea Pictures: WMF drawings fill their frame like Word plays them (#739)
Word plays a WMF into the picture's box the way a GDI player does: the
placeable header's bounds are only the first window, mapped onto a
viewport of the picture's size, and the file's own SETWINDOWORG and
SETWINDOWEXT records replace that window. WordCraft mapped the header's
bounds through the file's final mapping instead, which broke three
pictures in docxide-pdf's fixtures:

- bulgarian_farmland_allocation_order: the coat of arms draws in a
  window centred on the origin, nowhere near the header's bounds, so
  the lion was blown up into a solid black box.
- ukrainian_municipal_heating_resolution: a portrait window replaced
  the landscape bounds, so the emblem was drawn at a fifth of its size.
- A plain WMF in anisotropic mode that sets a window but no viewport
  kept the 1 x 1 default viewport and shrank to a point; its first
  window is now its target.

Files that set their own viewport, or use the fixed-scale map modes,
are read as before.

SETROP2 is now read too: with R2_NOP, shapes and paths draw nothing
(the coat of arms first draws its bounding rectangle that way, which
showed as an outline). The mode is saved and restored with the DC.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:45:53 -04:00
b2491a28dd #657: input borders reach 3:1 contrast in both themes (#696)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:43:19 -04:00
219b125eeb #661: Table of Authorities buttons labelled, with Update Table (#693)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:41:03 -04:00
1b96e04b2c Add fonts at run time: Add Font…, drop a font file, font.add (#582) (#668)
* Add fonts at run time: Add Font…, drop a font file, font.add (#582)

Rebased onto main as one commit (the branch had merge commits from main).

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

* #582: font test no longer depends on the system's fallback font

The FreeBSD runner has no system fonts, so a missing family fell back to
Source Serif 4, the font the test compared against. The test now adds a
renamed JetBrains Mono under a name without Serif/Sans/Mono (which picks a
fallback of that kind), with narrow-letter sample text: monospaced, it
measures unlike any proportional fallback.

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:38:20 -04:00
8bd3427012 View: Hide White Space between pages in Print Layout (#645) (#684)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:34:30 -04:00
Brandon ThomasandClaude Opus 5.5 19684cff26 layout: make Myanmar fallback tests deterministic (#807)
tests_complex::myanmar_syllables_are_single_clusters and
myanmar_in_a_latin_font_falls_back_to_a_regular_myanmar_face failed about
1 run in 4 of the full layout suite (cluster starts [0, 3, 6, 12, 18], "one
face for the word") but passed alone.

Root cause: a race in FontDb's system font fallback, not the tests. Tests
share FontDb::global(), and layout asks for a fallback per character from
several threads. A search marks a preferred family ("Padauk" for Myanmar) as
tried before loading it; a second search racing it skipped the family as
already tried, fell through to the coverage index, and loaded the first
cataloged file in path order that maps the character (Padauk-Bold.ttf on
its own). Characters of one syllable then fell back to different faces, so
the shaper split the syllable and the word's faces differed. Separately, a
search's answer was cached under the face count read after the search, so
an answer chosen before another thread's load could be cached as current.

Fix (crates/fonts):
- System fallback searches run one at a time (a `searching` lock). A search
  that waited re-checks the loaded faces first, so it uses the family the
  previous search loaded instead of loading another file.
- The fallback cache keys an answer by the face count it was chosen from,
  and doesn't cache an answer from before a concurrent load.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:29:58 -04:00
Brandon ThomasandClaude Opus 5.5 46e694f5f5 Release workflow: pass the Azure signing secrets so Windows builds are signed (#752)
Since the release workflow moved to storytold/craft-common-actions, it has
passed only the Apple and WINDOWS_CERTIFICATE secrets. The signing job reads
values from the release environment, but only for names the caller lists, so
the AZURE_* names that Trusted Signing uses came through empty and Windows
builds would ship unsigned. Same fix as storytold/craft-launcher#174.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:27:33 -04:00
0918a52e57 docs: fix coordination error in ROADMAP.md (#715)
Co-authored-by: lunar-me <lunar-me@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:20:52 -04:00
85698c4d79 Security: pictures travel in the reply, exported links keep only safe targets (#678, #679) (#681)
- `ui.render` and `ui.screenshot` without a `path` return the PNG as base64 `png` in the
  reply instead of writing a fixed name into the shared temporary folder, where another
  user could have planted a link to one of this user's files; the MCP bridge's
  `render_page` and `screenshot` use that and no longer round-trip through
  `temp_dir()/wordcraft-mcp-*.png`. A given `path` still works as before (#678).
- One allow-list, `wordcraft_doc::link`: `is_web_or_mail_link` for Ctrl/⌘-click and
  `is_safe_href` (web, mail, `#bookmark`, relative path; schemes read the way browsers
  do, so `java\nscript:` is a script) for the HTML and Markdown exporters and the HTML
  importer. Scripts and other schemes export as plain text (#679).
- Tests: the allow-list, exporters with hostile links, the MCP bridge against a fake app
  (no `path` ever asked for), the control channel's in-reply PNGs.

Co-authored-by: petycomppety <30414255+petycomppety@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:57:08 -04:00
62b58d1f87 Design tab: style set gallery gets its own box so it can't overlap Colors and Fonts (#638) (#673)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:42:20 -04:00
f7a84fbfb8 Print Markup: comments in PDF, print, images and text copies (#589) (#667)
Rebased onto main as one commit (the branch had merge commits from main).

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:38:29 -04:00
381c475edc M-print: File > Print preview, page range, odd/even, copies and collate (#641) (#674)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:30:58 -04:00
39f51ca292 More Colors… and a Colors dialog for custom colours (#606) (#666)
Rebased onto main as one commit (the branch had merge commits from main).

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:27:34 -04:00
3fd97c3e58 Complex scripts: shape Myanmar, Khmer and Indic text; caret and line breaks respect syllables (#604) (#652)
Rebased onto main as one commit (the branch had merge commits from main).

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:22:57 -04:00
2544372a22 #611: DOCX diagrams: theme-styled shapes and connectors, VML lines (#671)
Shapes and connectors that take their fill and outline from the theme
(wps:style a:fillRef/a:lnRef) now show; scheme, preset, system, scRGB and
HSL colours resolve. Callouts and flowchart boxes draw as the nearest
shape. Legacy VML lines (with arrowheads), rectangles and ovals are read.
A DrawingML line runs top-left to bottom-right, as the preset does.

Rebuilt on main, which now has connectors, arrowheads and canvases (#416,
#344): those parts of the earlier branch were dropped in favour of main's.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:15:28 -04:00
39e541fc8b Table: a vertical merge lays out and hit-tests as one cell (#640) (#669)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:11:40 -04:00
ab49e4f5fa Open playlists and other text files (.m3u, .m3u8, .log, .ini, …) (#569) (#648)
Rebased onto main as one commit (the branch had merge commits from main).

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:07:41 -04:00
14080c614c Drag after a double/triple-click extends the selection by words/paragraphs (#549) (#649)
Rebased onto main as one commit (the branch had merge commits from main).

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:58:11 -04:00
ReesandBrandon Thomas 0eea21c2c8 M1: preserve omitted DOCX paragraph defaults (#647)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:47:03 -04:00
b75bd3c56a Fonts: Palatino Linotype and Book Antiqua prefer Palatino look-alikes (#670) (#677)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:42:05 -04:00
4fffa81003 #664: brand licence names WordCraft, not DesignCraft (#682)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:39:47 -04:00
a37253109e #665: rustdoc names private items as code, not links (#683)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:33:33 -04:00
alexanderalberandBrandon Thomas 29ee256c86 Paragraph dialog: OK keeps Exactly and At Least line spacing (#467) (#564)
The dialog shows line spacing as a multiple, so Exactly and At Least
read as 1.0, and OK (and Tabs…) always sent that back as Multiple 1.0.
Accepting the dialog unchanged, or changing only Keep with next, turned
a fixed rule into single spacing.

The dialog now remembers the value it showed and sends line spacing only
when it changed, as it already does for Asian Typography. A changed
value still sets a multiple.

generated with Claude Code

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:30:57 -04:00
DevCoderMaxandBrandon Thomas 0652e7f72f Pictures in headers/footers can be moved, resized and rotated (#591)
* Pictures in headers/footers can be moved, resized and rotated

Hit testing (layout.object_at_in), arrange.bounds/position/nudge/group and the canvas selection, drag, resize and rotation handles used to be body-only, so pictures in headers and footers could not be moved (#82). They now work within their own header/footer story: drag to move, handles to resize, rotation handle to turn, arrows to nudge. Regression test: header_pictures_can_be_resized_and_dragged.

* Credit DevCoderMax (Gleyson do Nascimento Carvalho) in About

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:27:44 -04:00
MarekSpodBiedryandBrandon Thomas d1064bbaad Zotero: a note with a picture stays when its citation moves to body text (#469) (#581)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:23:54 -04:00
MarekSpodBiedryandBrandon Thomas 387873ae05 Zotero: a pending citation no longer edits a replacement document (#470) (#580)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:20:33 -04:00
MarekSpodBiedryandBrandon Thomas cfb65364c7 Web: a CSV picked as recipients no longer turns the next TXT drop into recipients (#472) (#579)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:18:07 -04:00
alexanderalberandBrandon Thomas 0db810d0e8 Layout › Page Setup: the group launcher opens the Page Setup dialog (#471) (#563)
The launcher ran ui.dialog, which needs a dialog name, but group
launchers run their command with no parameters, so nothing opened. It
now runs layout.pageSetup, which asks the UI for the Page Setup dialog,
as the Font and Paragraph launchers run their dialog commands.

The test opens the dialog through layout.pageSetup and checks that no
ribbon group uses ui.dialog as its launcher.

generated with Claude Code

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:15:27 -04:00
MarekSpodBiedryandBrandon Thomas e9da654903 IME composition text uses document fallback fonts instead of boxes (#565) (#578)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:12:39 -04:00
alexanderalberandBrandon Thomas aaaf68c6c7 Contributors: add my own entry (alexanderalber) (#566)
generated with Claude Code

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:09:49 -04:00
alexanderalberandBrandon Thomas f91ce21f90 Find: each Find Next in the dialog moves one match (#468) (#556)
The Find and Replace dialog ran edit.find and then edit.findNext on every
click (and Enter). edit.find already selects the first match after the
caret, so the step after it skipped one: with four matches only the
second and fourth were ever reached, with two only the second.

edit.findNext and edit.findPrevious now take the same optional search
options as edit.find (text, matchCase, wholeWord, regex), and the dialog
sends one edit.findNext with its options. Calls without options behave
as before (F3, Mod+G).

generated with Claude Code

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:02:37 -04:00
baeb8d9c6c Open and save Word XML Document (Flat OPC); open Word 2003 XML (#548) (#560)
- docx: Flat OPC (`pkg:package`) read by unpacking the parts into an
  in-memory package for the DOCX reader; written from the DOCX writer's
  parts (XML inline, binary parts in base64). Part count, part size,
  total size and base64 length are capped.
- docx: Word 2003 XML (`w:wordDocument`) imported by rewriting it as the
  corresponding package (hyphenated names, w:hlink, w:listPr, w:lists,
  inline headers/footers, notes, aml annotations, w:binData pictures,
  o:DocumentProperties) and reading that with the DOCX reader.
- `.xml` told apart by root element; other XML is refused with
  "not a Word XML document". File > Export gets "Word XML Document (*.xml)"
  (all 9 catalogs); .xml saves keep the document like .docx.
- Tests: Flat OPC round trip equals DOCX round trip, hand-written Flat OPC
  package, Word 2003 import, hostile inputs, engine io.

Fixes #548

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:59:00 -04:00
7c5f72a31b DOCX: theme colours and theme fonts take precedence over the fixed values, as in Word (#626)
ECMA-376 says a theme attribute supersedes the fixed value next to it, and
Word renders it that way. The reader did the opposite:

- w:color read only w:val, so a style written as
  <w:color w:val="4472C4" w:themeColor="accent1" w:themeShade="BF"/>
  stayed blue under a theme whose accent1 is another colour. w:themeColor
  (and w:u's theme colour, and w:shd's w:themeFill) now resolve through the
  theme's colour scheme and settings' w:clrSchemeMapping, with
  w:themeTint / w:themeShade applied to the HSL luminance. Without a theme
  part the fixed colour is kept. The mapping is read before the styles,
  which come before settings in the package.
- w:rFonts preferred w:ascii over w:asciiTheme (and w:hAnsi over
  w:hAnsiTheme), so a heading with both showed the fixed font instead of
  the theme's major font. The theme font now wins in each slot.

Saving writes the resolved colour as w:val.

Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:53:05 -04:00
a608332646 Chinese documents: character-unit indents, .doc indents and line spacing, floating tables, blank pages (#534) (#550)
- Indents in characters (w:firstLineChars, w:hangingChars, w:leftChars,
  w:rightChars; sprmPDxc* in .doc) are kept in the model, read and written,
  and laid out in the paragraph mark's font size, winning over the points
  as in Word. Indent commands cancel an inherited character indent.
- .doc: sprmPDxaLeft1 has a two-byte operand (was read as four, so every
  first-line indent was lost); sprmPDxaLeft1_80 is read; LSPD line spacing
  is decoded as signed dyaLine + fMultLinespace (exact 20pt read as 799pt,
  a page per empty paragraph).
- A floating table placed on the page or margin that would cover the text
  already on the page and doesn't fit below it moves to the next page.
- A line holding only a page break stays at the bottom of a full page
  instead of leaving a blank page.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:49:25 -04:00
dab5092e83 Font dialog: separate Asian and Latin text fonts (#505) (#536)
* Font list: installed Chinese fonts are listed, by their Chinese names in a Chinese interface (#505)

The font database read only the English family name of each font, so a
Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei
by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a
document naming a font by its Chinese name fell back to another font.

- fonts: keep each face's localized family names (name IDs 16 and 1,
  every face of a collection, read during the scan without loading the
  file); FontDb::families_in(lang) lists them in place of the English
  names for a matching interface language (zh-hans, zh-hant, ja, ko);
  face, has_family and styles accept either name.
- ui: the ribbon and mini toolbar font boxes show the interface
  language's names and store the English name.
- East Asian font: choosing a Chinese, Japanese or Korean font also sets
  it as the East Asian font (w:eastAsia), read and written in DOCX;
  layout draws CJK text in a run's installed East Asian font.

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

* Font dialog: separate Asian and Latin text fonts (#505)

Word's Font dialog sets the font of Chinese, Japanese and Korean text
apart from the font of Latin text; WordCraft's had one font box.

- Font dialog: an "Asian text font" box (the installed Chinese, Japanese
  and Korean fonts, by their interface-language names, type to find)
  above the "Latin text font" box, shown for a Chinese or Japanese
  interface, East Asian text in the selection or a selection with an
  Asian font. OK sets only the fonts changed, in one undo step.
- format.font takes "eastAsia" (w:eastAsia) besides "name"; either or
  both; format.state reports fontEastAsia.
- fonts: the scan reads each font's OS/2 code pages, so
  FontDb::east_asian_families lists CJK fonts without loading them.
- Tests: dialog sets each font on its own; layout sets CJK text in the
  run's Asian font and Latin text in its Latin font (skipped without an
  installed CJK font); command and undo; the scan finds a CJK font.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:42:56 -04:00
60cd1bf568 Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534) (#561)
* Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534)

A list with w:numFmt="chineseCounting" and lvlText "%1、" showed "2、", and the
digit was drawn with glyph ids of the CJK font that supplied the comma, so it
came out as an unrelated letter: the list label was laid out in font runs but
drawn with the style of its last run only.

- NumFormat knows every ST_NumberFormat value (ECMA-376 §17.18.59), so a file
  saves back the format it was opened with; unknown values are decimal.
- New doc::cjk_numbers: Chinese/Taiwanese counting (with 十 below 100, digit by
  digit above) and counting-thousand (百/千/万/亿, 零 for gaps), simplified and
  traditional legal numerals, Japanese counting/legal/digital ten-thousand,
  Korean counting/digital/legal, ideograph digits, heavenly stems, earthly
  branches and the sixty-year cycle, aiueo/iroha (half and full width),
  ganada/chosung, enclosed numbers (①, ⒈, ⑴, ㊀) and full-width digits. Values
  past a sequence's end fall back to decimal.
- Legal numbering (w:isLgl) shows every level, the current one too, in decimal.
- List labels keep their font runs and draw each with its own face.
- A list level without w:ind no longer gets a 36pt indent on read.
- .doc number format codes 0x00-0x27 map to the formats (0x16 decimalZero).
- PAGE/NUMPAGES fields take \* ChineseNum1/2/3.

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

* rustfmt after merging main

* Layout test: end the list range inside the third paragraph (#477 skips a paragraph the range ends at the start of)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:35:41 -04:00
b2c51c6500 Fonts: index system font coverage once, so fallback no longer re-reads every font per character (#605) (#610)
System fallback read and parsed every cataloged font file (<40 MB) again for each distinct
character no loaded font covers; only that character's miss was remembered. Text in a script
no installed font covers (Tangut, CJK Ext G, ...) froze layout for seconds to minutes.

Each file is now read once, when a fallback search first reaches it, and its coverage is kept
as sorted code point ranges; later searches answer from the ranges and only read a file that
maps the character (to load it). Rescans keep the ranges of files still installed. Preferred
fallback families are tried once each, the miss set is capped, and indexing a damaged file is
bounded (faces, mappings, ranges). Search order and the fonts chosen are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:24:48 -04:00
f422d6ccbb Mini toolbar: every button fits, flush font box, proper dropdown arrows, dark-mode font list (#535) (#540)
- Two rows like Word's mini toolbar (font, size, grow, shrink; B I U S, highlight,
  font colour, four alignments), sized from the ribbon widgets' real metrics so no
  control is clipped any more (the one-row bar cut off everything after Align Left).
- Combo lists drop from the box's left edge instead of from the arrow.
- The dropdown triangle fills its box, so it no longer shrinks to a dot beside
  combos and split buttons.
- Font list entries are an ink mask tinted with the theme's text colour, so they no
  longer sit on white strips in dark mode.

Tests: the bar drawn over a real selection contains every control and is exactly as
wide as them; font entries take the theme text colour (dark and light) and paint no
background; the ink mask keeps coverage and drops the paper.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:19:26 -04:00
7484a6bd11 Find and Replace: special characters (^p, ^t, …) and wildcards (#543) (#553)
- Find what understands Word's ^ codes (^p ^t ^? ^# ^$ ^^ ^l ^m ^n ^b ^+ ^= ^s ^~ ^- ^w ^g ^% ^v,
  ^nnn / ^unnnn); Replace with adds ^& (found text) and ^c (clipboard). `special: false` reads
  the text literally.
- Use wildcards: ? * [abc] [a-z] [!x] {n} {n,} {n,m} @ < > ( ), \1-\9 in Replace with; always
  matches case, like Word. Patterns are translated to the linear-time regex engine (no
  catastrophic backtracking), length and counts capped, bad patterns are errors.
- ^p matches paragraph marks between sibling paragraphs; a ^p in the replacement splits the
  paragraph. Replace All stays one undo step.
- Match prefix / suffix, Ignore punctuation / white-space characters.
- Dialog: Use wildcards checkbox, Search Options with the new options and a Special menu that
  inserts codes into the focused field (a shorter list with wildcards); strings in all 9 catalogs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:15:31 -04:00
850fa0576a AutoFormat: +---+ tables, arrow bullets, smileys inside words, border-line undo (#546) (#547)
- `+---+------+` Enter turns the paragraph into a one-row table across the text
  width, columns proportional to the hyphens, caret in the first cell (body,
  top level, at most 63 columns).
- `> ` `-> ` `=> ` start a bulleted list with an arrow bullet (➢ ➔ ⇨).
- Smileys `:)` `:-)` `:(` `:-(` `:|` are replaced also straight after a word.
- Undo right after a border line or table AutoFormat restores the typed
  characters and keeps the Enter (its own undo step).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:11:22 -04:00
46d5e99b3e Styles: Update to Match Selection takes the selection's formatting (#555) (#562)
styles.updateToMatch copied only the direct run/paragraph formatting at the
caret onto the style, so text whose look came from its own style (Normal,
Title, ...) changed nothing; it then wiped that paragraph's direct formatting
without applying the style, so the selection lost its look too.

Now the style takes the selection's effective formatting (paragraph style
chain, character style chain, direct formatting) as a diff against what the
style currently gives, keeping its outline level and numbering. The selected
paragraphs take the style and drop only the direct formatting it now carries.
The Home styles gallery tiles get the right-click menu (Update to Match
Selection, Modify...).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:06:14 -04:00