1#![deny(missing_docs)]
11
12use std::collections::{HashMap, HashSet};
13use std::io::Read;
14use std::sync::Arc;
15
16use fontdb::Database;
17use krilla::color::rgb;
18use krilla::geom::{Rect, Size, Transform};
19use krilla::paint::FillRule;
20use krilla::surface::Surface;
21use krilla::text::Font;
22use krilla::text::GlyphId;
23use usvg::{fontdb, roxmltree, Group, ImageKind, Node, Tree};
24
25use crate::util::RectExt;
26
27mod clip_path;
28mod filter;
29mod group;
30mod image;
31mod mask;
32mod path;
33mod text;
34mod util;
35
36#[derive(Copy, Clone, Debug)]
38pub struct SvgSettings {
39 pub embed_text: bool,
42 pub filter_scale: f32,
45}
46
47impl Default for SvgSettings {
48 fn default() -> Self {
49 Self {
50 embed_text: true,
51 filter_scale: 4.0,
52 }
53 }
54}
55
56pub trait SurfaceExt {
58 fn draw_svg(&mut self, tree: &Tree, size: Size, svg_settings: SvgSettings) -> Option<()>;
60}
61
62impl SurfaceExt for Surface<'_> {
63 fn draw_svg(&mut self, tree: &Tree, size: Size, svg_settings: SvgSettings) -> Option<()> {
64 let old_fill = self.get_fill().cloned();
65 let old_stroke = self.get_stroke().cloned();
66
67 let transform = Transform::from_scale(
68 size.width() / tree.size().width(),
69 size.height() / tree.size().height(),
70 );
71 self.push_transform(&transform);
72 self.push_clip_path(
73 &Rect::from_xywh(0.0, 0.0, tree.size().width(), tree.size().height())
74 .unwrap()
75 .to_clip_path(),
76 &FillRule::NonZero,
77 );
78 render_tree(tree, svg_settings, self);
79 self.pop();
80 self.pop();
81
82 self.set_fill(old_fill);
83 self.set_stroke(old_stroke);
84
85 Some(())
86 }
87}
88
89struct ProcessContext {
90 fonts: HashMap<fontdb::ID, Font>,
91 svg_settings: SvgSettings,
92}
93
94impl ProcessContext {
95 fn new(fonts: HashMap<fontdb::ID, Font>, svg_settings: SvgSettings) -> Self {
96 Self {
97 fonts,
98 svg_settings,
99 }
100 }
101}
102
103pub(crate) fn render_tree(tree: &Tree, svg_settings: SvgSettings, surface: &mut Surface) {
104 let mut db = tree.fontdb().clone();
105 let mut fc = get_context_from_group(Arc::make_mut(&mut db), svg_settings, tree.root());
106 group::render(tree.root(), surface, &mut fc);
107}
108
109pub(crate) fn render_node(
110 node: &Node,
111 mut tree_fontdb: Arc<Database>,
112 svg_settings: SvgSettings,
113 surface: &mut Surface,
114) {
115 let mut fc = get_context_from_node(Arc::make_mut(&mut tree_fontdb), svg_settings, node);
116 group::render_node(node, surface, &mut fc);
117}
118
119pub fn render_svg_glyph(
122 data: &[u8],
123 context_color: rgb::Color,
124 glyph: GlyphId,
125 default_size: (f32, f32),
126 surface: &mut Surface,
127) -> Option<()> {
128 let mut data = data;
129 let settings = SvgSettings::default();
130
131 let default_size = usvg::Size::from_wh(default_size.0, default_size.1).unwrap();
132
133 let mut decoded = vec![];
134 if data.starts_with(&[0x1f, 0x8b]) {
135 let mut decoder = flate2::read::GzDecoder::new(data);
136 decoder.read_to_end(&mut decoded).ok()?;
137 data = &decoded;
138 }
139
140 let xml = std::str::from_utf8(data).ok()?;
141 let has_viewbox = xml.contains("viewBox");
143 let document = roxmltree::Document::parse(xml).ok()?;
144
145 let opts = usvg::Options {
151 style_sheet: Some(format!(
152 "svg {{ color: rgb({}, {}, {}) }}",
153 context_color.red(),
154 context_color.green(),
155 context_color.blue()
156 )),
157 default_size,
158 ..Default::default()
159 };
160 let tree = Tree::from_xmltree(&document, &opts).ok()?;
161
162 let apply_scale = default_size != tree.size() && has_viewbox;
163
164 if apply_scale {
173 let scale = (default_size.width() / tree.size().width())
174 .min(default_size.height() / tree.size().height());
175 surface.push_transform(&Transform::from_scale(scale, scale))
176 }
177
178 if let Some(node) = tree.node_by_id(&format!("glyph{}", glyph.to_u32())) {
179 render_node(node, tree.fontdb().clone(), settings, surface)
180 } else {
181 render_tree(&tree, settings, surface)
184 };
185
186 if apply_scale {
187 surface.pop();
188 }
189
190 Some(())
191}
192
193fn get_context_from_group(
194 tree_fontdb: &mut Database,
195 svg_settings: SvgSettings,
196 group: &Group,
197) -> ProcessContext {
198 let mut ids = HashSet::new();
199 get_ids_from_group_impl(group, &mut ids);
200 let ids = ids.into_iter().collect::<Vec<_>>();
201 let db = convert_fontdb(tree_fontdb, Some(ids));
202
203 ProcessContext::new(db, svg_settings)
204}
205
206fn get_context_from_node(
207 tree_fontdb: &mut Database,
208 svg_settings: SvgSettings,
209 node: &Node,
210) -> ProcessContext {
211 let mut ids = HashSet::new();
212 get_ids_impl(node, &mut ids);
213 let ids = ids.into_iter().collect::<Vec<_>>();
214 let db = convert_fontdb(tree_fontdb, Some(ids));
215
216 ProcessContext::new(db, svg_settings)
217}
218
219fn get_ids_from_group_impl(group: &Group, ids: &mut HashSet<fontdb::ID>) {
220 for child in group.children() {
221 get_ids_impl(child, ids);
222 }
223}
224
225fn get_ids_impl(node: &Node, ids: &mut HashSet<fontdb::ID>) {
227 match node {
228 Node::Text(t) => {
229 for span in t.layouted() {
230 for g in &span.positioned_glyphs {
231 ids.insert(g.font);
232 }
233 }
234 }
235 Node::Group(group) => {
236 get_ids_from_group_impl(group, ids);
237 }
238 Node::Image(image) => {
239 if let ImageKind::SVG(svg) = image.kind() {
240 get_ids_from_group_impl(svg.root(), ids);
241 }
242 }
243 _ => {}
244 }
245
246 node.subroots(|subroot| get_ids_from_group_impl(subroot, ids));
247}
248
249fn convert_fontdb(db: &mut Database, ids: Option<Vec<fontdb::ID>>) -> HashMap<fontdb::ID, Font> {
250 let mut map = HashMap::new();
251
252 let ids = ids.unwrap_or(db.faces().map(|f| f.id).collect::<Vec<_>>());
253
254 for id in ids {
255 if let Some((font_data, index)) = unsafe { db.make_shared_face_data(id) } {
261 if let Some(font) = Font::new(font_data.into(), index) {
262 map.insert(id, font);
263 }
264 }
265 }
266
267 map
268}