1#![deny(missing_docs)]
2#![warn(missing_debug_implementations)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5#[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#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
85#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86pub enum FillRule {
87 EvenOdd,
90 #[default]
94 NonZero,
95}
96
97#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
99pub enum BlendFactor {
100 Zero,
102 One,
104 SrcColor,
106 OneMinusSrcColor,
108 DstColor,
110 OneMinusDstColor,
112 SrcAlpha,
114 OneMinusSrcAlpha,
116 DstAlpha,
118 OneMinusDstAlpha,
120 SrcAlphaSaturate,
122}
123
124#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash)]
126pub enum CompositeOperation {
127 SourceOver,
129 SourceIn,
131 SourceOut,
133 Atop,
135 DestinationOver,
137 DestinationIn,
139 DestinationOut,
141 DestinationAtop,
143 Lighter,
145 Copy,
147 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 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 if b != 0.0 || c != 0.0 {
207 return None;
208 }
209
210 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#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default)]
228#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
229pub enum LineCap {
230 #[default]
232 Butt,
233 Round,
235 Square,
238}
239
240#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default)]
244#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
245pub enum LineJoin {
246 #[default]
250 Miter,
251 Round,
255 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#[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 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 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 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 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 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 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 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 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 pub fn screenshot(&mut self) -> Result<ImgVec<RGBA8>, ErrorKind> {
440 self.renderer.screenshot()
441 }
442
443 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 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 pub fn reset(&mut self) {
467 *self.state_mut() = State::default();
468 }
469
470 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 pub fn set_global_alpha(&mut self, alpha: f32) {
487 self.state_mut().alpha = alpha;
488 }
489
490 pub fn set_composite_operation(&mut self, op: CompositeOperation) {
492 self.state_mut().composite_operation = CompositeOperationState::new(op);
493 }
494
495 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 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 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 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 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 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 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 pub fn get_image(&self, id: ImageId) -> Option<&T::Image> {
580 self.images.get(id)
581 }
582
583 pub fn get_image_mut(&mut self, id: ImageId) -> Option<&mut T::Image> {
585 self.images.get_mut(id)
586 }
587
588 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 #[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 #[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 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 pub fn delete_image(&mut self, id: ImageId) {
638 self.images.remove(&mut self.renderer, id);
639 }
640
641 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 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 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 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 pub fn reset_transform(&mut self) {
710 self.state_mut().transform = Transform2D::identity();
711 }
712
713 pub fn set_transform(&mut self, transform: &Transform2D) {
715 self.state_mut().transform.premultiply(transform);
716 }
717
718 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 pub fn rotate(&mut self, angle: f32) {
727 let t = Transform2D::rotation(angle);
728 self.state_mut().transform.premultiply(&t);
729 }
730
731 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 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 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 pub fn transform(&self) -> Transform2D {
754 self.state().transform
755 }
756
757 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 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 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 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 pub fn reset_scissor(&mut self) {
816 self.state_mut().scissor = Scissor::default();
817 }
818
819 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 let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
828
829 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 pub fn path_bbox(&self, path: &Path) -> Bounds {
843 let transform = self.state().transform;
844
845 let path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
847
848 path_cache.bounds
849 }
850
851 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 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 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 paint_flavor.mul_alpha(self.state().alpha);
878
879 let scissor = self.state().scissor;
880
881 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 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 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 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 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 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 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 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 let mut path_cache = path.cache(&transform, self.tess_tol, self.dist_tol);
1033
1034 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 let mut line_width = (stroke.line_width * transform.average_scale()).max(0.0);
1052
1053 if line_width < self.fringe_width {
1054 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 paint_flavor.mul_alpha(self.state().alpha);
1064
1065 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 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 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 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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 #[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 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 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 #[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 #[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 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#[derive(Clone, Debug)]
1703pub struct PositionedGlyph {
1704 pub x: f32,
1706 pub y: f32,
1708 pub glyph_id: u16,
1711}
1712
1713#[cfg(feature = "image-loading")]
1715pub use ::image as img;
1716
1717pub use imgref;
1718pub use rgb;
1719
1720#[cfg(test)]
1722#[derive(Default, Debug)]
1723pub struct RecordingRenderer {
1724 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#[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}