Skip to main content

ferroui_vello/
platform_render_interface.rs

1use crate::combined_geometry_impl::CombinedGeometryImpl;
2use crate::ellipse_geometry_impl::EllipseGeometryImpl;
3use crate::geometry_group_impl::GeometryGroupImpl;
4use crate::geometry_impl::{FillPath, VelloPath};
5use crate::glyph_run_impl::GlyphRunImpl;
6use crate::helpers::pixel_format_helper;
7use crate::immutable_bitmap::ImmutableBitmap;
8use crate::line_geometry_impl::LineGeometryImpl;
9use crate::rectangle_geometry_impl::RectangleGeometryImpl;
10use crate::render_target_bitmap_impl::RenderTargetBitmapImpl;
11use crate::simple_geometry_impl::SimpleGeometryImpl;
12use crate::stream_geometry_builder::StreamGeometryBuilder;
13use crate::vello_backend_context::VelloContext;
14use crate::vello_options::{VelloOptions, VelloRenderingMode};
15use crate::vello_region_impl::VelloRegionImpl;
16use crate::vello_typeface::VelloTypeface;
17use crate::writeable_bitmap_impl::WriteableBitmapImpl;
18use ferroui_base::media::imaging::BitmapInterpolationMode;
19use ferroui_base::media::text_formatting::GlyphInfo;
20use ferroui_base::media::{FillRule, GeometryCombineMode, GlyphRun, GlyphTypeface};
21use ferroui_base::platform::{
22    AlphaFormat, IBitmapImpl, IGeometryImpl, IGlyphRunImpl, IPlatformGraphicsContext, IPlatformRenderInterface,
23    IPlatformRenderInterfaceContext, IPlatformRenderInterfaceRegion, IRenderTargetBitmapImpl, IStreamGeometryBuilder,
24    IWriteableBitmapImpl, PixelFormat,
25};
26use ferroui_base::{PixelSize, Point, Rect, Vector};
27use kurbo::BezPath;
28use std::fs::File;
29use std::io::{self, Read};
30use std::rc::Rc;
31use std::sync::Arc;
32
33/// Vello platform render interface.
34pub struct PlatformRenderInterface {
35    /// The rendering modes in the order they are tried.
36    rendering_modes: Vec<VelloRenderingMode>,
37}
38
39impl PlatformRenderInterface {
40    /// Creates the render interface of the given options.
41    pub fn new(options: VelloOptions) -> Self {
42        Self { rendering_modes: options.rendering_mode_order() }
43    }
44
45    /// The rendering modes scenes are drawn in, in the order they are tried.
46    pub fn rendering_modes(&self) -> &[VelloRenderingMode] {
47        &self.rendering_modes
48    }
49}
50
51impl Default for PlatformRenderInterface {
52    fn default() -> Self {
53        Self::new(VelloOptions::default())
54    }
55}
56
57impl IPlatformRenderInterface for PlatformRenderInterface {
58    fn create_ellipse_geometry(&self, rect: Rect) -> Arc<dyn IGeometryImpl> {
59        EllipseGeometryImpl::new(rect)
60    }
61
62    fn create_line_geometry(&self, p1: Point, p2: Point) -> Arc<dyn IGeometryImpl> {
63        LineGeometryImpl::new(p1, p2)
64    }
65
66    fn create_rectangle_geometry(&self, rect: Rect) -> Arc<dyn IGeometryImpl> {
67        RectangleGeometryImpl::new(rect)
68    }
69
70    fn create_stream_geometry_builder(&self, source: Option<&dyn IGeometryImpl>) -> Box<dyn IStreamGeometryBuilder> {
71        Box::new(StreamGeometryBuilder::new(source))
72    }
73
74    fn create_geometry_group(&self, fill_rule: FillRule, children: &[Arc<dyn IGeometryImpl>]) -> Arc<dyn IGeometryImpl> {
75        GeometryGroupImpl::new(fill_rule, children)
76    }
77
78    fn create_combined_geometry(
79        &self,
80        combine_mode: GeometryCombineMode,
81        g1: Arc<dyn IGeometryImpl>,
82        g2: Arc<dyn IGeometryImpl>,
83    ) -> Arc<dyn IGeometryImpl> {
84        CombinedGeometryImpl::force_create(combine_mode, &*g1, &*g2)
85    }
86
87    fn build_glyph_run_geometry(&self, glyph_run: &GlyphRun) -> Arc<dyn IGeometryImpl> {
88        let glyph_typeface = VelloTypeface::try_get(&**glyph_run.glyph_typeface().platform_typeface())
89            .unwrap_or_else(|| panic!("PlatformImpl can't be null."));
90
91        let font_rendering_em_size = glyph_run.font_rendering_em_size();
92
93        let mut path = BezPath::new();
94
95        let baseline_origin = glyph_run.baseline_origin();
96        let (mut current_x, current_y) = (baseline_origin.x, baseline_origin.y);
97
98        let glyph_infos = glyph_run.glyph_infos();
99
100        // As the Skia backend builds it: the outline of every glyph without
101        // hinting at its pen position on the baseline.
102        for glyph_info in glyph_infos.borrow().iter() {
103            if let Some(glyph_path) = glyph_typeface.face().glyph_path(glyph_info.glyph_index, font_rendering_em_size) {
104                if !glyph_path.elements().is_empty() {
105                    path.extend(kurbo::Affine::translate((current_x, current_y)) * glyph_path);
106                }
107            }
108
109            current_x += glyph_info.glyph_advance;
110        }
111
112        SimpleGeometryImpl::new(VelloPath::new(path, FillRule::NonZero), FillPath::SameAsStroke, None)
113    }
114
115    fn create_glyph_run(
116        &self,
117        glyph_typeface: &Rc<GlyphTypeface>,
118        font_rendering_em_size: f64,
119        glyph_infos: &[GlyphInfo],
120        baseline_origin: Point,
121    ) -> Arc<dyn IGlyphRunImpl> {
122        Arc::new(GlyphRunImpl::new(glyph_typeface, font_rendering_em_size, glyph_infos, baseline_origin))
123    }
124
125    fn create_render_target_bitmap(&self, size: PixelSize, dpi: Vector) -> Arc<dyn IRenderTargetBitmapImpl> {
126        if size.width < 1 {
127            panic!("Width can't be less than 1");
128        }
129
130        if size.height < 1 {
131            panic!("Height can't be less than 1");
132        }
133
134        Arc::new(RenderTargetBitmapImpl::new(size, dpi, self.rendering_modes.clone()))
135    }
136
137    fn create_writeable_bitmap(
138        &self,
139        size: PixelSize,
140        dpi: Vector,
141        format: PixelFormat,
142        alpha_format: AlphaFormat,
143    ) -> Arc<dyn IWriteableBitmapImpl> {
144        WriteableBitmapImpl::new(size, dpi, format, alpha_format)
145    }
146
147    fn load_bitmap_from_file(&self, file_name: &str) -> io::Result<Arc<ferroui_base::platform::SharedBitmapImpl>> {
148        let mut stream = File::open(file_name)?;
149        self.load_bitmap(&mut stream)
150    }
151
152    fn load_bitmap(&self, stream: &mut dyn Read) -> io::Result<Arc<ferroui_base::platform::SharedBitmapImpl>> {
153        Ok(Arc::new(ImmutableBitmap::from_stream(stream)?))
154    }
155
156    fn load_writeable_bitmap_to_width(
157        &self,
158        stream: &mut dyn Read,
159        width: i32,
160        interpolation_mode: BitmapInterpolationMode,
161    ) -> io::Result<Arc<dyn IWriteableBitmapImpl>> {
162        Ok(WriteableBitmapImpl::from_stream_to_size(stream, width, true, interpolation_mode)?)
163    }
164
165    fn load_writeable_bitmap_to_height(
166        &self,
167        stream: &mut dyn Read,
168        height: i32,
169        interpolation_mode: BitmapInterpolationMode,
170    ) -> io::Result<Arc<dyn IWriteableBitmapImpl>> {
171        Ok(WriteableBitmapImpl::from_stream_to_size(stream, height, false, interpolation_mode)?)
172    }
173
174    fn load_writeable_bitmap_from_file(&self, file_name: &str) -> io::Result<Arc<dyn IWriteableBitmapImpl>> {
175        let mut stream = File::open(file_name)?;
176        self.load_writeable_bitmap(&mut stream)
177    }
178
179    fn load_writeable_bitmap(&self, stream: &mut dyn Read) -> io::Result<Arc<dyn IWriteableBitmapImpl>> {
180        Ok(WriteableBitmapImpl::from_stream(stream)?)
181    }
182
183    fn load_bitmap_to_width(
184        &self,
185        stream: &mut dyn Read,
186        width: i32,
187        interpolation_mode: BitmapInterpolationMode,
188    ) -> io::Result<Arc<ferroui_base::platform::SharedBitmapImpl>> {
189        Ok(Arc::new(ImmutableBitmap::from_stream_to_size(stream, width, true, interpolation_mode)?))
190    }
191
192    fn load_bitmap_to_height(
193        &self,
194        stream: &mut dyn Read,
195        height: i32,
196        interpolation_mode: BitmapInterpolationMode,
197    ) -> io::Result<Arc<ferroui_base::platform::SharedBitmapImpl>> {
198        Ok(Arc::new(ImmutableBitmap::from_stream_to_size(stream, height, false, interpolation_mode)?))
199    }
200
201    fn resize_bitmap(
202        &self,
203        bitmap_impl: &dyn IBitmapImpl,
204        destination_size: PixelSize,
205        interpolation_mode: BitmapInterpolationMode,
206    ) -> Arc<ferroui_base::platform::SharedBitmapImpl> {
207        match bitmap_impl.as_any().downcast_ref::<ImmutableBitmap>() {
208            Some(ibmp) => Arc::new(
209                ImmutableBitmap::resized(ibmp, destination_size, interpolation_mode)
210                    .unwrap_or_else(|error| panic!("Unable to resize the bitmap: {error}")),
211            ),
212            None => panic!("Invalid source bitmap type."),
213        }
214    }
215
216    fn load_bitmap_from_pixels(
217        &self,
218        format: PixelFormat,
219        alpha_format: AlphaFormat,
220        data: &[u8],
221        size: PixelSize,
222        dpi: Vector,
223        stride: i32,
224    ) -> Arc<ferroui_base::platform::SharedBitmapImpl> {
225        Arc::new(
226            ImmutableBitmap::from_pixels(size, dpi, stride, format, alpha_format, data)
227                .unwrap_or_else(|error| panic!("{error}")),
228        )
229    }
230
231    fn create_backend_context(
232        &self,
233        graphics_api_context: Option<Rc<dyn IPlatformGraphicsContext>>,
234    ) -> Rc<dyn IPlatformRenderInterfaceContext> {
235        // The WebGL2 context of a canvas of a web page: the hybrid mode
236        // draws the canvas with the renderer of that context.
237        #[cfg(feature = "hybrid-webgl")]
238        if let Some(graphics_context) = &graphics_api_context {
239            match crate::web_gl::VelloWebGlGpu::try_new(graphics_context) {
240                Some(Ok(web_gl)) => return Rc::new(VelloContext::with_web_gl(web_gl, self.rendering_modes.clone())),
241                Some(Err(error)) => panic!("The Vello backend cannot draw with the WebGL2 context of the canvas: {error}."),
242                None => {}
243            }
244        }
245
246        match graphics_api_context {
247            None => Rc::new(VelloContext::new(self.rendering_modes.clone())),
248            // A scene is drawn into the surface of a window on a device
249            // made over the graphics context of the platform.
250            #[cfg(any(feature = "hybrid", feature = "gpu"))]
251            Some(graphics_context) => {
252                Rc::new(VelloContext::with_gpu(crate::gpu::create_gpu(&graphics_context), self.rendering_modes.clone()))
253            }
254            #[cfg(not(any(feature = "hybrid", feature = "gpu")))]
255            Some(_) => panic!(
256                "The Vello backend was built without a GPU mode for this graphics context (the features `hybrid` and \
257                 `gpu` of ferroui-vello draw on the device of a desktop platform, `hybrid-webgl` with the WebGL2 \
258                 context of a canvas): it draws into the framebuffer of a platform that renders in software"
259            ),
260        }
261    }
262
263    fn supports_individual_round_rects(&self) -> bool {
264        true
265    }
266
267    fn default_alpha_format(&self) -> AlphaFormat {
268        AlphaFormat::Premul
269    }
270
271    fn default_pixel_format(&self) -> PixelFormat {
272        // What the renderers of the Vello project draw.
273        PixelFormat::RGBA8888
274    }
275
276    fn is_supported_bitmap_pixel_format(&self, format: PixelFormat) -> bool {
277        pixel_format_helper::is_supported(format)
278    }
279
280    fn supports_regions(&self) -> bool {
281        true
282    }
283
284    fn create_region(&self) -> Rc<dyn IPlatformRenderInterfaceRegion> {
285        Rc::new(VelloRegionImpl::new())
286    }
287}