1
//! The main context structure which drives the drawing process.
2

            
3
use float_cmp::approx_eq;
4
use glib::translate::*;
5
use pango::ffi::PangoMatrix;
6
use pango::prelude::FontMapExt;
7
use regex::{Captures, Regex};
8
use std::cell::RefCell;
9
use std::convert::TryFrom;
10
use std::f64::consts::*;
11
use std::rc::Rc;
12
use std::{borrow::Cow, sync::OnceLock};
13

            
14
use crate::accept_language::UserLanguage;
15
use crate::bbox::BoundingBox;
16
use crate::color::color_to_rgba;
17
use crate::coord_units::CoordUnits;
18
use crate::document::{AcquiredNodes, NodeId};
19
use crate::dpi::Dpi;
20
use crate::element::{Element, ElementData};
21
use crate::error::{AcquireError, ImplementationLimit, InternalRenderingError};
22
use crate::filters::{self, FilterSpec};
23
use crate::float_eq_cairo::ApproxEqCairo;
24
use crate::gradient::{GradientVariant, SpreadMethod, UserSpaceGradient};
25
use crate::layout::{
26
    Filter, Group, Image, Layer, LayerKind, LayoutViewport, Shape, StackingContext, Stroke, Text,
27
    TextSpan,
28
};
29
use crate::length::*;
30
use crate::marker;
31
use crate::node::{CascadedValues, Node, NodeBorrow, NodeDraw};
32
use crate::paint_server::{PaintSource, UserSpacePaintSource};
33
use crate::path_builder::*;
34
use crate::pattern::UserSpacePattern;
35
use crate::properties::{
36
    ClipRule, ComputedValues, FillRule, ImageRendering, MaskType, MixBlendMode, Opacity,
37
    PaintTarget, ShapeRendering, StrokeLinecap, StrokeLinejoin, TextRendering,
38
};
39
use crate::rect::{rect_to_transform, IRect, Rect};
40
use crate::rsvg_log;
41
use crate::session::Session;
42
use crate::surface_utils::shared_surface::{
43
    ExclusiveImageSurface, Interpolation, SharedImageSurface, SurfaceType,
44
};
45
use crate::transform::{Transform, ValidTransform};
46
use crate::unit_interval::UnitInterval;
47
use crate::viewbox::ViewBox;
48
use crate::{borrow_element_as, is_element_of_type};
49

            
50
/// Opaque font options for a DrawingCtx.
51
///
52
/// This is used for DrawingCtx::create_pango_context.
53
pub struct FontOptions {
54
    options: cairo::FontOptions,
55
}
56

            
57
/// Set path on the cairo context, or clear it.
58
/// This helper object keeps track whether the path has been set already,
59
/// so that it isn't recalculated every so often.
60
struct PathHelper<'a> {
61
    cr: &'a cairo::Context,
62
    transform: ValidTransform,
63
    path: &'a Path,
64
    is_square_linecap: bool,
65
    has_path: Option<bool>,
66
}
67

            
68
impl<'a> PathHelper<'a> {
69
948187
    pub fn new(
70
        cr: &'a cairo::Context,
71
        transform: ValidTransform,
72
        path: &'a Path,
73
        linecap: StrokeLinecap,
74
    ) -> Self {
75
948187
        PathHelper {
76
            cr,
77
            transform,
78
            path,
79
948187
            is_square_linecap: linecap == StrokeLinecap::Square,
80
948187
            has_path: None,
81
        }
82
948187
    }
83

            
84
2844112
    pub fn set(&mut self) -> Result<(), InternalRenderingError> {
85
2844112
        match self.has_path {
86
            Some(false) | None => {
87
948703
                self.has_path = Some(true);
88
948703
                self.cr.set_matrix(self.transform.into());
89
948703
                self.path.to_cairo(self.cr, self.is_square_linecap)
90
            }
91
1895409
            Some(true) => Ok(()),
92
        }
93
2844112
    }
94

            
95
1896109
    pub fn unset(&mut self) {
96
1896109
        match self.has_path {
97
            Some(true) | None => {
98
948659
                self.has_path = Some(false);
99
948659
                self.cr.new_path();
100
            }
101
            Some(false) => {}
102
        }
103
1896109
    }
104
}
105

            
106
/// Holds the size of the current viewport in the user's coordinate system.
107
105594
#[derive(Clone)]
108
pub struct Viewport {
109
52797
    pub dpi: Dpi,
110

            
111
    /// Corners of the current coordinate space.
112
52797
    pub vbox: ViewBox,
113

            
114
    /// The viewport's coordinate system, or "user coordinate system" in SVG terms.
115
52797
    transform: Transform,
116
}
117

            
118
impl Viewport {
119
    /// FIXME: this is just used in Handle::with_height_to_user(), and in length.rs's test suite.
120
    /// Find a way to do this without involving a default identity transform.
121
166
    pub fn new(dpi: Dpi, view_box_width: f64, view_box_height: f64) -> Viewport {
122
166
        Viewport {
123
            dpi,
124
166
            vbox: ViewBox::from(Rect::from_size(view_box_width, view_box_height)),
125
166
            transform: Default::default(),
126
        }
127
166
    }
128

            
129
    /// Creates a new viewport suitable for a certain kind of units.
130
    ///
131
    /// For `objectBoundingBox`, CSS lengths which are in percentages
132
    /// refer to the size of the current viewport.  Librsvg implements
133
    /// that by keeping the same current transformation matrix, and
134
    /// setting a viewport size of (1.0, 1.0).
135
    ///
136
    /// For `userSpaceOnUse`, we just duplicate the current viewport,
137
    /// since that kind of units means to use the current coordinate
138
    /// system unchanged.
139
501224
    pub fn with_units(&self, units: CoordUnits) -> Viewport {
140
501224
        match units {
141
404
            CoordUnits::ObjectBoundingBox => Viewport {
142
404
                dpi: self.dpi,
143
404
                vbox: ViewBox::from(Rect::from_size(1.0, 1.0)),
144
404
                transform: self.transform,
145
404
            },
146

            
147
500820
            CoordUnits::UserSpaceOnUse => Viewport {
148
500820
                dpi: self.dpi,
149
500820
                vbox: self.vbox,
150
500820
                transform: self.transform,
151
500820
            },
152
        }
153
501224
    }
154

            
155
    /// Returns a viewport with a new size for normalizing `Length` values.
156
193
    pub fn with_view_box(&self, width: f64, height: f64) -> Viewport {
157
193
        Viewport {
158
193
            dpi: self.dpi,
159
193
            vbox: ViewBox::from(Rect::from_size(width, height)),
160
193
            transform: self.transform,
161
        }
162
193
    }
163
}
164

            
165
pub struct DrawingCtx {
166
    session: Session,
167

            
168
    initial_viewport: Viewport,
169

            
170
    dpi: Dpi,
171

            
172
    cr_stack: Rc<RefCell<Vec<cairo::Context>>>,
173
    cr: cairo::Context,
174

            
175
    user_language: UserLanguage,
176

            
177
    drawsub_stack: Vec<Node>,
178

            
179
    svg_nesting: SvgNesting,
180

            
181
    measuring: bool,
182
    testing: bool,
183
}
184

            
185
pub enum DrawingMode {
186
    LimitToStack { node: Node, root: Node },
187

            
188
    OnlyNode(Node),
189
}
190

            
191
/// Whether an SVG document is being rendered standalone or referenced from an `<image>` element.
192
///
193
/// Normally, the coordinate system used when rendering a toplevel SVG is determined from the
194
/// initial viewport and the `<svg>` element's `viewBox` and `preserveAspectRatio` attributes.
195
/// However, when an SVG document is referenced from an `<image>` element, as in `<image href="foo.svg"/>`,
196
/// its `preserveAspectRatio` needs to be ignored so that the one from the `<image>` element can
197
/// be used instead.  This lets the parent document (the one with the `<image>` element) specify
198
/// how it wants the child SVG to be scaled into the viewport.
199
#[derive(Copy, Clone)]
200
pub enum SvgNesting {
201
    Standalone,
202
    ReferencedFromImageElement,
203
}
204

            
205
/// The toplevel drawing routine.
206
///
207
/// This creates a DrawingCtx internally and starts drawing at the specified `node`.
208
1105
pub fn draw_tree(
209
    session: Session,
210
    mode: DrawingMode,
211
    cr: &cairo::Context,
212
    viewport_rect: Rect,
213
    user_language: &UserLanguage,
214
    dpi: Dpi,
215
    svg_nesting: SvgNesting,
216
    measuring: bool,
217
    testing: bool,
218
    acquired_nodes: &mut AcquiredNodes<'_>,
219
) -> Result<BoundingBox, InternalRenderingError> {
220
2218
    let (drawsub_stack, node) = match mode {
221
1087
        DrawingMode::LimitToStack { node, root } => (node.ancestors().collect(), root),
222

            
223
18
        DrawingMode::OnlyNode(node) => (Vec::new(), node),
224
    };
225

            
226
1113
    let cascaded = CascadedValues::new_from_node(&node);
227

            
228
    // Preserve the user's transform and use it for the outermost bounding box.  All bounds/extents
229
    // will be converted to this transform in the end.
230
1105
    let user_transform = Transform::from(cr.matrix());
231
1104
    let mut user_bbox = BoundingBox::new().with_transform(user_transform);
232

            
233
    // https://www.w3.org/TR/SVG2/coords.html#InitialCoordinateSystem
234
    //
235
    // "For the outermost svg element, the SVG user agent must
236
    // determine an initial viewport coordinate system and an
237
    // initial user coordinate system such that the two
238
    // coordinates systems are identical. The origin of both
239
    // coordinate systems must be at the origin of the SVG
240
    // viewport."
241
    //
242
    // "... the initial viewport coordinate system (and therefore
243
    // the initial user coordinate system) must have its origin at
244
    // the top/left of the viewport"
245

            
246
    // Translate so (0, 0) is at the viewport's upper-left corner.
247
1105
    let transform = user_transform.pre_translate(viewport_rect.x0, viewport_rect.y0);
248

            
249
    // Here we exit immediately if the transform is not valid, since we are in the
250
    // toplevel drawing function.  Downstream cases would simply not render the current
251
    // element and ignore the error.
252
2209
    let valid_transform = ValidTransform::try_from(transform)?;
253
1104
    cr.set_matrix(valid_transform.into());
254

            
255
    // Per the spec, so the viewport has (0, 0) as upper-left.
256
1108
    let viewport_rect = viewport_rect.translate((-viewport_rect.x0, -viewport_rect.y0));
257
1105
    let initial_viewport = Viewport {
258
        dpi,
259
1105
        vbox: ViewBox::from(viewport_rect),
260
        transform,
261
    };
262

            
263
1105
    let mut draw_ctx = DrawingCtx::new(
264
1105
        session,
265
        cr,
266
        &initial_viewport,
267
1105
        user_language.clone(),
268
        dpi,
269
        svg_nesting,
270
        measuring,
271
        testing,
272
1105
        drawsub_stack,
273
1105
    );
274

            
275
1105
    let content_bbox = draw_ctx.draw_node_from_stack(
276
        &node,
277
        acquired_nodes,
278
        &cascaded,
279
        &initial_viewport,
280
        false,
281
1
    )?;
282

            
283
1104
    user_bbox.insert(&content_bbox);
284

            
285
1104
    Ok(user_bbox)
286
1105
}
287

            
288
2009075
pub fn with_saved_cr<O, F>(cr: &cairo::Context, f: F) -> Result<O, InternalRenderingError>
289
where
290
    F: FnOnce() -> Result<O, InternalRenderingError>,
