diff --git a/crates/gosub_styling/src/css_node_tree.rs b/crates/gosub_styling/src/css_node_tree.rs index 2026605fc..6292453af 100644 --- a/crates/gosub_styling/src/css_node_tree.rs +++ b/crates/gosub_styling/src/css_node_tree.rs @@ -35,7 +35,7 @@ pub fn generate_css_node_tree(document: DocumentHandle) -> Result { // Selector matched, so we add all declared values to the map for declaration in rule.declarations().iter() { - let property = declaration.property.clone(); + let prop_name = declaration.property.clone(); let declaration = DeclarationProperty { value: CssValue::String(declaration.value.clone()), // @TODO: parse the value into the correct CSSValue @@ -46,13 +46,13 @@ pub fn generate_css_node_tree(document: DocumentHandle) -> Result { }; if let std::collections::hash_map::Entry::Vacant(e) = - css_map_entry.properties.entry(property.clone()) + css_map_entry.properties.entry(prop_name.clone()) { - let mut entry = CssProperty::new(); + let mut entry = CssProperty::new(prop_name.as_str()); entry.declared.push(declaration); e.insert(entry); } else { - let entry = css_map_entry.properties.get_mut(&property).unwrap(); + let entry = css_map_entry.properties.get_mut(&prop_name).unwrap(); entry.declared.push(declaration); } } @@ -302,6 +302,8 @@ impl Ord for DeclarationProperty { /// all the computed values. #[derive(Debug, Clone)] pub struct CssProperty { + /// The name of the property + pub name: String, /// True when this property needs to be recalculated pub dirty: bool, /// List of all declared values for this property @@ -317,15 +319,10 @@ pub struct CssProperty { pub actual: CssValue, } -impl Default for CssProperty { - fn default() -> Self { - Self::new() - } -} - impl CssProperty { - pub fn new() -> Self { + pub fn new(prop_name: &str) -> Self { Self { + name: prop_name.to_string(), dirty: true, declared: Vec::new(), cascaded: None, @@ -355,17 +352,58 @@ impl CssProperty { } fn calculate_value(&mut self) { - // Step 1: Find cascaded value + self.cascaded = self.find_cascaded_value(); + self.specified = self.find_specified_value(); + self.computed = self.find_computed_value(); + self.used = self.find_used_value(); + self.actual = self.find_actual_value(); + } + + fn find_cascaded_value(&self) -> Option { + let mut declared = self.declared.clone(); + + declared.sort(); + declared.sort_by(|a, b| { + if a.priority() == b.priority() { + return Ordering::Equal; + } + + a.specificity.cmp(&b.specificity) + }); + + declared.last().map(|d| d.value.clone()) + } - // Step 2: Find specified value + fn find_specified_value(&self) -> CssValue { + match self.declared.iter().max() { + Some(decl) => decl.value.clone(), + None => CssValue::None, + } + } - // Step 3: Find computed value (needs viewport?) + fn find_computed_value(&self) -> CssValue { + if self.specified != CssValue::None { + return self.specified.clone(); + } - // Step 4: Find used value (??) + if self.is_inheritable() { + while let Some(parent) = self.get_parent() { + if let Some(parent_value) = parent { + return parent_value.find_computed_value(); + } + } + } - // Step 5: Find actual value by rounding numbers (when possible) + self.get_initial_value() + } + + fn find_used_value(&self) -> CssValue { + self.computed.clone() + } + + fn find_actual_value(&self) -> CssValue { // @TODO: stuff like clipping and such should occur as well - self.actual = match &self.used { + match &self.used { CssValue::Number(len) => { CssValue::Number(len.round()) } @@ -378,6 +416,24 @@ impl CssProperty { _ => self.used.clone() } } + + + // Returns true when the property is inheritable, false otherwise + fn is_inheritable(&self) -> bool { + crate::property_list::PROPERTY_TABLE + .iter() + .find(|entry| entry.name == self.name) + .map(|entry| entry.inheritable) + .unwrap_or(false) + } + + // Returns the initial value for the property, if any + fn get_initial_value(&self) -> Option { + crate::property_list::PROPERTY_TABLE + .iter() + .find(|entry| entry.name == self.name) + .map(|entry| entry.initial) + } } /// Map of all declared values for a single node. Note that these are only the defined properties, not @@ -402,7 +458,7 @@ impl CssProperties { /// Actual CSS value, can be a color, length, percentage, string or unit. Some relative values will be computed /// from other values (ie: Percent(50) will convert to Length(100) when the parent width is 200) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum CssValue { None, Color(RgbColor), @@ -472,9 +528,6 @@ impl CssNodeTree { /// Retrieves the property for the given node, or None when not found pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option { - if node_id == NodeId::from(71) { - println!("get_property({:?}, {})", node_id, prop_name); - } let props = self.nodes.get(&node_id); if props.is_none() { return None; @@ -490,23 +543,6 @@ impl CssNodeTree { pub fn get_all_properties(&self, node_id: NodeId) -> Option<&CssProperties> { self.nodes.get(&node_id) } - - // Returns true when the property is inheritable, false otherwise - pub fn is_inheritable(&self, prop_name: &str) -> bool { - crate::property_list::PROPERTY_TABLE - .iter() - .find(|entry| entry.name == prop_name) - .map(|entry| entry.inheritable) - .unwrap_or(false) - } - - // Returns the initial value for the property, if any - pub fn get_initial_value(&self, prop_name: &str) -> Option<&str> { - crate::property_list::PROPERTY_TABLE - .iter() - .find(|entry| entry.name == prop_name) - .map(|entry| entry.initial) - } } #[cfg(test)] diff --git a/crates/gosub_styling/src/property_list.rs b/crates/gosub_styling/src/property_list.rs index 87903bc0b..cec538123 100644 --- a/crates/gosub_styling/src/property_list.rs +++ b/crates/gosub_styling/src/property_list.rs @@ -1,11 +1,12 @@ use lazy_static::lazy_static; +use crate::css_node_tree::CssValue; // Values for this table is taken from https://www.w3.org/TR/CSS21/propidx.html // Probably not the complete list, but it will do for now pub struct PropertyTableEntry { pub(crate) name: &'static str, - pub(crate) initial: &'static str, + pub(crate) initial: CssValue, pub(crate) inheritable: bool, } @@ -13,562 +14,562 @@ lazy_static! { pub static ref PROPERTY_TABLE: &'static [PropertyTableEntry] = &[ PropertyTableEntry { name: "azimuth", - initial: "center", + initial: CssValue::String("center".into()), inheritable: true, }, PropertyTableEntry { name: "background-attachment", - initial: "scroll", + initial: CssValue::String("scroll".into()), inheritable: false, }, PropertyTableEntry { name: "background-color", - initial: "transparent", + initial: CssValue::String("transparent".into()), inheritable: false, }, PropertyTableEntry { name: "background-image", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "background-position", - initial: "0% 0%", + initial: CssValue::String("0% 0%".into()), inheritable: false, }, PropertyTableEntry { name: "background-repeat", - initial: "repeat", + initial: CssValue::String("repeat".into()), inheritable: false, }, PropertyTableEntry { name: "border-collapse", - initial: "separate", + initial: CssValue::String("separate".into()), inheritable: false, }, PropertyTableEntry { name: "border-color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-spacing", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "border-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "border-top", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-right", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-bottom", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-left", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-top-color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-right-color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-bottom-color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-left-color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "border-top-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "border-right-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "border-bottom-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "border-left-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "border-top-width", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "border-right-width", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "border-bottom-width", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "border-left-width", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "border-width", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "bottom", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "caption-side", - initial: "top", + initial: CssValue::String("top".into()), inheritable: false, }, PropertyTableEntry { name: "clear", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "clip", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "color", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: true, }, PropertyTableEntry { name: "content", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: false, }, PropertyTableEntry { name: "counter-increment", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "counter-reset", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "cue", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "cue-after", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "cue-before", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "cursor", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "direction", - initial: "ltr", + initial: CssValue::String("ltr".into()), inheritable: true, }, PropertyTableEntry { name: "display", - initial: "inline", + initial: CssValue::String("inline".into()), inheritable: false, }, PropertyTableEntry { name: "elevation", - initial: "level", + initial: CssValue::String("level".into()), inheritable: false, }, PropertyTableEntry { name: "empty-cells", - initial: "show", + initial: CssValue::String("show".into()), inheritable: false, }, PropertyTableEntry { name: "float", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "font", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "font-family", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "font-size", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: true, }, PropertyTableEntry { name: "font-style", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "font-variant", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "font-weight", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "height", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "left", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "letter-spacing", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "line-height", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "list-style", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "list-style-image", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "list-style-position", - initial: "outside", + initial: CssValue::String("outside".into()), inheritable: false, }, PropertyTableEntry { name: "list-style-type", - initial: "disc", + initial: CssValue::String("disc".into()), inheritable: false, }, PropertyTableEntry { name: "margin", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "margin-top", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "margin-right", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "margin-bottom", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "margin-left", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "max-height", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "max-width", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "min-height", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "min-width", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "orphans", - initial: "2", + initial: CssValue::Number(2_f32), inheritable: true, }, PropertyTableEntry { name: "outline", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "outline-color", - initial: "invert", + initial: CssValue::String("invert".into()), inheritable: false, }, PropertyTableEntry { name: "outline-style", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "outline-width", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "overflow", - initial: "visible", + initial: CssValue::String("visible".into()), inheritable: false, }, PropertyTableEntry { name: "padding", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "padding-top", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "padding-right", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "padding-bottom", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "padding-left", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "page-break-after", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "page-break-before", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "page-break-inside", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "pause-after", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "pause-before", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: false, }, PropertyTableEntry { name: "pitch", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "pitch-range", - initial: "50", + initial: CssValue::Number(50_f32), inheritable: false, }, PropertyTableEntry { name: "play-during", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "position", - initial: "static", + initial: CssValue::String("static".into()), inheritable: false, }, PropertyTableEntry { name: "quotes", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "richness", - initial: "50", + initial: CssValue::Number(50_f32), inheritable: false, }, PropertyTableEntry { name: "right", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "speak", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: false, }, PropertyTableEntry { name: "speak-header", - initial: "once", + initial: CssValue::String("once".into()), inheritable: false, }, PropertyTableEntry { name: "speak-numeral", - initial: "continuous", + initial: CssValue::String("continuous".into()), inheritable: false, }, PropertyTableEntry { name: "speak-punctuation", - initial: "none", + initial: CssValue::String("none".into()), inheritable: false, }, PropertyTableEntry { name: "speech-rate", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "stress", - initial: "50", + initial: CssValue::Number(50_f32), inheritable: false, }, PropertyTableEntry { name: "table-layout", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "text-align", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: true, }, PropertyTableEntry { name: "text-decoration", - initial: "none", + initial: CssValue::String("none".into()), inheritable: true, }, PropertyTableEntry { name: "text-indent", - initial: "0", + initial: CssValue::Number(0_f32), inheritable: true, }, PropertyTableEntry { name: "text-transform", - initial: "none", + initial: CssValue::String("none".into()), inheritable: true, }, PropertyTableEntry { name: "top", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "unicode-bidi", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "vertical-align", - initial: "baseline", + initial: CssValue::String("baseline".into()), inheritable: true, }, PropertyTableEntry { name: "visibility", - initial: "visible", + initial: CssValue::String("visible".into()), inheritable: false, }, PropertyTableEntry { name: "voice-family", - initial: "initial", + initial: CssValue::String("initial".into()), inheritable: false, }, PropertyTableEntry { name: "volume", - initial: "medium", + initial: CssValue::String("medium".into()), inheritable: false, }, PropertyTableEntry { name: "white-space", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "widows", - initial: "2", + initial: CssValue::Number(2_f32), inheritable: true, }, PropertyTableEntry { name: "width", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, PropertyTableEntry { name: "word-spacing", - initial: "normal", + initial: CssValue::String("normal".into()), inheritable: true, }, PropertyTableEntry { name: "z-index", - initial: "auto", + initial: CssValue::String("auto".into()), inheritable: false, }, ]; diff --git a/src/bin/style-parser.rs b/src/bin/style-parser.rs index b0c63ad87..9f0994bc3 100644 --- a/src/bin/style-parser.rs +++ b/src/bin/style-parser.rs @@ -5,12 +5,11 @@ use gosub_html5::node::data::document::DocumentData; use gosub_html5::node::data::element::ElementData; use gosub_html5::node::data::text::TextData; use gosub_html5::node::Node; -use gosub_html5::parser::document::{DocumentBuilder, DocumentHandle}; +use gosub_html5::parser::document::{DocumentBuilder}; use gosub_html5::parser::document::{visit, Document}; use gosub_html5::parser::Html5Parser; use gosub_html5::visit::Visitor; use gosub_shared::bytes::{CharIterator, Confidence, Encoding}; -use gosub_styling::pipeline::Pipeline; use std::fs; use url::Url; use gosub_styling::css_node_tree::{CssNodeTree, CssValue, generate_css_node_tree}; @@ -21,10 +20,10 @@ struct TextVisitor { } impl TextVisitor { - fn new(doc: DocumentHandle) -> Self { + fn new(css_node_tree: CssNodeTree) -> Self { Self { color: String::from(""), - css_nodetree: CssNodeTree::new(doc), + css_nodetree: css_node_tree, } } } @@ -67,7 +66,9 @@ impl Visitor for TextVisitor { Some(mut prop) => { match prop.compute_value() { CssValue::Color(col) => { - print!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b) + println!("Color EL ENTER: {:?}", col); + // print!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b) + self.color = format!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b) } _ => {} } @@ -79,6 +80,8 @@ impl Visitor for TextVisitor { Some(mut prop) => { match prop.compute_value() { CssValue::Color(col) => { + println!("BGCOL EL ENTER: {:?}", col); + print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b) } _ => {} @@ -95,6 +98,8 @@ impl Visitor for TextVisitor { Some(mut prop) => { match prop.compute_value() { CssValue::Color(col) => { + println!("COL EL LEAVE: {:?}", col); + self.color = format!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b) // print!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b); } @@ -108,6 +113,7 @@ impl Visitor for TextVisitor { Some(mut prop) => { match prop.compute_value() { CssValue::Color(col) => { + println!("BGCOL EL LEAVE: {:?}", col); print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b) } _ => {} @@ -169,11 +175,11 @@ fn main() -> Result<()> { // calculator.find_used_values(/*layout*/); // we need to have a layout for calculating these values // calculator.find_actual_values(); // Makes sure we use 2px instead of a computed 2.25px - let pipeline = Pipeline::new(); - let render_tree = pipeline.generate_render_tree(css_tree); + // let pipeline = Pipeline::new(); + // let render_tree = pipeline.generate_render_tree(css_tree); - let mut visitor = Box::new(TextVisitor::new(Document::clone(&doc_handle))) as Box>; - visit(&Document::clone(&render_tree), &mut visitor); + let mut visitor = Box::new(TextVisitor::new(css_tree)) as Box>; + visit(&Document::clone(&doc_handle), &mut visitor); Ok(()) }