Skip to content

fix(docx): grow a line holding a picture where the picture stays within its text - #957

Merged
DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-least-picture-lines
Oct 25, 2026
Merged

DemchaAV merged 2 commits into
2.5-devfrom
fix/docx-least-picture-lines

Conversation

@DemchaAV

@DemchaAV DemchaAV commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Why

A line is written at a least height (w:lineRule="atLeast"), which Word grows when a reader makes the text larger, where it is at least as tall as its runs' own line (#955, #956). A line holding a picture set in it was never read: DocxGrownLines.reachOf gave it no reach, so an icon or a drawn bullet beside words kept the line exact. With Normal two points larger, Word cut the tops off those letters on screen. The corpus had 195 such exact lines at least as tall as their text's own.

Measured in Word 16 and LibreOffice 24.2 (CI's build), on 8pt and 10pt Lato beside pictures 6 to 11pt tall, lowered 0 to 3pt by their run's position, in exact and least lines of 9.95, 12 and 15pt:

  • Word lowers the picture by its run's position, in a least line too. A least line is as tall as the higher of the text's and the picture's reach above the baseline, plus the deeper of the two below it. Its baseline stands that depth above the foot. This held within 0.05pt in every case.
  • LibreOffice ignores the position. It stands the picture a tenth of a point above the baseline and grows the line where the picture then passes the text's top: a 9pt icon lowered 1.5pt beside 8pt Lato left a 9.95pt least line as tall in Word, and grew it to 10.75pt in LibreOffice.
  • A picture run's own size sets none of the line in either editor: a 24pt picture run left an 8pt least line as tall.

So a line whose pictures stay inside its text, by both readings, stands its text where it would stand without them.

What changed

  • DocxGrownLines.reachOf reads a run holding only rPr and pictures set in the line (wp:inline):

    • the pictures' top above the baseline, the higher of Word's place, lowered by the run's position, and LibreOffice's, PICTURE_RISE above the baseline;
    • Word's lowering as their foot;
    • not the run's face.

    It gives no reach where:

    • a picture has no extent written, or its run's position is not a number;
    • a drawing is anchored beside the pictures, or holds none;
    • the paragraph's lines are broken (w:br), since a broken line may hold a picture alone;
    • the paragraph holds no letter beside its pictures, as a rating's dots between spaces, no-break spaces included. LibreOffice set such a line taller: MidnightNavy's language dots stood the rows under them 0.56pt low, then a point lower a row.
  • Reach carries the two (pictureTop, pictureFoot). holdsItsPictures() asks whether no picture reaches above the text's own line, its faces' line gap included, nor below its shallowest descent. It reads the text's own line, not the line written: a line makeRoomForPictures wrote taller for a picture, raised off the baseline say, holds that picture, and its text's own line does not.

  • mayGrow requires it.

  • lineOf reads a least line holding pictures only where it is at least its text's own line and its pictures stay within that line. A line makeRoomForPictures grew to a picture that rises past the text stays unread, as before, and is not seated.

  • shiftToThePagesBaseline writes no position for a least line. Word does not move a least line's text by one, and LibreOffice moves it and grows the line. A line makeRoomForPictures wrote at least, whose picture stays under its faces' own line (an 11pt picture beside 10pt Poppins, two points between its lines), is now seated, by the space around it but not by a position. No document of the corpus holds one: the export with these two fixes is the same, byte for byte but for the time stamp, as the one measured below.

  • What is written as before:

    • a line whose picture rises past its text in either editor, as CharcoalGold's contact icons do in LibreOffice;
    • a picture lowered past the text's descent in Word;
    • one-line paragraphs held to their pictures (holdPicturesInTheLine), which are cut to fit.
  • Docs: the recipe's Line height row, the CHANGELOG, the older least-line entry's picture clause, and makeRoomForPictures' Javadoc.

Verification

  • Probes (scratch, not committed), each in Word and LibreOffice 24.2:
    • 36 cases of picture height, position, line height and rule, plus controls;
    • a picture run in its own 8, 16 and 24pt;
    • 10pt Lato lines with 6 and 9pt pictures.
  • Tests:
    • DocxGrownLinesTest, aLineMayGrowOnlyWhereItsPicturesStayWithinItsTextInBothEditors:
      • a picture within the text grows; one lowered within it grows;
      • one 9.9pt up in LibreOffice does not; one raised 4pt, 10pt up in Word, does not; one lowered past the descent does not;
      • a 24pt picture run grows;
      • pictures beside a break, among spaces and among no-break spaces have no reach;
      • lineOf reads a least line's baseline, and not a picture past the text, nor a line written shorter than its text's own, nor one written taller for a raised picture.
    • DocxGrownLinesTest, aPictureRunHoldingAnythingButPicturesOfAWrittenSizeIsNotRead: an anchor beside a picture in one drawing, and a position in points, give no reach.
    • DocxGrownLinesExportTest (+4):
      • a 6pt baseline picture beside 10pt Lato makes a least line at Lato's own line, with no position;
      • a Poppins paragraph grown to an 11pt centred icon, shorter than Poppins' own line, keeps its text unpositioned;
      • one with two points between its lines, an 11pt picture under Poppins' own line, is seated and still given no position;
      • a 9pt picture raised 2pt opening the page: its line is written taller than Lato's own, its picture keeps its raise and its text no position.
    • DocxComposedListTest: a drawn marker's item in a cell is now least, as the flow's.
  • Fail-closed: 12 sabotages, each run alone, all caught. They break:
    • reading pictures at all;
    • LibreOffice's rise; the foot; the position's sign;
    • the break rule; the spaces rule; no-break spaces;
    • lineOf's fit, and its line-height check;
    • leaving the run's face unread;
    • holding pictures by a line two points taller than the text's own;
    • writing a position for a least line.
  • Corpus: 7 of 62 documents change, 92 paragraphs exact → least. The report changes only in their line height counts.
  • Word edit protocol, restyle-body-through-normal:
    • on the seven documents, squeezed paragraphs 67 → 31, and TealPulse passes;
    • corpus-wide 1005 of 1832, from 1041, with 14 documents passing, from 13.
  • Word fidelity gate (scripts/docx-visual/word-fidelity.ps1): BUILD SUCCESS. On the seven documents:
    • steps from a line to the next off by more than 0.2pt: 29 of 472, from 31; p90 0.13, from 0.14;
    • lines: 9 nearer the page and 55 further, of 882.
    • ConsultingInvoice's line items (36 lines, both lengths) stand one step of Word's 0.12pt grid lower. Their description cells now seat the row's lift out of the space above the table by 7 twips less, and the other cells' space above takes as much less. LibreOffice sets them where they stood.
    • TealPulse's duties (14) stand 0.24pt lower. Their 9.8pt line holds a 0.2pt gap above the text, which the space above them does not take. That is inside the quarter point a least line is held to, but further than the exact line stood.
  • LibreOffice 24.2 (CI's build, in a container), base and change: 16 lines nearer and 9 further, of 880. Steps off by more than 0.2pt: 34 of 489, from 36; p90 0.07, as before. TealPulse's and OrangeOps' skills move 0.3pt down, from 0.1–0.33pt high to between 0.21pt low and 0.03pt high.
  • Gate:
    • ./mvnw -B -ntp clean verify -pl <core, render-pdf, render-docx, render-pptx, templates, testing, qa, coverage> -am → BUILD SUCCESS: core 823, render-pdf 339, render-docx 2178, testing 5, render-pptx 142, templates 127, qa 1830.
    • The JDK 17 knowledge checks pass with the API surfaces unchanged, and core javadoc has 0 warnings.
    • ./mvnw -f examples/pom.xml test → BUILD SUCCESS, 93 tests.

Lane: shared-engine (render-docx). No public API change. Picture lines' line rules change in the file.

…in its text

- DocxGrownLines reads a run holding only pictures set in the line: its
  pictures' top, the higher of Word's place (lowered by the run's
  position) and LibreOffice's (a tenth of a point above the baseline,
  whatever the position), and Word's lowering as their foot. The run's
  own face sets none of the line.
- A line may grow only where its pictures stay within its text in both
  places (Reach.holdsItsPictures); lineOf reads a least line holding
  pictures only where it is at least its text's own line and holds them.
- Pictures in a paragraph of broken lines, or beside spaces alone, give
  no reach.
- Tests: within, lowered, raised, too tall, too deep, own size, broken,
  spaces and no-break spaces, lineOf for a line grown to its picture; a
  drawn marker's composed item now grows as in the flow.
@DemchaAV
DemchaAV marked this pull request as draft October 25, 2026 04:49
…least line no position

- Reach.holdsItsPictures reads the text's own line, its faces' line gap
  included, not the line written: a line makeRoomForPictures wrote taller
  for a raised picture held that picture, and its least line was seated
  and given a position.
- shiftToThePagesBaseline writes no position for a least line, whose text
  Word does not move by one; LibreOffice moves it and grows the line.
- Tests: a raised picture's line and one with two points between its
  lines are given no position; an anchor beside a picture and a position
  in points give no reach; the corpus export is unchanged.
@DemchaAV
DemchaAV marked this pull request as ready for review October 25, 2026 05:09
@DemchaAV
DemchaAV merged commit 1a360f5 into 2.5-dev Oct 25, 2026
13 checks passed
@DemchaAV
DemchaAV deleted the fix/docx-least-picture-lines branch October 25, 2026 07:34
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