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