1use std::rc::Rc;
4
5use cssparser::{ParseErrorKind, Parser};
6
7use crate::color::{Color, resolve_color};
8use crate::document::{AcquiredNodes, NodeId};
9use crate::drawing_ctx::Viewport;
10use crate::element::ElementData;
11use crate::error::{AcquireError, NodeIdError, ParseError, ValueErrorKind};
12use crate::gradient::{ResolvedGradient, UserSpaceGradient};
13use crate::length::NormalizeValues;
14use crate::node::NodeBorrow;
15use crate::parsers::Parse;
16use crate::pattern::{ResolvedPattern, UserSpacePattern};
17use crate::rect::Rect;
18use crate::rsvg_log;
19use crate::session::Session;
20use crate::unit_interval::UnitInterval;
21
22#[derive(Debug, Clone, PartialEq)]
30pub enum PaintServer {
31 None,
33
34 Iri {
36 iri: Box<NodeId>,
37 alternate: Option<Color>,
38 },
39
40 SolidColor(Color),
42
43 ContextFill,
45
46 ContextStroke,
48}
49
50pub enum PaintSource {
55 None,
56 Gradient(ResolvedGradient, Option<Color>),
57 Pattern(ResolvedPattern, Option<Color>),
58 SolidColor(Color),
59}
60
61pub enum UserSpacePaintSource {
65 None,
66 Gradient(UserSpaceGradient, Option<Color>),
67 Pattern(UserSpacePattern, Option<Color>),
68 SolidColor(Color),
69}
70
71impl Parse for PaintServer {
72 fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<PaintServer, ParseError<'i>> {
73 if parser
74 .try_parse(|i| i.expect_ident_matching("none"))
75 .is_ok()
76 {
77 Ok(PaintServer::None)
78 } else if parser
79 .try_parse(|i| i.expect_ident_matching("context-fill"))
80 .is_ok()
81 {
82 Ok(PaintServer::ContextFill)
83 } else if parser
84 .try_parse(|i| i.expect_ident_matching("context-stroke"))
85 .is_ok()
86 {
87 Ok(PaintServer::ContextStroke)
88 } else if let Ok(url) = parser.try_parse(|i| i.expect_url()) {
89 let loc = parser.current_source_location();
90
91 let alternate = if !parser.is_exhausted() {
92 if parser
93 .try_parse(|i| i.expect_ident_matching("none"))
94 .is_ok()
95 {
96 None
97 } else {
98 Some(parser.try_parse(Color::parse).map_err(|e| ParseError {
99 kind: ParseErrorKind::Custom(ValueErrorKind::parse_error(
100 "Could not parse color",
101 )),
102 location: e.location,
103 })?)
104 }
105 } else {
106 None
107 };
108
109 Ok(PaintServer::Iri {
110 iri: Box::new(
111 NodeId::parse(&url)
112 .map_err(|e: NodeIdError| -> ValueErrorKind { e.into() })
113 .map_err(|e| loc.new_custom_error(e))?,
114 ),
115 alternate,
116 })
117 } else {
118 <Color as Parse>::parse(parser).map(PaintServer::SolidColor)
119 }
120 }
121}
122
123impl PaintServer {
124 pub fn resolve(
136 &self,
137 acquired_nodes: &mut AcquiredNodes<'_>,
138 referencing_element_name: &str,
139 opacity: UnitInterval,
140 current_color: Color,
141 context_fill: Option<Rc<PaintSource>>,
142 context_stroke: Option<Rc<PaintSource>>,
143 session: &Session,
144 ) -> Rc<PaintSource> {
145 match self {
146 PaintServer::Iri { iri, alternate } => acquired_nodes
147 .acquire(referencing_element_name, iri)
148 .and_then(|acquired| {
149 let node = acquired.get();
150 assert!(node.is_element());
151
152 match *node.borrow_element_data() {
153 ElementData::LinearGradient(ref g) => {
154 g.resolve(node, acquired_nodes, opacity, session).map(|g| {
155 Rc::new(PaintSource::Gradient(
156 g,
157 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
158 ))
159 })
160 }
161 ElementData::Pattern(ref p) => {
162 p.resolve(node, acquired_nodes, opacity, session).map(|p| {
163 Rc::new(PaintSource::Pattern(
164 p,
165 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
166 ))
167 })
168 }
169 ElementData::RadialGradient(ref g) => {
170 g.resolve(node, acquired_nodes, opacity, session).map(|g| {
171 Rc::new(PaintSource::Gradient(
172 g,
173 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
174 ))
175 })
176 }
177 _ => {
178 rsvg_log!(session, "{node} is not a gradient or pattern, ignoring");
179 Err(AcquireError::InvalidLinkType(iri.as_ref().clone()))
180 }
181 }
182 })
183 .unwrap_or_else(|_| match alternate {
184 Some(color) => {
197 rsvg_log!(
198 session,
199 "could not resolve paint server \"{}\", using alternate color",
200 iri
201 );
202
203 Rc::new(PaintSource::SolidColor(resolve_color(
204 color,
205 opacity,
206 ¤t_color,
207 )))
208 }
209
210 None => {
211 rsvg_log!(
212 session,
213 "could not resolve paint server \"{}\", no alternate color specified",
214 iri
215 );
216
217 Rc::new(PaintSource::None)
218 }
219 }),
220
221 PaintServer::SolidColor(color) => Rc::new(PaintSource::SolidColor(resolve_color(
222 color,
223 opacity,
224 ¤t_color,
225 ))),
226
227 PaintServer::ContextFill => {
228 if let Some(paint) = context_fill {
229 paint
230 } else {
231 Rc::new(PaintSource::None)
232 }
233 }
234
235 PaintServer::ContextStroke => {
236 if let Some(paint) = context_stroke {
237 paint
238 } else {
239 Rc::new(PaintSource::None)
240 }
241 }
242
243 PaintServer::None => Rc::new(PaintSource::None),
244 }
245 }
246}
247
248impl PaintSource {
249 pub fn to_user_space(
251 &self,
252 object_bbox: &Option<Rect>,
253 viewport: &Viewport,
254 values: &NormalizeValues,
255 ) -> UserSpacePaintSource {
256 match *self {
257 PaintSource::None => UserSpacePaintSource::None,
258 PaintSource::SolidColor(c) => UserSpacePaintSource::SolidColor(c),
259
260 PaintSource::Gradient(ref g, c) => {
261 match (g.to_user_space(object_bbox, viewport, values), c) {
262 (Some(gradient), c) => UserSpacePaintSource::Gradient(gradient, c),
263 (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
264 (None, None) => UserSpacePaintSource::None,
265 }
266 }
267
268 PaintSource::Pattern(ref p, c) => {
269 match (p.to_user_space(object_bbox, viewport, values), c) {
270 (Some(pattern), c) => UserSpacePaintSource::Pattern(pattern, c),
271 (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
272 (None, None) => UserSpacePaintSource::None,
273 }
274 }
275 }
276 }
277}
278
279impl std::fmt::Debug for PaintSource {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
281 match *self {
282 PaintSource::None => f.write_str("PaintSource::None"),
283 PaintSource::Gradient(_, _) => f.write_str("PaintSource::Gradient"),
284 PaintSource::Pattern(_, _) => f.write_str("PaintSource::Pattern"),
285 PaintSource::SolidColor(_) => f.write_str("PaintSource::SolidColor"),
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 use crate::color::RGBA;
295
296 #[test]
297 fn catches_invalid_syntax() {
298 assert!(PaintServer::parse_str("").is_err());
299 assert!(PaintServer::parse_str("42").is_err());
300 assert!(PaintServer::parse_str("invalid").is_err());
301 }
302
303 #[test]
304 fn parses_none() {
305 assert_eq!(PaintServer::parse_str("none").unwrap(), PaintServer::None);
306 }
307
308 #[test]
309 fn parses_solid_color() {
310 assert_eq!(
311 PaintServer::parse_str("rgb(255, 128, 64, 0.5)").unwrap(),
312 PaintServer::SolidColor(Color::Rgba(RGBA::new(255, 128, 64, 0.5)))
313 );
314
315 assert_eq!(
316 PaintServer::parse_str("currentColor").unwrap(),
317 PaintServer::SolidColor(Color::CurrentColor)
318 );
319 }
320
321 #[test]
322 fn parses_iri() {
323 assert_eq!(
324 PaintServer::parse_str("url(#link)").unwrap(),
325 PaintServer::Iri {
326 iri: Box::new(NodeId::Internal("link".to_string())),
327 alternate: None,
328 }
329 );
330
331 assert_eq!(
332 PaintServer::parse_str("url(foo#link) none").unwrap(),
333 PaintServer::Iri {
334 iri: Box::new(NodeId::External("foo".to_string(), "link".to_string())),
335 alternate: None,
336 }
337 );
338
339 assert_eq!(
340 PaintServer::parse_str("url(#link) #ff8040").unwrap(),
341 PaintServer::Iri {
342 iri: Box::new(NodeId::Internal("link".to_string())),
343 alternate: Some(Color::Rgba(RGBA::new(255, 128, 64, 1.0))),
344 }
345 );
346
347 assert_eq!(
348 PaintServer::parse_str("url(#link) rgb(255, 128, 64, 0.5)").unwrap(),
349 PaintServer::Iri {
350 iri: Box::new(NodeId::Internal("link".to_string())),
351 alternate: Some(Color::Rgba(RGBA::new(255, 128, 64, 0.5))),
352 }
353 );
354
355 assert_eq!(
356 PaintServer::parse_str("url(#link) currentColor").unwrap(),
357 PaintServer::Iri {
358 iri: Box::new(NodeId::Internal("link".to_string())),
359 alternate: Some(Color::CurrentColor),
360 }
361 );
362
363 assert!(PaintServer::parse_str("url(#link) invalid").is_err());
364 }
365
366 #[test]
367 fn resolves_explicit_color() {
368 assert_eq!(
369 resolve_color(
370 &Color::Rgba(RGBA::new(255, 0, 0, 0.5)),
371 UnitInterval::clamp(0.5),
372 &Color::Rgba(RGBA::new(0, 255, 0, 1.0)),
373 ),
374 Color::Rgba(RGBA::new(255, 0, 0, 0.25)),
375 );
376 }
377
378 #[test]
379 fn resolves_current_color() {
380 assert_eq!(
381 resolve_color(
382 &Color::CurrentColor,
383 UnitInterval::clamp(0.5),
384 &Color::Rgba(RGBA::new(0, 255, 0, 0.5)),
385 ),
386 Color::Rgba(RGBA::new(0, 255, 0, 0.25)),
387 );
388 }
389}