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::bbox::BoundingBox;
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;
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 opacity: Opacity,
53
    pub filter: Option<Filter>,
54
    pub clip_rect: Option<Rect>,
55
    pub clip_in_user_space: Option<Node>,
56
    pub clip_in_object_space: Option<Node>,
57
    pub mask: Option<Node>,
58
    pub mix_blend_mode: MixBlendMode,
59
    pub isolation: Isolation,
60

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

            
65
/// The item being rendered inside a stacking context.
66
pub struct Layer {
67
    pub kind: LayerKind,
68
    pub stacking_ctx: StackingContext,
69
}
70
pub enum LayerKind {
71
    Shape(Box<Shape>),
72
    Text(Box<Text>),
73
    Image(Box<Image>),
74
    Group(Box<Group>),
75
}
76

            
77
pub struct Group {
78
    pub children: Vec<Layer>,
79
    pub is_visible: bool, // FIXME: move to Layer?  All of them have this...
80
    pub establish_viewport: Option<LayoutViewport>,
81
}
82

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

            
90
    /// viewBox attribute
91
    pub vbox: Option<ViewBox>,
92

            
93
    /// preserveAspectRatio attribute
94
    pub preserve_aspect_ratio: AspectRatio,
95

            
96
    /// overflow property
97
    pub overflow: Overflow,
