Skip to content

test(docx): hold a drawing to the paragraph whose space above it stands in - #981

Merged
DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-paragraph-place-from-page-geometry
Oct 30, 2026
Merged

DemchaAV merged 3 commits into
2.5-devfrom
fix/docx-paragraph-place-from-page-geometry

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Why

The editing protocol decides which paragraph each floating drawing stands beside, and then holds
the drawing to how that paragraph moved. The two sides of that comparison were read differently. A
drawing's place is where Word's page geometry draws it; the paragraph's was where a range reports
its first letter, and a drawing placed from its paragraph stands at the top of that paragraph's
box, the space above the paragraph included. The drawing therefore stood that whole space away
from its own text, and nearer the text above it wherever the space was the wider of the two.

The change that fixed the drawing's own place measured what that leaves: a drawing at the box top of
a paragraph with 12pt above it stands 11.87pt from its own first line and 15.98pt from the line
above, so 4.11pt of margin stood between the right attribution and the wrong one, and a space wider
than the line above it would cross that. No document of the corpus crosses it, which is why this is
a reading that could not be relied on rather than a figure that was wrong — and the same is true of
the page, below.

The scenario also read only half of what it set out to read: a drawing beside text an edit moved had
to move with it, while a drawing beside text the edit left standing was not read at all. One could
wander off a page with its text standing still and the protocol would say nothing of it.

And which paragraph the scenario lengthens decides how much of a document moves at all. It
lengthened the longest paragraph, which in these templates is often one of the last things the
document sets, so most drawings stood beside text that never moved and were never measured.

What changed

scripts/docx-visual/edit-protocol-corpus.ps1

  • The space above a paragraph is read from Word's page geometry, wherever the geometry draws
    that paragraph a box and nothing else, and a drawing standing in that space counts as standing no
    distance down the page from that paragraph — the width its text runs to still has its say, so a
    drawing in the space above one column's paragraph can still belong to another's text. That
    space's top is the point Word reckons the paragraph's offsets from, so a drawing at offset 0 is
    now no distance from its own paragraph where the line above it is a line away. Get-PageGeometry
    reads the shape rectangles and the text lines in one walk of the pages; Get-ParagraphPlaces
    carries each paragraph's first line, the top of its own space where it has one, its page, and the
    width its text runs to.
  • A paragraph's page is read from the geometry too, as a drawing's is, so it is the page Word
    draws the text on rather than the page a range reports. A paragraph of a row Word draws on more
    than one page — a repeated head, or a row a page break runs through — has a line on each of those
    pages, and none of them answers which page that paragraph is drawn on; there the page a range
    reports is taken, and the scenario's detail says how many paragraphs that was. A paragraph the
    reading cannot place at all is named in the detail with the reason. It does not fail the
    scenario — what the scenario answers for is the drawings — but a drawing held to another
    paragraph for want of it can then be seen for what it is.
  • A reading that fails is raised rather than recorded as a document with nothing to measure,
    as Word naming no rectangle for any of a document's drawings already is: when no paragraph of a
    document with text can be placed, and when no drawing found text to stand beside while some
    paragraph could not be placed — a page whose text lines could not be read, say. Without those the
    scenario would find no owner for anything and call the document one with nothing to edit, which
    is the one thing a failing instrument must not be able to say.
  • A move onto another page is written as the pages and the points. A place counts its page as
    10000 points, so a drawing that crossed a page read as "by 9225" — which is a page on and 775pt
    up, not 9225pt. It is now written as both, because the first reader of that number took it for
    points.
  • A drawing beside text the edit leaves standing has to stay standing. The scenario counts
    those drawings and the ones that stayed with their text beside the ones that followed text that
    moved, and a drawing that leaves standing text fails the scenario.
  • The scenario lengthens the first paragraph of body text rather than the longest, so that as
    much of the document moves as one edit can move. Test-BodyParagraph is what both finders now
    measure a paragraph of body text by — 40 characters or more, outside a frame, holding no field —
    so "the first" and "the longest" cannot come to mean different things.
  • -SelfCheck runs ten probe documents: the four that fix where a drawing is drawn, and six that
    run the scenario, three of them new — a paragraph with more space above it than the line above is
    tall, which must keep its own drawing; a drawing that moves while the text it stands beside stays,
    which must be caught; and a row a page break runs through, whose paragraph the geometry alone
    cannot put on a page.

CONTRIBUTING.md says what each side is read from and what the scenario now holds; CHANGELOG.md
carries it under Tests. The export is untouched, so docs/recipes/docx-export.md and the capability
matrix are unchanged, and no public API moved, so the knowledge pack is unchanged.

