Skip to content

fix(pptx): let a hanging-indent bullet separate with its tab alone - #793

Merged
developer0hye merged 1 commit into
mainfrom
fix/pptx-bullet-space
Aug 5, 2026
Merged

developer0hye merged 1 commit into
mainfrom
fix/pptx-bullet-space

Conversation

@developer0hye

Copy link
Copy Markdown
Owner

Summary

fixed_text_list_marker appends a space after the bullet glyph, and the
hanging-indent branch of prepend_fixed_text_list_marker_run then appends the
tab that carries the gap to the indent. Both together — "• \t" — put the body
one space past the indent.

Measured

tests/fixtures/pptx/office2pdf_introduction_ko.pptx page 28. First body glyph
of the first bullet, from mutool draw -F trace with the full affine applied:

first body glyph x
reference 99.01pt
before 101.60pt
after 99.00pt

The bullet itself sits at 72.00pt in all three. 2.59pt of excess removed, landing
0.01pt from the reference.

Change

The hanging-indent branch trims the marker's trailing space before appending the
tab. Only that branch: the non-hanging path gets no tab, so it still needs the
space, and the ordered-list grid path has its own separator by design.

Blast radius, and what is not verified

11 of 73 chart- and text-bearing fixtures render differently — every unordered
list with a hanging indent, which is the intended reach. I measured the indent
exactly on the audited deck only. On the others I confirmed the output changes
but did not verify each moves toward its reference; the mechanism is identical
in all of them (one redundant separator removed where a tab already spans the
gap), and it is not a tuned constant, but that breadth is stated rather than
demonstrated.

Tests

One, failing before the change. The tab compiles into segments, so it asserts
the marker is the whole first segment — let tab_segment_0 = [•] — and never
[• ].

Visual impact

  • No rendered PDF change
  • Rendered PDF change or visual evidence added

Visual audit

The GT image is a LibreOffice 24.2.7.2 render, not a PowerPoint export. It
agrees with this issue's own native-Office measurement on the one number that
matters here: the body indent.

Preview
GT GT
Before Before
After After
  • Rendered all evidence at 150 DPI or higher
  • Stored progressive JPEG quality 86 assets with metadata stripped
  • Inspected matched region crops at full resolution
  • Ran the 5% fuzz pixel-difference sweep
  • Inventoried hairlines and border dash styles
  • Inventoried font weight, italic, and underline emphasis
Item Result
Page count/order Matches GT — 30 pages on all three renders
Element presence Matches GT — every bullet and its text present on both sides
Position/size Fixed — body text moves 101.60pt → 99.00pt against the reference's 99.01pt; the bullet stays at 72.00pt
Rotation/flip No deviation observed — nothing on the page is rotated
Fill No deviation observed — this fix emits no fill
Stroke/border No deviation observed — the page carries no rule this fix touches
Text content Matches GT — the same words; only where they wrap changes
Font family/weight/style Matches GT — unchanged by this fix
Text color Matches GT — unchanged
Alignment Fixed — the body now starts at the indent the paragraph declares
Line/paragraph spacing Matches GT — vertical rhythm unchanged
Clipping/overflow No deviation observed — nothing is clipped

Hairline inventory: page 28 has no element at or below 1pt; it is text and
bullet glyphs only.

The 5% fuzz sweep moves 113,629 → 113,590 against the reference, which measures
nothing useful here: the page's Korean glyphs differ from the reference's
throughout for font reasons, and a 2.6pt shift of one column of text is lost in
that. The glyph-x table above is the evidence.

Related: #685

fixed_text_list_marker appends a space after the bullet glyph, and the
hanging-indent branch then appends the tab that carries the gap to the
indent. Together -- "* \t" -- they put the body one space past it.

That branch now trims the trailing space. Only that branch: the
non-hanging path gets no tab and still needs the space, and the
ordered-list grid path has its own separator by design.

Measured on office2pdf_introduction_ko page 28: the first body glyph
moves from 101.60pt to 99.00pt, against a reference at 99.01pt, with the
bullet itself at 72.00pt throughout.

Related: #685
Signed-off-by: developer0hye <developer.0hye@gmail.com>
@developer0hye

Copy link
Copy Markdown
Owner Author

Follow-up on the "what is not verified" section — I tried to close that gap and could not, so recording the attempt.

First finding, which sharpens the mechanism: removing the space does not always move visible text. The tab advances to a stop, so where marker + space still fit before that stop, only the invisible space glyph disappears and every glyph stays put. Page 1 of poi_sample and powerpoint_sample are exactly that case — visible glyph positions are byte-identical before and after. Text moves only where the space pushed the marker past the stop and forced the tab to the next one, which is what happens on the audited deck (101.60pt → 99.00pt).

Across whole documents the other ten fixtures do move visible glyphs, so the reach is real:

45545_Comment      835 of 1983 glyphs moved
customGeo         1583 of 12958
themes              86 of 541
powerpoint_sample   51 of 367
poi_sample          22 of 117
...

Second finding: I could not establish direction for them. Matching our glyphs to the LibreOffice reference by character and nearest baseline gives mean |dx| of 24–45pt on those decks, which is far too large to be real — the matcher is pairing repeated characters across the page rather than corresponding glyphs. The numbers it produced disagree with each other (themes 23.986 → 24.288, 45545_Comment 45.550 → 44.419) and I do not trust either.

So the position in the PR body stands unchanged: measured exactly on one deck, mechanism understood and not a tuned constant, breadth confirmed but direction unverified elsewhere.

@developer0hye
developer0hye merged commit 00979b8 into main Aug 5, 2026
17 checks passed
@developer0hye
developer0hye deleted the fix/pptx-bullet-space branch August 5, 2026 10:33
@developer0hye

Copy link
Copy Markdown
Owner Author

Correction to the blast-radius number in this PR body.

I reported "11 of 73" fixtures changed. That sweep was run against a stale main binary and the figure is inflated.

My local main build had been failing since #790 merged: Cargo.lock is gitignored, mine still pinned a docx-rs commit predating the word_wrap field, and I was piping cargo build through | tail -1, so the error never reached me. target/release/office2pdf stayed at a pre-#790 revision. Every sweep after that point therefore compared the branch under test against a "before" binary that was missing one or more already-merged fixes, so earlier merges' effects were attributed to the branch.

Scale of the distortion, measured while sweeping #799: that sweep read 5 of 69 against the stale binary and 3 of 69 once main was built correctly. The two extras were the icon changes from #795 and #796 being misattributed to header/footer parsing.

Affected: the sweep counts in #793 through #797. This one's "11 of 73" is the least trustworthy, being the earliest after the break.

Not affected: every targeted before/after in those PRs, each of which was measured against a committed native-Office ground truth rather than against a locally built main. The fixes themselves and the evidence for them stand — only the breadth-of-change figures are wrong.

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