drawing_ctx.rs 36.4 KB
Newer Older
1
use cairo;
2
use cairo::MatrixTrait;
3 4
use cairo_sys;
use glib::translate::*;
5
use glib_sys;
6
use libc;
7
use pango::{self, ContextExt, FontMapExt, LayoutExt};
8
use pango_cairo_sys;
9
use pango_sys;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
10
use pangocairo;
11
use std::cell::RefCell;
12
use std::rc::{Rc, Weak};
13

14
use bbox::BoundingBox;
15 16
use clip_path::{ClipPathUnits, NodeClipPath};
use coord_units::CoordUnits;
17
use defs::{Defs, RsvgDefs};
18
use error::RenderingError;
19
use filters;
20 21
use float_eq_cairo::ApproxEqCairo;
use length::Dasharray;
22
use mask::NodeMask;
23
use node::{CascadedValues, NodeType, RsvgNode};
24
use paint_server::{self, PaintServer};
25
use rect::RectangleExt;
26
use state::{
27
    ClipRule,
28 29 30
    CompOp,
    ComputedValues,
    EnableBackground,
31 32
    FillRule,
    ShapeRendering,
33
    StrokeDasharray,
34 35 36
    StrokeLinecap,
    StrokeLinejoin,
    TextRendering,
37
};
Paolo Borelli's avatar
Paolo Borelli committed
38
use tree::{RsvgTree, Tree};
39
use unitinterval::UnitInterval;
40
use viewbox::ViewBox;
41

42
/// Holds values that are required to normalize `Length` values to a current viewport.
43 44 45
///
/// This struct is created by calling `DrawingCtx::push_view_box()` or
/// `DrawingCtx::get_view_params()`.
46 47 48 49 50
///
/// This struct holds the size of the current viewport in the user's coordinate system.  A
/// viewport pushed with `DrawingCtx::push_view_box()` will remain in place until the
/// returned `ViewParams` is dropped; at that point, the `DrawingCtx` will resume using its
/// previous viewport.
51
pub struct ViewParams {
52 53 54 55
    dpi_x: f64,
    dpi_y: f64,
    view_box_width: f64,
    view_box_height: f64,
56
    view_box_stack: Option<Weak<RefCell<Vec<ViewBox>>>>,
57 58 59 60 61 62 63 64 65 66
}

impl ViewParams {
    #[cfg(test)]
    pub fn new(dpi_x: f64, dpi_y: f64, view_box_width: f64, view_box_height: f64) -> ViewParams {
        ViewParams {
            dpi_x,
            dpi_y,
            view_box_width,
            view_box_height,
67
            view_box_stack: None,
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
        }
    }

    pub fn dpi_x(&self) -> f64 {
        self.dpi_x
    }

    pub fn dpi_y(&self) -> f64 {
        self.dpi_y
    }

    pub fn view_box_width(&self) -> f64 {
        self.view_box_width
    }

    pub fn view_box_height(&self) -> f64 {
        self.view_box_height
    }
86 87
}

88 89
impl Drop for ViewParams {
    fn drop(&mut self) {
90 91 92 93
        if let Some(ref weak_stack) = self.view_box_stack {
            let stack = weak_stack
                .upgrade()
                .expect("A ViewParams was dropped after its DrawingCtx!?");
94 95 96 97 98
            stack.borrow_mut().pop();
        }
    }
}

99 100
pub enum RsvgDrawingCtx {}

101
pub struct DrawingCtx<'a> {
102 103 104
    rect: cairo::Rectangle,
    dpi_x: f64,
    dpi_y: f64,
105

106 107 108 109 110 111 112 113 114 115 116 117
    /// This is a mitigation for the security-related bug
    /// https://gitlab.gnome.org/GNOME/librsvg/issues/323 - imagine
    /// the XML [billion laughs attack], but done by creating deeply
    /// nested groups of `<use>` elements.  The first one references
    /// the second one ten times, the second one references the third
    /// one ten times, and so on.  In the file given, this causes
    /// 10^17 objects to be rendered.  While this does not exhaust
    /// memory, it would take a really long time.
    ///
    /// [billion laughs attack]: https://bitbucket.org/tiran/defusedxml
    num_elements_rendered_through_use: usize,

118 119 120
    cr_stack: Vec<cairo::Context>,
    cr: cairo::Context,
    initial_cr: cairo::Context,
121

122
    surfaces_stack: Vec<cairo::ImageSurface>,
123

124
    view_box_stack: Rc<RefCell<Vec<ViewBox>>>,
125

126 127
    bbox: BoundingBox,
    bbox_stack: Vec<BoundingBox>,
128

129
    drawsub_stack: Vec<RsvgNode>,
130

131
    defs: RefCell<&'a mut Defs>,
132
    acquired_nodes: Rc<RefCell<Vec<RsvgNode>>>,
133

134 135
    is_testing: bool,
}
136

