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