1use lyon::math::Point;
2use lyon::path::Path;
3use lyon::tessellation;
4use lyon::tessellation::geometry_builder::{simple_builder, VertexBuffers};
5use usvg::TreeParsing;
6
7use abstutil::VecMap;
8use geom::{Bounds, Pt2D, Tessellation};
9
10use crate::{Color, Fill, GeomBatch, LinearGradient, Prerender};
11
12pub const HIGH_QUALITY: f32 = 0.01;
13pub const LOW_QUALITY: f32 = 1.0;
14
15pub fn load_svg(prerender: &Prerender, filename: &str) -> (GeomBatch, Bounds) {
19 let cache_key = format!("file://{}", filename);
20 if let Some(pair) = prerender.assets.get_cached_svg(&cache_key) {
21 return pair;
22 }
23
24 let bytes = (prerender.assets.read_svg)(filename);
25 load_svg_from_bytes_uncached(&bytes)
26 .map(|(batch, bounds)| {
27 prerender.assets.cache_svg(cache_key, batch.clone(), bounds);
28 (batch, bounds)
29 })
30 .unwrap_or_else(|_| panic!("error loading svg: {}", filename))
31}
32
33pub fn load_svg_bytes(
34 prerender: &Prerender,
35 cache_key: &str,
36 bytes: &[u8],
37) -> anyhow::Result<(GeomBatch, Bounds)> {
38 let cache_key = format!("bytes://{}", cache_key);
39 if let Some(pair) = prerender.assets.get_cached_svg(&cache_key) {
40 return Ok(pair);
41 }
42
43 load_svg_from_bytes_uncached(bytes).map(|(batch, bounds)| {
44 prerender.assets.cache_svg(cache_key, batch.clone(), bounds);
45 (batch, bounds)
46 })
47}
48
49pub fn load_svg_from_bytes_uncached(bytes: &[u8]) -> anyhow::Result<(GeomBatch, Bounds)> {
50 let svg_tree = usvg::Tree::from_data(bytes, &usvg::Options::default())?;
51 let mut batch = GeomBatch::new();
52 match add_svg_inner(&mut batch, svg_tree, HIGH_QUALITY) {
53 Ok(bounds) => Ok((batch, bounds)),
54 Err(err) => Err(anyhow!(err)),
55 }
56}
57
58pub(crate) fn add_svg_inner(
62 batch: &mut GeomBatch,
63 svg_tree: usvg::Tree,
64 tolerance: f32,
65) -> Result<Bounds, String> {
66 let mut fill_tess = tessellation::FillTessellator::new();
67 let mut stroke_tess = tessellation::StrokeTessellator::new();
68 let mut mesh_per_color: VecMap<Fill, VertexBuffers<_, u16>> = VecMap::new();
70
71 for node in svg_tree.root.descendants() {
72 if let usvg::NodeKind::Path(ref p) = *node.borrow() {
73 if let Some(ref fill) = p.fill {
76 let color = convert_color(&fill.paint, fill.opacity.get());
77 let geom = mesh_per_color.mut_or_insert(color, VertexBuffers::new);
78 if let Err(err) = fill_tess.tessellate(
79 &convert_path(p),
80 &tessellation::FillOptions::tolerance(tolerance),
81 &mut simple_builder(geom),
82 ) {
83 return Err(format!("Couldn't tessellate something: {err}"));
84 }
85 }
86
87 if let Some(ref stroke) = p.stroke {
88 let (color, stroke_opts) = convert_stroke(stroke, tolerance);
89 let geom = mesh_per_color.mut_or_insert(color, VertexBuffers::new);
90 stroke_tess
91 .tessellate(&convert_path(p), &stroke_opts, &mut simple_builder(geom))
92 .unwrap();
93 }
94 }
95 }
96
97 for (color, mesh) in mesh_per_color.consume() {
98 batch.push(
99 color,
100 Tessellation::new(
101 mesh.vertices
102 .into_iter()
103 .map(|v| Pt2D::new(f64::from(v.x), f64::from(v.y)))
104 .collect(),
105 mesh.indices.into_iter().map(|idx| idx as usize).collect(),
106 ),
107 );
108 }
109 Ok(Bounds::from(&[
110 Pt2D::new(0.0, 0.0),
111 Pt2D::new(svg_tree.size.width(), svg_tree.size.height()),
112 ]))
113}
114
115fn convert_path(p: &usvg::Path) -> Path {
116 let mut builder = Path::builder().with_svg();
117 for segment in p.data.segments() {
118 match segment {
119 usvg::PathSegment::MoveTo { x, y } => {
120 builder.move_to(Point::new(x as f32, y as f32));
121 }
122 usvg::PathSegment::LineTo { x, y } => {
123 builder.line_to(Point::new(x as f32, y as f32));
124 }
125 usvg::PathSegment::CurveTo {
126 x1,
127 y1,
128 x2,
129 y2,
130 x,
131 y,
132 } => {
133 builder.cubic_bezier_to(
134 Point::new(x1 as f32, y1 as f32),
135 Point::new(x2 as f32, y2 as f32),
136 Point::new(x as f32, y as f32),
137 );
138 }
139 usvg::PathSegment::ClosePath => {
140 builder.close();
141 }
142 }
143 }
144 builder.build()
145}
146
147fn convert_stroke(s: &usvg::Stroke, tolerance: f32) -> (Fill, tessellation::StrokeOptions) {
148 let color = convert_color(&s.paint, s.opacity.get());
149 let linecap = match s.linecap {
150 usvg::LineCap::Butt => tessellation::LineCap::Butt,
151 usvg::LineCap::Square => tessellation::LineCap::Square,
152 usvg::LineCap::Round => tessellation::LineCap::Round,
153 };
154 let linejoin = match s.linejoin {
155 usvg::LineJoin::Miter => tessellation::LineJoin::Miter,
156 usvg::LineJoin::Bevel => tessellation::LineJoin::Bevel,
157 usvg::LineJoin::Round => tessellation::LineJoin::Round,
158 };
159
160 let opt = tessellation::StrokeOptions::tolerance(tolerance)
161 .with_line_width(s.width.get() as f32)
162 .with_line_cap(linecap)
163 .with_line_join(linejoin);
164
165 (color, opt)
166}
167
168fn convert_color(paint: &usvg::Paint, opacity: f64) -> Fill {
169 match paint {
170 usvg::Paint::Color(c) => Fill::Color(Color::rgba(
171 c.red as usize,
172 c.green as usize,
173 c.blue as usize,
174 opacity as f32,
175 )),
176 usvg::Paint::LinearGradient(lg) => LinearGradient::new_fill(lg),
177 _ => panic!("Unsupported color style {:?}", paint),
179 }
180}