From 6b952fd310e5ab859434fe12d915c843b7fe19ae Mon Sep 17 00:00:00 2001 From: Shark Date: Thu, 19 Sep 2024 00:18:36 +0200 Subject: [PATCH] WIP: split css things out of render backend --- crates/gosub_css3/src/lib.rs | 58 +- crates/gosub_css3/src/matcher/styling.rs | 132 ++++- crates/gosub_css3/src/parser.rs | 47 +- crates/gosub_css3/src/parser/anplusb.rs | 52 +- crates/gosub_css3/src/parser/at_rule.rs | 15 +- .../src/parser/at_rule/container.rs | 7 +- .../gosub_css3/src/parser/at_rule/import.rs | 14 +- crates/gosub_css3/src/parser/at_rule/media.rs | 24 +- crates/gosub_css3/src/parser/block.rs | 7 +- crates/gosub_css3/src/parser/pseudo.rs | 15 +- crates/gosub_css3/src/parser/selector.rs | 28 +- crates/gosub_css3/src/parser/stylesheet.rs | 9 +- crates/gosub_css3/src/stylesheet.rs | 79 ++- crates/gosub_css3/src/system.rs | 256 ++++++++ crates/gosub_css3/src/tokenizer.rs | 2 +- crates/gosub_html5/src/document/builder.rs | 2 +- crates/gosub_html5/src/document/document.rs | 32 +- crates/gosub_html5/src/document/task_queue.rs | 9 +- crates/gosub_html5/src/node/data/element.rs | 28 +- crates/gosub_html5/src/node/data/text.rs | 4 + crates/gosub_html5/src/node/node.rs | 126 ++-- crates/gosub_html5/src/parser.rs | 66 +-- crates/gosub_html5/src/parser/errors.rs | 2 +- crates/gosub_html5/src/parser/helper.rs | 23 +- crates/gosub_html5/src/tokenizer.rs | 36 +- crates/gosub_html5/src/tokenizer/token.rs | 12 +- crates/gosub_html5/src/writer.rs | 8 +- crates/gosub_render_backend/src/layout.rs | 37 +- .../gosub_render_backend/src/render_tree.rs | 552 ++++-------------- .../src/render_tree/desc.rs | 22 +- crates/gosub_shared/src/traits/css3.rs | 75 ++- crates/gosub_shared/src/traits/node.rs | 37 +- crates/gosub_testing/src/testing/tokenizer.rs | 24 +- 33 files changed, 991 insertions(+), 849 deletions(-) create mode 100644 crates/gosub_css3/src/system.rs diff --git a/crates/gosub_css3/src/lib.rs b/crates/gosub_css3/src/lib.rs index 5be8e804d..41890bbf1 100644 --- a/crates/gosub_css3/src/lib.rs +++ b/crates/gosub_css3/src/lib.rs @@ -3,29 +3,29 @@ use crate::errors::Error; use crate::stylesheet::CssStylesheet; use crate::tokenizer::Tokenizer; -use gosub_shared::{timing_start, timing_stop}; use gosub_shared::byte_stream::{ByteStream, Encoding, Location}; -use gosub_shared::traits::Context; use gosub_shared::traits::css3::CssOrigin; +use gosub_shared::traits::Context; use gosub_shared::traits::ParserConfig; use gosub_shared::types::Result; +use gosub_shared::{timing_start, timing_stop}; +pub mod ast; /// This CSS3 parser is heavily based on the MIT licensed CssTree parser written by /// Roman Dvornov (https://github.com/lahmatiy). /// The original version can be found at https://github.com/csstree/csstree - pub mod colors; -pub mod ast; +mod errors; +mod functions; +#[allow(dead_code)] +pub mod matcher; pub mod node; pub mod parser; pub mod stylesheet; +mod system; pub mod tokenizer; mod unicode; pub mod walker; -#[allow(dead_code)] -pub mod matcher; -mod errors; -mod functions; pub struct Css3<'stream> { /// The tokenizer is responsible for reading the input stream and @@ -42,7 +42,12 @@ pub struct Css3<'stream> { impl<'stream> Css3<'stream> { /// Creates a new parser with the given byte stream so only parse() needs to be called. - fn new(stream: &'stream mut ByteStream, config: ParserConfig, origin: CssOrigin, source: &str) -> Self { + fn new( + stream: &'stream mut ByteStream, + config: ParserConfig, + origin: CssOrigin, + source: &str, + ) -> Self { Self { tokenizer: Tokenizer::new(stream, Location::default()), allow_values_in_argument_list: Vec::new(), @@ -53,7 +58,12 @@ impl<'stream> Css3<'stream> { } /// Parses a direct string to a CssStyleSheet - pub fn parse_str(data: &str, config: ParserConfig, origin: CssOrigin, source_url: &str) -> Result { + pub fn parse_str( + data: &str, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> Result { let mut stream = ByteStream::new(Encoding::UTF8, None); stream.read_from_str(data, Some(Encoding::UTF8)); stream.close(); @@ -62,13 +72,22 @@ impl<'stream> Css3<'stream> { } /// Parses a direct stream to a CssStyleSheet - pub fn parse_stream(stream: &mut ByteStream, config: ParserConfig, origin: CssOrigin, source_url: &str) -> Result { + pub fn parse_stream( + stream: &mut ByteStream, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> Result { Css3::new(stream, config, origin, source_url).parse() } fn parse(&mut self) -> Result { if self.config.context != Context::Stylesheet { - return Err(Error::Parse("Expected a stylesheet context".to_string(), Location::default()).into()); + return Err(Error::Parse( + "Expected a stylesheet context".to_string(), + Location::default(), + ) + .into()); } let t_id = timing_start!("css3.parse", self.config.source.as_deref().unwrap_or("")); @@ -87,9 +106,13 @@ impl<'stream> Css3<'stream> { timing_stop!(t_id); match node_tree { - Ok(None) => return Err(Error::Parse("No node tree found".to_string(), Location::default()).into()), - Ok(Some(node)) => convert_ast_to_stylesheet(&node, self.origin.clone(), self.source.clone().as_str()), - Err(e) => Err(e.into()), + Ok(None) => { + Err(Error::Parse("No node tree found".to_string(), Location::default()).into()) + } + Ok(Some(node)) => { + convert_ast_to_stylesheet(&node, self.origin, self.source.clone().as_str()) + } + Err(e) => Err(e), } } } @@ -106,10 +129,10 @@ pub fn load_default_useragent_stylesheet() -> CssStylesheet { }; let css_data = include_str!("../resources/useragent.css"); - Css3::parse_str(css_data, config, CssOrigin::UserAgent, url).expect("Could not parse useragent stylesheet") + Css3::parse_str(css_data, config, CssOrigin::UserAgent, url) + .expect("Could not parse useragent stylesheet") } - #[cfg(test)] mod tests { use super::*; @@ -133,7 +156,6 @@ mod tests { let res = Css3::parse_str(css.as_str(), config, CssOrigin::Author, filename); if res.is_err() { println!("{:?}", res.err().unwrap()); - return; } // let binding = res.unwrap(); diff --git a/crates/gosub_css3/src/matcher/styling.rs b/crates/gosub_css3/src/matcher/styling.rs index ce577e172..dbb2038eb 100644 --- a/crates/gosub_css3/src/matcher/styling.rs +++ b/crates/gosub_css3/src/matcher/styling.rs @@ -1,17 +1,19 @@ use core::fmt::Debug; -use itertools::Itertools; -use std::cmp::Ordering; -use std::collections::HashMap; - -use gosub_shared::traits::node::ElementDataType; +use gosub_shared::document::DocumentHandle; use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssOrigin, CssPropertyMap, CssSystem}; use gosub_shared::traits::document::Document; +use gosub_shared::traits::node::ElementDataType; use gosub_shared::traits::node::Node; -use gosub_shared::document::DocumentHandle; -use gosub_shared::traits::css3::{CssOrigin, CssSystem}; +use itertools::Itertools; +use nom::Parser; +use std::cmp::Ordering; +use std::collections::HashMap; use crate::matcher::property_definitions::get_css_definitions; -use crate::stylesheet::{Combinator, CssSelector, CssSelectorPart, CssValue, MatcherType, Specificity}; +use crate::stylesheet::{ + Combinator, CssSelector, CssSelectorPart, CssValue, MatcherType, Specificity, +}; // Matches a complete selector (all parts) against the given node(id) pub(crate) fn match_selector, C: CssSystem>( @@ -100,14 +102,19 @@ fn match_selector_part<'a, D: Document, C: CssSystem>( if !current_node.is_element_node() { return false; } - current_node.get_element_data().unwrap().classes().contains(name) + current_node + .get_element_data() + .unwrap() + .classes() + .contains(name) } CssSelectorPart::Id(name) => { if !current_node.is_element_node() { return false; } current_node - .get_element_data().unwrap() + .get_element_data() + .unwrap() .attributes() .get("id") .unwrap_or(&"".to_string()) @@ -269,7 +276,6 @@ fn match_selector_part<'a, D: Document, C: CssSystem>( match_selector_part(last, prev, doc, next_node, parts) } Combinator::SubsequentSibling => { - let parent_node = doc.node_by_id(current_node.parent_id().unwrap()); let Some(children) = parent_node.map(|p| p.children()) else { return false; @@ -310,7 +316,10 @@ fn match_selector_part<'a, D: Document, C: CssSystem>( return false; }; - current_node.get_element_data().unwrap().is_namespace(namespace) + current_node + .get_element_data() + .unwrap() + .is_namespace(namespace) } Combinator::Column => { //TODO @@ -531,6 +540,75 @@ impl CssProperty { } } +impl gosub_shared::traits::css3::CssProperty for CssProperty { + type Value = CssValue; + + fn compute_value(&mut self) { + self.compute_value(); + } + fn unit_to_px(&self) -> f32 { + self.actual.unit_to_px() + } + + fn as_string(&self) -> Option<&str> { + if let CssValue::String(str) = &self.actual { + Some(str) + } else { + None + } + } + + fn as_percentage(&self) -> Option { + if let CssValue::Percentage(percent) = &self.actual { + Some(*percent) + } else { + None + } + } + + fn as_unit(&self) -> Option<(f32, &str)> { + if let CssValue::Unit(value, unit) = &self.actual { + Some((*value, unit)) + } else { + None + } + } + + fn as_color(&self) -> Option<(f32, f32, f32, f32)> { + if let CssValue::Color(color) = &self.actual { + Some((color.r, color.g, color.b, color.a)) + } else { + None + } + } + + fn parse_color(&self) -> Option<(f32, f32, f32, f32)> { + self.actual + .to_color() + .map(|color| (color.r, color.g, color.b, color.a)) + } + + fn as_number(&self) -> Option { + if let CssValue::Number(num) = &self.actual { + Some(*num) + } else { + None + } + } + + fn as_list(&self) -> Option> { + if let CssValue::List(list) = &self.actual { + Some(list.iter().cloned().collect()) + } else { + None + } + } + + fn is_none(&self) -> bool { + matches!(self.actual, CssValue::None) + } +} + /// Map of all declared values for a single node. Note that these are only the defined properties, not /// the non-existing properties. #[derive(Debug)] @@ -556,6 +634,34 @@ impl CssProperties { } } +impl CssPropertyMap for CssProperties { + type Property = CssProperty; + + fn get(&self, name: &str) -> Option<&Self::Property> { + todo!() + } + + fn get_mut(&mut self, name: &str) -> Option<&mut Self::Property> { + todo!() + } + + fn make_dirty(&mut self) { + todo!() + } + + fn iter(&self) -> impl Iterator + '_ { + Vec::new().into_iter() + } + + fn iter_mut(&mut self) -> impl Iterator + '_ { + Vec::new().into_iter() + } + + fn make_clean(&mut self) { + todo!() + } +} + pub fn prop_is_inherit(name: &str) -> bool { get_css_definitions() .find_property(name) @@ -565,8 +671,8 @@ pub fn prop_is_inherit(name: &str) -> bool { #[cfg(test)] mod tests { - use crate::colors::RgbColor; use super::*; + use crate::colors::RgbColor; #[test] fn css_props() { diff --git a/crates/gosub_css3/src/parser.rs b/crates/gosub_css3/src/parser.rs index 284d9cae1..6ba4aa1a6 100644 --- a/crates/gosub_css3/src/parser.rs +++ b/crates/gosub_css3/src/parser.rs @@ -1,9 +1,6 @@ -use gosub_shared::traits::css3::{CssOrigin, CssSystem}; -use gosub_shared::traits::ParserConfig; -use gosub_shared::types::Result; use crate::tokenizer::{Number, Token, TokenType}; use crate::{Css3, Error}; -use crate::stylesheet::CssStylesheet as CssStylesheetImpl; +use gosub_shared::types::Result; mod anplusb; mod at_rule; @@ -23,18 +20,6 @@ mod stylesheet; mod url; mod value; -#[derive(Debug, Clone)] -struct Css3Parser { -} - -impl CssSystem for Css3Parser { - type Stylesheet = CssStylesheetImpl; - - fn parse_str(str: &str, config: ParserConfig, origin: CssOrigin, source_url: &str) -> Result { - Css3::parse_str(str, config, origin, source_url) - } -} - impl Css3<'_> { /// Consumes a specific token pub fn consume(&mut self, token_type: TokenType) -> Result { @@ -43,7 +28,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Expected {:?}, got {:?}", token_type, t), self.tokenizer.current_location(), - ).into()); + ) + .into()); } Ok(t) @@ -61,7 +47,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected function, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -72,7 +59,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected number, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -83,7 +71,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected delimiter, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -94,7 +83,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected string, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -105,7 +95,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected delimiter '{}', got {:?}", delimiter, t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -131,7 +122,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected ident, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -142,7 +134,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected ident, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -157,14 +150,16 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Expected ident, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } TokenType::Ident(s) => Ok(s), _ => Err(Error::Parse( format!("Expected ident, got {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } diff --git a/crates/gosub_css3/src/parser/anplusb.rs b/crates/gosub_css3/src/parser/anplusb.rs index 954596949..63458a574 100644 --- a/crates/gosub_css3/src/parser/anplusb.rs +++ b/crates/gosub_css3/src/parser/anplusb.rs @@ -4,17 +4,17 @@ use crate::{Css3, Error}; use gosub_shared::types::Result; impl Css3<'_> { - fn do_dimension_block( - &mut self, - value: Number, - unit: String, - ) -> Result<(String, String)> { + fn do_dimension_block(&mut self, value: Number, unit: String) -> Result<(String, String)> { log::trace!("do_dimension_block"); let value = value.to_string(); if unit.chars().nth(0).unwrap().to_lowercase().to_string() != "n" { - return Err(Error::Parse(format!("Expected n, found {}", unit).to_string(), self.tokenizer.current_location()).into()); + return Err(Error::Parse( + format!("Expected n, found {}", unit).to_string(), + self.tokenizer.current_location(), + ) + .into()); } Ok(if unit.len() == 1 { (value.to_string(), self.parse_anplusb_b()?) @@ -23,12 +23,7 @@ impl Css3<'_> { }) } - fn check_integer( - &mut self, - value: &str, - offset: usize, - allow_sign: bool, - ) -> Result { + fn check_integer(&mut self, value: &str, offset: usize, allow_sign: bool) -> Result { let sign = value .chars() .nth(offset) @@ -39,7 +34,11 @@ impl Css3<'_> { if sign == "+" || sign == "-" { if !allow_sign { - return Err(Error::Parse(format!("Unexpected sign {}", sign).to_string(), self.tokenizer.current_location()).into()); + return Err(Error::Parse( + format!("Unexpected sign {}", sign).to_string(), + self.tokenizer.current_location(), + ) + .into()); } pos += 1; } @@ -61,7 +60,11 @@ impl Css3<'_> { .to_lowercase() .to_string(); if nval != c { - return Err(Error::Parse(format!("Expected {}", c).to_string(), self.tokenizer.current_location()).into()); + return Err(Error::Parse( + format!("Expected {}", c).to_string(), + self.tokenizer.current_location(), + ) + .into()); } Ok(true) @@ -99,7 +102,15 @@ impl Css3<'_> { false } _ => { - return Err(Error::Parse(format!("Expected +, - or number, found {:?}", self.tokenizer.lookahead(0).token_type).to_string(), self.tokenizer.current_location()).into()); + return Err(Error::Parse( + format!( + "Expected +, - or number, found {:?}", + self.tokenizer.lookahead(0).token_type + ) + .to_string(), + self.tokenizer.current_location(), + ) + .into()); } }; @@ -213,7 +224,11 @@ impl Css3<'_> { } _ => { self.tokenizer.reconsume(); - return Err(Error::Parse(format!("Expected anplusb").to_string(), self.tokenizer.current_location()).into()); + return Err(Error::Parse( + "Expected anplusb".to_string(), + self.tokenizer.current_location(), + ) + .into()); } } @@ -233,8 +248,8 @@ impl Css3<'_> { mod test { use super::*; use gosub_shared::byte_stream::{ByteStream, Encoding}; - use gosub_shared::traits::ParserConfig; use gosub_shared::traits::css3::CssOrigin; + use gosub_shared::traits::ParserConfig; macro_rules! test { ($func:ident, $input:expr, $expected:expr) => { @@ -242,7 +257,8 @@ mod test { stream.read_from_str($input, Some(Encoding::UTF8)); stream.close(); - let mut parser = crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); + let mut parser = + crate::Css3::new(&mut stream, ParserConfig::default(), CssOrigin::User, ""); let result = parser.$func().unwrap(); assert_eq!(result.node_type, $expected); diff --git a/crates/gosub_css3/src/parser/at_rule.rs b/crates/gosub_css3/src/parser/at_rule.rs index 2c0f4c5e7..206f2cb38 100644 --- a/crates/gosub_css3/src/parser/at_rule.rs +++ b/crates/gosub_css3/src/parser/at_rule.rs @@ -45,7 +45,7 @@ impl Css3<'_> { fn read_sequence_at_rule_prelude(&mut self) -> Result { log::trace!("read_sequence_at_rule_prelude"); - let loc = self.tokenizer.lookahead(0).location.clone(); + let loc = self.tokenizer.lookahead(0).location; Ok(Node::new( NodeType::Container { @@ -82,18 +82,15 @@ impl Css3<'_> { { return Err(Error::Parse( "Expected semicolon or left curly brace".to_string(), - t.location.clone(), - ).into()); + t.location, + ) + .into()); } Ok(node) } - fn parse_at_rule_block( - &mut self, - name: String, - is_declaration: bool, - ) -> Result> { + fn parse_at_rule_block(&mut self, name: String, is_declaration: bool) -> Result> { log::trace!("parse_at_rule_block"); let t = self.tokenizer.consume(); @@ -177,7 +174,7 @@ impl Css3<'_> { prelude, block, }, - t.location.clone(), + t.location, )) } } diff --git a/crates/gosub_css3/src/parser/at_rule/container.rs b/crates/gosub_css3/src/parser/at_rule/container.rs index 0f510dd52..63a81d159 100644 --- a/crates/gosub_css3/src/parser/at_rule/container.rs +++ b/crates/gosub_css3/src/parser/at_rule/container.rs @@ -12,15 +12,12 @@ impl Css3<'_> { let t = self.consume_any()?; if let TokenType::Ident(value) = t.token_type { if !["none", "and", "not", "or"].contains(&value.as_str()) { - children.push(Node::new(NodeType::Ident { value }, t.location.clone())); + children.push(Node::new(NodeType::Ident { value }, t.location)); } } children.push(self.parse_condition(FeatureKind::Container)?); - Ok(Node::new( - NodeType::Container { children }, - t.location.clone(), - )) + Ok(Node::new(NodeType::Container { children }, t.location)) } } diff --git a/crates/gosub_css3/src/parser/at_rule/import.rs b/crates/gosub_css3/src/parser/at_rule/import.rs index e0a313c28..a9fbfdb49 100644 --- a/crates/gosub_css3/src/parser/at_rule/import.rs +++ b/crates/gosub_css3/src/parser/at_rule/import.rs @@ -14,17 +14,21 @@ impl Css3<'_> { let t = self.consume_any()?; match t.token_type { TokenType::QuotedString(value) => { - children.push(Node::new(NodeType::String { value }, loc.clone())); + children.push(Node::new(NodeType::String { value }, loc)); } TokenType::Url(url) => { - children.push(Node::new(NodeType::Url { url }, loc.clone())); + children.push(Node::new(NodeType::Url { url }, loc)); } TokenType::Function(name) if name.eq_ignore_ascii_case("url") => { self.tokenizer.reconsume(); children.push(self.parse_url()?); } _ => { - return Err(Error::Parse(format!("Expected string or url()").to_string(), t.location.clone()).into()); + return Err(Error::Parse( + "Expected string or url()".to_string().to_string(), + t.location, + ) + .into()); } } @@ -33,7 +37,7 @@ impl Css3<'_> { let t = self.tokenizer.lookahead_sc(0); match t.token_type { TokenType::Ident(value) if value.eq_ignore_ascii_case("layer") => { - children.push(Node::new(NodeType::Ident { value }, t.location.clone())); + children.push(Node::new(NodeType::Ident { value }, t.location)); } TokenType::Function(name) if name.eq_ignore_ascii_case("layer") => { children.push(self.parse_function()?); @@ -67,6 +71,6 @@ impl Css3<'_> { // _ => {} // } - Ok(Node::new(NodeType::ImportList { children }, loc.clone())) + Ok(Node::new(NodeType::ImportList { children }, loc)) } } diff --git a/crates/gosub_css3/src/parser/at_rule/media.rs b/crates/gosub_css3/src/parser/at_rule/media.rs index dcb312011..dd130075c 100644 --- a/crates/gosub_css3/src/parser/at_rule/media.rs +++ b/crates/gosub_css3/src/parser/at_rule/media.rs @@ -29,7 +29,11 @@ impl Css3<'_> { loc, )) } - _ => Err(Error::Parse(format!("Expected identifier, number, dimension, or ratio"), loc).into()) + _ => Err(Error::Parse( + "Expected identifier, number, dimension, or ratio".to_string(), + loc, + ) + .into()), } } @@ -128,7 +132,11 @@ impl Css3<'_> { )) } _ => { - return Err(Error::Parse("Expected identifier, number, dimension, or ratio".to_string(), t.location).into()); + return Err(Error::Parse( + "Expected identifier, number, dimension, or ratio".to_string(), + t.location, + ) + .into()); } }; @@ -233,7 +241,11 @@ impl Css3<'_> { // skip; } _ => { - return Err(Error::Parse("Expected identifier or parenthesis".to_string(), t.location).into()); + return Err(Error::Parse( + "Expected identifier or parenthesis".to_string(), + t.location, + ) + .into()); } } } else { @@ -247,7 +259,11 @@ impl Css3<'_> { // skip } _ => { - return Err(Error::Parse("Expected identifier or parenthesis".to_string(), t.location).into()); + return Err(Error::Parse( + "Expected identifier or parenthesis".to_string(), + t.location, + ) + .into()); } } } diff --git a/crates/gosub_css3/src/parser/block.rs b/crates/gosub_css3/src/parser/block.rs index bfb44651e..31ec78279 100644 --- a/crates/gosub_css3/src/parser/block.rs +++ b/crates/gosub_css3/src/parser/block.rs @@ -1,8 +1,8 @@ +use crate::errors::Error; use crate::node::{Node, NodeType}; use crate::tokenizer::TokenType; use crate::Css3; use gosub_shared::types::Result; -use crate::errors::Error; #[derive(Debug, PartialEq)] pub enum BlockParseMode { @@ -64,7 +64,7 @@ impl Css3<'_> { // End the block self.tokenizer.reconsume(); - let n = Node::new(NodeType::Block { children }, t.location.clone()); + let n = Node::new(NodeType::Block { children }, t.location); return Ok(n); } TokenType::Whitespace(_) | TokenType::Comment(_) => { @@ -90,7 +90,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Expected a ; got {:?}", t), self.tokenizer.current_location(), - ).into()); + ) + .into()); } self.tokenizer.reconsume(); diff --git a/crates/gosub_css3/src/parser/pseudo.rs b/crates/gosub_css3/src/parser/pseudo.rs index f78466bf3..2d24bdc04 100644 --- a/crates/gosub_css3/src/parser/pseudo.rs +++ b/crates/gosub_css3/src/parser/pseudo.rs @@ -3,7 +3,6 @@ use crate::tokenizer::TokenType; use crate::{Css3, Error}; use gosub_shared::types::Result; - impl Css3<'_> { fn parse_pseudo_function_selector_list(&mut self) -> Result { log::trace!("parse_pseudo_function_selector_list"); @@ -41,14 +40,14 @@ impl Css3<'_> { a: "2".into(), b: "1".into(), }, - loc.clone(), + loc, ), TokenType::Ident(value) if value == "even" => Node::new( NodeType::AnPlusB { a: "2".into(), b: "0".into(), }, - loc.clone(), + loc, ), TokenType::Ident(_) => { self.tokenizer.reconsume(); @@ -58,12 +57,13 @@ impl Css3<'_> { self.tokenizer.reconsume(); self.parse_anplusb()? } - TokenType::Number(value) => Node::new(NodeType::Number { value }, loc.clone()), + TokenType::Number(value) => Node::new(NodeType::Number { value }, loc), _ => { return Err(Error::Parse( format!("Unexpected token {:?}", self.tokenizer.lookahead(0)), self.tokenizer.current_location(), - ).into()); + ) + .into()); } }; @@ -78,7 +78,7 @@ impl Css3<'_> { } } - Ok(Node::new(NodeType::Nth { nth, selector }, loc.clone())) + Ok(Node::new(NodeType::Nth { nth, selector }, loc)) } pub(crate) fn parse_pseudo_function(&mut self, name: &str) -> Result { @@ -103,7 +103,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Unexpected pseudo function {:?}", name), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } } diff --git a/crates/gosub_css3/src/parser/selector.rs b/crates/gosub_css3/src/parser/selector.rs index 1383c9aeb..5dbb9cc31 100644 --- a/crates/gosub_css3/src/parser/selector.rs +++ b/crates/gosub_css3/src/parser/selector.rs @@ -18,10 +18,9 @@ impl Css3<'_> { _ => { self.tokenizer.reconsume(); - return Err(Error::Parse( - format!("Expected attribute operator, got {:?}", c), - loc, - ).into()); + return Err( + Error::Parse(format!("Expected attribute operator, got {:?}", c), loc).into(), + ); } } @@ -69,7 +68,8 @@ impl Css3<'_> { _ => Err(Error::Parse( format!("Unexpected token {:?}", t), self.tokenizer.current_location(), - ).into()), + ) + .into()), } } @@ -141,7 +141,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Unexpected token {:?}", t), self.tokenizer.current_location(), - ).into()); + ) + .into()); } } @@ -182,7 +183,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Unexpected token {:?}", t), self.tokenizer.current_location(), - ).into()); + ) + .into()); } }; @@ -204,7 +206,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Unexpected token {:?}", t), self.tokenizer.current_location(), - ).into()); + ) + .into()); }; Ok(Node::new(NodeType::PseudoElementSelector { value }, loc)) @@ -237,7 +240,8 @@ impl Css3<'_> { return Err(Error::Parse( format!("Unexpected token {:?}", t), self.tokenizer.current_location(), - ).into()); + ) + .into()); } }; @@ -254,7 +258,7 @@ impl Css3<'_> { // When true, we have encountered a space which means we need to emit a descendant combinator let mut space = false; - let mut whitespace_location = loc.clone(); + let mut whitespace_location = loc; let mut skip_space = false; @@ -274,7 +278,7 @@ impl Css3<'_> { if t.is_whitespace() { // on whitespace for selector - whitespace_location = t.location.clone(); + whitespace_location = t.location; space = true; continue; } @@ -352,7 +356,7 @@ impl Css3<'_> { NodeType::Combinator { value: " ".to_string(), }, - whitespace_location.clone(), + whitespace_location, ); // insert before the last added node children.push(node); diff --git a/crates/gosub_css3/src/parser/stylesheet.rs b/crates/gosub_css3/src/parser/stylesheet.rs index 2ca1d8e40..6237d1b15 100644 --- a/crates/gosub_css3/src/parser/stylesheet.rs +++ b/crates/gosub_css3/src/parser/stylesheet.rs @@ -19,17 +19,14 @@ impl Css3<'_> { TokenType::Whitespace(_) => {} TokenType::Comment(comment) => { if comment.chars().nth(2) == Some('!') { - children.push(Node::new( - NodeType::Comment { value: comment }, - t.location.clone(), - )); + children.push(Node::new(NodeType::Comment { value: comment }, t.location)); } } TokenType::Cdo => { - children.push(Node::new(NodeType::Cdo, t.location.clone())); + children.push(Node::new(NodeType::Cdo, t.location)); } TokenType::Cdc => { - children.push(Node::new(NodeType::Cdc, t.location.clone())); + children.push(Node::new(NodeType::Cdc, t.location)); } TokenType::AtKeyword(_keyword) => { self.tokenizer.reconsume(); diff --git a/crates/gosub_css3/src/stylesheet.rs b/crates/gosub_css3/src/stylesheet.rs index 15eceff65..fc90d9e2c 100644 --- a/crates/gosub_css3/src/stylesheet.rs +++ b/crates/gosub_css3/src/stylesheet.rs @@ -1,7 +1,12 @@ +use anyhow::anyhow; use core::fmt::Debug; +use gosub_shared::byte_stream::Location; +use gosub_shared::traits::css3::CssOrigin; +use gosub_shared::types::Result; use std::cmp::Ordering; use std::fmt::Display; -use gosub_shared::byte_stream::Location; + +use crate::colors::RgbColor; /// Severity of a CSS error #[derive(Debug, PartialEq)] @@ -71,16 +76,14 @@ impl CssLog { impl Debug for CssLog { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}] ({}:{}): {}", self.severity, self.location.line, self.location.column, self.message) + write!( + f, + "[{}] ({}:{}): {}", + self.severity, self.location.line, self.location.column, self.message + ) } } -use anyhow::anyhow; -use gosub_shared::traits::css3::CssOrigin; -use gosub_shared::types::Result; - -use crate::colors::RgbColor; - /// Defines a complete stylesheet with all its rules and the location where it was found #[derive(Debug, PartialEq)] pub struct CssStylesheet { @@ -91,7 +94,7 @@ pub struct CssStylesheet { /// Url or file path where the stylesheet was found pub location: String, /// Any issues during parsing of the stylesheet - pub parse_log: Vec + pub parse_log: Vec, } impl gosub_shared::traits::css3::CssStylesheet for CssStylesheet { @@ -518,6 +521,64 @@ impl CssValue { } } +impl gosub_shared::traits::css3::CssValue for CssValue { + fn unit_to_px(&self) -> f32 { + self.unit_to_px() + } + + fn as_string(&self) -> Option<&str> { + if let CssValue::String(str) = &self { + Some(str) + } else { + None + } + } + + fn as_percentage(&self) -> Option { + if let CssValue::Percentage(percent) = &self { + Some(*percent) + } else { + None + } + } + + fn as_unit(&self) -> Option<(f32, &str)> { + if let CssValue::Unit(value, unit) = &self { + Some((*value, unit)) + } else { + None + } + } + + fn as_color(&self) -> Option<(f32, f32, f32, f32)> { + if let CssValue::Color(color) = &self { + Some((color.r, color.g, color.b, color.a)) + } else { + None + } + } + + fn as_number(&self) -> Option { + if let CssValue::Number(num) = &self { + Some(*num) + } else { + None + } + } + + fn as_list(&self) -> Option> { + if let CssValue::List(list) = &self { + Some(list.iter().cloned().collect()) + } else { + None + } + } + + fn is_none(&self) -> bool { + matches!(self, CssValue::None) + } +} + #[cfg(test)] mod test { use std::vec; diff --git a/crates/gosub_css3/src/system.rs b/crates/gosub_css3/src/system.rs new file mode 100644 index 000000000..0e407d495 --- /dev/null +++ b/crates/gosub_css3/src/system.rs @@ -0,0 +1,256 @@ +use crate::matcher::styling::{match_selector, CssProperties, CssProperty, DeclarationProperty}; +use crate::Css3; +use gosub_shared::document::DocumentHandle; +use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssOrigin, CssSystem}; +use gosub_shared::traits::document::Document; +use gosub_shared::traits::node::{ElementDataType, Node, TextDataType}; +use gosub_shared::traits::ParserConfig; +use log::warn; + +use crate::functions::attr::resolve_attr; +use crate::functions::calc::resolve_calc; +use crate::functions::var::resolve_var; +use crate::matcher::property_definitions::get_css_definitions; +use crate::matcher::shorthands::FixList; +use crate::stylesheet::{CssDeclaration, CssValue, Specificity}; +use gosub_shared::types::Result; + +#[derive(Debug, Clone)] +struct Css3System; + +impl CssSystem for Css3System { + type Stylesheet = crate::stylesheet::CssStylesheet; + + type PropertyMap = CssProperties; + + type Property = CssProperty; + + fn parse_str( + str: &str, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> Result { + Css3::parse_str(str, config, origin, source_url) + } + + fn properties_from_node>( + node: &D::Node, + sheets: &[Self::Stylesheet], + handle: DocumentHandle, + id: NodeId, + ) -> Option { + let mut css_map_entry = CssProperties::new(); + + // Extract name and namespace from the node if it's an element node + let mut node_element_namespace = ""; + let mut node_element_name = ""; + if let Some(data) = node.get_element_data() { + node_element_name = data.name(); + node_element_namespace = data.namespace(); + } + + if node_is_unrenderable::(node) { + return None; + } + + let definitions = get_css_definitions(); + + let mut fix_list = FixList::new(); + + for sheet in sheets { + for rule in &sheet.rules { + for selector in rule.selectors().iter() { + let (matched, specificity) = + match_selector(DocumentHandle::clone(&handle), id, selector); + + if !matched { + continue; + } + + // 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 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; + }; + + let value = resolve_functions(&declaration.value, node, handle.clone()); + + // Check if the declaration matches the definition and return the "expanded" order + let res = definition.matches_and_shorthands(&value, &mut fix_list); + if !res { + warn!("Declaration does not match definition: {:?}", declaration); + continue; + } + + // create property for the given values + let property_name = declaration.property.clone(); + let decl = CssDeclaration { + property: property_name.to_string(), + value, + important: declaration.important, + }; + + add_property_to_map(&mut css_map_entry, sheet, specificity.clone(), &decl); + } + } + } + } + + fix_list.resolve_nested(definitions); + + fix_list.apply(&mut css_map_entry); + + Some(css_map_entry) + } +} + +/* TODO +fn resolve_inheritance(&mut self, node_id: NodeId, inherit_props: &Vec<(String, CssValue)>) { + let Some(current_node) = self.get_node(node_id) else { + return; + }; + + for prop in inherit_props { + current_node + .properties + .entry(prop.0.clone()) + .or_insert_with(|| { + let mut p = CssProperty::new(prop.0.as_str()); + + p.inherited = prop.1.clone(); + + p + }); + } + + let mut inherit_props = inherit_props.clone(); + + 'props: for (name, prop) in &mut current_node.properties.iter_mut() { + prop.compute_value(); + + let value = prop.actual.clone(); + + if prop_is_inherit(name) { + for (k, v) in &mut inherit_props { + if k == name { + *v = value; + continue 'props; + } + } + + inherit_props.push((name.clone(), value)); + } + } + + let Some(children) = self.get_children(node_id) else { + return; + }; + + for child in children.clone() { + self.resolve_inheritance(child, &inherit_props); + } +} + + + + */ +pub fn add_property_to_map( + css_map_entry: &mut CssProperties, + sheet: &crate::stylesheet::CssStylesheet, + specificity: Specificity, + declaration: &CssDeclaration, +) { + let property_name = declaration.property.clone(); + // 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); + // } + // } + // + let declaration = DeclarationProperty { + // @todo: this seems wrong. We only get the first values from the declared values + value: declaration.value.first().unwrap().clone(), + origin: sheet.origin, + important: declaration.important, + location: sheet.location.clone(), + specificity, + }; + + if let std::collections::hash_map::Entry::Vacant(e) = + css_map_entry.properties.entry(property_name.clone()) + { + // Generate new property in the css map + let mut entry = CssProperty::new(property_name.as_str()); + entry.declared.push(declaration); + e.insert(entry); + } else { + // Just add the declaration to the existing property + let entry = css_map_entry.properties.get_mut(&property_name).unwrap(); + entry.declared.push(declaration); + } +} + +pub fn node_is_unrenderable, C: CssSystem>(node: &D::Node) -> bool { + // There are more elements that are not renderable, but for now we only remove the most common ones + + const REMOVABLE_ELEMENTS: [&str; 6] = ["head", "script", "style", "svg", "noscript", "title"]; + + if let Some(element_data) = node.get_element_data() { + if REMOVABLE_ELEMENTS.contains(&element_data.name()) { + return true; + } + } + + if let Some(text_data) = &node.get_text_data() { + if text_data.value().chars().all(|c| c.is_whitespace()) { + return true; + } + } + + false +} + +pub fn resolve_functions, C: CssSystem>( + value: &[CssValue], + node: &D::Node, + handle: DocumentHandle, +) -> Vec { + let mut result = Vec::with_capacity(value.len()); //TODO: we could give it a &mut Vec and reuse the allocation + + for val in value { + match val { + CssValue::Function(func, values) => { + let resolved = match func.as_str() { + "calc" => resolve_calc(values), + "attr" => resolve_attr(values, node), + "var" => resolve_var(values, &*handle.get(), node), + _ => vec![val.clone()], + }; + + result.extend(resolved); + } + _ => result.push(val.clone()), + } + } + + result +} diff --git a/crates/gosub_css3/src/tokenizer.rs b/crates/gosub_css3/src/tokenizer.rs index 8dce822a8..739c4c0b1 100644 --- a/crates/gosub_css3/src/tokenizer.rs +++ b/crates/gosub_css3/src/tokenizer.rs @@ -256,7 +256,7 @@ impl<'stream> Tokenizer<'stream> { /// Returns the current location (line/col) of the tokenizer pub fn current_location(&self) -> Location { - self.location_handler.cur_location.clone() + self.location_handler.cur_location } /// Returns true when there is no next element, and the stream is closed diff --git a/crates/gosub_html5/src/document/builder.rs b/crates/gosub_html5/src/document/builder.rs index 8016e050d..724209b79 100644 --- a/crates/gosub_html5/src/document/builder.rs +++ b/crates/gosub_html5/src/document/builder.rs @@ -33,7 +33,7 @@ impl gosub_shared::traits::document::DocumentBuilder for Docume "html", Some(HTML_NAMESPACE), HashMap::new(), - context_node.location().clone(), + context_node.location(), ); let fragment_doc = >::new( DocumentType::HTML, diff --git a/crates/gosub_html5/src/document/document.rs b/crates/gosub_html5/src/document/document.rs index c4d754165..6b0f6cb20 100755 --- a/crates/gosub_html5/src/document/document.rs +++ b/crates/gosub_html5/src/document/document.rs @@ -106,10 +106,7 @@ impl Document for DocumentImpl { /// Returns the URL of the document, or "" when no location is set fn url(&self) -> Option { - match self.url { - Some(ref url) => Some(url.clone()), - None => None, - } + self.url.as_ref().map(|url| url.clone()) } fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) { @@ -232,14 +229,11 @@ impl Document for DocumentImpl { /// if the node is still available as a child or parent in the tree. fn delete_node_by_id(&mut self, node_id: NodeId) { let node = self.arena.node(node_id).unwrap(); - let parent_id = node.parent_id().clone(); + let parent_id = node.parent_id(); - match parent_id { - Some(parent_id) => { - let parent = self.node_by_id_mut(parent_id).unwrap(); - parent.remove(node_id); - } - None => {} + if let Some(parent_id) = parent_id { + let parent = self.node_by_id_mut(parent_id).unwrap(); + parent.remove(node_id); } self.arena.delete_node(node_id); @@ -457,7 +451,7 @@ fn internal_visit( node: & as Document>::Node, visitor: &mut Box as Document>::Node, C>>, ) { - visitor.document_enter(&node); + visitor.document_enter(node); let binding = handle.get(); for child_id in node.children() { @@ -466,31 +460,33 @@ fn internal_visit( } // Leave node - visitor.document_leave(&node); + visitor.document_leave(node); } /// Constructs an iterator from a given DocumentHandle. /// WARNING: mutations in the document would be reflected /// in the iterator. It's advised to consume the entire iterator /// before mutating the document again. -pub struct TreeIterator, C: CssSystem> { +pub struct TreeIterator, C: CssSystem> { current_node_id: Option, node_stack: Vec, document: DocumentHandle, } -impl + Clone, C: CssSystem> TreeIterator { +impl, C: CssSystem> TreeIterator { #[must_use] pub fn new(doc: DocumentHandle) -> Self { + let node_stack = vec![doc.get().get_root().id()]; + Self { current_node_id: None, - document: doc.clone(), - node_stack: vec![doc.get().get_root().id()], + document: doc, + node_stack, } } } -impl + Clone, C: CssSystem> Iterator for TreeIterator { +impl, C: CssSystem> Iterator for TreeIterator { type Item = NodeId; fn next(&mut self) -> Option { diff --git a/crates/gosub_html5/src/document/task_queue.rs b/crates/gosub_html5/src/document/task_queue.rs index ab82bba76..12c7f0534 100644 --- a/crates/gosub_html5/src/document/task_queue.rs +++ b/crates/gosub_html5/src/document/task_queue.rs @@ -89,7 +89,7 @@ impl, C: CssSystem> DocumentTaskQueue { name, Some(namespace), HashMap::new(), - location.clone(), + *location, ); self.document .get_mut() @@ -100,7 +100,7 @@ impl, C: CssSystem> DocumentTaskQueue { parent_id, location, } => { - let node = D::new_text_node(self.document.clone(), content, location.clone()); + let node = D::new_text_node(self.document.clone(), content, *location); self.document .get_mut() .register_node_at(node, *parent_id, None); @@ -110,8 +110,7 @@ impl, C: CssSystem> DocumentTaskQueue { parent_id, location, } => { - let node = - D::new_comment_node(self.document.clone(), content, location.clone()); + let node = D::new_comment_node(self.document.clone(), content, *location); self.document .get_mut() .register_node_at(node, *parent_id, None); @@ -122,7 +121,7 @@ impl, C: CssSystem> DocumentTaskQueue { element_id, } => { if let Some(node) = self.document.get_mut().node_by_id_mut(*element_id) { - if let Some(mut data) = node.get_element_data_mut() { + if let Some(data) = node.get_element_data_mut() { data.attributes_mut().insert(key.clone(), value.clone()); // let mut attributes = node.get_element_data().unwrap().attributes().clone(); // attributes.insert(key.clone(), value.clone()); diff --git a/crates/gosub_html5/src/node/data/element.rs b/crates/gosub_html5/src/node/data/element.rs index 6cdfaf39b..2067c76fc 100644 --- a/crates/gosub_html5/src/node/data/element.rs +++ b/crates/gosub_html5/src/node/data/element.rs @@ -1,12 +1,14 @@ +use crate::document::document::DocumentImpl; +use crate::document::fragment::DocumentFragmentImpl; +use crate::node::elements::{ + FORMATTING_HTML_ELEMENTS, SPECIAL_HTML_ELEMENTS, SPECIAL_MATHML_ELEMENTS, SPECIAL_SVG_ELEMENTS, +}; +use crate::node::{HTML_NAMESPACE, MATHML_NAMESPACE, SVG_NAMESPACE}; use core::fmt::{Debug, Formatter}; -use std::collections::HashMap; -use std::fmt; use gosub_shared::traits::css3::CssSystem; use gosub_shared::traits::node::ElementDataType; -use crate::node::elements::{FORMATTING_HTML_ELEMENTS, SPECIAL_HTML_ELEMENTS, SPECIAL_MATHML_ELEMENTS, SPECIAL_SVG_ELEMENTS}; -use crate::node::{HTML_NAMESPACE, MATHML_NAMESPACE, SVG_NAMESPACE}; -use crate::document::document::DocumentImpl; -use crate::document::fragment::DocumentFragmentImpl; +use std::collections::HashMap; +use std::fmt; #[derive(Debug, Clone, PartialEq)] pub struct ElementClass { @@ -106,7 +108,6 @@ impl From<&str> for ElementClass { } } - /// Data structure for element nodes #[derive(PartialEq, Clone)] pub struct ElementData { @@ -127,7 +128,6 @@ pub struct ElementData { pub force_async: bool, // Template contents (when it's a template element) pub template_contents: Option>, - } impl Debug for ElementData { @@ -164,7 +164,11 @@ impl ElementDataType for ElementData { } fn active_classes(&self) -> Vec { - self.classes.iter().filter(|(_, &active)| active).map(|(name, _)| name.clone()).collect() + self.classes + .iter() + .filter(|(_, &active)| active) + .map(|(name, _)| name.clone()) + .collect() } fn attribute(&self, name: &str) -> Option<&String> { @@ -195,7 +199,7 @@ impl ElementDataType for ElementData { return false; } - return self.attributes.eq(&other_data.attributes); + self.attributes.eq(&other_data.attributes) } /// Returns true if the given node is a mathml integration point @@ -232,7 +236,7 @@ impl ElementDataType for ElementData { namespace == SVG_NAMESPACE && ["foreignObject", "desc", "title"].contains(&self.name.as_str()) } - None => return false, + None => false, } } @@ -295,10 +299,8 @@ impl ElementData { template_contents, } } - } - #[cfg(test)] mod tests { use super::*; diff --git a/crates/gosub_html5/src/node/data/text.rs b/crates/gosub_html5/src/node/data/text.rs index a6be7a77a..8cb46ce3c 100644 --- a/crates/gosub_html5/src/node/data/text.rs +++ b/crates/gosub_html5/src/node/data/text.rs @@ -34,6 +34,10 @@ impl TextDataType for TextData { &self.value } + fn string_value(&self) -> String { + self.value.clone() + } + fn value_mut(&mut self) -> &mut String { &mut self.value } diff --git a/crates/gosub_html5/src/node/node.rs b/crates/gosub_html5/src/node/node.rs index 0b80de7c2..575d8e828 100644 --- a/crates/gosub_html5/src/node/node.rs +++ b/crates/gosub_html5/src/node/node.rs @@ -1,16 +1,15 @@ -use gosub_shared::traits::node::{Node, NodeType}; +use crate::document::document::DocumentImpl; use crate::node::data::comment::CommentData; use crate::node::data::doctype::DocTypeData; use crate::node::data::document::DocumentData; +use crate::node::data::element::ElementData; use crate::node::data::text::TextData; use core::fmt::Debug; -use std::cell::{Ref, RefCell, RefMut}; use gosub_shared::byte_stream::Location; use gosub_shared::document::DocumentHandle; use gosub_shared::node::NodeId; use gosub_shared::traits::css3::CssSystem; -use crate::document::document::DocumentImpl; -use crate::node::data::element::ElementData; +use gosub_shared::traits::node::{Node, NodeData, NodeType}; /// Implementation of the NodeDataType trait #[derive(Debug, Clone, PartialEq)] @@ -36,7 +35,7 @@ pub struct NodeImpl { /// any children of the node pub children: Vec, /// actual data of the node - pub data: RefCell>, + pub data: NodeDataTypeInternal, /// Handle to the document in which this node resides pub document: DocumentHandle, C>, // Returns true when the given node is registered into the document arena @@ -58,15 +57,15 @@ impl Node for NodeImpl { } fn set_id(&mut self, id: NodeId) { - self.id = id.clone() + self.id = id } fn location(&self) -> Location { - self.location.clone() + self.location } fn parent_id(&self) -> Option { - self.parent.clone() + self.parent } fn set_parent(&mut self, parent_id: Option) { @@ -82,7 +81,7 @@ impl Node for NodeImpl { } fn type_of(&self) -> NodeType { - match *self.data.borrow() { + match self.data { NodeDataTypeInternal::Document(_) => NodeType::DocumentNode, NodeDataTypeInternal::DocType(_) => NodeType::DocTypeNode, NodeDataTypeInternal::Text(_) => NodeType::TextNode, @@ -95,99 +94,51 @@ impl Node for NodeImpl { self.type_of() == NodeType::ElementNode } - fn get_element_data(&self) -> Option> { - let borrowed_data = self.data.borrow(); - - if let NodeDataTypeInternal::Element(_) = *borrowed_data { - return Some(Ref::map(borrowed_data, |d| - if let NodeDataTypeInternal::Element(ref element_data) = d { - element_data - } else { - unreachable!() - } - )); + fn get_element_data(&self) -> Option<&Self::ElementData> { + if let NodeDataTypeInternal::Element(data) = &self.data { + return Some(data); } None } - fn get_element_data_mut(&self) -> Option>> { - let borrowed_data = self.data.borrow_mut(); - - if let NodeDataTypeInternal::Element(_) = *borrowed_data { - return Some(RefMut::map(borrowed_data, |d| { - if let NodeDataTypeInternal::Element(ref mut element_data) = d { - element_data - } else { - unreachable!() - } - })); + fn get_element_data_mut(&mut self) -> Option<&mut ElementData> { + if let NodeDataTypeInternal::Element(data) = &mut self.data { + return Some(data); } None } fn is_text_node(&self) -> bool { - match *self.data.borrow() { + match self.data { NodeDataTypeInternal::Text(_) => true, _ => false, } } - fn get_text_data(&self) -> Option> { - let borrowed_data = self.data.borrow(); - - if let NodeDataTypeInternal::Text(_) = *borrowed_data { - return Some(Ref::map(borrowed_data, |d| - if let NodeDataTypeInternal::Text(ref text_data) = d { - text_data - } else { - unreachable!() - } - )); + fn get_text_data(&self) -> Option<&Self::TextData> { + if let NodeDataTypeInternal::Text(data) = &self.data { + return Some(data); } None } - fn get_text_data_mut(&self) -> Option> { - let borrowed_data = self.data.borrow_mut(); - - if let NodeDataTypeInternal::Text(_) = *borrowed_data { - return Some(RefMut::map(borrowed_data, |d| { - if let NodeDataTypeInternal::Text(ref mut text_data) = d { - text_data - } else { - unreachable!() - } - })); + fn get_text_data_mut(&mut self) -> Option<&mut TextData> { + if let NodeDataTypeInternal::Text(data) = &mut self.data { + return Some(data); } None } - fn get_comment_data(&self) -> Option> { - let borrowed_data = self.data.borrow(); - - if let NodeDataTypeInternal::Comment(_) = *borrowed_data { - return Some(Ref::map(borrowed_data, |d| - if let NodeDataTypeInternal::Comment(ref text_data) = d { - text_data - } else { - unreachable!() - } - )); + fn get_comment_data(&self) -> Option<&Self::CommentData> { + if let NodeDataTypeInternal::Comment(data) = &self.data { + return Some(data); } None } - fn get_doctype_data(&self) -> Option> { - let borrowed_data = self.data.borrow(); - - if let NodeDataTypeInternal::DocType(_) = *borrowed_data { - return Some(Ref::map(borrowed_data, |d| - if let NodeDataTypeInternal::DocType(ref text_data) = d { - text_data - } else { - unreachable!() - } - )); + fn get_doctype_data(&self) -> Option<&Self::DocTypeData> { + if let NodeDataTypeInternal::DocType(data) = &self.data { + return Some(data); } None } @@ -197,7 +148,7 @@ impl Node for NodeImpl { } fn remove(&mut self, node_id: NodeId) { - self.children = self.children.iter().filter(|&x| x != &node_id).cloned().collect(); + self.children.retain(|x| x != &node_id); } fn insert(&mut self, node_id: NodeId, idx: usize) { @@ -207,6 +158,16 @@ impl Node for NodeImpl { fn push(&mut self, node_id: NodeId) { self.children.push(node_id); } + + fn data(&self) -> NodeData { + match self.data { + NodeDataTypeInternal::Document(ref data) => NodeData::Document(data), + NodeDataTypeInternal::DocType(ref data) => NodeData::DocType(data), + NodeDataTypeInternal::Text(ref data) => NodeData::Text(data), + NodeDataTypeInternal::Comment(ref data) => NodeData::Comment(data), + NodeDataTypeInternal::Element(ref data) => NodeData::Element(data), + } + } } impl PartialEq for NodeImpl { @@ -235,7 +196,7 @@ impl Clone for NodeImpl { data: self.data.clone(), document: self.document.clone(), is_registered: self.is_registered, - location: self.location.clone(), + location: self.location, } } } @@ -243,13 +204,17 @@ impl Clone for NodeImpl { impl NodeImpl { /// create a new `Node` #[must_use] - pub fn new(document: DocumentHandle, C>, location: Location, data: &NodeDataTypeInternal) -> Self { + pub fn new( + document: DocumentHandle, C>, + location: Location, + data: &NodeDataTypeInternal, + ) -> Self { let (id, parent, children, is_registered) = <_>::default(); Self { id, parent, children, - data: data.clone().into(), + data: data.clone(), document: document.clone(), is_registered, location, @@ -318,7 +283,6 @@ impl NodeImpl { // ) // } - /// Returns true if this node is registered into an arena pub fn is_registered(&self) -> bool { self.is_registered diff --git a/crates/gosub_html5/src/parser.rs b/crates/gosub_html5/src/parser.rs index 2061bc833..d25794b6d 100644 --- a/crates/gosub_html5/src/parser.rs +++ b/crates/gosub_html5/src/parser.rs @@ -359,7 +359,7 @@ where // 3. let error_logger = Rc::new(RefCell::new(ErrorLogger::new())); - let tokenizer = Tokenizer::new(stream, None, error_logger.clone(), start_location.clone()); + let tokenizer = Tokenizer::new(stream, None, error_logger.clone(), start_location); let mut parser = Html5Parser::init(tokenizer, document.clone(), error_logger, options); // 4. / 12. @@ -385,7 +385,7 @@ where name: context_node_element_data.name().to_string(), is_self_closing: false, attributes: node_attributes, - location: start_location.clone(), + location: start_location, }; // 10. @@ -575,11 +575,11 @@ where let acn = self.get_adjusted_current_node(); let acn_element_data = get_element_data!(acn); - if acn_element_data.is_namespace(MATHML_NAMESPACE.into()) { + if acn_element_data.is_namespace(MATHML_NAMESPACE) { self.adjust_mathml_attributes(&mut current_token); } - if acn_element_data.is_namespace(SVG_NAMESPACE.into()) { + if acn_element_data.is_namespace(SVG_NAMESPACE) { self.adjust_svg_tag_names(&mut current_token); self.adjust_svg_attributes(&mut current_token); } @@ -635,7 +635,7 @@ where node_idx -= 1; node = get_node_by_id!(self.document, self.open_elements[node_idx]); - if !get_element_data!(node).is_namespace(HTML_NAMESPACE.into()) { + if !get_element_data!(node).is_namespace(HTML_NAMESPACE) { continue; } @@ -679,7 +679,7 @@ where // We don't need to skip 1 char, but we can skip 1 byte, as we just checked for \n self.current_token = Token::Text { text: value.chars().skip(1).collect::(), - location: location.clone(), + location: *location, }; } } @@ -1813,7 +1813,7 @@ where let node = current_node!(self); let element_data = get_element_data!(node); - if element_data.name() == name && element_data.is_namespace(HTML_NAMESPACE.into()) { + if element_data.name() == name && element_data.is_namespace(HTML_NAMESPACE) { self.open_elements.pop(); break; } @@ -1849,7 +1849,7 @@ where let element_node = get_node_by_id!(self.document, node_id.unwrap()); let data = get_element_data!(element_node); - if arr.contains(&&data.name()) { + if arr.contains((&data.name())) { break; } } @@ -1927,7 +1927,7 @@ where Some(value) => Some(value.as_str()), None => None, }, - location.clone(), + *location, ), Token::StartTag { name, @@ -1939,25 +1939,25 @@ where name, namespace.into(), attributes.clone(), - location.clone(), + *location, ), Token::EndTag { name, location, .. } => D::new_element_node( self.document.clone(), name, namespace.into(), HashMap::new(), - location.clone(), + *location, ), Token::Comment { comment: value, location, .. - } => D::new_comment_node(self.document.clone(), value, location.clone()), + } => D::new_comment_node(self.document.clone(), value, *location), Token::Text { text: value, location, .. - } => D::new_text_node(self.document.clone(), value.as_str(), location.clone()), + } => D::new_text_node(self.document.clone(), value.as_str(), *location), Token::Eof { .. } => { panic!("EOF token not allowed"); } @@ -1981,7 +1981,7 @@ where let data = get_element_data!(node); let tag = data.name(); - let is_html = get_element_data!(node).is_namespace(HTML_NAMESPACE.into()); + let is_html = get_element_data!(node).is_namespace(HTML_NAMESPACE); if let Some(except) = except { if except == tag && is_html { return; @@ -2163,7 +2163,7 @@ where for &node_id in self.open_elements.iter().rev() { let node = get_node_by_id!(self.document, node_id).clone(); let node_element_data = get_element_data!(node); - if node_element_data.name() == tag && node_element_data.is_namespace(namespace.into()) { + if node_element_data.name() == tag && node_element_data.is_namespace(namespace) { return true; } let default_html_scope = [ @@ -2173,35 +2173,35 @@ where let default_svg_scope = ["foreignObject", "desc", "title"]; match scope { Scope::Regular => { - if (node_element_data.is_namespace(HTML_NAMESPACE.into()) + if (node_element_data.is_namespace(HTML_NAMESPACE) && default_html_scope.contains(&node_element_data.name())) - || (node_element_data.is_namespace(MATHML_NAMESPACE.into()) + || (node_element_data.is_namespace(MATHML_NAMESPACE) && default_mathml_scope.contains(&node_element_data.name())) - || (node_element_data.is_namespace(SVG_NAMESPACE.into()) + || (node_element_data.is_namespace(SVG_NAMESPACE) && default_svg_scope.contains(&node_element_data.name())) { return false; } } Scope::ListItem => { - if (node_element_data.is_namespace(HTML_NAMESPACE.into()) + if (node_element_data.is_namespace(HTML_NAMESPACE) && (default_html_scope.contains(&node_element_data.name()) || ["ol", "ul"].contains(&node_element_data.name()))) - || (node_element_data.is_namespace(MATHML_NAMESPACE.into()) + || (node_element_data.is_namespace(MATHML_NAMESPACE) && default_mathml_scope.contains(&node_element_data.name())) - || (node_element_data.is_namespace(SVG_NAMESPACE.into()) + || (node_element_data.is_namespace(SVG_NAMESPACE) && default_svg_scope.contains(&node_element_data.name())) { return false; } } Scope::Button => { - if (node_element_data.is_namespace(HTML_NAMESPACE.into()) + if (node_element_data.is_namespace(HTML_NAMESPACE) && (default_html_scope.contains(&node_element_data.name()) || node_element_data.name() == "button")) - || (node_element_data.is_namespace(MATHML_NAMESPACE.into()) + || (node_element_data.is_namespace(MATHML_NAMESPACE) && default_mathml_scope.contains(&node_element_data.name())) - || (node_element_data.is_namespace(SVG_NAMESPACE.into()) + || (node_element_data.is_namespace(SVG_NAMESPACE) && default_svg_scope.contains(&node_element_data.name())) { return false; @@ -2215,7 +2215,7 @@ where } } Scope::Select => { - if !(node_element_data.is_namespace(HTML_NAMESPACE.into()) + if !(node_element_data.is_namespace(HTML_NAMESPACE) && ["optgroup", "option"].contains(&node_element_data.name())) { return false; @@ -2289,7 +2289,7 @@ where let first_node = doc.node_by_id_mut(first_node_id).expect("node not found"); if first_node.is_element_node() { - let mut element_data = get_element_data_mut!(first_node); + let element_data = get_element_data_mut!(first_node); for (key, value) in attributes { let attrs = element_data.attributes_mut(); @@ -2337,14 +2337,14 @@ where let node_element_data = get_element_data!(node); node_element_data.name() == "body" - && node_element_data.is_namespace(HTML_NAMESPACE.into()) + && node_element_data.is_namespace(HTML_NAMESPACE) }); if let Some(body_node_id) = body_node_id { let mut doc = self.document.get_mut(); let body_node = doc.node_by_id_mut(*body_node_id).expect("node not found"); - let mut element_data = get_element_data_mut!(body_node); + let element_data = get_element_data_mut!(body_node); for (key, value) in attributes { if !element_data.attributes_mut().contains_key(key) { element_data @@ -3224,7 +3224,7 @@ where let mut binding = self.document.get_mut(); let node = binding.node_by_id_mut(node_id).expect("node not found"); if node.is_element_node() { - let mut element_data = get_element_data_mut!(node); + let element_data = get_element_data_mut!(node); element_data.set_template_contents(D::Fragment::new( clone_document, current_node_id, @@ -3724,7 +3724,7 @@ where &ActiveElement::Node(id) => { let current_node = get_node_by_id!(self.document, id); if get_element_data!(current_node) - .matches_tag_and_attrs_without_order(&node_element_data) + .matches_tag_and_attrs_without_order(node_element_data) { if matched >= 2 { first_matched = Some(id); @@ -4005,7 +4005,7 @@ where { self.token_queue.push(Token::Text { text: value, - location: location.clone(), + location: location, }); // for c in value.chars() { // self.token_queue.push(Token::Text(c.to_string())); @@ -4092,7 +4092,7 @@ where while !current_node_element_data.is_mathml_integration_point() && !current_node_element_data.is_html_integration_point() - && !current_node_element_data.is_namespace(HTML_NAMESPACE.into()) + && !current_node_element_data.is_namespace(HTML_NAMESPACE) { self.open_elements.pop(); if self.open_elements.is_empty() { @@ -4113,7 +4113,7 @@ where /// Find the correct tokenizer state when we are about to parse a fragment case fn find_initial_state_for_context(&self, context_node: &D::Node) -> State { let context_node_element_data = get_element_data!(context_node); - if !context_node_element_data.is_namespace(HTML_NAMESPACE.into()) { + if !context_node_element_data.is_namespace(HTML_NAMESPACE) { return State::Data; } diff --git a/crates/gosub_html5/src/parser/errors.rs b/crates/gosub_html5/src/parser/errors.rs index 2f5304238..0aad26f37 100755 --- a/crates/gosub_html5/src/parser/errors.rs +++ b/crates/gosub_html5/src/parser/errors.rs @@ -190,7 +190,7 @@ impl ErrorLogger { self.errors.push(ParseError { message: message.to_string(), - location: location.clone(), + location: location, }); } } diff --git a/crates/gosub_html5/src/parser/helper.rs b/crates/gosub_html5/src/parser/helper.rs index a1e0dfb56..c5bb91f1e 100644 --- a/crates/gosub_html5/src/parser/helper.rs +++ b/crates/gosub_html5/src/parser/helper.rs @@ -135,7 +135,7 @@ where let mut last_node = get_node_by_id!(mut_handle, last_node_id); if last_node.is_text_node() { - let mut data = get_text_data_mut!(&mut last_node); + let data = get_text_data_mut!(&mut last_node); data.value_mut().push_str(&token.to_string()); return; } @@ -155,7 +155,7 @@ where let mut last_node = get_node_by_id!(mut_handle, last_node_id); if last_node.is_text_node() { - let mut data = get_text_data_mut!(&mut last_node); + let data = get_text_data_mut!(&mut last_node); data.value_mut().push_str(&token.to_string()); return; }; @@ -198,7 +198,7 @@ where let mut_handle = &mut self.document.clone(); let mut node = get_node_by_id!(mut_handle, node.id()); - let mut data = get_element_data_mut!(&mut node); + let data = get_element_data_mut!(&mut node); if let Some(class_string) = data.attributes().get("class") { let class_string = class_string.clone(); @@ -221,7 +221,7 @@ where let mut_handle = &mut self.document.clone(); let mut new_node = get_node_by_id!(mut_handle, new_node.id()); - let mut data = get_element_data_mut!(&mut new_node); + let data = get_element_data_mut!(&mut new_node); if let Some(class_string) = data.attributes().get("class") { let class_string = class_string.clone(); data.add_class(&class_string); @@ -299,8 +299,7 @@ where if !(self.foster_parenting && ["table", "tbody", "thead", "tfoot", "tr"].contains(&element_data.name())) { - if element_data.name() == "template" && element_data.is_namespace(HTML_NAMESPACE.into()) - { + if element_data.name() == "template" && element_data.is_namespace(HTML_NAMESPACE) { if let Some(template_fragment) = element_data.template_contents() { return InsertionPositionMode::LastChild { handle: template_fragment.handle(), @@ -359,7 +358,7 @@ where // step 2 if current_data.name() == *subject - && current_data.is_namespace(HTML_NAMESPACE.into()) + && current_data.is_namespace(HTML_NAMESPACE) && self .find_position_in_active_format(current_node.id()) .is_none() @@ -407,7 +406,7 @@ where }; // step 4.5 - if !self.is_in_scope(&format_element_data.name(), HTML_NAMESPACE, Scope::Regular) { + if !self.is_in_scope(format_element_data.name(), HTML_NAMESPACE, Scope::Regular) { self.parse_error("format_element_node not in regular scope"); return; } @@ -477,10 +476,10 @@ where let replacement_node = D::new_element_node( self.document.clone(), - &element_data.name(), + element_data.name(), Some(element_data.namespace()), element_data.attributes().clone(), - element_node.location().clone(), + element_node.location(), ); let replace_node_id = self.document.get_mut().register_node(replacement_node); @@ -514,10 +513,10 @@ where // step 4.15 let new_format_node = D::new_element_node( self.document.clone(), - &format_element_data.name(), + format_element_data.name(), Some(format_element_data.namespace()), format_element_data.attributes().clone(), - format_node.location().clone(), + format_node.location(), ); // step 4.16 diff --git a/crates/gosub_html5/src/tokenizer.rs b/crates/gosub_html5/src/tokenizer.rs index acfe9c9d3..6dc356e9f 100644 --- a/crates/gosub_html5/src/tokenizer.rs +++ b/crates/gosub_html5/src/tokenizer.rs @@ -9,6 +9,7 @@ mod test_cases; use crate::errors::Error; use crate::node::HTML_NAMESPACE; +use crate::parser::errors::{ErrorLogger, ParserError}; use crate::tokenizer::state::State; use crate::tokenizer::token::Token; use gosub_shared::byte_stream::Character::{Ch, StreamEnd}; @@ -17,7 +18,6 @@ use gosub_shared::types::Result; use std::cell::{Ref, RefCell}; use std::collections::HashMap; use std::rc::Rc; -use crate::parser::errors::{ErrorLogger, ParserError}; /// Constants that are not directly captured as visible chars pub const CHAR_NUL: char = '\u{0000}'; @@ -138,7 +138,7 @@ impl<'stream> Tokenizer<'stream> { /// Returns the current location in the stream (with line/col number and byte offset) #[inline] pub(crate) fn get_location(&self) -> Location { - self.location_handler.cur_location.clone() + self.location_handler.cur_location } /// Retrieves the next token from the input stream or Token::EOF when the end is reached @@ -180,7 +180,7 @@ impl<'stream> Tokenizer<'stream> { Ch('&') => self.state = State::CharacterReferenceInData, Ch('<') => { self.state = { - self.last_token_location = loc.clone(); + self.last_token_location = loc; State::TagOpen } } @@ -274,7 +274,7 @@ impl<'stream> Tokenizer<'stream> { name: String::new(), is_self_closing: false, attributes: HashMap::new(), - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); self.state = State::TagName; @@ -282,7 +282,7 @@ impl<'stream> Tokenizer<'stream> { Ch('?') => { self.current_token = Some(Token::Comment { comment: String::new(), - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.parse_error( ParserError::UnexpectedQuestionMarkInsteadOfTagName, @@ -312,7 +312,7 @@ impl<'stream> Tokenizer<'stream> { self.current_token = Some(Token::EndTag { name: String::new(), is_self_closing: false, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); self.state = State::TagName; @@ -331,7 +331,7 @@ impl<'stream> Tokenizer<'stream> { self.parse_error(ParserError::InvalidFirstCharacterOfTagName, loc); self.current_token = Some(Token::Comment { comment: String::new(), - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); self.state = State::BogusComment; @@ -380,7 +380,7 @@ impl<'stream> Tokenizer<'stream> { self.current_token = Some(Token::EndTag { name: String::new(), is_self_closing: false, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); self.state = State::RCDATAEndTagName; @@ -467,7 +467,7 @@ impl<'stream> Tokenizer<'stream> { self.current_token = Some(Token::EndTag { name: String::new(), is_self_closing: false, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); self.state = State::RAWTEXTEndTagName; @@ -562,7 +562,7 @@ impl<'stream> Tokenizer<'stream> { self.current_token = Some(Token::EndTag { name: format!("{}", to_lowercase!(ch)), is_self_closing: false, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.temporary_buffer.push(ch); @@ -760,7 +760,7 @@ impl<'stream> Tokenizer<'stream> { self.current_token = Some(Token::EndTag { name: String::new(), is_self_closing: false, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.stream_prev(); @@ -1275,7 +1275,7 @@ impl<'stream> Tokenizer<'stream> { self.parse_error(ParserError::IncorrectlyOpenedComment, self.get_location()); self.current_token = Some(Token::Comment { comment: String::new(), - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.state = State::BogusComment; @@ -1513,7 +1513,7 @@ impl<'stream> Tokenizer<'stream> { force_quirks: false, pub_identifier: None, sys_identifier: None, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.add_to_token_name(to_lowercase!(ch)); @@ -1526,7 +1526,7 @@ impl<'stream> Tokenizer<'stream> { force_quirks: false, pub_identifier: None, sys_identifier: None, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.add_to_token_name(CHAR_REPLACEMENT); @@ -1539,7 +1539,7 @@ impl<'stream> Tokenizer<'stream> { force_quirks: true, pub_identifier: None, sys_identifier: None, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.state = State::Data; @@ -1553,7 +1553,7 @@ impl<'stream> Tokenizer<'stream> { force_quirks: true, pub_identifier: None, sys_identifier: None, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.state = State::Data; @@ -1564,7 +1564,7 @@ impl<'stream> Tokenizer<'stream> { force_quirks: false, pub_identifier: None, sys_identifier: None, - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.add_to_token_name(c.into()); @@ -2178,7 +2178,7 @@ impl<'stream> Tokenizer<'stream> { self.token_queue.push(Token::Text { text: value.to_string(), - location: self.last_token_location.clone(), + location: self.last_token_location, }); self.clear_consume_buffer(); diff --git a/crates/gosub_html5/src/tokenizer/token.rs b/crates/gosub_html5/src/tokenizer/token.rs index 977ef91e6..23104f527 100644 --- a/crates/gosub_html5/src/tokenizer/token.rs +++ b/crates/gosub_html5/src/tokenizer/token.rs @@ -79,12 +79,12 @@ impl Token { pub fn get_location(&self) -> Location { match self { - Token::DocType { location, .. } => location.clone(), - Token::StartTag { location, .. } => location.clone(), - Token::EndTag { location, .. } => location.clone(), - Token::Comment { location, .. } => location.clone(), - Token::Text { location, .. } => location.clone(), - Token::Eof { location, .. } => location.clone(), + Token::DocType { location, .. } => *location, + Token::StartTag { location, .. } => *location, + Token::EndTag { location, .. } => *location, + Token::Comment { location, .. } => *location, + Token::Text { location, .. } => *location, + Token::Eof { location, .. } => *location, } } diff --git a/crates/gosub_html5/src/writer.rs b/crates/gosub_html5/src/writer.rs index 5404b9bf7..8ffc7f886 100644 --- a/crates/gosub_html5/src/writer.rs +++ b/crates/gosub_html5/src/writer.rs @@ -87,7 +87,7 @@ impl, C: CssSystem> Visitor for DocumentWriter { fn doctype_enter(&mut self, node: &N) { if let Some(data) = node.get_doctype_data() { self.buffer.push_str("'); } } @@ -96,7 +96,7 @@ impl, C: CssSystem> Visitor for DocumentWriter { fn text_enter(&mut self, node: &N) { if let Some(data) = node.get_text_data() { - self.buffer.push_str(&data.value()); + self.buffer.push_str(data.value()); } } @@ -105,7 +105,7 @@ impl, C: CssSystem> Visitor for DocumentWriter { fn comment_enter(&mut self, node: &N) { if let Some(data) = node.get_comment_data() { self.buffer.push_str(""); } } @@ -114,7 +114,7 @@ impl, C: CssSystem> Visitor for DocumentWriter { fn element_enter(&mut self, node: &N) { if let Some(data) = node.get_element_data() { - self.buffer.push_str("<"); + self.buffer.push('<'); self.buffer.push_str(data.name()); for (name, value) in data.attributes() { diff --git a/crates/gosub_render_backend/src/layout.rs b/crates/gosub_render_backend/src/layout.rs index b49b213f3..a8912763b 100644 --- a/crates/gosub_render_backend/src/layout.rs +++ b/crates/gosub_render_backend/src/layout.rs @@ -1,5 +1,5 @@ +use gosub_shared::traits::css3::{CssProperty, CssSystem}; use std::fmt::Debug; -use gosub_shared::traits::css3::CssSystem; use gosub_shared::types::Result; use gosub_typeface::font::{Font, Glyph}; @@ -31,7 +31,6 @@ pub trait LayoutTree: Sized { pub trait Layouter: Sized + Clone { type Cache: Default; type Layout: Layout; - type CssSystem: CssSystem; type TextLayout: TextLayout; @@ -123,7 +122,6 @@ pub trait Layout: Default { } } -/// TODO: This struct should be removed and done somehow differently... pub trait Node { type Property: CssProperty; @@ -141,39 +139,6 @@ pub trait HasTextLayout { fn set_text_layout(&mut self, layout: L::TextLayout); } -pub trait CssProperty: Debug + Sized { - type Value: CssValue; - - fn compute_value(&mut self); - - fn unit_to_px(&self) -> f32; - - fn as_string(&self) -> Option<&str>; - fn as_percentage(&self) -> Option; - fn as_unit(&self) -> Option<(f32, &str)>; - fn as_color(&self) -> Option<(f32, f32, f32, f32)>; - - fn parse_color(&self) -> Option<(f32, f32, f32, f32)>; - - fn as_number(&self) -> Option; - fn as_list(&self) -> Option>; - - fn is_none(&self) -> bool; -} - -pub trait CssValue: Sized { - fn unit_to_px(&self) -> f32; - - fn as_string(&self) -> Option<&str>; - fn as_percentage(&self) -> Option; - fn as_unit(&self) -> Option<(f32, &str)>; - fn as_color(&self) -> Option<(f32, f32, f32, f32)>; - fn as_number(&self) -> Option; - fn as_list(&self) -> Option>; - - fn is_none(&self) -> bool; -} - pub trait TextLayout { type Font: Font; fn dbg_layout(&self) -> String; diff --git a/crates/gosub_render_backend/src/render_tree.rs b/crates/gosub_render_backend/src/render_tree.rs index a5dbc8c48..542ef4b89 100644 --- a/crates/gosub_render_backend/src/render_tree.rs +++ b/crates/gosub_render_backend/src/render_tree.rs @@ -1,23 +1,19 @@ -use std::collections::HashMap; -use std::fmt::{Debug, Formatter}; -use std::marker::PhantomData; -use log::warn; -use gosub_css3::matcher::property_definitions::get_css_definitions; -use gosub_css3::matcher::shorthands::FixList; -use gosub_css3::matcher::styling::{CssProperty, CssProperties, DeclarationProperty, prop_is_inherit}; -use gosub_css3::stylesheet::{CssDeclaration, CssValue, Specificity}; -use gosub_html5::document::document::TreeIterator; -use gosub_html5::node::data::element::ElementData; use crate::geo::Size; use crate::layout::{HasTextLayout, Layout, LayoutTree, Layouter, Node, TextLayout}; +use gosub_html5::document::document::TreeIterator; +use gosub_html5::node::data::element::ElementData; use gosub_shared::document::DocumentHandle; use gosub_shared::node::NodeId; -use gosub_shared::traits::css3::CssSystem; +use gosub_shared::traits::css3::CssStylesheet; +use gosub_shared::traits::css3::{CssProperty, CssPropertyMap, CssSystem}; use gosub_shared::traits::document::Document; -use gosub_shared::traits::node::{ElementDataType, Node as DocumentNode, TextDataType}; use gosub_shared::traits::node::NodeData; +use gosub_shared::traits::node::{ElementDataType, Node as DocumentNode, TextDataType}; use gosub_shared::types::Result; -use gosub_shared::traits::css3::CssStylesheet; +use log::warn; +use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; mod desc; @@ -29,18 +25,18 @@ const INLINE_ELEMENTS: [&str; 31] = [ /// Map of all declared values for all nodes in the document #[derive(Debug)] -pub struct RenderTree> { - pub nodes: HashMap>, +pub struct RenderTree, C: CssSystem> { + pub nodes: HashMap>, pub root: NodeId, pub dirty: bool, next_id: NodeId, - phantom_data: PhantomData, + handle: Option>, } #[allow(unused)] -impl> LayoutTree for RenderTree { +impl, C: CssSystem> LayoutTree for RenderTree { type NodeId = NodeId; - type Node = RenderTreeNode; + type Node = RenderTreeNode; fn children(&self, id: Self::NodeId) -> Option> { self.get_children(id).cloned() @@ -101,7 +97,7 @@ impl> LayoutTree for RenderTree } } -impl> RenderTree { +impl, C: CssSystem> RenderTree { // Generates a new render tree with a root node pub fn with_capacity(capacity: usize) -> Self { let mut tree = Self { @@ -109,14 +105,14 @@ impl> RenderTree { root: NodeId::root(), dirty: false, next_id: NodeId::from(1u64), - phantom_data: PhantomData, + handle: None, }; tree.insert_node( NodeId::root(), RenderTreeNode { id: NodeId::root(), - properties: CssProperties::new(), + properties: C::PropertyMap::default(), css_dirty: true, children: Vec::new(), parent: None, @@ -132,17 +128,17 @@ impl> RenderTree { } /// Returns the root node of the render tree - pub fn get_root(&self) -> &RenderTreeNode { + pub fn get_root(&self) -> &RenderTreeNode { self.nodes.get(&self.root).expect("root node") } /// Returns the node with the given id - pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { + pub fn get_node(&self, id: NodeId) -> Option<&RenderTreeNode> { self.nodes.get(&id) } /// Returns a mutable reference to the node with the given id - pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { + pub fn get_node_mut(&mut self, id: NodeId) -> Option<&mut RenderTreeNode> { self.nodes.get_mut(&id) } @@ -161,12 +157,12 @@ impl> RenderTree { /// Inserts a new node into the render tree, note that you are responsible for the node id /// and the children of the node - pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { + pub fn insert_node(&mut self, id: NodeId, node: RenderTreeNode) { self.nodes.insert(id, node); } /// Deletes the node with the given id from the render tree - pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { + pub fn delete_node(&mut self, id: &NodeId) -> Option<(NodeId, RenderTreeNode)> { // println!("Deleting node: {id:?}"); if let Some(n) = self.nodes.get(id) { @@ -203,41 +199,31 @@ impl> RenderTree { /// 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(); - } + props.properties.make_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(); - } + props.properties.make_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() + pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option<&C::Property> { + let props = self.nodes.get(&node_id)?; + + props.properties.get(prop_name) } /// 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> { + pub fn get_all_properties(&self, node_id: NodeId) -> Option<&C::PropertyMap> { self.nodes.get(&node_id).map(|props| &props.properties) } /// Generate a render tree from the given document - pub fn from_document(document: DocumentHandle) -> Self { + pub fn from_document(document: DocumentHandle) -> Self { let mut render_tree = RenderTree::with_capacity(document.get().node_count()); render_tree.generate_from(document); @@ -245,107 +231,27 @@ impl> RenderTree { render_tree } - fn generate_from(&mut self, handle: DocumentHandle) { + fn generate_from(&mut self, handle: DocumentHandle) { // Iterate the complete document tree - for current_node_id in TreeIterator::new(handle.clone()) { - let mut css_map_entry = CssProperties::new(); - - let doc = handle.get(); - let node = doc.node_by_id(current_node_id).expect("node not found"); - - // Extract name and namespace from the node if it's an element node - let mut node_element_namespace = ""; - let mut node_element_name = ""; - if let Some(data) = node.get_element_data() { - node_element_name = data.name().clone(); - node_element_namespace = data.namespace().clone(); - } - if node_is_unrenderable(node) { - if let Some(parent) = node.parent_id() { - if let Some(parent) = self.get_node_mut(parent) { - parent.children.retain(|id| *id != current_node_id); - // continue - //TODO: somehow we still can't continue here, I don't know why - } - } - - drop(doc); - - let mut doc = doc.get_mut(); - doc.detach_node_from_parent(current_node_id); - - continue; - } - - let definitions = get_css_definitions(); - - let mut fix_list = FixList::new(); - - for sheet in handle.get().stylesheets().iter() { - for rule in sheet.rules().iter() { - for selector in rule.selectors().iter() { - let (matched, specificity) = match_selector( - handle.clone(), - current_node_id, - selector, - ); - - if !matched { - continue; - } + let iter_handle = DocumentHandle::clone(&handle); - // 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 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; - }; - - let value = resolve_functions(&declaration.value, node, handle.clone()); - - // Check if the declaration matches the definition and return the "expanded" order - let res = definition.matches_and_shorthands(&value, &mut fix_list); - if !res { - warn!("Declaration does not match definition: {:?}", declaration); - continue; - } - - // create property for the given values - let property_name = declaration.property.clone(); - let decl = CssDeclaration { - property: property_name.to_string(), - value, - important: declaration.important, - }; - - add_property_to_map( - &mut css_map_entry, - sheet, - specificity.clone(), - &decl, - ); - } - } - } - } + for current_node_id in TreeIterator::new(iter_handle) { + let doc = handle.get(); - fix_list.resolve_nested(definitions); + let node = doc.node_by_id(current_node_id).unwrap(); - fix_list.apply(&mut css_map_entry); + let Some(properties) = + C::properties_from_node(node, doc.stylesheets(), handle.clone(), current_node_id) + else { + //we need to remove it from the parent in the render tree and from the document - let binding = handle.get(); - let current_node = binding.node_by_id(current_node_id).unwrap(); + todo!("unrenderable node"); + }; - let data = || RenderNodeData::from_node_data(current_node.data().clone()); + let data = node.data(); - let data = match data() { + let render_data = match RenderNodeData::from_node_data(data) { ControlFlow::Ok(data) => data, ControlFlow::Drop => continue, ControlFlow::Error(e) => { @@ -354,14 +260,26 @@ impl> RenderTree { } }; + let mut namespace: Option = None; + + let name = match data { + NodeData::Element(data) => { + namespace = Some(data.namespace().to_string()); + data.name().to_string() + } + NodeData::Text(_) => "#text".to_owned(), + NodeData::Document(_) => "#document".to_owned(), + _ => String::new(), + }; + let render_tree_node = RenderTreeNode { id: current_node_id, - properties: css_map_entry, - children: current_node.children().to_vec(), + properties, + children: node.children().to_vec(), parent: node.parent_id(), - name: node_element_name, // We might be able to move node into render_tree_node - namespace: node_element_namespace, - data, + name, // We might be able to move node into render_tree_node + namespace, + data: render_data, css_dirty: true, cache: L::Cache::default(), layout: L::Layout::default(), @@ -374,7 +292,7 @@ impl> RenderTree { self.remove_unrenderable_nodes(); - self.resolve_inheritance(self.root, &Vec::new()); + // self.resolve_inheritance(self.root, &Vec::new()); TODO: Implement inheritance if L::COLLAPSE_INLINE { self.collapse_inline(self.root); @@ -383,52 +301,6 @@ impl> RenderTree { // self.print_tree(); } - fn resolve_inheritance(&mut self, node_id: NodeId, inherit_props: &Vec<(String, CssValue)>) { - let Some(current_node) = self.get_node(node_id) else { - return; - }; - - for prop in inherit_props { - current_node - .properties - .entry(prop.0.clone()) - .or_insert_with(|| { - let mut p = CssProperty::new(prop.0.as_str()); - - p.inherited = prop.1.clone(); - - p - }); - } - - let mut inherit_props = inherit_props.clone(); - - 'props: for (name, prop) in &mut current_node.properties.properties { - prop.compute_value(); - - let value = prop.actual.clone(); - - if prop_is_inherit(name) { - for (k, v) in &mut inherit_props { - if k == name { - *v = value; - continue 'props; - } - } - - inherit_props.push((name.clone(), value)); - } - } - - let Some(children) = self.get_children(node_id) else { - return; - }; - - for child in children.clone() { - self.resolve_inheritance(child, &inherit_props); - } - } - /// Removes all unrenderable nodes from the render tree fn remove_unrenderable_nodes(&mut self) { // There are more elements that are not renderable, but for now we only remove the most common ones @@ -437,7 +309,7 @@ impl> RenderTree { for id in self.nodes.keys() { // 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" { + if prop.as_string() == Some("none") { delete_list.append(&mut self.get_child_node_ids(*id)); delete_list.push(*id); continue; @@ -488,7 +360,7 @@ impl> RenderTree { } else { let wrapper_node = RenderTreeNode { id: self.next_id, - properties: CssProperties::new(), + properties: C::PropertyMap::default(), css_dirty: true, children: vec![child_id], parent: Some(node_id), @@ -555,72 +427,39 @@ impl> RenderTree { } // Generates a declaration property and adds it to the css_map_entry -pub fn add_property_to_map( - css_map_entry: &mut CssProperties, - sheet: &gosub_css3::stylesheet::CssStylesheet, - specificity: Specificity, - declaration: &CssDeclaration, -) { - let property_name = declaration.property.clone(); - // 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); - // } - // } - // - let declaration = DeclarationProperty { - // @todo: this seems wrong. We only get the first values from the declared values - value: declaration.value.first().unwrap().clone(), - origin: sheet.origin.clone(), - important: declaration.important, - location: sheet.location.clone(), - specificity, - }; - - if let std::collections::hash_map::Entry::Vacant(e) = - css_map_entry.properties.entry(property_name.clone()) - { - // Generate new property in the css map - let mut entry = CssProperty::new(property_name.as_str()); - entry.declared.push(declaration); - e.insert(entry); - } else { - // Just add the declaration to the existing property - let entry = css_map_entry.properties.get_mut(&property_name).unwrap(); - entry.declared.push(declaration); - } -} -pub enum RenderNodeData> { +pub enum RenderNodeData { Document, - Element(Box>), + Element, Text(Box>), AnonymousInline, } -impl> Debug for RenderNodeData { +impl Debug for RenderNodeData { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { RenderNodeData::Document => f.write_str("Document"), - RenderNodeData::Element(data) => { - f.debug_struct("ElementData").field("data", data).finish() - } + RenderNodeData::Element => f.write_str("Element"), RenderNodeData::Text(data) => f.debug_struct("TextData").field("data", data).finish(), RenderNodeData::AnonymousInline => f.write_str("AnonymousInline"), } } } +// impl Debug for RenderNodeData { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// RenderNodeData::Document => f.write_str("Document"), +// RenderNodeData::Element => { +// f.debug_struct("ElementData").field("data", data).finish() +// } +// RenderNodeData::Text(data) => f.debug_struct("TextData").field("data", data).finish(), +// RenderNodeData::AnonymousInline => f.write_str("AnonymousInline"), +// } +// } +// } +// + pub struct TextData { pub text: String, pub layout: Option, @@ -641,12 +480,14 @@ pub enum ControlFlow { Error(anyhow::Error), } -impl> RenderNodeData { - pub fn from_node_data(node: NodeData) -> ControlFlow { +impl RenderNodeData { + pub fn from_node_data, C: CssSystem>( + node: NodeData, + ) -> ControlFlow { ControlFlow::Ok(match node { - NodeData::Element(data) => RenderNodeData::Element(data), + NodeData::Element(_) => RenderNodeData::Element, NodeData::Text(data) => { - let text = pre_transform_text(data.value()); + let text = pre_transform_text(data.string_value()); RenderNodeData::Text(Box::new(TextData { text, layout: None })) } @@ -675,20 +516,20 @@ fn pre_transform_text(text: String) -> String { new_text } -pub struct RenderTreeNode> { +pub struct RenderTreeNode { pub id: NodeId, - pub properties: CssProperties, + pub properties: C::PropertyMap, pub css_dirty: bool, pub children: Vec, pub parent: Option, pub name: String, pub namespace: Option, - pub data: RenderNodeData, + pub data: RenderNodeData, pub cache: L::Cache, pub layout: L::Layout, } -impl> Debug for RenderTreeNode { +impl Debug for RenderTreeNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RenderTreeNode") .field("id", &self.id) @@ -703,10 +544,10 @@ impl> Debug for RenderTreeNode { } } -impl> RenderTreeNode { +impl RenderTreeNode { /// Returns true if the node is an element node pub fn is_element(&self) -> bool { - matches!(self.data, RenderNodeData::Element(_)) + matches!(self.data, RenderNodeData::Element) } /// Returns true if the node is a text node @@ -715,16 +556,8 @@ impl> RenderTreeNode { } /// Returns the requested property for the node - pub fn get_property(&mut self, prop_name: &str) -> Option<&mut CssProperty> { - self.properties.properties.get_mut(prop_name) - } - - /// Returns the requested attribute for the node - pub fn get_attribute(&self, attr_name: &str) -> Option<&String> { - match &self.data { - RenderNodeData::Element(element) => element.attributes.get(attr_name), - _ => None, - } + pub fn get_property(&mut self, prop_name: &str) -> Option<&mut C::Property> { + self.properties.get_mut(prop_name) } pub fn is_inline(&mut self) -> bool { @@ -733,7 +566,7 @@ impl> RenderTreeNode { } if let Some(d) = self.properties.get("display").and_then(|prop| { - let CssValue::String(val) = &prop.actual else { + let Some(val) = prop.as_string() else { return None; }; @@ -761,7 +594,7 @@ impl> RenderTreeNode { } } -impl> HasTextLayout for RenderTreeNode { +impl HasTextLayout for RenderTreeNode { fn set_text_layout(&mut self, layout: L::TextLayout) { if let RenderNodeData::Text(text) = &mut self.data { text.layout = Some(layout); @@ -769,11 +602,11 @@ impl> HasTextLayout for RenderTreeNode } } -impl> Node for RenderTreeNode { - type Property = CssProperty; +impl Node for RenderTreeNode { + type Property = C::Property; fn get_property(&mut self, name: &str) -> Option<&mut Self::Property> { - self.properties.properties.get_mut(name) + self.get_property(name) } fn text_data(&self) -> Option<&str> { if let RenderNodeData::Text(text) = &self.data { @@ -796,194 +629,15 @@ impl> Node for RenderTreeNode { } } -pub struct Value(pub CssValue); - -impl crate::layout::CssProperty for CssProperty { - type Value = Value; - - fn compute_value(&mut self) { - self.compute_value(); - } - fn unit_to_px(&self) -> f32 { - self.actual.unit_to_px() - } - - fn as_string(&self) -> Option<&str> { - if let CssValue::String(str) = &self.actual { - Some(str) - } else { - None - } - } - - fn as_percentage(&self) -> Option { - if let CssValue::Percentage(percent) = &self.actual { - Some(*percent) - } else { - None - } - } - - fn as_unit(&self) -> Option<(f32, &str)> { - if let CssValue::Unit(value, unit) = &self.actual { - Some((*value, unit)) - } else { - None - } - } - - fn as_color(&self) -> Option<(f32, f32, f32, f32)> { - if let CssValue::Color(color) = &self.actual { - Some((color.r, color.g, color.b, color.a)) - } else { - None - } - } - - fn parse_color(&self) -> Option<(f32, f32, f32, f32)> { - self.actual - .to_color() - .map(|color| (color.r, color.g, color.b, color.a)) - } - - fn as_number(&self) -> Option { - if let CssValue::Number(num) = &self.actual { - Some(*num) - } else { - None - } - } - - fn as_list(&self) -> Option> { - if let CssValue::List(list) = &self.actual { - Some(list.iter().map(|v| v.clone().into()).collect()) - } else { - None - } - } - - fn is_none(&self) -> bool { - matches!(self.actual, CssValue::None) - } -} - -impl crate::layout::CssValue for Value { - fn unit_to_px(&self) -> f32 { - self.0.unit_to_px() - } - - fn as_string(&self) -> Option<&str> { - if let CssValue::String(str) = &self.0 { - Some(str) - } else { - None - } - } - - fn as_percentage(&self) -> Option { - if let CssValue::Percentage(percent) = &self.0 { - Some(*percent) - } else { - None - } - } - - fn as_unit(&self) -> Option<(f32, &str)> { - if let CssValue::Unit(value, unit) = &self.0 { - Some((*value, unit)) - } else { - None - } - } - - fn as_color(&self) -> Option<(f32, f32, f32, f32)> { - if let CssValue::Color(color) = &self.0 { - Some((color.r, color.g, color.b, color.a)) - } else { - None - } - } - - fn as_number(&self) -> Option { - if let CssValue::Number(num) = &self.0 { - Some(*num) - } else { - None - } - } - - fn as_list(&self) -> Option> { - if let CssValue::List(list) = &self.0 { - Some(list.iter().map(|v| v.clone().into()).collect()) - } else { - None - } - } - - fn is_none(&self) -> bool { - matches!(self.0, CssValue::None) - } -} - -impl From for Value { - fn from(val: CssValue) -> Self { - Value(val) - } -} - /// Generates a render tree for the given document based on its loaded stylesheets -pub fn generate_render_tree, D: Document + Clone, C: CssSystem>(document: DocumentHandle) -> Result> { +pub fn generate_render_tree, C: CssSystem>( + document: DocumentHandle, +) -> Result> { let render_tree = RenderTree::from_document(document); Ok(render_tree) } -pub fn node_is_unrenderable, C: CssSystem>(node: &D::Node) -> bool { - // There are more elements that are not renderable, but for now we only remove the most common ones - - const REMOVABLE_ELEMENTS: [&str; 6] = ["head", "script", "style", "svg", "noscript", "title"]; - - if let Some(element_data) = node.get_element_data() { - if REMOVABLE_ELEMENTS.contains(&element_data.name()) { - return true; - } - } - - if let Some(text_data) = &node.get_text_data() { - if text_data.value().chars().all(|c| c.is_whitespace()) { - return true; - } - } - - false -} - -pub fn resolve_functions, C: CssSystem>( - value: &[CssValue], - node: &impl DocumentNode, - handle: DocumentHandle, -) -> Vec { - let mut result = Vec::with_capacity(value.len()); //TODO: we could give it a &mut Vec and reuse the allocation - - for val in value { - match val { - CssValue::Function(func, values) => { - let resolved = match func.as_str() { - "calc" => resolve_calc(values), - "attr" => resolve_attr(values, node), - "var" => resolve_var(values, handle.clone(), node), - _ => vec![val.clone()], - }; - - result.extend(resolved); - } - _ => result.push(val.clone()), - } - } - - result -} - // pub fn walk_render_tree(tree: &RenderTree, visitor: &mut Box>) { // let root = tree.get_root(); // internal_walk_render_tree(tree, root, visitor); diff --git a/crates/gosub_render_backend/src/render_tree/desc.rs b/crates/gosub_render_backend/src/render_tree/desc.rs index 95eae76ec..f017baaf3 100644 --- a/crates/gosub_render_backend/src/render_tree/desc.rs +++ b/crates/gosub_render_backend/src/render_tree/desc.rs @@ -1,10 +1,11 @@ -use crate::render_tree::{RenderNodeData, RenderTree}; use crate::layout::{Layout, Layouter}; +use crate::render_tree::{RenderNodeData, RenderTree}; use crate::{NodeDesc, Point, Size}; use gosub_shared::node::NodeId; +use gosub_shared::traits::css3::{CssPropertyMap, CssSystem}; use gosub_shared::traits::document::Document; -impl> RenderTree { +impl, C: CssSystem> RenderTree { pub fn desc(&self) -> NodeDesc { self.desc_node(self.root) } @@ -23,11 +24,15 @@ impl> RenderTree { }; }; - let attributes = if let RenderNodeData::Element(e) = &node.data { - e.attributes - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect() + let attributes = if let RenderNodeData::Element = &node.data { + // we need to get the attributes from the document, not from the render tree + + todo!("attributes") + + // e.attributes + // .iter() + // .map(|(k, v)| (k.clone(), v.clone())) + // .collect() } else { vec![] }; @@ -48,10 +53,9 @@ impl> RenderTree { .collect(), attributes, properties: node - .properties .properties .iter() - .map(|(k, v)| (k.clone(), v.actual.to_string())) + .map(|(k, v)| (k.to_owned(), format!("{v:?}"))) .collect(), text, pos: node.layout.rel_pos(), diff --git a/crates/gosub_shared/src/traits/css3.rs b/crates/gosub_shared/src/traits/css3.rs index a8f4c22ff..f1fb9fe72 100644 --- a/crates/gosub_shared/src/traits/css3.rs +++ b/crates/gosub_shared/src/traits/css3.rs @@ -1,5 +1,9 @@ +use crate::document::DocumentHandle; +use crate::node::NodeId; +use crate::traits::document::Document; use crate::traits::ParserConfig; use crate::types::Result; +use std::fmt::{Debug, Display}; /// Defines the origin of the stylesheet (or declaration) #[derive(Debug, PartialEq, Clone, Copy)] @@ -12,14 +16,31 @@ pub enum CssOrigin { User, } - /// The CssSystem trait is a trait that defines all things CSS3 that are used by other non-css3 crates. This is the main trait that /// is used to parse CSS3 files. It contains sub elements like the Stylesheet trait that is used in for instance the Document trait. pub trait CssSystem: Clone { type Stylesheet: CssStylesheet; + type PropertyMap: CssPropertyMap; + + type Property: CssProperty; + /// Parses a string into a CSS3 stylesheet - fn parse_str(str: &str, config: ParserConfig, origin: CssOrigin, source_url: &str) -> Result; + fn parse_str( + str: &str, + config: ParserConfig, + origin: CssOrigin, + source_url: &str, + ) -> Result; + + /// Returns the properties of a node + /// If `None` is returned, the node is not renderable + fn properties_from_node>( + node: &D::Node, + sheets: &[Self::Stylesheet], + handle: DocumentHandle, + id: NodeId, + ) -> Option; } pub trait CssStylesheet: PartialEq { @@ -28,4 +49,52 @@ pub trait CssStylesheet: PartialEq { /// Returns the source URL of the stylesheet fn location(&self) -> &str; -} \ No newline at end of file +} + +pub trait CssPropertyMap: Default + Debug { + type Property: CssProperty; + + fn get(&self, name: &str) -> Option<&Self::Property>; + + fn get_mut(&mut self, name: &str) -> Option<&mut Self::Property>; + + fn make_dirty(&mut self); + + fn iter(&self) -> impl Iterator + '_; + + fn iter_mut(&mut self) -> impl Iterator + '_; + + fn make_clean(&mut self); +} +pub trait CssProperty: Debug + Sized { + type Value: CssValue; + + fn compute_value(&mut self); // this should probably be removed + + fn unit_to_px(&self) -> f32; + + fn as_string(&self) -> Option<&str>; + fn as_percentage(&self) -> Option; + fn as_unit(&self) -> Option<(f32, &str)>; + fn as_color(&self) -> Option<(f32, f32, f32, f32)>; + + fn parse_color(&self) -> Option<(f32, f32, f32, f32)>; + + fn as_number(&self) -> Option; + fn as_list(&self) -> Option>; + + fn is_none(&self) -> bool; +} + +pub trait CssValue: Sized { + fn unit_to_px(&self) -> f32; + + fn as_string(&self) -> Option<&str>; + fn as_percentage(&self) -> Option; + fn as_unit(&self) -> Option<(f32, &str)>; + fn as_color(&self) -> Option<(f32, f32, f32, f32)>; + fn as_number(&self) -> Option; + fn as_list(&self) -> Option>; + + fn is_none(&self) -> bool; +} diff --git a/crates/gosub_shared/src/traits/node.rs b/crates/gosub_shared/src/traits/node.rs index 2adcf1cc2..a7be9331c 100644 --- a/crates/gosub_shared/src/traits/node.rs +++ b/crates/gosub_shared/src/traits/node.rs @@ -1,11 +1,10 @@ -use std::cell::{Ref, RefMut}; -use crate::traits::document::DocumentFragment; -use std::collections::HashMap; use crate::byte_stream::Location; use crate::document::DocumentHandle; use crate::node::NodeId; use crate::traits::css3::CssSystem; use crate::traits::document::Document; +use crate::traits::document::DocumentFragment; +use std::collections::HashMap; #[derive(PartialEq, Debug, Copy, Clone)] pub enum QuirksMode { @@ -34,6 +33,14 @@ pub enum NodeData<'a, C: CssSystem, N: Node> { Element(&'a N::ElementData), } +impl> Copy for NodeData<'_, C, N> {} + +impl> Clone for NodeData<'_, C, N> { + fn clone(&self) -> Self { + *self + } +} + pub trait DocumentDataType { fn quirks_mode(&self) -> QuirksMode; fn set_quirks_mode(&mut self, quirks_mode: QuirksMode); @@ -47,6 +54,8 @@ pub trait DocTypeDataType { pub trait TextDataType { fn value(&self) -> &str; + + fn string_value(&self) -> String; fn value_mut(&mut self) -> &mut String; } @@ -97,7 +106,11 @@ pub trait Node: Clone + PartialEq { type DocTypeData: DocTypeDataType; type TextData: TextDataType; type CommentData: CommentDataType; - type ElementData: ElementDataType>::Fragment>; + type ElementData: ElementDataType< + C, + Document = Self::Document, + DocumentFragment = >::Fragment, + >; /// Return the ID of the node fn id(&self) -> NodeId; @@ -119,16 +132,16 @@ pub trait Node: Clone + PartialEq { fn type_of(&self) -> NodeType; fn is_element_node(&self) -> bool; - fn get_element_data(&self) -> Option>; - fn get_element_data_mut(&self) -> Option>; + fn get_element_data(&self) -> Option<&Self::ElementData>; + fn get_element_data_mut(&mut self) -> Option<&mut Self::ElementData>; fn is_text_node(&self) -> bool; - fn get_text_data(&self) -> Option>; - fn get_text_data_mut(&self) -> Option>; + fn get_text_data(&self) -> Option<&Self::TextData>; + fn get_text_data_mut(&mut self) -> Option<&mut Self::TextData>; + + fn get_comment_data(&self) -> Option<&Self::CommentData>; + fn get_doctype_data(&self) -> Option<&Self::DocTypeData>; - fn get_comment_data(&self) -> Option>; - fn get_doctype_data(&self) -> Option>; - /// Returns the document handle of the node fn handle(&self) -> DocumentHandle; /// Removes a child node from the node @@ -137,4 +150,6 @@ pub trait Node: Clone + PartialEq { fn insert(&mut self, node_id: NodeId, idx: usize); /// Pushes a child node to the node fn push(&mut self, node_id: NodeId); + + fn data(&self) -> NodeData; } diff --git a/crates/gosub_testing/src/testing/tokenizer.rs b/crates/gosub_testing/src/testing/tokenizer.rs index 91c5cdc1f..af6f2a1af 100644 --- a/crates/gosub_testing/src/testing/tokenizer.rs +++ b/crates/gosub_testing/src/testing/tokenizer.rs @@ -1,12 +1,10 @@ -use gosub_html5::parser::errors::ErrorLogger; use super::FIXTURE_ROOT; +use gosub_html5::parser::errors::ErrorLogger; use gosub_html5::tokenizer::ParserData; -use gosub_html5::{ - tokenizer::{ - state::State as TokenState, - token::Token, - {Options, Tokenizer}, - }, +use gosub_html5::tokenizer::{ + state::State as TokenState, + token::Token, + {Options, Tokenizer}, }; use gosub_shared::byte_stream::{ByteStream, Config, Encoding, Location}; use gosub_shared::types::Result; @@ -315,7 +313,7 @@ impl TestSpec { location, } => Token::Comment { comment: escape(value), - location: location.clone(), + location: *location, }, Token::DocType { @@ -329,7 +327,7 @@ impl TestSpec { force_quirks: *force_quirks, pub_identifier: pub_identifier.as_ref().map(Into::into), sys_identifier: sys_identifier.as_ref().map(Into::into), - location: location.clone(), + location: *location, }, Token::EndTag { @@ -339,11 +337,11 @@ impl TestSpec { } => Token::EndTag { name: escape(name), is_self_closing: *is_self_closing, - location: location.clone(), + location: *location, }, Token::Eof { location } => Token::Eof { - location: location.clone(), + location: *location, }, Token::StartTag { @@ -355,7 +353,7 @@ impl TestSpec { name: escape(name), is_self_closing: *is_self_closing, attributes: attributes.clone(), - location: location.clone(), + location: *location, }, Token::Text { @@ -363,7 +361,7 @@ impl TestSpec { location, } => Token::Text { text: escape(value), - location: location.clone(), + location: *location, }, } }