Skip to content

Implement vertical-align via Parley - #832

Draft
nicoburns wants to merge 15 commits into
mainfrom
devin/1788386630-vertical-align
Draft

Implement vertical-align via Parley#832
nicoburns wants to merge 15 commits into
mainfrom
devin/1788386630-vertical-align

Conversation

@nicoburns

@nicoburns nicoburns commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

Companion to DioxusLabs/parley#18 (Parley now builds the CSS inline alignment tree itself: root strut, ancestor inline boxes, parent-relative baseline shifts, top/bottom aligned subtrees). Cargo.toml pins Parley to that branch's head (6268e53); this should be repointed once #18 lands.

  • stylo_to_parley::vertical_align: Stylo stores vertical-align as the css-inline-3 longhands, and Parley's VerticalAlign { alignment, shift } is the same compound, so both are mapped losslessly: alignment-baseline (baseline|text-top|text-bottom|middle) → parley::AlignmentBaseline, baseline-shift (sub|super|top|bottom|<length-percentage>) → parley::BaselineShift. Percentages resolve against the element's own line-height. baseline-shift: center has no Parley equivalent yet and is approximated as middle; baseline-source is ignored.
  • Set TextStyle::vertical_align on every span and InlineBox::vertical_align on atomic inlines.
  • InlineBox::baseline is now populated for inline-blocks from taffy's baselines.last.or(first) (+ top margin), or None (bottom margin edge) when overflow isn't visible / there are no line boxes (CSS 2 §10.8.1). Inline layout now also reports baselines.last upward.
  • Removed the root_line_height floor on each span's line-height — Parley adds the strut and every ancestor span's own box to the line, so the floor was double counting and broke shifted spans.
  • Floats/abspos inline boxes call append_inline_box_to_line(.., NEG_INFINITY, NEG_INFINITY, ..) so they contribute no height.
  • API churn: Cluster::style_index() replaces Glyph::style_index(); Run::font_metrics(), run.font().font, NormalizedCoord::to_bits().

WPT (css/CSS2/linebox css/css-inline css/CSS2/text)

454 → 490 tests passing, 1198 → 1299 subtests, 0 crashes. Four previously-passing tests now fail; all are pre-existing gaps that the old always-tall strut happened to hide:

  • text-decoration-va-length-001.xht — Blitz paints decorations per glyph run at the run's (now shifted) baseline; they should sit at the decorating ancestor's baseline. Needs decoration propagation in blitz-paint.
  • linebox/baseline-block-with-overflow-001.html — taffy block layout propagates a child's baseline through an overflow: hidden block; should synthesize from the bottom margin edge (taffy issue).
  • css-inline/empty-span-size-002.html — Blitz doesn't model inline border/padding boxes, so an empty bordered span doesn't make the line visible.
  • dominant-baseline-mixed-writing-modes-002.html — vertical writing modes / dominant-baseline unsupported.

WPT results

Subtests: 321 newly passing, 48 newly failing (net +273).