137
impl<'a> DrawingCtx<'a> {
138 139
    pub fn new(
        cr: cairo::Context,
140 141
        width: f64,
        height: f64,
142 143 144 145
        vb_width: f64,
        vb_height: f64,
        dpi_x: f64,
        dpi_y: f64,
146
        defs: &mut Defs,
147
        is_testing: bool,
148
    ) -> DrawingCtx<'_> {
149 150 151 152 153 154
        let mut affine = cr.get_matrix();
        let rect = cairo::Rectangle {
            x: 0.0,
            y: 0.0,
            width,
            height,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
155 156
        }
        .transform(&affine)
Jordan Petridis's avatar
Jordan Petridis committed
157
        .outer();
158 159 160 161 162 163 164 165 166 167 168 169

        // scale according to size set by size_func callback
        let mut scale = cairo::Matrix::identity();
        scale.scale(width / vb_width, height / vb_height);
        affine = cairo::Matrix::multiply(&affine, &scale);

        // adjust transform so that the corner of the
        // bounding box above is at (0,0)
        affine.x0 -= rect.x;
        affine.y0 -= rect.y;
        cr.set_matrix(affine);

170 171 172
        let mut view_box_stack = Vec::new();
        view_box_stack.push(ViewBox::new(0.0, 0.0, vb_width, vb_height));

173 174 175 176
        DrawingCtx {
            rect,
            dpi_x,
            dpi_y,
177
            num_elements_rendered_through_use: 0,
178
            cr_stack: Vec::new(),
179 180 181
            cr: cr.clone(),
            initial_cr: cr.clone(),
            surfaces_stack: Vec::new(),
182
            view_box_stack: Rc::new(RefCell::new(view_box_stack)),
183 184 185
            bbox: BoundingBox::new(&affine),
            bbox_stack: Vec::new(),
            drawsub_stack: Vec::new(),
186
            defs: RefCell::new(defs),
187
            acquired_nodes: Rc::new(RefCell::new(Vec::new())),
188 189
            is_testing,
        }
190 191
    }

192 193
    pub fn get_cairo_context(&self) -> cairo::Context {
        self.cr.clone()
194
    }
195

196 197 198 199 200 201 202
    // FIXME: Usage of this function is more less a hack... The caller
    // manually saves and then restore the draw_ctx.cr.
    // It would be better to have an explicit push/pop for the cairo_t, or
    // pushing a temporary surface, or something that does not involve
    // monkeypatching the cr directly.
    pub fn set_cairo_context(&mut self, cr: &cairo::Context) {
        self.cr = cr.clone();
Jordan Petridis's avatar
Jordan Petridis committed
203
    }
204

205
    pub fn is_cairo_context_nested(&self, cr: &cairo::Context) -> bool {
206
        cr.to_raw_none() != self.initial_cr.to_raw_none()
Jordan Petridis's avatar
Jordan Petridis committed
207
    }
208

209
    pub fn get_cr_stack(&self) -> &Vec<cairo::Context> {
210 211
        &self.cr_stack
    }
212

213 214
    pub fn get_width(&self) -> f64 {
        self.rect.width
Jordan Petridis's avatar
Jordan Petridis committed
215
    }
216

217 218
    pub fn get_height(&self) -> f64 {
        self.rect.height
Jordan Petridis's avatar
Jordan Petridis committed
219
    }
220

221 222 223
    pub fn get_raw_offset(&self) -> (f64, f64) {
        (self.rect.x, self.rect.y)
    }
224

225 226 227 228 229
    pub fn get_offset(&self) -> (f64, f64) {
        if self.is_cairo_context_nested(&self.get_cairo_context()) {
            (0.0, 0.0)
        } else {
            (self.rect.x, self.rect.y)
230
        }
231
    }
232

233
    /// Gets the viewport that was last pushed with `push_view_box()`.
234
    pub fn get_view_params(&self) -> ViewParams {
235 236
        let view_box_stack = self.view_box_stack.borrow();
        let last = view_box_stack.len() - 1;
237
        let stack_top = &view_box_stack[last];
238

239 240 241
        ViewParams {
            dpi_x: self.dpi_x,
            dpi_y: self.dpi_y,
242 243 244
            view_box_width: stack_top.0.width,
            view_box_height: stack_top.0.height,
            view_box_stack: None,
245 246 247
        }
    }

248 249 250 251 252 253 254
    /// Pushes a viewport size for normalizing `Length` values.
    ///
    /// You should pass the returned `ViewParams` to all subsequent `Length.normalize()`
    /// calls that correspond to this viewport.
    ///
    /// The viewport will stay in place, and will be the one returned by
    /// `get_view_params()`, until the returned `ViewParams` is dropped.
255
    pub fn push_view_box(&self, width: f64, height: f64) -> ViewParams {
256 257 258
        self.view_box_stack
            .borrow_mut()
            .push(ViewBox::new(0.0, 0.0, width, height));
259

260 261 262 263 264
        ViewParams {
            dpi_x: self.dpi_x,
            dpi_y: self.dpi_y,
            view_box_width: width,
            view_box_height: height,
265
            view_box_stack: Some(Rc::downgrade(&self.view_box_stack)),
266
        }
267
    }
268

269 270 271
    pub fn insert_bbox(&mut self, bbox: &BoundingBox) {
        self.bbox.insert(bbox);
    }
272

273 274 275
    pub fn set_bbox(&mut self, bbox: &BoundingBox) {
        self.bbox = *bbox;
    }
276

