Skip to main content

i_slint_renderer_software/
lib.rs

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