diff --git a/crates/gosub_html5/src/parser/document.rs b/crates/gosub_html5/src/parser/document.rs index 518327993..51c3b4b84 100755 --- a/crates/gosub_html5/src/parser/document.rs +++ b/crates/gosub_html5/src/parser/document.rs @@ -1,3 +1,16 @@ +use core::fmt; +use core::fmt::Debug; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::Display; +use std::ops::{Deref, DerefMut}; +use std::rc::{Rc, Weak}; + +use url::Url; + +use gosub_css3::stylesheet::CssStylesheet; +use gosub_shared::types::Result; + use crate::element_class::ElementClass; use crate::errors::Error; use crate::node::arena::NodeArena; @@ -11,16 +24,6 @@ use crate::parser::quirks::QuirksMode; use crate::parser::tree_builder::TreeBuilder; use crate::util::is_valid_id_attribute_value; use crate::visit::Visitor; -use core::fmt; -use core::fmt::Debug; -use gosub_css3::stylesheet::CssStylesheet; -use gosub_shared::types::Result; -use std::cell::RefCell; -use std::collections::HashMap; -use std::fmt::Display; -use std::ops::{Deref, DerefMut}; -use std::rc::{Rc, Weak}; -use url::Url; /// Type of the given document #[derive(PartialEq, Debug, Copy, Clone)] @@ -276,6 +279,9 @@ impl Document { } } + pub fn count_nodes(&self) -> usize { + self.arena.count_nodes() + } /// Returns a shared reference-counted handle for the document pub fn shared(location: Option) -> DocumentHandle { DocumentHandle(Rc::new(RefCell::new(Self::new(location)))) @@ -1019,12 +1025,13 @@ impl Iterator for TreeIterator { #[cfg(test)] mod tests { + use std::collections::HashMap; + use crate::node::{NodeTrait, NodeType, HTML_NAMESPACE}; use crate::parser::document::{DocumentBuilder, DocumentTaskQueue, TreeIterator}; use crate::parser::query::Query; use crate::parser::tree_builder::TreeBuilder; use crate::parser::{Node, NodeData, NodeId}; - use std::collections::HashMap; #[test] fn relocate() { diff --git a/crates/gosub_styling/src/css_node_tree.rs b/crates/gosub_styling/src/css_values.rs similarity index 77% rename from crates/gosub_styling/src/css_node_tree.rs rename to crates/gosub_styling/src/css_values.rs index 4b238964a..b44bbf230 100644 --- a/crates/gosub_styling/src/css_node_tree.rs +++ b/crates/gosub_styling/src/css_values.rs @@ -1,91 +1,22 @@ -use crate::css_colors::RgbColor; use core::fmt::Debug; -use gosub_css3::stylesheet::{ - CssOrigin, CssSelector, CssSelectorPart, CssSelectorType, MatcherType, Specificity, -}; -use gosub_html5::node::NodeId; -use gosub_html5::parser::document::{DocumentHandle, TreeIterator}; -use gosub_shared::types::Result; use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Display; -/// Generates a css node tree for the given document based on its loaded stylesheets -pub fn generate_css_node_tree(document: DocumentHandle) -> Result { - // Restart css map - let mut css_node_tree = CssNodeTree::new(DocumentHandle::clone(&document)); - - // Iterate the complete document tree - let tree_iterator = TreeIterator::new(&document); - for current_node_id in tree_iterator { - let mut css_map_entry = CssProperties::new(); - - let binding = document.get(); - let node = binding - .get_node_by_id(current_node_id) - .expect("node not found"); - if !node.is_element() { - continue; - } - - for sheet in document.get().stylesheets.iter() { - for rule in sheet.rules.iter() { - for selector in rule.selectors().iter() { - if !match_selector(DocumentHandle::clone(&document), current_node_id, selector) - { - continue; - } - - // Selector matched, so we add all declared values to the map - for declaration in rule.declarations().iter() { - let prop_name = declaration.property.clone(); - - let declaration = DeclarationProperty { - value: CssValue::String(declaration.value.clone()), // @TODO: parse the value into the correct CSSValue - origin: sheet.origin.clone(), - important: declaration.important, - location: sheet.location.clone(), - specificity: selector.specificity(), - }; - - if let std::collections::hash_map::Entry::Vacant(e) = - css_map_entry.properties.entry(prop_name.clone()) - { - 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(&prop_name).unwrap(); - entry.declared.push(declaration); - } - } - } - } - } - - css_node_tree.nodes.insert(current_node_id, css_map_entry); - } - - for (node_id, props) in css_node_tree.nodes.iter() { - println!("Node: {:?}", node_id); - for (prop, values) in props.properties.iter() { - println!(" {}", prop); - if prop == "color" { - for decl in values.declared.iter() { - println!( - " {:?} {:?} {:?} {:?}", - decl.origin, decl.location, decl.value, decl.specificity - ); - } - } - } - } +use gosub_css3::stylesheet::{ + CssOrigin, CssSelector, CssSelectorPart, CssSelectorType, MatcherType, Specificity, +}; +use gosub_html5::node::NodeId; +use gosub_html5::parser::document::DocumentHandle; - Ok(css_node_tree) -} +use crate::css_colors::RgbColor; // Matches a complete selector (all parts) against the given node(id) -fn match_selector(document: DocumentHandle, node_id: NodeId, selector: &CssSelector) -> bool { +pub(crate) fn match_selector( + document: DocumentHandle, + node_id: NodeId, + selector: &CssSelector, +) -> bool { let mut parts = selector.parts.clone(); parts.reverse(); match_selector_part(document, node_id, &mut parts) @@ -451,7 +382,7 @@ impl CssProperty { /// Map of all declared values for a single node. Note that these are only the defined properties, not /// the non-existing properties. pub struct CssProperties { - properties: HashMap, + pub(crate) properties: HashMap, } impl Default for CssProperties { @@ -495,58 +426,6 @@ impl Display for CssValue { } } -/// Map of all declared values for all nodes in the document -pub struct CssNodeTree { - nodes: HashMap, - document: DocumentHandle, -} - -impl CssNodeTree { - /// Creates a new CssNodeTree for the given document - pub fn new(doc: DocumentHandle) -> Self { - Self { - document: doc, - nodes: HashMap::new(), - } - } - - /// Returns a clone of the document handle - pub fn get_document(&self) -> DocumentHandle { - DocumentHandle::clone(&self.document) - } - - /// Mark the given node as dirty, so it will be recalculated - pub fn mark_dirty(&mut self, node_id: NodeId) { - if let Some(props) = self.nodes.get_mut(&node_id) { - for prop in props.properties.values_mut() { - prop.mark_dirty(); - } - } - } - - /// Mark the given node as clean, so it will not be recalculated - pub fn mark_clean(&mut self, node_id: NodeId) { - if let Some(props) = self.nodes.get_mut(&node_id) { - for prop in props.properties.values_mut() { - prop.mark_clean(); - } - } - } - - /// Retrieves the property for the given node, or None when not found - pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option { - let props = self.nodes.get(&node_id); - props?; - - props.expect("props").properties.get(prop_name).cloned() - } - - /// Retrieves the value for the given property for the given node, or None when not found - pub fn get_all_properties(&self, node_id: NodeId) -> Option<&CssProperties> { - self.nodes.get(&node_id) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/gosub_styling/src/lib.rs b/crates/gosub_styling/src/lib.rs index 756e4f4c2..3017daf79 100644 --- a/crates/gosub_styling/src/lib.rs +++ b/crates/gosub_styling/src/lib.rs @@ -3,15 +3,17 @@ //! This crate connects CSS3 and HTML5 into a styling pipeline //! +use std::fs; + use gosub_css3::convert::ast_converter::convert_ast_to_stylesheet; use gosub_css3::parser_config::ParserConfig; use gosub_css3::stylesheet::{CssOrigin, CssStylesheet}; use gosub_css3::Css3; -use std::fs; pub mod css_colors; -pub mod css_node_tree; +pub mod css_values; mod property_list; +pub mod render_tree; /// Loads the default user agent stylesheet pub fn load_default_useragent_stylesheet() -> anyhow::Result { diff --git a/crates/gosub_styling/src/property_list.rs b/crates/gosub_styling/src/property_list.rs index dab2aafe8..0a2f9b587 100644 --- a/crates/gosub_styling/src/property_list.rs +++ b/crates/gosub_styling/src/property_list.rs @@ -1,6 +1,7 @@ -use crate::css_node_tree::CssValue; use lazy_static::lazy_static; +use crate::css_values::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 diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs new file mode 100644 index 000000000..1546bd1d1 --- /dev/null +++ b/crates/gosub_styling/src/render_tree.rs @@ -0,0 +1,199 @@ +use std::collections::HashMap; + +use gosub_html5::node::{NodeData, NodeId}; +use gosub_html5::parser::document::{DocumentHandle, TreeIterator}; +use gosub_shared::types::Result; + +use crate::css_values::{ + match_selector, CssProperties, CssProperty, CssValue, DeclarationProperty, +}; + +/// Map of all declared values for all nodes in the document +#[derive(Default)] +pub struct RenderTree { + pub nodes: HashMap, +} + +impl RenderTree { + pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { + self.nodes.remove_entry(id) + } + + fn remove_unrenderable_nodes(&mut self) { + // There are more elements that are not renderable, but for now we only remove the most common ones + let removable_elements = ["head", "script", "style", "svg"]; + + let mut delete = Vec::new(); + + for (id, node) in &self.nodes { + if let NodeData::Element(element) = &node.data { + if removable_elements.contains(&element.name.as_str()) { + delete.push(*id); + continue; + } + } + + // Check CSS styles and remove if not renderable + if let Some(mut prop) = self.get_property(*id, "display") { + if prop.compute_value().to_string() == "none" { + delete.push(*id); + continue; + } + } + } + + for id in delete { + self.delete_node(&id); + } + } +} + +pub struct RenderTreeNode { + pub properties: CssProperties, + pub children: Vec, + pub parent: Option, + pub name: String, + pub namespace: Option, + pub data: NodeData, +} + +impl RenderTreeNode { + /// Returns true if the node is an element node + pub fn is_element(&self) -> bool { + matches!(self.data, NodeData::Element(_)) + } + + /// Returns true if the node is a text node + pub fn is_text(&self) -> bool { + matches!(self.data, NodeData::Text(_)) + } +} + +impl RenderTree { + pub fn with_capacity(capacity: usize) -> Self { + Self { + nodes: HashMap::with_capacity(capacity), + } + } + + /// Mark the given node as dirty, so it will be recalculated + pub fn mark_dirty(&mut self, node_id: NodeId) { + if let Some(props) = self.nodes.get_mut(&node_id) { + for prop in props.properties.properties.values_mut() { + prop.mark_dirty(); + } + } + } + + /// Mark the given node as clean, so it will not be recalculated + pub fn mark_clean(&mut self, node_id: NodeId) { + if let Some(props) = self.nodes.get_mut(&node_id) { + for prop in props.properties.properties.values_mut() { + prop.mark_clean(); + } + } + } + + /// Retrieves the property for the given node, or None when not found + pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option { + let props = self.nodes.get(&node_id); + props?; + + props + .expect("props") + .properties + .properties + .get(prop_name) + .cloned() + } + + /// Retrieves the value for the given property for the given node, or None when not found + pub fn get_all_properties(&self, node_id: NodeId) -> Option<&CssProperties> { + self.nodes.get(&node_id).map(|props| &props.properties) + } +} + +/// Generates a render tree for the given document based on its loaded stylesheets +pub fn generate_render_tree(document: DocumentHandle) -> Result { + // Restart css map + let mut render_tree = RenderTree::with_capacity(document.get().count_nodes()); + + // Iterate the complete document tree + let tree_iterator = TreeIterator::new(&document); + for current_node_id in tree_iterator { + let mut css_map_entry = CssProperties::new(); + + let binding = document.get(); + let node = binding + .get_node_by_id(current_node_id) + .expect("node not found"); + if !node.is_element() { + continue; + } + + for sheet in document.get().stylesheets.iter() { + for rule in sheet.rules.iter() { + for selector in rule.selectors().iter() { + if !match_selector(DocumentHandle::clone(&document), current_node_id, selector) + { + continue; + } + + // Selector matched, so we add all declared values to the map + for declaration in rule.declarations().iter() { + let prop_name = declaration.property.clone(); + + let declaration = DeclarationProperty { + value: CssValue::String(declaration.value.clone()), // @TODO: parse the value into the correct CSSValue + origin: sheet.origin.clone(), + important: declaration.important, + location: sheet.location.clone(), + specificity: selector.specificity(), + }; + + if let std::collections::hash_map::Entry::Vacant(e) = + css_map_entry.properties.entry(prop_name.clone()) + { + 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(&prop_name).unwrap(); + entry.declared.push(declaration); + } + } + } + } + } + + let render_tree_node = RenderTreeNode { + properties: css_map_entry, + children: Vec::new(), + parent: node.parent, + name: node.name.clone(), // We might be able to move node into render_tree_node + namespace: node.namespace.clone(), + data: node.data.clone(), + }; + + render_tree.nodes.insert(current_node_id, render_tree_node); + } + + for (node_id, render_node) in render_tree.nodes.iter() { + println!("Node: {:?}", node_id); + for (prop, values) in render_node.properties.properties.iter() { + println!(" {}", prop); + if prop == "color" { + for decl in values.declared.iter() { + println!( + " {:?} {:?} {:?} {:?}", + decl.origin, decl.location, decl.value, decl.specificity + ); + } + } + } + } + + render_tree.remove_unrenderable_nodes(); + + Ok(render_tree) +} diff --git a/src/bin/style-parser.rs b/src/bin/style-parser.rs index 72a719287..8fef1a333 100644 --- a/src/bin/style-parser.rs +++ b/src/bin/style-parser.rs @@ -1,4 +1,8 @@ +use std::fs; + use anyhow::{bail, Result}; +use url::Url; + use gosub_html5::node::data::comment::CommentData; use gosub_html5::node::data::doctype::DocTypeData; use gosub_html5::node::data::document::DocumentData; @@ -10,17 +14,16 @@ 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::css_node_tree::{generate_css_node_tree, CssNodeTree, CssValue}; -use std::fs; -use url::Url; +use gosub_styling::css_values::CssValue; +use gosub_styling::render_tree::{generate_render_tree, RenderTree}; struct TextVisitor { color: String, - css_nodetree: CssNodeTree, + css_nodetree: RenderTree, } impl TextVisitor { - fn new(css_node_tree: CssNodeTree) -> Self { + fn new(css_node_tree: RenderTree) -> Self { Self { color: String::from(""), css_nodetree: css_node_tree, @@ -133,9 +136,9 @@ fn main() -> Result<()> { let _parse_errors = Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?; - let css_tree = generate_css_node_tree(Document::clone(&doc_handle))?; + let render_tree = generate_render_tree(Document::clone(&doc_handle))?; - let mut visitor = Box::new(TextVisitor::new(css_tree)) as Box>; + let mut visitor = Box::new(TextVisitor::new(render_tree)) as Box>; visit(&Document::clone(&doc_handle), &mut visitor); Ok(())