291
{
292
2009075
    cr.save()?;
293
2008028
    match f() {
294
2007684
        Ok(o) => {
295
4017767
            cr.restore()?;
296
2008138
            Ok(o)
297
2008138
        }
298

            
299
37
        Err(e) => Err(e),
300
    }
301
2008175
}
302

            
303
impl Drop for DrawingCtx {
304
51883
    fn drop(&mut self) {
305
51883
        self.cr_stack.borrow_mut().pop();
306
51883
    }
307
}
308

            
309
const CAIRO_TAG_LINK: &str = "Link";
310

            
311
impl DrawingCtx {
312
1104
    fn new(
313
        session: Session,
314
        cr: &cairo::Context,
315
        initial_viewport: &Viewport,
316
        user_language: UserLanguage,
317
        dpi: Dpi,
318
        svg_nesting: SvgNesting,
319
        measuring: bool,
320
        testing: bool,
321
        drawsub_stack: Vec<Node>,
322
    ) -> DrawingCtx {
323
1102
        DrawingCtx {
324
1104
            session,
325
1104
            initial_viewport: initial_viewport.clone(),
326
            dpi,
327
1104
            cr_stack: Rc::new(RefCell::new(Vec::new())),
328
1102
            cr: cr.clone(),
329
1102
            user_language,
330
1102
            drawsub_stack,
331
            svg_nesting,
332
            measuring,
333
            testing,
334
        }
335
1102
    }
336

            
337
    /// Copies a `DrawingCtx` for temporary use on a Cairo surface.
338
    ///
339
    /// `DrawingCtx` maintains state using during the drawing process, and sometimes we
340
    /// would like to use that same state but on a different Cairo surface and context
341
    /// than the ones being used on `self`.  This function copies the `self` state into a
342
    /// new `DrawingCtx`, and ties the copied one to the supplied `cr`.
343
50778
    fn nested(&self, cr: cairo::Context) -> DrawingCtx {
344
50778
        let cr_stack = self.cr_stack.clone();
345

            
346
50778
        cr_stack.borrow_mut().push(self.cr.clone());
347

            
348
50778
        DrawingCtx {
349
50778
            session: self.session.clone(),
350
50778
            initial_viewport: self.initial_viewport.clone(),
351
50778
            dpi: self.dpi,
352
50778
            cr_stack,
353
50778
            cr,
354
50778
            user_language: self.user_language.clone(),
355
50778
            drawsub_stack: self.drawsub_stack.clone(),
356
50778
            svg_nesting: self.svg_nesting,
357
50778
            measuring: self.measuring,
358
50778
            testing: self.testing,
359
        }
360
50778
    }
361

            
362
6907149
    pub fn session(&self) -> &Session {
363
6907149
        &self.session
364
6907149
    }
365

            
366
33
    pub fn user_language(&self) -> &UserLanguage {
367
33
        &self.user_language
368
33
    }
369

            
370
1742
    pub fn toplevel_viewport(&self) -> Rect {
371
1742
        *self.initial_viewport.vbox
372
1742
    }
373

            
374
    /// Gets the transform that will be used on the target surface,
375
    /// whether using an isolated stacking context or not.
376
    ///
377
    /// This is only used in the text code, and we should probably try
378
    /// to remove it.
379
949022
    pub fn get_transform_for_stacking_ctx(
380
        &self,
381
        stacking_ctx: &StackingContext,
382
        clipping: bool,
383
    ) -> Result<ValidTransform, InternalRenderingError> {
384
949449
        if stacking_ctx.should_isolate() && !clipping {
385
427
            let affines = CompositingAffines::new(
386
427
                *self.get_transform(),
387
427
                self.initial_viewport.transform,
388
427
                self.cr_stack.borrow().len(),
389
427
            );
390

            
391
949449
            Ok(ValidTransform::try_from(affines.for_temporary_surface)?)
392
        } else {
393
948595
            Ok(self.get_transform())
394
        }
395
949022
    }
396

            
397
1120
    pub fn svg_nesting(&self) -> SvgNesting {
398
1120
        self.svg_nesting
399
1120
    }
400

            
401
1084
    pub fn is_measuring(&self) -> bool {
402
1084
        self.measuring
403
1084
    }
404

            
405
14
    pub fn is_testing(&self) -> bool {
406
14
        self.testing
407
14
    }
408

            
409
9463589
    pub fn get_transform(&self) -> ValidTransform {
410
9463589
        let t = Transform::from(self.cr.matrix());
411
9463589
        ValidTransform::try_from(t)
412
            .expect("Cairo should already have checked that its current transform is valid")
413
9463589
    }
414

            
415
3514340
    pub fn empty_bbox(&self) -> BoundingBox {
416
3514340
        BoundingBox::new().with_transform(*self.get_transform())
417
3514340
    }
418

            
419
740
    fn size_for_temporary_surface(&self) -> (i32, i32) {
420
740
        let rect = self.toplevel_viewport();
421

            
422
740
        let (viewport_width, viewport_height) = (rect.width(), rect.height());
423

            
424
740
        let (width, height) = self
425
            .initial_viewport
426
            .transform
427
            .transform_distance(viewport_width, viewport_height);
428

            
429
        // We need a size in whole pixels, so use ceil() to ensure the whole viewport fits
430
        // into the temporary surface.
431
740
        (width.ceil().abs() as i32, height.ceil().abs() as i32)
432
740
    }
433

            
434
360
    pub fn create_surface_for_toplevel_viewport(
435
        &self,
436
    ) -> Result<cairo::ImageSurface, InternalRenderingError> {
437
360
        let (w, h) = self.size_for_temporary_surface();
438

            
439
360
        Ok(cairo::ImageSurface::create(cairo::Format::ARgb32, w, h)?)
440
360
    }
441

            
442
380
    fn create_similar_surface_for_toplevel_viewport(
443
        &self,
444
        surface: &cairo::Surface,
445
    ) -> Result<cairo::Surface, InternalRenderingError> {
446
380
        let (w, h) = self.size_for_temporary_surface();
447

            
448
380
        Ok(cairo::Surface::create_similar(
449
            surface,
450
380
            cairo::Content::ColorAlpha,
451
            w,
452
            h,
453
        )?)
454
380
    }
455

            
456
    /// Creates a new coordinate space inside a viewport and sets a clipping rectangle.
457
    ///
458
    /// Note that this actually changes the `draw_ctx.cr`'s transformation to match
459
    /// the new coordinate space, but the old one is not restored after the
460
    /// result's `Viewport` is dropped.  Thus, this function must be called
461
    /// inside `with_saved_cr` or `draw_ctx.with_discrete_layer`.
462
1239
    pub fn push_new_viewport(
463
        &self,
464
        current_viewport: &Viewport,
465
        layout_viewport: &LayoutViewport,
466
    ) -> Option<Viewport> {
467
        let LayoutViewport {
468
1239
            geometry,
469
1239
            vbox,
470
1239
            preserve_aspect_ratio,
471
1239
            overflow,
472
        } = *layout_viewport;
473

            
474
1239
        if !overflow.overflow_allowed() || (vbox.is_some() && preserve_aspect_ratio.is_slice()) {
475
145
            clip_to_rectangle(&self.cr, &geometry);
476
        }
477

            
478
3711
        preserve_aspect_ratio
479
1237
            .viewport_to_viewbox_transform(vbox, &geometry)
480
1239
            .unwrap_or_else(|_e| {
481
2
                match vbox {
482
                    None => unreachable!(
483
                        "viewport_to_viewbox_transform only returns errors when vbox != None"
484
                    ),
485
2
                    Some(v) => {
486
2
                        rsvg_log!(
487
2
                            self.session,
488
                            "ignoring viewBox ({}, {}, {}, {}) since it is not usable",
489
                            v.x0,
490
                            v.y0,
491
                            v.width(),
492
                            v.height()
493
                        );
494
                    }
495
                }
496
2
                None
497
2
            })
498
2474
            .map(|t| {
499
1237
                self.cr.transform(t.into());
500

            
501
1237
                Viewport {
502
1237
                    dpi: self.dpi,
503
1237
                    vbox: vbox.unwrap_or(current_viewport.vbox),
504
1237
                    transform: current_viewport.transform.post_transform(&t),
505
                }
506
1237
            })
507
1237
    }
508

            
509
2006736
    fn clip_to_node(
510
        &mut self,
511
        clip_node: &Option<Node>,
512
        acquired_nodes: &mut AcquiredNodes<'_>,
513
        viewport: &Viewport,
514
        bbox: &BoundingBox,
515
    ) -> Result<(), InternalRenderingError> {
516
2006736
        if clip_node.is_none() {
517
2006607
            return Ok(());
518
        }
519

            
520
129
        let node = clip_node.as_ref().unwrap();
521
129
        let units = borrow_element_as!(node, ClipPath).get_units();
522

            
523
129
        if let Ok(transform) = rect_to_transform(&bbox.rect, units) {
524
127
            let cascaded = CascadedValues::new_from_node(node);
525
127
            let values = cascaded.get();
526

            
527
43
            let node_transform = values.transform().post_transform(&transform);
528
2006779
            let transform_for_clip = ValidTransform::try_from(node_transform)?;
529

            
530
42
            let orig_transform = self.get_transform();
531
42
            self.cr.transform(transform_for_clip.into());
532

            
533
168
            for child in node.children().filter(|c| {
534
126
                c.is_element() && element_can_be_used_inside_clip_path(&c.borrow_element())
535
126
            }) {
536
42
                child.draw(
537
                    acquired_nodes,
538
42
                    &CascadedValues::clone_with_node(&cascaded, &child),
539
                    viewport,
540
                    self,
541
                    true,
542
42
                )?;
543
42
            }
544

            
545
42
            self.cr.clip();
546

            
547
42
            self.cr.set_matrix(orig_transform.into());
548
43
        }
549

            
550
44
        Ok(())
551
2006652
    }
552

            
553
60
    fn generate_cairo_mask(
554
        &mut self,
555
        mask_node: &Node,
556
        viewport: &Viewport,
557
        transform: Transform,
558
        bbox: &BoundingBox,
559
        acquired_nodes: &mut AcquiredNodes<'_>,
560
    ) -> Result<Option<cairo::ImageSurface>, InternalRenderingError> {
561
60
        if bbox.rect.is_none() {
562
            // The node being masked is empty / doesn't have a
563
            // bounding box, so there's nothing to mask!
564
1
            return Ok(None);
565
        }
566

            
567
59
        let _mask_acquired = match acquired_nodes.acquire_ref(mask_node) {
568
59
            Ok(n) => n,
569

            
570
            Err(AcquireError::CircularReference(_)) => {
571
                rsvg_log!(self.session, "circular reference in element {}", mask_node);
572
                return Ok(None);
573
            }
574

            
575
            _ => unreachable!(),
576
59
        };
577

            
578
59
        let mask_element = mask_node.borrow_element();
579
59
        let mask = borrow_element_as!(mask_node, Mask);
580

            
581
59
        let bbox_rect = bbox.rect.as_ref().unwrap();
582

            
583
59
        let cascaded = CascadedValues::new_from_node(mask_node);
584
59
        let values = cascaded.get();
585

            
586
59
        let mask_units = mask.get_units();
587

            
588
        let mask_rect = {
589
59
            let params = NormalizeParams::new(values, &viewport.with_units(mask_units));
590
59
            mask.get_rect(&params)
591
        };
592

            
593
59
        let mask_transform = values.transform().post_transform(&transform);
594
59
        let transform_for_mask = ValidTransform::try_from(mask_transform)?;
595

            
596
59
        let mask_content_surface = self.create_surface_for_toplevel_viewport()?;
597

            
598
        // Use a scope because mask_cr needs to release the
599
        // reference to the surface before we access the pixels
600
        {
601
59
            let mask_cr = cairo::Context::new(&mask_content_surface)?;
602
59
            mask_cr.set_matrix(transform_for_mask.into());
603

            
604
59
            let bbtransform = Transform::new_unchecked(
605
59
                bbox_rect.width(),
606
                0.0,
607
                0.0,
608
59
                bbox_rect.height(),
609
59
                bbox_rect.x0,
610
59
                bbox_rect.y0,
611
            );
612

            
613
59
            let clip_rect = if mask_units == CoordUnits::ObjectBoundingBox {
614
10
                bbtransform.transform_rect(&mask_rect)
615
            } else {
616
49
                mask_rect
617
            };
618

            
619
59
            clip_to_rectangle(&mask_cr, &clip_rect);
620

            
621
59
            if mask.get_content_units() == CoordUnits::ObjectBoundingBox {
622
5
                if bbox_rect.is_empty() {
623
1
                    return Ok(None);
624
                }
625
64
                mask_cr.transform(ValidTransform::try_from(bbtransform)?.into());
626
            }
627

            
628
58
            let mask_viewport = viewport.with_units(mask.get_content_units());
629

            
630
58
            let mut mask_draw_ctx = self.nested(mask_cr);
631

            
632
58
            let stacking_ctx = StackingContext::new(
633
58
                self.session(),
634
                acquired_nodes,
635
58
                &mask_element,
636
58
                Transform::identity(),
637
58
                None,
638
                values,
639
            );
640

            
641
58
            rsvg_log!(self.session, "(mask {}", mask_element);
642

            
643
58
            let res = mask_draw_ctx.with_discrete_layer(
644
                &stacking_ctx,
645
                acquired_nodes,
646
                &mask_viewport,
647
58
                None,
648
                false,
649
116
                &mut |an, dc, new_viewport| {
650
58
                    mask_node.draw_children(an, &cascaded, new_viewport, dc, false)
651
58
                },
652
58
            );
653

            
654
58
            rsvg_log!(self.session, ")");
655

            
656
58
            res?;
657
59
        }
658

            
659
58
        let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
660

            
661
58
        let mask_result = match values.mask_type() {
662
57
            MaskType::Luminance => tmp.to_luminance_mask()?,
663
1
            MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
664
        };
665

            
666
58
        let mask = mask_result.into_image_surface()?;
667

            
668
58
        Ok(Some(mask))
669
60
    }
670

            
671
2005396
    pub fn with_discrete_layer(
672
        &mut self,
673
        stacking_ctx: &StackingContext,
674
        acquired_nodes: &mut AcquiredNodes<'_>,
675
        viewport: &Viewport,
676
        layout_viewport: Option<LayoutViewport>,
677
        clipping: bool,
678
        draw_fn: &mut dyn FnMut(
679
            &mut AcquiredNodes<'_>,
680
            &mut DrawingCtx,
681
            &Viewport,
682
        ) -> Result<BoundingBox, InternalRenderingError>,
683
    ) -> Result<BoundingBox, InternalRenderingError> {
684
2005396
        let stacking_ctx_transform = ValidTransform::try_from(stacking_ctx.transform)?;
685

            
686
2005395
        let orig_transform = self.get_transform();
687
2005395
        self.cr.transform(stacking_ctx_transform.into());
688

            
689
2005395
        let res = if clipping {
690
40
            if let Some(layout_viewport) = layout_viewport.as_ref() {
691
                // FIXME: here we ignore the Some() result of push_new_viewport().  We do that because
692
                // the returned one is just a copy of the one that got passeed in, but with a changed
693
                // transform.  However, we are in fact not using that transform anywhere!
694
                //
695
                // In case push_new_viewport() returns None, we just don't draw anything.
696
                //
697
                // Note that push_new_viewport() changes the cr's transform.  However it will be restored
698
                // at the end of this function with set_matrix.
699
                if let Some(new_viewport) = self.push_new_viewport(viewport, layout_viewport) {
700
                    draw_fn(acquired_nodes, self, &new_viewport)
701
                } else {
702
                    Ok(self.empty_bbox())
703
                }
704
            } else {
705
40
                draw_fn(acquired_nodes, self, viewport)
706
            }
707
        } else {
708
4009496
            with_saved_cr(&self.cr.clone(), || {
709
2004141
                if let Some(ref link_target) = stacking_ctx.link_target {
710
2
                    self.link_tag_begin(link_target);
711
                }
712

            
713
2004141
                let Opacity(UnitInterval(opacity)) = stacking_ctx.opacity;
714

            
715
2004141
                let affine_at_start = self.get_transform();
716

            
717
2004141
                if let Some(rect) = stacking_ctx.clip_rect.as_ref() {
718
184
                    clip_to_rectangle(&self.cr, rect);
719
                }
720

            
721
                // Here we are clipping in user space, so the bbox doesn't matter
722
4008282
                self.clip_to_node(
723
2004141
                    &stacking_ctx.clip_in_user_space,
724
2004141
                    acquired_nodes,
725
2004141
                    viewport,
726
2004141
                    &self.empty_bbox(),
727
1
                )?;
728

            
729
2004140
                let should_isolate = stacking_ctx.should_isolate();
730

            
731
2004140
                let res = if should_isolate {
732
                    // Compute our assortment of affines
733

            
734
681
                    let affines = CompositingAffines::new(
735
681
                        *affine_at_start,
736
681
                        self.initial_viewport.transform,
737
681
                        self.cr_stack.borrow().len(),
738
681
                    );
739

            
740
                    // Create temporary surface and its cr
741

            
742
681
                    let cr = match stacking_ctx.filter {
743
380
                        None => cairo::Context::new(
744
760
                            &self
745
380
                                .create_similar_surface_for_toplevel_viewport(&self.cr.target())?,
746
380
                        )?,
747
                        Some(_) => {
748
301
                            cairo::Context::new(self.create_surface_for_toplevel_viewport()?)?
749
301
                        }
750
                    };
751

            
752
681
                    cr.set_matrix(ValidTransform::try_from(affines.for_temporary_surface)?.into());
753

            
754
681
                    let (source_surface, mut res, bbox) = {
755
681
                        let mut temporary_draw_ctx = self.nested(cr.clone());
756

            
757
                        // Draw!
758

            
759
1362
                        let res = with_saved_cr(&cr, || {
760
681
                            if let Some(layout_viewport) = layout_viewport.as_ref() {
761
                                // FIXME: here we ignore the Some() result of push_new_viewport().  We do that because
762
                                // the returned one is just a copy of the one that got passeed in, but with a changed
763
                                // transform.  However, we are in fact not using that transform anywhere!
764
                                //
765
                                // In case push_new_viewport() returns None, we just don't draw anything.
766
62
                                if let Some(new_viewport) =
767
62
                                    temporary_draw_ctx.push_new_viewport(viewport, layout_viewport)
768
                                {
769
62
                                    draw_fn(acquired_nodes, &mut temporary_draw_ctx, &new_viewport)
770
                                } else {
771
                                    Ok(self.empty_bbox())
772
                                }
773
                            } else {
774
619
                                draw_fn(acquired_nodes, &mut temporary_draw_ctx, viewport)
775
                            }
776
1362
                        });
777

            
778
681
                        let bbox = if let Ok(ref bbox) = res {
779
681
                            *bbox
780
                        } else {
781
                            BoundingBox::new().with_transform(affines.for_temporary_surface)
782
                        };
783

            
784
1362
                        if let Some(ref filter) = stacking_ctx.filter {
785
301
                            let surface_to_filter = SharedImageSurface::copy_from_surface(
786
301
                                &cairo::ImageSurface::try_from(temporary_draw_ctx.cr.target())
787
                                    .unwrap(),
788
301
                            )?;
789

            
790
                            let stroke_paint_source =
791
301
                                Rc::new(filter.stroke_paint_source.to_user_space(
792
                                    &bbox.rect,
793
301
                                    viewport,
794
301
                                    &filter.normalize_values,
795
301
                                ));
796
                            let fill_paint_source =
797
301
                                Rc::new(filter.fill_paint_source.to_user_space(
798
                                    &bbox.rect,
799
301
                                    viewport,
800
301
                                    &filter.normalize_values,
801
301
                                ));
802

            
803
                            // Filter functions (like "blend()", not the <filter> element) require
804
                            // being resolved in userSpaceonUse units, since that is the default
805
                            // for primitive_units.  So, get the corresponding NormalizeParams
806
                            // here and pass them down.
807
301
                            let user_space_params = NormalizeParams::from_values(
808
301
                                &filter.normalize_values,
809
301
                                &viewport.with_units(CoordUnits::UserSpaceOnUse),
810
                            );
811

            
812
301
                            let filtered_surface = temporary_draw_ctx
813
                                .run_filters(
814
301
                                    viewport,
815
301
                                    surface_to_filter,
816
                                    filter,
817
301
                                    acquired_nodes,
818
301
                                    &stacking_ctx.element_name,
819
                                    &user_space_params,
820
301
                                    stroke_paint_source,
821
301
                                    fill_paint_source,
822
301
                                    bbox,
823
301
                                )?
824
                                .into_image_surface()?;
825

            
826
301
                            let generic_surface: &cairo::Surface = &filtered_surface; // deref to Surface
827

            
828
301
                            (generic_surface.clone(), res, bbox)
829
301
                        } else {
830
380
                            (temporary_draw_ctx.cr.target(), res, bbox)
831
                        }
832
681
                    };
833

            
834
                    // Set temporary surface as source
835

            
836
1362
                    self.cr
837
681
                        .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
838
681
                    self.cr.set_source_surface(&source_surface, 0.0, 0.0)?;
839

            
840
                    // Clip
841

            
842
1362
                    self.cr.set_matrix(
843
681
                        ValidTransform::try_from(affines.outside_temporary_surface)?.into(),
844
                    );
845
1362
                    self.clip_to_node(
846
681
                        &stacking_ctx.clip_in_object_space,
847
681
                        acquired_nodes,
848
681
                        viewport,
849
                        &bbox,
850
                    )?;
851

            
852
                    // Mask
853

            
854
681
                    if let Some(ref mask_node) = stacking_ctx.mask {
855
120
                        res = res.and_then(|bbox| {
856
240
                            self.generate_cairo_mask(
857
60
                                mask_node,
858
60
                                viewport,
859
60
                                affines.for_temporary_surface,
860
                                &bbox,
861
60
                                acquired_nodes,
862
                            )
863
120
                            .and_then(|mask_surf| {
864
60
                                if let Some(surf) = mask_surf {
865
58
                                    self.cr.push_group();
866

            
867
116
                                    self.cr.set_matrix(
868
58
                                        ValidTransform::try_from(affines.compositing)?.into(),
869
                                    );
870
58
                                    self.cr.mask_surface(&surf, 0.0, 0.0)?;
871

            
872
118
                                    Ok(self.cr.pop_group_to_source()?)
873
58
                                } else {
874
2
                                    Ok(())
875
                                }
876
60
                            })
877
120
                            .map(|_: ()| bbox)
878
60
                        });
879
                    }
880

            
881
                    {
882
                        // Composite the temporary surface
883

            
884
1362
                        self.cr
885
681
                            .set_matrix(ValidTransform::try_from(affines.compositing)?.into());
886
681
                        self.cr.set_operator(stacking_ctx.mix_blend_mode.into());
887

            
888
681
                        if opacity < 1.0 {
889
313
                            self.cr.paint_with_alpha(opacity)?;
890
                        } else {
891
368
                            self.cr.paint()?;
892
                        }
893
                    }
894

            
895
681
                    self.cr.set_matrix(affine_at_start.into());
896
681
                    res
897
2004140
                } else if let Some(layout_viewport) = layout_viewport.as_ref() {
898
                    // FIXME: here we ignore the Some() result of push_new_viewport().  We do that because
899
                    // the returned one is just a copy of the one that got passeed in, but with a changed
900
                    // transform.  However, we are in fact not using that transform anywhere!
901
                    //
902
                    // In case push_new_viewport() returns None, we just don't draw anything.
903
                    //
904
                    // Note that push_new_viewport() changes the cr's transform.  However it will be restored
905
                    // at the end of this function with set_matrix.
906
1178
                    if let Some(new_viewport) = self.push_new_viewport(viewport, layout_viewport) {
907
1176
                        draw_fn(acquired_nodes, self, &new_viewport)
908
                    } else {
909
2
                        self.cr.set_matrix(orig_transform.into());
910
2
                        Ok(self.empty_bbox())
911
                    }
912
                } else {
913
2002281
                    draw_fn(acquired_nodes, self, viewport)
914
                };
915

            
916
2004140
                if stacking_ctx.link_target.is_some() {
917
2
                    self.link_tag_end();
918
                }
919

            
920
2004140
                res
921
2004141
            })
922
2005355
        };
923

            
924
2005395
        self.cr.set_matrix(orig_transform.into());
925
2005395
        res
926
2005396
    }
927

            
928
    /// Run the drawing function with the specified opacity
929
50026
    fn with_alpha(
930
        &mut self,
931
        opacity: UnitInterval,
932
        draw_fn: &mut dyn FnMut(&mut DrawingCtx) -> Result<BoundingBox, InternalRenderingError>,
933
    ) -> Result<BoundingBox, InternalRenderingError> {
934
50026
        let res;
935
50026
        let UnitInterval(o) = opacity;
936
100051
        if o < 1.0 {
937
1
            self.cr.push_group();
938
1
            res = draw_fn(self);
939
1
            self.cr.pop_group_to_source()?;
940
50027
            self.cr.paint_with_alpha(o)?;
941
        } else {
942
50025
            res = draw_fn(self);
943
        }
944

            
945
50026
        res
946
50026
    }
947

            
948
    /// Start a Cairo tag for PDF links
949
6
    fn link_tag_begin(&mut self, link_target: &str) {
950
6
        let attributes = format!("uri='{}'", escape_link_target(link_target));
951

            
952
6
        let cr = self.cr.clone();
953
6
        cr.tag_begin(CAIRO_TAG_LINK, &attributes);
954
6
    }
955

            
956
    /// End a Cairo tag for PDF links
957
6
    fn link_tag_end(&mut self) {
958
6
        self.cr.tag_end(CAIRO_TAG_LINK);
959
6
    }
960

            
961
301
    fn run_filters(
962
        &mut self,
963
        viewport: &Viewport,
964
        surface_to_filter: SharedImageSurface,
965
        filter: &Filter,
966
        acquired_nodes: &mut AcquiredNodes<'_>,
967
        node_name: &str,
968
        user_space_params: &NormalizeParams,
969
        stroke_paint_source: Rc<UserSpacePaintSource>,
970
        fill_paint_source: Rc<UserSpacePaintSource>,
971
        node_bbox: BoundingBox,
972
    ) -> Result<SharedImageSurface, InternalRenderingError> {
973
301
        let session = self.session();
974

            
975
        // We try to convert each item in the filter_list to a FilterSpec.
976
        //
977
        // However, the spec mentions, "If the filter references a non-existent object or
978
        // the referenced object is not a filter element, then the whole filter chain is
979
        // ignored." - https://www.w3.org/TR/filter-effects/#FilterProperty
980
        //
981
        // So, run through the filter_list and collect into a Result<Vec<FilterSpec>>.
982
        // This will return an Err if any of the conversions failed.
983
301
        let filter_specs = filter
984
            .filter_list
985
            .iter()
986
605
            .map(|filter_value| {
987
304
                filter_value.to_filter_spec(
988
304
                    acquired_nodes,
989
304
                    user_space_params,
990
304
                    filter.current_color,
991
304
                    viewport,
992
304
                    session,
993
304
                    node_name,
994
                )
995
304
            })
996
            .collect::<Result<Vec<FilterSpec>, _>>();
997

            
998
301
        match filter_specs {
999
298
            Ok(specs) => {
                // Start with the surface_to_filter, and apply each filter spec in turn;
                // the final result is our return value.
597
                specs.iter().try_fold(surface_to_filter, |surface, spec| {
299
                    filters::render(
299
                        spec,
299
                        stroke_paint_source.clone(),
299
                        fill_paint_source.clone(),
299
                        surface,
299
                        acquired_nodes,
299
                        self,
299
                        *self.get_transform(),
299
                        node_bbox,
299
                    )
299
                })
298
            }
3
            Err(e) => {
3
                rsvg_log!(
3
                    self.session,
                    "not rendering filter list on node {} because it was in error: {}",
                    node_name,
                    e
                );
                // just return the original surface without filtering it
3
                Ok(surface_to_filter)
            }
        }
301
    }
167
    fn set_gradient(&mut self, gradient: &UserSpaceGradient) -> Result<(), InternalRenderingError> {
167
        let g = match gradient.variant {
128
            GradientVariant::Linear { x1, y1, x2, y2 } => {
128
                cairo::Gradient::clone(&cairo::LinearGradient::new(x1, y1, x2, y2))
128
            }
            GradientVariant::Radial {
39
                cx,
39
                cy,
39
                r,
39
                fx,
39
                fy,
39
                fr,
39
            } => cairo::Gradient::clone(&cairo::RadialGradient::new(fx, fy, fr, cx, cy, r)),
        };
167
        g.set_matrix(ValidTransform::try_from(gradient.transform)?.into());
163
        g.set_extend(cairo::Extend::from(gradient.spread));
614
        for stop in &gradient.stops {
453
            let UnitInterval(stop_offset) = stop.offset;
453
            let rgba = color_to_rgba(&stop.color);
451
            g.add_color_stop_rgba(
                stop_offset,
452
                f64::from(rgba.red.unwrap_or(0)) / 255.0,
451
                f64::from(rgba.green.unwrap_or(0)) / 255.0,
451
                f64::from(rgba.blue.unwrap_or(0)) / 255.0,
451
                f64::from(rgba.alpha.unwrap_or(0.0)),
            );
        }
329
        Ok(self.cr.set_source(&g)?)
163
    }
500033
    fn set_pattern(
        &mut self,
        pattern: &UserSpacePattern,
        acquired_nodes: &mut AcquiredNodes<'_>,
    ) -> Result<bool, InternalRenderingError> {
        // Bail out early if the pattern has zero size, per the spec
500033
        if approx_eq!(f64, pattern.width, 0.0) || approx_eq!(f64, pattern.height, 0.0) {
9
            return Ok(false);
        }
        // Bail out early if this pattern has a circular reference
500024
        let pattern_node_acquired = match pattern.acquire_pattern_node(acquired_nodes) {
500024
            Ok(n) => n,
            Err(AcquireError::CircularReference(ref node)) => {
                rsvg_log!(self.session, "circular reference in element {}", node);
                return Ok(false);
            }
            _ => unreachable!(),
500024
        };
500024
        let pattern_node = pattern_node_acquired.get();
500024
        let taffine = self.get_transform().pre_transform(&pattern.transform);
500024
        let mut scwscale = (taffine.xx.powi(2) + taffine.xy.powi(2)).sqrt();
500024
        let mut schscale = (taffine.yx.powi(2) + taffine.yy.powi(2)).sqrt();
500024
        let pw: i32 = (pattern.width * scwscale) as i32;
500024
        let ph: i32 = (pattern.height * schscale) as i32;
500024
        if pw < 1 || ph < 1 {
449998
            return Ok(false);
        }
50026
        scwscale = f64::from(pw) / pattern.width;
50026
        schscale = f64::from(ph) / pattern.height;
        // Apply the pattern transform
100029
        let (affine, caffine) = if scwscale.approx_eq_cairo(1.0) && schscale.approx_eq_cairo(1.0) {
23
            (pattern.coord_transform, pattern.content_transform)
        } else {
50003
            (
100006
                pattern
                    .coord_transform
50003
                    .pre_scale(1.0 / scwscale, 1.0 / schscale),
50003
                pattern.content_transform.post_scale(scwscale, schscale),
            )
        };
        // Draw to another surface
50026
        let surface = self
            .cr
            .target()
100052
            .create_similar(cairo::Content::ColorAlpha, pw, ph)?;
50026
        let cr_pattern = cairo::Context::new(&surface)?;
        // Set up transformations to be determined by the contents units
50026
        let transform = ValidTransform::try_from(caffine)?;
50026
        cr_pattern.set_matrix(transform.into());
        // Draw everything
        {
50026
            let mut pattern_draw_ctx = self.nested(cr_pattern);
50026
            let pattern_viewport = Viewport {
50026
                dpi: self.dpi,
50026
                vbox: ViewBox::from(Rect::from_size(pattern.width, pattern.height)),
50026
                transform: *transform,
            };
50026
            pattern_draw_ctx
100052
                .with_alpha(pattern.opacity, &mut |dc| {
50026
                    let pattern_cascaded = CascadedValues::new_from_node(pattern_node);
50026
                    let pattern_values = pattern_cascaded.get();
50026
                    let elt = pattern_node.borrow_element();
50026
                    let stacking_ctx = StackingContext::new(
50026
                        self.session(),
50026
                        acquired_nodes,
50026
                        &elt,
50026
                        Transform::identity(),
50026
                        None,
                        pattern_values,
                    );
50026
                    dc.with_discrete_layer(
                        &stacking_ctx,
50026
                        acquired_nodes,
50026
                        &pattern_viewport,
50026
                        None,
                        false,
100052
                        &mut |an, dc, new_viewport| {
100052
                            pattern_node.draw_children(
                                an,
50026
                                &pattern_cascaded,
                                new_viewport,
                                dc,
                                false,
                            )
50026
                        },
                    )
50026
                })
50026
                .map(|_| ())?;
50026
        }
        // Set the final surface as a Cairo pattern into the Cairo context
50026
        let pattern = cairo::SurfacePattern::create(&surface);
50026
        if let Some(m) = affine.invert() {
50026
            pattern.set_matrix(ValidTransform::try_from(m)?.into());
50026
            pattern.set_extend(cairo::Extend::Repeat);
50026
            pattern.set_filter(cairo::Filter::Best);
550059
            self.cr.set_source(&pattern)?;
        }
50026
        Ok(true)
500033
    }
1898579
    fn set_paint_source(
        &mut self,
        paint_source: &UserSpacePaintSource,
        acquired_nodes: &mut AcquiredNodes<'_>,
    ) -> Result<bool, InternalRenderingError> {
1898579
        match *paint_source {
163
            UserSpacePaintSource::Gradient(ref gradient, _c) => {
1898579
                self.set_gradient(gradient)?;
163
                Ok(true)
163
            }
500033
            UserSpacePaintSource::Pattern(ref pattern, ref c) => {
500033
                if self.set_pattern(pattern, acquired_nodes)? {
50026
                    Ok(true)
450007
                } else if let Some(c) = c {
9
                    set_source_color_on_cairo(&self.cr, c);
9
                    Ok(true)
                } else {
449998
                    Ok(false)
                }
            }
949104
            UserSpacePaintSource::SolidColor(ref c) => {
949104
                set_source_color_on_cairo(&self.cr, c);
949104
                Ok(true)
949104
            }
449279
            UserSpacePaintSource::None => Ok(false),
        }
1898579
    }
    /// Computes and returns a surface corresponding to the given paint server.
13
    pub fn get_paint_source_surface(
        &mut self,
        width: i32,
        height: i32,
        acquired_nodes: &mut AcquiredNodes<'_>,
        paint_source: &UserSpacePaintSource,
    ) -> Result<SharedImageSurface, InternalRenderingError> {
13
        let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
26
        surface.draw(&mut |cr| {
13
            let mut temporary_draw_ctx = self.nested(cr);
            // FIXME: we are ignoring any error
            let had_paint_server =
13
                temporary_draw_ctx.set_paint_source(paint_source, acquired_nodes)?;
13
            if had_paint_server {
26
                temporary_draw_ctx.cr.paint()?;
            }
13
            Ok(())
13
        })?;
13
        Ok(surface.share()?)
13
    }
948507
    fn stroke(
        &mut self,
        cr: &cairo::Context,
        acquired_nodes: &mut AcquiredNodes<'_>,
        paint_source: &UserSpacePaintSource,
    ) -> Result<(), InternalRenderingError> {
948507
        let had_paint_server = self.set_paint_source(paint_source, acquired_nodes)?;
948507
        if had_paint_server {
1449654
            cr.stroke_preserve()?;
        }
948507
        Ok(())
948507
    }
948389
    fn fill(
        &mut self,
        cr: &cairo::Context,
        acquired_nodes: &mut AcquiredNodes<'_>,
        paint_source: &UserSpacePaintSource,
    ) -> Result<(), InternalRenderingError> {
948389
        let had_paint_server = self.set_paint_source(paint_source, acquired_nodes)?;
948389
        if had_paint_server {
1446639
            cr.fill_preserve()?;
        }
948389
        Ok(())
948389
    }
949115
    pub fn draw_layer(
        &mut self,
        layer: &Layer,
        acquired_nodes: &mut AcquiredNodes<'_>,
        clipping: bool,
        viewport: &Viewport,
    ) -> Result<BoundingBox, InternalRenderingError> {
949115
        match &layer.kind {
1896186
            LayerKind::Shape(shape) => self.draw_shape(
948093
                shape,
                &layer.stacking_ctx,
                acquired_nodes,
                clipping,
                viewport,
            ),
1828
            LayerKind::Text(text) => self.draw_text(
914
                text,
                &layer.stacking_ctx,
                acquired_nodes,
                clipping,
                viewport,
            ),
216
            LayerKind::Image(image) => self.draw_image(
108
                image,
                &layer.stacking_ctx,
                acquired_nodes,
                clipping,
                viewport,
            ),
            LayerKind::Group(group) => self.draw_group(
                group,
                &layer.stacking_ctx,
                acquired_nodes,
                clipping,
                viewport,
            ),
        }
949115
    }
948029
    fn draw_shape(
        &mut self,
        shape: &Shape,
        stacking_ctx: &StackingContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        clipping: bool,
        viewport: &Viewport,
    ) -> Result<BoundingBox, InternalRenderingError> {
948029
        if shape.extents.is_none() {
18
            return Ok(self.empty_bbox());
        }
948011
        self.with_discrete_layer(
            stacking_ctx,
            acquired_nodes,
            viewport,
948011
            None,
948011
            clipping,
1910570
            &mut |an, dc, new_viewport| {
962559
                let cr = dc.cr.clone();
962559
                let transform = dc.get_transform_for_stacking_ctx(stacking_ctx, clipping)?;
                let mut path_helper =
948456
                    PathHelper::new(&cr, transform, &shape.path, shape.stroke.line_cap);
948370
                if clipping {
39
                    if shape.is_visible {
38
                        cr.set_fill_rule(cairo::FillRule::from(shape.clip_rule));
38
                        path_helper.set()?;
                    }
39
                    return Ok(dc.empty_bbox());
                }
948331
                cr.set_antialias(cairo::Antialias::from(shape.shape_rendering));
948848
                setup_cr_for_stroke(&cr, &shape.stroke);
948101
                cr.set_fill_rule(cairo::FillRule::from(shape.fill_rule));
948859
                path_helper.set()?;
948326
                let bbox = compute_stroke_and_fill_box(
                    &cr,
948850
                    &shape.stroke,
948850
                    &shape.stroke_paint,
948850
                    &dc.initial_viewport,
                )?;
948508
                if shape.is_visible {
3793882
                    for &target in &shape.paint_order.targets {
                        // fill and stroke operations will preserve the path.
                        // markers operation will clear the path.
2845108
                        match target {
                            PaintTarget::Fill => {
948379
                                path_helper.set()?;
948296
                                dc.fill(&cr, an, &shape.fill_paint)?;
                            }
                            PaintTarget::Stroke => {
948331
                                path_helper.set()?;
948272
                                let backup_matrix = if shape.stroke.non_scaling {
1
                                    let matrix = cr.matrix();
1
                                    cr.set_matrix(
1
                                        ValidTransform::try_from(dc.initial_viewport.transform)?
                                            .into(),
                                    );
1
                                    Some(matrix)
                                } else {
948270
                                    None
                                };
1910830
                                dc.stroke(&cr, an, &shape.stroke_paint)?;
948373
                                if let Some(matrix) = backup_matrix {
1
                                    cr.set_matrix(matrix);
                                }
                            }
948663
                            PaintTarget::Markers => {
948398
                                path_helper.unset();
948387
                                marker::render_markers_for_shape(
949179
                                    shape,
                                    new_viewport,
                                    dc,
                                    an,
949179
                                    clipping,
                                )?;
                            }
                        }
                    }
                }
952801
                path_helper.unset();
948516
                Ok(bbox)
948555
            },
        )
948029
    }
108
    fn paint_surface(
        &mut self,
        surface: &SharedImageSurface,
        width: f64,
        height: f64,
        image_rendering: ImageRendering,
    ) -> Result<(), cairo::Error> {
108
        let cr = self.cr.clone();
        // We need to set extend appropriately, so can't use cr.set_source_surface().
        //
        // If extend is left at its default value (None), then bilinear scaling uses
        // transparency outside of the image producing incorrect results.
        // For example, in svg1.1/filters-blend-01-b.svgthere's a completely
        // opaque 100×1 image of a gradient scaled to 100×98 which ends up
        // transparent almost everywhere without this fix (which it shouldn't).
108
        let ptn = surface.to_cairo_pattern();
108
        ptn.set_extend(cairo::Extend::Pad);
108
        let interpolation = Interpolation::from(image_rendering);
108
        ptn.set_filter(cairo::Filter::from(interpolation));
216
        cr.set_source(&ptn)?;
        // Clip is needed due to extend being set to pad.
108
        clip_to_rectangle(&cr, &Rect::from_size(width, height));
108
        cr.paint()
108
    }
108
    fn draw_image(
        &mut self,
        image: &Image,
        stacking_ctx: &StackingContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        clipping: bool,
        viewport: &Viewport,
    ) -> Result<BoundingBox, InternalRenderingError> {
108
        let image_width = image.surface.width();
108
        let image_height = image.surface.height();
108
        if clipping || image.rect.is_empty() || image_width == 0 || image_height == 0 {
            return Ok(self.empty_bbox());
        }
108
        let image_width = f64::from(image_width);
108
        let image_height = f64::from(image_height);
108
        let vbox = ViewBox::from(Rect::from_size(image_width, image_height));
        // The bounding box for <image> is decided by the values of the image's x, y, w, h
        // and not by the final computed image bounds.
108
        let bounds = self.empty_bbox().with_rect(image.rect);
108
        let layout_viewport = LayoutViewport {
108
            vbox: Some(vbox),
108
            geometry: image.rect,
108
            preserve_aspect_ratio: image.aspect,
108
            overflow: image.overflow,
        };
108
        if image.is_visible {
108
            self.with_discrete_layer(
                stacking_ctx,
                acquired_nodes,
                viewport,
108
                Some(layout_viewport),
                clipping,
216
                &mut |_an, dc, _new_viewport| {
216
                    dc.paint_surface(
108
                        &image.surface,
108
                        image_width,
108
                        image_height,
108
                        image.image_rendering,
                    )?;
108
                    Ok(bounds)
108
                },
            )
        } else {
            Ok(bounds)
        }
108
    }
    fn draw_group(
        &mut self,
        _group: &Group,
        _stacking_ctx: &StackingContext,
        _acquired_nodes: &mut AcquiredNodes<'_>,
        _clipping: bool,
        _viewport: &Viewport,
    ) -> Result<BoundingBox, InternalRenderingError> {
        unimplemented!()
    }
1037
    fn draw_text_span(
        &mut self,
        span: &TextSpan,
        acquired_nodes: &mut AcquiredNodes<'_>,
        clipping: bool,
    ) -> Result<BoundingBox, InternalRenderingError> {
1037
        let path = pango_layout_to_path(span.x, span.y, &span.layout, span.gravity)?;
1037
        if path.is_empty() {
            // Empty strings, or only-whitespace text, get turned into empty paths.
            // In that case, we really want to return "no bounds" rather than an
            // empty rectangle.
59
            return Ok(self.empty_bbox());
        }
        // #851 - We can't just render all text as paths for PDF; it
        // needs the actual text content so text is selectable by PDF
        // viewers.
978
        let can_use_text_as_path = self.cr.target().type_() != cairo::SurfaceType::Pdf;
1956
        with_saved_cr(&self.cr.clone(), || {
1956
            self.cr
978
                .set_antialias(cairo::Antialias::from(span.text_rendering));
978
            setup_cr_for_stroke(&self.cr, &span.stroke);
978
            if clipping {
1
                path.to_cairo(&self.cr, false)?;
1
                return Ok(self.empty_bbox());
            }
977
            path.to_cairo(&self.cr, false)?;
977
            let bbox = compute_stroke_and_fill_box(
977
                &self.cr,
977
                &span.stroke,
977
                &span.stroke_paint,
977
                &self.initial_viewport,
            )?;
977
            self.cr.new_path();
977
            if span.is_visible {
967
                if let Some(ref link_target) = span.link_target {
4
                    self.link_tag_begin(link_target);
                }
3867
                for &target in &span.paint_order.targets {
2900
                    match target {
                        PaintTarget::Fill => {
                            let had_paint_server =
967
                                self.set_paint_source(&span.fill_paint, acquired_nodes)?;
967
                            if had_paint_server {
964
                                if can_use_text_as_path {
960
                                    path.to_cairo(&self.cr, false)?;
960
                                    self.cr.fill()?;
960
                                    self.cr.new_path();
                                } else {
4
                                    self.cr.move_to(span.x, span.y);
4
                                    let matrix = self.cr.matrix();
4
                                    let rotation_from_gravity = span.gravity.to_rotation();
4
                                    if !rotation_from_gravity.approx_eq_cairo(0.0) {
                                        self.cr.rotate(-rotation_from_gravity);
                                    }
4
                                    pangocairo::functions::update_layout(&self.cr, &span.layout);
4
                                    pangocairo::functions::show_layout(&self.cr, &span.layout);
4
                                    self.cr.set_matrix(matrix);
                                }
                            }
                        }
                        PaintTarget::Stroke => {
                            let had_paint_server =
967
                                self.set_paint_source(&span.stroke_paint, acquired_nodes)?;
967
                            if had_paint_server {
35
                                path.to_cairo(&self.cr, false)?;
35
                                self.cr.stroke()?;
35
                                self.cr.new_path();
                            }
                        }
                        PaintTarget::Markers => {}
                    }
                }
967
                if span.link_target.is_some() {
4
                    self.link_tag_end();
                }
            }
977
            Ok(bbox)
978
        })
1037
    }
914
    fn draw_text(
        &mut self,
        text: &Text,
        stacking_ctx: &StackingContext,
        acquired_nodes: &mut AcquiredNodes<'_>,
        clipping: bool,
        viewport: &Viewport,
    ) -> Result<BoundingBox, InternalRenderingError> {
914
        self.with_discrete_layer(
            stacking_ctx,
            acquired_nodes,
            viewport,
914
            None,
914
            clipping,
1828
            &mut |an, dc, _new_viewport| {
914
                let mut bbox = dc.empty_bbox();
1951
                for span in &text.spans {
1037
                    let span_bbox = dc.draw_text_span(span, an, clipping)?;
1037
                    bbox.insert(&span_bbox);
                }
914
                Ok(bbox)
914
            },
        )
914
    }
10
    pub fn get_snapshot(
        &self,
        width: i32,
        height: i32,
    ) -> Result<SharedImageSurface, InternalRenderingError> {
        // TODO: as far as I can tell this should not render elements past the last (topmost) one
        // with enable-background: new (because technically we shouldn't have been caching them).
        // Right now there are no enable-background checks whatsoever.
        //
        // Addendum: SVG 2 has deprecated the enable-background property, and replaced it with an
        // "isolation" property from the CSS Compositing and Blending spec.
        //
        // Deprecation:
        //   https://www.w3.org/TR/filter-effects-1/#AccessBackgroundImage
        //
        // BackgroundImage, BackgroundAlpha in the "in" attribute of filter primitives:
        //   https://www.w3.org/TR/filter-effects-1/#attr-valuedef-in-backgroundimage
        //
        // CSS Compositing and Blending, "isolation" property:
        //   https://www.w3.org/TR/compositing-1/#isolation
10
        let mut surface = ExclusiveImageSurface::new(width, height, SurfaceType::SRgb)?;
20
        surface.draw(&mut |cr| {
            // TODO: apparently DrawingCtx.cr_stack is just a way to store pairs of
            // (surface, transform).  Can we turn it into a DrawingCtx.surface_stack
            // instead?  See what CSS isolation would like to call that; are the pairs just
            // stacking contexts instead, or the result of rendering stacking contexts?
22
            for (depth, draw) in self.cr_stack.borrow().iter().enumerate() {
12
                let affines = CompositingAffines::new(
12
                    Transform::from(draw.matrix()),
12
                    self.initial_viewport.transform,
                    depth,
                );
12
                cr.set_matrix(ValidTransform::try_from(affines.for_snapshot)?.into());
12
                cr.set_source_surface(&draw.target(), 0.0, 0.0)?;
22
                cr.paint()?;
            }
10
            Ok(())
10
        })?;
10
        Ok(surface.share()?)
10
    }
25
    pub fn draw_node_to_surface(
        &mut self,
        node: &Node,
        acquired_nodes: &mut AcquiredNodes<'_>,
        cascaded: &CascadedValues<'_>,
        affine: Transform,
        width: i32,
        height: i32,
    ) -> Result<SharedImageSurface, InternalRenderingError> {
25
        let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
25
        let save_cr = self.cr.clone();
        {
25
            let cr = cairo::Context::new(&surface)?;
50
            cr.set_matrix(ValidTransform::try_from(affine)?.into());
25
            self.cr = cr;
25
            let viewport = Viewport {
25
                dpi: self.dpi,
                transform: affine,
25
                vbox: ViewBox::from(Rect::from_size(f64::from(width), f64::from(height))),
            };
25
            let _ = self.draw_node_from_stack(node, acquired_nodes, cascaded, &viewport, false)?;
25
        }
25
        self.cr = save_cr;
25
        Ok(SharedImageSurface::wrap(surface, SurfaceType::SRgb)?)
25
    }
1459955
    pub fn draw_node_from_stack(
        &mut self,
        node: &Node,
        acquired_nodes: &mut AcquiredNodes<'_>,
        cascaded: &CascadedValues<'_>,
        viewport: &Viewport,
        clipping: bool,
    ) -> Result<BoundingBox, InternalRenderingError> {
1459955
        let stack_top = self.drawsub_stack.pop();
1459955
        let draw = if let Some(ref top) = stack_top {
2898
            top == node
        } else {
1457057
            true
        };
1460372
        let res = if draw {
1458282
            node.draw(acquired_nodes, cascaded, viewport, self, clipping)
        } else {
1045
            Ok(self.empty_bbox())
        };
1458699
        if let Some(top) = stack_top {
2271
            self.drawsub_stack.push(top);
        }
1458699
        res
1458699
    }
500185
    pub fn draw_from_use_node(
        &mut self,
        node: &Node,
        acquired_nodes: &mut AcquiredNodes<'_>,
        values: &ComputedValues,
        use_rect: Rect,
        link: &NodeId,
        clipping: bool,
        viewport: &Viewport,
        fill_paint: Rc<PaintSource>,
        stroke_paint: Rc<PaintSource>,
    ) -> Result<BoundingBox, InternalRenderingError> {
        // <use> is an element that is used directly, unlike
        // <pattern>, which is used through a fill="url(#...)"
        // reference.  However, <use> will always reference another
        // element, potentially itself or an ancestor of itself (or
        // another <use> which references the first one, etc.).  So,
        // we acquire the <use> element itself so that circular
        // references can be caught.
500185
        let _self_acquired = match acquired_nodes.acquire_ref(node) {
500161
            Ok(n) => n,
            Err(AcquireError::CircularReference(_)) => {
4
                rsvg_log!(self.session, "circular reference in element {}", node);
4
                return Ok(self.empty_bbox());
            }
            _ => unreachable!(),
500165
        };
500161
        let acquired = match acquired_nodes.acquire(link) {
500178
            Ok(acquired) => acquired,
            Err(AcquireError::CircularReference(node)) => {
                rsvg_log!(self.session, "circular reference in element {}", node);
                return Ok(self.empty_bbox());
            }
            Err(AcquireError::MaxReferencesExceeded) => {
1
                return Err(InternalRenderingError::LimitExceeded(
1
                    ImplementationLimit::TooManyReferencedElements,
                ));
            }
            Err(AcquireError::InvalidLinkType(_)) => unreachable!(),
4
            Err(AcquireError::LinkNotFound(node_id)) => {
4
                rsvg_log!(
4
                    self.session,
                    "element {} references nonexistent \"{}\"",
                    node,
                    node_id
                );
4
                return Ok(self.empty_bbox());
4
            }
5
        };
        // width or height set to 0 disables rendering of the element
        // https://www.w3.org/TR/SVG/struct.html#UseElementWidthAttribute
500178
        if use_rect.is_empty() {
            return Ok(self.empty_bbox());
        }
500156
        let child = acquired.get();
500156
        if clipping && !element_can_be_used_inside_use_inside_clip_path(&child.borrow_element()) {
1
            return Ok(self.empty_bbox());
        }
500155
        let orig_transform = self.get_transform();
1000310
        self.cr
1000340
            .transform(ValidTransform::try_from(values.transform())?.into());
500155
        let use_element = node.borrow_element();
500155
        let defines_a_viewport = if is_element_of_type!(child, Symbol) {
9
            let symbol = borrow_element_as!(child, Symbol);
9
            Some((symbol.get_viewbox(), symbol.get_preserve_aspect_ratio()))
500155
        } else if is_element_of_type!(child, Svg) {
1
            let svg = borrow_element_as!(child, Svg);
1
            Some((svg.get_viewbox(), svg.get_preserve_aspect_ratio()))
1
        } else {
500145
            None
        };
500155
        let res = if let Some((vbox, preserve_aspect_ratio)) = defines_a_viewport {
            // <symbol> and <svg> define a viewport, as described in the specification:
            // https://www.w3.org/TR/SVG2/struct.html#UseElement
            // https://gitlab.gnome.org/GNOME/librsvg/-/issues/875#note_1482705
10
            let elt = child.borrow_element();
10
            let child_values = elt.get_computed_values();
10
            let stacking_ctx = StackingContext::new(
10
                self.session(),
                acquired_nodes,
10
                &use_element,
10
                Transform::identity(),
10
                None,
                values,
            );
10
            let layout_viewport = LayoutViewport {
                vbox,
10
                geometry: use_rect,
                preserve_aspect_ratio,
10
                overflow: child_values.overflow(),
            };
10
            self.with_discrete_layer(
                &stacking_ctx,
                acquired_nodes,
                viewport,
10
                Some(layout_viewport),
10
                clipping,
20
                &mut |an, dc, new_viewport| {
20
                    child.draw_children(
                        an,
10
                        &CascadedValues::new_from_values(
10
                            child,
10
                            values,
10
                            Some(fill_paint.clone()),
10
                            Some(stroke_paint.clone()),
10
                        ),
                        new_viewport,
                        dc,
10
                        clipping,
                    )
10
                },
10
            )
10
        } else {
            // otherwise the referenced node is not a <symbol>; process it generically
500145
            let stacking_ctx = StackingContext::new(
500145
                self.session(),
                acquired_nodes,
500145
                &use_element,
500145
                Transform::new_translate(use_rect.x0, use_rect.y0),
500145
                None,
                values,
            );
500145
            self.with_discrete_layer(
                &stacking_ctx,
                acquired_nodes,
                viewport,
500145
                None,
500145
                clipping,
1000290
                &mut |an, dc, new_viewport| {
1000290
                    child.draw(
                        an,
500145
                        &CascadedValues::new_from_values(
500145
                            child,
500145
                            values,
500145
                            Some(fill_paint.clone()),
500145
                            Some(stroke_paint.clone()),
500145
                        ),
                        new_viewport,
                        dc,
500145
                        clipping,
                    )
500145
                },
500165
            )
500145
        };
500155
        self.cr.set_matrix(orig_transform.into());
1000293
        if let Ok(bbox) = res {
500138
            let mut res_bbox = BoundingBox::new().with_transform(*orig_transform);
500138
            res_bbox.insert(&bbox);
500138
            Ok(res_bbox)
        } else {
17
            res
        }
500164
    }
    /// Extracts the font options for the current state of the DrawingCtx.
    ///
    /// You can use the font options later with create_pango_context().
914
    pub fn get_font_options(&self) -> FontOptions {
914
        let mut options = cairo::FontOptions::new().unwrap();
914
        if self.testing {
869
            options.set_antialias(cairo::Antialias::Gray);
        }
914
        options.set_hint_style(cairo::HintStyle::None);
914
        options.set_hint_metrics(cairo::HintMetrics::Off);
914
        FontOptions { options }
914
    }
}
impl From<ImageRendering> for Interpolation {
174
    fn from(r: ImageRendering) -> Interpolation {
174
        match r {
            ImageRendering::Pixelated
            | ImageRendering::CrispEdges
1
            | ImageRendering::OptimizeSpeed => Interpolation::Nearest,
            ImageRendering::Smooth
            | ImageRendering::OptimizeQuality
            | ImageRendering::HighQuality
173
            | ImageRendering::Auto => Interpolation::Smooth,
        }
174
    }
}
949460
pub fn compute_path_extents(path: &Path) -> Result<Option<Rect>, InternalRenderingError> {
949460
    if path.is_empty() {
18
        return Ok(None);
    }
949442
    let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
949442
    let cr = cairo::Context::new(&surface)?;
1898734
    path.to_cairo(&cr, false)?;
947977
    let (x0, y0, x1, y1) = cr.path_extents()?;
947860
    Ok(Some(Rect::new(x0, y0, x1, y1)))
948684
}
/// Create a Pango context with a particular configuration.
1039
pub fn create_pango_context(font_options: &FontOptions, transform: &Transform) -> pango::Context {
1039
    let font_map = pangocairo::FontMap::default();
1039
    let context = font_map.create_context();
1039
    context.set_round_glyph_positions(false);
1039
    let pango_matrix = PangoMatrix {
1039
        xx: transform.xx,
1039
        xy: transform.xy,
1039
        yx: transform.yx,
1039
        yy: transform.yy,
1039
        x0: transform.x0,
1039
        y0: transform.y0,
    };
1039
    let pango_matrix_ptr: *const PangoMatrix = &pango_matrix;
1039
    let matrix = unsafe { pango::Matrix::from_glib_none(pango_matrix_ptr) };
1039
    context.set_matrix(Some(&matrix));
1040
    pangocairo::functions::context_set_font_options(&context, Some(&font_options.options));
    // Pango says this about pango_cairo_context_set_resolution():
    //
    //     Sets the resolution for the context. This is a scale factor between
    //     points specified in a #PangoFontDescription and Cairo units. The
    //     default value is 96, meaning that a 10 point font will be 13
    //     units high. (10 * 96. / 72. = 13.3).
    //
    // I.e. Pango font sizes in a PangoFontDescription are in *points*, not pixels.
    // However, we are normalizing everything to userspace units, which amount to
    // pixels.  So, we will use 72.0 here to make Pango not apply any further scaling
    // to the size values we give it.
    //
    // An alternative would be to divide our font sizes by (dpi_y / 72) to effectively
    // cancel out Pango's scaling, but it's probably better to deal with Pango-isms
    // right here, instead of spreading them out through our Length normalization
    // code.
1039
    pangocairo::functions::context_set_resolution(&context, 72.0);
1037
    context
1037
}
949136
pub fn set_source_color_on_cairo(cr: &cairo::Context, color: &cssparser::Color) {
949136
    let rgba = color_to_rgba(color);
949136
    cr.set_source_rgba(
949136
        f64::from(rgba.red.unwrap_or(0)) / 255.0,
949136
        f64::from(rgba.green.unwrap_or(0)) / 255.0,
949136
        f64::from(rgba.blue.unwrap_or(0)) / 255.0,
949136
        f64::from(rgba.alpha.unwrap_or(0.0)),
    );
949136
}
/// Converts a Pango layout to a Cairo path on the specified cr starting at (x, y).
/// Does not clear the current path first.
1037
fn pango_layout_to_cairo(
    x: f64,
    y: f64,
    layout: &pango::Layout,
    gravity: pango::Gravity,
    cr: &cairo::Context,
) {
1037
    let rotation_from_gravity = gravity.to_rotation();
1037
    let rotation = if !rotation_from_gravity.approx_eq_cairo(0.0) {
        Some(-rotation_from_gravity)
    } else {
1037
        None
    };
1037
    cr.move_to(x, y);
1037
    let matrix = cr.matrix();
1037
    if let Some(rot) = rotation {
        cr.rotate(rot);
    }
1037
    pangocairo::functions::update_layout(cr, layout);
1037
    pangocairo::functions::layout_path(cr, layout);
1037
    cr.set_matrix(matrix);
1037
}
/// Converts a Pango layout to a Path starting at (x, y).
1039
pub fn pango_layout_to_path(
    x: f64,
    y: f64,
    layout: &pango::Layout,
    gravity: pango::Gravity,
) -> Result<Path, InternalRenderingError> {
1039
    let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
1039
    let cr = cairo::Context::new(&surface)?;
1039
    pango_layout_to_cairo(x, y, layout, gravity, &cr);
1037
    let cairo_path = cr.copy_path()?;
1037
    Ok(Path::from_cairo(cairo_path))
1037
}
// https://www.w3.org/TR/css-masking-1/#ClipPathElement
43
fn element_can_be_used_inside_clip_path(element: &Element) -> bool {
    use ElementData::*;
43
    matches!(
43
        element.element_data,
        Circle(_)
            | Ellipse(_)
            | Line(_)
            | Path(_)
            | Polygon(_)
            | Polyline(_)
            | Rect(_)
            | Text(_)
            | Use(_)
    )
43
}
// https://www.w3.org/TR/css-masking-1/#ClipPathElement
1
fn element_can_be_used_inside_use_inside_clip_path(element: &Element) -> bool {
    use ElementData::*;
1
    matches!(
1
        element.element_data,
        Circle(_) | Ellipse(_) | Line(_) | Path(_) | Polygon(_) | Polyline(_) | Rect(_) | Text(_)
    )
1
}
#[derive(Debug)]
struct CompositingAffines {
    pub outside_temporary_surface: Transform,
    #[allow(unused)]
    pub initial: Transform,
    pub for_temporary_surface: Transform,
    pub compositing: Transform,
    pub for_snapshot: Transform,
}
impl CompositingAffines {
1120
    fn new(current: Transform, initial: Transform, cr_stack_depth: usize) -> CompositingAffines {
1120
        let is_topmost_temporary_surface = cr_stack_depth == 0;
1120
        let initial_inverse = initial.invert().unwrap();
1120
        let outside_temporary_surface = if is_topmost_temporary_surface {
648
            current
        } else {
472
            current.post_transform(&initial_inverse)
        };
1120
        let (scale_x, scale_y) = initial.transform_distance(1.0, 1.0);
1120
        let for_temporary_surface = if is_topmost_temporary_surface {
648
            current
                .post_transform(&initial_inverse)
                .post_scale(scale_x, scale_y)
        } else {
472
            current
        };
1120
        let compositing = if is_topmost_temporary_surface {
648
            initial.pre_scale(1.0 / scale_x, 1.0 / scale_y)
        } else {
472
            Transform::identity()
        };
1120
        let for_snapshot = compositing.invert().unwrap();
1120
        CompositingAffines {
1120
            outside_temporary_surface,
1120
            initial,
1120
            for_temporary_surface,
1120
            compositing,
            for_snapshot,
        }
1120
    }
}
949110
fn compute_stroke_and_fill_extents(
    cr: &cairo::Context,
    stroke: &Stroke,
    stroke_paint_source: &UserSpacePaintSource,
    initial_viewport: &Viewport,
) -> Result<PathExtents, InternalRenderingError> {
    // Dropping the precision of cairo's bezier subdivision, yielding 2x
    // _rendering_ time speedups, are these rather expensive operations
    // really needed here? */
949110
    let backup_tolerance = cr.tolerance();
949110
    cr.set_tolerance(1.0);
    // Bounding box for fill
    //
    // Unlike the case for stroke, for fills we always compute the bounding box.
    // In GNOME we have SVGs for symbolic icons where each icon has a bounding
    // rectangle with no fill and no stroke, and inside it there are the actual
    // paths for the icon's shape.  We need to be able to compute the bounding
    // rectangle's extents, even when it has no fill nor stroke.
949110
    let (x0, y0, x1, y1) = cr.fill_extents()?;
949110
    let fill_extents = if x0 != 0.0 || y0 != 0.0 || x1 != 0.0 || y1 != 0.0 {
504414
        Some(Rect::new(x0, y0, x1, y1))
    } else {
444696
        None
    };
    // Bounding box for stroke
    //
    // When presented with a line width of 0, Cairo returns a
    // stroke_extents rectangle of (0, 0, 0, 0).  This would cause the
    // bbox to include a lone point at the origin, which is wrong, as a
    // stroke of zero width should not be painted, per
    // https://www.w3.org/TR/SVG2/painting.html#StrokeWidth
    //
    // So, see if the stroke width is 0 and just not include the stroke in the
    // bounding box if so.
1897886
    let stroke_extents = if !stroke.width.approx_eq_cairo(0.0)
948776
        && !matches!(stroke_paint_source, UserSpacePaintSource::None)
    {
501181
        let backup_matrix = if stroke.non_scaling {
1
            let matrix = cr.matrix();
949111
            cr.set_matrix(ValidTransform::try_from(initial_viewport.transform)?.into());
1
            Some(matrix)
        } else {
501179
            None
        };
501180
        let (x0, y0, x1, y1) = cr.stroke_extents()?;
501180
        if let Some(matrix) = backup_matrix {
1
            cr.set_matrix(matrix);
        }
501180
        Some(Rect::new(x0, y0, x1, y1))
    } else {
447930
        None
    };
    // objectBoundingBox
949110
    let (x0, y0, x1, y1) = cr.path_extents()?;
949110
    let path_extents = Some(Rect::new(x0, y0, x1, y1));
    // restore tolerance
949110
    cr.set_tolerance(backup_tolerance);
949110
    Ok(PathExtents {
        path_only: path_extents,
949110
        fill: fill_extents,
949110
        stroke: stroke_extents,
    })
949110
}
949881
fn compute_stroke_and_fill_box(
    cr: &cairo::Context,
    stroke: &Stroke,
    stroke_paint_source: &UserSpacePaintSource,
    initial_viewport: &Viewport,
) -> Result<BoundingBox, InternalRenderingError> {
    let extents =
949881
        compute_stroke_and_fill_extents(cr, stroke, stroke_paint_source, initial_viewport)?;
949881
    let ink_rect = match (extents.fill, extents.stroke) {
444466
        (None, None) => None,
4235
        (Some(f), None) => Some(f),
230
        (None, Some(s)) => Some(s),
500950
        (Some(f), Some(s)) => Some(f.union(&s)),
    };
949881
    let mut bbox = BoundingBox::new().with_transform(Transform::from(cr.matrix()));
949881
    if let Some(rect) = extents.path_only {
949719
        bbox = bbox.with_rect(rect);
    }
949881
    if let Some(ink_rect) = ink_rect {
505415
        bbox = bbox.with_ink_rect(ink_rect);
    }
949881
    Ok(bbox)
949881
}
949226
fn setup_cr_for_stroke(cr: &cairo::Context, stroke: &Stroke) {
949226
    cr.set_line_width(stroke.width);
949226
    cr.set_miter_limit(stroke.miter_limit.0);
949226
    cr.set_line_cap(cairo::LineCap::from(stroke.line_cap));
949226
    cr.set_line_join(cairo::LineJoin::from(stroke.line_join));
949226
    let total_length: f64 = stroke.dashes.iter().sum();
949226
    if total_length > 0.0 {
106
        cr.set_dash(&stroke.dashes, stroke.dash_offset);
    } else {
949120
        cr.set_dash(&[], 0.0);
    }
949226
}
/// escape quotes and backslashes with backslash
6
fn escape_link_target(value: &str) -> Cow<'_, str> {
    let regex = {
        static REGEX: OnceLock<Regex> = OnceLock::new();
9
        REGEX.get_or_init(|| Regex::new(r"['\\]").unwrap())
    };
