mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-08 20:01:30 +11:00
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:
parent
ed60031185
commit
44058a8578
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue