Skip to main content

femto_g/
lib.rs

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