Skip to content

test(docx): read a drawing's place in the editing protocol from Word's page geometry - #980

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-follow-text-row-top
Oct 30, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-follow-text-row-top

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Why

The editing protocol is what the DOCX work measures "a drawing follows the text it stands beside"
with, and it reckoned a drawing's place itself — wrongly. A drawing placed from its paragraph was
taken to stand where that paragraph's text begins, plus the offset the drawing states. Word reckons
that offset from the top of the paragraph's box, the space above the paragraph included, while a
range reports the top of the paragraph's first line: 11.87pt lower wherever 12pt of space stands
above it.

A place a line out is held against the text a line out, since a drawing is attributed to the
paragraph nearest it. Seven drawings in four invoices were recorded as not following text they never
stood beside — each read as not having moved at all, while the paragraph it was held against moved
9.4 to 10.9pt. Measuring how well the export carries drawings with this protocol would have measured
the protocol's own reckoning first.

It was first supposed that Word reckons the offset from the top of the row's content. Measurement
refutes that: a drawing at offset 0 in the second paragraph of a cell is drawn at 87.98pt, that
paragraph's own top, where the row's content begins 15.98pt higher.

What changed

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

  • Get-ShapeRectangles reads Word's own page geometry: a drawing has a rectangle there wherever
    Word draws it, in points from the top-left corner of that page, and the rectangle's range starts
    on the character the shape's anchor sits on. Find-ShapeRectangle matches the two one for one —
    by that character, by the story the character counts from, and by the size Word rounds the shape
    to. Nothing is reckoned from the anchor any more, so the vertical place, the left edge and the
    page all come from Word.
  • The story matters because a character counts from the start of its own story: the export writes a
    page's background as a shape anchored in a header, and its rectangle sits on character 0 of that
    header — the character a body drawing that opens the document sits on too. In invoice-metered
    that rectangle is a 595x41pt one on character 0 of a header story, and only its size kept it from
    being taken for a body drawing's.
  • A drawing the page geometry does not name is reported with the reason and not measured, in the
    scenario's own detail and as a failure: a document the instrument cannot read is not a document
    that passed. A place reckoned from its anchor is no fallback, since that is what was wrong, and
    neither is a rectangle of another size — Word states the size it draws and clips nothing, measured
    at 700pt on a 595pt page. A row Word repeats as a table's head is drawn on every page the table
    reaches, and the one drawing it holds stands in a rectangle on each of them; which of those
    followed the text is not a question with an answer. Word naming no rectangle for any drawing of a
    document is told apart from that and raised, so the instrument failing cannot read as every
    drawing of that document leaving its text behind.
  • The scenario's own count now says what it counted: the drawings it measured are those standing
    beside text, and the ones standing beside no text on their page are counted beside them instead
    of being left out of the detail altogether.
  • A drawing is held to its text within a point and a half rather than a point
    ($FOLLOWS_WITHIN_POINTS): Word states the geometry in whole points, so the place read before an
    edit and the place read after it can each be half a point out, and a drawing that moved with its
    text exactly can read a whole point adrift. The limit must therefore be more than a point. At a
    point and a half every drawing whose own drift is under half a point passes, and where the
    rounding runs the other way one that drifted by as much as two and a half can pass too — the
    geometry Word states cannot tell those apart, and resolving drift finer wants a reading finer
    than whole points.
  • -SelfCheck runs the protocol over seven documents it writes itself. RelativeVerticalPosition,
    RelativeHorizontalPosition and the two constants for them are gone with the reckoning they
    served.

CONTRIBUTING.md says what a drawing's place is read from and that a drawing which cannot be read
fails the scenario; CHANGELOG.md carries the change under Tests. The export itself is untouched,
so docs/recipes/docx-export.md and the capability matrix are unchanged; no public API moved, so
the knowledge pack is unchanged.

What was deliberately left alone, and what it costs

The paragraph side of the comparison still reads line tops (Get-ParagraphPlaces), not paragraph
boxes, so the two sides of Find-Owner are not in one frame. Two things follow, and both are now
written at Find-Owner rather than left for the next reader to find:

  • Down the page, a drawing at its paragraph's box top stands 11.87pt from that paragraph's own
    line and 15.98pt from the line above it, so it is still attributed to its own paragraph — by
    4.11pt in the one measured here. That margin is the space above the drawing's paragraph against
    the height of the line above it, so a paragraph whose space above exceeds that line (a heading at
    18 or 24pt over a 13.8pt line) would attribute its drawing to the paragraph before. None of the
    corpus's attributions moved that way, but the rule is not safe by construction.
  • Across pages the mismatch is a hard filter, not a distance: a drawing's page is the page Word
    draws it on, a paragraph's is the page a range reports it on, and those differ for a row that
    begins on one page and is drawn on the next — measured in this very change. There a drawing can
    have the paragraph it stands beside filtered out and fall to another paragraph of that page, or
    to none.

