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