What the measurements refuted

  • The paragraph's page was never the wrong page. It was supposed to be: Word reports a cell's
    paragraph on the page its row begins, so a drawing on the page after could have its own paragraph
    filtered out. Measured over the corpus — the 3647 paragraphs of the 38 documents with drawings —
    and over ten probes, including a row that opens a page, a row a page break runs through, a row
    held to 340pt that begins on the page before, and a paragraph whose lines run over the page's
    foot: wherever the geometry names one page for a paragraph it is the page a range reports as
    well, in 3629 of those 3647 and in every probe. For the other 18 the geometry names several —
    Word draws their row on more than one page — and the range's answer is one of them.
    What Word reported on the page before was a drawing's anchor, not the paragraph. A sabotage
    that takes the page from a range instead passes all ten probes, so this part of the change removes
    a way of being wrong rather than a wrong number, and is named as such.
  • The paragraph side cannot be read from the geometry down the page. A text rectangle holds a
    block and not a paragraph: a whole table is one, and inside it the geometry's lines run one to a
    row, so only 73 of those 3647 paragraphs have a box of their own and a page laid out as one
    table row is drawn as a single box from 0 to 812pt. Reading every paragraph at its box's top was
    measured and refused: it moves 96 of the 760 attributions, because in a sidebar it puts every
    paragraph of the page at the top of that page and leaves nothing to tell them apart down it.
    Holding a drawing to the box that contains it was measured and refused too: 7 attributions move,
    and two of them (cv-orange_ops Drawing 1 and 2) move off the paragraph the export anchored the
    drawing in, 4pt below, to a paragraph whose text is 74pt above. So a paragraph keeps its first
    line, only the space above it is read, and a paragraph of a row — which has no space of its own —
    keeps the limit that goes with that: where the space above it is wider than the text above it
    within that row, its drawing is still held against that text.
  • "The first paragraph with any text" is not a paragraph of body text. Measured: where that text
    is one character — a glyph in a sidebar's own cell — lengthening it by a sentence grew that cell's
    row past the page, and Word, which will not break the row, drew it over the page's edge, putting
    page 1's rectangles at 1006 and 1510pt. Every drawing of that document was then measured against a
    layout that had collapsed. Hence the same 40-character measure the longest paragraph is found by.

Verification

  • -SelfCheck: 10 of 10. Seven sabotages, six of them caught by the probe written for each,
    the file restored byte for byte from a copy taken at the start of each run and asserted against
    it:
    • the space above not read from the geometry → the paragraph with space above it fails, and for
      its own reason: the drawing falls to the line above, which stands still while the drawing moves
      32pt;
    • that space not counted as the paragraph's own → the same probe, the same reason;
    • the standing-text check removed → the drawing that leaves standing text passes, which is the
      false pass this change exists to prevent;
    • the page of a row drawn on more than one page no longer taken from a range → the split-row probe
      falls to N/A, which also shows that probe's row really does break over the page;
    • the count of those paragraphs no longer reported → the same probe fails, since its expected
      detail holds it to having reached that case rather than to passing;
    • the story filter removed from the geometry walk → the header drawing sharing character 0 reads
      as standing in two rectangles;
    • the page taken from a range instead → nothing catches it, measured, as above. Five rules,
      six sabotages that must be caught, and one that must not be.
  • Over the template corpus, 71 documents, against the same export the protocol was last
    measured on:
    • 305 of the 333 drawings standing beside text the edit moved follow it — 91.6%, where 213 of
      227 did, 93.8%. The instrument now measures 106 more drawings against text that moved,
      because the edit moves everything the document sets below the first paragraph of body text
      rather than below one of its last; of those 106, 92 follow their text and 14 do not.
    • 419 of the 427 drawings standing beside text the edit leaves where it is stand still too —
      a question the scenario did not ask before, over drawings it did not read at all.
    • 28 documents pass, 10 fail, 33 are N/A, where it was 31, 7 and 33. Four documents passed
      before and fail now, each on what the old edit never moved or the old scenario never read:
      cv-timeline_minimal, six drawings crossing to the next page 40pt from their own text;
      invoice-payments, eleven that do not move at all while their text moves 40pt;
      cv-terracotta_rail, four moving 23 to 24pt while their text stands; invoice-merchant, one
      crossing onto the page after while its text stays. One document fails no longer
      (invoice-obsidian_long): its drawings now stand beside text this edit leaves standing, and
      all five stand still. Every one of those is the export's to answer for, which is what this
      instrument exists to say.
    • The owner rule itself moves nothing. Scored over a reading of all 3647 paragraphs and 760
      drawings of the 38 documents with drawings, taken from Word before and after the same edit:
      the paragraph each drawing is held to is the same under the present rule and under this one,
      for all 760. The figure moved because of what is measured, not because of who owns what.
    • The corpus was run twice, once before this change's review fixes and once after, and the
      two agree on every count of every document — the only differences in the JSON are the new
      count in the detail and which three of a document's strays the detail names, which varies
      between runs because the names are enumerated from a hashtable.
  • The export is untouched: the corpus was exported again from this tree and compared with the
    export the measurements were taken on — 71 documents, no part changed, the report's notes 1497
    either way.

