1use std::rc::Rc;
6
7use float_cmp::approx_eq;
8
9use crate::aspect_ratio::AspectRatio;
10use crate::cairo_path::CairoPath;
11use crate::color::Color;
12use crate::coord_units::CoordUnits;
13use crate::dasharray::Dasharray;
14use crate::document::AcquiredNodes;
15use crate::element::{Element, ElementData};
16use crate::filter::FilterValueList;
17use crate::length::*;
18use crate::node::*;
19use crate::paint_server::{PaintSource, UserSpacePaintSource};
20use crate::path_builder::Path as SvgPath;
21use 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};
27use crate::rect::Rect;
28use crate::rsvg_log;
29use crate::session::Session;
30use crate::surface_utils::shared_surface::SharedImageSurface;
31use crate::transform::Transform;
32use crate::unit_interval::UnitInterval;
33use crate::viewbox::ViewBox;
34use crate::{borrow_element_as, is_element_of_type};
35
36pub 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 pub link_target: Option<String>,
64}
65
66pub struct Layer {
68 pub kind: LayerKind,
69 pub stacking_ctx: StackingContext,
70}
71
72pub enum LayerKind {
73 Shape(Box<Shape>),
74 Text(Box<Text>),
75 Image(Box<Image>),
76 Group(Box<Group>),
77}
78
79pub struct Group {
80 pub children: Vec<Layer>,
81 pub establish_viewport: Option<LayoutViewport>,
82 pub extents: Option<Rect>,
83}
84
85pub struct LayoutViewport {
87 pub geometry: Rect,
91
92 pub vbox: Option<ViewBox>,
94
95 pub preserve_aspect_ratio: AspectRatio,
97
98 pub overflow: Overflow,
100}
101
102pub 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 pub non_scaling: bool,
112}
113
114pub struct Path {
116 pub cairo_path: CairoPath,
117 pub path: Rc<SvgPath>,
118 pub extents: Option<Rect>,
119}
120
121pub 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
136pub struct Marker {
137 pub node_ref: Option<Node>,
138 pub context_stroke: Rc<PaintSource>,
139 pub context_fill: Rc<PaintSource>,
140}
141
142pub 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
151pub 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
167pub struct Text {
169 pub spans: Vec<TextSpan>,
170 pub extents: Option<Rect>,
171}
172
173pub 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
188pub 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
196fn get_filter(
197 values: &ComputedValues,
198 acquired_nodes: &mut AcquiredNodes<'_>,
199 session: &Session,
200) -> Option<Filter> {
201 match values.filter() {
202 properties::Filter::None => None,
203
204 properties::Filter::List(filter_list) => Some(get_filter_from_filter_list(
205 filter_list,
206 acquired_nodes,
207 values,
208 session,
209 )),
210 }
211}
212
213fn get_filter_from_filter_list(
214 filter_list: FilterValueList,
215 acquired_nodes: &mut AcquiredNodes<'_>,
216 values: &ComputedValues,
217 session: &Session,
218) -> Filter {
219 let current_color = values.color().0;
220
221 let stroke_paint_source = values.stroke().0.resolve(
222 acquired_nodes,
223 values.stroke_opacity().0,
224 current_color,
225 None,
226 None,
227 session,
228 );
229
230 let fill_paint_source = values.fill().0.resolve(
231 acquired_nodes,
232 values.fill_opacity().0,
233 current_color,
234 None,
235 None,
236 session,
237 );
238
239 let normalize_values = NormalizeValues::new(values);
240
241 Filter {
242 filter_list,
243 current_color,
244 stroke_paint_source,
245 fill_paint_source,
246 normalize_values,
247 }
248}
249
250impl StackingContext {
251 pub fn new(
252 session: &Session,
253 acquired_nodes: &mut AcquiredNodes<'_>,
254 element: &Element,
255 transform: Transform,
256 clip_rect: Option<Rect>,
257 values: &ComputedValues,
258 ) -> StackingContext {
259 let element_name = format!("{element}");
260
261 let is_visible = values.is_visible();
262
263 let opacity;
264 let filter;
265
266 match element.element_data {
267 ElementData::Mask(_) => {
270 opacity = Opacity(UnitInterval::clamp(1.0));
271 filter = None;
272 }
273
274 _ => {
275 opacity = values.opacity();
276 filter = get_filter(values, acquired_nodes, session);
277 }
278 }
279
280 let clip_path = values.clip_path();
281 let clip_uri = clip_path.0.get();
282 let (clip_in_user_space, clip_in_object_space) = clip_uri
283 .and_then(|node_id| {
284 acquired_nodes
285 .acquire(node_id)
286 .ok()
287 .filter(|a| is_element_of_type!(*a.get(), ClipPath))
288 })
289 .map(|acquired| {
290 let clip_node = acquired.get().clone();
291
292 let units = borrow_element_as!(clip_node, ClipPath).get_units();
293
294 match units {
295 CoordUnits::UserSpaceOnUse => (Some(clip_node), None),
296 CoordUnits::ObjectBoundingBox => (None, Some(clip_node)),
297 }
298 })
299 .unwrap_or((None, None));
300
301 let mask = values.mask().0.get().and_then(|mask_id| {
302 if let Ok(acquired) = acquired_nodes.acquire(mask_id) {
303 let node = acquired.get();
304 match *node.borrow_element_data() {
305 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 rsvg_log!(
320 session,
321 "element {} references nonexistent mask \"{}\"",
322 element,
323 mask_id
324 );
325
326 None
327 }
328 });
329
330 let mix_blend_mode = values.mix_blend_mode();
331 let isolation = values.isolation();
332
333 StackingContext {
334 element_name,
335 transform,
336 is_visible,
337 opacity,
338 filter,
339 clip_rect,
340 clip_in_user_space,
341 clip_in_object_space,
342 mask,
343 mix_blend_mode,
344 isolation,
345 link_target: None,
346 }
347 }
348
349 pub fn new_with_link(
350 session: &Session,
351 acquired_nodes: &mut AcquiredNodes<'_>,
352 element: &Element,
353 transform: Transform,
354 values: &ComputedValues,
355 link_target: Option<String>,
356 ) -> StackingContext {
357 let mut ctx = Self::new(session, acquired_nodes, element, transform, None, values);
360 ctx.link_target = link_target;
361 ctx
362 }
363
364 pub fn should_isolate(&self) -> bool {
365 let Opacity(UnitInterval(opacity)) = self.opacity;
366 match self.isolation {
367 Isolation::Auto => {
368 let is_opaque = approx_eq!(f64, opacity, 1.0);
369 !(is_opaque
370 && self.filter.is_none()
371 && self.mask.is_none()
372 && self.mix_blend_mode == MixBlendMode::Normal
373 && self.clip_in_object_space.is_none())
374 }
375 Isolation::Isolate => true,
376 }
377 }
378}
379
380impl LayerKind {
381 pub fn local_extents(&self) -> Option<Rect> {
388 match *self {
389 LayerKind::Shape(ref shape) => shape.path.extents,
390 LayerKind::Text(ref text) => text.extents,
391 LayerKind::Image(ref image) => Some(image.rect),
392 LayerKind::Group(ref group) => group.extents,
393 }
394 }
395}
396
397impl Stroke {
398 pub fn new(values: &ComputedValues, params: &NormalizeParams) -> Stroke {
399 let width = values.stroke_width().0.to_user(params);
400 let miter_limit = values.stroke_miterlimit();
401 let line_cap = values.stroke_line_cap();
402 let line_join = values.stroke_line_join();
403 let dash_offset = values.stroke_dashoffset().0.to_user(params);
404 let non_scaling = values.vector_effect() == VectorEffect::NonScalingStroke;
405
406 let dashes = match values.stroke_dasharray() {
407 StrokeDasharray(Dasharray::None) => Box::new([]),
408 StrokeDasharray(Dasharray::Array(dashes)) => dashes
409 .iter()
410 .map(|l| l.to_user(params))
411 .collect::<Box<[f64]>>(),
412 };
413
414 Stroke {
415 width,
416 miter_limit,
417 line_cap,
418 line_join,
419 dash_offset,
420 dashes,
421 non_scaling,
422 }
423 }
424}
425
426impl FontProperties {
427 pub fn new(values: &ComputedValues, params: &NormalizeParams) -> FontProperties {
432 FontProperties {
433 xml_lang: values.xml_lang(),
434 unicode_bidi: values.unicode_bidi(),
435 direction: values.direction(),
436 font_family: values.font_family(),
437 font_style: values.font_style(),
438 font_variant: values.font_variant(),
439 font_weight: values.font_weight(),
440 font_stretch: values.font_stretch(),
441 font_size: values.font_size().to_user(params),
442 letter_spacing: values.letter_spacing().to_user(params),
443 text_decoration: values.text_decoration(),
444 }
445 }
446}