6
    regex.replace_all(value, |caps: &Captures<'_>| {
        match caps.get(0).unwrap().as_str() {
            "'" => "\\'".to_owned(),
            "\\" => "\\\\".to_owned(),
            _ => unreachable!(),
        }
    })
6
}
496
fn clip_to_rectangle(cr: &cairo::Context, r: &Rect) {
496
    cr.rectangle(r.x0, r.y0, r.width(), r.height());
496
    cr.clip();
496
}
impl From<SpreadMethod> for cairo::Extend {
163
    fn from(s: SpreadMethod) -> cairo::Extend {
163
        match s {
157
            SpreadMethod::Pad => cairo::Extend::Pad,
3
            SpreadMethod::Reflect => cairo::Extend::Reflect,
3
            SpreadMethod::Repeat => cairo::Extend::Repeat,
        }
163
    }
}
impl From<StrokeLinejoin> for cairo::LineJoin {
949647
    fn from(j: StrokeLinejoin) -> cairo::LineJoin {
949647
        match j {
949514
            StrokeLinejoin::Miter => cairo::LineJoin::Miter,
131
            StrokeLinejoin::Round => cairo::LineJoin::Round,
2
            StrokeLinejoin::Bevel => cairo::LineJoin::Bevel,
        }
949647
    }
}
impl From<StrokeLinecap> for cairo::LineCap {
949460
    fn from(j: StrokeLinecap) -> cairo::LineCap {
949460
        match j {
949270
            StrokeLinecap::Butt => cairo::LineCap::Butt,
158
            StrokeLinecap::Round => cairo::LineCap::Round,
32
            StrokeLinecap::Square => cairo::LineCap::Square,
        }
949460
    }
}
impl From<MixBlendMode> for cairo::Operator {
681
    fn from(m: MixBlendMode) -> cairo::Operator {
        use cairo::Operator;
681
        match m {
665
            MixBlendMode::Normal => Operator::Over,
1
            MixBlendMode::Multiply => Operator::Multiply,
1
            MixBlendMode::Screen => Operator::Screen,
1
            MixBlendMode::Overlay => Operator::Overlay,
1
            MixBlendMode::Darken => Operator::Darken,
1
            MixBlendMode::Lighten => Operator::Lighten,
1
            MixBlendMode::ColorDodge => Operator::ColorDodge,
1
            MixBlendMode::ColorBurn => Operator::ColorBurn,
1
            MixBlendMode::HardLight => Operator::HardLight,
1
            MixBlendMode::SoftLight => Operator::SoftLight,
2
            MixBlendMode::Difference => Operator::Difference,
1
            MixBlendMode::Exclusion => Operator::Exclusion,
1
            MixBlendMode::Hue => Operator::HslHue,
1
            MixBlendMode::Saturation => Operator::HslSaturation,
1
            MixBlendMode::Color => Operator::HslColor,
1
            MixBlendMode::Luminosity => Operator::HslLuminosity,
        }
681
    }
}
impl From<ClipRule> for cairo::FillRule {
38
    fn from(c: ClipRule) -> cairo::FillRule {
38
        match c {
37
            ClipRule::NonZero => cairo::FillRule::Winding,
1
            ClipRule::EvenOdd => cairo::FillRule::EvenOdd,
        }
38
    }
}
impl From<FillRule> for cairo::FillRule {
948078
    fn from(f: FillRule) -> cairo::FillRule {
948078
        match f {
947914
            FillRule::NonZero => cairo::FillRule::Winding,
164
            FillRule::EvenOdd => cairo::FillRule::EvenOdd,
        }
948078
    }
}
impl From<ShapeRendering> for cairo::Antialias {
948234
    fn from(sr: ShapeRendering) -> cairo::Antialias {
948234
        match sr {
948221
            ShapeRendering::Auto | ShapeRendering::GeometricPrecision => cairo::Antialias::Default,
13
            ShapeRendering::OptimizeSpeed | ShapeRendering::CrispEdges => cairo::Antialias::None,
        }
948234
    }
}
impl From<TextRendering> for cairo::Antialias {
978
    fn from(tr: TextRendering) -> cairo::Antialias {
978
        match tr {
            TextRendering::Auto
            | TextRendering::OptimizeLegibility
978
            | TextRendering::GeometricPrecision => cairo::Antialias::Default,
            TextRendering::OptimizeSpeed => cairo::Antialias::None,
        }
978
    }
}
impl From<cairo::Matrix> for Transform {
    #[inline]
10423777
    fn from(m: cairo::Matrix) -> Self {
10423777
        Self::new_unchecked(m.xx(), m.yx(), m.xy(), m.yy(), m.x0(), m.y0())
10423777
    }
}
impl From<ValidTransform> for cairo::Matrix {
    #[inline]
6063208
    fn from(t: ValidTransform) -> cairo::Matrix {
6063208
        cairo::Matrix::new(t.xx, t.yx, t.xy, t.yy, t.x0, t.y0)
6063208
    }
}
/// Extents for a path in its current coordinate system.
///
/// Normally you'll want to convert this to a BoundingBox, which has knowledge about just
/// what that coordinate system is.
pub struct PathExtents {
    /// Extents of the "plain", unstroked path, or `None` if the path is empty.
    pub path_only: Option<Rect>,
    /// Extents of just the fill, or `None` if the path is empty.
    pub fill: Option<Rect>,
    /// Extents for the stroked path, or `None` if the path is empty or zero-width.
    pub stroke: Option<Rect>,
}
impl Path {
1898456
    pub fn to_cairo(
        &self,
        cr: &cairo::Context,
        is_square_linecap: bool,
    ) -> Result<(), InternalRenderingError> {
1898456
        assert!(!self.is_empty());
3830879
        for subpath in self.iter_subpath() {
            // If a subpath is empty and the linecap is a square, then draw a square centered on
            // the origin of the subpath. See #165.
1932423
            if is_square_linecap {
34
                let (x, y) = subpath.origin();
34
                if subpath.is_zero_length() {
1898456
                    let stroke_size = 0.002;
1
                    cr.move_to(x - stroke_size / 2., y);
1
                    cr.line_to(x + stroke_size / 2., y);
                }
            }
13838031
            for cmd in subpath.iter_commands() {
11905608
                cmd.to_cairo(cr);
            }
        }
        // We check the cr's status right after feeding it a new path for a few reasons:
        //
        // * Any of the individual path commands may cause the cr to enter an error state, for
        //   example, if they come with coordinates outside of Cairo's supported range.
        //
        // * The *next* call to the cr will probably be something that actually checks the status
        //   (i.e. in cairo-rs), and we don't want to panic there.
1898456
        cr.status().map_err(|e| e.into())
1898456
    }
    /// Converts a `cairo::Path` to a librsvg `Path`.
1038
    fn from_cairo(cairo_path: cairo::Path) -> Path {
1038
        let mut builder = PathBuilder::default();
        // Cairo has the habit of appending a MoveTo to some paths, but we don't want a
        // path for empty text to generate that lone point.  So, strip out paths composed
        // only of MoveTo.
1038
        if !cairo_path_is_only_move_tos(&cairo_path) {
252160
            for segment in cairo_path.iter() {
251184
                match segment {
19591
                    cairo::PathSegment::MoveTo((x, y)) => builder.move_to(x, y),
75942
                    cairo::PathSegment::LineTo((x, y)) => builder.line_to(x, y),
137039
                    cairo::PathSegment::CurveTo((x2, y2), (x3, y3), (x4, y4)) => {
137039
                        builder.curve_to(x2, y2, x3, y3, x4, y4)
                    }
18612
                    cairo::PathSegment::ClosePath => builder.close_path(),
                }
            }
        }
1034
        builder.into_path()
1030
    }
}
1038
fn cairo_path_is_only_move_tos(path: &cairo::Path) -> bool {
1038
    path.iter()
2017
        .all(|seg| matches!(seg, cairo::PathSegment::MoveTo((_, _))))
1038
}
impl PathCommand {
11908498
    fn to_cairo(&self, cr: &cairo::Context) {
11908498
        match *self {
1938144
            PathCommand::MoveTo(x, y) => cr.move_to(x, y),
7742012
            PathCommand::LineTo(x, y) => cr.line_to(x, y),
290421
            PathCommand::CurveTo(ref curve) => curve.to_cairo(cr),
2588
            PathCommand::Arc(ref arc) => arc.to_cairo(cr),
1935333
            PathCommand::ClosePath => cr.close_path(),
        }
11908498
    }
}
impl EllipticalArc {
2588
    fn to_cairo(&self, cr: &cairo::Context) {
2588
        match self.center_parameterization() {
            ArcParameterization::CenterParameters {
2587
                center,
2587
                radii,
2587
                theta1,
2587
                delta_theta,
            } => {
2587
                let n_segs = (delta_theta / (PI * 0.5 + 0.001)).abs().ceil() as u32;
2587
                let d_theta = delta_theta / f64::from(n_segs);
2587
                let mut theta = theta1;
6603
                for _ in 0..n_segs {
4016
                    arc_segment(center, radii, self.x_axis_rotation, theta, theta + d_theta)
                        .to_cairo(cr);
4016
                    theta += d_theta;
                }
            }
            ArcParameterization::LineTo => {
                let (x2, y2) = self.to;
                cr.line_to(x2, y2);
            }
            ArcParameterization::Omit => {}
        }
2588
    }
}
impl CubicBezierCurve {
294432
    fn to_cairo(&self, cr: &cairo::Context) {
294432
        let Self { pt1, pt2, to } = *self;
294432
        cr.curve_to(pt1.0, pt1.1, pt2.0, pt2.1, to.0, to.1);
294432
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
2
    fn rsvg_path_from_cairo_path() {
1
        let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 10, 10).unwrap();
1
        let cr = cairo::Context::new(&surface).unwrap();
1
        cr.move_to(1.0, 2.0);
1
        cr.line_to(3.0, 4.0);
1
        cr.curve_to(5.0, 6.0, 7.0, 8.0, 9.0, 10.0);
1
        cr.close_path();
1
        let cairo_path = cr.copy_path().unwrap();
1
        let path = Path::from_cairo(cairo_path);
2
        assert_eq!(
1
            path.iter().collect::<Vec<PathCommand>>(),
1
            vec![
1
                PathCommand::MoveTo(1.0, 2.0),
1
                PathCommand::LineTo(3.0, 4.0),
1
                PathCommand::CurveTo(CubicBezierCurve {
1
                    pt1: (5.0, 6.0),
1
                    pt2: (7.0, 8.0),
1
                    to: (9.0, 10.0),
                }),
1
                PathCommand::ClosePath,
1
                PathCommand::MoveTo(1.0, 2.0), // cairo inserts a MoveTo after ClosePath
            ],
        );
2
    }
}