277 278 279
    pub fn get_bbox(&self) -> &BoundingBox {
        &self.bbox
    }
280

281 282 283 284 285 286 287 288 289 290
    // Use this function when looking up urls to other nodes. This function
    // does proper recursion checking and thereby avoids infinite loops.
    //
    // Nodes acquired by this function must be released in reverse
    // acquiring order.
    //
    // Note that if you acquire a node, you have to release it before trying to
    // acquire it again.  If you acquire a node "#foo" and don't release it before
    // trying to acquire "foo" again, you will obtain a %NULL the second time.
    pub fn get_acquired_node(&mut self, url: &str) -> Option<AcquiredNode> {
291
        if let Some(node) = self.defs.borrow_mut().lookup(url) {
292
            if !self.acquired_nodes_contains(node) {
293
                self.acquired_nodes.borrow_mut().push(node.clone());
294 295
                let acq = AcquiredNode(self.acquired_nodes.clone(), node.clone());
                return Some(acq);
296 297 298
            }
        }

299 300
        None
    }
301

302
    fn acquired_nodes_contains(&self, node: &RsvgNode) -> bool {
303 304 305
        self.acquired_nodes
            .borrow()
            .iter()
306
            .find(|n| Rc::ptr_eq(n, node))
307
            .is_some()
308 309
    }

310 311 312 313 314 315 316 317 318 319 320 321
    // Use this function when looking up urls to other nodes, and when you expect
    // the node to be of a particular type. This function does proper recursion
    // checking and thereby avoids infinite loops.
    //
    // Malformed SVGs, for example, may reference a marker by its IRI, but
    // the object referenced by the IRI is not a marker.
    //
    // Note that if you acquire a node, you have to release it before trying to
    // acquire it again.  If you acquire a node "#foo" and don't release it before
    // trying to acquire "foo" again, you will obtain a None the second time.
    //
    // For convenience, this function will return None if url is None.
322 323

    // FIXME: return a Result<AcquiredNode, RenderingError::InvalidReference>
324 325 326 327 328 329 330 331 332 333 334
    pub fn get_acquired_node_of_type(
        &mut self,
        url: Option<&str>,
        node_type: NodeType,
    ) -> Option<AcquiredNode> {
        url.and_then(move |url| self.get_acquired_node(url))
            .and_then(|acquired| {
                if acquired.get().get_type() == node_type {
                    Some(acquired)
                } else {
                    None
335
                }
336 337
            })
    }
338

