1 | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | |
5 | use std::str::FromStr; |
6 | use std::sync::Arc; |
7 | |
8 | use strict_num::PositiveF32; |
9 | use svgtypes::{Length, LengthUnit as Unit}; |
10 | |
11 | use super::converter::{self, Cache, SvgColorExt}; |
12 | use super::svgtree::{AId, EId, SvgNode}; |
13 | use super::OptionLog; |
14 | use crate::*; |
15 | |
16 | pub(crate) enum ServerOrColor { |
17 | Server(Paint), |
18 | Color { color: Color, opacity: Opacity }, |
19 | } |
20 | |
21 | pub(crate) fn convert( |
22 | node: SvgNode, |
23 | state: &converter::State, |
24 | cache: &mut converter::Cache, |
25 | ) -> Option<ServerOrColor> { |
26 | // Check for existing. |
27 | if let Some(paint: &Paint) = cache.paint.get(node.element_id()) { |
28 | return Some(ServerOrColor::Server(paint.clone())); |
29 | } |
30 | |
31 | // Unwrap is safe, because we already checked for is_paint_server(). |
32 | let paint: Option = match node.tag_name().unwrap() { |
33 | EId::LinearGradient => convert_linear(node, state), |
34 | EId::RadialGradient => convert_radial(node, state), |
35 | EId::Pattern => convert_pattern(node, state, cache), |
36 | _ => unreachable!(), |
37 | }; |
38 | |
39 | if let Some(ServerOrColor::Server(ref paint: &Paint)) = paint { |
40 | cache |
41 | .paint |
42 | .insert(k:node.element_id().to_string(), v:paint.clone()); |
43 | } |
44 | |
45 | paint |
46 | } |
47 | |
48 | #[inline (never)] |
49 | fn convert_linear(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> { |
50 | let id = NonEmptyString::new(node.element_id().to_string())?; |
51 | |
52 | let stops = convert_stops(find_gradient_with_stops(node)?); |
53 | if stops.len() < 2 { |
54 | return stops_to_color(&stops); |
55 | } |
56 | |
57 | let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox); |
58 | let transform = node.resolve_transform(AId::GradientTransform, state); |
59 | |
60 | let gradient = LinearGradient { |
61 | x1: resolve_number(node, AId::X1, units, state, Length::zero()), |
62 | y1: resolve_number(node, AId::Y1, units, state, Length::zero()), |
63 | x2: resolve_number( |
64 | node, |
65 | AId::X2, |
66 | units, |
67 | state, |
68 | Length::new(100.0, Unit::Percent), |
69 | ), |
70 | y2: resolve_number(node, AId::Y2, units, state, Length::zero()), |
71 | base: BaseGradient { |
72 | id, |
73 | units, |
74 | transform, |
75 | spread_method: convert_spread_method(node), |
76 | stops, |
77 | }, |
78 | }; |
79 | |
80 | Some(ServerOrColor::Server(Paint::LinearGradient(Arc::new( |
81 | gradient, |
82 | )))) |
83 | } |
84 | |
85 | #[inline (never)] |
86 | fn convert_radial(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> { |
87 | let id = NonEmptyString::new(node.element_id().to_string())?; |
88 | |
89 | let stops = convert_stops(find_gradient_with_stops(node)?); |
90 | if stops.len() < 2 { |
91 | return stops_to_color(&stops); |
92 | } |
93 | |
94 | let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox); |
95 | let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent)); |
96 | |
97 | // 'A value of zero will cause the area to be painted as a single color |
98 | // using the color and opacity of the last gradient stop.' |
99 | // |
100 | // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute |
101 | if !r.is_valid_length() { |
102 | let stop = stops.last().unwrap(); |
103 | return Some(ServerOrColor::Color { |
104 | color: stop.color, |
105 | opacity: stop.opacity, |
106 | }); |
107 | } |
108 | |
109 | let spread_method = convert_spread_method(node); |
110 | let cx = resolve_number( |
111 | node, |
112 | AId::Cx, |
113 | units, |
114 | state, |
115 | Length::new(50.0, Unit::Percent), |
116 | ); |
117 | let cy = resolve_number( |
118 | node, |
119 | AId::Cy, |
120 | units, |
121 | state, |
122 | Length::new(50.0, Unit::Percent), |
123 | ); |
124 | let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx as f64)); |
125 | let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy as f64)); |
126 | let transform = node.resolve_transform(AId::GradientTransform, state); |
127 | |
128 | let gradient = RadialGradient { |
129 | cx, |
130 | cy, |
131 | r: PositiveF32::new(r).unwrap(), |
132 | fx, |
133 | fy, |
134 | base: BaseGradient { |
135 | id, |
136 | units, |
137 | transform, |
138 | spread_method, |
139 | stops, |
140 | }, |
141 | }; |
142 | |
143 | Some(ServerOrColor::Server(Paint::RadialGradient(Arc::new( |
144 | gradient, |
145 | )))) |
146 | } |
147 | |
148 | #[inline (never)] |
149 | fn convert_pattern( |
150 | node: SvgNode, |
151 | state: &converter::State, |
152 | cache: &mut converter::Cache, |
153 | ) -> Option<ServerOrColor> { |
154 | let node_with_children = find_pattern_with_children(node)?; |
155 | |
156 | let id = NonEmptyString::new(node.element_id().to_string())?; |
157 | |
158 | let view_box = { |
159 | let n1 = resolve_attr(node, AId::ViewBox); |
160 | let n2 = resolve_attr(node, AId::PreserveAspectRatio); |
161 | n1.parse_viewbox().map(|vb| ViewBox { |
162 | rect: vb, |
163 | aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(), |
164 | }) |
165 | }; |
166 | |
167 | let units = convert_units(node, AId::PatternUnits, Units::ObjectBoundingBox); |
168 | let content_units = convert_units(node, AId::PatternContentUnits, Units::UserSpaceOnUse); |
169 | |
170 | let transform = node.resolve_transform(AId::PatternTransform, state); |
171 | |
172 | let rect = NonZeroRect::from_xywh( |
173 | resolve_number(node, AId::X, units, state, Length::zero()), |
174 | resolve_number(node, AId::Y, units, state, Length::zero()), |
175 | resolve_number(node, AId::Width, units, state, Length::zero()), |
176 | resolve_number(node, AId::Height, units, state, Length::zero()), |
177 | ); |
178 | let rect = rect.log_none(|| { |
179 | log::warn!( |
180 | "Pattern ' {}' has an invalid size. Skipped." , |
181 | node.element_id() |
182 | ) |
183 | })?; |
184 | |
185 | let mut patt = Pattern { |
186 | id, |
187 | units, |
188 | content_units, |
189 | transform, |
190 | rect, |
191 | view_box, |
192 | root: Group::empty(), |
193 | }; |
194 | |
195 | converter::convert_children(node_with_children, state, cache, &mut patt.root); |
196 | |
197 | if !patt.root.has_children() { |
198 | return None; |
199 | } |
200 | |
201 | patt.root.calculate_bounding_boxes(); |
202 | |
203 | Some(ServerOrColor::Server(Paint::Pattern(Arc::new(patt)))) |
204 | } |
205 | |
206 | fn convert_spread_method(node: SvgNode) -> SpreadMethod { |
207 | let node: SvgNode<'_, '_> = resolve_attr(node, name:AId::SpreadMethod); |
208 | node.attribute(AId::SpreadMethod).unwrap_or_default() |
209 | } |
210 | |
211 | pub(crate) fn convert_units(node: SvgNode, name: AId, def: Units) -> Units { |
212 | let node: SvgNode<'_, '_> = resolve_attr(node, name); |
213 | node.attribute(name).unwrap_or(default:def) |
214 | } |
215 | |
216 | fn find_gradient_with_stops<'a, 'input: 'a>( |
217 | node: SvgNode<'a, 'input>, |
218 | ) -> Option<SvgNode<'a, 'input>> { |
219 | for link: SvgNode<'_, '_> in node.href_iter() { |
220 | if !link.tag_name().unwrap().is_gradient() { |
221 | log::warn!( |
222 | "Gradient ' {}' cannot reference ' {}' via 'xlink:href'." , |
223 | node.element_id(), |
224 | link.tag_name().unwrap() |
225 | ); |
226 | return None; |
227 | } |
228 | |
229 | if link.children().any(|n: SvgNode<'_, '_>| n.tag_name() == Some(EId::Stop)) { |
230 | return Some(link); |
231 | } |
232 | } |
233 | |
234 | None |
235 | } |
236 | |
237 | fn find_pattern_with_children<'a, 'input: 'a>( |
238 | node: SvgNode<'a, 'input>, |
239 | ) -> Option<SvgNode<'a, 'input>> { |
240 | for link: SvgNode<'_, '_> in node.href_iter() { |
241 | if link.tag_name() != Some(EId::Pattern) { |
242 | log::warn!( |
243 | "Pattern ' {}' cannot reference ' {}' via 'xlink:href'." , |
244 | node.element_id(), |
245 | link.tag_name().unwrap() |
246 | ); |
247 | return None; |
248 | } |
249 | |
250 | if link.has_children() { |
251 | return Some(link); |
252 | } |
253 | } |
254 | |
255 | None |
256 | } |
257 | |
258 | fn convert_stops(grad: SvgNode) -> Vec<Stop> { |
259 | let mut stops = Vec::new(); |
260 | |
261 | { |
262 | let mut prev_offset = Length::zero(); |
263 | for stop in grad.children() { |
264 | if stop.tag_name() != Some(EId::Stop) { |
265 | log::warn!("Invalid gradient child: ' {:?}'." , stop.tag_name().unwrap()); |
266 | continue; |
267 | } |
268 | |
269 | // `number` can be either a number or a percentage. |
270 | let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset); |
271 | let offset = match offset.unit { |
272 | Unit::None => offset.number, |
273 | Unit::Percent => offset.number / 100.0, |
274 | _ => prev_offset.number, |
275 | }; |
276 | prev_offset = Length::new_number(offset); |
277 | let offset = crate::f32_bound(0.0, offset as f32, 1.0); |
278 | |
279 | let (color, opacity) = match stop.attribute(AId::StopColor) { |
280 | Some("currentColor" ) => stop |
281 | .find_attribute(AId::Color) |
282 | .unwrap_or_else(svgtypes::Color::black), |
283 | Some(value) => { |
284 | if let Ok(c) = svgtypes::Color::from_str(value) { |
285 | c |
286 | } else { |
287 | log::warn!("Failed to parse stop-color value: ' {}'." , value); |
288 | svgtypes::Color::black() |
289 | } |
290 | } |
291 | _ => svgtypes::Color::black(), |
292 | } |
293 | .split_alpha(); |
294 | |
295 | let stop_opacity = stop |
296 | .attribute::<Opacity>(AId::StopOpacity) |
297 | .unwrap_or(Opacity::ONE); |
298 | stops.push(Stop { |
299 | offset: StopOffset::new_clamped(offset), |
300 | color, |
301 | opacity: opacity * stop_opacity, |
302 | }); |
303 | } |
304 | } |
305 | |
306 | // Remove stops with equal offset. |
307 | // |
308 | // Example: |
309 | // offset="0.5" |
310 | // offset="0.7" |
311 | // offset="0.7" <-- this one should be removed |
312 | // offset="0.7" |
313 | // offset="0.9" |
314 | if stops.len() >= 3 { |
315 | let mut i = 0; |
316 | while i < stops.len() - 2 { |
317 | let offset1 = stops[i + 0].offset.get(); |
318 | let offset2 = stops[i + 1].offset.get(); |
319 | let offset3 = stops[i + 2].offset.get(); |
320 | |
321 | if offset1.approx_eq_ulps(&offset2, 4) && offset2.approx_eq_ulps(&offset3, 4) { |
322 | // Remove offset in the middle. |
323 | stops.remove(i + 1); |
324 | } else { |
325 | i += 1; |
326 | } |
327 | } |
328 | } |
329 | |
330 | // Remove zeros. |
331 | // |
332 | // From: |
333 | // offset="0.0" |
334 | // offset="0.0" |
335 | // offset="0.7" |
336 | // |
337 | // To: |
338 | // offset="0.0" |
339 | // offset="0.00000001" |
340 | // offset="0.7" |
341 | if stops.len() >= 2 { |
342 | let mut i = 0; |
343 | while i < stops.len() - 1 { |
344 | let offset1 = stops[i + 0].offset.get(); |
345 | let offset2 = stops[i + 1].offset.get(); |
346 | |
347 | if offset1.approx_eq_ulps(&0.0, 4) && offset2.approx_eq_ulps(&0.0, 4) { |
348 | stops[i + 1].offset = StopOffset::new_clamped(offset1 + f32::EPSILON); |
349 | } |
350 | |
351 | i += 1; |
352 | } |
353 | } |
354 | |
355 | // Shift equal offsets. |
356 | // |
357 | // From: |
358 | // offset="0.5" |
359 | // offset="0.7" |
360 | // offset="0.7" |
361 | // |
362 | // To: |
363 | // offset="0.5" |
364 | // offset="0.699999999" |
365 | // offset="0.7" |
366 | { |
367 | let mut i = 1; |
368 | while i < stops.len() { |
369 | let offset1 = stops[i - 1].offset.get(); |
370 | let offset2 = stops[i - 0].offset.get(); |
371 | |
372 | // Next offset must be smaller then previous. |
373 | if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) { |
374 | // Make previous offset a bit smaller. |
375 | let new_offset = offset1 - f32::EPSILON; |
376 | stops[i - 1].offset = StopOffset::new_clamped(new_offset); |
377 | stops[i - 0].offset = StopOffset::new_clamped(offset1); |
378 | } |
379 | |
380 | i += 1; |
381 | } |
382 | } |
383 | |
384 | stops |
385 | } |
386 | |
387 | #[inline (never)] |
388 | pub(crate) fn resolve_number( |
389 | node: SvgNode, |
390 | name: AId, |
391 | units: Units, |
392 | state: &converter::State, |
393 | def: Length, |
394 | ) -> f32 { |
395 | resolve_attr(node, name).convert_length(aid:name, object_units:units, state, def) |
396 | } |
397 | |
398 | fn resolve_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { |
399 | if node.has_attribute(aid:name) { |
400 | return node; |
401 | } |
402 | |
403 | match node.tag_name().unwrap() { |
404 | EId::LinearGradient => resolve_lg_attr(node, name), |
405 | EId::RadialGradient => resolve_rg_attr(node, name), |
406 | EId::Pattern => resolve_pattern_attr(node, name), |
407 | EId::Filter => resolve_filter_attr(node, aid:name), |
408 | _ => node, |
409 | } |
410 | } |
411 | |
412 | fn resolve_lg_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { |
413 | for link in node.href_iter() { |
414 | let tag_name = match link.tag_name() { |
415 | Some(v) => v, |
416 | None => return node, |
417 | }; |
418 | |
419 | match (name, tag_name) { |
420 | // Coordinates can be resolved only from |
421 | // ref element with the same type. |
422 | (AId::X1, EId::LinearGradient) |
423 | | (AId::Y1, EId::LinearGradient) |
424 | | (AId::X2, EId::LinearGradient) |
425 | | (AId::Y2, EId::LinearGradient) |
426 | // Other attributes can be resolved |
427 | // from any kind of gradient. |
428 | | (AId::GradientUnits, EId::LinearGradient) |
429 | | (AId::GradientUnits, EId::RadialGradient) |
430 | | (AId::SpreadMethod, EId::LinearGradient) |
431 | | (AId::SpreadMethod, EId::RadialGradient) |
432 | | (AId::GradientTransform, EId::LinearGradient) |
433 | | (AId::GradientTransform, EId::RadialGradient) => { |
434 | if link.has_attribute(name) { |
435 | return link; |
436 | } |
437 | } |
438 | _ => break, |
439 | } |
440 | } |
441 | |
442 | node |
443 | } |
444 | |
445 | fn resolve_rg_attr<'a, 'input>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> { |
446 | for link in node.href_iter() { |
447 | let tag_name = match link.tag_name() { |
448 | Some(v) => v, |
449 | None => return node, |
450 | }; |
451 | |
452 | match (name, tag_name) { |
453 | // Coordinates can be resolved only from |
454 | // ref element with the same type. |
455 | (AId::Cx, EId::RadialGradient) |
456 | | (AId::Cy, EId::RadialGradient) |
457 | | (AId::R, EId::RadialGradient) |
458 | | (AId::Fx, EId::RadialGradient) |
459 | | (AId::Fy, EId::RadialGradient) |
460 | // Other attributes can be resolved |
461 | // from any kind of gradient. |
462 | | (AId::GradientUnits, EId::LinearGradient) |
463 | | (AId::GradientUnits, EId::RadialGradient) |
464 | | (AId::SpreadMethod, EId::LinearGradient) |
465 | | (AId::SpreadMethod, EId::RadialGradient) |
466 | | (AId::GradientTransform, EId::LinearGradient) |
467 | | (AId::GradientTransform, EId::RadialGradient) => { |
468 | if link.has_attribute(name) { |
469 | return link; |
470 | } |
471 | } |
472 | _ => break, |
473 | } |
474 | } |
475 | |
476 | node |
477 | } |
478 | |
479 | fn resolve_pattern_attr<'a, 'input: 'a>( |
480 | node: SvgNode<'a, 'input>, |
481 | name: AId, |
482 | ) -> SvgNode<'a, 'input> { |
483 | for link: SvgNode<'_, '_> in node.href_iter() { |
484 | let tag_name: EId = match link.tag_name() { |
485 | Some(v: EId) => v, |
486 | None => return node, |
487 | }; |
488 | |
489 | if tag_name != EId::Pattern { |
490 | break; |
491 | } |
492 | |
493 | if link.has_attribute(aid:name) { |
494 | return link; |
495 | } |
496 | } |
497 | |
498 | node |
499 | } |
500 | |
501 | fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> { |
502 | for link: SvgNode<'_, '_> in node.href_iter() { |
503 | let tag_name: EId = match link.tag_name() { |
504 | Some(v: EId) => v, |
505 | None => return node, |
506 | }; |
507 | |
508 | if tag_name != EId::Filter { |
509 | break; |
510 | } |
511 | |
512 | if link.has_attribute(aid) { |
513 | return link; |
514 | } |
515 | } |
516 | |
517 | node |
518 | } |
519 | |
520 | fn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> { |
521 | if stops.is_empty() { |
522 | None |
523 | } else { |
524 | Some(ServerOrColor::Color { |
525 | color: stops[0].color, |
526 | opacity: stops[0].opacity, |
527 | }) |
528 | } |
529 | } |
530 | |
531 | // Convert object units to user one. |
532 | |
533 | pub fn to_user_coordinates(group: &mut Group, text_bbox: Option<Rect>, cache: &mut Cache) { |
534 | for child: &mut Node in &mut group.children { |
535 | node_to_user_coordinates(node:child, text_bbox, cache); |
536 | } |
537 | } |
538 | |
539 | // When parsing clipPaths, masks and filters we already know group's bounding box. |
540 | // But with gradients and patterns we don't, because we have to know text bounding box |
541 | // before we even parsed it. Which is impossible. |
542 | // Therefore our only choice is to parse gradients and patterns preserving their units |
543 | // and then replace them with `userSpaceOnUse` after the whole tree parsing is finished. |
544 | // So while gradients and patterns do still store their units, |
545 | // they are not exposed in the public API and for the caller they are always `userSpaceOnUse`. |
546 | fn node_to_user_coordinates(node: &mut Node, text_bbox: Option<Rect>, cache: &mut Cache) { |
547 | match node { |
548 | Node::Group(ref mut g) => { |
549 | // No need to check clip paths, because they cannot have paint servers. |
550 | |
551 | if let Some(ref mut mask) = g.mask { |
552 | if let Some(ref mut mask) = Arc::get_mut(mask) { |
553 | to_user_coordinates(&mut mask.root, None, cache); |
554 | |
555 | if let Some(ref mut sub_mask) = mask.mask { |
556 | if let Some(ref mut sub_mask) = Arc::get_mut(sub_mask) { |
557 | to_user_coordinates(&mut sub_mask.root, None, cache); |
558 | } |
559 | } |
560 | } |
561 | } |
562 | |
563 | for filter in &mut g.filters { |
564 | if let Some(ref mut filter) = Arc::get_mut(filter) { |
565 | for primitive in &mut filter.primitives { |
566 | if let filter::Kind::Image(ref mut image) = primitive.kind { |
567 | if let filter::ImageKind::Use(ref mut use_node) = image.data { |
568 | to_user_coordinates(use_node, None, cache); |
569 | } |
570 | } |
571 | } |
572 | } |
573 | } |
574 | |
575 | to_user_coordinates(g, text_bbox, cache); |
576 | } |
577 | Node::Path(ref mut path) => { |
578 | // Paths inside `Text::flattened` are special and must use text's bounding box |
579 | // instead of their own. |
580 | let bbox = text_bbox.unwrap_or(path.bounding_box); |
581 | |
582 | process_fill(&mut path.fill, bbox, cache); |
583 | process_stroke(&mut path.stroke, bbox, cache); |
584 | } |
585 | Node::Image(ref mut image) => { |
586 | if let ImageKind::SVG(ref mut tree) = image.kind { |
587 | to_user_coordinates(&mut tree.root, None, cache); |
588 | } |
589 | } |
590 | Node::Text(ref mut text) => { |
591 | // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox. |
592 | // Therefore we have to use text's bbox when converting tspan and flatted text |
593 | // paint servers. |
594 | let bbox = text.bounding_box; |
595 | |
596 | for chunk in &mut text.chunks { |
597 | for span in &mut chunk.spans { |
598 | process_fill(&mut span.fill, bbox, cache); |
599 | process_stroke(&mut span.stroke, bbox, cache); |
600 | process_text_decoration(&mut span.decoration.underline, bbox, cache); |
601 | process_text_decoration(&mut span.decoration.overline, bbox, cache); |
602 | process_text_decoration(&mut span.decoration.line_through, bbox, cache); |
603 | } |
604 | } |
605 | |
606 | to_user_coordinates(&mut text.flattened, Some(bbox), cache); |
607 | } |
608 | } |
609 | } |
610 | |
611 | fn process_fill(fill: &mut Option<Fill>, bbox: Rect, cache: &mut Cache) { |
612 | let mut ok: bool = false; |
613 | if let Some(ref mut fill: &mut Fill) = fill { |
614 | ok = process_paint(&mut fill.paint, bbox, cache); |
615 | } |
616 | if !ok { |
617 | *fill = None; |
618 | } |
619 | } |
620 | |
621 | fn process_stroke(stroke: &mut Option<Stroke>, bbox: Rect, cache: &mut Cache) { |
622 | let mut ok: bool = false; |
623 | if let Some(ref mut stroke: &mut Stroke) = stroke { |
624 | ok = process_paint(&mut stroke.paint, bbox, cache); |
625 | } |
626 | if !ok { |
627 | *stroke = None; |
628 | } |
629 | } |
630 | |
631 | fn process_paint(paint: &mut Paint, bbox: Rect, cache: &mut Cache) -> bool { |
632 | if paint.units() == Units::ObjectBoundingBox |
633 | || paint.content_units() == Units::ObjectBoundingBox |
634 | { |
635 | if paint.to_user_coordinates(bbox, cache).is_none() { |
636 | return false; |
637 | } |
638 | } |
639 | |
640 | if let Paint::Pattern(ref mut patt: &mut Arc) = paint { |
641 | if let Some(ref mut patt: &mut &mut Pattern) = Arc::get_mut(this:patt) { |
642 | to_user_coordinates(&mut patt.root, text_bbox:None, cache); |
643 | } |
644 | } |
645 | |
646 | true |
647 | } |
648 | |
649 | fn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) { |
650 | if let Some(ref mut style: &mut TextDecorationStyle) = style { |
651 | process_fill(&mut style.fill, bbox, cache); |
652 | process_stroke(&mut style.stroke, bbox, cache); |
653 | } |
654 | } |
655 | |
656 | impl Paint { |
657 | fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> { |
658 | let name = if matches!(self, Paint::Pattern(_)) { |
659 | "Pattern" |
660 | } else { |
661 | "Gradient" |
662 | }; |
663 | let bbox = bbox |
664 | .to_non_zero_rect() |
665 | .log_none(|| log::warn!(" {} on zero-sized shapes is not allowed." , name))?; |
666 | |
667 | // `Arc::get_mut()` allow us to modify some paint servers in-place. |
668 | // This reduces the amount of cloning and preserves the original ID as well. |
669 | match self { |
670 | Paint::Color(_) => {} // unreachable |
671 | Paint::LinearGradient(ref mut lg) => { |
672 | let transform = lg.transform.post_concat(Transform::from_bbox(bbox)); |
673 | if let Some(ref mut lg) = Arc::get_mut(lg) { |
674 | lg.base.transform = transform; |
675 | lg.base.units = Units::UserSpaceOnUse; |
676 | } else { |
677 | *lg = Arc::new(LinearGradient { |
678 | x1: lg.x1, |
679 | y1: lg.y1, |
680 | x2: lg.x2, |
681 | y2: lg.y2, |
682 | base: BaseGradient { |
683 | id: cache.gen_linear_gradient_id(), |
684 | units: Units::UserSpaceOnUse, |
685 | transform, |
686 | spread_method: lg.spread_method, |
687 | stops: lg.stops.clone(), |
688 | }, |
689 | }); |
690 | } |
691 | } |
692 | Paint::RadialGradient(ref mut rg) => { |
693 | let transform = rg.transform.post_concat(Transform::from_bbox(bbox)); |
694 | if let Some(ref mut rg) = Arc::get_mut(rg) { |
695 | rg.base.transform = transform; |
696 | rg.base.units = Units::UserSpaceOnUse; |
697 | } else { |
698 | *rg = Arc::new(RadialGradient { |
699 | cx: rg.cx, |
700 | cy: rg.cy, |
701 | r: rg.r, |
702 | fx: rg.fx, |
703 | fy: rg.fy, |
704 | base: BaseGradient { |
705 | id: cache.gen_radial_gradient_id(), |
706 | units: Units::UserSpaceOnUse, |
707 | transform, |
708 | spread_method: rg.spread_method, |
709 | stops: rg.stops.clone(), |
710 | }, |
711 | }); |
712 | } |
713 | } |
714 | Paint::Pattern(ref mut patt) => { |
715 | let rect = if patt.units == Units::ObjectBoundingBox { |
716 | patt.rect.bbox_transform(bbox) |
717 | } else { |
718 | patt.rect |
719 | }; |
720 | |
721 | if let Some(ref mut patt) = Arc::get_mut(patt) { |
722 | patt.rect = rect; |
723 | patt.units = Units::UserSpaceOnUse; |
724 | |
725 | if patt.content_units == Units::ObjectBoundingBox && patt.view_box().is_none() { |
726 | // No need to shift patterns. |
727 | let transform = Transform::from_scale(bbox.width(), bbox.height()); |
728 | |
729 | let mut g = std::mem::replace(&mut patt.root, Group::empty()); |
730 | g.transform = transform; |
731 | g.abs_transform = transform; |
732 | |
733 | patt.root.children.push(Node::Group(Box::new(g))); |
734 | patt.root.calculate_bounding_boxes(); |
735 | } |
736 | |
737 | patt.content_units = Units::UserSpaceOnUse; |
738 | } else { |
739 | let root = if patt.content_units == Units::ObjectBoundingBox |
740 | && patt.view_box().is_none() |
741 | { |
742 | // No need to shift patterns. |
743 | let transform = Transform::from_scale(bbox.width(), bbox.height()); |
744 | |
745 | let mut g = patt.root.clone(); |
746 | g.transform = transform; |
747 | g.abs_transform = transform; |
748 | |
749 | let mut root = Group::empty(); |
750 | root.children.push(Node::Group(Box::new(g))); |
751 | root.calculate_bounding_boxes(); |
752 | root |
753 | } else { |
754 | patt.root.clone() |
755 | }; |
756 | |
757 | *patt = Arc::new(Pattern { |
758 | id: cache.gen_pattern_id(), |
759 | units: Units::UserSpaceOnUse, |
760 | content_units: Units::UserSpaceOnUse, |
761 | transform: patt.transform, |
762 | rect, |
763 | view_box: patt.view_box, |
764 | root, |
765 | }) |
766 | } |
767 | } |
768 | } |
769 | |
770 | Some(()) |
771 | } |
772 | } |
773 | |
774 | impl Paint { |
775 | #[inline ] |
776 | pub(crate) fn units(&self) -> Units { |
777 | match self { |
778 | Self::Color(_) => Units::UserSpaceOnUse, |
779 | Self::LinearGradient(ref lg: &Arc) => lg.units, |
780 | Self::RadialGradient(ref rg: &Arc) => rg.units, |
781 | Self::Pattern(ref patt: &Arc) => patt.units, |
782 | } |
783 | } |
784 | |
785 | #[inline ] |
786 | pub(crate) fn content_units(&self) -> Units { |
787 | match self { |
788 | Self::Pattern(ref patt: &Arc) => patt.content_units, |
789 | _ => Units::UserSpaceOnUse, |
790 | } |
791 | } |
792 | } |
793 | |