diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index 2cdbe8e..20adb76 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -341,6 +341,7 @@ pub fn layout_dom_with_images( font_size: Option, font_weight: Option, font_family: Option, + text_align: Option, visibility_hidden: bool, opacity_product: f32, list_style: crate::ListStyle, @@ -362,6 +363,7 @@ pub fn layout_dom_with_images( font_size: None, font_weight: None, font_family: None, + text_align: None, visibility_hidden: false, opacity_product: 1.0, // CSS initial value of list-style-type. @@ -466,6 +468,7 @@ pub fn layout_dom_with_images( } match &style.font_weight { Some(w) => inh.font_weight = Some(w.clone()), None => style.font_weight = inh.font_weight.clone() } match &style.font_family { Some(f) => inh.font_family = Some(f.clone()), None => style.font_family = inh.font_family.clone() } + match style.text_align { Some(a) => inh.text_align = Some(a), None => style.text_align = inh.text_align } inh.visibility_hidden = style.visibility_hidden.unwrap_or(inh.visibility_hidden); inh.opacity_product *= style.opacity.unwrap_or(1.0); style.effectively_invisible = inh.visibility_hidden || inh.opacity_product < 0.02; @@ -2056,17 +2059,11 @@ fn build( taffy_style.flex_direction = taffy::FlexDirection::Row; taffy_style.flex_wrap = taffy::FlexWrap::Wrap; - // Before promotion, `align_items` was the horizontal-position stand-in - // for a column container (from `text-align` or `
`; see - // `style::apply_value`'s "text-align" arm): correct there, since a - // column's cross axis is horizontal. The promoted container's *main* - // axis is horizontal instead, so that same "where should content sit - // horizontally" intent now belongs on `justify_content`, not - // `align_items` — otherwise e.g. `text-align: right` shoves wrapped - // text to the bottom of its line (the new cross axis) rather than to - // the right. Real `justify-content` from actual CSS wins if present. + // The promoted container's main axis is horizontal, so text alignment + // belongs on `justify_content`. Real `justify-content` from actual CSS + // wins if present. if style.justify_content.is_none() { - taffy_style.justify_content = match style.align_items { + taffy_style.justify_content = match style.text_align { Some(taffy::AlignItems::FLEX_END) => Some(taffy::JustifyContent::FLEX_END), Some(taffy::AlignItems::CENTER) => Some(taffy::JustifyContent::CENTER), _ => taffy_style.justify_content, @@ -2349,11 +2346,11 @@ fn run_leaf_style() -> taffy::Style { /// Style for an anonymous inline-run wrapper: a full-width block-level box /// whose interior is the flex-wrap approximation of an inline formatting -/// context. The parent's `text-align` stand-in (align_items) moves the run's -/// line content via justify-content, exactly as the old whole-container +/// context. The parent's `text-align` moves the run's line content via +/// justify-content, exactly as the old whole-container /// promotion did, but scoped to the run so sibling blocks stay full width. fn run_wrapper_style(parent: &crate::LayoutStyle) -> taffy::Style { - let justify = match parent.align_items { + let justify = match parent.text_align { Some(taffy::AlignItems::FLEX_END) => Some(taffy::JustifyContent::FLEX_END), Some(taffy::AlignItems::CENTER) => Some(taffy::JustifyContent::CENTER), _ => None, diff --git a/crates/obscura-render/src/inline.rs b/crates/obscura-render/src/inline.rs index 24faa87..e7c7ac0 100644 --- a/crates/obscura-render/src/inline.rs +++ b/crates/obscura-render/src/inline.rs @@ -246,7 +246,7 @@ impl TextEngine { let rich = spans.iter().map(|(t, a)| (t.as_str(), a.to_attrs())); buffer.set_rich_text(&mut self.font_system, rich, Attrs::new().family(Family::Name(FAMILY)), Shaping::Advanced); - let align = match base.align_items { + let align = match base.text_align { Some(taffy::AlignItems::CENTER) => Some(Align::Center), Some(taffy::AlignItems::FLEX_END) => Some(Align::End), _ => None, diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index 5a5a3e3..123c79d 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -278,6 +278,10 @@ pub struct LayoutStyle { /// resolves it to a bundled face (Liberation Sans/Serif/Mono) the way /// Chromium picks a generic family on this host. pub font_family: Option, + /// Inherited `text-align`, represented with the matching horizontal + /// alignment keywords. Kept separate from flex/grid `align-items`: using + /// one field for both made `text-align:left` shrink-wrap flex children. + pub text_align: Option, pub align_items: Option, pub flex_direction: Option, pub flex_wrap: Option, @@ -644,16 +648,12 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { BoxSizing::Inherit => taffy::BoxSizing::ContentBox, }; - // A block box that wants non-default cross-axis alignment (from - // text-align: center/right, the only way our engine currently sets - // align_items on a block-level element) needs a flex column to have any - // effect at all: taffy's native block algorithm (used for plain - // Display::Block) has no align-items concept whatsoever, it only ever - // places block children at the start edge. Promote such boxes to a flex - // column, matching the same column-flex approximation already used for - // elements like and
. + // A block box with centered/right inline content needs a flex-column + // stand-in because taffy's native block algorithm has no line alignment. + // `text_align` is separate from real flex/grid `align-items`, so a + // text-align declaration never changes how flex children are sized. let promote_for_alignment = style.display == Display::Block - && matches!(style.align_items, Some(taffy::AlignItems::CENTER) | Some(taffy::AlignItems::FLEX_END)); + && matches!(style.text_align, Some(taffy::AlignItems::CENTER) | Some(taffy::AlignItems::FLEX_END)); s.display = match style.display { Display::Block if promote_for_alignment => taffy::style::Display::Flex, @@ -665,6 +665,7 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { }; if promote_for_alignment { s.flex_direction = taffy::FlexDirection::Column; + s.align_items = style.text_align; } if let Some(fd) = style.flex_direction { s.flex_direction = fd; @@ -704,8 +705,13 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { s.aspect_ratio = Some(ar); } } - if let Some(ai) = style.align_items { - s.align_items = Some(ai); + if style.display != Display::Block { + if let Some(ai) = style.align_items { + s.align_items = Some(ai); + } + } else if !promote_for_alignment { + // `align-items` has no effect on a block formatting context. + s.align_items = None; } if let Some(jc) = style.justify_content { s.justify_content = Some(jc); diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index 3d4e04e..457c689 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -394,15 +394,13 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { style.font_family = Some(v); } } - // Our engine has no real inline formatting context, so text-align is - // approximated the same way as align-items: it positions a block's - // (or column-flex's) children along the cross axis. See - // `to_taffy_style`'s block-to-flex-column promotion, which is what - // makes this actually take effect on plain block elements. + // Text alignment is inherited and applies to inline line boxes. It is + // deliberately separate from flex/grid `align-items`, which positions + // child boxes rather than text inside them. "text-align" => match value { - "right" => style.align_items = Some(taffy::AlignItems::FLEX_END), - "center" => style.align_items = Some(taffy::AlignItems::CENTER), - "left" | "start" | "justify" => style.align_items = Some(taffy::AlignItems::FLEX_START), + "right" | "end" => style.text_align = Some(taffy::AlignItems::FLEX_END), + "center" => style.text_align = Some(taffy::AlignItems::CENTER), + "left" | "start" | "justify" => style.text_align = Some(taffy::AlignItems::FLEX_START), _ => {} }, "align-items" => {