Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline SVG support #106

Merged
merged 1 commit into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/assets/google.html
Original file line number Diff line number Diff line change
Expand Up @@ -3090,7 +3090,7 @@
alt=""
height="24"
width="24"
style="border:none;display:none \9"
style="border:none;display:none"
></image>
</svg>
</a>
Expand Down
18 changes: 18 additions & 0 deletions packages/blitz-dom/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::events::{EventData, HitResult, RendererEvent};
use crate::node::{ImageData, NodeSpecificData, TextBrush};
use crate::util::parse_svg;
use crate::{ElementNodeData, Node, NodeData, TextNodeData, Viewport};
use app_units::Au;
use html5ever::local_name;
Expand Down Expand Up @@ -675,6 +676,23 @@ impl Document {
self.add_stylesheet_for_node(sheet, target_id);
}

pub fn process_svg_element(&mut self, target_id: usize) {
let outer_html = self.nodes[target_id].outer_html();
println!("{}", outer_html);
match parse_svg(outer_html.as_bytes()) {
Ok(svg) => {
println!("SVG parsed successfully");
self.nodes[target_id]
.element_data_mut()
.unwrap()
.node_specific_data = NodeSpecificData::Svg(svg);
}
Err(err) => {
dbg!(err);
}
};
}

pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
if let Some(sheet) = self.ua_stylesheets.remove(contents) {
self.stylist.remove_stylesheet(sheet, &self.guard.read());
Expand Down
9 changes: 8 additions & 1 deletion packages/blitz-dom/src/htmlsink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ fn html5ever_to_blitz_attr(attr: html5ever::Attribute) -> Attribute {

pub struct DocumentHtmlParser<'a> {
doc: RefCell<&'a mut Document>,

style_nodes: RefCell<Vec<usize>>,
svg_nodes: RefCell<Vec<usize>>,

/// Errors that occurred during parsing.
pub errors: RefCell<Vec<Cow<'static, str>>>,
Expand All @@ -41,6 +41,7 @@ impl DocumentHtmlParser<'_> {
DocumentHtmlParser {
doc: RefCell::new(doc),
style_nodes: RefCell::new(Vec::new()),
svg_nodes: RefCell::new(Vec::new()),
errors: RefCell::new(Vec::new()),
quirks_mode: Cell::new(QuirksMode::NoQuirks),
net_provider,
Expand Down Expand Up @@ -180,6 +181,11 @@ impl<'b> TreeSink for DocumentHtmlParser<'b> {
doc.process_style_element(*id);
}

// Parse inline SVGs (<svg> elements)
for id in self.svg_nodes.borrow().iter() {
doc.process_svg_element(*id);
}

for error in self.errors.borrow().iter() {
println!("ERROR: {}", error);
}
Expand Down Expand Up @@ -234,6 +240,7 @@ impl<'b> TreeSink for DocumentHtmlParser<'b> {
"img" => self.load_image(id),
"input" => self.process_button_input(id),
"style" => self.style_nodes.borrow_mut().push(id),
"svg" => self.svg_nodes.borrow_mut().push(id),
_ => {}
}

Expand Down
27 changes: 4 additions & 23 deletions packages/blitz-dom/src/net.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use image::DynamicImage;
use selectors::context::QuirksMode;
use std::{
io::Cursor,
str::FromStr,
sync::atomic::AtomicBool,
sync::{Arc, OnceLock},
};
use std::{io::Cursor, str::FromStr, sync::atomic::AtomicBool, sync::Arc};
use style::{
font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source},
media_queries::MediaList,
Expand All @@ -27,7 +22,7 @@ use blitz_traits::net::{Bytes, RequestHandler, SharedCallback, SharedProvider};
use url::Url;
use usvg::Tree;

static FONT_DB: OnceLock<Arc<usvg::fontdb::Database>> = OnceLock::new();
use crate::util::parse_svg;

#[derive(Clone, Debug)]
pub enum Resource {
Expand Down Expand Up @@ -256,24 +251,10 @@ impl RequestHandler for ImageHandler {
callback.call(Resource::Image(self.0, Arc::new(image)));
return;
};
// Try parse SVG

// TODO: Use fontique
let fontdb = FONT_DB.get_or_init(|| {
let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();
Arc::new(fontdb)
});

let options = usvg::Options {
fontdb: fontdb.clone(),
..Default::default()
};

// Try parse SVG
const DUMMY_SVG : &[u8] = r#"<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>"#.as_bytes();

let tree = Tree::from_data(&bytes, &options)
.unwrap_or_else(|_| Tree::from_data(DUMMY_SVG, &options).unwrap());
let tree = parse_svg(&bytes).unwrap_or(parse_svg(DUMMY_SVG).unwrap());
callback.call(Resource::Svg(self.0, Box::new(tree)));
}
}
60 changes: 60 additions & 0 deletions packages/blitz-dom/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use style::{
stylesheets::CssRuleType,
};
use style_dom::ElementState;
use style_traits::values::ToCss;
use taffy::{
prelude::{Layout, Style},
Cache,
Expand Down Expand Up @@ -793,6 +794,65 @@ impl Node {
s
}

pub fn outer_html(&self) -> String {
let mut output = String::new();
self.write_outer_html(&mut output);
output
}

pub fn write_outer_html(&self, writer: &mut String) {
let has_children = !self.children.is_empty();
let current_color = self
.primary_styles()
.map(|style| style.clone_color())
.map(|color| color.to_css_string());

match &self.raw_dom_data {
NodeData::Document => {}
NodeData::Comment => {}
NodeData::AnonymousBlock(_) => {}
// NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
NodeData::Text(data) => {
writer.push_str(data.content.as_str());
}
NodeData::Element(data) => {
writer.push('<');
writer.push_str(&data.name.local);

for attr in data.attrs() {
writer.push(' ');
writer.push_str(&attr.name.local);
writer.push_str("=\"");
#[allow(clippy::unnecessary_unwrap)] // Convert to if-let chain once stabilised
if current_color.is_some() && attr.value.contains("currentColor") {
writer.push_str(
&attr
.value
.replace("currentColor", current_color.as_ref().unwrap()),
);
} else {
writer.push_str(&attr.value);
}
writer.push('"');
}
if !has_children {
writer.push_str(" /");
}
writer.push('>');

if has_children {
for &child_id in &self.children {
self.tree()[child_id].write_outer_html(writer);
}

writer.push_str("</");
writer.push_str(&data.name.local);
writer.push('>');
}
}
}
}

pub fn attrs(&self) -> Option<&[Attribute]> {
Some(&self.element_data()?.attrs)
}
Expand Down
19 changes: 19 additions & 0 deletions packages/blitz-dom/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
use std::sync::{Arc, LazyLock};

use crate::node::{Node, NodeData};
use peniko::Color as PenikoColor;
use style::color::AbsoluteColor;
use usvg::fontdb;

pub(crate) static FONT_DB: LazyLock<Arc<fontdb::Database>> = LazyLock::new(|| {
let mut db = fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
});

// Debug print an RcDom
pub fn walk_tree(indent: usize, node: &Node) {
Expand Down Expand Up @@ -68,6 +77,16 @@ pub fn walk_tree(indent: usize, node: &Node) {
}
}

pub(crate) fn parse_svg(source: &[u8]) -> Result<usvg::Tree, usvg::Error> {
let options = usvg::Options {
fontdb: Arc::clone(&*FONT_DB),
..Default::default()
};

let tree = usvg::Tree::from_data(source, &options)?;
Ok(tree)
}

pub trait ToPenikoColor {
fn as_peniko(&self) -> PenikoColor;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/blitz-renderer-vello/src/renderer/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,12 @@ impl VelloSceneGenerator<'_> {
cx.stroke_border(scene);
cx.stroke_devtools(scene);

// Render inline SVG elements
if element.local_name() == "svg" {
cx.draw_svg(scene);
return;
}

// Now that background has been drawn, offset pos and cx in order to draw our contents scrolled
let pos = Point {
x: pos.x - element.scroll_offset.x,
Expand Down