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