summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarge Bot <marge-bot@gnome.org>2022-10-07 03:01:55 +0000
committerMarge Bot <marge-bot@gnome.org>2022-10-07 03:01:55 +0000
commit394e7ff970d7fd610786d2150dd964abf2acd100 (patch)
treef342933e2f039e140493bc8ed8fe79cc8206351b
parente9f6b7dfcde2ce59e0d163b44766ddb03eddb72f (diff)
parentfa68d27b7be77e5fff2e39f51011fffbbac02c9d (diff)
downloadlibrsvg-394e7ff970d7fd610786d2150dd964abf2acd100.tar.gz
Merge branch 'compute-shape-bbox' into 'main'
Let layout::Shape know its own bounds, and make it carry UserSpacePaintSource See merge request GNOME/librsvg!757
-rw-r--r--devel-docs/index.rst2
-rw-r--r--devel-docs/render_tree.rst227
-rw-r--r--src/bbox.rs31
-rw-r--r--src/drawing_ctx.rs40
-rw-r--r--src/gradient.rs6
-rw-r--r--src/layout.rs10
-rw-r--r--src/paint_server.rs8
-rw-r--r--src/pattern.rs11
-rw-r--r--src/rect.rs35
-rw-r--r--src/shapes.rs15
-rw-r--r--src/text.rs5
11 files changed, 313 insertions, 77 deletions
diff --git a/devel-docs/index.rst b/devel-docs/index.rst
index 562d85d8..34625c15 100644
--- a/devel-docs/index.rst
+++ b/devel-docs/index.rst
@@ -11,6 +11,7 @@ Development guide for librsvg
contributing
ci
text_layout
+ render_tree
api_observability
performance_tracking
releasing
@@ -67,6 +68,7 @@ request. We can then discuss it before coding. This way we will have
a sort of big-picture development history apart from commit messages.
- :doc:`text_layout`
+- :doc:`render_tree`
- :doc:`api_observability`
- :doc:`performance_tracking`
diff --git a/devel-docs/render_tree.rst b/devel-docs/render_tree.rst
new file mode 100644
index 00000000..ef7f41a8
--- /dev/null
+++ b/devel-docs/render_tree.rst
@@ -0,0 +1,227 @@
+Render tree
+===========
+
+As of 2022/Oct/06, librsvg does not compute a render tree data
+structure prior to rendering. Instead, in a very 2000s fashion, it
+walks the tree of elements and calls a ``.draw()`` method for each
+one. Each element then calls whatever methods it needs from
+``DrawingCtx`` to draw itself. Elements which don't produce graphical
+output (e.g. ``<defs>`` or ``<marker>``) simply have an empty
+``draw()`` method.
+
+Over time we have been refactoring that in the direction of actually
+being able to produce a render tree. What would that look like?
+Consider an SVG document like this:
+
+.. code-block:: xml
+
+ <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+ <defs>
+ <rect id="TheRect" x="10" y="10" width="20" height="20" fill="blue"/>
+ </defs>
+
+ <g>
+ <use href="#TheRect" stroke="red" stroke-width="2"/>
+
+ <circle cx="50" cy="50" r="20" fill="yellow"/>
+ </g>
+ </svg>
+
+A render tree would be a list of nested instructions like this:
+
+::
+
+ group { # refers to the toplevel SVG
+ width: 100
+ height: 100
+ establishes_viewport: true # because it is an <svg> element
+
+ children {
+ group { # refers to the <g>
+ establishes_viewport: false # because it is a simple <g>
+
+ children {
+ shape {
+ path="the <rect> above but resolved to path commands"
+
+ # note how the following is the cascaded style and the <use> semantics
+ fill: blue
+ stroke: red
+ stroke-width: 2
+ }
+
+ shape {
+ path="the <circle> above but resolved to path commands"
+
+ fill: yellow
+ }
+ }
+ }
+ }
+ }
+
+That is, we take the high-level SVG instructions and "lower" them to a
+few possible drawing primitives like path-based shapes that can be
+grouped. All the primitives have everything that is needed to draw
+them, like their set of computed values for styles, and their
+coordinates resolved to their user-space coordinate system.
+
+Browser engines produce render trees more or less similar to the above
+(they don't always call them that), and get various benefits:
+
+- The various recursively-nested subtrees can be rendered concurrently.
+
+- Having low-level primitives makes it easier to switch to another
+ rendering engine in the future.
+
+- The tree can be re-rendered without recomputation, or subtrees can
+ be recomputed efficiently if e.g. an animated element changes a few
+ of its properties.
+
+Why did librsvg not do that since the beginning?
+------------------------------------------------
+
+Librsvg was originally written in the early 2000s, when several things
+were happening at the same time:
+
+- libxml2 (one of the early widely-available parsers for XML) had
+ recently gotten a SAX API for parsing XML. This lets an application
+ stream in the parsed XML elements and process them one by one,
+ without having to build a tree of elements+attributes first. In
+ those days, memory was at a premium and "not producing a tree" was
+ seen as beneficial.
+
+- The SVG spec itself was being written, and it did not have all of
+ the features we know now. In particular, maybe at some point it
+ didn't have elements that worked by referencing others, like
+ ``<use>`` or ``<filter>``. The CSS cascade could be done on the fly
+ for the XML elements being streamed in, and one could emit rendering
+ commands for each element to produce the final result.
+
+That is, at that time, it was indeed feasible to do this: stream in
+parsed XML elements one by one as produced by libxml2, and for each
+element, compute its CSS cascade and render it.
+
+This scheme probably stopped working at some point when SVG got
+features that allowed referencing elements that have not been declared
+yet (think of ``<use href="#foo"/>`` but with the ``<defs> <path
+id="foo" .../> </defs>`` declared until later in the document). Or
+elements that referenced others, like ``<rect filter="url(#blah)">``.
+In both cases, one needs to actually build an in-memory tree of parsed
+elements, and *then* resolve the references between them.
+
+That is where much of the complexity of librsvg's code flow comes from:
+
+- ``AcquiredNodes`` is the thing that resolves references when needed.
+ It also detects reference cycles, which are an error.
+
+- ``ComputedValues`` often get resolved until pretty late, by passing
+ the ``CascadedValues`` state down to children as they are drawn.
+
+- ``DrawingCtx`` was originally a giant ball of mutable state, but we
+ have been whittling it down and moving part of that state elsewhere.
+
+
+Summary of the SVG rendering model
+----------------------------------
+
+FIXME:
+
+paint
+clip
+mask
+filter
+composite
+
+
+Current state
+-------------
+
+``layout.rs`` has the beginnings of the render tree. It's probably mis-named? It contains this:
+
+- A primitive for path-based shapes.
+
+- A primitive for text.
+
+- A stacking context, which indicates each layer's opacity/clip/mask/filters.
+
+- Various ancillary structures that try to have only user-space
+ coordinates (e.g. a number of CSS pixels instead of ``5cm``) and no
+ references to other things.
+
+The last point is not yet fully realized. For example,
+``StackingContext.clip_in_user_space`` has a reference to an element,
+which will be used as the clip path — that one needs to be normalized
+to user-space coordinates in the end. Also,
+``StackingContext.filter`` is a filter list as parsed from the SVG,
+not a ``FilterSpec`` that has been resolved to user space.
+
+It would be good to resolve everything as early as possible to allow
+lowering concepts to their final renderable form. Whenever we have
+done this via refactoring, it has simplified the code closer to the
+actual rendering via Cairo.
+
+Major subprojects
+-----------------
+
+Path based shapes (``layout::Shape``) and text primitives
+(``layout::Text``) are almost done. The only missing thing for shapes
+would be to "explode" their markers into the actual primitives that
+would be rendered for them. However...
+
+There is no primitive for groups yet. Every SVG element that allows
+renderable children must produce a group primitive of some sort:
+``svg``, ``g``, ``use``, ``marker``, etc. Among those, ``use` and
+``marker`` are especially interesting since they must explode their
+referenced subtree into a shadow DOM, which librsvg doesn't support
+yet for CSS cascading purposes (the reference subtree gets rendered
+properly, but the full semantics of shadow DOM are not implemented
+yet).
+
+Elements that establish a viewport (``svg``, ``symbol``, ``image``,
+``marker``, ``pattern``) need to carry information about this
+viewport, which is a ``viewBox`` plus ``preserveAspectRatio``. See #298.
+
+The ``layout::StackingContext`` struct should contain another field,
+probably called ``layer``, with something like this:
+
+.. code-block:: rust
+
+ struct StackingContext {
+ // ... all its current fields
+
+ layer: Layer
+ }
+
+ enum Layer {
+ Shape(Box<Shape>),
+ Text(Box<Text>),
+ StackingContext(Box<StackingContext>)
+ }
+
+That is, every stacking context should contain the thing that it will
+draw, and that thing may be a shape/text or another stacking context!
+
+Bounding boxes
+--------------
+
+SVG depends on the ``objectBoundingBox`` of an element in many places:
+to resolve a gradient's or pattern's units, to determine the size of
+masks and clips, to determine the size of the filter region.
+
+The current big bug to solve is #778, which requires knowing the
+``objectBoundingBox`` of an element **before** rendering it, so that a
+temporary surface of the appropriate size can be created for rendering
+the element if it has isolated opacity or masks/filters. Currently
+librsvg creates a temporary surface with the size and position of the
+toplevel viewport, and this is wrong for shapes that fall outside the
+viewport.
+
+The problem is that librsvg computes bounding boxes at the time of
+rendering, not before that. However, now ``layout::Shape`` and
+``layout::Text`` already know their bounding box beforehand. Work
+needs to be done to do the same for a ``layout::Group`` or whatever
+that primitive ends up being called (by taking the union of its
+children's bounding boxes, so e.g. that a group with a filter can
+create a temporary surface to be able to render all of its children
+and then filter the surface).
diff --git a/src/bbox.rs b/src/bbox.rs
index ab33f9a9..c851c473 100644
--- a/src/bbox.rs
+++ b/src/bbox.rs
@@ -1,6 +1,5 @@
//! Bounding boxes that know their coordinate space.
-use crate::coord_units::CoordUnits;
use crate::rect::Rect;
use crate::transform::Transform;
@@ -62,36 +61,6 @@ impl BoundingBox {
pub fn clip(&mut self, src: &BoundingBox) {
self.combine(src, true);
}
-
- /// Creates a transform to map to the `self.rect`.
- ///
- /// This depends on a `CoordUnits` parameter. When this is
- /// `CoordUnits::ObjectBoundingBox`, the bounding box must not be
- /// empty, since the calling code would then not have a usable
- /// size to work with. In that case, if the bbox is empty, this
- /// function returns `Err(())`.
- ///
- /// Usually calling code can simply ignore the action it was about
- /// to take if this function returns an error.
- pub fn rect_to_transform(&self, units: CoordUnits) -> Result<Transform, ()> {
- match units {
- CoordUnits::UserSpaceOnUse => Ok(Transform::identity()),
- CoordUnits::ObjectBoundingBox => {
- if self.rect.as_ref().map_or(true, |r| r.is_empty()) {
- Err(())
- } else {
- let r = self.rect.as_ref().unwrap();
- let t = Transform::new_unchecked(r.width(), 0.0, 0.0, r.height(), r.x0, r.y0);
-
- if t.is_invertible() {
- Ok(t)
- } else {
- Err(())
- }
- }
- }
- }
- }
}
fn combine_rects(
diff --git a/src/drawing_ctx.rs b/src/drawing_ctx.rs
index 3a617e06..fc458798 100644
--- a/src/drawing_ctx.rs
+++ b/src/drawing_ctx.rs
@@ -37,7 +37,7 @@ use crate::properties::{
ClipRule, ComputedValues, FillRule, Filter, Isolation, MaskType, MixBlendMode, Opacity,
Overflow, PaintTarget, ShapeRendering, StrokeLinecap, StrokeLinejoin, TextRendering,
};
-use crate::rect::{IRect, Rect};
+use crate::rect::{rect_to_transform, IRect, Rect};
use crate::session::Session;
use crate::surface_utils::{
shared_surface::ExclusiveImageSurface, shared_surface::SharedImageSurface,
@@ -538,7 +538,7 @@ impl DrawingCtx {
let node = clip_node.as_ref().unwrap();
let units = borrow_element_as!(node, ClipPath).get_units();
- if let Ok(transform) = bbox.rect_to_transform(units) {
+ if let Ok(transform) = rect_to_transform(&bbox.rect, units) {
let cascaded = CascadedValues::new_from_node(node);
let values = cascaded.get();
@@ -808,7 +808,7 @@ impl DrawingCtx {
None,
self.session(),
)
- .to_user_space(&bbox, &params, values),
+ .to_user_space(&bbox.rect, &params, values),
);
let fill_paint_source = Rc::new(
@@ -823,7 +823,7 @@ impl DrawingCtx {
None,
self.session(),
)
- .to_user_space(&bbox, &params, values),
+ .to_user_space(&bbox.rect, &params, values),
);
// Filter functions (like "blend()", not the <filter> element) require
@@ -1257,16 +1257,29 @@ impl DrawingCtx {
Ok(())
}
+ pub fn compute_path_extents(&self, path: &Path) -> Result<Option<Rect>, RenderingError> {
+ if path.is_empty() {
+ return Ok(None);
+ }
+
+ let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
+ let cr = cairo::Context::new(&surface)?;
+
+ path.to_cairo(&cr, false)?;
+ let (x0, y0, x1, y1) = cr.path_extents()?;
+
+ Ok(Some(Rect::new(x0, y0, x1, y1)))
+ }
+
pub fn draw_shape(
&mut self,
- view_params: &ViewParams,
shape: &Shape,
stacking_ctx: &StackingContext,
acquired_nodes: &mut AcquiredNodes<'_>,
values: &ComputedValues,
clipping: bool,
) -> Result<BoundingBox, RenderingError> {
- if shape.path.is_empty() {
+ if shape.extents.is_none() {
return Ok(self.empty_bbox());
}
@@ -1303,9 +1316,6 @@ impl DrawingCtx {
&dc.initial_viewport,
)?;
- let stroke_paint = shape.stroke_paint.to_user_space(&bbox, view_params, values);
- let fill_paint = shape.fill_paint.to_user_space(&bbox, view_params, values);
-
if shape.is_visible {
for &target in &shape.paint_order.targets {
// fill and stroke operations will preserve the path.
@@ -1313,7 +1323,7 @@ impl DrawingCtx {
match target {
PaintTarget::Fill => {
path_helper.set()?;
- dc.fill(&cr, an, &fill_paint)?;
+ dc.fill(&cr, an, &shape.fill_paint)?;
}
PaintTarget::Stroke => {
@@ -1325,7 +1335,7 @@ impl DrawingCtx {
} else {
None
};
- dc.stroke(&cr, an, &stroke_paint)?;
+ dc.stroke(&cr, an, &shape.stroke_paint)?;
if let Some(matrix) = backup_matrix {
cr.set_matrix(matrix);
}
@@ -1459,7 +1469,7 @@ impl DrawingCtx {
let bbox = compute_stroke_and_fill_box(
&self.cr,
&span.stroke,
- &span.stroke_paint_source,
+ &span.stroke_paint,
&self.initial_viewport,
)?;
self.cr.new_path();
@@ -2007,7 +2017,7 @@ impl CompositingAffines {
fn compute_stroke_and_fill_extents(
cr: &cairo::Context,
stroke: &Stroke,
- stroke_paint_source: &PaintSource,
+ stroke_paint_source: &UserSpacePaintSource,
initial_viewport: &Viewport,
) -> Result<PathExtents, RenderingError> {
// Dropping the precision of cairo's bezier subdivision, yielding 2x
@@ -2039,7 +2049,7 @@ fn compute_stroke_and_fill_extents(
// bounding box if so.
let stroke_extents = if !stroke.width.approx_eq_cairo(0.0)
- && !matches!(stroke_paint_source, PaintSource::None)
+ && !matches!(stroke_paint_source, UserSpacePaintSource::None)
{
let backup_matrix = if stroke.non_scaling {
let matrix = cr.matrix();
@@ -2076,7 +2086,7 @@ fn compute_stroke_and_fill_extents(
fn compute_stroke_and_fill_box(
cr: &cairo::Context,
stroke: &Stroke,
- stroke_paint_source: &PaintSource,
+ stroke_paint_source: &UserSpacePaintSource,
initial_viewport: &Viewport,
) -> Result<BoundingBox, RenderingError> {
let extents =
diff --git a/src/gradient.rs b/src/gradient.rs
index d44e4f2c..13e10559 100644
--- a/src/gradient.rs
+++ b/src/gradient.rs
@@ -5,7 +5,6 @@ use markup5ever::{
expanded_name, local_name, namespace_url, ns, ExpandedName, LocalName, Namespace,
};
-use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::{AcquiredNodes, NodeId, NodeStack};
use crate::drawing_ctx::ViewParams;
@@ -17,6 +16,7 @@ use crate::node::{CascadedValues, Node, NodeBorrow};
use crate::paint_server::resolve_color;
use crate::parsers::{Parse, ParseValue};
use crate::properties::ComputedValues;
+use crate::rect::{rect_to_transform, Rect};
use crate::session::Session;
use crate::transform::{Transform, TransformAttribute};
use crate::unit_interval::UnitInterval;
@@ -678,12 +678,12 @@ impl Draw for RadialGradient {}
impl ResolvedGradient {
pub fn to_user_space(
&self,
- bbox: &BoundingBox,
+ object_bbox: &Option<Rect>,
current_params: &ViewParams,
values: &ComputedValues,
) -> Option<UserSpaceGradient> {
let units = self.units.0;
- let transform = bbox.rect_to_transform(units).ok()?;
+ let transform = rect_to_transform(object_bbox, units).ok()?;
let view_params = current_params.with_units(units);
let params = NormalizeParams::new(values, &view_params);
diff --git a/src/layout.rs b/src/layout.rs
index be858196..f0c79c4f 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -68,17 +68,14 @@ pub struct Stroke {
}
/// Paths and basic shapes resolved to a path.
-///
-/// Note that `stroke_paint` and `fill_paint` are not in user-space coordinates;
-/// they are just resolved to a `PaintSource`. Turning them to a `UserSpacePaintSource`
-/// involves knowing the bounding box of the path.
pub struct Shape {
pub path: Rc<Path>,
+ pub extents: Option<Rect>,
pub is_visible: bool,
pub paint_order: PaintOrder,
pub stroke: Stroke,
- pub stroke_paint: Arc<PaintSource>,
- pub fill_paint: Arc<PaintSource>,
+ pub stroke_paint: UserSpacePaintSource,
+ pub fill_paint: UserSpacePaintSource,
pub fill_rule: FillRule,
pub clip_rule: ClipRule,
pub shape_rendering: ShapeRendering,
@@ -113,7 +110,6 @@ pub struct TextSpan {
pub paint_order: PaintOrder,
pub stroke: Stroke,
pub stroke_paint: UserSpacePaintSource,
- pub stroke_paint_source: Arc<PaintSource>,
pub fill_paint: UserSpacePaintSource,
pub text_rendering: TextRendering,
pub link_target: Option<String>,
diff --git a/src/paint_server.rs b/src/paint_server.rs
index e9832bd3..07d8dafc 100644
--- a/src/paint_server.rs
+++ b/src/paint_server.rs
@@ -4,7 +4,6 @@ use std::sync::Arc;
use cssparser::Parser;
-use crate::bbox::BoundingBox;
use crate::document::{AcquiredNodes, NodeId};
use crate::drawing_ctx::ViewParams;
use crate::element::Element;
@@ -14,6 +13,7 @@ use crate::node::NodeBorrow;
use crate::parsers::Parse;
use crate::pattern::{ResolvedPattern, UserSpacePattern};
use crate::properties::ComputedValues;
+use crate::rect::Rect;
use crate::session::Session;
use crate::unit_interval::UnitInterval;
use crate::util;
@@ -242,7 +242,7 @@ impl PaintSource {
/// Converts lengths to user-space.
pub fn to_user_space(
&self,
- bbox: &BoundingBox,
+ object_bbox: &Option<Rect>,
current_params: &ViewParams,
values: &ComputedValues,
) -> UserSpacePaintSource {
@@ -251,7 +251,7 @@ impl PaintSource {
PaintSource::SolidColor(c) => UserSpacePaintSource::SolidColor(c),
PaintSource::Gradient(ref g, c) => {
- match (g.to_user_space(bbox, current_params, values), c) {
+ match (g.to_user_space(object_bbox, current_params, values), c) {
(Some(gradient), c) => UserSpacePaintSource::Gradient(gradient, c),
(None, Some(c)) => UserSpacePaintSource::SolidColor(c),
(None, None) => UserSpacePaintSource::None,
@@ -259,7 +259,7 @@ impl PaintSource {
}
PaintSource::Pattern(ref p, c) => {
- match (p.to_user_space(bbox, current_params, values), c) {
+ match (p.to_user_space(object_bbox, current_params, values), c) {
(Some(pattern), c) => UserSpacePaintSource::Pattern(pattern, c),
(None, Some(c)) => UserSpacePaintSource::SolidColor(c),
(None, None) => UserSpacePaintSource::None,
diff --git a/src/pattern.rs b/src/pattern.rs
index 6b1d394c..6841586e 100644
--- a/src/pattern.rs
+++ b/src/pattern.rs
@@ -3,7 +3,6 @@
use markup5ever::{expanded_name, local_name, namespace_url, ns};
use crate::aspect_ratio::*;
-use crate::bbox::BoundingBox;
use crate::coord_units::CoordUnits;
use crate::document::{AcquiredNode, AcquiredNodes, NodeId, NodeStack};
use crate::drawing_ctx::ViewParams;
@@ -308,8 +307,8 @@ impl UnresolvedChildren {
}
}
-fn nonempty_rect_from_bbox(bbox: &BoundingBox) -> Option<Rect> {
- match bbox.rect {
+fn nonempty_rect(bbox: &Option<Rect>) -> Option<Rect> {
+ match *bbox {
None => None,
Some(r) if r.is_empty() => None,
Some(r) => Some(r),
@@ -338,7 +337,7 @@ impl ResolvedPattern {
pub fn to_user_space(
&self,
- bbox: &BoundingBox,
+ object_bbox: &Option<Rect>,
current_params: &ViewParams,
values: &ComputedValues,
) -> Option<UserSpacePattern> {
@@ -352,7 +351,7 @@ impl ResolvedPattern {
// Create the pattern coordinate system
let (width, height, coord_transform) = match self.units {
PatternUnits(CoordUnits::ObjectBoundingBox) => {
- let bbrect = nonempty_rect_from_bbox(bbox)?;
+ let bbrect = nonempty_rect(object_bbox)?;
(
rect.width() * bbrect.width(),
rect.height() * bbrect.height(),
@@ -387,7 +386,7 @@ impl ResolvedPattern {
} else {
match self.content_units {
PatternContentUnits(CoordUnits::ObjectBoundingBox) => {
- let bbrect = nonempty_rect_from_bbox(bbox)?;
+ let bbrect = nonempty_rect(object_bbox)?;
Transform::new_scale(bbrect.width(), bbrect.height())
}
PatternContentUnits(CoordUnits::UserSpaceOnUse) => Transform::identity(),
diff --git a/src/rect.rs b/src/rect.rs
index 8ec4f1ed..b0bbb201 100644
--- a/src/rect.rs
+++ b/src/rect.rs
@@ -1,5 +1,8 @@
//! Types for rectangles.
+use crate::coord_units::CoordUnits;
+use crate::transform::Transform;
+
#[allow(clippy::module_inception)]
mod rect {
use crate::float_eq_cairo::ApproxEqCairo;
@@ -205,6 +208,38 @@ impl From<Rect> for cairo::Rectangle {
}
}
+/// Creates a transform to map to a rectangle.
+///
+/// The rectangle is an `Option<Rect>` to indicate the possibility that there is no
+/// bounding box from where the rectangle could be obtained.
+///
+/// This depends on a `CoordUnits` parameter. When this is
+/// `CoordUnits::ObjectBoundingBox`, the bounding box must not be empty, since the calling
+/// code would then not have a usable size to work with. In that case, if the bbox is
+/// empty, this function returns `Err(())`.
+///
+/// Usually calling code can simply ignore the action it was about to take if this
+/// function returns an error.
+pub fn rect_to_transform(rect: &Option<Rect>, units: CoordUnits) -> Result<Transform, ()> {
+ match units {
+ CoordUnits::UserSpaceOnUse => Ok(Transform::identity()),
+ CoordUnits::ObjectBoundingBox => {
+ if rect.as_ref().map_or(true, |r| r.is_empty()) {
+ Err(())
+ } else {
+ let r = rect.as_ref().unwrap();
+ let t = Transform::new_unchecked(r.width(), 0.0, 0.0, r.height(), r.x0, r.y0);
+
+ if t.is_invertible() {
+ Ok(t)
+ } else {
+ Err(())
+ }
+ }
+ }
+ }
+}
+
pub type IRect = rect::Rect<i32>;
impl From<IRect> for Rect {
diff --git a/src/shapes.rs b/src/shapes.rs
index b21e6339..ad8e3259 100644
--- a/src/shapes.rs
+++ b/src/shapes.rs
@@ -116,8 +116,14 @@ fn draw_basic_shape(
context_fill: fill_paint.clone(),
};
+ let extents = draw_ctx.compute_path_extents(&shape_def.path)?;
+
+ let stroke_paint = stroke_paint.to_user_space(&extents, &view_params, values);
+ let fill_paint = fill_paint.to_user_space(&extents, &view_params, values);
+
let shape = Shape {
path: shape_def.path,
+ extents,
is_visible,
paint_order,
stroke,
@@ -140,14 +146,7 @@ fn draw_basic_shape(
values,
);
- draw_ctx.draw_shape(
- &view_params,
- &shape,
- &stacking_ctx,
- acquired_nodes,
- values,
- clipping,
- )
+ draw_ctx.draw_shape(&shape, &stacking_ctx, acquired_nodes, values, clipping)
}
macro_rules! impl_draw {
diff --git a/src/text.rs b/src/text.rs
index 430ffcc0..6115d09a 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -830,12 +830,12 @@ impl Draw for Text {
let mut text_spans = Vec::new();
for span in layout_spans {
let stroke_paint = span.stroke_paint.to_user_space(
- &text_bbox,
+ &text_bbox.rect,
&layout_context.view_params,
&span.values,
);
let fill_paint = span.fill_paint.to_user_space(
- &text_bbox,
+ &text_bbox.rect,
&layout_context.view_params,
&span.values,
);
@@ -850,7 +850,6 @@ impl Draw for Text {
paint_order: span.paint_order,
stroke: span.stroke,
stroke_paint,
- stroke_paint_source: span.stroke_paint,
fill_paint,
text_rendering: span.text_rendering,
link_target: span.link_target,