diff --git a/crates/gosub_html5/src/node.rs b/crates/gosub_html5/src/node.rs
index 69e39095e..0a4f08bff 100644
--- a/crates/gosub_html5/src/node.rs
+++ b/crates/gosub_html5/src/node.rs
@@ -130,6 +130,12 @@ pub struct Node {
pub is_registered: bool,
}
+impl Node {
+ pub fn is_root(&self) -> bool {
+ self.id.is_root()
+ }
+}
+
impl PartialEq for Node {
fn eq(&self, other: &Node) -> bool {
self.id == other.id
diff --git a/crates/gosub_html5/src/parser/document.rs b/crates/gosub_html5/src/parser/document.rs
index 1ccb802c5..518327993 100755
--- a/crates/gosub_html5/src/parser/document.rs
+++ b/crates/gosub_html5/src/parser/document.rs
@@ -339,6 +339,14 @@ impl Document {
None
}
+ /// Returns the parent node of the given node, or None when no parent is found
+ pub fn parent_node(&self, node: &Node) -> Option<&Node> {
+ match node.parent {
+ Some(parent_node_id) => self.get_node_by_id(parent_node_id),
+ None => None,
+ }
+ }
+
pub fn add_new_node(&mut self, node: Node) -> NodeId {
// if a node contains attributes when adding to the tree,
// be sure to handle the special attributes "id" and "class"
diff --git a/crates/gosub_styling/src/calculator.rs b/crates/gosub_styling/src/calculator.rs
deleted file mode 100644
index b30e04ed1..000000000
--- a/crates/gosub_styling/src/calculator.rs
+++ /dev/null
@@ -1,549 +0,0 @@
-use crate::css_colors::RgbColor;
-use core::fmt::Debug;
-use gosub_css3::convert::ast_converter::convert_ast_to_stylesheet;
-use gosub_css3::parser_config::ParserConfig;
-use gosub_css3::stylesheet::{
- CssOrigin, CssSelector, CssSelectorPart, CssSelectorType, CssStylesheet, MatcherType,
- Specificity,
-};
-use gosub_css3::Css3;
-use gosub_html5::node::{Node, NodeId};
-use gosub_html5::parser::document::{Document, DocumentHandle, TreeIterator};
-use std::cmp::Ordering;
-use std::collections::HashMap;
-use std::fs;
-
-/// Style calculator will generate a declared values map for all nodes in the document based on the stylesheets given
-pub struct StyleCalculator {
- /// List of stylesheets to use for the calculation
- stylesheets: Vec,
- /// Document to calculate the styles for
- document: DocumentHandle,
- /// Map of all declared values for all nodes in the document. Will be generated by the calculator
- css_map: CssMap,
-}
-
-impl StyleCalculator {
- /// Creates a new style calculator for the given document
- pub fn new(document: DocumentHandle) -> Self {
- let mut sheets = vec![];
- for css in document.get().stylesheets.iter() {
- sheets.push(css.clone());
- }
-
- let mut this = Self {
- stylesheets: Vec::new(),
- document,
- css_map: CssMap::new(),
- };
-
- for sheet in sheets {
- this.add_stylesheet(sheet);
- }
-
- this
- }
-
- /// Adds another stylesheet to the calculator. Order is not important (@todo: is it?)
- pub fn add_stylesheet(&mut self, stylesheet: CssStylesheet) {
- self.stylesheets.push(stylesheet);
- }
-
- /// Extracts all declared values from the stylesheets and stores them in the calculator
- pub fn find_declared_values(&mut self) {
- // Restart css map
- self.css_map = CssMap::new();
-
- // Iterate the complete document tree
- let tree_iterator = TreeIterator::new(&self.document);
- for current_node_id in tree_iterator {
- let mut css_map_entry = CssMapEntry::new();
-
- let doc = self.document.get();
- let node = doc.get_node_by_id(current_node_id).expect("node not found");
- if !node.is_element() {
- continue;
- }
-
- for sheet in self.stylesheets.iter() {
- for rule in sheet.rules.iter() {
- for selector in rule.selectors().iter() {
- if !self.match_selector(current_node_id, selector) {
- continue;
- }
-
- // Selector matched, so we add all declared values to the map
- for declaration in rule.declarations().iter() {
- let property = declaration.property.clone();
-
- let declaration = DeclarationProperty {
- value: declaration.value.clone(),
- origin: sheet.origin.clone(),
- important: declaration.important,
- location: "".into(),
- specificity: selector.specificity(),
- };
-
- if let std::collections::hash_map::Entry::Vacant(e) =
- css_map_entry.properties.entry(property.clone())
- {
- let mut entry = ValueEntry::new();
- entry.declared.push(declaration);
- e.insert(entry);
- } else {
- let entry = css_map_entry.properties.get_mut(&property).unwrap();
- entry.declared.push(declaration);
- }
- }
- }
- }
- }
-
- self.css_map.nodes.insert(current_node_id, css_map_entry);
- }
- }
-
- /// Orders all declared values and finds the cascaded values
- pub fn find_cascaded_values(&mut self) {
- for (_, css_map_entry) in self.css_map.nodes.iter_mut() {
- for (_, entry) in css_map_entry.properties.iter_mut() {
- // Sort on origin and importance
- entry.declared.sort();
-
- // sort on specificity
- entry.declared.sort_by(|a, b| {
- if a.priority() != b.priority() {
- return Ordering::Equal;
- }
- a.specificity.cmp(&b.specificity)
- });
-
- // @todo: sort on scoping proximity
-
- // order of appearance in the stylesheet. We use the last entry as the cascaded value
- entry.cascaded = entry.declared.last().map(|d| d.value.clone());
- }
- }
- }
-
- pub fn find_specified_values(&mut self) {
- for (_, css_map_entry) in self.css_map.nodes.iter_mut() {
- for (_, entry) in css_map_entry.properties.iter_mut() {
- match entry.cascaded {
- Some(ref cascaded) => {
- entry.specified = cascaded.clone();
- }
- None => {
- // @todo: find default value for this property
- entry.specified = "".into();
- }
- }
- }
- }
- }
-
- /// Returns the list of all properties (cascaded values for now) for the given node
- pub fn get_css_properties_for_node(&self, node_id: NodeId) -> Option<&CssMapEntry> {
- self.css_map.nodes.get(&node_id)
- }
-
- // Matches a complete selector (all parts) against the given node(id)
- fn match_selector(&self, node_id: NodeId, selector: &CssSelector) -> bool {
- let mut parts = selector.parts.clone();
- parts.reverse();
- self.match_selector_part(node_id, &mut parts)
- }
-
- /// Returns true when the given node matches the part(s)
- fn match_selector_part(
- &self,
- node_id: NodeId,
- selector_parts: &mut Vec,
- ) -> bool {
- let binding = self.document.get();
- let mut next_current_node = Some(binding.get_node_by_id(node_id).expect("node not found"));
-
- while !selector_parts.is_empty() {
- if next_current_node.is_none() {
- return false;
- }
- let current_node = next_current_node.expect("current_node not found");
- let part = selector_parts.remove(0);
-
- match part.type_ {
- CssSelectorType::Universal => {
- // '*' always matches any selector
- }
- CssSelectorType::Type => {
- if part.value != current_node.as_element().name {
- return false;
- }
- }
- CssSelectorType::Class => {
- if !current_node.as_element().classes.contains(&part.value) {
- return false;
- }
- }
- CssSelectorType::Id => {
- if current_node
- .as_element()
- .attributes
- .get("id")
- .unwrap_or(&"".to_string())
- != &part.value
- {
- return false;
- }
- }
- CssSelectorType::Attribute => {
- let wanted_attr_name = part.name.clone();
-
- if !current_node.has_attribute(&wanted_attr_name) {
- return false;
- }
-
- let mut wanted_attr_value = part.value.clone();
- let mut got_attr_value = current_node
- .get_attribute(&wanted_attr_name)
- .unwrap_or(&"".to_string())
- .to_string();
-
- // If we need to match case-insensitive, just convert everything to lowercase for comparison
- if part.flags.eq_ignore_ascii_case("i") {
- wanted_attr_value = wanted_attr_value.to_lowercase();
- got_attr_value = got_attr_value.to_lowercase();
- };
-
- match part.matcher {
- MatcherType::None => {
- // Just the presence of the attribute is enough
- return true;
- }
- MatcherType::Equals => {
- // Exact match
- return wanted_attr_value == got_attr_value;
- }
- MatcherType::Includes => {
- // Contains word
- return wanted_attr_value
- .split_whitespace()
- .any(|s| s == got_attr_value);
- }
- MatcherType::DashMatch => {
- // Exact value or value followed by a hyphen
- return got_attr_value == wanted_attr_value
- || got_attr_value.starts_with(&format!("{}-", wanted_attr_value));
- }
- MatcherType::PrefixMatch => {
- // Starts with
- return got_attr_value.starts_with(&wanted_attr_value);
- }
- MatcherType::SuffixMatch => {
- // Ends with
- return got_attr_value.ends_with(&wanted_attr_value);
- }
- MatcherType::SubstringMatch => {
- // Contains
- return got_attr_value.contains(&wanted_attr_value);
- }
- }
- }
- CssSelectorType::PseudoClass => {
- // @Todo: implement pseudo classes
- if part.value == "link" {
- return false;
- }
- return false;
- }
- CssSelectorType::PseudoElement => {
- // @Todo: implement pseudo elements
- if part.value == "first-child" {
- return false;
- }
- return false;
- }
- CssSelectorType::Combinator => {
- // We don't have the descendant combinator (space), as this is the default behaviour
- match part.value.as_str() {
- // @todo: We also should do: column combinator ('||' experimental)
- // @todo: Namespace combinator ('|')
- " " => {
- // Descendant combinator, any parent that matches the previous selector will do
- if !self.match_selector_part(current_node.id, selector_parts) {
- // we insert the combinator back so we the next loop will match against the parent node
- selector_parts.insert(0, part);
- }
- }
- ">" => {
- // Child combinator. Only matches the direct child
- if !self.match_selector_part(current_node.id, selector_parts) {
- return false;
- }
- }
- "+" => {
- // We need to match the previous sibling of the current node
- }
- "~" => {
- // We need to match the previous siblings of the current node
- }
- _ => {
- panic!("Unknown combinator: {}", part.value);
- }
- }
- }
- }
-
- // We have matched this part, so we move up the chain
- next_current_node = parent_node(&binding, current_node);
- }
-
- // All parts of the selector have matched
- true
- }
-}
-
-/// Returns the parent node of the given node, or None when no parent is found
-fn parent_node<'b>(doc: &'b Document, node: &'b Node) -> Option<&'b Node> {
- // Find the next element node in the parent chain. Will return None if we hit the root of the chain
- let mut cur_node = node;
-
- loop {
- let node_id = cur_node.parent;
- node_id?;
-
- cur_node = doc
- .get_node_by_id(node_id.expect("node_id"))
- .expect("node not found");
- if cur_node.is_element() {
- return Some(cur_node);
- }
- }
-}
-
-/// Loads the default user agent stylesheet
-pub fn load_default_useragent_stylesheet() -> anyhow::Result {
- // @todo: we should be able to browse to gosub://useragent.css and see the actual useragent css file
- let location = "gosub://useragent.css";
- let config = ParserConfig {
- source: Some(String::from(location)),
- ignore_errors: true,
- ..Default::default()
- };
-
- let css =
- fs::read_to_string("resources/useragent.css").expect("Could not load useragent stylesheet");
- let css_ast = Css3::parse(css.as_str(), config).expect("Could not parse useragent stylesheet");
-
- convert_ast_to_stylesheet(&css_ast, CssOrigin::UserAgent, location)
-}
-
-/// A declarationProperty defines a single value for a property (color: red;). It consists of the value,
-/// origin, importance, location and specificity of the declaration.
-#[derive(Debug)]
-pub struct DeclarationProperty {
- /// The actual value of the property
- pub value: String,
- /// Origin of the declaration (user stylesheet, author stylesheet etc)
- pub origin: CssOrigin,
- /// Whether the declaration is !important
- pub important: bool,
- /// The location of the declaration in the stylesheet (name.css:123) or empty
- pub location: String,
- /// The specificity of the selector that declared this property
- pub specificity: Specificity,
-}
-
-impl DeclarationProperty {
- /// Priority of the declaration based on the origin and importance as defined in https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade
- fn priority(&self) -> u8 {
- match self.origin {
- CssOrigin::UserAgent => {
- if self.important {
- 7
- } else {
- 1
- }
- }
- CssOrigin::User => {
- if self.important {
- 6
- } else {
- 2
- }
- }
- CssOrigin::Author => {
- if self.important {
- 5
- } else {
- 3
- }
- }
- }
- }
-}
-
-impl PartialEq for DeclarationProperty {
- fn eq(&self, other: &Self) -> bool {
- self.priority() == other.priority()
- }
-}
-
-impl PartialOrd for DeclarationProperty {
- fn partial_cmp(&self, other: &Self) -> Option {
- Some(self.cmp(other))
- }
-}
-
-impl Eq for DeclarationProperty {}
-
-impl Ord for DeclarationProperty {
- fn cmp(&self, other: &Self) -> Ordering {
- self.priority().cmp(&other.priority())
- }
-}
-
-/// A value entry contains all values for a single property for a single node. It contains the declared values, and
-/// all the computed values.
-#[derive(Debug)]
-pub struct ValueEntry {
- /// List of all declared values for this property
- pub declared: Vec,
- /// Cascaded value
- pub cascaded: Option,
- pub specified: String,
- pub computed: String,
- pub used: String,
- pub actual: String,
-}
-
-impl Default for ValueEntry {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl ValueEntry {
- pub fn new() -> Self {
- Self {
- declared: Vec::new(),
- cascaded: None,
- specified: "".into(),
- computed: "".into(),
- used: "".into(),
- actual: "".into(),
- }
- }
-}
-
-/// Map of all declared values for a single node
-pub struct CssMapEntry {
- properties: HashMap,
-}
-
-impl CssMapEntry {
- pub fn new() -> Self {
- Self {
- properties: HashMap::new(),
- }
- }
-
- // @todo: This should not be here. When we resolve a property, we should also resolve the value so we can
- // use entry.actual or something
- pub fn get_color_value(&self, prop_name: &str) -> Option {
- let prop_name = prop_name.to_lowercase();
-
- self.properties
- .get(&prop_name)
- .map(|entry| RgbColor::from(entry.specified.as_ref()))
- }
-}
-
-impl Default for CssMapEntry {
- fn default() -> Self {
- Self::new()
- }
-}
-
-/// Map of all declared values for all nodes in the document
-pub struct CssMap {
- nodes: HashMap,
-}
-
-impl CssMap {
- fn new() -> Self {
- Self {
- nodes: HashMap::new(),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn compare_declared() {
- let a = DeclarationProperty {
- value: "red".into(),
- origin: CssOrigin::Author,
- important: false,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
- let b = DeclarationProperty {
- value: "blue".into(),
- origin: CssOrigin::UserAgent,
- important: false,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
- let c = DeclarationProperty {
- value: "green".into(),
- origin: CssOrigin::User,
- important: false,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
- let d = DeclarationProperty {
- value: "yellow".into(),
- origin: CssOrigin::Author,
- important: true,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
- let e = DeclarationProperty {
- value: "orange".into(),
- origin: CssOrigin::UserAgent,
- important: true,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
- let f = DeclarationProperty {
- value: "purple".into(),
- origin: CssOrigin::User,
- important: true,
- location: "".into(),
- specificity: Specificity::new(1, 0, 0),
- };
-
- assert_eq!(3, a.priority());
- assert_eq!(1, b.priority());
- assert_eq!(2, c.priority());
- assert_eq!(5, d.priority());
- assert_eq!(7, e.priority());
- assert_eq!(6, f.priority());
-
- assert!(a > b);
- assert!(b < c);
- assert!(c < d);
- assert!(d < e);
- assert!(f < e);
- assert!(a < e);
- assert!(b < d);
- assert!(a < d);
- assert!(b < d);
- assert!(c < d);
- assert!(c == c);
- assert!(d == d);
- }
-}
diff --git a/crates/gosub_styling/src/css_colors.rs b/crates/gosub_styling/src/css_colors.rs
index f490d751b..56c120df1 100644
--- a/crates/gosub_styling/src/css_colors.rs
+++ b/crates/gosub_styling/src/css_colors.rs
@@ -1,14 +1,18 @@
use lazy_static::lazy_static;
use std::convert::From;
+use std::fmt::Debug;
// Values for this table is taken from https://www.w3.org/TR/CSS21/propidx.html
// Probably not the complete list, but it will do for now
+/// A list of CSS color names
pub struct CssColorEntry {
pub name: &'static str,
pub value: &'static str,
}
+/// A RGB color with alpha channel
+#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RgbColor {
/// Red component
pub r: u8,
@@ -21,6 +25,7 @@ pub struct RgbColor {
}
impl RgbColor {
+ /// Create a new color with r,g,b and alpha values
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
RgbColor { r, g, b, a }
}
@@ -28,6 +33,7 @@ impl RgbColor {
impl Default for RgbColor {
fn default() -> Self {
+ // Default full alpha (solid) with black color
RgbColor {
r: 0,
g: 0,
@@ -47,12 +53,19 @@ impl From<&str> for RgbColor {
}
if value.starts_with("rgb(") {
// Rgb function
+ todo!()
}
if value.starts_with("rgba(") {
// Rgba function
+ todo!()
}
if value.starts_with("hsl(") {
// HSL function
+ todo!()
+ }
+ if value.starts_with("hsla(") {
+ // HSLA function
+ todo!()
}
return get_hex_color_from_name(value).map_or(RgbColor::default(), parse_hex);
@@ -726,6 +739,10 @@ lazy_static! {
name: "yellowgreen",
value: "#9acd32",
},
+ CssColorEntry {
+ name: "rebeccapurple",
+ value: "#663399",
+ },
];
}
@@ -829,4 +846,31 @@ mod tests {
assert_eq!(color.b, 0);
assert_eq!(color.a, 255);
}
+
+ #[test]
+ fn color_names() {
+ let color = super::RgbColor::from("red");
+ assert_eq!(color.r, 255);
+ assert_eq!(color.g, 0);
+ assert_eq!(color.b, 0);
+ assert_eq!(color.a, 255);
+
+ let color = super::RgbColor::from("green");
+ assert_eq!(color.r, 0);
+ assert_eq!(color.g, 128);
+ assert_eq!(color.b, 0);
+ assert_eq!(color.a, 255);
+
+ let color = super::RgbColor::from("blue");
+ assert_eq!(color.r, 0);
+ assert_eq!(color.g, 0);
+ assert_eq!(color.b, 255);
+ assert_eq!(color.a, 255);
+
+ let color = super::RgbColor::from("rebeccapurple");
+ assert_eq!(color.r, 0x66);
+ assert_eq!(color.g, 0x33);
+ assert_eq!(color.b, 0x99);
+ assert_eq!(color.a, 255);
+ }
}
diff --git a/crates/gosub_styling/src/css_node_tree.rs b/crates/gosub_styling/src/css_node_tree.rs
new file mode 100644
index 000000000..4b238964a
--- /dev/null
+++ b/crates/gosub_styling/src/css_node_tree.rs
@@ -0,0 +1,619 @@
+use crate::css_colors::RgbColor;
+use core::fmt::Debug;
+use gosub_css3::stylesheet::{
+ CssOrigin, CssSelector, CssSelectorPart, CssSelectorType, MatcherType, Specificity,
+};
+use gosub_html5::node::NodeId;
+use gosub_html5::parser::document::{DocumentHandle, TreeIterator};
+use gosub_shared::types::Result;
+use std::cmp::Ordering;
+use std::collections::HashMap;
+use std::fmt::Display;
+
+/// Generates a css node tree for the given document based on its loaded stylesheets
+pub fn generate_css_node_tree(document: DocumentHandle) -> Result {
+ // Restart css map
+ let mut css_node_tree = CssNodeTree::new(DocumentHandle::clone(&document));
+
+ // Iterate the complete document tree
+ let tree_iterator = TreeIterator::new(&document);
+ for current_node_id in tree_iterator {
+ let mut css_map_entry = CssProperties::new();
+
+ let binding = document.get();
+ let node = binding
+ .get_node_by_id(current_node_id)
+ .expect("node not found");
+ if !node.is_element() {
+ continue;
+ }
+
+ for sheet in document.get().stylesheets.iter() {
+ for rule in sheet.rules.iter() {
+ for selector in rule.selectors().iter() {
+ if !match_selector(DocumentHandle::clone(&document), current_node_id, selector)
+ {
+ continue;
+ }
+
+ // Selector matched, so we add all declared values to the map
+ for declaration in rule.declarations().iter() {
+ let prop_name = declaration.property.clone();
+
+ let declaration = DeclarationProperty {
+ value: CssValue::String(declaration.value.clone()), // @TODO: parse the value into the correct CSSValue
+ origin: sheet.origin.clone(),
+ important: declaration.important,
+ location: sheet.location.clone(),
+ specificity: selector.specificity(),
+ };
+
+ if let std::collections::hash_map::Entry::Vacant(e) =
+ css_map_entry.properties.entry(prop_name.clone())
+ {
+ let mut entry = CssProperty::new(prop_name.as_str());
+ entry.declared.push(declaration);
+ e.insert(entry);
+ } else {
+ let entry = css_map_entry.properties.get_mut(&prop_name).unwrap();
+ entry.declared.push(declaration);
+ }
+ }
+ }
+ }
+ }
+
+ css_node_tree.nodes.insert(current_node_id, css_map_entry);
+ }
+
+ for (node_id, props) in css_node_tree.nodes.iter() {
+ println!("Node: {:?}", node_id);
+ for (prop, values) in props.properties.iter() {
+ println!(" {}", prop);
+ if prop == "color" {
+ for decl in values.declared.iter() {
+ println!(
+ " {:?} {:?} {:?} {:?}",
+ decl.origin, decl.location, decl.value, decl.specificity
+ );
+ }
+ }
+ }
+ }
+
+ Ok(css_node_tree)
+}
+
+// Matches a complete selector (all parts) against the given node(id)
+fn match_selector(document: DocumentHandle, node_id: NodeId, selector: &CssSelector) -> bool {
+ let mut parts = selector.parts.clone();
+ parts.reverse();
+ match_selector_part(document, node_id, &mut parts)
+}
+
+/// Returns true when the given node matches the part(s)
+fn match_selector_part(
+ document: DocumentHandle,
+ node_id: NodeId,
+ selector_parts: &mut Vec,
+) -> bool {
+ let binding = document.get();
+ let mut next_current_node = Some(binding.get_node_by_id(node_id).expect("node not found"));
+
+ while !selector_parts.is_empty() {
+ if next_current_node.is_none() {
+ return false;
+ }
+ let current_node = next_current_node.expect("current_node not found");
+ if current_node.is_root() {
+ return false;
+ }
+
+ let part = selector_parts.remove(0);
+
+ match part.type_ {
+ CssSelectorType::Universal => {
+ // '*' always matches any selector
+ }
+ CssSelectorType::Type => {
+ if part.value != current_node.as_element().name {
+ return false;
+ }
+ }
+ CssSelectorType::Class => {
+ if !current_node.as_element().classes.contains(&part.value) {
+ return false;
+ }
+ }
+ CssSelectorType::Id => {
+ if current_node
+ .as_element()
+ .attributes
+ .get("id")
+ .unwrap_or(&"".to_string())
+ != &part.value
+ {
+ return false;
+ }
+ }
+ CssSelectorType::Attribute => {
+ let wanted_attr_name = part.name.clone();
+
+ if !current_node.has_attribute(&wanted_attr_name) {
+ return false;
+ }
+
+ let mut wanted_attr_value = part.value.clone();
+ let mut got_attr_value = current_node
+ .get_attribute(&wanted_attr_name)
+ .unwrap_or(&"".to_string())
+ .to_string();
+
+ // If we need to match case-insensitive, just convert everything to lowercase for comparison
+ if part.flags.eq_ignore_ascii_case("i") {
+ wanted_attr_value = wanted_attr_value.to_lowercase();
+ got_attr_value = got_attr_value.to_lowercase();
+ };
+
+ return match part.matcher {
+ MatcherType::None => {
+ // Just the presence of the attribute is enough
+ true
+ }
+ MatcherType::Equals => {
+ // Exact match
+ wanted_attr_value == got_attr_value
+ }
+ MatcherType::Includes => {
+ // Contains word
+ wanted_attr_value
+ .split_whitespace()
+ .any(|s| s == got_attr_value)
+ }
+ MatcherType::DashMatch => {
+ // Exact value or value followed by a hyphen
+ got_attr_value == wanted_attr_value
+ || got_attr_value.starts_with(&format!("{}-", wanted_attr_value))
+ }
+ MatcherType::PrefixMatch => {
+ // Starts with
+ got_attr_value.starts_with(&wanted_attr_value)
+ }
+ MatcherType::SuffixMatch => {
+ // Ends with
+ got_attr_value.ends_with(&wanted_attr_value)
+ }
+ MatcherType::SubstringMatch => {
+ // Contains
+ got_attr_value.contains(&wanted_attr_value)
+ }
+ };
+ }
+ CssSelectorType::PseudoClass => {
+ // @Todo: implement pseudo classes
+ if part.value == "link" {
+ return false;
+ }
+ return false;
+ }
+ CssSelectorType::PseudoElement => {
+ // @Todo: implement pseudo elements
+ if part.value == "first-child" {
+ return false;
+ }
+ return false;
+ }
+ CssSelectorType::Combinator => {
+ // We don't have the descendant combinator (space), as this is the default behaviour
+ match part.value.as_str() {
+ // @todo: We also should do: column combinator ('||' experimental)
+ // @todo: Namespace combinator ('|')
+ " " => {
+ // Descendant combinator, any parent that matches the previous selector will do
+ if !match_selector_part(
+ DocumentHandle::clone(&document),
+ current_node.id,
+ selector_parts,
+ ) {
+ // we insert the combinator back so we the next loop will match against the parent node
+ selector_parts.insert(0, part);
+ }
+ }
+ ">" => {
+ // Child combinator. Only matches the direct child
+ if !match_selector_part(
+ DocumentHandle::clone(&document),
+ current_node.id,
+ selector_parts,
+ ) {
+ return false;
+ }
+ }
+ "+" => {
+ // We need to match the previous sibling of the current node
+ }
+ "~" => {
+ // We need to match the previous siblings of the current node
+ }
+ _ => {
+ panic!("Unknown combinator: {}", part.value);
+ }
+ }
+ }
+ }
+
+ // We have matched this part, so we move up the chain
+ // let binding = document.get();
+ next_current_node = binding.parent_node(current_node);
+ }
+
+ // All parts of the selector have matched
+ true
+}
+
+/// A declarationProperty defines a single value for a property (color: red;). It consists of the value,
+/// origin, importance, location and specificity of the declaration.
+#[derive(Debug, Clone)]
+pub struct DeclarationProperty {
+ /// The actual value of the property
+ pub value: CssValue,
+ /// Origin of the declaration (user stylesheet, author stylesheet etc.)
+ pub origin: CssOrigin,
+ /// Whether the declaration is !important
+ pub important: bool,
+ /// The location of the declaration in the stylesheet (name.css:123) or empty
+ pub location: String,
+ /// The specificity of the selector that declared this property
+ pub specificity: Specificity,
+}
+
+impl DeclarationProperty {
+ /// Priority of the declaration based on the origin and importance as defined in https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade
+ fn priority(&self) -> u8 {
+ match self.origin {
+ CssOrigin::UserAgent => {
+ if self.important {
+ 7
+ } else {
+ 1
+ }
+ }
+ CssOrigin::User => {
+ if self.important {
+ 6
+ } else {
+ 2
+ }
+ }
+ CssOrigin::Author => {
+ if self.important {
+ 5
+ } else {
+ 3
+ }
+ }
+ }
+ }
+}
+
+impl PartialEq for DeclarationProperty {
+ fn eq(&self, other: &Self) -> bool {
+ self.priority() == other.priority()
+ }
+}
+
+impl PartialOrd for DeclarationProperty {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ Some(self.cmp(other))
+ }
+}
+
+impl Eq for DeclarationProperty {}
+
+impl Ord for DeclarationProperty {
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.priority().cmp(&other.priority())
+ }
+}
+
+/// A value entry contains all values for a single property for a single node. It contains the declared values, and
+/// all the computed values.
+#[derive(Debug, Clone)]
+pub struct CssProperty {
+ /// The name of the property
+ pub name: String,
+ /// True when this property needs to be recalculated
+ pub dirty: bool,
+ /// List of all declared values for this property
+ pub declared: Vec,
+ /// Cascaded value from the declared values (if any)
+ pub cascaded: Option,
+ // Specified value from the cascaded value (if any), or inherited value, or initial value
+ pub specified: CssValue,
+ // Computed value from the specified value (needs viewport size etc.)
+ pub computed: CssValue,
+ pub used: CssValue,
+ // Actual value used in the rendering (after rounding, clipping etc.)
+ pub actual: CssValue,
+}
+
+impl CssProperty {
+ pub fn new(prop_name: &str) -> Self {
+ Self {
+ name: prop_name.to_string(),
+ dirty: true,
+ declared: Vec::new(),
+ cascaded: None,
+ specified: CssValue::None,
+ computed: CssValue::None,
+ used: CssValue::None,
+ actual: CssValue::None,
+ }
+ }
+
+ pub fn mark_dirty(&mut self) {
+ self.dirty = true;
+ }
+
+ pub fn mark_clean(&mut self) {
+ self.dirty = false;
+ }
+
+ /// Returns the actual value of the property. Will compute the value when needed
+ pub fn compute_value(&mut self) -> &CssValue {
+ if self.dirty {
+ self.calculate_value();
+ self.dirty = false;
+ }
+
+ &self.actual
+ }
+
+ fn calculate_value(&mut self) {
+ self.cascaded = self.find_cascaded_value();
+ self.specified = self.find_specified_value();
+ self.computed = self.find_computed_value();
+ self.used = self.find_used_value();
+ self.actual = self.find_actual_value();
+ }
+
+ fn find_cascaded_value(&self) -> Option {
+ let mut declared = self.declared.clone();
+
+ declared.sort();
+ declared.sort_by(|a, b| {
+ if a.priority() == b.priority() {
+ return Ordering::Equal;
+ }
+
+ a.specificity.cmp(&b.specificity)
+ });
+
+ declared.last().map(|d| d.value.clone())
+ }
+
+ fn find_specified_value(&self) -> CssValue {
+ match self.declared.iter().max() {
+ Some(decl) => decl.value.clone(),
+ None => CssValue::None,
+ }
+ }
+
+ fn find_computed_value(&self) -> CssValue {
+ if self.specified != CssValue::None {
+ return self.specified.clone();
+ }
+
+ if self.is_inheritable() {
+ todo!("inheritable properties")
+ // while let Some(parent) = self.get_parent() {
+ // if let Some(parent_value) = parent {
+ // return parent_value.find_computed_value();
+ // }
+ // }
+ }
+
+ self.get_initial_value().unwrap_or(CssValue::None)
+ }
+
+ fn find_used_value(&self) -> CssValue {
+ self.computed.clone()
+ }
+
+ fn find_actual_value(&self) -> CssValue {
+ // @TODO: stuff like clipping and such should occur as well
+ match &self.used {
+ CssValue::Number(len) => CssValue::Number(len.round()),
+ CssValue::Percentage(perc) => CssValue::Percentage(perc.round()),
+ CssValue::Unit(value, unit) => CssValue::Unit(value.round(), unit.clone()),
+ _ => self.used.clone(),
+ }
+ }
+
+ // Returns true when the property is inheritable, false otherwise
+ fn is_inheritable(&self) -> bool {
+ crate::property_list::PROPERTY_TABLE
+ .iter()
+ .find(|entry| entry.name == self.name)
+ .map(|entry| entry.inheritable)
+ .unwrap_or(false)
+ }
+
+ // Returns the initial value for the property, if any
+ fn get_initial_value(&self) -> Option {
+ crate::property_list::PROPERTY_TABLE
+ .iter()
+ .find(|entry| entry.name == self.name)
+ .map(|entry| entry.initial.clone())
+ }
+}
+
+/// Map of all declared values for a single node. Note that these are only the defined properties, not
+/// the non-existing properties.
+pub struct CssProperties {
+ properties: HashMap,
+}
+
+impl Default for CssProperties {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl CssProperties {
+ pub fn new() -> Self {
+ Self {
+ properties: HashMap::new(),
+ }
+ }
+}
+
+/// Actual CSS value, can be a color, length, percentage, string or unit. Some relative values will be computed
+/// from other values (ie: Percent(50) will convert to Length(100) when the parent width is 200)
+#[derive(Debug, Clone, PartialEq)]
+pub enum CssValue {
+ None,
+ Color(RgbColor),
+ Number(f32),
+ Percentage(f32),
+ String(String),
+ Unit(f32, String),
+}
+
+impl Display for CssValue {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ CssValue::None => write!(f, "none"),
+ CssValue::Color(col) => {
+ write!(f, "#{:02x}{:02x}{:02x}{:02x}", col.r, col.g, col.b, col.a)
+ }
+ CssValue::Number(num) => write!(f, "{}", num),
+ CssValue::Percentage(p) => write!(f, "{}%", p),
+ CssValue::String(s) => write!(f, "{}", s),
+ CssValue::Unit(val, unit) => write!(f, "{}{}", val, unit),
+ }
+ }
+}
+
+/// Map of all declared values for all nodes in the document
+pub struct CssNodeTree {
+ nodes: HashMap,
+ document: DocumentHandle,
+}
+
+impl CssNodeTree {
+ /// Creates a new CssNodeTree for the given document
+ pub fn new(doc: DocumentHandle) -> Self {
+ Self {
+ document: doc,
+ nodes: HashMap::new(),
+ }
+ }
+
+ /// Returns a clone of the document handle
+ pub fn get_document(&self) -> DocumentHandle {
+ DocumentHandle::clone(&self.document)
+ }
+
+ /// Mark the given node as dirty, so it will be recalculated
+ pub fn mark_dirty(&mut self, node_id: NodeId) {
+ if let Some(props) = self.nodes.get_mut(&node_id) {
+ for prop in props.properties.values_mut() {
+ prop.mark_dirty();
+ }
+ }
+ }
+
+ /// Mark the given node as clean, so it will not be recalculated
+ pub fn mark_clean(&mut self, node_id: NodeId) {
+ if let Some(props) = self.nodes.get_mut(&node_id) {
+ for prop in props.properties.values_mut() {
+ prop.mark_clean();
+ }
+ }
+ }
+
+ /// Retrieves the property for the given node, or None when not found
+ pub fn get_property(&self, node_id: NodeId, prop_name: &str) -> Option {
+ let props = self.nodes.get(&node_id);
+ props?;
+
+ props.expect("props").properties.get(prop_name).cloned()
+ }
+
+ /// Retrieves the value for the given property for the given node, or None when not found
+ pub fn get_all_properties(&self, node_id: NodeId) -> Option<&CssProperties> {
+ self.nodes.get(&node_id)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn compare_declared() {
+ let a = DeclarationProperty {
+ value: CssValue::String("red".into()),
+ origin: CssOrigin::Author,
+ important: false,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+ let b = DeclarationProperty {
+ value: CssValue::String("blue".into()),
+ origin: CssOrigin::UserAgent,
+ important: false,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+ let c = DeclarationProperty {
+ value: CssValue::String("green".into()),
+ origin: CssOrigin::User,
+ important: false,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+ let d = DeclarationProperty {
+ value: CssValue::String("yellow".into()),
+ origin: CssOrigin::Author,
+ important: true,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+ let e = DeclarationProperty {
+ value: CssValue::String("orange".into()),
+ origin: CssOrigin::UserAgent,
+ important: true,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+ let f = DeclarationProperty {
+ value: CssValue::String("purple".into()),
+ origin: CssOrigin::User,
+ important: true,
+ location: "".into(),
+ specificity: Specificity::new(1, 0, 0),
+ };
+
+ assert_eq!(3, a.priority());
+ assert_eq!(1, b.priority());
+ assert_eq!(2, c.priority());
+ assert_eq!(5, d.priority());
+ assert_eq!(7, e.priority());
+ assert_eq!(6, f.priority());
+
+ assert!(a > b);
+ assert!(b < c);
+ assert!(c < d);
+ assert!(d < e);
+ assert!(f < e);
+ assert!(a < e);
+ assert!(b < d);
+ assert!(a < d);
+ assert!(b < d);
+ assert!(c < d);
+ assert_eq!(c, c);
+ assert_eq!(d, d);
+ }
+}
diff --git a/crates/gosub_styling/src/lib.rs b/crates/gosub_styling/src/lib.rs
index 4e84ae4bb..756e4f4c2 100644
--- a/crates/gosub_styling/src/lib.rs
+++ b/crates/gosub_styling/src/lib.rs
@@ -3,6 +3,29 @@
//! This crate connects CSS3 and HTML5 into a styling pipeline
//!
-pub mod calculator;
+use gosub_css3::convert::ast_converter::convert_ast_to_stylesheet;
+use gosub_css3::parser_config::ParserConfig;
+use gosub_css3::stylesheet::{CssOrigin, CssStylesheet};
+use gosub_css3::Css3;
+use std::fs;
+
pub mod css_colors;
-pub mod pipeline;
+pub mod css_node_tree;
+mod property_list;
+
+/// Loads the default user agent stylesheet
+pub fn load_default_useragent_stylesheet() -> anyhow::Result {
+ // @todo: we should be able to browse to gosub://useragent.css and see the actual useragent css file
+ let location = "gosub://useragent.css";
+ let config = ParserConfig {
+ source: Some(String::from(location)),
+ ignore_errors: true,
+ ..Default::default()
+ };
+
+ let css =
+ fs::read_to_string("resources/useragent.css").expect("Could not load useragent stylesheet");
+ let css_ast = Css3::parse(css.as_str(), config).expect("Could not parse useragent stylesheet");
+
+ convert_ast_to_stylesheet(&css_ast, CssOrigin::UserAgent, location)
+}
diff --git a/crates/gosub_styling/src/pipeline.rs b/crates/gosub_styling/src/pipeline.rs
deleted file mode 100644
index 209a825a8..000000000
--- a/crates/gosub_styling/src/pipeline.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-use crate::calculator::StyleCalculator;
-use gosub_html5::node::NodeId;
-use gosub_html5::parser::document::DocumentHandle;
-
-/// The rendering pipeline to convert a document and stylesheets into a rendered page
-/// It's a very simple pipeline with a single step (generate_render_tree). But more
-/// will follow later.
-pub struct Pipeline {}
-
-impl Default for Pipeline {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl Pipeline {
- pub fn new() -> Self {
- Self {}
- }
-
- /// Generates a render tree by duplicating the DOM tree and removing all nodes that are not renderable or hidden.
- pub fn generate_render_tree(
- &self,
- doc_handle: DocumentHandle,
- _calculator: &StyleCalculator,
- ) -> DocumentHandle {
- // Create a complete copy of the document tree into the render tree
- let mut rendertree_handle = doc_handle.deep_clone();
-
- // Iterate tree and remove elements that are hidden or not renderable
- remove_unrenderable_nodes(&mut rendertree_handle, NodeId::root(), _calculator);
-
- DocumentHandle::clone(&rendertree_handle)
- }
-}
-
-fn remove_unrenderable_nodes(
- rendertree_handle: &mut DocumentHandle,
- node_id: NodeId,
- _calculator: &StyleCalculator,
-) {
- let node;
- {
- let binding = rendertree_handle.get();
- node = binding.get_node_by_id(node_id).unwrap().clone();
- }
-
- // There are more elements that are not renderable, but for now we only remove the most common ones
- let removable_elements = ["head", "script", "style", "svg"];
-
- if node.is_element() && removable_elements.contains(&node.as_element().name.as_str()) {
- rendertree_handle.get_mut().delete_node(&node);
- return;
- }
-
- // Check CSS styles and remove if not renderable
-
- // Iterate all children from this node
- for &child_id in &node.children {
- remove_unrenderable_nodes(rendertree_handle, child_id, _calculator);
- }
-}
diff --git a/crates/gosub_styling/src/property_list.rs b/crates/gosub_styling/src/property_list.rs
index eb8ea1181..dab2aafe8 100644
--- a/crates/gosub_styling/src/property_list.rs
+++ b/crates/gosub_styling/src/property_list.rs
@@ -1,592 +1,576 @@
+use crate::css_node_tree::CssValue;
use lazy_static::lazy_static;
// Values for this table is taken from https://www.w3.org/TR/CSS21/propidx.html
// Probably not the complete list, but it will do for now
-struct PropertyTableEntry {
- name: &'static str,
- initial: &'static str,
- inheritable: bool,
+pub struct PropertyTableEntry {
+ pub(crate) name: &'static str,
+ pub(crate) initial: CssValue,
+ pub(crate) inheritable: bool,
}
lazy_static! {
- static ref PROPERTY_TABLE: &'static [PropertyTableEntry] = &[
+ pub static ref PROPERTY_TABLE: Vec = vec![
PropertyTableEntry {
name: "azimuth",
- initial: "center",
+ initial: CssValue::String("center".into()),
inheritable: true,
},
PropertyTableEntry {
name: "background-attachment",
- initial: "scroll",
+ initial: CssValue::String("scroll".into()),
inheritable: false,
},
PropertyTableEntry {
name: "background-color",
- initial: "transparent",
+ initial: CssValue::String("transparent".into()),
inheritable: false,
},
PropertyTableEntry {
name: "background-image",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "background-position",
- initial: "0% 0%",
+ initial: CssValue::String("0% 0%".into()),
inheritable: false,
},
PropertyTableEntry {
name: "background-repeat",
- initial: "repeat",
+ initial: CssValue::String("repeat".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-collapse",
- initial: "separate",
+ initial: CssValue::String("separate".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-spacing",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "border-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-top",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-right",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-bottom",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-left",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-top-color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-right-color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-bottom-color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-left-color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-top-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-right-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-bottom-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-left-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-top-width",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-right-width",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-bottom-width",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-left-width",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "border-width",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "bottom",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "caption-side",
- initial: "top",
+ initial: CssValue::String("top".into()),
inheritable: false,
},
PropertyTableEntry {
name: "clear",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "clip",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "color",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: true,
},
PropertyTableEntry {
name: "content",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: false,
},
PropertyTableEntry {
name: "counter-increment",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "counter-reset",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "cue",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "cue-after",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "cue-before",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "cursor",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "direction",
- initial: "ltr",
+ initial: CssValue::String("ltr".into()),
inheritable: true,
},
PropertyTableEntry {
name: "display",
- initial: "inline",
+ initial: CssValue::String("inline".into()),
inheritable: false,
},
PropertyTableEntry {
name: "elevation",
- initial: "level",
+ initial: CssValue::String("level".into()),
inheritable: false,
},
PropertyTableEntry {
name: "empty-cells",
- initial: "show",
+ initial: CssValue::String("show".into()),
inheritable: false,
},
PropertyTableEntry {
name: "float",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "font",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "font-family",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "font-size",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: true,
},
PropertyTableEntry {
name: "font-style",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "font-variant",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "font-weight",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "height",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "left",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "letter-spacing",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "line-height",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "list-style",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "list-style-image",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "list-style-position",
- initial: "outside",
+ initial: CssValue::String("outside".into()),
inheritable: false,
},
PropertyTableEntry {
name: "list-style-type",
- initial: "disc",
+ initial: CssValue::String("disc".into()),
inheritable: false,
},
PropertyTableEntry {
name: "margin",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "margin-top",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "margin-right",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "margin-bottom",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "margin-left",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "max-height",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "max-width",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "min-height",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "min-width",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "orphans",
- initial: "2",
+ initial: CssValue::Number(2_f32),
inheritable: true,
},
PropertyTableEntry {
name: "outline",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "outline-color",
- initial: "invert",
+ initial: CssValue::String("invert".into()),
inheritable: false,
},
PropertyTableEntry {
name: "outline-style",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "outline-width",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "overflow",
- initial: "visible",
+ initial: CssValue::String("visible".into()),
inheritable: false,
},
PropertyTableEntry {
name: "padding",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "padding-top",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "padding-right",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "padding-bottom",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "padding-left",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "page-break-after",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "page-break-before",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "page-break-inside",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "pause-after",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "pause-before",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: false,
},
PropertyTableEntry {
name: "pitch",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "pitch-range",
- initial: "50",
+ initial: CssValue::Number(50_f32),
inheritable: false,
},
PropertyTableEntry {
name: "play-during",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "position",
- initial: "static",
+ initial: CssValue::String("static".into()),
inheritable: false,
},
PropertyTableEntry {
name: "quotes",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "richness",
- initial: "50",
+ initial: CssValue::Number(50_f32),
inheritable: false,
},
PropertyTableEntry {
name: "right",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "speak",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: false,
},
PropertyTableEntry {
name: "speak-header",
- initial: "once",
+ initial: CssValue::String("once".into()),
inheritable: false,
},
PropertyTableEntry {
name: "speak-numeral",
- initial: "continuous",
+ initial: CssValue::String("continuous".into()),
inheritable: false,
},
PropertyTableEntry {
name: "speak-punctuation",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: false,
},
PropertyTableEntry {
name: "speech-rate",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "stress",
- initial: "50",
+ initial: CssValue::Number(50_f32),
inheritable: false,
},
PropertyTableEntry {
name: "table-layout",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "text-align",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: true,
},
PropertyTableEntry {
name: "text-decoration",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: true,
},
PropertyTableEntry {
name: "text-indent",
- initial: "0",
+ initial: CssValue::Number(0_f32),
inheritable: true,
},
PropertyTableEntry {
name: "text-transform",
- initial: "none",
+ initial: CssValue::String("none".into()),
inheritable: true,
},
PropertyTableEntry {
name: "top",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "unicode-bidi",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "vertical-align",
- initial: "baseline",
+ initial: CssValue::String("baseline".into()),
inheritable: true,
},
PropertyTableEntry {
name: "visibility",
- initial: "visible",
+ initial: CssValue::String("visible".into()),
inheritable: false,
},
PropertyTableEntry {
name: "voice-family",
- initial: "initial",
+ initial: CssValue::String("initial".into()),
inheritable: false,
},
PropertyTableEntry {
name: "volume",
- initial: "medium",
+ initial: CssValue::String("medium".into()),
inheritable: false,
},
PropertyTableEntry {
name: "white-space",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "widows",
- initial: "2",
+ initial: CssValue::Number(2_f32),
inheritable: true,
},
PropertyTableEntry {
name: "width",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
PropertyTableEntry {
name: "word-spacing",
- initial: "normal",
+ initial: CssValue::String("normal".into()),
inheritable: true,
},
PropertyTableEntry {
name: "z-index",
- initial: "auto",
+ initial: CssValue::String("auto".into()),
inheritable: false,
},
];
}
-
-#[allow(dead_code)]
-fn get_initial_value(property: &str) -> Option<&'static str> {
- PROPERTY_TABLE
- .iter()
- .find(|entry| entry.name == property)
- .map(|entry| entry.initial)
-}
-
-#[allow(dead_code)]
-fn is_inheritable(property: &str) -> bool {
- PROPERTY_TABLE
- .iter()
- .find(|entry| entry.name == property)
- .map(|entry| entry.inheritable)
- .unwrap_or(false)
-}
diff --git a/src/bin/style-parser.rs b/src/bin/style-parser.rs
index 459894e40..72a719287 100644
--- a/src/bin/style-parser.rs
+++ b/src/bin/style-parser.rs
@@ -10,21 +10,20 @@ use gosub_html5::parser::document::{visit, Document};
use gosub_html5::parser::Html5Parser;
use gosub_html5::visit::Visitor;
use gosub_shared::bytes::{CharIterator, Confidence, Encoding};
-use gosub_styling::calculator::StyleCalculator;
-use gosub_styling::pipeline::Pipeline;
+use gosub_styling::css_node_tree::{generate_css_node_tree, CssNodeTree, CssValue};
use std::fs;
use url::Url;
struct TextVisitor {
color: String,
- calculator: StyleCalculator,
+ css_nodetree: CssNodeTree,
}
impl TextVisitor {
- fn new(calculator: StyleCalculator) -> Self {
+ fn new(css_node_tree: CssNodeTree) -> Self {
Self {
color: String::from(""),
- calculator,
+ css_nodetree: css_node_tree,
}
}
}
@@ -63,14 +62,15 @@ impl Visitor for TextVisitor {
fn comment_leave(&mut self, _node: &Node, _data: &CommentData) {}
fn element_enter(&mut self, node: &Node, data: &ElementData) {
- let props = self.calculator.get_css_properties_for_node(node.id);
- if props.is_some() {
- let props = props.unwrap();
- if let Some(col) = props.get_color_value("color") {
- print!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b);
+ if let Some(mut prop) = self.css_nodetree.get_property(node.id, "color") {
+ if let CssValue::Color(col) = prop.compute_value() {
+ self.color = format!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b)
}
- if let Some(col) = props.get_color_value("background-color") {
- print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b);
+ }
+
+ if let Some(mut prop) = self.css_nodetree.get_property(node.id, "background-color") {
+ if let CssValue::Color(col) = prop.compute_value() {
+ print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b)
}
}
@@ -78,14 +78,15 @@ impl Visitor for TextVisitor {
}
fn element_leave(&mut self, node: &Node, data: &ElementData) {
- let props = self.calculator.get_css_properties_for_node(node.id);
- if props.is_some() {
- let props = props.unwrap();
- if let Some(col) = props.get_color_value("color") {
- print!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b);
+ if let Some(mut prop) = self.css_nodetree.get_property(node.id, "color") {
+ if let CssValue::Color(col) = prop.compute_value() {
+ self.color = format!("\x1b[38;2;{};{};{}m", col.r, col.g, col.b)
}
- if let Some(col) = props.get_color_value("background-color") {
- print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b);
+ }
+
+ if let Some(mut prop) = self.css_nodetree.get_property(node.id, "background-color") {
+ if let CssValue::Color(col) = prop.compute_value() {
+ print!("\x1b[48;2;{};{};{}m", col.r, col.g, col.b)
}
}
@@ -132,23 +133,10 @@ fn main() -> Result<()> {
let _parse_errors =
Html5Parser::parse_document(&mut chars, Document::clone(&doc_handle), None)?;
- // Create stylesheet calculator and load default user agent stylesheets
- let mut calculator = StyleCalculator::new(Document::clone(&doc_handle));
- // calculator.add_stylesheet(load_default_useragent_stylesheet()?);
-
- // pipeline
- calculator.find_declared_values();
- calculator.find_cascaded_values();
- calculator.find_specified_values();
- // calculator.find_computed_values(1024, 786); // Do we need more info?
- // calculator.find_used_values(/*layout*/); // we need to have a layout for calculating these values
- // calculator.find_actual_values(); // Makes sure we use 2px instead of a computed 2.25px
-
- let pipeline = Pipeline::new();
- let render_tree = pipeline.generate_render_tree(Document::clone(&doc_handle), &calculator);
+ let css_tree = generate_css_node_tree(Document::clone(&doc_handle))?;
- let mut visitor = Box::new(TextVisitor::new(calculator)) as Box>;
- visit(&Document::clone(&render_tree), &mut visitor);
+ let mut visitor = Box::new(TextVisitor::new(css_tree)) as Box>;
+ visit(&Document::clone(&doc_handle), &mut visitor);
Ok(())
}