1#![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#[non_exhaustive]
70#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
71pub enum RenderingRotation {
72 #[default]
74 NoRotation,
75 Rotate90,
77 Rotate180,
79 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 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
110trait Transform {
112 #[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
173pub trait LineBufferProvider {
183 type TargetPixel: TargetPixel;
185
186 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#[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#[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 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 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 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))); 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
335fn 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 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
428pub struct SoftwareRenderer {
438 repaint_buffer_type: Cell<RepaintBufferType>,
439 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 pub fn new() -> Self {
464 Default::default()
465 }
466
467 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 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 pub fn repaint_buffer_type(&self) -> RepaintBufferType {
487 self.repaint_buffer_type.get()
488 }
489
490 pub fn set_rendering_rotation(&self, rotation: RenderingRotation) {
494 self.rotation.set(rotation)
495 }
496
497 pub fn rendering_rotation(&self) -> RenderingRotation {
499 self.rotation.get()
500 }
501
502 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 #[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 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 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 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 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 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 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 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 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 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 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 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 }
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 }
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 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 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 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 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 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 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 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; };
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(¶graph, 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(¶graph, 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 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; };
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(¶graph, 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 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 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; };
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(¶graph, 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 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 }
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 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 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 let Some(clipped_geom) = physical_geom.intersection(&physical_clip) else {
2873 return;
2874 };
2875
2876 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 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 }
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 }
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 }
2952
2953 fn scale(&mut self, _x_factor: f32, _y_factor: f32) {
2954 }
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 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(¶graph, 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(¶graph, 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 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}