1use data_url::mime::Mime;
4use glib::prelude::*;
5use markup5ever::QualName;
6use std::cell::Cell;
7use std::collections::HashMap;
8use std::collections::hash_map::Entry;
9use std::fmt;
10use std::include_str;
11use std::io::Cursor;
12use std::rc::Rc;
13use std::str::FromStr;
14use std::sync::Arc;
15use std::{cell::RefCell, sync::OnceLock};
16
17use crate::accept_language::UserLanguage;
18use crate::bbox::BoundingBox;
19use crate::borrow_element_as;
20use crate::css::{self, Origin, Stylesheet};
21use crate::dpi::Dpi;
22use crate::drawing_ctx::{
23 DrawingMode, RenderingConfiguration, SvgNesting, draw_tree, with_saved_cr,
24};
25use crate::error::{
26 AcquireError, InternalRenderingError, LoadingDepthError, LoadingError, NodeIdError,
27};
28use crate::io::{self, BinaryData};
29use crate::is_element_of_type;
30use crate::limits;
31use crate::node::{CascadedValues, Node, NodeBorrow, NodeData};
32use crate::rect::Rect;
33use crate::rsvg_log;
34use crate::session::Session;
35use crate::structure::IntrinsicDimensions;
36use crate::surface_utils::shared_surface::SharedImageSurface;
37use crate::url_resolver::{AllowedUrl, UrlResolver};
38use crate::xml::{Attributes, xml_load_from_possibly_compressed_stream};
39
40#[derive(Debug, PartialEq, Clone)]
42pub enum NodeId {
43 Internal(String),
45 External(String, String),
47}
48
49impl NodeId {
50 pub fn parse(href: &str) -> Result<NodeId, NodeIdError> {
51 let (url, id) = match href.rfind('#') {
52 None => (Some(href), None),
53 Some(0) => (None, Some(&href[1..])),
54 Some(p) => (Some(&href[..p]), Some(&href[(p + 1)..])),
55 };
56
57 match (url, id) {
58 (None, Some(id)) if !id.is_empty() => Ok(NodeId::Internal(String::from(id))),
59 (Some(url), Some(id)) if !id.is_empty() => {
60 Ok(NodeId::External(String::from(url), String::from(id)))
61 }
62 _ => Err(NodeIdError::NodeIdRequired),
63 }
64 }
65}
66
67impl fmt::Display for NodeId {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 NodeId::Internal(id) => write!(f, "#{id}"),
71 NodeId::External(url, id) => write!(f, "{url}#{id}"),
72 }
73 }
74}
75
76pub struct LoadOptions {
78 pub url_resolver: UrlResolver,
80
81 pub unlimited_size: bool,
83
84 pub keep_image_data: bool,
86}
87
88impl LoadOptions {
89 pub fn new(url_resolver: UrlResolver) -> Self {
91 LoadOptions {
92 url_resolver,
93 unlimited_size: false,
94 keep_image_data: false,
95 }
96 }
97
98 pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
102 self.unlimited_size = unlimited;
103 self
104 }
105
106 pub fn keep_image_data(mut self, keep: bool) -> Self {
112 self.keep_image_data = keep;
113 self
114 }
115
116 pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
121 let mut url_resolver = self.url_resolver.clone();
122 url_resolver.base_url = Some((**base_url).clone());
123
124 LoadOptions {
125 url_resolver,
126 unlimited_size: self.unlimited_size,
127 keep_image_data: self.keep_image_data,
128 }
129 }
130}
131
132pub struct RenderingOptions {
137 pub dpi: Dpi,
138 pub cancellable: Option<gio::Cancellable>,
139 pub user_language: UserLanguage,
140 pub svg_nesting: SvgNesting,
141 pub testing: bool,
142}
143
144impl RenderingOptions {
145 fn to_rendering_configuration(&self, measuring: bool) -> RenderingConfiguration {
147 RenderingConfiguration {
148 dpi: self.dpi,
149 cancellable: self.cancellable.clone(),
150 user_language: self.user_language.clone(),
151 svg_nesting: self.svg_nesting,
152 testing: self.testing,
153 measuring,
154 }
155 }
156}
157
158pub struct Document {
160 tree: RefCell<Node>,
165
166 session: Session,
168
169 ids: HashMap<String, Node>,
171
172 resources: RefCell<Resources>,
178
179 load_options: Arc<LoadOptions>,
181
182 stylesheets: Vec<Stylesheet>,
184
185 needs_cascade: Cell<bool>,
192}
193
194impl Document {
195 pub fn load_from_stream(
200 session: Session,
201 load_options: Arc<LoadOptions>,
202 load_limiter: LoadingDepthLimiter,
203 stream: &gio::InputStream,
204 cancellable: Option<&gio::Cancellable>,
205 ) -> Result<Document, LoadingError> {
206 load_limiter.increment()?;
215
216 let res = xml_load_from_possibly_compressed_stream(
217 session.clone(),
218 DocumentBuilder::new(session, load_options.clone(), load_limiter.clone()),
219 load_options,
220 load_limiter.clone(),
221 stream,
222 cancellable,
223 );
224
225 load_limiter.decrement();
226
227 res
228 }
229
230 #[cfg(test)]
232 pub fn load_from_bytes(input: &'static [u8]) -> Document {
233 let bytes = glib::Bytes::from_static(input);
234 let stream = gio::MemoryInputStream::from_bytes(&bytes);
235
236 let session = Session::new_for_test_suite();
237
238 let document = Document::load_from_stream(
239 session.clone(),
240 Arc::new(LoadOptions::new(UrlResolver::new(None))),
241 LoadingDepthLimiter::new(),
242 &stream.upcast(),
243 None::<&gio::Cancellable>,
244 )
245 .unwrap();
246
247 document.ensure_is_cascaded();
248
249 document
250 }
251
252 pub fn root(&self) -> Node {
254 self.tree.borrow().clone()
255 }
256
257 fn lookup_node(
259 &self,
260 node_id: &NodeId,
261 cancellable: Option<&gio::Cancellable>,
262 ) -> Option<Node> {
263 match node_id {
264 NodeId::Internal(id) => self.lookup_internal_node(id),
265 NodeId::External(url, id) => self
266 .resources
267 .borrow_mut()
268 .lookup_node(&self.session, &self.load_options, url, id, cancellable)
269 .ok(),
270 }
271 }
272
273 pub fn lookup_internal_node(&self, id: &str) -> Option<Node> {
275 self.ids.get(id).map(|n| (*n).clone())
276 }
277
278 fn lookup_resource(
280 &self,
281 url: &str,
282 cancellable: Option<&gio::Cancellable>,
283 ) -> Result<Resource, LoadingError> {
284 let aurl = self
285 .load_options
286 .url_resolver
287 .resolve_href(url)
288 .map_err(|_| LoadingError::BadUrl)?;
289
290 self.resources.borrow_mut().lookup_resource(
291 &self.session,
292 &self.load_options,
293 &aurl,
294 cancellable,
295 )
296 }
297
298 pub fn cascade(&self, extra: &[Stylesheet]) {
303 self.needs_cascade.set(false);
304
305 let stylesheets = {
306 static UA_STYLESHEETS: OnceLock<Vec<Stylesheet>> = OnceLock::new();
307 UA_STYLESHEETS.get_or_init(|| {
308 vec![
309 Stylesheet::from_data(
310 include_str!("ua.css"),
311 &UrlResolver::new(None),
312 Origin::UserAgent,
313 self.resources.borrow().load_limiter.clone(),
314 Session::default(),
315 )
316 .expect("could not parse user agent stylesheet for librsvg, there's a bug!"),
317 ]
318 })
319 };
320 css::cascade(
321 &mut self.tree.borrow_mut(),
322 stylesheets,
323 &self.stylesheets,
324 extra,
325 &self.session,
326 );
327 }
328
329 pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
330 self.ensure_is_cascaded();
331
332 let root = self.root();
333 let cascaded = CascadedValues::new_from_node(&root);
334 let values = cascaded.get();
335 borrow_element_as!(self.root(), Svg).get_intrinsic_dimensions(values)
336 }
337
338 pub fn render_document(
339 &self,
340 cr: &cairo::Context,
341 viewport: &cairo::Rectangle,
342 options: &RenderingOptions,
343 ) -> Result<(), InternalRenderingError> {
344 let root = self.root();
345 self.render_layer(cr, root, viewport, options)
346 }
347
348 pub fn render_layer(
349 &self,
350 cr: &cairo::Context,
351 node: Node,
352 viewport: &cairo::Rectangle,
353 options: &RenderingOptions,
354 ) -> Result<(), InternalRenderingError> {
355 cr.status()?;
356
357 let root = self.root();
358
359 let viewport = Rect::from(*viewport);
360
361 let config = options.to_rendering_configuration(false);
362
363 with_saved_cr(cr, || {
364 self.draw_tree(
365 DrawingMode::LimitToStack { node, root },
366 cr,
367 viewport,
368 config,
369 )
370 })
371 .map(|_bbox| ())
372 .map_err(|err| *err)
373 }
374
375 fn geometry_for_layer(
376 &self,
377 node: Node,
378 viewport: Rect,
379 options: &RenderingOptions,
380 ) -> Result<(Rect, Rect), Box<InternalRenderingError>> {
381 let root = self.root();
382
383 let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
384 let cr = cairo::Context::new(&target)?;
385
386 let config = options.to_rendering_configuration(true);
387
388 let bbox = self.draw_tree(
389 DrawingMode::LimitToStack { node, root },
390 &cr,
391 viewport,
392 config,
393 )?;
394
395 let ink_rect = bbox.ink_rect.unwrap_or_default();
396 let logical_rect = bbox.rect.unwrap_or_default();
397
398 Ok((ink_rect, logical_rect))
399 }
400
401 pub fn get_geometry_for_layer(
402 &self,
403 node: Node,
404 viewport: &cairo::Rectangle,
405 options: &RenderingOptions,
406 ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
407 let viewport = Rect::from(*viewport);
408
409 let (ink_rect, logical_rect) = self
410 .geometry_for_layer(node, viewport, options)
411 .map_err(|err| *err)?;
412
413 Ok((
414 cairo::Rectangle::from(ink_rect),
415 cairo::Rectangle::from(logical_rect),
416 ))
417 }
418
419 fn get_bbox_for_element(
420 &self,
421 node: &Node,
422 options: &RenderingOptions,
423 ) -> Result<BoundingBox, InternalRenderingError> {
424 let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
425 let cr = cairo::Context::new(&target)?;
426
427 let node = node.clone();
428
429 let config = options.to_rendering_configuration(true);
430
431 self.draw_tree(DrawingMode::OnlyNode(node), &cr, unit_rectangle(), config)
432 .map_err(|err| *err)
433 }
434
435 pub fn get_geometry_for_element(
437 &self,
438 node: Node,
439 options: &RenderingOptions,
440 ) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
441 let bbox = self.get_bbox_for_element(&node, options)?;
442
443 let ink_rect = bbox.ink_rect.unwrap_or_default();
444 let logical_rect = bbox.rect.unwrap_or_default();
445
446 let ofs = (-ink_rect.x0, -ink_rect.y0);
448
449 Ok((
450 cairo::Rectangle::from(ink_rect.translate(ofs)),
451 cairo::Rectangle::from(logical_rect.translate(ofs)),
452 ))
453 }
454
455 pub fn render_element(
456 &self,
457 cr: &cairo::Context,
458 node: Node,
459 element_viewport: &cairo::Rectangle,
460 options: &RenderingOptions,
461 ) -> Result<(), InternalRenderingError> {
462 cr.status()?;
463
464 let bbox = self.get_bbox_for_element(&node, options)?;
465
466 if bbox.ink_rect.is_none() || bbox.rect.is_none() {
467 return Ok(());
469 }
470
471 let ink_r = bbox.ink_rect.unwrap_or_default();
472
473 if ink_r.is_empty() {
474 return Ok(());
475 }
476
477 with_saved_cr(cr, || {
480 let factor = (element_viewport.width() / ink_r.width())
481 .min(element_viewport.height() / ink_r.height());
482
483 cr.translate(element_viewport.x(), element_viewport.y());
484 cr.scale(factor, factor);
485 cr.translate(-ink_r.x0, -ink_r.y0);
486
487 let config = options.to_rendering_configuration(false);
488
489 self.draw_tree(DrawingMode::OnlyNode(node), cr, unit_rectangle(), config)
490 })
491 .map(|_bbox| ())
492 .map_err(|err| *err)
493 }
494
495 fn draw_tree(
498 &self,
499 drawing_mode: DrawingMode,
500 cr: &cairo::Context,
501 viewport_rect: Rect,
502 config: RenderingConfiguration,
503 ) -> Result<BoundingBox, Box<InternalRenderingError>> {
504 self.ensure_is_cascaded();
505
506 let cancellable = config.cancellable.clone();
507
508 draw_tree(
509 self.session.clone(),
510 drawing_mode,
511 cr,
512 viewport_rect,
513 config,
514 &mut AcquiredNodes::new(self, cancellable),
515 )
516 .map(|boxed_bbox| *boxed_bbox)
517 }
518
519 fn ensure_is_cascaded(&self) {
520 if self.needs_cascade.get() {
521 self.cascade(&[]);
522 }
523 }
524}
525
526#[derive(Clone)]
535pub struct LoadingDepthLimiter(Cell<usize>);
536
537impl LoadingDepthLimiter {
538 pub fn new() -> Self {
540 Self(Cell::new(0))
541 }
542
543 pub fn increment(&self) -> Result<(), LoadingDepthError> {
548 let depth = self.0.get();
549 if depth == limits::MAX_FILE_LOADING_DEPTH {
550 Err(LoadingDepthError)
551 } else {
552 self.0.set(depth + 1);
553 Ok(())
554 }
555 }
556
557 pub fn decrement(&self) {
559 let depth = self.0.get();
560 if depth == 0 {
561 panic!("too many decrements in LoadingDepthLimiter");
562 } else {
563 self.0.set(depth - 1);
564 }
565 }
566}
567
568fn unit_rectangle() -> Rect {
569 Rect::from_size(1.0, 1.0)
570}
571
572#[derive(Clone)]
574pub enum Resource {
575 Document(Rc<Document>),
576 Image(SharedImageSurface),
577}
578
579struct Resources {
584 resources: HashMap<AllowedUrl, Result<Resource, LoadingError>>,
585 load_limiter: LoadingDepthLimiter,
586}
587
588impl Resources {
589 fn new(load_limiter: LoadingDepthLimiter) -> Resources {
590 Resources {
591 resources: Default::default(),
592 load_limiter,
593 }
594 }
595
596 fn lookup_node(
603 &mut self,
604 session: &Session,
605 load_options: &LoadOptions,
606 url: &str,
607 id: &str,
608 cancellable: Option<&gio::Cancellable>,
609 ) -> Result<Node, LoadingError> {
610 self.get_extern_document(session, load_options, url, cancellable)
611 .and_then(|resource| match resource {
612 Resource::Document(doc) => doc.lookup_internal_node(id).ok_or(LoadingError::BadUrl),
613 _ => unreachable!("get_extern_document() should already have ensured the document"),
614 })
615 }
616
617 fn get_extern_document(
622 &mut self,
623 session: &Session,
624 load_options: &LoadOptions,
625 href: &str,
626 cancellable: Option<&gio::Cancellable>,
627 ) -> Result<Resource, LoadingError> {
628 let aurl = load_options
629 .url_resolver
630 .resolve_href(href)
631 .map_err(|_| LoadingError::BadUrl)?;
632
633 let resource = self.lookup_resource(session, load_options, &aurl, cancellable)?;
634
635 match resource {
636 Resource::Document(_) => Ok(resource),
637 _ => Err(LoadingError::Other(format!(
638 "{href} is not an SVG document"
639 ))),
640 }
641 }
642
643 fn lookup_resource(
645 &mut self,
646 session: &Session,
647 load_options: &LoadOptions,
648 aurl: &AllowedUrl,
649 cancellable: Option<&gio::Cancellable>,
650 ) -> Result<Resource, LoadingError> {
651 match self.resources.entry(aurl.clone()) {
652 Entry::Occupied(e) => e.get().clone(),
653
654 Entry::Vacant(e) => {
655 let resource_result = load_resource(
656 session,
657 load_options,
658 self.load_limiter.clone(),
659 aurl,
660 cancellable,
661 );
662 e.insert(resource_result.clone());
663 resource_result
664 }
665 }
666 }
667}
668
669#[derive(Clone, Copy)]
670enum ResourceType {
671 Unknown,
672 Svg,
673 Png,
674 Jpeg,
675 Gif,
676 WebP,
677 Avif,
678}
679
680fn is_mime_type(mime: &Mime, type_: &str, subtype: &str) -> bool {
682 mime.type_ == type_ && mime.subtype == subtype
683}
684
685impl ResourceType {
686 fn from(mime_type: &Option<Mime>) -> ResourceType {
687 match mime_type {
688 None => ResourceType::Unknown,
689
690 Some(x) if *x == Mime::from_str("text/plain;charset=US-ASCII").unwrap() => {
696 ResourceType::Unknown
697 }
698
699 Some(x) if is_mime_type(x, "image", "svg+xml") => ResourceType::Svg,
700 Some(x) if is_mime_type(x, "image", "png") => ResourceType::Png,
701 Some(x) if is_mime_type(x, "image", "jpeg") => ResourceType::Jpeg,
702 Some(x) if is_mime_type(x, "image", "gif") => ResourceType::Gif,
703 Some(x) if is_mime_type(x, "image", "webp") => ResourceType::WebP,
704 Some(x) if is_mime_type(x, "image", "avif") => ResourceType::Avif,
705
706 _ => ResourceType::Unknown,
707 }
708 }
709
710 fn is_known(&self) -> bool {
711 !matches!(*self, ResourceType::Unknown)
712 }
713
714 fn to_image_format(self) -> image::ImageFormat {
715 use ResourceType::*;
716
717 match self {
718 Svg => unreachable!(),
719
720 Png => image::ImageFormat::Png,
721 Jpeg => image::ImageFormat::Jpeg,
722 Gif => image::ImageFormat::Gif,
723 WebP => image::ImageFormat::WebP,
724 Avif => image::ImageFormat::Avif,
725
726 _ => unreachable!(),
727 }
728 }
729}
730
731fn load_resource(
740 session: &Session,
741 load_options: &LoadOptions,
742 load_limiter: LoadingDepthLimiter,
743 aurl: &AllowedUrl,
744 cancellable: Option<&gio::Cancellable>,
745) -> Result<Resource, LoadingError> {
746 let data = io::acquire_data(aurl, cancellable)?;
747
748 let BinaryData {
749 data: bytes,
750 mime_type,
751 } = data;
752
753 let resource_type = ResourceType::from(&mime_type);
754
755 if resource_type.is_known() {
756 use ResourceType::*;
757
758 match resource_type {
759 Png | Jpeg | Gif | WebP | Avif => {
760 let format = resource_type.to_image_format();
761 load_image_resource_from_bytes(load_options, aurl, bytes, format)
762 }
763 Svg => load_svg_resource_from_bytes(
764 session,
765 load_options,
766 load_limiter,
767 aurl,
768 bytes,
769 cancellable,
770 ),
771 _ => unreachable!(),
772 }
773 } else {
774 if let Ok(format) = image::guess_format(&bytes) {
778 load_image_resource_from_bytes(load_options, aurl, bytes, format)
779 } else {
780 load_svg_resource_from_bytes(
781 session,
782 load_options,
783 load_limiter,
784 aurl,
785 bytes,
786 cancellable,
787 )
788 }
789 }
790}
791
792fn load_svg_resource_from_bytes(
794 session: &Session,
795 load_options: &LoadOptions,
796 load_limiter: LoadingDepthLimiter,
797 aurl: &AllowedUrl,
798 input_bytes: Vec<u8>,
799 cancellable: Option<&gio::Cancellable>,
800) -> Result<Resource, LoadingError> {
801 let bytes = glib::Bytes::from_owned(input_bytes);
802 let stream = gio::MemoryInputStream::from_bytes(&bytes);
803
804 let document = Document::load_from_stream(
805 session.clone(),
806 Arc::new(load_options.copy_with_base_url(aurl)),
807 load_limiter,
808 &stream.upcast(),
809 cancellable,
810 )?;
811
812 document.ensure_is_cascaded();
813
814 Ok(Resource::Document(Rc::new(document)))
815}
816
817fn load_image_resource_from_bytes(
825 load_options: &LoadOptions,
826 aurl: &AllowedUrl,
827 bytes: Vec<u8>,
828 format: image::ImageFormat,
829) -> Result<Resource, LoadingError> {
830 if bytes.is_empty() {
831 return Err(LoadingError::Other(String::from("no image data")));
832 }
833
834 if cfg!(fuzzing) {
838 return Err(LoadingError::Other(String::from(
839 "images are not decoded while fuzzing",
840 )));
841 }
842
843 load_image_with_image_rs(aurl, bytes, format, load_options)
844}
845
846fn is_supported_image_format(format: image::ImageFormat) -> bool {
853 use image::ImageFormat::*;
854
855 match format {
856 Png => true,
857 Jpeg => true,
858 Gif => true,
859 WebP => true,
860
861 #[cfg(feature = "avif")]
862 Avif => true,
863
864 _ => false,
865 }
866}
867
868fn image_format_to_content_type(format: image::ImageFormat) -> String {
869 use image::ImageFormat::*;
870
871 let mime_type = match format {
872 Png => "image/png",
873 Jpeg => "image/jpeg",
874 Gif => "image/gif",
875 WebP => "image/webp",
876 Avif => "image/avif",
877 _ => unreachable!("we should have already filtered supported image types"),
878 };
879
880 mime_type.to_string()
881}
882
883fn load_image_with_image_rs(
884 aurl: &AllowedUrl,
885 bytes: Vec<u8>,
886 format: image::ImageFormat,
887 load_options: &LoadOptions,
888) -> Result<Resource, LoadingError> {
889 if is_supported_image_format(format) {
890 let cursor = Cursor::new(&bytes);
891
892 let mut reader = image::ImageReader::with_format(cursor, format);
893 if load_options.unlimited_size {
894 reader.no_limits();
895 }
896
897 let image = reader
898 .decode()
899 .map_err(|e| LoadingError::Other(format!("error decoding image: {e}")))?;
900
901 let bytes = if load_options.keep_image_data {
902 Some(bytes)
903 } else {
904 None
905 };
906
907 let content_type = image_format_to_content_type(format);
908
909 let surface = SharedImageSurface::from_image(&image, Some(&content_type), bytes)
910 .map_err(|e| image_loading_error_from_cairo(e, aurl))?;
911
912 Ok(Resource::Image(surface))
913 } else {
914 Err(LoadingError::Other(String::from(
915 "unsupported image format {format:?}",
916 )))
917 }
918}
919
920fn human_readable_url(aurl: &AllowedUrl) -> &str {
923 if aurl.scheme() == "data" {
924 "data URL"
926 } else {
927 aurl.as_ref()
928 }
929}
930
931fn image_loading_error_from_cairo(status: cairo::Error, aurl: &AllowedUrl) -> LoadingError {
934 let url = human_readable_url(aurl);
935
936 match status {
937 cairo::Error::NoMemory => LoadingError::OutOfMemory(format!("loading image: {url}")),
938 cairo::Error::InvalidSize => LoadingError::Other(format!("image too big: {url}")),
939 _ => LoadingError::Other(format!("cairo error: {status}")),
940 }
941}
942
943pub struct AcquiredNode {
944 stack: Option<Rc<RefCell<NodeStack>>>,
945 node: Node,
946}
947
948impl Drop for AcquiredNode {
949 fn drop(&mut self) {
950 if let Some(ref stack) = self.stack {
951 let mut stack = stack.borrow_mut();
952 let last = stack.pop().unwrap();
953 assert!(last == self.node);
954 }
955 }
956}
957
958impl AcquiredNode {
959 pub fn get(&self) -> &Node {
960 &self.node
961 }
962}
963
964pub struct AcquiredNodes<'i> {
996 document: &'i Document,
997 num_elements_acquired: usize,
998 node_stack: Rc<RefCell<NodeStack>>,
999 nodes_with_cycles: Vec<Node>,
1000 cancellable: Option<gio::Cancellable>,
1001}
1002
1003impl<'i> AcquiredNodes<'i> {
1004 pub fn new(document: &Document, cancellable: Option<gio::Cancellable>) -> AcquiredNodes<'_> {
1005 AcquiredNodes {
1006 document,
1007 num_elements_acquired: 0,
1008 node_stack: Rc::new(RefCell::new(NodeStack::new())),
1009 nodes_with_cycles: Vec::new(),
1010 cancellable,
1011 }
1012 }
1013
1014 pub fn lookup_resource(&self, url: &str) -> Result<Resource, LoadingError> {
1015 self.document
1016 .lookup_resource(url, self.cancellable.as_ref())
1017 }
1018
1019 pub fn acquire(
1027 &mut self,
1028 referencing_element_name: &str,
1029 node_id: &NodeId,
1030 ) -> Result<AcquiredNode, AcquireError> {
1031 let session = &self.document.session;
1032
1033 self.num_elements_acquired += 1;
1034
1035 if self.num_elements_acquired > limits::MAX_REFERENCED_ELEMENTS {
1038 rsvg_log!(
1039 session,
1040 "maximum number of referenced elements exceeded in {referencing_element_name}"
1041 );
1042 return Err(AcquireError::MaxReferencesExceeded);
1043 }
1044
1045 let node = self
1053 .document
1054 .lookup_node(node_id, self.cancellable.as_ref())
1055 .ok_or_else(|| {
1056 rsvg_log!(
1057 session,
1058 "reference \"{}\" does not exist, referenced from {referencing_element_name}",
1059 node_id
1060 );
1061
1062 AcquireError::LinkNotFound(node_id.clone())
1063 })?;
1064
1065 if self.nodes_with_cycles.contains(&node) {
1066 rsvg_log!(
1067 session,
1068 "circular reference from {referencing_element_name} to element {node}",
1069 );
1070 return Err(AcquireError::CircularReference(node.clone()));
1071 }
1072
1073 if node.borrow_element().is_accessed_by_reference() {
1074 self.acquire_ref(&node)
1075 } else {
1076 Ok(AcquiredNode { stack: None, node })
1077 }
1078 }
1079
1080 pub fn acquire_ref(&mut self, node: &Node) -> Result<AcquiredNode, AcquireError> {
1093 let session = &self.document.session;
1094
1095 if self.nodes_with_cycles.contains(node) {
1096 rsvg_log!(session, "circular reference for {node}");
1097 Err(AcquireError::CircularReference(node.clone()))
1098 } else if self.node_stack.borrow().contains(node) {
1099 self.nodes_with_cycles.push(node.clone());
1100 rsvg_log!(session, "circular reference for {node}");
1101 Err(AcquireError::CircularReference(node.clone()))
1102 } else {
1103 self.node_stack.borrow_mut().push(node);
1104 Ok(AcquiredNode {
1105 stack: Some(self.node_stack.clone()),
1106 node: node.clone(),
1107 })
1108 }
1109 }
1110}
1111
1112pub struct NodeStack(Vec<Node>);
1118
1119impl NodeStack {
1120 pub fn new() -> NodeStack {
1121 NodeStack(Vec::new())
1122 }
1123
1124 pub fn push(&mut self, node: &Node) {
1125 self.0.push(node.clone());
1126 }
1127
1128 pub fn pop(&mut self) -> Option<Node> {
1129 self.0.pop()
1130 }
1131
1132 pub fn contains(&self, node: &Node) -> bool {
1133 self.0.contains(node)
1134 }
1135}
1136
1137pub struct DocumentBuilder {
1149 session: Session,
1151
1152 load_options: Arc<LoadOptions>,
1154
1155 load_limiter: LoadingDepthLimiter,
1157
1158 tree: Option<Node>,
1160
1161 ids: HashMap<String, Node>,
1163
1164 stylesheets: Vec<Stylesheet>,
1166}
1167
1168impl DocumentBuilder {
1169 pub fn new(
1170 session: Session,
1171 load_options: Arc<LoadOptions>,
1172 load_limiter: LoadingDepthLimiter,
1173 ) -> DocumentBuilder {
1174 DocumentBuilder {
1175 session,
1176 load_options,
1177 load_limiter,
1178 tree: None,
1179 ids: HashMap::new(),
1180 stylesheets: Vec::new(),
1181 }
1182 }
1183
1184 pub fn append_stylesheet(&mut self, stylesheet: Stylesheet) {
1188 self.stylesheets.push(stylesheet);
1189 }
1190
1191 pub fn append_element(
1203 &mut self,
1204 name: &QualName,
1205 attrs: Attributes,
1206 parent: Option<Node>,
1207 ) -> Node {
1208 let node = Node::new(NodeData::new_element(&self.session, name, attrs));
1209
1210 if let Some(id) = node.borrow_element().get_id() {
1211 match self.ids.entry(id.to_string()) {
1212 Entry::Occupied(_) => {
1213 rsvg_log!(self.session, "ignoring duplicate id {id} for {node}");
1214 }
1215
1216 Entry::Vacant(e) => {
1217 e.insert(node.clone());
1218 }
1219 }
1220 }
1221
1222 if let Some(parent) = parent {
1223 parent.append(node.clone());
1224 } else if self.tree.is_none() {
1225 self.tree = Some(node.clone());
1226 } else {
1227 panic!("The tree root has already been set");
1228 }
1229
1230 node
1231 }
1232
1233 pub fn append_characters(&mut self, text: &str, parent: &mut Node) {
1235 if !text.is_empty() {
1236 if let Some(child) = parent.last_child().filter(|c| c.is_chars()) {
1239 child.borrow_chars().append(text);
1240 } else {
1241 parent.append(Node::new(NodeData::new_chars(text)));
1242 };
1243 }
1244 }
1245
1246 pub fn build(self) -> Result<Document, LoadingError> {
1248 let DocumentBuilder {
1249 load_options,
1250 load_limiter,
1251 session,
1252 tree,
1253 ids,
1254 stylesheets,
1255 ..
1256 } = self;
1257
1258 match tree {
1259 Some(root) if root.is_element() => {
1260 if is_element_of_type!(root, Svg) {
1261 let document = Document {
1262 tree: RefCell::new(root),
1263 session: session.clone(),
1264 ids,
1265 resources: RefCell::new(Resources::new(load_limiter)),
1266 load_options,
1267 stylesheets,
1268 needs_cascade: Cell::new(true),
1269 };
1270
1271 Ok(document)
1272 } else {
1273 Err(LoadingError::NoSvgRoot)
1274 }
1275 }
1276 _ => Err(LoadingError::NoSvgRoot),
1277 }
1278 }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283 use super::*;
1284
1285 #[test]
1286 fn parses_node_id() {
1287 assert_eq!(
1288 NodeId::parse("#foo").unwrap(),
1289 NodeId::Internal("foo".to_string())
1290 );
1291
1292 assert_eq!(
1293 NodeId::parse("uri#foo").unwrap(),
1294 NodeId::External("uri".to_string(), "foo".to_string())
1295 );
1296
1297 assert!(matches!(
1298 NodeId::parse("uri"),
1299 Err(NodeIdError::NodeIdRequired)
1300 ));
1301 }
1302
1303 #[test]
1304 fn ignores_stylesheet_with_invalid_utf8() {
1305 let handle = crate::api::Loader::new()
1306 .read_path("tests/fixtures/loading/non-utf8-stylesheet.svg")
1307 .unwrap();
1308 assert!(handle.document.stylesheets.is_empty());
1309 }
1310}