1use std::rc::Rc;
4
5use cssparser::{ParseErrorKind, Parser};
6
7use crate::color::{resolve_color, 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 opacity: UnitInterval,
139 current_color: Color,
140 context_fill: Option<Rc<PaintSource>>,
141 context_stroke: Option<Rc<PaintSource>>,
142 session: &Session,
143 ) -> Rc<PaintSource> {
144 match self {
145 PaintServer::Iri {
146 ref iri,
147 ref alternate,
148 } => acquired_nodes
149 .acquire(iri)
150 .and_then(|acquired| {
151 let node = acquired.get();
152 assert!(node.is_element());
153
154 match *node.borrow_element_data() {
155 ElementData::LinearGradient(ref g) => {
156 g.resolve(node, acquired_nodes, opacity).map(|g| {
157 Rc::new(PaintSource::Gradient(
158 g,
159 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
160 ))
161 })
162 }
163 ElementData::Pattern(ref p) => {
164 p.resolve(node, acquired_nodes, opacity, session).map(|p| {
165 Rc::new(PaintSource::Pattern(
166 p,
167 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
168 ))
169 })
170 }
171 ElementData::RadialGradient(ref g) => {
172 g.resolve(node, acquired_nodes, opacity).map(|g| {
173 Rc::new(PaintSource::Gradient(
174 g,
175 alternate.map(|c| resolve_color(&c, opacity, ¤t_color)),
176 ))
177 })
178 }
179 _ => Err(AcquireError::InvalidLinkType(iri.as_ref().clone())),
180 }
181 })
182 .unwrap_or_else(|_| match alternate {
183 Some(color) => {
196 rsvg_log!(
197 session,
198 "could not resolve paint server \"{}\", using alternate color",
199 iri
200 );
201
202 Rc::new(PaintSource::SolidColor(resolve_color(
203 color,
204 opacity,
205 ¤t_color,
206 )))
207 }
208
209 None => {
210 rsvg_log!(
211 session,
212 "could not resolve paint server \"{}\", no alternate color specified",
213 iri
214 );
215
216 Rc::new(PaintSource::None)
217 }
218 }),
219
220 PaintServer::SolidColor(color) => Rc::new(PaintSource::SolidColor(resolve_color(
221 color,
222 opacity,
223 ¤t_color,
224 ))),
225
226 PaintServer::ContextFill => {
227 if let Some(paint) = context_fill {
228 paint
229 } else {
230 Rc::new(PaintSource::None)
231 }
232 }
233
234 PaintServer::ContextStroke => {
235 if let Some(paint) = context_stroke {
236 paint
237 } else {
238 Rc::new(PaintSource::None)
239 }
240 }
241
242 PaintServer::None => Rc::new(PaintSource::None),
243 }
244 }
245}
246
247impl PaintSource {
248 pub fn to_user_space(
250 &self,
251 object_bbox: &Option<Rect>,
252 viewport: &Viewport,
253 values: &NormalizeValues,
254 ) -> UserSpacePaintSource {
255 match *self {
256 PaintSource::None => UserSpacePaintSource::None,
257 PaintSource::SolidColor(c) => UserSpacePaintSource::SolidColor(c),
258
259 PaintSource::Gradient(ref g, c) => {
260 match (g.to_user_space(object_bbox, viewport, values), c) {
261 (Some(gradient), c) => UserSpacePaintSource::Gradient(gradient, c),
262 (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
263 (None, None) => UserSpacePaintSource::None,
264 }
265 }
266
267 PaintSource::Pattern(ref p, c) => {
268 match (p.to_user_space(object_bbox, viewport, values), c) {
269 (Some(pattern), c) => UserSpacePaintSource::Pattern(pattern, c),
270 (None, Some(c)) => UserSpacePaintSource::SolidColor(c),
271 (None, None) => UserSpacePaintSource::None,
272 }
273 }
274 }
275 }
276}
277
278impl std::fmt::Debug for PaintSource {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
280 match *self {
281 PaintSource::None => f.write_str("PaintSource::None"),
282 PaintSource::Gradient(_, _) => f.write_str("PaintSource::Gradient"),
283 PaintSource::Pattern(_, _) => f.write_str("PaintSource::Pattern"),
284 PaintSource::SolidColor(_) => f.write_str("PaintSource::SolidColor"),
285 }
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 use crate::color::RGBA;
294
295 #[test]
296 fn catches_invalid_syntax() {
297 assert!(PaintServer::parse_str("").is_err());
298 assert!(PaintServer::parse_str("42").is_err());
299 assert!(PaintServer::parse_str("invalid").is_err());
300 }
301
302 #[test]
303 fn parses_none() {
304 assert_eq!(PaintServer::parse_str("none").unwrap(), PaintServer::None);
305 }
306
307 #[test]
308 fn parses_solid_color() {
309 assert_eq!(
310 PaintServer::parse_str("rgb(255, 128, 64, 0.5)").unwrap(),
311 PaintServer::SolidColor(Color::Rgba(RGBA::new(255, 128, 64, 0.5)))
312 );
313
314 assert_eq!(
315 PaintServer::parse_str("currentColor").unwrap(),
316 PaintServer::SolidColor(Color::CurrentColor)
317 );
318 }
319
320 #[test]
321 fn parses_iri() {
322 assert_eq!(
323 PaintServer::parse_str("url(#link)").unwrap(),
324 PaintServer::Iri {
325 iri: Box::new(NodeId::Internal("link".to_string())),
326 alternate: None,
327 }
328 );
329
330 assert_eq!(
331 PaintServer::parse_str("url(foo#link) none").unwrap(),
332 PaintServer::Iri {
333 iri: Box::new(NodeId::External("foo".to_string(), "link".to_string())),
334 alternate: None,
335 }
336 );
337
338 assert_eq!(
339 PaintServer::parse_str("url(#link) #ff8040").unwrap(),
340 PaintServer::Iri {
341 iri: Box::new(NodeId::Internal("link".to_string())),
342 alternate: Some(Color::Rgba(RGBA::new(255, 128, 64, 1.0))),
343 }
344 );
345
346 assert_eq!(
347 PaintServer::parse_str("url(#link) rgb(255, 128, 64, 0.5)").unwrap(),
348 PaintServer::Iri {
349 iri: Box::new(NodeId::Internal("link".to_string())),
350 alternate: Some(Color::Rgba(RGBA::new(255, 128, 64, 0.5))),
351 }
352 );
353
354 assert_eq!(
355 PaintServer::parse_str("url(#link) currentColor").unwrap(),
356 PaintServer::Iri {
357 iri: Box::new(NodeId::Internal("link".to_string())),
358 alternate: Some(Color::CurrentColor),
359 }
360 );
361
362 assert!(PaintServer::parse_str("url(#link) invalid").is_err());
363 }
364
365 #[test]
366 fn resolves_explicit_color() {
367 assert_eq!(
368 resolve_color(
369 &Color::Rgba(RGBA::new(255, 0, 0, 0.5)),
370 UnitInterval::clamp(0.5),
371 &Color::Rgba(RGBA::new(0, 255, 0, 1.0)),
372 ),
373 Color::Rgba(RGBA::new(255, 0, 0, 0.25)),
374 );
375 }
376
377 #[test]
378 fn resolves_current_color() {
379 assert_eq!(
380 resolve_color(
381 &Color::CurrentColor,
382 UnitInterval::clamp(0.5),
383 &Color::Rgba(RGBA::new(0, 255, 0, 0.5)),
384 ),
385 Color::Rgba(RGBA::new(0, 255, 0, 0.25)),
386 );
387 }
388}