1/*!
2 * The femtovg API is (like [NanoVG](https://github.com/memononen/nanovg))
3 * loosely modeled on the
4 * [HTML5 Canvas API](https://bucephalus.org/text/CanvasHandbook/CanvasHandbook.html).
5 *
6 * The coordinate system’s origin is the top-left corner,
7 * with positive X rightwards, positive Y downwards.
8 */
9
10/*
11TODO:
12 - Documentation
13 - Tests
14*/
15
16#[cfg(feature = "serde")]
17#[macro_use]
18extern crate serde;
19
20use std::{cell::RefCell, ops::Range, path::Path as FilePath, rc::Rc};
21
22use imgref::ImgVec;
23use rgb::RGBA8;
24
25mod utils;
26
27mod text;
28
29mod error;
30pub use error::ErrorKind;
31
32pub use text::{
33 Align, Atlas, Baseline, DrawCommand, FontId, FontMetrics, GlyphDrawCommands, Quad, RenderMode, TextContext,
34 TextMetrics,
35};
36
37use text::{GlyphAtlas, TextContextImpl};
38
39mod image;
40use crate::image::ImageStore;
41pub use crate::image::{ImageFilter, ImageFlags, ImageId, ImageInfo, ImageSource, PixelFormat};
42
43mod color;
44pub use color::Color;
45
46pub mod renderer;
47pub use renderer::{RenderTarget, Renderer};
48
49use renderer::{Command, CommandType, Drawable, Params, ShaderType, Vertex};
50
51pub(crate) mod geometry;
52pub use geometry::Transform2D;
53use geometry::*;
54
55mod paint;
56pub use paint::Paint;
57use paint::{GlyphTexture, PaintFlavor, StrokeSettings};
58
59mod path;
60use path::Convexity;
61pub use path::{Path, Solidity};
62
63mod gradient_store;
64use gradient_store::GradientStore;
65
66/// The fill rule used when filling paths: `EvenOdd`, `NonZero` (default).
67#[derive(Copy, Clone, Debug, Eq, PartialEq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69pub enum FillRule {
70 EvenOdd,
71 NonZero,
72}
73
74impl Default for FillRule {
75 fn default() -> Self {
76 Self::NonZero
77 }
78}
79
80/// Blend factors.
81#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
82pub enum BlendFactor {
83 /// Not all
84 Zero,
85 /// All use
86 One,
87 /// Using the source color
88 SrcColor,
89 /// Minus the source color
90 OneMinusSrcColor,
91 /// Using the target color
92 DstColor,
93 /// Minus the target color
94 OneMinusDstColor,
95 /// Using the source alpha
96 SrcAlpha,
97 /// Minus the source alpha
98 OneMinusSrcAlpha,
99 /// Using the target alpha
100 DstAlpha,
101 /// Minus the target alpha
102 OneMinusDstAlpha,
103 /// Scale color by minimum of source alpha and destination alpha
104 SrcAlphaSaturate,
105}
106
107/// Predefined composite oprations.
108#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
109pub enum CompositeOperation {
110 /// Displays the source over the destination.
111 SourceOver,
112 /// Displays the source in the destination, i.e. only the part of the source inside the destination is shown and the destination is transparent.
113 SourceIn,
114 /// Only displays the part of the source that is outside the destination, which is made transparent.
115 SourceOut,
116 /// Displays the source on top of the destination. The part of the source outside the destination is not shown.
117 Atop,
118 /// Displays the destination over the source.
119 DestinationOver,
120 /// Only displays the part of the destination that is inside the source, which is made transparent.
121 DestinationIn,
122 /// Only displays the part of the destination that is outside the source, which is made transparent.
123 DestinationOut,
124 /// Displays the destination on top of the source. The part of the destination that is outside the source is not shown.
125 DestinationAtop,
126 /// Displays the source together with the destination, the overlapping area is rendered lighter.
127 Lighter,
128 /// Ignores the destination and just displays the source.
129 Copy,
130 /// Only the areas that exclusively belong either to the destination or the source are displayed. Overlapping parts are ignored.
131 Xor,
132}
133
134/// Determines how a new ("source") data is displayed against an existing ("destination") data.
135#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
136pub struct CompositeOperationState {
137 src_rgb: BlendFactor,
138 src_alpha: BlendFactor,
139 dst_rgb: BlendFactor,
140 dst_alpha: BlendFactor,
141}
142
143impl CompositeOperationState {
144 /// Creates a new CompositeOperationState from the provided CompositeOperation
145 pub fn new(op: CompositeOperation) -> Self {
146 let (sfactor, dfactor) = match op {
147 CompositeOperation::SourceOver => (BlendFactor::One, BlendFactor::OneMinusSrcAlpha),
148 CompositeOperation::SourceIn => (BlendFactor::DstAlpha, BlendFactor::Zero),
149 CompositeOperation::SourceOut => (BlendFactor::OneMinusDstAlpha, BlendFactor::Zero),
150 CompositeOperation::Atop => (BlendFactor::DstAlpha, BlendFactor::OneMinusSrcAlpha),
151 CompositeOperation::DestinationOver => (BlendFactor::OneMinusDstAlpha, BlendFactor::One),
152 CompositeOperation::DestinationIn => (BlendFactor::Zero, BlendFactor::SrcAlpha),
153 CompositeOperation::DestinationOut => (BlendFactor::Zero, BlendFactor::OneMinusSrcAlpha),
154 CompositeOperation::DestinationAtop => (BlendFactor::OneMinusDstAlpha, BlendFactor::SrcAlpha),
155 CompositeOperation::Lighter => (BlendFactor::One, BlendFactor::One),
156 CompositeOperation::Copy => (BlendFactor::One, BlendFactor::Zero),
157 CompositeOperation::Xor => (BlendFactor::OneMinusDstAlpha, BlendFactor::OneMinusSrcAlpha),
158 };
159
160 Self {
161 src_rgb: sfactor,
162 src_alpha: sfactor,
163 dst_rgb: dfactor,
164 dst_alpha: dfactor,
165 }
166 }
167
168 /// Creates a new CompositeOperationState with source and destination blend factors.
169 pub fn with_blend_factors(src_factor: BlendFactor, dst_factor: BlendFactor) -> Self {
170 Self {
171 src_rgb: src_factor,
172 src_alpha: src_factor,
173 dst_rgb: dst_factor,
174 dst_alpha: dst_factor,
175 }
176 }
177}
178
179impl Default for CompositeOperationState {
180 fn default() -> Self {
181 Self::new(op:CompositeOperation::SourceOver)
182 }
183}
184
185#[derive(Copy, Clone, Debug, Default)]
186struct Scissor {
187 transform: Transform2D,
188 extent: Option<[f32; 2]>,
189}
190
191impl Scissor {
192 /// Returns the bounding rect if the scissor clip if it's an untransformed rectangular clip
193 fn as_rect(&self, canvas_width: f32, canvas_height: f32) -> Option<Rect> {
194 let extent = match self.extent {
195 Some(extent) => extent,
196 None => return Some(Rect::new(0., 0., canvas_width, canvas_height)),
197 };
198
199 // Abort if we're skewing (usually doesn't happen)
200 if self.transform[1] != 0.0 || self.transform[2] != 0.0 {
201 return None;
202 }
203
204 // Abort if we're scaling
205 if self.transform[0] != 1.0 || self.transform[3] != 1.0 {
206 return None;
207 }
208
209 let half_width = extent[0];
210 let half_height = extent[1];
211 Some(Rect::new(
212 self.transform[4] - half_width,
213 self.transform[5] - half_height,
214 half_width * 2.0,
215 half_height * 2.0,
216 ))
217 }
218}
219
220/// Determines the shape used to draw the end points of lines:
221/// `Butt` (default), `Round`, `Square`.
222#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
223#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
224pub enum LineCap {
225 /// The ends of lines are squared off at the endpoints. Default value.
226 Butt,
227 /// The ends of lines are rounded.
228 Round,
229 /// The ends of lines are squared off by adding a box with an equal
230 /// width and half the height of the line's thickness.
231 Square,
232}
233
234impl Default for LineCap {
235 fn default() -> Self {
236 Self::Butt
237 }
238}
239
240/// Determines the shape used to join two line segments where they meet.
241/// `Miter` (default), `Round`, `Bevel`.
242#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)]
243#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
244pub enum LineJoin {
245 /// Connected segments are joined by extending their outside edges to
246 /// connect at a single point, with the effect of filling an additional
247 /// lozenge-shaped area. This setting is affected by the miterLimit property.
248 /// Default value.
249 Miter,
250 /// Rounds off the corners of a shape by filling an additional sector
251 /// of disc centered at the common endpoint of connected segments.
252 /// The radius for these rounded corners is equal to the line width.
253 Round,
254 /// Fills an additional triangular area between the common endpoint
255 /// of connected segments, and the separate outside rectangular
256 /// corners of each segment.
257 Bevel,
258}
259
260impl Default for LineJoin {
261 fn default() -> Self {
262 Self::Miter
263 }
264}
265
266#[derive(Copy, Clone, Debug)]
267struct State {
268 composite_operation: CompositeOperationState,
269 transform: Transform2D,
270 scissor: Scissor,
271 alpha: f32,
272}
273
274impl Default for State {
275 fn default() -> Self {
276 Self {
277 composite_operation: Default::default(),
278 transform: Transform2D::identity(),
279 scissor: Default::default(),
280 alpha: 1.0,
281 }
282 }
283}
284
285/// Main 2D drawing context.
286pub struct Canvas<T: Renderer> {
287 width: u32,
288 height: u32,
289 renderer: T,
290 text_context: Rc<RefCell<TextContextImpl>>,
291 glyph_atlas: Rc<GlyphAtlas>,
292 // Glyph atlas used for direct rendering of color glyphs, dropped after flush()
293 ephemeral_glyph_atlas: Option<Rc<GlyphAtlas>>,
294 current_render_target: RenderTarget,
295 state_stack: Vec<State>,
296 commands: Vec<Command>,
297 verts: Vec<Vertex>,
298 images: ImageStore<T::Image>,
299 fringe_width: f32,
300 device_px_ratio: f32,
301 tess_tol: f32,
302 dist_tol: f32,
303 gradients: GradientStore,
304}
305
306impl<T> Canvas<T>
307where
308 T: Renderer,
309{
310 /// Creates a new canvas.
311 pub fn new(renderer: T) -> Result<Self, ErrorKind> {
312 let mut canvas = Self {
313 width: 0,
314 height: 0,
315 renderer,
316 text_context: Default::default(),
317 glyph_atlas: Default::default(),
318 ephemeral_glyph_atlas: Default::default(),
319 current_render_target: RenderTarget::Screen,
320 state_stack: Default::default(),
321 commands: Default::default(),
322 verts: Default::default(),
323 images: ImageStore::new(),
324 fringe_width: 1.0,
325 device_px_ratio: 1.0,
326 tess_tol: 0.25,
327 dist_tol: 0.01,
328 gradients: GradientStore::new(),
329 };
330
331 canvas.save();
332
333 Ok(canvas)
334 }
335
336 /// Creates a new canvas with the specified renderer and using the fonts registered with the
337 /// provided [`TextContext`]. Note that the context is explicitly shared, so that any fonts
338 /// registered with a clone of this context will also be visible to this canvas.
339 pub fn new_with_text_context(renderer: T, text_context: TextContext) -> Result<Self, ErrorKind> {
340 let mut canvas = Self {
341 width: 0,
342 height: 0,
343 renderer,
344 text_context: text_context.0,
345 glyph_atlas: Default::default(),
346 ephemeral_glyph_atlas: Default::default(),
347 current_render_target: RenderTarget::Screen,
348 state_stack: Default::default(),
349 commands: Default::default(),
350 verts: Default::default(),
351 images: ImageStore::new(),
352 fringe_width: 1.0,
353 device_px_ratio: 1.0,
354 tess_tol: 0.25,
355 dist_tol: 0.01,
356 gradients: GradientStore::new(),
357 };
358
359 canvas.save();
360
361 Ok(canvas)
362 }
363
364 /// Sets the size of the default framebuffer (screen size)
365 pub fn set_size(&mut self, width: u32, height: u32, dpi: f32) {
366 self.width = width;
367 self.height = height;
368 self.fringe_width = 1.0 / dpi;
369 self.tess_tol = 0.25 / dpi;
370 self.dist_tol = 0.01 / dpi;
371 self.device_px_ratio = dpi;
372
373 self.renderer.set_size(width, height, dpi);
374
375 self.append_cmd(Command::new(CommandType::SetRenderTarget(RenderTarget::Screen)));
376 }
377
378 /// Clears the rectangle area defined by left upper corner (x,y), width and height with the provided color.
379 pub fn clear_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) {
380 let cmd = Command::new(CommandType::ClearRect {
381 x,
382 y,
383 width,
384 height,
385 color,
386 });
387
388 self.append_cmd(cmd);
389 }
390
391 /// Returns the width of the current render target.
392 pub fn width(&self) -> u32 {
393 match self.current_render_target {
394 RenderTarget::Image(id) => self.image_info(id).map(|info| info.width() as u32).unwrap_or(0),
395 RenderTarget::Screen => self.width,
396 }
397 }
398
399 /// Returns the height of the current render target.
400 pub fn height(&self) -> u32 {
401 match self.current_render_target {
402 RenderTarget::Image(id) => self.image_info(id).map(|info| info.height() as u32).unwrap_or(0),
403 RenderTarget::Screen => self.height,
404 }
405 }
406
407 /// Tells the renderer to execute all drawing commands and clears the current internal state
408 ///
409 /// Call this at the end of each frame.
410 pub fn flush(&mut self) {
411 self.renderer
412 .render(&mut self.images, &self.verts, std::mem::take(&mut self.commands));
413 self.verts.clear();
414 self.gradients
415 .release_old_gradients(&mut self.images, &mut self.renderer);
416 if let Some(atlas) = self.ephemeral_glyph_atlas.take() {
417 atlas.clear(self);
418 }
419 }
420
421 pub fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> {
422 self.flush();
423 self.renderer.screenshot()
424 }
425
426 // State Handling
427
428 /// Pushes and saves the current render state into a state stack.
429 ///
430 /// A matching restore() must be used to restore the state.
431 pub fn save(&mut self) {
432 let state = self.state_stack.last().map_or_else(State::default, |state| *state);
433
434 self.state_stack.push(state);
435 }
436
437 /// Restores the previous render state
438 ///
439 /// Restoring the initial/first state will just reset it to the defaults
440 pub fn restore(&mut self) {
441 if self.state_stack.len() > 1 {
442 self.state_stack.pop();
443 } else {
444 self.reset();
445 }
446 }
447
448 /// Resets current state to default values. Does not affect the state stack.
449 pub fn reset(&mut self) {
450 *self.state_mut() = Default::default();
451 }
452
453 /// Saves the current state before calling the callback and restores it afterwards
454 ///
455 /// This is less error prone than remembering to match save() -> restore() calls
456 pub fn save_with(&mut self, mut callback: impl FnMut(&mut Self)) {
457 self.save();
458
459 callback(self);
460
461 self.restore();
462 }
463
464 // Render styles
465
466 /// Sets the transparency applied to all rendered shapes.
467 ///
468 /// Already transparent paths will get proportionally more transparent as well.
469 pub fn set_global_alpha(&mut self, alpha: f32) {
470 self.state_mut().alpha = alpha;
471 }
472
473 /// Sets the composite operation.
474 pub fn global_composite_operation(&mut self, op: CompositeOperation) {
475 self.state_mut().composite_operation = CompositeOperationState::new(op);
476 }
477
478 /// Sets the composite operation with custom pixel arithmetic.
479 pub fn global_composite_blend_func(&mut self, src_factor: BlendFactor, dst_factor: BlendFactor) {
480 self.global_composite_blend_func_separate(src_factor, dst_factor, src_factor, dst_factor);
481 }
482
483 /// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately.
484 pub fn global_composite_blend_func_separate(
485 &mut self,
486 src_rgb: BlendFactor,
487 dst_rgb: BlendFactor,
488 src_alpha: BlendFactor,
489 dst_alpha: BlendFactor,
490 ) {
491 self.state_mut().composite_operation = CompositeOperationState {
492 src_rgb,
493 src_alpha,
494 dst_rgb,
495 dst_alpha,
496 }
497 }
498
499 /// Sets a new render target. All drawing operations after this call will happen on the provided render target
500 pub fn set_render_target(&mut self, target: RenderTarget) {
501 if self.current_render_target != target {
502 self.append_cmd(Command::new(CommandType::SetRenderTarget(target)));
503 self.current_render_target = target;
504 }
505 }
506
507 fn append_cmd(&mut self, cmd: Command) {
508 self.commands.push(cmd);
509 }
510
511 // Images
512
513 /// Allocates an empty image with the provided domensions and format.
514 pub fn create_image_empty(
515 &mut self,
516 width: usize,
517 height: usize,
518 format: PixelFormat,
519 flags: ImageFlags,
520 ) -> Result<ImageId, ErrorKind> {
521 let info = ImageInfo::new(flags, width, height, format);
522
523 self.images.alloc(&mut self.renderer, info)
524 }
525
526 /// Allocates an image that wraps the given backend-specific texture.
527 /// Use this function to import external textures into the rendering of a scene
528 /// with femtovg.
529 ///
530 /// It is necessary to call `[Self::delete_image`] to free femtovg specific
531 /// book-keeping data structures, the underlying backend-specific texture memory
532 /// will not be freed. It is the caller's responsible to delete it.
533 pub fn create_image_from_native_texture(
534 &mut self,
535 texture: T::NativeTexture,
536 info: ImageInfo,
537 ) -> Result<ImageId, ErrorKind> {
538 self.images.register_native_texture(&mut self.renderer, texture, info)
539 }
540
541 /// Creates image from specified image data.
542 pub fn create_image<'a, S: Into<ImageSource<'a>>>(
543 &mut self,
544 src: S,
545 flags: ImageFlags,
546 ) -> Result<ImageId, ErrorKind> {
547 let src = src.into();
548 let size = src.dimensions();
549 let id = self.create_image_empty(size.width, size.height, src.format(), flags)?;
550 self.images.update(&mut self.renderer, id, src, 0, 0)?;
551 Ok(id)
552 }
553
554 /// Returns the native texture of an image given its ID.
555 pub fn get_native_texture(&self, id: ImageId) -> Result<T::NativeTexture, ErrorKind> {
556 self.get_image(id)
557 .ok_or(ErrorKind::ImageIdNotFound)
558 .and_then(|image| self.renderer.get_native_texture(image))
559 }
560
561 pub fn get_image(&self, id: ImageId) -> Option<&T::Image> {
562 self.images.get(id)
563 }
564
565 pub fn get_image_mut(&mut self, id: ImageId) -> Option<&mut T::Image> {
566 self.images.get_mut(id)
567 }
568
569 /// Resizes an image to the new provided dimensions.
570 pub fn realloc_image(
571 &mut self,
572 id: ImageId,
573 width: usize,
574 height: usize,
575 format: PixelFormat,
576 flags: ImageFlags,
577 ) -> Result<(), ErrorKind> {
578 let info = ImageInfo::new(flags, width, height, format);
579 self.images.realloc(&mut self.renderer, id, info)
580 }
581
582 /// Decode an image from file
583 #[cfg(feature = "image-loading")]
584 pub fn load_image_file<P: AsRef<FilePath>>(
585 &mut self,
586 filename: P,
587 flags: ImageFlags,
588 ) -> Result<ImageId, ErrorKind> {
589 let image = ::image::open(filename)?;
590
591 let src = ImageSource::try_from(&image)?;
592
593 self.create_image(src, flags)
594 }
595
596 /// Decode an image from memory
597 #[cfg(feature = "image-loading")]
598 pub fn load_image_mem(&mut self, data: &[u8], flags: ImageFlags) -> Result<ImageId, ErrorKind> {
599 let image = ::image::load_from_memory(data)?;
600
601 let src = ImageSource::try_from(&image)?;
602
603 self.create_image(src, flags)
604 }
605
606 /// Updates image data specified by image handle.
607 pub fn update_image<'a, S: Into<ImageSource<'a>>>(
608 &mut self,
609 id: ImageId,
610 src: S,
611 x: usize,
612 y: usize,
613 ) -> Result<(), ErrorKind> {
614 self.images.update(&mut self.renderer, id, src.into(), x, y)
615 }
616
617 /// Deletes created image.
618 pub fn delete_image(&mut self, id: ImageId) {
619 self.images.remove(&mut self.renderer, id);
620 }
621
622 /// Returns image info
623 pub fn image_info(&self, id: ImageId) -> Result<ImageInfo, ErrorKind> {
624 if let Some(info) = self.images.info(id) {
625 Ok(info)
626 } else {
627 Err(ErrorKind::ImageIdNotFound)
628 }
629 }
630
631 /// Returns the size in pixels of the image for the specified id.
632 pub fn image_size(&self, id: ImageId) -> Result<(usize, usize), ErrorKind> {
633 let info = self.image_info(id)?;
634 Ok((info.width(), info.height()))
635 }
636
637 /// Renders the given source_image into target_image while applying a filter effect.
638 ///
639 /// The target image must have the same size as the source image. The filtering is recorded
640 /// as a drawing command and run by the renderer when [`Self::flush()`] is called.
641 ///
642 /// The filtering does not take any transformation set on the Canvas into account nor does it
643 /// change the current rendering target.
644 pub fn filter_image(&mut self, target_image: ImageId, filter: ImageFilter, source_image: ImageId) {
645 let (image_width, image_height) = match self.image_size(source_image) {
646 Ok((w, h)) => (w, h),
647 Err(_) => return,
648 };
649
650 // The renderer will receive a RenderFilteredImage command with two triangles attached that
651 // cover the image and the source image.
652 let mut cmd = Command::new(CommandType::RenderFilteredImage { target_image, filter });
653 cmd.image = Some(source_image);
654
655 let vertex_offset = self.verts.len();
656
657 let image_width = image_width as f32;
658 let image_height = image_height as f32;
659
660 let quad_x0 = 0.0;
661 let quad_y0 = -image_height;
662 let quad_x1 = image_width;
663 let quad_y1 = image_height;
664
665 let texture_x0 = -(image_width / 2.);
666 let texture_y0 = -(image_height / 2.);
667 let texture_x1 = (image_width) / 2.;
668 let texture_y1 = (image_height) / 2.;
669
670 self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0));
671 self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1));
672 self.verts.push(Vertex::new(quad_x1, quad_y0, texture_x1, texture_y0));
673 self.verts.push(Vertex::new(quad_x0, quad_y0, texture_x0, texture_y0));
674 self.verts.push(Vertex::new(quad_x0, quad_y1, texture_x0, texture_y1));
675 self.verts.push(Vertex::new(quad_x1, quad_y1, texture_x1, texture_y1));
676
677 cmd.triangles_verts = Some((vertex_offset, 6));
678
679 self.append_cmd(cmd)
680 }
681
682 // Transforms
683
684 /// Resets current transform to a identity matrix.
685 pub fn reset_transform(&mut self) {
686 self.state_mut().transform = Transform2D::identity();
687 }
688
689 #[allow(clippy::many_single_char_names)]
690 /// Premultiplies current coordinate system by specified transform.
691 pub fn set_transform(&mut self, transform: &Transform2D) {
692 self.state_mut().transform.premultiply(transform);
693 }
694
695 /// Translates the current coordinate system.
696 pub fn translate(&mut self, x: f32, y: f32) {
697 let mut t = Transform2D::identity();
698 t.translate(x, y);
699 self.state_mut().transform.premultiply(&t);
700 }
701
702 /// Rotates the current coordinate system. Angle is specified in radians.
703 pub fn rotate(&mut self, angle: f32) {
704 let mut t = Transform2D::identity();
705 t.rotate(angle);
706 self.state_mut().transform.premultiply(&t);
707 }
708
709 /// Skews the current coordinate system along X axis. Angle is specified in radians.
710 pub fn skew_x(&mut self, angle: f32) {
711 let mut t = Transform2D::identity();
712 t.skew_x(angle);
713 self.state_mut().transform.premultiply(&t);
714 }
715
716 /// Skews the current coordinate system along Y axis. Angle is specified in radians.
717 pub fn skew_y(&mut self, angle: f32) {
718 let mut t = Transform2D::identity();
719 t.skew_y(angle);
720 self.state_mut().transform.premultiply(&t);
721 }
722
723 /// Scales the current coordinate system.
724 pub fn scale(&mut self, x: f32, y: f32) {
725 let mut t = Transform2D::identity();
726 t.scale(x, y);
727 self.state_mut().transform.premultiply(&t);
728 }
729
730 /// Returns the current transformation matrix
731 pub fn transform(&self) -> Transform2D {
732 self.state().transform
733 }
734
735 // Scissoring
736
737 /// Sets the current scissor rectangle.
738 ///
739 /// The scissor rectangle is transformed by the current transform.
740 pub fn scissor(&mut self, x: f32, y: f32, w: f32, h: f32) {
741 let state = self.state_mut();
742
743 let w = w.max(0.0);
744 let h = h.max(0.0);
745
746 let mut transform = Transform2D::new_translation(x + w * 0.5, y + h * 0.5);
747 transform.multiply(&state.transform);
748 state.scissor.transform = transform;
749
750 state.scissor.extent = Some([w * 0.5, h * 0.5]);
751 }
752
753 /// Intersects current scissor rectangle with the specified rectangle.
754 ///
755 /// The scissor rectangle is transformed by the current transform.
756 /// Note: in case the rotation of previous scissor rect differs from
757 /// the current one, the intersection will be done between the specified
758 /// rectangle and the previous scissor rectangle transformed in the current
759 /// transform space. The resulting shape is always rectangle.
760 pub fn intersect_scissor(&mut self, x: f32, y: f32, w: f32, h: f32) {
761 let state = self.state_mut();
762
763 // If no previous scissor has been set, set the scissor as current scissor.
764 if state.scissor.extent.is_none() {
765 self.scissor(x, y, w, h);
766 return;
767 }
768
769 let extent = state.scissor.extent.unwrap();
770
771 // Transform the current scissor rect into current transform space.
772 // If there is difference in rotation, this will be approximation.
773
774 let mut pxform = state.scissor.transform;
775
776 let mut invxform = state.transform;
777 invxform.inverse();
778
779 pxform.multiply(&invxform);
780
781 let ex = extent[0];
782 let ey = extent[1];
783
784 let tex = ex * pxform[0].abs() + ey * pxform[2].abs();
785 let tey = ex * pxform[1].abs() + ey * pxform[3].abs();
786
787 let rect = Rect::new(pxform[4] - tex, pxform[5] - tey, tex * 2.0, tey * 2.0);
788 let res = rect.intersect(Rect::new(x, y, w, h));
789
790 self.scissor(res.x, res.y, res.w, res.h);
791 }
792
793 /// Reset and disables scissoring.
794 pub fn reset_scissor(&mut self) {
795 self.state_mut().scissor = Scissor::default();
796 }
797
798 // Paths
799
800 /// Returns true if the specified point (x,y) is in the provided path, and false otherwise.
801 pub fn contains_point(&self, path: &Path, x: f32, y: f32, fill_rule: FillRule) -> bool {
802 let transform = self.state().transform;
803
804 // The path cache saves a flattened and transformed version of the path.
805 let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
806
807 // Early out if path is outside the canvas bounds
808 if path_cache.bounds.maxx < 0.0
809 || path_cache.bounds.minx > self.width() as f32
810 || path_cache.bounds.maxy < 0.0
811 || path_cache.bounds.miny > self.height() as f32
812 {
813 return false;
814 }
815
816 path_cache.contains_point(x, y, fill_rule)
817 }
818
819 /// Return the bounding box for a Path
820 pub fn path_bbox(&self, path: &Path) -> Bounds {
821 let transform = self.state().transform;
822
823 // The path cache saves a flattened and transformed version of the path.
824 let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
825
826 path_cache.bounds
827 }
828
829 /// Fills the provided Path with the specified Paint.
830 pub fn fill_path(&mut self, path: &Path, paint: &Paint) {
831 self.fill_path_internal(path, &paint.flavor, paint.shape_anti_alias, paint.fill_rule);
832 }
833
834 fn fill_path_internal(&mut self, path: &Path, paint_flavor: &PaintFlavor, anti_alias: bool, fill_rule: FillRule) {
835 let mut paint_flavor = paint_flavor.clone();
836 let transform = self.state().transform;
837
838 // The path cache saves a flattened and transformed version of the path.
839 let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
840
841 let canvas_width = self.width();
842 let canvas_height = self.height();
843
844 // Early out if path is outside the canvas bounds
845 if path_cache.bounds.maxx < 0.0
846 || path_cache.bounds.minx > canvas_width as f32
847 || path_cache.bounds.maxy < 0.0
848 || path_cache.bounds.miny > canvas_height as f32
849 {
850 return;
851 }
852
853 // Apply global alpha
854 paint_flavor.mul_alpha(self.state().alpha);
855
856 let scissor = self.state().scissor;
857
858 // Calculate fill vertices.
859 // expand_fill will fill path_cache.contours[].{stroke, fill} with vertex data for the GPU
860 // fringe_with is the size of the strip of triangles generated at the path border used for AA
861 let fringe_width = if anti_alias { self.fringe_width } else { 0.0 };
862 path_cache.expand_fill(fringe_width, LineJoin::Miter, 2.4);
863
864 // Detect if this path fill is in fact just an unclipped image copy
865
866 if let (Some(path_rect), Some(scissor_rect), true) = (
867 path_cache.path_fill_is_rect(),
868 scissor.as_rect(canvas_width as f32, canvas_height as f32),
869 paint_flavor.is_straight_tinted_image(anti_alias),
870 ) {
871 if scissor_rect.contains_rect(&path_rect) {
872 self.render_unclipped_image_blit(&path_rect, &transform, &paint_flavor);
873 return;
874 } else if let Some(intersection) = path_rect.intersection(&scissor_rect) {
875 self.render_unclipped_image_blit(&intersection, &transform, &paint_flavor);
876 return;
877 } else {
878 return;
879 }
880 }
881
882 // GPU uniforms
883 let flavor = if path_cache.contours.len() == 1 && path_cache.contours[0].convexity == Convexity::Convex {
884 let params = Params::new(
885 &self.images,
886 &transform,
887 &paint_flavor,
888 &Default::default(),
889 &scissor,
890 self.fringe_width,
891 self.fringe_width,
892 -1.0,
893 );
894
895 CommandType::ConvexFill { params }
896 } else {
897 let stencil_params = Params {
898 stroke_thr: -1.0,
899 shader_type: ShaderType::Stencil,
900 ..Params::default()
901 };
902
903 let fill_params = Params::new(
904 &self.images,
905 &transform,
906 &paint_flavor,
907 &Default::default(),
908 &scissor,
909 self.fringe_width,
910 self.fringe_width,
911 -1.0,
912 );
913
914 CommandType::ConcaveFill {
915 stencil_params,
916 fill_params,
917 }
918 };
919
920 // GPU command
921 let mut cmd = Command::new(flavor);
922 cmd.fill_rule = fill_rule;
923 cmd.composite_operation = self.state().composite_operation;
924
925 if let PaintFlavor::Image { id, .. } = paint_flavor {
926 cmd.image = Some(id);
927 } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
928 cmd.image = self
929 .gradients
930 .lookup_or_add(stops, &mut self.images, &mut self.renderer)
931 .ok();
932 }
933
934 // All verts from all shapes are kept in a single buffer here in the canvas.
935 // Drawable struct is used to describe the range of vertices each draw call will operate on
936 let mut offset = self.verts.len();
937
938 cmd.drawables.reserve_exact(path_cache.contours.len());
939 for contour in &path_cache.contours {
940 let mut drawable = Drawable::default();
941
942 // Fill commands can have both fill and stroke vertices. Fill vertices are used to fill
943 // the body of the shape while stroke vertices are used to prodice antialiased edges
944
945 if !contour.fill.is_empty() {
946 drawable.fill_verts = Some((offset, contour.fill.len()));
947 self.verts.extend_from_slice(&contour.fill);
948 offset += contour.fill.len();
949 }
950
951 if !contour.stroke.is_empty() {
952 drawable.stroke_verts = Some((offset, contour.stroke.len()));
953 self.verts.extend_from_slice(&contour.stroke);
954 offset += contour.stroke.len();
955 }
956
957 cmd.drawables.push(drawable);
958 }
959
960 if let CommandType::ConcaveFill { .. } = cmd.cmd_type {
961 // Concave shapes are first filled by writing to a stencil buffer and then drawing a quad
962 // over the shape area with stencil test enabled to produce the final fill. These are
963 // the verts needed for the covering quad
964 self.verts.push(Vertex::new(
965 path_cache.bounds.maxx + fringe_width,
966 path_cache.bounds.maxy + fringe_width,
967 0.5,
968 1.0,
969 ));
970 self.verts.push(Vertex::new(
971 path_cache.bounds.maxx + fringe_width,
972 path_cache.bounds.miny - fringe_width,
973 0.5,
974 1.0,
975 ));
976 self.verts.push(Vertex::new(
977 path_cache.bounds.minx - fringe_width,
978 path_cache.bounds.maxy + fringe_width,
979 0.5,
980 1.0,
981 ));
982 self.verts.push(Vertex::new(
983 path_cache.bounds.minx - fringe_width,
984 path_cache.bounds.miny,
985 0.5,
986 1.0,
987 ));
988
989 cmd.triangles_verts = Some((offset, 4));
990 }
991
992 self.append_cmd(cmd);
993 }
994
995 /// Strokes the provided Path with the specified Paint.
996 pub fn stroke_path(&mut self, path: &Path, paint: &Paint) {
997 self.stroke_path_internal(path, &paint.flavor, paint.shape_anti_alias, &paint.stroke);
998 }
999
1000 fn stroke_path_internal(
1001 &mut self,
1002 path: &Path,
1003 paint_flavor: &PaintFlavor,
1004 anti_alias: bool,
1005 stroke: &StrokeSettings,
1006 ) {
1007 let mut paint_flavor = paint_flavor.clone();
1008 let transform = self.state().transform;
1009
1010 // The path cache saves a flattened and transformed version of the path.
1011 let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
1012
1013 // Early out if path is outside the canvas bounds
1014 if path_cache.bounds.maxx < 0.0
1015 || path_cache.bounds.minx > self.width() as f32
1016 || path_cache.bounds.maxy < 0.0
1017 || path_cache.bounds.miny > self.height() as f32
1018 {
1019 return;
1020 }
1021
1022 let scissor = self.state().scissor;
1023
1024 // Scale stroke width by current transform scale.
1025 // Note: I don't know why the original author clamped the max stroke width to 200, but it didn't
1026 // look correct when zooming in. There was probably a good reson for doing so and I may have
1027 // introduced a bug by removing the upper bound.
1028 //paint.set_stroke_width((paint.stroke_width() * transform.average_scale()).max(0.0).min(200.0));
1029 let mut line_width = (stroke.line_width * transform.average_scale()).max(0.0);
1030
1031 if line_width < self.fringe_width {
1032 // If the stroke width is less than pixel size, use alpha to emulate coverage.
1033 // Since coverage is area, scale by alpha*alpha.
1034 let alpha = (line_width / self.fringe_width).max(0.0).min(1.0);
1035
1036 paint_flavor.mul_alpha(alpha * alpha);
1037 line_width = self.fringe_width;
1038 }
1039
1040 // Apply global alpha
1041 paint_flavor.mul_alpha(self.state().alpha);
1042
1043 // Calculate stroke vertices.
1044 // expand_stroke will fill path_cache.contours[].stroke with vertex data for the GPU
1045 let fringe_with = if anti_alias { self.fringe_width } else { 0.0 };
1046 path_cache.expand_stroke(
1047 line_width * 0.5,
1048 fringe_with,
1049 stroke.line_cap_start,
1050 stroke.line_cap_end,
1051 stroke.line_join,
1052 stroke.miter_limit,
1053 self.tess_tol,
1054 );
1055
1056 // GPU uniforms
1057 let params = Params::new(
1058 &self.images,
1059 &transform,
1060 &paint_flavor,
1061 &Default::default(),
1062 &scissor,
1063 line_width,
1064 self.fringe_width,
1065 -1.0,
1066 );
1067
1068 let flavor = if stroke.stencil_strokes {
1069 let params2 = Params::new(
1070 &self.images,
1071 &transform,
1072 &paint_flavor,
1073 &Default::default(),
1074 &scissor,
1075 line_width,
1076 self.fringe_width,
1077 1.0 - 0.5 / 255.0,
1078 );
1079
1080 CommandType::StencilStroke {
1081 params1: params,
1082 params2,
1083 }
1084 } else {
1085 CommandType::Stroke { params }
1086 };
1087
1088 // GPU command
1089 let mut cmd = Command::new(flavor);
1090 cmd.composite_operation = self.state().composite_operation;
1091
1092 if let PaintFlavor::Image { id, .. } = paint_flavor {
1093 cmd.image = Some(id);
1094 } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
1095 cmd.image = self
1096 .gradients
1097 .lookup_or_add(stops, &mut self.images, &mut self.renderer)
1098 .ok();
1099 }
1100
1101 // All verts from all shapes are kept in a single buffer here in the canvas.
1102 // Drawable struct is used to describe the range of vertices each draw call will operate on
1103 let mut offset = self.verts.len();
1104
1105 cmd.drawables.reserve_exact(path_cache.contours.len());
1106 for contour in &path_cache.contours {
1107 let mut drawable = Drawable::default();
1108
1109 if !contour.stroke.is_empty() {
1110 drawable.stroke_verts = Some((offset, contour.stroke.len()));
1111 self.verts.extend_from_slice(&contour.stroke);
1112 offset += contour.stroke.len();
1113 }
1114
1115 cmd.drawables.push(drawable);
1116 }
1117
1118 self.append_cmd(cmd);
1119 }
1120
1121 fn render_unclipped_image_blit(&mut self, target_rect: &Rect, transform: &Transform2D, paint_flavor: &PaintFlavor) {
1122 let scissor = self.state().scissor;
1123
1124 let mut params = Params::new(
1125 &self.images,
1126 transform,
1127 paint_flavor,
1128 &Default::default(),
1129 &scissor,
1130 0.,
1131 0.,
1132 -1.0,
1133 );
1134 params.shader_type = ShaderType::TextureCopyUnclipped;
1135
1136 let mut cmd = Command::new(CommandType::Triangles { params });
1137 cmd.composite_operation = self.state().composite_operation;
1138
1139 let x0 = target_rect.x;
1140 let y0 = target_rect.y;
1141 let x1 = x0 + target_rect.w;
1142 let y1 = y0 + target_rect.h;
1143
1144 let (p0, p1) = (x0, y0);
1145 let (p2, p3) = (x1, y0);
1146 let (p4, p5) = (x1, y1);
1147 let (p6, p7) = (x0, y1);
1148
1149 // Apply the same mapping from vertex coordinates to texture coordinates as in the fragment shader,
1150 // but now ahead of time.
1151 let mut to_texture_space_transform = Transform2D::identity();
1152 to_texture_space_transform.scale(1. / params.extent[0], 1. / params.extent[1]);
1153 to_texture_space_transform.premultiply(&Transform2D([
1154 params.paint_mat[0],
1155 params.paint_mat[1],
1156 params.paint_mat[4],
1157 params.paint_mat[5],
1158 params.paint_mat[8],
1159 params.paint_mat[9],
1160 ]));
1161
1162 let (s0, t0) = to_texture_space_transform.transform_point(target_rect.x, target_rect.y);
1163 let (s1, t1) =
1164 to_texture_space_transform.transform_point(target_rect.x + target_rect.w, target_rect.y + target_rect.h);
1165
1166 let verts = [
1167 Vertex::new(p0, p1, s0, t0),
1168 Vertex::new(p4, p5, s1, t1),
1169 Vertex::new(p2, p3, s1, t0),
1170 Vertex::new(p0, p1, s0, t0),
1171 Vertex::new(p6, p7, s0, t1),
1172 Vertex::new(p4, p5, s1, t1),
1173 ];
1174
1175 if let &PaintFlavor::Image { id, .. } = paint_flavor {
1176 cmd.image = Some(id);
1177 }
1178
1179 cmd.triangles_verts = Some((self.verts.len(), verts.len()));
1180 self.append_cmd(cmd);
1181
1182 self.verts.extend_from_slice(&verts);
1183 }
1184
1185 // Text
1186
1187 /// Adds a font file to the canvas
1188 pub fn add_font<P: AsRef<FilePath>>(&mut self, file_path: P) -> Result<FontId, ErrorKind> {
1189 self.text_context.borrow_mut().add_font_file(file_path)
1190 }
1191
1192 /// Adds a font to the canvas by reading it from the specified chunk of memory.
1193 pub fn add_font_mem(&mut self, data: &[u8]) -> Result<FontId, ErrorKind> {
1194 self.text_context.borrow_mut().add_font_mem(data)
1195 }
1196
1197 /// Adds all .ttf files from a directory
1198 pub fn add_font_dir<P: AsRef<FilePath>>(&mut self, dir_path: P) -> Result<Vec<FontId>, ErrorKind> {
1199 self.text_context.borrow_mut().add_font_dir(dir_path)
1200 }
1201
1202 /// Returns information on how the provided text will be drawn with the specified paint.
1203 pub fn measure_text<S: AsRef<str>>(
1204 &self,
1205 x: f32,
1206 y: f32,
1207 text: S,
1208 paint: &Paint,
1209 ) -> Result<TextMetrics, ErrorKind> {
1210 let scale = self.font_scale() * self.device_px_ratio;
1211
1212 let mut text_settings = paint.text.clone();
1213 text_settings.font_size *= scale;
1214 text_settings.letter_spacing *= scale;
1215
1216 let scale = self.font_scale() * self.device_px_ratio;
1217 let invscale = 1.0 / scale;
1218
1219 self.text_context
1220 .borrow_mut()
1221 .measure_text(x * scale, y * scale, text, &text_settings)
1222 .map(|mut metrics| {
1223 metrics.scale(invscale);
1224 metrics
1225 })
1226 }
1227
1228 /// Returns font metrics for a particular Paint.
1229 pub fn measure_font(&self, paint: &Paint) -> Result<FontMetrics, ErrorKind> {
1230 let scale = self.font_scale() * self.device_px_ratio;
1231
1232 self.text_context
1233 .borrow_mut()
1234 .measure_font(paint.text.font_size * scale, &paint.text.font_ids)
1235 }
1236
1237 /// Returns the maximum index-th byte of text that will fit inside max_width.
1238 ///
1239 /// The retuned index will always lie at the start and/or end of a UTF-8 code point sequence or at the start or end of the text
1240 pub fn break_text<S: AsRef<str>>(&self, max_width: f32, text: S, paint: &Paint) -> Result<usize, ErrorKind> {
1241 let scale = self.font_scale() * self.device_px_ratio;
1242
1243 let mut text_settings = paint.text.clone();
1244 text_settings.font_size *= scale;
1245 text_settings.letter_spacing *= scale;
1246
1247 let max_width = max_width * scale;
1248
1249 self.text_context
1250 .borrow_mut()
1251 .break_text(max_width, text, &text_settings)
1252 }
1253
1254 /// Returnes a list of ranges representing each line of text that will fit inside max_width
1255 pub fn break_text_vec<S: AsRef<str>>(
1256 &self,
1257 max_width: f32,
1258 text: S,
1259 paint: &Paint,
1260 ) -> Result<Vec<Range<usize>>, ErrorKind> {
1261 let scale = self.font_scale() * self.device_px_ratio;
1262
1263 let mut text_settings = paint.text.clone();
1264 text_settings.font_size *= scale;
1265 text_settings.letter_spacing *= scale;
1266
1267 let max_width = max_width * scale;
1268
1269 self.text_context
1270 .borrow_mut()
1271 .break_text_vec(max_width, text, &text_settings)
1272 }
1273
1274 /// Fills the provided string with the specified Paint.
1275 pub fn fill_text<S: AsRef<str>>(
1276 &mut self,
1277 x: f32,
1278 y: f32,
1279 text: S,
1280 paint: &Paint,
1281 ) -> Result<TextMetrics, ErrorKind> {
1282 self.draw_text(x, y, text.as_ref(), paint, RenderMode::Fill)
1283 }
1284
1285 /// Strokes the provided string with the specified Paint.
1286 pub fn stroke_text<S: AsRef<str>>(
1287 &mut self,
1288 x: f32,
1289 y: f32,
1290 text: S,
1291 paint: &Paint,
1292 ) -> Result<TextMetrics, ErrorKind> {
1293 self.draw_text(x, y, text.as_ref(), paint, RenderMode::Stroke)
1294 }
1295
1296 /// Dispatch an explicit set of GlyphDrawCommands to the renderer. Use this only if you are
1297 /// using a custom font rasterizer/layout.
1298 pub fn draw_glyph_commands(&mut self, draw_commands: GlyphDrawCommands, paint: &Paint, scale: f32) {
1299 let transform = self.state().transform;
1300 let invscale = 1.0 / scale;
1301 let create_vertices = |quads: &Vec<text::Quad>| {
1302 let mut verts = Vec::with_capacity(quads.len() * 6);
1303
1304 for quad in quads {
1305 let (p0, p1) = transform.transform_point(quad.x0 * invscale, quad.y0 * invscale);
1306 let (p2, p3) = transform.transform_point(quad.x1 * invscale, quad.y0 * invscale);
1307 let (p4, p5) = transform.transform_point(quad.x1 * invscale, quad.y1 * invscale);
1308 let (p6, p7) = transform.transform_point(quad.x0 * invscale, quad.y1 * invscale);
1309
1310 verts.push(Vertex::new(p0, p1, quad.s0, quad.t0));
1311 verts.push(Vertex::new(p4, p5, quad.s1, quad.t1));
1312 verts.push(Vertex::new(p2, p3, quad.s1, quad.t0));
1313 verts.push(Vertex::new(p0, p1, quad.s0, quad.t0));
1314 verts.push(Vertex::new(p6, p7, quad.s0, quad.t1));
1315 verts.push(Vertex::new(p4, p5, quad.s1, quad.t1));
1316 }
1317 verts
1318 };
1319
1320 // Apply global alpha
1321 let mut paint_flavor = paint.flavor.clone();
1322 paint_flavor.mul_alpha(self.state().alpha);
1323
1324 for cmd in draw_commands.alpha_glyphs {
1325 let verts = create_vertices(&cmd.quads);
1326
1327 self.render_triangles(&verts, &transform, &paint_flavor, GlyphTexture::AlphaMask(cmd.image_id));
1328 }
1329
1330 for cmd in draw_commands.color_glyphs {
1331 let verts = create_vertices(&cmd.quads);
1332
1333 self.render_triangles(
1334 &verts,
1335 &transform,
1336 &paint_flavor,
1337 GlyphTexture::ColorTexture(cmd.image_id),
1338 );
1339 }
1340 }
1341
1342 // Private
1343
1344 fn draw_text(
1345 &mut self,
1346 x: f32,
1347 y: f32,
1348 text: &str,
1349 paint: &Paint,
1350 render_mode: RenderMode,
1351 ) -> Result<TextMetrics, ErrorKind> {
1352 let scale = self.font_scale() * self.device_px_ratio;
1353 let invscale = 1.0 / scale;
1354
1355 let mut stroke = paint.stroke.clone();
1356 stroke.line_width *= scale;
1357
1358 let mut text_settings = paint.text.clone();
1359 text_settings.font_size *= scale;
1360 text_settings.letter_spacing *= scale;
1361
1362 let mut layout = text::shape(
1363 x * scale,
1364 y * scale,
1365 &mut self.text_context.borrow_mut(),
1366 &text_settings,
1367 text,
1368 None,
1369 )?;
1370 //let layout = self.layout_text(x, y, text, &paint)?;
1371
1372 // TODO: Early out if text is outside the canvas bounds, or maybe even check for each character in layout.
1373
1374 let bitmap_glyphs = layout.has_bitmap_glyphs();
1375 let need_direct_rendering = text_settings.font_size > 92.0;
1376
1377 if need_direct_rendering && !bitmap_glyphs {
1378 text::render_direct(
1379 self,
1380 &layout,
1381 &paint.flavor,
1382 paint.shape_anti_alias,
1383 &stroke,
1384 text_settings.font_size,
1385 render_mode,
1386 invscale,
1387 )?;
1388 } else {
1389 let atlas = if bitmap_glyphs && need_direct_rendering {
1390 self.ephemeral_glyph_atlas.get_or_insert_with(Default::default).clone()
1391 } else {
1392 self.glyph_atlas.clone()
1393 };
1394
1395 let draw_commands =
1396 atlas.render_atlas(self, &layout, text_settings.font_size, stroke.line_width, render_mode)?;
1397 self.draw_glyph_commands(draw_commands, paint, scale);
1398 }
1399
1400 layout.scale(invscale);
1401
1402 Ok(layout)
1403 }
1404
1405 fn render_triangles(
1406 &mut self,
1407 verts: &[Vertex],
1408 transform: &Transform2D,
1409 paint_flavor: &PaintFlavor,
1410 glyph_texture: GlyphTexture,
1411 ) {
1412 let scissor = self.state().scissor;
1413
1414 let params = Params::new(
1415 &self.images,
1416 transform,
1417 paint_flavor,
1418 &glyph_texture,
1419 &scissor,
1420 1.0,
1421 1.0,
1422 -1.0,
1423 );
1424
1425 let mut cmd = Command::new(CommandType::Triangles { params });
1426 cmd.composite_operation = self.state().composite_operation;
1427 cmd.glyph_texture = glyph_texture;
1428
1429 if let &PaintFlavor::Image { id, .. } = paint_flavor {
1430 cmd.image = Some(id);
1431 } else if let Some(paint::GradientColors::MultiStop { stops }) = paint_flavor.gradient_colors() {
1432 cmd.image = self
1433 .gradients
1434 .lookup_or_add(stops, &mut self.images, &mut self.renderer)
1435 .ok();
1436 }
1437
1438 cmd.triangles_verts = Some((self.verts.len(), verts.len()));
1439 self.append_cmd(cmd);
1440
1441 self.verts.extend_from_slice(verts);
1442 }
1443
1444 fn font_scale(&self) -> f32 {
1445 let avg_scale = self.state().transform.average_scale();
1446
1447 geometry::quantize(avg_scale, 0.1).min(7.0)
1448 }
1449
1450 //
1451
1452 fn state(&self) -> &State {
1453 self.state_stack.last().unwrap()
1454 }
1455
1456 fn state_mut(&mut self) -> &mut State {
1457 self.state_stack.last_mut().unwrap()
1458 }
1459
1460 #[cfg(feature = "debug_inspector")]
1461 pub fn debug_inspector_get_font_textures(&self) -> Vec<ImageId> {
1462 self.glyph_atlas
1463 .glyph_textures
1464 .borrow()
1465 .iter()
1466 .map(|t| t.image_id)
1467 .collect()
1468 }
1469
1470 #[cfg(feature = "debug_inspector")]
1471 pub fn debug_inspector_draw_image(&mut self, id: ImageId) {
1472 if let Ok(size) = self.image_size(id) {
1473 let width = size.0 as f32;
1474 let height = size.1 as f32;
1475 let mut path = Path::new();
1476 path.rect(0f32, 0f32, width, height);
1477 self.fill_path(&path, &Paint::image(id, 0f32, 0f32, width, height, 0f32, 1f32));
1478 }
1479 }
1480}
1481
1482impl<T: Renderer> Drop for Canvas<T> {
1483 fn drop(&mut self) {
1484 self.images.clear(&mut self.renderer);
1485 }
1486}
1487
1488// re-exports
1489#[cfg(feature = "image-loading")]
1490pub use ::image as img;
1491
1492pub use imgref;
1493pub use rgb;
1494
1495#[cfg(test)]
1496#[derive(Default)]
1497pub struct RecordingRenderer {
1498 pub last_commands: Rc<RefCell<Vec<renderer::Command>>>,
1499}
1500
1501#[cfg(test)]
1502impl Renderer for RecordingRenderer {
1503 type Image = DummyImage;
1504 type NativeTexture = ();
1505
1506 fn set_size(&mut self, _width: u32, _height: u32, _dpi: f32) {}
1507
1508 fn render(
1509 &mut self,
1510 _images: &mut ImageStore<Self::Image>,
1511 _verts: &[renderer::Vertex],
1512 commands: Vec<renderer::Command>,
1513 ) {
1514 *self.last_commands.borrow_mut() = commands;
1515 }
1516
1517 fn alloc_image(&mut self, info: crate::ImageInfo) -> Result<Self::Image, ErrorKind> {
1518 Ok(Self::Image { info })
1519 }
1520
1521 fn create_image_from_native_texture(
1522 &mut self,
1523 _native_texture: Self::NativeTexture,
1524 _info: crate::ImageInfo,
1525 ) -> Result<Self::Image, ErrorKind> {
1526 Err(ErrorKind::UnsupportedImageFormat)
1527 }
1528
1529 fn update_image(
1530 &mut self,
1531 image: &mut Self::Image,
1532 data: crate::ImageSource,
1533 x: usize,
1534 y: usize,
1535 ) -> Result<(), ErrorKind> {
1536 let size = data.dimensions();
1537
1538 if x + size.width > image.info.width() {
1539 return Err(ErrorKind::ImageUpdateOutOfBounds);
1540 }
1541
1542 if y + size.height > image.info.height() {
1543 return Err(ErrorKind::ImageUpdateOutOfBounds);
1544 }
1545
1546 Ok(())
1547 }
1548
1549 fn delete_image(&mut self, _image: Self::Image, _image_id: crate::ImageId) {}
1550
1551 fn screenshot(&mut self) -> Result<imgref::ImgVec<rgb::RGBA8>, ErrorKind> {
1552 Ok(imgref::ImgVec::new(Vec::new(), 0, 0))
1553 }
1554}
1555
1556#[cfg(test)]
1557pub struct DummyImage {
1558 info: ImageInfo,
1559}
1560
1561#[test]
1562fn test_image_blit_fast_path() {
1563 use renderer::{Command, CommandType};
1564
1565 let renderer = RecordingRenderer::default();
1566 let recorded_commands = renderer.last_commands.clone();
1567 let mut canvas = Canvas::new(renderer).unwrap();
1568 canvas.set_size(100, 100, 1.);
1569 let mut path = Path::new();
1570 path.rect(10., 10., 50., 50.);
1571 let image = canvas
1572 .create_image_empty(30, 30, PixelFormat::Rgba8, ImageFlags::empty())
1573 .unwrap();
1574 let paint = Paint::image(image, 0., 0., 30., 30., 0., 0.).with_anti_alias(false);
1575 canvas.fill_path(&path, &paint);
1576 canvas.flush();
1577
1578 let commands = recorded_commands.borrow();
1579 let mut commands = commands.iter();
1580 assert!(matches!(
1581 commands.next(),
1582 Some(Command {
1583 cmd_type: CommandType::SetRenderTarget(..),
1584 ..
1585 })
1586 ));
1587 assert!(matches!(
1588 commands.next(),
1589 Some(Command {
1590 cmd_type: CommandType::Triangles {
1591 params: Params {
1592 shader_type: renderer::ShaderType::TextureCopyUnclipped,
1593 ..
1594 }
1595 },
1596 ..
1597 })
1598 ));
1599}
1600