* #1002: equation pictures share one texture, keeping theme colours on Windows
The Equation tab's gallery tiles, structure buttons and symbols were a
texture each: hundreds of small textures, created as galleries opened and
all freed at once when the preview cache filled. On Windows (DirectX 12)
tiles then showed other textures' memory (inverted pictures, red and teal
fills, damaged interface letters). They are now packed into one atlas
texture written in place, the way egui keeps its glyphs; it starts over
when full (at most once a frame).
Tests: every equation picture composited in both themes is only the
theme's greys; all pictures share one texture, none freed while browsing,
none overlapping; the atlas packs rows and starts over when full.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1002: the theme test looks for paper anywhere, not in the corner (Windows fonts reach it)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1002: equation atlas uses up to four pages and never wipes a page drawn from this frame
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>
* #816: copied equations reach other programs as MathML
A copy or cut whose selection holds an equation now also puts HTML on the
system clipboard, with each equation as Presentation MathML (wrapped in
<semantics> with the linear format as an application/x-unicodemath
annotation), so Word, LibreOffice, Google Docs and browsers paste a
formatted equation instead of `y=2x_s`.
- formats: new mathml module, a writer from the equation tree (runs,
fractions, scripts and pre-scripts, radicals, n-ary operators, delimiters,
functions, limits, accents, bars, border boxes, group characters, equation
arrays, matrices, phantoms) and a bounded reader (our annotation, else the
presentation elements, else a TeX annotation). HTML export writes
equations as MathML; HTML import (open and paste) reads <math> back.
- engine: edit.copy/edit.cut (and column copy) return `html` and keep
Session::clipboard_html when the fragment holds an equation.
- ui-egui: Services::set_clipboard_html; copy sites put HTML with the text
through it, falling back to egui's text copy.
- wordcraft: arboard set().html() with the text alternative; on Wayland
without data-control the text-only path is kept.
OMML on the clipboard (Word's "Office Open XML") needs custom clipboard
formats arboard can't set; left as a follow-up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #816: honest clipboard docs; MathML reader fixes (token cap, function names, column vectors)
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>
* #822: a chosen interface size is absolute; Auto keeps the display scale
On a display the system reports at 200 % (a bare window manager on X11,
where winit guesses the scale from the monitor's DPI), "100%" drew at
200 %. As in PhotoCraft and PdfCraft (pdfcraft#1005), a chosen size now
divides the display scale back out, so 100% is one screen pixel per
point. The new default, Auto, keeps the display scale and its menu entry
shows the size it draws at. Sizes run 75-300 %; ui.json moves to
uiScale and migrates the old interfaceSize (100 -> Auto).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #822: an old interface size becomes the size it drew at; a valid uiScale wins
A pre-#822 interfaceSize was on top of the display scale, so it is kept
until the first frame knows that scale and becomes old x native
(125% on a 2x display stays at 250%). Note that on the web a chosen
size cancels browser zoom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #822: rustfmt
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>
* #1116: keep a paragraph's space after clear of the footnote separator
On a page with footnotes (already there or brought by the line), a
paragraph's last line only fits if its space after fits above the notes
area too; otherwise it goes to the next page with its note (and, with
widow control, one more line). Contextual spacing and automatic spacing
between list items drop the requirement; pages without notes are
unchanged (space after may still run past the bottom margin).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1116: space after vs footnote separator uses the spacing code's tests, only on pages with notes
Contextual spacing drops a paragraph's space after only when that paragraph
asks for it, and the next paragraph counts as a list item only when it gets
a label (Numbering::shows_label), as in the spacing between paragraphs.
The kept space after is worked out only for a last line on a page with notes.
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>
* #884: a dragged shape anchors in the column it lands in, so that column's text wraps around it
arrange.bounds (the canvas drag) re-anchored a moved object to the paragraph
under its top edge but ignored x: on a multi-column page it took the last
paragraph starting above that y in any column, usually one in the last column.
Layout places a float when it lays out its anchor, so text in an earlier column
(already laid out) ran through a shape dropped there. anchor_paragraph now picks
the paragraph in the column under the object's centre (the ink placement uses
the first stroke point).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #884: a dropped object finds its column from the section's column geometry
Columns came from the distinct left edges of the page's text, so a
paragraph shifted by mirrored indents on an even page made a column of its
own and the drop anchored to the wrong paragraph. The columns now come from
the section's column boxes on the page. Notes in the layout parity doc that
lines a paragraph continues into the next column don't wrap yet.
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>
egui drops the page's keyboard focus on any click elsewhere. Command
buttons handed it back (#686), but gallery tiles, tab names, empty ribbon
space, View check boxes and the status bar's items did not, so the caret
vanished and typing went nowhere. After a click on background chrome that
leaves nothing focused (no text field, dialog, popup, Backstage or comment
balloon), the canvas takes focus back, as in Word. Tabs, style tiles and
status-bar items now report their name and state to screen readers.
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>
* #299: caret contrasts with the paper, page colour, shading or highlight under it
The caret was black (white only in Switch Modes' dark page), so it vanished
on a dark page colour, dark shading or a dark highlight. It now takes black
or white by WCAG contrast with the background at the caret
(wordcraft_render::background_at), cached per layout/view/position.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #299: shapes colour the caret's background only inside their outline; look up only when drawn
background_at hit-tests a filled shape's real outline (shape_path), so an ellipse's corner
shows what lies beneath. The caret's colour is worked out only when the caret (or IME text)
is drawn, and the unused Tokens::caret is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #299: rustfmt
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>
* #1129: no document behind the Start screen; Back shows an empty window
Launched with nothing to open, the Start screen no longer has a blank
document behind it. Esc or Back shows an empty window (no page, ribbon
greyed out but for Help, Info/Save/Save As/Print/Export off in File,
title "WordCraft") until Blank document, a template or a file is
opened; closing asks nothing. Commands that need a document say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1129: explicit no-document allow-list; control inspect/render need a document; fonts can be added
works_without_document lists the ui.* commands that make sense with no document instead of
letting every ui.* command through; font.add/list/remove work so a font file dropped on the
empty window is added. The control channel's document.inspect and ui.render answer that no
document is open instead of reading the hidden placeholder.
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>
A press on a selected balloon's Delete, Reply or Resolve button counted
as a click on the page and closed the balloon before the button saw its
click. Presses inside a balloon no longer leave it.
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>
* #1062: honour Remove personal information / Remove date and time on save
w:removePersonalInformation and w:removeDateAndTime (ECMA-376 §17.15.1.69-70)
are read into Settings, written back in CT_Settings order, and honoured by the
DOCX writer: comment and revision authors saved as "Author" (initials "A"), no
dc:creator / cp:lastModifiedBy, no w:date on comments and tracked changes. The
open document keeps its real values. New file.privacyOptions command sets both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1062: reading privacy options has no side effects; File › Options › Save check boxes
file.privacy reads the settings (a pure command: no undo step, no unsaved changes);
file.privacyOptions only changes them and refuses an empty call. File › Options › Save gets
a "Privacy for this document" section with the two check boxes, translated in every catalog.
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>
A middle click on the canvas starts scrolling toward the pointer, faster the farther it is from the click, with a dead zone around it. Another click, Escape, any key, the wheel or losing focus stops it; held and dragged, the release stops it. Same gesture and speed curve as pdfcraft's autoscroll; on for Windows and Linux/BSD, off on macOS and the web.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Text boxes: VML text boxes keep their internal margins, fit to their text and alignment
A VML text box's inset ("l,t,r,b", ECMA-376 Part 4 §19.1.2.22) was ignored,
so its text sat Word's default 7.2 pt in and 3.6 pt down even where the file
says 0,0,0,0, which is how letterheads and form headers place address blocks
exactly. It's now read like DrawingML's lIns/tIns/rIns/bIns: each value left
out keeps the default, a bare number is in EMUs, o:insetmode="auto" leaves
the defaults. mso-fit-shape-to-text (VML's "Resize shape to fit text") is read
as auto-fit, like a:spAutoFit, and the shape's v-text-anchor as the text's
vertical alignment. In a group, a member's margins go with that member's
text.
The reader feeds layout, so the editor, PDF and page images all change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drawings: hidden shapes and pictures are kept but not shown
A drawing marked hidden (VML visibility:hidden, floating or inline;
DrawingML wp:docPr/@hidden, or a group member's cNvPr/@hidden) was drawn,
took room and wrapped text. Word for Windows shows it in no view; it's now
kept and saved hidden, but left out of layout: not placed, no wrap area, no
room in its line, nothing drawn, its text box's notes not placed. Exports to
other formats and copies leave it out, the accessibility checker no longer
asks for its alt text, and the selection pane says it's hidden.
Word for Mac draws hidden VML shapes; Windows follows the markup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
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>
Reads rectangles, ellipses, triangles, arrows, stars, a connector with an
arrowhead and a text box, saves and reads back: kind, size, fill, outline
and width, position, wrapping, arrowhead and text-box text all match, and
the saved XML holds a wps:wsp per shape with its preset. The data loss in
#886 was fixed on the reading side by #671; this guards it.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SSIM of the greyscale pages (8x8 windows), share of differing pixels, the
share of reference lines with the same line break, median |dy| / max |dx| of
matched lines and page-start agreement, as result.json, plus diff maps
(reference red, WordCraft blue). --private writes numbers and line hashes only.
Std + serde_json like the rest of xtask; poppler's pdftoppm / pdftotext do the
rasterising and text extraction.
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
ui.wheel injects a mouse-wheel / trackpad scroll at a window point (the
control channel had no scroll input). perf_probe runs the real UI
headless, opens a document and times the frames that handle typing,
scrolling, zooming, ribbon tabs and resizes (numbers only).
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
canvas::show took the layout, painted the pages and only then handled the
frame's key events, so typed text changed the document after the frame
was painted and appeared one frame later (about 17 ms at 60 Hz). Keys are
now handled first, with the canvas focus from the last frame, and the
same frame lays out and paints the change. Measured headless with the
perf_probe example on a 106-page document: the work moves into the
keystroke's frame (median 0.4 ms -> 16.6 ms there, same total), removing
one frame of latency.
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
file.exportPng (and `wordcraft-cli render`) rendered with the on-screen
display options, which dim headers and footers while the body is edited,
so exported pages showed grey headers; the PDF export and Word's exports
don't. Page images now render with dimming off.
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Since content controls are kept (#392), a picture content control puts a
zero-width start marker before its picture. Line breaking keeps a line's
first item even when it doesn't fit, but counted the marker as that item, so
a picture a little wider than its line wrapped onto a second line, leaving a
marker-only line above it; in a fixed-height text box the picture's line was
then cut off. A logo in a header text box (which 0.5.0 drew) disappeared.
Markers before an item no longer count, as a display equation's check already
does.
Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Italian proofing: it-IT spelling, suggestions and grammar (#951)
* Italian proofing: Review Language menu and editing language
* Italian proofing: interface test for editing language
* Italian proofing: find the Swiss it_CH dictionary too
* Italian proofing: TeX hyphenation patterns (MIT option)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Write WordprocessingML 2003 by rewriting the DOCX writer's package into the 2003 vocabulary; file.save/saveAs take format wordml2003, File > Export lists the format, CLI convert --format wordml2003, a plain save over a 2003 file keeps it.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Rgb now reads "RRGGBB" / "#RRGGBB" as well as [r, g, b], so every colour
in CharProps and ParaProps takes the hex that format.color, para.borders
and table.shading take, and {"Rgb": "1B1611"} works for color. It is
still written as [r, g, b], so saved documents and settings are unchanged.
Fixes#865
Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The closure's borrow of the session already ends at its last use, and
clippy 1.98 rejects dropping a non-Drop value, which stopped xtask ci.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Track Changes: emptying a paragraph no longer turns its mark into the text's revision (#864)
Paragraph::delete copies the formatting of what it deletes onto the paragraph
mark when it empties the paragraph. That copy included the revision ids, so
accepting a tracked deletion of a whole paragraph text made the mark a deleted
mark too, and Accept All joined the following paragraph. Rejecting typed text
in an empty paragraph likewise rejected the paragraph mark. The mark now keeps
its own ins, del and fmt_change.
* Credit Silvertree2010 in the About window
* Docs: update the progress docs for this change
* Docs: Last reviewed stays as it was
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Microsoft Word treats generated numbered-list labels as independent words
in document word statistics, while omitting them from character counts.
Previously, word counting in WordCraft inspected plain run text only,
causing sample_document() to report 357 words instead of Word's 361.
- Add Document::story_word_count and story_word_count_of to compute words directly,
including generated numbered labels while skipping bullet markers and empty labels.
- Handle LevelSuffix::Nothing correctly by counting labels as independent words
without string concatenation artifacts.
- Keep Document::plain_text unadorned to maintain exact run text across docbin,
clipboard, and tests.
- In review.wordCount, report words via story_word_count while preserving character
counts from plain_text.
- Add regression tests covering numbered lists, bullets, LevelSuffix::Nothing, and
review.wordCount word vs character parity.
Fixes#893
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Docs: list the CharProps and ParaProps fields (#865)
* docs/mcp.md: color also takes RRGGBB and #RRGGBB (#865, #881)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A character colour given as a string now reads the way format.color reads it: RRGGBB, #RRGGBB, or auto/automatic in any case. The serialized forms ("Auto", {"Rgb": [r, g, b]}) read as before and are still what gets written, so documents and settings on disk round-trip unchanged. A colour that isn't one of these is still a parameter error.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Adds the 374 strings the Polish catalog still lacked, mostly from #750 by
@olekkut, with a few corrections, and drops the Russian exception from the
Polish coverage test now that Polish has every Russian key.
Co-authored-by: Aleksander Kutycki <74899889+olekkut@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Open RTF in its code page, and Word/RTF files whose extension is wrong
RTF import read every 8-bit byte as Windows-1252, so Cyrillic (and any
other non-Western) RTF came out as mojibake. Bytes now decode in the
current font's code page (\fcharset, \cpg), else the default font's,
else the document's (\ansicpg), for the single-byte Windows code pages
874 and 1250-1258.
A .doc, .docx or .rtf whose content is another of these formats now opens
by its signature, as Word does: RTF saved as .doc (hh.ru résumés and
accounting exports are commonly saved that way), a .docx renamed .doc, a
Word 97-2003 file renamed .docx.
* RTF: read the page size and margins, lay tables out on their cell edges
RTF documents opened on US Letter with 1" margins whatever they said, and
every table got equal columns and a full grid. Russian accounting and HR
exports (A4, 2 cm margins, borderless layout tables whose rows have
different \cellx edges) came out about twice as long as in Word.
- \paperw/\paperh/\margl…\margb (and the …sxn section forms), \headery,
\footery and \landscape set the document's section; out-of-range values
keep Word's defaults. Tables size to that page.
- A table's column grid is the union of its rows' \cellx edges; each cell
spans the grid columns between its left and right edges, as Word does.
- A table with no left/right cell borders is laid out without the grid
style (\clbrdrb rules alone don't make it ruled; \brdrnone is no border).
On 44 RTF files (mostly .doc-named exports) checked against Word's page
counts: total page-count difference 198 -> 30, exact matches 3 -> 17, none
worse.
* RTF: paragraph spacing and indents, RTF defaults, the default font
- \sb, \sa, \sl/\slmult (multiple, at least, exactly), \li/\lin, \ri/\rin
and \fi are read onto each paragraph (new model::Spacing); \pard resets
them.
- The document's paragraph defaults are RTF's (single spacing, no space
around paragraphs) instead of a new document's 1.15 and 8 pt after.
- Text that never selects a font is in the default font (\deff, font 0
when the file names none), as Word shows it.
Against Word's page counts on 251 real documents (44 RTF, mostly .doc-named,
plus .docx): exact matches 212 -> 216, within 10% 248 -> 249; a 68-page
Russian federal law RTF goes from 89 to 73 pages (the rest is the Word
installation's own default margins, which the file doesn't name).
* RTF: import headers and footers
\header, \headerr, \headerl, \headerf and the \footer… forms were skipped,
so letterheads, logos and "page N" lines were lost. Each group is now read
into its own output (the body's paragraph, table and row state is set aside
and put back when the group closes), built into the document with its
pictures (new model::part_blocks), and attached to the section as the
default, first-page or even-page header or footer; \titlepg sets the
separate first page. A header left open by a truncated file still ends.
40 of 41 RTF files in the corpus have headers or footers; against Word's
page counts exact matches go 216 -> 218 of 251, none worse.
* RTF: cell and paragraph borders
Tables either got a full grid (any left/right cell border) or none, and
paragraph borders were kept only on empty paragraphs (as a rule), so the
rules under letterheads and section titles were lost.
- Cells: \clbrdrt/l/b/r with their line style (\brdrs, \brdrdb, \brdrdot,
\brdrdash, \brdrth, \brdrtriple, \brdrwavy…, \brdrnone), \brdrw width and
\brdrcf colour become the cell's own borders (model::Cell::borders); RTF
tables get no grid style, so only the borders the file names are drawn,
as in Word.
- Paragraphs: \brdrt/b/l/r likewise become the paragraph's borders
(model::Para::borders), reset by \pard; an empty paragraph with only a
bottom border stays a rule.
40 of 41 RTF files in the corpus name cell borders; page counts against
Word are unchanged (218 of 251 exact).
---------
Co-authored-by: Магда М.Г. <mail@mgmagda.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
RTF import read every 8-bit byte as Windows-1252, so Cyrillic (and any
other non-Western) RTF came out as mojibake. Bytes now decode in the
current font's code page (\fcharset, \cpg), else the default font's,
else the document's (\ansicpg), for the single-byte Windows code pages
874 and 1250-1258.
A .doc, .docx or .rtf whose content is another of these formats now opens
by its signature, as Word does: RTF saved as .doc (hh.ru résumés and
accounting exports are commonly saved that way), a .docx renamed .doc, a
Word 97-2003 file renamed .docx.
Co-authored-by: Магда М.Г. <mail@mgmagda.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Native-speaker review of the rows added late in #729:
- Overtype is « Refrappe », the term current versions of Word use in French
- « Attacher… » to match « Attacher un modèle »
- Outline promote/demote and the Outlining tab (« Mode Plan »), consistent
with the existing Promote/Demote rows and « Fermer le mode Plan »
- SmartArt basic layouts « … de base »
- tense and wording in three help texts, and the Previous 1 or More wildcard
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Old Russian and Central European documents name fonts by their Windows 3.1
charset aliases: "Times New Roman Cyr", "Arial CYR", "Courier New CE",
"Arial (Hebrew)". Windows maps these to the font itself (FontSubstitutes);
we treated them as unknown families and fell back to the sans default.
resolve() now tries the base family, and substitutes for it when it isn't
installed (Times New Roman Cyr -> Liberation Serif, like Times New Roman).
Co-authored-by: Магда М.Г. <mail@mgmagda.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Word's symbol fonts are proprietary and never bundled, so text set in them
showed the substitute's letters. New wordcraft_doc::symbol_fonts maps every
code (stored as U+F020-U+F0FF, e.g. w:sym, or as a plain 8-bit code) to its
Unicode equivalent: Symbol from Unicode's Adobe symbol.txt, the Wingdings
and Webdings sets from Unicode L2/12-368 (WG2 N4384), checked against UCD.
Layout shapes the mapped characters (fallback faces draw them); PDF text,
copy as text and TXT export read as the symbols; the stored text and docx
round trips are unchanged.
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>
WMF and EMF pictures already drew (crates/metafile) when they came inside a
document, but Insert › Picture refused them ("not a supported image"), and the
file dialog, drag-and-drop and paste lists left them out. A metafile is now
placed at its own frame size (points for placeable WMF and EMF; very large
frames scaled to 22 inches on the longer side) and stored as .wmf or .emf, so a
save writes it as such. render gains metafile_size().
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>
* #848: font list previews no longer load every family they show
The Font box drew each entry in its own face through the full layout
pipeline, which loaded the whole family into the font database for good
(faces are never unloaded). Every trip through the list kept tens of
megabytes. Entries are now drawn with FontDb::preview_face: a loaded
family's face is shared, an installed one is read into a face the
database doesn't keep and is freed when dropped, and render_text_line
draws it without the shared outline cache.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Font preview test tolerates other tests loading fallbacks; gaps rows 1–3 de-duplicated, #848 noted in row 3
---------
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 previous release build failed in craft-common-actions, not in this
repo: riscv64 packaging got --formats deb,rpm,tar (fixed in
craft-common-actions#3), Linux jobs lacked the ALSA headers
(craft-common-actions#5), and Windows signing needed the AZURE_* secrets
listed in the caller.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A click in the left margin beside body text selects that line (trailing
space, and the paragraph mark on a paragraph's last line), a double-click
the paragraph, a triple-click or Ctrl/Cmd+click the document; dragging
there selects whole lines and Shift+click extends by lines. The pointer
shows the arrow there. Layout gains selection_bar_at, body_line_at and
line_span; select.line takes an optional pos; select.dragUnit takes the
units line and document.
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>