Word's page geometry would give paragraph boxes and their pages too — a text rectangle's top is the
paragraph's box top, 88.00pt where that paragraph's line is at 100.40 — but reading the paragraph
side from it changes which paragraph owns a drawing across the whole corpus, and that wants its own
before and after. It belongs with the work on the attribution itself (how the owner is chosen, what
an empty pass counts as, and a drawing that moves while its text stands still, which this scenario
does not look at), not in the change that fixes the place.

Verification

  • Measured in Word 16.0.20527 on 29 probe documents of one 20x10pt rectangle each at offset 0 —
    where the place Word draws it at is the very point Word reckons the offset from — against the same
    rectangle in Word's own PDF: the page geometry is right within 0.41pt in all 29, and names the
    page the drawing is drawn on, which the old reckoning got wrong altogether for a drawing in a row
    that begins on the page before: there Word reports the anchor on page 1 while the rectangle, and
    the ink, are on page 2. The probes cover space above a paragraph in the body and in a cell, the
    first and the second paragraph of a cell, the first paragraph of a document, a cell's own top
    margin, a 3pt top border, a centred cell, an exact line, a taller neighbour, the second row of a
    table, an indented paragraph and the second column, a row and a body paragraph that open a page, a
    hard break, a header taller than the top margin, automatic space above, contextual spacing, and a
    drawing wider than the page.
  • Read in LibreOffice 24.2 as well, over the same 29: it reckons the offset from the same point.
    Thirteen probes put the drawing exactly where Word does, and the six with space above the
    paragraph put it 1.98pt higher — which is the height of the line above it, not the reference:
    Word sets that first line 15.98pt tall and LibreOffice 14.00pt, and each editor draws the drawing
    the stated 12pt above the second paragraph's own text (12.42pt above its ink in Word, 12.01 in
    LibreOffice). The rest differ by what is already recorded of that build: a centred cell carrying a
    drawing is set from its top, and its table rows are shorter. The protocol itself drives Word
    alone.
  • -SelfCheck: 7 of 7. Four fix the place Word's PDF puts a drawing at, each with the drawing
    at offset 0 — 12pt of space above a body paragraph, the second paragraph of a cell a line below
    the top of its row, a row Word opens a page with, and a body drawing sharing character 0 with a
    header drawing of its size — and three run the scenario itself: the same drawing beside the same
    line of text, anchored to that line's paragraph where it must follow and to the page at the very
    place it already stands where it must not, and one in a repeated head where its place cannot be
    read. Each of the three is read for the reason the protocol gave and not only for whether it
    passed, since a document meant to fail would be satisfied by any breakage at all — the repeated
    head is held to its own refusal, "stands in 2 rectangles of its own 20x10pt", not to the phrase
    every refusal carries.
  • Seven sabotages, each caught by the probe written for it: the place taken from where the
    paragraph begins again (caught by the body paragraph with space above it, and by the row that
    opens a page), the page dropped from the place (the row that opens a page), the left edge taken
    from the offset the shape states (the second paragraph of a cell), the tolerance widened to a
    thousand points and the unfollowed drawing no longer failing the scenario (the drawing anchored to
    the page), the story filter removed (the header drawing, which then reads as standing in two
    rectangles), and the refusal left unreported (the repeated head, which then falls to a silent
    N/A). The protocol was restored byte for byte after each batch, from a copy taken at the start of
    that run.
  • Over the template corpus, 71 documents, the old protocol and the new one on the same export:
    31 documents pass where 27 did, 7 fail where 11 did, 33 are N/A either way, and no document went
    the other way. All 760 drawings were named by the page geometry — none refused. Of the drawings
    standing beside text the edit moved, 213 of 227 follow it, where 211 of 232 did. The four invoices
    the old reckoning had wrong now pass; two of them with no drawing beside text the edit moves at
    all, their tiles standing beside text that stays where it is. The corpus was then read a second
    time, after the story of the anchor's character and the refusal of a rectangle of another size
    were added, and a third on the final tree: the same figures document for document, every time —
    the size of a drawing had been keeping the header's rectangle out of its way by itself. The count
    the scenario now reports of drawings standing beside no text on their page is 0 over all 71,
    so none was dropped unmeasured, and none was refused.
  • Of the seven documents that still fail, the drawings placed from the page are the export's doing —
    those cannot follow anything, and are counted elsewhere. The ones whose nearest text is a page
    away are not attributed here: that is the signature of the across-pages mismatch above, and
    telling the two apart needs a paragraph page read the way the drawing's is.
  • The ratio this instrument exists to produce, 213 of 227, is 93.8% — below the 95% the
    acceptance step asks for. This change makes the figure the export's to answer for rather than the
    reading's; it does not meet the bar.
  • The export is untouched: the corpus was exported again from this tree and compared document by
    document with the export this change was measured against — 71 documents, no part changed, and the
    report's notes stand at 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. Word naming no geometry at all, and a shape rectangle whose
