1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Options for drawing paths.

use std::rc::Rc;

/// Options for drawing stroked lines.
///
/// You may configure particular aspects of the style by using the
/// methods described below.
///
/// ## Defaults
///
/// Currently, the style (and its various consituent parts) have [`Default`]
/// impls that conform to the defaults described in the
/// [Postscript Language Manual, 3rd Edition][PLRMv3]; that document is the
/// basis for the choice of these types, and can be consulted for detailed
/// explanations and illustrations.
///
/// It is possible that in the future certain of these defaults may change;
/// if you are particular about your style you can create the various types
/// explicitly instead of relying on the default impls.
///
/// ```
/// use piet::{LineJoin, StrokeStyle};
///
/// const CONST_STLYE: StrokeStyle = StrokeStyle::new()
///     .dash_pattern(&[5.0, 1.0, 2.0])
///     .line_join(LineJoin::Round);
///
/// let style = StrokeStyle::new()
///     .dash_pattern(&[10.0, 5.0, 2.0])
///     .dash_offset(5.0);
///
/// ```
///
/// [PLRMv3]: https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf
#[derive(Clone, PartialEq, Debug, Default)]
pub struct StrokeStyle {
    /// How to join segments of the path.
    ///
    /// By default, this is [`LineJoin::Miter`] with a `limit` of `10.0`.
    pub line_join: LineJoin,
    /// How to terminate open paths.
    ///
    /// (closed paths do not have ends.)
    ///
    /// by default, this is [`LineCap::Butt`].
    pub line_cap: LineCap,
    /// The sequence of alternating dashes and gaps uses to draw the line.
    ///
    /// If the sequence is not empty, all numbers should be finite and
    /// non-negative, and the sequence should not be all zeros.
    ///
    /// On platforms that do not support an odd number of lengths in the array,
    /// the implementation may concatenate two copies of the array to reach
    /// an even count.
    ///
    /// By default, this is empty (`&[]`), indicating a solid line.
    pub dash_pattern: StrokeDash,
    /// The distance into the `dash_pattern` at which drawing begins.
    ///
    /// By default, this is `0.0`.
    pub dash_offset: f64,
}

/// A type that represents an alternating pattern of drawn and undrawn segments.
///
/// We use our own type as a way of making this work in `const` contexts.
///
/// This type `Deref`s to `&[f64]`.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct StrokeDash {
    slice: &'static [f64],
    alloc: Option<Rc<[f64]>>,
}

/// Options for angled joins in strokes.
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum LineJoin {
    /// The outer edges of the two paths are extended until they intersect.
    ///
    /// Because the miter length can be extreme for small angles, you must supply
    /// a 'limit' at which we will fallback on [`LineJoin::Bevel`].
    ///
    /// This limit is the distance from the point where the inner edges of the
    /// stroke meet to the point where the outer edges meet.
    ///
    /// The default limit is `10.0`.
    ///
    /// This is also currently the default `LineJoin`; you should only need to
    /// construct it if you need to customize the `limit`.
    Miter {
        /// The maximum distance between the inner and outer stroke edges before beveling.
        limit: f64,
    },
    /// The two lines are joined by a circular arc.
    Round,
    /// The two segments are capped with [`LineCap::Butt`], and the notch is filled.
    Bevel,
}

impl LineJoin {
    /// The default maximum length for a [`LineJoin::Miter`].
    ///
    /// This is defined in the [Postscript Language Reference][PLRMv3] (pp 676).
    ///
    /// [PLRMv3]: https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/PLRM.pdf
    pub const DEFAULT_MITER_LIMIT: f64 = 10.0;
}

/// Options for the cap of stroked lines.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LineCap {
    /// The stroke is squared off at the endpoint of the path.
    Butt,
    /// The stroke ends in a semicircular arc with a diameter equal to the line width.
    Round,
    /// The stroke projects past the end of the path, and is squared off.
    ///
    /// The stroke projects for a distance equal to half the width of the line.
    Square,
}

