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