[librsvg: 1/3] bbox: rework API and internals



commit 5d5b54845ff37eb29fba20a26d8505f19ccb4b1b
Author: Paolo Borelli <pborelli gnome org>
Date:   Mon Dec 9 11:23:15 2019 +0100

    bbox: rework API and internals
    
    Make the rect arg to with_rect() and with_ink_rect mandatory, add a
    clear() method to set them to None. Drop the with_extents variant
    and replace it with Rectangle::from_extents. Also move the
    transform_rect to an extension trait of Matrix, since this is the
    same API exposed by euler.
    Finally, add unit tests for the combine_rects function.

 rsvg_internals/src/bbox.rs           |  82 ++++++++++++++++++++--------
 rsvg_internals/src/drawing_ctx.rs    |   9 ++-
 rsvg_internals/src/filter.rs         |  39 ++++++-------
 rsvg_internals/src/filters/bounds.rs |   7 ++-
 rsvg_internals/src/handle.rs         |   6 +-
 rsvg_internals/src/image.rs          |   7 +--
 rsvg_internals/src/mask.rs           |  26 +++------
 rsvg_internals/src/rect.rs           | 103 ++++++++++++++++++++---------------
 rsvg_internals/src/text.rs           |  36 ++++++------
 9 files changed, 176 insertions(+), 139 deletions(-)
---
diff --git a/rsvg_internals/src/bbox.rs b/rsvg_internals/src/bbox.rs
index d8bde22a..2c4d7c82 100644
--- a/rsvg_internals/src/bbox.rs
+++ b/rsvg_internals/src/bbox.rs
@@ -1,6 +1,6 @@
 use cairo;
 
-use crate::rect::RectangleExt;
+use crate::rect::{RectangleExt, TransformRect};
 
 #[derive(Debug, Copy, Clone)]
 pub struct BoundingBox {
@@ -18,20 +18,23 @@ impl BoundingBox {
         }
     }
 
