1use float_cmp::approx_eq;
4use gio::prelude::*;
5use pango::prelude::FontMapExt;
6use regex::{Captures, Regex};
7use std::cell::RefCell;
8use std::convert::TryFrom;
9use std::rc::Rc;
10use std::{borrow::Cow, sync::OnceLock};
11
12use crate::accept_language::UserLanguage;
13use crate::bbox::BoundingBox;
14use crate::cairo_path::CairoPath;
15use crate::color::{color_to_rgba, Color};
16use crate::coord_units::CoordUnits;
17use crate::document::{AcquiredNodes, NodeId, RenderingOptions};
18use crate::dpi::Dpi;
19use crate::element::{Element, ElementData};
20use crate::error::{AcquireError, ImplementationLimit, InternalRenderingError, InvalidTransform};
21use crate::filters::{self, FilterPlan, FilterSpec, InputRequirements};
22use crate::float_eq_cairo::ApproxEqCairo;
23use crate::gradient::{GradientVariant, SpreadMethod, UserSpaceGradient};
24use crate::layout::{
25 Filter, Group, Image, Layer, LayerKind, LayoutViewport, Shape, StackingContext, Stroke, Text,
26 TextSpan,
27};
28use crate::length::*;
29use crate::limits;
30use crate::marker;
31use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw};
32use crate::paint_server::{PaintSource, UserSpacePaintSource};
33use crate::pattern::UserSpacePattern;
34use crate::properties::{
35 ClipRule, ComputedValues, FillRule, ImageRendering, MaskType, MixBlendMode, Opacity,
36 PaintTarget, ShapeRendering, StrokeLinecap, StrokeLinejoin, TextRendering,
37};
38use crate::rect::{rect_to_transform, IRect, Rect};
39use crate::rsvg_log;
40use crate::session::Session;
41use crate::surface_utils::shared_surface::{
42 ExclusiveImageSurface, Interpolation, SharedImageSurface, SurfaceType,
43};
44use crate::transform::{Transform, ValidTransform};
45use crate::unit_interval::UnitInterval;
46use crate::viewbox::ViewBox;
47use crate::{borrow_element_as, is_element_of_type};
48
49pub struct FontOptions {
53 options: cairo::FontOptions,
54}
55
56struct PathHelper<'a> {
60 cr: &'a cairo::Context,
61 transform: ValidTransform,
62 cairo_path: &'a CairoPath,
63 has_path: Option<bool>,
64}
65
66impl<'a> PathHelper<'a> {
67 pub fn new(
68 cr: &'a cairo::Context,
69 transform: ValidTransform,
70 cairo_path: &'a CairoPath,
71 ) -> Self {
72 PathHelper {
73 cr,
74 transform,
75 cairo_path,
76 has_path: None,
77 }
78 }
79
80 pub fn set(&mut self) -> Result<(), InternalRenderingError> {
81 match self.has_path {
82 Some(false) | None => {
83 self.has_path = Some(true);
84 self.cr.set_matrix(self.transform.into());
85 self.cairo_path.to_cairo_context(self.cr)
86 }
87 Some(true) => Ok(()),
88 }
89 }
90
91 pub fn unset(&mut self) {
92 match self.has_path {
93 Some(true) | None => {
94 self.has_path = Some(false);
95 self.cr.new_path();
96 }
97 Some(false) => {}
98 }
99 }
100}
101
102#[derive(Clone, Copy)]
104pub struct Viewport {
105 pub dpi: Dpi,
106
107 pub vbox: ViewBox,
109
110 pub transform: ValidTransform,
112}
113
114impl Viewport {
115 pub fn new(dpi: Dpi, view_box_width: f64, view_box_height: f64) -> Viewport {
118 Viewport {
119 dpi,
120 vbox: ViewBox::from(Rect::from_size(view_box_width, view_box_height)),
121 transform: Default::default(),
122 }
123 }
124
125 pub fn with_units(&self, units: CoordUnits) -> Viewport {
136 match units {
137 CoordUnits::ObjectBoundingBox => Viewport {
138 dpi: self.dpi,
139 vbox: ViewBox::from(Rect::from_size(1.0, 1.0)),
140 transform: self.transform,
141 },
142
143 CoordUnits::UserSpaceOnUse => Viewport {
144 dpi: self.dpi,
145 vbox: self.vbox,
146 transform: self.transform,
147 },
148 }
149 }
150
151 pub fn with_view_box(&self, width: f64, height: f64) -> Viewport {
153 Viewport {
154 dpi: self.dpi,
155 vbox: ViewBox::from(Rect::from_size(width, height)),
156 transform: self.transform,
157 }
158 }
159
160 pub fn with_explicit_transform(&self, transform: ValidTransform) -> Viewport {
165 Viewport {
166 dpi: self.dpi,
167 vbox: self.vbox,
168 transform,
169 }
170 }
171
172 pub fn with_composed_transform(
173 &self,
174 transform: ValidTransform,
175 ) -> Result<Viewport, InvalidTransform> {
176 let composed_transform =
177 ValidTransform::try_from((*self.transform).pre_transform(&transform))?;
178
179 Ok(Viewport {
180 dpi: self.dpi,
181 vbox: self.vbox,
182 transform: composed_transform,
183 })
184 }
185
186 pub fn empty_bbox(&self) -> BoundingBox {
187 BoundingBox::new().with_transform(*self.transform)
188 }
189}
190
191#[derive(Clone)]
193pub struct RenderingConfiguration {
194 pub dpi: Dpi,
195 pub cancellable: Option<gio::Cancellable>,
196 pub user_language: UserLanguage,
197 pub svg_nesting: SvgNesting,
198 pub measuring: bool,
199 pub testing: bool,
200}
201
202pub struct DrawingCtx {
203 session: Session,
204
205 initial_viewport: Viewport,
206
207 cr_stack: Rc<RefCell<Vec<cairo::Context>>>,
208 cr: cairo::Context,
209
210 drawsub_stack: Vec<Node>,
211
212 config: RenderingConfiguration,
213
214 recursion_depth: u16,
219}
220
221pub enum DrawingMode {
222 LimitToStack { node: Node, root: Node },
223
224 OnlyNode(Node),
225}
226
227#[derive(Copy, Clone)]
236pub enum SvgNesting {
237 Standalone,
238 ReferencedFromImageElement,
239}
240
241pub fn draw_tree(
245 session: Session,
246 mode: DrawingMode,
247 cr: &cairo::Context,
248 viewport_rect: Rect,
249 config: RenderingConfiguration,
250 acquired_nodes: &mut AcquiredNodes<'_>,
251) -> Result<BoundingBox, InternalRenderingError> {
252 let (drawsub_stack, node) = match mode {
253 DrawingMode::LimitToStack { node, root } => (node.ancestors().collect(), root),
254
255 DrawingMode::OnlyNode(node) => (Vec::new(), node),
256 };
257
258 let cascaded = CascadedValues::new_from_node(&node);
259
260 let user_transform = Transform::from(cr.matrix());
263 let mut user_bbox = BoundingBox::new().with_transform(user_transform);
264
265 let transform = user_transform.pre_translate(viewport_rect.x0, viewport_rect.y0);
280
281 let valid_transform = ValidTransform::try_from(transform)?;
285 cr.set_matrix(valid_transform.into());
286
287 let viewport_rect = viewport_rect.translate((-viewport_rect.x0, -viewport_rect.y0));
289 let initial_viewport = Viewport {
290 dpi: config.dpi,
291 vbox: ViewBox::from(viewport_rect),
292 transform: valid_transform,
293 };
294
295 let mut draw_ctx = DrawingCtx::new(session, cr, &initial_viewport, config, drawsub_stack);
296
297 let content_bbox = draw_ctx.draw_node_from_stack(
298 &node,
299 acquired_nodes,
300 &cascaded,
301 &initial_viewport,
302 false,
303 )?;
304
305 user_bbox.insert(&content_bbox);
306
307 if draw_ctx.is_rendering_cancelled() {
308 Err(InternalRenderingError::Cancelled)
309 } else {
310 Ok(user_bbox)
311 }
312}
313
314pub fn with_saved_cr<O, F>(cr: &cairo::Context, f: F) -> Result<O, InternalRenderingError>
315where
316 F: FnOnce() -> Result<O, InternalRenderingError>,
317{
318 cr.save()?;
319 match f() {
320 Ok(o) => {
321 cr.restore()?;
322 Ok(o)
323 }
324
325 Err(e) => Err(e),
326 }
327}
328
329impl Drop for DrawingCtx {
330 fn drop(&mut self) {
331 self.cr_stack.borrow_mut().pop();
332 }
333}
334
335const CAIRO_TAG_LINK: &str = "Link";
336
337impl DrawingCtx {
338 pub fn new(
339 session: Session,
340 cr: &cairo::Context,
341 initial_viewport: &Viewport,
342 config: RenderingConfiguration,
343 drawsub_stack: Vec<Node>,
344 ) -> DrawingCtx {
345 DrawingCtx {
346 session,
347 initial_viewport: *initial_viewport,
348 cr_stack: Rc::new(RefCell::new(Vec::new())),
349 cr: cr.clone(),
350 drawsub_stack,
351 config,
352 recursion_depth: 0,
353 }
354 }
355
356 fn nested(&self, cr: cairo::Context) -> Box<DrawingCtx> {
367 let cr_stack = self.cr_stack.clone();
368
369 cr_stack.borrow_mut().push(self.cr.clone());
370
371 Box::new(DrawingCtx {
372 session: self.session.clone(),
373 initial_viewport: self.initial_viewport,
374 cr_stack,
375 cr,
376 drawsub_stack: self.drawsub_stack.clone(),
377 config: self.config.clone(),
378 recursion_depth: self.recursion_depth,
379 })
380 }
381
382 pub fn session(&self) -> &Session {
383 &self.session
384 }
385
386 pub fn rendering_options(&self, svg_nesting: SvgNesting) -> RenderingOptions {
388 RenderingOptions {
389 dpi: self.config.dpi,
390 cancellable: self.config.cancellable.clone(),
391 user_language: self.config.user_language.clone(),
392 svg_nesting,
393 testing: self.config.testing,
394 }
395 }
396
397 pub fn user_language(&self) -> &UserLanguage {
398 &self.config.user_language
399 }
400
401 pub fn toplevel_viewport(&self) -> Rect {
402 *self.initial_viewport.vbox
403 }
404
405 fn get_transform_for_stacking_ctx(
408 &self,
409 viewport: &Viewport,
410 stacking_ctx: &StackingContext,
411 clipping: bool,
412 ) -> Result<ValidTransform, InternalRenderingError> {
413 if stacking_ctx.should_isolate() && !clipping {
414 let affines = CompositingAffines::new(
415 *viewport.transform,
416 *self.initial_viewport.transform,
417 self.cr_stack.borrow().len(),
418 );
419
420 Ok(ValidTransform::try_from(affines.for_temporary_surface)?)
421 } else {
422 Ok(viewport.transform)
423 }
424 }
425
426 pub fn svg_nesting(&self) -> SvgNesting {
427 self.config.svg_nesting
428 }
429
430 pub fn is_measuring(&self) -> bool {
431 self.config.measuring
432 }
433
434 pub fn is_testing(&self) -> bool {
435 self.config.testing
436 }
437
438 fn size_for_temporary_surface(&self) -> (i32, i32) {
439 let rect = self.toplevel_viewport();
440
441 let (viewport_width, viewport_height) = (rect.width(), rect.height());
442
443 let (width, height) = self
444 .initial_viewport
445 .transform
446 .transform_distance(viewport_width, viewport_height);
447
448 (width.ceil().abs() as i32, height.ceil().abs() as i32)
451 }
452
453 pub fn create_surface_for_toplevel_viewport(
454 &self,
455 ) -> Result<cairo::ImageSurface, InternalRenderingError> {
456 let (w, h) = self.size_for_temporary_surface();
457
458 Ok(cairo::ImageSurface::create(cairo::Format::ARgb32, w, h)?)
459 }
460
461 fn create_similar_surface_for_toplevel_viewport(
462 &self,
463 surface: &cairo::Surface,
464 ) -> Result<cairo::Surface, InternalRenderingError> {
465 let (w, h) = self.size_for_temporary_surface();
466
467 Ok(cairo::Surface::create_similar(
468 surface,
469 cairo::Content::ColorAlpha,
470 w,
471 h,
472 )?)
473 }
474
475 fn push_new_viewport(
481 &self,
482 current_viewport: &Viewport,
483 layout_viewport: &LayoutViewport,
484 ) -> Option<Viewport> {
485 let LayoutViewport {
486 geometry,
487 vbox,
488 preserve_aspect_ratio,
489 overflow,
490 } = *layout_viewport;
491
492 if !overflow.overflow_allowed() || (vbox.is_some() && preserve_aspect_ratio.is_slice()) {
493 clip_to_rectangle(&self.cr, ¤t_viewport.transform, &geometry);
494 }
495
496 preserve_aspect_ratio
497 .viewport_to_viewbox_transform(vbox, &geometry)
498 .unwrap_or_else(|_e| {
499 match vbox {
500 None => unreachable!(
501 "viewport_to_viewbox_transform only returns errors when vbox != None"
502 ),
503 Some(v) => {
504 rsvg_log!(
505 self.session,
506 "ignoring viewBox ({}, {}, {}, {}) since it is not usable",
507 v.x0,
508 v.y0,
509 v.width(),
510 v.height()
511 );
512 }
513 }
514 None
515 })
516 .and_then(|t| {
517 let transform =
518 ValidTransform::try_from(current_viewport.transform.pre_transform(&t)).ok()?;
519
520 Some(Viewport {
521 dpi: self.config.dpi,
522 vbox: vbox.unwrap_or(current_viewport.vbox),
523 transform,
524 })
525 })
526 }
527
528 fn clip_to_node(
529 &mut self,
530 clip_node: &Option<Node>,
531 acquired_nodes: &mut AcquiredNodes<'_>,
532 viewport: &Viewport,
533 bbox: &BoundingBox,
534 ) -> Result<(), InternalRenderingError> {
535 if clip_node.is_none() {
536 return Ok(());
537 }
538
539 let node = clip_node.as_ref().unwrap();
540 let units = borrow_element_as!(node, ClipPath).get_units();
541
542 if let Ok(transform) = rect_to_transform(&bbox.rect, units) {
543 let cascaded = CascadedValues::new_from_node(node);
544 let values = cascaded.get();
545
546 let node_transform = values.transform().post_transform(&transform);
547 let transform_for_clip = ValidTransform::try_from(node_transform)?;
548
549 let clip_viewport = viewport.with_composed_transform(transform_for_clip)?;
550
551 for child in node.children().filter(|c| {
552 c.is_element() && element_can_be_used_inside_clip_path(&c.borrow_element())
553 }) {
554 child.draw(
555 acquired_nodes,
556 &CascadedValues::clone_with_node(&cascaded, &child),
557 &clip_viewport,
558 self,
559 true,
560 )?;
561 }
562
563 self.cr.clip();
564 }
565
566 Ok(())
567 }
568
569 fn generate_cairo_mask(
570 &mut self,
571 mask_node: &Node,
572 viewport: &Viewport,
573 transform: Transform,
574 bbox: &BoundingBox,
575 acquired_nodes: &mut AcquiredNodes<'_>,
576 ) -> Result<Option<cairo::ImageSurface>, InternalRenderingError> {
577 if bbox.rect.is_none() {
578 return Ok(None);
581 }
582
583 let _mask_acquired = match acquired_nodes.acquire_ref(mask_node) {
584 Ok(n) => n,
585
586 Err(AcquireError::CircularReference(_)) => {
587 rsvg_log!(self.session, "circular reference in element {}", mask_node);
588 return Ok(None);
589 }
590
591 _ => unreachable!(),
592 };
593
594 let mask_element = mask_node.borrow_element();
595 let mask = borrow_element_as!(mask_node, Mask);
596
597 let cascaded = CascadedValues::new_from_node(mask_node);
598 let values = cascaded.get();
599
600 let mask_units = mask.get_units();
601
602 let mask_rect = {
603 let params = NormalizeParams::new(values, &viewport.with_units(mask_units));
604 mask.get_rect(¶ms)
605 };
606
607 let transform_for_mask =
608 ValidTransform::try_from(values.transform().post_transform(&transform))?;
609
610 let bbtransform = if let Ok(t) = rect_to_transform(&bbox.rect, mask_units)
611 .map_err(|_: ()| InvalidTransform)
612 .and_then(ValidTransform::try_from)
613 {
614 t
615 } else {
616 return Ok(None);
617 };
618
619 let mask_content_surface = self.create_surface_for_toplevel_viewport()?;
620
621 {
624 let mask_cr = cairo::Context::new(&mask_content_surface)?;
625
626 let clip_rect = (*bbtransform).transform_rect(&mask_rect);
627 clip_to_rectangle(&mask_cr, &transform_for_mask, &clip_rect);
628
629 let mask_viewport = if mask.get_content_units() == CoordUnits::ObjectBoundingBox {
630 viewport
631 .with_units(mask.get_content_units())
632 .with_explicit_transform(transform_for_mask)
633 .with_composed_transform(bbtransform)?
634 } else {
635 viewport
636 .with_units(mask.get_content_units())
637 .with_explicit_transform(transform_for_mask)
638 };
639
640 let mut mask_draw_ctx = self.nested(mask_cr);
641
642 let stacking_ctx = Box::new(StackingContext::new(
643 self.session(),
644 acquired_nodes,
645 &mask_element,
646 Transform::identity(),
647 None,
648 values,
649 ));
650
651 rsvg_log!(self.session, "(mask {}", mask_element);
652
653 let res = mask_draw_ctx.with_discrete_layer(
654 &stacking_ctx,
655 acquired_nodes,
656 &mask_viewport,
657 None,
658 false,
659 &mut |an, dc, new_viewport| {
660 mask_node.draw_children(an, &cascaded, new_viewport, dc, false)
661 },
662 );
663
664 rsvg_log!(self.session, ")");
665
666 res?;
667 }
668
669 let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
670
671 let mask_result = match values.mask_type() {
672 MaskType::Luminance => tmp.to_luminance_mask()?,
673 MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
674 };
675
676 let mask = mask_result.into_image_surface()?;
677
678 Ok(Some(mask))
679 }
680
681 fn is_rendering_cancelled(&self) -> bool {
682 match &self.config.cancellable {
683 None => false,
684 Some(cancellable) => cancellable.is_cancelled(),
685 }
686 }
687
688 fn check_cancellation(&self) -> Result<(), InternalRenderingError> {
693 if self.is_rendering_cancelled() {
694 return Err(InternalRenderingError::Cancelled);
695 }
696
697 Ok(())
698 }
699
700 fn check_layer_nesting_depth(&self) -> Result<(), InternalRenderingError> {
701 if self.recursion_depth > limits::MAX_LAYER_NESTING_DEPTH {
702 return Err(InternalRenderingError::LimitExceeded(
703 ImplementationLimit::MaximumLayerNestingDepthExceeded,
704 ));
705 }
706
707 Ok(())
708 }
709
710 fn filter_current_surface(
711 &mut self,
712 acquired_nodes: &mut AcquiredNodes<'_>,
713 filter: &Filter,
714 viewport: &Viewport,
715 element_name: &str,
716 bbox: &BoundingBox,
717 ) -> Result<cairo::Surface, InternalRenderingError> {
718 let surface_to_filter = SharedImageSurface::copy_from_surface(
719 &cairo::ImageSurface::try_from(self.cr.target()).unwrap(),
720 )?;
721
722 let stroke_paint_source = Rc::new(filter.stroke_paint_source.to_user_space(
723 &bbox.rect,
724 viewport,
725 &filter.normalize_values,
726 ));
727 let fill_paint_source = Rc::new(filter.fill_paint_source.to_user_space(
728 &bbox.rect,
729 viewport,
730 &filter.normalize_values,
731 ));
732
733 let user_space_params = NormalizeParams::from_values(
738 &filter.normalize_values,
739 &viewport.with_units(CoordUnits::UserSpaceOnUse),
740 );
741
742 let filtered_surface = self
743 .run_filters(
744 viewport,
745 surface_to_filter,
746 filter,
747 acquired_nodes,
748 element_name,
749 &user_space_params,
750 stroke_paint_source,
751 fill_paint_source,
752 bbox,
753 )?
754 .into_image_surface()?;
755
756 let generic_surface: &cairo::Surface = &filtered_surface; Ok(generic_surface.clone())
759 }
760
761 fn draw_in_optional_new_viewport(
762 &mut self,
763 acquired_nodes: &mut AcquiredNodes<'_>,
764 viewport: &Viewport,
765 layout_viewport: &Option<LayoutViewport>,
766 draw_fn: &mut dyn FnMut(
767 &mut AcquiredNodes<'_>,
768 &mut DrawingCtx,
769 &Viewport,
770 ) -> Result<BoundingBox, InternalRenderingError>,
771 ) -> Result<BoundingBox, InternalRenderingError> {
772 if let Some(layout_viewport) = layout_viewport.as_ref() {
773 if let Some(new_viewport) = self.push_new_viewport(viewport, layout_viewport) {
774 draw_fn(acquired_nodes, self, &new_viewport)
775 } else {
776 Ok(viewport.empty_bbox())
777 }
778 } else {
779 draw_fn(acquired_nodes, self, viewport)
780 }
781 }
782
783 fn draw_layer_internal(
784 &mut self,
785 stacking_ctx: &StackingContext,
786 acquired_nodes: &mut AcquiredNodes<'_>,
787 viewport: &Viewport,
788 layout_viewport: Option<LayoutViewport>,
789 clipping: bool,
790 draw_fn: &mut dyn FnMut(
791 &mut AcquiredNodes<'_>,
792 &mut DrawingCtx,
793 &Viewport,
794 ) -> Result<BoundingBox, InternalRenderingError>,
795 ) -> Result<BoundingBox, InternalRenderingError> {
796 let stacking_ctx_transform = ValidTransform::try_from(stacking_ctx.transform)?;
797
798 let viewport = viewport.with_composed_transform(stacking_ctx_transform)?;
799
800 let res = if clipping {
801 self.draw_in_optional_new_viewport(acquired_nodes, &viewport, &layout_viewport, draw_fn)
802 } else {
803 with_saved_cr(&self.cr.clone(), || {
804 if let Some(ref link_target) = stacking_ctx.link_target {
805 self.link_tag_begin(link_target);
806 }
807
808 let Opacity(UnitInterval(opacity)) = stacking_ctx.opacity;
809
810 if let Some(rect) = stacking_ctx.clip_rect.as_ref() {
811 clip_to_rectangle(&self.cr, &viewport.transform, rect);
812 }
813
814 self.clip_to_node(
816 &stacking_ctx.clip_in_user_space,
817 acquired_nodes,
818 &viewport,
819 &viewport.empty_bbox(),
820 )?;
821
822 let should_isolate = stacking_ctx.should_isolate();
823
824 let res = if should_isolate {
825 let affines = Box::new(CompositingAffines::new(
828 *viewport.transform,
829 *self.initial_viewport.transform,
830 self.cr_stack.borrow().len(),
831 ));
832
833 let cr = match stacking_ctx.filter {
836 None => cairo::Context::new(
837 &self
838 .create_similar_surface_for_toplevel_viewport(&self.cr.target())?,
839 )?,
840 Some(_) => {
841 cairo::Context::new(self.create_surface_for_toplevel_viewport()?)?
842 }
843 };
844
845 let transform_for_temporary_surface =
846 ValidTransform::try_from(affines.for_temporary_surface)?;
847
848 let (source_surface, mut res, bbox) = {
849 let mut temporary_draw_ctx = self.nested(cr.clone());
850
851 let viewport_for_temporary_surface = Viewport::with_explicit_transform(
852 &viewport,
853 transform_for_temporary_surface,
854 );
855
856 let res = temporary_draw_ctx.draw_in_optional_new_viewport(
859 acquired_nodes,
860 &viewport_for_temporary_surface,
861 &layout_viewport,
862 draw_fn,
863 );
864
865 let bbox = if let Ok(ref bbox) = res {
866 *bbox
867 } else {
868 BoundingBox::new().with_transform(*transform_for_temporary_surface)
869 };
870
871 if let Some(ref filter) = stacking_ctx.filter {
872 let filtered_surface = temporary_draw_ctx.filter_current_surface(
873 acquired_nodes,
874 filter,
875 &viewport_for_temporary_surface,
876 &stacking_ctx.element_name,
877 &bbox,
878 )?;
879
880 (filtered_surface, res, bbox)
885 } else {
886 (temporary_draw_ctx.cr.target(), res, bbox)
887 }
888 };
889
890 self.cr
893 .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
894 self.cr.set_source_surface(&source_surface, 0.0, 0.0)?;
895
896 let transform_for_clip =
899 ValidTransform::try_from(affines.outside_temporary_surface)?;
900
901 let viewport_for_clip = viewport.with_explicit_transform(transform_for_clip);
902 self.cr.set_matrix(transform_for_clip.into());
903
904 self.clip_to_node(
905 &stacking_ctx.clip_in_object_space,
906 acquired_nodes,
907 &viewport_for_clip,
908 &bbox,
909 )?;
910
911 if let Some(ref mask_node) = stacking_ctx.mask {
914 res = res.and_then(|bbox| {
915 self.generate_cairo_mask(
916 mask_node,
917 &viewport,
918 affines.for_temporary_surface,
919 &bbox,
920 acquired_nodes,
921 )
922 .and_then(|mask_surf| {
923 if let Some(surf) = mask_surf {
924 self.cr.push_group();
925
926 self.cr.set_matrix(
927 ValidTransform::try_from(affines.compositing)?.into(),
928 );
929 self.cr.mask_surface(&surf, 0.0, 0.0)?;
930
931 Ok(self.cr.pop_group_to_source()?)
932 } else {
933 Ok(())
934 }
935 })
936 .map(|_: ()| bbox)
937 });
938 }
939
940 {
941 self.cr
944 .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
945 self.cr.set_operator(stacking_ctx.mix_blend_mode.into());
946
947 if opacity < 1.0 {
948 self.cr.paint_with_alpha(opacity)?;
949 } else {
950 self.cr.paint()?;
951 }
952 }
953
954 res
955 } else {
956 self.draw_in_optional_new_viewport(
957 acquired_nodes,
958 &viewport,
959 &layout_viewport,
960 draw_fn,
961 )
962 };
963
964 if stacking_ctx.link_target.is_some() {
965 self.link_tag_end();
966 }
967
968 res
969 })
970 };
971
972 res
973 }
974
975 pub fn with_discrete_layer(
976 &mut self,
977 stacking_ctx: &StackingContext,
978 acquired_nodes: &mut AcquiredNodes<'_>,
979 viewport: &Viewport,
980 layout_viewport: Option<LayoutViewport>,
981 clipping: bool,
982 draw_fn: &mut dyn FnMut(
983 &mut AcquiredNodes<'_>,
984 &mut DrawingCtx,
985 &Viewport,
986 ) -> Result<BoundingBox, InternalRenderingError>,
987 ) -> Result<BoundingBox, InternalRenderingError> {
988 self.check_cancellation()?;
989
990 self.recursion_depth += 1;
991
992 match self.check_layer_nesting_depth() {
993 Ok(()) => {
994 let res = self.draw_layer_internal(
995 stacking_ctx,
996 acquired_nodes,
997 viewport,
998 layout_viewport,
999 clipping,
1000 draw_fn,
1001 );
1002
1003 self.recursion_depth -= 1;
1004 res
1005 }
1006
1007 Err(e) => Err(e),
1008 }
1009 }
1010
1011 fn with_alpha(
1013 &mut self,
1014 opacity: UnitInterval,
1015 draw_fn: &mut dyn FnMut(&mut DrawingCtx) -> Result<BoundingBox, InternalRenderingError>,
1016 ) -> Result<BoundingBox, InternalRenderingError> {
1017 let res;
1018 let UnitInterval(o) = opacity;
1019 if o < 1.0 {
1020 self.cr.push_group();
1021 res = draw_fn(self);
1022 self.cr.pop_group_to_source()?;
1023 self.cr.paint_with_alpha(o)?;
1024 } else {
1025 res = draw_fn(self);
1026 }
1027
1028 res
1029 }
1030
1031 fn link_tag_begin(&mut self, link_target: &str) {
1033 let attributes = format!("uri='{}'", escape_link_target(link_target));
1034
1035 let cr = self.cr.clone();
1036 cr.tag_begin(CAIRO_TAG_LINK, &attributes);
1037 }
1038
1039 fn link_tag_end(&mut self) {
1041 self.cr.tag_end(CAIRO_TAG_LINK);
1042 }
1043
1044 fn make_filter_plan(
1045 &mut self,
1046 acquired_nodes: &mut AcquiredNodes<'_>,
1047 specs: &[FilterSpec],
1048 source_image_width: i32,
1049 source_image_height: i32,
1050 stroke_paint_source: Rc<UserSpacePaintSource>,
1051 fill_paint_source: Rc<UserSpacePaintSource>,
1052 viewport: &Viewport,
1053 ) -> Result<Rc<FilterPlan>, InternalRenderingError> {
1054 let requirements = InputRequirements::new_from_filter_specs(specs);
1055
1056 let background_image =
1057 if requirements.needs_background_image || requirements.needs_background_alpha {
1058 Some(self.get_snapshot(source_image_width, source_image_height)?)
1059 } else {
1060 None
1061 };
1062
1063 let stroke_paint_image = if requirements.needs_stroke_paint_image {
1064 Some(self.get_paint_source_surface(
1065 source_image_width,
1066 source_image_height,
1067 acquired_nodes,
1068 &stroke_paint_source,
1069 viewport,
1070 )?)
1071 } else {
1072 None
1073 };
1074
1075 let fill_paint_image = if requirements.needs_fill_paint_image {
1076 Some(self.get_paint_source_surface(
1077 source_image_width,
1078 source_image_height,
1079 acquired_nodes,
1080 &fill_paint_source,
1081 viewport,
1082 )?)
1083 } else {
1084 None
1085 };
1086
1087 Ok(Rc::new(FilterPlan::new(
1088 self.session(),
1089 *viewport,
1090 requirements,
1091 background_image,
1092 stroke_paint_image,
1093 fill_paint_image,
1094 )?))
1095 }
1096
1097 fn run_filters(
1098 &mut self,
1099 viewport: &Viewport,
1100 surface_to_filter: SharedImageSurface,
1101 filter: &Filter,
1102 acquired_nodes: &mut AcquiredNodes<'_>,
1103 node_name: &str,
1104 user_space_params: &NormalizeParams,
1105 stroke_paint_source: Rc<UserSpacePaintSource>,
1106 fill_paint_source: Rc<UserSpacePaintSource>,
1107 node_bbox: &BoundingBox,
1108 ) -> Result<SharedImageSurface, InternalRenderingError> {
1109 let session = self.session();
1110
1111 let filter_specs = filter
1120 .filter_list
1121 .iter()
1122 .map(|filter_value| {
1123 filter_value.to_filter_spec(
1124 acquired_nodes,
1125 user_space_params,
1126 filter.current_color,
1127 viewport,
1128 session,
1129 node_name,
1130 )
1131 })
1132 .collect::<Result<Vec<FilterSpec>, _>>();
1133
1134 match filter_specs {
1135 Ok(specs) => {
1136 let plan = self.make_filter_plan(
1137 acquired_nodes,
1138 &specs,
1139 surface_to_filter.width(),
1140 surface_to_filter.height(),
1141 stroke_paint_source,
1142 fill_paint_source,
1143 viewport,
1144 )?;
1145
1146 specs.iter().try_fold(surface_to_filter, |surface, spec| {
1149 filters::render(plan.clone(), spec, surface, acquired_nodes, self, node_bbox)
1150 })
1151 }
1152
1153 Err(e) => {
1154 rsvg_log!(
1155 self.session,
1156 "not rendering filter list on node {} because it was in error: {}",
1157 node_name,
1158 e
1159 );
1160 Ok(surface_to_filter)
1162 }
1163 }
1164 }
1165
1166 fn set_pattern(
1167 &mut self,
1168 pattern: &UserSpacePattern,
1169 acquired_nodes: &mut AcquiredNodes<'_>,
1170 viewport: &Viewport,
1171 ) -> Result<bool, InternalRenderingError> {
1172 if approx_eq!(f64, pattern.width, 0.0) || approx_eq!(f64, pattern.height, 0.0) {
1174 return Ok(false);
1175 }
1176
1177 let pattern_node_acquired = match pattern.acquire_pattern_node(acquired_nodes) {
1179 Ok(n) => n,
1180
1181 Err(AcquireError::CircularReference(ref node)) => {
1182 rsvg_log!(self.session, "circular reference in element {}", node);
1183 return Ok(false);
1184 }
1185
1186 _ => unreachable!(),
1187 };
1188
1189 let pattern_node = pattern_node_acquired.get();
1190
1191 let taffine = viewport.transform.pre_transform(&pattern.transform);
1192
1193 let mut scwscale = (taffine.xx.powi(2) + taffine.xy.powi(2)).sqrt();
1194 let mut schscale = (taffine.yx.powi(2) + taffine.yy.powi(2)).sqrt();
1195
1196 let pw: i32 = (pattern.width * scwscale) as i32;
1197 let ph: i32 = (pattern.height * schscale) as i32;
1198
1199 if pw < 1 || ph < 1 {
1200 return Ok(false);
1201 }
1202
1203 scwscale = f64::from(pw) / pattern.width;
1204 schscale = f64::from(ph) / pattern.height;
1205
1206 let (affine, caffine) = if scwscale.approx_eq_cairo(1.0) && schscale.approx_eq_cairo(1.0) {
1208 (pattern.coord_transform, pattern.content_transform)
1209 } else {
1210 (
1211 pattern
1212 .coord_transform
1213 .pre_scale(1.0 / scwscale, 1.0 / schscale),
1214 pattern.content_transform.post_scale(scwscale, schscale),
1215 )
1216 };
1217
1218 let surface = self
1220 .cr
1221 .target()
1222 .create_similar(cairo::Content::ColorAlpha, pw, ph)?;
1223
1224 let cr_pattern = cairo::Context::new(&surface)?;
1225
1226 let transform = ValidTransform::try_from(caffine)?;
1229 cr_pattern.set_matrix(transform.into());
1230
1231 {
1234 let mut pattern_draw_ctx = self.nested(cr_pattern);
1235
1236 let pattern_viewport = viewport
1237 .with_view_box(pattern.width, pattern.height)
1238 .with_explicit_transform(transform);
1239
1240 let pattern_cascaded = CascadedValues::new_from_node(pattern_node);
1241 let pattern_values = pattern_cascaded.get();
1242
1243 let elt = pattern_node.borrow_element();
1244
1245 let stacking_ctx = Box::new(StackingContext::new(
1246 self.session(),
1247 acquired_nodes,
1248 &elt,
1249 Transform::identity(),
1250 None,
1251 pattern_values,
1252 ));
1253
1254 pattern_draw_ctx
1255 .with_alpha(pattern.opacity, &mut |dc| {
1256 dc.with_discrete_layer(
1257 &stacking_ctx,
1258 acquired_nodes,
1259 &pattern_viewport,
1260 None,
1261 false,
1262 &mut |an, dc, new_viewport| {
1263 pattern_node.draw_children(
1264 an,
1265 &pattern_cascaded,
1266 new_viewport,
1267 dc,
1268 false,
1269 )
1270 },
1271 )
1272 })
1273 .map(|_| ())?;
1274 }
1275
1276 let pattern = cairo::SurfacePattern::create(&surface);
1278
1279 if let Some(m) = affine.invert() {
1280 pattern.set_matrix(ValidTransform::try_from(m)?.into());
1281 pattern.set_extend(cairo::Extend::Repeat);
1282 pattern.set_filter(cairo::Filter::Best);
1283 self.cr.set_source(&pattern)?;
1284 }
1285
1286 Ok(true)
1287 }
1288
1289 fn set_paint_source(
1290 &mut self,
1291 paint_source: &UserSpacePaintSource,
1292 acquired_nodes: &mut AcquiredNodes<'_>,
1293 viewport: &Viewport,
1294 ) -> Result<bool, InternalRenderingError> {
1295 match *paint_source {
1296 UserSpacePaintSource::Gradient(ref gradient, _c) => {
1297 set_gradient_on_cairo(&self.cr, gradient)?;
1298 Ok(true)
1299 }
1300 UserSpacePaintSource::Pattern(ref pattern, ref c) => {
1301 if self.set_pattern(pattern, acquired_nodes, viewport)? {
1302 Ok(true)
1303 } else if let Some(c) = c {
1304 set_source_color_on_cairo(&self.cr, c);
1305 Ok(true)
1306 } else {
1307 Ok(false)
1308 }
1309 }
1310 UserSpacePaintSource::SolidColor(ref c) => {
1311 set_source_color_on_cairo(&self.cr, c);
1312 Ok(true)
1313 }
1314 UserSpacePaintSource::None => Ok(false),
1315 }
1316 }
1317
1318 pub fn get_paint_source_surface(
1320 &mut self,
1321 width: i32,
1322 height: i32,
1323 acquired_nodes: &mut AcquiredNodes<'_>,
1324 paint_source: &UserSpacePaintSource,
1325 viewport: &Viewport,
1326 ) -> Result<SharedImageSurface, InternalRenderingError> {
1327 let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
1328
1329 surface.draw(&mut |cr| {
1330 let mut temporary_draw_ctx = self.nested(cr);
1331
1332 let had_paint_server =
1335 temporary_draw_ctx.set_paint_source(paint_source, acquired_nodes, viewport)?;
1336 if had_paint_server {
1337 temporary_draw_ctx.cr.paint()?;
1338 }
1339
1340 Ok(())
1341 })?;
1342
1343 Ok(surface.share()?)
1344 }
1345
1346 pub fn draw_layer(
1347 &mut self,
1348 layer: &Layer,
1349 acquired_nodes: &mut AcquiredNodes<'_>,
1350 clipping: bool,
1351 viewport: &Viewport,
1352 ) -> Result<BoundingBox, InternalRenderingError> {
1353 match &layer.kind {
1354 LayerKind::Shape(shape) => self.draw_shape(
1355 shape,
1356 &layer.stacking_ctx,
1357 acquired_nodes,
1358 clipping,
1359 viewport,
1360 ),
1361 LayerKind::Text(text) => self.draw_text(
1362 text,
1363 &layer.stacking_ctx,
1364 acquired_nodes,
1365 clipping,
1366 viewport,
1367 ),
1368 LayerKind::Image(image) => self.draw_image(
1369 image,
1370 &layer.stacking_ctx,
1371 acquired_nodes,
1372 clipping,
1373 viewport,
1374 ),
1375 LayerKind::Group(group) => self.draw_group(
1376 group,
1377 &layer.stacking_ctx,
1378 acquired_nodes,
1379 clipping,
1380 viewport,
1381 ),
1382 }
1383 }
1384
1385 fn draw_shape(
1386 &mut self,
1387 shape: &Shape,
1388 stacking_ctx: &StackingContext,
1389 acquired_nodes: &mut AcquiredNodes<'_>,
1390 clipping: bool,
1391 viewport: &Viewport,
1392 ) -> Result<BoundingBox, InternalRenderingError> {
1393 self.with_discrete_layer(
1394 stacking_ctx,
1395 acquired_nodes,
1396 viewport,
1397 None,
1398 clipping,
1399 &mut |an, dc, new_viewport| {
1400 let cr = dc.cr.clone();
1401
1402 let transform =
1403 dc.get_transform_for_stacking_ctx(new_viewport, stacking_ctx, clipping)?;
1404 let mut path_helper = PathHelper::new(&cr, transform, &shape.path.cairo_path);
1405
1406 if clipping {
1407 if stacking_ctx.is_visible {
1408 cr.set_fill_rule(cairo::FillRule::from(shape.clip_rule));
1409 path_helper.set()?;
1410 }
1411 return Ok(new_viewport.empty_bbox());
1412 }
1413
1414 cr.set_antialias(cairo::Antialias::from(shape.shape_rendering));
1415
1416 setup_cr_for_stroke(&cr, &shape.stroke);
1417
1418 cr.set_fill_rule(cairo::FillRule::from(shape.fill_rule));
1419
1420 path_helper.set()?;
1421 let bbox = compute_stroke_and_fill_box(
1422 &cr,
1423 &shape.stroke,
1424 &shape.stroke_paint,
1425 &dc.initial_viewport,
1426 )?;
1427
1428 if stacking_ctx.is_visible {
1429 for &target in &shape.paint_order.targets {
1430 match target {
1433 PaintTarget::Fill => {
1434 path_helper.set()?;
1435 let had_paint_server =
1436 dc.set_paint_source(&shape.fill_paint, an, viewport)?;
1437 if had_paint_server {
1438 cr.fill_preserve()?;
1439 }
1440 }
1441
1442 PaintTarget::Stroke => {
1443 path_helper.set()?;
1444 if shape.stroke.non_scaling {
1445 cr.set_matrix(dc.initial_viewport.transform.into());
1446 }
1447
1448 let had_paint_server =
1449 dc.set_paint_source(&shape.stroke_paint, an, viewport)?;
1450 if had_paint_server {
1451 cr.stroke_preserve()?;
1452 }
1453 }
1454
1455 PaintTarget::Markers => {
1456 path_helper.unset();
1457 marker::render_markers_for_shape(
1458 shape,
1459 new_viewport,
1460 dc,
1461 an,
1462 clipping,
1463 )?;
1464 }
1465 }
1466 }
1467 }
1468
1469 path_helper.unset();
1470 Ok(bbox)
1471 },
1472 )
1473 }
1474
1475 fn paint_surface(
1476 &mut self,
1477 surface: &SharedImageSurface,
1478 width: f64,
1479 height: f64,
1480 image_rendering: ImageRendering,
1481 viewport: &Viewport,
1482 ) -> Result<(), cairo::Error> {
1483 let cr = self.cr.clone();
1484
1485 let ptn = surface.to_cairo_pattern();
1493 ptn.set_extend(cairo::Extend::Pad);
1494
1495 let interpolation = Interpolation::from(image_rendering);
1496
1497 ptn.set_filter(cairo::Filter::from(interpolation));
1498 cr.set_matrix(viewport.transform.into());
1499 cr.set_source(&ptn)?;
1500
1501 clip_to_rectangle(&cr, &viewport.transform, &Rect::from_size(width, height));
1503
1504 cr.paint()
1505 }
1506
1507 fn draw_image(
1508 &mut self,
1509 image: &Image,
1510 stacking_ctx: &StackingContext,
1511 acquired_nodes: &mut AcquiredNodes<'_>,
1512 clipping: bool,
1513 viewport: &Viewport,
1514 ) -> Result<BoundingBox, InternalRenderingError> {
1515 let image_width = image.surface.width();
1516 let image_height = image.surface.height();
1517 if clipping || image.rect.is_empty() || image_width == 0 || image_height == 0 {
1518 return Ok(viewport.empty_bbox());
1519 }
1520
1521 let image_width = f64::from(image_width);
1522 let image_height = f64::from(image_height);
1523 let vbox = ViewBox::from(Rect::from_size(image_width, image_height));
1524
1525 let bounds = viewport.empty_bbox().with_rect(image.rect);
1528
1529 let layout_viewport = LayoutViewport {
1530 vbox: Some(vbox),
1531 geometry: image.rect,
1532 preserve_aspect_ratio: image.aspect,
1533 overflow: image.overflow,
1534 };
1535
1536 if stacking_ctx.is_visible {
1537 self.with_discrete_layer(
1538 stacking_ctx,
1539 acquired_nodes,
1540 viewport,
1541 Some(layout_viewport),
1542 clipping,
1543 &mut |_an, dc, new_viewport| {
1544 dc.paint_surface(
1545 &image.surface,
1546 image_width,
1547 image_height,
1548 image.image_rendering,
1549 new_viewport,
1550 )?;
1551
1552 Ok(bounds)
1553 },
1554 )
1555 } else {
1556 Ok(bounds)
1557 }
1558 }
1559
1560 fn draw_group(
1561 &mut self,
1562 _group: &Group,
1563 _stacking_ctx: &StackingContext,
1564 _acquired_nodes: &mut AcquiredNodes<'_>,
1565 _clipping: bool,
1566 _viewport: &Viewport,
1567 ) -> Result<BoundingBox, InternalRenderingError> {
1568 unimplemented!();
1569 }
1584
1585 fn draw_text_span(
1586 &mut self,
1587 span: &TextSpan,
1588 acquired_nodes: &mut AcquiredNodes<'_>,
1589 clipping: bool,
1590 viewport: &Viewport,
1591 ) -> Result<BoundingBox, InternalRenderingError> {
1592 let path = pango_layout_to_cairo_path(span.x, span.y, &span.layout, span.gravity)?;
1593 if path.is_empty() {
1594 return Ok(viewport.empty_bbox());
1598 }
1599
1600 let can_use_text_as_path = self.cr.target().type_() != cairo::SurfaceType::Pdf;
1604
1605 self.cr
1606 .set_antialias(cairo::Antialias::from(span.text_rendering));
1607
1608 setup_cr_for_stroke(&self.cr, &span.stroke);
1609
1610 self.cr.set_matrix(viewport.transform.into());
1611
1612 if clipping {
1613 path.to_cairo_context(&self.cr)?;
1614 return Ok(viewport.empty_bbox());
1615 }
1616
1617 path.to_cairo_context(&self.cr)?;
1618 let bbox = compute_stroke_and_fill_box(
1619 &self.cr,
1620 &span.stroke,
1621 &span.stroke_paint,
1622 &self.initial_viewport,
1623 )?;
1624 self.cr.new_path();
1625
1626 if span.is_visible {
1627 if let Some(ref link_target) = span.link_target {
1628 self.link_tag_begin(link_target);
1629 }
1630
1631 for &target in &span.paint_order.targets {
1632 match target {
1633 PaintTarget::Fill => {
1634 let had_paint_server =
1635 self.set_paint_source(&span.fill_paint, acquired_nodes, viewport)?;
1636
1637 if had_paint_server {
1638 if can_use_text_as_path {
1639 path.to_cairo_context(&self.cr)?;
1640 self.cr.fill()?;
1641 self.cr.new_path();
1642 } else {
1643 self.cr.move_to(span.x, span.y);
1644
1645 let matrix = self.cr.matrix();
1646
1647 let rotation_from_gravity = span.gravity.to_rotation();
1648 if !rotation_from_gravity.approx_eq_cairo(0.0) {
1649 self.cr.rotate(-rotation_from_gravity);
1650 }
1651
1652 pangocairo::functions::update_layout(&self.cr, &span.layout);
1653 pangocairo::functions::show_layout(&self.cr, &span.layout);
1654
1655 self.cr.set_matrix(matrix);
1656 }
1657 }
1658 }
1659
1660 PaintTarget::Stroke => {
1661 let had_paint_server =
1662 self.set_paint_source(&span.stroke_paint, acquired_nodes, viewport)?;
1663
1664 if had_paint_server {
1665 path.to_cairo_context(&self.cr)?;
1666 self.cr.stroke()?;
1667 self.cr.new_path();
1668 }
1669 }
1670
1671 PaintTarget::Markers => {}
1672 }
1673 }
1674
1675 if span.link_target.is_some() {
1676 self.link_tag_end();
1677 }
1678 }
1679
1680 Ok(bbox)
1681 }
1682
1683 fn draw_text(
1684 &mut self,
1685 text: &Text,
1686 stacking_ctx: &StackingContext,
1687 acquired_nodes: &mut AcquiredNodes<'_>,
1688 clipping: bool,
1689 viewport: &Viewport,
1690 ) -> Result<BoundingBox, InternalRenderingError> {
1691 self.with_discrete_layer(
1692 stacking_ctx,
1693 acquired_nodes,
1694 viewport,
1695 None,
1696 clipping,
1697 &mut |an, dc, new_viewport| {
1698 let mut bbox = new_viewport.empty_bbox();
1699
1700 for span in &text.spans {
1701 let span_bbox = dc.draw_text_span(span, an, clipping, new_viewport)?;
1702 bbox.insert(&span_bbox);
1703 }
1704
1705 Ok(bbox)
1706 },
1707 )
1708 }
1709
1710 pub fn get_snapshot(
1711 &self,
1712 width: i32,
1713 height: i32,
1714 ) -> Result<SharedImageSurface, InternalRenderingError> {
1715 let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
1731
1732 surface.draw(&mut |cr| {
1733 for (depth, draw) in self.cr_stack.borrow().iter().enumerate() {
1738 let affines = CompositingAffines::new(
1739 Transform::from(draw.matrix()),
1740 *self.initial_viewport.transform,
1741 depth,
1742 );
1743
1744 cr.set_matrix(ValidTransform::try_from(affines.for_snapshot)?.into());
1745 cr.set_source_surface(draw.target(), 0.0, 0.0)?;
1746 cr.paint()?;
1747 }
1748
1749 Ok(())
1750 })?;
1751
1752 Ok(surface.share()?)
1753 }
1754
1755 pub fn draw_node_to_surface(
1756 &mut self,
1757 node: &Node,
1758 acquired_nodes: &mut AcquiredNodes<'_>,
1759 cascaded: &CascadedValues<'_>,
1760 transform: ValidTransform,
1761 width: i32,
1762 height: i32,
1763 ) -> Result<SharedImageSurface, InternalRenderingError> {
1764 let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
1765
1766 let save_cr = self.cr.clone();
1767
1768 {
1769 let cr = cairo::Context::new(&surface)?;
1770 cr.set_matrix(transform.into());
1771
1772 self.cr = cr;
1773 let viewport = Viewport {
1774 dpi: self.config.dpi,
1775 transform,
1776 vbox: ViewBox::from(Rect::from_size(f64::from(width), f64::from(height))),
1777 };
1778
1779 let _ = self.draw_node_from_stack(node, acquired_nodes, cascaded, &viewport, false)?;
1781 }
1782
1783 self.cr = save_cr;
1784
1785 Ok(SharedImageSurface::wrap(surface, SurfaceType::SRgb)?)
1786 }
1787
1788 pub fn draw_node_from_stack(
1789 &mut self,
1790 node: &Node,
1791 acquired_nodes: &mut AcquiredNodes<'_>,
1792 cascaded: &CascadedValues<'_>,
1793 viewport: &Viewport,
1794 clipping: bool,
1795 ) -> Result<BoundingBox, InternalRenderingError> {
1796 let stack_top = self.drawsub_stack.pop();
1797
1798 let draw = if let Some(ref top) = stack_top {
1799 top == node
1800 } else {
1801 true
1802 };
1803
1804 let res = if draw {
1805 node.draw(acquired_nodes, cascaded, viewport, self, clipping)
1806 } else {
1807 Ok(viewport.empty_bbox())
1808 };
1809
1810 if let Some(top) = stack_top {
1811 self.drawsub_stack.push(top);
1812 }
1813
1814 res
1815 }
1816
1817 pub fn draw_from_use_node(
1818 &mut self,
1819 node: &Node,
1820 acquired_nodes: &mut AcquiredNodes<'_>,
1821 values: &ComputedValues,
1822 use_rect: Rect,
1823 link: &NodeId,
1824 clipping: bool,
1825 viewport: &Viewport,
1826 fill_paint: Rc<PaintSource>,
1827 stroke_paint: Rc<PaintSource>,
1828 ) -> Result<BoundingBox, InternalRenderingError> {
1829 let _self_acquired = match acquired_nodes.acquire_ref(node) {
1837 Ok(n) => n,
1838
1839 Err(AcquireError::CircularReference(circular)) => {
1840 rsvg_log!(self.session, "circular reference in element {}", circular);
1841 return Err(InternalRenderingError::CircularReference(circular));
1842 }
1843
1844 _ => unreachable!(),
1845 };
1846
1847 let acquired = match acquired_nodes.acquire(link) {
1848 Ok(acquired) => acquired,
1849
1850 Err(AcquireError::CircularReference(circular)) => {
1851 rsvg_log!(
1852 self.session,
1853 "circular reference from {} to element {}",
1854 node,
1855 circular
1856 );
1857 return Err(InternalRenderingError::CircularReference(circular));
1858 }
1859
1860 Err(AcquireError::MaxReferencesExceeded) => {
1861 return Err(InternalRenderingError::LimitExceeded(
1862 ImplementationLimit::TooManyReferencedElements,
1863 ));
1864 }
1865
1866 Err(AcquireError::InvalidLinkType(_)) => unreachable!(),
1867
1868 Err(AcquireError::LinkNotFound(node_id)) => {
1869 rsvg_log!(
1870 self.session,
1871 "element {} references nonexistent \"{}\"",
1872 node,
1873 node_id
1874 );
1875 return Ok(viewport.empty_bbox());
1876 }
1877 };
1878
1879 if use_rect.is_empty() {
1882 return Ok(viewport.empty_bbox());
1883 }
1884
1885 let child = acquired.get();
1886
1887 if clipping && !element_can_be_used_inside_use_inside_clip_path(&child.borrow_element()) {
1888 return Ok(viewport.empty_bbox());
1889 }
1890
1891 let use_transform = ValidTransform::try_from(values.transform())?;
1892 let use_viewport = viewport.with_composed_transform(use_transform)?;
1893
1894 let use_element = node.borrow_element();
1895
1896 let defines_a_viewport = if is_element_of_type!(child, Symbol) {
1897 let symbol = borrow_element_as!(child, Symbol);
1898 Some((symbol.get_viewbox(), symbol.get_preserve_aspect_ratio()))
1899 } else if is_element_of_type!(child, Svg) {
1900 let svg = borrow_element_as!(child, Svg);
1901 Some((svg.get_viewbox(), svg.get_preserve_aspect_ratio()))
1902 } else {
1903 None
1904 };
1905
1906 let res = if let Some((vbox, preserve_aspect_ratio)) = defines_a_viewport {
1907 let elt = child.borrow_element();
1912
1913 let child_values = elt.get_computed_values();
1914
1915 let stacking_ctx = Box::new(StackingContext::new(
1916 self.session(),
1917 acquired_nodes,
1918 &use_element,
1919 Transform::identity(),
1920 None,
1921 values,
1922 ));
1923
1924 let layout_viewport = LayoutViewport {
1925 vbox,
1926 geometry: use_rect,
1927 preserve_aspect_ratio,
1928 overflow: child_values.overflow(),
1929 };
1930
1931 self.with_discrete_layer(
1932 &stacking_ctx,
1933 acquired_nodes,
1934 &use_viewport,
1935 Some(layout_viewport),
1936 clipping,
1937 &mut |an, dc, new_viewport| {
1938 child.draw_children(
1939 an,
1940 &CascadedValues::new_from_values(
1941 child,
1942 values,
1943 Some(fill_paint.clone()),
1944 Some(stroke_paint.clone()),
1945 ),
1946 new_viewport,
1947 dc,
1948 clipping,
1949 )
1950 },
1951 )
1952 } else {
1953 let stacking_ctx = Box::new(StackingContext::new(
1956 self.session(),
1957 acquired_nodes,
1958 &use_element,
1959 Transform::new_translate(use_rect.x0, use_rect.y0),
1960 None,
1961 values,
1962 ));
1963
1964 self.with_discrete_layer(
1965 &stacking_ctx,
1966 acquired_nodes,
1967 &use_viewport,
1968 None,
1969 clipping,
1970 &mut |an, dc, new_viewport| {
1971 child.draw(
1972 an,
1973 &CascadedValues::new_from_values(
1974 child,
1975 values,
1976 Some(fill_paint.clone()),
1977 Some(stroke_paint.clone()),
1978 ),
1979 new_viewport,
1980 dc,
1981 clipping,
1982 )
1983 },
1984 )
1985 };
1986
1987 if let Ok(bbox) = res {
1988 let mut res_bbox = BoundingBox::new().with_transform(*viewport.transform);
1989 res_bbox.insert(&bbox);
1990 Ok(res_bbox)
1991 } else {
1992 res
1993 }
1994 }
1995
1996 pub fn get_font_options(&self) -> FontOptions {
2000 let mut options = cairo::FontOptions::new().unwrap();
2001 if self.config.testing {
2002 options.set_antialias(cairo::Antialias::Gray);
2003 }
2004
2005 options.set_hint_style(cairo::HintStyle::None);
2006 options.set_hint_metrics(cairo::HintMetrics::Off);
2007
2008 FontOptions { options }
2009 }
2010}
2011
2012impl From<ImageRendering> for Interpolation {
2013 fn from(r: ImageRendering) -> Interpolation {
2014 match r {
2015 ImageRendering::Pixelated
2016 | ImageRendering::CrispEdges
2017 | ImageRendering::OptimizeSpeed => Interpolation::Nearest,
2018
2019 ImageRendering::Smooth
2020 | ImageRendering::OptimizeQuality
2021 | ImageRendering::HighQuality
2022 | ImageRendering::Auto => Interpolation::Smooth,
2023 }
2024 }
2025}
2026
2027pub fn create_pango_context(font_options: &FontOptions) -> pango::Context {
2029 let font_map = pangocairo::FontMap::default();
2030 let context = font_map.create_context();
2031
2032 context.set_round_glyph_positions(false);
2033
2034 pangocairo::functions::context_set_font_options(&context, Some(&font_options.options));
2035
2036 pangocairo::functions::context_set_resolution(&context, 72.0);
2053
2054 context
2055}
2056
2057pub fn set_source_color_on_cairo(cr: &cairo::Context, color: &Color) {
2058 let rgba = color_to_rgba(color);
2059
2060 cr.set_source_rgba(
2061 f64::from(rgba.red) / 255.0,
2062 f64::from(rgba.green) / 255.0,
2063 f64::from(rgba.blue) / 255.0,
2064 f64::from(rgba.alpha),
2065 );
2066}
2067
2068fn set_gradient_on_cairo(
2069 cr: &cairo::Context,
2070 gradient: &UserSpaceGradient,
2071) -> Result<(), InternalRenderingError> {
2072 let g = match gradient.variant {
2073 GradientVariant::Linear { x1, y1, x2, y2 } => {
2074 cairo::Gradient::clone(&cairo::LinearGradient::new(x1, y1, x2, y2))
2075 }
2076
2077 GradientVariant::Radial {
2078 cx,
2079 cy,
2080 r,
2081 fx,
2082 fy,
2083 fr,
2084 } => cairo::Gradient::clone(&cairo::RadialGradient::new(fx, fy, fr, cx, cy, r)),
2085 };
2086
2087 g.set_matrix(ValidTransform::try_from(gradient.transform)?.into());
2088 g.set_extend(cairo::Extend::from(gradient.spread));
2089
2090 for stop in &gradient.stops {
2091 let UnitInterval(stop_offset) = stop.offset;
2092
2093 let rgba = color_to_rgba(&stop.color);
2094
2095 g.add_color_stop_rgba(
2096 stop_offset,
2097 f64::from(rgba.red) / 255.0,
2098 f64::from(rgba.green) / 255.0,
2099 f64::from(rgba.blue) / 255.0,
2100 f64::from(rgba.alpha),
2101 );
2102 }
2103
2104 Ok(cr.set_source(&g)?)
2105}
2106
2107fn pango_layout_to_cairo(
2110 x: f64,
2111 y: f64,
2112 layout: &pango::Layout,
2113 gravity: pango::Gravity,
2114 cr: &cairo::Context,
2115) {
2116 let rotation_from_gravity = gravity.to_rotation();
2117 let rotation = if !rotation_from_gravity.approx_eq_cairo(0.0) {
2118 Some(-rotation_from_gravity)
2119 } else {
2120 None
2121 };
2122
2123 cr.move_to(x, y);
2124
2125 let matrix = cr.matrix();
2126 if let Some(rot) = rotation {
2127 cr.rotate(rot);
2128 }
2129
2130 pangocairo::functions::update_layout(cr, layout);
2131 pangocairo::functions::layout_path(cr, layout);
2132 cr.set_matrix(matrix);
2133}
2134
2135fn pango_layout_to_cairo_path(
2137 x: f64,
2138 y: f64,
2139 layout: &pango::Layout,
2140 gravity: pango::Gravity,
2141) -> Result<CairoPath, InternalRenderingError> {
2142 let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
2143 let cr = cairo::Context::new(&surface)?;
2144
2145 pango_layout_to_cairo(x, y, layout, gravity, &cr);
2146
2147 let cairo_path = cr.copy_path()?;
2148 Ok(CairoPath::from_cairo(cairo_path))
2149}
2150
2151fn element_can_be_used_inside_clip_path(element: &Element) -> bool {
2153 use ElementData::*;
2154
2155 matches!(
2156 element.element_data,
2157 Circle(_)
2158 | Ellipse(_)
2159 | Line(_)
2160 | Path(_)
2161 | Polygon(_)
2162 | Polyline(_)
2163 | Rect(_)
2164 | Text(_)
2165 | Use(_)
2166 )
2167}
2168
2169fn element_can_be_used_inside_use_inside_clip_path(element: &Element) -> bool {
2171 use ElementData::*;
2172
2173 matches!(
2174 element.element_data,
2175 Circle(_) | Ellipse(_) | Line(_) | Path(_) | Polygon(_) | Polyline(_) | Rect(_) | Text(_)
2176 )
2177}
2178
2179#[derive(Debug)]
2180struct CompositingAffines {
2181 pub outside_temporary_surface: Transform,
2182 #[allow(unused)]
2183 pub initial: Transform,
2184 pub for_temporary_surface: Transform,
2185 pub compositing: Transform,
2186 pub for_snapshot: Transform,
2187}
2188
2189impl CompositingAffines {
2190 fn new(current: Transform, initial: Transform, cr_stack_depth: usize) -> CompositingAffines {
2191 let is_topmost_temporary_surface = cr_stack_depth == 0;
2192
2193 let initial_inverse = initial.invert().unwrap();
2194
2195 let outside_temporary_surface = if is_topmost_temporary_surface {
2196 current
2197 } else {
2198 current.post_transform(&initial_inverse)
2199 };
2200
2201 let (scale_x, scale_y) = initial.transform_distance(1.0, 1.0);
2202
2203 let for_temporary_surface = if is_topmost_temporary_surface {
2204 current
2205 .post_transform(&initial_inverse)
2206 .post_scale(scale_x, scale_y)
2207 } else {
2208 current
2209 };
2210
2211 let compositing = if is_topmost_temporary_surface {
2212 initial.pre_scale(1.0 / scale_x, 1.0 / scale_y)
2213 } else {
2214 Transform::identity()
2215 };
2216
2217 let for_snapshot = compositing.invert().unwrap();
2218
2219 CompositingAffines {
2220 outside_temporary_surface,
2221 initial,
2222 for_temporary_surface,
2223 compositing,
2224 for_snapshot,
2225 }
2226 }
2227}
2228
2229fn compute_stroke_and_fill_extents(
2230 cr: &cairo::Context,
2231 stroke: &Stroke,
2232 stroke_paint_source: &UserSpacePaintSource,
2233 initial_viewport: &Viewport,
2234) -> Result<PathExtents, InternalRenderingError> {
2235 let backup_tolerance = cr.tolerance();
2239 cr.set_tolerance(1.0);
2240
2241 let (x0, y0, x1, y1) = cr.fill_extents()?;
2250 let fill_extents = if x0 != 0.0 || y0 != 0.0 || x1 != 0.0 || y1 != 0.0 {
2251 Some(Rect::new(x0, y0, x1, y1))
2252 } else {
2253 None
2254 };
2255
2256 let stroke_extents = if !stroke.width.approx_eq_cairo(0.0)
2268 && !matches!(stroke_paint_source, UserSpacePaintSource::None)
2269 {
2270 let backup_matrix = if stroke.non_scaling {
2271 let matrix = cr.matrix();
2272 cr.set_matrix(initial_viewport.transform.into());
2273 Some(matrix)
2274 } else {
2275 None
2276 };
2277 let (x0, y0, x1, y1) = cr.stroke_extents()?;
2278 if let Some(matrix) = backup_matrix {
2279 cr.set_matrix(matrix);
2280 }
2281 Some(Rect::new(x0, y0, x1, y1))
2282 } else {
2283 None
2284 };
2285
2286 let (x0, y0, x1, y1) = cr.path_extents()?;
2289 let path_extents = Some(Rect::new(x0, y0, x1, y1));
2290
2291 cr.set_tolerance(backup_tolerance);
2294
2295 Ok(PathExtents {
2296 path_only: path_extents,
2297 fill: fill_extents,
2298 stroke: stroke_extents,
2299 })
2300}
2301
2302fn compute_stroke_and_fill_box(
2303 cr: &cairo::Context,
2304 stroke: &Stroke,
2305 stroke_paint_source: &UserSpacePaintSource,
2306 initial_viewport: &Viewport,
2307) -> Result<BoundingBox, InternalRenderingError> {
2308 let extents =
2309 compute_stroke_and_fill_extents(cr, stroke, stroke_paint_source, initial_viewport)?;
2310
2311 let ink_rect = match (extents.fill, extents.stroke) {
2312 (None, None) => None,
2313 (Some(f), None) => Some(f),
2314 (None, Some(s)) => Some(s),
2315 (Some(f), Some(s)) => Some(f.union(&s)),
2316 };
2317
2318 let mut bbox = BoundingBox::new().with_transform(Transform::from(cr.matrix()));
2319
2320 if let Some(rect) = extents.path_only {
2321 bbox = bbox.with_rect(rect);
2322 }
2323
2324 if let Some(ink_rect) = ink_rect {
2325 bbox = bbox.with_ink_rect(ink_rect);
2326 }
2327
2328 Ok(bbox)
2329}
2330
2331fn setup_cr_for_stroke(cr: &cairo::Context, stroke: &Stroke) {
2332 cr.set_line_width(stroke.width);
2333 cr.set_miter_limit(stroke.miter_limit.0);
2334 cr.set_line_cap(cairo::LineCap::from(stroke.line_cap));
2335 cr.set_line_join(cairo::LineJoin::from(stroke.line_join));
2336
2337 let total_length: f64 = stroke.dashes.iter().sum();
2338
2339 if total_length > 0.0 {
2340 cr.set_dash(&stroke.dashes, stroke.dash_offset);
2341 } else {
2342 cr.set_dash(&[], 0.0);
2343 }
2344}
2345
2346fn escape_link_target(value: &str) -> Cow<'_, str> {
2348 let regex = {
2349 static REGEX: OnceLock<Regex> = OnceLock::new();
2350 REGEX.get_or_init(|| Regex::new(r"['\\]").unwrap())
2351 };
2352
2353 regex.replace_all(value, |caps: &Captures<'_>| {
2354 match caps.get(0).unwrap().as_str() {
2355 "'" => "\\'".to_owned(),
2356 "\\" => "\\\\".to_owned(),
2357 _ => unreachable!(),
2358 }
2359 })
2360}
2361
2362fn clip_to_rectangle(cr: &cairo::Context, transform: &ValidTransform, r: &Rect) {
2363 cr.set_matrix((*transform).into());
2364
2365 cr.rectangle(r.x0, r.y0, r.width(), r.height());
2366 cr.clip();
2367}
2368
2369impl From<SpreadMethod> for cairo::Extend {
2370 fn from(s: SpreadMethod) -> cairo::Extend {
2371 match s {
2372 SpreadMethod::Pad => cairo::Extend::Pad,
2373 SpreadMethod::Reflect => cairo::Extend::Reflect,
2374 SpreadMethod::Repeat => cairo::Extend::Repeat,
2375 }
2376 }
2377}
2378
2379impl From<StrokeLinejoin> for cairo::LineJoin {
2380 fn from(j: StrokeLinejoin) -> cairo::LineJoin {
2381 match j {
2382 StrokeLinejoin::Miter => cairo::LineJoin::Miter,
2383 StrokeLinejoin::Round => cairo::LineJoin::Round,
2384 StrokeLinejoin::Bevel => cairo::LineJoin::Bevel,
2385 }
2386 }
2387}
2388
2389impl From<StrokeLinecap> for cairo::LineCap {
2390 fn from(j: StrokeLinecap) -> cairo::LineCap {
2391 match j {
2392 StrokeLinecap::Butt => cairo::LineCap::Butt,
2393 StrokeLinecap::Round => cairo::LineCap::Round,
2394 StrokeLinecap::Square => cairo::LineCap::Square,
2395 }
2396 }
2397}
2398
2399impl From<MixBlendMode> for cairo::Operator {
2400 fn from(m: MixBlendMode) -> cairo::Operator {
2401 use cairo::Operator;
2402
2403 match m {
2404 MixBlendMode::Normal => Operator::Over,
2405 MixBlendMode::Multiply => Operator::Multiply,
2406 MixBlendMode::Screen => Operator::Screen,
2407 MixBlendMode::Overlay => Operator::Overlay,
2408 MixBlendMode::Darken => Operator::Darken,
2409 MixBlendMode::Lighten => Operator::Lighten,
2410 MixBlendMode::ColorDodge => Operator::ColorDodge,
2411 MixBlendMode::ColorBurn => Operator::ColorBurn,
2412 MixBlendMode::HardLight => Operator::HardLight,
2413 MixBlendMode::SoftLight => Operator::SoftLight,
2414 MixBlendMode::Difference => Operator::Difference,
2415 MixBlendMode::Exclusion => Operator::Exclusion,
2416 MixBlendMode::Hue => Operator::HslHue,
2417 MixBlendMode::Saturation => Operator::HslSaturation,
2418 MixBlendMode::Color => Operator::HslColor,
2419 MixBlendMode::Luminosity => Operator::HslLuminosity,
2420 }
2421 }
2422}
2423
2424impl From<ClipRule> for cairo::FillRule {
2425 fn from(c: ClipRule) -> cairo::FillRule {
2426 match c {
2427 ClipRule::NonZero => cairo::FillRule::Winding,
2428 ClipRule::EvenOdd => cairo::FillRule::EvenOdd,
2429 }
2430 }
2431}
2432
2433impl From<FillRule> for cairo::FillRule {
2434 fn from(f: FillRule) -> cairo::FillRule {
2435 match f {
2436 FillRule::NonZero => cairo::FillRule::Winding,
2437 FillRule::EvenOdd => cairo::FillRule::EvenOdd,
2438 }
2439 }
2440}
2441
2442impl From<ShapeRendering> for cairo::Antialias {
2443 fn from(sr: ShapeRendering) -> cairo::Antialias {
2444 match sr {
2445 ShapeRendering::Auto | ShapeRendering::GeometricPrecision => cairo::Antialias::Default,
2446 ShapeRendering::OptimizeSpeed | ShapeRendering::CrispEdges => cairo::Antialias::None,
2447 }
2448 }
2449}
2450
2451impl From<TextRendering> for cairo::Antialias {
2452 fn from(tr: TextRendering) -> cairo::Antialias {
2453 match tr {
2454 TextRendering::Auto
2455 | TextRendering::OptimizeLegibility
2456 | TextRendering::GeometricPrecision => cairo::Antialias::Default,
2457 TextRendering::OptimizeSpeed => cairo::Antialias::None,
2458 }
2459 }
2460}
2461
2462impl From<cairo::Matrix> for Transform {
2463 #[inline]
2464 fn from(m: cairo::Matrix) -> Self {
2465 Self::new_unchecked(m.xx(), m.yx(), m.xy(), m.yy(), m.x0(), m.y0())
2466 }
2467}
2468
2469impl From<ValidTransform> for cairo::Matrix {
2470 #[inline]
2471 fn from(t: ValidTransform) -> cairo::Matrix {
2472 cairo::Matrix::new(t.xx, t.yx, t.xy, t.yy, t.x0, t.y0)
2473 }
2474}
2475
2476pub struct PathExtents {
2481 pub path_only: Option<Rect>,
2483
2484 pub fill: Option<Rect>,
2486
2487 pub stroke: Option<Rect>,
2489}