Improve the svg parser (#247)

* Improve the svg parser

* Handle `opacity`

* Double the segments buffer

* Rotate the multiplication

This matches firefox's output, although it is contrary to my use
of 3d transformation matrices

* Double ptcl size

Seems to be required to show entire scene at 4k
This commit is contained in:
Daniel McNab 2023-01-16 18:22:35 +00:00 committed by GitHub
parent ed60031185
commit 44058a8578
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 23 deletions

View file

@ -1,6 +1,6 @@
//! A loader for a tiny fragment of SVG
use std::str::FromStr;
use std::{num::ParseFloatError, str::FromStr};
use roxmltree::{Document, Node};
use vello::{
@ -39,45 +39,71 @@ impl PicoSvg {
let root = doc.root_element();
let mut items = Vec::new();
let mut parser = Parser::new(&mut items, scale);
let transform = if scale >= 0.0 {
Affine::scale(scale)
} else {
Affine::new([-scale, 0.0, 0.0, scale, 0.0, 0.0])
};
let props = RecursiveProperties {
transform,
fill: Some(Color::BLACK),
};
// The root element is the svg document element, which we don't care about
for node in root.children() {
parser.rec_parse(node)?;
parser.rec_parse(node, &props)?;
}
Ok(PicoSvg { items })
}
}
#[derive(Clone)]
struct RecursiveProperties {
transform: Affine,
fill: Option<Color>,
}
impl<'a> Parser<'a> {
fn new(items: &'a mut Vec<Item>, scale: f64) -> Parser<'a> {
Parser { scale, items }
}
fn rec_parse(&mut self, node: Node) -> Result<(), Box<dyn std::error::Error>> {
let transform = if self.scale >= 0.0 {
Affine::scale(self.scale)
} else {
Affine::new([-self.scale, 0.0, 0.0, self.scale, 0.0, 1536.0])
};
fn rec_parse(
&mut self,
node: Node,
properties: &RecursiveProperties,
) -> Result<(), Box<dyn std::error::Error>> {
if node.is_element() {
let mut properties = properties.clone();
if let Some(fill_color) = node.attribute("fill") {
if fill_color == "none" {
properties.fill = None;
} else {
let color = parse_color(fill_color);
let color = modify_opacity(color, "fill-opacity", node);
// TODO: Handle recursive opacity properly
let color = modify_opacity(color, "opacity", node);
properties.fill = Some(color);
}
}
if let Some(transform) = node.attribute("transform") {
let new_transform = parse_transform(transform);
properties.transform = properties.transform * new_transform;
}
match node.tag_name().name() {
"g" => {
for child in node.children() {
self.rec_parse(child)?;
self.rec_parse(child, &properties)?;
}
}
"path" => {
let d = node.attribute("d").ok_or("missing 'd' attribute")?;
let bp = BezPath::from_svg(d)?;
let path = transform * bp;
// TODO: default fill color is black, but this is overridden in tiger to this logic.
if let Some(fill_color) = node.attribute("fill") {
if fill_color != "none" {
let color = parse_color(fill_color);
let color = modify_opacity(color, "fill-opacity", node);
self.items.push(Item::Fill(FillItem {
color,
path: path.clone(),
}));
}
let path = properties.transform * bp;
if let Some(color) = properties.fill {
self.items.push(Item::Fill(FillItem {
color,
path: path.clone(),
}));
}
if let Some(stroke_color) = node.attribute("stroke") {
if stroke_color != "none" {
@ -87,19 +113,40 @@ impl<'a> Parser<'a> {
)?;
let color = parse_color(stroke_color);
let color = modify_opacity(color, "stroke-opacity", node);
// TODO: Handle recursive opacity properly
let color = modify_opacity(color, "opacity", node);
self.items
.push(Item::Stroke(StrokeItem { width, color, path }));
}
}
}
_ => (),
other => eprintln!("Unhandled node type {other}"),
}
}
Ok(())
}
}
fn parse_transform(transform: &str) -> Affine {
let transform = transform.trim();
if transform.starts_with("matrix(") {
let vals = transform["matrix(".len()..transform.len() - 1]
.split(|c| matches!(c, ',' | ' '))
.map(str::parse)
.collect::<Result<Vec<f64>, ParseFloatError>>()
.expect("Could parse all values of 'matrix' as floats");
Affine::new(
vals.try_into()
.expect("Should be six arguments to `matrix`"),
)
} else {
eprintln!("Did not understand transform attribute {transform:?}");
Affine::IDENTITY
}
}
fn parse_color(color: &str) -> Color {
let color = color.trim();
if color.as_bytes()[0] == b'#' {
let mut hex = u32::from_str_radix(&color[1..], 16).unwrap();
if color.len() == 4 {

View file

@ -362,7 +362,7 @@ pub fn render_encoding_full(
],
);
let segments_buf = ResourceProxy::new_buf(1 << 24, "segments_buf");
let segments_buf = ResourceProxy::new_buf(1 << 26, "segments_buf");
recording.dispatch(
shaders.path_coarse,
(path_coarse_wgs, 1, 1),
@ -382,7 +382,7 @@ pub fn render_encoding_full(
(path_wgs, 1, 1),
[config_buf, path_buf, tile_buf],
);
let ptcl_buf = ResourceProxy::new_buf(1 << 24, "ptcl_buf");
let ptcl_buf = ResourceProxy::new_buf(1 << 25, "ptcl_buf");
recording.dispatch(
shaders.coarse,
(width_in_bins, height_in_bins, 1),