* #3674: PDF export writes a glyph id above 65535 as .notdef instead of failing
Shaping gives 32-bit glyph ids (a format-12 cmap can map a character to
any of them), and the PDF exporter passed them to krilla unchecked.
krilla's CID font takes 16-bit ids and unwraps the conversion
(vendor/krilla/src/text/cid.rs:161 at craft-libs b926b5c0), so one such
glyph failed the whole export natively and aborted the tab on wasm.
The three places that build a KrillaGlyph (body text, metafile text and
the text watermark) now go through glyph_id(), which writes an id that
doesn't fit as glyph 0. The glyph keeps its place and its text range, so
the rest of the run is unchanged.
Regression test: glyph_id_above_u16_exports lays out a paragraph, sets
one glyph's id to 70000 and exports the layout. It failed before with
"PDF export failed internally: ... TryFromIntError(PosOverflow)".
* #3674: the replaced glyph keeps its place, not its text
krilla writes no ToUnicode entry for glyph 0, so a glyph written as
.notdef doesn't extract: "Wide glyph" with the W's id set to 70000
extracts as "\u{FFFD}ide glyph". The rest of the run extracts as
before. The comment on glyph_id() said the glyph kept its text.
* Adapt glyph_id imports to main's exporter text module
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #4696: built-in heading styles have their outline level without w:outlineLvl
Some producers write "heading 1"…"heading 9" in styles.xml without w:outlineLvl. Word
gives those built-in styles their outline level anyway (checked: Word for Windows reports
OutlineLevel 1 and 2, whatever the style id), so their headings show in the Navigation
pane. WordCraft read no level, and the Headings tab stayed empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4696: review — w:customStyle="on" marks a custom style too
A custom style named "heading 4" with customStyle="on" (ST_OnOff) was taken for Word's
built-in heading and got an outline level.
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>
* #4548: comment ranges inside footnotes and endnotes are kept on read
read_opened read footnotes.xml and endnotes.xml before comments.xml, so
a comment range inside a note looked its id up in an empty comment map
and was dropped. Comments are now read first. read_comments uses nothing
the notes set up.
Regression test: tests/it/note_comments.rs (synthetic markup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: file-format-parity row for comment ranges in notes (#4548)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The RTF reader only recognised \pngblip and \jpegblip, so a \pict
holding \wmetafile8 (how Word writes pasted and floating pictures) or
\emfblip was dropped and the document opened without its pictures.
Keep them as wmf/emf media when the bytes are a metafile (the extension
comes from the bytes; raster bytes go through the usual image path,
anything else is dropped), sized from \picwgoal/\pichgoal and the scale,
else from \picw/\pich (0.01 mm, or pixels under \wmetafile1; zero or
negative falls back to the default). The metafile renderer already
draws them and the DOCX writer saves them as .wmf/.emf.
RTF export dropped WMF/EMF pictures as well (it re-encoded every other
format through the raster decoder): it now writes \emfblip with the raw
bytes and \wmetafile8 without the placeable header, \picw/\pich in
0.01 mm.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/formats/src/markdown/export.rs:167-182 (inlines_md_impl, emphasis marker emission)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M1 #4658: bound Markdown reference-label work
* #4658: align Markdown progress status dates
* docs: keep ROADMAP's second status line as on main
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Formats: preserve HTML input newlines (#4810)
* docs: fold the #4810 notes into the existing status lines
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/formats/src/latex/export.rs:402-444 (escape_text, `prev` resets per call) and :230-237 (inlines, runs concatenated)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/formats/src/html/builder.rs:338-344 (Builder::text)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(formats): saturate HTML <font size> offsets so extreme values cannot overflow
* test(formats): assert the saturated HTML font sizes, not only that the text survives
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/formats/src/model/color.rs:45-47 (parse_color, 8-digit hex branch)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/formats/src/sheet/date.rs:136-152 (iso_time, PT duration branch)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The Linux same-user check read the uid of the listener's side of the
connection from /proc/net/tcp. Until the listener accepts it, that row has
no socket file (inode 0) and reads uid 0 whoever listens, so a Zotero that
hadn't accepted yet was refused as "another user (uid 0)". The ui-egui
Zotero tests failed about one run in three on a busy machine because of it.
Rows without a socket file (unaccepted connections, TIME_WAIT) are now
skipped, and the listening row on the listener's address stands in for the
connection's own; two users listening there is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Ctrl+V pastes a clipboard picture again with egui 0.37
Since the egui 0.37 migration, egui-winit keeps both the Mod+V press and
its release, so the canvas never sees the V release it pasted a clipboard
picture on. Outside Wayland, egui-winit instead sends Event::PasteImage
when the clipboard holds a picture and no text, which the canvas ignored.
Ctrl+V pasted nothing while the ribbon and context-menu Paste still worked.
Answer PasteImage with edit.paste, which reads the picture through the host
like the ribbon's Paste. The web build sends no PasteImage, so nothing
pastes twice there.
Part of #4782
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Mark the clipboard picture as pasted on PasteImage so a later V release cannot paste twice
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
File › Options › Proofing › AutoCorrect Options did nothing on the Start
screen: tools.autocorrect wasn't in works_without_document, so the click
was dropped. Its options are user preferences (session.prefs), like the
other File › Options settings, so it now works without a document.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Shading buttons show the last picked colour as their bar (#4412)
The paragraph and table Shading buttons always drew the icon's yellow bar,
whatever colour a click applied. They now draw the remembered colour as the
bar, as Font Color and Text Highlight Color do (#4087), on an icon without a
bar of its own.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* docs: ui-parity row for the Shading button colour (#4412)
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
With the painter armed, only dragging across the text applied the copied
formatting: a double-click (a word) or triple-click (a paragraph) selected
the text and returned before the painter was used. Both paths now go through
one helper, so the click paints what it selected and the painter goes off.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Options: a blank User Name stays blank after a restart (#4368)
* docs: ui-parity row for the blank User Name (#4368)
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Inserting WordArt selects its placeholder text, so typing should replace it. From the Insert
gallery the first keystrokes went nowhere: the gallery's cells (and the Text Fill, Text Outline,
Glow and Transform galleries beside them) ran their command with `app.run` instead of
`run_from_ribbon`, so the canvas never asked for focus back and the page ignored the keys.
They now go through `run_from_ribbon` like every menu item.
Regression test renders the style gallery, picks a style and checks the page asks for the
keyboard; it fails without the change.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Adds the key that Apache POI's Excel-written test file
protected_passtika.xlsx (password "tika") derives, computed from
[MS-OFFCRYPTO] §2.3.4.7 independently of the implementation, so real
Word 2007 files stay pinned.
Also adds the contributor's people.toml entry.
Co-authored-by: Bra1nZ <bra1nz@astraoffice.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Layout parity: field results take \* DollarText, \* Hex and \* ArabicDash
docs/layout-parity.md gets a row for the three field switches that #4226
added, a revision entry and a new status-line clause. The row describes
main as it is after 8c035fbb, where \* ArabicDash formats any number
field and table formulas, not PAGE alone.
* Layout parity: dollar text keeps its digits only past 999,999.99
Cardinal and ordinal text keep their digits past 999,999. `\* DollarText` writes
results up to 999,999.99 (crates/layout/src/fields.rs dollar_text).
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M14: validate window fallback geometry and avoid duplicate symbol names
Verified regression cases and full cargo xtask ci before publication.
* M14: document review helpers and regression contracts
Documentation-only follow-up; full cargo xtask ci passed.
---------
Co-authored-by: kalmarr <680152+kalmarr@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
AGENTS.md (and CLAUDE.md where present) gets a CI workflows section: workflows only call
craft-common-actions at @main, with no other jobs; feature and fix PRs don't touch CI and
link or open a craft-common-actions issue (a PR only for a small fix that is safe for
every app and does not increase CI time); legacy jobs
aren't added to and move only as their own task, staying until a shared workflow runs them.
windows-arm64.yml moves from its SHA pin to @main. The pinned revision is on
craft-common-actions main and the workflow (and the actions it calls) is identical
there, so nothing changes in behaviour.
Ported onto current main (title_stem now lives in app/frame.rs; the window
title is built in windows.rs).
Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com>
Count table opens in a single linear pass instead of rescanning
earlier events for every lone table-open marker (O(events) instead of
O(lone * events)).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* i18n: Traditional Chinese interface draws hanzi with an installed TC font
The zh-hant interface shared the Simplified Chinese font order, so its first
CJK face was the embedded Noto Sans CJK SC and traditional characters were
drawn with mainland glyph shapes (說, 資, 這, 為, 戶).
On the desktop, a Traditional Chinese interface now puts an installed
Traditional Chinese font before every CJK face: PingFang TC on macOS,
Microsoft JhengHei on Windows, Noto Sans CJK TC on Linux. Noto Sans CJK SC
stays after it as the fallback. Without an installed TC font, and on wasm,
the order is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Credits: add TobyWu666 to contributors/people.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: find Traditional Chinese fonts at the root of a font folder
Linux user font folders (~/.fonts, ~/.local/share/fonts) often hold font
files directly; the Traditional Chinese candidates only named files in
distribution subfolders, so those fonts were missed (CodeRabbit review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: skip the Traditional Chinese font test without embedded catalogs
Since the catalogs come from craft-i18n (#4072), a build without
CRAFT_I18N_DIR is English only, so the test now skips like the other
catalog tests. Also rustfmt hant_font_chars.
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>
* #1103: ignore macOS launch arguments and name the real cause when a file won't open
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: filter launch args on macOS only; leave Backstage after a failed Finder open
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: leave Backstage after a failed drag-and-drop open
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: name the file when an open fails after the Save changes prompt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: report failed opens from every deferred path in execute_user
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: keep the named failed-open message after a browser save
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: only opens held back by a prompt or save leave Backstage on failure
A direct open from Recent keeps File open and reports there (#2938).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1103: re-apply open-failure reporting on main's split ui-egui modules
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Qualify i18n paths in report_open_failure after merging main
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1744: WIP
* #1744: Select Table copies and cuts the whole table
Copy of a whole-table selection used to keep only the cells' paragraphs
(copy_blocks flattened a cross-cell range), and Cut only cleared the text.
Now Copy puts the table on the clipboard and Cut removes the table.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1744: selected table rows and cells copy as a table; Cut clears their text
Copy of whole rows, cells of a row or a block of cells puts a table of just
those cells on the clipboard (Table::slice). Cut of rows or cells clears their
text and keeps the rows and cells, as in Word (Word check A).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1744: review fixes
Table::slice widens every row to a merged cell partly selected (no ragged
copy); an empty selected table or cell copies and cuts; a text selection over
exactly a nested table copies it; one selected_table per Cut; tests for
slice merges/widths/controls, tracked and locked cuts, and the text route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1744: Cut clears every cell Copy takes; Spike stays off for empty cell selections
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1744: a copied part of a percentage-width table keeps a point width
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* ROADMAP: tidy merged header
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* ROADMAP: keep only the #1744 rows over main's version
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* ROADMAP: remove conflict markers; keep only the #1744 rows over main's version
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* #1744: rename the range Cut helper clear_range_cells (main added clear_cells)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Remove conflict markers from ROADMAP.md; leave main's header line alone
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* cargo fmt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Import Path in table structure after merging 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>
* Tables: share span minima by preferred widths (#1883)
* 1222: Match nested preferred span weights
* docs: word the #1883 layout-parity entries
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* packaging: Flatpak finds and prints to CUPS printers
The Print dialog listed no printers in the Flatpak: the runtime has libcups but not
lpstat/lp, the sandbox couldn't reach the CUPS socket, and driverless network printers
(lpstat -e) need Avahi. Bundle lpstat/lp/lpoptions from CUPS 2.4.20 (Apache-2.0,
statically linked, licence installed) and grant --socket=cups and
--system-talk-name=org.freedesktop.Avahi in both manifests.
* packaging: Flatpak reads the user's CUPS settings (~/.cups, read-only)
Review (CodeRabbit): without it the sandbox's lp/lpstat ignore the user's default
printer and saved options (lpoptions) and a print server named in their client.conf.
* packaging: Flatpak CUPS tools reach the host socket when client.conf names a custom path
Review: Flatpak mounts the host's CUPS socket at /var/run/cups/cups.sock whatever
path ~/.cups/client.conf or CUPS_SERVER names, so lp/lpstat/lpoptions now run through
a wrapper that points a socket path missing in the sandbox there. Server names
(remote servers) and sockets that exist are left alone. Tested in the sandbox:
CUPS_SERVER=/run/custom/cups.sock lists the host's printers through the wrapper and
none without it; a host name is passed through unchanged.
* packaging: redirect a custom CUPS socket only when it is the one Flatpak mounted
Review (CodeRabbit): Flatpak picks the socket it mounts at /var/run/cups/cups.sock
with stricter rules than CUPS (flatpak-run-cups.c: a socket CUPS_SERVER, else the
first line spelled exactly "ServerName /path" in ~/.cups/client.conf, else the
default), while CUPS reads the keyword in any case and takes the last ServerName. The
wrapper now redirects only when CUPS' socket is the one Flatpak chose, so a job can't
reach a different server; otherwise CUPS fails on the missing path.
* packaging: never redirect a CUPS_SERVER the sandbox was given
Review (CodeRabbit): Flatpak picks the socket it mounts from the host's CUPS_SERVER,
so one set by flatpak run --env or an override may not be what it mounted. The
wrapper now leaves any CUPS_SERVER alone and redirects only a client.conf socket that
matches Flatpak's choice; otherwise CUPS fails on the missing path instead of
reaching a different server.
* packaging: point CUPS at the mounted socket only when it is the one Flatpak chose, else fail
Review (CodeRabbit): with a custom socket listed first and the default last in
~/.cups/client.conf, Flatpak mounts the custom one at /var/run/cups/cups.sock while
CUPS picks the last line, finds that path, and talks to the custom server. The
wrapper now compares CUPS' socket with Flatpak's choice even when the path exists:
equal, CUPS is pointed at the mount; different, the tool fails with an error instead
of reaching another server.
Add packaging/freebsd/package.sh, matching the sibling craft apps. The release
workflow cross-compiles aarch64 FreeBSD in an x86_64 VM with CARGO_BUILD_TARGET
set; the script builds with --target, reads binaries from the target directory,
names the archive after the target arch, and checks ELF/FreeBSD/arch instead of
running binaries that cannot run on the host.