1
//! SVG paint servers.
2

            
3
use std::rc::Rc;
4

            
5
use cssparser::{
6
    Color, ColorFunction, Hsl, Hwb, Lab, Lch, Oklab, Oklch, ParseErrorKind, Parser, RGBA,
7
};
8

            
9
use crate::document::{AcquiredNodes, NodeId};
10
use crate::drawing_ctx::Viewport;
11
use crate::element::ElementData;
12
use crate::error::{AcquireError, NodeIdError, ParseError, ValueErrorKind};
13
use crate::gradient::{ResolvedGradient, UserSpaceGradient};
14
use crate::length::NormalizeValues;
15
use crate::node::NodeBorrow;
16
use crate::parsers::Parse;
17
use crate::pattern::{ResolvedPattern, UserSpacePattern};
18
use crate::rect::Rect;
19
use crate::rsvg_log;
20
use crate::session::Session;
21
use crate::unit_interval::UnitInterval;
22
use crate::util;
23

            
24
/// Unresolved SVG paint server straight from the DOM data.
25
///
26
/// This is either a solid color (which if `currentColor` needs to be extracted from the
27
/// `ComputedValues`), or a paint server like a gradient or pattern which is referenced by
28
/// a URL that points to a certain document node.
29
///
30
/// Use [`PaintServer.resolve`](#method.resolve) to turn this into a [`PaintSource`].
31
15671270
#[derive(Debug, Clone, PartialEq)]
32
pub enum PaintServer {
33
    /// For example, `fill="none"`.
34
    None,
35

            
36
    /// For example, `fill="url(#some_gradient) fallback_color"`.
37
    Iri {
38
1003785
        iri: Box<NodeId>,
39
1003780
        alternate: Option<cssparser::Color>,
40
    },
41

            
42
    /// For example, `fill="blue"`.
43
8839275
    SolidColor(cssparser::Color),
44

            
45
    /// For example, `fill="context-fill"`
46
    ContextFill,
47

            
48
    /// For example, `fill="context-stroke"`
49
    ContextStroke,
50
}
51

            
52
/// Paint server with resolved references, with unnormalized lengths.
53
///
54
/// Use [`PaintSource.to_user_space`](#method.to_user_space) to turn this into a
55
/// [`UserSpacePaintSource`].
56
pub enum PaintSource {
57
    None,
58
    Gradient(ResolvedGradient, Option<Color>),
59
    Pattern(ResolvedPattern, Option<Color>),
60
    SolidColor(Color),
61
}
62

            
63
/// Fully resolved paint server, in user-space units.
64
///
65
/// This has everything required for rendering.
66
pub enum UserSpacePaintSource {
67
    None,
68
    Gradient(UserSpaceGradient, Option<Color>),
69
    Pattern(UserSpacePattern, Option<Color>),
70
    SolidColor(Color),
71
}
72

            
73
impl Parse for PaintServer {
74
1034789
    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<PaintServer, ParseError<'i>> {
75
1034789
        if parser
76
1034753
            .try_parse(|i| i.expect_ident_matching("none"))
77
1034789
            .is_ok()
78
        {
79
2804
            Ok(PaintServer::None)
80
1031985
        } else if parser
81
1031918
            .try_parse(|i| i.expect_ident_matching("context-fill"))
82
1031985
            .is_ok()
83
        {
84
2
            Ok(PaintServer::ContextFill)
85
2063962
        } else if parser
86
1031897
            .try_parse(|i| i.expect_ident_matching("context-stroke"))
87
1031983
            .is_ok()
88
        {
89
3
            Ok(PaintServer::ContextStroke)
90
2063885
        } else if let Ok(url) = parser.try_parse(|i| i.expect_url()) {
91
289
            let loc = parser.current_source_location();
92

            
93
289
            let alternate = if !parser.is_exhausted() {
94
39
                if parser
95
20
                    .try_parse(|i| i.expect_ident_matching("none"))
96
20
                    .is_ok()
97
                {
98
1
                    None
99
                } else {
100
18
                    Some(
101
19
                        parser
102
                            .try_parse(cssparser::Color::parse)
103
1
                            .map_err(|e| ParseError {
104
1
                                kind: ParseErrorKind::Custom(ValueErrorKind::parse_error(
105
                                    "Could not parse color",
106
                                )),
107
1
                                location: e.location,
108
2
                            })?,
109
                    )
110
                }
111
            } else {
112
269
                None
113
            };
114

            
115
298
            Ok(PaintServer::Iri {
116
288
                iri: Box::new(
117
288
                    NodeId::parse(&url)
118
                        .map_err(|e: NodeIdError| -> ValueErrorKind { e.into() })
119
                        .map_err(|e| loc.new_custom_error(e))?,
120
                ),
121
298
                alternate,
122
            })
123
289
        } else {
124
1031691
            <Color as Parse>::parse(parser).map(PaintServer::SolidColor)
125
1031980
        }
126
1034789
    }
