1
//! Layout tree.
2
//!
3
//! The idea is to take the DOM tree and produce a layout tree with SVG concepts.
4

            
5
use std::rc::Rc;
6

            
7
use cssparser::Color;
8
use float_cmp::approx_eq;
9

            
10
use crate::aspect_ratio::AspectRatio;
11
use crate::cairo_path::CairoPath;
12
use crate::coord_units::CoordUnits;
13
use crate::dasharray::Dasharray;
14
use crate::document::AcquiredNodes;
15
use crate::element::{Element, ElementData};
16
use crate::filter::FilterValueList;
17
use crate::length::*;
18
use crate::node::*;
19
use crate::paint_server::{PaintSource, UserSpacePaintSource};
20
use crate::path_builder::Path as SvgPath;
21
use crate::properties::{
22
    self, ClipRule, ComputedValues, Direction, FillRule, FontFamily, FontStretch, FontStyle,
23
    FontVariant, FontWeight, ImageRendering, Isolation, MixBlendMode, Opacity, Overflow,
24
    PaintOrder, ShapeRendering, StrokeDasharray, StrokeLinecap, StrokeLinejoin, StrokeMiterlimit,
25
    TextDecoration, TextRendering, UnicodeBidi, VectorEffect, XmlLang,
26
};
27
use crate::rect::Rect;
28
use crate::rsvg_log;
29
use crate::session::Session;
30
use crate::surface_utils::shared_surface::SharedImageSurface;
31
use crate::transform::Transform;
32
use crate::unit_interval::UnitInterval;
33
use crate::viewbox::ViewBox;
34
use crate::{borrow_element_as, is_element_of_type};
35

            
36
/// SVG Stacking context, an inner node in the layout tree.
37
///
38
/// <https://www.w3.org/TR/SVG2/render.html#EstablishingStackingContex>
39
///
40
/// This is not strictly speaking an SVG2 stacking context, but a
41
/// looser version of it.  For example. the SVG spec mentions that a
42
/// an element should establish a stacking context if the `filter`
43
/// property applies to the element and is not `none`.  In that case,
44
/// the element is rendered as an "isolated group" -
45
/// <https://www.w3.org/TR/2015/CR-compositing-1-20150113/#csscompositingrules_SVG>
46
///
47
/// Here we store all the parameters that may lead to the decision to actually
48
/// render an element as an isolated group.
49
pub struct StackingContext {
50
    pub element_name: String,
51
    pub transform: Transform,
52
    pub is_visible: bool,
53
    pub opacity: Opacity,
54
    pub filter: Option<Filter>,
55
    pub clip_rect: Option<Rect>,
56
    pub clip_in_user_space: Option<Node>,
57
    pub clip_in_object_space: Option<Node>,
58
    pub mask: Option<Node>,
59
    pub mix_blend_mode: MixBlendMode,
60
    pub isolation: Isolation,
61

            
62
    /// Target from an `<a>` element
63
    pub link_target: Option<String>,
64
}
65

            
66
/// The item being rendered inside a stacking context.
67
pub struct Layer {
68
    pub kind: LayerKind,
69
    pub stacking_ctx: StackingContext,
70
}
71

            
72
pub enum LayerKind {
73
    Shape(Box<Shape>),
74
    Text(Box<Text>),
75
    Image(Box<Image>),
76
    Group(Box<Group>),
77
}
78

            
79
pub struct Group {
80
    pub children: Vec<Layer>,
81
    pub establish_viewport: Option<LayoutViewport>,
82
    pub extents: Option<Rect>,
83
}
84

            
85
/// Used for elements that need to establish a new viewport, like `<svg>`.
86
pub struct LayoutViewport {
87
    // transform goes in the group's layer's StackingContext
88
    /// Position and size of the element, per its x/y/width/height properties.
89
    /// For markers, this is markerWidth/markerHeight.
90
    pub geometry: Rect,
91

            
92
    /// viewBox attribute
93
    pub vbox: Option<ViewBox>,
94

            
95
    /// preserveAspectRatio attribute
96
    pub preserve_aspect_ratio: AspectRatio,
97

            
98
    /// overflow property
99
    pub overflow: Overflow,
100
}
101

            
102
/// Stroke parameters in user-space coordinates.
103
pub struct Stroke {
104
    pub width: f64,
105
    pub miter_limit: StrokeMiterlimit,
106
    pub line_cap: StrokeLinecap,
107
    pub line_join: StrokeLinejoin,
108
    pub dash_offset: f64,
109
    pub dashes: Box<[f64]>,
110
    // https://svgwg.org/svg2-draft/painting.html#non-scaling-stroke
111
    pub non_scaling: bool,
112
}
113

            
114
/// A path known to be representable by Cairo.
115
pub struct Path {
116
    pub cairo_path: CairoPath,
117
    pub path: Rc<SvgPath>,
118
    pub extents: Option<Rect>,
119
}
120

            
121
/// Paths and basic shapes resolved to a path.
122
pub struct Shape {
123
    pub path: Path,
124
    pub paint_order: PaintOrder,
125
    pub stroke_paint: UserSpacePaintSource,
126
    pub fill_paint: UserSpacePaintSource,
127
    pub stroke: Stroke,
128
    pub fill_rule: FillRule,
129
    pub clip_rule: ClipRule,
130
    pub shape_rendering: ShapeRendering,
131
    pub marker_start: Marker,
132
    pub marker_mid: Marker,
133
    pub marker_end: Marker,
134
}
135

            
136
pub struct Marker {
137
    pub node_ref: Option<Node>,
138
    pub context_stroke: Rc<PaintSource>,
139
    pub context_fill: Rc<PaintSource>,
140
}
141

            
142
/// Image in user-space coordinates.
143
pub struct Image {
144
    pub surface: SharedImageSurface,
145
    pub rect: Rect,
146
    pub aspect: AspectRatio,
147
    pub overflow: Overflow,
148
    pub image_rendering: ImageRendering,
149
}
150

            
151
/// A single text span in user-space coordinates.
152
pub struct TextSpan {
153
    pub layout: pango::Layout,
154
    pub gravity: pango::Gravity,
155
    pub extents: Option<Rect>,
156
    pub is_visible: bool,
157
    pub x: f64,
158
    pub y: f64,
159
    pub paint_order: PaintOrder,
160
    pub stroke: Stroke,
161
    pub stroke_paint: UserSpacePaintSource,
162
    pub fill_paint: UserSpacePaintSource,
163
    pub text_rendering: TextRendering,
164
    pub link_target: Option<String>,
165
}
166

            
167
/// Fully laid-out text in user-space coordinates.
168
pub struct Text {
169
    pub spans: Vec<TextSpan>,
170
    pub extents: Option<Rect>,
171
}
172

            
173
/// Font-related properties extracted from `ComputedValues`.
174
pub struct FontProperties {
175
    pub xml_lang: XmlLang,
176
    pub unicode_bidi: UnicodeBidi,
177
    pub direction: Direction,
178
    pub font_family: FontFamily,
179
    pub font_style: FontStyle,
180
    pub font_variant: FontVariant,
181
    pub font_weight: FontWeight,
182
    pub font_stretch: FontStretch,
183
    pub font_size: f64,
184
    pub letter_spacing: f64,
185
    pub text_decoration: TextDecoration,
186
}
187

            
188
pub struct Filter {
189
    pub filter_list: FilterValueList,
190
    pub current_color: Color,
191
    pub stroke_paint_source: Rc<PaintSource>,
192
    pub fill_paint_source: Rc<PaintSource>,
193
    pub normalize_values: NormalizeValues,
194
}
195

            
196
38140281
fn get_filter(
197
38140281
    values: &ComputedValues,
198
38140281
    acquired_nodes: &mut AcquiredNodes<'_>,
199
38140281
    session: &Session,
200
38140281
) -> Option<Filter> {
201
38140281
    match values.filter() {
202
38134524
        properties::Filter::None => None,
203

            
204
5757
        properties::Filter::List(filter_list) => Some(get_filter_from_filter_list(
205
5757
            filter_list,
206
5757
            acquired_nodes,
207
5757
            values,
208
5757
            session,
209
5757
        )),
210
    }
211
38140281
}
212

            
213
5757
fn get_filter_from_filter_list(
214
5757
    filter_list: FilterValueList,
215
5757
    acquired_nodes: &mut AcquiredNodes<'_>,
216
5757
    values: &ComputedValues,
217
5757
    session: &Session,
218
5757
) -> Filter {
219
5757
    let current_color = values.color().0;
220
5757

            
221
5757
    let stroke_paint_source = values.stroke().0.resolve(
222
5757
        acquired_nodes,
223
5757
        values.stroke_opacity().0,
224
5757
        current_color,
225
5757
        None,
226
5757
        None,
227
5757
        session,
228
5757
    );
229
5757

            
230
5757
    let fill_paint_source = values.fill().0.resolve(
231
5757
        acquired_nodes,
232
5757
        values.fill_opacity().0,
233
5757
        current_color,
234
5757
        None,
235
5757
        None,
236
5757
        session,
237
5757
    );
238
5757

            
239
5757
    let normalize_values = NormalizeValues::new(values);
240
5757

            
241
5757
    Filter {
242
5757
        filter_list,
243
5757
        current_color,
244
5757
        stroke_paint_source,
245
5757
        fill_paint_source,
246
5757
        normalize_values,
247
5757
    }
248
5757
}
249

            
250
impl StackingContext {
251
38141402
    pub fn new(
252
38141402
        session: &Session,
253
38141402
        acquired_nodes: &mut AcquiredNodes<'_>,
254
38141402
        element: &Element,
255
38141402
        transform: Transform,
256
38141402
        clip_rect: Option<Rect>,
257
38141402
        values: &ComputedValues,
258
38141402
    ) -> StackingContext {
259
38141402
        let element_name = format!("{element}");
260
38141402

            
261
38141402
        let is_visible = values.is_visible();
262
38141402

            
263
38141402
        let opacity;
264
38141402
        let filter;
265
38141402

            
266
38141402
        match element.element_data {
267
            // "The opacity, filter and display properties do not apply to the mask element"
268
            // https://drafts.fxtf.org/css-masking-1/#MaskElement
269
1121
            ElementData::Mask(_) => {
270
1121
                opacity = Opacity(UnitInterval::clamp(1.0));
271
1121
                filter = None;
272
1121
            }
273

            
274
38140281
            _ => {
275
38140281
                opacity = values.opacity();
276
38140281
                filter = get_filter(values, acquired_nodes, session);
277
38140281
            }
278
        }
279

            
280
38141402
        let clip_path = values.clip_path();
281
38141402
        let clip_uri = clip_path.0.get();
282
38141402
        let (clip_in_user_space, clip_in_object_space) = clip_uri
283
38141402
            .and_then(|node_id| {
284
874
                acquired_nodes
285
874
                    .acquire(node_id)
286
874
                    .ok()
287
874
                    .filter(|a| is_element_of_type!(*a.get(), ClipPath))
288
38141402
            })
289
38141402
            .map(|acquired| {
290
855
                let clip_node = acquired.get().clone();
291
855

            
292
855
                let units = borrow_element_as!(clip_node, ClipPath).get_units();
293
855

            
294
855
                match units {
295
760
                    CoordUnits::UserSpaceOnUse => (Some(clip_node), None),
296
95
                    CoordUnits::ObjectBoundingBox => (None, Some(clip_node)),
297
                }
298
38141402
            })
299
38141402
            .unwrap_or((None, None));
300
38141402

            
301
38141402
        let mask = values.mask().0.get().and_then(|mask_id| {
302
4180
            if let Ok(acquired) = acquired_nodes.acquire(mask_id) {
303
1216
                let node = acquired.get();
304
1216
                match *node.borrow_element_data() {
305
1216
                    ElementData::Mask(_) => Some(node.clone()),
306

            
307
                    _ => {
308
                        rsvg_log!(
309
                            session,
310
                            "element {} references \"{}\" which is not a mask",
311
                            element,
312
                            mask_id
313
                        );
314

            
315
                        None
316
                    }
317
                }
318
            } else {
319
2964
                rsvg_log!(
320
2964
                    session,
321
2964
                    "element {} references nonexistent mask \"{}\"",
322
2964
                    element,
323
2964
                    mask_id
324
2964
                );
325

            
326
2964
                None
327
            }
328
38141402
        });
329
38141402

            
330
38141402
        let mix_blend_mode = values.mix_blend_mode();
331
38141402
        let isolation = values.isolation();
332
38141402

            
333
38141402
        StackingContext {
334
38141402
            element_name,
335
38141402
            transform,
336
38141402
            is_visible,
337
38141402
            opacity,
338
38141402
            filter,
339
38141402
            clip_rect,
340
38141402
            clip_in_user_space,
341
38141402
            clip_in_object_space,
342
38141402
            mask,
343
38141402
            mix_blend_mode,
344
38141402
            isolation,
345
38141402
            link_target: None,
346
38141402
        }
347
38141402
    }
348

            
349
57
    pub fn new_with_link(
350
57
        session: &Session,
351
57
        acquired_nodes: &mut AcquiredNodes<'_>,
352
57
        element: &Element,
353
57
        transform: Transform,
354
57
        values: &ComputedValues,
355
57
        link_target: Option<String>,
356
57
    ) -> StackingContext {
357
57
        // Note that the clip_rect=Some(...) argument is only used by the markers code,
358
57
        // hence it is None here.  Something to refactor later.
359
57
        let mut ctx = Self::new(session, acquired_nodes, element, transform, None, values);
360
57
        ctx.link_target = link_target;
361
57
        ctx
362
57
    }
363

            
364
56169624
    pub fn should_isolate(&self) -> bool {
365
56169624
        let Opacity(UnitInterval(opacity)) = self.opacity;
366
56169624
        match self.isolation {
367
            Isolation::Auto => {
368
56169567
                let is_opaque = approx_eq!(f64, opacity, 1.0);
369
56169567
                !(is_opaque
370
56159326
                    && self.filter.is_none()
371
56150244
                    && self.mask.is_none()
372
56148933
                    && self.mix_blend_mode == MixBlendMode::Normal
373
56148762
                    && self.clip_in_object_space.is_none())
374
            }
375
57
            Isolation::Isolate => true,
376
        }
377
56169624
    }
378
}
379

            
380
impl LayerKind {
381
    /// Gets the extents of a layer in its local coordinate system.
382
    ///
383
    /// Each object or layer is able to compute its own extents, in its local coordinate
384
    /// system.  When the parent group layer wants to take the union of the extents of its
385
    /// children, that parent group will need to convert the children's extents using each
386
    /// child layer's transform.
387
3
    pub fn local_extents(&self) -> Option<Rect> {
388
3
        match *self {
389
2
            LayerKind::Shape(ref shape) => shape.path.extents,
390
            LayerKind::Text(ref text) => text.extents,
391
            LayerKind::Image(ref image) => Some(image.rect),
392
1
            LayerKind::Group(ref group) => group.extents,
393
        }
394
3
    }
395
}
396

            
397
impl Stroke {
398
18049090
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> Stroke {
399
18049090
        let width = values.stroke_width().0.to_user(params);
400
18049090
        let miter_limit = values.stroke_miterlimit();
401
18049090
        let line_cap = values.stroke_line_cap();
402
18049090
        let line_join = values.stroke_line_join();
403
18049090
        let dash_offset = values.stroke_dashoffset().0.to_user(params);
404
18049090
        let non_scaling = values.vector_effect() == VectorEffect::NonScalingStroke;
405

            
406
18049090
        let dashes = match values.stroke_dasharray() {
407
18047057
            StrokeDasharray(Dasharray::None) => Box::new([]),
408
2033
            StrokeDasharray(Dasharray::Array(dashes)) => dashes
409
2033
                .iter()
410
4370
                .map(|l| l.to_user(params))
411
2033
                .collect::<Box<[f64]>>(),
412
        };
413

            
414
18049090
        Stroke {
415
18049090
            width,
416
18049090
            miter_limit,
417
18049090
            line_cap,
418
18049090
            line_join,
419
18049090
            dash_offset,
420
18049090
            dashes,
421
18049090
            non_scaling,
422
18049090
        }
423
18049090
    }
424
}
425

            
426
impl FontProperties {
427
    /// Collects font properties from a `ComputedValues`.
428
    ///
429
    /// The `writing-mode` property is passed separately, as it must come from the `<text>` element,
430
    /// not the `<tspan>` whose computed values are being passed.
431
19937
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> FontProperties {
432
19937
        FontProperties {
433
19937
            xml_lang: values.xml_lang(),
434
19937
            unicode_bidi: values.unicode_bidi(),
435
19937
            direction: values.direction(),
436
19937
            font_family: values.font_family(),
437
19937
            font_style: values.font_style(),
438
19937
            font_variant: values.font_variant(),
439
19937
            font_weight: values.font_weight(),
440
19937
            font_stretch: values.font_stretch(),
441
19937
            font_size: values.font_size().to_user(params),
442
19937
            letter_spacing: values.letter_spacing().to_user(params),
443
19937
            text_decoration: values.text_decoration(),
444
19937
        }
445
19937
    }
446
}