339 340 341 342 343
    pub fn with_discrete_layer(
        &mut self,
        node: &RsvgNode,
        values: &ComputedValues,
        clipping: bool,
344
        draw_fn: &mut FnMut(&mut DrawingCtx<'_>) -> Result<(), RenderingError>,
345
    ) -> Result<(), RenderingError> {
346
        if clipping {
347
            draw_fn(self)
348 349 350
        } else {
            let original_cr = self.cr.clone();
            original_cr.save();
351

352 353
            let clip_uri = values.clip_path.0.get();
            let mask = values.mask.0.get();
354

355 356 357 358 359 360 361
            // The `filter` property does not apply to masks.
            let filter = if node.get_type() == NodeType::Mask {
                None
            } else {
                values.filter.0.get()
            };

362 363 364
            let UnitInterval(opacity) = values.opacity.0;
            let comp_op = values.comp_op;
            let enable_background = values.enable_background;
365

366
            let affine = original_cr.get_matrix();
367

368 369 370 371 372 373 374 375 376 377
            let (acquired_clip, clip_units) = {
                if let Some(acquired) = self.get_acquired_node_of_type(clip_uri, NodeType::ClipPath)
                {
                    let ClipPathUnits(units) = acquired
                        .get()
                        .with_impl(|clip_path: &NodeClipPath| clip_path.get_units());

                    (Some(acquired), Some(units))
                } else {
                    (None, None)
378
                }
379
            };
380

381
            if clip_units == Some(CoordUnits::UserSpaceOnUse) {
382 383 384 385
                let clip_node = acquired_clip.as_ref().unwrap().get();
                let res = clip_node.with_impl(|clip_path: &NodeClipPath| {
                    clip_path.to_cairo_context(clip_node, &affine, self)
                });
386 387 388 389

                if let Err(e) = res {
                    original_cr.restore();
                    return Err(e);
390 391 392
                }
            }

393 394 395
            let needs_temporary_surface = !(opacity == 1.0
                && filter.is_none()
                && mask.is_none()
396 397 398 399
                && (clip_units == None || clip_units == Some(CoordUnits::UserSpaceOnUse))
                && comp_op == CompOp::SrcOver
                && enable_background == EnableBackground::Accumulate);

400 401 402 403 404
            if needs_temporary_surface {
                let surface = cairo::ImageSurface::create(
                    cairo::Format::ARgb32,
                    self.rect.width as i32,
                    self.rect.height as i32,
405
                )?;
406 407 408

                if filter.is_some() {
                    self.surfaces_stack.push(surface.clone());
409
                }
410 411 412 413 414 415 416 417 418 419

                let cr = cairo::Context::new(&surface);
                cr.set_matrix(affine);

                self.cr_stack.push(self.cr.clone());
                self.cr = cr.clone();

                self.bbox_stack.push(self.bbox);
                self.bbox = BoundingBox::new(&affine);
            }
420

421
            let mut res = draw_fn(self);
422

423
            if needs_temporary_surface {
424 425
                let child_surface = cairo::ImageSurface::from(self.cr.get_target()).unwrap();

426 427 428 429 430
                let filter_result_surface = if let Some(filter_uri) = filter {
                    self.run_filter(filter_uri, values, &child_surface)?
                } else {
                    child_surface
                };
431

432
                self.cr = self.cr_stack.pop().unwrap();
433

434
                let (xofs, yofs) = self.get_offset();
435

436 437
                original_cr.identity_matrix();
                original_cr.set_source_surface(&filter_result_surface, xofs, yofs);
438

439
                if clip_units == Some(CoordUnits::ObjectBoundingBox) {
440 441 442 443
                    let clip_node = acquired_clip.as_ref().unwrap().get();
                    let res = clip_node.with_impl(|clip_path: &NodeClipPath| {
                        clip_path.to_cairo_context(clip_node, &affine, self)
                    });
444 445 446 447

                    if let Err(e) = res {
                        original_cr.restore();
                        return Err(e);
448 449
                    }
                }
450

451
                original_cr.set_operator(cairo::Operator::from(comp_op));
452

453 454 455 456 457
                if let Some(mask) = mask {
                    if let Some(acquired) =
                        self.get_acquired_node_of_type(Some(mask), NodeType::Mask)
                    {
                        let node = acquired.get();
458

459 460 461 462
                        res = res.and_then(|_| {
                            node.with_impl(|mask: &NodeMask| {
                                mask.generate_cairo_mask(&node, &affine, self)
                            })
463 464 465 466 467 468 469
                        });
                    }
                } else if opacity < 1.0 {
                    original_cr.paint_with_alpha(opacity);
                } else {
                    original_cr.paint();
                }
470

471 472 473 474
                let bbox = self.bbox;
                self.bbox = self.bbox_stack.pop().unwrap();
                self.bbox.insert(&bbox);
            }
475

476
            original_cr.restore();
477 478

            res
479
        }
480 481
    }

482 483 484 485 486 487 488 489 490 491 492 493 494 495
    fn run_filter(
        &mut self,
        filter_uri: &str,
        values: &ComputedValues,
        child_surface: &cairo::ImageSurface,
    ) -> Result<cairo::ImageSurface, RenderingError> {
        let output = self.surfaces_stack.pop().unwrap();

        match self.get_acquired_node_of_type(Some(filter_uri), NodeType::Filter) {
            Some(acquired) => {
                let filter_node = acquired.get();

                if !filter_node.is_in_error() {
                    // FIXME: deal with out of memory here
496
                    filters::render(&filter_node, values, &output, self)
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
                } else {
                    Ok(child_surface.clone())
                }
            }

            None => {
                // Non-existing filters must act as null filters (that is, an
                // empty surface is returned).
                Ok(cairo::ImageSurface::create(
                    cairo::Format::ARgb32,
                    child_surface.get_width(),
                    child_surface.get_height(),
                )?)
            }
        }
    }

514 515 516 517 518
    pub fn get_pango_context(&self) -> pango::Context {
        let font_map = pangocairo::FontMap::get_default().unwrap();
        let context = font_map.create_context().unwrap();
        let cr = self.get_cairo_context();
        pangocairo::functions::update_context(&cr, &context);
519

520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
        // 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.
        set_resolution(&context, 72.0);
537

538 539
        if self.is_testing {
            let mut options = cairo::FontOptions::new();
540

541 542 543
            options.set_antialias(cairo::Antialias::Gray);
            options.set_hint_style(cairo::enums::HintStyle::Full);
            options.set_hint_metrics(cairo::enums::HintMetrics::On);
544

545 546
            set_font_options(&context, &options);
        }
547

548
        context
549 550
    }

551 552 553 554 555 556 557
    pub fn draw_pango_layout(
        &mut self,
        layout: &pango::Layout,
        values: &ComputedValues,
        x: f64,
        y: f64,
        clipping: bool,
558
    ) -> Result<(), RenderingError> {
559 560 561
        let (ink, _) = layout.get_extents();

        if ink.width == 0 || ink.height == 0 {
562
            return Ok(());
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
        }

        let cr = self.get_cairo_context();
        cr.save();

        self.set_affine_on_cr(&cr);

        let affine = cr.get_matrix();

        let gravity = layout.get_context().unwrap().get_gravity();
        let bbox = compute_text_bbox(&ink, x, y, &affine, gravity);

        if !clipping {
            self.insert_bbox(&bbox);
        }

        cr.set_antialias(cairo::Antialias::from(values.text_rendering));

        self.setup_cr_for_stroke(&cr, values);

        let rotation = unsafe { pango_sys::pango_gravity_to_rotation(gravity.to_glib()) };

        cr.move_to(x, y);
        if !rotation.approx_eq_cairo(&0.0) {
            cr.rotate(-rotation);
        }

        let current_color = &values.color.0;

        let fill_opacity = &values.fill_opacity.0;

594 595
        let res = if !clipping {
            paint_server::set_source_paint_server(
596 597 598 599 600
                self,
                &values.fill.0,
                fill_opacity,
                &bbox,
                current_color,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
601 602
            )
            .and_then(|had_paint_server| {
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
                if had_paint_server {
                    pangocairo::functions::update_layout(&cr, layout);
                    pangocairo::functions::show_layout(&cr, layout);
                };
                Ok(())
            })
        } else {
            Ok(())
        };

        if res.is_ok() {
            let stroke_opacity = &values.stroke_opacity.0;

            let mut need_layout_path = clipping;

            let res = if !clipping {
                paint_server::set_source_paint_server(
                    self,
                    &values.stroke.0,
                    stroke_opacity,
                    &bbox,
                    &current_color,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
625 626
                )
                .and_then(|had_paint_server| {
627 628 629 630 631 632 633 634
                    if had_paint_server {
                        need_layout_path = true;
                    }
                    Ok(())
                })
            } else {
                Ok(())
            };
635

636 637 638 639
            if res.is_ok() {
                if need_layout_path {
                    pangocairo::functions::update_layout(&cr, layout);
                    pangocairo::functions::layout_path(&cr, layout);
640

641 642 643 644 645 646
                    if !clipping {
                        let ib = BoundingBox::new(&affine).with_ink_extents(cr.stroke_extents());
                        cr.stroke();
                        self.insert_bbox(&ib);
                    }
                }
647 648 649 650
            }
        }

        cr.restore();
651 652

        res
653 654
    }

655
    fn setup_cr_for_stroke(&self, cr: &cairo::Context, values: &ComputedValues) {
656 657 658
        let params = self.get_view_params();

        cr.set_line_width(values.stroke_width.0.normalize(values, &params));
659 660 661 662 663
        cr.set_miter_limit(values.stroke_miterlimit.0);
        cr.set_line_cap(cairo::LineCap::from(values.stroke_line_cap));
        cr.set_line_join(cairo::LineJoin::from(values.stroke_line_join));

        if let StrokeDasharray(Dasharray::Array(ref dashes)) = values.stroke_dasharray {
664 665 666 667
            let normalized_dashes: Vec<f64> = dashes
                .iter()
                .map(|l| l.normalize(values, &params))
                .collect();
668 669 670 671

            let total_length = normalized_dashes.iter().fold(0.0, |acc, &len| acc + len);

            if total_length > 0.0 {
672
                let offset = values.stroke_dashoffset.0.normalize(values, &params);
673 674 675 676 677 678 679
                cr.set_dash(&normalized_dashes, offset);
            } else {
                cr.set_dash(&[], 0.0);
            }
        }
    }

680 681 682 683 684
    pub fn stroke_and_fill(
        &mut self,
        cr: &cairo::Context,
        values: &ComputedValues,
    ) -> Result<(), RenderingError> {
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
        cr.set_antialias(cairo::Antialias::from(values.shape_rendering));

        self.setup_cr_for_stroke(cr, values);

        // Update the bbox in the rendering context.  Below, we actually set the
        // fill/stroke patterns on the cairo_t.  That process requires the
        // rendering context to have an updated bbox; for example, for the
        // coordinate system in patterns.
        let bbox = compute_stroke_and_fill_box(cr, values);
        self.insert_bbox(&bbox);

        let current_color = &values.color.0;

        let fill_opacity = &values.fill_opacity.0;

700
        let res = paint_server::set_source_paint_server(
701 702 703 704 705
            self,
            &values.fill.0,
            fill_opacity,
            &bbox,
            current_color,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
706 707
        )
        .and_then(|had_paint_server| {
708 709 710 711 712 713
            if had_paint_server {
                if values.stroke.0 == PaintServer::None {
                    cr.fill();
                } else {
                    cr.fill_preserve();
                }
714 715
            }

716
            Ok(())
Federico Mena Quintero's avatar
Federico Mena Quintero committed
717 718
        })
        .and_then(|_| {
719
            let stroke_opacity = values.stroke_opacity.0;
720

721 722 723 724 725 726
            paint_server::set_source_paint_server(
                self,
                &values.stroke.0,
                &stroke_opacity,
                &bbox,
                &current_color,
Federico Mena Quintero's avatar
Federico Mena Quintero committed
727 728
            )
            .and_then(|had_paint_server| {
729 730 731 732 733 734
                if had_paint_server {
                    cr.stroke();
                }
                Ok(())
            })
        });
735 736 737 738

        // clear the path in case stroke == fill == None; otherwise
        // we leave it around from computing the bounding box
        cr.new_path();
739 740

        res
741 742
    }

743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
    pub fn set_affine_on_cr(&self, cr: &cairo::Context) {
        let (x0, y0) = self.get_offset();
        let affine = cr.get_matrix();
        let matrix = cairo::Matrix::new(
            affine.xx,
            affine.yx,
            affine.xy,
            affine.yy,
            affine.x0 + x0,
            affine.y0 + y0,
        );
        cr.set_matrix(matrix);
    }

    pub fn clip(&mut self, x: f64, y: f64, w: f64, h: f64) {
        let cr = self.get_cairo_context();
        let save_affine = cr.get_matrix();

        self.set_affine_on_cr(&cr);

        cr.rectangle(x, y, w, h);
        cr.clip();
        cr.set_matrix(save_affine);
    }

768 769 770
    pub fn draw_node_on_surface(
        &mut self,
        node: &RsvgNode,
771
        cascaded: &CascadedValues<'_>,
772 773 774
        surface: &cairo::ImageSurface,
        width: f64,
        height: f64,
775
    ) -> Result<(), RenderingError> {
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
        let save_cr = self.cr.clone();
        let save_initial_cr = self.initial_cr.clone();
        let save_rect = self.rect;
        let save_affine = self.get_cairo_context().get_matrix();

        let cr = cairo::Context::new(surface);
        cr.set_matrix(save_affine);

        self.cr = cr;
        self.initial_cr = self.cr.clone();
        self.rect.x = 0.0;
        self.rect.y = 0.0;
        self.rect.width = width;
        self.rect.height = height;

791
        let res = self.draw_node_from_stack(cascaded, node, false);
792 793 794 795

        self.cr = save_cr;
        self.initial_cr = save_initial_cr;
        self.rect = save_rect;
796 797

        res
798
    }
799

800 801
    pub fn draw_node_from_stack(
        &mut self,
802
        cascaded: &CascadedValues<'_>,
803 804
        node: &RsvgNode,
        clipping: bool,
805
    ) -> Result<(), RenderingError> {
806
        let mut draw = true;
807
        let mut res = Ok(());
808

809 810 811
        let stack_top = self.drawsub_stack.pop();

        if let Some(ref top) = stack_top {
812
            if !Rc::ptr_eq(top, node) {
813 814
                draw = false;
            }
815 816 817 818 819
        }

        if draw {
            let values = cascaded.get();
            if values.is_visible() {
820
                res = node.draw(node, cascaded, self, clipping);
821
            }
822
        }
823 824 825 826

        if let Some(top) = stack_top {
            self.drawsub_stack.push(top);
        }
827

828 829 830 831
        if res.is_ok() {
            res = self.check_limits();
        }

832
        res
833 834
    }

835
    pub fn add_node_and_ancestors_to_stack(&mut self, node: &RsvgNode) {
836 837 838 839 840
        self.drawsub_stack.push(node.clone());
        if let Some(ref parent) = node.get_parent() {
            self.add_node_and_ancestors_to_stack(parent);
        }
    }
841 842 843 844

    pub fn increase_num_elements_rendered_through_use(&mut self, n: usize) {
        self.num_elements_rendered_through_use += n;
    }
845 846 847 848 849 850 851 852

    fn check_limits(&self) -> Result<(), RenderingError> {
        if self.num_elements_rendered_through_use > 500_000 {
            Err(RenderingError::InstancingLimit)
        } else {
            Ok(())
        }
    }
853 854
}

