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)
}
}