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