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