diff options
author | Marge Bot <marge-bot@gnome.org> | 2022-10-07 03:01:55 +0000 |
---|---|---|
committer | Marge Bot <marge-bot@gnome.org> | 2022-10-07 03:01:55 +0000 |
commit | 394e7ff970d7fd610786d2150dd964abf2acd100 (patch) | |
tree | f342933e2f039e140493bc8ed8fe79cc8206351b | |
parent | e9f6b7dfcde2ce59e0d163b44766ddb03eddb72f (diff) | |
parent | fa68d27b7be77e5fff2e39f51011fffbbac02c9d (diff) | |
download | librsvg-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.rst | 2 | ||||
-rw-r--r-- | devel-docs/render_tree.rst | 227 | ||||
-rw-r--r-- | src/bbox.rs | 31 | ||||
-rw-r--r-- | src/drawing_ctx.rs | 40 | ||||
-rw-r--r-- | src/gradient.rs | 6 | ||||
-rw-r--r-- | src/layout.rs | 10 | ||||
-rw-r--r-- | src/paint_server.rs | 8 | ||||
-rw-r--r-- | src/pattern.rs | 11 | ||||
-rw-r--r-- | src/rect.rs | 35 | ||||
-rw-r--r-- | src/shapes.rs | 15 | ||||
-rw-r--r-- | src/text.rs | 5 |
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, ¶ms, values), + .to_user_space(&bbox.rect, ¶ms, values), ); let fill_paint_source = Rc::new( @@ -823,7 +823,7 @@ impl DrawingCtx { None, self.session(), ) - .to_user_space(&bbox, ¶ms, values), + .to_user_space(&bbox.rect, ¶ms, 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, |