855 856
// remove this binding once pangocairo-rs has ContextExt::set_resolution()
fn set_resolution(context: &pango::Context, dpi: f64) {
857
    unsafe {
858
        pango_cairo_sys::pango_cairo_context_set_resolution(context.to_glib_none().0, dpi);
859 860 861
    }
}

862 863
// remove this binding once pangocairo-rs has ContextExt::set_font_options()
fn set_font_options(context: &pango::Context, options: &cairo::FontOptions) {
864
    unsafe {
865 866 867 868
        pango_cairo_sys::pango_cairo_context_set_font_options(
            context.to_glib_none().0,
            options.to_glib_none().0,
        );
869 870 871
    }
}

872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
// FIXME: should the pango crate provide this like PANGO_GRAVITY_IS_VERTICAL() ?
fn gravity_is_vertical(gravity: pango::Gravity) -> bool {
    match gravity {
        pango::Gravity::East | pango::Gravity::West => true,
        _ => false,
    }
}

fn compute_text_bbox(
    ink: &pango::Rectangle,
    x: f64,
    y: f64,
    affine: &cairo::Matrix,
    gravity: pango::Gravity,
) -> BoundingBox {
    let pango_scale = f64::from(pango::SCALE);

    let ink_x = f64::from(ink.x);
    let ink_y = f64::from(ink.y);
    let ink_width = f64::from(ink.width);
    let ink_height = f64::from(ink.height);

894 895
    let rect = if gravity_is_vertical(gravity) {
        cairo::Rectangle {
896 897 898 899
            x: x + (ink_x - ink_height) / pango_scale,
            y: y + ink_y / pango_scale,
            width: ink_height / pango_scale,
            height: ink_width / pango_scale,
900
        }
901
    } else {
902
        cairo::Rectangle {
903 904 905 906
            x: x + ink_x / pango_scale,
            y: y + ink_y / pango_scale,
            width: ink_width / pango_scale,
            height: ink_height / pango_scale,
907 908 909 910 911 912
        }
    };

    BoundingBox::new(affine)
        .with_rect(Some(rect))
        .with_ink_rect(Some(rect))
913 914
}