-    pub fn with_rect(self, rect: Option<cairo::Rectangle>) -> BoundingBox {
-        BoundingBox { rect, ..self }
-    }
-
-    pub fn with_ink_rect(self, ink_rect: Option<cairo::Rectangle>) -> BoundingBox {
-        BoundingBox { ink_rect, ..self }
+    pub fn with_rect(self, rect: cairo::Rectangle) -> BoundingBox {
+        BoundingBox {
+            rect: Some(rect),
+            ..self
+        }
     }
 
-    pub fn with_extents(self, extents: (f64, f64, f64, f64)) -> BoundingBox {
-        self.with_rect(rect_from_extents(extents))
+    pub fn with_ink_rect(self, ink_rect: cairo::Rectangle) -> BoundingBox {
+        BoundingBox {
+            ink_rect: Some(ink_rect),
+            ..self
+        }
     }
 
-    pub fn with_ink_extents(self, extents: (f64, f64, f64, f64)) -> BoundingBox {
-        self.with_ink_rect(rect_from_extents(extents))
+    pub fn clear(mut self) {
+        self.rect = None;
+        self.ink_rect = None;
     }
 
     fn combine(&mut self, src: &BoundingBox, clip: bool) {
@@ -58,15 +61,6 @@ impl BoundingBox {
     }
 }
 
-fn rect_from_extents((x1, y1, x2, y2): (f64, f64, f64, f64)) -> Option<cairo::Rectangle> {
-    Some(cairo::Rectangle {
-        x: x1,
-        y: y1,
-        width: x2 - x1,
-        height: y2 - y1,
-    })
-}
-
 fn combine_rects(
     r1: Option<cairo::Rectangle>,
     r2: Option<cairo::Rectangle>,
@@ -75,11 +69,51 @@ fn combine_rects(
 ) -> Option<cairo::Rectangle> {
     match (r1, r2, clip) {
         (r1, None, _) => r1,
-        (None, Some(r2), _) => Some(r2.transform(&affine)),
-        (Some(r1), Some(r2), true) => r2
-            .transform(&affine)
+        (None, Some(r2), _) => Some(affine.transform_rect(&r2)),
+        (Some(r1), Some(r2), true) => affine
+            .transform_rect(&r2)
             .intersection(&r1)
             .or_else(|| Some(cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0))),
-        (Some(r1), Some(r2), false) => Some(r2.transform(&affine).union(&r1)),
+        (Some(r1), Some(r2), false) => Some(affine.transform_rect(&r2).union(&r1)),
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn combine() {
+        let r1 = cairo::Rectangle::new(1.0, 2.0, 3.0, 4.0);
+        let r2 = cairo::Rectangle::new(1.5, 2.5, 3.0, 4.0);
+        let r3 = cairo::Rectangle::new(10.0, 11.0, 12.0, 13.0);
+        let affine = cairo::Matrix::new(1.0, 0.0, 0.0, 1.0, 0.5, 0.5);
+
+        let res = combine_rects(None, None, &affine, true);
+        assert_eq!(res, None);
+
+        let res = combine_rects(None, None, &affine, false);
+        assert_eq!(res, None);
+
+        let res = combine_rects(Some(r1), None, &affine, true);
+        assert_eq!(res, Some(r1));
+
+        let res = combine_rects(Some(r1), None, &affine, false);
+        assert_eq!(res, Some(r1));
+
+        let res = combine_rects(None, Some(r2), &affine, true);
+        assert_eq!(res, Some(cairo::Rectangle::new(2.0, 3.0, 3.0, 4.0)));
+
+        let res = combine_rects(None, Some(r2), &affine, false);
+        assert_eq!(res, Some(cairo::Rectangle::new(2.0, 3.0, 3.0, 4.0)));
+
+        let res = combine_rects(Some(r1), Some(r2), &affine, true);
+        assert_eq!(res, Some(cairo::Rectangle::new(2.0, 3.0, 2.0, 3.0)));
+
+        let res = combine_rects(Some(r1), Some(r3), &affine, true);
+        assert_eq!(res, Some(cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0)));
+
+        let res = combine_rects(Some(r1), Some(r2), &affine, false);
+        assert_eq!(res, Some(cairo::Rectangle::new(1.0, 2.0, 4.0, 5.0)));
     }
 }
diff --git a/rsvg_internals/src/drawing_ctx.rs b/rsvg_internals/src/drawing_ctx.rs
index 29e805f1..a776b468 100644
--- a/rsvg_internals/src/drawing_ctx.rs
+++ b/rsvg_internals/src/drawing_ctx.rs
@@ -965,19 +965,22 @@ fn compute_stroke_and_fill_box(cr: &cairo::Context, values: &ComputedValues) ->
     // 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());
+    let (x0, y0, x1, y1) = cr.fill_extents();
+    let fb = BoundingBox::new(&affine).with_ink_rect(cairo::Rectangle::from_extents(x0, y0, x1, y1));
     bbox.insert(&fb);
 
     // Bounding box for stroke
 
     if values.stroke.0 != PaintServer::None {
-        let sb = BoundingBox::new(&affine).with_ink_extents(cr.stroke_extents());
+        let (x0, y0, x1, y1) = cr.stroke_extents();
+        let sb = BoundingBox::new(&affine).with_ink_rect(cairo::Rectangle::from_extents(x0, y0, x1, y1));
         bbox.insert(&sb);
     }
 
     // objectBoundingBox
 
-    let ob = BoundingBox::new(&affine).with_extents(cr.path_extents());
+    let (x0, y0, x1, y1) = cr.path_extents();
+    let ob = BoundingBox::new(&affine).with_rect(cairo::Rectangle::from_extents(x0, y0, x1, y1));
     bbox.insert(&ob);
 
     // restore tolerance
diff --git a/rsvg_internals/src/filter.rs b/rsvg_internals/src/filter.rs
index a1779740..3d5cd5f1 100644
--- a/rsvg_internals/src/filter.rs
+++ b/rsvg_internals/src/filter.rs
@@ -10,6 +10,7 @@ use crate::node::{NodeResult, NodeTrait, RsvgNode};
 use crate::parsers::{Parse, ParseValue};
 use crate::properties::ComputedValues;
 use crate::property_bag::PropertyBag;
+use crate::rect::RectangleExt;
 
 /// The <filter> node.
 pub struct Filter {
@@ -75,36 +76,32 @@ impl Filter {
 
         // With filterunits == ObjectBoundingBox, lengths represent fractions or percentages of the
         // referencing node. No units are allowed (it's checked during attribute parsing).
-        let rect = if self.filterunits == CoordUnits::ObjectBoundingBox {
-            cairo::Rectangle {
-                x: self.x.length,
-                y: self.y.length,
-                width: self.width.length,
-                height: self.height.length,
-            }
+        let (x, y, w, h) = if self.filterunits == CoordUnits::ObjectBoundingBox {
+            (
+                self.x.length,
+                self.y.length,
+                self.width.length,
+                self.height.length,
+            )
         } else {
-            cairo::Rectangle {
-                x: self.x.normalize(values, &params),
-                y: self.y.normalize(values, &params),
-                width: self.width.normalize(values, &params),
-                height: self.height.normalize(values, &params),
-            }
+            (
+                self.x.normalize(values, &params),
+                self.y.normalize(values, &params),
+                self.width.normalize(values, &params),
+                self.height.normalize(values, &params),
+            )
         };
 
-        let other_bbox = BoundingBox::new(&affine).with_rect(Some(rect));
+        let rect = cairo::Rectangle::new(x, y, w, h);
+        let other_bbox = BoundingBox::new(&affine).with_rect(rect);
 
         // At this point all of the previous viewbox and matrix business gets converted to pixel
         // coordinates in the final surface, because bbox is created with an identity affine.
         bbox.insert(&other_bbox);
 
         // Finally, clip to the width and height of our surface.
-        let rect = cairo::Rectangle {
-            x: 0f64,
-            y: 0f64,
-            width,
-            height,
-        };
-        let other_bbox = BoundingBox::new(&cairo::Matrix::identity()).with_rect(Some(rect));
+        let rect = cairo::Rectangle::from_size(width, height);
+        let other_bbox = BoundingBox::new(&cairo::Matrix::identity()).with_rect(rect);
         bbox.clip(&other_bbox);
 
         bbox
diff --git a/rsvg_internals/src/filters/bounds.rs b/rsvg_internals/src/filters/bounds.rs
index 10094f9d..178a2461 100644
--- a/rsvg_internals/src/filters/bounds.rs
+++ b/rsvg_internals/src/filters/bounds.rs
@@ -64,8 +64,8 @@ impl<'a> BoundsBuilder<'a> {
                 self.standard_input_was_referenced = true;
             }
             FilterInput::PrimitiveOutput(ref output) => {
-                let input_bbox = BoundingBox::new(&cairo::Matrix::identity())
-                    .with_rect(Some(output.bounds.into()));
+                let input_bbox =
+                    BoundingBox::new(&cairo::Matrix::identity()).with_rect(output.bounds.into());
                 self.bbox.insert(&input_bbox);
             }
         }
@@ -99,7 +99,8 @@ impl<'a> BoundsBuilder<'a> {
             let effects_region = self.ctx.effects_region();
 
             // Clear out the rect.
-            self.bbox = self.bbox.with_rect(None);
+            self.bbox.clear();
+
             // Convert into the paffine coordinate system.
             self.bbox.insert(&effects_region);
         }
diff --git a/rsvg_internals/src/handle.rs b/rsvg_internals/src/handle.rs
index d2da4a8b..853d55db 100644
--- a/rsvg_internals/src/handle.rs
+++ b/rsvg_internals/src/handle.rs
@@ -320,10 +320,8 @@ impl Handle {
             if let Some((root_width, root_height)) =
                 node.borrow().get_impl::<Svg>().get_size(&values, dpi)
             {
-                let ink_r = cairo::Rectangle::from_size(
-                    f64::from(root_width),
-                    f64::from(root_height),
-                );
+                let ink_r =
+                    cairo::Rectangle::from_size(f64::from(root_width), f64::from(root_height));
 
                 let logical_r = ink_r;
 
diff --git a/rsvg_internals/src/image.rs b/rsvg_internals/src/image.rs
index 873b75e5..23b4d3af 100644
--- a/rsvg_internals/src/image.rs
+++ b/rsvg_internals/src/image.rs
@@ -99,12 +99,7 @@ impl NodeTrait for Image {
 
             // The bounding box for <image> is decided by the values of x, y, w, h and not by
             // the final computed image bounds.
-            let bbox = dc.empty_bbox().with_rect(Some(cairo::Rectangle {
-                x,
-                y,
-                width: w,
-                height: h,
-            }));
+            let bbox = dc.empty_bbox().with_rect(cairo::Rectangle::new(x, y, w, h));
 
             dc.with_saved_cr(&mut |dc| {
                 let cr = dc.get_cairo_context();
diff --git a/rsvg_internals/src/mask.rs b/rsvg_internals/src/mask.rs
index 4f67d197..ee25288d 100644
--- a/rsvg_internals/src/mask.rs
+++ b/rsvg_internals/src/mask.rs
@@ -55,6 +55,8 @@ impl Mask {
         }
 
         let bbox_rect = bbox.rect.as_ref().unwrap();
+        let (bb_x, bb_y) = (bbox_rect.x, bbox_rect.y);
+        let (bb_w, bb_h) = (bbox_rect.width, bbox_rect.height);
 
         let cascaded = CascadedValues::new_from_node(mask_node);
         let values = cascaded.get();
@@ -88,27 +90,17 @@ impl Mask {
 
             draw_ctx.push_cairo_context(mask_cr);
 
-            if mask_units == CoordUnits::ObjectBoundingBox {
-                draw_ctx.clip(
-                    x * bbox_rect.width + bbox_rect.x,
-                    y * bbox_rect.height + bbox_rect.y,
-                    w * bbox_rect.width,
-                    h * bbox_rect.height,
-                );
+            let (x, y, w, h) = if mask_units == CoordUnits::ObjectBoundingBox {
+                (x * bb_w + bb_x, y * bb_h + bb_y, w * bb_w, h * bb_h)
             } else {
-                draw_ctx.clip(x, y, w, h);
-            }
+                (x, y, w, h)
+            };
+
+            draw_ctx.clip(x, y, w, h);
 
             {
                 let _params = if content_units == CoordUnits::ObjectBoundingBox {
-                    let bbtransform = cairo::Matrix::new(
-                        bbox_rect.width,
-                        0.0,
-                        0.0,
-                        bbox_rect.height,
-                        bbox_rect.x,
-                        bbox_rect.y,
-                    );
+                    let bbtransform = cairo::Matrix::new(bb_w, 0.0, 0.0, bb_h, bb_x, bb_y);
 
                     draw_ctx.get_cairo_context().transform(bbtransform);
 
diff --git a/rsvg_internals/src/rect.rs b/rsvg_internals/src/rect.rs
index 8fa400c9..5c875be2 100644
--- a/rsvg_internals/src/rect.rs
+++ b/rsvg_internals/src/rect.rs
@@ -6,10 +6,10 @@ use crate::float_eq_cairo::ApproxEqCairo;
 pub trait RectangleExt {
     fn new(x: f64, y: f64, width: f64, height: f64) -> cairo::Rectangle;
     fn from_size(width: f64, height: f64) -> cairo::Rectangle;
+    fn from_extents(x0: f64, y0: f64, x1: f64, y1: f64) -> cairo::Rectangle;
     fn is_empty(&self) -> bool;
     fn intersection(&self, rect: &cairo::Rectangle) -> Option<cairo::Rectangle>;
     fn union(&self, rect: &cairo::Rectangle) -> cairo::Rectangle;
-    fn transform(&self, affine: &cairo::Matrix) -> cairo::Rectangle;
     fn translate(&self, by: (f64, f64)) -> cairo::Rectangle;
     fn outer(&self) -> cairo::Rectangle;
 }
@@ -33,6 +33,15 @@ impl RectangleExt for cairo::Rectangle {
         }
     }
 
+    fn from_extents(x0: f64, y0: f64, x1: f64, y1: f64) -> cairo::Rectangle {
+        cairo::Rectangle {
+            x: x0,
+            y: y0,
+            width: x1 - x0,
+            height: y1 - y0,
+        }
+    }
+
     fn is_empty(&self) -> bool {
         self.width.approx_eq_cairo(0.0) || self.height.approx_eq_cairo(0.0)
     }
@@ -73,12 +82,38 @@ impl RectangleExt for cairo::Rectangle {
         }
     }
 
-    fn transform(&self, affine: &cairo::Matrix) -> cairo::Rectangle {
+    fn translate(&self, by: (f64, f64)) -> cairo::Rectangle {
+        cairo::Rectangle {
+            x: self.x + by.0,
+            y: self.y + by.1,
+            width: self.width,
+            height: self.height,
+        }
+    }
+
+    fn outer(&self) -> cairo::Rectangle {
+        let (x, y) = (self.x.floor(), self.y.floor());
+
+        cairo::Rectangle {
+            x,
+            y,
+            width: (self.x + self.width).ceil() - x,
+            height: (self.y + self.height).ceil() - y,
+        }
+    }
+}
+
+pub trait TransformRect {
+    fn transform_rect(&self, rect: &cairo::Rectangle) -> cairo::Rectangle;
+}
+
+impl TransformRect for cairo::Matrix {
+    fn transform_rect(&self, rect: &cairo::Rectangle) -> cairo::Rectangle {
         let points = vec![
-            affine.transform_point(self.x, self.y),
-            affine.transform_point(self.x + self.width, self.y),
-            affine.transform_point(self.x, self.y + self.height),
-            affine.transform_point(self.x + self.width, self.y + self.height),
+            self.transform_point(rect.x, rect.y),
+            self.transform_point(rect.x + rect.width, rect.y),
+            self.transform_point(rect.x, rect.y + rect.height),
+            self.transform_point(rect.x + rect.width, rect.y + rect.height),
         ];
 
         let (mut xmin, mut ymin, mut xmax, mut ymax) = {
@@ -112,26 +147,6 @@ impl RectangleExt for cairo::Rectangle {
             height: ymax - ymin,
         }
     }
-
-    fn translate(&self, by: (f64, f64)) -> cairo::Rectangle {
-        cairo::Rectangle {
-            x: self.x + by.0,
-            y: self.y + by.1,
-            width: self.width,
-            height: self.height,
-        }
-    }
-
-    fn outer(&self) -> cairo::Rectangle {
-        let (x, y) = (self.x.floor(), self.y.floor());
-
-        cairo::Rectangle {
-            x,
-            y,
-            width: (self.x + self.width).ceil() - x,
-            height: (self.y + self.height).ceil() - y,
-        }
-    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -332,6 +347,22 @@ mod tests {
         assert_approx_eq_cairo!(4.34_f64, r.height);
     }
 
+    #[test]
+    fn outer_rect() {
+        let r = cairo::Rectangle {
+            x: 1.42,
+            y: 1.42,
+            width: 3.14,
+            height: 3.14,
+        };
+
+        let or = r.outer();
+        assert_eq!(1.0, or.x);
+        assert_eq!(1.0, or.y);
+        assert_eq!(4.0, or.width);
+        assert_eq!(4.0, or.height);
+    }
+
     #[test]
     fn transform_rect() {
         let r = cairo::Rectangle {
@@ -342,30 +373,14 @@ mod tests {
         };
 
         let m = cairo::Matrix::identity();
-        let tr = r.transform(&m);
+        let tr = m.transform_rect(&r);
         assert_eq!(tr, r);
 
         let m = cairo::Matrix::new(2.0, 0.0, 0.0, 2.0, 1.5, 1.5);
-        let tr = r.transform(&m);
+        let tr = m.transform_rect(&r);
         assert_approx_eq_cairo!(2.34_f64, tr.x);
         assert_approx_eq_cairo!(2.34_f64, tr.y);
         assert_approx_eq_cairo!(6.28_f64, tr.width);
         assert_approx_eq_cairo!(6.28_f64, tr.height);
     }
-
-    #[test]
-    fn outer_rect() {
-        let r = cairo::Rectangle {
-            x: 1.42,
-            y: 1.42,
-            width: 3.14,
-            height: 3.14,
-        };
-
-        let or = r.outer();
-        assert_eq!(1.0, or.x);
-        assert_eq!(1.0, or.y);
-        assert_eq!(4.0, or.width);
-        assert_eq!(4.0, or.height);
-    }
 }
diff --git a/rsvg_internals/src/text.rs b/rsvg_internals/src/text.rs
index 29f13c84..90cea086 100644
--- a/rsvg_internals/src/text.rs
+++ b/rsvg_internals/src/text.rs
@@ -28,6 +28,7 @@ use crate::property_defs::{
     XmlLang,
     XmlSpace,
 };
+use crate::rect::RectangleExt;
 use crate::space::{xml_space_normalize, NormalizeDefault, XmlSpaceNormalize};
 
 /// An absolutely-positioned array of `Span`s
@@ -350,7 +351,9 @@ impl PositionedSpan {
                     pangocairo::functions::layout_path(&cr, &self.layout);
 
                     if !clipping {
-                        let ib = BoundingBox::new(&affine).with_ink_extents(cr.stroke_extents());
+                        let (x0, y0, x1, y1) = cr.stroke_extents();
+                        let r = cairo::Rectangle::from_extents(x0, y0, x1, y1);
+                        let ib = BoundingBox::new(&affine).with_ink_rect(r);
                         cr.stroke();
                         bbox.insert(&ib);
                     }
@@ -378,25 +381,24 @@ impl PositionedSpan {
         let ink_height = f64::from(ink.height);
         let pango_scale = f64::from(pango::SCALE);
 
-        let rect = if gravity_is_vertical(gravity) {
-            cairo::Rectangle {
-                x: x + (ink_x - ink_height) / pango_scale,
-                y: y + ink_y / pango_scale,
-                width: ink_height / pango_scale,
-                height: ink_width / pango_scale,
-            }
+        let (x, y, w, h) = if gravity_is_vertical(gravity) {
+            (
+                x + (ink_x - ink_height) / pango_scale,
+                y + ink_y / pango_scale,
+                ink_height / pango_scale,
+                ink_width / pango_scale,
+            )
         } else {
-            cairo::Rectangle {
-                x: x + ink_x / pango_scale,
-                y: y + ink_y / pango_scale,
-                width: ink_width / pango_scale,
-                height: ink_height / pango_scale,
-            }
+            (
+                x + ink_x / pango_scale,
+                y + ink_y / pango_scale,
+                ink_width / pango_scale,
+                ink_height / pango_scale,
+            )
         };
 
-        let bbox = BoundingBox::new(affine)
-            .with_rect(Some(rect))
-            .with_ink_rect(Some(rect));
+        let r = cairo::Rectangle::new(x, y, w, h);
+        let bbox = BoundingBox::new(affine).with_rect(r).with_ink_rect(r);
 
         Some(bbox)
     }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]