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;
211const CASCADE_BYTES: usize = 128 << 20;
214const DECL_ENTRY: usize = core::mem::size_of::<(u8, String)>();
215
216fn abs_len(v: Option<&str>) -> Option<f64> {
218 let l = values::length(v?)?;
219 let px = match l.u {
220 values::Unit::Px => l.v,
221 values::Unit::Em => l.v * 16.0,
222 values::Unit::Ex => l.v * 8.0,
223 values::Unit::Pct => return None,
224 };
225 if px > 0.0 { Some(px.min(SIZE_MAX)) } else { None }
226}
227
228impl Svg {
229 pub fn parse(data: &[u8]) -> Result<Svg, i32> {
232 let inflated;
233 let data = if data.len() >= 3 && data[0] == 0x1F && data[1] == 0x8B && data[2] == 8 {
234 inflated = crate::gunzip(data)?;
235 &inflated[..]
236 } else {
237 data
238 };
239 let dom = xml::parse(data)?;
240 Svg::from_dom(dom)
241 }
242
243 fn from_dom(mut dom: xml::Dom) -> Result<Svg, i32> {
244 if dom.els.first().map(|e| e.name.as_str()) != Some("svg") {
245 return Err(img_core::ERR_HDR);
246 }
247 let mut rules: Vec<css::Rule> = Vec::new();
249 let mut order = 0;
250 for e in &dom.els {
251 if e.name == "style" && e.attr("type").is_none_or(|t| t.trim().is_empty() || t.trim().eq_ignore_ascii_case("text/css")) {
252 let mut s = String::new();
253 for k in &e.kids {
254 if let Item::Text(t) = k {
255 s.push_str(t);
256 }
257 }
258 css::sheet(&s, &mut order, &mut rules);
259 }
260 }
261 rules.sort_by(|a, b| (a.sel.spec, a.order).cmp(&(b.sel.spec, b.order)));
262 let mut steps = CASCADE_STEPS;
264 let mut copied = 0usize;
265 let mut props_all: Vec<Vec<(u8, String)>> = Vec::with_capacity(dom.els.len());
266 for k in 0..dom.els.len() {
267 let e = &dom.els[k];
268 let mut props: Vec<(u8, String)> = Vec::new();
269 for (n, v) in &e.attrs {
270 if let Some(p) = prop_id(n) {
271 props.push((p, v.clone()));
272 }
273 }
274 for r in &rules {
275 if steps == 0 {
276 break;
277 }
278 if r.sel.matches(&dom, k as u32, &mut steps) {
279 steps = steps.saturating_sub(u32::try_from(r.decls.len()).unwrap_or(u32::MAX));
281 for (n, v) in r.decls.iter() {
282 if let Some(p) = prop_id(n) {
283 copied = copied.saturating_add(v.len() + DECL_ENTRY);
284 if copied > CASCADE_BYTES {
285 return Err(img_core::ERR_LIMIT);
286 }
287 props.push((p, v.clone()));
288 }
289 }
290 }
291 }
292 if let Some(st) = e.attr("style") {
293 for (n, v) in css::decls(st) {
294 if let Some(p) = prop_id(&n) {
295 props.push((p, v));
296 }
297 }
298 }
299 let mut out: Vec<(u8, String)> = Vec::with_capacity(props.len());
301 for (p, v) in props.into_iter().rev() {
302 if !out.iter().any(|(q, _)| *q == p) {
303 out.push((p, v));
304 }
305 }
306 out.reverse();
307 props_all.push(out);
308 }
309 let mut nodes = Vec::with_capacity(dom.els.len());
311 let mut ids = BTreeMap::new();
312 let mut images = Vec::new();
313 for (k, (e, props)) in dom.els.drain(..).zip(props_all).enumerate() {
314 let kind = kind_of(&e.name);
315 let tf_attr = match kind {
316 Kind::LinearGradient | Kind::RadialGradient => "gradientTransform",
317 _ => "transform",
318 };
319 let tf = e.attr(tf_attr).and_then(values::transform);
320 let pd = match kind {
321 Kind::Path => e.attr("d").map(crate::pathd::parse), Kind::Polyline | Kind::Polygon => e.attr("points").map(|p| points(p, kind == Kind::Polygon)),
323 _ => None,
324 };
325 let img = if kind == Kind::Image {
326 let href = e.attr("href").or_else(|| e.attr("xlink:href"));
327 match href.and_then(crate::data_uri_bytes) {
328 Some(data) => {
329 images.push(Img { data, sf: SpinLock::new(None) });
330 Some(images.len() as u32 - 1)
331 }
332 None => None,
333 }
334 } else {
335 None
336 };
337 if let Some(id) = e.attr("id") {
338 ids.entry(String::from(id)).or_insert(k as u32);
339 }
340 nodes.push(Node { kind, attrs: e.attrs, props, kids: e.kids, parent: e.parent, tf, pd, img });
341 }
342 let root = &nodes[0];
344 let view_box = root.attr("viewBox").and_then(values::view_box);
345 let par = root.attr("preserveAspectRatio").map(values::par).unwrap_or_default();
346 let (mut w, mut h) = (abs_len(root.attr("width")), abs_len(root.attr("height")));
347 if let Some(vb) = view_box {
348 match (w, h) {
349 (None, None) => {
350 w = Some(vb[2]);
351 h = Some(vb[3]);
352 }
353 (Some(x), None) => h = Some(x * vb[3] / vb[2]),
354 (None, Some(y)) => w = Some(y * vb[2] / vb[3]),
355 _ => {}
356 }
357 }
358 let width = w.unwrap_or(DEFAULT_W).clamp(1.0e-3, SIZE_MAX);
359 let height = h.unwrap_or(DEFAULT_H).clamp(1.0e-3, SIZE_MAX);
360 let mut title = String::new();
362 for kid in &root.kids {
363 if let Item::El(c) = kid {
364 if let Some(n) = nodes.get(*c as usize) {
365 if n.kind == Kind::Title {
366 title = collapse(&n.text());
367 break;
368 }
369 }
370 }
371 }
372 Ok(Svg { nodes, ids, images, width, height, view_box, par, title, truncated: dom.truncated, img_px: AtomicU64::new(0) })
373 }
374
375 pub fn size(&self) -> (f64, f64) {
378 (self.width, self.height)
379 }
380
381 pub fn view_box(&self) -> Option<[f64; 4]> {
383 self.view_box
384 }
385
386 pub fn title(&self) -> &str {
388 &self.title
389 }
390
391 pub fn truncated(&self) -> bool {
393 self.truncated
394 }
395
396 pub fn element_count(&self) -> usize {
398 self.nodes.len()
399 }
400
401 pub(crate) fn with_image<R>(&self, i: u32, f: impl FnOnce(&Surface) -> R) -> Option<R> {
403 let img = self.images.get(i as usize)?;
404 let mut g = img.sf.lock();
405 if g.is_none() {
406 let used = self.img_px.load(Ordering::Relaxed);
407 let sf = crate::decode_image(&img.data, crate::MAX_IMAGE_PX.saturating_sub(used));
408 if let Some(s) = &sf {
409 self.img_px.fetch_add(s.w as u64 * s.h as u64, Ordering::Relaxed);
410 }
411 *g = Some(sf);
412 }
413 match &*g {
414 Some(Some(sf)) => Some(f(sf)),
415 _ => None,
416 }
417 }
418
419 pub(crate) fn node(&self, k: u32) -> Option<&Node> {
420 self.nodes.get(k as usize)
421 }
422
423 pub(crate) fn by_id(&self, id: &str) -> Option<u32> {
425 let id = id.trim();
426 let id = id.strip_prefix('#').unwrap_or(id);
427 self.ids.get(id).copied()
428 }
429
430 pub(crate) fn href_target(&self, n: &Node) -> Option<u32> {
432 let h = n.href()?.trim();
433 if !h.starts_with('#') {
434 return None;
435 }
436 self.by_id(h)
437 }
438
439 #[allow(dead_code)]
440 pub(crate) fn parent_of(&self, k: u32) -> u32 {
441 self.node(k).map_or(NONE, |n| n.parent)
442 }
443}
444
445pub(crate) fn collapse(s: &str) -> String {
447 let mut out = String::with_capacity(s.len());
448 for w in s.split_ascii_whitespace() {
449 if !out.is_empty() {
450 out.push(' ');
451 }
452 out.push_str(w);
453 }
454 out
455}
456
457fn points(v: &str, close: bool) -> PData {
459 let s = v.as_bytes();
460 let mut pd = PData::new();
461 let mut i = 0;
462 let mut first = true;
463 loop {
464 values::sep(s, &mut i);
465 let Some(x) = values::number(s, &mut i) else { break };
466 values::sep(s, &mut i);
467 let Some(y) = values::number(s, &mut i) else { break };
468 if first {
469 pd.move_to(x, y);
470 first = false;
471 } else {
472 pd.line_to(x, y);
473 }
474 }
475 if close && !first {
476 pd.close();
477 }
478 pd.err = 0;
479 pd
480}