915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
fn compute_stroke_and_fill_box(cr: &cairo::Context, values: &ComputedValues) -> BoundingBox {
    let affine = cr.get_matrix();

    let mut bbox = BoundingBox::new(&affine);

    // Dropping the precision of cairo's bezier subdivision, yielding 2x
    // _rendering_ time speedups, are these rather expensive operations
    // really needed here? */
    let backup_tolerance = cr.get_tolerance();
    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.

    let fb = BoundingBox::new(&affine).with_ink_extents(cr.fill_extents());
    bbox.insert(&fb);

    // Bounding box for stroke

    if values.stroke.0 != PaintServer::None {
        let sb = BoundingBox::new(&affine).with_ink_extents(cr.stroke_extents());
        bbox.insert(&sb);
    }

    // objectBoundingBox

    let ob = BoundingBox::new(&affine).with_extents(path_extents(cr));
    bbox.insert(&ob);

    // restore tolerance

    cr.set_tolerance(backup_tolerance);

    bbox
}

// remove this binding once cairo-rs has Context::path_extents()
fn path_extents(cr: &cairo::Context) -> (f64, f64, f64, f64) {
    let mut x1: f64 = 0.0;
    let mut y1: f64 = 0.0;
    let mut x2: f64 = 0.0;
    let mut y2: f64 = 0.0;

    unsafe {
        cairo_sys::cairo_path_extents(cr.to_glib_none().0, &mut x1, &mut y1, &mut x2, &mut y2);
    }
    (x1, y1, x2, y2)
}

