Merge upstream9d1067fc, preserving both progress-document histories.
Copy the session highlighter choice into command previews, and verify that the
preview toggles the chosen colour without changing the document or undo history.
Document the shared-registry constructor as well as the public constructor.
Validation: complete integrated cargo xtask ci passed all8 gates, including all4
highlight regressions; RUST_TEST_THREADS=1. Local named-function doc audit16/16.
Native Word comparison pending. No new interface strings in this fix.
Resolve highlight runs one at a time without the formatting-state query cap.
Pin mixed and inherited tails at 10000/10001/10002 runs, including Undo/Redo.
Document the changed command/session/helpers and explicit palette setter behavior.
Validation: red regression at10001 runs; all3 highlight tests pass after fix.
Full cargo xtask ci passed all8 gates:3963 passed,0 failed,4 existing ignored.
RUST_TEST_THREADS=1. Local named-function doc audit:13/13 documented; CodeRabbit
uses its own analyzer. Native Word comparison remains pending.
Review of #4171: cacheable() shrank an oversized picture to half the cache
minus its source size, so a source of 64 MiB or more left no room, and the
cache, which counted the source bytes each entry held, refused the entry.
Such a picture (an uncompressed 5000 × 5000 BMP is 75 MB) was still decoded
on every render. A broken source of 128 MiB or more was refused the same
way, so its decode was retried on every render too.
The cache held each source Arc only to recognise it. Before #293 it keyed
entries by the raw address, which a freed source could hand to new bytes.
A Weak keeps that guarantee, since the allocation itself stays while the
Weak lives, but the bytes are freed with the document. The cache now
counts only what it decoded, and cacheable() no longer depends on the
source size.
Tests: the #3665 test caches its shrunk picture for a source larger than
the whole cache. media_cache_counts_what_it_keeps_and_lets_sources_go
replaces media_cache_counts_the_source_bytes_it_holds, which pinned the
old accounting, and the same-allocation test also checks that a freed
source's address isn't taken for a new one. Counting the source again
fails two tests; keeping a strong reference fails one.
Word's Read Mode has View › Page Color with None, Sepia and Inverse.
The Read Mode toolbar now has a Page color menu beside Line spacing,
backed by the view.readPageColor command. None draws the columns on
white and does not show the document's page color or page picture.
Sepia draws them on Word's sepia paper (RGB 112, 66, 20 at 80%
transparency over white) and leaves the text's colors alone. Inverse
draws the dark page that View › Switch Modes draws.
The setting is screen only, is not saved, and starts at None on each
entry to Read Mode, as the other reading controls do. In Read Mode it
sets the paper even while Switch Modes is on. Print Layout and the
other views still show the document's page color, and Switch Modes
still darkens them.
insertColumnRight shrank the grid but left each cell's width at the old value,
so the page laid the cells out wider than the grid and the saved w:tcW disagreed
with it; the cells now take their grid columns' widths.
file.versions with a restore also saved when the restore was refused (protected
document, no such version, or a restore that is not a non-negative integer):
the restore is checked before anything is saved, a non-integer restore is
refused instead of being read as absent, and an index that does not fit in
usize names no version rather than wrapping to a small one on a 32-bit target.
pixmap_for offered every decode to the media cache, and the cache
refuses an entry larger than its whole budget (128 MB, source bytes
included). A picture between that and the 512 MB decode budget (about
34 to 67 M pixels, which can be a small PNG) was decoded, thrown away
and decoded again each time a page holding it was drawn: while typing,
scrolling or zooming.
Such a picture is now averaged over square blocks of whole pixels down
to half the cache before it is stored. The pixels are premultiplied, so
edges keep their colour. Pictures the cache already held keep their
full size. Pages draw a picture scaled to its frame, so it lands in the
same place; a tiled page background now takes its tile size from the
picture's natural size instead of the pixmap's.
Measured with a 7000 x 7000 PNG on a page, dev profile (workspace
opt-level 1): every render took 0.27 to 0.28 s before. Now the first
takes 0.43 s and the next ones 0.3 to 0.5 ms. Peak RSS stays 399 MB,
the decode's own.
On Windows the paste path asked arboard (3.6.1) for decoded pixels.
arboard decodes the registered "PNG" format and CF_DIBV5 with no image
limits, so a clipboard item of a few dozen bytes whose header declares
100,000 x 100,000 pixels made the decoder allocate tens of GB. The
allocation failure aborted the app outside Session::run's guard, before
paste_picture's size checks ever saw the picture.
On Windows the app now reads the clipboard itself (clipboard-win, the
raw read capped at 200 MB):
- "PNG" goes to Insert Picture as encoded bytes, once its header shows
a size a paste takes (16,000 a side, 50 M pixels).
- Otherwise CF_DIBV5 or CF_DIB is probed through
wordcraft_geom::picture::probe_dib, checked the same way, then decoded
within the decode budget into pixels.
probe_dib reads a DIB in the layout the image crate expects: a 32-bit
BI_RGB DIB with an alpha mask becomes bit fields, so its alpha is kept
as arboard did. A V4 or V5 bit-field DIB without the copy of its colour
masks after the header (arboard writes one) gets that copy, because the
decoder skips 12 bytes there and would otherwise read into the pixels.
Linux keeps using arboard, which decodes the clipboard's PNG within the
image crate's default 512 MB limit. macOS keeps it too: arboard decodes
the pasteboard's TIFF there, which can still reach the JPEG strip
decoder of #3645; that path is untouched here because it can't be built
or run on this machine.
cargo check and clippy for x86_64-pc-windows-gnu pass on the
Windows-only code; it hasn't been run on Windows.
Remove Background pushed the four neighbours of every pixel it cleared
before looking at them, so its stack grew with the picture. It now
fills a row span at a time and keeps one seed for each run of matching
pixels beside a filled span, besides the border's. A uniform 2000 x
2000 picture now peaks at the border's 8,000 seeds. The region it
clears is the same: background reachable from the border, colour kept.
The Shadow style blurred a padded copy of a plain rectangle with a
Gaussian of sigma max(w, h) / 80, a kernel that grows with the picture,
through several full-size buffers. The blur of a rectangle is the
product of its sides' blurs along each axis, so the shadow is now drawn
from two profiles, each one difference of the kernel's running sums
(the same kernel image::imageops::blur uses). A test compares it with
the old blur: no channel differs by more than 2.
Measured on a uniform picture, dev profile, wall time and peak RSS of
the test process:
- Shadow, 4000 x 4000: 15.4 s and 414 MB before, 0.31 s and 203 MB now
- Remove Background, 6000 x 6000: 1.02 s and 1,028 MB before,
0.54 s and 325 MB now (the picture and its RGBA copy alone: 289 MB)
The input is already held to the decode budget (#3644).
adjust_media capped only the decoded input. The frame, shadow and
default styles then sized their bitmaps from it with unchecked u32 sums.
A thin 1 x 2,000,000 PNG of a few KB and Thick Frame asked for about
1.8 TB. The failed allocation aborted the app outside Session::run's
panic guard, so every window's unsaved work was lost.
picture.border {"width":200} on it asked for 3.2 GB.
Picture operations are now fallible. Every bitmap a style or border
draws on comes from canvas(), which refuses a size the renderer couldn't
draw either (two RGBA buffers within the 512 MB decode budget) before
allocating, and the margins are added with checked arithmetic. The edit
then fails with "picture is too large to edit" and the picture stays as
it was.
The tiff crate (0.11.3, through the image crate's tiff feature) decodes
a JPEG-compressed strip (compression 7) with a zune-jpeg decoder of its
own and default options: up to 16384 x 16384 a strip, whatever size the
TIFF declares. Neither the image crate's limits nor the budget reach it.
The picture probe now reads the first image's compression from the
TIFF's IFD and decodes only none, CCITT Group 4, LZW, Deflate and
PackBits. Anything else, old-style JPEG (6) included, is refused before
a decoder is made. Drawing, PDF export, wrap outlines, the crop dialog
and picture edits all ask the probe, so such a TIFF in an opened
document gets the grey box.
Insert Picture and Change Picture read only the header's size, so they
took such a TIFF. They now refuse it and say why.
Document pictures now decode through wordcraft_geom::picture (behind a
new `image` feature of the geom crate). It reads the header, refuses the
picture before anything is allocated when the decode's peak would pass
the budget, then decodes with the header's size and the budget as
limits.
The peak is the larger of three moments: the decoder's output beside
what it holds while decoding, the output beside its RGBA8 conversion,
and that beside a premultiplied copy. A progressive JPEG's decoder
(zune-jpeg) holds an i16 for every coefficient of every component, in
whole MCUs, for the whole picture; the SOF header gives that size. A
7900 x 7900 progressive JPEG without subsampling needs 9 bytes a pixel
(561 MB), past the 512 MB budget, although two RGBA buffers (499 MB)
fit. With the fix reverted, the test's header-only file of that size
was decoded and the test process peaked at 542 MiB resident.
- Render and PDF export ask the probe in their wrappers (decode_pixmap,
load_image), so a picture past the budget gets the grey box or is
left out, as before.
- The crop dialog and every picture edit decoded with load_from_memory
and looked at the size afterwards. They decode through the probe now,
and so do wrap outlines and the RTF re-encode.
- A picture edit has the drawing budget instead of an 80 M pixel cap
checked after decoding. A picture past that budget (about 67 M RGBA
pixels) can't be drawn either, so no edit that could be seen is lost.
A paragraph whose list level is past the last one goes to the last level instead of overflowing, for Tab before a later line, Increase Indent and Demote.
Found while testing crates/formats/src/latex.rs:2212-2216 (accent command handling, fallback for an empty argument)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Connecting eagerly made the server fail when the app wasn't open yet, and a
client marks a failed MCP server failed for its whole session: an unavailable
app now leaves a lazy remote that connects on the first call, warns about a
missing control key only once connected, and says which state it is in.
--connect addresses are checked before the server starts, so 127.0.0.1:notaport
reports a numeric port instead of retrying on every tool call.
Tab at the start of a later line of a list item numbered by its style now takes the item down a level, as it does for an item numbered directly, instead of setting a left indent. A list level past the last one goes to the last level instead of overflowing.