diff --git a/crates/gosub_styling/src/lib.rs b/crates/gosub_styling/src/lib.rs index 9aecc9ca6..4fd03c32b 100644 --- a/crates/gosub_styling/src/lib.rs +++ b/crates/gosub_styling/src/lib.rs @@ -11,6 +11,7 @@ use gosub_css3::Css3; mod errors; pub mod property_definitions; pub mod render_tree; +mod shorthands; pub mod styling; mod syntax; mod syntax_matcher; diff --git a/crates/gosub_styling/src/property_definitions.rs b/crates/gosub_styling/src/property_definitions.rs index 4c079de3d..3c21105c3 100644 --- a/crates/gosub_styling/src/property_definitions.rs +++ b/crates/gosub_styling/src/property_definitions.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; -use std::sync::LazyLock; use log::warn; use gosub_css3::stylesheet::CssValue; +use crate::shorthands::{FixList, Shorthands}; +use std::sync::LazyLock; + use crate::syntax::GroupCombinators::Juxtaposition; use crate::syntax::{CssSyntax, SyntaxComponent}; use crate::syntax_matcher::CssSyntaxTree; @@ -70,6 +72,8 @@ pub struct PropertyDefinition { pub initial_value: Option, // True when this element is resolved pub resolved: bool, + /// Shorthand resolver, used to expand computed values + pub shorthands: Option, } impl PropertyDefinition { @@ -104,6 +108,16 @@ impl PropertyDefinition { self.syntax.matches(input) } + pub fn matches_and_shorthands(&self, input: &[CssValue], fix_list: &mut FixList) -> bool { + if let Some(shorthands) = &self.shorthands { + let resolver = shorthands.get_resolver(fix_list); + + self.syntax.matches_and_shorthands(input, resolver) + } else { + self.syntax.matches(input) + } + } + pub fn check_expanded_properties(&self, _values: &[CssValue]) -> bool { // if values.len() != self.expanded_properties.len() { // return false; @@ -119,13 +133,17 @@ impl PropertyDefinition { true } + + pub fn is_shorthand(&self) -> bool { + self.computed.len() > 1 + } } /// A syntax definition that can be used to resolve a property definition #[derive(Debug, Clone)] pub struct SyntaxDefinition { /// Actual syntax - syntax: CssSyntaxTree, + pub syntax: CssSyntaxTree, /// True when the element has already been resolved resolved: bool, } @@ -222,7 +240,7 @@ impl CssDefinitions { } /// Resolve a syntax component - fn resolve_component( + pub fn resolve_component( &mut self, component: &SyntaxComponent, prop_name: &str, @@ -267,9 +285,12 @@ impl CssDefinitions { // If the resolved syntax is just a single element (be it a group, or a single element), // return that component. if resolved_prop.syntax.components.len() == 1 { - return resolved_prop.syntax.components[0].clone(); - } + let mut component = resolved_prop.syntax.components[0].clone(); + + component.update_multipliers(multipliers.clone()); + return component; + } // Otherwise, we return a group with the components return SyntaxComponent::Group { components: resolved_prop.syntax.components.clone(), @@ -352,6 +373,8 @@ fn pars_definition_files() -> CssDefinitions { properties, syntax, }; + + definitions.index_shorthands(); definitions.resolve(); definitions @@ -472,6 +495,7 @@ fn parse_property_file(json: serde_json::Value) -> HashMap RenderTree { fn generate_from(&mut self, document: DocumentHandle) { // Iterate the complete document tree let tree_iterator = TreeIterator::new(&document); + + { + let doc = document.get(); + + println!("Stylesheets: {:?}", doc.stylesheets.len()); + + for stylesheet in &doc.stylesheets { + println!(" {:?}", stylesheet.location); + println!(" {:?}", stylesheet.origin); + } + } + for current_node_id in tree_iterator { let mut css_map_entry = CssProperties::new(); @@ -248,7 +260,13 @@ impl RenderTree { let definitions = get_css_definitions(); + let mut fix_list = FixList::new(); + for sheet in document.get().stylesheets.iter() { + if sheet.origin == CssOrigin::UserAgent { + continue; + } + for rule in sheet.rules.iter() { for selector in rule.selectors().iter() { if !match_selector( @@ -262,18 +280,19 @@ impl RenderTree { // Selector matched, so we add all declared values to the map for declaration in rule.declarations().iter() { // Step 1: find the property in our CSS definition list - let definition = definitions.find_property(&declaration.property); - // If not found, we skip this declaration - if definition.is_none() { + let Some(definition) = definitions.find_property(&declaration.property) + else { + // If not found, we skip this declaration warn!( "Definition is not found for property {:?}", declaration.property ); continue; - } + }; // Check if the declaration matches the definition and return the "expanded" order - let res = definition.unwrap().matches(&declaration.value); + let res = definition + .matches_and_shorthands(&declaration.value, &mut fix_list); if !res { warn!("Declaration does not match definition: {:?}", declaration); continue; @@ -293,6 +312,10 @@ impl RenderTree { } } + fix_list.resolve_nested(definitions); + + fix_list.apply(&mut css_map_entry); + let binding = document.get(); let current_node = binding.get_node_by_id(current_node_id).unwrap(); @@ -490,29 +513,29 @@ impl RenderTree { } // Generates a declaration property and adds it to the css_map_entry -fn add_property_to_map( +pub fn add_property_to_map( css_map_entry: &mut CssProperties, sheet: &CssStylesheet, selector: &CssSelector, declaration: &CssDeclaration, ) { let property_name = declaration.property.clone(); - let entry = CssProperty::new(property_name.as_str()); + // let entry = CssProperty::new(property_name.as_str()); // If the property is a shorthand css property, we need fetch the individual properties // It's possible that need to recurse here as these individual properties can be shorthand as well - if entry.is_shorthand() { - for property_name in entry.get_props_from_shorthand() { - let decl = CssDeclaration { - property: property_name.to_string(), - value: declaration.value.clone(), - important: declaration.important, - }; - - add_property_to_map(css_map_entry, sheet, selector, &decl); - } - } - + // if entry.is_shorthand() { + // for property_name in entry.get_props_from_shorthand() { + // let decl = CssDeclaration { + // property: property_name.to_string(), + // value: declaration.value.clone(), + // important: declaration.important, + // }; + // + // add_property_to_map(css_map_entry, sheet, selector, &decl); + // } + // } + // let declaration = DeclarationProperty { // @todo: this seems wrong. We only get the first values from the declared values value: declaration.value.first().unwrap().clone(), @@ -674,10 +697,21 @@ impl RenderTreeNode { return true; } - return self - .properties + let tag_name = self.name.to_lowercase(); + + const INLINE_ELEMENTS: [&str; 31] = [ + "a", "abbr", "acronym", "b", "bdo", "big", "br", "button", "cite", "code", "dfn", "em", + "i", "img", "input", "kbd", "label", "map", "object", "q", "samp", "script", "select", + "small", "span", "strong", "sub", "sup", "textarea", "tt", "var", + ]; + + if INLINE_ELEMENTS.contains(&tag_name.as_str()) { + return true; + } + + self.properties .get("display") - .map_or(false, |prop| prop.compute_value().to_string() == "inline"); + .map_or(false, |prop| prop.compute_value().to_string() == "inline") } } diff --git a/crates/gosub_styling/src/shorthands.rs b/crates/gosub_styling/src/shorthands.rs new file mode 100644 index 000000000..76232ce90 --- /dev/null +++ b/crates/gosub_styling/src/shorthands.rs @@ -0,0 +1,770 @@ +use std::collections::hash_map::Entry; + +use gosub_css3::stylesheet::{CssOrigin, CssValue, Specificity}; + +use crate::property_definitions::CssDefinitions; +use crate::styling::{CssProperties, CssProperty, DeclarationProperty}; +use crate::syntax::{SyntaxComponent, SyntaxComponentMultiplier}; +use crate::syntax_matcher::CssSyntaxTree; + +impl CssSyntaxTree { + pub fn has_property_syntax(&self, property: &str) -> Option { + let component = self.components.first()?; + + let mut path = Vec::with_capacity(1); + + if component.has_property_syntax(property, &mut path) { + Some(Shorthand { + name: property.to_string(), + components: path, + }) + } else { + None + } + } +} + +impl SyntaxComponent { + pub fn has_property_syntax(&self, prop: &str, path: &mut Vec) -> bool { + match self { + SyntaxComponent::Property { property, .. } => prop == property, + SyntaxComponent::Definition { + datatype, quoted, .. + } if *quoted => prop == datatype, + SyntaxComponent::Group { components, .. } => { + for (i, component) in components.iter().enumerate() { + path.push(i); + if component.has_property_syntax(prop, path) { + return true; + } + path.pop(); + } + false + } + _ => false, + } + } + + pub fn multipliers(&self) -> &[SyntaxComponentMultiplier] { + match self { + SyntaxComponent::GenericKeyword { multipliers, .. } => multipliers, + SyntaxComponent::Property { multipliers, .. } => multipliers, + SyntaxComponent::Function { multipliers, .. } => multipliers, + SyntaxComponent::Definition { multipliers, .. } => multipliers, + SyntaxComponent::Inherit { multipliers, .. } => multipliers, + SyntaxComponent::Initial { multipliers, .. } => multipliers, + SyntaxComponent::Unset { multipliers, .. } => multipliers, + SyntaxComponent::Literal { multipliers, .. } => multipliers, + SyntaxComponent::Value { multipliers, .. } => multipliers, + SyntaxComponent::Group { multipliers, .. } => multipliers, + SyntaxComponent::Unit { multipliers, .. } => multipliers, + SyntaxComponent::Builtin { multipliers, .. } => multipliers, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Multiplier { + None, // component has no multiplier TODO: do we need this? + NextProp, + QuadMulti, // we can use everything that matches this component, and go to the next if we have more (e.g, border-radius) + DuoMulti, + OnlyMatched, // we need to extract values out of the matched components (e.g, background) +} + +impl Multiplier { + fn get_names(self, completed: Vec<&str>, multi: usize) -> Option> { + println!("get names: {completed:?}, {multi}"); + + match self { + Multiplier::NextProp => Some(vec![completed.get(multi)?]), + + Multiplier::DuoMulti => { + if multi == 0 { + return Some(completed); + } + + Some(vec![completed.get(1)?]) + } + + Multiplier::QuadMulti => match multi { + 0 => Some(completed), + 1 => Some(completed.get(1..3)?.to_vec()), + 2 => Some(vec![completed.first()?]), + 3 => Some(vec![completed.get(1)?]), + + _ => None, + }, + + _ => None, + } + } +} + +#[derive(Debug, Clone)] +pub struct Shorthands { + multiplier: Multiplier, + shorthands: Vec, + name: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FixList { + list: Vec<(String, Vec)>, + multipliers: Vec<(String, usize)>, +} + +#[derive(Debug, Clone)] +pub struct Shorthand { + name: String, + components: Vec, +} + +#[derive(Debug)] +pub struct ShorthandResolver<'a> { + name: &'a str, + pub multiplier: Multiplier, + fix_list: &'a mut FixList, + shorthands: Vec>, +} + +pub fn copy_resolver<'a>(res: &'a mut Option) -> Option> { + if let Some(resolver) = res { + Some(ShorthandResolver { + multiplier: resolver.multiplier, + fix_list: resolver.fix_list, + shorthands: resolver + .shorthands + .iter() + .map(|s| ResolveShorthand { + name: s.name, + components: s.components, + }) + .collect(), + name: resolver.name, + }) + } else { + None + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ResolveShorthand<'a> { + pub name: &'a str, + pub components: &'a [usize], +} + +pub struct CompleteStep<'a> { + list: &'a mut FixList, + name: Vec<&'a str>, + completed: bool, + snapshot: Option, +} + +impl Drop for CompleteStep<'_> { + fn drop(&mut self) { + if !self.completed { + if let Some(snap) = self.snapshot.take() { + *self.list = snap.fix_list; + } + } + } +} + +impl Shorthands { + pub fn get_resolver<'a>(&'a self, fix_list: &'a mut FixList) -> ShorthandResolver<'a> { + ShorthandResolver { + multiplier: self.multiplier, + fix_list, + shorthands: self.shorthands.iter().map(Shorthand::resolver).collect(), + name: &self.name, + } + } +} + +impl Shorthand { + pub fn resolver(&self) -> ResolveShorthand { + ResolveShorthand { + name: &self.name, + components: &self.components, + } + } +} + +pub struct Snapshot { + fix_list: FixList, +} + +impl<'a> ShorthandResolver<'a> { + pub fn step(&'a mut self, idx: usize) -> Result, CompleteStep<'a>> { + let snapshot = Some(self.snapshot()); + + let mut shorthands = Vec::with_capacity(self.shorthands.len()); + + if matches!( + self.multiplier, + Multiplier::QuadMulti | Multiplier::DuoMulti | Multiplier::NextProp + ) { + let mut complete = Vec::with_capacity(self.shorthands.len()); + + for shorthand in self.shorthands.iter() { + match shorthand.step_complete(idx) { + Some(Some(elem)) => { + shorthands.push(elem); + } + Some(None) => { + complete.push(shorthand.name); + } + None => {} + } + } + + if !complete.is_empty() { + let idx = self + .fix_list + .multipliers + .iter_mut() + .find(|m| m.0 == self.name); + + if let Some(idx) = idx { + let Some(items) = self.multiplier.get_names(complete, idx.1) else { + return Ok(None); + }; + + idx.1 += 1; + + return Err(CompleteStep { + list: self.fix_list, + name: items, + snapshot, + completed: false, + }); + } + + let Some(items) = self.multiplier.get_names(complete, 0) else { + return Ok(None); + }; + + self.fix_list.multipliers.push((self.name.to_string(), 1)); + + return Err(CompleteStep { + list: self.fix_list, + name: items, + snapshot, + completed: false, + }); + } + } + + for shorthand in self.shorthands.iter() { + match shorthand.step_complete(idx) { + Some(Some(elem)) => { + shorthands.push(elem); + } + Some(None) => { + return Err(CompleteStep { + list: self.fix_list, + name: vec![shorthand.name], + snapshot, + completed: false, + }); + } + None => {} + } + } + + if shorthands.is_empty() { + return Ok(None); + } + + Ok(Some(Self { + multiplier: self.multiplier, + fix_list: self.fix_list, + shorthands, + name: self.name, + })) + } + + pub fn snapshot(&self) -> Snapshot { + Snapshot { + fix_list: self.fix_list.clone(), + } + } +} + +impl<'a> ResolveShorthand<'a> { + fn step_complete<'c>(&'c self, idx: usize) -> Option>> { + if self.components.is_empty() { + return Some(None); + } + + if self.components.first().copied() == Some(idx) { + let components = &self.components[1..]; + + if components.is_empty() { + return Some(None); + } + + return Some(Some(Self { + name: self.name, + components, + })); + } + + None + } +} + +impl Default for FixList { + fn default() -> Self { + Self::new() + } +} + +impl FixList { + pub fn new() -> Self { + Self { + list: Vec::new(), + multipliers: Vec::new(), + } + } + + pub fn insert(&mut self, name: String, value: Vec) { + for (k, v) in &mut self.list { + if *k == name { + *v = value; + return; + } + } + + self.list.push((name, value)); + } + + pub fn resolve_nested(&mut self, definitions: &CssDefinitions) { + let mut fix_list = FixList::new(); + + let mut had_shorthands = false; + + for (name, value) in &self.list { + let Some(prop) = definitions.find_property(name) else { + continue; + }; + + if !prop.is_shorthand() { + continue; + } + + had_shorthands = true; + + prop.matches_and_shorthands(value, &mut fix_list); + } + + if had_shorthands { + fix_list.resolve_nested(definitions); + } + + self.append(fix_list); + } + + pub fn append(&mut self, mut other: FixList) { + self.list.append(&mut other.list); + } + + pub fn apply(&mut self, props: &mut CssProperties) { + for (name, value) in &self.list { + let Some(value) = value.first().cloned() else { + continue; + }; + + let decl = DeclarationProperty { + value, + origin: CssOrigin::Author, + important: false, + location: "".to_string(), + specificity: Specificity::new(0, 1, 0), + }; + + match props.properties.entry(name.clone()) { + Entry::Occupied(mut entry) => { + println!("Entry occupied"); + let prop = entry.get_mut(); + + dbg!(&prop); + + prop.declared.push(decl); + dbg!(&prop); + } + Entry::Vacant(entry) => { + println!("Entry vacant"); + + let mut prop = CssProperty::new(name); + + prop.declared.push(decl); + + entry.insert(prop); + } + } + } + } +} + +impl CompleteStep<'_> { + pub fn complete(mut self, value: Vec) { + for name in self.name.clone() { + self.list.insert(name.to_string(), value.clone()); + } + + self.completed = true; + } +} + +impl CssDefinitions { + pub fn index_shorthands(&mut self) { + let mut shorthands = Vec::new(); + + for prop in self.properties.values() { + let syntax = self.resolve_shorthands(&prop.computed, &prop.syntax, &prop.name); + + if let Some(syntax) = syntax { + shorthands.push((prop.name.clone(), syntax)); + } + } + + for (name, syntax) in shorthands { + let Some(prop) = self.properties.get_mut(&name) else { + continue; + }; + + prop.shorthands = Some(syntax); + } + } + + pub fn resolve_shorthands( + &self, + computed: &[String], + syntax: &CssSyntaxTree, + name: &str, + ) -> Option { + if computed.len() <= 1 || syntax.components.is_empty() { + return None; + } + + let mut shorthands: Vec = Vec::with_capacity(computed.len()); + + if let Some(component) = syntax.components.first() { + for m in component.multipliers() { + match m { + SyntaxComponentMultiplier::Between(_, b) => { + if *b == computed.len() { + for c in computed { + shorthands.push(Shorthand { + name: c.clone(), + components: vec![], + }); + } + + let multiplier; + + if computed.len() == 2 { + multiplier = Multiplier::DuoMulti; + } else if computed.len() == 4 { + multiplier = Multiplier::QuadMulti; + } else { + multiplier = Multiplier::NextProp; + } + + return Some(Shorthands { + multiplier, + shorthands, + name: name.to_string(), + }); + } + } + + SyntaxComponentMultiplier::CommaSeparatedRepeat(_, b) => { + if *b == computed.len() { + for c in computed { + shorthands.push(Shorthand { + name: c.clone(), + components: vec![], + }); + } + + let multiplier; + + if computed.len() == 2 { + multiplier = Multiplier::DuoMulti; + } else if computed.len() == 4 { + multiplier = Multiplier::QuadMulti; + } else { + multiplier = Multiplier::NextProp; + } + + return Some(Shorthands { + multiplier, + shorthands, + name: name.to_string(), + }); + } + } + + _ => {} + } + } + } + + let mut found_props = Vec::with_capacity(computed.len()); + for shorthand in computed { + if let Some(shorthand) = syntax.has_property_syntax(shorthand) { + found_props.push(shorthand); + } + } + + if found_props.len() == computed.len() { + return Some(Shorthands { + multiplier: Multiplier::None, + shorthands: found_props, + name: name.to_string(), + }); + } + + if let Some(SyntaxComponent::Group { + components, + // multipliers, + .. + }) = syntax.components.first() + { + if components.len() == computed.len() { + for (i, property) in computed.iter().enumerate() { + shorthands.push(Shorthand { + name: property.clone(), + components: vec![i], + }); + } + + return Some(Shorthands { + multiplier: Multiplier::None, + shorthands, + name: name.to_string(), + }); + } + } + + if syntax.components.len() == 1 { + let component = syntax.components.first().unwrap(); + + match component { + SyntaxComponent::Definition { datatype, .. } => { + if let Some(d) = self.syntax.get(datatype) { + if let Some(mut shorthands) = + self.resolve_shorthands(computed, &d.syntax, name) + { + shorthands.multiplier = Multiplier::None; + + return Some(shorthands); + } + } + + if let Some(p) = self.properties.get(datatype) { + //currently properties get parsed as definitions + if let Some(mut shorthands) = + self.resolve_shorthands(computed, &p.syntax, name) + { + shorthands.multiplier = Multiplier::None; + + return Some(shorthands); + } + } + } + + SyntaxComponent::Property { property, .. } => { + if let Some(d) = self.properties.get(property) { + if let Some(mut shorthands) = + self.resolve_shorthands(computed, &d.syntax, name) + { + shorthands.multiplier = Multiplier::None; + + return Some(shorthands); + } + } + } + + _ => {} + } + } + + // if !found_props.is_empty() { + // println!("Found partial expanded properties for shorthand: {}", name); + // } else { + // println!("Missing properties for shorthand: {}", name); + // } + // let missing = computed + // .iter() + // .filter(|computed| !found_props.iter().any(|p| p.name == **computed)) + // .collect::>(); + // + // println!("Missing properties: {:?}", missing); + + None + } +} + +#[cfg(test)] +mod tests { + use gosub_css3::colors::RgbColor; + use gosub_css3::stylesheet::CssValue; + + use crate::property_definitions::get_css_definitions; + use crate::shorthands::FixList; + + macro_rules! str { + ($s:expr) => { + CssValue::String($s.to_string()) + }; + } + + macro_rules! unit { + ($v:expr, $u:expr) => { + CssValue::Unit($v, $u.to_string()) + }; + } + + #[test] + fn margin() { + let definitions = get_css_definitions(); + + let prop = definitions.find_property("margin").unwrap(); + + let mut fix_list = FixList::new(); + + assert!(prop + .clone() + .matches_and_shorthands(&[unit!(1.0, "px"),], &mut fix_list,)); + + assert_eq!( + fix_list, + FixList { + list: vec![ + ("margin-bottom".to_string(), vec![unit!(1.0, "px")]), + ("margin-left".to_string(), vec![unit!(1.0, "px")]), + ("margin-right".to_string(), vec![unit!(1.0, "px")]), + ("margin-top".to_string(), vec![unit!(1.0, "px")]), + ], + multipliers: vec![("margin".to_string(), 1),], + } + ); + + fix_list = FixList::new(); + + assert!(prop + .clone() + .matches_and_shorthands(&[unit!(1.0, "px"), unit!(2.0, "px"),], &mut fix_list,)); + + assert_eq!( + fix_list, + FixList { + list: vec![ + ("margin-bottom".to_string(), vec![unit!(1.0, "px")]), + ("margin-left".to_string(), vec![unit!(2.0, "px")]), + ("margin-right".to_string(), vec![unit!(2.0, "px")]), + ("margin-top".to_string(), vec![unit!(1.0, "px")]), + ], + multipliers: vec![("margin".to_string(), 2),], + } + ); + + fix_list = FixList::new(); + assert!(prop.clone().matches_and_shorthands( + &[unit!(1.0, "px"), unit!(2.0, "px"), unit!(3.0, "px"),], + &mut fix_list, + )); + + assert_eq!( + fix_list, + FixList { + list: vec![ + ("margin-bottom".to_string(), vec![unit!(3.0, "px")]), + ("margin-left".to_string(), vec![unit!(2.0, "px")]), + ("margin-right".to_string(), vec![unit!(2.0, "px")]), + ("margin-top".to_string(), vec![unit!(1.0, "px")]), + ], + multipliers: vec![("margin".to_string(), 3),], + } + ); + + fix_list = FixList::new(); + assert!(prop.clone().matches_and_shorthands( + &[ + unit!(1.0, "px"), + unit!(2.0, "px"), + unit!(3.0, "px"), + unit!(4.0, "px"), + ], + &mut fix_list, + )); + + assert_eq!( + fix_list, + FixList { + list: vec![ + ("margin-bottom".to_string(), vec![unit!(3.0, "px")]), + ("margin-left".to_string(), vec![unit!(4.0, "px")]), + ("margin-right".to_string(), vec![unit!(2.0, "px")]), + ("margin-top".to_string(), vec![unit!(1.0, "px")]), + ], + multipliers: vec![("margin".to_string(), 4),], + } + ); + + dbg!(fix_list); + } + + #[test] + fn border() { + let definitions = get_css_definitions(); + + let prop = definitions.find_property("border").unwrap(); + + let mut fix_list = FixList::new(); + + assert!(prop.clone().matches_and_shorthands( + &[ + unit!(1.0, "px"), + str!("solid"), + CssValue::Color(RgbColor::new(0.0, 0.0, 0.0, 0.0)) + ], + &mut fix_list, + )); + + dbg!(&fix_list); + + fix_list.resolve_nested(definitions); + + dbg!(&fix_list); + + fix_list = FixList::new(); + + assert!(prop.clone().matches_and_shorthands( + &[ + str!("solid"), + CssValue::Color(RgbColor::new(0.0, 0.0, 0.0, 0.0)) + ], + &mut fix_list, + )); + + dbg!(fix_list); + + fix_list = FixList::new(); + + assert!(prop.clone().matches_and_shorthands( + &[ + str!("solid"), + CssValue::Color(RgbColor::new(0.0, 0.0, 0.0, 0.0)), + unit!(1.0, "px") + ], + &mut fix_list, + )); + + dbg!(fix_list); + } +} diff --git a/crates/gosub_styling/src/syntax_matcher.rs b/crates/gosub_styling/src/syntax_matcher.rs index 8ab202806..aa47bee33 100644 --- a/crates/gosub_styling/src/syntax_matcher.rs +++ b/crates/gosub_styling/src/syntax_matcher.rs @@ -1,6 +1,7 @@ use gosub_css3::colors::{is_named_color, is_system_color}; use gosub_css3::stylesheet::CssValue; +use crate::shorthands::{copy_resolver, ShorthandResolver}; use crate::syntax::{GroupCombinators, SyntaxComponent, SyntaxComponentMultiplier}; /// Structure to return from a matching function. @@ -40,7 +41,16 @@ impl CssSyntaxTree { panic!("Syntax tree must have exactly one root component"); } - let res = match_component(input, &self.components[0]); + let res = match_component(input, &self.components[0], None); + res.matched && res.remainder.is_empty() + } + + pub fn matches_and_shorthands(&self, input: &[CssValue], resolver: ShorthandResolver) -> bool { + if self.components.len() != 1 { + panic!("Syntax tree must have exactly one root component"); + } + + let res = match_component(input, &self.components[0], Some(resolver)); res.matched && res.remainder.is_empty() } } @@ -48,6 +58,7 @@ impl CssSyntaxTree { fn match_component_inner<'a>( raw_input: &'a [CssValue], component: &SyntaxComponent, + mut shorthand_resolver: Option, ) -> MatchResult<'a> { let mut input = raw_input; let mut matched_values = vec![]; @@ -74,7 +85,7 @@ fn match_component_inner<'a>( // Check either single or group component let res = if component.is_group() { - match_component_group(input, component) + match_component_group(input, component, copy_resolver(&mut shorthand_resolver)) } else { match_component_single(input, component) }; @@ -134,7 +145,11 @@ fn match_component_inner<'a>( /// Matches a component against the input values. After the match, there might be remaining /// elements in the input. This is passed back in the MatchResult structure. -fn match_component<'a>(raw_input: &'a [CssValue], component: &SyntaxComponent) -> MatchResult<'a> { +fn match_component<'a>( + raw_input: &'a [CssValue], + component: &SyntaxComponent, + mut shorthand_resolver: Option, +) -> MatchResult<'a> { let _gid = rand::random::(); let mut input = raw_input; @@ -155,7 +170,8 @@ fn match_component<'a>(raw_input: &'a [CssValue], component: &SyntaxComponent) - // CSV loop loop { - let inner_result = match_component_inner(input, component); + let inner_result = + match_component_inner(input, component, copy_resolver(&mut shorthand_resolver)); if !comma_separated { // We don't need to check for comma separated values, so just return this result return inner_result; @@ -206,6 +222,7 @@ fn match_component<'a>(raw_input: &'a [CssValue], component: &SyntaxComponent) - fn match_component_group<'a>( input: &'a [CssValue], component: &SyntaxComponent, + shorthand_resolver: Option, ) -> MatchResult<'a> { match &component { SyntaxComponent::Group { @@ -216,12 +233,18 @@ fn match_component_group<'a>( // println!("We need to do a group match on {:?}, our value is: {:?}", combinator, input); match combinator { - GroupCombinators::Juxtaposition => match_group_juxtaposition(input, components), - GroupCombinators::AllAnyOrder => match_group_all_any_order(input, components), + GroupCombinators::Juxtaposition => { + match_group_juxtaposition(input, components, shorthand_resolver) + } + GroupCombinators::AllAnyOrder => { + match_group_all_any_order(input, components, shorthand_resolver) + } GroupCombinators::AtLeastOneAnyOrder => { - match_group_at_least_one_any_order(input, components) + match_group_at_least_one_any_order(input, components, shorthand_resolver) + } + GroupCombinators::ExactlyOne => { + match_group_exactly_one(input, components, shorthand_resolver) } - GroupCombinators::ExactlyOne => match_group_exactly_one(input, components), } } e => { @@ -363,7 +386,7 @@ fn match_component_single<'a>( return first_match(input); } - todo!("Function not implemented yet. We must match the arguments"); + // todo!("Function not implemented yet. We must match the arguments"); // let list = CssValue::List(c_args.clone()); // return match_internal(&list, arguments); } @@ -386,6 +409,7 @@ fn match_component_single<'a>( fn match_group_exactly_one<'a>( raw_input: &'a [CssValue], components: &[SyntaxComponent], + mut shorthand_resolver: Option, ) -> MatchResult<'a> { let input = raw_input; let mut matched_values = vec![]; @@ -397,19 +421,48 @@ fn match_group_exactly_one<'a>( break; } - let component = &components[c_idx]; + if let Some(mut resolver) = copy_resolver(&mut shorthand_resolver) { + let step = resolver.step(c_idx); - let res = match_component(input, component); - if res.matched { - matched_values.append(&mut res.matched_values.clone()); + let mut complete = None; + let mut resolver = None; + + match step { + Ok(Some(r)) => resolver = Some(r), + Ok(None) => {} + Err(c) => complete = Some(c), + } - // input = res.remainder.clone(); + let component = &components[c_idx]; - components_matched.push((c_idx, res.matched_values, res.remainder)); + let res = match_component(input, component, resolver); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + + // input = res.remainder.clone(); + + components_matched.push((c_idx, res.matched_values.clone(), res.remainder)); + + if let Some(complete) = complete { + complete.complete(res.matched_values); + } + } else { + // No match. That's all right. + } } else { - // No match. That's all right. - } + let component = &components[c_idx]; + + let res = match_component(input, component, None); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + + // input = res.remainder.clone(); + components_matched.push((c_idx, res.matched_values, res.remainder)); + } else { + // No match. That's all right. + } + } c_idx += 1; } @@ -446,6 +499,7 @@ fn match_group_exactly_one<'a>( fn match_group_at_least_one_any_order<'a>( raw_input: &'a [CssValue], components: &[SyntaxComponent], + mut shorthand_resolver: Option, ) -> MatchResult<'a> { let mut input = raw_input; let mut matched_values = vec![]; @@ -457,25 +511,64 @@ fn match_group_at_least_one_any_order<'a>( break; } - let component = &components[c_idx]; + if let Some(mut resolver) = copy_resolver(&mut shorthand_resolver) { + let step = resolver.step(c_idx); - let res = match_component(input, component); - if res.matched { - matched_values.append(&mut res.matched_values.clone()); - components_matched.push(c_idx); + let mut complete = None; + let mut resolver = None; + + match step { + Ok(Some(r)) => resolver = Some(r), + Ok(None) => {} + Err(c) => complete = Some(c), + } + + let component = &components[c_idx]; + + let res = match_component(input, component, resolver); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + components_matched.push(c_idx); - input = res.remainder; + input = res.remainder; - // Found a match, so loop around for new matches - c_idx = 0; - while components_matched.contains(&c_idx) { + // Found a match, so loop around for new matches + c_idx = 0; + while components_matched.contains(&c_idx) { + c_idx += 1; + } + + if let Some(complete) = complete { + complete.complete(res.matched_values); + } + } else { + // Element didn't match. That might be alright, and we continue with the next unmatched component c_idx += 1; + while components_matched.contains(&c_idx) { + c_idx += 1; + } } } else { - // Element didn't match. That might be alright, and we continue with the next unmatched component - c_idx += 1; - while components_matched.contains(&c_idx) { + let component = &components[c_idx]; + + let res = match_component(input, component, None); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + components_matched.push(c_idx); + + input = res.remainder; + + // Found a match, so loop around for new matches + c_idx = 0; + while components_matched.contains(&c_idx) { + c_idx += 1; + } + } else { + // Element didn't match. That might be alright, and we continue with the next unmatched component c_idx += 1; + while components_matched.contains(&c_idx) { + c_idx += 1; + } } } } @@ -494,6 +587,7 @@ fn match_group_at_least_one_any_order<'a>( fn match_group_all_any_order<'a>( raw_input: &'a [CssValue], components: &[SyntaxComponent], + mut shorthand_resolver: Option, ) -> MatchResult<'a> { let mut input = raw_input; let mut matched_values = vec![]; @@ -505,25 +599,63 @@ fn match_group_all_any_order<'a>( break; } - let component = &components[c_idx]; + if let Some(mut resolver) = copy_resolver(&mut shorthand_resolver) { + let step = resolver.step(c_idx); - let res = match_component(input, component); - if res.matched { - matched_values.append(&mut res.matched_values.clone()); - components_matched.push(c_idx); + let mut complete = None; + let mut resolver = None; + + match step { + Ok(Some(r)) => resolver = Some(r), + Ok(None) => {} + Err(c) => complete = Some(c), + } + let component = &components[c_idx]; - input = res.remainder; + let res = match_component(input, component, resolver); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + components_matched.push(c_idx); - // Found a match, so loop around for new matches - c_idx = 0; - while components_matched.contains(&c_idx) { + input = res.remainder; + + // Found a match, so loop around for new matches + c_idx = 0; + while components_matched.contains(&c_idx) { + c_idx += 1; + } + + if let Some(complete) = complete { + complete.complete(res.matched_values); + } + } else { + // Element didn't match. That might be alright, and we continue with the next unmatched component c_idx += 1; + while components_matched.contains(&c_idx) { + c_idx += 1; + } } } else { - // Element didn't match. That might be alright, and we continue with the next unmatched component - c_idx += 1; - while components_matched.contains(&c_idx) { + let component = &components[c_idx]; + + let res = match_component(input, component, None); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + components_matched.push(c_idx); + + input = res.remainder; + + // Found a match, so loop around for new matches + c_idx = 0; + while components_matched.contains(&c_idx) { + c_idx += 1; + } + } else { + // Element didn't match. That might be alright, and we continue with the next unmatched component c_idx += 1; + while components_matched.contains(&c_idx) { + c_idx += 1; + } } } } @@ -542,20 +674,47 @@ fn match_group_all_any_order<'a>( fn match_group_juxtaposition<'a>( raw_input: &'a [CssValue], components: &[SyntaxComponent], + mut shorthand_resolver: Option, ) -> MatchResult<'a> { let mut input = raw_input; let mut matched_values = vec![]; let mut c_idx = 0; while c_idx < components.len() { - let component = &components[c_idx]; + if let Some(mut resolver) = copy_resolver(&mut shorthand_resolver) { + let step = resolver.step(c_idx); - let res = match_component(input, component); - if res.matched { - matched_values.append(&mut res.matched_values.clone()); - input = res.remainder; + let mut complete = None; + let mut resolver = None; + + match step { + Ok(Some(r)) => resolver = Some(r), + Ok(None) => {} + Err(c) => complete = Some(c), + } + let component = &components[c_idx]; + + let res = match_component(input, component, resolver); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + input = res.remainder; + + if let Some(complete) = complete { + complete.complete(res.matched_values); + } + } else { + break; + } } else { - break; + let component = &components[c_idx]; + + let res = match_component(input, component, None); + if res.matched { + matched_values.append(&mut res.matched_values.clone()); + input = res.remainder; + } else { + break; + } } c_idx += 1; @@ -802,19 +961,19 @@ mod tests { let tree = CssSyntax::new("auto none block").compile().unwrap(); if let SyntaxComponent::Group { components, .. } = &tree.components[0] { let input = [str!("auto")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); let input = [str!("auto"), str!("none")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); let input = [str!("auto"), str!("none"), str!("block")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_match!(res); let input = [str!("none"), str!("block"), str!("auto")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); let input = [ @@ -824,12 +983,12 @@ mod tests { str!("auto"), str!("none"), ]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); let input = [str!("none"), str!("banana"), str!("car"), str!("block")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_not_match!(res); } } @@ -842,15 +1001,15 @@ mod tests { .unwrap(); if let SyntaxComponent::Group { components, .. } = &tree.components[0] { let input = [str!("top"), str!("up"), str!("strange")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_match!(res); let input = [str!("bottom"), str!("up"), str!("strange")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_match!(res); let input = [str!("bottom"), str!("down"), str!("charm")]; - let res = match_group_juxtaposition(&input, components); + let res = match_group_juxtaposition(&input, components, None); assert_match!(res); } } @@ -861,20 +1020,20 @@ mod tests { if let SyntaxComponent::Group { components, .. } = &tree.components[0] { let input = [str!("auto")]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_not_match!(res); let input = [str!("auto"), str!("none")]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_not_match!(res); let input = [str!("auto"), str!("none"), str!("block")]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_match!(res); let input = [str!("none"), str!("block"), str!("auto")]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_match!(res); let input = [ @@ -885,12 +1044,12 @@ mod tests { str!("none"), ]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_not_match!(res); let input = [str!("none"), str!("banana"), str!("car"), str!("block")]; - let res = match_group_all_any_order(&input, components); + let res = match_group_all_any_order(&input, components, None); assert_not_match!(res); } } @@ -900,23 +1059,23 @@ mod tests { let tree = CssSyntax::new("auto none block").compile().unwrap(); if let SyntaxComponent::Group { components, .. } = &tree.components[0] { let input = [str!("auto")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); let input = [str!("auto"), str!("none")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); let input = [str!("auto"), str!("none"), str!("block")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); let input = [str!("none"), str!("block"), str!("auto")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); let input = [str!("none"), str!("block"), str!("auto")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); let input = [ @@ -928,17 +1087,17 @@ mod tests { str!("none"), ]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); assert_eq!(vec![str!("none"), str!("block")], res.matched_values); let input = [str!("none"), str!("block"), str!("banana"), str!("auto")]; - let res = match_group_at_least_one_any_order(&input, components); + let res = match_group_at_least_one_any_order(&input, components, None); assert_match!(res); assert_eq!(vec![str!("none"), str!("block")], res.matched_values); assert_eq!(vec![str!("banana"), str!("auto")], res.remainder); - let res = match_group_at_least_one_any_order(&[], components); + let res = match_group_at_least_one_any_order(&[], components, None); assert_not_match!(res); } } @@ -1132,6 +1291,7 @@ mod tests { inherited: false, initial_value: None, resolved: false, + shorthands: None, }, ); definitions.resolve(); @@ -1177,6 +1337,7 @@ mod tests { inherited: false, initial_value: None, resolved: false, + shorthands: None, }, ); definitions.resolve(); @@ -1228,6 +1389,7 @@ mod tests { inherited: false, initial_value: None, resolved: false, + shorthands: None, }, ); definitions.resolve(); @@ -1251,7 +1413,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::Once], }, - 0 + 0, ), Fulfillment::NotYetFulfilled ); @@ -1263,7 +1425,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::Once], }, - 1 + 1, ), Fulfillment::Fulfilled ); @@ -1275,7 +1437,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::Once], }, - 2 + 2, ), Fulfillment::NotFulfilled ); @@ -1287,7 +1449,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::ZeroOrMore], }, - 0 + 0, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1299,7 +1461,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::ZeroOrMore], }, - 1 + 1, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1311,7 +1473,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::ZeroOrMore], }, - 2 + 2, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1323,7 +1485,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::OneOrMore], }, - 0 + 0, ), Fulfillment::NotYetFulfilled ); @@ -1335,7 +1497,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::OneOrMore], }, - 1 + 1, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1347,7 +1509,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::OneOrMore], }, - 2 + 2, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1359,7 +1521,7 @@ mod tests { combinator: GroupCombinators::Juxtaposition, multipliers: vec![SyntaxComponentMultiplier::Optional], }, - 0 + 0, ), Fulfillment::FulfilledButMoreAllowed ); @@ -1389,6 +1551,7 @@ mod tests { inherited: false, initial_value: None, resolved: false, + shorthands: None, }, ); definitions.resolve(); diff --git a/crates/gosub_taffy/src/compute/inline.rs b/crates/gosub_taffy/src/compute/inline.rs index 842236975..7b679812a 100644 --- a/crates/gosub_taffy/src/compute/inline.rs +++ b/crates/gosub_taffy/src/compute/inline.rs @@ -80,9 +80,6 @@ pub fn compute_inline_layout>( size.height = size.height.min(height); } - dbg!(size); - dbg!(content_size); - LayoutOutput { size, content_size, diff --git a/crates/gosub_vello/src/lib.rs b/crates/gosub_vello/src/lib.rs index fed09519b..4cc55c757 100644 --- a/crates/gosub_vello/src/lib.rs +++ b/crates/gosub_vello/src/lib.rs @@ -164,7 +164,7 @@ impl RenderBackend for VelloBackend { &window_data.scene.0, &surface_texture, &RenderParams { - base_color: VelloColor::BLACK, + base_color: VelloColor::WHITE, width, height, antialiasing_method: AaConfig::Msaa16,