98
}
99

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

            
112
/// Paths and basic shapes resolved to a path.
113
pub struct Shape {
114
    pub path: Rc<Path>,
115
    pub extents: Option<Rect>,
116
    pub is_visible: bool,
117
    pub paint_order: PaintOrder,
118
    pub stroke: Stroke,
119
    pub stroke_paint: UserSpacePaintSource,
120
    pub fill_paint: UserSpacePaintSource,
121
    pub fill_rule: FillRule,
122
    pub clip_rule: ClipRule,
123
    pub shape_rendering: ShapeRendering,
124
    pub marker_start: Marker,
125
    pub marker_mid: Marker,
126
    pub marker_end: Marker,
127
}
128

            
129
pub struct Marker {
130
    pub node_ref: Option<Node>,
131
    pub context_stroke: Rc<PaintSource>,
132
    pub context_fill: Rc<PaintSource>,
133
}
134

            
135
/// Image in user-space coordinates.
136
pub struct Image {
137
    pub surface: SharedImageSurface,
138
    pub is_visible: bool,
139
    pub rect: Rect,
140
    pub aspect: AspectRatio,
141
    pub overflow: Overflow,
142
    pub image_rendering: ImageRendering,
143
}
144

            
145
/// A single text span in user-space coordinates.
146
pub struct TextSpan {
147
    pub layout: pango::Layout,
148
    pub gravity: pango::Gravity,
149
    pub bbox: Option<BoundingBox>,
150
    pub is_visible: bool,
151
    pub x: f64,
152
    pub y: f64,
153
    pub paint_order: PaintOrder,
154
    pub stroke: Stroke,
155
    pub stroke_paint: UserSpacePaintSource,
156
    pub fill_paint: UserSpacePaintSource,
157
    pub text_rendering: TextRendering,
158
    pub link_target: Option<String>,
159
}
160

            
161
/// Fully laid-out text in user-space coordinates.
162
pub struct Text {
163
    pub spans: Vec<TextSpan>,
164
}
165

            
166
/// Font-related properties extracted from `ComputedValues`.
167
pub struct FontProperties {
168
    pub xml_lang: XmlLang,
169
    pub unicode_bidi: UnicodeBidi,
170
    pub direction: Direction,
171
    pub font_family: FontFamily,
172
    pub font_style: FontStyle,
173
    pub font_variant: FontVariant,
174
    pub font_weight: FontWeight,
175
    pub font_stretch: FontStretch,
176
    pub font_size: f64,
177
    pub letter_spacing: f64,
178
    pub text_decoration: TextDecoration,
179
}
180

            
181
pub struct Filter {
182
    pub filter_list: FilterValueList,
183
    pub current_color: Color,
184
    pub stroke_paint_source: Rc<PaintSource>,
185
    pub fill_paint_source: Rc<PaintSource>,
186
    pub normalize_values: NormalizeValues,
187
}
188

            
189
2005244
fn get_filter(
190
    values: &ComputedValues,
191
    acquired_nodes: &mut AcquiredNodes<'_>,
192
    session: &Session,
193
) -> Option<Filter> {
194
2005244
    match values.filter() {
195
2004943
        properties::Filter::None => None,
196

            
197
301
        properties::Filter::List(filter_list) => Some(get_filter_from_filter_list(
198
            filter_list,
199
            acquired_nodes,
200
            values,
201
            session,
202
        )),
203
    }
204
2005244
}
205

            
206
301
fn get_filter_from_filter_list(
207
    filter_list: FilterValueList,
208
    acquired_nodes: &mut AcquiredNodes<'_>,
209
    values: &ComputedValues,
210
    session: &Session,
211
) -> Filter {
212
301
    let current_color = values.color().0;
213

            
214
602
    let stroke_paint_source = values.stroke().0.resolve(
215
        acquired_nodes,
216
301
        values.stroke_opacity().0,
217
        current_color,
218
301
        None,
219
301
        None,
220
        session,
221
301
    );
222

            
223
602
    let fill_paint_source = values.fill().0.resolve(
224
        acquired_nodes,
225
301
        values.fill_opacity().0,
226
        current_color,
227
301
        None,
228
301
        None,
229
        session,
230
301
    );
231

            
232
301
    let normalize_values = NormalizeValues::new(values);
233

            
234
301
    Filter {
235
301
        filter_list,
236
        current_color,
237
301
        stroke_paint_source,
238
301
        fill_paint_source,
239
        normalize_values,
240
    }
241
301
}
242

            
243
impl StackingContext {
244
2005857
    pub fn new(
245
        session: &Session,
246
        acquired_nodes: &mut AcquiredNodes<'_>,
247
        element: &Element,
248
        transform: Transform,
249
        clip_rect: Option<Rect>,
250
        values: &ComputedValues,
251
    ) -> StackingContext {
252
2005857
        let element_name = format!("{element}");
253

            
254
        let opacity;
255
        let filter;
256

            
257
2005857
        match element.element_data {
258
            // "The opacity, filter and display properties do not apply to the mask element"
259
            // https://drafts.fxtf.org/css-masking-1/#MaskElement
260
58
            ElementData::Mask(_) => {
261
58
                opacity = Opacity(UnitInterval::clamp(1.0));
262
58
                filter = None;
263
            }
264

            
265
2005239
            _ => {
266
2005799
                opacity = values.opacity();
267
2005375
                filter = get_filter(values, acquired_nodes, session);
268
            }
269
        }
270

            
271
2005297
        let clip_path = values.clip_path();
272
2006388
        let clip_uri = clip_path.0.get();
273
2005957
        let (clip_in_user_space, clip_in_object_space) = clip_uri
274
2006003
            .and_then(|node_id| {
275
46
                acquired_nodes
276
                    .acquire(node_id)
277
                    .ok()
278
45
                    .filter(|a| is_element_of_type!(*a.get(), ClipPath))
279
46
            })
280
45
            .map(|acquired| {
281
45
                let clip_node = acquired.get().clone();
282

            
283
45
                let units = borrow_element_as!(clip_node, ClipPath).get_units();
284

            
285
45
                match units {
286
40
                    CoordUnits::UserSpaceOnUse => (Some(clip_node), None),
287
5
                    CoordUnits::ObjectBoundingBox => (None, Some(clip_node)),
288
                }
289
45
            })
290
2005562
            .unwrap_or((None, None));
291

            
292
2005587
        let mask = values.mask().0.get().and_then(|mask_id| {
293
62
            if let Ok(acquired) = acquired_nodes.acquire(mask_id) {
294
60
                let node = acquired.get();
295
60
                match *node.borrow_element_data() {
296
60
                    ElementData::Mask(_) => Some(node.clone()),
297

            
298
                    _ => {
299
                        rsvg_log!(
300
                            session,
301
                            "element {} references \"{}\" which is not a mask",
302
                            element,
303
                            mask_id
304
                        );
305

            
306
                        None
307
                    }
308
                }
309
60
            } else {
310
1
                rsvg_log!(
311
1
                    session,
312
                    "element {} references nonexistent mask \"{}\"",
313
                    element,
314
                    mask_id
315
                );
316

            
317
1
                None
318
            }
319
2004341
        });
320

            
321
2004050
        let mix_blend_mode = values.mix_blend_mode();
322
2003986
        let isolation = values.isolation();
323

            
324
2003820
        StackingContext {
325
2003820
            element_name,
326
            transform,
327
2003820
            opacity,
328
2003820
            filter,
329
            clip_rect,
330
2003820
            clip_in_user_space,
331
2003820
            clip_in_object_space,
332
2003820
            mask,
333
            mix_blend_mode,
334
            isolation,
335
2003820
            link_target: None,
336
        }
337
2003820
    }
338

            
339
3
    pub fn new_with_link(
340
        session: &Session,
341
        acquired_nodes: &mut AcquiredNodes<'_>,
342
        element: &Element,
343
        transform: Transform,
344
        values: &ComputedValues,
345
        link_target: Option<String>,
346
    ) -> StackingContext {
347
        // Note that the clip_rect=Some(...) argument is only used by the markers code,
348
        // hence it is None here.  Something to refactor later.
349
3
        let mut ctx = Self::new(session, acquired_nodes, element, transform, None, values);
350
3
        ctx.link_target = link_target;
351
3
        ctx
352
3
    }
353

            
354
2953046
    pub fn should_isolate(&self) -> bool {
355
2953046
        let Opacity(UnitInterval(opacity)) = self.opacity;
356
2953046
        match self.isolation {
357
            Isolation::Auto => {
358
2953043
                let is_opaque = approx_eq!(f64, opacity, 1.0);
359
5905104
                !(is_opaque
360
2952061
                    && self.filter.is_none()
361
2951048
                    && self.mask.is_none()
362
2950277
                    && self.mix_blend_mode == MixBlendMode::Normal
363
2950006
                    && self.clip_in_object_space.is_none())
364
2951105
            }
365
3
            Isolation::Isolate => true,
366
        }
367
2951108
    }
368
}
369

            
370
impl Stroke {
371
949027
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> Stroke {
372
949027
        let width = values.stroke_width().0.to_user(params);
373
949027
        let miter_limit = values.stroke_miterlimit();
374
949027
        let line_cap = values.stroke_line_cap();
375
949027
        let line_join = values.stroke_line_join();
376
949027
        let dash_offset = values.stroke_dashoffset().0.to_user(params);
377
949027
        let non_scaling = values.vector_effect() == VectorEffect::NonScalingStroke;
378

            
379
949027
        let dashes = match values.stroke_dasharray() {
380
948920
            StrokeDasharray(Dasharray::None) => Box::new([]),
381
107
            StrokeDasharray(Dasharray::Array(dashes)) => dashes
382
                .iter()
383
337
                .map(|l| l.to_user(params))
384
107
                .collect::<Box<[f64]>>(),
385
        };
386

            
387
949027
        Stroke {
388
            width,
389
            miter_limit,
390
            line_cap,
391
            line_join,
392
            dash_offset,
393
949027
            dashes,
394
            non_scaling,
395
        }
396
949027
    }
397
}
398

            
399
impl FontProperties {
400
    /// Collects font properties from a `ComputedValues`.
401
    ///
402
    /// The `writing-mode` property is passed separately, as it must come from the `<text>` element,
403
    /// not the `<tspan>` whose computed values are being passed.
404
1039
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> FontProperties {
405
1039
        FontProperties {
406
1039
            xml_lang: values.xml_lang(),
407
1039
            unicode_bidi: values.unicode_bidi(),
408
1039
            direction: values.direction(),
409
1039
            font_family: values.font_family(),
410
1039
            font_style: values.font_style(),
411
1039
            font_variant: values.font_variant(),
412
1039
            font_weight: values.font_weight(),
413
1039
            font_stretch: values.font_stretch(),
414
1039
            font_size: values.font_size().to_user(params),
415
1039
            letter_spacing: values.letter_spacing().to_user(params),
416
1039
            text_decoration: values.text_decoration(),
417
        }
418
1039
    }
419
}