1// TODO: prefix paint creation functions with make_ or new_
2// so that they are easier to find when autocompleting
3
4use std::rc::Rc;
5
6use crate::{geometry::Position, Align, Baseline, Color, FillRule, FontId, ImageId, LineCap, LineJoin};
7
8#[derive(Copy, Clone, Debug, PartialEq, Default)]
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10pub(crate) struct GradientStop(pub f32, pub Color);
11
12// We use MultiStopGradient as a key since we cache them. We either need
13// to define Hash (for HashMap) or Ord for (BTreeMap).
14impl Eq for GradientStop {}
15impl Ord for GradientStop {
16 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
17 if (other.0, other.1) < (self.0, self.1) {
18 std::cmp::Ordering::Less
19 } else if (self.0, self.1) < (other.0, other.1) {
20 std::cmp::Ordering::Greater
21 } else {
22 std::cmp::Ordering::Equal
23 }
24 }
25}
26
27impl PartialOrd for GradientStop {
28 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
29 Some(self.cmp(other))
30 }
31}
32
33#[derive(Clone, Debug, PartialEq)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub(crate) struct MultiStopGradient {
36 shared_stops: Rc<[GradientStop]>,
37 tint: f32,
38}
39
40impl MultiStopGradient {
41 pub(crate) fn get(&self, index: usize) -> GradientStop {
42 let mut stop: GradientStop = self
43 .shared_stops
44 .get(index)
45 .cloned()
46 .unwrap_or_else(|| GradientStop(2.0, Color::black()));
47
48 stop.1.a *= self.tint;
49 stop
50 }
51
52 pub(crate) fn pairs(&self) -> impl Iterator<Item = [GradientStop; 2]> + '_ {
53 self.shared_stops.as_ref().windows(size:2).map(move |pair: &[GradientStop]| {
54 let mut stops: [GradientStop; 2] = [pair[0], pair[1]];
55 stops[0].1.a *= self.tint;
56 stops[1].1.a *= self.tint;
57 stops
58 })
59 }
60}
61
62impl Eq for MultiStopGradient {}
63
64impl PartialOrd for MultiStopGradient {
65 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66 Some(self.cmp(other))
67 }
68}
69
70impl Ord for MultiStopGradient {
71 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72 if (&other.shared_stops, other.tint) < (&self.shared_stops, self.tint) {
73 std::cmp::Ordering::Less
74 } else if (&self.shared_stops, self.tint) < (&other.shared_stops, other.tint) {
75 std::cmp::Ordering::Greater
76 } else {
77 std::cmp::Ordering::Equal
78 }
79 }
80}
81
82#[allow(clippy::large_enum_variant)]
83#[derive(Clone, Debug, PartialEq, PartialOrd)]
84#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
85pub(crate) enum GradientColors {
86 TwoStop { start_color: Color, end_color: Color },
87 MultiStop { stops: MultiStopGradient },
88}
89impl GradientColors {
90 fn mul_alpha(&mut self, a: f32) {
91 match self {
92 GradientColors::TwoStop { start_color, end_color } => {
93 start_color.a *= a;
94 end_color.a *= a;
95 }
96 GradientColors::MultiStop { stops, .. } => {
97 stops.tint *= a;
98 }
99 }
100 }
101 fn from_stops<Stops>(stops: Stops) -> GradientColors
102 where
103 Stops: IntoIterator<Item = (f32, Color)>,
104 {
105 let mut stops = stops.into_iter();
106 let first_stop = match stops.next() {
107 Some(stop) => stop,
108 None => {
109 // No stops, we use black.
110 return GradientColors::TwoStop {
111 start_color: Color::black(),
112 end_color: Color::black(),
113 };
114 }
115 };
116 let second_stop = match stops.next() {
117 Some(stop) => stop,
118 None => {
119 // One stop devolves to a solid color fill (but using the gradient shader variation).
120 return GradientColors::TwoStop {
121 start_color: first_stop.1,
122 end_color: first_stop.1,
123 };
124 }
125 };
126
127 let maybe_third_stop = stops.next();
128
129 if maybe_third_stop.is_none() && first_stop.0 <= 0.0 && second_stop.0 >= 1.0 {
130 // Two stops takes the classic gradient path, so long as the stop positions are at
131 // the extents (if the stop positions are inset then we'll fill to them).
132 return GradientColors::TwoStop {
133 start_color: first_stop.1,
134 end_color: second_stop.1,
135 };
136 }
137
138 // Actual multistop gradient. We copy out the stops and then use a stop with a
139 // position > 1.0 as a sentinel. GradientStore ignores stop positions > 1.0
140 // when synthesizing the gradient texture.
141 let out_stops = [first_stop, second_stop]
142 .into_iter()
143 .chain(maybe_third_stop)
144 .chain(stops)
145 .map(|(stop, color)| GradientStop(stop, color))
146 .collect();
147 GradientColors::MultiStop {
148 stops: MultiStopGradient {
149 shared_stops: out_stops,
150 tint: 1.0,
151 },
152 }
153 }
154}
155
156#[derive(Clone, Debug)]
157#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
158pub(crate) enum PaintFlavor {
159 Color(Color),
160 #[cfg_attr(feature = "serde", serde(skip))]
161 Image {
162 id: ImageId,
163 center: Position,
164 width: f32,
165 height: f32,
166 angle: f32,
167 tint: Color,
168 },
169 LinearGradient {
170 start: Position,
171 end: Position,
172 colors: GradientColors,
173 },
174 BoxGradient {
175 pos: Position,
176 width: f32,
177 height: f32,
178 radius: f32,
179 feather: f32,
180 colors: GradientColors,
181 },
182 RadialGradient {
183 center: Position,
184 in_radius: f32,
185 out_radius: f32,
186 colors: GradientColors,
187 },
188}
189
190// Convenience method to fetch the GradientColors out of a PaintFlavor
191impl PaintFlavor {
192 pub(crate) fn mul_alpha(&mut self, a: f32) {
193 match self {
194 PaintFlavor::Color(color) => {
195 color.a *= a;
196 }
197 PaintFlavor::Image { tint, .. } => {
198 tint.a *= a;
199 }
200 PaintFlavor::LinearGradient { colors, .. } => {
201 colors.mul_alpha(a);
202 }
203 PaintFlavor::BoxGradient { colors, .. } => {
204 colors.mul_alpha(a);
205 }
206 PaintFlavor::RadialGradient { colors, .. } => {
207 colors.mul_alpha(a);
208 }
209 }
210 }
211
212 pub(crate) fn gradient_colors(&self) -> Option<&GradientColors> {
213 match self {
214 PaintFlavor::LinearGradient { colors, .. } => Some(colors),
215 PaintFlavor::BoxGradient { colors, .. } => Some(colors),
216 PaintFlavor::RadialGradient { colors, .. } => Some(colors),
217 _ => None,
218 }
219 }
220
221 /// Returns true if this paint is an untransformed image paint without anti-aliasing at the edges in case of a fill
222 pub(crate) fn is_straight_tinted_image(&self, shape_anti_alias: bool) -> bool {
223 matches!(self, &PaintFlavor::Image { angle, .. } if angle == 0.0 && !shape_anti_alias)
224 }
225}
226
227#[derive(Copy, Clone, Debug)]
228pub(crate) enum GlyphTexture {
229 None,
230 AlphaMask(ImageId),
231 ColorTexture(ImageId),
232}
233
234impl Default for GlyphTexture {
235 fn default() -> Self {
236 Self::None
237 }
238}
239
240#[derive(Clone, Debug)]
241#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
242pub(crate) struct StrokeSettings {
243 pub(crate) stencil_strokes: bool,
244 pub(crate) miter_limit: f32,
245 pub(crate) line_width: f32,
246 pub(crate) line_cap_start: LineCap,
247 pub(crate) line_cap_end: LineCap,
248 pub(crate) line_join: LineJoin,
249}
250
251impl Default for StrokeSettings {
252 fn default() -> Self {
253 Self {
254 stencil_strokes: true,
255 miter_limit: 10.0,
256 line_width: 1.0,
257 line_cap_start: Default::default(),
258 line_cap_end: Default::default(),
259 line_join: Default::default(),
260 }
261 }
262}
263
264#[derive(Clone, Debug)]
265#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266pub(crate) struct TextSettings {
267 #[cfg_attr(feature = "serde", serde(skip))]
268 pub(crate) font_ids: [Option<FontId>; 8],
269 pub(crate) font_size: f32,
270 pub(crate) letter_spacing: f32,
271 pub(crate) text_baseline: Baseline,
272 pub(crate) text_align: Align,
273}
274
275impl Default for TextSettings {
276 fn default() -> Self {
277 Self {
278 font_ids: Default::default(),
279 font_size: 16.0,
280 letter_spacing: 0.0,
281 text_baseline: Default::default(),
282 text_align: Default::default(),
283 }
284 }
285}
286
287/// Struct controlling how graphical shapes are rendered.
288///
289/// The Paint struct is a relatively lightweight object which contains all the information needed to
290/// display something on the canvas. Unlike the HTML canvas where the current drawing style is stored
291/// in an internal stack this paint struct is simply passed to the relevant drawing methods on the canvas.
292///
293/// Clients code can have as many paints as they desire for different use cases and styles. This makes
294/// the internal stack in the [Canvas](struct.Canvas.html) struct much lighter since it only needs to
295/// contain the transform stack and current scissor rectangle.
296///
297/// # Example
298/// ```
299/// use femtovg::{Paint, Path, Color, Canvas, renderer::Void};
300///
301/// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
302///
303/// let fill_paint = Paint::color(Color::hex("454545"));
304/// let mut stroke_paint = Paint::color(Color::hex("bababa"));
305/// stroke_paint.set_line_width(4.0);
306///
307/// let mut path = Path::new();
308/// path.rounded_rect(10.0, 10.0, 100.0, 100.0, 20.0);
309/// canvas.fill_path(&path, &fill_paint);
310/// canvas.stroke_path(&path, &stroke_paint);
311/// ```
312#[derive(Clone, Debug)]
313#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
314pub struct Paint {
315 pub(crate) flavor: PaintFlavor,
316 pub(crate) shape_anti_alias: bool,
317 pub(crate) stroke: StrokeSettings,
318 pub(crate) text: TextSettings,
319 pub(crate) fill_rule: FillRule,
320}
321
322impl Default for Paint {
323 fn default() -> Self {
324 Self {
325 flavor: PaintFlavor::Color(Color::white()),
326 shape_anti_alias: true,
327 stroke: StrokeSettings::default(),
328 text: TextSettings::default(),
329 fill_rule: Default::default(),
330 }
331 }
332}
333
334impl Paint {
335 /// Creates a new solid color paint
336 pub fn color(color: Color) -> Self {
337 Paint::with_flavor(PaintFlavor::Color(color))
338 }
339
340 fn with_flavor(flavor: PaintFlavor) -> Self {
341 Paint {
342 flavor,
343 ..Default::default()
344 }
345 }
346
347 /// Creates a new image pattern paint.
348 ///
349 /// * `id` - is handle to the image to render
350 /// * `cx` `cy` - Specify the top-left location of the image pattern
351 /// * `width` `height` - The size of one image
352 /// * `angle` - Rotation around the top-left corner
353 /// * `alpha` - Transparency applied on the image
354 ///
355 /// # Example
356 /// ```
357 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
358 ///
359 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
360 ///
361 /// let image_id = canvas.load_image_file("examples/assets/rust-logo.png", ImageFlags::GENERATE_MIPMAPS).expect("Cannot create image");
362 /// let fill_paint = Paint::image(image_id, 10.0, 10.0, 85.0, 85.0, 0.0, 1.0);
363 ///
364 /// let mut path = Path::new();
365 /// path.rect(10.0, 10.0, 85.0, 85.0);
366 /// canvas.fill_path(&path, &fill_paint);
367 /// ```
368 pub fn image(id: ImageId, cx: f32, cy: f32, width: f32, height: f32, angle: f32, alpha: f32) -> Self {
369 Paint::with_flavor(PaintFlavor::Image {
370 id,
371 center: Position { x: cx, y: cy },
372 width,
373 height,
374 angle,
375 tint: Color::rgbaf(1.0, 1.0, 1.0, alpha),
376 })
377 }
378
379 /// Like `image`, but allows for adding a tint, or a color which will transform each pixel's
380 /// color via channel-wise multiplication.
381 pub fn image_tint(id: ImageId, cx: f32, cy: f32, width: f32, height: f32, angle: f32, tint: Color) -> Self {
382 Paint::with_flavor(PaintFlavor::Image {
383 id,
384 center: Position { x: cx, y: cy },
385 width,
386 height,
387 angle,
388 tint,
389 })
390 }
391
392 /// Creates and returns a linear gradient paint.
393 ///
394 /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
395 ///
396 /// # Example
397 /// ```
398 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
399 ///
400 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
401 ///
402 /// let bg = Paint::linear_gradient(0.0, 0.0, 0.0, 100.0, Color::rgba(255, 255, 255, 16), Color::rgba(0, 0, 0, 16));
403 /// let mut path = Path::new();
404 /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
405 /// canvas.fill_path(&path, &bg);
406 /// ```
407 pub fn linear_gradient(
408 start_x: f32,
409 start_y: f32,
410 end_x: f32,
411 end_y: f32,
412 start_color: Color,
413 end_color: Color,
414 ) -> Self {
415 Paint::with_flavor(PaintFlavor::LinearGradient {
416 start: Position { x: start_x, y: start_y },
417 end: Position { x: end_x, y: end_y },
418 colors: GradientColors::TwoStop { start_color, end_color },
419 })
420 }
421 /// Creates and returns a linear gradient paint with two or more stops.
422 ///
423 /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
424 ///
425 /// # Example
426 /// ```
427 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
428 ///
429 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
430 ///
431 /// let bg = Paint::linear_gradient_stops(
432 /// 0.0, 0.0,
433 /// 0.0, 100.0,
434 /// [
435 /// (0.0, Color::rgba(255, 255, 255, 16)),
436 /// (0.5, Color::rgba(0, 0, 0, 16)),
437 /// (1.0, Color::rgba(255, 0, 0, 16))
438 /// ]);
439 /// let mut path = Path::new();
440 /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
441 /// canvas.fill_path(&path, &bg);
442 /// ```
443 pub fn linear_gradient_stops(
444 start_x: f32,
445 start_y: f32,
446 end_x: f32,
447 end_y: f32,
448 stops: impl IntoIterator<Item = (f32, Color)>,
449 ) -> Self {
450 Paint::with_flavor(PaintFlavor::LinearGradient {
451 start: Position { x: start_x, y: start_y },
452 end: Position { x: end_x, y: end_y },
453 colors: GradientColors::from_stops(stops),
454 })
455 }
456
457 #[allow(clippy::too_many_arguments)]
458 /// Creates and returns a box gradient.
459 ///
460 /// Box gradient is a feathered rounded rectangle, it is useful for rendering
461 /// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle,
462 /// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry
463 /// the border of the rectangle is. Parameter inner_color specifies the inner color and outer_color the outer color of the gradient.
464 /// The gradient is transformed by the current transform when it is passed to fill_path() or stroke_path().
465 ///
466 /// # Example
467 /// ```
468 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
469 ///
470 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
471 ///
472 /// let bg = Paint::box_gradient(
473 /// 0.0,
474 /// 0.0,
475 /// 100.0,
476 /// 100.0,
477 /// 10.0,
478 /// 10.0,
479 /// Color::rgba(0, 0, 0, 128),
480 /// Color::rgba(0, 0, 0, 0),
481 /// );
482 ///
483 /// let mut path = Path::new();
484 /// path.rounded_rect(0.0, 0.0, 100.0, 100.0, 5.0);
485 /// canvas.fill_path(&path, &bg);
486 /// ```
487 pub fn box_gradient(
488 x: f32,
489 y: f32,
490 width: f32,
491 height: f32,
492 radius: f32,
493 feather: f32,
494 inner_color: Color,
495 outer_color: Color,
496 ) -> Self {
497 Paint::with_flavor(PaintFlavor::BoxGradient {
498 pos: Position { x, y },
499 width,
500 height,
501 radius,
502 feather,
503 colors: GradientColors::TwoStop {
504 start_color: inner_color,
505 end_color: outer_color,
506 },
507 })
508 }
509
510 /// Creates and returns a radial gradient.
511 ///
512 /// Parameters (cx,cy) specify the center, in_radius and out_radius specify
513 /// the inner and outer radius of the gradient, inner_color specifies the start color and outer_color the end color.
514 /// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
515 ///
516 /// # Example
517 /// ```
518 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
519 ///
520 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
521 ///
522 /// let bg = Paint::radial_gradient(
523 /// 50.0,
524 /// 50.0,
525 /// 18.0,
526 /// 24.0,
527 /// Color::rgba(0, 0, 0, 128),
528 /// Color::rgba(0, 0, 0, 0),
529 /// );
530 ///
531 /// let mut path = Path::new();
532 /// path.circle(50.0, 50.0, 20.0);
533 /// canvas.fill_path(&path, &bg);
534 /// ```
535 pub fn radial_gradient(
536 cx: f32,
537 cy: f32,
538 in_radius: f32,
539 out_radius: f32,
540 inner_color: Color,
541 outer_color: Color,
542 ) -> Self {
543 Paint::with_flavor(PaintFlavor::RadialGradient {
544 center: Position { x: cx, y: cy },
545 in_radius,
546 out_radius,
547 colors: GradientColors::TwoStop {
548 start_color: inner_color,
549 end_color: outer_color,
550 },
551 })
552 }
553
554 /// Creates and returns a multi-stop radial gradient.
555 ///
556 /// Parameters (cx,cy) specify the center, in_radius and out_radius specify the inner and outer radius of the gradient,
557 /// colors specifies a list of color stops with offsets. The first offset should be 0.0 and the last offset should be 1.0.
558 ///
559 /// The gradient is transformed by the current transform when it is passed to fill_paint() or stroke_paint().
560 ///
561 /// # Example
562 /// ```
563 /// use femtovg::{Paint, Path, Color, Canvas, ImageFlags, renderer::Void};
564 ///
565 /// let mut canvas = Canvas::new(Void).expect("Cannot create canvas");
566 ///
567 /// let bg = Paint::radial_gradient_stops(
568 /// 50.0,
569 /// 50.0,
570 /// 18.0,
571 /// 24.0,
572 /// [
573 /// (0.0, Color::rgba(0, 0, 0, 128)),
574 /// (0.5, Color::rgba(0, 0, 128, 128)),
575 /// (1.0, Color::rgba(0, 128, 0, 128))
576 /// ]
577 /// );
578 ///
579 /// let mut path = Path::new();
580 /// path.circle(50.0, 50.0, 20.0);
581 /// canvas.fill_path(&path, &bg);
582 /// ```
583 pub fn radial_gradient_stops(
584 cx: f32,
585 cy: f32,
586 in_radius: f32,
587 out_radius: f32,
588 stops: impl IntoIterator<Item = (f32, Color)>,
589 ) -> Self {
590 Paint::with_flavor(PaintFlavor::RadialGradient {
591 center: Position { x: cx, y: cy },
592 in_radius,
593 out_radius,
594 colors: GradientColors::from_stops(stops),
595 })
596 }
597
598 /// Creates a new solid color paint
599 pub fn set_color(&mut self, color: Color) {
600 self.flavor = PaintFlavor::Color(color);
601 }
602
603 /// Returns the paint with a new solid color set to the specified value.
604 pub fn with_color(mut self, color: Color) -> Self {
605 self.set_color(color);
606 self
607 }
608
609 /// Returns boolean if the shapes drawn with this paint will be antialiased.
610 pub fn anti_alias(&self) -> bool {
611 self.shape_anti_alias
612 }
613
614 /// Sets whether shapes drawn with this paint will be anti aliased. Enabled by default.
615 pub fn set_anti_alias(&mut self, value: bool) {
616 self.shape_anti_alias = value;
617 }
618
619 /// Returns the paint with anti alias set to the specified value.
620 pub fn with_anti_alias(mut self, value: bool) -> Self {
621 self.set_anti_alias(value);
622 self
623 }
624
625 /// True if this paint uses higher quality stencil strokes.
626 pub fn stencil_strokes(&self) -> bool {
627 self.stroke.stencil_strokes
628 }
629
630 /// Sets whether to use higher quality stencil strokes.
631 pub fn set_stencil_strokes(&mut self, value: bool) {
632 self.stroke.stencil_strokes = value;
633 }
634
635 /// Returns the paint with stencil strokes set to the specified value.
636 pub fn with_stencil_strokes(mut self, value: bool) -> Self {
637 self.set_stencil_strokes(value);
638 self
639 }
640
641 /// Returns the current line width.
642 pub fn line_width(&self) -> f32 {
643 self.stroke.line_width
644 }
645
646 /// Sets the line width for shapes stroked with this paint.
647 pub fn set_line_width(&mut self, width: f32) {
648 self.stroke.line_width = width;
649 }
650
651 /// Returns the paint with line width set to the specified value.
652 pub fn with_line_width(mut self, width: f32) -> Self {
653 self.set_line_width(width);
654 self
655 }
656
657 /// Getter for the miter limit
658 pub fn miter_limit(&self) -> f32 {
659 self.stroke.miter_limit
660 }
661
662 /// Sets the limit at which a sharp corner is drawn beveled.
663 ///
664 /// If the miter at a corner exceeds this limit, LineJoin is replaced with LineJoin::Bevel.
665 pub fn set_miter_limit(&mut self, limit: f32) {
666 self.stroke.miter_limit = limit;
667 }
668
669 /// Returns the paint with the miter limit set to the specified value.
670 pub fn with_miter_limit(mut self, limit: f32) -> Self {
671 self.set_miter_limit(limit);
672 self
673 }
674
675 /// Returns the current start line cap for this paint.
676 pub fn line_cap_start(&self) -> LineCap {
677 self.stroke.line_cap_start
678 }
679
680 /// Returns the current start line cap for this paint.
681 pub fn line_cap_end(&self) -> LineCap {
682 self.stroke.line_cap_end
683 }
684
685 /// Sets how the start and end of the line (cap) is drawn
686 ///
687 /// By default it's set to LineCap::Butt
688 pub fn set_line_cap(&mut self, cap: LineCap) {
689 self.stroke.line_cap_start = cap;
690 self.stroke.line_cap_end = cap;
691 }
692
693 /// Returns the paint with line cap set to the specified value.
694 pub fn with_line_cap(mut self, cap: LineCap) -> Self {
695 self.set_line_cap(cap);
696 self
697 }
698
699 /// Sets how the beggining cap of the line is drawn
700 ///
701 /// By default it's set to LineCap::Butt
702 pub fn set_line_cap_start(&mut self, cap: LineCap) {
703 self.stroke.line_cap_start = cap;
704 }
705
706 /// Returns the paint with the beginning cap of the line set to the specified value.
707 pub fn with_line_cap_start(mut self, cap: LineCap) -> Self {
708 self.set_line_cap_start(cap);
709 self
710 }
711
712 /// Sets how the end cap of the line is drawn
713 ///
714 /// By default it's set to LineCap::Butt
715 pub fn set_line_cap_end(&mut self, cap: LineCap) {
716 self.stroke.line_cap_end = cap;
717 }
718
719 /// Returns the paint with the beginning cap of the line set to the specified value.
720 pub fn with_line_cap_end(mut self, cap: LineCap) -> Self {
721 self.set_line_cap_end(cap);
722 self
723 }
724
725 /// Returns the current line join for this paint.
726 pub fn line_join(&self) -> LineJoin {
727 self.stroke.line_join
728 }
729
730 /// Sets how sharp path corners are drawn.
731 ///
732 /// By default it's set to LineJoin::Miter
733 pub fn set_line_join(&mut self, join: LineJoin) {
734 self.stroke.line_join = join;
735 }
736
737 /// Returns the paint with the line join set to the specified value.
738 pub fn with_line_join(mut self, join: LineJoin) -> Self {
739 self.set_line_join(join);
740 self
741 }
742
743 pub fn set_font(&mut self, font_ids: &[FontId]) {
744 self.text.font_ids = Default::default();
745
746 for (i, id) in font_ids.iter().take(8).enumerate() {
747 self.text.font_ids[i] = Some(*id);
748 }
749 }
750
751 /// Returns the paint with the font set to the specified value.
752 pub fn with_font(mut self, font_ids: &[FontId]) -> Self {
753 self.set_font(font_ids);
754 self
755 }
756
757 /// Returns the current font size
758 ///
759 /// Only has effect on canvas text operations
760 pub fn font_size(&self) -> f32 {
761 self.text.font_size
762 }
763
764 /// Sets the font size.
765 ///
766 /// Only has effect on canvas text operations
767 pub fn set_font_size(&mut self, size: f32) {
768 self.text.font_size = size;
769 }
770
771 /// Returns the paint with the font size set to the specified value.
772 pub fn with_font_size(mut self, size: f32) -> Self {
773 self.set_font_size(size);
774 self
775 }
776
777 /// Returns the current letter spacing
778 pub fn letter_spacing(&self) -> f32 {
779 self.text.letter_spacing
780 }
781
782 /// Sets the letter spacing for this paint
783 ///
784 /// Only has effect on canvas text operations
785 pub fn set_letter_spacing(&mut self, spacing: f32) {
786 self.text.letter_spacing = spacing;
787 }
788
789 /// Returns the paint with the letter spacing set to the specified value.
790 pub fn with_letter_spacing(mut self, spacing: f32) -> Self {
791 self.set_letter_spacing(spacing);
792 self
793 }
794
795 /// Returns the current vertical align
796 pub fn text_baseline(&self) -> Baseline {
797 self.text.text_baseline
798 }
799
800 /// Sets the text vertical alignment for this paint
801 ///
802 /// Only has effect on canvas text operations
803 pub fn set_text_baseline(&mut self, align: Baseline) {
804 self.text.text_baseline = align;
805 }
806
807 /// Returns the paint with the text vertical alignment set to the specified value.
808 pub fn with_text_baseline(mut self, align: Baseline) -> Self {
809 self.set_text_baseline(align);
810 self
811 }
812
813 /// Returns the current horizontal align
814 pub fn text_align(&self) -> Align {
815 self.text.text_align
816 }
817
818 /// Sets the text horizontal alignment for this paint
819 ///
820 /// Only has effect on canvas text operations
821 pub fn set_text_align(&mut self, align: Align) {
822 self.text.text_align = align;
823 }
824
825 /// Returns the paint with the text horizontal alignment set to the specified value.
826 pub fn with_text_align(mut self, align: Align) -> Self {
827 self.set_text_align(align);
828 self
829 }
830
831 /// Retrieves the current fill rule setting for this paint
832 pub fn fill_rule(&self) -> FillRule {
833 self.fill_rule
834 }
835
836 /// Sets the current rule to be used when filling a path
837 ///
838 /// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule>
839 pub fn set_fill_rule(&mut self, rule: FillRule) {
840 self.fill_rule = rule;
841 }
842
843 /// Returns the paint with the rule for filling a path set to the specified value.
844 pub fn with_fill_rule(mut self, rule: FillRule) -> Self {
845 self.set_fill_rule(rule);
846 self
847 }
848}
849