Full diff (272 changed tests)
+ FAIL => PASS    [1/1]   +1  css/CSS2/abspos/between-float-and-text.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/backgrounds/background-position-applies-to-007.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/backgrounds/background-position-applies-to-009.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/backgrounds/background-position-applies-to-012.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/backgrounds/background-position-applies-to-013.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/backgrounds/background-position-applies-to-014.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/bidi-text/bidi-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/box-display/containing-block-030.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c414-flt-fit-004.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c43-rpl-ibx-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c44-ln-box-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c44-ln-box-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgre-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgre-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgreps-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgreps-002.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgreps-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgreps-004.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c534-bgreps-005.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c536-bgpos-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c536-bgpos-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c547-indent-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5502-mrgn-r-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5504-mrgn-l-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5505-mrgn-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5511-brdr-tw-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5513-brdr-bw-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5515-brdr-w-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c5525-fltwidth-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/css1/c61-rel-len-000.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/clear-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/clear-inline-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/floats-007.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/floats-038.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/floats-040.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/floats-124.xht
- PASS => FAIL    [0/1]   -1  css/CSS2/floats-clear/floats-149.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/floats-clear/margin-collapse-clear-014.xht
- PASS => FAIL    [0/1]   -1  css/CSS2/fonts/font-family-applies-to-005.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/fonts/font-family-rule-005.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/fonts/font-size-124.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/generated-content/content-174.xht
- PASS => FAIL    [0/1]   -1  css/CSS2/linebox/baseline-block-with-overflow-001.html
+ FAIL => FAIL   [7/13]   +5  css/CSS2/linebox/inline-negative-margin-001.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-breaking-font-size-zero-001.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-127.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-002.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-004.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-007.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-008.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-009.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-012.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-013.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/line-height-applies-to-014.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-007.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-008.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-019.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-020.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-031.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-032.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-043.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-044.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-055.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-056.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-067.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-068.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-079.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-080.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-091.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-092.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-103.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-104.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-122.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/linebox/vertical-align-applies-to-008.xht
+ FAIL => PASS  [20/20]  +16  css/CSS2/linebox/vertical-align-top-bottom-001.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/margin-padding-clear/padding-applies-to-017.xht
- PASS => FAIL    [0/1]   -1  css/CSS2/normal-flow/inline-block-zorder-002.xht
- PASS => FAIL    [0/1]   -1  css/CSS2/normal-flow/inline-table-zorder-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/normal-flow/max-height-036.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/normal-flow/max-height-047.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/normal-flow/min-height-036.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/normal-flow/min-height-047.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/positioning/absolute-non-replaced-max-height-002.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/positioning/absolute-non-replaced-max-height-009.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/positioning/abspos-011.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/positioning/abspos-012.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/selectors/first-line-pseudo-014.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/selectors/first-line-pseudo-015.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/tables/table-anonymous-objects-171.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/text/letter-spacing-justify-001.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/text/text-indent-011.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/text/white-space-007.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/text/white-space-normal-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/values/units-003.xht
+ FAIL => PASS    [1/1]   +1  css/CSS2/visudet/content-height-001.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/visudet/content-height-002.html
+ FAIL => PASS    [1/1]   +1  css/CSS2/visudet/content-height-003.html
- PASS => FAIL    [0/1]   -1  css/CSS2/visudet/line-height-204.html
- PASS => FAIL    [0/1]   -1  css/css-align/baseline-of-scrollable-1b.html
- FAIL => FAIL    [0/2]   -1  css/css-align/baseline-of-single-axis-scroll-container.html
+ FAIL => PASS    [7/7]   +5  css/css-align/baseline-rules/synthesized-baseline-flexbox-001.html
+ FAIL => PASS    [7/7]   +5  css/css-align/baseline-rules/synthesized-baseline-grid-001.html
+ FAIL => PASS    [3/3]   +3  css/css-align/baseline-rules/synthesized-baseline-inline-block-001.html
+ FAIL => PASS    [1/1]   +1  css/css-backgrounds/box-shadow-outset-without-border-radius-001.html
+ FAIL => PASS    [1/1]   +1  css/css-break/flexbox/multi-line-row-flex-fragmentation-081a-print.html
+ FAIL => PASS    [1/1]   +1  css/css-break/flexbox/multi-line-row-flex-fragmentation-081b-print.html
+ FAIL => PASS    [1/1]   +1  css/css-break/flexbox/multi-line-row-flex-fragmentation-081c-print.html
+ FAIL => PASS    [1/1]   +1  css/css-break/flexbox/multi-line-row-flex-fragmentation-081d-print.html
- PASS => FAIL    [0/1]   -1  css/css-break/ruby-003.html
- PASS => FAIL    [0/1]   -1  css/css-contain/contain-animation-001.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-content-004.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-layout-cell-002.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-layout-ink-overflow-013.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-layout-ink-overflow-015.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-paint-022.html
+ FAIL => PASS    [1/1]   +1  css/css-contain/contain-paint-023.html
- PASS => FAIL    [0/1]   -1  css/css-contain/contain-size-064.html
- PASS => FAIL    [0/1]   -1  css/css-contain/contain-size-flexbox-002.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flex-direction-row-reverse.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-align-self-horiz-001-block.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-align-self-vert-003.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-baseline-align-self-baseline-vert-001.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-baseline-multi-item-vert-001a.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-baseline-multi-item-vert-001b.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-baseline-multi-line-horiz-001.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-baseline-multi-line-horiz-002.html
- PASS => FAIL    [0/1]   -1  css/css-flexbox/flexbox-baseline-multi-line-vert-002.html
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-canvas-vert-001.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-canvas-vert-001v.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-iframe-vert-001.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-img-vert-001.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-textarea-vert-001.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-basic-video-vert-001.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-justify-content-horiz-002.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexbox-justify-content-horiz-004.xhtml
+ FAIL => PASS    [1/1]   +1  css/css-flexbox/flexible-box-float.html
- PASS => FAIL    [0/1]   -1  css/css-fonts/font-colorization.html
+ FAIL => PASS    [1/1]   +1  css/css-fonts/font-size-zero-2.html
+ FAIL => PASS    [1/1]   +1  css/css-fonts/font-variant-emoji-005.html
- PASS => FAIL    [0/1]   -1  css/css-fonts/small-caps-letter-spacing-002.html
+ FAIL => PASS    [1/1]   +1  css/css-fonts/variations/font-weight-metrics.html
+ FAIL => FAIL    [7/9]   +1  css/css-grid/alignment/grid-align-baseline.html
- PASS => FAIL    [0/1]   -1  css/css-grid/alignment/grid-baseline-001.html
- PASS => FAIL    [0/1]   -1  css/css-grid/alignment/grid-baseline-002.html
+ FAIL => FAIL  [16/18]  +16  css/css-grid/alignment/grid-baseline-004.html
+ FAIL => FAIL   [3/11]   +3  css/css-grid/alignment/grid-container-baseline-001.html
- PASS => FAIL    [0/1]   -1  css/css-grid/alignment/grid-inline-baseline.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-001.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/alignment/self-baseline/grid-self-baseline-horiz-003.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-lr-001.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/alignment/self-baseline/grid-self-baseline-vertical-rl-001.html
- PASS => FAIL    [0/1]   -1  css/css-grid/grid-items/grid-inline-items-002.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/grid-items/grid-layout-z-order-a.html
+ FAIL => PASS    [1/1]   +1  css/css-grid/grid-items/grid-layout-z-order-b.html
- PASS => FAIL    [0/1]   -1  css/css-highlight-api/painting/custom-highlight-painting-inheritance-001.html
- PASS => FAIL    [0/1]   -1  css/css-highlight-api/painting/custom-highlight-painting-inheritance-002.html
+ FAIL => PASS    [1/1]   +1  css/css-images/image-orientation/image-orientation-none-cross-origin.html
+ FAIL => PASS    [1/1]   +1  css/css-inline/baseline-shift/baseline-shift-bottom.html
+ FAIL => PASS    [1/1]   +1  css/css-inline/baseline-shift/baseline-shift-center.html
+ FAIL => PASS    [1/1]   +1  css/css-inline/baseline-shift/baseline-shift-top.html
+ FAIL => FAIL  [15/22]  +15  css/css-inline/baseline-source/baseline-source-first-001.html
+ FAIL => FAIL   [9/12]   +9  css/css-inline/baseline-source/baseline-source-first-textarea-001.tentative.html
+ FAIL => FAIL  [10/22]  +10  css/css-inline/baseline-source/baseline-source-last-001.html
+ FAIL => PASS  [12/12]  +12  css/css-inline/baseline-source/baseline-source-last-textarea-001.tentative.html
- PASS => FAIL    [0/1]   -1  css/css-inline/dominant-baseline/dominant-baseline-mixed-writing-modes-002.html
- PASS => FAIL    [0/1]   -1  css/css-lists/list-and-block-in-inline.html
+ FAIL => PASS    [1/1]   +1  css/css-multicol/multicol-width-negative-001.xht
+ FAIL => PASS    [1/1]   +1  css/css-page/monolithic-overflow-014-print.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/br-clear-all-000.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/empty-ruby-base-container.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/empty-ruby-text-container-abs.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/empty-ruby-text-container-float.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/ruby-base-container-abs.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/ruby-base-container-float.html
- PASS => FAIL    [0/1]   -1  css/css-ruby/ruby-overhang-spaces-022.html
- PASS => FAIL    [0/1]   -1  css/css-sizing/intrinsic-percent-replaced-008.html
- PASS => FAIL    [0/1]   -1  css/css-sizing/intrinsic-percent-replaced-dynamic-008.html
+ FAIL => FAIL   [7/15]   +5  css/css-tables/tentative/baseline-table.html
+ FAIL => PASS    [1/1]   +1  css/css-text-decor/text-decoration-inset-003.html
+ FAIL => PASS    [1/1]   +1  css/css-text-decor/text-decoration-inset-008.html
- PASS => FAIL    [0/1]   -1  css/css-text-decor/text-decoration-skip-spaces-001.html
- PASS => FAIL    [0/1]   -1  css/css-text/hanging-punctuation/hanging-punctuation-first-002.html
- PASS => FAIL    [0/1]   -1  css/css-text/line-break/line-break-anywhere-overrides-uax-behavior-004.html
- PASS => FAIL    [0/1]   -1  css/css-text/line-break/line-break-anywhere-overrides-uax-behavior-006.html
+ FAIL => PASS    [1/1]   +1  css/css-text/line-breaking/line-breaking-atomic-002.html
+ FAIL => PASS    [1/1]   +1  css/css-text/line-breaking/line-breaking-atomic-009.html
+ FAIL => PASS    [1/1]   +1  css/css-text/overflow-wrap/overflow-wrap-min-content-size-002.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-002.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-003.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-008.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-017.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-018.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-021.html
+ FAIL => PASS    [1/1]   +1  css/css-text/shaping/shaping-024.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-align/text-align-end-009.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-align/text-align-match-parent-05.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-encoding/shaping-join-003.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-encoding/shaping-tatweel-003.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-justify/text-justify-and-trailing-spaces-001.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-justify/text-justify-and-trailing-spaces-002.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-justify/text-justify-and-trailing-spaces-003.html
+ FAIL => PASS    [1/1]   +1  css/css-text/text-justify/text-justify-and-trailing-spaces-004.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/control-chars-00B.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/control-chars-085.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/full-width-leading-spaces-002.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/full-width-leading-spaces-003.html
- PASS => FAIL    [0/1]   -1  css/css-text/white-space/full-width-leading-spaces-004.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/full-width-leading-spaces-005.html
- PASS => FAIL    [0/1]   -1  css/css-text/white-space/hanging-whitespace-002.tentative.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/pre-wrap-014.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-006.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-007.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-009.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-012.html
- PASS => FAIL    [0/1]   -1  css/css-text/white-space/trailing-ideographic-space-013.html
- PASS => FAIL    [0/1]   -1  css/css-text/white-space/trailing-ideographic-space-014.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-017.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-019.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-020.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-022.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-023.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/trailing-ideographic-space-025.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/white-space-nowrap-011.html
+ FAIL => PASS    [1/1]   +1  css/css-text/white-space/white-space-pre-wrap-justify-003.html
- PASS => FAIL    [0/1]   -1  css/css-text/white-space/white-space-zero-fontsize-001.html
+ FAIL => PASS    [1/1]   +1  css/css-text/word-break/word-break-break-all-008.html
+ FAIL => PASS    [1/1]   +1  css/css-text/word-break/word-break-break-all-018.html
+ FAIL => PASS    [1/1]   +1  css/css-text/word-break/word-break-break-all-021.html
- FAIL => FAIL   [2/12]   -3  css/css-transforms/3d-point-mapping-2-transforminterop.html
+ FAIL => PASS    [1/1]   +1  css/css-ui/text-overflow-023.html
+ FAIL => PASS    [1/1]   +1  css/css-values/ch-unit-001.html
- FAIL => FAIL    [2/8]   -2  css/css-values/lh-rlh-on-root-001.html
+ FAIL => PASS    [1/1]   +1  css/css-values/lh-unit-001.html
+ FAIL => PASS    [1/1]   +1  css/css-values/lh-unit-002.html
- PASS => FAIL    [0/1]   -1  css/css-viewport/zoom/vertical-align.html
- PASS => FAIL    [0/1]   -1  css/css-writing-modes/abs-pos-with-replaced-child.html
+ FAIL => PASS    [1/1]   +1  css/css-writing-modes/baseline-with-orthogonal-flow-001.html
+ FAIL => PASS    [1/1]   +1  css/css-writing-modes/text-combine-upright-decorations-001.html
+ FAIL => PASS    [1/1]   +1  css/css-writing-modes/text-combine-upright-shadow.html
+ FAIL => FAIL   [5/11]   +2  css/cssom-view/elementFromPoint.html
+ FAIL => FAIL    [5/9]   +2  css/cssom-view/elementsFromPoint.html
+ FAIL => PASS    [1/1]   +1  css/filter-effects/effect-reference-after-001.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-021.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-022.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-024.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-025.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-026.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-027.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-028.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-029.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-030.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-031.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-032.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-034.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-035.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-036.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-041.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-042.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-044.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-045.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-046.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-047.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-048.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-049.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-050.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-051.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-052.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-054.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-055.html
+ FAIL => PASS    [1/1]   +1  css/selectors/i18n/css3-selectors-lang-056.html

