1#![forbid(unsafe_code, future_incompatible)]
40
41pub use piet;
43pub use tiny_skia;
44
45use cosmic_text::{Command as ZenoCommand, SwashCache};
46use piet::kurbo::{self, Affine, PathEl, Shape};
47use piet::Error as Pierror;
48
49use std::fmt;
50use std::mem;
51use std::slice;
52
53use tiny_skia as ts;
54use tinyvec::TinyVec;
55use ts::{Mask, PathBuilder, PixmapMut, Shader};
56
57pub trait AsPixmapMut {
62 fn as_pixmap_mut(&mut self) -> PixmapMut<'_>;
64}
65
66impl<T: AsPixmapMut + ?Sized> AsPixmapMut for &mut T {
67 fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
68 (**self).as_pixmap_mut()
69 }
70}
71
72impl<'a> AsPixmapMut for PixmapMut<'a> {
73 fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
74 let width = self.width();
76 let height = self.height();
77
78 PixmapMut::from_bytes(self.data_mut(), width, height)
79 .expect("PixmapMut::from_bytes should succeed")
80 }
81}
82
83impl AsPixmapMut for ts::Pixmap {
84 fn as_pixmap_mut(&mut self) -> PixmapMut<'_> {
85 self.as_mut()
86 }
87}
88
89pub struct Cache {
94 path_builder: Option<PathBuilder>,
96
97 text: Text,
99
100 glyph_cache: Option<SwashCache>,
102
103 dash_buffer: Vec<f32>,
105}
106
107impl fmt::Debug for Cache {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 f.pad("Cache { .. }")
110 }
111}
112
113impl Default for Cache {
114 fn default() -> Self {
115 Self {
116 path_builder: Some(PathBuilder::new()),
117 text: Text(piet_cosmic_text::Text::new()),
118 glyph_cache: Some(SwashCache::new()),
119 dash_buffer: vec![],
120 }
121 }
122}
123
124pub struct RenderContext<'cache, Target: ?Sized> {
126 cache: &'cache mut Cache,
128
129 last_error: Result<(), Pierror>,
131
132 states: TinyVec<[RenderState; 1]>,
134
135 tolerance: f64,
137
138 bitmap_scale: f64,
140
141 ignore_state: bool,
143
144 target: Target,
146}
147
148struct RenderState {
150 transform: Affine,
152
153 clip: Option<Mask>,
155}
156
157impl Default for RenderState {
158 fn default() -> Self {
159 Self {
160 transform: Affine::IDENTITY,
161 clip: None,
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
170pub struct Text(piet_cosmic_text::Text);
171
172impl Text {
173 #[inline]
175 pub fn dpi(&self) -> f64 {
176 self.0.dpi()
177 }
178
179 #[inline]
181 pub fn set_dpi(&mut self, dpi: f64) {
182 self.0.set_dpi(dpi);
183 }
184}
185
186#[derive(Debug)]
190pub struct TextLayoutBuilder(piet_cosmic_text::TextLayoutBuilder);
191
192#[derive(Debug, Clone)]
196pub struct TextLayout(piet_cosmic_text::TextLayout);
197
198#[derive(Clone)]
202pub struct Brush(BrushInner);
203
204#[derive(Clone)]
205enum BrushInner {
206 Solid(piet::Color),
208
209 LinearGradient(piet::FixedLinearGradient),
211
212 RadialGradient(piet::FixedRadialGradient),
214}
215
216#[derive(Clone)]
218pub struct Image(tiny_skia::Pixmap);
219
220impl Cache {
221 pub fn new() -> Self {
223 Self::default()
224 }
225
226 pub fn render_context<Target: AsPixmapMut>(
228 &mut self,
229 target: Target,
230 ) -> RenderContext<'_, Target> {
231 RenderContext {
232 cache: self,
233 target,
234 last_error: Ok(()),
235 states: tinyvec::tiny_vec![RenderState::default(); 1],
236 tolerance: 0.1,
237 bitmap_scale: 1.0,
238 ignore_state: false,
239 }
240 }
241}
242
243impl<T: AsPixmapMut + ?Sized> RenderContext<'_, T> {
244 pub fn bitmap_scale(&self) -> f64 {
246 self.bitmap_scale
247 }
248
249 pub fn set_bitmap_scale(&mut self, scale: f64) {
251 self.bitmap_scale = scale;
252 }
253
254 pub fn tolerance(&self) -> f64 {
256 self.tolerance
257 }
258
259 pub fn set_tolerance(&mut self, tolerance: f64) {
261 self.tolerance = tolerance;
262 }
263
264 pub fn target(&self) -> &T {
266 &self.target
267 }
268
269 pub fn target_mut(&mut self) -> &mut T {
271 &mut self.target
272 }
273
274 pub fn into_target(self) -> T
276 where
277 T: Sized,
278 {
279 self.target
280 }
281
282 fn fill_impl(
283 &mut self,
284 shape: impl Shape,
285 shader: tiny_skia::Shader<'_>,
286 rule: tiny_skia::FillRule,
287 ) {
288 let Self {
290 cache,
291 target,
292 states,
293 tolerance,
294 ..
295 } = self;
296
297 let mut builder = cache.path_builder.take().unwrap_or_default();
299
300 cvt_shape_to_skia_path(&mut builder, shape, *tolerance);
302 let path = match builder.finish() {
303 Some(path) => path,
304 None => return,
305 };
306
307 let paint = tiny_skia::Paint {
309 shader,
310 ..Default::default()
311 };
312 let state = states.last().unwrap();
313
314 let (transform, mask) = if self.ignore_state {
315 (
316 ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32),
317 None,
318 )
319 } else {
320 let real_transform = Affine::scale(self.bitmap_scale) * state.transform;
321 (cvt_affine(real_transform), state.clip.as_ref())
322 };
323
324 target
325 .as_pixmap_mut()
326 .fill_path(&path, &paint, rule, transform, mask);
327
328 self.cache.path_builder = Some(path.clear());
330 }
331
332 fn stroke_impl(
333 &mut self,
334 shape: impl Shape,
335 shader: tiny_skia::Shader<'_>,
336 stroke: &ts::Stroke,
337 ) {
338 let Self {
340 cache,
341 target,
342 states,
343 tolerance,
344 ..
345 } = self;
346
347 let mut builder = cache.path_builder.take().unwrap_or_default();
349
350 cvt_shape_to_skia_path(&mut builder, shape, *tolerance);
352 let path = match builder.finish() {
353 Some(path) => path,
354 None => return,
355 };
356
357 let paint = tiny_skia::Paint {
359 shader,
360 ..Default::default()
361 };
362 let state = states.last().unwrap();
363
364 let (transform, mask) = if self.ignore_state {
365 (
366 ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32),
367 None,
368 )
369 } else {
370 let real_transform = Affine::scale(self.bitmap_scale) * state.transform;
371 (cvt_affine(real_transform), state.clip.as_ref())
372 };
373
374 target
375 .as_pixmap_mut()
376 .stroke_path(&path, &paint, stroke, transform, mask);
377
378 self.cache.path_builder = Some(path.clear());
380 }
381
382 #[allow(clippy::if_same_then_else)]
383 fn draw_glyph(&mut self, pos: kurbo::Point, glyph: &cosmic_text::LayoutGlyph, run_y: f32) {
384 let mut glyph_cache = self
386 .cache
387 .glyph_cache
388 .take()
389 .unwrap_or_else(SwashCache::new);
390
391 let physical = glyph.physical((0., 0.), 1.0);
392 self.cache.text.clone().0.with_font_system_mut(|system| {
393 if let Some(outline) = glyph_cache.get_outline_commands(system, physical.cache_key) {
395 let offset = kurbo::Affine::translate((
409 pos.x + physical.x as f64 + physical.cache_key.x_bin.as_float() as f64,
410 pos.y
411 + run_y as f64
412 + physical.y as f64
413 + physical.cache_key.y_bin.as_float() as f64,
414 )) * Affine::scale_non_uniform(1.0, -1.0);
415 let color = glyph.color_opt.map_or(
416 {
417 let (r, g, b, a) = piet::util::DEFAULT_TEXT_COLOR.as_rgba();
418 ts::Color::from_rgba(r as f32, g as f32, b as f32, a as f32)
419 .expect("default text color should be valid")
420 },
421 |c| {
422 let [r, g, b, a] = [c.r(), c.g(), c.b(), c.a()];
423 ts::Color::from_rgba8(r, g, b, a)
424 },
425 );
426
427 self.fill_impl(
429 ZenoShape {
430 cmds: outline,
431 offset,
432 },
433 ts::Shader::SolidColor(color),
434 ts::FillRule::EvenOdd,
435 );
436 } else {
437 let default_color = {
439 let (r, g, b, a) = piet::util::DEFAULT_TEXT_COLOR.as_rgba8();
440 cosmic_text::Color::rgba(r, g, b, a)
441 };
442 glyph_cache.with_pixels(system, physical.cache_key, default_color, |x, y, clr| {
443 let [r, g, b, a] = [clr.r(), clr.g(), clr.b(), clr.a()];
444 let color = ts::Color::from_rgba8(r, g, b, a);
445
446 self.fill_impl(
448 kurbo::Rect::from_origin_size((x as f64, y as f64), (1., 1.)),
449 Shader::SolidColor(color),
450 ts::FillRule::EvenOdd,
451 );
452 });
453 }
454 });
455
456 self.cache.glyph_cache = Some(glyph_cache);
457 }
458}
459
460macro_rules! leap {
461 ($this:expr,$e:expr,$msg:literal) => {{
462 match ($e) {
463 Some(v) => v,
464 None => {
465 $this.last_error = Err(Pierror::BackendError($msg.into()));
466 return;
467 }
468 }
469 }};
470}
471
472impl<T: AsPixmapMut + ?Sized> piet::RenderContext for RenderContext<'_, T> {
473 type Brush = Brush;
474 type Image = Image;
475 type Text = Text;
476 type TextLayout = TextLayout;
477
478 fn status(&mut self) -> Result<(), Pierror> {
479 mem::replace(&mut self.last_error, Ok(()))
480 }
481
482 fn solid_brush(&mut self, color: piet::Color) -> Self::Brush {
483 Brush(BrushInner::Solid(color))
484 }
485
486 fn gradient(
487 &mut self,
488 gradient: impl Into<piet::FixedGradient>,
489 ) -> Result<Self::Brush, Pierror> {
490 Ok(Brush(match gradient.into() {
491 piet::FixedGradient::Linear(lin) => BrushInner::LinearGradient(lin),
492 piet::FixedGradient::Radial(rad) => BrushInner::RadialGradient(rad),
493 }))
494 }
495
496 fn clear(&mut self, region: impl Into<Option<kurbo::Rect>>, mut color: piet::Color) {
497 let region = region.into();
498
499 let clamp = |x: f64| {
501 if x < 0.0 {
502 0.0
503 } else if x > 1.0 {
504 1.0
505 } else {
506 x
507 }
508 };
509 let (r, g, b, a) = color.as_rgba();
510 let r = clamp(r * a);
511 let g = clamp(g * a);
512 let b = clamp(b * a);
513 color = piet::Color::rgba(r, g, b, 1.0);
514
515 if let Some(region) = region {
516 self.ignore_state = true;
518 self.fill_impl(
519 region,
520 Shader::SolidColor(cvt_color(color)),
521 tiny_skia::FillRule::Winding,
522 );
523 self.ignore_state = false;
525 } else {
526 self.target.as_pixmap_mut().fill(cvt_color(color));
528 }
529 }
530
531 fn stroke(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>, width: f64) {
532 self.stroke_styled(shape, brush, width, &piet::StrokeStyle::default())
533 }
534
535 fn stroke_styled(
536 &mut self,
537 shape: impl kurbo::Shape,
538 brush: &impl piet::IntoBrush<Self>,
539 width: f64,
540 style: &piet::StrokeStyle,
541 ) {
542 let mut stroke = ts::Stroke {
543 width: width as f32,
544 line_cap: match style.line_cap {
545 piet::LineCap::Butt => ts::LineCap::Butt,
546 piet::LineCap::Round => ts::LineCap::Round,
547 piet::LineCap::Square => ts::LineCap::Square,
548 },
549 dash: if style.dash_pattern.is_empty() {
550 None
551 } else {
552 let dashes = {
553 let mut dashes = mem::take(&mut self.cache.dash_buffer);
554 dashes.clear();
555 dashes.extend(style.dash_pattern.iter().map(|&x| x as f32));
556
557 if dashes.len() % 2 != 0 {
559 dashes.extend_from_within(0..dashes.len() - 1);
560 }
561
562 dashes
563 };
564
565 ts::StrokeDash::new(dashes, style.dash_offset as f32)
566 },
567 ..Default::default()
568 };
569
570 match style.line_join {
571 piet::LineJoin::Bevel => stroke.line_join = ts::LineJoin::Bevel,
572 piet::LineJoin::Round => stroke.line_join = ts::LineJoin::Round,
573 piet::LineJoin::Miter { limit } => {
574 stroke.line_join = ts::LineJoin::Miter;
575 stroke.miter_limit = limit as f32;
576 }
577 }
578
579 let shader = leap!(
580 self,
581 brush.make_brush(self, || shape.bounding_box()).to_shader(),
582 "Failed to create shader"
583 );
584
585 self.stroke_impl(shape, shader, &stroke);
586
587 }
589
590 fn fill(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>) {
591 let shader = leap!(
592 self,
593 brush.make_brush(self, || shape.bounding_box()).to_shader(),
594 "Failed to create shader"
595 );
596 self.fill_impl(&shape, shader, tiny_skia::FillRule::Winding)
597 }
598
599 fn fill_even_odd(&mut self, shape: impl kurbo::Shape, brush: &impl piet::IntoBrush<Self>) {
600 let shader = leap!(
601 self,
602 brush.make_brush(self, || shape.bounding_box()).to_shader(),
603 "Failed to create shader"
604 );
605 self.fill_impl(&shape, shader, tiny_skia::FillRule::EvenOdd)
606 }
607
608 fn clip(&mut self, shape: impl kurbo::Shape) {
609 let current_state = self.states.last_mut().unwrap();
610 let bitmap_scale =
611 ts::Transform::from_scale(self.bitmap_scale as f32, self.bitmap_scale as f32);
612 let path = {
613 let mut builder = self.cache.path_builder.take().unwrap_or_default();
614 cvt_shape_to_skia_path(&mut builder, shape, self.tolerance);
615 match builder.finish() {
616 Some(path) => path,
617 None => return,
618 }
619 };
620
621 match &mut current_state.clip {
622 slot @ None => {
623 let target = self.target.as_pixmap_mut();
625 let mut clip = Mask::new(target.width(), target.height())
626 .expect("Pixmap width/height should be valid clipmask width/height");
627 clip.fill_path(&path, tiny_skia::FillRule::EvenOdd, false, bitmap_scale);
628 *slot = Some(clip);
629 }
630
631 Some(mask) => {
632 mask.intersect_path(&path, tiny_skia::FillRule::EvenOdd, false, bitmap_scale);
633 }
634 }
635 }
636
637 fn text(&mut self) -> &mut Self::Text {
638 &mut self.cache.text
639 }
640
641 fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<kurbo::Point>) {
642 let pos = pos.into();
643 let mut line_processor = piet_cosmic_text::LineProcessor::new();
644
645 for run in layout.0.layout_runs() {
646 for glyph in run.glyphs {
647 let color = glyph.color_opt.unwrap_or({
649 let piet_color = piet::util::DEFAULT_TEXT_COLOR;
650 let (r, g, b, a) = piet_color.as_rgba8();
651 cosmic_text::Color::rgba(r, g, b, a)
652 });
653 line_processor.handle_glyph(glyph, run.line_y, color);
654
655 self.draw_glyph(pos, glyph, run.line_y);
656 }
657 }
658
659 let mov = pos.to_vec2();
661 for line in line_processor.lines() {
662 self.fill_impl(
663 line.into_rect() + mov,
664 Shader::SolidColor(cvt_color(line.color)),
665 ts::FillRule::EvenOdd,
666 );
667 }
668 }
669
670 fn save(&mut self) -> Result<(), Pierror> {
671 let current_state = self.states.last().unwrap();
672 self.states.push(RenderState {
673 transform: current_state.transform,
674 clip: current_state.clip.clone(),
675 });
676 Ok(())
677 }
678
679 fn restore(&mut self) -> Result<(), Pierror> {
680 if self.states.len() == 1 {
681 return Err(Pierror::StackUnbalance);
682 }
683
684 self.states.pop();
685 Ok(())
686 }
687
688 fn finish(&mut self) -> Result<(), Pierror> {
689 Ok(())
691 }
692
693 fn transform(&mut self, transform: Affine) {
694 self.states.last_mut().unwrap().transform *= transform;
695 }
696
697 fn make_image(
698 &mut self,
699 width: usize,
700 height: usize,
701 buf: &[u8],
702 format: piet::ImageFormat,
703 ) -> Result<Self::Image, Pierror> {
704 let data_buffer = match format {
705 piet::ImageFormat::RgbaPremul => buf.to_vec(),
706 piet::ImageFormat::RgbaSeparate => buf
707 .chunks_exact(4)
708 .flat_map(|chunk| {
709 let [r, g, b, a]: &[u8; 4] = chunk.try_into().unwrap();
710 let color = tiny_skia::ColorU8::from_rgba(*r, *g, *b, *a);
711 let premul = color.premultiply();
712 [premul.red(), premul.green(), premul.blue(), premul.alpha()]
713 })
714 .collect(),
715 piet::ImageFormat::Rgb => buf
716 .chunks_exact(3)
717 .flat_map(|chunk| {
718 let [r, g, b]: &[u8; 3] = chunk.try_into().unwrap();
719 [*r, *g, *b, 0xFF]
720 })
721 .collect(),
722 piet::ImageFormat::Grayscale => buf.iter().flat_map(|&v| [v, v, v, 0xFF]).collect(),
723 _ => return Err(Pierror::NotSupported),
724 };
725
726 let size = tiny_skia::IntSize::from_wh(
728 width.try_into().map_err(|_| Pierror::InvalidInput)?,
729 height.try_into().map_err(|_| Pierror::InvalidInput)?,
730 )
731 .ok_or_else(|| Pierror::InvalidInput)?;
732 let pixmap =
733 tiny_skia::Pixmap::from_vec(data_buffer, size).ok_or_else(|| Pierror::InvalidInput)?;
734
735 Ok(Image(pixmap))
736 }
737
738 fn draw_image(
739 &mut self,
740 image: &Self::Image,
741 dst_rect: impl Into<kurbo::Rect>,
742 interp: piet::InterpolationMode,
743 ) {
744 let bounds = kurbo::Rect::new(0.0, 0.0, image.0.width().into(), image.0.height().into());
745 self.draw_image_area(image, bounds, dst_rect, interp);
746 }
747
748 fn draw_image_area(
749 &mut self,
750 image: &Self::Image,
751 src_rect: impl Into<kurbo::Rect>,
752 dst_rect: impl Into<kurbo::Rect>,
753 interp: piet::InterpolationMode,
754 ) {
755 let src_rect = src_rect.into();
757 let dst_rect = dst_rect.into();
758 let scale_x = dst_rect.width() / src_rect.width();
759 let scale_y = dst_rect.height() / src_rect.height();
760
761 let transform = Affine::translate(-src_rect.origin().to_vec2())
762 * Affine::translate(dst_rect.origin().to_vec2())
763 * Affine::scale_non_uniform(scale_x, scale_y);
764
765 self.fill_impl(
766 dst_rect,
767 tiny_skia::Pattern::new(
768 image.0.as_ref(),
769 tiny_skia::SpreadMode::Repeat,
770 cvt_filter(interp),
771 1.0,
772 cvt_affine(transform),
773 ),
774 tiny_skia::FillRule::Winding,
775 )
776 }
777
778 fn capture_image_area(
779 &mut self,
780 src_rect: impl Into<kurbo::Rect>,
781 ) -> Result<Self::Image, Pierror> {
782 let src_rect = {
784 let src_rect = src_rect.into();
785
786 match ts::IntRect::from_xywh(
787 (src_rect.x0 * self.bitmap_scale) as i32,
788 (src_rect.y0 * self.bitmap_scale) as i32,
789 (src_rect.width() * self.bitmap_scale) as u32,
790 (src_rect.height() * self.bitmap_scale) as u32,
791 ) {
792 Some(src_rect) => src_rect,
793 None => return Err(Pierror::InvalidInput),
794 }
795 };
796
797 self.target
798 .as_pixmap_mut()
799 .as_ref()
800 .clone_rect(src_rect)
801 .ok_or_else(|| Pierror::InvalidInput)
802 .map(Image)
803 }
804
805 fn blurred_rect(
806 &mut self,
807 input_rect: kurbo::Rect,
808 blur_radius: f64,
809 brush: &impl piet::IntoBrush<Self>,
810 ) {
811 let size = piet::util::size_for_blurred_rect(input_rect, blur_radius);
812 let width = size.width as u32;
813 let height = size.height as u32;
814 if width == 0 || height == 0 {
815 return;
816 }
817
818 let (mask, rect_exp) = {
820 let mut mask = Mask::new(width, height).unwrap();
821
822 let rect_exp = piet::util::compute_blurred_rect(
823 input_rect,
824 blur_radius,
825 width.try_into().unwrap(),
826 mask.data_mut(),
827 );
828
829 (mask, rect_exp)
830 };
831
832 let mut image = Image(
834 ts::Pixmap::new(width, height)
835 .expect("Pixmap width/height should be valid clipmask width/height"),
836 );
837 let shader = leap!(
838 self,
839 brush.make_brush(self, || input_rect).to_shader(),
840 "Failed to create shader"
841 );
842 image.0.fill(ts::Color::TRANSPARENT);
843 image.0.fill_rect(
844 ts::Rect::from_xywh(0., 0., width as f32, height as f32).unwrap(),
845 &ts::Paint {
846 shader,
847 ..Default::default()
848 },
849 ts::Transform::identity(),
850 Some(&mask),
851 );
852
853 self.draw_image(&image, rect_exp, piet::InterpolationMode::Bilinear);
855 }
856
857 fn current_transform(&self) -> Affine {
858 self.states.last().unwrap().transform
859 }
860}
861
862impl Brush {
863 fn to_shader(&self) -> Option<tiny_skia::Shader<'static>> {
864 match &self.0 {
865 BrushInner::Solid(color) => Some(Shader::SolidColor(cvt_color(*color))),
866 BrushInner::LinearGradient(linear) => tiny_skia::LinearGradient::new(
867 cvt_point(linear.start),
868 cvt_point(linear.end),
869 linear
870 .stops
871 .iter()
872 .map(|s| cvt_gradient_stop(s.clone()))
873 .collect(),
874 tiny_skia::SpreadMode::Pad,
875 tiny_skia::Transform::identity(),
876 ),
877 BrushInner::RadialGradient(radial) => tiny_skia::RadialGradient::new(
878 cvt_point(radial.center + radial.origin_offset),
879 cvt_point(radial.center),
880 radial.radius as f32,
881 radial
882 .stops
883 .iter()
884 .map(|s| cvt_gradient_stop(s.clone()))
885 .collect(),
886 tiny_skia::SpreadMode::Pad,
887 tiny_skia::Transform::identity(),
888 ),
889 }
890 }
891}
892
893impl<T: AsPixmapMut + ?Sized> piet::IntoBrush<RenderContext<'_, T>> for Brush {
894 fn make_brush<'b>(
895 &'b self,
896 _piet: &mut RenderContext<'_, T>,
897 _bbox: impl FnOnce() -> kurbo::Rect,
898 ) -> std::borrow::Cow<'b, Brush> {
899 std::borrow::Cow::Borrowed(self)
900 }
901}
902
903impl piet::Image for Image {
904 fn size(&self) -> kurbo::Size {
905 kurbo::Size::new(self.0.width() as f64, self.0.height() as f64)
906 }
907}
908
909impl piet::Text for Text {
910 type TextLayout = TextLayout;
911 type TextLayoutBuilder = TextLayoutBuilder;
912
913 fn font_family(&mut self, family_name: &str) -> Option<piet::FontFamily> {
914 self.0.font_family(family_name)
915 }
916
917 fn load_font(&mut self, data: &[u8]) -> Result<piet::FontFamily, Pierror> {
918 self.0.load_font(data)
919 }
920
921 fn new_text_layout(&mut self, text: impl piet::TextStorage) -> Self::TextLayoutBuilder {
922 TextLayoutBuilder(self.0.new_text_layout(text))
923 }
924}
925
926impl piet::TextLayoutBuilder for TextLayoutBuilder {
927 type Out = TextLayout;
928
929 fn max_width(self, width: f64) -> Self {
930 Self(self.0.max_width(width))
931 }
932
933 fn alignment(self, alignment: piet::TextAlignment) -> Self {
934 Self(self.0.alignment(alignment))
935 }
936
937 fn default_attribute(self, attribute: impl Into<piet::TextAttribute>) -> Self {
938 Self(self.0.default_attribute(attribute))
939 }
940
941 fn range_attribute(
942 self,
943 range: impl std::ops::RangeBounds<usize>,
944 attribute: impl Into<piet::TextAttribute>,
945 ) -> Self {
946 Self(self.0.range_attribute(range, attribute))
947 }
948
949 fn build(self) -> Result<Self::Out, Pierror> {
950 Ok(TextLayout(self.0.build()?))
951 }
952}
953
954impl piet::TextLayout for TextLayout {
955 fn size(&self) -> kurbo::Size {
956 self.0.size()
957 }
958
959 fn trailing_whitespace_width(&self) -> f64 {
960 self.0.trailing_whitespace_width()
961 }
962
963 fn image_bounds(&self) -> kurbo::Rect {
964 self.0.image_bounds()
965 }
966
967 fn text(&self) -> &str {
968 self.0.text()
969 }
970
971 fn line_text(&self, line_number: usize) -> Option<&str> {
972 self.0.line_text(line_number)
973 }
974
975 fn line_metric(&self, line_number: usize) -> Option<piet::LineMetric> {
976 self.0.line_metric(line_number)
977 }
978
979 fn line_count(&self) -> usize {
980 self.0.line_count()
981 }
982
983 fn hit_test_point(&self, point: kurbo::Point) -> piet::HitTestPoint {
984 self.0.hit_test_point(point)
985 }
986
987 fn hit_test_text_position(&self, idx: usize) -> piet::HitTestPosition {
988 self.0.hit_test_text_position(idx)
989 }
990}
991
992struct ZenoShape<'a> {
993 cmds: &'a [ZenoCommand],
994 offset: kurbo::Affine,
995}
996
997impl Shape for ZenoShape<'_> {
998 type PathElementsIter<'iter> = ZenoIter<'iter> where Self: 'iter;
999
1000 fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
1001 ZenoIter {
1002 inner: self.cmds.iter(),
1003 offset: self.offset,
1004 }
1005 }
1006
1007 fn area(&self) -> f64 {
1008 self.to_path(1.0).area()
1009 }
1010
1011 fn perimeter(&self, accuracy: f64) -> f64 {
1012 self.to_path(accuracy).perimeter(accuracy)
1013 }
1014
1015 fn winding(&self, pt: kurbo::Point) -> i32 {
1016 self.to_path(1.0).winding(pt)
1017 }
1018
1019 fn bounding_box(&self) -> kurbo::Rect {
1020 self.to_path(1.0).bounding_box()
1021 }
1022}
1023
1024#[derive(Clone)]
1025struct ZenoIter<'a> {
1026 inner: slice::Iter<'a, ZenoCommand>,
1027 offset: kurbo::Affine,
1028}
1029
1030impl ZenoIter<'_> {
1031 fn leap(&self) -> impl Fn(&ZenoCommand) -> kurbo::PathEl {
1032 let offset = self.offset;
1033 move |&cmd| offset * cvt_zeno_command(cmd)
1034 }
1035}
1036
1037impl Iterator for ZenoIter<'_> {
1038 type Item = kurbo::PathEl;
1039
1040 fn next(&mut self) -> Option<Self::Item> {
1041 self.inner.next().map(self.leap())
1042 }
1043
1044 fn size_hint(&self) -> (usize, Option<usize>) {
1045 self.inner.size_hint()
1046 }
1047
1048 fn nth(&mut self, n: usize) -> Option<Self::Item> {
1049 self.inner.nth(n).map(self.leap())
1050 }
1051
1052 fn fold<B, F>(self, init: B, f: F) -> B
1053 where
1054 Self: Sized,
1055 F: FnMut(B, Self::Item) -> B,
1056 {
1057 let m = self.leap();
1058 self.inner.map(m).fold(init, f)
1059 }
1060}
1061
1062fn cvt_zeno_command(cmd: ZenoCommand) -> kurbo::PathEl {
1063 let cvt_vector = |v: zeno::Vector| {
1064 let [x, y]: [f32; 2] = v.into();
1065 kurbo::Point::new(x as f64, y as f64)
1066 };
1067
1068 match cmd {
1069 ZenoCommand::Close => kurbo::PathEl::ClosePath,
1070 ZenoCommand::MoveTo(p) => kurbo::PathEl::MoveTo(cvt_vector(p)),
1071 ZenoCommand::LineTo(p) => kurbo::PathEl::LineTo(cvt_vector(p)),
1072 ZenoCommand::QuadTo(p1, p2) => kurbo::PathEl::QuadTo(cvt_vector(p1), cvt_vector(p2)),
1073 ZenoCommand::CurveTo(p1, p2, p3) => {
1074 kurbo::PathEl::CurveTo(cvt_vector(p1), cvt_vector(p2), cvt_vector(p3))
1075 }
1076 }
1077}
1078
1079fn cvt_shape_to_skia_path(builder: &mut PathBuilder, shape: impl Shape, tolerance: f64) {
1080 shape.path_elements(tolerance).for_each(|el| match el {
1081 PathEl::ClosePath => builder.close(),
1082 PathEl::MoveTo(p) => builder.move_to(p.x as f32, p.y as f32),
1083 PathEl::LineTo(p) => builder.line_to(p.x as f32, p.y as f32),
1084 PathEl::QuadTo(p1, p2) => {
1085 builder.quad_to(p1.x as f32, p1.y as f32, p2.x as f32, p2.y as f32)
1086 }
1087 PathEl::CurveTo(p1, p2, p3) => builder.cubic_to(
1088 p1.x as f32,
1089 p1.y as f32,
1090 p2.x as f32,
1091 p2.y as f32,
1092 p3.x as f32,
1093 p3.y as f32,
1094 ),
1095 })
1096}
1097
1098fn cvt_affine(p: kurbo::Affine) -> tiny_skia::Transform {
1099 let [a, b, c, d, e, f] = p.as_coeffs();
1100 tiny_skia::Transform::from_row(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32)
1101}
1102
1103fn cvt_gradient_stop(stop: piet::GradientStop) -> tiny_skia::GradientStop {
1104 tiny_skia::GradientStop::new(stop.pos, cvt_color(stop.color))
1105}
1106
1107fn cvt_color(p: piet::Color) -> tiny_skia::Color {
1108 let (r, g, b, a) = p.as_rgba();
1109 tiny_skia::Color::from_rgba(r as f32, g as f32, b as f32, a as f32).expect("Color out of range")
1110}
1111
1112fn cvt_point(p: kurbo::Point) -> tiny_skia::Point {
1113 tiny_skia::Point {
1114 x: p.x as f32,
1115 y: p.y as f32,
1116 }
1117}
1118
1119fn cvt_filter(p: piet::InterpolationMode) -> tiny_skia::FilterQuality {
1120 match p {
1121 piet::InterpolationMode::NearestNeighbor => tiny_skia::FilterQuality::Nearest,
1122 piet::InterpolationMode::Bilinear => tiny_skia::FilterQuality::Bilinear,
1123 }
1124}