Commit Graph
556 Commits
Author SHA1 Message Date
Martin BallandClaude Opus 5.5 94a6466bf0 #6005: machine reason codes, source_part and totals in the strict report
JSON reasons are snake_case codes (missing_part, external_target, link_only,
undecodable, unsupported_format); "in" becomes "source_part" (full story part);
total_missing_glyphs and total_missing_pictures count past the 256 caps (code
point bitset, saturating picture counter). Human lines unchanged. README
documents the schema. Layout missing_glyphs tests call test_fonts().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
2026-11-08 22:54:37 +00:00
Martin Ball 44e188d8c0 Merge remote-tracking branch 'origin/main' into 6005-c
; Conflicts:
;	ROADMAP.md
2026-11-08 21:44:40 +00:00
FredApps fda8758ec9 Merge upstream text hinting and window scrolling 2026-11-08 17:59:05 +01:00
FredApps ae257ba784 Merge upstream font tests and window scrolling 2026-11-08 17:26:55 +01:00
FredApps 0495fbb789 Merge pull request #2783 from vangel76/test/fixed-layout-fonts-2729
#2729: Layout tests use pinned bundled fonts
2026-11-08 17:24:43 +01:00
vangel76andClaude Opus 5.5 e7e9bc2759 Merge upstream/main into PR #3195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:24:03 +01:00
Martin BallandClaude Sonnet 5.5 23f658e353 #6005: strict export checks the page background and watermark, per-output picture limit
Page background picture and watermark text are checked like the display list
(watermark shaped by one shared helper used by the PDF export, the page raster and
the check). Picture side limit is per output (PDF 30000, PNG 65535). Separate
truncation flags per list. Glossary pictures name the glossary part.
MissingPictures::push dedupes in O(1). Hostile params cover strict, README note,
ToUnicode regression test for chart and equation runs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
2026-11-08 14:01:38 +00:00
Martin Ball f063e66f9c #6005: merge part B (missing pictures) 2026-11-08 12:59:38 +00:00
Martin BallandClaude Sonnet 5.5 34f6a1ff46 #6005: record dropped pictures at open and check drawability for strict export
doc: Document.missing_pictures (MissingPicture, MissingPictureReason), capped at 256,
never saved, ignored by ==.
docx reader: records a missing part, an external target, and r:link / VML r:href
only pictures with their story part; nothing is fetched.
geom: decode_for_drawing / check_for_drawing, the one decision pdf and render now
take (output unchanged, pinned by golden hash tests); layout: media_check and
undecodable_pictures walk a layout's drawn pictures per story.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
2026-11-08 12:47:32 +00:00
Martin BallandClaude Sonnet 5.5 fa3b297951 #6005: DocLayout::missing_glyphs, ranges on chart and equation runs
Walks the display list (as the PDF and PNG exports draw it) for glyph id 0
and names each by code point, capped at 256 distinct. Chart/SmartArt label
runs and equation runs now carry per-glyph text ranges.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
2026-11-08 12:34:07 +00:00
Dylan M. TaylorandMartin Ball 4afff61f10 fix(geom): measure PNGs from their chunk headers, not the decoder's header read (#5459)
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 07:45:02 +01:00
David PrietoandMartin Ball 735c2a5382 layout: keep post-break anchors with text across a wrapped row (#5937)
* layout: keep wrapped-row anchors after page breaks

* test: pin wrapped picture drops, history and DOCX

* docs: record wrapped-row anchor coverage

* docs: log wrapped-row picture anchoring fix

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:59:44 +01:00
a891d861b7 Layout: contextual spacing drops the space after a cell's last Normal paragraph (#5450)
Word treats the end-of-cell mark as a paragraph in the default (Normal)
style, so a cell's last paragraph with contextual spacing in that style
(or in an undefined style, formatted as Normal) loses its space after, as
beside any same-style neighbour; one in another style keeps it.
`layout_box` kept the space after of every last cell paragraph except
automatic spacing, so the row grew by it and the text below the table sat
that much lower (docxide-pdf cell-end-text-context, cell-end-empty-context:
8 pt).

A middle or bottom cell of a row split across pages lines up with the
other cells' kept text, which `align_in_part` counted with its full space
after; it now stops at the cell's content end, so a dropped space after
(contextual or automatic) no longer pushes the aligned cell down.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:27:07 +01:00
Kevin JosephandMartin Ball 85434d2c20 layout: line numbers take the "Line Number" style, not a fixed 10 pt grey (#5788)
* layout: line numbers take the "Line Number" style, not a fixed 10 pt grey

Line numbers were laid out in 10 pt grey in the default font, whatever
the document said. Word formats them with the "Line Number" character
style over the document's defaults: in a manuscript whose defaults are
Aptos 12 pt, Word's PDF shows them in Aptos 12 pt black (measured in
Word for Mac), WordCraft's in 10 pt grey.

Lay the number out with the document's "Line Number" style when it has
one, else its plain defaults, so size, font and colour follow the
document.

Test: line numbers take the document's default size and automatic
colour, and a "Line Number" style's size and colour when there is one.

* docs: progress log and parity entries for line-number-style

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:26:44 +01:00
Kevin JosephandMartin Ball 7634553900 Layout: superscript and subscript text at 0.65 of its size rounded to the half point (#5802)
Word for Mac sets raised and lowered text at 0.65 of the run's size
rounded to the half point (measured from digit advances in its PDF
export at base sizes 8 to 36 pt: 11 pt -> 7, 12 -> 8, 24 -> 15.5).
WordCraft used 0.65 exactly, so lines with superscripts were up to
0.5 pt wider and some broke a word early.

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:02:01 +01:00
batistalucasv c4d5f70c1d DOCX: resolve LibreOffice font fallback lists to first installed family (#5843) (#5851)
* #5843: resolve LibreOffice semicolon-separated font fallback lists to first installed family

* #5843: address review comments on font fallback list resolution

* #5843: verify fallback list text on cache hit and accept Times New Roman in tests

* #5843: bound the fallback lists read from a DOCX and reuse their classification

A document could name thousands of fallback families in `w:ascii`/`w:hAnsi`.
Nothing bounded that value, and the name-based rules (roman classification,
Thai family, named substitutes) then scanned every candidate for each shaped
piece, so opening the document took seconds.

`w:ascii` and `w:hAnsi` now keep the leading families within MAX_FONT_NAME
bytes, cut at a `;` so no name is split, as `w:cs`/`w:eastAsia` already bound
their names. The family a real list would pick is kept: a LibreOffice list
names a few families, well under the bound.

The Thai-family and named-substitute answers are worked out once per list and
kept in the fallback-list cache, keyed by the list text, so shaping no longer
rescans candidates for every piece. Resolution still reads every candidate.

A 500-paragraph document whose font names 20,000 candidates laid out in 10.8 s;
bounded on read it takes 17 ms.

Tests:
- a hostile fallback list keeps its leading families, cut on a `;`
- a real LibreOffice list is read whole
- a later Thai or named fallback still classifies, and lists are cached
2026-11-08 05:55:24 +01:00
3291be0fbf Layout: an empty paragraph after a leading floating table takes no space before (#5440)
Word drops the space before of an empty paragraph that follows a document's
leading floating table: its PDFs of docxide-pdf's leading-empty-before-float-0
and -12 put the text after the empty anchor (beside the table) at the same
place for 0 pt and 12 pt before.

Flow::gap_before (crates/layout/src/flow.rs) kept it: a floating table
takes no room in the flow and clears `pb.prev`, so the paragraph after it
was laid out like a document's first paragraph, with its full space before.
It now drops it for body block 1 when block 0 was placed floating on this
page (the page's float_tables), the page is fresh and the paragraph holds
nothing but markers (bookmarks, comment and permission ranges). It
doesn't check that the paragraph sits beside the table, so below a
full-width leading float an empty paragraph loses its space before too;
that case is untested. Later pages and columns, and paragraphs with text,
keep their space before.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:45:17 +01:00
4259dd1b2e Layout: before Word 2013 layout a floating table moved below another keeps its own left edge (#5442)
A floating table with tblOverlap=never that is moved below another floating
table steps in to its distance from the text's left edge only in Word 2013
layout (compatibility mode 15). clear_of_float_tables read the mode from the
legacy shift, the first cell's left margin before mode 15, being zero, so a
mode 12 table whose first cell has no left margin was taken for Word 2013
layout and moved 9 pt right (docxide displaced-anchor-*-compat12-overlap0-*:
Word 72 pt, WordCraft 81 pt). Pass the mode itself.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:31:43 +01:00
vangel76andClaude Opus 5.5 e344bb4243 Merge upstream/main into PR #3197
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00
vangel76andClaude Opus 5.5 4a507de3d6 Merge upstream/main into PR #3195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00
5c0a62a736 Layout: no line break between a full stop and an opening parenthesis (#5437)
UAX #14 allows a break between a full stop (IS) and an opening
parenthesis (OP), so `break_positions` (crates/layout/src/para/paragraph.rs)
let "29.05.2023г.(оформление ЗУ)" and "2023.(Example detail)" wrap after
the full stop. Word keeps such a token whole and wraps it at the space
before, as the docxide-pdf dot-open-token* Word references show. The
break between the two is now dropped (before the kinsoku pass, so
break-anywhere wrapping still applies).

Test: layout::tests::a_full_stop_before_a_parenthesis_is_no_break.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:18:09 +01:00
855a0fe9bd Layout: a nested table's Can't Split row moves on with its outer row (#5456)
A row is split across pages from its flattened items, so a row of a nested
table marked w:cantSplit was cut between its lines like any other text in
the outer cell. Word leaves such a row whole: the outer row moves to the
next page whole when that row comes first in it (docxide-pdf
nested-first-row-prefix300/330-cant1), and otherwise splits above it
(inferred).

table_flow now moves the cut up to just below the top of the highest nested
Can't Split row it runs through (nested_keep_cut): the outer row's bottom
band below that top, so the text and nested rows ending there stay on the
page. A row runs through the cut when it ends below the room left for text,
as in the split itself; one walk over the rows from the lowest top up moves
the cut until none does. A vertically merged cell doesn't stretch its row's
band, and a cell aligned to the middle or bottom is measured from its top, as
the split runs it. This applies to the first part of a row and to the kept-chain and
header-row checks. On a fresh page, where moving on gains nothing, a nested
row taller than the room still splits at the room rather than running into
the bottom margin.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:03:54 +01:00
d3fcf10bcc Layout: a picture in a paragraph whose hidden mark closes a section keeps its room (#5288) (#5347)
* Layout: a picture in a paragraph whose hidden mark closes a section keeps its room (#5288)

A paragraph with a hidden mark and no plain text was a quiet mark: laid out
with no room, its drawing kept. An in-line picture adds no plain text, so a
hidden-mark paragraph holding only a picture qualified, and the text after it
(the next continuous section in the report) was drawn over the picture. Such
a paragraph, or one holding a note reference mark, now keeps its line; one
with only floating objects stays quiet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Layout: a legacy check box in a hidden closing mark keeps its room too (#5288)

A FORMCHECKBOX field has an empty result, so a hidden-mark paragraph holding
only a check box still counted as quiet and the next continuous section was
drawn over the box. Fields now count as visible content when the paragraph
layout shows them as something other than their result: their code, a legacy
check box, or a drop-down's entry (the same test as the layout's `field`).

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: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:48:48 +01:00
7152143e45 Layout: a line can break after a hyphen before a slash, like Word (#3408) (#5348)
Word wraps "Dataprotection-/Secrecyviolation" as "Dataprotection-" /
"/Secrecyviolation". UAX #14 (LB13) forbids a break before the slash, so
the compound had no break opportunity: lines moved it whole and auto-fit
table columns widened to fit it, taking width from their neighbours.
Add the break after U+002D when a slash and a letter follow, next to the
existing break before a digit (#2712); Word keeps "fitness-/1589" whole,
so a slash before a digit gains no break. Always sort the added breaks.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:36:10 +01:00
c40719f431 Layout: a turned inline picture with no recorded room for the turn keeps its line to its frame, as in Word (#5448)
A file that turns an inline picture but writes no wp:effectExtent (or a zero
one), as other tools do, makes Word keep the picture's line to its frame and
let the turn overhang it, cut at the line's top and bottom: the frame as it
is in Word 2007 layout (compatibilityMode < 15), with width and height
exchanged from 45 to 135 and 225 to 315 degrees in Word 2013+ layout.
WordCraft always reserved the exact rotated bounds (Float::spin_pad), which
is what Word's own files encode through their effect extents, so the text
after such a picture sat up to 34 pt too low (docxide-pdf
standalone-picture-compat12-* / compat15-angle45/135).

The docx reader now marks such objects (Float::bare_turn, cleared when the
object is turned again); the inline room comes from Float::line_pad, which
display uses too to place the frame in it, and the line clips the picture's
overhang. The writer keeps writing no turn overhang for them, so a save
round-trips. Word-saved pictures are unchanged.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 04:34:00 +01:00
vangel76andClaude Opus 5.5 9655b2da50 Merge upstream/main into test/fixed-layout-fonts-2729
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 04:21:38 +01:00
vangel76andClaude Opus 5.5 66e169c343 Merge upstream/main into ui-text-hinting
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 04:21:35 +01:00
Dylan M. TaylorandMartin Ball bd8bc96cb5 Layout: look at the text after a decimal tab once, not once per cluster (#5343)
* Layout: look at the text after a decimal tab once, not once per cluster

Text after a decimal tab is shifted after each of its clusters, and each
shift walked the text again from the tab to find its first '.'. A long run
without a '.' (soft hyphens take no room, so it stays on one line) made a
single paragraph's line breaking quadratic. The scan now carries on from the
cluster it reached.

* address review: layout progress doc for decimal-tab scan

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:15:42 +01:00
Fin WarmanandMartin Ball 7830333c34 Layout: a page break before applies at a continuous section's top mid-page (#5287)
* Layout: a page break before applies at a continuous section's top mid-page

A continuous section moves the column top to the current line, so at_top() was true mid-page and the paragraph's page break before was skipped. Check fresh() instead, as Word does.

* 1485: record continuous-section page-break parity

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:15:24 +01:00
050b6b0da3 Arabic letters join across a change of formatting (#4093) (#5691)
* Arabic letters join across a change of formatting (#4093)

Each run of a paragraph was shaped on its own, so a letter at a run's edge
took the form of a word's start or end. A coloured letter stood apart from
its word, and a word edited with Track Changes on read with a space in it
(ال ذكاء).

Runs are now shaped with the text shown on either side of them, through the
shaper's pre- and post-context (fonts::ShapeContext). Runs left out of the
layout (hidden text, deletions in a final view) are skipped, so letters join
what is on the page. Only paragraphs with a joining script do the work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2QwHfTULQtWYgVEXk

* #4093 review: no letter joins a drop cap formatted on its own

A drop cap letter in a run of its own (coloured, say) was still passed to
the next run as the text before it, so that run's first letter joined the
drop cap, which is drawn apart from the word. The shown text now has a floor
past the drop cap, and no run takes context from before it. The drop cap
test also covers the letter in a run of its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LvG6E2QwHfTULQtWYgVEXk

---------

Co-authored-by: Filip Strajnar <filip.strajnar@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 03:44:01 +01:00
vangel76andClaude Opus 5.5 beab4e8e9c Merge upstream/main into zoom-stretch
Upstream's new layout tests start with crate::test_fonts(), like the others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:25:18 +01:00
9b6e9564a2 Lists: a numFmt none level shows its literal text, so RESPONSE: labels the paragraph (#4985) (#5055)
* #4985: a numFmt none list level shows its literal text

Word labels a paragraph whose level has numFmt none and lvlText RESPONSE:
with RESPONSE:. Counters::next_label returned no label for any none level;
now only a none level of placeholders shows nothing. Its own %n stays
empty, and it still doesn't count. Numbering::shows_label agrees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: a "none" list level counts, and only its own number is empty

Taken from MeHail-02's numbering-literal-none branch (46405e66), checked in Word
for Windows: a none level is an ordinary level whose own number reads as
nothing. It counts and restarts the levels below it, stays empty in legal
numbering, and an empty label still tabs the text to the indent unless it
already ends there (the Chapter 1 list's Heading 2 stays at the margin).

Co-Authored-By: MeHail <mihailsmetanin15@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: STYLEREF in full context keeps a none-level heading's parent

Review: a none level now has a label, so its headings reach the style index,
which leaves the none level out of the levels it keeps; \w and \r took the last
kept level for the heading's own and dropped the parent. Levels now carry their
index, and the levels above are picked by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: STYLEREF number switches on a none-level heading

Review: in full context an empty label of its own added a trailing period (3.),
and the suppress switch gave a none level's label (Note), which holds no number,
back whole. An empty own part is left out, and a none level's label with no
number is suppressed to nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: STYLEREF \s and \t on none-level labels

Review: \s took a none level's label (Note) for a chapter number; the heading's
count stands in, as for an unnumbered heading. \t matched any ancestor's number
inside a word (the a of Chapter); only the numbers the label's template shows
count now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: STYLEREF takes a label's numbers by their spans

Review: \s took the last word of a label (Response of "3 Response") and fell
back on the heading count for a none level showing its parent's number (%1);
\t kept text attached to a number (Section3). A label's numbers are now the
spans of the values its template shows, with their delimiters (a letter number
only standing apart from other letters); \s takes the last of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #4985: tests edit the numbering through Arc::make_mut after merging main

Main shares the numbering between undo steps (#2694), so `Document::numbering` is an
`Arc<Numbering>`; the tests this PR adds wrote to it directly and no longer built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: MeHail <mihailsmetanin15@gmail.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 03:20:49 +01:00
vangel76andClaude Opus 5.5 c606533853 Merge upstream/main into zoom-stretch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 03:17:59 +01:00
bc0f691c13 Layout: a picture lowered past its height lowers its line as far, as in Word (#5796)
* Layout: a picture lowered past its height lowers its line as far, as in Word

A picture lowered further than its own height had its lowering cut at that
height. Word keeps its top on the baseline but lowers the line as far as the
position says: a 6 pt picture lowered 30 pt makes a 30 pt line. Exact spacing
keeps the line's baseline. Closes the gap noted with #3984: all 156 measured
lines now match Word.

* Lowered pictures: an exact-spacing test; gaps and progress log

An exact-spacing line keeps its baseline when a picture is lowered past its
height (the test fails without the cap). Gap 4 and the progress log note the
change.

---------

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 02:54:47 +01:00
vangel76andClaude Opus 5.5 20a10a5f33 Merge upstream/main into zoom-stretch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 02:26:30 +01:00
Harrison BoothandMartin Ball 7855a2d814 fix(fields): quoted-argument backslash escapes; body PAGE in a cardinal-text section (#4579)
* fix(fields): quoted-argument backslash escapes and body PAGE in a cardinal-text section

A backslash inside a quoted field argument makes the next character literal, so TITLE "x \* Upper" shows x * Upper as Word does. A body PAGE field now uses the section's page number format (cardinal text shows words), as headers and footers already did.

* Fields: PAGE in a body table follows the section number format; parity docs

* fmt

* docs: clarify quoted field escape rule

* Fix page fields in fitted text boxes

* Move fitted cache test after layout helpers

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 02:08:54 +01:00
vangel76andClaude Sonnet 5.5 f1b5f0b4de Layout test: install the pinned fonts before the #4720 chart fallback lookup
The test asked the global font database for a fallback face before any
layout call, so the pinned fonts could no longer be installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:24:08 +01:00
vangel76andClaude Sonnet 5.5 ddb1d37de3 Merge upstream/main into test/fixed-layout-fonts-2729
Keep upstream's split of layout_para and install the pinned fonts at its
start. Refit the main tests added since (#4250 widow and footnote length,
#4720 chart fallback font) to the pinned test fonts. Merge the two
wordcraft-layout dev-dependency tables.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 18:22:44 +01:00
vangel76andClaude Sonnet 5.5 d98a335ae8 Hinting test: render a bundled face at a fixed size; refit a #4250 footnote test to the pinned fonts
The hinting test took whatever default font the machine resolved, so its grey-pixel threshold
depended on the system. It now sets Source Sans 3 at 12 pt (4.7 percent fewer grey pixels).
The footnote test's note length was chosen against system fonts and is now the one that fits
the pinned test fonts, which every layout call now installs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:35:41 +01:00
vangel76andClaude Sonnet 5.5 bbc6d94dbe Layout tests: install the pinned fonts in the layout entry points and font helpers
layout, layout_para, layout_equation and mark_text_width call test_fonts() first in test
builds, so the shared lay helper and everything that goes through them is covered. Tests and
helpers that look up fonts directly (the Times New Roman substitute test, the Symbol bullet
test, the Arabic/Myanmar/Thai face probes and the nbsp fixture) call it themselves. Audited by
running every layout test in its own process with a temporary guard that panicked on any
global font access before the pinned fonts were installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:32:22 +01:00
vangel76andClaude Sonnet 5.5 d94bbc8fe0 Layout tests: install the pinned fonts in the layout entry points and font helpers
layout, layout_para, layout_equation and mark_text_width call test_fonts() first in test
builds, so the shared lay helper and everything that goes through them is covered. Tests and
helpers that look up fonts directly (the Times New Roman substitute test, the Symbol bullet
test, the Arabic/Myanmar/Thai face probes and the nbsp fixture) call it themselves. Audited by
running every layout test in its own process with a temporary guard that panicked on any
global font access before the pinned fonts were installed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:16:20 +01:00
vangel76andClaude Sonnet 5.5 18a26b41ce layout tests: give a #4250 widow test a paragraph-length range that fits the pinned test fonts
With the fixed test fonts the paragraph with a widow needs fewer repeats
than the search range started at.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:22:51 +01:00
vangel76andClaude Sonnet 5.5 9f9190f0d7 layout tests: give two #4250 tests geometry that fits the pinned test fonts
The footnote text length and the paragraph-length search range in these two
tests were chosen against system fonts; with the fixed test fonts the page
breaks fall elsewhere.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 12:16:50 +01:00
vangel76andClaude Sonnet 5.5 2d6da7a326 Merge upstream/main into ui-text-hinting
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 11:38:09 +01:00
vangel76andClaude Sonnet 5.5 7ca1dd1802 Merge upstream/main into zoom-stretch
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 11:37:52 +01:00
Martin BallandRatkoR 8833c604e0 Layout: a shape dropped just after a page break stays on the new page (#4262) (#4297)
Lands #4297.

Co-Authored-By: RatkoR <6648783+RatkoR@users.noreply.github.com>
2026-11-06 13:12:16 +01:00
Martin BallandD13GOOOO cad8ede1e8 Shapes: pictures and text boxes inside a drawing canvas are selected and moved on their own (#2637) (#2670)
Lands #2670.

Co-Authored-By: D13GOOOO <106814760+D13GOOOO@users.noreply.github.com>
2026-11-06 12:44:48 +01:00
Martin BallandRatkoR beca3f3485 Layout: a float anchored on a later page of a long paragraph is drawn there (#4250) (#4267)
Lands #4267.

Co-Authored-By: RatkoR <6648783+RatkoR@users.noreply.github.com>
2026-11-06 12:13:11 +01:00
Martin Ballandfinwarman 3d79fff0ad Layout: reserve flow space below bottom paragraph borders (#2437)
Lands #2437.

Co-Authored-By: finwarman <1339254+finwarman@users.noreply.github.com>
2026-11-06 11:29:32 +01:00
Martin BallandMorzilla dc388cfaab Layout: a line of only pictures takes no descent; multiple spacing adds its share (#3984)
Lands #3984.

Co-Authored-By: Morzilla <3956663+Morzilla@users.noreply.github.com>
2026-11-06 10:52:32 +01:00