range cannot be read, are both raised rather than measured but have no probe of their own — neither
can be provoked from a document.

…s page geometry

The protocol took a drawing placed from its paragraph to stand where that
paragraph's text begins, plus the offset the drawing states. Word reckons that
offset from the top of the paragraph's box, the space above the paragraph
included, while a range reports the top of its first line — 11.87pt lower
wherever 12pt of space stands above it. A place a line out is held against the
text a line out, since a drawing is attributed to the paragraph nearest it: seven
drawings in four invoices were recorded as not following text they never stood
beside, each read as not having moved at all while the paragraph it was held
against moved 9.4 to 10.9pt.

Word's own page geometry gives every drawing a rectangle in points from the
corner of the page it is drawn on, and that rectangle's range starts on the
character the shape's anchor sits on, so the two are matched one for one. The
vertical place, the left edge and the page now all come from Word, and nothing is
reckoned from the anchor. Measured on 29 probe documents of one 20x10pt rectangle
each at offset 0 — where the place Word draws it at is the very point Word
reckons the offset from — against the same rectangle in Word's own PDF: right
within 0.41pt in all 29, where the old reckoning was out by the space above the
paragraph and named the wrong page altogether for a drawing in a row that begins
on the page before. Read in LibreOffice 24.2 as well, which reckons from the same
point. It was first supposed that Word reckons the offset from the top of the
row's content; a drawing at offset 0 in the second paragraph of a cell is drawn
at that paragraph's own top, where the row's content begins 15.98pt higher.

A drawing the page geometry does not name is reported with the reason and not
measured, since a place reckoned from its anchor is what was wrong: a row Word
repeats as a table's head is drawn on every page the table reaches, and the one
drawing it holds stands in a rectangle on each of them. A drawing is held to its
text within a point and a half rather than a point, because Word states the
geometry in whole points and two readings of a place can each be half a point
out. -SelfCheck runs the protocol over six documents it writes itself and holds
it to where Word's own PDF draws the same drawing.

Over the template corpus, 71 documents, the old protocol and the new one on the
same export: 31 documents pass where 27 did, 7 fail where 11 did, no document
went the other way, and all 760 drawings were named by the page geometry.
…what the scenario counted

A character counts from the start of its own story, and the export writes a page's background as a
shape anchored in a header: its rectangle sits on character 0 of that header, which is the character
a body drawing that opens the document sits on too. In invoice-metered that is a 595x41pt rectangle,
and only its size kept it from being taken for a body drawing's. The story a rectangle's character
counts from is now part of the match, with a probe of a body drawing and a header drawing of its own
size both on character 0, and Word naming no rectangle at all for a document that has drawings is
raised rather than reported as a finding against every one of them.

The scenario's detail says what it counted: the drawings it measured are those standing beside text,
and the ones standing beside no text on their page are counted beside them instead of being left out
of it, so a document that measured one drawing of twelve cannot read as a document of one.

Three places where the prose outran the code: the probe that anchors a drawing to the page stands it
at 103.95pt, the place the probe beside it draws the same rectangle at, measured — at 87.98 it was a
line above that and won its owner by 0.05pt, so Word rounding the other way would have failed the
check for no fault of the protocol; the repeated head is held to its own refusal rather than to the
phrase every refusal carries; and the limit of a point and a half passes drift under half a point
and, where the rounding runs the other way, as much as two and a half, which is what the geometry
Word states can resolve.

Find-Owner now carries the limit of its attribution: a drawing's place is where Word draws it, a
paragraph's is where a range reports its first line and the page that range reports, so a drawing of
a row that begins on one page and is drawn on the next can have the paragraph it stands beside
filtered out by the page test. Reading the paragraphs from the same geometry would settle it and
would change which paragraph owns a drawing throughout, which belongs with the attribution itself.

-SelfCheck is seven documents, four of them held to a place in Word's own PDF.
@DemchaAV
DemchaAV marked this pull request as ready for review October 30, 2026 11:43
@DemchaAV
DemchaAV merged commit d99996d into 2.5-dev Oct 30, 2026
11 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-follow-text-row-top branch October 30, 2026 11:44
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