feat(render): implement basic CSS cascade matching from <style> blocks
This commit is contained in:
1 parent
bed6d6f619
commit
193e30deb5
5 files changed
+93
-17
No files matched your search
Generated
+1
@@ -1880,6 +1880,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"base64",
|
"base64",
|
||||||
|
"cssparser",
|
||||||
"image",
|
"image",
|
||||||
"obscura-dom",
|
"obscura-dom",
|
||||||
"taffy",
|
"taffy",
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ ab_glyph = { workspace = true, optional = true }
|
|||||||
image = { version = "0.24", default-features = false, features = ["jpeg", "png", "webp"], optional = true }
|
image = { version = "0.24", default-features = false, features = ["jpeg", "png", "webp"], optional = true }
|
||||||
ureq = { version = "2.9", default-features = false, features = ["tls"], optional = true }
|
ureq = { version = "2.9", default-features = false, features = ["tls"], optional = true }
|
||||||
base64 = { workspace = true, optional = true }
|
base64 = { workspace = true, optional = true }
|
||||||
|
cssparser = "0.34"
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
@@ -11,15 +11,57 @@ use std::collections::HashMap;
|
|||||||
use obscura_dom::tree::{DomTree, NodeId};
|
use obscura_dom::tree::{DomTree, NodeId};
|
||||||
use taffy::prelude::*;
|
use taffy::prelude::*;
|
||||||
|
|
||||||
use crate::{compute_style, Rect, to_taffy_style};
|
use crate::{Rect, to_taffy_style};
|
||||||
|
|
||||||
/// Per-element border boxes after layout, in viewport coordinates.
|
/// Per-element border boxes after layout, in viewport coordinates.
|
||||||
pub struct DomLayout {
|
pub struct DomLayout {
|
||||||
pub rects: HashMap<NodeId, Rect>,
|
pub rects: HashMap<NodeId, Rect>,
|
||||||
|
pub styles: HashMap<NodeId, crate::LayoutStyle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lay out a DOM tree within `viewport` (width, height) in CSS pixels.
|
/// Lay out a DOM tree within `viewport` (width, height) in CSS pixels.
|
||||||
pub fn layout_dom(tree: &DomTree, viewport: (f32, f32)) -> DomLayout {
|
pub fn layout_dom(tree: &DomTree, viewport: (f32, f32)) -> DomLayout {
|
||||||
|
let mut css_rules = Vec::new();
|
||||||
|
for nid in tree.descendants(tree.document()) {
|
||||||
|
if let Some(node) = tree.get_node(nid) {
|
||||||
|
if let Some(elem) = node.as_element() {
|
||||||
|
if elem.local.as_ref() == "style" {
|
||||||
|
let css = tree.text_content(nid);
|
||||||
|
css_rules.extend(parse_simple_css(&css));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut styles: HashMap<NodeId, crate::LayoutStyle> = HashMap::new();
|
||||||
|
for nid in tree.descendants(tree.document()) {
|
||||||
|
if let Some(node) = tree.get_node(nid) {
|
||||||
|
if let Some(elem) = node.as_element() {
|
||||||
|
styles.insert(nid, crate::style::ua_style(elem.local.as_ref()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (selector, decls) in &css_rules {
|
||||||
|
if let Ok(matched) = tree.query_selector_all(selector) {
|
||||||
|
for nid in matched {
|
||||||
|
if let Some(style) = styles.get_mut(&nid) {
|
||||||
|
crate::style::apply_inline(style, decls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for nid in tree.descendants(tree.document()) {
|
||||||
|
if let Some(node) = tree.get_node(nid) {
|
||||||
|
if let Some(inline) = node.get_attribute("style") {
|
||||||
|
if let Some(style) = styles.get_mut(&nid) {
|
||||||
|
crate::style::apply_inline(style, inline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut taffy_tree: TaffyTree = TaffyTree::new();
|
let mut taffy_tree: TaffyTree = TaffyTree::new();
|
||||||
let mut id_map: HashMap<taffy::NodeId, NodeId> = HashMap::new();
|
let mut id_map: HashMap<taffy::NodeId, NodeId> = HashMap::new();
|
||||||
|
|
||||||
@@ -32,7 +74,7 @@ pub fn layout_dom(tree: &DomTree, viewport: (f32, f32)) -> DomLayout {
|
|||||||
|
|
||||||
let mut rects = HashMap::new();
|
let mut rects = HashMap::new();
|
||||||
if let Some(root_id) = root {
|
if let Some(root_id) = root {
|
||||||
if let Some(taffy_root) = build(tree, root_id, &mut taffy_tree, &mut id_map) {
|
if let Some(taffy_root) = build(tree, root_id, &mut taffy_tree, &mut id_map, &styles) {
|
||||||
let _ = taffy_tree.compute_layout(
|
let _ = taffy_tree.compute_layout(
|
||||||
taffy_root,
|
taffy_root,
|
||||||
taffy::Size {
|
taffy::Size {
|
||||||
@@ -55,7 +97,39 @@ pub fn layout_dom(tree: &DomTree, viewport: (f32, f32)) -> DomLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DomLayout { rects }
|
DomLayout { rects, styles }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_simple_css(css: &str) -> Vec<(String, String)> {
|
||||||
|
let mut rules = Vec::new();
|
||||||
|
let mut current_selector = String::new();
|
||||||
|
let mut current_decls = String::new();
|
||||||
|
let mut in_block = false;
|
||||||
|
|
||||||
|
for c in css.chars() {
|
||||||
|
if c == '{' && !in_block {
|
||||||
|
in_block = true;
|
||||||
|
} else if c == '}' && in_block {
|
||||||
|
in_block = false;
|
||||||
|
let sel = current_selector.trim();
|
||||||
|
let decls = current_decls.trim();
|
||||||
|
for s in sel.split(',') {
|
||||||
|
let s = s.trim();
|
||||||
|
if !s.is_empty() {
|
||||||
|
rules.push((s.to_string(), decls.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_selector.clear();
|
||||||
|
current_decls.clear();
|
||||||
|
} else {
|
||||||
|
if in_block {
|
||||||
|
current_decls.push(c);
|
||||||
|
} else {
|
||||||
|
current_selector.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rules
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
@@ -63,17 +137,17 @@ fn build(
|
|||||||
id: NodeId,
|
id: NodeId,
|
||||||
taffy_tree: &mut TaffyTree,
|
taffy_tree: &mut TaffyTree,
|
||||||
id_map: &mut HashMap<taffy::NodeId, NodeId>,
|
id_map: &mut HashMap<taffy::NodeId, NodeId>,
|
||||||
|
styles: &HashMap<NodeId, crate::LayoutStyle>,
|
||||||
) -> Option<taffy::NodeId> {
|
) -> Option<taffy::NodeId> {
|
||||||
let node = tree.get_node(id)?;
|
let node = tree.get_node(id)?;
|
||||||
let name = node.as_element()?;
|
let _name = node.as_element()?;
|
||||||
let tag = name.local.as_ref();
|
let style = styles.get(&id)?;
|
||||||
let style = compute_style(tag, node.get_attribute("style"));
|
let taffy_style = to_taffy_style(style);
|
||||||
let taffy_style = to_taffy_style(&style);
|
|
||||||
|
|
||||||
let child_ids: Vec<taffy::NodeId> = tree
|
let child_ids: Vec<taffy::NodeId> = tree
|
||||||
.children(id)
|
.children(id)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|cid| build(tree, cid, taffy_tree, id_map))
|
.filter_map(|cid| build(tree, cid, taffy_tree, id_map, styles))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let taffy_id = if child_ids.is_empty() {
|
let taffy_id = if child_ids.is_empty() {
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32)) -> Option<Pixmap> {
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let style = crate::compute_style(name.local.as_ref(), node.get_attribute("style"));
|
let style = match laid.styles.get(&nid) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(bg) = style.background_color {
|
if let Some(bg) = style.background_color {
|
||||||
let mut path = PathBuilder::new();
|
let mut path = PathBuilder::new();
|
||||||
@@ -119,12 +122,9 @@ fn paint_text_node(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Find parent element to get geometry and color
|
// Find parent element to get geometry and color
|
||||||
let parent_id = node.parent?;
|
let parent = node.parent?;
|
||||||
let parent = tree.get_node(parent_id)?;
|
let rect = laid.rects.get(&parent)?;
|
||||||
let parent_name = parent.as_element()?;
|
let style = laid.styles.get(&parent)?;
|
||||||
|
|
||||||
let rect = laid.rects.get(&parent_id)?;
|
|
||||||
let style = crate::compute_style(parent_name.local.as_ref(), parent.get_attribute("style"));
|
|
||||||
// Default color to black if not specified.
|
// Default color to black if not specified.
|
||||||
let color = style.color.unwrap_or([0, 0, 0, 255]);
|
let color = style.color.unwrap_or([0, 0, 0, 255]);
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ pub fn compute_style(tag: &str, inline_css: Option<&str>) -> LayoutStyle {
|
|||||||
|
|
||||||
/// Built-in UA defaults. Inline elements currently map to block layout; real
|
/// Built-in UA defaults. Inline elements currently map to block layout; real
|
||||||
/// inline/text layout arrives with the text/paint phase.
|
/// inline/text layout arrives with the text/paint phase.
|
||||||
fn ua_style(tag: &str) -> LayoutStyle {
|
pub fn ua_style(tag: &str) -> LayoutStyle {
|
||||||
let _ = tag;
|
let _ = tag;
|
||||||
LayoutStyle { display: Display::Block, ..Default::default() }
|
LayoutStyle { display: Display::Block, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_inline(style: &mut LayoutStyle, css: &str) {
|
pub fn apply_inline(style: &mut LayoutStyle, css: &str) {
|
||||||
for raw in css.split(';') {
|
for raw in css.split(';') {
|
||||||
let decl = raw.trim();
|
let decl = raw.trim();
|
||||||
if decl.is_empty() {
|
if decl.is_empty() {
|
||||||
|
|||||||
Reference in new issue
Block a user