impl From<StrokeLinejoin> for cairo::LineJoin {
    fn from(j: StrokeLinejoin) -> cairo::LineJoin {
        match j {
            StrokeLinejoin::Miter => cairo::LineJoin::Miter,
            StrokeLinejoin::Round => cairo::LineJoin::Round,
            StrokeLinejoin::Bevel => cairo::LineJoin::Bevel,
        }
    }
}

impl From<StrokeLinecap> for cairo::LineCap {
    fn from(j: StrokeLinecap) -> cairo::LineCap {
        match j {
            StrokeLinecap::Butt => cairo::LineCap::Butt,
            StrokeLinecap::Round => cairo::LineCap::Round,
            StrokeLinecap::Square => cairo::LineCap::Square,
        }
    }
}

impl From<CompOp> for cairo::Operator {
    fn from(op: CompOp) -> cairo::Operator {
        match op {
            CompOp::Clear => cairo::Operator::Clear,
            CompOp::Src => cairo::Operator::Source,
            CompOp::Dst => cairo::Operator::Dest,
            CompOp::SrcOver => cairo::Operator::Over,
            CompOp::DstOver => cairo::Operator::DestOver,
            CompOp::SrcIn => cairo::Operator::In,
            CompOp::DstIn => cairo::Operator::DestIn,
            CompOp::SrcOut => cairo::Operator::Out,
            CompOp::DstOut => cairo::Operator::DestOut,
            CompOp::SrcAtop => cairo::Operator::Atop,
            CompOp::DstAtop => cairo::Operator::DestAtop,
            CompOp::Xor => cairo::Operator::Xor,
            CompOp::Plus => cairo::Operator::Add,
            CompOp::Multiply => cairo::Operator::Multiply,
            CompOp::Screen => cairo::Operator::Screen,
            CompOp::Overlay => cairo::Operator::Overlay,
            CompOp::Darken => cairo::Operator::Darken,
            CompOp::Lighten => cairo::Operator::Lighten,
            CompOp::ColorDodge => cairo::Operator::ColorDodge,
            CompOp::ColorBurn => cairo::Operator::ColorBurn,
            CompOp::HardLight => cairo::Operator::HardLight,
            CompOp::SoftLight => cairo::Operator::SoftLight,
            CompOp::Difference => cairo::Operator::Difference,
            CompOp::Exclusion => cairo::Operator::Exclusion,
        }
    }
}

impl From<ClipRule> for cairo::FillRule {
    fn from(c: ClipRule) -> cairo::FillRule {
        match c {
            ClipRule::NonZero => cairo::FillRule::Winding,
            ClipRule::EvenOdd => cairo::FillRule::EvenOdd,
        }
    }
}

impl From<FillRule> for cairo::FillRule {
    fn from(f: FillRule) -> cairo::FillRule {
        match f {
            FillRule::NonZero => cairo::FillRule::Winding,
            FillRule::EvenOdd => cairo::FillRule::EvenOdd,
        }
    }
}

impl From<ShapeRendering> for cairo::Antialias {
    fn from(sr: ShapeRendering) -> cairo::Antialias {
        match sr {
            ShapeRendering::Auto | ShapeRendering::GeometricPrecision => cairo::Antialias::Default,
            ShapeRendering::OptimizeSpeed | ShapeRendering::CrispEdges => cairo::Antialias::None,
        }
    }
}

impl From<TextRendering> for cairo::Antialias {
    fn from(tr: TextRendering) -> cairo::Antialias {
        match tr {
            TextRendering::Auto
            | TextRendering::OptimizeLegibility
            | TextRendering::GeometricPrecision => cairo::Antialias::Default,
            TextRendering::OptimizeSpeed => cairo::Antialias::None,
        }
    }
}

