rsvg/
drawing_ctx.rs

1//! The main context structure which drives the drawing process.
2
3use 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
49/// Opaque font options for a DrawingCtx.
50///
51/// This is used for DrawingCtx::create_pango_context.
52pub struct FontOptions {
53    options: cairo::FontOptions,
54}
55
56/// Set path on the cairo context, or clear it.
57/// This helper object keeps track whether the path has been set already,
58/// so that it isn't recalculated every so often.
59struct 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/// Holds the size of the current viewport in the user's coordinate system.
103#[derive(Clone, Copy)]
104pub struct Viewport {
105    pub dpi: Dpi,
106
107    /// Corners of the current coordinate space.
108    pub vbox: ViewBox,
109
110    /// The viewport's coordinate system, or "user coordinate system" in SVG terms.
111    pub transform: ValidTransform,
112}
113
114impl Viewport {
115    /// FIXME: this is just used in Handle::with_height_to_user(), and in length.rs's test suite.
116    /// Find a way to do this without involving a default identity transform.
117    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    /// Creates a new viewport suitable for a certain kind of units.
126    ///
127    /// For `objectBoundingBox`, CSS lengths which are in percentages
128    /// refer to the size of the current viewport.  Librsvg implements
129    /// that by keeping the same current transformation matrix, and
130    /// setting a viewport size of (1.0, 1.0).
131    ///
132    /// For `userSpaceOnUse`, we just duplicate the current viewport,
133    /// since that kind of units means to use the current coordinate
134    /// system unchanged.
135    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    /// Returns a viewport with a new size for normalizing `Length` values.
152    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    /// Copies the viewport, but just uses a new transform.
161    ///
162    /// This is used when we are about to draw in a temporary surface: the transform for
163    /// that surface is computed independenly of the one in the current viewport.
164    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/// Values that stay constant during rendering with a DrawingCtx.
192#[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    /// Depth of nested layers while drawing.
215    ///
216    /// We use this to set a hard limit on how many nested layers there can be, to avoid
217    /// malicious SVGs that would cause unbounded stack consumption.
218    recursion_depth: u16,
219}
220
221pub enum DrawingMode {
222    LimitToStack { node: Node, root: Node },
223
224    OnlyNode(Node),
225}
226
227/// Whether an SVG document is being rendered standalone or referenced from an `<image>` element.
228///
229/// Normally, the coordinate system used when rendering a toplevel SVG is determined from the
230/// initial viewport and the `<svg>` element's `viewBox` and `preserveAspectRatio` attributes.
231/// However, when an SVG document is referenced from an `<image>` element, as in `<image href="foo.svg"/>`,
232/// its `preserveAspectRatio` needs to be ignored so that the one from the `<image>` element can
233/// be used instead.  This lets the parent document (the one with the `<image>` element) specify
234/// how it wants the child SVG to be scaled into the viewport.
235#[derive(Copy, Clone)]
236pub enum SvgNesting {
237    Standalone,
238    ReferencedFromImageElement,
239}
240
241/// The toplevel drawing routine.
242///
243/// This creates a DrawingCtx internally and starts drawing at the specified `node`.
244pub 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    // Preserve the user's transform and use it for the outermost bounding box.  All bounds/extents
261    // will be converted to this transform in the end.
262    let user_transform = Transform::from(cr.matrix());
263    let mut user_bbox = BoundingBox::new().with_transform(user_transform);
264
265    // https://www.w3.org/TR/SVG2/coords.html#InitialCoordinateSystem
266    //
267    // "For the outermost svg element, the SVG user agent must
268    // determine an initial viewport coordinate system and an
269    // initial user coordinate system such that the two
270    // coordinates systems are identical. The origin of both
271    // coordinate systems must be at the origin of the SVG
272    // viewport."
273    //
274    // "... the initial viewport coordinate system (and therefore
275    // the initial user coordinate system) must have its origin at
276    // the top/left of the viewport"
277
278    // Translate so (0, 0) is at the viewport's upper-left corner.
279    let transform = user_transform.pre_translate(viewport_rect.x0, viewport_rect.y0);
280
281    // Here we exit immediately if the transform is not valid, since we are in the
282    // toplevel drawing function.  Downstream cases would simply not render the current
283    // element and ignore the error.
284    let valid_transform = ValidTransform::try_from(transform)?;
285    cr.set_matrix(valid_transform.into());
286
287    // Per the spec, so the viewport has (0, 0) as upper-left.
288    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    /// Copies a `DrawingCtx` for temporary use on a Cairo surface.
357    ///
358    /// `DrawingCtx` maintains state using during the drawing process, and sometimes we
359    /// would like to use that same state but on a different Cairo surface and context
360    /// than the ones being used on `self`.  This function copies the `self` state into a
361    /// new `DrawingCtx`, and ties the copied one to the supplied `cr`.
362    ///
363    /// Note that if this function is called, it means that a temporary surface is being used.
364    /// That surface needs a viewport which starts with a special transform; see
365    /// [`Viewport::with_explicit_transform`] and how it is used elsewhere.
366    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    /// Returns the `RenderingOptions` being used for rendering.
387    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    /// Gets the transform that will be used on the target surface,
406    /// whether using an isolated stacking context or not.
407    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        // We need a size in whole pixels, so use ceil() to ensure the whole viewport fits
449        // into the temporary surface.
450        (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    /// Creates a new coordinate space inside a viewport and sets a clipping rectangle.
476    ///
477    /// Returns the new viewport with the new coordinate space, or `None` if the transform
478    /// inside the new viewport turned out to be invalid.  In this case, the caller can simply
479    /// not render the object in question.
480    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, &current_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            // The node being masked is empty / doesn't have a
579            // bounding box, so there's nothing to mask!
580            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(&params)
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        // Use a scope because mask_cr needs to release the
622        // reference to the surface before we access the pixels
623        {
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    /// Checks whether the rendering has been cancelled in the middle.
689    ///
690    /// If so, returns an Err.  This is used from [`DrawingCtx::with_discrete_layer`] to
691    /// exit early instead of proceeding with rendering.
692    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        // Filter functions (like "blend()", not the <filter> element) require
734        // being resolved in userSpaceonUse units, since that is the default
735        // for primitive_units.  So, get the corresponding NormalizeParams
736        // here and pass them down.
737        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; // deref to Surface
757
758        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                // Here we are clipping in user space, so the bbox doesn't matter
815                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                    // Compute our assortment of affines
826
827                    let affines = Box::new(CompositingAffines::new(
828                        *viewport.transform,
829                        *self.initial_viewport.transform,
830                        self.cr_stack.borrow().len(),
831                    ));
832
833                    // Create temporary surface and its cr
834
835                    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                        // Draw!
857
858                        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                            // FIXME: "res" was declared mutable above so that we could overwrite it
881                            // with the result of filtering, so that if filtering produces an error,
882                            // then the masking below wouldn't take place.  Test for that and fix this;
883                            // we are *not* modifying res in case of error.
884                            (filtered_surface, res, bbox)
885                        } else {
886                            (temporary_draw_ctx.cr.target(), res, bbox)
887                        }
888                    };
889
890                    // Set temporary surface as source
891
892                    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                    // Clip
897
898                    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                    // Mask
912
913                    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                        // Composite the temporary surface
942
943                        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    /// Run the drawing function with the specified opacity
1012    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    /// Start a Cairo tag for PDF links
1032    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    /// End a Cairo tag for PDF links
1040    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        // We try to convert each item in the filter_list to a FilterSpec.
1112        //
1113        // However, the spec mentions, "If the filter references a non-existent object or
1114        // the referenced object is not a filter element, then the whole filter chain is
1115        // ignored." - https://www.w3.org/TR/filter-effects/#FilterProperty
1116        //
1117        // So, run through the filter_list and collect into a Result<Vec<FilterSpec>>.
1118        // This will return an Err if any of the conversions failed.
1119        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                // Start with the surface_to_filter, and apply each filter spec in turn;
1147                // the final result is our return value.
1148                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                // just return the original surface without filtering it
1161                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        // Bail out early if the pattern has zero size, per the spec
1173        if approx_eq!(f64, pattern.width, 0.0) || approx_eq!(f64, pattern.height, 0.0) {
1174            return Ok(false);
1175        }
1176
1177        // Bail out early if this pattern has a circular reference
1178        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        // Apply the pattern transform
1207        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        // Draw to another surface
1219        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        // Set up transformations to be determined by the contents units
1227
1228        let transform = ValidTransform::try_from(caffine)?;
1229        cr_pattern.set_matrix(transform.into());
1230
1231        // Draw everything
1232
1233        {
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        // Set the final surface as a Cairo pattern into the Cairo context
1277        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    /// Computes and returns a surface corresponding to the given paint server.
1319    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            // FIXME: we are ignoring any error
1333
1334            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                        // fill and stroke operations will preserve the path.
1431                        // markers operation will clear the path.
1432                        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        // We need to set extend appropriately, so can't use cr.set_source_surface().
1486        //
1487        // If extend is left at its default value (None), then bilinear scaling uses
1488        // transparency outside of the image producing incorrect results.
1489        // For example, in svg1.1/filters-blend-01-b.svgthere's a completely
1490        // opaque 100×1 image of a gradient scaled to 100×98 which ends up
1491        // transparent almost everywhere without this fix (which it shouldn't).
1492        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 is needed due to extend being set to pad.
1502        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        // The bounding box for <image> is decided by the values of the image's x, y, w, h
1526        // and not by the final computed image bounds.
1527        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        /*
1570        self.with_discrete_layer(
1571            stacking_ctx,
1572            acquired_nodes,
1573            viewport,
1574            group.establish_viewport,
1575            clipping,
1576            &mut |an, dc, new_viewport| {
1577                for layer in &group.children {
1578                    dc.draw_layer(layer, an, clipping, &new_viewport)?;
1579                }
1580            },
1581        )
1582        */
1583    }
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            // Empty strings, or only-whitespace text, get turned into empty paths.
1595            // In that case, we really want to return "no bounds" rather than an
1596            // empty rectangle.
1597            return Ok(viewport.empty_bbox());
1598        }
1599
1600        // #851 - We can't just render all text as paths for PDF; it
1601        // needs the actual text content so text is selectable by PDF
1602        // viewers.
1603        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        // TODO: as far as I can tell this should not render elements past the last (topmost) one
1716        // with enable-background: new (because technically we shouldn't have been caching them).
1717        // Right now there are no enable-background checks whatsoever.
1718        //
1719        // Addendum: SVG 2 has deprecated the enable-background property, and replaced it with an
1720        // "isolation" property from the CSS Compositing and Blending spec.
1721        //
1722        // Deprecation:
1723        //   https://www.w3.org/TR/filter-effects-1/#AccessBackgroundImage
1724        //
1725        // BackgroundImage, BackgroundAlpha in the "in" attribute of filter primitives:
1726        //   https://www.w3.org/TR/filter-effects-1/#attr-valuedef-in-backgroundimage
1727        //
1728        // CSS Compositing and Blending, "isolation" property:
1729        //   https://www.w3.org/TR/compositing-1/#isolation
1730        let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
1731
1732        surface.draw(&mut |cr| {
1733            // TODO: apparently DrawingCtx.cr_stack is just a way to store pairs of
1734            // (surface, transform).  Can we turn it into a DrawingCtx.surface_stack
1735            // instead?  See what CSS isolation would like to call that; are the pairs just
1736            // stacking contexts instead, or the result of rendering stacking contexts?
1737            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            // FIXME: if this returns an error, we will not restore the self.cr as per below
1780            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        // <use> is an element that is used directly, unlike
1830        // <pattern>, which is used through a fill="url(#...)"
1831        // reference.  However, <use> will always reference another
1832        // element, potentially itself or an ancestor of itself (or
1833        // another <use> which references the first one, etc.).  So,
1834        // we acquire the <use> element itself so that circular
1835        // references can be caught.
1836        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        // width or height set to 0 disables rendering of the element
1880        // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
1881        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            // <symbol> and <svg> define a viewport, as described in the specification:
1908            // https://www.w3.org/TR/SVG2/struct.html#UseElement
1909            // https://gitlab.gnome.org/GNOME/librsvg/-/issues/875#note_1482705
1910
1911            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            // otherwise the referenced node is not a <symbol>; process it generically
1954
1955            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    /// Extracts the font options for the current state of the DrawingCtx.
1997    ///
1998    /// You can use the font options later with create_pango_context().
1999    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
2027/// Create a Pango context with a particular configuration.
2028pub 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    // Pango says this about pango_cairo_context_set_resolution():
2037    //
2038    //     Sets the resolution for the context. This is a scale factor between
2039    //     points specified in a #PangoFontDescription and Cairo units. The
2040    //     default value is 96, meaning that a 10 point font will be 13
2041    //     units high. (10 * 96. / 72. = 13.3).
2042    //
2043    // I.e. Pango font sizes in a PangoFontDescription are in *points*, not pixels.
2044    // However, we are normalizing everything to userspace units, which amount to
2045    // pixels.  So, we will use 72.0 here to make Pango not apply any further scaling
2046    // to the size values we give it.
2047    //
2048    // An alternative would be to divide our font sizes by (dpi_y / 72) to effectively
2049    // cancel out Pango's scaling, but it's probably better to deal with Pango-isms
2050    // right here, instead of spreading them out through our Length normalization
2051    // code.
2052    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
2107/// Converts a Pango layout to a Cairo path on the specified cr starting at (x, y).
2108/// Does not clear the current path first.
2109fn 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
2135/// Converts a Pango layout to a CairoPath starting at (x, y).
2136fn 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
2151// https://www.w3.org/TR/css-masking-1/#ClipPathElement
2152fn 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
2169// https://www.w3.org/TR/css-masking-1/#ClipPathElement
2170fn 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    // Dropping the precision of cairo's bezier subdivision, yielding 2x
2236    // _rendering_ time speedups, are these rather expensive operations
2237    // really needed here? */
2238    let backup_tolerance = cr.tolerance();
2239    cr.set_tolerance(1.0);
2240
2241    // Bounding box for fill
2242    //
2243    // Unlike the case for stroke, for fills we always compute the bounding box.
2244    // In GNOME we have SVGs for symbolic icons where each icon has a bounding
2245    // rectangle with no fill and no stroke, and inside it there are the actual
2246    // paths for the icon's shape.  We need to be able to compute the bounding
2247    // rectangle's extents, even when it has no fill nor stroke.
2248
2249    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    // Bounding box for stroke
2257    //
2258    // When presented with a line width of 0, Cairo returns a
2259    // stroke_extents rectangle of (0, 0, 0, 0).  This would cause the
2260    // bbox to include a lone point at the origin, which is wrong, as a
2261    // stroke of zero width should not be painted, per
2262    // https://www.w3.org/TR/SVG2/painting.html#StrokeWidth
2263    //
2264    // So, see if the stroke width is 0 and just not include the stroke in the
2265    // bounding box if so.
2266
2267    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    // objectBoundingBox
2287
2288    let (x0, y0, x1, y1) = cr.path_extents()?;
2289    let path_extents = Some(Rect::new(x0, y0, x1, y1));
2290
2291    // restore tolerance
2292
2293    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
2346/// escape quotes and backslashes with backslash
2347fn 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
2476/// Extents for a path in its current coordinate system.
2477///
2478/// Normally you'll want to convert this to a BoundingBox, which has knowledge about just
2479/// what that coordinate system is.
2480pub struct PathExtents {
2481    /// Extents of the "plain", unstroked path, or `None` if the path is empty.
2482    pub path_only: Option<Rect>,
2483
2484    /// Extents of just the fill, or `None` if the path is empty.
2485    pub fill: Option<Rect>,
2486
2487    /// Extents for the stroked path, or `None` if the path is empty or zero-width.
2488    pub stroke: Option<Rect>,
2489}