#305 replaced symbols the interface fonts lack with drawn icons, but the
new buttons switched state instantly and the pane close button had no
pressed state at all.
- widgets::drawn_state: the shared look for drawn buttons. Hover fades in
over egui's animation time and the pressed state blends in while the
pointer is down. Framed buttons take egui's widget visuals (fill,
stroke, text colour, expansion) so they match ui.button; frameless
ones take the ribbon's hover/pressed tokens, like the ribbon buttons.
- widgets::icon_button: a frameless icon button built on it; the panes'
close button uses it.
- icon_text_button (Editing/Reviewing, Comments, New Comment) uses it too.
widgets::tests::drawn_buttons_fade_in_on_hover_and_darken_while_pressed
drives both kinds with real pointer events at 60 fps and checks the
idle, mid-fade, hovered and pressed fills.
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The stack's squash merges on GitHub came out empty; this brings their code back. Conflicts
resolved keeping both sides: table-style regions extend main's region_of/region_text (#287)
with last column and column bands; charts/diagrams (Graphic, #292) count as drawings in the
stack's frame/float_mut/set_size helpers but stay frozen and can't be grouped; group reading
sits after main's chart/diagram URI check; i18n catalogs unioned (duplicate Preview dropped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AutoSave writes only a file saved in this session, in a format that keeps
everything (.docx, .docm, .dotx, .dotm, .odt, .rtf, .json), and never in the
browser, where saving is a download. The title-bar switch still showed on for
a saved .md file (#196) and AutoSave looked available in the web app (#176),
so edits stayed unsaved with no explanation.
WordApp::autosave_block() now says why AutoSave can't cover the document
(browser, never saved, a format that can't keep everything, opened but not
saved here). The switch is greyed out with that reason as its tooltip, and
clicking it offers Save As (desktop) so saving as .docx turns AutoSave on.
In the browser, File > Options' AutoSave checkbox is disabled with the same
explanation. file.autosave (scripts, MCP, the switch) now sets the app's
AutoSave and returns the reason as an error when asked to turn it on where it
can't save; turning it off always works. New strings in every catalog.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Several buttons put symbol characters in their text that neither Inter nor
egui's emoji fallbacks contain, so they drew as empty boxes: the panes' close
button (✕), the title-row Editing/Reviewing (✎) and Comments (💬) buttons,
the Comments pane's New comment (➕), File › Open's Browse… (📂), the
Equation tab's symbol-set button (⋯) and the ➢ tile in the Bullet Library.
- Pane close buttons paint the existing `close` icon, with a Close tooltip.
- New `widgets::icon_text_button` paints an icon from icons.rs next to text;
Editing/Reviewing (pencil), Comments and New Comment use it. The
Editing/Reviewing menu is now an egui Popup on that button.
- Browse… drops the emoji; ⋯ becomes …; the ➢ bullet stays a bullet choice
and only its menu tile draws the arrowhead.
- Catalogs: `📂 Browse…` is renamed `Browse…` (translation without the
emoji) and `➕ New comment` is removed in favour of the existing
`New Comment`, in every language.
theme::tests::interface_symbols_have_glyphs scans the UI sources' string
literals for non-letter, non-ASCII characters and checks each against the
real interface font setup, with a short commented exception list (equation
templates drawn by the math renderer, the ➢ bullet value).
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Shapes and text boxes get an effects model (outer shadow with colour,
transparency, blur, distance and angle; glow with colour, size and
transparency; soft-edge radius), the shape.effects command (presets,
custom values or null per effect, applied to every selected shape,
undoable, hostile numbers clamped), screen and PDF rendering with the
same banded approximation of a blur, DOCX a:effectLst read/write
(outerShdw, glow, softEdge) with a matching wp:effectExtent, and a
contextual Shape Format tab with Shape Fill, Shape Outline and Shape
Effects (Shadow, Glow, Soft Edges submenus) plus Arrange.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Shift+click (or Ctrl+click) adds pictures, shapes and text boxes to the
selection; Group (arrange.group) combines the selected floating objects
into one group object that moves, wraps and resizes as one, with its
members keeping their places and scaling with the group. Ungroup
(arrange.ungroup) splits it back, where the group showed them, and
leaves them selected so Group puts them back together.
- Model: InlineObject::Group with the members' coordinate space (like
DrawingML a:chExt), so resizing the group scales its members.
- Layout draws the members inside the group's frame; hit testing
selects the group; text boxes in a group show their text.
- DOCX writes wpg:wgp with a:chOff/a:chExt and each member's a:xfrm,
and reads Word's groups (including inside mc:AlternateContent and
nested groups, flattened).
- select.addObject makes multi-selection scriptable (pos or Selection
Pane index).
Parity 378 -> 379 of 428 (88.6%); floor raised to 88.5%.
Fixes#265
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Paragraphs gain the Asian Typography flags (line-breaking rules, wrapping
Latin text in the middle of a word, hanging punctuation, compressing
punctuation at the start of a line, and automatic space between Asian and
Latin text and between Asian text and numbers). They inherit through styles,
round-trip through .docx pPr, are set by the new para.asianTypography
command and appear on a new Asian Typography tab of the Paragraph dialog.
Layout applies the kinsoku lists (no line starts with closing East Asian
punctuation or ends with opening punctuation), breaks between any East Asian
characters when kinsoku is off, and breaks Latin words anywhere when word
wrap is off. Text without East Asian characters breaks as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
egui's paste event only carries text, so a clipboard holding just a picture
sent no paste at all on Ctrl+V, and the ribbon/context-menu Paste only knew
WordCraft's own copy ("the clipboard is empty").
Desktop: the app reads the clipboard's picture through a new
Services::clipboard_picture (arboard, already in the build via egui-winit):
copied files first, else image pixels. Mod+V whose release arrives without a
paste event, and Paste from the ribbon or menus, insert that picture through
insert.picture (one undo step); text naming copied picture files inserts the
pictures. Pixel sizes are hostile: at most 16,000 px a side and 50 Mpx, the
buffer must match, files at most 200 MB.
Web: a document paste listener reads image files from the paste event when
there's no text and delivers them through the inbox like dropped pictures.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Home › Styles › Manage Styles (styles.manage) opens a dialog from the
Styles pane listing the document's paragraph, character, linked and table
styles, sorted as recommended or alphabetically, with a preview line and a
description (type, formatting it sets, what it is based on). From there:
Modify… and New Style… reuse the existing dialogs and come back, Delete
removes custom styles, and two checkboxes show the style in the Styles
gallery (w:qFormat) or hide it from style lists (w:semiHidden).
styles.manage returns the list for scripts (name, type, builtIn,
inGallery, hidden, basedOn, priority, own formatting). styles.delete now
moves paragraphs, runs and tables that used the style to its base (in
every story), rebases styles built on it and takes a custom linked
character half with it; built-in styles are refused and it is undoable.
New styles.setVisibility {style, gallery?, hidden?} sets the flags, which
round-trip through .docx.
Fixes#266
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Follow-up to the custom table styles work: the New/Modify Table Style
dialog now covers every region a table style formats, shows what the
style will look like, and table styles can be deleted.
- engine: table.newStyle / table.modifyStyle take lastRow (total row),
firstColumn, lastColumn and bandedColumns regions with the same props
as the others. New table.deleteStyle removes a custom table style:
built-in ones are refused, styles based on it are re-based on its own
base with its formatting folded in (so they look the same), and tables
using it fall back to Table Grid. Undoable like any command.
- doc/layout: table styles gain fill and cell borders for the first and
last column and total row, and fill, text and borders for column
bands; layout applies them in the standard order (column bands, row
bands, first/last column, header/total row).
- docx: firstCol/lastCol/lastRow/band1Vert shading and cell borders are
read and written.
- ui: the dialog's "Apply formatting to" lists all seven regions and a
small live preview table shows the pending changes; Styles menu gets
Delete Table Style (disabled for built-ins); the gallery shows the
document's own table styles first.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Copy and Cut now also keep each item (rich fragment plus plain text) in a
session list of up to 24, newest first. Items over ~4 MB (estimated) are
skipped and the list stays under ~24 MB in total; copying the same thing
twice in a row adds it once. The system clipboard is untouched.
New commands: edit.clipboardPane (toggle the pane, also the Clipboard
group's dialog launcher), edit.clipboardItems (list with previews),
edit.pasteClipboardItem {index}, edit.pasteAllClipboard,
edit.deleteClipboardItem {index} and edit.clearClipboard. Pasting goes
through the normal paste path, so it is one undo step and respects Track
Changes. The pane lists the items with a short preview; clicking one pastes
it at the caret, and each has Delete, plus Paste All and Clear All.
Parity: 378 -> 379 of 428 features.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Table sizing: Height/Width boxes, real AutoFit Contents, Table Properties dialog (#44)
- Table Layout > Cell Size gets Height and Width boxes (inches) for the caret cell's row
and column; each edit runs table.rowHeight / table.columnWidth, a drag is one Undo.
- AutoFit Contents now measures every column's narrowest (longest word) and widest
(unwrapped text) content with the layout's text shaping, shares the text width the way
Word does, and writes the widths into the table grid and cell widths.
- Table Properties opens a dialog (table width, alignment, indent; row height and rule,
break across pages, header row; column width; cell width and vertical alignment). OK
applies only what changed as one table.properties step; with settings the command still
works headlessly, and without them it reports the current values.
- table.columnWidth clears the table's preferred width so the column keeps the new size;
table.rowHeight takes an optional exact rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Progress docs: Table Properties dialog and AutoFit Contents landed (#44)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Table sizing: saturating column-span sum in sync_cell_widths
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: one region_text (main's) after #287 met the landed table-style code
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>
A chart or SmartArt diagram in a DOCX was dropped on import: it took no
room, so the text after it moved up (five of the six chart documents in
docxide-pdf's fixtures came out a page short), and nothing was drawn.
They are now an InlineObject::Graphic: it takes its room, floats and
wraps like a picture, and draws a list of neutral items (shapes, paths,
one-line texts, pictures) that layout scales to the object's box.
- Charts (read/chart.rs) are drawn from the values the chart part
caches: clustered, stacked and 100% bar and column charts, line, area,
pie, doughnut and scatter charts, with title, legend, gridlines, axis
labels and Excel number and date formats.
- SmartArt (read/diagram.rs) is drawn from the drawing Word stores
beside the diagram: preset shapes, theme colours, text and pictures.
- Theme colour handling is shared (read/drawing_color.rs).
- Each chart or diagram part is read once, only through a relationship
of its type, under a per-file budget of items.
- Tagged PDF tags them as figures with their alternative text, and the
accessibility check reports charts without one.
Not yet: charts and diagrams aren't written back when saving a DOCX
(they weren't before either), so the editor doesn't let you move or
resize them. Radar, stock, 3-D and combination charts draw their first
supported chart type only; SmartArt pictures aren't clipped to their
shape.
docxide-pdf bench (365 documents, vs main + #135): page counts right
320 -> 326 (all five chart documents and one SmartArt document), Jaccard
passes 208 -> 215, SSIM passes 76 -> 79; eight documents better, none
worse.
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Clicking a comment balloon in the markup area selects it like Word: its text
becomes editable in place, with Reply, Resolve/Reopen and Delete, and replies
are shown (and editable) under the comment they answer. Each edit is written
back through a new review.editComment command, one undo step per editing
session; the Comments pane uses the same path (it used to write the text
without an undo step) and gains a Reply button.
The margin edges on the horizontal ruler (left/right) and the vertical ruler
(top/bottom) can be dragged to set the section's margins through
layout.margins, one undo step per drag, snapped to 1/16 inch and clamped so
at least an inch of text remains. Indent markers keep priority where they
overlap an edge. Setting margins by number no longer switches off mirrored
margins; only a preset decides that.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
View › Gridlines now draws the drawing grid at the document's own spacing:
DOCX keeps it in settings.xml as w:drawingGridHorizontalSpacing and
w:drawingGridVerticalSpacing (ECMA-376 §17.15.1.44/45), which WordCraft now
reads (clamped) and writes (only when it differs from the default), with a
round-trip test. Documents that don't say use the spec's 180 twentieths of
a point (1/8 inch) instead of a fixed 0.5 cm; across and down can differ.
Table Layout › View Gridlines (table.viewGridlines) is on by default, as in
Word, so tables without borders still show their cells.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The test put a two-line header 36pt from the top of the page and checked
that the body still starts at the 72pt top margin. How tall those two
lines are depends on the font the default text resolves to: with the
installed Carlito-style fonts they fit, but with only the bundled
fallback fonts (as on the FreeBSD CI VM) they end at 72.6pt, so the
header correctly pushed the body down and the control check failed.
Start the header 18pt from the top instead, which leaves the two lines
plenty of room above the margin with any font. The checks are unchanged:
the control body starts at the top margin, the floating box changes
neither the body start nor the page count, and the same box inline
still pushes the body down.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Windows metafiles (WMF, EMF), common for logos, crests and diagrams in
older documents, couldn't be decoded and showed as grey boxes, on screen
and in PDF.
The new crate wordcraft-metafile (layer 1) reads them from the public
[MS-WMF] and [MS-EMF] specifications into paths and bitmaps, and places
them in a picture's box; render and pdf paint the result as vectors.
It covers the drawing records Office pictures use (lines, polygons,
curves, rectangles, ellipses, arcs, path brackets), pens and brushes,
the mapping modes and world transforms, and bitmaps in DIB form with the
usual raster operations. Input is treated as hostile: every read is
bounded, and a byte budget caps what one picture may allocate.
Not yet: text records (ExtTextOut) and EMF+ records are skipped, so a
metafile whose content is mostly text shows only its lines and fills.
docxide-pdf bench (365 documents, vs main + #135): the twelve documents
with WMF/EMF pictures show them; the pictures are a small part of their
pages, so the scores move little (Jaccard passes 208 -> 209, five
documents up to 3 SSIM points better, none worse).
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Word sizes the columns of a table that may follow its content (tblLayout
not fixed) from what is in them; we kept the stored grid as it was:
- A table with no preferred width anywhere (no tblW, no tcW: what
generators write) now gets its columns from their content: each its
widest line if all fit, else shared out between longest word and
widest line, as Word does (case51: a nested table no longer fills its
cell, and the document is two pages like Word's).
- In other autofit tables no column is narrower than its longest word;
the columns with room give up the difference ("ON" no longer breaks
into "O/N" in case91).
Floating tables (w:tblpPr) inside a table cell were laid out in the
text flow. They now stand at their position in the cell and take no
room; the text after them wraps around them, a table that may not
overlap goes below the ones in its way, and the cell grows to hold
them. A row whose lines all fit but whose nested floating table runs
past the page bottom splits there instead of moving to the next page.
Text now flows beside a floating object into gaps of 20pt or more
(was 36pt): Word puts a paragraph beside a floating table in a 21.3pt
gap but not in an 18.6pt one (docxide-pdf case139-148 vary just that).
docxide-pdf bench (365 documents, vs main + #135): Jaccard passes
208 -> 227, SSIM passes 76 -> 86, page counts right 320 -> 324; three
documents slightly worse (case149 -4 SSIM: Word moves the whole floating
table to the next page, we split it; two others -1 or less).
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On desktop, the Print button on File › Print now renders the document
to PDF, writes it to a fresh wordcraft-print-*.pdf in the OS temp
folder and opens it without blocking the window: in Preview on macOS,
and in the default PDF app on Linux/BSD (xdg-open, gio, ...) and
Windows (via the `open` crate, already in the tree). The user prints
from there through the system print dialog. If no viewer can be
started, the status bar says so and the file is removed. Print files
older than an hour are cleaned up off the UI thread at start-up and
after each print.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: translate the remaining untranslated interface strings; test the font fallback cache with legacy family names (#233, #239)
Follow-ups from integrating #233 and #239: every tl! string now has a row in every
catalog, and a test pins that loading a face by its legacy family name drops the
fallback cache.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Serbian rows for the newly translated strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#280 now builds on #267's object model: selected_all() uses InlineObject::is_drawing (groups
included); group members carry effects everywhere #267 handles them (docx writer's shared
wsp() writes a:effectLst for standalone shapes and group members alike, the reader's read_wsp
reads it for both, layout's float_items/display draw members with their effects); the
groups_round_trip test gives a member a shadow and soft edges and checks they come back.
shape.effects also applies to shapes added to the selection with Shift+click (#267's
also_selected). Serbian rows for the Shape Effects strings; parity regenerated (390/431) and
floor raised to 90.4%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session: also_selected kept next to main's column/column_mode (constructor, open, snapshot);
Serbian rows for Add Object to Selection and Ungroup; parity regenerated (389/431) and floor
raised to 90.2%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Manual line breaks end the line by cluster kind, not by break opportunity, so kinsoku's filter
can't drop one before 」; pin that with a layout test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dialogs.rs: both apply_asian and Manage Styles' helpers kept; Serbian rows for the Asian
Typography strings; parity regenerated (388/431) and floor raised to 90.0%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The key-release fallback that pastes the system clipboard's picture treated every Mod+V release as
Paste. It now requires plain Mod+V or the Paste key (no Shift, no Alt), with a unit test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
styles.delete on a table style now calls table_style::delete (#263): its tables take Table Grid,
styles based on it keep their look, and Word's built-in table styles are refused even when a
.docx marks them custom (the Manage Styles list reports them built-in too). Document::restyle no
longer retargets tables' own styles, so there is one place that does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, layout, hardware, UI and localization parity docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dialog enum/name/title/body: both Manage Styles and New/Modify Table Style kept (NewStyle keeps
#270's back field); module doc and Styles pane buttons unioned; Serbian rows for the Manage
Styles strings; parity regenerated (387/431) and floor raised to 89.7%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .docx can set w:customStyle on any style, which made Word's built-in table styles look custom
and deletable. table.deleteStyle and the ribbon's Delete Table Style now also recognise them by
id and name, so no table is left on a style that's gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#263 carried #256's commit, already on main as d8f7195; main's version is kept and only #263's
own change (2fefdeb) is applied on top. Six-region TableStyle tuple in layout, union of catalog
rows (duplicates dropped), Serbian rows for Delete Table Style and Preview, parity regenerated
(386/431).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerate docs/parity.md (385/430), raise the parity floor to 89.5%, add Serbian (sr, sr-latn)
rows for the clipboard pane strings and drop the now-duplicate "Clear All" catalog rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show section breaks with Show/Hide ¶ (#42)
With formatting marks on, the last paragraph of a section now ends in a
dotted double rule with the break's name centred in it (Section Break
(Next Page), (Continuous), (Even Page), (Odd Page)) instead of a plain ¶.
The type shown is the start type of the section that follows. The mark
is screen-only: PDF export skips it, and print/PNG never turn marks on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Show/Hide ¶: put the paragraph mark past mixed-direction text (#274)
The paragraph mark (and the section-break mark) was anchored at the caret
position after the paragraph's logically last character. When that text
runs against the paragraph's direction (Hebrew ending a left-to-right
paragraph, Latin ending a right-to-left one) that position is inside the
line, so the mark was drawn on top of a letter. The mark now sits at the
line's visual end for the paragraph's direction: right of the rightmost
glyph in a left-to-right paragraph, left of the leftmost in a right-to-left
one. Pure left-to-right and pure right-to-left lines are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Info page edited a copy of the document properties that was re-read
from the document every frame and only written back when a field lost
focus, so each keystroke vanished on the next frame and nothing was ever
committed. The fields now commit through file.properties as you type
(undoable, one step per visit to a field, marks the document dirty).
The web build's Save writes the bytes itself instead of running
file.save, so it never stamped the modified time, last-modified-by and
revision. Both paths now share Session::stamp_save.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
On a Linux machine with Carlito and Noto Naskh Arabic installed, four
layout and PDF tests failed on main:
- field_results_are_extractable_text found a real bug: a field result is
laid out as one cluster, and the display list gave each of its glyphs one
character. A ligature (Carlito, the Calibri substitute, joins "ti") took
only its first letter and shifted the rest, so the PDF text read
"Sectio 3.01 fir details3". The layout now keeps each glyph's text range
from shaping, so a ligature carries all its letters. A new test pins this
with bundled Source Sans 3 ("fi", "ffi"), whatever fonts are installed.
- fragmented_hidden_text_lays_out_like_the_visible_text compared visible
text in alternating bold runs with an all-regular paragraph, so it only
passed when the default font's bold happened to break lines the same.
The plain paragraph now has the same bold runs.
- persian_letters_join_and_brackets_mirror counted glyphs, but Noto Naskh
Arabic draws a dotted letter as two glyphs (the dotless form and the
dot). It now compares letters by shaping cluster.
- glyph_runs_carry_their_text_for_pdf_export expected لا as one ligature
glyph; Noto Naskh Arabic draws the lam and the alef separately. The
ligature check now applies when the font forms the ligature.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Serbian is officially digraphic — Cyrillic and Latin are both in
everyday use, with Cyrillic the default per BCP 47 when no script is
given. Adds both as separate catalogs, same as the existing
Simplified/Traditional Chinese split handles script rather than
region:
- sr-latn.tsv: the actual translation, written clean-room from the
plain meaning of each English label (never from Microsoft Word's or
another product's Serbian localisation), covering the full existing
catalog (874 entries, same key set as zh-hans.tsv).
- sr.tsv: mechanically transliterated from sr-latn.tsv by a small
script (not committed — a one-off; the mapping is the standard
digraph-aware Serbian Latin→Cyrillic table). Brand/product/format
names (WordCraft, PDF, Discord, Zotero, SmartArt, file extensions…)
and `{placeholders}` are protected from transliteration, same names
that stay in Latin letters in every other catalog. Hand-editing this
file isn't the workflow — change sr-latn.tsv and re-transliterate.
- mod.rs: registers `sr` (Cyrillic) and `sr-latn`. No special-casing
needed in `candidates()` for the script split, unlike Chinese's
region-implies-script fallback — `sr-Latn-RS` already produces
`sr-latn` as a candidate before falling back to plain `sr`, and a
bare `sr`/`sr-RS` (no script tag, the common case) resolves straight
to `sr` since that's the registered default-script code.
- tests.rs: locale-matching coverage for both scripts, full-catalog
parity with the reference language, and bundled-font glyph coverage
for both Cyrillic and Serbian Latin's extended letters (š č ć ž đ),
mirroring the existing Ukrainian/Portuguese tests.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
select.column (Mod+Shift+F8) selects the same x range on a run of lines,
from two positions, two page points (Alt+drag), or by toggling column
mode where the arrow keys extend the block. Copy/Cut give one line per
row, Delete/Backspace remove only the block, character formatting and
Change Case apply per row, typing/paste replace the block. Any ordinary
caret move drops it. F8 keys now reach their shortcuts in the UI.
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>
styles.inspector shows/hides the Style Inspector pane and returns the
paragraph style, character style and the direct formatting that differs
from each; styles.inspect is the pure query; styles.inspectorClear
resets one level (paragraphStyle, paragraphFormatting, characterStyle,
characterFormatting) as a single undo step. The pane opens from the
Styles pane, with a clear button per level. Parity floor raised to 87%.
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>
New edit.pasteSpecial command ({"as"?, "text"?, "html"?, "rtf"?}). Without "as" it
lists the clipboard's formats and asks the front end for the dialog; with "as" it pastes in
that format. HTML/RTF go through the wordcraft-formats importers, with their lists, styles and
images merged into the document under fresh ids. Respects Track Changes (now also inside pasted
tables) and caps payloads at 16 MB. UI: Paste Special dialog (radio list + result hint),
Mod+Alt+V and Paste menu entry; strings in all four catalogs. Parity 355/405, floor 87.5%.
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>
Home › Editing › Find › Advanced Find (edit.advancedFind) opens the Find
and Replace dialog. With params it returns every match in the body, or
only inside the selection ("in": "selection"), and "highlight": true
turns on Reading Highlight, which marks all matches on the page and
follows edits until it's turned off. The ribbon's Find button becomes a
menu with Find, Advanced Find and Go To; the Find dialog gets a Reading
Highlight toggle. Parity 355/405.
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>
New Table Style and Modify Table Style (Table Design > Table Styles) make
real table styles in the document's style sheet: name, based on, and
formatting for the whole table, the header row and banded rows (borders,
fill, bold, font colour). The table at the caret takes the new style, it
shows in the gallery, and other tools that read table styles by name
(pandoc's custom-style, for one) see it.
- engine: table.newStyle / table.modifyStyle with documented params
(regions take borders, fill, bold, italic, colour, size, font; bandSize;
para); table.style only accepts table styles now.
- doc/layout: table styles gain header-row and row-band cell borders,
row-band character formatting and rows per band, applied by layout.
- docx: tblStyleRowBandSize and firstRow/band1Horz tcBorders and rPr are
read and written, so custom styles round-trip.
- ui: a compact dialog from the Styles menu next to the gallery; table
style tiles redraw when a style changes.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
View › Show › Gridlines only outlined table cells, so with no table on the
page it seemed to do nothing. It now draws a light 0.5 cm grid over the
page's text area, inside the margins, in Print Layout.
Table Layout › View Gridlines gets its own command, table.viewGridlines,
with its own view flag, and draws the table cell outlines as before. The
ribbon button, keytip and parity catalog point at it.
Both are screen-only overlays drawn by the canvas; print, PDF and image
exports never show them.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Zoom In/Out stepped from the stored manual zoom, which goes stale while a
fit mode (Page Width, One Page, Multiple Pages) sizes the page: from a 163%
Page Width, Zoom In jumped to 110%, i.e. zoomed out. The canvas now keeps
view.zoom equal to the shown zoom while a fit mode is on, so Zoom In/Out,
the Zoom dialog and view.state all start from what the user sees.
The View › Zoom group gains Zoom In / Zoom Out buttons (code-drawn icons;
the catalog already lists them there), and the Zoom dialog's slider is
logarithmic and wider so a short drag no longer jumps from 100% to 480%.
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>
The document canvas used egui's built-in wheel handling: touchpad deltas
lost their first and last events, Wayland compositors leave kinetic
scrolling to the app so a flick stopped dead, and a wheel notch scrolled
only 40 px.
The canvas now scrolls itself (crates/ui-egui/src/scroll.rs): touchpad
pixel deltas move the page 1:1 in the same frame; on Linux/BSD a flick
keeps coasting with time-based exponential decay, stopped by a click, a
key, a wheel notch or the end of the document (macOS, Windows and
browsers already send their own momentum); a wheel notch scrolls about
three lines (56 px at 100%, gently scaled with zoom) and still eases in.
Ctrl+wheel zoom and Shift+wheel sideways scrolling are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Shapes and text boxes get an effects model (outer shadow with colour,
transparency, blur, distance and angle; glow with colour, size and
transparency; soft-edge radius), the shape.effects command (presets,
custom values or null per effect, applied to every selected shape,
undoable, hostile numbers clamped), screen and PDF rendering with the
same banded approximation of a blur, DOCX a:effectLst read/write
(outerShdw, glow, softEdge) with a matching wp:effectExtent, and a
contextual Shape Format tab with Shape Fill, Shape Outline and Shape
Effects (Shadow, Glow, Soft Edges submenus) plus Arrange.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
egui's paste event only carries text, so a clipboard holding just a picture
sent no paste at all on Ctrl+V, and the ribbon/context-menu Paste only knew
WordCraft's own copy ("the clipboard is empty").
Desktop: the app reads the clipboard's picture through a new
Services::clipboard_picture (arboard, already in the build via egui-winit):
copied files first, else image pixels. Mod+V whose release arrives without a
paste event, and Paste from the ribbon or menus, insert that picture through
insert.picture (one undo step); text naming copied picture files inserts the
pictures. Pixel sizes are hostile: at most 16,000 px a side and 50 Mpx, the
buffer must match, files at most 200 MB.
Web: a document paste listener reads image files from the paste event when
there's no text and delivers them through the inbox like dropped pictures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>