Skip to main content

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