Generated by the WPT workflow.

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/da7669e341814cc7976e4ebf32b7eacb
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/da7669e341814cc7976e4ebf32b7eacb?variant=devin-insiders
Requested by: @nicoburns

Map Stylo's alignment-baseline/baseline-shift longhands to Parley's new
VerticalAlign property on text styles and atomic inline boxes, report the
inline-block baseline (last line box, unless overflow is not visible) to
Parley, and drop the root line-height floor workaround now that Parley
adds the strut and ancestor inline boxes itself.
@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

Blitz real-page benchmark: Barack Obama Wikipedia article, 1280 px

Headless parse + style + layout of https://en.wikipedia.org/wiki/Barack_Obama in Blitz, comparing the Parley revision pinned by Blitz main, by Blitz PR #832 (Parley #18, vertical-align), and by #832 repinned to Parley PR #19 (cross-layout font cache). A fourth variant (Parley main without #18) was added after the first run to separate #18 from the rest of the Parley main churn.

Variants

Variant Blitz revision Parley Notes
A main @ 188486089089eaa47706383674be1fc6f7f0ce4d crates.io parley 0.11.1 (as pinned)
B PR #832 @ a875dd478708f4f92d33353a51d4f53ba8fbd1b7 git ff838004f1912240ec1bfe488e106bd2cb8a9c70 (Parley #18 head) as pinned by #832
C PR #832 @ a875dd47 + root Cargo.toml repinned git 5c31f79b4f30f7d29d2bf6d260890e9049d5f52e (Parley #19 head) also needed FontContext::from_parts in blitz-dom (2 struct literals) since #19 adds a private field
D (extra) main @ 18848608 + minimal API shims git 83acf8f9d4801154f440a860c15231f9748ed3f3 (Parley main, merge-base of #18) isolates "Parley main vs 0.11.1" from "#18"

Identical features for all (default features of the root blitz crate, --release), rustc 1.94.0, Intel Xeon Platinum 8559C, 8 vCPU, nothing else running. Blitz builds Parley single-threaded here (no parallel-construct), so one FontContext is shared by every inline layout in the document — the case #19's cache is meant for.

Method

mkdir -p ~/bench/obama && cd ~/bench/obama
curl -L -o obama.html https://en.wikipedia.org/wiki/Barack_Obama
# the two load.php stylesheets referenced by <link rel=stylesheet> were fetched to style0.css / style1.css
# and the <link href> attributes rewritten to those local names. No other network input.

# one git worktree per variant, scratch example copied in (not committed anywhere):
cp obama_bench.rs ~/repos/blitz-{A,B,C,D}/examples/
(cd ~/repos/blitz-$V && cargo build --release --example obama_bench)

# 3 interleaved rounds A B C D, then medians of the per-run medians:
for round in 1 2 3; do for v in A B C D; do
  /usr/bin/time -v ~/repos/blitz-$v/target/release/examples/obama_bench ~/bench/obama/obama.html 20 3
done; done

obama_bench.rs (source attached alongside this report):

  • HtmlDocument::from_html(html, DocumentConfig { base_url: file://…, net_provider: LocalFiles, viewport: 1280×800 @ scale 1, .. }), then resolve(0.0) twice (the NetProvider serves file:// synchronously, so the stylesheets are applied in the first resolve; non-file:// URLs — images, fonts — are dropped).
  • first layout = wall time of from_html + the two resolve() calls, i.e. parse + style + layout from scratch. Repeated 3 warm-ups + 20 measured builds (each build is a fresh document; the process-wide font collection / FontContext persists only inside one document, so this also measures cold-per-document behaviour).
  • relayout = wall time of resolve(0.0) after set_viewport() toggling the scale between 1.0 and 1.00001. In Blitz this invalidates every inline context (all text is re-shaped and re-laid-out) and re-runs style resolution; 3 warm-ups + 20 measured.
  • Median / p90 over the 20 iterations, then the median across the 3 rounds is reported.
  • Heap via a counting #[global_allocator] around System (live bytes after the first build, live bytes after the last relayout, peak live bytes). Peak RSS from /usr/bin/time -v.
  • Cache entry counts for C read through a scratch BaseDocument::font_ctx_cache_len() getter (built separately with --cfg font_cache, not part of the timed binary).

Sanity check that all variants do the same work: an instrumented build counted the items Blitz walks in compute_inline_layout_inner per document: A lines=120658 items=186955 glyphs=1025593, B lines=120808 items=188877 glyphs=1036132 (146k runs in both, same Σ glyphs² per run, i.e. same style fragmentation).

Results (median of 3 interleaved rounds; run 2, which includes D)

Variant first layout ms (median / p90) relayout ms (median / p90) peak RSS MB heap after 1st layout MB heap after last relayout MB peak heap MB cache entries (primary fonts, metrics)
A Blitz main, parley 0.11.1 269 / 282 147 / 148 172.8 77.4 77.5 157.3 n/a
B #832, parley #18 847 / 898 735 / 774 199.4 91.1 91.3 184.7 n/a
C #832, parley #19 1070 / 1149 959 / 1045 199.5 91.1 91.3 184.7 6 / 84
D main, parley main (no #18) 1050 / 1129 931 / 990 186.9 84.6 84.7 171.7 n/a

Per-round medians (first / relayout, ms) — run 2: A 282/158, 269/146, 265/147 · B 848/741, 847/735, 845/734 · C 1070/959, 1080/950, 1068/961 · D 1045/925, 1050/931, 1051/941. Run 1 (A B C only) gave the same picture: A 267/161, B 853/742, C 1088/952. Run-to-run noise is ~±3%.

Where the time goes (perf, cpu-clock, frame-pointer release builds, 4 builds + 4 relayouts per profile; ms = samples × 250 µs)

A B C D
total 1 670 5 906 7 206 7 188
parley::layout::line::GlyphRunIter::next (called from Blitz compute_inline_layout_inner's for item in line.items() loop) 216 3 826 4 929 4 980
of which libc memcpy (self) 2 093 3 286 3 427
build_inline_layout_into / parley::builder::build_into_layout (tree build + shaping + style metrics) 448 524 484 517
fontique::Query::matches_with (all callers) 104 102 21 97
parley::layout::style_metrics::resolve_style_metrics 16.5 6.5
harfrust … hb_font_t::shape (sanity, should be constant) 112 111 103 110

Interpretation

  1. The 3–4× regression from A to B/C/D is not Add patch for naga to fix macOS support #18 and not the cache; it is Parley main vs the 0.11.1 release, and it is entirely in GlyphRunIter::next. D (Blitz main code, only the Parley pin moved to Parley main) is as slow as C. Blitz's compute_inline_layout_inner iterates line.items() for every line to position inline boxes; GlyphRunIter::next re-walks run.visual_clusters().flat_map(|c| c.glyphs()).skip(glyph_start) from the start of the run for every glyph run (already O(glyphs × glyph-runs) in 0.11.1), and on Parley main each step now copies a large Cluster (Copy struct holding an Atom/ShapedSlice) — the rep movsb memcpy is 35–46% of the whole run. Same item/run/glyph counts in A and B, ~18× more time per item. This is worth fixing upstream (keep a cursor in GlyphRunIter instead of skip, and/or slim Cluster); it dwarfs everything else on this page.

  2. Is Add patch for naga to fix macOS support #18's regression visible on a real page? Barely. Comparing B with D (same Parley base, ± Add patch for naga to fix macOS support #18): the parley construction stage (build_inline_layout_into) is 524 vs 517 ms over 8 layouts (~+1%), resolve_style_metrics costs 16.5 ms per 8 layouts (~2 ms per full page), and heap grows 84.6 → 91.1 MB (+6.5 MB live, plausibly the per-layout style_metrics / per-run box_metrics data kept for every inline context) — that last part is the most tangible cost of Add patch for naga to fix macOS support #18 here. Wall-time-wise B is actually faster than D (847 vs 1050 ms), but see 4.

  3. Does the cache recover it? Yes for the part it targets. In C, fontique::Query::matches_with drops from 102 ms to 21 ms (−80%) and resolve_style_metrics from 16.5 to 6.5 ms per 8 layouts; total construction time 524 → 484 ms (−8%, i.e. ~5 ms per full-page layout). Only 6 primary-font entries and 84 metrics entries are needed for the whole article, so the cache's own footprint is negligible: heap after layout is identical to B to within 10 kB (91.08 vs 91.07 MB), peak RSS 199.5 vs 199.4 MB. Relative to A's absolute numbers (~270 ms/page) those 5 ms are ~2%; relative to the Tango micro-benchmarks they line up with the 5–13% Latin wins there.

  4. Unexplained: B is ~20% faster end-to-end than both C and D, and perf puts all of that difference in GlyphRunIter::next/memcpy (3 826 vs 4 929/4 980 ms), a code path neither add support for font-size #19 nor Blitz Implement vertical-align via Parley #832 touches (line.rs is byte-identical between B and C; Cluster/Run types are unchanged). Shaping and query time in C are lower than B, so this is not the cache being slow. The most likely explanation is codegen (inlining / copy-elision) differences in the flat_map().skip() chain between the three builds; I did not chase it further because the fix in point 1 makes the whole path irrelevant. Treat the B-vs-C wall-clock gap as noise from that path, and the stage-level numbers above as the signal.

Memory summary

A B C D
peak RSS 172.8 MB 199.4 MB 199.5 MB 186.9 MB
live heap after layout 77.5 MB 91.3 MB 91.3 MB 84.7 MB

Parley main vs 0.11.1: +7 MB; #18: +6.5 MB; #19 cache: +~10 kB (6 + 84 entries).

Caveats

  • Images / web fonts are not loaded (only the HTML and its two stylesheets), so text is laid out in the system fallback fonts available on the VM; font-face-heavy pages would exercise the metrics cache more (here only 84 (font, size) pairs).
  • The relayout path re-shapes all text (Blitz has no cheaper incremental relayout that keeps shaped text), so "steady-state relayout" ≈ "layout without parsing".
  • D required 5 one-line API shims in Blitz main to compile against Parley main (InlineBox::baseline, append_inline_box_to_line arity, Cluster::style_index, plus Implement vertical-align via Parley #832's blitz-paint/src/text.rs diff); none are on a timed path.
  • Nothing from this benchmark was committed to any PR branch; the worktrees/scratch example live only on the benchmark VM.
obama_bench.rs (scratch, not committed)
//! Scratch benchmark: parse + style + layout of a local HTML file with Blitz's headless path.
//!
//! Usage: obama_bench <path/to/page.html> [iterations] [warmups]
//! Outputs one JSON line with the timings.

use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Instant;

use blitz_dom::DocumentConfig;
use blitz_html::HtmlDocument;
use blitz_traits::net::{Bytes, NetHandler, NetProvider, Request};
use blitz_traits::shell::{ColorScheme, Viewport};

struct Counting;

static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for Counting {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let p = unsafe { System.alloc(layout) };
        if !p.is_null() {
            let cur = ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed) + layout.size();
            PEAK.fetch_max(cur, Ordering::Relaxed);
        }
        p
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        ALLOCATED.fetch_sub(layout.size(), Ordering::Relaxed);
        unsafe { System.dealloc(ptr, layout) }
    }
    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
        let p = unsafe { System.realloc(ptr, layout, new_size) };
        if !p.is_null() {
            if new_size >= layout.size() {
                let cur =
                    ALLOCATED.fetch_add(new_size - layout.size(), Ordering::Relaxed) + new_size
                        - layout.size();
                PEAK.fetch_max(cur, Ordering::Relaxed);
            } else {
                ALLOCATED.fetch_sub(layout.size() - new_size, Ordering::Relaxed);
            }
        }
        p
    }
}

#[global_allocator]
static GLOBAL: Counting = Counting;

/// Serves `file://` URLs synchronously from disk; ignores everything else (no network).
struct LocalFiles;

impl NetProvider for LocalFiles {
    fn fetch(&self, _doc_id: usize, request: Request, handler: Box<dyn NetHandler>) {
        if request.url.scheme() != "file" {
            return;
        }
        let Ok(path) = request.url.to_file_path() else {
            return;
        };
        let Ok(bytes) = std::fs::read(&path) else {
            eprintln!("missing local file: {}", path.display());
            return;
        };
        handler.bytes(request.url.to_string(), Bytes::from(bytes));
    }
}

const WIDTH: u32 = 1280;
const HEIGHT: u32 = 800;

fn viewport(scale: f32) -> Viewport {
    Viewport::new(WIDTH, HEIGHT, scale, ColorScheme::Light)
}

fn build(html: &str, base_url: &str, net: &Arc<LocalFiles>) -> HtmlDocument {
    let mut doc = HtmlDocument::from_html(
        html,
        DocumentConfig {
            base_url: Some(base_url.to_string()),
            net_provider: Some(Arc::clone(net) as _),
            viewport: Some(viewport(1.0)),
            ..Default::default()
        },
    );
    // Stylesheets are delivered synchronously by `LocalFiles`; two resolves make sure
    // any nested `@import`s delivered during the first are applied too.
    doc.resolve(0.0);
    doc.resolve(0.0);
    doc
}

fn stats(mut v: Vec<f64>) -> (f64, f64) {
    v.sort_by(|a, b| a.partial_cmp(b).unwrap());
    let median = v[v.len() / 2];
    let p90 = v[((v.len() as f64 * 0.9).ceil() as usize).min(v.len()) - 1];
    (median, p90)
}

fn main() {
    let path = std::env::args().nth(1).expect("html path");
    let iterations: usize = std::env::args()
        .nth(2)
        .and_then(|s| s.parse().ok())
        .unwrap_or(20);
    let warmups: usize = std::env::args()
        .nth(3)
        .and_then(|s| s.parse().ok())
        .unwrap_or(3);

    let path = std::fs::canonicalize(&path).unwrap();
    let html = std::fs::read_to_string(&path).unwrap();
    let base_url = format!("file://{}", path.display());
    let net = Arc::new(LocalFiles);

    let heap_before = ALLOCATED.load(Ordering::Relaxed);

    // (i) full parse + style + layout, repeated from scratch
    let mut full = Vec::new();
    let mut doc = None;
    let mut heap_after_first_layout = 0;
    let mut root_height = 0.0;
    for i in 0..warmups + iterations {
        let start = Instant::now();
        let d = build(&html, &base_url, &net);
        let elapsed = start.elapsed().as_secs_f64() * 1e3;
        if i == 0 {
            heap_after_first_layout = ALLOCATED.load(Ordering::Relaxed) - heap_before;
            root_height = d.as_ref().root_element().final_layout().size.height;
        }
        if i >= warmups {
            full.push(elapsed);
        }
        doc = Some(d);
    }
    let mut doc = doc.unwrap();
    let heap_after_layout = ALLOCATED.load(Ordering::Relaxed) - heap_before;

    // (ii) steady-state relayout: toggling the scale invalidates every inline (text)
    // layout, rebuilds the stylist device (restyle) and re-runs layout.
    let scales = [1.0_f32, 1.00001_f32];
    let mut relayout = Vec::new();
    for i in 0..warmups + iterations {
        doc.as_mut().set_viewport(viewport(scales[i % 2]));
        let start = Instant::now();
        doc.resolve(0.0);
        let elapsed = start.elapsed().as_secs_f64() * 1e3;
        if i >= warmups {
            relayout.push(elapsed);
        }
    }

    let (full_med, full_p90) = stats(full);
    let (re_med, re_p90) = stats(relayout);

    #[allow(unused_mut)]
    let mut cache = String::from("null");
    #[cfg(font_cache)]
    {
        let (p, m) = doc.as_ref().font_ctx_cache_len();
        cache = format!("[{p}, {m}]");
    }

    println!(
        "{{\"first_layout_ms\": {{\"median\": {full_med:.2}, \"p90\": {full_p90:.2}}}, \
         \"relayout_ms\": {{\"median\": {re_med:.2}, \"p90\": {re_p90:.2}}}, \
         \"heap_after_first_layout_mb\": {:.2}, \"heap_after_layout_mb\": {:.2}, \
         \"heap_peak_mb\": {:.2}, \"root_height\": {root_height:.1}, \"cache_entries\": {cache}}}",
        heap_after_first_layout as f64 / 1e6,
        heap_after_layout as f64 / 1e6,
        PEAK.load(Ordering::Relaxed) as f64 / 1e6,
    );
}

Written by Devin

@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

WPT follow-up for Blitz #832 / Parley #18 (vertical-align)

WPT revision a95401e4e06351eb1e15f0e15cf50abf08fa545f (from .github/workflows/wpt.yml), runner cargo run --release --package wpt, separate target dirs for main (parley 83acf8f) and #832 (parley ff838004). All numbers below are tests (not subtests) with status PASS / RUN (RUN = PASS+FAIL+CRASH, skipped tests excluded; 4615 found, 427 skipped in every run, 0 crashes).

PRs opened

Repo PR Base Content
parley DioxusLabs/parley#20 (head 1fd63fba72b38df9ec14892bd6f20e96fe2c1176) devin/1788383056-vertical-align trailing/consecutive hanging spaces no longer start an extra line; Style::parent() + Line::style_baseline() made public; 2 regression tests + 5 snapshots
blitz #833 devin/1788386630-vertical-align decorations painted at the decorating box's baseline (blitz-paint/src/text.rs::decorating_box_baseline); parley repinned to 1fd63fba72b38df9ec14892bd6f20e96fe2c1176

Verification: parley cargo fmt --all / cargo test --workspace (172 parley_tests + others, all ok) / cargo clippy --workspace --all-targets -D warnings clean. Blitz cargo fmt --check, cargo check --workspace --all-targets, cargo clippy -p blitz-dom -D warnings clean. (cargo clippy -p blitz-paint -D warnings fails on a pre-existing needless_return at packages/blitz-dom/src/mutator.rs:1250, untouched by these PRs and also present on the #832 head.)

Per-directory pass counts

Directory main #832 #832 + fixes (#833)
css/CSS2/linebox 137/201 169/201 169/201
css/CSS2/text 268/410 270/410 271/410
css/css-text 982/1872 1006/1872 1021/1872
css/css-inline 47/234 49/234 49/234
css/CSS2/visudet 15/39 17/39 17/39
css/CSS2/normal-flow 648/772 648/772 648/772
css/CSS2/css1 94/164 116/164 116/164
css/css-values 229/496 232/496 232/496
total 2420/4188 2507/4188 2523/4188

#832#832+fixes: 19 tests FAIL→PASS, 3 PASS→FAIL (all three are white-space: break-spaces tests, see rows below — they passed on #832 only because of the wrong break-after-first-hanging-space behaviour, since Blitz maps break-spaces to pre-wrap; stylo_to_parley.rs:291).

Newly passing: CSS2/text/text-decoration-va-length-001.xht, css-text/white-space/pre-wrap-00{1..7}.html, pre-wrap-leading-spaces-015/017, white-space-pre-wrap-trailing-spaces-005/007/008/011/021, hanging-whitespace-004, trailing-space-align-start.tentative, line-break-anywhere-and-white-space-006, hyphens-auto-002.

Root causes fixed

  1. (a) Parley – hanging spaces (parley/src/layout/line_break.rs:1060-1085, the is_space && Wrap overflow branch). After appending an overflowing space the breaker always called start_new_line. Effects: "AAA " at end of layout → a second, empty line (every block ending in a space got a phantom line box; layout.height() doubled); "XXXX XX" in 4ch broke after the first space instead of hanging both. Fix: continue when the next atom is another U+0020, or when this is the last atom of the last item. NBSP deliberately not chained (Whitespace::Space only; a broader variant chaining every space-class regressed white-space-intrinsic-size-001). Tests: trailing_whitespace_does_not_add_line, vertical_align_style_baseline.
  2. (b) Blitz – decoration baseline (packages/blitz-paint/src/text.rs, stroke_text). Decorations were positioned at each glyph run's shifted baseline; CSS 2 §16.3.1 positions them from the decorating box. Fix walks Style::parent() from the run's style to the style whose brush is the decorating node and uses Line::style_baseline. Fixes text-decoration-va-length-001.

Investigated failing tests

Status columns: main → #832#832+fixes.

Test Class Diagnosis Fix / reason not fixed Status
css/CSS2/text/text-decoration-va-length-001.xht b underline of <u> followed the vertical-align: <length> child's shifted glyph baseline (blitz-paint/src/text.rs, DecorationRunGeometry.baseline = glyph_run.baseline()) blitz#833 F→F→P
css/css-text/white-space/pre-wrap-001…007.html, white-space-pre-wrap-trailing-spaces-005/007/008/011/021, pre-wrap-leading-spaces-015/017, hanging-whitespace-004, trailing-space-align-start.tentative, CSS2/text/white-space-processing-002/003/008/009/010 (the last five only pass with line-height: normal → metrics, see question 1) a second and following hanging spaces were pushed to a new line; trailing space at end of content created an empty line box (line_break.rs:1060) parley#20 F→F→P (the white-space-processing-* five: F→F→F)
css/css-text/line-break/line-break-anywhere-and-white-space-006.html, css/css-text/hyphens/hyphens-auto-002.html a same phantom trailing line parley#20 F→F→P
css/css-text/white-space/break-spaces-001.html, break-spaces-010.html, white-space-intrinsic-size-001.html c white-space: break-spaces is mapped to Preserve/pre-wrap (stylo_to_parley.rs:289-291 TODO) so spaces hang instead of wrapping and don't count toward min-content; passed on #832 only via the bug fixed above needs a break-spaces mode in Parley (WhiteSpaceCollapse::BreakSpaces: spaces are break opportunities and never hang) P→P→F
css/CSS2/visudet/line-height-204.html d (+ design question) Test sets line-height on one div to getComputedStyle(other).height and requires equal baselines. Font Revalia.woff has positive hhea.descender = 382 (non-standard sign), which skrifa/parley read as descent −18.65px at 100px (ascent 104.8). Blitz maps line-height: normal to 1.2 × font-size = 120px (stylo_to_parley.rs:354), so the strut baseline is at 105+floor((120−86)/2)=122; the 20px inline-block sitting on the baseline extends the line box to 122px, the script then sets line-height: 122px → half-leading 18 → baseline 123 → 1px shift. Browsers compute normal from font metrics (ascent+descent+lineGap = 86px here), which the test relies on. not fixed: changing normalLineHeight::MetricsRelative(1.0) was tried; net +2 (+8/−6: fixes white-space-processing-002/003/008/009/010, pre-wrap-008/009, block-in-inline-first-line-001; regresses inline-formatting-context-012, block-formatting-contexts-011, inline-block-width-002a/b, overflow-wrap-cluster-001/002) and doesn't fix this test either; it is a design decision for #832 owner (question 1) P→F→F
css/CSS2/linebox/baseline-block-with-overflow-001.html b (undiagnosed detail) inline-block whose only child is an `overflow: hidden scroll autoblock. Blitz uses taffy'sbaselines.last.or(first) (layout/inline.rs:~660); taffy 0.14 compute_block_layoutsynthesizes a baseline from a scroll-container child's border-box bottom, so the inline-block gets a baseline where CSS 2 §10.8.1 says "bottom margin edge" *only for the inline-block itself*. Pixel diff: first XX groups aligned, later groups off. Suppressing baselines ofoverflow != visibleblock containers inlayout/mod.rs` compiled but did not make the test pass, so the wrong detail is not yet isolated (could also be the inline-block's own margin handling).
css/CSS2/linebox/vertical-align-negative-leading-001.html a (undiagnosed detail) negative half-leading (line-height < content area); line-box blue spans matched the ref at sampled columns but glyph/inline-box y differs, so the strut is right and the shift/quantisation of the child is off. Not narrowed to a line. not fixed F→F→F
css/CSS2/linebox/empty-inline-002/003.xht, inline-box-001/002.xht, border-padding-bleed-001…003.xht, split-inline-borders.html, inline-formatting-context-002…007/022/023.xht c need inline border/padding/margin boxes on non-atomic inlines (border: 25px solid on <span>, etc.); Blitz has no inline fragment boxes missing feature F→F→F
css/CSS2/linebox/vertical-align-baseline-003.xht, -004a.xht, -005a.xht, -006.xht b (not individually diagnosed) all are inline-block baseline = last line box, with margins (-004a: margin-bottom: 99px, margin: 8px); the inline-block baseline path is inline.rs ibox.baseline = (margin.top + baseline) * scale; source read, screenshots not compared not fixed F→F→F
css/CSS2/linebox/vertical-align-baseline-009.xht, vertical-align-applies-to-007/015.xht, line-height-applies-to-015.xht c display: table-caption / table-cell vertical-align and table layout tables unsupported F→F→F
css/CSS2/linebox/vertical-align-117a/118a.xht, -121.xht b/a (not individually diagnosed) text-bottom with margin: auto -1em on inline-blocks (117a/118a); top/bottom inline-blocks with border-bottom (121); source read only not fixed F→F→F
css/CSS2/linebox/line-height-125.xht c padding-top on an inline <fontsize> element (inline padding box) missing feature F→F→F
css/CSS2/linebox/inline-negative-margin-001.html b (not diagnosed) inline-block with margin-left: 10ch not fixed F→F→F
css/CSS2/visudet/inline-block-baseline-001…006/011/014/016 b (not individually diagnosed) inline-block baseline family; also fail on main not fixed F→F→F
css/CSS2/visudet/line-height-205.html d same Revalia / line-height: normal issue as -204 see question 1 F→F→F
css/CSS2/visudet/content-height-004/005.html, replaced-elements-*.html c replaced-element sizing, not line-box geometry out of scope F→F→F
css/css-inline/alignment-baseline/alignment-baseline-horizontal-002/003.html, alignment-baseline-nested-001/002.html c need central/ideographic/mathematical/alphabetic/hanging baseline tables (BASE table / synthesized) — only `baseline middle text-top
css/css-inline/alignment-baseline/*-vertical-rl-*, dominant-baseline/dominant-baseline-mixed-writing-modes-002.html c vertical writing modes out of scope (the latter's main PASS was a false positive: unsupported writing-mode rendering happened to match) P→F→F / F→F→F
css/css-inline/baseline-shift/baseline-shift-sub-super.html, baseline-shift-length-percentage.html d / c sub/super use WebKit's font-size/5, /3; the ref is built with explicit lengths matching the reference browser's constants. Percentage test also uses baseline-source. A spec-neutral alternative would be the font's OS/2 subscriptYOffset/superscriptYOffset, which is what css-inline-3 suggests when available not changed (constants are a design choice, see question 2) F→F→F
css/css-inline/baseline-shift/*-svg.html, baseline-shift-top/bottom/center-svg c SVG text out of scope F→F→F
css/css-inline/empty-span-size-001/002.html c empty <span> with padding/border (inline padding box); 002's main PASS was accidental (no vertical shift applied to the empty box) missing feature F→F→F / P→F→F
css/css-values/lh-rlh-on-root-001.html, lh-unit-003/004.html c lh/rlh units with line-height: normal need the metrics-based normal (Stylo resolves lh from the computed value; normal has none) see question 1 F→F→F
css/CSS2/normal-flow/inline-block-zorder-002/004.xht b (undiagnosed) overlapping red/green inline-blocks; looked at paint order, no explanation found not fixed F→F→F

Not investigated: failures whose feature flags/contents are floats, direction/writing-mode, calc, script-driven, text-box-trim, ruby, initial-letter, hanging-punctuation, text-wrap balance/pretty, hyphenation, text-autospace, letter-spacing, text-align/justify.

Questions for the #832 design

  1. line-height: normal = 1.2 × font-size (stylo_to_parley.rs:330,354, kept from main). Browsers use ascent+descent+lineGap of the primary font. Switching to parley::LineHeight::MetricsRelative(1.0) is net +2 on the eight directories (list above) and is what line-height-204/205, lh-unit-003/004, white-space-processing-* assume, but it changes the height of every block of text in Blitz. Not done here; flagged.
  2. sub/super constants (style_metrics.rs:255-258): WebKit/Blink constants chosen; Firefox uses the font's OS/2 sub/superscript offsets. baseline-shift-sub-super.html cannot pass with either unless the reference browser's choice is matched.
  3. text-top/text-bottom use the parent's font ascent/descent (content area) — matches spec; no test was found failing because of this.

Not fixed but in scope (honest gaps)

baseline-block-with-overflow-001, vertical-align-negative-leading-001, the inline-block baseline families (vertical-align-baseline-003…006, visudet/inline-block-baseline-*, vertical-align-117a/118a/121, inline-negative-margin-001), and inline-block-zorder-002/004 were opened and screenshot-compared (or source-read as marked) but the wrong line was not isolated within this session; they were not changed.

@staging-devin-ai-integration

Copy link
Copy Markdown
Contributor

Blitz #832 (vertical-align) — WPT regression triage

Scope: the 51 tests listed as regressed in the full-suite WPT diff of #832 (head a875dd47, parley ff838004).

Method

Three local builds, separate target dirs, WPT at CI's pinned revision a95401e4:

build what purpose
main Blitz main, parley 0.11.1 (crates.io, 92b63a2) baseline
main+parley-main Blitz main + parley main (83acf8f, the merge-base of parley#18) with API-only shims isolates the dependency bump from the vertical-align logic
#832 Blitz #832 head a875dd47 reproduce

All 51 regressions reproduce locally on #832 vs main (statuses in the table below are local; the "fixed" column is the head of blitz#834 + parley#22).

Key finding: 29 of the 51 also fail on main+parley-main, i.e. they are caused by the 70-commit parley bump (0.11.1 → 83acf8f: #734 NBSP preservation, #738 spacing model, #697 CSS line-box sizing, #743 negative half-leading, …) and not by parley#18 / blitz#832's vertical-align code. The remaining 22 are attributable to the vertical-align branches; of those, 9 were genuine bugs and are fixed, 13 are pre-existing gaps that the old always-tall strut / ignored-inline-box-baseline behaviour masked.

PRs opened

Validation: parley cargo fmt/test/clippy -D warnings clean; blitz cargo check --workspace --all-targets, cargo clippy -p blitz-dom -D warnings, cargo fmt --check clean. Full css/ WPT run of the fixed head vs pristine #832: 18 FAIL→PASS, 1 PASS→FAIL (css/css-contain/contain-size-064.html, see "Exposed gap" below), no subtest regressions. The 8 non-listed improvements (absolute-non-replaced-max-height-002, multi-line-row-flex-fragmentation-081a–d-print, svg-g-no-size-container, flex-direction-row-reverse, flexbox-align-self-vert-003) all come from the negative-margin / quantization fixes to add_inline_box.

Shared root causes (clusters)

cluster shared cause class
NBSP (zorder-002/004/005, inline-table-zorder-001, t41-html4-keywords-a, contain-animation-001, highlight-001/002, empty-span-size-002) parley #734 stopped collapsing U+00A0 but is_space_or_nbsp() still classified it as trailing/hangable space in calculate_content_widths (parley/src/layout/data.rs:373), so &nbsp;-only shrink-to-fit boxes had 0 min/max-content width. On main old parley collapsed NBSP to empty text, which sent Blitz down the collapse-through path (blitz-dom/src/layout/inline.rs:192-240) producing 0×0 boxes — the "passing" tests were test==ref both blank. a (width) / c (what the width now exposes)
Atomic-inline baseline export (baseline-of-scrollable-1a/1b, single-axis, contain-layout-baseline-001/005, grid-baseline-, grid-inline-, list-and-block-in-inline, font-family-applies-to-005, flexbox-baseline-multi-line-vert-002) #832 started honouring InlineBox::baseline from taffy. Blitz's own mapping (inline.rs:300-360) was wrong for scroll containers / contain:layout (fixed, b). The rest are taffy 0.14 limits: block containers only export a first baseline (taffy/src/compute/block.rs:712 Baselines::from_first) and child scroll-container baselines are clamped, not synthesized (block.rs:1501); grid only measures item baselines when ≥2 items baseline-align (grid/track_sizing.rs:496-530). On main the baseline was ignored (box bottom sat on the baseline), which happened to coincide with the expected result. b + c
Root strut now always present (contain-size-flexbox-002, intrinsic-percent-replaced-008/dynamic-008, abs-pos-with-replaced-child, ruby-overhang-spaces-022) Old inline layout dropped the strut on lines containing only inline boxes; #18 applies CSS2 §10.8.1 strut (style-table entry 0) correctly. Tests that relied on the missing strut (vertical writing modes, scrollbars, ruby) now expose the actual unsupported feature. c
Ruby (ruby-003, br-clear-all-000, empty-ruby-, ruby-base-container-) display: ruby* is not implemented — construct.rs has no DisplayInside::Ruby* arm so <ruby>/<rt> fall through to inline flow (construct.rs:599), with rt { font-size:50%; line-height:1 } from default.css:1009-1015. Bump-attributable: parley #697 CSS line-box sizing made <rt> text contribute its own inline box metrics; on main per-inline metrics were ignored. c
White-space / hanging (hanging-punctuation-first-002, full-width-leading-spaces-004, hanging-whitespace-002, trailing-ideographic-space-013/014, text-decoration-skip-spaces-001) U+3000 and other Zs are Whitespace::None (parley_engine/src/shape/data.rs:239-250); hanging glyphs / hanging-punctuation are unimplemented. Bump-attributable (#738 spacing model stops mutating advances; these previously passed through accidental 0-advance handling). c
Floats (floats-141 fixed; floats-149) floats-141: add_inline_box treated a finite negative-height box (negative margins) as no-content (a). floats-149: parley #697 line box sizing + float placement of an empty float in an inline with margin-left; not VA. a / c
lh/rlh, transforms, small-caps, line-height-204 bump-attributable, see rows. c

Per-test table

Status columns: main / #832 (a875dd47) / fixed (blitz#834 head); x/y = passing subtests.

# test class root cause fix main → #832 → fixed
1 css/CSS2/floats-clear/floats-141.xht a Two parley#18 bugs in LineBoxMetrics::add_inline_box (parley/src/layout/line_break.rs:296): (1) a box whose ascent+descent <= 0 (negative margin-top atomic inline) was treated as no content → strut dropped, line collapsed; (2) with quantize, ascent and descent were rounded separately, reserving 1px more than the box's own (integral) height when the baseline split it into .5 halves. parley#22 PASS → FAIL → PASS
2 css/CSS2/floats-clear/floats-149.xht c Fails on main+parley-main (not VA). Empty float:left inside an inline with margin-left:40px gets placed on line 1 after parley #697 line-box sizing changed where the float anchor is measured (inline.rs float placement via break_remaining). Old behaviour passed by accident. won't fix: parley bump / float placement, unrelated to vertical-align PASS → FAIL → FAIL
3 css/CSS2/fonts/font-family-applies-to-005.xht c Inline-block with 3 block lines: taffy 0.14 block.rs:1493-1513 only computes Baselines::first, last is None, so inline.rs:337-350 falls back to the first-line baseline (14px) instead of the last (52.4px, CSS2 §10.8.1) → line grows to 59px and box shifts 1–2px. Main ignored the baseline (bottom edge on baseline = same as last-line for this test). Verified with dbg: first=Some(14), last=None. Not a font-fallback issue. won't fix: needs taffy last-baseline for block containers PASS → FAIL → FAIL
4 css/CSS2/linebox/baseline-block-with-overflow-001.html c Confirmed #832's explanation: inline-block with overflow:hidden on an inner block child; taffy block.rs:1501 clamps the child scroll-container baseline instead of synthesizing from its bottom margin edge (css-align-3 §9.1). Main passed because the inline-block's baseline was ignored. won't fix: taffy PASS → FAIL → FAIL
5 css/CSS2/normal-flow/inline-block-zorder-002.xht c NBSP cluster. &nbsp;-only positioned block was 0×0 on main (collapse-through, inline.rs:192-240) so test and ref were both blank. Now it has size and paints in tree order after the inline-block: blitz-paint/src/render.rs:965 draw_children has no CSS2 Appendix E phase separation (in-flow block backgrounds must paint before atomic-inline content). won't fix: pre-existing paint-order gap PASS → FAIL → FAIL
6 css/CSS2/normal-flow/inline-block-zorder-004.xht a NBSP cluster: calculate_content_widths (data.rs:373) trimmed NBSP as trailing whitespace → inline-block collapsed to 0 width. parley#22 PASS → FAIL → PASS
7 css/CSS2/normal-flow/inline-block-zorder-005.xht a same as #6 parley#22 PASS → FAIL → PASS
8 css/CSS2/normal-flow/inline-table-zorder-001.xht c same mechanism as #5 (inline-table instead of inline-block); Appendix E ordering. won't fix: paint order PASS → FAIL → FAIL
9 css/CSS2/text/text-decoration-va-length-001.xht b blitz-paint/src/text.rs flush_line_decorations (≈:395): a decorating inline root with no run of its own fell back to the first descendant run's geometry, including its vertical-align-shifted baseline, so the underline moved with the shifted child. Now uses the line baseline for the root. (#832 called this pre-existing; it is a fixable painting bug.) blitz#834 PASS → FAIL → PASS
10 css/CSS2/visudet/line-height-204.html c Fails on main+parley-main. Revalia.woff has a positive hhea.descender; parley_engine FontMetrics::from_skrifa negates it → descent −18.65. parley #743 (preserve negative half-leading) makes the strut over=122/under=−2, so used height ≠ line-height: normal; the test's explicit 122px then puts the baseline 1px lower. Not #18. won't fix: font-metric sign handling in parley_engine (parley bump) PASS → FAIL → FAIL
11 css/css-align/baseline-of-scrollable-1a.html b inline.rs:300-350 exported the content baseline of an overflow:hidden inline-block. css-align-3 §9.1: a block-axis scroll container's baseline is its bottom margin edge. Now is_block_axis_scroll_container ⇒ exports_baseline = false (bottom-edge fallback). blitz#834 PASS → FAIL → PASS
12 css/css-align/baseline-of-scrollable-1b.html c Scroll container is a child block of the inline-block; taffy block.rs:1501 clamps its baseline to the border box instead of synthesizing from the bottom margin edge. Main passed because inline-block baseline was ignored. won't fix: taffy PASS → FAIL → FAIL
13 css/css-align/baseline-of-single-axis-scroll-container.html c overflow-y: clip; overflow-x: scroll is fixed up by stylo to overflow-y: hidden (css-overflow-3 §3.1 computed-value rule), so inline.rs:325 sees a two-axis scroll container and synthesizes the bottom-edge baseline; Chrome treats the clip axis as non-scrolling. Needs the pre-fixup value from stylo. won't fix: needs stylo-level info 1/2 → 0/2 → 0/2
14 css/css-break/ruby-003.html c Ruby cluster; fails on main+parley-main. <rt> laid out as inline text with own metrics (parley #697) makes the ruby line 2 lines tall; multicol + ruby unsupported. won't fix: ruby unimplemented PASS → FAIL → FAIL
15 css/css-color/t41-html4-keywords-a.xht a NBSP cluster: swatches are &nbsp;-only inline-blocks that collapsed to 0 width. Geometry, not colour parsing. parley#22 PASS → FAIL → PASS
16 css/css-contain/contain-animation-001.html c contain: size unimplemented (no Contain::SIZE handling in blitz-dom/taffy). Test passed on main only because the &nbsp; child was 0×0, giving the 100×100 the ref expects by accident. won't fix: contain:size unsupported PASS → FAIL → FAIL
17 css/css-contain/contain-layout-baseline-001.html b contain: layout suppresses baseline export (css-contain-1 §3.2); inline.rs exported the content baseline. Now contain_layout ⇒ exports_baseline = false. blitz#834 PASS → FAIL → PASS
18 css/css-contain/contain-layout-baseline-005.html b same as #17 (fieldset variant). blitz#834 PASS → FAIL → PASS
19 css/css-contain/contain-size-flexbox-002.html d/c Chrome/Firefox pass only because overflow:scroll adds a ≈15px scrollbar making both the flex item and the inline-block ≥16px tall. Blitz has 0-width scrollbars, so flex row = 2px vs inline-block row = 16px (strut). Passed on main because the old inline layout omitted the strut on inline-box-only lines (a bug). won't fix: depends on scrollbar geometry PASS → FAIL → FAIL
20 css/css-flexbox/flexbox-baseline-multi-line-vert-002.html c Test side is correct (first baseline 13). The ref is misrendered: .flexContainer > * { display:inline-block; width/height:20px } matches <br>, and construct.rs:981/1192 only special-cases <br> when its display is inline, so the <br> becomes a 20×20 atomic inline and a 2nd line. Masked on main because inline-box baselines were ignored. won't fix: <br> with non-inline display (pre-existing) PASS → FAIL → FAIL
21 css/css-flexbox/flexbox_inline.html b Atomic inline with negative vertical margins: inline.rs:357 clamped the reserved height to ≥0 and inline.rs:875 positioned with margin.top.max(0), so the box no longer sat on the baseline that parley#18 now honours. Baseline-bearing boxes now use the (possibly negative) margin box. blitz#834 PASS → FAIL → PASS
22 css/css-fonts/small-caps-letter-spacing-002.html c Fails on main+parley-main; unrelated to vertical-align. Most likely parley #738 (spacing model) / #731 (ligature suppression with letter-spacing) changed spacing of synthesized small-caps; not narrowed further. won't fix: parley bump, not investigated beyond attribution PASS → FAIL → FAIL
23 css/css-grid/alignment/grid-baseline-001.html c Baseline cluster: taffy 0.14 grid only measures item baselines when ≥2 items participate in baseline alignment (grid/track_sizing.rs:496-530), else container baseline = first item's bottom (grid/mod.rs:853). Now that the inline-grid's baseline is used by parley, the wrong one shows. won't fix: taffy PASS → FAIL → FAIL
24 css/css-grid/alignment/grid-baseline-002.html c same as #23 won't fix: taffy PASS → FAIL → FAIL
25 css/css-grid/alignment/grid-inline-baseline.html c same as #23 won't fix: taffy PASS → FAIL → FAIL
26 css/css-grid/grid-items/grid-inline-items-002.html c same as #23 won't fix: taffy PASS → FAIL → FAIL
27 css/css-highlight-api/painting/custom-highlight-painting-inheritance-001.html c Highlight API painting unimplemented. Test & ref both contain &nbsp;-only spans which were 0-width on main, so neither showed green → accidental match. won't fix: Highlight API unsupported PASS → FAIL → FAIL
28 css/css-highlight-api/painting/custom-highlight-painting-inheritance-002.html c same as #27 won't fix PASS → FAIL → FAIL
29 css/css-inline/dominant-baseline/dominant-baseline-mixed-writing-modes-002.html c Confirmed #832's explanation: ref uses alignment-baseline: text-top (now mapped in stylo_to_parley.rs:301-303) while the test uses dominant-baseline (unsupported) plus vertical writing modes (unsupported). won't fix PASS → FAIL → FAIL
30 css/css-inline/empty-span-size-002.html b Parley (since the bump, incl. #18) lays out empty text as one strut-height line; Blitz only short-circuited empty content when the block had no border/padding (inline.rs:192-240), so a bordered block with only empty spans got a 19px line. Fix: has_inline_content == false ⇒ height 0, no baselines (CSS2 §9.4.2). (#832 called this pre-existing; it is fixable in Blitz.) blitz#834 PASS → FAIL → PASS
31 css/css-lists/list-and-block-in-inline.html c Same as #3: taffy block containers export only the first baseline (block.rs:712); an inline-block's baseline is its last line box. Main matched by accident (bottom edge). won't fix: taffy last-baseline PASS → FAIL → FAIL
32 css/css-ruby/br-clear-all-000.html c Ruby cluster (bump). <rt> as inline text with own line-height contributes extra height; clear + ruby annotation overflow into padding not modelled. won't fix: ruby unimplemented 1/1 → 0/1 → 0/1
33 css/css-ruby/empty-ruby-base-container.html c Ruby cluster (bump): a<ruby><rt>b</rt></ruby>c with line-height:3<rt> (50% font, line-height:1) is an inline with its own metrics, ref has no annotation. won't fix PASS → FAIL → FAIL
34 css/css-ruby/empty-ruby-text-container-abs.html c Ruby cluster (bump), as #33. won't fix PASS → FAIL → FAIL
35 css/css-ruby/empty-ruby-text-container-float.html c Ruby cluster (bump), as #33. won't fix PASS → FAIL → FAIL
36 css/css-ruby/ruby-base-container-abs.html c Ruby cluster (bump), as #33 (+ NBSP now has width). won't fix PASS → FAIL → FAIL
37 css/css-ruby/ruby-base-container-float.html c Ruby cluster (bump), as #33. won't fix PASS → FAIL → FAIL
38 css/css-ruby/ruby-overhang-spaces-022.html c Ruby unsupported: <rt> laid out as inline text contributes its own inline-box metrics to the line (25px vs 24px in ref) now that #18 honours per-inline metrics. won't fix PASS → FAIL → FAIL
39 css/css-sizing/intrinsic-percent-replaced-008.html c Vertical writing modes unsupported. Abs-pos block containing only a canvas: with a proper root strut the line is canvas + strut descent (104px) instead of 100. Chrome gets 100 because in vertical-lr the strut extends the inline size. Main passed because the strut was dropped on inline-box-only lines. won't fix: vertical writing modes PASS → FAIL → FAIL
40 css/css-sizing/intrinsic-percent-replaced-dynamic-008.html c same as #39 won't fix PASS → FAIL → FAIL
41 css/css-text-decor/text-decoration-skip-spaces-001.html c White-space cluster (bump): U+2000–U+200A/U+205F/U+3000/U+1680 are Whitespace::None (parley_engine/src/shape/data.rs:239-250), so they neither hang nor are skipped by the underline; text-decoration-skip not implemented. won't fix PASS → FAIL → FAIL
42 css/css-text/hanging-punctuation/hanging-punctuation-first-002.html c hanging-punctuation unimplemented; U+3000 is Whitespace::None. Bump-attributable. won't fix PASS → FAIL → FAIL
43 css/css-text/white-space/full-width-leading-spaces-004.html c U+3000 not treated as hangable space for min-content sizing (data.rs:373 only knows Space/NBSP). Bump-attributable. won't fix PASS → FAIL → FAIL
44 css/css-text/white-space/hanging-whitespace-002.tentative.html c pre-wrap spaces followed by an unconditionally-hanging U+3000 must hang; U+3000 isn't a hanging glyph in parley. Bump-attributable. won't fix PASS → FAIL → FAIL
45 css/css-text/white-space/trailing-ideographic-space-013.html c Trailing U+3000 must hang; Whitespace::None. Bump-attributable. won't fix PASS → FAIL → FAIL
46 css/css-text/white-space/trailing-ideographic-space-014.html c same as #45 won't fix PASS → FAIL → FAIL
47 css/css-text/white-space/white-space-zero-fontsize-001.html c Parley emits a phantom line for a block-final \n (line_break.rs pushes an empty run for the trailing newline; the height correction never fires because item_range is non-empty — same on parley main). Browsers create no line box for a block-final forced break. On main both test (2 lines) and ref (3 lines) were 45px due to the old copy-previous-line hack; #18 (1c93d69) correctly sizes the phantom line by the newline's font-size:0 style so the test is 31px (= browsers) but the ref is still 45px. won't fix here: parley block-final-newline line box (pre-existing) PASS → FAIL → FAIL
48 css/css-transforms/3d-point-mapping-2-transforminterop.html c 5/12 → 2/12 on main+parley-main too: elementFromPoint through 3D transforms; the 3 lost subtests depend on text line geometry changed by parley #697/#743, not on vertical-align (stale-baseline hit-testing ruled out: identical on #832 and fixed head). won't fix: transform hit-testing (bump) 5/12 → 2/12 → 2/12
49 css/css-values/lh-rlh-on-root-001.html c 4/8 → 2/8 on main+parley-main too. lh/rlh on the root resolve against the root's line-height: normal, whose value changed with parley #743 (negative half-leading preserved). Not #18. won't fix: parley bump / root lh resolution 4/8 → 2/8 → 2/8
50 css/css-viewport/zoom/vertical-align.html c stylo 0.20: inherit on a reset zoom-dependent property copies the parent's computed value verbatim (properties.mako.rs inherit_*; cascade.rs:1134-1141 only rescales inherited props). Firefox fails this test too (wpt.fyi). Masked on main because vertical-align lengths weren't implemented at all. won't fix: stylo PASS → FAIL → FAIL
51 css/css-writing-modes/abs-pos-with-replaced-child.html c same mechanism as #39 (vertical writing mode; root strut adds descent under the replaced element). won't fix PASS → FAIL → FAIL

Totals: a 4 (floats-141, zorder-004, zorder-005, t41) · b 6 (text-decoration-va-length-001, baseline-of-scrollable-1a, contain-layout-baseline-001/005, flexbox_inline, empty-span-size-002) · c 40 · d 1 (contain-size-flexbox-002, also c). 10 of 51 fixed; 29 of the remaining 41 are the parley dependency bump rather than #832/#18.

Exposed gap introduced by the fixes

css/css-contain/contain-size-064.html PASS → FAIL on the fixed head. contain: size is unimplemented; the test passed only because its &nbsp;-only grid cells had zero min-content width on both test and ref. With the (spec-correct) NBSP width fix the ref shows the green cells the test can't produce. Same mechanism as #16 (contain-animation-001). Flagging rather than reverting: the NBSP fix repairs 3 listed tests + 8 others.

Things deliberately not changed / questions

  • Line-break-time NBSP handling (parley/src/layout/line_break.rs:944 is_space_or_nbsp()) was left as-is. Changing it to == Space makes 8 more tests pass (letter-spacing-justify-001, text-justify-and-trailing-spaces-001…004, white-space-nowrap-011, word-break-break-all-018/021) but regresses line-break-anywhere-overrides-uax-behavior-004/006. Worth a separate parley PR with an investigation of the line-break: anywhere interaction.
  • No vertical-align semantics were changed (root strut = style entry 0, parent-relative alignment, compound VerticalAlign { alignment, shift } all intact). The quantize parameter of LineBoxMetrics::add_inline_box is now unused (kept to avoid changing append_inline_box_to_line's public signature, which parley_tests/tests/floats.rs:183 calls) — question: should the public quantize arg be dropped in a follow-up?
  • Implement vertical-align via Parley #832's description explanations: baseline-block-with-overflow-001 and dominant-baseline-mixed-writing-modes-002 are confirmed pre-existing gaps; text-decoration-va-length-001 and empty-span-size-002 were actually fixable in Blitz and are fixed in Fix atomic-inline baseline export, empty line boxes and root decoration baseline #834.

…seline

- Inline-blocks that are block-axis scroll containers export no baseline
  (bottom margin edge is used); other atomic inlines (flex/grid/table)
  keep their content baseline, clamped to the border box when scrollable.
  contain: layout boxes export no baseline.
- Atomic inlines with a baseline may reserve negative space in the line
  (negative vertical margins) and are positioned from that baseline.
- An inline root with no text and no inline boxes is a zero-height line
  box and exports no baselines.
- Text decorations on the inline root fall back to the line baseline
  rather than the (vertical-align shifted) first run's baseline.
- Repin parley to ac4c61c33c5bbc63303570b336b4e27cfa0cdd83.
staging-devin-ai-integration Bot pushed a commit that referenced this pull request Sep 3, 2026
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