Skip to main content

i_slint_renderer_software/
lib.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![doc = include_str!("README.md")]
5#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
6#![cfg_attr(docsrs, feature(doc_cfg))]
7#![deny(unsafe_code)]
8#![cfg_attr(slint_nightly_test, feature(non_exhaustive_omitted_patterns_lint))]
9#![cfg_attr(slint_nightly_test, warn(non_exhaustive_omitted_patterns))]
10#![no_std]
11#![warn(missing_docs)]
12
13extern crate alloc;
14#[cfg(feature = "std")]
15extern crate std;
16
17mod draw_functions;
18mod fixed;
19mod fonts;
20mod minimal_software_window;
21#[cfg(feature = "path")]
22mod path;
23mod scene;
24
25use self::fonts::GlyphRenderer;
26pub use self::minimal_software_window::MinimalSoftwareWindow;
27use self::scene::*;
28use alloc::rc::{Rc, Weak};
29use alloc::vec::Vec;
30use core::cell::{Cell, RefCell};
31use core::pin::Pin;
32use euclid::Length;
33use fixed::Fixed;
34use i_slint_core::api::PlatformError;
35use i_slint_core::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector};
36use i_slint_core::graphics::{BorderRadius, Rgba8Pixel, SharedImageBuffer, SharedPixelBuffer};
37use i_slint_core::item_rendering::HasFont;
38use i_slint_core::item_rendering::{
39    CachedRenderingData, ItemRenderer, PlainOrStyledText, RenderBorderRectangle, RenderImage,
40    RenderRectangle,
41};
42use i_slint_core::item_tree::ItemTreeWeak;
43use i_slint_core::items::{ItemRc, TextOverflow, TextWrap};
44use i_slint_core::lengths::{
45    LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
46    PhysicalPx, PointLengths, RectLengths, ScaleFactor, SizeLengths,
47};
48use i_slint_core::partial_renderer::{DirtyRegion, PartialRenderingState};
49use i_slint_core::renderer::RendererSealed;
50use i_slint_core::textlayout::{AbstractFont, FontMetrics, TextParagraphLayout};
51use i_slint_core::window::{WindowAdapter, WindowInner};
52use i_slint_core::{Brush, Color, ImageInner, StaticTextures};
53#[allow(unused)]
54use num_traits::Float;
55use num_traits::NumCast;
56
57pub use draw_functions::{PremultipliedRgbaColor, Rgb565Pixel, TargetPixel};
58
59type PhysicalLength = euclid::Length<i16, PhysicalPx>;
60type PhysicalRect = euclid::Rect<i16, PhysicalPx>;
61type PhysicalSize = euclid::Size2D<i16, PhysicalPx>;
62type PhysicalPoint = euclid::Point2D<i16, PhysicalPx>;
63type PhysicalBorderRadius = BorderRadius<i16, PhysicalPx>;
64
65pub use i_slint_core::partial_renderer::RepaintBufferType;
66
67/// This enum describes the rotation that should be applied to the contents rendered by the software renderer.
68///
69/// Argument to be passed in [`SoftwareRenderer::set_rendering_rotation`].
70#[non_exhaustive]
71#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
72pub enum RenderingRotation {
73    /// No rotation
74    #[default]
75    NoRotation,
76    /// Rotate 90° to the right
77    Rotate90,
78    /// 180° rotation (upside-down)
79    Rotate180,
80    /// Rotate 90° to the left
81    Rotate270,
82}
83
84impl RenderingRotation {
85    fn is_transpose(self) -> bool {
86        matches!(self, Self::Rotate90 | Self::Rotate270)
87    }
88    fn mirror_width(self) -> bool {
89        matches!(self, Self::Rotate270 | Self::Rotate180)
90    }
91    fn mirror_height(self) -> bool {
92        matches!(self, Self::Rotate90 | Self::Rotate180)
93    }
94    /// Angle of the rotation in degrees
95    pub fn angle(self) -> f32 {
96        match self {
97            RenderingRotation::NoRotation => 0.,
98            RenderingRotation::Rotate90 => 90.,
99            RenderingRotation::Rotate180 => 180.,
100            RenderingRotation::Rotate270 => 270.,
101        }
102    }
103}
104
105#[derive(Copy, Clone, Debug)]
106struct RotationInfo {
107    orientation: RenderingRotation,
108    screen_size: PhysicalSize,
109}
110
111/// Extension trait for euclid type to transpose coordinates (swap x and y, as well as width and height)
112trait Transform {
113    /// Return a copy of Self whose coordinate are swapped (x swapped with y)
114    #[must_use]
115    fn transformed(self, info: RotationInfo) -> Self;
116}
117
118impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Point2D<T, PhysicalPx> {
119    fn transformed(mut self, info: RotationInfo) -> Self {
120        if info.orientation.mirror_width() {
121            self.x = T::from(info.screen_size.width).unwrap() - self.x - T::from(1).unwrap()
122        }
123        if info.orientation.mirror_height() {
124            self.y = T::from(info.screen_size.height).unwrap() - self.y - T::from(1).unwrap()
125        }
126        if info.orientation.is_transpose() {
127            core::mem::swap(&mut self.x, &mut self.y);
128        }
129        self
130    }
131}
132
133impl<T: Copy> Transform for euclid::Size2D<T, PhysicalPx> {
134    fn transformed(mut self, info: RotationInfo) -> Self {
135        if info.orientation.is_transpose() {
136            core::mem::swap(&mut self.width, &mut self.height);
137        }
138        self
139    }
140}
141
142impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Rect<T, PhysicalPx> {
143    fn transformed(self, info: RotationInfo) -> Self {
144        let one = T::from(1).unwrap();
145        let mut origin = self.origin.transformed(info);
146        let size = self.size.transformed(info);
147        if info.orientation.mirror_width() {
148            origin.y = origin.y - (size.height - one);
149        }
150        if info.orientation.mirror_height() {
151            origin.x = origin.x - (size.width - one);
152        }
153        Self::new(origin, size)
154    }
155}
156
157impl<T: Copy> Transform for BorderRadius<T, PhysicalPx> {
158    fn transformed(self, info: RotationInfo) -> Self {
159        match info.orientation {
160            RenderingRotation::NoRotation => self,
161            RenderingRotation::Rotate90 => {
162                Self::new(self.bottom_left, self.top_left, self.top_right, self.bottom_right)
163            }
164            RenderingRotation::Rotate180 => {
165                Self::new(self.bottom_right, self.bottom_left, self.top_left, self.top_right)
166            }
167            RenderingRotation::Rotate270 => {
168                Self::new(self.top_right, self.bottom_right, self.bottom_left, self.top_left)
169            }
170        }
171    }
172}
173
174/// This trait defines a bi-directional interface between Slint and your code to send lines to your screen, when using
175/// the [`SoftwareRenderer::render_by_line`] function.
176///
177/// * Through the associated `TargetPixel` type Slint knows how to create and manipulate pixels without having to know
178///   the exact device-specific binary representation and operations for blending.
179/// * Through the `process_line` function Slint notifies you when a line can be rendered and provides a callback that
180///   you can invoke to fill a slice of pixels for the given line.
181///
182/// See the [`render_by_line`](SoftwareRenderer::render_by_line) documentation for an example.
183pub trait LineBufferProvider {
184    /// The pixel type of the buffer
185    type TargetPixel: TargetPixel;
186
187    /// Called once per line, you will have to call the render_fn back with the buffer.
188    ///
189    /// The `line` is the y position of the line to be drawn.
190    /// The `range` is the range within the line that is going to be rendered (eg, within the dirty region)
191    /// The `render_fn` function should be called to render the line, passing the buffer
192    /// corresponding to the specified line and range.
193    fn process_line(
194        &mut self,
195        line: usize,
196        range: core::ops::Range<usize>,
197        render_fn: impl FnOnce(&mut [Self::TargetPixel]),
198    );
199}
200
201#[cfg(not(cbindgen))]
202const PHYSICAL_REGION_MAX_SIZE: usize = DirtyRegion::MAX_COUNT;
203// cbindgen can't understand associated const correctly, so hardcode the value
204#[cfg(cbindgen)]
205pub const PHYSICAL_REGION_MAX_SIZE: usize = 3;
206const _: () = {
207    assert!(PHYSICAL_REGION_MAX_SIZE == 3);
208    assert!(DirtyRegion::MAX_COUNT == 3);
209};
210
211/// Represents a rectangular region on the screen, used for partial rendering.
212///
213/// The region may be composed of multiple sub-regions.
214#[derive(Clone, Debug, Default)]
215#[repr(C)]
216pub struct PhysicalRegion {
217    rectangles: [euclid::Box2D<i16, PhysicalPx>; PHYSICAL_REGION_MAX_SIZE],
218    count: usize,
219}
220
221impl PhysicalRegion {
222    fn iter_box(&self) -> impl Iterator<Item = euclid::Box2D<i16, PhysicalPx>> + '_ {
223        (0..self.count).map(|x| self.rectangles[x])
224    }
225
226    fn bounding_rect(&self) -> PhysicalRect {
227        if self.count == 0 {
228            return Default::default();
229        }
230        let mut r = self.rectangles[0];
231        for i in 1..self.count {
232            r = r.union(&self.rectangles[i]);
233        }
234        r.to_rect()
235    }
236
237    /// Returns the size of the bounding box of this region.
238    pub fn bounding_box_size(&self) -> i_slint_core::api::PhysicalSize {
239        let bb = self.bounding_rect();
240        i_slint_core::api::PhysicalSize { width: bb.width() as _, height: bb.height() as _ }
241    }
242    /// Returns the origin of the bounding box of this region.
243    pub fn bounding_box_origin(&self) -> i_slint_core::api::PhysicalPosition {
244        let bb = self.bounding_rect();
245        i_slint_core::api::PhysicalPosition { x: bb.origin.x as _, y: bb.origin.y as _ }
246    }
247
248    /// Returns an iterator over the rectangles in this region.
249    /// Each rectangle is represented by its position and its size.
250    /// They do not overlap.
251    pub fn iter(
252        &self,
253    ) -> impl Iterator<Item = (i_slint_core::api::PhysicalPosition, i_slint_core::api::PhysicalSize)> + '_
254    {
255        let mut line_ranges = Vec::<core::ops::Range<i16>>::new();
256        let mut begin_line = 0;
257        let mut end_line = 0;
258        core::iter::from_fn(move || {
259            loop {
260                match line_ranges.pop() {
261                    Some(r) => {
262                        return Some((
263                            i_slint_core::api::PhysicalPosition {
264                                x: r.start as _,
265                                y: begin_line as _,
266                            },
267                            i_slint_core::api::PhysicalSize {
268                                width: r.len() as _,
269                                height: (end_line - begin_line) as _,
270                            },
271                        ));
272                    }
273                    None => {
274                        begin_line = end_line;
275                        end_line = match region_line_ranges(self, begin_line, &mut line_ranges) {
276                            Some(end_line) => end_line,
277                            None => return None,
278                        };
279                        line_ranges.reverse();
280                    }
281                }
282            }
283        })
284    }
285
286    fn intersection(&self, clip: &PhysicalRect) -> PhysicalRegion {
287        let mut res = Self::default();
288        let clip = clip.to_box2d();
289        let mut count = 0;
290        for i in 0..self.count {
291            if let Some(r) = self.rectangles[i].intersection(&clip) {
292                res.rectangles[count] = r;
293                count += 1;
294            }
295        }
296        res.count = count;
297        res
298    }
299}
300
301#[test]
302fn region_iter() {
303    let mut region = PhysicalRegion::default();
304    assert_eq!(region.iter().next(), None);
305    region.rectangles[0] =
306        euclid::Box2D::from_origin_and_size(euclid::point2(1, 1), euclid::size2(2, 3));
307    region.rectangles[1] =
308        euclid::Box2D::from_origin_and_size(euclid::point2(6, 2), euclid::size2(3, 20));
309    region.rectangles[2] =
310        euclid::Box2D::from_origin_and_size(euclid::point2(0, 10), euclid::size2(10, 5));
311    assert_eq!(region.iter().next(), None);
312    region.count = 1;
313    let r = |x, y, width, height| {
314        (
315            i_slint_core::api::PhysicalPosition { x, y },
316            i_slint_core::api::PhysicalSize { width, height },
317        )
318    };
319
320    let mut iter = region.iter();
321    assert_eq!(iter.next(), Some(r(1, 1, 2, 3)));
322    assert_eq!(iter.next(), None);
323    drop(iter);
324
325    region.count = 3;
326    let mut iter = region.iter();
327    assert_eq!(iter.next(), Some(r(1, 1, 2, 1))); // the two first rectangle could have been merged
328    assert_eq!(iter.next(), Some(r(1, 2, 2, 2)));
329    assert_eq!(iter.next(), Some(r(6, 2, 3, 2)));
330    assert_eq!(iter.next(), Some(r(6, 4, 3, 6)));
331    assert_eq!(iter.next(), Some(r(0, 10, 10, 5)));
332    assert_eq!(iter.next(), Some(r(6, 15, 3, 7)));
333    assert_eq!(iter.next(), None);
334}
335
336/// Computes what are the x ranges that intersects the region for specified y line.
337///
338/// This uses a mutable reference to a Vec so that the memory is re-used between calls.
339///
340/// Returns the y position until which this range is valid
341fn region_line_ranges(
342    region: &PhysicalRegion,
343    line: i16,
344    line_ranges: &mut Vec<core::ops::Range<i16>>,
345) -> Option<i16> {
346    line_ranges.clear();
347    let mut next_validity = None::<i16>;
348    for geom in region.iter_box() {
349        if geom.is_empty() {
350            continue;
351        }
352        if geom.y_range().contains(&line) {
353            match &mut next_validity {
354                Some(val) => *val = geom.max.y.min(*val),
355                None => next_validity = Some(geom.max.y),
356            }
357            let mut tmp = Some(geom.x_range());
358            line_ranges.retain_mut(|it| {
359                if let Some(r) = &mut tmp {
360                    if it.end < r.start {
361                        true
362                    } else if it.start <= r.start {
363                        if it.end >= r.end {
364                            tmp = None;
365                            return true;
366                        }
367                        r.start = it.start;
368                        false
369                    } else if it.start <= r.end {
370                        if it.end <= r.end {
371                            false
372                        } else {
373                            it.start = r.start;
374                            tmp = None;
375                            true
376                        }
377                    } else {
378                        core::mem::swap(it, r);
379                        true
380                    }
381                } else {
382                    true
383                }
384            });
385            if let Some(r) = tmp {
386                line_ranges.push(r);
387            }
388            continue;
389        } else if geom.min.y >= line {
390            match &mut next_validity {
391                Some(val) => *val = geom.min.y.min(*val),
392                None => next_validity = Some(geom.min.y),
393            }
394        }
395    }
396    // check that current items are properly sorted
397    debug_assert!(line_ranges.windows(2).all(|x| x[0].end < x[1].start));
398    next_validity
399}
400
401mod target_pixel_buffer;
402
403#[cfg(feature = "experimental")]
404pub use target_pixel_buffer::{
405    DrawRectangleArgs, DrawTextureArgs, TargetPixelBuffer, TexturePixelFormat,
406};
407
408#[cfg(not(feature = "experimental"))]
409use target_pixel_buffer::TexturePixelFormat;
410
411struct TargetPixelSlice<'a, T> {
412    data: &'a mut [T],
413    pixel_stride: usize,
414}
415
416impl<'a, T: TargetPixel> target_pixel_buffer::TargetPixelBuffer for TargetPixelSlice<'a, T> {
417    type TargetPixel = T;
418
419    fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] {
420        let offset = line_number * self.pixel_stride;
421        &mut self.data[offset..offset + self.pixel_stride]
422    }
423
424    fn num_lines(&self) -> usize {
425        self.data.len() / self.pixel_stride
426    }
427}
428
429/// A Renderer that do the rendering in software
430///
431/// The renderer can remember what items needs to be redrawn from the previous iteration.
432///
433/// There are two kind of possible rendering
434///  1. Using [`render()`](Self::render()) to render the window in a buffer
435///  2. Using [`render_by_line()`](Self::render()) to render the window line by line. This
436///     is only useful if the device does not have enough memory to render the whole window
437///     in one single buffer
438pub struct SoftwareRenderer {
439    repaint_buffer_type: Cell<RepaintBufferType>,
440    /// This is the area which was dirty on the previous frame.
441    /// Only used if repaint_buffer_type == RepaintBufferType::SwappedBuffers
442    prev_frame_dirty: Cell<DirtyRegion>,
443    partial_rendering_state: PartialRenderingState,
444    maybe_window_adapter: RefCell<Option<Weak<dyn i_slint_core::window::WindowAdapter>>>,
445    rotation: Cell<RenderingRotation>,
446    rendering_metrics_collector: Option<Rc<RenderingMetricsCollector>>,
447    #[cfg(feature = "systemfonts")]
448    text_layout_cache: sharedparley::TextLayoutCache,
449}
450
451impl Default for SoftwareRenderer {
452    fn default() -> Self {
453        Self {
454            partial_rendering_state: Default::default(),
455            prev_frame_dirty: Default::default(),
456            maybe_window_adapter: Default::default(),
457            rotation: Default::default(),
458            rendering_metrics_collector: RenderingMetricsCollector::new("software"),
459            repaint_buffer_type: Default::default(),
460            #[cfg(feature = "systemfonts")]
461            text_layout_cache: Default::default(),
462        }
463    }
464}
465
466#[cfg(feature = "testing")]
467impl SoftwareRenderer {
468    /// Returns a reference to the text layout cache for testing purposes.
469    pub fn text_layout_cache(&self) -> &sharedparley::TextLayoutCache {
470        &self.text_layout_cache
471    }
472}
473
474impl SoftwareRenderer {
475    /// Create a new Renderer
476    pub fn new() -> Self {
477        Default::default()
478    }
479
480    /// Create a new SoftwareRenderer.
481    ///
482    /// The `repaint_buffer_type` parameter specify what kind of buffer are passed to [`Self::render`]
483    pub fn new_with_repaint_buffer_type(repaint_buffer_type: RepaintBufferType) -> Self {
484        let self_ = Self::default();
485        self_.repaint_buffer_type.set(repaint_buffer_type);
486        self_
487    }
488
489    /// Change the what kind of buffer is being passed to [`Self::render`]
490    ///
491    /// This may clear the internal caches
492    pub fn set_repaint_buffer_type(&self, repaint_buffer_type: RepaintBufferType) {
493        if self.repaint_buffer_type.replace(repaint_buffer_type) != repaint_buffer_type {
494            self.partial_rendering_state.clear_cache();
495        }
496    }
497
498    /// Returns the kind of buffer that must be passed to  [`Self::render`]
499    pub fn repaint_buffer_type(&self) -> RepaintBufferType {
500        self.repaint_buffer_type.get()
501    }
502
503    /// Set how the window need to be rotated in the buffer.
504    ///
505    /// This is typically used to implement screen rotation in software
506    pub fn set_rendering_rotation(&self, rotation: RenderingRotation) {
507        self.rotation.set(rotation)
508    }
509
510    /// Return the current rotation. See [`Self::set_rendering_rotation()`]
511    pub fn rendering_rotation(&self) -> RenderingRotation {
512        self.rotation.get()
513    }
514
515    /// Render the window to the given frame buffer.
516    ///
517    /// The renderer uses a cache internally and will only render the part of the window
518    /// which are dirty. The `extra_draw_region` is an extra region which will also
519    /// be rendered. (eg: the previous dirty region in case of double buffering)
520    /// This function returns the region that was rendered.
521    ///
522    /// The pixel_stride is the size (in pixels) between two lines in the buffer.
523    /// It is equal `width` if the screen is not rotated, and `height` if the screen is rotated by 90°.
524    /// The buffer needs to be big enough to contain the window, so its size must be at least
525    /// `pixel_stride * height`, or `pixel_stride * width` if the screen is rotated by 90°.
526    ///
527    /// Returns the physical dirty region for this frame, excluding the extra_draw_region,
528    /// in the window frame of reference. It is affected by the screen rotation.
529    pub fn render(&self, buffer: &mut [impl TargetPixel], pixel_stride: usize) -> PhysicalRegion {
530        self.render_buffer_impl(&mut TargetPixelSlice { data: buffer, pixel_stride })
531    }
532
533    /// Render the window to the given frame buffer.
534    ///
535    /// The renderer uses a cache internally and will only render the part of the window
536    /// which are dirty. The `extra_draw_region` is an extra region which will also
537    /// be rendered. (eg: the previous dirty region in case of double buffering)
538    /// This function returns the region that was rendered.
539    ///
540    /// The buffer's line slices need to be wide enough to if the `width` of the screen and the line count the `height`,
541    /// or the `height` and `width` swapped if the screen is rotated by 90°.
542    ///
543    /// Returns the physical dirty region for this frame, excluding the extra_draw_region,
544    /// in the window frame of reference. It is affected by the screen rotation.
545    #[cfg(feature = "experimental")]
546    pub fn render_into_buffer(&self, buffer: &mut impl TargetPixelBuffer) -> PhysicalRegion {
547        self.render_buffer_impl(buffer)
548    }
549
550    fn render_buffer_impl(
551        &self,
552        buffer: &mut impl target_pixel_buffer::TargetPixelBuffer,
553    ) -> PhysicalRegion {
554        let pixels_per_line = buffer.line_slice(0).len();
555        let num_lines = buffer.num_lines();
556        let buffer_pixel_count = num_lines * pixels_per_line;
557
558        let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
559        else {
560            return Default::default();
561        };
562        let window_inner = WindowInner::from_pub(window.window());
563        #[cfg(feature = "systemfonts")]
564        self.text_layout_cache.clear_cache_if_scale_factor_changed(window.window());
565        let factor = ScaleFactor::new(window_inner.scale_factor());
566        let rotation = self.rotation.get();
567        let (size, background) = if let Some(window_item) =
568            window_inner.window_item().as_ref().map(|item| item.as_pin_ref())
569        {
570            (
571                (LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
572                    * factor)
573                    .cast(),
574                window_item.background(),
575            )
576        } else if rotation.is_transpose() {
577            (euclid::size2(num_lines as _, pixels_per_line as _), Brush::default())
578        } else {
579            (euclid::size2(pixels_per_line as _, num_lines as _), Brush::default())
580        };
581        if size.is_empty() {
582            return Default::default();
583        }
584        assert!(
585            if rotation.is_transpose() {
586                pixels_per_line >= size.height as usize
587                    && buffer_pixel_count
588                        >= (size.width as usize * pixels_per_line + size.height as usize)
589                            - pixels_per_line
590            } else {
591                pixels_per_line >= size.width as usize
592                    && buffer_pixel_count
593                        >= (size.height as usize * pixels_per_line + size.width as usize)
594                            - pixels_per_line
595            },
596            "buffer of size {} with {pixels_per_line} pixels per line is too small to handle a window of size {size:?}",
597            buffer_pixel_count
598        );
599        let buffer_renderer = SceneBuilder::new(
600            size,
601            factor,
602            window_inner,
603            RenderToBuffer {
604                buffer,
605                dirty_range_cache: Vec::new(),
606                dirty_region: Default::default(),
607                scale_factor: factor,
608            },
609            rotation,
610            #[cfg(feature = "systemfonts")]
611            &self.text_layout_cache,
612        );
613        let mut renderer = self.partial_rendering_state.create_partial_renderer(buffer_renderer);
614        let window_adapter = renderer.window_adapter.clone();
615
616        window_inner
617            .draw_contents(|components, post_render| {
618                let logical_size = (size.cast() / factor).cast();
619
620                match self.repaint_buffer_type.get() {
621                    RepaintBufferType::NewBuffer => {
622                        renderer.dirty_region = LogicalRect::from_size(logical_size).into();
623                        self.partial_rendering_state.clear_cache();
624                    }
625                    RepaintBufferType::ReusedBuffer => {
626                        self.partial_rendering_state.apply_dirty_region(
627                            &mut renderer,
628                            components,
629                            logical_size,
630                            None,
631                        );
632                    }
633                    RepaintBufferType::SwappedBuffers => {
634                        let dirty_region_for_this_frame =
635                            self.partial_rendering_state.apply_dirty_region(
636                                &mut renderer,
637                                components,
638                                logical_size,
639                                Some(self.prev_frame_dirty.take()),
640                            );
641                        self.prev_frame_dirty.set(dirty_region_for_this_frame);
642                    }
643                }
644
645                let rotation = RotationInfo { orientation: rotation, screen_size: size };
646                let screen_rect = PhysicalRect::from_size(size);
647                let mut i = renderer.dirty_region.iter().filter_map(|r| {
648                    (r.cast() * factor)
649                        .to_rect()
650                        .round_out()
651                        .cast()
652                        .intersection(&screen_rect)?
653                        .transformed(rotation)
654                        .into()
655                });
656                let dirty_region = PhysicalRegion {
657                    rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
658                    count: renderer.dirty_region.iter().count(),
659                };
660                drop(i);
661
662                renderer.actual_renderer.processor.dirty_region = dirty_region.clone();
663                if !renderer
664                    .actual_renderer
665                    .processor
666                    .buffer
667                    .fill_background(&background, &dirty_region)
668                {
669                    let mut bg = TargetPixel::background();
670                    // TODO: gradient background
671                    TargetPixel::blend(&mut bg, background.color().into());
672                    renderer.actual_renderer.processor.foreach_ranges(
673                        &dirty_region.bounding_rect(),
674                        |_, buffer, _, _| {
675                            buffer.fill(bg);
676                        },
677                    );
678                }
679
680                let partial = self.repaint_buffer_type.get() != RepaintBufferType::NewBuffer;
681                for (component, origin) in components {
682                    if let Some(component) = ItemTreeWeak::upgrade(component) {
683                        i_slint_core::item_rendering::render_component_items(
684                            &component,
685                            if partial { &mut renderer } else { &mut renderer.actual_renderer },
686                            *origin,
687                            &window_adapter,
688                        );
689                    }
690                }
691
692                if partial {
693                    post_render(&mut renderer);
694                } else {
695                    post_render(&mut renderer.actual_renderer);
696                }
697
698                self.measure_frame_rendered(&mut renderer);
699
700                dirty_region
701            })
702            .unwrap_or_default()
703    }
704
705    fn measure_frame_rendered(&self, renderer: &mut dyn ItemRenderer) {
706        if let Some(metrics) = &self.rendering_metrics_collector {
707            let prev_frame_dirty = self.prev_frame_dirty.take();
708            let m = i_slint_core::graphics::rendering_metrics_collector::RenderingMetrics {
709                dirty_region: Some(prev_frame_dirty.clone()),
710                ..Default::default()
711            };
712            self.prev_frame_dirty.set(prev_frame_dirty);
713            metrics.measure_frame_rendered(renderer, m);
714            if metrics.refresh_mode() == RefreshMode::FullSpeed {
715                self.partial_rendering_state.force_screen_refresh();
716            }
717        }
718    }
719
720    /// Render the window, line by line, into the line buffer provided by the [`LineBufferProvider`].
721    ///
722    /// The renderer uses a cache internally and will only render the part of the window
723    /// which are dirty, depending on the dirty tracking policy set in [`SoftwareRenderer::new`]
724    /// This function returns the physical region that was rendered considering the rotation.
725    ///
726    /// The [`LineBufferProvider::process_line()`] function will be called for each line and should
727    ///  provide a buffer to draw into.
728    ///
729    /// As an example, let's imagine we want to render into a plain buffer.
730    /// (You wouldn't normally use `render_by_line` for that because the [`Self::render`] would
731    /// then be more efficient)
732    ///
733    /// ```rust
734    /// # use i_slint_renderer_software::{LineBufferProvider, SoftwareRenderer, Rgb565Pixel};
735    /// # fn xxx<'a>(the_frame_buffer: &'a mut [Rgb565Pixel], display_width: usize, renderer: &SoftwareRenderer) {
736    /// struct FrameBuffer<'a>{ frame_buffer: &'a mut [Rgb565Pixel], stride: usize }
737    /// impl<'a> LineBufferProvider for FrameBuffer<'a> {
738    ///     type TargetPixel = Rgb565Pixel;
739    ///     fn process_line(
740    ///         &mut self,
741    ///         line: usize,
742    ///         range: core::ops::Range<usize>,
743    ///         render_fn: impl FnOnce(&mut [Self::TargetPixel]),
744    ///     ) {
745    ///         let line_begin = line * self.stride;
746    ///         render_fn(&mut self.frame_buffer[line_begin..][range]);
747    ///         // The line has been rendered and there could be code here to
748    ///         // send the pixel to the display
749    ///     }
750    /// }
751    /// renderer.render_by_line(FrameBuffer{ frame_buffer: the_frame_buffer, stride: display_width });
752    /// # }
753    /// ```
754    pub fn render_by_line(&self, line_buffer: impl LineBufferProvider) -> PhysicalRegion {
755        let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
756        else {
757            return Default::default();
758        };
759        let window_inner = WindowInner::from_pub(window.window());
760        #[cfg(feature = "systemfonts")]
761        self.text_layout_cache.clear_cache_if_scale_factor_changed(window.window());
762        let component_rc = window_inner.component();
763        let component = i_slint_core::item_tree::ItemTreeRc::borrow_pin(&component_rc);
764        if let Some(window_item) = i_slint_core::items::ItemRef::downcast_pin::<
765            i_slint_core::items::WindowItem,
766        >(component.as_ref().get_item_ref(0))
767        {
768            let factor = ScaleFactor::new(window_inner.scale_factor());
769            let size = LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
770                * factor;
771            render_window_frame_by_line(
772                window_inner,
773                window_item.background(),
774                size.cast(),
775                self,
776                line_buffer,
777            )
778        } else {
779            PhysicalRegion { ..Default::default() }
780        }
781    }
782}
783
784#[doc(hidden)]
785impl RendererSealed for SoftwareRenderer {
786    fn text_size(
787        &self,
788        text_item: Pin<&dyn i_slint_core::item_rendering::RenderString>,
789        item_rc: &i_slint_core::item_tree::ItemRc,
790        max_width: Option<LogicalLength>,
791        text_wrap: TextWrap,
792    ) -> LogicalSize {
793        let Some(scale_factor) = self.scale_factor() else {
794            return LogicalSize::default();
795        };
796        let font_request = text_item.font_request(item_rc);
797        // Evaluate text() before borrowing font_context: the binding can
798        // re-enter text_size for other elements and would panic on a second
799        // borrow_mut().
800        let content = text_item.text();
801        #[cfg(feature = "systemfonts")]
802        let Some(slint_ctx) = self.slint_context() else {
803            return Default::default();
804        };
805        let font = {
806            #[cfg(feature = "systemfonts")]
807            let mut font_ctx = slint_ctx.font_context().borrow_mut();
808            fonts::match_font(
809                &font_request,
810                scale_factor,
811                #[cfg(feature = "systemfonts")]
812                &mut font_ctx,
813            )
814        };
815
816        #[cfg(feature = "systemfonts")]
817        if matches!(font, fonts::Font::VectorFont(_)) && !parley_disabled() {
818            return sharedparley::text_size(
819                self,
820                text_item,
821                item_rc,
822                max_width,
823                text_wrap,
824                Some(&self.text_layout_cache),
825            )
826            .unwrap_or_default();
827        }
828
829        let string = match &content {
830            PlainOrStyledText::Plain(string) => alloc::borrow::Cow::Borrowed(string.as_str()),
831            PlainOrStyledText::Styled(styled_text) => {
832                i_slint_core::styled_text::get_raw_text(styled_text)
833            }
834        };
835        let (longest_line_width, height) = match &font {
836            #[cfg(feature = "systemfonts")]
837            fonts::Font::VectorFont(vf) => {
838                let layout = fonts::text_layout_for_font(vf, &font_request, scale_factor);
839                layout.text_size(
840                    &string,
841                    max_width.map(|max_width| (max_width.cast() * scale_factor).cast()),
842                    text_wrap,
843                )
844            }
845            fonts::Font::PixelFont(pf) => {
846                let layout = fonts::text_layout_for_font(pf, &font_request, scale_factor);
847                layout.text_size(
848                    &string,
849                    max_width.map(|max_width| (max_width.cast() * scale_factor).cast()),
850                    text_wrap,
851                )
852            }
853        };
854        (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor).cast()
855    }
856
857    fn char_size(
858        &self,
859        text_item: Pin<&dyn i_slint_core::item_rendering::HasFont>,
860        item_rc: &i_slint_core::item_tree::ItemRc,
861        ch: char,
862    ) -> LogicalSize {
863        let Some(scale_factor) = self.scale_factor() else {
864            return LogicalSize::default();
865        };
866        let font_request = text_item.font_request(item_rc);
867        #[cfg(feature = "systemfonts")]
868        let Some(slint_ctx) = self.slint_context() else {
869            return Default::default();
870        };
871        let font = {
872            #[cfg(feature = "systemfonts")]
873            let mut font_ctx = slint_ctx.font_context().borrow_mut();
874            fonts::match_font(
875                &font_request,
876                scale_factor,
877                #[cfg(feature = "systemfonts")]
878                &mut font_ctx,
879            )
880        };
881
882        match (font, parley_disabled()) {
883            #[cfg(feature = "systemfonts")]
884            (fonts::Font::VectorFont(_), false) => {
885                let mut font_ctx = slint_ctx.font_context().borrow_mut();
886                sharedparley::char_size(&mut font_ctx, text_item, item_rc, ch).unwrap_or_default()
887            }
888            #[cfg(feature = "systemfonts")]
889            (fonts::Font::VectorFont(vf), true) => {
890                let mut buf = [0u8, 0u8, 0u8, 0u8];
891                let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
892                let (longest_line_width, height) =
893                    layout.text_size(ch.encode_utf8(&mut buf), None, TextWrap::NoWrap);
894                (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor)
895                    .cast()
896            }
897            (fonts::Font::PixelFont(pf), _) => {
898                let mut buf = [0u8, 0u8, 0u8, 0u8];
899                let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
900                let (longest_line_width, height) =
901                    layout.text_size(ch.encode_utf8(&mut buf), None, TextWrap::NoWrap);
902                (PhysicalSize::from_lengths(longest_line_width, height).cast() / scale_factor)
903                    .cast()
904            }
905        }
906    }
907
908    fn font_metrics(
909        &self,
910        font_request: i_slint_core::graphics::FontRequest,
911    ) -> i_slint_core::items::FontMetrics {
912        let Some(scale_factor) = self.scale_factor() else {
913            return i_slint_core::items::FontMetrics::default();
914        };
915        #[cfg(feature = "systemfonts")]
916        let Some(slint_ctx) = self.slint_context() else {
917            return Default::default();
918        };
919        #[cfg(feature = "systemfonts")]
920        let mut font_ctx = slint_ctx.font_context().borrow_mut();
921        let font = fonts::match_font(
922            &font_request,
923            scale_factor,
924            #[cfg(feature = "systemfonts")]
925            &mut font_ctx,
926        );
927
928        match (font, parley_disabled()) {
929            #[cfg(feature = "systemfonts")]
930            (fonts::Font::VectorFont(_), false) => {
931                sharedparley::font_metrics(&mut font_ctx, font_request)
932            }
933            #[cfg(feature = "systemfonts")]
934            (fonts::Font::VectorFont(font), true) => {
935                let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast();
936                let descent: LogicalLength = (font.descent().cast() / scale_factor).cast();
937                let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast();
938                let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast();
939
940                i_slint_core::items::FontMetrics {
941                    ascent: ascent.get() as _,
942                    descent: descent.get() as _,
943                    x_height: x_height.get() as _,
944                    cap_height: cap_height.get() as _,
945                }
946            }
947            (fonts::Font::PixelFont(font), _) => {
948                let ascent: LogicalLength = (font.ascent().cast() / scale_factor).cast();
949                let descent: LogicalLength = (font.descent().cast() / scale_factor).cast();
950                let x_height: LogicalLength = (font.x_height().cast() / scale_factor).cast();
951                let cap_height: LogicalLength = (font.cap_height().cast() / scale_factor).cast();
952
953                i_slint_core::items::FontMetrics {
954                    ascent: ascent.get() as _,
955                    descent: descent.get() as _,
956                    x_height: x_height.get() as _,
957                    cap_height: cap_height.get() as _,
958                }
959            }
960        }
961    }
962
963    fn text_input_byte_offset_for_position(
964        &self,
965        text_input: Pin<&i_slint_core::items::TextInput>,
966        item_rc: &ItemRc,
967        pos: LogicalPoint,
968    ) -> usize {
969        let Some(scale_factor) = self.scale_factor() else {
970            return 0;
971        };
972        let font_request = text_input.font_request(item_rc);
973        #[cfg(feature = "systemfonts")]
974        let Some(slint_ctx) = self.slint_context() else {
975            return Default::default();
976        };
977        let font = {
978            #[cfg(feature = "systemfonts")]
979            let mut font_ctx = slint_ctx.font_context().borrow_mut();
980            fonts::match_font(
981                &font_request,
982                scale_factor,
983                #[cfg(feature = "systemfonts")]
984                &mut font_ctx,
985            )
986        };
987
988        match (font, parley_disabled()) {
989            #[cfg(feature = "systemfonts")]
990            (fonts::Font::VectorFont(_), false) => {
991                sharedparley::text_input_byte_offset_for_position(self, text_input, item_rc, pos)
992            }
993            #[cfg(feature = "systemfonts")]
994            (fonts::Font::VectorFont(vf), true) => {
995                let visual_representation = text_input.visual_representation(None);
996
997                let width = (text_input.width().cast() * scale_factor).cast();
998                let height = (text_input.height().cast() * scale_factor).cast();
999
1000                let pos = (pos.cast() * scale_factor)
1001                    .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast())
1002                    .cast();
1003
1004                let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
1005
1006                let paragraph = TextParagraphLayout {
1007                    string: &visual_representation.text,
1008                    layout,
1009                    max_width: width,
1010                    max_height: height,
1011                    horizontal_alignment: text_input.horizontal_alignment(),
1012                    vertical_alignment: text_input.vertical_alignment(),
1013                    wrap: text_input.wrap(),
1014                    overflow: TextOverflow::Clip,
1015                    single_line: false,
1016                };
1017
1018                visual_representation.map_byte_offset_from_visual_text_to_actual_text(
1019                    paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
1020                )
1021            }
1022            (fonts::Font::PixelFont(pf), _) => {
1023                let visual_representation = text_input.visual_representation(None);
1024
1025                let width = (text_input.width().cast() * scale_factor).cast();
1026                let height = (text_input.height().cast() * scale_factor).cast();
1027
1028                let pos = (pos.cast() * scale_factor)
1029                    .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast())
1030                    .cast();
1031
1032                let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
1033
1034                let paragraph = TextParagraphLayout {
1035                    string: &visual_representation.text,
1036                    layout,
1037                    max_width: width,
1038                    max_height: height,
1039                    horizontal_alignment: text_input.horizontal_alignment(),
1040                    vertical_alignment: text_input.vertical_alignment(),
1041                    wrap: text_input.wrap(),
1042                    overflow: TextOverflow::Clip,
1043                    single_line: false,
1044                };
1045
1046                visual_representation.map_byte_offset_from_visual_text_to_actual_text(
1047                    paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
1048                )
1049            }
1050        }
1051    }
1052
1053    fn text_input_cursor_rect_for_byte_offset(
1054        &self,
1055        text_input: Pin<&i_slint_core::items::TextInput>,
1056        item_rc: &ItemRc,
1057        byte_offset: usize,
1058    ) -> LogicalRect {
1059        let Some(scale_factor) = self.scale_factor() else {
1060            return LogicalRect::default();
1061        };
1062        let font_request = text_input.font_request(item_rc);
1063        #[cfg(feature = "systemfonts")]
1064        let Some(slint_ctx) = self.slint_context() else {
1065            return Default::default();
1066        };
1067        let font = {
1068            #[cfg(feature = "systemfonts")]
1069            let mut font_ctx = slint_ctx.font_context().borrow_mut();
1070            fonts::match_font(
1071                &font_request,
1072                scale_factor,
1073                #[cfg(feature = "systemfonts")]
1074                &mut font_ctx,
1075            )
1076        };
1077
1078        match (font, parley_disabled()) {
1079            #[cfg(feature = "systemfonts")]
1080            (fonts::Font::VectorFont(_), false) => {
1081                sharedparley::text_input_cursor_rect_for_byte_offset(
1082                    self,
1083                    text_input,
1084                    item_rc,
1085                    byte_offset,
1086                )
1087            }
1088            #[cfg(feature = "systemfonts")]
1089            (fonts::Font::VectorFont(vf), true) => {
1090                let visual_representation = text_input.visual_representation(None);
1091
1092                let width = (text_input.width().cast() * scale_factor).cast();
1093                let height = (text_input.height().cast() * scale_factor).cast();
1094
1095                let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
1096
1097                let paragraph = TextParagraphLayout {
1098                    string: &visual_representation.text,
1099                    layout,
1100                    max_width: width,
1101                    max_height: height,
1102                    horizontal_alignment: text_input.horizontal_alignment(),
1103                    vertical_alignment: text_input.vertical_alignment(),
1104                    wrap: text_input.wrap(),
1105                    overflow: TextOverflow::Clip,
1106                    single_line: false,
1107                };
1108
1109                let cursor_position = paragraph.cursor_pos_for_byte_offset(byte_offset);
1110                let cursor_height = vf.height();
1111
1112                (PhysicalRect::new(
1113                    PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1),
1114                    PhysicalSize::from_lengths(
1115                        (text_input.text_cursor_width().cast() * scale_factor).cast(),
1116                        cursor_height,
1117                    ),
1118                )
1119                .cast()
1120                    / scale_factor)
1121                    .cast()
1122            }
1123            (fonts::Font::PixelFont(pf), _) => {
1124                let visual_representation = text_input.visual_representation(None);
1125
1126                let width = (text_input.width().cast() * scale_factor).cast();
1127                let height = (text_input.height().cast() * scale_factor).cast();
1128
1129                let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
1130
1131                let paragraph = TextParagraphLayout {
1132                    string: &visual_representation.text,
1133                    layout,
1134                    max_width: width,
1135                    max_height: height,
1136                    horizontal_alignment: text_input.horizontal_alignment(),
1137                    vertical_alignment: text_input.vertical_alignment(),
1138                    wrap: text_input.wrap(),
1139                    overflow: TextOverflow::Clip,
1140                    single_line: false,
1141                };
1142
1143                let cursor_position = paragraph.cursor_pos_for_byte_offset(byte_offset);
1144                let cursor_height = pf.height();
1145
1146                (PhysicalRect::new(
1147                    PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1),
1148                    PhysicalSize::from_lengths(
1149                        (text_input.text_cursor_width().cast() * scale_factor).cast(),
1150                        cursor_height,
1151                    ),
1152                )
1153                .cast()
1154                    / scale_factor)
1155                    .cast()
1156            }
1157        }
1158    }
1159
1160    fn free_graphics_resources(
1161        &self,
1162        _component: i_slint_core::item_tree::ItemTreeRef,
1163        items: &mut dyn Iterator<Item = Pin<i_slint_core::items::ItemRef<'_>>>,
1164    ) -> Result<(), i_slint_core::platform::PlatformError> {
1165        #[cfg(feature = "systemfonts")]
1166        self.text_layout_cache.component_destroyed(_component);
1167        self.partial_rendering_state.free_graphics_resources(items);
1168        Ok(())
1169    }
1170
1171    fn mark_dirty_region(&self, region: DirtyRegion) {
1172        self.partial_rendering_state.mark_dirty_region(region);
1173    }
1174
1175    fn register_bitmap_font(&self, font_data: &'static i_slint_core::graphics::BitmapFont) {
1176        fonts::register_bitmap_font(font_data);
1177    }
1178
1179    #[cfg(feature = "systemfonts")]
1180    fn register_font_from_memory(
1181        &self,
1182        data: &'static [u8],
1183    ) -> Result<(), std::boxed::Box<dyn std::error::Error>> {
1184        let ctx = self.slint_context().ok_or("slint platform not initialized")?;
1185        ctx.font_context().borrow_mut().register_static_font(data);
1186        Ok(())
1187    }
1188
1189    #[cfg(all(feature = "systemfonts", not(target_arch = "wasm32")))]
1190    fn register_font_from_path(
1191        &self,
1192        path: &std::path::Path,
1193    ) -> Result<(), std::boxed::Box<dyn std::error::Error>> {
1194        let ctx = self.slint_context().ok_or("slint platform not initialized")?;
1195        self::fonts::systemfonts::register_font_from_path(
1196            &mut ctx.font_context().borrow_mut().collection,
1197            path,
1198        )
1199    }
1200
1201    fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
1202        *self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
1203        #[cfg(feature = "systemfonts")]
1204        self.text_layout_cache.clear_all();
1205        self.partial_rendering_state.clear_cache();
1206    }
1207
1208    fn window_adapter(&self) -> Option<Rc<dyn WindowAdapter>> {
1209        self.maybe_window_adapter
1210            .borrow()
1211            .as_ref()
1212            .and_then(|window_adapter| window_adapter.upgrade())
1213    }
1214
1215    fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
1216        let Some(window_adapter) =
1217            self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
1218        else {
1219            return Err(
1220                "SoftwareRenderer's screenshot called without a window adapter present".into()
1221            );
1222        };
1223
1224        let window = window_adapter.window();
1225        let size = window.size();
1226
1227        if size.width == 0 || size.height == 0 {
1228            // Nothing to render
1229            return Err("take_snapshot() called on window with invalid size".into());
1230        };
1231
1232        // Render into a premultiplied buffer so that windows with a transparent
1233        // or semi-transparent background end up with the right alpha in the
1234        // snapshot. PremultipliedRgbaColor::background() is (0,0,0,0), so
1235        // anything the window doesn't paint stays fully transparent.
1236        let mut premul = SharedPixelBuffer::<PremultipliedRgbaColor>::new(size.width, size.height);
1237
1238        let old_repaint_buffer_type = self.repaint_buffer_type();
1239        // ensure that caches are clear
1240        self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
1241        self.render(premul.make_mut_slice(), size.width as usize);
1242        self.set_repaint_buffer_type(old_repaint_buffer_type);
1243
1244        let mut target_buffer_with_alpha =
1245            SharedPixelBuffer::<Rgba8Pixel>::new(premul.width(), premul.height());
1246        for (target_pixel, source_pixel) in
1247            target_buffer_with_alpha.make_mut_slice().iter_mut().zip(premul.as_slice().iter())
1248        {
1249            // Un-premultiply: straight RGBA is what the public API exposes (and
1250            // what PNG encoders expect). Round half up to keep `255 * a / a == 255`.
1251            let a = source_pixel.alpha;
1252            if a == 0 {
1253                *target_pixel = Rgba8Pixel::new(0, 0, 0, 0);
1254            } else {
1255                let unp = |c: u8| ((c as u32 * 255 + (a as u32 / 2)) / a as u32).min(255) as u8;
1256                *target_pixel = Rgba8Pixel::new(
1257                    unp(source_pixel.red),
1258                    unp(source_pixel.green),
1259                    unp(source_pixel.blue),
1260                    a,
1261                );
1262            }
1263        }
1264        Ok(target_buffer_with_alpha)
1265    }
1266
1267    fn supports_transformations(&self) -> bool {
1268        false
1269    }
1270}
1271
1272fn parley_disabled() -> bool {
1273    #[cfg(feature = "systemfonts")]
1274    {
1275        std::env::var("SLINT_SOFTWARE_RENDERER_PARLEY_DISABLED").is_ok()
1276    }
1277    #[cfg(not(feature = "systemfonts"))]
1278    false
1279}
1280
1281fn render_window_frame_by_line(
1282    window: &WindowInner,
1283    background: Brush,
1284    size: PhysicalSize,
1285    renderer: &SoftwareRenderer,
1286    mut line_buffer: impl LineBufferProvider,
1287) -> PhysicalRegion {
1288    let mut scene = prepare_scene(window, size, renderer);
1289
1290    let to_draw_tr = scene.dirty_region.bounding_rect();
1291
1292    let mut background_color = TargetPixel::background();
1293    // FIXME gradient
1294    TargetPixel::blend(&mut background_color, background.color().into());
1295
1296    while scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
1297        for r in &scene.current_line_ranges {
1298            line_buffer.process_line(
1299                scene.current_line.get() as usize,
1300                r.start as usize..r.end as usize,
1301                |line_buffer| {
1302                    let offset = r.start;
1303
1304                    line_buffer.fill(background_color);
1305                    for span in scene.items[0..scene.current_items_index].iter().rev() {
1306                        debug_assert!(scene.current_line >= span.pos.y_length());
1307                        debug_assert!(
1308                            scene.current_line < span.pos.y_length() + span.size.height_length(),
1309                        );
1310                        if span.pos.x >= r.end {
1311                            continue;
1312                        }
1313                        let begin = r.start.max(span.pos.x);
1314                        let end = r.end.min(span.pos.x + span.size.width);
1315                        if begin >= end {
1316                            continue;
1317                        }
1318
1319                        let extra_left_clip = begin - span.pos.x;
1320                        let extra_right_clip = span.pos.x + span.size.width - end;
1321                        let range_buffer =
1322                            &mut line_buffer[(begin - offset) as usize..(end - offset) as usize];
1323
1324                        match span.command {
1325                            SceneCommand::Rectangle { color } => {
1326                                TargetPixel::blend_slice(range_buffer, color);
1327                            }
1328                            SceneCommand::Texture { texture_index } => {
1329                                let texture = &scene.vectors.textures[texture_index as usize];
1330                                draw_functions::draw_texture_line(
1331                                    &PhysicalRect { origin: span.pos, size: span.size },
1332                                    scene.current_line,
1333                                    texture,
1334                                    range_buffer,
1335                                    extra_left_clip,
1336                                    extra_right_clip,
1337                                );
1338                            }
1339                            SceneCommand::SharedBuffer { shared_buffer_index } => {
1340                                let texture = scene.vectors.shared_buffers
1341                                    [shared_buffer_index as usize]
1342                                    .as_texture();
1343                                draw_functions::draw_texture_line(
1344                                    &PhysicalRect { origin: span.pos, size: span.size },
1345                                    scene.current_line,
1346                                    &texture,
1347                                    range_buffer,
1348                                    extra_left_clip,
1349                                    extra_right_clip,
1350                                );
1351                            }
1352                            SceneCommand::RoundedRectangle { rectangle_index } => {
1353                                let rr =
1354                                    &scene.vectors.rounded_rectangles[rectangle_index as usize];
1355                                draw_functions::draw_rounded_rectangle_line(
1356                                    &PhysicalRect { origin: span.pos, size: span.size },
1357                                    scene.current_line,
1358                                    rr,
1359                                    range_buffer,
1360                                    extra_left_clip,
1361                                    extra_right_clip,
1362                                );
1363                            }
1364                            SceneCommand::LinearGradient { linear_gradient_index } => {
1365                                let g =
1366                                    &scene.vectors.linear_gradients[linear_gradient_index as usize];
1367
1368                                draw_functions::draw_linear_gradient(
1369                                    &PhysicalRect { origin: span.pos, size: span.size },
1370                                    scene.current_line,
1371                                    g,
1372                                    range_buffer,
1373                                    extra_left_clip,
1374                                );
1375                            }
1376                            SceneCommand::RadialGradient { radial_gradient_index } => {
1377                                let g =
1378                                    &scene.vectors.radial_gradients[radial_gradient_index as usize];
1379                                draw_functions::draw_radial_gradient(
1380                                    &PhysicalRect { origin: span.pos, size: span.size },
1381                                    scene.current_line,
1382                                    g,
1383                                    range_buffer,
1384                                    extra_left_clip,
1385                                    extra_right_clip,
1386                                );
1387                            }
1388                            SceneCommand::ConicGradient { conic_gradient_index } => {
1389                                let g =
1390                                    &scene.vectors.conic_gradients[conic_gradient_index as usize];
1391                                draw_functions::draw_conic_gradient(
1392                                    &PhysicalRect { origin: span.pos, size: span.size },
1393                                    scene.current_line,
1394                                    g,
1395                                    range_buffer,
1396                                    extra_left_clip,
1397                                    extra_right_clip,
1398                                );
1399                            }
1400                        }
1401                    }
1402                },
1403            );
1404        }
1405
1406        if scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
1407            scene.next_line();
1408        }
1409    }
1410    scene.dirty_region
1411}
1412
1413fn prepare_scene(
1414    window: &WindowInner,
1415    size: PhysicalSize,
1416    software_renderer: &SoftwareRenderer,
1417) -> Scene {
1418    let factor = ScaleFactor::new(window.scale_factor());
1419    let prepare_scene = SceneBuilder::new(
1420        size,
1421        factor,
1422        window,
1423        PrepareScene { scale_factor: factor, ..Default::default() },
1424        software_renderer.rotation.get(),
1425        #[cfg(feature = "systemfonts")]
1426        &software_renderer.text_layout_cache,
1427    );
1428    let mut renderer =
1429        software_renderer.partial_rendering_state.create_partial_renderer(prepare_scene);
1430    let window_adapter = renderer.window_adapter.clone();
1431
1432    let mut dirty_region = PhysicalRegion::default();
1433    window.draw_contents(|components, post_render| {
1434        let logical_size = (size.cast() / factor).cast();
1435
1436        match software_renderer.repaint_buffer_type.get() {
1437            RepaintBufferType::NewBuffer => {
1438                // NewBuffer always redraws the full screen, so skip dirty region
1439                // tracking to avoid unbounded growth of the partial rendering cache.
1440                renderer.dirty_region = LogicalRect::from_size(logical_size).into();
1441                software_renderer.partial_rendering_state.clear_cache();
1442            }
1443            RepaintBufferType::ReusedBuffer => {
1444                software_renderer.partial_rendering_state.apply_dirty_region(
1445                    &mut renderer,
1446                    components,
1447                    logical_size,
1448                    None,
1449                );
1450            }
1451            RepaintBufferType::SwappedBuffers => {
1452                let dirty_region_for_this_frame =
1453                    software_renderer.partial_rendering_state.apply_dirty_region(
1454                        &mut renderer,
1455                        components,
1456                        logical_size,
1457                        Some(software_renderer.prev_frame_dirty.take()),
1458                    );
1459                software_renderer.prev_frame_dirty.set(dirty_region_for_this_frame);
1460            }
1461        }
1462
1463        let rotation =
1464            RotationInfo { orientation: software_renderer.rotation.get(), screen_size: size };
1465        let screen_rect = PhysicalRect::from_size(size);
1466        let mut i = renderer.dirty_region.iter().filter_map(|r| {
1467            (r.cast() * factor)
1468                .to_rect()
1469                .round_out()
1470                .cast()
1471                .intersection(&screen_rect)?
1472                .transformed(rotation)
1473                .into()
1474        });
1475        dirty_region = PhysicalRegion {
1476            rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
1477            count: renderer.dirty_region.iter().count(),
1478        };
1479        drop(i);
1480
1481        let partial = software_renderer.repaint_buffer_type.get() != RepaintBufferType::NewBuffer;
1482        for (component, origin) in components {
1483            if let Some(component) = ItemTreeWeak::upgrade(component) {
1484                i_slint_core::item_rendering::render_component_items(
1485                    &component,
1486                    if partial { &mut renderer } else { &mut renderer.actual_renderer },
1487                    *origin,
1488                    &window_adapter,
1489                );
1490            }
1491        }
1492
1493        if partial {
1494            post_render(&mut renderer);
1495        } else {
1496            post_render(&mut renderer.actual_renderer);
1497        }
1498    });
1499
1500    software_renderer.measure_frame_rendered(&mut renderer);
1501
1502    let prepare_scene = renderer.into_inner();
1503
1504    /* // visualize dirty regions
1505    let mut prepare_scene = prepare_scene;
1506    for rect in dirty_region.iter() {
1507        prepare_scene.processor.process_rounded_rectangle(
1508            euclid::rect(rect.0.x as _, rect.0.y as _, rect.1.width as _, rect.1.height as _),
1509            RoundedRectangle {
1510                radius: BorderRadius::default(),
1511                width: Length::new(1),
1512                border_color: Color::from_argb_u8(128, 255, 0, 0).into(),
1513                inner_color: PremultipliedRgbaColor::default(),
1514                left_clip: Length::default(),
1515                right_clip: Length::default(),
1516                top_clip: Length::default(),
1517                bottom_clip: Length::default(),
1518            },
1519        )
1520    } // */
1521
1522    Scene::new(prepare_scene.processor.items, prepare_scene.processor.vectors, dirty_region)
1523}
1524
1525trait ProcessScene {
1526    fn process_scene_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>);
1527    fn process_target_texture(
1528        &mut self,
1529        texture: &target_pixel_buffer::DrawTextureArgs,
1530        clip: PhysicalRect,
1531    );
1532    fn process_rectangle(&mut self, _: &target_pixel_buffer::DrawRectangleArgs, clip: PhysicalRect);
1533
1534    fn process_simple_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor);
1535    fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle);
1536    fn process_linear_gradient(&mut self, geometry: PhysicalRect, gradient: LinearGradientCommand);
1537    fn process_radial_gradient(&mut self, geometry: PhysicalRect, gradient: RadialGradientCommand);
1538    fn process_conic_gradient(&mut self, geometry: PhysicalRect, gradient: ConicGradientCommand);
1539    #[cfg(feature = "path")]
1540    fn process_filled_path(
1541        &mut self,
1542        path_geometry: PhysicalRect,
1543        clip_geometry: PhysicalRect,
1544        commands: alloc::vec::Vec<path::Command>,
1545        color: PremultipliedRgbaColor,
1546    );
1547    #[cfg(feature = "path")]
1548    fn process_stroked_path(
1549        &mut self,
1550        path_geometry: PhysicalRect,
1551        clip_geometry: PhysicalRect,
1552        commands: alloc::vec::Vec<path::Command>,
1553        color: PremultipliedRgbaColor,
1554        stroke_width: f32,
1555        stroke_line_cap: i_slint_core::items::LineCap,
1556        stroke_line_join: i_slint_core::items::LineJoin,
1557        stroke_miter_limit: f32,
1558    );
1559}
1560
1561fn process_rectangle_impl(
1562    processor: &mut dyn ProcessScene,
1563    args: &target_pixel_buffer::DrawRectangleArgs,
1564    clip: &PhysicalRect,
1565    scale_factor: ScaleFactor,
1566) {
1567    let geom = args.geometry();
1568    let Some(clipped) = geom.intersection(&clip.cast()) else { return };
1569    let geom_w = geom.width();
1570    let geom_h = geom.height();
1571    let to_clipped_center = |cx: f32, cy: f32| {
1572        (geom.min_x() + cx - clipped.min_x(), geom.min_y() + cy - clipped.min_y())
1573    };
1574
1575    let color = if let Brush::LinearGradient(g) = &args.background {
1576        let angle = g.angle() + args.rotation.angle();
1577        let tan = angle.to_radians().tan().abs();
1578        let start = if !tan.is_finite() {
1579            255.
1580        } else {
1581            let h = tan * geom.width();
1582            255. * h / (h + geom.height())
1583        } as u8;
1584        let mut angle = angle as i32 % 360;
1585        if angle < 0 {
1586            angle += 360;
1587        }
1588        let mut stops = g
1589            .stops()
1590            .copied()
1591            .map(|mut s| {
1592                s.color = alpha_color(s.color, args.alpha);
1593                s
1594            })
1595            .peekable();
1596        let mut idx = 0;
1597        let stop_count = g.stops().count();
1598        while let (Some(mut s1), Some(mut s2)) = (stops.next(), stops.peek().copied()) {
1599            let mut flags = 0;
1600            if (angle % 180) > 90 {
1601                flags |= 0b1;
1602            }
1603            if angle <= 90 || angle > 270 {
1604                core::mem::swap(&mut s1, &mut s2);
1605                s1.position = 1. - s1.position;
1606                s2.position = 1. - s2.position;
1607                if idx == 0 {
1608                    flags |= 0b100;
1609                }
1610                if idx == stop_count - 2 {
1611                    flags |= 0b010;
1612                }
1613            } else {
1614                if idx == 0 {
1615                    flags |= 0b010;
1616                }
1617                if idx == stop_count - 2 {
1618                    flags |= 0b100;
1619                }
1620            }
1621
1622            idx += 1;
1623
1624            let (adjust_left, adjust_right) = if (angle % 180) > 90 {
1625                (
1626                    (geom.width() * s1.position).floor() as i16,
1627                    (geom.width() * (1. - s2.position)).ceil() as i16,
1628                )
1629            } else {
1630                (
1631                    (geom.width() * (1. - s2.position)).ceil() as i16,
1632                    (geom.width() * s1.position).floor() as i16,
1633                )
1634            };
1635
1636            let gr = LinearGradientCommand {
1637                color1: s1.color.into(),
1638                color2: s2.color.into(),
1639                start,
1640                flags,
1641                top_clip: Length::new(
1642                    (clipped.min_y() - geom.min_y() - (geom.height() * s1.position).floor()) as i16,
1643                ),
1644                bottom_clip: Length::new(
1645                    (geom.max_y() - clipped.max_y() - (geom.height() * (1. - s2.position)).ceil())
1646                        as i16,
1647                ),
1648                left_clip: Length::new((clipped.min_x() - geom.min_x()) as i16 - adjust_left),
1649                right_clip: Length::new((geom.max_x() - clipped.max_x()) as i16 - adjust_right),
1650            };
1651
1652            let act_rect = clipped.round().cast();
1653            let size_y = act_rect.height_length() + gr.top_clip + gr.bottom_clip;
1654            let size_x = act_rect.width_length() + gr.left_clip + gr.right_clip;
1655            if size_x.get() == 0 || size_y.get() == 0 {
1656                // the position are too close to each other
1657                // FIXME: For the first or the last, we should draw a plain color to the end
1658                continue;
1659            }
1660
1661            processor.process_linear_gradient(act_rect, gr);
1662        }
1663        Color::default()
1664    } else if let Brush::RadialGradient(g) = &args.background {
1665        let (cx, cy) = g.center_or_default_scaled(geom_w, geom_h, scale_factor.get());
1666        let (center_x, center_y) = to_clipped_center(cx, cy);
1667        let radius = g.radius_or_default_scaled(geom_w, geom_h, scale_factor.get());
1668
1669        let radial_grad = RadialGradientCommand {
1670            stops: g
1671                .stops()
1672                .map(|s| {
1673                    let mut stop = *s;
1674                    stop.color = alpha_color(stop.color, args.alpha);
1675                    stop
1676                })
1677                .collect(),
1678            center_x,
1679            center_y,
1680            radius,
1681        };
1682
1683        processor.process_radial_gradient(clipped.cast(), radial_grad);
1684        Color::default()
1685    } else if let Brush::ConicGradient(g) = &args.background {
1686        let (cx, cy) = g.center_or_default_scaled(geom_w, geom_h, scale_factor.get());
1687        let (center_x, center_y) = to_clipped_center(cx, cy);
1688        let conic_grad = ConicGradientCommand {
1689            stops: g
1690                .stops()
1691                .map(|s| {
1692                    let mut stop = *s;
1693                    stop.color = alpha_color(stop.color, args.alpha);
1694                    stop
1695                })
1696                .collect(),
1697            center_x,
1698            center_y,
1699        };
1700
1701        processor.process_conic_gradient(clipped.cast(), conic_grad);
1702        Color::default()
1703    } else {
1704        alpha_color(args.background.color(), args.alpha)
1705    };
1706
1707    let mut border_color =
1708        PremultipliedRgbaColor::from(alpha_color(args.border.color(), args.alpha));
1709    let color = PremultipliedRgbaColor::from(color);
1710    let mut border = PhysicalLength::new(args.border_width as _);
1711    if border_color.alpha == 0 {
1712        border = PhysicalLength::new(0);
1713    } else if border_color.alpha < 255 {
1714        // Find a color for the border which is an equivalent to blend the background and then the border.
1715        // In the end, the resulting of blending the background and the color is
1716        // (A + B) + C, where A is the buffer color, B is the background, and C is the border.
1717        // which expands to (A*(1-Bα) + B*Bα)*(1-Cα) + C*Cα = A*(1-(Bα+Cα-Bα*Cα)) + B*Bα*(1-Cα) + C*Cα
1718        // so let the new alpha be: Nα = Bα+Cα-Bα*Cα, then this is A*(1-Nα) + N*Nα
1719        // with N = (B*Bα*(1-Cα) + C*Cα)/Nα
1720        // N being the equivalent color of the border that mixes the background and the border
1721        // In pre-multiplied space, the formula simplifies further N' = B'*(1-Cα) + C'
1722        let b = border_color;
1723        let b_alpha_16 = b.alpha as u16;
1724        border_color = PremultipliedRgbaColor {
1725            red: ((color.red as u16 * (255 - b_alpha_16)) / 255) as u8 + b.red,
1726            green: ((color.green as u16 * (255 - b_alpha_16)) / 255) as u8 + b.green,
1727            blue: ((color.blue as u16 * (255 - b_alpha_16)) / 255) as u8 + b.blue,
1728            alpha: (color.alpha as u16 + b_alpha_16 - (color.alpha as u16 * b_alpha_16) / 255)
1729                as u8,
1730        }
1731    }
1732
1733    let radius = PhysicalBorderRadius {
1734        top_left: args.top_left_radius as _,
1735        top_right: args.top_right_radius as _,
1736        bottom_right: args.bottom_right_radius as _,
1737        bottom_left: args.bottom_left_radius as _,
1738        _unit: Default::default(),
1739    };
1740
1741    if !radius.is_zero() {
1742        // Add a small value to make sure that the clip is always positive despite floating point shenanigans
1743        const E: f32 = 0.00001;
1744
1745        processor.process_rounded_rectangle(
1746            clipped.round().cast(),
1747            RoundedRectangle {
1748                radius,
1749                width: border,
1750                border_color,
1751                inner_color: color,
1752                top_clip: PhysicalLength::new((clipped.min_y() - geom.min_y() + E) as _),
1753                bottom_clip: PhysicalLength::new((geom.max_y() - clipped.max_y() + E) as _),
1754                left_clip: PhysicalLength::new((clipped.min_x() - geom.min_x() + E) as _),
1755                right_clip: PhysicalLength::new((geom.max_x() - clipped.max_x() + E) as _),
1756            },
1757        );
1758        return;
1759    }
1760
1761    if color.alpha > 0
1762        && let Some(r) =
1763            geom.round().cast().inflate(-border.get(), -border.get()).intersection(clip)
1764    {
1765        processor.process_simple_rectangle(r, color);
1766    }
1767
1768    if border_color.alpha > 0 {
1769        let mut add_border = |r: PhysicalRect| {
1770            if let Some(r) = r.intersection(clip) {
1771                processor.process_simple_rectangle(r, border_color);
1772            }
1773        };
1774        let b = border.get();
1775        let g = geom.round().cast();
1776        add_border(euclid::rect(g.min_x(), g.min_y(), g.width(), b));
1777        add_border(euclid::rect(g.min_x(), g.min_y() + g.height() - b, g.width(), b));
1778        add_border(euclid::rect(g.min_x(), g.min_y() + b, b, g.height() - b - b));
1779        add_border(euclid::rect(g.min_x() + g.width() - b, g.min_y() + b, b, g.height() - b - b));
1780    }
1781}
1782
1783struct RenderToBuffer<'a, TargetPixelBuffer> {
1784    buffer: &'a mut TargetPixelBuffer,
1785    dirty_range_cache: Vec<core::ops::Range<i16>>,
1786    dirty_region: PhysicalRegion,
1787    scale_factor: ScaleFactor,
1788}
1789
1790impl<B: target_pixel_buffer::TargetPixelBuffer> RenderToBuffer<'_, B> {
1791    fn foreach_ranges(
1792        &mut self,
1793        geometry: &PhysicalRect,
1794        mut f: impl FnMut(i16, &mut [B::TargetPixel], i16, i16),
1795    ) {
1796        let mut line = geometry.min_y();
1797        while let Some(mut next) =
1798            region_line_ranges(&self.dirty_region, line, &mut self.dirty_range_cache)
1799        {
1800            next = next.min(geometry.max_y());
1801            for r in &self.dirty_range_cache {
1802                if geometry.origin.x >= r.end {
1803                    continue;
1804                }
1805                let begin = r.start.max(geometry.origin.x);
1806                let end = r.end.min(geometry.origin.x + geometry.size.width);
1807                if begin >= end {
1808                    continue;
1809                }
1810                let extra_left_clip = begin - geometry.origin.x;
1811                let extra_right_clip = geometry.origin.x + geometry.size.width - end;
1812
1813                let region = PhysicalRect {
1814                    origin: PhysicalPoint::new(begin, line),
1815                    size: PhysicalSize::new(end - begin, next - line),
1816                };
1817
1818                for l in region.y_range() {
1819                    f(
1820                        l,
1821                        &mut self.buffer.line_slice(l as usize)
1822                            [region.min_x() as usize..region.max_x() as usize],
1823                        extra_left_clip,
1824                        extra_right_clip,
1825                    );
1826                }
1827            }
1828            if next == geometry.max_y() {
1829                break;
1830            }
1831            line = next;
1832        }
1833    }
1834
1835    fn process_texture_impl(&mut self, geometry: PhysicalRect, texture: SceneTexture<'_>) {
1836        self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
1837            draw_functions::draw_texture_line(
1838                &geometry,
1839                PhysicalLength::new(line),
1840                &texture,
1841                buffer,
1842                extra_left_clip,
1843                extra_right_clip,
1844            );
1845        });
1846    }
1847}
1848
1849impl<B: target_pixel_buffer::TargetPixelBuffer> ProcessScene for RenderToBuffer<'_, B> {
1850    fn process_scene_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
1851        self.process_texture_impl(geometry, texture);
1852    }
1853
1854    fn process_target_texture(
1855        &mut self,
1856        texture: &target_pixel_buffer::DrawTextureArgs,
1857        clip: PhysicalRect,
1858    ) {
1859        if self.buffer.draw_texture(texture, &self.dirty_region.intersection(&clip)) {
1860            return;
1861        }
1862
1863        let Some((texture, geometry)) = SceneTexture::from_target_texture(texture, &clip) else {
1864            return;
1865        };
1866
1867        self.process_texture_impl(geometry, texture);
1868    }
1869
1870    fn process_rectangle(
1871        &mut self,
1872        args: &target_pixel_buffer::DrawRectangleArgs,
1873        clip: PhysicalRect,
1874    ) {
1875        if self.buffer.draw_rectangle(args, &self.dirty_region.intersection(&clip)) {
1876            return;
1877        }
1878
1879        let scale_factor = self.scale_factor;
1880        process_rectangle_impl(self, args, &clip, scale_factor);
1881    }
1882
1883    fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, rr: RoundedRectangle) {
1884        self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
1885            draw_functions::draw_rounded_rectangle_line(
1886                &geometry,
1887                PhysicalLength::new(line),
1888                &rr,
1889                buffer,
1890                extra_left_clip,
1891                extra_right_clip,
1892            );
1893        });
1894    }
1895
1896    fn process_simple_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
1897        self.foreach_ranges(&geometry, |_line, buffer, _extra_left_clip, _extra_right_clip| {
1898            <B::TargetPixel>::blend_slice(buffer, color)
1899        });
1900    }
1901
1902    fn process_linear_gradient(&mut self, geometry: PhysicalRect, g: LinearGradientCommand) {
1903        self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, _extra_right_clip| {
1904            draw_functions::draw_linear_gradient(
1905                &geometry,
1906                PhysicalLength::new(line),
1907                &g,
1908                buffer,
1909                extra_left_clip,
1910            );
1911        });
1912    }
1913    fn process_radial_gradient(&mut self, geometry: PhysicalRect, g: RadialGradientCommand) {
1914        self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
1915            draw_functions::draw_radial_gradient(
1916                &geometry,
1917                PhysicalLength::new(line),
1918                &g,
1919                buffer,
1920                extra_left_clip,
1921                extra_right_clip,
1922            );
1923        });
1924    }
1925    fn process_conic_gradient(&mut self, geometry: PhysicalRect, g: ConicGradientCommand) {
1926        self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
1927            draw_functions::draw_conic_gradient(
1928                &geometry,
1929                PhysicalLength::new(line),
1930                &g,
1931                buffer,
1932                extra_left_clip,
1933                extra_right_clip,
1934            );
1935        });
1936    }
1937
1938    #[cfg(feature = "path")]
1939    fn process_filled_path(
1940        &mut self,
1941        path_geometry: PhysicalRect,
1942        clip_geometry: PhysicalRect,
1943        commands: alloc::vec::Vec<path::Command>,
1944        color: PremultipliedRgbaColor,
1945    ) {
1946        path::render_filled_path(&commands, &path_geometry, &clip_geometry, color, self.buffer);
1947    }
1948
1949    #[cfg(feature = "path")]
1950    fn process_stroked_path(
1951        &mut self,
1952        path_geometry: PhysicalRect,
1953        clip_geometry: PhysicalRect,
1954        commands: alloc::vec::Vec<path::Command>,
1955        color: PremultipliedRgbaColor,
1956        stroke_width: f32,
1957        stroke_line_cap: i_slint_core::items::LineCap,
1958        stroke_line_join: i_slint_core::items::LineJoin,
1959        stroke_miter_limit: f32,
1960    ) {
1961        path::render_stroked_path(
1962            &commands,
1963            &path_geometry,
1964            &clip_geometry,
1965            color,
1966            stroke_width,
1967            stroke_line_cap,
1968            stroke_line_join,
1969            stroke_miter_limit,
1970            self.buffer,
1971        );
1972    }
1973}
1974
1975#[derive(Default)]
1976struct PrepareScene {
1977    items: Vec<SceneItem>,
1978    vectors: SceneVectors,
1979    scale_factor: ScaleFactor,
1980}
1981
1982impl ProcessScene for PrepareScene {
1983    fn process_scene_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
1984        let texture_index = self.vectors.textures.len() as u16;
1985        self.vectors.textures.push(texture);
1986        self.items.push(SceneItem {
1987            pos: geometry.origin,
1988            size: geometry.size,
1989            z: self.items.len() as u16,
1990            command: SceneCommand::Texture { texture_index },
1991        });
1992    }
1993
1994    fn process_target_texture(
1995        &mut self,
1996        texture: &target_pixel_buffer::DrawTextureArgs,
1997        clip: PhysicalRect,
1998    ) {
1999        let Some((extra, geometry)) = SceneTextureExtra::from_target_texture(texture, &clip) else {
2000            return;
2001        };
2002        match &texture.data {
2003            target_pixel_buffer::TextureDataContainer::Static(texture_data) => {
2004                let texture_index = self.vectors.textures.len() as u16;
2005                let pixel_stride =
2006                    (texture_data.byte_stride / texture_data.pixel_format.bpp()) as u16;
2007                self.vectors.textures.push(SceneTexture {
2008                    data: texture_data.data,
2009                    format: texture_data.pixel_format,
2010                    pixel_stride,
2011                    extra,
2012                });
2013                self.items.push(SceneItem {
2014                    pos: geometry.origin,
2015                    size: geometry.size,
2016                    z: self.items.len() as u16,
2017                    command: SceneCommand::Texture { texture_index },
2018                });
2019            }
2020            target_pixel_buffer::TextureDataContainer::Shared { buffer, source_rect } => {
2021                let shared_buffer_index = self.vectors.shared_buffers.len() as u16;
2022                self.vectors.shared_buffers.push(SharedBufferCommand {
2023                    buffer: buffer.clone(),
2024                    source_rect: *source_rect,
2025                    extra,
2026                });
2027                self.items.push(SceneItem {
2028                    pos: geometry.origin,
2029                    size: geometry.size,
2030                    z: self.items.len() as u16,
2031                    command: SceneCommand::SharedBuffer { shared_buffer_index },
2032                });
2033            }
2034        }
2035    }
2036
2037    fn process_rectangle(
2038        &mut self,
2039        args: &target_pixel_buffer::DrawRectangleArgs,
2040        clip: PhysicalRect,
2041    ) {
2042        let scale_factor = self.scale_factor;
2043        process_rectangle_impl(self, args, &clip, scale_factor);
2044    }
2045
2046    fn process_simple_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
2047        let size = geometry.size;
2048        if !size.is_empty() {
2049            let z = self.items.len() as u16;
2050            let pos = geometry.origin;
2051            self.items.push(SceneItem { pos, size, z, command: SceneCommand::Rectangle { color } });
2052        }
2053    }
2054
2055    fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle) {
2056        let size = geometry.size;
2057        if !size.is_empty() {
2058            let rectangle_index = self.vectors.rounded_rectangles.len() as u16;
2059            self.vectors.rounded_rectangles.push(data);
2060            self.items.push(SceneItem {
2061                pos: geometry.origin,
2062                size,
2063                z: self.items.len() as u16,
2064                command: SceneCommand::RoundedRectangle { rectangle_index },
2065            });
2066        }
2067    }
2068
2069    fn process_linear_gradient(&mut self, geometry: PhysicalRect, gradient: LinearGradientCommand) {
2070        let size = geometry.size;
2071        if !size.is_empty() {
2072            let gradient_index = self.vectors.linear_gradients.len() as u16;
2073            self.vectors.linear_gradients.push(gradient);
2074            self.items.push(SceneItem {
2075                pos: geometry.origin,
2076                size,
2077                z: self.items.len() as u16,
2078                command: SceneCommand::LinearGradient { linear_gradient_index: gradient_index },
2079            });
2080        }
2081    }
2082    fn process_radial_gradient(&mut self, geometry: PhysicalRect, gradient: RadialGradientCommand) {
2083        let size = geometry.size;
2084        if !size.is_empty() {
2085            let radial_gradient_index = self.vectors.radial_gradients.len() as u16;
2086            self.vectors.radial_gradients.push(gradient);
2087            self.items.push(SceneItem {
2088                pos: geometry.origin,
2089                size,
2090                z: self.items.len() as u16,
2091                command: SceneCommand::RadialGradient { radial_gradient_index },
2092            });
2093        }
2094    }
2095    fn process_conic_gradient(&mut self, geometry: PhysicalRect, gradient: ConicGradientCommand) {
2096        let size = geometry.size;
2097        if !size.is_empty() {
2098            let conic_gradient_index = self.vectors.conic_gradients.len() as u16;
2099            self.vectors.conic_gradients.push(gradient);
2100            self.items.push(SceneItem {
2101                pos: geometry.origin,
2102                size,
2103                z: self.items.len() as u16,
2104                command: SceneCommand::ConicGradient { conic_gradient_index },
2105            });
2106        }
2107    }
2108
2109    #[cfg(feature = "path")]
2110    fn process_filled_path(
2111        &mut self,
2112        _path_geometry: PhysicalRect,
2113        _clip_geometry: PhysicalRect,
2114        _commands: alloc::vec::Vec<path::Command>,
2115        _color: PremultipliedRgbaColor,
2116    ) {
2117        // Path rendering is not supported in line-by-line mode (PrepareScene/render_by_line)
2118        // Only works with buffer-based rendering (RenderToBuffer)
2119    }
2120
2121    #[cfg(feature = "path")]
2122    fn process_stroked_path(
2123        &mut self,
2124        _path_geometry: PhysicalRect,
2125        _clip_geometry: PhysicalRect,
2126        _commands: alloc::vec::Vec<path::Command>,
2127        _color: PremultipliedRgbaColor,
2128        _stroke_width: f32,
2129        _stroke_line_cap: i_slint_core::items::LineCap,
2130        _stroke_line_join: i_slint_core::items::LineJoin,
2131        _stroke_miter_limit: f32,
2132    ) {
2133        // Path rendering is not supported in line-by-line mode (PrepareScene/render_by_line)
2134        // Only works with buffer-based rendering (RenderToBuffer)
2135    }
2136}
2137
2138struct SceneBuilder<'a, T> {
2139    processor: T,
2140    state_stack: Vec<RenderState>,
2141    current_state: RenderState,
2142    scale_factor: ScaleFactor,
2143    window: &'a WindowInner,
2144    rotation: RotationInfo,
2145    #[cfg(feature = "systemfonts")]
2146    text_layout_cache: &'a sharedparley::TextLayoutCache,
2147}
2148
2149impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
2150    fn new(
2151        screen_size: PhysicalSize,
2152        scale_factor: ScaleFactor,
2153        window: &'a WindowInner,
2154        processor: T,
2155        orientation: RenderingRotation,
2156        #[cfg(feature = "systemfonts")] text_layout_cache: &'a sharedparley::TextLayoutCache,
2157    ) -> Self {
2158        Self {
2159            processor,
2160            state_stack: Vec::new(),
2161            current_state: RenderState {
2162                alpha: 1.,
2163                offset: LogicalPoint::default(),
2164                clip: LogicalRect::new(
2165                    LogicalPoint::default(),
2166                    (screen_size.cast() / scale_factor).cast(),
2167                ),
2168            },
2169            scale_factor,
2170            window,
2171            rotation: RotationInfo { orientation, screen_size },
2172            #[cfg(feature = "systemfonts")]
2173            text_layout_cache,
2174        }
2175    }
2176
2177    fn should_draw(&self, rect: &LogicalRect) -> bool {
2178        !rect.size.is_empty()
2179            && self.current_state.alpha > 0.01
2180            && self.current_state.clip.intersects(rect)
2181    }
2182
2183    fn draw_image_impl(
2184        &mut self,
2185        image_inner: &ImageInner,
2186        i_slint_core::graphics::FitResult {
2187            clip_rect: source_rect,
2188            source_to_target_x,
2189            source_to_target_y,
2190            size: fit_size,
2191            offset: image_fit_offset,
2192            tiled,
2193        }: i_slint_core::graphics::FitResult,
2194        colorize: Color,
2195    ) {
2196        let global_alpha_u16 = (self.current_state.alpha * 255.) as u16;
2197        let offset =
2198            self.current_state.offset.cast() * self.scale_factor + image_fit_offset.to_vector();
2199
2200        let physical_clip =
2201            (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
2202                * self.scale_factor)
2203                .round()
2204                .cast()
2205                .transformed(self.rotation);
2206
2207        match image_inner {
2208            ImageInner::None => (),
2209            ImageInner::StaticTextures(StaticTextures {
2210                data,
2211                textures,
2212                size,
2213                original_size,
2214                ..
2215            }) => {
2216                let adjust_x = size.width as f32 / original_size.width as f32;
2217                let adjust_y = size.height as f32 / original_size.height as f32;
2218                let source_to_target_x = source_to_target_x / adjust_x;
2219                let source_to_target_y = source_to_target_y / adjust_y;
2220                let source_rect =
2221                    source_rect.cast::<f32>().scale(adjust_x, adjust_y).round().to_box2d().cast();
2222
2223                for t in textures.as_slice() {
2224                    let t_rect = t.rect.to_box2d();
2225                    // That's the source rect in the whole image coordinate
2226                    let Some(src_rect) = t_rect.intersection(&source_rect) else { continue };
2227
2228                    let target_rect = if tiled.is_some() {
2229                        euclid::Rect::new(offset, fit_size).round().cast::<i32>()
2230                    } else {
2231                        // The slice maps onto the fit rect `offset ..= offset + fit_size`;
2232                        // this texture only covers `src_rect` of the slice's `source_rect`, so
2233                        // inset each edge by the uncovered source amount. Edges reaching the
2234                        // slice boundary keep the exact fit rect, so abutting slices share a
2235                        // seamless edge (scaling each from its source extent drifted apart).
2236                        let inset = |a: i32, b: i32, s2t: f32| (a - b) as f32 * s2t;
2237                        euclid::Box2D::<f32, PhysicalPx>::new(
2238                            euclid::point2(
2239                                offset.x
2240                                    + inset(src_rect.min.x, source_rect.min.x, source_to_target_x),
2241                                offset.y
2242                                    + inset(src_rect.min.y, source_rect.min.y, source_to_target_y),
2243                            ),
2244                            euclid::point2(
2245                                offset.x + fit_size.width
2246                                    - inset(source_rect.max.x, src_rect.max.x, source_to_target_x),
2247                                offset.y + fit_size.height
2248                                    - inset(source_rect.max.y, src_rect.max.y, source_to_target_y),
2249                            ),
2250                        )
2251                        .round()
2252                        .to_rect()
2253                        .cast::<i32>()
2254                    };
2255                    let target_rect = target_rect.transformed(self.rotation).round();
2256
2257                    let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
2258                        continue;
2259                    };
2260
2261                    let pixel_stride = t.rect.width() as usize;
2262                    let core::ops::Range { start, end } = compute_range_in_buffer(
2263                        &PhysicalRect::from_untyped(
2264                            &src_rect.to_rect().translate(-t.rect.origin.to_vector()).cast(),
2265                        ),
2266                        pixel_stride,
2267                    );
2268                    let bpp = t.format.bpp();
2269
2270                    let color = if colorize.alpha() > 0 { colorize } else { t.color };
2271                    let alpha = if colorize.alpha() > 0 || t.format == TexturePixelFormat::AlphaMap
2272                    {
2273                        color.alpha() as u16 * global_alpha_u16 / 255
2274                    } else {
2275                        global_alpha_u16
2276                    } as u8;
2277
2278                    let tiling = tiled.map(|tile_o| {
2279                        let src_o = src_rect.min - source_rect.min;
2280                        let gap = (src_o) + (source_rect.max - src_rect.max);
2281                        target_pixel_buffer::TilingInfo {
2282                            offset_x: ((src_o.x as f32 - tile_o.x as f32) * source_to_target_x)
2283                                .round() as _,
2284                            offset_y: ((src_o.y as f32 - tile_o.y as f32) * source_to_target_y)
2285                                .round() as _,
2286                            scale_x: 1. / source_to_target_x,
2287                            scale_y: 1. / source_to_target_y,
2288                            gap_x: (gap.x as f32 * source_to_target_x).round() as _,
2289                            gap_y: (gap.y as f32 * source_to_target_y).round() as _,
2290                        }
2291                    });
2292
2293                    let t = target_pixel_buffer::DrawTextureArgs {
2294                        data: target_pixel_buffer::TextureDataContainer::Static(
2295                            target_pixel_buffer::TextureData::new(
2296                                &data.as_slice()[t.index..][start * bpp..end * bpp],
2297                                t.format,
2298                                pixel_stride * bpp,
2299                                src_rect.size().cast(),
2300                            ),
2301                        ),
2302                        colorize: (color.alpha() > 0).then_some(color),
2303                        alpha,
2304                        dst_x: target_rect.origin.x as _,
2305                        dst_y: target_rect.origin.y as _,
2306                        dst_width: target_rect.size.width as _,
2307                        dst_height: target_rect.size.height as _,
2308                        rotation: self.rotation.orientation,
2309                        tiling,
2310                    };
2311
2312                    self.processor.process_target_texture(&t, clipped_target.cast());
2313                }
2314            }
2315
2316            ImageInner::NineSlice(..) => unreachable!(),
2317            _ => {
2318                let target_rect =
2319                    euclid::Rect::new(offset, fit_size).round().cast().transformed(self.rotation);
2320                let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
2321                    return;
2322                };
2323
2324                let orig = image_inner.size().cast::<f32>();
2325                let svg_target_size = if tiled.is_some() {
2326                    euclid::size2(orig.width * source_to_target_x, orig.height * source_to_target_y)
2327                        .round()
2328                        .cast()
2329                } else {
2330                    target_rect.size.cast()
2331                };
2332                if let Some(buffer) = image_inner.render_to_buffer(Some(svg_target_size)) {
2333                    let buf_size = buffer.size().cast::<f32>();
2334
2335                    let alpha = if colorize.alpha() > 0 {
2336                        colorize.alpha() as u16 * global_alpha_u16 / 255
2337                    } else {
2338                        global_alpha_u16
2339                    } as u8;
2340
2341                    let tiling = tiled.map(|tile_o| target_pixel_buffer::TilingInfo {
2342                        offset_x: (tile_o.x as f32 * -source_to_target_x).round() as _,
2343                        offset_y: (tile_o.y as f32 * -source_to_target_y).round() as _,
2344                        scale_x: 1. / source_to_target_x,
2345                        scale_y: 1. / source_to_target_y,
2346                        gap_x: 0,
2347                        gap_y: 0,
2348                    });
2349
2350                    let t = target_pixel_buffer::DrawTextureArgs {
2351                        data: target_pixel_buffer::TextureDataContainer::Shared {
2352                            buffer: SharedBufferData::SharedImage(buffer),
2353                            source_rect: PhysicalRect::from_untyped(
2354                                &source_rect
2355                                    .cast::<f32>()
2356                                    .scale(
2357                                        buf_size.width / orig.width,
2358                                        buf_size.height / orig.height,
2359                                    )
2360                                    .round()
2361                                    .cast(),
2362                            ),
2363                        },
2364                        colorize: (colorize.alpha() > 0).then_some(colorize),
2365                        alpha,
2366                        dst_x: target_rect.origin.x as _,
2367                        dst_y: target_rect.origin.y as _,
2368                        dst_width: target_rect.size.width as _,
2369                        dst_height: target_rect.size.height as _,
2370                        rotation: self.rotation.orientation,
2371                        tiling,
2372                    };
2373
2374                    self.processor.process_target_texture(&t, clipped_target.cast());
2375                } else {
2376                    unimplemented!("The image cannot be rendered")
2377                }
2378            }
2379        };
2380    }
2381
2382    fn draw_text_paragraph<Font>(
2383        &mut self,
2384        paragraph: &TextParagraphLayout<'_, Font>,
2385        physical_clip: euclid::Rect<f32, PhysicalPx>,
2386        offset: euclid::Vector2D<f32, PhysicalPx>,
2387        color: Color,
2388        selection: Option<SelectionInfo>,
2389    ) where
2390        Font: AbstractFont
2391            + i_slint_core::textlayout::TextShaper<Length = PhysicalLength>
2392            + GlyphRenderer,
2393    {
2394        let slint_context = self.window.context();
2395        paragraph
2396            .layout_lines::<()>(
2397                |glyphs, line_x, line_y, _, sel| {
2398                    let baseline_y = line_y + paragraph.layout.font.ascent();
2399                    if let (Some(sel), Some(selection)) = (sel, &selection) {
2400                        let geometry = euclid::rect(
2401                            line_x.get() + sel.start.get(),
2402                            line_y.get(),
2403                            (sel.end - sel.start).get(),
2404                            paragraph.layout.font.height().get(),
2405                        );
2406                        if let Some(clipped_src) = geometry.intersection(&physical_clip.cast()) {
2407                            let geometry =
2408                                clipped_src.translate(offset.cast()).transformed(self.rotation);
2409                            let args = target_pixel_buffer::DrawRectangleArgs::from_rect(
2410                                geometry.cast(),
2411                                selection.selection_background.into(),
2412                            );
2413                            self.processor.process_rectangle(&args, geometry);
2414                        }
2415                    }
2416                    let scale_delta = paragraph.layout.font.scale_delta();
2417                    for positioned_glyph in glyphs {
2418                        let Some(glyph) = paragraph
2419                            .layout
2420                            .font
2421                            .render_glyph(positioned_glyph.glyph_id, slint_context)
2422                        else {
2423                            continue;
2424                        };
2425
2426                        let gl_x = PhysicalLength::new((-glyph.x).truncate() as i16);
2427                        let gl_y = PhysicalLength::new(glyph.y.truncate() as i16);
2428                        let target_rect = PhysicalRect::new(
2429                            PhysicalPoint::from_lengths(
2430                                line_x + positioned_glyph.x - gl_x,
2431                                baseline_y - gl_y - glyph.height,
2432                            ),
2433                            glyph.size(),
2434                        )
2435                        .cast();
2436
2437                        let color = match &selection {
2438                            Some(s) if s.selection.contains(&positioned_glyph.text_byte_offset) => {
2439                                s.selection_color
2440                            }
2441                            _ => color,
2442                        };
2443
2444                        let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
2445                            continue;
2446                        };
2447
2448                        let data = match &glyph.alpha_map {
2449                            fonts::GlyphAlphaMap::Static(data) => {
2450                                if glyph.sdf {
2451                                    let geometry = clipped_target.translate(offset).round();
2452                                    let origin =
2453                                        (geometry.origin - offset.round()).round().cast::<i16>();
2454                                    let off_x = origin.x - target_rect.origin.x as i16;
2455                                    let off_y = origin.y - target_rect.origin.y as i16;
2456                                    let pixel_stride = glyph.pixel_stride;
2457                                    let mut geometry = geometry.cast();
2458                                    if geometry.size.width > glyph.width.get() - off_x {
2459                                        geometry.size.width = glyph.width.get() - off_x
2460                                    }
2461                                    if geometry.size.height > glyph.height.get() - off_y {
2462                                        geometry.size.height = glyph.height.get() - off_y
2463                                    }
2464                                    let source_size = geometry.size;
2465                                    if source_size.is_empty() {
2466                                        continue;
2467                                    }
2468
2469                                    let delta32 = Fixed::<i32, 8>::from_fixed(scale_delta);
2470                                    let normalize = |x: Fixed<i32, 8>| {
2471                                        if x < Fixed::from_integer(0) {
2472                                            x + Fixed::from_integer(1)
2473                                        } else {
2474                                            x
2475                                        }
2476                                    };
2477                                    let fract_x = normalize(
2478                                        (-glyph.x) - Fixed::from_integer(gl_x.get() as _),
2479                                    );
2480                                    let off_x = delta32 * off_x as i32 + fract_x;
2481                                    let fract_y =
2482                                        normalize(glyph.y - Fixed::from_integer(gl_y.get() as _));
2483                                    let off_y = delta32 * off_y as i32 + fract_y;
2484                                    let texture = SceneTexture {
2485                                        data,
2486                                        pixel_stride,
2487                                        format: TexturePixelFormat::SignedDistanceField,
2488                                        extra: SceneTextureExtra {
2489                                            colorize: color,
2490                                            // color already is mixed with global alpha
2491                                            alpha: color.alpha(),
2492                                            rotation: self.rotation.orientation,
2493                                            dx: scale_delta,
2494                                            dy: scale_delta,
2495                                            off_x: Fixed::try_from_fixed(off_x).unwrap(),
2496                                            off_y: Fixed::try_from_fixed(off_y).unwrap(),
2497                                        },
2498                                    };
2499                                    self.processor.process_scene_texture(
2500                                        geometry.transformed(self.rotation),
2501                                        texture,
2502                                    );
2503                                    continue;
2504                                };
2505
2506                                target_pixel_buffer::TextureDataContainer::Static(
2507                                    target_pixel_buffer::TextureData::new(
2508                                        data,
2509                                        TexturePixelFormat::AlphaMap,
2510                                        glyph.pixel_stride as usize,
2511                                        euclid::size2(glyph.width.get(), glyph.height.get()).cast(),
2512                                    ),
2513                                )
2514                            }
2515                            fonts::GlyphAlphaMap::Shared(data) => {
2516                                let source_rect = euclid::rect(0, 0, glyph.width.0, glyph.height.0);
2517                                target_pixel_buffer::TextureDataContainer::Shared {
2518                                    buffer: SharedBufferData::AlphaMap {
2519                                        data: data.clone(),
2520                                        width: glyph.pixel_stride,
2521                                    },
2522                                    source_rect,
2523                                }
2524                            }
2525                        };
2526                        let clipped_target =
2527                            clipped_target.translate(offset).round().transformed(self.rotation);
2528                        let target_rect =
2529                            target_rect.translate(offset).round().transformed(self.rotation);
2530                        let t = target_pixel_buffer::DrawTextureArgs {
2531                            data,
2532                            colorize: Some(color),
2533                            // color already is mixed with global alpha
2534                            alpha: color.alpha(),
2535                            dst_x: target_rect.origin.x as _,
2536                            dst_y: target_rect.origin.y as _,
2537                            dst_width: target_rect.size.width as _,
2538                            dst_height: target_rect.size.height as _,
2539                            rotation: self.rotation.orientation,
2540                            tiling: None,
2541                        };
2542
2543                        self.processor.process_target_texture(&t, clipped_target.cast());
2544                    }
2545                    core::ops::ControlFlow::Continue(())
2546                },
2547                selection.as_ref().map(|s| s.selection.clone()),
2548            )
2549            .ok();
2550    }
2551
2552    /// Returns the color, mixed with the current_state's alpha
2553    fn alpha_color(&self, color: Color) -> Color {
2554        if self.current_state.alpha < 1.0 {
2555            Color::from_argb_u8(
2556                (color.alpha() as f32 * self.current_state.alpha) as u8,
2557                color.red(),
2558                color.green(),
2559                color.blue(),
2560            )
2561        } else {
2562            color
2563        }
2564    }
2565}
2566
2567fn alpha_color(color: Color, alpha: u8) -> Color {
2568    if alpha < 255 {
2569        Color::from_argb_u8(
2570            ((color.alpha() as u32 * alpha as u32) / 255) as u8,
2571            color.red(),
2572            color.green(),
2573            color.blue(),
2574        )
2575    } else {
2576        color
2577    }
2578}
2579
2580struct SelectionInfo {
2581    selection_color: Color,
2582    selection_background: Color,
2583    selection: core::ops::Range<usize>,
2584}
2585
2586#[derive(Clone, Copy, Debug)]
2587struct RenderState {
2588    alpha: f32,
2589    offset: LogicalPoint,
2590    clip: LogicalRect,
2591}
2592
2593impl<T: ProcessScene> i_slint_core::item_rendering::ItemRenderer for SceneBuilder<'_, T> {
2594    fn draw_rectangle(
2595        &mut self,
2596        rect: Pin<&dyn RenderRectangle>,
2597        _: &ItemRc,
2598        size: LogicalSize,
2599        _cache: &CachedRenderingData,
2600    ) {
2601        let geom = LogicalRect::from(size);
2602        if self.should_draw(&geom) {
2603            let geom = (geom.translate(self.current_state.offset.to_vector()).cast()
2604                * self.scale_factor)
2605                .transformed(self.rotation);
2606
2607            let clipped =
2608                (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
2609                    * self.scale_factor)
2610                    .round()
2611                    .cast()
2612                    .transformed(self.rotation);
2613
2614            let mut args =
2615                target_pixel_buffer::DrawRectangleArgs::from_rect(geom, rect.background());
2616            args.alpha = (self.current_state.alpha * 255.) as u8;
2617            args.rotation = self.rotation.orientation;
2618            self.processor.process_rectangle(&args, clipped);
2619        }
2620    }
2621
2622    fn draw_border_rectangle(
2623        &mut self,
2624        rect: Pin<&dyn RenderBorderRectangle>,
2625        _: &ItemRc,
2626        size: LogicalSize,
2627        _: &CachedRenderingData,
2628    ) {
2629        let geom = LogicalRect::from(size);
2630        if self.should_draw(&geom) {
2631            let geom = (geom.translate(self.current_state.offset.to_vector()).cast()
2632                * self.scale_factor)
2633                .transformed(self.rotation);
2634
2635            let clipped =
2636                (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
2637                    * self.scale_factor)
2638                    .round()
2639                    .cast()
2640                    .transformed(self.rotation);
2641
2642            let radius = (rect.border_radius().cast() * self.scale_factor)
2643                .transformed(self.rotation)
2644                .min(BorderRadius::from_length(geom.width_length() / 2.))
2645                .min(BorderRadius::from_length(geom.height_length() / 2.));
2646
2647            let border = rect.border_width().cast() * self.scale_factor;
2648            let border_color =
2649                if border.get() > 0.01 { rect.border_color() } else { Default::default() };
2650
2651            let args = target_pixel_buffer::DrawRectangleArgs {
2652                x: geom.origin.x,
2653                y: geom.origin.y,
2654                width: geom.size.width,
2655                height: geom.size.height,
2656                top_left_radius: radius.top_left,
2657                top_right_radius: radius.top_right,
2658                bottom_right_radius: radius.bottom_right,
2659                bottom_left_radius: radius.bottom_left,
2660                border_width: border.get(),
2661                background: rect.background(),
2662                border: border_color,
2663                alpha: (self.current_state.alpha * 255.) as u8,
2664                rotation: self.rotation.orientation,
2665            };
2666
2667            self.processor.process_rectangle(&args, clipped);
2668        }
2669    }
2670
2671    fn draw_window_background(
2672        &mut self,
2673        rect: Pin<&dyn RenderRectangle>,
2674        _self_rc: &ItemRc,
2675        _size: LogicalSize,
2676        _cache: &CachedRenderingData,
2677    ) {
2678        // register a dependency for the partial renderer's dirty tracker. The actual rendering is done earlier in the software renderer.
2679        let _ = rect.background();
2680    }
2681
2682    fn draw_image(
2683        &mut self,
2684        image: Pin<&dyn RenderImage>,
2685        _: &ItemRc,
2686        size: LogicalSize,
2687        _: &CachedRenderingData,
2688    ) {
2689        let geom = LogicalRect::from(size);
2690        if self.should_draw(&geom) {
2691            let source = image.source();
2692
2693            let image_inner: &ImageInner = (&source).into();
2694            if let ImageInner::NineSlice(nine) = image_inner {
2695                let colorize = image.colorize().color();
2696                let source_size = source.size();
2697                for fit in i_slint_core::graphics::fit9slice(
2698                    source_size,
2699                    nine.1,
2700                    size.cast() * self.scale_factor,
2701                    self.scale_factor,
2702                    image.alignment(),
2703                    image.tiling(),
2704                ) {
2705                    self.draw_image_impl(&nine.0, fit, colorize);
2706                }
2707                return;
2708            }
2709
2710            let source_clip = image.source_clip().map_or_else(
2711                || euclid::Rect::new(Default::default(), source.size().cast()),
2712                |clip| {
2713                    clip.intersection(&euclid::Rect::from_size(source.size().cast()))
2714                        .unwrap_or_default()
2715                },
2716            );
2717
2718            let phys_size = geom.size_length().cast() * self.scale_factor;
2719            let fit = i_slint_core::graphics::fit(
2720                image.image_fit(),
2721                phys_size,
2722                source_clip,
2723                self.scale_factor,
2724                image.alignment(),
2725                image.tiling(),
2726            );
2727            self.draw_image_impl(image_inner, fit, image.colorize().color());
2728        }
2729    }
2730
2731    fn draw_text(
2732        &mut self,
2733        text: Pin<&dyn i_slint_core::item_rendering::RenderText>,
2734        self_rc: &ItemRc,
2735        size: LogicalSize,
2736        _cache: &CachedRenderingData,
2737    ) {
2738        let font_request = text.font_request(self_rc);
2739
2740        #[cfg(feature = "systemfonts")]
2741        let mut font_ctx = self.window.context().font_context().borrow_mut();
2742        let font = fonts::match_font(
2743            &font_request,
2744            self.scale_factor,
2745            #[cfg(feature = "systemfonts")]
2746            &mut font_ctx,
2747        );
2748
2749        #[cfg(feature = "systemfonts")]
2750        if matches!(font, fonts::Font::VectorFont(_)) && !parley_disabled() {
2751            drop(font_ctx);
2752            sharedparley::draw_text(self, text, Some(self_rc), size, Some(self.text_layout_cache));
2753            return;
2754        }
2755
2756        let content = text.text();
2757        let string = match &content {
2758            PlainOrStyledText::Plain(string) => alloc::borrow::Cow::Borrowed(string.as_str()),
2759            PlainOrStyledText::Styled(styled_text) => {
2760                i_slint_core::styled_text::get_raw_text(styled_text)
2761            }
2762        };
2763
2764        if string.trim().is_empty() {
2765            return;
2766        }
2767
2768        let geom = LogicalRect::from(size);
2769        if !self.should_draw(&geom) {
2770            return;
2771        }
2772
2773        let color = self.alpha_color(text.color().color());
2774        let max_size = (geom.size.cast() * self.scale_factor).cast();
2775
2776        // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside
2777        // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below).
2778        // FIXME: we should allow drawing outside of the Text element's boundaries.
2779        let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom)
2780        {
2781            logical_clip.cast() * self.scale_factor
2782        } else {
2783            return; // This should have been caught earlier already
2784        };
2785        let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
2786
2787        let (horizontal_alignment, vertical_alignment) = text.alignment();
2788
2789        match &font {
2790            fonts::Font::PixelFont(pf) => {
2791                let layout = fonts::text_layout_for_font(pf, &font_request, self.scale_factor);
2792                let paragraph = TextParagraphLayout {
2793                    string: &string,
2794                    layout,
2795                    max_width: max_size.width_length(),
2796                    max_height: max_size.height_length(),
2797                    horizontal_alignment,
2798                    vertical_alignment,
2799                    wrap: text.wrap(),
2800                    overflow: text.overflow(),
2801                    single_line: false,
2802                };
2803
2804                self.draw_text_paragraph(&paragraph, physical_clip, offset, color, None);
2805            }
2806            #[cfg(feature = "systemfonts")]
2807            fonts::Font::VectorFont(vf) => {
2808                let layout = fonts::text_layout_for_font(vf, &font_request, self.scale_factor);
2809                let paragraph = TextParagraphLayout {
2810                    string: &string,
2811                    layout,
2812                    max_width: max_size.width_length(),
2813                    max_height: max_size.height_length(),
2814                    horizontal_alignment,
2815                    vertical_alignment,
2816                    wrap: text.wrap(),
2817                    overflow: text.overflow(),
2818                    single_line: false,
2819                };
2820
2821                self.draw_text_paragraph(&paragraph, physical_clip, offset, color, None);
2822            }
2823        };
2824    }
2825
2826    fn draw_text_input(
2827        &mut self,
2828        text_input: Pin<&i_slint_core::items::TextInput>,
2829        self_rc: &ItemRc,
2830        size: LogicalSize,
2831    ) {
2832        let font_request = text_input.font_request(self_rc);
2833        #[cfg(feature = "systemfonts")]
2834        let mut font_ctx = self.window.context().font_context().borrow_mut();
2835        let font = fonts::match_font(
2836            &font_request,
2837            self.scale_factor,
2838            #[cfg(feature = "systemfonts")]
2839            &mut font_ctx,
2840        );
2841
2842        match (font, parley_disabled()) {
2843            #[cfg(feature = "systemfonts")]
2844            (fonts::Font::VectorFont(_), false) => {
2845                drop(font_ctx);
2846                sharedparley::draw_text_input(self, text_input, self_rc, size, None);
2847            }
2848            #[cfg(feature = "systemfonts")]
2849            (fonts::Font::VectorFont(vf), true) => {
2850                let geom = LogicalRect::from(size);
2851                if !self.should_draw(&geom) {
2852                    return;
2853                }
2854
2855                let max_size = (geom.size.cast() * self.scale_factor).cast();
2856
2857                // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside
2858                // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below).
2859                // FIXME: we should allow drawing outside of the Text element's boundaries.
2860                let physical_clip =
2861                    if let Some(logical_clip) = self.current_state.clip.intersection(&geom) {
2862                        logical_clip.cast() * self.scale_factor
2863                    } else {
2864                        return; // This should have been caught earlier already
2865                    };
2866                let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
2867
2868                let text_visual_representation = text_input.visual_representation(None);
2869                let color = self.alpha_color(text_visual_representation.text_color.color());
2870
2871                let selection = (!text_visual_representation.selection_range.is_empty()).then_some(
2872                    SelectionInfo {
2873                        selection_background: self
2874                            .alpha_color(text_input.selection_background_color()),
2875                        selection_color: self.alpha_color(text_input.selection_foreground_color()),
2876                        selection: text_visual_representation.selection_range.clone(),
2877                    },
2878                );
2879
2880                let paragraph = TextParagraphLayout {
2881                    string: &text_visual_representation.text,
2882                    layout: fonts::text_layout_for_font(&vf, &font_request, self.scale_factor),
2883                    max_width: max_size.width_length(),
2884                    max_height: max_size.height_length(),
2885                    horizontal_alignment: text_input.horizontal_alignment(),
2886                    vertical_alignment: text_input.vertical_alignment(),
2887                    wrap: text_input.wrap(),
2888                    overflow: TextOverflow::Clip,
2889                    single_line: text_input.single_line(),
2890                };
2891
2892                self.draw_text_paragraph(&paragraph, physical_clip, offset, color, selection);
2893
2894                let cursor_pos_and_height =
2895                    text_visual_representation.cursor_position.map(|cursor_offset| {
2896                        (paragraph.cursor_pos_for_byte_offset(cursor_offset), vf.height())
2897                    });
2898
2899                if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height {
2900                    let cursor_rect = PhysicalRect::new(
2901                        PhysicalPoint::from_lengths(cursor_x, cursor_y),
2902                        PhysicalSize::from_lengths(
2903                            (text_input.text_cursor_width().cast() * self.scale_factor).cast(),
2904                            cursor_height,
2905                        ),
2906                    );
2907
2908                    if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) {
2909                        let geometry =
2910                            clipped_src.translate(offset.cast()).transformed(self.rotation);
2911                        let args = target_pixel_buffer::DrawRectangleArgs::from_rect(
2912                            geometry.cast(),
2913                            self.alpha_color(text_visual_representation.cursor_color).into(),
2914                        );
2915                        self.processor.process_rectangle(&args, geometry);
2916                    }
2917                }
2918            }
2919            (fonts::Font::PixelFont(pf), _) => {
2920                let geom = LogicalRect::from(size);
2921                if !self.should_draw(&geom) {
2922                    return;
2923                }
2924
2925                let max_size = (geom.size.cast() * self.scale_factor).cast();
2926
2927                // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside
2928                // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below).
2929                // FIXME: we should allow drawing outside of the Text element's boundaries.
2930                let physical_clip =
2931                    if let Some(logical_clip) = self.current_state.clip.intersection(&geom) {
2932                        logical_clip.cast() * self.scale_factor
2933                    } else {
2934                        return; // This should have been caught earlier already
2935                    };
2936                let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
2937
2938                let text_visual_representation = text_input.visual_representation(None);
2939                let color = self.alpha_color(text_visual_representation.text_color.color());
2940
2941                let selection = (!text_visual_representation.selection_range.is_empty()).then_some(
2942                    SelectionInfo {
2943                        selection_background: self
2944                            .alpha_color(text_input.selection_background_color()),
2945                        selection_color: self.alpha_color(text_input.selection_foreground_color()),
2946                        selection: text_visual_representation.selection_range.clone(),
2947                    },
2948                );
2949
2950                let paragraph = TextParagraphLayout {
2951                    string: &text_visual_representation.text,
2952                    layout: fonts::text_layout_for_font(&pf, &font_request, self.scale_factor),
2953                    max_width: max_size.width_length(),
2954                    max_height: max_size.height_length(),
2955                    horizontal_alignment: text_input.horizontal_alignment(),
2956                    vertical_alignment: text_input.vertical_alignment(),
2957                    wrap: text_input.wrap(),
2958                    overflow: TextOverflow::Clip,
2959                    single_line: text_input.single_line(),
2960                };
2961
2962                self.draw_text_paragraph(&paragraph, physical_clip, offset, color, selection);
2963
2964                let cursor_pos_and_height =
2965                    text_visual_representation.cursor_position.map(|cursor_offset| {
2966                        (paragraph.cursor_pos_for_byte_offset(cursor_offset), pf.height())
2967                    });
2968
2969                if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height {
2970                    let cursor_rect = PhysicalRect::new(
2971                        PhysicalPoint::from_lengths(cursor_x, cursor_y),
2972                        PhysicalSize::from_lengths(
2973                            (text_input.text_cursor_width().cast() * self.scale_factor).cast(),
2974                            cursor_height,
2975                        ),
2976                    );
2977
2978                    if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) {
2979                        let geometry =
2980                            clipped_src.translate(offset.cast()).transformed(self.rotation);
2981                        let args = target_pixel_buffer::DrawRectangleArgs::from_rect(
2982                            geometry.cast(),
2983                            self.alpha_color(text_visual_representation.cursor_color).into(),
2984                        );
2985                        self.processor.process_rectangle(&args, geometry);
2986                    }
2987                }
2988            }
2989        }
2990    }
2991
2992    #[cfg(all(feature = "std", not(feature = "path")))]
2993    fn draw_path(
2994        &mut self,
2995        _path: Pin<&i_slint_core::items::Path>,
2996        _self_rc: &ItemRc,
2997        _size: LogicalSize,
2998    ) {
2999        // Path rendering is disabled without the path feature
3000    }
3001
3002    #[cfg(feature = "path")]
3003    fn draw_path(
3004        &mut self,
3005        path: Pin<&i_slint_core::items::Path>,
3006        self_rc: &ItemRc,
3007        size: LogicalSize,
3008    ) {
3009        let geom = LogicalRect::from(size);
3010        if !self.should_draw(&geom) {
3011            return;
3012        }
3013
3014        // Get the fitted path events from the Path item
3015        let Some((offset, path_iterator)) = path.fitted_path_events(self_rc) else {
3016            return;
3017        };
3018
3019        let physical_geom_f32 =
3020            geom.translate(self.current_state.offset.to_vector()).cast() * self.scale_factor;
3021        let physical_geom = physical_geom_f32.round().cast().transformed(self.rotation);
3022
3023        let rotation = RotationInfo {
3024            orientation: self.rotation.orientation,
3025            screen_size: physical_geom.size + euclid::size2(1, 1),
3026        };
3027
3028        let offset = offset * self.scale_factor
3029            + (physical_geom_f32.origin - physical_geom_f32.round().origin);
3030
3031        // Convert to zeno commands
3032        let zeno_commands =
3033            path::convert_path_data_to_zeno(path_iterator, rotation, self.scale_factor, offset);
3034
3035        let physical_clip =
3036            (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
3037                * self.scale_factor)
3038                .round()
3039                .cast::<i16>()
3040                .transformed(self.rotation);
3041
3042        // Clip the geometry - early return if nothing to draw
3043        let Some(clipped_geom) = physical_geom.intersection(&physical_clip) else {
3044            return;
3045        };
3046
3047        // Draw fill if specified
3048        let fill_brush = path.fill();
3049        if !fill_brush.is_transparent() {
3050            let fill_color = self.alpha_color(fill_brush.color());
3051            if fill_color.alpha() > 0 {
3052                self.processor.process_filled_path(
3053                    physical_geom,
3054                    clipped_geom,
3055                    zeno_commands.clone(),
3056                    fill_color.into(),
3057                );
3058            }
3059        }
3060
3061        // Draw stroke if specified
3062        let stroke_brush = path.stroke();
3063        let stroke_width = path.stroke_width();
3064        if !stroke_brush.is_transparent() && stroke_width.get() > 0.0 {
3065            let stroke_color = self.alpha_color(stroke_brush.color());
3066            if stroke_color.alpha() > 0 {
3067                let physical_stroke_width = (stroke_width.cast() * self.scale_factor).get();
3068                let stroke_line_cap = path.stroke_line_cap();
3069                let stroke_line_join = path.stroke_line_join();
3070                let stroke_miter_limit = path.stroke_miter_limit();
3071                self.processor.process_stroked_path(
3072                    physical_geom,
3073                    clipped_geom,
3074                    zeno_commands,
3075                    stroke_color.into(),
3076                    physical_stroke_width,
3077                    stroke_line_cap,
3078                    stroke_line_join,
3079                    stroke_miter_limit,
3080                );
3081            }
3082        }
3083    }
3084
3085    fn draw_box_shadow(
3086        &mut self,
3087        _box_shadow: Pin<&i_slint_core::items::BoxShadow>,
3088        _: &ItemRc,
3089        _size: LogicalSize,
3090    ) {
3091        // TODO
3092    }
3093
3094    fn combine_clip(
3095        &mut self,
3096        other: LogicalRect,
3097        _radius: LogicalBorderRadius,
3098        _border_width: LogicalLength,
3099    ) -> bool {
3100        match self.current_state.clip.intersection(&other) {
3101            Some(r) => {
3102                self.current_state.clip = r;
3103                true
3104            }
3105            None => {
3106                self.current_state.clip = LogicalRect::default();
3107                false
3108            }
3109        }
3110        // TODO: handle radius and border
3111    }
3112
3113    fn get_current_clip(&self) -> LogicalRect {
3114        self.current_state.clip
3115    }
3116
3117    fn translate(&mut self, distance: LogicalVector) {
3118        self.current_state.offset += distance;
3119        self.current_state.clip = self.current_state.clip.translate(-distance)
3120    }
3121
3122    fn current_transform(&self) -> i_slint_core::lengths::ItemTransform {
3123        let v = self.current_state.offset.to_vector().cast::<f32>();
3124        i_slint_core::lengths::ItemTransform::translation(v.x, v.y)
3125    }
3126
3127    fn rotate(&mut self, _angle_in_degrees: f32) {
3128        // TODO (#6068)
3129    }
3130
3131    fn scale(&mut self, _x_factor: f32, _y_factor: f32) {
3132        // TODO
3133    }
3134
3135    fn apply_opacity(&mut self, opacity: f32) {
3136        self.current_state.alpha *= opacity;
3137    }
3138
3139    fn save_state(&mut self) {
3140        self.state_stack.push(self.current_state);
3141    }
3142
3143    fn restore_state(&mut self) {
3144        self.current_state = self.state_stack.pop().unwrap();
3145    }
3146
3147    fn scale_factor(&self) -> f32 {
3148        self.scale_factor.0
3149    }
3150
3151    fn draw_cached_pixmap(
3152        &mut self,
3153        _item: &ItemRc,
3154        update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
3155    ) {
3156        // FIXME: actually cache the pixmap
3157        update_fn(&mut |width, height, data| {
3158            let img = SharedImageBuffer::RGBA8Premultiplied(SharedPixelBuffer::clone_from_slice(
3159                data, width, height,
3160            ));
3161
3162            let physical_clip = (self.current_state.clip.cast() * self.scale_factor).cast();
3163            let source_rect = euclid::rect(0, 0, width as _, height as _);
3164
3165            if let Some(clipped_src) = source_rect.intersection(&physical_clip) {
3166                let offset = self.current_state.offset.cast() * self.scale_factor;
3167                let geometry = clipped_src.translate(offset.to_vector().cast()).round_in();
3168
3169                let t = target_pixel_buffer::DrawTextureArgs {
3170                    data: target_pixel_buffer::TextureDataContainer::Shared {
3171                        buffer: SharedBufferData::SharedImage(img),
3172                        source_rect,
3173                    },
3174                    colorize: None,
3175                    alpha: (self.current_state.alpha * 255.) as u8,
3176                    dst_x: offset.x as _,
3177                    dst_y: offset.y as _,
3178                    dst_width: width as _,
3179                    dst_height: height as _,
3180                    rotation: self.rotation.orientation,
3181                    tiling: None,
3182                };
3183                self.processor
3184                    .process_target_texture(&t, geometry.cast().transformed(self.rotation));
3185            }
3186        });
3187    }
3188
3189    fn draw_string(&mut self, string: &str, color: Color) {
3190        let font_request = Default::default();
3191        #[cfg(feature = "systemfonts")]
3192        let mut font_ctx = self.window.context().font_context().borrow_mut();
3193        let font = fonts::match_font(
3194            &font_request,
3195            self.scale_factor,
3196            #[cfg(feature = "systemfonts")]
3197            &mut font_ctx,
3198        );
3199        let clip = self.current_state.clip.cast() * self.scale_factor;
3200
3201        match (font, parley_disabled()) {
3202            #[cfg(feature = "systemfonts")]
3203            (fonts::Font::VectorFont(_), false) => {
3204                drop(font_ctx);
3205                sharedparley::draw_text(
3206                    self,
3207                    std::pin::pin!((i_slint_core::SharedString::from(string), Brush::from(color))),
3208                    None,
3209                    self.current_state.clip.size.cast(),
3210                    None,
3211                );
3212            }
3213            #[cfg(feature = "systemfonts")]
3214            (fonts::Font::VectorFont(vf), true) => {
3215                let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor);
3216
3217                let paragraph = TextParagraphLayout {
3218                    string,
3219                    layout,
3220                    max_width: clip.width_length().cast(),
3221                    max_height: clip.height_length().cast(),
3222                    horizontal_alignment: Default::default(),
3223                    vertical_alignment: Default::default(),
3224                    wrap: Default::default(),
3225                    overflow: Default::default(),
3226                    single_line: false,
3227                };
3228
3229                self.draw_text_paragraph(&paragraph, clip, Default::default(), color, None);
3230            }
3231            (fonts::Font::PixelFont(pf), _) => {
3232                let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor);
3233
3234                let paragraph = TextParagraphLayout {
3235                    string,
3236                    layout,
3237                    max_width: clip.width_length().cast(),
3238                    max_height: clip.height_length().cast(),
3239                    horizontal_alignment: Default::default(),
3240                    vertical_alignment: Default::default(),
3241                    wrap: Default::default(),
3242                    overflow: Default::default(),
3243                    single_line: false,
3244                };
3245
3246                self.draw_text_paragraph(&paragraph, clip, Default::default(), color, None);
3247            }
3248        }
3249    }
3250
3251    fn draw_image_direct(&mut self, image: i_slint_core::graphics::Image) {
3252        let image_inner: &ImageInner = (&image).into();
3253        let source_size = image.size();
3254        if source_size.is_empty() {
3255            return;
3256        }
3257        let target_size = euclid::Size2D::<f32, i_slint_core::lengths::LogicalPx>::from_untyped(
3258            source_size.cast(),
3259        ) * self.scale_factor;
3260        let fit = i_slint_core::graphics::fit(
3261            i_slint_core::items::ImageFit::Fill,
3262            target_size,
3263            i_slint_core::graphics::IntRect::from_size(source_size.cast()),
3264            self.scale_factor,
3265            Default::default(),
3266            Default::default(),
3267        );
3268        self.draw_image_impl(image_inner, fit, i_slint_core::Color::default());
3269    }
3270
3271    fn window(&self) -> &i_slint_core::window::WindowInner {
3272        self.window
3273    }
3274
3275    fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
3276        None
3277    }
3278}
3279
3280impl<T: ProcessScene> i_slint_core::item_rendering::ItemRendererFeatures for SceneBuilder<'_, T> {
3281    const SUPPORTS_TRANSFORMATIONS: bool = false;
3282}
3283
3284#[cfg(feature = "systemfonts")]
3285use i_slint_core::textlayout::sharedparley::{self, fontique};
3286
3287#[cfg(feature = "systemfonts")]
3288impl<T: ProcessScene> sharedparley::GlyphRenderer for SceneBuilder<'_, T> {
3289    type PlatformBrush = Color;
3290
3291    fn platform_brush_for_color(&mut self, color: &Color) -> Option<Self::PlatformBrush> {
3292        Some(*color)
3293    }
3294
3295    fn platform_text_fill_brush(
3296        &mut self,
3297        brush: Brush,
3298        _size: LogicalSize,
3299    ) -> Option<Self::PlatformBrush> {
3300        Some(brush.color())
3301    }
3302
3303    fn platform_text_stroke_brush(
3304        &mut self,
3305        brush: Brush,
3306        _physical_stroke_width: f32,
3307        _size: LogicalSize,
3308    ) -> Option<Self::PlatformBrush> {
3309        Some(brush.color())
3310    }
3311
3312    fn fill_rectangle(&mut self, mut physical_rect: sharedparley::PhysicalRect, color: Color) {
3313        if color.alpha() == 0 {
3314            return;
3315        }
3316
3317        let global_offset =
3318            (self.current_state.offset.to_vector().cast() * self.scale_factor).cast();
3319
3320        physical_rect.origin += global_offset;
3321        let physical_rect = physical_rect.cast().transformed(self.rotation);
3322
3323        let args = target_pixel_buffer::DrawRectangleArgs::from_rect(
3324            physical_rect.cast(),
3325            Brush::SolidColor(color),
3326        );
3327        self.processor.process_rectangle(&args, physical_rect);
3328    }
3329
3330    fn draw_glyph_run(
3331        &mut self,
3332        font: &sharedparley::parley::FontData,
3333        font_size: sharedparley::PhysicalLength,
3334        normalized_coords: &[i16],
3335        _synthesis: &fontique::Synthesis,
3336        color: Self::PlatformBrush,
3337        y_offset: sharedparley::PhysicalLength,
3338        glyphs_it: &mut dyn Iterator<Item = sharedparley::parley::layout::Glyph>,
3339    ) {
3340        let slint_context = self.window.context();
3341        let (swash_key, swash_offset) =
3342            fonts::systemfonts::get_swash_font_info(&font.data, font.index);
3343        let font = fonts::vectorfont::VectorFont::new_from_blob_and_index_with_coords(
3344            font.data.clone(),
3345            font.index,
3346            swash_key,
3347            swash_offset,
3348            font_size.cast(),
3349            normalized_coords,
3350        );
3351
3352        let global_offset =
3353            (self.current_state.offset.to_vector().cast() * self.scale_factor).cast();
3354
3355        for positioned_glyph in glyphs_it {
3356            let Some(glyph) = std::num::NonZero::new(positioned_glyph.id as u16)
3357                .and_then(|id| font.render_vector_glyph(id, slint_context))
3358            else {
3359                continue;
3360            };
3361
3362            let glyph_offset: euclid::Vector2D<i16, PhysicalPx> = euclid::Vector2D::from_lengths(
3363                euclid::Length::new(positioned_glyph.x),
3364                euclid::Length::new(positioned_glyph.y) + y_offset,
3365            )
3366            .cast();
3367
3368            let gl_y = PhysicalLength::new(glyph.y.truncate() as i16);
3369            let target_rect: PhysicalRect = euclid::Rect::<f32, PhysicalPx>::new(
3370                (PhysicalPoint::from_lengths(PhysicalLength::new(0), -gl_y - glyph.height)
3371                    + global_offset
3372                    + glyph_offset)
3373                    .cast()
3374                    + euclid::vec2(glyph.glyph_origin_x, 0.0),
3375                glyph.size().cast(),
3376            )
3377            .cast()
3378            .transformed(self.rotation);
3379
3380            let data = {
3381                let source_rect = euclid::rect(0, 0, glyph.width.0, glyph.height.0);
3382                target_pixel_buffer::TextureDataContainer::Shared {
3383                    buffer: SharedBufferData::AlphaMap {
3384                        data: glyph.alpha_map,
3385                        width: glyph.pixel_stride,
3386                    },
3387                    source_rect,
3388                }
3389            };
3390
3391            let color = self.alpha_color(color);
3392            let physical_clip =
3393                (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
3394                    * self.scale_factor)
3395                    .round()
3396                    .transformed(self.rotation);
3397
3398            let t = target_pixel_buffer::DrawTextureArgs {
3399                data,
3400                colorize: Some(color),
3401                // color already is mixed with global alpha
3402                alpha: color.alpha(),
3403                dst_x: target_rect.origin.x as _,
3404                dst_y: target_rect.origin.y as _,
3405                dst_width: target_rect.size.width as _,
3406                dst_height: target_rect.size.height as _,
3407                rotation: self.rotation.orientation,
3408                tiling: None,
3409            };
3410
3411            self.processor.process_target_texture(&t, physical_clip.cast());
3412        }
3413    }
3414}