Skip to main content

i_slint_renderer_software/
lib.rs

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