1use alloc::collections::BTreeMap;
10use alloc::string::String;
11use alloc::vec::Vec;
12use core::sync::atomic::{AtomicU64, Ordering};
13
14use gfx::pathdata::PData;
15use gfx::xform::Matrix;
16use gfx::Surface;
17use rt::sync::SpinLock;
18
19use crate::css;
20use crate::values::{self, Len, Par};
21use crate::xml::{self, Item, NONE};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub(crate) enum Kind {
26 Svg,
27 G,
28 Defs,
29 Use,
30 Symbol,
31 Switch,
32 A,
33 Path,
34 Rect,
35 Circle,
36 Ellipse,
37 Line,
38 Polyline,
39 Polygon,
40 Text,
41 Tspan,
42 Image,
43 LinearGradient,
44 RadialGradient,
45 Stop,
46 ClipPath,
47 Style,
48 Title,
49 Other,
50}
51
52fn kind_of(name: &str) -> Kind {
53 match name {
54 "svg" => Kind::Svg,
55 "g" => Kind::G,
56 "defs" => Kind::Defs,
57 "use" => Kind::Use,
58 "symbol" => Kind::Symbol,
59 "switch" => Kind::Switch,
60 "a" => Kind::A,
61 "path" => Kind::Path,
62 "rect" => Kind::Rect,
63 "circle" => Kind::Circle,
64 "ellipse" => Kind::Ellipse,
65 "line" => Kind::Line,
66 "polyline" => Kind::Polyline,
67 "polygon" => Kind::Polygon,
68 "text" => Kind::Text,
69 "tspan" | "textPath" => Kind::Tspan,
70 "image" => Kind::Image,
71 "linearGradient" => Kind::LinearGradient,
72 "radialGradient" => Kind::RadialGradient,
73 "stop" => Kind::Stop,
74 "clipPath" => Kind::ClipPath,
75 "style" => Kind::Style,
76 "title" => Kind::Title,
77 _ => Kind::Other,
78 }
79}
80
81pub(crate) const P_FILL: u8 = 0;
83pub(crate) const P_FILL_OPACITY: u8 = 1;
84pub(crate) const P_FILL_RULE: u8 = 2;
85pub(crate) const P_STROKE: u8 = 3;
86pub(crate) const P_STROKE_WIDTH: u8 = 4;
87pub(crate) const P_STROKE_OPACITY: u8 = 5;
88pub(crate) const P_LINECAP: u8 = 6;
89pub(crate) const P_LINEJOIN: u8 = 7;
90pub(crate) const P_MITER: u8 = 8;
91pub(crate) const P_DASHARRAY: u8 = 9;
92pub(crate) const P_DASHOFFSET: u8 = 10;
93pub(crate) const P_OPACITY: u8 = 11;
94pub(crate) const P_COLOR: u8 = 12;
95pub(crate) const P_DISPLAY: u8 = 13;
96pub(crate) const P_VISIBILITY: u8 = 14;
97pub(crate) const P_CLIP_PATH: u8 = 15;
98pub(crate) const P_CLIP_RULE: u8 = 16;
99pub(crate) const P_STOP_COLOR: u8 = 17;
100pub(crate) const P_STOP_OPACITY: u8 = 18;
101pub(crate) const P_FONT_SIZE: u8 = 19;
102pub(crate) const P_FONT_FAMILY: u8 = 20;
103pub(crate) const P_FONT_WEIGHT: u8 = 21;
104pub(crate) const P_FONT_STYLE: u8 = 22;
105pub(crate) const P_TEXT_ANCHOR: u8 = 23;
106pub(crate) const P_OVERFLOW: u8 = 24;
107const PROP_NAMES: [&str; 25] = [
108 "fill",
109 "fill-opacity",
110 "fill-rule",
111 "stroke",
112 "stroke-width",
113 "stroke-opacity",
114 "stroke-linecap",
115 "stroke-linejoin",
116 "stroke-miterlimit",
117 "stroke-dasharray",
118 "stroke-dashoffset",
119 "opacity",
120 "color",
121 "display",
122 "visibility",
123 "clip-path",
124 "clip-rule",
125 "stop-color",
126 "stop-opacity",
127 "font-size",
128 "font-family",
129 "font-weight",
130 "font-style",
131 "text-anchor",
132 "overflow",
133];
134
135fn prop_id(name: &str) -> Option<u8> {
136 PROP_NAMES.iter().position(|&n| n == name).map(|k| k as u8)
137}
138
139pub(crate) struct Node {
140 pub kind: Kind,
141 pub attrs: Vec<(String, String)>,
142 pub props: Vec<(u8, String)>,
144 pub kids: Vec<Item>,
145 pub parent: u32,
146 pub tf: Option<Matrix>,
148 pub pd: Option<PData>,
150 pub img: Option<u32>,
152}
153
154impl Node {
155 pub fn attr(&self, name: &str) -> Option<&str> {
156 self.attrs.iter().find(|(n, _)| n == name).map(|(_, v)| v.as_str())
157 }
158
159 pub fn href(&self) -> Option<&str> {
161 self.attr("href").or_else(|| self.attr("xlink:href"))
162 }
163
164 pub fn len(&self, name: &str) -> Option<Len> {
166 self.attr(name).and_then(values::length)
167 }
168
169 pub fn prop(&self, p: u8) -> Option<&str> {
171 self.props.iter().rev().find(|(k, _)| *k == p).map(|(_, v)| v.as_str())
172 }
173
174 pub fn text(&self) -> String {
176 let mut s = String::new();
177 for k in &self.kids {
178 if let Item::Text(t) = k {
179 s.push_str(t);
180 }
181 }
182 s
183 }
184}
185
186pub(crate) struct Img {
189 data: Vec<u8>,
190 sf: SpinLock<Option<Option<Surface>>>,
191}
192
193pub struct Svg {
195 pub(crate) nodes: Vec<Node>,
196 pub(crate) ids: BTreeMap<String, u32>,
197 pub(crate) images: Vec<Img>,
198 pub(crate) width: f64,
199 pub(crate) height: f64,
200 pub(crate) view_box: Option<[f64; 4]>,
201 pub(crate) par: Par,
202 title: String,
203 truncated: bool,
204 img_px: AtomicU64, }
206
207const DEFAULT_W: f64 = 300.0;
208const DEFAULT_H: f64 = 150.0;
209const SIZE_MAX: f64 = 1.0e6;
210const CASCADE_STEPS: u32 = 50_000_000;
211
212fn abs_len(v: Option<&str>) -> Option<f64> {
214 let l = values::length(v?)?;
215 let px = match l.u {
216 values::Unit::Px => l.v,
217 values::Unit::Em => l.v * 16.0,
218 values::Unit::Ex => l.v * 8.0,
219 values::Unit::Pct => return None,
220 };
221 if px > 0.0 { Some(px.min(SIZE_MAX)) } else { None }
222}
223
224impl Svg {
225 pub fn parse(data: &[u8]) -> Result<Svg, i32> {
228 let inflated;
229 let data = if data.len() >= 3 && data[0] == 0x1F && data[1] == 0x8B && data[2] == 8 {
230 inflated = crate::gunzip(data)?;
231 &inflated[..]
232 } else {
233 data
234 };
235 let dom = xml::parse(data)?;
236 Svg::from_dom(dom)
237 }
238
239 fn from_dom(mut dom: xml::Dom) -> Result<Svg, i32> {
240 if dom.els.first().map(|e| e.name.as_str()) != Some("svg") {
241 return Err(img_core::ERR_HDR);
242 }
243 let mut rules: Vec<css::Rule> = Vec::new();
245 let mut order = 0;
246 for e in &dom.els {
247 if e.name == "style" && e.attr("type").is_none_or(|t| t.trim().is_empty() || t.trim().eq_ignore_ascii_case("text/css")) {
248 let mut s = String::new();
249 for k in &e.kids {
250 if let Item::Text(t) = k {
251 s.push_str(t);
252 }
253 }
254 css::sheet(&s, &mut order, &mut rules);
255 }
256 }
257 rules.sort_by(|a, b| (a.sel.spec, a.order).cmp(&(b.sel.spec, b.order)));
258 let mut steps = CASCADE_STEPS;
260 let mut props_all: Vec<Vec<(u8, String)>> = Vec::with_capacity(dom.els.len());
261 for k in 0..dom.els.len() {
262 let e = &dom.els[k];
263 let mut props: Vec<(u8, String)> = Vec::new();
264 for (n, v) in &e.attrs {
265 if let Some(p) = prop_id(n) {
266 props.push((p, v.clone()));
267 }
268 }
269 for r in &rules {
270 if steps == 0 {
271 break;
272 }
273 if r.sel.matches(&dom, k as u32, &mut steps) {
274 for (n, v) in &r.decls {
275 if let Some(p) = prop_id(n) {
276 props.push((p, v.clone()));
277 }
278 }
279 }
280 }
281 if let Some(st) = e.attr("style") {
282 for (n, v) in css::decls(st) {
283 if let Some(p) = prop_id(&n) {
284 props.push((p, v));
285 }
286 }
287 }
288 let mut out: Vec<(u8, String)> = Vec::with_capacity(props.len());
290 for (p, v) in props.into_iter().rev() {
291 if !out.iter().any(|(q, _)| *q == p) {
292 out.push((p, v));
293 }
294 }
295 out.reverse();
296 props_all.push(out);
297 }
298 let mut nodes = Vec::with_capacity(dom.els.len());
300 let mut ids = BTreeMap::new();
301 let mut images = Vec::new();
302 for (k, (e, props)) in dom.els.drain(..).zip(props_all).enumerate() {
303 let kind = kind_of(&e.name);
304 let tf_attr = match kind {
305 Kind::LinearGradient | Kind::RadialGradient => "gradientTransform",
306 _ => "transform",
307 };
308 let tf = e.attr(tf_attr).and_then(values::transform);
309 let pd = match kind {
310 Kind::Path => e.attr("d").map(crate::pathd::parse), Kind::Polyline | Kind::Polygon => e.attr("points").map(|p| points(p, kind == Kind::Polygon)),
312 _ => None,
313 };
314 let img = if kind == Kind::Image {
315 let href = e.attr("href").or_else(|| e.attr("xlink:href"));
316 match href.and_then(crate::data_uri_bytes) {
317 Some(data) => {
318 images.push(Img { data, sf: SpinLock::new(None) });
319 Some(images.len() as u32 - 1)
320 }
321 None => None,
322 }
323 } else {
324 None
325 };
326 if let Some(id) = e.attr("id") {
327 ids.entry(String::from(id)).or_insert(k as u32);
328 }
329 nodes.push(Node { kind, attrs: e.attrs, props, kids: e.kids, parent: e.parent, tf, pd, img });
330 }
331 let root = &nodes[0];
333 let view_box = root.attr("viewBox").and_then(values::view_box);
334 let par = root.attr("preserveAspectRatio").map(values::par).unwrap_or_default();
335 let (mut w, mut h) = (abs_len(root.attr("width")), abs_len(root.attr("height")));
336 if let Some(vb) = view_box {
337 match (w, h) {
338 (None, None) => {
339 w = Some(vb[2]);
340 h = Some(vb[3]);
341 }
342 (Some(x), None) => h = Some(x * vb[3] / vb[2]),
343 (None, Some(y)) => w = Some(y * vb[2] / vb[3]),
344 _ => {}
345 }
346 }
347 let width = w.unwrap_or(DEFAULT_W).clamp(1.0e-3, SIZE_MAX);
348 let height = h.unwrap_or(DEFAULT_H).clamp(1.0e-3, SIZE_MAX);
349 let mut title = String::new();
351 for kid in &root.kids {
352 if let Item::El(c) = kid {
353 if let Some(n) = nodes.get(*c as usize) {
354 if n.kind == Kind::Title {
355 title = collapse(&n.text());
356 break;
357 }
358 }
359 }
360 }
361 Ok(Svg { nodes, ids, images, width, height, view_box, par, title, truncated: dom.truncated, img_px: AtomicU64::new(0) })
362 }
363
364 pub fn size(&self) -> (f64, f64) {
367 (self.width, self.height)
368 }
369
370 pub fn view_box(&self) -> Option<[f64; 4]> {
372 self.view_box
373 }
374
375 pub fn title(&self) -> &str {
377 &self.title
378 }
379
380 pub fn truncated(&self) -> bool {
382 self.truncated
383 }
384
385 pub fn element_count(&self) -> usize {
387 self.nodes.len()
388 }
389
390 pub(crate) fn with_image<R>(&self, i: u32, f: impl FnOnce(&Surface) -> R) -> Option<R> {
392 let img = self.images.get(i as usize)?;
393 let mut g = img.sf.lock();
394 if g.is_none() {
395 let used = self.img_px.load(Ordering::Relaxed);
396 let sf = crate::decode_image(&img.data, crate::MAX_IMAGE_PX.saturating_sub(used));
397 if let Some(s) = &sf {
398 self.img_px.fetch_add(s.w as u64 * s.h as u64, Ordering::Relaxed);
399 }
400 *g = Some(sf);
401 }
402 match &*g {
403 Some(Some(sf)) => Some(f(sf)),
404 _ => None,
405 }
406 }
407
408 pub(crate) fn node(&self, k: u32) -> Option<&Node> {
409 self.nodes.get(k as usize)
410 }
411
412 pub(crate) fn by_id(&self, id: &str) -> Option<u32> {
414 let id = id.trim();
415 let id = id.strip_prefix('#').unwrap_or(id);
416 self.ids.get(id).copied()
417 }
418
419 pub(crate) fn href_target(&self, n: &Node) -> Option<u32> {
421 let h = n.href()?.trim();
422 if !h.starts_with('#') {
423 return None;
424 }
425 self.by_id(h)
426 }
427
428 #[allow(dead_code)]
429 pub(crate) fn parent_of(&self, k: u32) -> u32 {
430 self.node(k).map_or(NONE, |n| n.parent)
431 }
432}
433
434pub(crate) fn collapse(s: &str) -> String {
436 let mut out = String::with_capacity(s.len());
437 for w in s.split_ascii_whitespace() {
438 if !out.is_empty() {
439 out.push(' ');
440 }
441 out.push_str(w);
442 }
443 out
444}
445
446fn points(v: &str, close: bool) -> PData {
448 let s = v.as_bytes();
449 let mut pd = PData::new();
450 let mut i = 0;
451 let mut first = true;
452 loop {
453 values::sep(s, &mut i);
454 let Some(x) = values::number(s, &mut i) else { break };
455 values::sep(s, &mut i);
456 let Some(y) = values::number(s, &mut i) else { break };
457 if first {
458 pd.move_to(x, y);
459 first = false;
460 } else {
461 pd.line_to(x, y);
462 }
463 }
464 if close && !first {
465 pd.close();
466 }
467 pd.err = 0;
468 pd
469}