The protocol needs Word and is run by hand, so no CI job covers it; -SelfCheck is what to run
after changing how a place is read or how an owner is chosen. What no probe covers, because no
document provokes it: Word naming no geometry at all, a shape rectangle whose range cannot be read,
a text rectangle or one of its lines whose range cannot be read, a document no paragraph of which
the reading can place (all four raised rather than measured), and a paragraph the reading cannot
place, which is named in the detail before the edit and counts among the drawings not found again
after it. The sabotage of the page reading is surgical — it leaves the bookkeeping of a row drawn
on more than one page in place — so a regression that simply deleted the geometry's page would in
fact be caught by the split-row probe's own count; what nothing catches is the page being taken
from a range while everything else holds.

…ds in

The editing protocol read the two sides of its own comparison differently. A
drawing's place is where Word's page geometry draws it; the paragraph it was
held against was read from where a range reports its first letter. A drawing
placed from its paragraph stands at the top of that paragraph's box, the space
above the paragraph included, so it stood that whole space away from its own
text and nearer the text above it wherever the space was the wider of the two —
a heading at 18 or 24pt over a 13.8pt line gives its drawing away.

The space above a paragraph is now read from the page geometry wherever the
geometry draws that paragraph a box and nothing else, and a drawing standing in
that space stands at that paragraph, however wide the space. The paragraph's
page is read from the geometry too, as the drawing's is; where a row is drawn on
more than one page and none of its lines answers which page a paragraph of it is
drawn on, the page a range reports is taken, and a paragraph the geometry draws
no line for is counted and named.

The scenario also reads what it did not read before: a drawing beside text the
edit leaves standing has to stay standing, where only drawings beside text that
moved were measured. And it lengthens the first paragraph of body text rather
than the longest, so that as much of the document moves as one edit can move.

Measured in Word 16: the geometry draws a box per block, so 73 of the 3647
paragraphs of the corpus's documents with drawings have one of their own and a
page laid out as one table row is a single box; reading every paragraph at its
box's top moves 96 of the 760 attributions and holding a drawing to the box that
contains it moves 7, two of them off the paragraph the export anchored the
drawing in. The rule as written moves none of them, and closes the hole by
construction. The page a range reports is the page the geometry draws the text
on in all 3647 and in ten probes, so that reading settles nothing a document has
shown and is named as such.

Over the corpus the instrument now measures 333 drawings against text that
moved where it measured 227, and 305 of them follow it: 91.6% where the figure
was 93.8% of a third as many. It also reads the 427 drawings standing beside
text the edit leaves where it is, 419 of which stand still, which it did not
read at all before.
… move across a page as pages and points

A document whose paragraphs the page geometry draws no line for left the
scenario with no owner for any drawing, which it recorded as a document with
nothing to edit: a failing instrument reading as a document that had nothing to
measure. It is raised now, as Word naming no rectangle for any of a document's
drawings already was.

A place counts the page it is on as 10000 points, so a drawing that crossed a
page was reported as having moved by 9225, which is a page on and 775pt up it,
not 9225pt. Both are written out now.

The prose follows the code where it ran ahead of it: an owner the reading cannot
place after the edit does fail the scenario, among the drawings it could not
find again; the page a range reports agrees with the geometry's wherever the
geometry names one, which is 3629 of the corpus's 3647 paragraphs rather than
all of them; of the rectangles Word draws, measured over four documents, only
text and shape rectangles carry any of the text; and the split row's probe holds
while the body above it leaves the row a line, failing on its own count rather
than passing quietly if it ever stops breaking.
…and say the page measurement as it was made

A page whose text lines the geometry cannot be read for leaves the paragraphs on
it unplaced; where the document's drawings all stand beside those, the scenario
found no owner for any of them and recorded the document as having nothing to
edit. That is raised now, as a document no paragraph of which can be placed
already was.

The page measurement is written as it was made: wherever the geometry names one
page for a paragraph it is the page a range reports, which is 3629 of the
corpus's 3647 paragraphs; for the other 18 the geometry names several, because
Word draws their row on more than one page, and the range's answer is one of
them. 'The same page in all 3647' was not well formed for those 18, and the
CHANGELOG carried it after the script had stopped saying it.

The count of those paragraphs is named for what it counts, since it includes the
one whose page a range did not settle either; the rectangle kinds Word was
measured to draw are named by their numbers, so the claim can be checked again;
the throw carries the reason the reading gave rather than naming a cause; and
the split row's probe no longer states two line counts that were not measured in
Word, the argument standing without them.
@DemchaAV
DemchaAV marked this pull request as ready for review October 30, 2026 15:17
@DemchaAV
DemchaAV merged commit d9f8f37 into 2.5-dev Oct 30, 2026
11 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-paragraph-place-from-page-geometry branch October 30, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant