@@ -455,6 +455,11 @@ void LineBuilder::update_last_line()
455455
456456 Optional<CSSPixels> first_unshifted_text_baseline;
457457
458+ struct InlineBoxAlignment {
459+ NodeWithStyleAndBoxModelMetrics const * box { nullptr };
460+ CSSPixels vertical_shift { 0 };
461+ };
462+
458463 for (auto & fragment : line_box.fragments ()) {
459464 CSSPixels new_fragment_inline_offset = inline_offset + fragment.inline_offset ();
460465
@@ -474,31 +479,44 @@ void LineBuilder::update_last_line()
474479 // Position the fragment according to the vertical-align of its own styled inline element.
475480 auto const & own_vertical_align = fragment.style_source ().computed_values ().vertical_align ();
476481 CSSPixels new_fragment_block_offset = block_offset_value_for_alignment (own_vertical_align, fragment_metrics);
482+ auto baseline_aligned_fragment_block_offset = block_offset_value_for_alignment (CSS ::VerticalAlign::Baseline, fragment_metrics);
477483 auto block_offset_before_ancestor_adjustments = new_fragment_block_offset;
478484
485+ Vector<InlineBoxAlignment, 4 > inline_box_alignments;
486+
479487 // A 'top'- or 'bottom'-aligned box forms the root of its own aligned subtree and is positioned relative to the
480488 // line box, so it must ignore the vertical-align of its ancestors.
481489 auto * own_alignment = own_vertical_align.get_pointer <CSS ::VerticalAlign>();
482490 bool own_alignment_is_line_relative = own_alignment && first_is_one_of (*own_alignment, CSS ::VerticalAlign::Top, CSS ::VerticalAlign::Bottom);
483491
484492 auto const & node = fragment.style_source ();
485493 auto const * containing_block = &m_context.containing_block ();
494+ if (&node != containing_block && node.is_fragmented_inline ()) {
495+ inline_box_alignments.append ({
496+ .box = &as<NodeWithStyleAndBoxModelMetrics>(node),
497+ .vertical_shift = new_fragment_block_offset - baseline_aligned_fragment_block_offset,
498+ });
499+ }
486500 auto const * first_ancestor = &node == containing_block ? nullptr : node.parent ();
487501 for (auto const * ancestor = first_ancestor;
488502 !own_alignment_is_line_relative && ancestor && ancestor->is_fragmented_inline () && ancestor != containing_block;
489503 ancestor = ancestor->parent ()) {
490504 auto const & ancestor_vertical_align = ancestor->computed_values ().vertical_align ();
491505 if (ancestor_vertical_align.has <CSS ::VerticalAlign>()) {
492506 auto keyword = ancestor_vertical_align.get <CSS ::VerticalAlign>();
493- if (keyword == CSS ::VerticalAlign::Baseline)
507+ if (keyword == CSS ::VerticalAlign::Baseline) {
508+ inline_box_alignments.append ({ &as<NodeWithStyleAndBoxModelMetrics>(*ancestor), 0 });
494509 continue ;
510+ }
495511 // FIXME: Implement aligning a 'top'- or 'bottom'-aligned ancestor's aligned subtree to the line box
496512 if (first_is_one_of (keyword, CSS ::VerticalAlign::Top, CSS ::VerticalAlign::Bottom))
497513 break ;
498514 }
499515 auto ancestor_metrics = inline_box_alignment_metrics (*ancestor);
500- new_fragment_block_offset + = block_offset_value_for_alignment (ancestor_vertical_align, ancestor_metrics)
516+ auto ancestor_vertical_shift = block_offset_value_for_alignment (ancestor_vertical_align, ancestor_metrics)
501517 - block_offset_value_for_alignment (CSS ::VerticalAlign::Baseline, ancestor_metrics);
518+ new_fragment_block_offset += ancestor_vertical_shift;
519+ inline_box_alignments.append ({ &as<NodeWithStyleAndBoxModelMetrics>(*ancestor), ancestor_vertical_shift });
502520 }
503521
504522 fragment.set_inline_offset (new_fragment_inline_offset);
@@ -546,6 +564,21 @@ void LineBuilder::update_last_line()
546564 if (!first_unshifted_text_baseline.has_value () && fragment_is_unshifted
547565 && fragment.layout_node ().is_text_node () && !fragment.is_fully_truncated ())
548566 first_unshifted_text_baseline = fragment.block_offset () + fragment.baseline () - m_current_block_offset;
567+
568+ // https://drafts.csswg.org/css-text-decor-4/#text-line-constancy
569+ // UAs must adjust line positions to match the shifted metrics of decorating boxes shifted with
570+ // vertical-align values other than baseline [CSS2] or subscripted/superscripted via
571+ // font-variant-position [CSS-FONTS-3], but must not adjust the line position or thickness in response to
572+ // descendants of a decorating box that are so styled.
573+ // NB: Walk from the innermost box outward, removing each box's shift before recording its parent's
574+ // baseline.
575+ if (fragment.layout_node ().is_text_node () && !fragment.is_fully_truncated ()) {
576+ auto inline_box_baseline = fragment.block_offset () + fragment.baseline ();
577+ for (auto const & alignment : inline_box_alignments) {
578+ line_box.set_inline_box_baseline (*alignment.box , inline_box_baseline);
579+ inline_box_baseline -= alignment.vertical_shift ;
580+ }
581+ }
549582 }
550583
551584 for (auto & marker : line_box.static_position_markers ()) {
0 commit comments