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 surface: &mut Surface,
126) -> Option<()> {
127 let mut data = data;
128 let settings = SvgSettings::default();
129
130 let mut decoded = vec![];
131 if data.starts_with(&[0x1f, 0x8b]) {
132 let mut decoder = flate2::read::GzDecoder::new(data);
133 decoder.read_to_end(&mut decoded).ok()?;
134 data = &decoded;
135 }
136
137 let xml = std::str::from_utf8(data).ok()?;
138 let document = roxmltree::Document::parse(xml).ok()?;
139
140 let opts = usvg::Options {
146 style_sheet: Some(format!(
147 "svg {{ color: rgb({}, {}, {}) }}",
148 context_color.red(),
149 context_color.green(),
150 context_color.blue()
151 )),
152 ..Default::default()
153 };
154 let tree = Tree::from_xmltree(&document, &opts).ok()?;
155
156 if let Some(node) = tree.node_by_id(&format!("glyph{}", glyph.to_u32())) {
157 render_node(node, tree.fontdb().clone(), settings, surface)
158 } else {
159 render_tree(&tree, settings, surface)
162 };
163
164 Some(())
165}
166
167fn get_context_from_group(
168 tree_fontdb: &mut Database,
169 svg_settings: SvgSettings,
170 group: &Group,
171) -> ProcessContext {
172 let mut ids = HashSet::new();
173 get_ids_from_group_impl(group, &mut ids);
174 let ids = ids.into_iter().collect::<Vec<_>>();
175 let db = convert_fontdb(tree_fontdb, Some(ids));
176
177 ProcessContext::new(db, svg_settings)
178}
179
180fn get_context_from_node(
181 tree_fontdb: &mut Database,
182 svg_settings: SvgSettings,
183 node: &Node,
184) -> ProcessContext {
185 let mut ids = HashSet::new();
186 get_ids_impl(node, &mut ids);
187 let ids = ids.into_iter().collect::<Vec<_>>();
188 let db = convert_fontdb(tree_fontdb, Some(ids));
189
190 ProcessContext::new(db, svg_settings)
191}
192
193fn get_ids_from_group_impl(group: &Group, ids: &mut HashSet<fontdb::ID>) {
194 for child in group.children() {
195 get_ids_impl(child, ids);
196 }
197}
198
199fn get_ids_impl(node: &Node, ids: &mut HashSet<fontdb::ID>) {
201 match node {
202 Node::Text(t) => {
203 for span in t.layouted() {
204 for g in &span.positioned_glyphs {
205 ids.insert(g.font);
206 }
207 }
208 }
209 Node::Group(group) => {
210 get_ids_from_group_impl(group, ids);
211 }
212 Node::Image(image) => {
213 if let ImageKind::SVG(svg) = image.kind() {
214 get_ids_from_group_impl(svg.root(), ids);
215 }
216 }
217 _ => {}
218 }
219
220 node.subroots(|subroot| get_ids_from_group_impl(subroot, ids));
221}
222
223fn convert_fontdb(db: &mut Database, ids: Option<Vec<fontdb::ID>>) -> HashMap<fontdb::ID, Font> {
224 let mut map = HashMap::new();
225
226 let ids = ids.unwrap_or(db.faces().map(|f| f.id).collect::<Vec<_>>());
227
228 for id in ids {
229 if let Some((font_data, index)) = unsafe { db.make_shared_face_data(id) } {
235 if let Some(font) = Font::new(font_data.into(), index) {
236 map.insert(id, font);
237 }
238 }
239 }
240
241 map
242}