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.
docs/gaps.md row 4: one row (t06 had kept a second, older copy of it); t05's #1840 and t06's #2964 clauses both in, main's #2963 clause kept. docs/recovery.md: t06's retry sentence with t01's base64 size text. Test files: both tracks' tests kept whole.
`now_ms` in layout, `now_unix` in engine, formats and pdf gated
`js_sys::Date::now()` on `target_arch = "wasm32"`, which also matches
wasm32-wasip1 and wasm32-wasip1-threads. There js-sys has no host to call,
so it panics ("cannot call wasm-bindgen imported functions on non-wasm
targets"), and since `layout()` times itself with `now_ms`, every document
laid out on WASI aborts. `std::time` works on WASI, so these now use the
browser clock only on wasm32-unknown-unknown, the gate wordcraft-docx
already uses.
contour::picture_outline turned the crop into a pixel window on its own,
so a sliver or overlapping crop gave an empty window and Tight/Through
wrapping fell back to the frame while the renderers draw a 0.5% sliver.
It now uses wordcraft_metafile::crop_window, with the upper pixel edges
rounded up. A PDF test covers the bitmap and metafile paths of the export
(CodeRabbit on #3247).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* WordArt text effects and transforms, and shape connectors (#416)
- Model: WordArt text effects on runs (fill solid/gradient/none, outline,
shadow, glow, reflection), text warps on shapes (all DrawingML presets
kept, adjust values capped), connector kinds (straight, elbow, curved)
with glued ends, arrowheads and drawing ids (ShapeExtra).
- DOCX: w14:textFill/textOutline/shadow/glow/reflection in w:rPr,
a:prstTxWarp in wps:bodyPr, wps:cNvCnPr a:stCxn/a:endCxn,
a:headEnd/a:tailEnd; glued-to shapes keep their docPr ids.
- Layout/render/PDF: text with effects drawn from glyph outlines as paint
layers (gradients, shadow/glow bands, faded reflection); warped text
boxes bent along arch up/down, circle, wave, slant, inflate/deflate,
chevron and triangle; connectors and arrowheads drawn as paths.
- Engine: insert.wordArt makes a floating WordArt text box with our own
styles; format.textEffects, wordArt.style/textFill/textOutline/transform,
shape.connect; glued connectors re-route after object edits.
- UI: WordArt style gallery (Insert, Home › Text Effects, Shape Format ›
WordArt Styles), Text Fill/Outline/Effects menus, Transform previews
drawn from the warp geometry, connectors in Insert › Shapes, drag a
connector end onto a shape to glue it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix tests for ShapeExtra, regenerate parity checklist, fmt (#416)
* Regenerate parity checklist after rebase (#416)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Text now wraps around a Tight or Through object's shape instead of its
bounding box. The outline is the object's wrap polygon (wp:wrapPolygon,
in the 21600-unit space of its frame) when it has one; otherwise it is
derived: a shape's geometry (ellipse, triangle, star, heart... flattened),
the convex hull of a picture's opaque pixels (alpha threshold on a coarse
grid, at most 64 points), or the rectangle. The outline turns and flips
with the object, keeps the left/right and top/bottom distances from text,
and is built once per placed object; each line asks it which stretches of
its band it covers. Through also lets text into gaps inside the outline
that are wide enough.
DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and
wrapThrough; a shape without one gets its outline written, other objects
their rectangle, since the schema requires a polygon. Square, top and
bottom, behind and in front of text are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
#214 gave now_iso the browser's clock on wasm; the other clock helpers were
still stubs there: Insert Date's weekday was always Thursday, the PDF
creation date was left out, and the layout and UI millisecond clocks
returned 0, so browser AutoSave's timer never fired, the caret didn't blink
and status messages never cleared.
The engine's clock is now one now_unix() (std on native, js_sys::Date in the
browser, as #214 does) that now_iso and the weekday share; the pdf, layout
and ui-egui clocks read js_sys::Date the same way on wasm. Refs #188.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(doc,docx): complex-script run properties and RTL OOXML round-trip
Model the formatting Word applies to Persian, Arabic and Hebrew text:
cs, font_cs, size_cs, bold_cs, italic_cs and lang_bidi on CharProps,
resolved with Word's fallbacks (ResolvedChar::complex). Size, bold and
italic pair with their complex-script values so a run's size or bold
also covers its Persian characters. Add doc::bidi script helpers, and
keep ZWNJ and Arabic marks inside words for word selection.
DOCX: read and write w:rFonts/@w:cs, w:szCs, w:bCs, w:iCs, w:cs and
w:lang/@w:bidi in schema order; stop using the cs font as the Latin font.
Tests round-trip Persian text with ZWNJ, digits and mixed Latin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(fonts): directional shaping and Persian font fallback
Add shape_run to shape a run in a known bidi direction (Arabic joining,
mirrored brackets). Prefer Persian-capable fonts (Vazirmatn, Noto Naskh/
Sans Arabic…) for right-to-left fallback, pick bold/italic fallback faces,
and map common Persian document fonts (B Nazanin, B Lotus, B Titr,
IRANSans…) to open substitutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(layout): Unicode Bidirectional Algorithm and right-to-left paragraphs
Run UAX #9 per paragraph (unicode-bidi), shape each level run in its
direction, keep marks and ZWNJ with their letter and a word in one
fallback face. After line breaking, apply rules L1/L2 per line and give
each cluster a visual box; right-to-left paragraphs are laid out from
their start edge and mirrored, so indents, tabs, alignment, list labels,
drop caps, floats and borders sit on the right.
Caret, hit testing and selection use visual geometry (split selections,
caret at bidi boundaries, DocLayout::visual_step for arrow keys). Display
items carry glyph-to-text ranges, which the PDF writer now uses. Plain
left-to-right lines are unchanged. No hyphenation of RTL words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(engine,ui): paragraph direction commands and RTL editing
Add para.ltr / para.rtl (Word's text direction buttons) that change only
the reading order, and make the alignment buttons visual in right-to-left
paragraphs (para.align also takes start/end). Arrow keys move visually in
bidirectional text, Ctrl+arrows follow reading order, and Bold/Italic/
Size/Font cover complex-script text. Ribbon buttons with code-drawn
icons, a mirrored ruler for RTL paragraphs, translations, catalog,
parity and roadmap updates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(contributors): add Saeed Azarehgar to people.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(engine): para.align with an explicit value sets it, without toggling
Only the alignment buttons toggle back to the start edge when pressed
again. para.align (used by the Paragraph dialog, scripts and agents) now
sets the requested alignment, as seen on the page, in each selected
paragraph's own direction. Previously pressing OK in the dialog with
Center or Right selected reset the paragraph to the start edge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(ui): Direction option in the Paragraph dialog
Add Word's Direction (Right-to-left / Left-to-right) to the Paragraph
dialog's General section. The dialog now shows alignment and Left/Right
indents as seen on the page, mapping them to the paragraph's logical
start/end values on OK, so an unchanged OK no longer flips a
right-to-left paragraph. OK logic moved to apply_paragraph, with tests;
ja/zh translations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(layout): allow hanging trailing spaces in the RTL display test
A wrapped right-to-left line's trailing spaces hang past its end edge
(the left margin), as left-to-right lines hang them past the right. The
test failed whenever the loaded fallback fonts made a line end there;
it now checks only visible glyphs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Clean-room foundations for WordCraft: Arc-shared document model with
paragraph/run editing, styles, lists, sections and tables; Word-style
font substitution; first-fit line breaking with tabs, lists, alignment,
pagination, tables, headers/footers and hit testing; vello_cpu page
rasteriser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>