1use std::fmt;
9use std::path::{Path, PathBuf};
10use std::sync::{Arc, OnceLock, RwLock};
11
12use agg_rust::math_stroke::{LineCap, LineJoin};
13use agg_rust::trans_affine::TransAffine;
14use usvg::tiny_skia_path::PathSegment;
15
16use crate::draw_ctx::{DrawCtx, FillRule};
17use crate::framebuffer::Framebuffer;
18use crate::gfx_ctx::GfxCtx;
19use crate::lcd_coverage::LcdBuffer;
20use crate::lcd_gfx_ctx::LcdGfxCtx;
21
22pub use compare::{
23 compare_svg_rgba, SvgCompareResult, SvgCompareThresholds, DEFAULT_ALPHA_TOLERANCE,
24 DEFAULT_MISMATCH_RATIO, DEFAULT_OPAQUE_RGB_TOLERANCE, DEFAULT_TRANSLUCENT_RGB_TOLERANCE,
25 DEFAULT_VISUAL_RGB_TOLERANCE,
26};
27
28pub type SvgTree = usvg::Tree;
29
30#[derive(Clone, Copy, Debug)]
31struct SvgRenderState {
32 opacity: f32,
33 layer_width: f64,
34 layer_height: f64,
35 source_cull: Option<SvgSourceRect>,
36}
37
38impl Default for SvgRenderState {
39 fn default() -> Self {
40 Self {
41 opacity: 1.0,
42 layer_width: 1.0,
43 layer_height: 1.0,
44 source_cull: None,
45 }
46 }
47}
48
49#[derive(Clone, Copy, Debug)]
50struct SvgSourceRect {
51 x: f64,
52 y: f64,
53 w: f64,
54 h: f64,
55}
56
57impl SvgSourceRect {
58 fn intersects(self, rect: SvgSourceRect) -> bool {
59 let ax0 = self.x;
60 let ay0 = self.y;
61 let ax1 = self.x + self.w;
62 let ay1 = self.y + self.h;
63 let bx0 = rect.x;
64 let by0 = rect.y;
65 let bx1 = rect.x + rect.w;
66 let by1 = rect.y + rect.h;
67 ax0 < bx1 && ax1 > bx0 && ay0 < by1 && ay1 > by0
68 }
69}
70
71#[derive(Debug)]
73pub enum SvgRenderError {
74 Parse(usvg::Error),
76 DecodeImage(image::ImageError),
78}
79
80impl fmt::Display for SvgRenderError {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 SvgRenderError::Parse(err) => write!(f, "failed to parse SVG: {err}"),
84 SvgRenderError::DecodeImage(err) => write!(f, "failed to decode SVG image: {err}"),
85 }
86 }
87}
88
89impl std::error::Error for SvgRenderError {}
90
91impl From<usvg::Error> for SvgRenderError {
92 fn from(err: usvg::Error) -> Self {
93 SvgRenderError::Parse(err)
94 }
95}
96
97impl From<image::ImageError> for SvgRenderError {
98 fn from(err: image::ImageError) -> Self {
99 SvgRenderError::DecodeImage(err)
100 }
101}
102
103#[derive(Clone, Default)]
109pub struct SvgParseOptions {
110 resources_dir: Option<PathBuf>,
111 font_family: Option<String>,
112 fontdb: Option<Arc<usvg::fontdb::Database>>,
113}
114
115impl SvgParseOptions {
116 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn with_resources_dir(mut self, resources_dir: impl Into<PathBuf>) -> Self {
122 self.resources_dir = Some(resources_dir.into());
123 self
124 }
125
126 pub fn with_font_family(mut self, family: impl Into<String>) -> Self {
128 self.font_family = Some(family.into());
129 self
130 }
131
132 pub fn with_fontdb(mut self, fontdb: Arc<usvg::fontdb::Database>) -> Self {
134 self.fontdb = Some(fontdb);
135 self
136 }
137}
138
139static DEFAULT_SVG_PARSE_OPTIONS: OnceLock<RwLock<SvgParseOptions>> = OnceLock::new();
140
141fn default_svg_parse_options_cell() -> &'static RwLock<SvgParseOptions> {
142 DEFAULT_SVG_PARSE_OPTIONS.get_or_init(|| RwLock::new(system_svg_parse_options()))
143}
144
145fn system_svg_parse_options() -> SvgParseOptions {
146 let mut fontdb = usvg::fontdb::Database::new();
147 fontdb.load_system_fonts();
148 font_defaults::configure_generic_font_families(&mut fontdb, None);
149 SvgParseOptions::new().with_fontdb(Arc::new(fontdb))
150}
151
152pub fn set_default_svg_parse_options(options: SvgParseOptions) {
157 *default_svg_parse_options_cell()
158 .write()
159 .expect("default SVG parse options lock poisoned") = options;
160}
161
162pub fn svg_fontdb_from_font_data<I>(
164 fonts: I,
165 generic_family: Option<&str>,
166) -> Arc<usvg::fontdb::Database>
167where
168 I: IntoIterator<Item = Vec<u8>>,
169{
170 let mut fontdb = usvg::fontdb::Database::new();
171 for bytes in fonts {
172 fontdb.load_font_data(bytes);
173 }
174 font_defaults::configure_generic_font_families(&mut fontdb, generic_family);
175 Arc::new(fontdb)
176}
177
178fn parse_svg_tree(data: &[u8], resources_dir: Option<&Path>) -> Result<usvg::Tree, SvgRenderError> {
179 let mut options = default_svg_parse_options_cell()
180 .read()
181 .expect("default SVG parse options lock poisoned")
182 .clone();
183 if let Some(dir) = resources_dir {
184 options = options.with_resources_dir(dir);
185 }
186 parse_svg(data, &options)
187}
188
189pub fn parse_svg(data: &[u8], svg_options: &SvgParseOptions) -> Result<usvg::Tree, SvgRenderError> {
191 let mut options = usvg::Options::default();
192 options.resources_dir = svg_options.resources_dir.clone();
193 if let Some(font_family) = &svg_options.font_family {
194 options.font_family = font_family.clone();
195 }
196 if let Some(fontdb) = &svg_options.fontdb {
197 options.fontdb = Arc::clone(fontdb);
198 }
199 Ok(usvg::Tree::from_data(data, &options)?)
200}
201
202pub fn render_svg(data: &[u8], ctx: &mut dyn DrawCtx) -> Result<(), SvgRenderError> {
207 let tree = parse_svg_tree(data, None)?;
208 render_svg_tree(&tree, ctx)
209}
210
211pub fn render_svg_with_options(
213 data: &[u8],
214 ctx: &mut dyn DrawCtx,
215 options: &SvgParseOptions,
216) -> Result<(), SvgRenderError> {
217 let tree = parse_svg(data, options)?;
218 render_svg_tree(&tree, ctx)
219}
220
221pub fn render_svg_at_size(
224 data: &[u8],
225 ctx: &mut dyn DrawCtx,
226 width: u32,
227 height: u32,
228) -> Result<(), SvgRenderError> {
229 let tree = parse_svg_tree(data, None)?;
230 render_svg_tree_at_size(&tree, ctx, width, height)
231}
232
233pub fn render_svg_at_size_with_options(
234 data: &[u8],
235 ctx: &mut dyn DrawCtx,
236 width: u32,
237 height: u32,
238 options: &SvgParseOptions,
239) -> Result<(), SvgRenderError> {
240 let tree = parse_svg(data, options)?;
241 render_svg_tree_at_size(&tree, ctx, width, height)
242}
243
244pub fn render_svg_at_size_with_resources(
245 data: &[u8],
246 ctx: &mut dyn DrawCtx,
247 width: u32,
248 height: u32,
249 resources_dir: &Path,
250) -> Result<(), SvgRenderError> {
251 let tree = parse_svg_tree(data, Some(resources_dir))?;
252 render_svg_tree_at_size(&tree, ctx, width, height)
253}
254
255pub fn render_svg_to_framebuffer(data: &[u8]) -> Result<Framebuffer, SvgRenderError> {
260 let tree = parse_svg_tree(data, None)?;
261 render_svg_tree_to_framebuffer(&tree)
262}
263
264pub fn render_svg_to_framebuffer_with_options(
265 data: &[u8],
266 options: &SvgParseOptions,
267) -> Result<Framebuffer, SvgRenderError> {
268 let tree = parse_svg(data, options)?;
269 render_svg_tree_to_framebuffer(&tree)
270}
271
272pub fn render_svg_to_framebuffer_at_size(
279 data: &[u8],
280 width: u32,
281 height: u32,
282) -> Result<Framebuffer, SvgRenderError> {
283 let tree = parse_svg_tree(data, None)?;
284 render_svg_tree_to_framebuffer_at_size(&tree, width, height)
285}
286
287pub fn render_svg_to_framebuffer_at_size_with_options(
288 data: &[u8],
289 width: u32,
290 height: u32,
291 options: &SvgParseOptions,
292) -> Result<Framebuffer, SvgRenderError> {
293 let tree = parse_svg(data, options)?;
294 render_svg_tree_to_framebuffer_at_size(&tree, width, height)
295}
296
297pub fn render_svg_to_framebuffer_at_size_with_resources(
298 data: &[u8],
299 width: u32,
300 height: u32,
301 resources_dir: &Path,
302) -> Result<Framebuffer, SvgRenderError> {
303 let tree = parse_svg_tree(data, Some(resources_dir))?;
304 render_svg_tree_to_framebuffer_at_size(&tree, width, height)
305}
306
307pub fn render_svg_tree_to_framebuffer(tree: &usvg::Tree) -> Result<Framebuffer, SvgRenderError> {
309 let width = tree.size().width().ceil().max(1.0) as u32;
310 let height = tree.size().height().ceil().max(1.0) as u32;
311 render_svg_tree_to_framebuffer_at_size(tree, width, height)
312}
313
314pub fn render_svg_tree_to_framebuffer_at_size(
316 tree: &usvg::Tree,
317 width: u32,
318 height: u32,
319) -> Result<Framebuffer, SvgRenderError> {
320 let width = width.max(1);
321 let height = height.max(1);
322 let mut fb = Framebuffer::new(width, height);
323 {
324 let mut ctx = GfxCtx::new(&mut fb);
325 render_svg_tree_at_size(tree, &mut ctx, width, height)?;
326 }
327 Ok(fb)
328}
329
330pub fn render_svg_tree_region_to_framebuffer_at_size(
332 tree: &usvg::Tree,
333 src_x: f64,
334 src_y: f64,
335 src_w: f64,
336 src_h: f64,
337 width: u32,
338 height: u32,
339) -> Result<Framebuffer, SvgRenderError> {
340 let width = width.max(1);
341 let height = height.max(1);
342 let mut fb = Framebuffer::new(width, height);
343 {
344 let mut ctx = GfxCtx::new(&mut fb);
345 render_svg_tree_region_at_size(tree, &mut ctx, src_x, src_y, src_w, src_h, width, height)?;
346 }
347 Ok(fb)
348}
349
350pub fn render_svg_to_lcd_buffer(data: &[u8]) -> Result<LcdBuffer, SvgRenderError> {
355 let tree = parse_svg_tree(data, None)?;
356 render_svg_tree_to_lcd_buffer(&tree)
357}
358
359pub fn render_svg_to_lcd_buffer_with_options(
360 data: &[u8],
361 options: &SvgParseOptions,
362) -> Result<LcdBuffer, SvgRenderError> {
363 let tree = parse_svg(data, options)?;
364 render_svg_tree_to_lcd_buffer(&tree)
365}
366
367pub fn render_svg_to_lcd_buffer_at_size(
370 data: &[u8],
371 width: u32,
372 height: u32,
373) -> Result<LcdBuffer, SvgRenderError> {
374 let tree = parse_svg_tree(data, None)?;
375 render_svg_tree_to_lcd_buffer_at_size(&tree, width, height)
376}
377
378pub fn render_svg_to_lcd_buffer_at_size_with_options(
379 data: &[u8],
380 width: u32,
381 height: u32,
382 options: &SvgParseOptions,
383) -> Result<LcdBuffer, SvgRenderError> {
384 let tree = parse_svg(data, options)?;
385 render_svg_tree_to_lcd_buffer_at_size(&tree, width, height)
386}
387
388pub fn render_svg_to_lcd_buffer_at_size_with_resources(
389 data: &[u8],
390 width: u32,
391 height: u32,
392 resources_dir: &Path,
393) -> Result<LcdBuffer, SvgRenderError> {
394 let tree = parse_svg_tree(data, Some(resources_dir))?;
395 render_svg_tree_to_lcd_buffer_at_size(&tree, width, height)
396}
397
398pub fn render_svg_tree_to_lcd_buffer(tree: &usvg::Tree) -> Result<LcdBuffer, SvgRenderError> {
400 let width = tree.size().width().ceil().max(1.0) as u32;
401 let height = tree.size().height().ceil().max(1.0) as u32;
402 render_svg_tree_to_lcd_buffer_at_size(tree, width, height)
403}
404
405pub fn render_svg_tree_to_lcd_buffer_at_size(
407 tree: &usvg::Tree,
408 width: u32,
409 height: u32,
410) -> Result<LcdBuffer, SvgRenderError> {
411 let width = width.max(1);
412 let height = height.max(1);
413 let mut buffer = LcdBuffer::new(width, height);
414 {
415 let mut ctx = LcdGfxCtx::new(&mut buffer);
416 render_svg_tree_at_size(tree, &mut ctx, width, height)?;
417 }
418 Ok(buffer)
419}
420
421pub fn render_svg_tree(tree: &usvg::Tree, ctx: &mut dyn DrawCtx) -> Result<(), SvgRenderError> {
427 let width = tree.size().width().ceil().max(1.0) as u32;
428 let height = tree.size().height().ceil().max(1.0) as u32;
429 render_svg_tree_at_size(tree, ctx, width, height)
430}
431
432pub fn render_svg_tree_at_size(
435 tree: &usvg::Tree,
436 ctx: &mut dyn DrawCtx,
437 width: u32,
438 height: u32,
439) -> Result<(), SvgRenderError> {
440 let saved_transform = ctx.transform();
441 let mut svg_to_ctx = saved_transform;
442 svg_to_ctx.premultiply(&svg_y_down_to_ctx_y_up(tree, width, height));
443
444 ctx.save();
445 ctx.set_transform(svg_to_ctx);
446 render_tree::render_group(
447 tree.root(),
448 ctx,
449 SvgRenderState {
450 layer_width: width.max(1) as f64,
451 layer_height: height.max(1) as f64,
452 ..SvgRenderState::default()
453 },
454 )?;
455 ctx.restore();
456 Ok(())
457}
458
459#[allow(clippy::too_many_arguments)]
462pub fn render_svg_tree_region_at_size(
463 tree: &usvg::Tree,
464 ctx: &mut dyn DrawCtx,
465 src_x: f64,
466 src_y: f64,
467 src_w: f64,
468 src_h: f64,
469 width: u32,
470 height: u32,
471) -> Result<(), SvgRenderError> {
472 let width = width.max(1);
473 let height = height.max(1);
474 let src_w = src_w.max(1.0);
475 let src_h = src_h.max(1.0);
476 let saved_transform = ctx.transform();
477 let mut svg_to_ctx = saved_transform;
478 svg_to_ctx.premultiply(&svg_y_down_region_to_ctx_y_up(
479 src_x, src_y, src_w, src_h, width, height,
480 ));
481
482 ctx.save();
483 ctx.set_transform(svg_to_ctx);
484 render_tree::render_group(
485 tree.root(),
486 ctx,
487 SvgRenderState {
488 layer_width: width as f64,
489 layer_height: height as f64,
490 source_cull: Some(SvgSourceRect {
491 x: src_x,
492 y: src_y,
493 w: src_w,
494 h: src_h,
495 }),
496 ..SvgRenderState::default()
497 },
498 )?;
499 ctx.restore();
500 Ok(())
501}
502
503fn svg_y_down_to_ctx_y_up(tree: &usvg::Tree, width: u32, height: u32) -> TransAffine {
504 let sx = width.max(1) as f64 / tree.size().width().max(1.0) as f64;
505 let sy = height.max(1) as f64 / tree.size().height().max(1.0) as f64;
506 TransAffine::new_custom(sx, 0.0, 0.0, -sy, 0.0, height.max(1) as f64)
507}
508
509fn svg_y_down_region_to_ctx_y_up(
510 src_x: f64,
511 src_y: f64,
512 src_w: f64,
513 src_h: f64,
514 width: u32,
515 height: u32,
516) -> TransAffine {
517 let sx = width as f64 / src_w;
518 let sy = height as f64 / src_h;
519 TransAffine::new_custom(sx, 0.0, 0.0, -sy, -src_x * sx, height as f64 + src_y * sy)
520}
521
522fn emit_path(path: &usvg::Path, ctx: &mut dyn DrawCtx) {
523 ctx.begin_path();
524 for segment in path.data().segments() {
525 match segment {
526 PathSegment::MoveTo(p) => ctx.move_to(p.x as f64, p.y as f64),
527 PathSegment::LineTo(p) => ctx.line_to(p.x as f64, p.y as f64),
528 PathSegment::QuadTo(p1, p2) => {
529 ctx.quad_to(p1.x as f64, p1.y as f64, p2.x as f64, p2.y as f64)
530 }
531 PathSegment::CubicTo(p1, p2, p3) => ctx.cubic_to(
532 p1.x as f64,
533 p1.y as f64,
534 p2.x as f64,
535 p2.y as f64,
536 p3.x as f64,
537 p3.y as f64,
538 ),
539 PathSegment::Close => ctx.close_path(),
540 }
541 }
542}
543
544fn apply_transform(ctx: &mut dyn DrawCtx, transform: usvg::Transform) {
545 let mut current = ctx.transform();
546 let node_transform = to_trans_affine(transform);
547 current.premultiply(&node_transform);
548 ctx.set_transform(current);
549}
550
551pub(super) fn to_trans_affine(transform: usvg::Transform) -> TransAffine {
552 TransAffine::new_custom(
553 transform.sx as f64,
554 transform.ky as f64,
555 transform.kx as f64,
556 transform.sy as f64,
557 transform.tx as f64,
558 transform.ty as f64,
559 )
560}
561
562fn transformed_rect(transform: &TransAffine, width: f64, height: f64) -> (f64, f64, f64, f64) {
563 let corners = [(0.0, 0.0), (width, 0.0), (width, height), (0.0, height)];
564 let mut min_x = f64::INFINITY;
565 let mut min_y = f64::INFINITY;
566 let mut max_x = f64::NEG_INFINITY;
567 let mut max_y = f64::NEG_INFINITY;
568 for (mut x, mut y) in corners {
569 transform.transform(&mut x, &mut y);
570 min_x = min_x.min(x);
571 min_y = min_y.min(y);
572 max_x = max_x.max(x);
573 max_y = max_y.max(y);
574 }
575
576 (min_x, min_y, (max_x - min_x).abs(), (max_y - min_y).abs())
577}
578
579fn transformed_svg_rect(rect: usvg::Rect, transform: usvg::Transform) -> SvgSourceRect {
580 let transform = to_trans_affine(transform);
581 let x = rect.x() as f64;
582 let y = rect.y() as f64;
583 let w = rect.width() as f64;
584 let h = rect.height() as f64;
585 let corners = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)];
586 let mut min_x = f64::INFINITY;
587 let mut min_y = f64::INFINITY;
588 let mut max_x = f64::NEG_INFINITY;
589 let mut max_y = f64::NEG_INFINITY;
590 for (mut x, mut y) in corners {
591 transform.transform(&mut x, &mut y);
592 min_x = min_x.min(x);
593 min_y = min_y.min(y);
594 max_x = max_x.max(x);
595 max_y = max_y.max(y);
596 }
597 SvgSourceRect {
598 x: min_x,
599 y: min_y,
600 w: (max_x - min_x).abs(),
601 h: (max_y - min_y).abs(),
602 }
603}
604
605fn map_line_cap(cap: usvg::LineCap) -> LineCap {
606 match cap {
607 usvg::LineCap::Butt => LineCap::Butt,
608 usvg::LineCap::Round => LineCap::Round,
609 usvg::LineCap::Square => LineCap::Square,
610 }
611}
612
613fn map_line_join(join: usvg::LineJoin) -> LineJoin {
614 match join {
615 usvg::LineJoin::Miter | usvg::LineJoin::MiterClip => LineJoin::Miter,
616 usvg::LineJoin::Round => LineJoin::Round,
617 usvg::LineJoin::Bevel => LineJoin::Bevel,
618 }
619}
620
621fn map_fill_rule(rule: usvg::FillRule) -> FillRule {
622 match rule {
623 usvg::FillRule::NonZero => FillRule::NonZero,
624 usvg::FillRule::EvenOdd => FillRule::EvenOdd,
625 }
626}
627
628#[cfg(test)]
629mod clip_tests;
630pub mod compare;
631#[cfg(test)]
632mod gradient_tests;
633#[cfg(test)]
634mod image_tests;
635#[cfg(test)]
636mod opacity_tests;
637#[cfg(test)]
638mod text_tests;
639
640mod font_defaults;
641mod paint;
642mod pattern;
643mod render_tree;
644use render_tree::render_group;
645
646#[cfg(test)]
647mod core_tests;