Skip to main content

floem_vger_renderer/
lib.rs

1use std::cell::RefCell;
2use std::mem;
3use std::sync::Arc;
4use std::sync::mpsc::sync_channel;
5
6use anyhow::Result;
7use floem_renderer::gpu_resources::GpuResources;
8use floem_renderer::text::{Glyph, GlyphRunProps};
9use floem_renderer::{Img, Renderer, tiny_skia};
10use floem_vger_rs::{GlyphImage, Image, PaintIndex, PixelFormat, Vger};
11use peniko::kurbo::{Size, Stroke};
12use peniko::{Blob, ImageData, LinearGradientPosition};
13use peniko::{
14    BrushRef, Color, GradientKind,
15    kurbo::{Affine, Point, Rect, Shape},
16};
17use swash::FontRef;
18use swash::scale::{Render, ScaleContext, Source, StrikeWith};
19use swash::zeno::Format;
20use wgpu::{
21    Adapter, Device, DeviceType, Queue, StoreOp, Surface, SurfaceConfiguration, TextureFormat,
22};
23
24thread_local! {
25    /// Swash [`ScaleContext`] used for CPU glyph rasterization on vger cache misses.
26    /// Thread-local so the `FnOnce` closure passed to `Vger::render_glyph` can
27    /// borrow it without conflicting with the `&mut self` borrow on [`VgerRenderer`].
28    static SCALE_CONTEXT: RefCell<ScaleContext> = RefCell::new(ScaleContext::new());
29}
30
31pub struct VgerRenderer {
32    device: Arc<Device>,
33    #[allow(unused)]
34    queue: Arc<Queue>,
35    surface: Surface<'static>,
36    vger: Vger,
37    alt_vger: Option<Vger>,
38    config: SurfaceConfiguration,
39    scale: f64,
40    transform: Affine,
41    clip: Option<Rect>,
42    capture: bool,
43    font_embolden: f32,
44    adapter: Adapter,
45}
46
47impl VgerRenderer {
48    pub fn new(
49        gpu_resources: GpuResources,
50        surface: wgpu::Surface<'static>,
51        width: u32,
52        height: u32,
53        scale: f64,
54        font_embolden: f32,
55    ) -> Result<Self> {
56        let GpuResources {
57            adapter,
58            device,
59            queue,
60            ..
61        } = gpu_resources;
62
63        if adapter.get_info().device_type == DeviceType::Cpu {
64            return Err(anyhow::anyhow!("only cpu adapter found"));
65        }
66
67        let mut required_downlevel_flags = wgpu::DownlevelFlags::empty();
68        required_downlevel_flags.set(wgpu::DownlevelFlags::VERTEX_STORAGE, true);
69
70        if !adapter
71            .get_downlevel_capabilities()
72            .flags
73            .contains(required_downlevel_flags)
74        {
75            return Err(anyhow::anyhow!(
76                "adapter doesn't support required downlevel flags"
77            ));
78        }
79
80        let device = Arc::new(device);
81        let queue = Arc::new(queue);
82
83        let surface_caps = surface.get_capabilities(&adapter);
84        let texture_format = surface_caps
85            .formats
86            .into_iter()
87            .find(|it| matches!(it, TextureFormat::Rgba8Unorm | TextureFormat::Bgra8Unorm))
88            .ok_or_else(|| anyhow::anyhow!("surface should support Rgba8Unorm or Bgra8Unorm"))?;
89
90        let latency = match adapter.get_info().backend {
91            wgpu::Backend::Vulkan => 2,
92            _ => 1,
93        };
94
95        let config = wgpu::SurfaceConfiguration {
96            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
97            format: texture_format,
98            width,
99            height,
100            present_mode: wgpu::PresentMode::AutoVsync,
101            alpha_mode: wgpu::CompositeAlphaMode::Auto,
102            view_formats: vec![],
103            desired_maximum_frame_latency: latency,
104        };
105        surface.configure(&device, &config);
106
107        let vger = floem_vger_rs::Vger::new(device.clone(), queue.clone(), texture_format);
108
109        Ok(Self {
110            device,
111            queue,
112            surface,
113            vger,
114            alt_vger: None,
115            scale,
116            config,
117            transform: Affine::IDENTITY,
118            clip: None,
119            capture: false,
120            font_embolden,
121            adapter,
122        })
123    }
124
125    pub fn resize(&mut self, width: u32, height: u32, scale: f64) {
126        if width != self.config.width || height != self.config.height {
127            self.config.width = width;
128            self.config.height = height;
129            self.surface.configure(&self.device, &self.config);
130        }
131        self.scale = scale;
132    }
133
134    pub fn set_scale(&mut self, scale: f64) {
135        self.scale = scale;
136    }
137
138    pub fn size(&self) -> Size {
139        Size::new(self.config.width as f64, self.config.height as f64)
140    }
141}
142
143impl VgerRenderer {
144    fn device_transform(&self) -> Affine {
145        self.transform
146    }
147
148    fn scale_components(&self) -> (f64, f64, f64) {
149        let coeffs = self.device_transform().as_coeffs();
150        let scale_x = coeffs[0].hypot(coeffs[1]);
151        let scale_y = coeffs[2].hypot(coeffs[3]);
152        let uniform = (scale_x + scale_y) * 0.5;
153        (scale_x, scale_y, uniform)
154    }
155
156    fn brush_to_paint<'b>(&mut self, brush: impl Into<BrushRef<'b>>) -> Option<PaintIndex> {
157        let paint = match brush.into() {
158            BrushRef::Solid(color) => self.vger.color_paint(vger_color(color)),
159            BrushRef::Gradient(g) => match g.kind {
160                GradientKind::Linear(LinearGradientPosition { start, end }) => {
161                    let mut stops = g.stops.iter();
162                    let first_stop = stops.next()?;
163                    let second_stop = stops.next()?;
164                    let inner_color = vger_color(first_stop.color.to_alpha_color());
165                    let outer_color = vger_color(second_stop.color.to_alpha_color());
166                    let start = floem_vger_rs::defs::LocalPoint::new(
167                        start.x as f32 * first_stop.offset,
168                        start.y as f32 * first_stop.offset,
169                    );
170                    let end = floem_vger_rs::defs::LocalPoint::new(
171                        end.x as f32 * second_stop.offset,
172                        end.y as f32 * second_stop.offset,
173                    );
174                    self.vger
175                        .linear_gradient(start, end, inner_color, outer_color, 0.0)
176                }
177                GradientKind::Radial { .. } => return None,
178                GradientKind::Sweep { .. } => return None,
179            },
180            BrushRef::Image(_) => return None,
181        };
182        Some(paint)
183    }
184
185    fn vger_point(&self, point: Point) -> floem_vger_rs::defs::LocalPoint {
186        let coeffs = self.device_transform().as_coeffs();
187
188        let transformed_x = coeffs[0] * point.x + coeffs[2] * point.y + coeffs[4];
189        let transformed_y = coeffs[1] * point.x + coeffs[3] * point.y + coeffs[5];
190
191        floem_vger_rs::defs::LocalPoint::new(transformed_x as f32, transformed_y as f32)
192    }
193
194    fn vger_rect(&self, rect: Rect) -> floem_vger_rs::defs::LocalRect {
195        let origin = rect.origin();
196        let origin = self.vger_point(origin);
197
198        let end = Point::new(rect.x1, rect.y1);
199        let end = self.vger_point(end);
200
201        let size = (end - origin).to_size();
202        floem_vger_rs::defs::LocalRect::new(origin, size)
203    }
204
205    fn render_image(&mut self) -> Option<peniko::ImageBrush> {
206        let width_align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1;
207        let width = (self.config.width + width_align) & !width_align;
208        let height = self.config.height;
209        let texture_desc = wgpu::TextureDescriptor {
210            size: wgpu::Extent3d {
211                width: self.config.width,
212                height,
213                depth_or_array_layers: 1,
214            },
215            mip_level_count: 1,
216            sample_count: 1,
217            dimension: wgpu::TextureDimension::D2,
218            format: wgpu::TextureFormat::Rgba8Unorm,
219            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
220            label: Some("render_texture"),
221            view_formats: &[wgpu::TextureFormat::Rgba8Unorm],
222        };
223        let texture = self.device.create_texture(&texture_desc);
224        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
225        let desc = wgpu::RenderPassDescriptor {
226            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
227                view: &view,
228                resolve_target: None,
229                ops: wgpu::Operations {
230                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
231                    store: StoreOp::Store,
232                },
233                depth_slice: None,
234            })],
235            ..Default::default()
236        };
237
238        self.vger.encode(&desc);
239
240        let bytes_per_pixel = 4;
241        let buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
242            label: None,
243            size: (width as u64 * height as u64 * bytes_per_pixel),
244            usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
245            mapped_at_creation: false,
246        });
247        let bytes_per_row = width * bytes_per_pixel as u32;
248        assert!(bytes_per_row.is_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT));
249
250        let mut encoder = self
251            .device
252            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
253        encoder.copy_texture_to_buffer(
254            texture.as_image_copy(),
255            wgpu::TexelCopyBufferInfo {
256                buffer: &buffer,
257                layout: wgpu::TexelCopyBufferLayout {
258                    offset: 0,
259                    bytes_per_row: Some(bytes_per_row),
260                    rows_per_image: None,
261                },
262            },
263            texture_desc.size,
264        );
265        let command_buffer = encoder.finish();
266        self.queue.submit(Some(command_buffer));
267        self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
268
269        let slice = buffer.slice(..);
270        let (tx, rx) = sync_channel(1);
271        slice.map_async(wgpu::MapMode::Read, move |r| tx.send(r).unwrap());
272
273        loop {
274            if let Ok(r) = rx.try_recv() {
275                break r.ok()?;
276            }
277            if let wgpu::PollStatus::WaitSucceeded =
278                self.device.poll(wgpu::PollType::wait_indefinitely()).ok()?
279            {
280                rx.recv().ok()?.ok()?;
281                break;
282            }
283        }
284
285        let mut cropped_buffer = Vec::new();
286        let buffer: Vec<u8> = slice.get_mapped_range().to_owned();
287
288        let mut cursor = 0;
289        let row_size = self.config.width as usize * bytes_per_pixel as usize;
290        for _ in 0..height {
291            cropped_buffer.extend_from_slice(&buffer[cursor..(cursor + row_size)]);
292            cursor += bytes_per_row as usize;
293        }
294
295        Some(peniko::ImageBrush::new(ImageData {
296            data: Blob::new(Arc::new(cropped_buffer)),
297            format: peniko::ImageFormat::Rgba8,
298            alpha_type: peniko::ImageAlphaType::AlphaPremultiplied,
299            width: self.config.width,
300            height,
301        }))
302        // RgbaImage::from_raw(self.config.width, height, cropped_buffer).map(DynamicImage::ImageRgba8)
303    }
304}
305
306impl Renderer for VgerRenderer {
307    fn begin(&mut self, capture: bool) {
308        // Switch to the capture Vger if needed
309        if self.capture != capture {
310            self.capture = capture;
311            if self.alt_vger.is_none() {
312                self.alt_vger = Some(floem_vger_rs::Vger::new(
313                    self.device.clone(),
314                    self.queue.clone(),
315                    TextureFormat::Rgba8Unorm,
316                ));
317            }
318            mem::swap(&mut self.vger, self.alt_vger.as_mut().unwrap())
319        }
320
321        self.transform = Affine::IDENTITY;
322        self.vger
323            .begin(self.config.width as f32, self.config.height as f32, 1.0);
324    }
325
326    fn stroke<'b, 's>(
327        &mut self,
328        shape: &impl Shape,
329        brush: impl Into<BrushRef<'b>>,
330        stroke: &'s Stroke,
331    ) {
332        let (_, _, scale) = self.scale_components();
333        let paint = match self.brush_to_paint(brush) {
334            Some(paint) => paint,
335            None => return,
336        };
337        let width = (stroke.width * scale).round() as f32;
338        if let Some(rect) = shape.as_rect() {
339            let min = rect.origin();
340            let max = min + rect.size().to_vec2();
341            self.vger.stroke_rect(
342                self.vger_point(min),
343                self.vger_point(max),
344                0.0,
345                width,
346                paint,
347            );
348            return;
349        } else if let Some(rect) = shape.as_rounded_rect() {
350            if rect.radii().top_left == rect.radii().top_right
351                && rect.radii().top_left == rect.radii().bottom_left
352                && rect.radii().top_left == rect.radii().bottom_right
353            {
354                let min = rect.origin();
355                let max = min + rect.rect().size().to_vec2();
356                let radius = (rect.radii().top_left * scale) as f32;
357                self.vger.stroke_rect(
358                    self.vger_point(min),
359                    self.vger_point(max),
360                    radius,
361                    width,
362                    paint,
363                );
364                return;
365            }
366        } else if let Some(line) = shape.as_line() {
367            self.vger.stroke_segment(
368                self.vger_point(line.p0),
369                self.vger_point(line.p1),
370                width,
371                paint,
372            );
373            return;
374        } else if let Some(circle) = shape.as_circle() {
375            self.vger.stroke_arc(
376                self.vger_point(circle.center),
377                (circle.radius * scale) as f32,
378                width,
379                0.0,
380                std::f32::consts::PI,
381                paint,
382            );
383            return;
384        }
385
386        for segment in shape.path_segments(0.001) {
387            match segment {
388                peniko::kurbo::PathSeg::Line(ln) => self.vger.stroke_segment(
389                    self.vger_point(ln.p0),
390                    self.vger_point(ln.p1),
391                    width,
392                    paint,
393                ),
394                peniko::kurbo::PathSeg::Quad(bez) => {
395                    self.vger.stroke_bezier(
396                        self.vger_point(bez.p0),
397                        self.vger_point(bez.p1),
398                        self.vger_point(bez.p2),
399                        width,
400                        paint,
401                    );
402                }
403
404                peniko::kurbo::PathSeg::Cubic(cubic) => {
405                    // Approximates the cubic curve (p0, p1, p2, p3) with a quadratic curve (p0, q1, p3)
406                    let q1 = ((cubic.p1.to_vec2() + cubic.p2.to_vec2()) * 3.0
407                        - cubic.p0.to_vec2()
408                        - cubic.p3.to_vec2())
409                        / 4.0;
410
411                    self.vger.stroke_bezier(
412                        self.vger_point(cubic.p0),
413                        self.vger_point(q1.to_point()),
414                        self.vger_point(cubic.p3),
415                        width,
416                        paint,
417                    );
418                }
419            }
420        }
421    }
422
423    fn fill<'b>(&mut self, path: &impl Shape, brush: impl Into<BrushRef<'b>>, blur_radius: f64) {
424        let (_, _, scale) = self.scale_components();
425        let paint = match self.brush_to_paint(brush) {
426            Some(paint) => paint,
427            None => return,
428        };
429        if let Some(rect) = path.as_rect() {
430            self.vger.fill_rect(
431                self.vger_rect(rect),
432                0.0,
433                paint,
434                (blur_radius * scale) as f32,
435            );
436            return;
437        } else if let Some(rect) = path.as_rounded_rect() {
438            if rect.radii().top_left == rect.radii().top_right
439                && rect.radii().top_left == rect.radii().bottom_left
440                && rect.radii().top_left == rect.radii().bottom_right
441            {
442                // Use `fill_rect` for uniform radii
443                self.vger.fill_rect(
444                    self.vger_rect(rect.rect()),
445                    (rect.radii().top_left * scale) as f32,
446                    paint,
447                    (blur_radius * scale) as f32,
448                );
449                return;
450            }
451        } else if let Some(circle) = path.as_circle() {
452            self.vger.fill_circle(
453                self.vger_point(circle.center),
454                (circle.radius * scale) as f32,
455                paint,
456            );
457            return;
458        }
459
460        let mut first = true;
461        for segment in path.path_segments(0.001) {
462            match segment {
463                peniko::kurbo::PathSeg::Line(line) => {
464                    if first {
465                        first = false;
466                        self.vger.move_to(self.vger_point(line.p0));
467                    }
468                    self.vger
469                        .quad_to(self.vger_point(line.p1), self.vger_point(line.p1));
470                }
471                peniko::kurbo::PathSeg::Quad(quad) => {
472                    if first {
473                        first = false;
474                        self.vger.move_to(self.vger_point(quad.p0));
475                    }
476                    self.vger
477                        .quad_to(self.vger_point(quad.p1), self.vger_point(quad.p2));
478                }
479                peniko::kurbo::PathSeg::Cubic(cubic) => {
480                    if first {
481                        first = false;
482                        self.vger.move_to(self.vger_point(cubic.p0));
483                    }
484
485                    // Approximates the cubic curve (p0, p1, p2, p3) with a quadratic curve (p0, q1, p3)
486                    let q1 = ((cubic.p1.to_vec2() + cubic.p2.to_vec2()) * 3.0
487                        - cubic.p0.to_vec2()
488                        - cubic.p3.to_vec2())
489                        / 4.0;
490                    self.vger
491                        .quad_to(self.vger_point(q1.to_point()), self.vger_point(cubic.p3));
492                }
493            }
494        }
495        self.vger.fill(paint);
496    }
497
498    fn draw_glyphs<'a>(
499        &mut self,
500        origin: Point,
501        props: &GlyphRunProps<'a>,
502        glyphs: impl Iterator<Item = Glyph> + 'a,
503    ) {
504        let font = &props.font;
505        let coeffs = props.transform.as_coeffs();
506        let pos = self.device_transform() * (origin + Point::new(coeffs[4], coeffs[5]).to_vec2());
507        // This assumes that text is axis-aligned.
508        let (_, _, scale) = self.scale_components();
509
510        let clip = self.clip;
511        let Some(font_ref) = FontRef::from_index(font.data.data(), font.index as usize) else {
512            return;
513        };
514        let font_blob_id = font.data.id();
515        let _color = match &props.brush {
516            peniko::Brush::Solid(color) => Color::from(*color),
517            _ => return,
518        };
519        let Some(paint) = self.brush_to_paint(props.brush) else {
520            return;
521        };
522        let skew = props
523            .glyph_transform
524            .map(|transform| transform.as_coeffs()[0].atan().to_degrees() as f32);
525        let embolden = scaled_embolden_strength(self.font_embolden, scale);
526
527        for glyph in glyphs {
528            let glyph_x = pos.x as f32 + glyph.x * scale as f32;
529            let glyph_y = pos.y as f32 + glyph.y * scale as f32;
530
531            if let Some(rect) = clip
532                && (glyph_x as f64) > rect.x1
533            {
534                break;
535            }
536
537            let scaled_font_size = (props.font_size * scale as f32).round() as u32;
538
539            let x_bin = ((glyph_x.fract() + 1.0).fract() * 4.0).min(3.0) as u8;
540            let y_bin = ((glyph_y.fract() + 1.0).fract() * 4.0).min(3.0) as u8;
541
542            let glyph_id = glyph.id as u16;
543            let scaled_size = props.font_size * scale as f32;
544            let coords = props.normalized_coords;
545
546            let synthesis_bits = skew.unwrap_or(0.0).to_bits() & 0xFFFF_FFFE;
547
548            self.vger.render_glyph(
549                glyph_x.floor(),
550                glyph_y.floor(),
551                font_blob_id,
552                glyph_id,
553                scaled_font_size,
554                (x_bin, y_bin),
555                synthesis_bits,
556                || {
557                    let image = SCALE_CONTEXT.with_borrow_mut(|ctx| {
558                        let mut scaler = ctx
559                            .builder(font_ref)
560                            .size(scaled_size)
561                            .hint(props.hint)
562                            .normalized_coords(coords)
563                            .build();
564                        let mut render = Render::new(&[
565                            Source::ColorOutline(0),
566                            Source::ColorBitmap(StrikeWith::BestFit),
567                            Source::Outline,
568                        ]);
569                        render
570                            .format(Format::Alpha)
571                            .offset(swash::zeno::Vector::new(glyph_x.fract(), glyph_y.fract()))
572                            .embolden(embolden);
573                        if let Some(angle) = skew {
574                            render.transform(Some(swash::zeno::Transform::skew(
575                                swash::zeno::Angle::from_degrees(angle),
576                                swash::zeno::Angle::ZERO,
577                            )));
578                        }
579                        render.render(&mut scaler, glyph_id)
580                    });
581                    match image {
582                        Some(img) => GlyphImage {
583                            colored: img.content != swash::scale::image::Content::Mask,
584                            data: img.data.into(),
585                            width: img.placement.width,
586                            height: img.placement.height,
587                            left: img.placement.left,
588                            top: img.placement.top,
589                        },
590                        None => GlyphImage {
591                            data: Blob::new(Arc::new([])),
592                            width: 0,
593                            height: 0,
594                            left: 0,
595                            top: 0,
596                            colored: false,
597                        },
598                    }
599                },
600                paint,
601            );
602        }
603    }
604
605    fn draw_img(&mut self, img: Img<'_>, rect: Rect) {
606        let transform = self.device_transform().as_coeffs();
607        let (scale_x, scale_y, _) = self.scale_components();
608
609        let origin = rect.origin();
610        let transformed_x = transform[0] * origin.x + transform[2] * origin.y + transform[4];
611        let transformed_y = transform[1] * origin.x + transform[3] * origin.y + transform[5];
612
613        let x = transformed_x.round() as f32;
614        let y = transformed_y.round() as f32;
615
616        let width = (rect.width() * scale_x.abs()).round().max(1.0) as u32;
617        let height = (rect.height() * scale_y.abs()).round().max(1.0) as u32;
618
619        self.vger.render_image(x, y, img.hash, width, height, || {
620            let data = img.img.image.data;
621            let (width, height) = (img.img.image.width, img.img.image.height);
622
623            Image {
624                width,
625                height,
626                data,
627                pixel_format: PixelFormat::Rgba,
628            }
629        });
630    }
631
632    fn draw_svg<'b>(
633        &mut self,
634        svg: floem_renderer::Svg<'b>,
635        rect: Rect,
636        brush: Option<impl Into<BrushRef<'b>>>,
637    ) {
638        let transform = self.device_transform().as_coeffs();
639        let (scale_x, scale_y, _) = self.scale_components();
640
641        let origin = rect.origin();
642        let transformed_x = transform[0] * origin.x + transform[2] * origin.y + transform[4];
643        let transformed_y = transform[1] * origin.x + transform[3] * origin.y + transform[5];
644
645        let x = transformed_x.round() as f32;
646        let y = transformed_y.round() as f32;
647
648        let width = (rect.width() * scale_x.abs()).round().max(1.0) as u32;
649        let height = (rect.height() * scale_y.abs()).round().max(1.0) as u32;
650
651        let paint = brush.and_then(|b| self.brush_to_paint(b));
652
653        self.vger.render_svg(
654            x,
655            y,
656            svg.hash,
657            width,
658            height,
659            || {
660                let mut img = tiny_skia::Pixmap::new(width, height).unwrap();
661
662                let svg_scale = (width as f32 / svg.tree.size().width())
663                    .min(height as f32 / svg.tree.size().height());
664
665                let final_scale_x = svg_scale;
666                let final_scale_y = svg_scale;
667
668                let transform = tiny_skia::Transform::from_scale(final_scale_x, final_scale_y);
669
670                resvg::render(svg.tree, transform, &mut img.as_mut());
671
672                img.take()
673            },
674            paint,
675        );
676    }
677
678    fn set_transform(&mut self, transform: Affine) {
679        self.transform = transform;
680    }
681
682    fn set_z_index(&mut self, z_index: i32) {
683        self.vger.set_z_index(z_index);
684    }
685
686    fn clip(&mut self, shape: &impl Shape) {
687        let (rect, radius) = if let Some(rect) = shape.as_rect() {
688            (rect, 0.0)
689        } else if let Some(rect) = shape.as_rounded_rect() {
690            (rect.rect(), rect.radii().top_left)
691        } else {
692            (shape.bounding_box(), 0.0)
693        };
694
695        let (_, _, scale) = self.scale_components();
696        self.vger
697            .scissor(self.vger_rect(rect), (radius * scale) as f32);
698
699        let transformed_rect = self.device_transform().transform_rect_bbox(rect);
700
701        self.clip = Some(transformed_rect);
702    }
703
704    fn clear_clip(&mut self) {
705        self.vger.reset_scissor();
706        self.clip = None;
707    }
708
709    fn finish(&mut self) -> Option<peniko::ImageBrush> {
710        if self.capture {
711            self.render_image()
712        } else {
713            if let Ok(frame) = self.surface.get_current_texture() {
714                let texture_view = frame
715                    .texture
716                    .create_view(&wgpu::TextureViewDescriptor::default());
717                let desc = wgpu::RenderPassDescriptor {
718                    label: None,
719                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
720                        view: &texture_view,
721                        resolve_target: None,
722                        ops: wgpu::Operations {
723                            load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
724                            store: StoreOp::Store,
725                        },
726                        depth_slice: None,
727                    })],
728                    depth_stencil_attachment: None,
729                    timestamp_writes: None,
730                    occlusion_query_set: None,
731                };
732
733                self.vger.encode(&desc);
734                frame.present();
735            }
736            None
737        }
738    }
739
740    fn push_layer(
741        &mut self,
742        _blend: impl Into<peniko::BlendMode>,
743        _alpha: f32,
744        _transform: Affine,
745        _clip: &impl Shape,
746    ) {
747    }
748
749    fn pop_layer(&mut self) {}
750
751    fn debug_info(&self) -> String {
752        use std::fmt::Write;
753
754        let mut out = String::new();
755        writeln!(out, "name: Vger").ok();
756        writeln!(out, "info: {:#?}", self.adapter.get_info()).ok();
757
758        out
759    }
760}
761
762fn vger_color(color: Color) -> floem_vger_rs::Color {
763    floem_vger_rs::Color {
764        r: color.components[0],
765        g: color.components[1],
766        b: color.components[2],
767        a: color.components[3],
768    }
769}
770
771fn scaled_embolden_strength(font_embolden: f32, scale: f64) -> f32 {
772    font_embolden * scale as f32
773}
774
775#[cfg(test)]
776mod tests {
777    use super::scaled_embolden_strength;
778
779    #[test]
780    fn embolden_strength_scales_with_raster_scale() {
781        assert!((scaled_embolden_strength(0.2, 1.5) - 0.3).abs() < f32::EPSILON);
782        assert_eq!(scaled_embolden_strength(0.2, 0.0), 0.0);
783    }
784}