impl StrokeStyle {
    /// Create a new `StrokeStyle` with the provided pattern.
    ///
    /// For no pattern (a solid line) pass `&[]`.
    ///
    /// This is available in a `const` context and does not allocate;
    /// the other methods for setting the dash pattern *do* allocate, for
    /// annoying reasons.
    ///
    /// # Example
    ///
    /// ```
    ///  use piet::{LineJoin, StrokeStyle};
    ///
    ///  const STYLE: StrokeStyle = StrokeStyle::new()
    ///     .dash_pattern(&[4.0, 2.0])
    ///     .dash_offset(8.0)
    ///     .line_join(LineJoin::Round);
    /// ```
    pub const fn new() -> StrokeStyle {
        StrokeStyle {
            dash_pattern: StrokeDash {
                slice: &[],
                alloc: None,
            },
            line_join: LineJoin::Miter {
                limit: LineJoin::DEFAULT_MITER_LIMIT,
            },
            line_cap: LineCap::Butt,
            dash_offset: 0.0,
        }
    }

    /// Builder-style method to set the [`LineJoin`].
    pub const fn line_join(mut self, line_join: LineJoin) -> Self {
        self.line_join = line_join;
        self
    }

    /// Builder-style method to set the [`LineCap`].
    pub const fn line_cap(mut self, line_cap: LineCap) -> Self {
        self.line_cap = line_cap;
        self
    }

    /// Builder-style method to set the [`dash_offset`].
    ///
    /// [`dash_offset`]: StrokeStyle#structfield.dash_offset
    pub const fn dash_offset(mut self, offset: f64) -> Self {
        self.dash_offset = offset;
        self
    }

    /// Builder-style method to set the [`dash_pattern`].
    ///
    /// This method takes a `&'static [f64]`, and does not allocate. If you
    /// do not have a static slice, you may use [`set_dash_pattern`] instead,
    /// which does allocate.
    ///
    /// [`dash_pattern`]: StrokeStyle#structfield.dash_pattern
    /// [`set_dash_pattern`]: StrokeStyle::set_dash_pattern
    pub const fn dash_pattern(mut self, lengths: &'static [f64]) -> Self {
        self.dash_pattern.slice = lengths;
        self
    }

    /// Set the [`LineJoin`].
    pub fn set_line_join(&mut self, line_join: LineJoin) {
        self.line_join = line_join;
    }

    /// Set the [`LineCap`].
    pub fn set_line_cap(&mut self, line_cap: LineCap) {
        self.line_cap = line_cap;
    }

    /// Set the dash offset.
    pub fn set_dash_offset(&mut self, offset: f64) {
        self.dash_offset = offset;
    }

    /// Set the dash pattern.
    ///
    /// This method always allocates. To construct without allocating, use the
    /// [`dash_pattern`] builder method.
    ///
    /// [`dash_pattern`]: StrokeStyle::dash_pattern()
    pub fn set_dash_pattern(&mut self, lengths: impl Into<Rc<[f64]>>) {
        self.dash_pattern.alloc = Some(lengths.into());
    }

    /// If the current [`LineJoin`] is [`LineJoin::Miter`] return the miter limit.
    pub fn miter_limit(&self) -> Option<f64> {
        match self.line_join {
            LineJoin::Miter { limit } => Some(limit),
            _ => None,
        }
    }
}

impl Default for LineJoin {
    fn default() -> Self {
        LineJoin::Miter {
            limit: LineJoin::DEFAULT_MITER_LIMIT,
        }
    }
}

impl Default for LineCap {
    fn default() -> Self {
        LineCap::Butt
    }
}

impl std::ops::Deref for StrokeDash {
    type Target = [f64];
    fn deref(&self) -> &Self::Target {
        self.alloc.as_deref().unwrap_or(self.slice)
    }
}