1058 1059
#[no_mangle]
pub extern "C" fn rsvg_drawing_ctx_draw_node_from_stack(
1060
    raw_draw_ctx: *mut RsvgDrawingCtx,
Paolo Borelli's avatar
Paolo Borelli committed
1061
    raw_tree: *const RsvgTree,
1062
) -> glib_sys::gboolean {
1063
    assert!(!raw_draw_ctx.is_null());
1064
    let draw_ctx = unsafe { &mut *(raw_draw_ctx as *mut DrawingCtx<'_>) };
1065

Paolo Borelli's avatar
Paolo Borelli committed
1066 1067
    assert!(!raw_tree.is_null());
    let tree = unsafe { &*(raw_tree as *const Tree) };
1068

1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079
    // FIXME: The public API doesn't let us return a GError from the rendering
    // functions, just a boolean.  Add a proper API to return proper errors from
    // the rendering path.
    if draw_ctx
        .draw_node_from_stack(&tree.root.get_cascaded_values(), &tree.root, false)
        .is_ok()
    {
        true.to_glib()
    } else {
        false.to_glib()
    }
1080 1081 1082 1083
}

#[no_mangle]
pub extern "C" fn rsvg_drawing_ctx_add_node_and_ancestors_to_stack(
1084
    raw_draw_ctx: *const RsvgDrawingCtx,
1085 1086
    raw_node: *const RsvgNode,
) {
1087
    assert!(!raw_draw_ctx.is_null());
1088
    let draw_ctx = unsafe { &mut *(raw_draw_ctx as *mut DrawingCtx<'_>) };
1089 1090 1091 1092

    assert!(!raw_node.is_null());
    let node = unsafe { &*raw_node };

1093
    draw_ctx.add_node_and_ancestors_to_stack(node);
1094 1095
}

1096 1097
#[no_mangle]
pub extern "C" fn rsvg_drawing_ctx_get_ink_rect(
1098
    raw_draw_ctx: *const RsvgDrawingCtx,
1099
    ink_rect: *mut cairo_sys::cairo_rectangle_t,
1100
) -> glib_sys::gboolean {
1101
    assert!(!raw_draw_ctx.is_null());
1102
    let draw_ctx = unsafe { &mut *(raw_draw_ctx as *mut DrawingCtx<'_>) };
1103

1104 1105
    assert!(!ink_rect.is_null());

1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
    let res = match draw_ctx.get_bbox().ink_rect {
        Some(r) => unsafe {
            (*ink_rect).x = r.x;
            (*ink_rect).y = r.y;
            (*ink_rect).width = r.width;
            (*ink_rect).height = r.height;
            true
        },
        _ => false,
    };

    res.to_glib()
1118 1119
}

1120
pub struct AcquiredNode(Rc<RefCell<Vec<RsvgNode>>>, RsvgNode);
1121

1122
impl Drop for AcquiredNode {
1123
    fn drop(&mut self) {
1124 1125 1126
        let mut v = self.0.borrow_mut();
        assert!(Rc::ptr_eq(v.last().unwrap(), &self.1));
        v.pop();
1127 1128 1129
    }
}

1130
impl AcquiredNode {
1131 1132
    pub fn get(&self) -> &RsvgNode {
        &self.1
1133 1134
    }
}
1135

1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156
/// Keeps a stack of nodes and can check if a certain node is contained in the stack
///
/// Sometimes parts of the code cannot plainly use the implicit stack of acquired
/// nodes as maintained by DrawingCtx::get_acquired_node(), and they must keep their
/// own stack of nodes to test for reference cycles.  NodeStack can be used to do that.
pub struct NodeStack(Vec<RsvgNode>);

impl NodeStack {
    pub fn new() -> NodeStack {
        NodeStack(Vec::new())
    }

    pub fn push(&mut self, node: &RsvgNode) {
        self.0.push(node.clone());
    }

    pub fn contains(&self, node: &RsvgNode) -> bool {
        self.0.iter().find(|n| Rc::ptr_eq(n, node)).is_some()
    }
}

1157
#[no_mangle]
1158
pub extern "C" fn rsvg_drawing_ctx_new(
1159
    cr: *mut cairo_sys::cairo_t,
1160 1161 1162 1163 1164 1165
    width: u32,
    height: u32,
    vb_width: libc::c_double,
    vb_height: libc::c_double,
    dpi_x: libc::c_double,
    dpi_y: libc::c_double,
1166
    defs: *mut RsvgDefs,
1167 1168
    is_testing: glib_sys::gboolean,
) -> *mut RsvgDrawingCtx {
1169
    assert!(!defs.is_null());
1170
    let defs = unsafe { &mut *(defs as *mut Defs) };
1171

1172
    Box::into_raw(Box::new(DrawingCtx::new(
1173
        unsafe { from_glib_none(cr) },
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187
        f64::from(width),
        f64::from(height),
        vb_width,
        vb_height,
        dpi_x,
        dpi_y,
        defs,
        from_glib(is_testing),
    ))) as *mut RsvgDrawingCtx
}

#[no_mangle]
pub extern "C" fn rsvg_drawing_ctx_free(raw_draw_ctx: *mut RsvgDrawingCtx) {
    assert!(!raw_draw_ctx.is_null());
1188
    let draw_ctx = unsafe { &mut *(raw_draw_ctx as *mut DrawingCtx<'_>) };
1189 1190

    unsafe {
1191
        Box::from_raw(draw_ctx);
1192 1193
    }
}