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, color_to_rgba};
16use crate::coord_units::CoordUnits;
17use crate::document::{AcquiredNodes, NodeId, RenderingOptions};
18use crate::dpi::Dpi;
19use crate::element::{DrawResult, 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    ClipPath, Filter, Group, Image, Layer, LayerKind, LayoutViewport, Shape, StackingContext,
26    Stroke, Text, TextSpan, element_can_be_used_inside_use_inside_clip_path,
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::{IRect, Rect, rect_to_transform};
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.
52#[derive(Clone)]
53pub struct FontOptions {
54    options: cairo::FontOptions,
55}
56
57impl FontOptions {
58    pub fn new(testing: bool) -> FontOptions {
59        let mut options = cairo::FontOptions::new().unwrap();
60        if testing {
61            options.set_antialias(cairo::Antialias::Gray);
62        }
63
64        options.set_hint_style(cairo::HintStyle::None);
65        options.set_hint_metrics(cairo::HintMetrics::Off);
66
67        FontOptions { options }
68    }
69}
70
71/// Set path on the cairo context, or clear it.
72/// This helper object keeps track whether the path has been set already,
73/// so that it isn't recalculated every so often.
74struct PathHelper<'a> {
75    cr: &'a cairo::Context,
76    transform: ValidTransform,
77    cairo_path: &'a CairoPath,
78    has_path: Option<bool>,
79}
80
81impl<'a> PathHelper<'a> {
82    pub fn new(
83        cr: &'a cairo::Context,
84        transform: ValidTransform,
85        cairo_path: &'a CairoPath,
86    ) -> Self {
87        PathHelper {
88            cr,
89            transform,
90            cairo_path,
91            has_path: None,
92        }
93    }
94
95    pub fn set(&mut self) -> Result<(), Box<InternalRenderingError>> {
96        match self.has_path {
97            Some(false) | None => {
98                self.has_path = Some(true);
99                self.cr.set_matrix(self.transform.into());
100                self.cairo_path.to_cairo_context(self.cr)
101            }
102            Some(true) => Ok(()),
103        }
104    }
105
106    pub fn unset(&mut self) {
107        match self.has_path {
108            Some(true) | None => {
109                self.has_path = Some(false);
110                self.cr.new_path();
111            }
112            Some(false) => {}
113        }
114    }
115}
116
117/// Holds the size of the current viewport in the user's coordinate system.
118#[derive(Clone, Copy)]
119pub struct Viewport {
120    pub dpi: Dpi,
121
122    /// Corners of the current coordinate space.
123    pub vbox: ViewBox,
124
125    /// The viewport's coordinate system, or "user coordinate system" in SVG terms.
126    pub transform: ValidTransform,
127}
128
129impl Viewport {
130    /// FIXME: this is just used in Handle::with_height_to_user(), and in length.rs's test suite.
131    /// Find a way to do this without involving a default identity transform.
132    pub fn new(dpi: Dpi, view_box_width: f64, view_box_height: f64) -> Viewport {
133        Viewport {
134            dpi,
135            vbox: ViewBox::from(Rect::from_size(view_box_width, view_box_height)),
136            transform: Default::default(),
137        }
138    }
139
140    /// Creates a new viewport suitable for a certain kind of units.
141    ///
142    /// For `objectBoundingBox`, CSS lengths which are in percentages
143    /// refer to the size of the current viewport.  Librsvg implements
144    /// that by keeping the same current transformation matrix, and
145    /// setting a viewport size of (1.0, 1.0).
146    ///
147    /// For `userSpaceOnUse`, we just duplicate the current viewport,
148    /// since that kind of units means to use the current coordinate
149    /// system unchanged.
150    pub fn with_units(&self, units: CoordUnits) -> Viewport {
151        match units {
152            CoordUnits::ObjectBoundingBox => Viewport {
153                dpi: self.dpi,
154                vbox: ViewBox::from(Rect::from_size(1.0, 1.0)),
155                transform: self.transform,
156            },
157
158            CoordUnits::UserSpaceOnUse => Viewport {
159                dpi: self.dpi,
160                vbox: self.vbox,
161                transform: self.transform,
162            },
163        }
164    }
165
166    /// Returns a viewport with a new size for normalizing `Length` values.
167    pub fn with_view_box(&self, width: f64, height: f64) -> Viewport {
168        Viewport {
169            dpi: self.dpi,
170            vbox: ViewBox::from(Rect::from_size(width, height)),
171            transform: self.transform,
172        }
173    }
174
175    /// Copies the viewport, but just uses a new transform.
176    ///
177    /// This is used when we are about to draw in a temporary surface: the transform for
178    /// that surface is computed independenly of the one in the current viewport.
179    pub fn with_explicit_transform(&self, transform: ValidTransform) -> Viewport {
180        Viewport {
181            dpi: self.dpi,
182            vbox: self.vbox,
183            transform,
184        }
185    }
186
187    pub fn with_composed_transform(
188        &self,
189        transform: ValidTransform,
190    ) -> Result<Viewport, InvalidTransform> {
191        let composed_transform =
192            ValidTransform::try_from((*self.transform).pre_transform(&transform))?;
193
194        Ok(Viewport {
195            dpi: self.dpi,
196            vbox: self.vbox,
197            transform: composed_transform,
198        })
199    }
200
201    pub fn empty_bbox(&self) -> Box<BoundingBox> {
202        Box::new(BoundingBox::new().with_transform(*self.transform))
203    }
204}
205
206/// Values that stay constant during rendering with a DrawingCtx.
207#[derive(Clone)]
208pub struct RenderingConfiguration {
209    pub dpi: Dpi,
210    pub cancellable: Option<gio::Cancellable>,
211    pub user_language: UserLanguage,
212    pub svg_nesting: SvgNesting,
213    pub measuring: bool,
214    pub testing: bool,
215}
216
217pub struct DrawingCtx {
218    session: Session,
219
220    initial_viewport: Viewport,
221
222    cr_stack: Rc<RefCell<Vec<cairo::Context>>>,
223    cr: cairo::Context,
224
225    drawsub_stack: Vec<Node>,
226
227    config: RenderingConfiguration,
228
229    /// Depth of nested layers while drawing.
230    ///
231    /// We use this to set a hard limit on how many nested layers there can be, to avoid
232    /// malicious SVGs that would cause unbounded stack consumption.
233    recursion_depth: u16,
234
235    /// Cheap hack to monitor stack usage in recursive calls.
236    ///
237    /// We store the address of a local variable when first creating the DrawingCtx, and
238    /// then subtract it from another local variable at trace points.  See the print_stack_depth()
239    /// function.
240    stack_ptr: *const u8,
241}
242
243pub enum DrawingMode {
244    LimitToStack { node: Node, root: Node },
245
246    OnlyNode(Node),
247}
248
249/// Whether an SVG document is being rendered standalone or referenced from an `<image>` element.
250///
251/// Normally, the coordinate system used when rendering a toplevel SVG is determined from the
252/// initial viewport and the `<svg>` element's `viewBox` and `preserveAspectRatio` attributes.
253/// However, when an SVG document is referenced from an `<image>` element, as in `<image href="foo.svg"/>`,
254/// its `preserveAspectRatio` needs to be ignored so that the one from the `<image>` element can
255/// be used instead.  This lets the parent document (the one with the `<image>` element) specify
256/// how it wants the child SVG to be scaled into the viewport.
257#[derive(Copy, Clone)]
258pub enum SvgNesting {
259    Standalone,
260    ReferencedFromImageElement,
261}
262
263/// The toplevel drawing routine.
264///
265/// This creates a DrawingCtx internally and starts drawing at the specified `node`.
266pub fn draw_tree(
267    session: Session,
268    mode: DrawingMode,
269    cr: &cairo::Context,
270    viewport_rect: Rect,
271    config: RenderingConfiguration,
272    acquired_nodes: &mut AcquiredNodes<'_>,
273) -> DrawResult {
274    let (drawsub_stack, node) = match mode {
275        DrawingMode::LimitToStack { node, root } => (node.ancestors().collect(), root),
276
277        DrawingMode::OnlyNode(node) => (Vec::new(), node),
278    };
279
280    let cascaded = CascadedValues::new_from_node(&node);
281
282    // Preserve the user's transform and use it for the outermost bounding box.  All bounds/extents
283    // will be converted to this transform in the end.
284    let user_transform = Transform::from(cr.matrix());
285    let mut user_bbox = Box::new(BoundingBox::new().with_transform(user_transform));
286
287    // https://www.w3.org/TR/SVG2/coords.html#InitialCoordinateSystem
288    //
289    // "For the outermost svg element, the SVG user agent must
290    // determine an initial viewport coordinate system and an
291    // initial user coordinate system such that the two
292    // coordinates systems are identical. The origin of both
293    // coordinate systems must be at the origin of the SVG
294    // viewport."
295    //
296    // "... the initial viewport coordinate system (and therefore
297    // the initial user coordinate system) must have its origin at
298    // the top/left of the viewport"
299
300    // Translate so (0, 0) is at the viewport's upper-left corner.
301    let transform = user_transform.pre_translate(viewport_rect.x0, viewport_rect.y0);
302
303    // Here we exit immediately if the transform is not valid, since we are in the
304    // toplevel drawing function.  Downstream cases would simply not render the current
305    // element and ignore the error.
306    let valid_transform = ValidTransform::try_from(transform)?;
307    cr.set_matrix(valid_transform.into());
308
309    // Per the spec, so the viewport has (0, 0) as upper-left.
310    let viewport_rect = viewport_rect.translate((-viewport_rect.x0, -viewport_rect.y0));
311    let initial_viewport = Viewport {
312        dpi: config.dpi,
313        vbox: ViewBox::from(viewport_rect),
314        transform: valid_transform,
315    };
316
317    let mut draw_ctx = DrawingCtx::new(session, cr, &initial_viewport, config, drawsub_stack);
318
319    let content_bbox =
320        draw_ctx.draw_node_from_stack(&node, acquired_nodes, &cascaded, &initial_viewport)?;
321
322    user_bbox.insert(&content_bbox);
323
324    if draw_ctx.is_rendering_cancelled() {
325        Err(InternalRenderingError::Cancelled)?
326    } else {
327        Ok(user_bbox)
328    }
329}
330
331pub fn with_saved_cr<O, F>(cr: &cairo::Context, f: F) -> Result<O, Box<InternalRenderingError>>
332where
333    F: FnOnce() -> Result<O, Box<InternalRenderingError>>,
334{
335    cr.save()?;
336    match f() {
337        Ok(o) => {
338            cr.restore()?;
339            Ok(o)
340        }
341
342        Err(e) => Err(e),
343    }
344}
345
346impl Drop for DrawingCtx {
347    fn drop(&mut self) {
348        self.cr_stack.borrow_mut().pop();
349    }
350}
351
352const CAIRO_TAG_LINK: &str = "Link";
353
354impl DrawingCtx {
355    pub fn new(
356        session: Session,
357        cr: &cairo::Context,
358        initial_viewport: &Viewport,
359        config: RenderingConfiguration,
360        drawsub_stack: Vec<Node>,
361    ) -> DrawingCtx {
362        let stack_variable: u8 = 42;
363
364        DrawingCtx {
365            session,
366            initial_viewport: *initial_viewport,
367            cr_stack: Rc::new(RefCell::new(Vec::new())),
368            cr: cr.clone(),
369            drawsub_stack,
370            config,
371            recursion_depth: 0,
372
373            // We store this pointer to pinpoint the stack depth at this point in the program.
374            // Later, in the print_stack_depth() function, we'll use it to subtract from the
375            // current stack pointer.  This is a cheap hack to monitor how much stack space
376            // is consumed between recursive calls to the drawing machinery.
377            //
378            // The pointer is otherwise meaningless and should never be dereferenced.
379            stack_ptr: &stack_variable,
380        }
381    }
382
383    /// Copies a `DrawingCtx` for temporary use on a Cairo surface.
384    ///
385    /// `DrawingCtx` maintains state using during the drawing process, and sometimes we
386    /// would like to use that same state but on a different Cairo surface and context
387    /// than the ones being used on `self`.  This function copies the `self` state into a
388    /// new `DrawingCtx`, and ties the copied one to the supplied `cr`.
389    ///
390    /// Note that if this function is called, it means that a temporary surface is being used.
391    /// That surface needs a viewport which starts with a special transform; see
392    /// [`Viewport::with_explicit_transform`] and how it is used elsewhere.
393    fn nested(&self, cr: cairo::Context) -> Box<DrawingCtx> {
394        let cr_stack = self.cr_stack.clone();
395
396        cr_stack.borrow_mut().push(self.cr.clone());
397
398        Box::new(DrawingCtx {
399            session: self.session.clone(),
400            initial_viewport: self.initial_viewport,
401            cr_stack,
402            cr,
403            drawsub_stack: self.drawsub_stack.clone(),
404            config: self.config.clone(),
405            recursion_depth: self.recursion_depth,
406            stack_ptr: self.stack_ptr,
407        })
408    }
409
410    pub fn session(&self) -> &Session {
411        &self.session
412    }
413
414    pub fn print_stack_depth(&self, place_name: &str) {
415        let stack_variable: u8 = 42;
416
417        let current_stack_ptr = &stack_variable;
418
419        let stack_size = unsafe { self.stack_ptr.byte_offset_from(current_stack_ptr) };
420        rsvg_log!(
421            self.session,
422            "{place_name}: recursion_depth={}, stack_depth={stack_size}",
423            self.recursion_depth
424        );
425    }
426
427    /// Returns the `RenderingOptions` being used for rendering.
428    pub fn rendering_options(&self, svg_nesting: SvgNesting) -> RenderingOptions {
429        RenderingOptions {
430            dpi: self.config.dpi,
431            cancellable: self.config.cancellable.clone(),
432            user_language: self.config.user_language.clone(),
433            svg_nesting,
434            testing: self.config.testing,
435        }
436    }
437
438    pub fn user_language(&self) -> &UserLanguage {
439        &self.config.user_language
440    }
441
442    pub fn toplevel_viewport(&self) -> Rect {
443        *self.initial_viewport.vbox
444    }
445
446    /// Gets the transform that will be used on the target surface,
447    /// whether using an isolated stacking context or not.
448    fn get_transform_for_stacking_ctx(
449        &self,
450        viewport: &Viewport,
451        stacking_ctx: &StackingContext,
452        clipping: bool,
453    ) -> Result<ValidTransform, Box<InternalRenderingError>> {
454        if stacking_ctx.should_isolate() && !clipping {
455            let affines = CompositingAffines::new(
456                *viewport.transform,
457                *self.initial_viewport.transform,
458                self.cr_stack.borrow().len(),
459            );
460
461            Ok(ValidTransform::try_from(affines.for_temporary_surface)?)
462        } else {
463            Ok(viewport.transform)
464        }
465    }
466
467    pub fn svg_nesting(&self) -> SvgNesting {
468        self.config.svg_nesting
469    }
470
471    pub fn is_measuring(&self) -> bool {
472        self.config.measuring
473    }
474
475    pub fn is_testing(&self) -> bool {
476        self.config.testing
477    }
478
479    fn size_for_temporary_surface(&self) -> (i32, i32) {
480        let rect = self.toplevel_viewport();
481
482        let (viewport_width, viewport_height) = (rect.width(), rect.height());
483
484        let (width, height) = self
485            .initial_viewport
486            .transform
487            .transform_distance(viewport_width, viewport_height);
488
489        // We need a size in whole pixels, so use ceil() to ensure the whole viewport fits
490        // into the temporary surface.
491        (width.ceil().abs() as i32, height.ceil().abs() as i32)
492    }
493
494    pub fn create_surface_for_toplevel_viewport(
495        &self,
496    ) -> Result<cairo::ImageSurface, Box<InternalRenderingError>> {
497        let (w, h) = self.size_for_temporary_surface();
498
499        Ok(cairo::ImageSurface::create(cairo::Format::ARgb32, w, h)?)
500    }
501
502    fn create_similar_surface_for_toplevel_viewport(
503        &self,
504        surface: &cairo::Surface,
505    ) -> Result<cairo::Surface, Box<InternalRenderingError>> {
506        let (w, h) = self.size_for_temporary_surface();
507
508        Ok(cairo::Surface::create_similar(
509            surface,
510            cairo::Content::ColorAlpha,
511            w,
512            h,
513        )?)
514    }
515
516    /// Creates a new coordinate space inside a viewport and sets a clipping rectangle.
517    ///
518    /// Returns the new viewport with the new coordinate space, or `None` if the transform
519    /// inside the new viewport turned out to be invalid.  In this case, the caller can simply
520    /// not render the object in question.
521    fn push_new_viewport(
522        &self,
523        current_viewport: &Viewport,
524        layout_viewport: &LayoutViewport,
525    ) -> Option<Viewport> {
526        let LayoutViewport {
527            geometry,
528            vbox,
529            preserve_aspect_ratio,
530            overflow,
531        } = *layout_viewport;
532
533        if !overflow.overflow_allowed() || (vbox.is_some() && preserve_aspect_ratio.is_slice()) {
534            clip_to_rectangle(&self.cr, &current_viewport.transform, &geometry);
535        }
536
537        preserve_aspect_ratio
538            .viewport_to_viewbox_transform(vbox, &geometry)
539            .unwrap_or_else(|_e| {
540                match vbox {
541                    None => unreachable!(
542                        "viewport_to_viewbox_transform only returns errors when vbox != None"
543                    ),
544                    Some(v) => {
545                        rsvg_log!(
546                            self.session,
547                            "ignoring viewBox ({}, {}, {}, {}) since it is not usable",
548                            v.x0,
549                            v.y0,
550                            v.width(),
551                            v.height()
552                        );
553                    }
554                }
555                None
556            })
557            .and_then(|t| {
558                let transform =
559                    ValidTransform::try_from(current_viewport.transform.pre_transform(&t)).ok()?;
560
561                Some(Viewport {
562                    dpi: self.config.dpi,
563                    vbox: vbox.unwrap_or(current_viewport.vbox),
564                    transform,
565                })
566            })
567    }
568
569    fn clip_to_node(
570        &mut self,
571        clip_node: &Option<Node>,
572        acquired_nodes: &mut AcquiredNodes<'_>,
573        viewport: &Viewport,
574        bbox: &BoundingBox,
575    ) -> Result<(), Box<InternalRenderingError>> {
576        if clip_node.is_none() {
577            return Ok(());
578        }
579
580        let node = clip_node.as_ref().unwrap();
581        let units = borrow_element_as!(node, ClipPath).get_units();
582
583        if let Ok(transform) = rect_to_transform(&bbox.rect, units) {
584            let cascaded = CascadedValues::new_from_node(node);
585            let values = cascaded.get();
586
587            let node_transform = values.transform().post_transform(&transform);
588            let transform_for_clip = ValidTransform::try_from(node_transform)?;
589
590            let clip_viewport = viewport.with_composed_transform(transform_for_clip)?;
591
592            for child in node.children().filter(|c| {
593                c.is_element() && element_can_be_used_inside_clip_path(&c.borrow_element())
594            }) {
595                child.draw(
596                    acquired_nodes,
597                    &CascadedValues::clone_with_node(&cascaded, &child),
598                    &clip_viewport,
599                    self,
600                    true,
601                )?;
602            }
603
604            self.cr.clip();
605        }
606
607        Ok(())
608    }
609
610    /// Sets up the `self.cr` with a clipping path.
611    ///
612    /// This composes all the clipping paths in a `clipPath` element and eventually calls
613    /// `self.cr.clip()`.  Note that this is not enough to represent SVG clip paths, which
614    /// can be added together; Cairo can only shrink clip paths, not take their union.
615    fn apply_clip_path(
616        &mut self,
617        viewport: &Viewport,
618        clip_path: &ClipPath,
619    ) -> Result<(), Box<InternalRenderingError>> {
620        // For every path in the ClipPath, we:
621        //   - apply the path's transform
622        //   - recursively clip the path against its own ClipPath
623        //   - apply the path
624        //
625        // At the end, we cr.clip().  Cairo cannot take the untion of clip-paths, just their
626        // intersections, so the following is not completely correct.  We'll unify clip paths
627        // and masks at some point by doing everything with alpha masks.
628
629        let orig_transform = self.cr.matrix();
630
631        let clip_path_transform = ValidTransform::try_from(clip_path.transform)?;
632        let viewport_for_clip_path = viewport.with_composed_transform(clip_path_transform)?;
633
634        if let Some(ref recursive_clip_path) = clip_path.clip_path {
635            self.apply_clip_path(&viewport_for_clip_path, recursive_clip_path)?;
636        }
637
638        for path_item in clip_path.paths.iter() {
639            let item_transform = ValidTransform::try_from(path_item.transform)?;
640            let viewport_for_item =
641                viewport_for_clip_path.with_composed_transform(item_transform)?;
642            self.cr.set_matrix(viewport_for_item.transform.into());
643
644            if let Some(ref recursive_clip_path) = path_item.clip_path {
645                self.apply_clip_path(&viewport_for_item, recursive_clip_path)?;
646            }
647
648            self.cr
649                .set_fill_rule(cairo::FillRule::from(path_item.clip_rule));
650            path_item.path.to_cairo_context(&self.cr)?;
651        }
652
653        self.cr.clip();
654        self.cr.set_matrix(orig_transform);
655
656        Ok(())
657    }
658
659    fn generate_cairo_mask(
660        &mut self,
661        mask_node: &Node,
662        viewport: &Viewport,
663        transform: Transform,
664        bbox: &BoundingBox,
665        acquired_nodes: &mut AcquiredNodes<'_>,
666    ) -> Result<Option<cairo::ImageSurface>, Box<InternalRenderingError>> {
667        if bbox.rect.is_none() {
668            // The node being masked is empty / doesn't have a
669            // bounding box, so there's nothing to mask!
670            return Ok(None);
671        }
672
673        let _mask_acquired = match acquired_nodes.acquire_ref(mask_node) {
674            Ok(n) => n,
675
676            _ => return Ok(None),
677        };
678
679        let mask_element = mask_node.borrow_element();
680        let mask = borrow_element_as!(mask_node, Mask);
681
682        let cascaded = CascadedValues::new_from_node(mask_node);
683        let values = cascaded.get();
684
685        let mask_units = mask.get_units();
686
687        let mask_rect = {
688            let params = NormalizeParams::new(values, &viewport.with_units(mask_units));
689            mask.get_rect(&params)
690        };
691
692        let transform_for_mask =
693            ValidTransform::try_from(values.transform().post_transform(&transform))?;
694
695        let bbtransform = if let Ok(t) = rect_to_transform(&bbox.rect, mask_units)
696            .map_err(|_: ()| InvalidTransform)
697            .and_then(ValidTransform::try_from)
698        {
699            t
700        } else {
701            return Ok(None);
702        };
703
704        let mask_content_surface = self.create_surface_for_toplevel_viewport()?;
705
706        // Use a scope because mask_cr needs to release the
707        // reference to the surface before we access the pixels
708        {
709            let mask_cr = cairo::Context::new(&mask_content_surface)?;
710
711            let clip_rect = (*bbtransform).transform_rect(&mask_rect);
712            clip_to_rectangle(&mask_cr, &transform_for_mask, &clip_rect);
713
714            let mask_viewport = if mask.get_content_units() == CoordUnits::ObjectBoundingBox {
715                viewport
716                    .with_units(mask.get_content_units())
717                    .with_explicit_transform(transform_for_mask)
718                    .with_composed_transform(bbtransform)?
719            } else {
720                viewport
721                    .with_units(mask.get_content_units())
722                    .with_explicit_transform(transform_for_mask)
723            };
724
725            let mut mask_draw_ctx = self.nested(mask_cr);
726
727            let stacking_ctx = Box::new(StackingContext::new(
728                self,
729                acquired_nodes,
730                &mask_element,
731                Transform::identity(),
732                None,
733                values,
734                viewport,
735            ));
736
737            rsvg_log!(self.session, "(mask {}", mask_element);
738
739            let res = mask_draw_ctx.with_discrete_layer(
740                &stacking_ctx,
741                acquired_nodes,
742                &mask_viewport,
743                None,
744                false,
745                &mut |an, dc, new_viewport| {
746                    mask_node.draw_children(an, &cascaded, new_viewport, dc)
747                },
748            );
749
750            rsvg_log!(self.session, ")");
751
752            res?;
753        }
754
755        let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
756
757        let mask_result = match values.mask_type() {
758            MaskType::Luminance => tmp.to_luminance_mask()?,
759            MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
760        };
761
762        let mask = mask_result.into_image_surface()?;
763
764        Ok(Some(mask))
765    }
766
767    fn is_rendering_cancelled(&self) -> bool {
768        match &self.config.cancellable {
769            None => false,
770            Some(cancellable) => cancellable.is_cancelled(),
771        }
772    }
773
774    /// Checks whether the rendering has been cancelled in the middle.
775    ///
776    /// If so, returns an Err.  This is used from [`DrawingCtx::with_discrete_layer`] to
777    /// exit early instead of proceeding with rendering.
778    fn check_cancellation(&self) -> Result<(), Box<InternalRenderingError>> {
779        if self.is_rendering_cancelled() {
780            return Err(Box::new(InternalRenderingError::Cancelled));
781        }
782
783        Ok(())
784    }
785
786    fn check_layer_nesting_depth(&self) -> Result<(), Box<InternalRenderingError>> {
787        if self.recursion_depth > limits::MAX_LAYER_NESTING_DEPTH {
788            return Err(Box::new(InternalRenderingError::LimitExceeded(
789                ImplementationLimit::MaximumLayerNestingDepthExceeded,
790            )));
791        }
792
793        Ok(())
794    }
795
796    fn filter_current_surface(
797        &mut self,
798        acquired_nodes: &mut AcquiredNodes<'_>,
799        filter: &Filter,
800        viewport: &Viewport,
801        element_name: &str,
802        bbox: &BoundingBox,
803    ) -> Result<cairo::Surface, Box<InternalRenderingError>> {
804        let surface_to_filter = SharedImageSurface::copy_from_surface(
805            &cairo::ImageSurface::try_from(self.cr.target()).unwrap(),
806        )?;
807
808        let stroke_paint_source = Rc::new(filter.stroke_paint_source.to_user_space(
809            &bbox.rect,
810            viewport,
811            &filter.normalize_values,
812        ));
813        let fill_paint_source = Rc::new(filter.fill_paint_source.to_user_space(
814            &bbox.rect,
815            viewport,
816            &filter.normalize_values,
817        ));
818
819        // Filter functions (like "blend()", not the <filter> element) require
820        // being resolved in userSpaceonUse units, since that is the default
821        // for primitive_units.  So, get the corresponding NormalizeParams
822        // here and pass them down.
823        let user_space_params = NormalizeParams::from_values(
824            &filter.normalize_values,
825            &viewport.with_units(CoordUnits::UserSpaceOnUse),
826        );
827
828        let filtered_surface = self
829            .run_filters(
830                viewport,
831                surface_to_filter,
832                filter,
833                acquired_nodes,
834                element_name,
835                &user_space_params,
836                stroke_paint_source,
837                fill_paint_source,
838                bbox,
839            )?
840            .into_image_surface()?;
841
842        let generic_surface: &cairo::Surface = &filtered_surface; // deref to Surface
843
844        Ok(generic_surface.clone())
845    }
846
847    fn draw_in_optional_new_viewport(
848        &mut self,
849        acquired_nodes: &mut AcquiredNodes<'_>,
850        viewport: &Viewport,
851        layout_viewport: &Option<LayoutViewport>,
852        draw_fn: &mut dyn FnMut(&mut AcquiredNodes<'_>, &mut DrawingCtx, &Viewport) -> DrawResult,
853    ) -> DrawResult {
854        if let Some(layout_viewport) = layout_viewport.as_ref() {
855            if let Some(new_viewport) = self.push_new_viewport(viewport, layout_viewport) {
856                draw_fn(acquired_nodes, self, &new_viewport)
857            } else {
858                Ok(viewport.empty_bbox())
859            }
860        } else {
861            draw_fn(acquired_nodes, self, viewport)
862        }
863    }
864
865    fn draw_layer_internal(
866        &mut self,
867        stacking_ctx: &StackingContext,
868        acquired_nodes: &mut AcquiredNodes<'_>,
869        viewport: &Viewport,
870        layout_viewport: Option<LayoutViewport>,
871        clipping: bool,
872        draw_fn: &mut dyn FnMut(&mut AcquiredNodes<'_>, &mut DrawingCtx, &Viewport) -> DrawResult,
873    ) -> DrawResult {
874        self.print_stack_depth("DrawingCtx::draw_layer_internal entry");
875
876        let stacking_ctx_transform = ValidTransform::try_from(stacking_ctx.transform)?;
877
878        let viewport = viewport.with_composed_transform(stacking_ctx_transform)?;
879
880        if clipping {
881            self.draw_in_optional_new_viewport(acquired_nodes, &viewport, &layout_viewport, draw_fn)
882        } else {
883            with_saved_cr(&self.cr.clone(), || {
884                self.link_tag_begin(&stacking_ctx.link_target);
885
886                if let Some(rect) = stacking_ctx.clip_rect.as_ref() {
887                    clip_to_rectangle(&self.cr, &viewport.transform, rect);
888                }
889
890                if let Some(ref clip_path) = stacking_ctx.clip_path
891                    && clip_path.clip_units == CoordUnits::UserSpaceOnUse
892                {
893                    self.apply_clip_path(&viewport, clip_path)?;
894                }
895
896                let res = if stacking_ctx.should_isolate() {
897                    self.print_stack_depth("DrawingCtx::draw_layer_internal should_isolate=true");
898
899                    // Compute our assortment of affines
900
901                    let affines = Box::new(CompositingAffines::new(
902                        *viewport.transform,
903                        *self.initial_viewport.transform,
904                        self.cr_stack.borrow().len(),
905                    ));
906
907                    // Create temporary surface and its cr
908
909                    let cr = match stacking_ctx.filter {
910                        None => cairo::Context::new(
911                            &self
912                                .create_similar_surface_for_toplevel_viewport(&self.cr.target())?,
913                        )?,
914                        Some(_) => {
915                            cairo::Context::new(self.create_surface_for_toplevel_viewport()?)?
916                        }
917                    };
918
919                    let transform_for_temporary_surface =
920                        ValidTransform::try_from(affines.for_temporary_surface)?;
921
922                    let (source_surface, mut res, bbox) = {
923                        let mut temporary_draw_ctx = self.nested(cr.clone());
924
925                        let viewport_for_temporary_surface = Viewport::with_explicit_transform(
926                            &viewport,
927                            transform_for_temporary_surface,
928                        );
929
930                        // Draw!
931
932                        let res = temporary_draw_ctx.draw_in_optional_new_viewport(
933                            acquired_nodes,
934                            &viewport_for_temporary_surface,
935                            &layout_viewport,
936                            draw_fn,
937                        );
938
939                        let bbox = if let Ok(ref bbox) = res {
940                            bbox.clone()
941                        } else {
942                            Box::new(
943                                BoundingBox::new().with_transform(*transform_for_temporary_surface),
944                            )
945                        };
946
947                        if let Some(ref filter) = stacking_ctx.filter {
948                            let filtered_surface = temporary_draw_ctx.filter_current_surface(
949                                acquired_nodes,
950                                filter,
951                                &viewport_for_temporary_surface,
952                                &stacking_ctx.element_name,
953                                &bbox,
954                            )?;
955
956                            // FIXME: "res" was declared mutable above so that we could overwrite it
957                            // with the result of filtering, so that if filtering produces an error,
958                            // then the masking below wouldn't take place.  Test for that and fix this;
959                            // we are *not* modifying res in case of error.
960                            (filtered_surface, res, bbox)
961                        } else {
962                            (temporary_draw_ctx.cr.target(), res, bbox)
963                        }
964                    };
965
966                    // Set temporary surface as source
967
968                    self.cr
969                        .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
970                    self.cr.set_source_surface(&source_surface, 0.0, 0.0)?;
971
972                    // Clip in object space
973                    /*
974                        if let Some(ref clip_path) = stacking_ctx.clip_path {
975                            if clip_path.clip_units == CoordUnits::ObjectBoundingBox {
976                                let transform_for_clip =
977                                    ValidTransform::try_from(affines.outside_temporary_surface)?;
978
979                                let viewport_for_clip = viewport.with_explicit_transform(transform_for_clip);
980
981                                self.apply_clip_path(&viewport_for_clip, clip_path)?;
982                            }
983                    }
984                        */
985
986                    let transform_for_clip =
987                        ValidTransform::try_from(affines.outside_temporary_surface)?;
988
989                    let viewport_for_clip = viewport.with_explicit_transform(transform_for_clip);
990                    self.cr.set_matrix(transform_for_clip.into());
991
992                    self.clip_to_node(
993                        &stacking_ctx.clip_in_object_space,
994                        acquired_nodes,
995                        &viewport_for_clip,
996                        &bbox,
997                    )?;
998
999                    // Mask
1000
1001                    if let Some(ref mask_node) = stacking_ctx.mask {
1002                        self.print_stack_depth("DrawingCtx::draw_layer_internal creating mask");
1003
1004                        res = res.and_then(|bbox| {
1005                            self.generate_cairo_mask(
1006                                mask_node,
1007                                &viewport,
1008                                affines.for_temporary_surface,
1009                                &bbox,
1010                                acquired_nodes,
1011                            )
1012                            .and_then(|mask_surf| {
1013                                if let Some(surf) = mask_surf {
1014                                    self.cr.push_group();
1015
1016                                    self.cr.set_matrix(
1017                                        ValidTransform::try_from(affines.compositing)?.into(),
1018                                    );
1019                                    self.cr.mask_surface(&surf, 0.0, 0.0)?;
1020
1021                                    Ok(self.cr.pop_group_to_source()?)
1022                                } else {
1023                                    Ok(())
1024                                }
1025                            })
1026                            .map(|_: ()| bbox)
1027                        });
1028                    }
1029
1030                    {
1031                        // Composite the temporary surface
1032
1033                        self.cr
1034                            .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
1035                        self.cr.set_operator(stacking_ctx.mix_blend_mode.into());
1036
1037                        let Opacity(UnitInterval(opacity)) = stacking_ctx.opacity;
1038
1039                        if opacity < 1.0 {
1040                            self.cr.paint_with_alpha(opacity)?;
1041                        } else {
1042                            self.cr.paint()?;
1043                        }
1044                    }
1045
1046                    res
1047                } else {
1048                    self.draw_in_optional_new_viewport(
1049                        acquired_nodes,
1050                        &viewport,
1051                        &layout_viewport,
1052                        draw_fn,
1053                    )
1054                };
1055
1056                self.link_tag_end(&stacking_ctx.link_target);
1057
1058                res
1059            })
1060        }
1061    }
1062
1063    pub fn with_discrete_layer(
1064        &mut self,
1065        stacking_ctx: &StackingContext,
1066        acquired_nodes: &mut AcquiredNodes<'_>,
1067        viewport: &Viewport,
1068        layout_viewport: Option<LayoutViewport>,
1069        clipping: bool,
1070        draw_fn: &mut dyn FnMut(&mut AcquiredNodes<'_>, &mut DrawingCtx, &Viewport) -> DrawResult,
1071    ) -> DrawResult {
1072        self.check_cancellation()?;
1073
1074        self.recursion_depth += 1;
1075        self.print_stack_depth("DrawingCtx::with_discrete_layer");
1076
1077        match self.check_layer_nesting_depth() {
1078            Ok(()) => {
1079                let res = self.draw_layer_internal(
1080                    stacking_ctx,
1081                    acquired_nodes,
1082                    viewport,
1083                    layout_viewport,
1084                    clipping,
1085                    draw_fn,
1086                );
1087
1088                self.recursion_depth -= 1;
1089                res
1090            }
1091
1092            Err(e) => Err(e),
1093        }
1094    }
1095
1096    /// Run the drawing function with the specified opacity
1097    fn with_alpha(
1098        &mut self,
1099        opacity: UnitInterval,
1100        draw_fn: &mut dyn FnMut(&mut DrawingCtx) -> DrawResult,
1101    ) -> DrawResult {
1102        let res;
1103        let UnitInterval(o) = opacity;
1104        if o < 1.0 {
1105            self.cr.push_group();
1106            res = draw_fn(self);
1107            self.cr.pop_group_to_source()?;
1108            self.cr.paint_with_alpha(o)?;
1109        } else {
1110            res = draw_fn(self);
1111        }
1112
1113        res
1114    }
1115
1116    /// Start a Cairo tag for PDF links
1117    fn link_tag_begin(&mut self, link_target: &Option<String>) {
1118        if let Some(ref link_target) = *link_target {
1119            let attributes = format!("uri='{}'", escape_link_target(link_target));
1120
1121            let cr = self.cr.clone();
1122            cr.tag_begin(CAIRO_TAG_LINK, &attributes);
1123        }
1124    }
1125
1126    /// End a Cairo tag for PDF links
1127    fn link_tag_end(&mut self, link_target: &Option<String>) {
1128        if link_target.is_some() {
1129            self.cr.tag_end(CAIRO_TAG_LINK);
1130        }
1131    }
1132
1133    fn make_filter_plan(
1134        &mut self,
1135        acquired_nodes: &mut AcquiredNodes<'_>,
1136        specs: &[FilterSpec],
1137        source_image_width: i32,
1138        source_image_height: i32,
1139        stroke_paint_source: Rc<UserSpacePaintSource>,
1140        fill_paint_source: Rc<UserSpacePaintSource>,
1141        viewport: &Viewport,
1142    ) -> Result<Rc<FilterPlan>, Box<InternalRenderingError>> {
1143        let requirements = InputRequirements::new_from_filter_specs(specs);
1144
1145        let background_image =
1146            if requirements.needs_background_image || requirements.needs_background_alpha {
1147                Some(self.get_snapshot(source_image_width, source_image_height)?)
1148            } else {
1149                None
1150            };
1151
1152        let stroke_paint_image = if requirements.needs_stroke_paint_image {
1153            Some(self.get_paint_source_surface(
1154                source_image_width,
1155                source_image_height,
1156                acquired_nodes,
1157                &stroke_paint_source,
1158                viewport,
1159            )?)
1160        } else {
1161            None
1162        };
1163
1164        let fill_paint_image = if requirements.needs_fill_paint_image {
1165            Some(self.get_paint_source_surface(
1166                source_image_width,
1167                source_image_height,
1168                acquired_nodes,
1169                &fill_paint_source,
1170                viewport,
1171            )?)
1172        } else {
1173            None
1174        };
1175
1176        Ok(Rc::new(FilterPlan::new(
1177            self.session(),
1178            *viewport,
1179            requirements,
1180            background_image,
1181            stroke_paint_image,
1182            fill_paint_image,
1183        )?))
1184    }
1185
1186    fn run_filters(
1187        &mut self,
1188        viewport: &Viewport,
1189        surface_to_filter: SharedImageSurface,
1190        filter: &Filter,
1191        acquired_nodes: &mut AcquiredNodes<'_>,
1192        node_name: &str,
1193        user_space_params: &NormalizeParams,
1194        stroke_paint_source: Rc<UserSpacePaintSource>,
1195        fill_paint_source: Rc<UserSpacePaintSource>,
1196        node_bbox: &BoundingBox,
1197    ) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
1198        let session = self.session();
1199
1200        // We try to convert each item in the filter_list to a FilterSpec.
1201        //
1202        // However, the spec mentions, "If the filter references a non-existent object or
1203        // the referenced object is not a filter element, then the whole filter chain is
1204        // ignored." - https://www.w3.org/TR/filter-effects/#FilterProperty
1205        //
1206        // So, run through the filter_list and collect into a Result<Vec<FilterSpec>>.
1207        // This will return an Err if any of the conversions failed.
1208        let filter_specs = filter
1209            .filter_list
1210            .iter()
1211            .map(|filter_value| {
1212                filter_value.to_filter_spec(
1213                    acquired_nodes,
1214                    user_space_params,
1215                    filter.current_color,
1216                    viewport,
1217                    session,
1218                    node_name,
1219                )
1220            })
1221            .collect::<Result<Vec<FilterSpec>, _>>();
1222
1223        match filter_specs {
1224            Ok(specs) => {
1225                let plan = self.make_filter_plan(
1226                    acquired_nodes,
1227                    &specs,
1228                    surface_to_filter.width(),
1229                    surface_to_filter.height(),
1230                    stroke_paint_source,
1231                    fill_paint_source,
1232                    viewport,
1233                )?;
1234
1235                // Start with the surface_to_filter, and apply each filter spec in turn;
1236                // the final result is our return value.
1237                specs.iter().try_fold(surface_to_filter, |surface, spec| {
1238                    filters::render(plan.clone(), spec, surface, acquired_nodes, self, node_bbox)
1239                })
1240            }
1241
1242            Err(e) => {
1243                rsvg_log!(
1244                    self.session,
1245                    "not rendering filter list on node {} because it was in error: {}",
1246                    node_name,
1247                    e
1248                );
1249                // just return the original surface without filtering it
1250                Ok(surface_to_filter)
1251            }
1252        }
1253    }
1254
1255    fn set_pattern(
1256        &mut self,
1257        pattern: &UserSpacePattern,
1258        acquired_nodes: &mut AcquiredNodes<'_>,
1259        viewport: &Viewport,
1260    ) -> Result<bool, Box<InternalRenderingError>> {
1261        // Bail out early if the pattern has zero size, per the spec
1262        if approx_eq!(f64, pattern.width, 0.0) || approx_eq!(f64, pattern.height, 0.0) {
1263            return Ok(false);
1264        }
1265
1266        // Bail out early if this pattern has a circular reference
1267        let pattern_node_acquired = match pattern.acquire_pattern_node(acquired_nodes) {
1268            Ok(n) => n,
1269
1270            Err(AcquireError::CircularReference(ref node)) => {
1271                rsvg_log!(self.session, "circular reference in element {}", node);
1272                return Ok(false);
1273            }
1274
1275            _ => unreachable!(),
1276        };
1277
1278        let pattern_node = pattern_node_acquired.get();
1279
1280        let taffine = viewport.transform.pre_transform(&pattern.transform);
1281
1282        let mut scwscale = (taffine.xx.powi(2) + taffine.xy.powi(2)).sqrt();
1283        let mut schscale = (taffine.yx.powi(2) + taffine.yy.powi(2)).sqrt();
1284
1285        let pw: i32 = (pattern.width * scwscale) as i32;
1286        let ph: i32 = (pattern.height * schscale) as i32;
1287
1288        if pw < 1 || ph < 1 {
1289            return Ok(false);
1290        }
1291
1292        scwscale = f64::from(pw) / pattern.width;
1293        schscale = f64::from(ph) / pattern.height;
1294
1295        // Apply the pattern transform
1296        let (affine, caffine) = if scwscale.approx_eq_cairo(1.0) && schscale.approx_eq_cairo(1.0) {
1297            (pattern.coord_transform, pattern.content_transform)
1298        } else {
1299            (
1300                pattern
1301                    .coord_transform
1302                    .pre_scale(1.0 / scwscale, 1.0 / schscale),
1303                pattern.content_transform.post_scale(scwscale, schscale),
1304            )
1305        };
1306
1307        // Draw to another surface
1308        let surface = self
1309            .cr
1310            .target()
1311            .create_similar(cairo::Content::ColorAlpha, pw, ph)?;
1312
1313        let cr_pattern = cairo::Context::new(&surface)?;
1314
1315        // Set up transformations to be determined by the contents units
1316
1317        let transform = ValidTransform::try_from(caffine)?;
1318        cr_pattern.set_matrix(transform.into());
1319
1320        // Draw everything
1321
1322        {
1323            let mut pattern_draw_ctx = self.nested(cr_pattern);
1324
1325            let pattern_viewport = viewport
1326                .with_view_box(pattern.width, pattern.height)
1327                .with_explicit_transform(transform);
1328
1329            let pattern_cascaded = CascadedValues::new_from_node(pattern_node);
1330            let pattern_values = pattern_cascaded.get();
1331
1332            let elt = pattern_node.borrow_element();
1333
1334            let stacking_ctx = Box::new(StackingContext::new(
1335                self,
1336                acquired_nodes,
1337                &elt,
1338                Transform::identity(),
1339                None,
1340                pattern_values,
1341                viewport,
1342            ));
1343
1344            pattern_draw_ctx
1345                .with_alpha(pattern.opacity, &mut |dc| {
1346                    dc.with_discrete_layer(
1347                        &stacking_ctx,
1348                        acquired_nodes,
1349                        &pattern_viewport,
1350                        None,
1351                        false,
1352                        &mut |an, dc, new_viewport| {
1353                            pattern_node.draw_children(an, &pattern_cascaded, new_viewport, dc)
1354                        },
1355                    )
1356                })
1357                .map(|_| ())?;
1358        }
1359
1360        // Set the final surface as a Cairo pattern into the Cairo context
1361        let pattern = cairo::SurfacePattern::create(&surface);
1362
1363        if let Some(m) = affine.invert() {
1364            pattern.set_matrix(ValidTransform::try_from(m)?.into());
1365            pattern.set_extend(cairo::Extend::Repeat);
1366            pattern.set_filter(cairo::Filter::Best);
1367            self.cr.set_source(&pattern)?;
1368        }
1369
1370        Ok(true)
1371    }
1372
1373    fn set_paint_source(
1374        &mut self,
1375        paint_source: &UserSpacePaintSource,
1376        acquired_nodes: &mut AcquiredNodes<'_>,
1377        viewport: &Viewport,
1378    ) -> Result<bool, Box<InternalRenderingError>> {
1379        match *paint_source {
1380            UserSpacePaintSource::Gradient(ref gradient, _c) => {
1381                set_gradient_on_cairo(&self.cr, gradient)?;
1382                Ok(true)
1383            }
1384            UserSpacePaintSource::Pattern(ref pattern, ref c) => {
1385                if self.set_pattern(pattern, acquired_nodes, viewport)? {
1386                    Ok(true)
1387                } else if let Some(c) = c {
1388                    set_source_color_on_cairo(&self.cr, c);
1389                    Ok(true)
1390                } else {
1391                    Ok(false)
1392                }
1393            }
1394            UserSpacePaintSource::SolidColor(ref c) => {
1395                set_source_color_on_cairo(&self.cr, c);
1396                Ok(true)
1397            }
1398            UserSpacePaintSource::None => Ok(false),
1399        }
1400    }
1401
1402    /// Computes and returns a surface corresponding to the given paint server.
1403    pub fn get_paint_source_surface(
1404        &mut self,
1405        width: i32,
1406        height: i32,
1407        acquired_nodes: &mut AcquiredNodes<'_>,
1408        paint_source: &UserSpacePaintSource,
1409        viewport: &Viewport,
1410    ) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
1411        let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
1412
1413        surface.draw(&mut |cr| {
1414            let mut temporary_draw_ctx = self.nested(cr);
1415
1416            // FIXME: we are ignoring any error
1417
1418            let had_paint_server =
1419                temporary_draw_ctx.set_paint_source(paint_source, acquired_nodes, viewport)?;
1420            if had_paint_server {
1421                temporary_draw_ctx.cr.paint()?;
1422            }
1423
1424            Ok(())
1425        })?;
1426
1427        Ok(surface.share()?)
1428    }
1429
1430    pub fn draw_layer(
1431        &mut self,
1432        layer: &Layer,
1433        acquired_nodes: &mut AcquiredNodes<'_>,
1434        clipping: bool,
1435        viewport: &Viewport,
1436    ) -> DrawResult {
1437        match &layer.kind {
1438            LayerKind::Shape(shape) => self.draw_shape(
1439                shape,
1440                &layer.stacking_ctx,
1441                acquired_nodes,
1442                clipping,
1443                viewport,
1444            ),
1445            LayerKind::Text(text) => self.draw_text(
1446                text,
1447                &layer.stacking_ctx,
1448                acquired_nodes,
1449                clipping,
1450                viewport,
1451            ),
1452            LayerKind::Image(image) => {
1453                self.draw_image(image, &layer.stacking_ctx, acquired_nodes, viewport)
1454            }
1455            LayerKind::Group(group) => {
1456                self.draw_group(group, &layer.stacking_ctx, acquired_nodes, viewport)
1457            }
1458        }
1459    }
1460
1461    fn draw_shape(
1462        &mut self,
1463        shape: &Shape,
1464        stacking_ctx: &StackingContext,
1465        acquired_nodes: &mut AcquiredNodes<'_>,
1466        clipping: bool,
1467        viewport: &Viewport,
1468    ) -> DrawResult {
1469        self.with_discrete_layer(
1470            stacking_ctx,
1471            acquired_nodes,
1472            viewport,
1473            None,
1474            clipping,
1475            &mut |an, dc, new_viewport| {
1476                dc.paint_shape(shape, stacking_ctx, an, clipping, new_viewport)
1477            },
1478        )
1479    }
1480
1481    fn paint_shape(
1482        &mut self,
1483        shape: &Shape,
1484        stacking_ctx: &StackingContext,
1485        acquired_nodes: &mut AcquiredNodes<'_>,
1486        clipping: bool,
1487        viewport: &Viewport,
1488    ) -> DrawResult {
1489        let cr = self.cr.clone();
1490
1491        let transform = self.get_transform_for_stacking_ctx(viewport, stacking_ctx, clipping)?;
1492        let mut path_helper = PathHelper::new(&cr, transform, &shape.path.cairo_path);
1493
1494        if clipping {
1495            if stacking_ctx.is_visible {
1496                cr.set_fill_rule(cairo::FillRule::from(shape.clip_rule));
1497                path_helper.set()?;
1498            }
1499            return Ok(viewport.empty_bbox());
1500        }
1501
1502        cr.set_antialias(cairo::Antialias::from(shape.shape_rendering));
1503
1504        setup_cr_for_stroke(&cr, &shape.stroke);
1505
1506        cr.set_fill_rule(cairo::FillRule::from(shape.fill_rule));
1507
1508        path_helper.set()?;
1509        let bbox = compute_stroke_and_fill_box(
1510            &cr,
1511            &shape.stroke,
1512            &shape.stroke_paint,
1513            &self.initial_viewport,
1514        )?;
1515
1516        if stacking_ctx.is_visible {
1517            for &target in &shape.paint_order.targets {
1518                // fill and stroke operations will preserve the path.
1519                // markers operation will clear the path.
1520                match target {
1521                    PaintTarget::Fill => {
1522                        path_helper.set()?;
1523                        let had_paint_server =
1524                            self.set_paint_source(&shape.fill_paint, acquired_nodes, viewport)?;
1525                        if had_paint_server {
1526                            cr.fill_preserve()?;
1527                        }
1528                    }
1529
1530                    PaintTarget::Stroke => {
1531                        path_helper.set()?;
1532                        if shape.stroke.non_scaling {
1533                            cr.set_matrix(self.initial_viewport.transform.into());
1534                        }
1535
1536                        let had_paint_server =
1537                            self.set_paint_source(&shape.stroke_paint, acquired_nodes, viewport)?;
1538                        if had_paint_server {
1539                            cr.stroke_preserve()?;
1540                        }
1541                    }
1542
1543                    PaintTarget::Markers => {
1544                        path_helper.unset();
1545                        marker::render_markers_for_shape(shape, viewport, self, acquired_nodes)?;
1546                    }
1547                }
1548            }
1549        }
1550
1551        path_helper.unset();
1552        Ok(bbox)
1553    }
1554
1555    fn paint_surface_from_image(
1556        &mut self,
1557        image: &Image,
1558        viewport: &Viewport,
1559    ) -> Result<(), cairo::Error> {
1560        let cr = &self.cr;
1561
1562        // We need to set extend appropriately, so can't use cr.set_source_surface().
1563        //
1564        // If extend is left at its default value (None), then bilinear scaling uses
1565        // transparency outside of the image producing incorrect results.
1566        // For example, in svg1.1/filters-blend-01-b.svgthere's a completely
1567        // opaque 100×1 image of a gradient scaled to 100×98 which ends up
1568        // transparent almost everywhere without this fix (which it shouldn't).
1569        let ptn = image.surface.to_cairo_pattern();
1570        ptn.set_extend(cairo::Extend::Pad);
1571
1572        let interpolation = Interpolation::from(image.image_rendering);
1573
1574        ptn.set_filter(cairo::Filter::from(interpolation));
1575        cr.set_matrix(viewport.transform.into());
1576        cr.set_source(&ptn)?;
1577
1578        let image_size_rect = Rect::from_size(
1579            f64::from(image.surface.width()),
1580            f64::from(image.surface.height()),
1581        );
1582
1583        // Clip is needed due to extend being set to pad.
1584        clip_to_rectangle(cr, &viewport.transform, &image_size_rect);
1585
1586        cr.paint()
1587    }
1588
1589    fn draw_image(
1590        &mut self,
1591        image: &Image,
1592        stacking_ctx: &StackingContext,
1593        acquired_nodes: &mut AcquiredNodes<'_>,
1594        viewport: &Viewport,
1595    ) -> DrawResult {
1596        let image_width = image.surface.width();
1597        let image_height = image.surface.height();
1598        if image.rect.is_empty() || image_width == 0 || image_height == 0 {
1599            return Ok(viewport.empty_bbox());
1600        }
1601
1602        let image_size_rect = Rect::from_size(f64::from(image_width), f64::from(image_height));
1603
1604        let vbox = ViewBox::from(image_size_rect);
1605
1606        // The bounding box for <image> is decided by the values of the image's x, y, w, h
1607        // and not by the final computed image bounds.
1608        let bounds = Box::new(viewport.empty_bbox().with_rect(image.rect));
1609
1610        let layout_viewport = LayoutViewport {
1611            vbox: Some(vbox),
1612            geometry: image.rect,
1613            preserve_aspect_ratio: image.aspect,
1614            overflow: image.overflow,
1615        };
1616
1617        if stacking_ctx.is_visible {
1618            self.with_discrete_layer(
1619                stacking_ctx,
1620                acquired_nodes,
1621                viewport,
1622                Some(layout_viewport),
1623                false,
1624                &mut |_an, dc, new_viewport| {
1625                    dc.paint_surface_from_image(image, new_viewport)?;
1626
1627                    Ok(bounds.clone())
1628                },
1629            )
1630        } else {
1631            Ok(bounds)
1632        }
1633    }
1634
1635    fn draw_group(
1636        &mut self,
1637        _group: &Group,
1638        _stacking_ctx: &StackingContext,
1639        _acquired_nodes: &mut AcquiredNodes<'_>,
1640        _viewport: &Viewport,
1641    ) -> DrawResult {
1642        unimplemented!();
1643        /*
1644        self.with_discrete_layer(
1645            stacking_ctx,
1646            acquired_nodes,
1647            viewport,
1648            group.establish_viewport,
1649            false,
1650            &mut |an, dc, new_viewport| {
1651                for layer in &group.children {
1652                    dc.draw_layer(layer, an, false, &new_viewport)?;
1653                }
1654            },
1655        )
1656        */
1657    }
1658
1659    fn draw_text_span(
1660        &mut self,
1661        span: &TextSpan,
1662        acquired_nodes: &mut AcquiredNodes<'_>,
1663        clipping: bool,
1664        viewport: &Viewport,
1665    ) -> DrawResult {
1666        let path = pango_layout_to_cairo_path(span.x, span.y, &span.layout, span.gravity)?;
1667        if path.is_empty() {
1668            // Empty strings, or only-whitespace text, get turned into empty paths.
1669            // In that case, we really want to return "no bounds" rather than an
1670            // empty rectangle.
1671            return Ok(viewport.empty_bbox());
1672        }
1673
1674        // #851 - We can't just render all text as paths for PDF; it
1675        // needs the actual text content so text is selectable by PDF
1676        // viewers.
1677        let can_use_text_as_path = self.cr.target().type_() != cairo::SurfaceType::Pdf;
1678
1679        self.cr
1680            .set_antialias(cairo::Antialias::from(span.text_rendering));
1681
1682        setup_cr_for_stroke(&self.cr, &span.stroke);
1683
1684        self.cr.set_matrix(viewport.transform.into());
1685
1686        if clipping {
1687            path.to_cairo_context(&self.cr)?;
1688            return Ok(viewport.empty_bbox());
1689        }
1690
1691        path.to_cairo_context(&self.cr)?;
1692        let bbox = compute_stroke_and_fill_box(
1693            &self.cr,
1694            &span.stroke,
1695            &span.stroke_paint,
1696            &self.initial_viewport,
1697        )?;
1698        self.cr.new_path();
1699
1700        if span.is_visible {
1701            self.link_tag_begin(&span.link_target);
1702
1703            for &target in &span.paint_order.targets {
1704                match target {
1705                    PaintTarget::Fill => {
1706                        let had_paint_server =
1707                            self.set_paint_source(&span.fill_paint, acquired_nodes, viewport)?;
1708
1709                        if had_paint_server {
1710                            if can_use_text_as_path {
1711                                path.to_cairo_context(&self.cr)?;
1712                                self.cr.fill()?;
1713                                self.cr.new_path();
1714                            } else {
1715                                self.cr.move_to(span.x, span.y);
1716
1717                                let matrix = self.cr.matrix();
1718
1719                                let rotation_from_gravity = span.gravity.to_rotation();
1720                                if !rotation_from_gravity.approx_eq_cairo(0.0) {
1721                                    self.cr.rotate(-rotation_from_gravity);
1722                                }
1723
1724                                pangocairo::functions::update_layout(&self.cr, &span.layout);
1725                                pangocairo::functions::show_layout(&self.cr, &span.layout);
1726
1727                                self.cr.set_matrix(matrix);
1728                            }
1729                        }
1730                    }
1731
1732                    PaintTarget::Stroke => {
1733                        let had_paint_server =
1734                            self.set_paint_source(&span.stroke_paint, acquired_nodes, viewport)?;
1735
1736                        if had_paint_server {
1737                            path.to_cairo_context(&self.cr)?;
1738                            self.cr.stroke()?;
1739                            self.cr.new_path();
1740                        }
1741                    }
1742
1743                    PaintTarget::Markers => {}
1744                }
1745            }
1746
1747            self.link_tag_end(&span.link_target);
1748        }
1749
1750        Ok(bbox)
1751    }
1752
1753    fn draw_text(
1754        &mut self,
1755        text: &Text,
1756        stacking_ctx: &StackingContext,
1757        acquired_nodes: &mut AcquiredNodes<'_>,
1758        clipping: bool,
1759        viewport: &Viewport,
1760    ) -> DrawResult {
1761        self.with_discrete_layer(
1762            stacking_ctx,
1763            acquired_nodes,
1764            viewport,
1765            None,
1766            clipping,
1767            &mut |an, dc, new_viewport| dc.paint_text_spans(text, an, clipping, new_viewport),
1768        )
1769    }
1770
1771    fn paint_text_spans(
1772        &mut self,
1773        text: &Text,
1774        acquired_nodes: &mut AcquiredNodes<'_>,
1775        clipping: bool,
1776        viewport: &Viewport,
1777    ) -> DrawResult {
1778        let mut bbox = viewport.empty_bbox();
1779
1780        for span in &text.spans {
1781            let span_bbox = self.draw_text_span(span, acquired_nodes, clipping, viewport)?;
1782            bbox.insert(&span_bbox);
1783        }
1784
1785        Ok(bbox)
1786    }
1787
1788    pub fn get_snapshot(
1789        &self,
1790        width: i32,
1791        height: i32,
1792    ) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
1793        // TODO: as far as I can tell this should not render elements past the last (topmost) one
1794        // with enable-background: new (because technically we shouldn't have been caching them).
1795        // Right now there are no enable-background checks whatsoever.
1796        //
1797        // Addendum: SVG 2 has deprecated the enable-background property, and replaced it with an
1798        // "isolation" property from the CSS Compositing and Blending spec.
1799        //
1800        // Deprecation:
1801        //   https://www.w3.org/TR/filter-effects-1/#AccessBackgroundImage
1802        //
1803        // BackgroundImage, BackgroundAlpha in the "in" attribute of filter primitives:
1804        //   https://www.w3.org/TR/filter-effects-1/#attr-valuedef-in-backgroundimage
1805        //
1806        // CSS Compositing and Blending, "isolation" property:
1807        //   https://www.w3.org/TR/compositing-1/#isolation
1808        let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
1809
1810        surface.draw(&mut |cr| {
1811            // TODO: apparently DrawingCtx.cr_stack is just a way to store pairs of
1812            // (surface, transform).  Can we turn it into a DrawingCtx.surface_stack
1813            // instead?  See what CSS isolation would like to call that; are the pairs just
1814            // stacking contexts instead, or the result of rendering stacking contexts?
1815            for (depth, draw) in self.cr_stack.borrow().iter().enumerate() {
1816                let affines = CompositingAffines::new(
1817                    Transform::from(draw.matrix()),
1818                    *self.initial_viewport.transform,
1819                    depth,
1820                );
1821
1822                cr.set_matrix(ValidTransform::try_from(affines.for_snapshot)?.into());
1823                cr.set_source_surface(draw.target(), 0.0, 0.0)?;
1824                cr.paint()?;
1825            }
1826
1827            Ok(())
1828        })?;
1829
1830        Ok(surface.share()?)
1831    }
1832
1833    pub fn draw_node_to_surface(
1834        &mut self,
1835        node: &Node,
1836        acquired_nodes: &mut AcquiredNodes<'_>,
1837        cascaded: &CascadedValues<'_>,
1838        transform: ValidTransform,
1839        width: i32,
1840        height: i32,
1841    ) -> Result<SharedImageSurface, Box<InternalRenderingError>> {
1842        let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
1843
1844        let save_cr = self.cr.clone();
1845
1846        {
1847            let cr = cairo::Context::new(&surface)?;
1848            cr.set_matrix(transform.into());
1849
1850            self.cr = cr;
1851            let viewport = Viewport {
1852                dpi: self.config.dpi,
1853                transform,
1854                vbox: ViewBox::from(Rect::from_size(f64::from(width), f64::from(height))),
1855            };
1856
1857            // FIXME: if this returns an error, we will not restore the self.cr as per below
1858            let _ = self.draw_node_from_stack(node, acquired_nodes, cascaded, &viewport)?;
1859        }
1860
1861        self.cr = save_cr;
1862
1863        Ok(SharedImageSurface::wrap(surface, SurfaceType::SRgb)?)
1864    }
1865
1866    pub fn draw_node_from_stack(
1867        &mut self,
1868        node: &Node,
1869        acquired_nodes: &mut AcquiredNodes<'_>,
1870        cascaded: &CascadedValues<'_>,
1871        viewport: &Viewport,
1872    ) -> DrawResult {
1873        self.print_stack_depth("DrawingCtx::draw_node_from_stack");
1874
1875        let stack_top = self.drawsub_stack.pop();
1876
1877        let draw = if let Some(ref top) = stack_top {
1878            top == node
1879        } else {
1880            true
1881        };
1882
1883        let res = if draw {
1884            node.draw(acquired_nodes, cascaded, viewport, self, false)
1885        } else {
1886            Ok(viewport.empty_bbox())
1887        };
1888
1889        if let Some(top) = stack_top {
1890            self.drawsub_stack.push(top);
1891        }
1892
1893        res
1894    }
1895
1896    pub fn draw_from_use_node(
1897        &mut self,
1898        node: &Node,
1899        acquired_nodes: &mut AcquiredNodes<'_>,
1900        values: &ComputedValues,
1901        use_rect: Rect,
1902        link: &NodeId,
1903        clipping: bool,
1904        viewport: &Viewport,
1905        fill_paint: Rc<PaintSource>,
1906        stroke_paint: Rc<PaintSource>,
1907    ) -> DrawResult {
1908        // <use> is an element that is used directly, unlike
1909        // <pattern>, which is used through a fill="url(#...)"
1910        // reference.  However, <use> will always reference another
1911        // element, potentially itself or an ancestor of itself (or
1912        // another <use> which references the first one, etc.).  So,
1913        // we acquire the <use> element itself so that circular
1914        // references can be caught.
1915        let _use_acquired = match acquired_nodes.acquire_ref(node) {
1916            Ok(n) => n,
1917
1918            Err(AcquireError::CircularReference(circular)) => {
1919                return Err(Box::new(InternalRenderingError::CircularReference(
1920                    circular,
1921                )));
1922            }
1923
1924            _ => unreachable!(),
1925        };
1926
1927        let use_element = node.borrow_element();
1928        let use_element_name = format!("{use_element}");
1929
1930        let acquired = match acquired_nodes.acquire(&use_element_name, link) {
1931            Ok(acquired) => acquired,
1932
1933            Err(AcquireError::CircularReference(circular)) => {
1934                return Err(Box::new(InternalRenderingError::CircularReference(
1935                    circular,
1936                )));
1937            }
1938
1939            Err(AcquireError::MaxReferencesExceeded) => {
1940                return Err(Box::new(InternalRenderingError::LimitExceeded(
1941                    ImplementationLimit::TooManyReferencedElements,
1942                )));
1943            }
1944
1945            Err(AcquireError::InvalidLinkType(_)) => unreachable!(),
1946
1947            Err(AcquireError::LinkNotFound(_)) => {
1948                return Ok(viewport.empty_bbox());
1949            }
1950        };
1951
1952        // width or height set to 0 disables rendering of the element
1953        // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
1954        if use_rect.is_empty() {
1955            return Ok(viewport.empty_bbox());
1956        }
1957
1958        let child = acquired.get();
1959
1960        if clipping && !element_can_be_used_inside_use_inside_clip_path(&child.borrow_element()) {
1961            return Ok(viewport.empty_bbox());
1962        }
1963
1964        let use_transform = ValidTransform::try_from(values.transform())?;
1965        let use_viewport = viewport.with_composed_transform(use_transform)?;
1966
1967        let defines_a_viewport = if is_element_of_type!(child, Symbol) {
1968            let symbol = borrow_element_as!(child, Symbol);
1969            Some((symbol.get_viewbox(), symbol.get_preserve_aspect_ratio()))
1970        } else if is_element_of_type!(child, Svg) {
1971            let svg = borrow_element_as!(child, Svg);
1972            Some((svg.get_viewbox(), svg.get_preserve_aspect_ratio()))
1973        } else {
1974            None
1975        };
1976
1977        let res = if let Some((vbox, preserve_aspect_ratio)) = defines_a_viewport {
1978            // <symbol> and <svg> define a viewport, as described in the specification:
1979            // https://www.w3.org/TR/SVG2/struct.html#UseElement
1980            // https://gitlab.gnome.org/GNOME/librsvg/-/issues/875#note_1482705
1981
1982            let elt = child.borrow_element();
1983
1984            let child_values = elt.get_computed_values();
1985
1986            let stacking_ctx = Box::new(StackingContext::new(
1987                self,
1988                acquired_nodes,
1989                &use_element,
1990                Transform::identity(),
1991                None,
1992                values,
1993                viewport,
1994            ));
1995
1996            let layout_viewport = LayoutViewport {
1997                vbox,
1998                geometry: use_rect,
1999                preserve_aspect_ratio,
2000                overflow: child_values.overflow(),
2001            };
2002
2003            self.with_discrete_layer(
2004                &stacking_ctx,
2005                acquired_nodes,
2006                &use_viewport,
2007                Some(layout_viewport),
2008                clipping,
2009                &mut |an, dc, new_viewport| {
2010                    child.draw_children(
2011                        an,
2012                        &CascadedValues::new_from_values(
2013                            child,
2014                            values,
2015                            Some(fill_paint.clone()),
2016                            Some(stroke_paint.clone()),
2017                        ),
2018                        new_viewport,
2019                        dc,
2020                    )
2021                },
2022            )
2023        } else {
2024            // otherwise the referenced node is not a <symbol>; process it generically
2025
2026            let stacking_ctx = Box::new(StackingContext::new(
2027                self,
2028                acquired_nodes,
2029                &use_element,
2030                Transform::new_translate(use_rect.x0, use_rect.y0),
2031                None,
2032                values,
2033                viewport,
2034            ));
2035
2036            self.with_discrete_layer(
2037                &stacking_ctx,
2038                acquired_nodes,
2039                &use_viewport,
2040                None,
2041                clipping,
2042                &mut |an, dc, new_viewport| {
2043                    child.draw(
2044                        an,
2045                        &CascadedValues::new_from_values(
2046                            child,
2047                            values,
2048                            Some(fill_paint.clone()),
2049                            Some(stroke_paint.clone()),
2050                        ),
2051                        new_viewport,
2052                        dc,
2053                        clipping,
2054                    )
2055                },
2056            )
2057        };
2058
2059        if let Ok(bbox) = res {
2060            let mut res_bbox = Box::new(BoundingBox::new().with_transform(*viewport.transform));
2061            res_bbox.insert(&bbox);
2062            Ok(res_bbox)
2063        } else {
2064            res
2065        }
2066    }
2067
2068    /// Extracts the font options for the current state of the DrawingCtx.
2069    ///
2070    /// You can use the font options later with create_pango_context().
2071    pub fn get_font_options(&self) -> FontOptions {
2072        FontOptions::new(self.config.testing)
2073    }
2074}
2075
2076impl From<ImageRendering> for Interpolation {
2077    fn from(r: ImageRendering) -> Interpolation {
2078        match r {
2079            ImageRendering::Pixelated
2080            | ImageRendering::CrispEdges
2081            | ImageRendering::OptimizeSpeed => Interpolation::Nearest,
2082
2083            ImageRendering::Smooth
2084            | ImageRendering::OptimizeQuality
2085            | ImageRendering::HighQuality
2086            | ImageRendering::Auto => Interpolation::Smooth,
2087        }
2088    }
2089}
2090
2091/// Create a Pango context with a particular configuration.
2092pub fn create_pango_context(font_options: &FontOptions) -> pango::Context {
2093    let font_map = pangocairo::FontMap::default();
2094    let context = font_map.create_context();
2095
2096    context.set_round_glyph_positions(false);
2097
2098    pangocairo::functions::context_set_font_options(&context, Some(&font_options.options));
2099
2100    // Pango says this about pango_cairo_context_set_resolution():
2101    //
2102    //     Sets the resolution for the context. This is a scale factor between
2103    //     points specified in a #PangoFontDescription and Cairo units. The
2104    //     default value is 96, meaning that a 10 point font will be 13
2105    //     units high. (10 * 96. / 72. = 13.3).
2106    //
2107    // I.e. Pango font sizes in a PangoFontDescription are in *points*, not pixels.
2108    // However, we are normalizing everything to userspace units, which amount to
2109    // pixels.  So, we will use 72.0 here to make Pango not apply any further scaling
2110    // to the size values we give it.
2111    //
2112    // An alternative would be to divide our font sizes by (dpi_y / 72) to effectively
2113    // cancel out Pango's scaling, but it's probably better to deal with Pango-isms
2114    // right here, instead of spreading them out through our Length normalization
2115    // code.
2116    pangocairo::functions::context_set_resolution(&context, 72.0);
2117
2118    context
2119}
2120
2121pub fn set_source_color_on_cairo(cr: &cairo::Context, color: &Color) {
2122    let rgba = color_to_rgba(color);
2123
2124    cr.set_source_rgba(
2125        f64::from(rgba.red) / 255.0,
2126        f64::from(rgba.green) / 255.0,
2127        f64::from(rgba.blue) / 255.0,
2128        f64::from(rgba.alpha),
2129    );
2130}
2131
2132fn set_gradient_on_cairo(
2133    cr: &cairo::Context,
2134    gradient: &UserSpaceGradient,
2135) -> Result<(), Box<InternalRenderingError>> {
2136    let g = match gradient.variant {
2137        GradientVariant::Linear { x1, y1, x2, y2 } => {
2138            cairo::Gradient::clone(&cairo::LinearGradient::new(x1, y1, x2, y2))
2139        }
2140
2141        GradientVariant::Radial {
2142            cx,
2143            cy,
2144            r,
2145            fx,
2146            fy,
2147            fr,
2148        } => cairo::Gradient::clone(&cairo::RadialGradient::new(fx, fy, fr, cx, cy, r)),
2149    };
2150
2151    g.set_matrix(ValidTransform::try_from(gradient.transform)?.into());
2152    g.set_extend(cairo::Extend::from(gradient.spread));
2153
2154    for stop in &gradient.stops {
2155        let UnitInterval(stop_offset) = stop.offset;
2156
2157        let rgba = color_to_rgba(&stop.color);
2158
2159        g.add_color_stop_rgba(
2160            stop_offset,
2161            f64::from(rgba.red) / 255.0,
2162            f64::from(rgba.green) / 255.0,
2163            f64::from(rgba.blue) / 255.0,
2164            f64::from(rgba.alpha),
2165        );
2166    }
2167
2168    Ok(cr.set_source(&g)?)
2169}
2170
2171/// Converts a Pango layout to a Cairo path on the specified cr starting at (x, y).
2172/// Does not clear the current path first.
2173fn pango_layout_to_cairo(
2174    x: f64,
2175    y: f64,
2176    layout: &pango::Layout,
2177    gravity: pango::Gravity,
2178    cr: &cairo::Context,
2179) {
2180    let rotation_from_gravity = gravity.to_rotation();
2181    let rotation = if !rotation_from_gravity.approx_eq_cairo(0.0) {
2182        Some(-rotation_from_gravity)
2183    } else {
2184        None
2185    };
2186
2187    cr.move_to(x, y);
2188
2189    let matrix = cr.matrix();
2190    if let Some(rot) = rotation {
2191        cr.rotate(rot);
2192    }
2193
2194    pangocairo::functions::update_layout(cr, layout);
2195    pangocairo::functions::layout_path(cr, layout);
2196    cr.set_matrix(matrix);
2197}
2198
2199/// Converts a Pango layout to a CairoPath starting at (x, y).
2200pub fn pango_layout_to_cairo_path(
2201    x: f64,
2202    y: f64,
2203    layout: &pango::Layout,
2204    gravity: pango::Gravity,
2205) -> Result<CairoPath, Box<InternalRenderingError>> {
2206    let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
2207    let cr = cairo::Context::new(&surface)?;
2208
2209    pango_layout_to_cairo(x, y, layout, gravity, &cr);
2210
2211    let cairo_path = cr.copy_path()?;
2212    Ok(CairoPath::from_cairo(cairo_path))
2213}
2214
2215// https://www.w3.org/TR/css-masking-1/#ClipPathElement
2216fn element_can_be_used_inside_clip_path(element: &Element) -> bool {
2217    use ElementData::*;
2218
2219    matches!(
2220        element.element_data,
2221        Circle(_)
2222            | Ellipse(_)
2223            | Line(_)
2224            | Path(_)
2225            | Polygon(_)
2226            | Polyline(_)
2227            | Rect(_)
2228            | Text(_)
2229            | Use(_)
2230    )
2231}
2232
2233#[derive(Debug)]
2234struct CompositingAffines {
2235    pub outside_temporary_surface: Transform,
2236    #[allow(unused)]
2237    pub initial: Transform,
2238    pub for_temporary_surface: Transform,
2239    pub compositing: Transform,
2240    pub for_snapshot: Transform,
2241}
2242
2243impl CompositingAffines {
2244    fn new(current: Transform, initial: Transform, cr_stack_depth: usize) -> CompositingAffines {
2245        let is_topmost_temporary_surface = cr_stack_depth == 0;
2246
2247        let initial_inverse = initial.invert().unwrap();
2248
2249        let outside_temporary_surface = if is_topmost_temporary_surface {
2250            current
2251        } else {
2252            current.post_transform(&initial_inverse)
2253        };
2254
2255        let (scale_x, scale_y) = initial.transform_distance(1.0, 1.0);
2256
2257        let for_temporary_surface = if is_topmost_temporary_surface {
2258            current
2259                .post_transform(&initial_inverse)
2260                .post_scale(scale_x, scale_y)
2261        } else {
2262            current
2263        };
2264
2265        let compositing = if is_topmost_temporary_surface {
2266            initial.pre_scale(1.0 / scale_x, 1.0 / scale_y)
2267        } else {
2268            Transform::identity()
2269        };
2270
2271        let for_snapshot = compositing.invert().unwrap();
2272
2273        CompositingAffines {
2274            outside_temporary_surface,
2275            initial,
2276            for_temporary_surface,
2277            compositing,
2278            for_snapshot,
2279        }
2280    }
2281}
2282
2283fn compute_stroke_and_fill_extents(
2284    cr: &cairo::Context,
2285    stroke: &Stroke,
2286    stroke_paint_source: &UserSpacePaintSource,
2287    initial_viewport: &Viewport,
2288) -> Result<PathExtents, Box<InternalRenderingError>> {
2289    // Dropping the precision of cairo's bezier subdivision, yielding 2x
2290    // _rendering_ time speedups, are these rather expensive operations
2291    // really needed here? */
2292    let backup_tolerance = cr.tolerance();
2293    cr.set_tolerance(1.0);
2294
2295    // Bounding box for fill
2296    //
2297    // Unlike the case for stroke, for fills we always compute the bounding box.
2298    // In GNOME we have SVGs for symbolic icons where each icon has a bounding
2299    // rectangle with no fill and no stroke, and inside it there are the actual
2300    // paths for the icon's shape.  We need to be able to compute the bounding
2301    // rectangle's extents, even when it has no fill nor stroke.
2302
2303    let (x0, y0, x1, y1) = cr.fill_extents()?;
2304    let fill_extents = if x0 != 0.0 || y0 != 0.0 || x1 != 0.0 || y1 != 0.0 {
2305        Some(Rect::new(x0, y0, x1, y1))
2306    } else {
2307        None
2308    };
2309
2310    // Bounding box for stroke
2311    //
2312    // When presented with a line width of 0, Cairo returns a
2313    // stroke_extents rectangle of (0, 0, 0, 0).  This would cause the
2314    // bbox to include a lone point at the origin, which is wrong, as a
2315    // stroke of zero width should not be painted, per
2316    // https://www.w3.org/TR/SVG2/painting.html#StrokeWidth
2317    //
2318    // So, see if the stroke width is 0 and just not include the stroke in the
2319    // bounding box if so.
2320
2321    let stroke_extents = if !stroke.width.approx_eq_cairo(0.0)
2322        && !matches!(stroke_paint_source, UserSpacePaintSource::None)
2323    {
2324        let backup_matrix = if stroke.non_scaling {
2325            let matrix = cr.matrix();
2326            cr.set_matrix(initial_viewport.transform.into());
2327            Some(matrix)
2328        } else {
2329            None
2330        };
2331        let (x0, y0, x1, y1) = cr.stroke_extents()?;
2332        if let Some(matrix) = backup_matrix {
2333            cr.set_matrix(matrix);
2334        }
2335        Some(Rect::new(x0, y0, x1, y1))
2336    } else {
2337        None
2338    };
2339
2340    // objectBoundingBox
2341
2342    let (x0, y0, x1, y1) = cr.path_extents()?;
2343    let path_extents = Some(Rect::new(x0, y0, x1, y1));
2344
2345    // restore tolerance
2346
2347    cr.set_tolerance(backup_tolerance);
2348
2349    Ok(PathExtents {
2350        path_only: path_extents,
2351        fill: fill_extents,
2352        stroke: stroke_extents,
2353    })
2354}
2355
2356fn compute_stroke_and_fill_box(
2357    cr: &cairo::Context,
2358    stroke: &Stroke,
2359    stroke_paint_source: &UserSpacePaintSource,
2360    initial_viewport: &Viewport,
2361) -> DrawResult {
2362    let extents =
2363        compute_stroke_and_fill_extents(cr, stroke, stroke_paint_source, initial_viewport)?;
2364
2365    let ink_rect = match (extents.fill, extents.stroke) {
2366        (None, None) => None,
2367        (Some(f), None) => Some(f),
2368        (None, Some(s)) => Some(s),
2369        (Some(f), Some(s)) => Some(f.union(&s)),
2370    };
2371
2372    let mut bbox = BoundingBox::new().with_transform(Transform::from(cr.matrix()));
2373
2374    if let Some(rect) = extents.path_only {
2375        bbox = bbox.with_rect(rect);
2376    }
2377
2378    if let Some(ink_rect) = ink_rect {
2379        bbox = bbox.with_ink_rect(ink_rect);
2380    }
2381
2382    Ok(Box::new(bbox))
2383}
2384
2385fn setup_cr_for_stroke(cr: &cairo::Context, stroke: &Stroke) {
2386    cr.set_line_width(stroke.width);
2387    cr.set_miter_limit(stroke.miter_limit.0);
2388    cr.set_line_cap(cairo::LineCap::from(stroke.line_cap));
2389    cr.set_line_join(cairo::LineJoin::from(stroke.line_join));
2390
2391    let total_length: f64 = stroke.dashes.iter().sum();
2392
2393    if total_length > 0.0 {
2394        cr.set_dash(&stroke.dashes, stroke.dash_offset);
2395    } else {
2396        cr.set_dash(&[], 0.0);
2397    }
2398}
2399
2400/// escape quotes and backslashes with backslash
2401fn escape_link_target(value: &str) -> Cow<'_, str> {
2402    let regex = {
2403        static REGEX: OnceLock<Regex> = OnceLock::new();
2404        REGEX.get_or_init(|| Regex::new(r"['\\]").unwrap())
2405    };
2406
2407    regex.replace_all(value, |caps: &Captures<'_>| {
2408        match caps.get(0).unwrap().as_str() {
2409            "'" => "\\'".to_owned(),
2410            "\\" => "\\\\".to_owned(),
2411            _ => unreachable!(),
2412        }
2413    })
2414}
2415
2416fn clip_to_rectangle(cr: &cairo::Context, transform: &ValidTransform, r: &Rect) {
2417    cr.set_matrix((*transform).into());
2418
2419    cr.rectangle(r.x0, r.y0, r.width(), r.height());
2420    cr.clip();
2421}
2422
2423impl From<SpreadMethod> for cairo::Extend {
2424    fn from(s: SpreadMethod) -> cairo::Extend {
2425        match s {
2426            SpreadMethod::Pad => cairo::Extend::Pad,
2427            SpreadMethod::Reflect => cairo::Extend::Reflect,
2428            SpreadMethod::Repeat => cairo::Extend::Repeat,
2429        }
2430    }
2431}
2432
2433impl From<StrokeLinejoin> for cairo::LineJoin {
2434    fn from(j: StrokeLinejoin) -> cairo::LineJoin {
2435        match j {
2436            StrokeLinejoin::Miter => cairo::LineJoin::Miter,
2437            StrokeLinejoin::Round => cairo::LineJoin::Round,
2438            StrokeLinejoin::Bevel => cairo::LineJoin::Bevel,
2439        }
2440    }
2441}
2442
2443impl From<StrokeLinecap> for cairo::LineCap {
2444    fn from(j: StrokeLinecap) -> cairo::LineCap {
2445        match j {
2446            StrokeLinecap::Butt => cairo::LineCap::Butt,
2447            StrokeLinecap::Round => cairo::LineCap::Round,
2448            StrokeLinecap::Square => cairo::LineCap::Square,
2449        }
2450    }
2451}
2452
2453impl From<MixBlendMode> for cairo::Operator {
2454    fn from(m: MixBlendMode) -> cairo::Operator {
2455        use cairo::Operator;
2456
2457        match m {
2458            MixBlendMode::Normal => Operator::Over,
2459            MixBlendMode::Multiply => Operator::Multiply,
2460            MixBlendMode::Screen => Operator::Screen,
2461            MixBlendMode::Overlay => Operator::Overlay,
2462            MixBlendMode::Darken => Operator::Darken,
2463            MixBlendMode::Lighten => Operator::Lighten,
2464            MixBlendMode::ColorDodge => Operator::ColorDodge,
2465            MixBlendMode::ColorBurn => Operator::ColorBurn,
2466            MixBlendMode::HardLight => Operator::HardLight,
2467            MixBlendMode::SoftLight => Operator::SoftLight,
2468            MixBlendMode::Difference => Operator::Difference,
2469            MixBlendMode::Exclusion => Operator::Exclusion,
2470            MixBlendMode::Hue => Operator::HslHue,
2471            MixBlendMode::Saturation => Operator::HslSaturation,
2472            MixBlendMode::Color => Operator::HslColor,
2473            MixBlendMode::Luminosity => Operator::HslLuminosity,
2474        }
2475    }
2476}
2477
2478impl From<ClipRule> for cairo::FillRule {
2479    fn from(c: ClipRule) -> cairo::FillRule {
2480        match c {
2481            ClipRule::NonZero => cairo::FillRule::Winding,
2482            ClipRule::EvenOdd => cairo::FillRule::EvenOdd,
2483        }
2484    }
2485}
2486
2487impl From<FillRule> for cairo::FillRule {
2488    fn from(f: FillRule) -> cairo::FillRule {
2489        match f {
2490            FillRule::NonZero => cairo::FillRule::Winding,
2491            FillRule::EvenOdd => cairo::FillRule::EvenOdd,
2492        }
2493    }
2494}
2495
2496impl From<ShapeRendering> for cairo::Antialias {
2497    fn from(sr: ShapeRendering) -> cairo::Antialias {
2498        match sr {
2499            ShapeRendering::Auto | ShapeRendering::GeometricPrecision => cairo::Antialias::Default,
2500            ShapeRendering::OptimizeSpeed | ShapeRendering::CrispEdges => cairo::Antialias::None,
2501        }
2502    }
2503}
2504
2505impl From<TextRendering> for cairo::Antialias {
2506    fn from(tr: TextRendering) -> cairo::Antialias {
2507        match tr {
2508            TextRendering::Auto
2509            | TextRendering::OptimizeLegibility
2510            | TextRendering::GeometricPrecision => cairo::Antialias::Default,
2511            TextRendering::OptimizeSpeed => cairo::Antialias::None,
2512        }
2513    }
2514}
2515
2516impl From<cairo::Matrix> for Transform {
2517    #[inline]
2518    fn from(m: cairo::Matrix) -> Self {
2519        Self::new_unchecked(m.xx(), m.yx(), m.xy(), m.yy(), m.x0(), m.y0())
2520    }
2521}
2522
2523impl From<ValidTransform> for cairo::Matrix {
2524    #[inline]
2525    fn from(t: ValidTransform) -> cairo::Matrix {
2526        cairo::Matrix::new(t.xx, t.yx, t.xy, t.yy, t.x0, t.y0)
2527    }
2528}
2529
2530/// Extents for a path in its current coordinate system.
2531///
2532/// Normally you'll want to convert this to a BoundingBox, which has knowledge about just
2533/// what that coordinate system is.
2534pub struct PathExtents {
2535    /// Extents of the "plain", unstroked path, or `None` if the path is empty.
2536    pub path_only: Option<Rect>,
2537
2538    /// Extents of just the fill, or `None` if the path is empty.
2539    pub fill: Option<Rect>,
2540
2541    /// Extents for the stroked path, or `None` if the path is empty or zero-width.
2542    pub stroke: Option<Rect>,
2543}