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::{borrow_element_as, is_element_of_type};
34

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

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

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

            
75
/// Stroke parameters in user-space coordinates.
76
pub struct Stroke {
77
    pub width: f64,
78
    pub miter_limit: StrokeMiterlimit,
79
    pub line_cap: StrokeLinecap,
80
    pub line_join: StrokeLinejoin,
81
    pub dash_offset: f64,
82
    pub dashes: Box<[f64]>,
83
    // https://svgwg.org/svg2-draft/painting.html#non-scaling-stroke
84
    pub non_scaling: bool,
85
}
86

            
87
/// Paths and basic shapes resolved to a path.
88
pub struct Shape {
89
    pub path: Rc<Path>,
90
    pub extents: Option<Rect>,
91
    pub is_visible: bool,
92
    pub paint_order: PaintOrder,
93
    pub stroke: Stroke,
94
    pub stroke_paint: UserSpacePaintSource,
95
    pub fill_paint: UserSpacePaintSource,
96
    pub fill_rule: FillRule,
97
    pub clip_rule: ClipRule,
98
    pub shape_rendering: ShapeRendering,
99
    pub marker_start: Marker,
100
    pub marker_mid: Marker,
101
    pub marker_end: Marker,
102
}
103

            
104
pub struct Marker {
105
    pub node_ref: Option<Node>,
106
    pub context_stroke: Rc<PaintSource>,
107
    pub context_fill: Rc<PaintSource>,
108
}
109

            
110
/// Image in user-space coordinates.
111
pub struct Image {
112
    pub surface: SharedImageSurface,
113
    pub is_visible: bool,
114
    pub rect: Rect,
115
    pub aspect: AspectRatio,
116
    pub overflow: Overflow,
117
    pub image_rendering: ImageRendering,
118
}
119

            
120
/// A single text span in user-space coordinates.
121
pub struct TextSpan {
122
    pub layout: pango::Layout,
123
    pub gravity: pango::Gravity,
124
    pub bbox: Option<BoundingBox>,
125
    pub is_visible: bool,
126
    pub x: f64,
127
    pub y: f64,
128
    pub paint_order: PaintOrder,
129
    pub stroke: Stroke,
130
    pub stroke_paint: UserSpacePaintSource,
131
    pub fill_paint: UserSpacePaintSource,
132
    pub text_rendering: TextRendering,
133
    pub link_target: Option<String>,
134
}
135

            
136
/// Fully laid-out text in user-space coordinates.
137
pub struct Text {
138
    pub spans: Vec<TextSpan>,
139
}
140

            
141
/// Font-related properties extracted from `ComputedValues`.
142
pub struct FontProperties {
143
    pub xml_lang: XmlLang,
144
    pub unicode_bidi: UnicodeBidi,
145
    pub direction: Direction,
146
    pub font_family: FontFamily,
147
    pub font_style: FontStyle,
148
    pub font_variant: FontVariant,
149
    pub font_weight: FontWeight,
150
    pub font_stretch: FontStretch,
151
    pub font_size: f64,
152
    pub letter_spacing: f64,
153
    pub text_decoration: TextDecoration,
154
}
155

            
156
pub struct Filter {
157
    pub filter_list: FilterValueList,
158
    pub current_color: Color,
159
    pub stroke_paint_source: Rc<PaintSource>,
160
    pub fill_paint_source: Rc<PaintSource>,
161
    pub normalize_values: NormalizeValues,
162
}
163

            
164
2005196
fn get_filter(
165
    values: &ComputedValues,
166
    acquired_nodes: &mut AcquiredNodes<'_>,
167
    session: &Session,
168
) -> Option<Filter> {
169
2005196
    match values.filter() {
170
2004895
        properties::Filter::None => None,
171

            
172
301
        properties::Filter::List(filter_list) => Some(get_filter_from_filter_list(
173
            filter_list,
174
            acquired_nodes,
175
            values,
176
            session,
177
        )),
178
    }
179
2005196
}
180

            
181
301
fn get_filter_from_filter_list(
182
    filter_list: FilterValueList,
183
    acquired_nodes: &mut AcquiredNodes<'_>,
184
    values: &ComputedValues,
185
    session: &Session,
186
) -> Filter {
187
301
    let current_color = values.color().0;
188

            
189
602
    let stroke_paint_source = values.stroke().0.resolve(
190
        acquired_nodes,
191
301
        values.stroke_opacity().0,
192
        current_color,
193
301
        None,
194
301
        None,
195
        session,
196
301
    );
197

            
198
602
    let fill_paint_source = values.fill().0.resolve(
199
        acquired_nodes,
200
301
        values.fill_opacity().0,
201
        current_color,
202
301
        None,
203
301
        None,
204
        session,
205
301
    );
206

            
207
301
    let normalize_values = NormalizeValues::new(values);
208

            
209
301
    Filter {
210
301
        filter_list,
211
        current_color,
212
301
        stroke_paint_source,
213
301
        fill_paint_source,
214
        normalize_values,
215
    }
216
301
}
217

            
218
impl StackingContext {
219
2005696
    pub fn new(
220
        session: &Session,
221
        acquired_nodes: &mut AcquiredNodes<'_>,
222
        element: &Element,
223
        transform: Transform,
224
        clip_rect: Option<Rect>,
225
        values: &ComputedValues,
226
    ) -> StackingContext {
227
2005696
        let element_name = format!("{element}");
228

            
229
        let opacity;
230
        let filter;
231

            
232
2005696
        match element.element_data {
233
            // "The opacity, filter and display properties do not apply to the mask element"
234
            // https://drafts.fxtf.org/css-masking-1/#MaskElement
235
58
            ElementData::Mask(_) => {
236
58
                opacity = Opacity(UnitInterval::clamp(1.0));
237
58
                filter = None;
238
            }
239

            
240
2005179
            _ => {
241
2005638
                opacity = values.opacity();
242
2005340
                filter = get_filter(values, acquired_nodes, session);
243
            }
244
        }
245

            
246
2005237
        let clip_path = values.clip_path();
247
2005823
        let clip_uri = clip_path.0.get();
248
2004796
        let (clip_in_user_space, clip_in_object_space) = clip_uri
249
2004842
            .and_then(|node_id| {
250
46
                acquired_nodes
251
                    .acquire(node_id)
252
                    .ok()
253
45
                    .filter(|a| is_element_of_type!(*a.get(), ClipPath))
254
46
            })
255
45
            .map(|acquired| {
256
45
                let clip_node = acquired.get().clone();
257

            
258
45
                let units = borrow_element_as!(clip_node, ClipPath).get_units();
259

            
260
45
                match units {
261
40
                    CoordUnits::UserSpaceOnUse => (Some(clip_node), None),
262
5
                    CoordUnits::ObjectBoundingBox => (None, Some(clip_node)),
263
                }
264
45
            })
265
2004496
            .unwrap_or((None, None));
266

            
267
2003830
        let mask = values.mask().0.get().and_then(|mask_id| {
268
62
            if let Ok(acquired) = acquired_nodes.acquire(mask_id) {
269
60
                let node = acquired.get();
270
60
                match *node.borrow_element_data() {
271
60
                    ElementData::Mask(_) => Some(node.clone()),
272

            
273
                    _ => {
274
                        rsvg_log!(
275
                            session,
276
                            "element {} references \"{}\" which is not a mask",
277
                            element,
278
                            mask_id
279
                        );
280

            
281
                        None
282
                    }
283
                }
284
60
            } else {
285
1
                rsvg_log!(
286
1
                    session,
287
                    "element {} references nonexistent mask \"{}\"",
288
                    element,
289
                    mask_id
290
                );
291

            
292
1
                None
293
            }
294
2003830
        });
295

            
296
2002916
        let mix_blend_mode = values.mix_blend_mode();
297
2002647
        let isolation = values.isolation();
298

            
299
2002063
        StackingContext {
300
2002063
            element_name,
301
            transform,
302
2002063
            opacity,
303
2002063
            filter,
304
            clip_rect,
305
2002063
            clip_in_user_space,
306
2002063
            clip_in_object_space,
307
2002063
            mask,
308
            mix_blend_mode,
309
            isolation,
310
2002063
            link_target: None,
311
        }
312
2002063
    }
313

            
314
3
    pub fn new_with_link(
315
        session: &Session,
316
        acquired_nodes: &mut AcquiredNodes<'_>,
317
        element: &Element,
318
        transform: Transform,
319
        values: &ComputedValues,
320
        link_target: Option<String>,
321
    ) -> StackingContext {
322
        // Note that the clip_rect=Some(...) argument is only used by the markers code,
323
        // hence it is None here.  Something to refactor later.
324
3
        let mut ctx = Self::new(session, acquired_nodes, element, transform, None, values);
325
3
        ctx.link_target = link_target;
326
3
        ctx
327
3
    }
328

            
329
2953325
    pub fn should_isolate(&self) -> bool {
330
2953325
        let Opacity(UnitInterval(opacity)) = self.opacity;
331
2953325
        match self.isolation {
332
            Isolation::Auto => {
333
2953322
                let is_opaque = approx_eq!(f64, opacity, 1.0);
334
5905675
                !(is_opaque
335
2952353
                    && self.filter.is_none()
336
2951207
                    && self.mask.is_none()
337
2950553
                    && self.mix_blend_mode == MixBlendMode::Normal
338
2950311
                    && self.clip_in_object_space.is_none())
339
2951410
            }
340
3
            Isolation::Isolate => true,
341
        }
342
2951413
    }
343
}
344

            
345
impl Stroke {
346
948887
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> Stroke {
347
948887
        let width = values.stroke_width().0.to_user(params);
348
948887
        let miter_limit = values.stroke_miterlimit();
349
948887
        let line_cap = values.stroke_line_cap();
350
948887
        let line_join = values.stroke_line_join();
351
948887
        let dash_offset = values.stroke_dashoffset().0.to_user(params);
352
948887
        let non_scaling = values.vector_effect() == VectorEffect::NonScalingStroke;
353

            
354
948887
        let dashes = match values.stroke_dasharray() {
355
948780
            StrokeDasharray(Dasharray::None) => Box::new([]),
356
107
            StrokeDasharray(Dasharray::Array(dashes)) => dashes
357
                .iter()
358
337
                .map(|l| l.to_user(params))
359
107
                .collect::<Box<[f64]>>(),
360
        };
361

            
362
948887
        Stroke {
363
            width,
364
            miter_limit,
365
            line_cap,
366
            line_join,
367
            dash_offset,
368
948887
            dashes,
369
            non_scaling,
370
        }
371
948887
    }
372
}
373

            
374
impl FontProperties {
375
    /// Collects font properties from a `ComputedValues`.
376
    ///
377
    /// The `writing-mode` property is passed separately, as it must come from the `<text>` element,
378
    /// not the `<tspan>` whose computed values are being passed.
379
1039
    pub fn new(values: &ComputedValues, params: &NormalizeParams) -> FontProperties {
380
1039
        FontProperties {
381
1039
            xml_lang: values.xml_lang(),
382
1039
            unicode_bidi: values.unicode_bidi(),
383
1039
            direction: values.direction(),
384
1039
            font_family: values.font_family(),
385
1039
            font_style: values.font_style(),
386
1039
            font_variant: values.font_variant(),
387
1039
            font_weight: values.font_weight(),
388
1039
            font_stretch: values.font_stretch(),
389
1039
            font_size: values.font_size().to_user(params),
390
1039
            letter_spacing: values.letter_spacing().to_user(params),
391
1039
            text_decoration: values.text_decoration(),
392
        }
393
1039
    }
394
}