Fix a bunch of renders#101
Merged
SimonCropp merged 24 commits intomainfrom May 10, 2026
Merged
Conversation
ScoreFace was penalising width mismatch 1000:1 against weight, so Arial_700.ttf (which reports usWidthClass=3 Condensed in its OS/2 table) lost to arial.ttf (width=5) for Bold/Black requests — leaving requested-bold runs rendered with the regular face. Drop the multiplier to 100. Also embolden synthetically in Skia when the resolved face is still 200+ weight units below target (e.g. Arial Black 900 → Arial Bold 700) so RAINBOW / Arial Black Style / Google Style render with the heavier strokes Word produces. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Skia's SKFont.Size is in pixels (the typeface is constructed with size * Scale), but ImageSharp's Font.Size is in points — the same scenario test code wasn't accounting for the difference, so the strikethrough sat ~1.3× closer to the baseline than it should and read as a near-baseline underline. Multiply by context.Scale so it lands mid-x-height as intended. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Word's highlighter pen uses w:highlight (named-color palette) instead of w:shd (arbitrary RGB). The parser only handled w:shd, so runs decorated with <w:highlight w:val="yellow"/> rendered as plain text with no background. Map the 16 named colors to RGB hex via HighlightToHex and feed the result through the existing BackgroundColorHex pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
A fixed 1px tonal companion is invisible against 72pt Impact, so the second EMBOSSED in the wordart fixture rendered indistinguishable from a flat dark glyph. Scale the offset to ~4% of font size (clamped to ≥1px) in both backends so the highlight/shadow companion is visibly offset on display-size runs while still pixel-tight on body text. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
ParseBorderEdge dropped the w:val style, so <w:top w:val=\"double\"/> resolved to a single thicker line. Capture the style on BorderEdge and split it across both renderers: each line gets ~1/3 of the declared width, separated by a gap totalling the remaining 1/3, so total span matches the OOXML width. Other non-single styles fall back to single until they're needed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Tables that set <w:tblW w:type=\"dxa\"/> declare a fixed preferred width, but CalculateColumnWidths' autofit branch grew columns to fill the page anyway — so a 3000-twip table aligned right rendered as full-width and the alignment became invisible. Capture the dxa value as PreferredWidthPoints during parse and skip the autofit-grow when it's set; pct/auto tables still flow as before. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Both inline-SDT parsing paths bailed via `continue` after seeing a w:br child, so a run like <w:r><w:br/><w:t>Sharma</w:t></w:r> emitted the newline but discarded the trailing text. The 'Chanchal\nSharma' resume heading rendered as just 'Chanchal'; address blocks dropped continuation lines after a soft break. Emit the newline run, then fall through to ParseRun so the same run's <w:t> children still get parsed. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
MeasureCellHeight subtracted padding.Top from the first paragraph's SpacingBefore (and likewise for the bottom), on the assumption padding \"absorbed\" leading/trailing paragraph spacing. Word doesn't collapse the two — padding sits between the cell border and the content area, paragraph spacing lives inside that area, and they sum. Removing the collapse makes tables that set w:tblCellMar (table_cell_padding, table_cell_padding_varied, business-plan/wedding decks) render at the row heights Word produces. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
The recent table-cell padding fix tightened row heights for autofit-grid tables, so the previously-stale verified PNG no longer matched. Promote the received image to baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
When a table sets <w:tblW w:type=\"pct\"/> (fill the container) but its cells have no explicit dxa widths, CalculateContentBasedColumnWidths fell into the \"hug content\" branch and produced narrow columns starting at the table's left edge — so vmerge / page-break / two-column layout tables collapsed their right cells next to the left cells instead of spreading across the page. Track the pct-fill intent on TableProperties and scale natural content widths up to the available width when set. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Word emits some section divider rules as a single <wps:wsp> with prstGeom prst="line" sitting directly under <a:graphicData> — no <wpg:wgp> wrapper, no <pic> child. The inline-image parser was bailing on this shape silently, dropping the divider lines that resume / cover- letter templates rely on. Wrap the standalone connector into a one-element InlineShapeGroup so the existing line-rendering path picks it up. Also reorder ParseSolidFillShape so the IsLineShape branch runs before the noFill bail-out (line connectors always carry an explicit <a:noFill/> alongside the stroke).
Word fills in an 8pt paragraph spacing-after when a docx doesn't carry its own pPrDefault — minimal docs (table-only templates, hand-built section-break tests) come through this path. Previously we assumed "styles.xml exists ⇒ author opted out of all defaults" and dropped to 0pt, which left rows 50% shorter than Word's render and packed extra content onto the wrong page in section-break flows. Switching the missing-docDefaults branch to 8pt brings 30+ scenarios visibly closer to expected_*.png. Several baselines updated to match, and resumes/10 dropped from 3 received pages to 2 — its old page-3 PNG was deleted as an orphan.
When a table has explicit cell widths (w:tcW) but no w:tblW, Word fits the table to those cell widths and leaves whitespace on the right rather than growing them to fill the page. The previous rule only suppressed the grow when w:tblW dxa was set, which caused narrow tables (e.g. a vertical-text sidebar) to span the full content width. Restrict the autofit-grow to FillContainer (w:tblW pct) so it only fires when the table actually asked to fill its container. Several baselines updated to match the now-tighter layout.
Word's behaviour for a TOC entry inside a narrow table cell: the dot leader fills the cell width and the page number after the tab is hidden (it would have lived past the cell's right edge). Previously we clamped the right-tab destination to the cell edge but still rendered the page number at that point, producing visible numbers Word doesn't show. Extend TabStopResolver to return a suppressFollowing flag when the clamp fires, and have both renderers skip the run sequence between the tab and the next tab/line break when set.
The IsDecorativeShape filter rejected any wsp containing cubic / quadratic bezier segments, dropping the floral overlays on wedding / cards / agendas-minutes templates and the gift-box artwork on greeting-card templates. The polygon flattener already turns those beziers into a polyline approximation via ExtractPolygonPoints; the guard was protecting a pipeline state that no longer exists. Drop the bezier-count rejection (both in ShapeParser.IsDecorativeShape and the parallel >50-bezier guard in DocumentParser.ParseSolidFillShape) and only filter genuinely unrenderable cases — ArcTo segments and the existing >50:1 aspect-ratio thin-line heuristic. ~18 templated backgrounds now render as solid-fill polygons; baselines updated.
The OOXML spec calls for top: 0, bottom: 0 default cell margins, but real Word adds about 2pt of breathing room top and bottom. Without it each cell row measures at line-height + spacing-after only — too tight to match Word's actual layout, so 30-row tables fit on one page in our renderer where Word splits across two. Use 2pt vertical implicit padding when neither w:tblCellMar nor a table style supplies one. Also makes most table-related ErrorMetrics improve slightly because rows now match Word's rendered height. Fixes #17 (table_multipage now paginates to 2 pages); ~20 baselines updated.
…pace Letter-style layouts use a w:tblPr table with one or two hRule="exact" rows where the body row consumes most of a page (~530pt). With prior content on the page (a heading, an intro), the fixed slot can't fit in the remaining bottom margin — Word lifts the entire table to the next page rather than overflowing or shrinking the slot. When a table contains an exact-height row and would overflow remaining space (with a 5pt buffer to avoid splitting on line-rounding noise), finish the current page and start a new one before drawing. Restricted to non-floating tables and to cases where the table fits on a fresh page so we don't trade overflow for ping-ponging through row-by-row. Fixes #18 (table_layout_tall_row paginates to 2 pages).
The renderer treated <w:docGrid w:linePitch="..."/> as the line slot for empty end-of-cell paragraphs — which made every empty checkbox cell in a 360-twip-grid template (wedding/04) measure at 18pt instead of the style font's natural ~12pt. Across a 42-row checklist that 6pt-per-row overhead added 250pt of phantom height and pushed half the content onto an extra page. Word's docGrid is for East Asian character-grid layouts; it doesn't override individual paragraph line metrics in tables. Drop the empty- paragraph linePitch enforcement so empty cells use the same compact line-height calculation as text-bearing ones. Page counts collapse to match Word for wedding/04, brochures/02 and several others; ~25 baselines updated.
Two related fixes drive page 2 of wedding/02 to render the correct floral overlays (and improve letters/13, newsletters/12, /14 by similar margins): 1. Behind-text FloatingImageElement now triggers AdvanceToBackgroundsTargetPage, the same as FloatingShapeElement. Without this, anchored decorations whose parent paragraph sits between two tables were left on the current page even when the next table forced a page break. 2. AdvanceToBackgroundsTargetPage's look-ahead used to bail at the next background, on the assumption "that one will handle its own advance". For a sequence of decorations belonging to the same target table, this anchored only the last decoration to the new page; earlier ones stayed behind. Walk past every background in the sequence so the whole group lifts together. 3. EstimatedNextElementHeight summed every row's declared height (or 25pt fallback) for tables, instead of returning only the first row's. Multi-row tables without explicit trHeights previously estimated to zero, so the advance check never fired.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.