Skip to content

Resolve anchored drawing offsets against their relativeFrom frame - #18

Merged
mantissaman merged 1 commit into
mainfrom
fix/anchor-relative-from
Jul 29, 2026
Merged

Resolve anchored drawing offsets against their relativeFrom frame#18
mantissaman merged 1 commit into
mainfrom
fix/anchor-relative-from

Conversation

@mantissaman

Copy link
Copy Markdown
Contributor

Closes #10.

The bug

A wp:anchor offset means nothing on its own. The same number lands somewhere different depending on whether it is measured from the page, the margin, the text column or the paragraph. We parsed relativeFrom into CT_Anchor and then never used it, so every offset was treated as an absolute page coordinate.

The file attached to #3 asks for:

<wp:positionH relativeFrom="column"><wp:posOffset>60325</wp:posOffset></wp:positionH>
<wp:positionV relativeFrom="paragraph"><wp:posOffset>635</wp:posOffset></wp:positionV>

That is 4.75pt from the column and 0.05pt from the paragraph. Read as page coordinates it is the very top left corner of the sheet, off the printable area, which is why the image looked like it had vanished. Resolved properly it is x 76.75, y 107.4, beside its paragraph.

Why this moved code around

Resolving a paragraph-relative offset needs the laid-out position of the paragraph, and that is only known during pagination. The old resolve_anchor_images ran afterwards with no paragraph-to-page mapping, which is why it also had to put every anchored drawing on page one regardless of where its paragraph ended up. Its own comment admitted this.

So anchors now travel with the ParagraphBlock and the paginator places them once the page and cursor are settled. The old pass is gone.

Two details that fall out of doing it this way:

  • behindDoc drawings are collected separately and emitted before the rest of the page, so they sit under the text rather than over it.
  • A paragraph split across a page boundary places its anchors with the first part only, so they are not emitted twice.

Coverage

All eight horizontal frames and all eight vertical frames are handled. margin and column coincide because multiple text columns are not laid out yet, and line falls back to the paragraph top because we do not track individual line boxes at that point. Both of those are noted in the code rather than left to be discovered.

Two tests: one pinning every frame against a known geometry, and one asserting the property the old code could not express at all, that the same offset resolves differently once the paragraph moves down the page.

What this does not fix

The image in #3 still does not appear, for an unrelated reason. It is an 8-bit colormap PNG, and decode_image returns None for colour type 3, so neither the PDF nor the PNG backend can draw it. Filed separately as #17. With that fixed the drawing will appear in the right place.

So the visible result for that specific document is unchanged. The fix is verified by the unit tests and by the resolved coordinates, not by the render.

Checks

cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings and the full suite all pass. 335 tests, up from 333.

Note for PR #2

This rewrites resolve_anchor_images, which PR #2 also touches. That PR adds alignment-based positioning (pos_h_align, pos_v_align), which is the natural next step on top of this and should slot into resolve_anchor_h and resolve_anchor_v. Its wrap, drop cap and footnote work is untouched by this change.

A wp:anchor offset is meaningless on its own. The same number lands
somewhere different depending on whether it is measured from the page,
the margin, the text column or the paragraph. We read relativeFrom into
CT_Anchor and then ignored it, treating every offset as an absolute page
coordinate, so anchored drawings ended up in the corner of the sheet.

In the file attached to #3 the anchor asks for column plus 4.75pt
horizontally and paragraph plus 0.05pt vertically. As page coordinates
that is the very top left of the page, off the printable area. Resolved
properly it is x 76.75, y 107.4, which is beside its paragraph.

Resolving a paragraph-relative offset needs the laid-out position of the
paragraph, which is only known during pagination. So the anchors now
travel with the ParagraphBlock and the paginator places them once the
page and the cursor are settled. That also removes the old pass, which
had to guess and put everything on page one regardless of where the
anchoring paragraph actually was.

behindDoc drawings are collected separately and emitted before the rest
of the page so they sit underneath the text rather than over it.

A paragraph split across a page boundary places its anchors with the
first part only, so they are not emitted twice.

Closes #10.
@mantissaman
mantissaman merged commit 9630ba7 into main Jul 29, 2026
7 checks passed
@mantissaman
mantissaman deleted the fix/anchor-relative-from branch July 29, 2026 22:49
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.

Anchored images ignore relativeFrom and land in the page corner

1 participant