Repository navigation
DOCX: a wrapSquare/wrapTight anchored picture floats to the page top and the text resumes below it at full width, instead of wrapping beside it #2353
Description
Problem
An anchored picture (wp:anchor) with wp:wrapSquare or wp:wrapTight is emitted as #place(top + left, dx: …, dy: …, float: true) (generate_floating_image_with_vertical_offset, crates/office2pdf/src/render/typst_gen.rs). A Typst top float sits at the top of the page region and pushes the flow below it.
Word does two things differently:
- It draws the picture at its anchor.
- It sets every line that meets the picture's vertical band beside the picture, on the sides
wrapTextallows.
As a result, office2pdf sets text that Word puts beside the picture below it at full width. A picture anchored below earlier text is also drawn above that text, at the top of the page.
Measurements
Fixtures: docxide-pdf tests/fixtures/cases/ at fe7b8bea, Apache-2.0 synthetic. Every reference.pdf reports Creator: Microsoft Word; the pages are Letter with 72pt margins. Ours is the office2pdf main release CLI at 80aba9ca. Line boxes come from mutool draw -F stext, and picture boxes from -F trace fill_image transforms (y down, pt).
case47 page 1. The picture is in the first paragraph: positionH column 0, positionV paragraph 0, 150×50pt, wrapSquare wrapText="largest". The picture's position agrees, so this isolates the missing side-wrap:
| picture x, y | "Page 1: A 50pt" x0 / y0 | first line back at the margin | |
|---|---|---|---|
| Word | 72.00, 72.00 | 231.10 / 72.08 | 72.03 / 126.60 (4th line) |
| ours | 72.00, 72.00 | 72.00 / 138.50 | every line, from 138.50 |
case41 page 1. The picture is alone in the second paragraph, after one text paragraph: positionH column align left, positionV paragraph 0, 180×180pt, wrapSquare bothSides, distR/distB 7.2pt:
| picture top | paragraph 1 y0 | paragraph 3 first line x0 / y0 | first line back at the margin | |
|---|---|---|---|---|
| Word | 111.65 | 72.08 | 259.35 / 136.60 (11 lines beside) | 72.03 / 299.65 |
| ours | 72.00 | 268.50 | 72.00 / 307.60 (full width) | — |
office2pdf draws the picture at the top margin, above paragraph 1, and pushes paragraph 1 down 196.4pt. case41 pages 2 and 3 show the same flow for right- and centre-aligned pictures. Their horizontal position is a separate defect, #2352.
Real-world case. The document is docxide scraped/indonesian_benchmarking_guide, a third-party public document that is not committed here (input.docx sha256 99fd8ef0…e095). Its 15-page Word reference reports Creator: Microsoft Word.
- It has a 251.1×373.5pt picture:
wrapSquare bothSides,positionH margin align left,positionV paragraph align top. - Five empty paragraphs follow the picture's own paragraph. Then comes a paragraph holding only
<w:br w:type="textWrapping" w:clear="all"/>. - Word draws the picture at the top of page 7 and resumes the heading "3. Metode Benchmarking" below it, at y0 459.79 on page 7.
- office2pdf floats the picture to the top of page 7 but sets the heading on page 6 at y0 605.97, so pagination differs from there on.
This is the drawing half of #2230. A clearing break has nothing to clear until the picture has a wrap band.
Expected
For wrapSquare, wrapTight and wrapThrough:
- The picture sits at its resolved anchor position.
- Each line whose box meets the picture's band, extended by
distT/distB, is shortened on the sideswrapTextallows (bothSides,left,right,largest), keepingdistL/distRfrom the picture. - Lines below the band return to the full column.
For wrapTight, Word follows the picture's wp:wrapPolygon. In case42, the lines narrow and widen along a non-rectangular outline. A square band is the wrapSquare rule only.
Lead
#2349 (crates/office2pdf/src/render/typst_gen_float_wrap.rs) implements this exclusion for w:tblpPr floating tables, using o2p-float-<id>-top/-bottom labels. #2351 builds the clearing break on that. A picture band could reuse both.
Activity
developer0hye commentedon Oct 19, 2026
Addendum: one
case41page 1 measurement contradicts the Expected rule as written. Settle the band's top edge by measurement or a probe when this is fixed, not from the wording above.mutool draw -F stext).distTis 7.2pt (91440 EMU). So that line ends above a band that starts at 104.45, and the "meets the band extended bydistT/distB" rule would keep it at the margin.