127
}
128

            
129
impl PaintServer {
130
    /// Resolves colors, plus node references for gradients and patterns.
131
    ///
132
    /// `opacity` depends on `strokeOpacity` or `fillOpacity` depending on whether
133
    /// the paint server is for the `stroke` or `fill` properties.
134
    ///
135
    /// `current_color` should be the value of `ComputedValues.color()`.
136
    ///
137
    /// After a paint server is resolved, the resulting [`PaintSource`] can be used in
138
    /// many places: for an actual shape, or for the `context-fill` of a marker for that
139
    /// shape.  Therefore, this returns an [`Rc`] so that the `PaintSource` may be shared
140
    /// easily.
141
2901902
    pub fn resolve(
142
        &self,
143
        acquired_nodes: &mut AcquiredNodes<'_>,
144
        opacity: UnitInterval,
145
        current_color: Color,
146
        context_fill: Option<Rc<PaintSource>>,
147
        context_stroke: Option<Rc<PaintSource>>,
148
        session: &Session,
149
    ) -> Rc<PaintSource> {
150
2901902
        match self {
151
            PaintServer::Iri {
152
500257
                ref iri,
153
500257
                ref alternate,
154
500257
            } => acquired_nodes
155
500257
                .acquire(iri)
156
1000498
                .and_then(|acquired| {
157
500240
                    let node = acquired.get();
158
500240
                    assert!(node.is_element());
159

            
160
500240
                    match *node.borrow_element_data() {
161
163
                        ElementData::LinearGradient(ref g) => {
162
325
                            g.resolve(node, acquired_nodes, opacity).map(|g| {
163
162
                                Rc::new(PaintSource::Gradient(
164
162
                                    g,
165
162
                                    alternate.map(|c| resolve_color(&c, opacity, &current_color)),
166
                                ))
167
162
                            })
168
                        }
169
500038
                        ElementData::Pattern(ref p) => {
170
1000075
                            p.resolve(node, acquired_nodes, opacity, session).map(|p| {
171
500037
                                Rc::new(PaintSource::Pattern(
172
500037
                                    p,
173
500046
                                    alternate.map(|c| resolve_color(&c, opacity, &current_color)),
174
                                ))
175
500037
                            })
176
                        }
177
37
                        ElementData::RadialGradient(ref g) => {
178
74
                            g.resolve(node, acquired_nodes, opacity).map(|g| {
179
37
                                Rc::new(PaintSource::Gradient(
180
37
                                    g,
181
37
                                    alternate.map(|c| resolve_color(&c, opacity, &current_color)),
182
                                ))
183
37
                            })
184
                        }
185
2
                        _ => Err(AcquireError::InvalidLinkType(iri.as_ref().clone())),
186
                    }
187
500240
                })
188
500278
                .unwrap_or_else(|_| match alternate {
189
                    // The following cases catch AcquireError::CircularReference and
190
                    // AcquireError::MaxReferencesExceeded.
191
                    //
192
                    // Circular references mean that there is a pattern or gradient with a
193
                    // reference cycle in its "href" attribute.  This is an invalid paint
194
                    // server, and per
195
                    // https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint we should
196
                    // try to fall back to the alternate color.
197
                    //
198
                    // Exceeding the maximum number of references will get caught again
199
                    // later in the drawing code, so it should be fine to translate this
200
                    // condition to that for an invalid paint server.
201
4
                    Some(color) => {
202
                        rsvg_log!(
203
4
                            session,
204
                            "could not resolve paint server \"{}\", using alternate color",
205
                            iri
206
                        );
207

            
208
4
                        Rc::new(PaintSource::SolidColor(resolve_color(
209
                            color,
210
4
                            opacity,
211
4
                            &current_color,
212
                        )))
213
                    }
214

            
215
                    None => {
216
                        rsvg_log!(
217
17
                            session,
218
                            "could not resolve paint server \"{}\", no alternate color specified",
219
                            iri
220
                        );
221

            
222
17
                        Rc::new(PaintSource::None)
223
                    }
224
21
                }),
225

            
226
1450472
            PaintServer::SolidColor(color) => Rc::new(PaintSource::SolidColor(resolve_color(
227
                color,
228
                opacity,
229
                &current_color,
230
            ))),
231

            
232
            PaintServer::ContextFill => {
233
8
                if let Some(paint) = context_fill {
234
7
                    paint
235
                } else {
236
1
                    Rc::new(PaintSource::None)
237
                }
238
            }
239

            
240
            PaintServer::ContextStroke => {
241
11
                if let Some(paint) = context_stroke {
242
10
                    paint
243
                } else {
244
1
                    Rc::new(PaintSource::None)
245
                }
246
            }
247

            
248
951154
            PaintServer::None => Rc::new(PaintSource::None),
249
        }
250
2900668
    }
251
}
252

            
253
impl PaintSource {
254
    /// Converts lengths to user-space.
255
1900376
    pub fn to_user_space(
256
        &self,
257
        object_bbox: &Option<Rect>,
258
        viewport: &Viewport,
259
        values: &NormalizeValues,
260
    ) -> UserSpacePaintSource {
261
1900376
        match *self {
262
449668
            PaintSource::None => UserSpacePaintSource::None,
263
950473
            PaintSource::SolidColor(c) => UserSpacePaintSource::SolidColor(c),
264

            
265
198
            PaintSource::Gradient(ref g, c) => {
266
198
                match (g.to_user_space(object_bbox, viewport, values), c) {
267
194
                    (Some(gradient), c) => UserSpacePaintSource::Gradient(gradient, c),
268
                    (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
269
4
                    (None, None) => UserSpacePaintSource::None,
270
                }
271
            }
272

            
273
500037
            PaintSource::Pattern(ref p, c) => {
274
500037
                match (p.to_user_space(object_bbox, viewport, values), c) {
275
500034
                    (Some(pattern), c) => UserSpacePaintSource::Pattern(pattern, c),
276
                    (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
277
3
                    (None, None) => UserSpacePaintSource::None,
278
                }
279
            }
280
        }
281
1900376
    }
282
}
283

            
284
/// Takes the `opacity` property and an alpha value from a CSS `<color>` and returns a resulting
285
/// alpha for a computed value.
286
///
287
/// `alpha` is `Option<f32>` because that is what cssparser uses everywhere.
288
1451136
fn resolve_alpha(opacity: UnitInterval, alpha: Option<f32>) -> Option<f32> {
289
1451136
    let UnitInterval(o) = opacity;
290

            
291
1451136
    let alpha = f64::from(alpha.unwrap_or(0.0)) * o;
292
1451136
    let alpha = util::clamp(alpha, 0.0, 1.0);
293
1451136
    let alpha = cast::f32(alpha).unwrap();
294

            
295
1451136
    Some(alpha)
296
1451136
}
297

            
298
fn black() -> Color {
299
    Color::Rgba(RGBA::new(Some(0), Some(0), Some(0), Some(1.0)))
300
}
301

            
302
/// Resolves a CSS color from itself, an `opacity` property, and a `color` property (to resolve `currentColor`).
303
///
304
/// A CSS color can be `currentColor`, in which case the computed value comes from
305
/// the `color` property.  You should pass the `color` property's value for `current_color`.
306
///
307
/// Note that `currrent_color` can itself have a value of `currentColor`.  In that case, we
308
/// consider it to be opaque black.
309
1451193
pub fn resolve_color(color: &Color, opacity: UnitInterval, current_color: &Color) -> Color {
310
1451193
    let without_opacity_applied = match color {
311
        Color::CurrentColor => {
312
41
            if let Color::CurrentColor = current_color {
313
                black()
314
            } else {
315
41
                *current_color
316
            }
317
        }
318

            
319
1451152
        _ => *color,
320
    };
321

            
322
1451193
    match without_opacity_applied {
323
        Color::CurrentColor => unreachable!(),
324

            
325
2902370
        Color::Rgba(rgba) => Color::Rgba(RGBA {
326
1451185
            alpha: resolve_alpha(opacity, rgba.alpha),
327
            ..rgba
328
1451185
        }),
329

            
330
8
        Color::Hsl(hsl) => Color::Hsl(Hsl {
331
4
            alpha: resolve_alpha(opacity, hsl.alpha),
332
            ..hsl
333
4
        }),
334

            
335
8
        Color::Hwb(hwb) => Color::Hwb(Hwb {
336
4
            alpha: resolve_alpha(opacity, hwb.alpha),
337
            ..hwb
338
4
        }),
339

            
340
        Color::Lab(lab) => Color::Lab(Lab {
341
            alpha: resolve_alpha(opacity, lab.alpha),
342
            ..lab
343
        }),
344

            
345
        Color::Lch(lch) => Color::Lch(Lch {
346
            alpha: resolve_alpha(opacity, lch.alpha),
347
            ..lch
348
        }),
349

            
350
        Color::Oklab(oklab) => Color::Oklab(Oklab {
351
            alpha: resolve_alpha(opacity, oklab.alpha),
352
            ..oklab
353
        }),
354

            
355
        Color::Oklch(oklch) => Color::Oklch(Oklch {
356
            alpha: resolve_alpha(opacity, oklch.alpha),
357
            ..oklch
358
        }),
359

            
360
        Color::ColorFunction(cf) => Color::ColorFunction(ColorFunction {
361
            alpha: resolve_alpha(opacity, cf.alpha),
362
            ..cf
363
        }),
364
    }
365
1451193
}
366

            
367
impl std::fmt::Debug for PaintSource {
368
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
369
        match *self {
370
            PaintSource::None => f.write_str("PaintSource::None"),
371
            PaintSource::Gradient(_, _) => f.write_str("PaintSource::Gradient"),
372
            PaintSource::Pattern(_, _) => f.write_str("PaintSource::Pattern"),
373
            PaintSource::SolidColor(_) => f.write_str("PaintSource::SolidColor"),
374
        }
375
    }
376
}
377

            
378
#[cfg(test)]
379
mod tests {
380
    use super::*;
381

            
382
    #[test]
383
2
    fn catches_invalid_syntax() {
384
1
        assert!(PaintServer::parse_str("").is_err());
385
1
        assert!(PaintServer::parse_str("42").is_err());
386
1
        assert!(PaintServer::parse_str("invalid").is_err());
387
2
    }
388

            
389
    #[test]
390
2
    fn parses_none() {
391
2
        assert_eq!(PaintServer::parse_str("none").unwrap(), PaintServer::None);
392
2
    }
393

            
394
    #[test]
395
2
    fn parses_solid_color() {
396
2
        assert_eq!(
397
1
            PaintServer::parse_str("rgb(255, 128, 64, 0.5)").unwrap(),
398
1
            PaintServer::SolidColor(cssparser::Color::Rgba(cssparser::RGBA::new(
399
1
                Some(255),
400
1
                Some(128),
401
1
                Some(64),
402
1
                Some(0.5)
403
            )))
404
        );
405

            
406
2
        assert_eq!(
407
1
            PaintServer::parse_str("currentColor").unwrap(),
408
            PaintServer::SolidColor(cssparser::Color::CurrentColor)
409
        );
410
2
    }
411

            
412
    #[test]
413
2
    fn parses_iri() {
414
2
        assert_eq!(
415
1
            PaintServer::parse_str("url(#link)").unwrap(),
416
1
            PaintServer::Iri {
417
1
                iri: Box::new(NodeId::Internal("link".to_string())),
418
1
                alternate: None,
419
            }
420
        );
421

            
422
2
        assert_eq!(
423
1
            PaintServer::parse_str("url(foo#link) none").unwrap(),
424
1
            PaintServer::Iri {
425
1
                iri: Box::new(NodeId::External("foo".to_string(), "link".to_string())),
426
1
                alternate: None,
427
            }
428
        );
429

            
430
2
        assert_eq!(
431
1
            PaintServer::parse_str("url(#link) #ff8040").unwrap(),
432
1
            PaintServer::Iri {
433
1
                iri: Box::new(NodeId::Internal("link".to_string())),
434
1
                alternate: Some(cssparser::Color::Rgba(cssparser::RGBA::new(
435
1
                    Some(255),
436
1
                    Some(128),
437
1
                    Some(64),
438
1
                    Some(1.0)
439
                ))),
440
            }
441
        );
442

            
443
2
        assert_eq!(
444
1
            PaintServer::parse_str("url(#link) rgb(255, 128, 64, 0.5)").unwrap(),
445
1
            PaintServer::Iri {
446
1
                iri: Box::new(NodeId::Internal("link".to_string())),
447
1
                alternate: Some(cssparser::Color::Rgba(cssparser::RGBA::new(
448
1
                    Some(255),
449
1
                    Some(128),
450
1
                    Some(64),
451
1
                    Some(0.5)
452
                ))),
453
            }
454
        );
455

            
456
2
        assert_eq!(
457
1
            PaintServer::parse_str("url(#link) currentColor").unwrap(),
458
1
            PaintServer::Iri {
459
1
                iri: Box::new(NodeId::Internal("link".to_string())),
460
1
                alternate: Some(cssparser::Color::CurrentColor),
461
            }
462
        );
463

            
464
1
        assert!(PaintServer::parse_str("url(#link) invalid").is_err());
465
2
    }
466

            
467
    #[test]
468
2
    fn resolves_explicit_color() {
469
1
        assert_eq!(
470
1
            resolve_color(
471
1
                &Color::Rgba(RGBA::new(Some(255), Some(0), Some(0), Some(0.5))),
472
1
                UnitInterval::clamp(0.5),
473
1
                &Color::Rgba(RGBA::new(Some(0), Some(255), Some(0), Some(1.0))),
474
            ),
475
1
            Color::Rgba(RGBA::new(Some(255), Some(0), Some(0), Some(0.25))),
476
        );
477
2
    }
478

            
479
    #[test]
480
2
    fn resolves_current_color() {
481
1
        assert_eq!(
482
1
            resolve_color(
483
                &Color::CurrentColor,
484
1
                UnitInterval::clamp(0.5),
485
1
                &Color::Rgba(RGBA::new(Some(0), Some(255), Some(0), Some(0.5))),
486
            ),
487
1
            Color::Rgba(RGBA::new(Some(0), Some(255), Some(0), Some(0.25))),
488
        );
489
2
    }
490
}