From ba92928ff5500af06ea86a196e79ee60e3f89378 Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:42:19 +0000 Subject: [PATCH] render: honor item self alignment --- crates/obscura-render/src/lib.rs | 4 ++ crates/obscura-render/src/style.rs | 92 ++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index f2d1a83..73056a3 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -287,6 +287,8 @@ pub struct LayoutStyle { /// one field for both made `text-align:left` shrink-wrap flex children. pub text_align: Option, pub align_items: Option, + pub align_self: Option, + pub justify_self: Option, pub flex_direction: Option, pub flex_wrap: Option, pub justify_content: Option, @@ -717,6 +719,8 @@ pub(crate) fn to_taffy_style(style: &LayoutStyle) -> Style { // `align-items` has no effect on a block formatting context. s.align_items = None; } + s.align_self = style.align_self; + s.justify_self = style.justify_self; 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 af34ec4..f728b36 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -431,6 +431,22 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { _ => {} } }, + "align-self" => { + if let Some(value) = self_alignment_value(value) { + style.align_self = value; + } + }, + "justify-self" => { + if let Some(value) = self_alignment_value(value) { + style.justify_self = value; + } + }, + "place-self" => { + if let Some((align, justify)) = self_alignment_pair(value) { + style.align_self = align; + style.justify_self = justify; + } + }, "justify-content" => { match value { "center" => style.justify_content = Some(taffy::JustifyContent::CENTER), @@ -677,6 +693,57 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { } } +/// Parse the common CSS Box Alignment values that taffy can represent. +/// +/// The outer option distinguishes an invalid declaration from `auto`, which +/// resets a preceding declaration to the inherited item-alignment behavior. +fn self_alignment_value(value: &str) -> Option> { + let normalized = value.trim().to_ascii_lowercase(); + let alignment = match normalized.as_str() { + "auto" => return Some(None), + "normal" => taffy::AlignSelf::STRETCH, + "start" | "self-start" => taffy::AlignSelf::START, + "end" | "self-end" => taffy::AlignSelf::END, + "flex-start" => taffy::AlignSelf::FLEX_START, + "flex-end" => taffy::AlignSelf::FLEX_END, + "center" => taffy::AlignSelf::CENTER, + "baseline" | "first baseline" => taffy::AlignSelf::BASELINE, + "stretch" => taffy::AlignSelf::STRETCH, + "safe start" | "safe self-start" => taffy::AlignSelf::SAFE_START, + "safe end" | "safe self-end" => taffy::AlignSelf::SAFE_END, + "safe flex-start" => taffy::AlignSelf::SAFE_FLEX_START, + "safe flex-end" => taffy::AlignSelf::SAFE_FLEX_END, + "safe center" => taffy::AlignSelf::SAFE_CENTER, + "unsafe start" | "unsafe self-start" => taffy::AlignSelf::START, + "unsafe end" | "unsafe self-end" => taffy::AlignSelf::END, + "unsafe flex-start" => taffy::AlignSelf::FLEX_START, + "unsafe flex-end" => taffy::AlignSelf::FLEX_END, + "unsafe center" => taffy::AlignSelf::CENTER, + _ => return None, + }; + Some(Some(alignment)) +} + +fn self_alignment_pair( + value: &str, +) -> Option<(Option, Option)> { + if let Some(alignment) = self_alignment_value(value) { + return Some((alignment, alignment)); + } + let tokens: Vec<&str> = value.split_whitespace().collect(); + for split in 1..tokens.len() { + let align = tokens[..split].join(" "); + let justify = tokens[split..].join(" "); + if let (Some(align), Some(justify)) = ( + self_alignment_value(&align), + self_alignment_value(&justify), + ) { + return Some((align, justify)); + } + } + None +} + /// Parse the subset of `transform` obscura applies at paint time: `translate`, /// `translateX`, `translateY` (px and %, stored unresolved so % can resolve /// against the element's own border box later) and `scale`/`scaleX`/`scaleY`. @@ -2609,6 +2676,31 @@ mod tests { assert!(invalid.internal_flex_container); } + #[test] + fn item_self_alignment_parses_and_resets() { + let aligned = compute_style( + "div", + Some("align-self:safe center;justify-self:flex-end"), + ); + assert_eq!(aligned.align_self, Some(taffy::AlignSelf::SAFE_CENTER)); + assert_eq!(aligned.justify_self, Some(taffy::JustifySelf::FLEX_END)); + + let reset = compute_style( + "div", + Some("align-self:center;align-self:auto;justify-self:end;justify-self:auto"), + ); + assert_eq!(reset.align_self, None); + assert_eq!(reset.justify_self, None); + + let normal = compute_style("div", Some("align-self:normal;justify-self:normal")); + assert_eq!(normal.align_self, Some(taffy::AlignSelf::STRETCH)); + assert_eq!(normal.justify_self, Some(taffy::JustifySelf::STRETCH)); + + let shorthand = compute_style("div", Some("place-self:safe center flex-end")); + assert_eq!(shorthand.align_self, Some(taffy::AlignSelf::SAFE_CENTER)); + assert_eq!(shorthand.justify_self, Some(taffy::JustifySelf::FLEX_END)); + } + #[test] fn font_shorthand_expands_layout_fields_and_resets_omissions() { let s = compute_style(