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