1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains the [`SoftwareRenderer`] and related types.
5//!
6//! It is only enabled when the `renderer-software` Slint feature is enabled.
7
8#![warn(missing_docs)]
9
10mod draw_functions;
11mod fixed;
12mod fonts;
13mod minimal_software_window;
14mod scene;
15
16use self::fonts::GlyphRenderer;
17pub use self::minimal_software_window::MinimalSoftwareWindow;
18use self::scene::*;
19use crate::api::PlatformError;
20use crate::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector};
21use crate::graphics::{BorderRadius, Rgba8Pixel, SharedImageBuffer, SharedPixelBuffer};
22use crate::item_rendering::{
23 CachedRenderingData, DirtyRegion, PartialRenderingState, RenderBorderRectangle, RenderImage,
24 RenderRectangle,
25};
26use crate::items::{ItemRc, TextOverflow, TextWrap};
27use crate::lengths::{
28 LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
29 PhysicalPx, PointLengths, RectLengths, ScaleFactor, SizeLengths,
30};
31use crate::renderer::RendererSealed;
32use crate::textlayout::{AbstractFont, FontMetrics, TextParagraphLayout};
33use crate::window::{WindowAdapter, WindowInner};
34use crate::{Brush, Color, Coord, ImageInner, StaticTextures};
35use alloc::rc::{Rc, Weak};
36use alloc::{vec, vec::Vec};
37use core::cell::{Cell, RefCell};
38use core::pin::Pin;
39use euclid::Length;
40use fixed::Fixed;
41#[allow(unused)]
42use num_traits::Float;
43use num_traits::NumCast;
44
45pub use draw_functions::{PremultipliedRgbaColor, Rgb565Pixel, TargetPixel};
46
47type PhysicalLength = euclid::Length<i16, PhysicalPx>;
48type PhysicalRect = euclid::Rect<i16, PhysicalPx>;
49type PhysicalSize = euclid::Size2D<i16, PhysicalPx>;
50type PhysicalPoint = euclid::Point2D<i16, PhysicalPx>;
51type PhysicalBorderRadius = BorderRadius<i16, PhysicalPx>;
52
53pub use crate::item_rendering::RepaintBufferType;
54
55/// This enum describes the rotation that should be applied to the contents rendered by the software renderer.
56///
57/// Argument to be passed in [`SoftwareRenderer::set_rendering_rotation`].
58#[non_exhaustive]
59#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
60pub enum RenderingRotation {
61 /// No rotation
62 #[default]
63 NoRotation,
64 /// Rotate 90° to the right
65 Rotate90,
66 /// 180° rotation (upside-down)
67 Rotate180,
68 /// Rotate 90° to the left
69 Rotate270,
70}
71
72impl RenderingRotation {
73 fn is_transpose(self) -> bool {
74 matches!(self, Self::Rotate90 | Self::Rotate270)
75 }
76 fn mirror_width(self) -> bool {
77 matches!(self, Self::Rotate270 | Self::Rotate180)
78 }
79 fn mirror_height(self) -> bool {
80 matches!(self, Self::Rotate90 | Self::Rotate180)
81 }
82 /// Angle of the rotation in degrees
83 pub fn angle(self) -> f32 {
84 match self {
85 RenderingRotation::NoRotation => 0.,
86 RenderingRotation::Rotate90 => 90.,
87 RenderingRotation::Rotate180 => 180.,
88 RenderingRotation::Rotate270 => 270.,
89 }
90 }
91}
92
93#[derive(Copy, Clone)]
94struct RotationInfo {
95 orientation: RenderingRotation,
96 screen_size: PhysicalSize,
97}
98
99/// Extension trait for euclid type to transpose coordinates (swap x and y, as well as width and height)
100trait Transform {
101 /// Return a copy of Self whose coordinate are swapped (x swapped with y)
102 #[must_use]
103 fn transformed(self, info: RotationInfo) -> Self;
104}
105
106impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Point2D<T, PhysicalPx> {
107 fn transformed(mut self, info: RotationInfo) -> Self {
108 if info.orientation.mirror_width() {
109 self.x = T::from(info.screen_size.width).unwrap() - self.x - T::from(1).unwrap()
110 }
111 if info.orientation.mirror_height() {
112 self.y = T::from(info.screen_size.height).unwrap() - self.y - T::from(1).unwrap()
113 }
114 if info.orientation.is_transpose() {
115 core::mem::swap(&mut self.x, &mut self.y);
116 }
117 self
118 }
119}
120
121impl<T: Copy> Transform for euclid::Size2D<T, PhysicalPx> {
122 fn transformed(mut self, info: RotationInfo) -> Self {
123 if info.orientation.is_transpose() {
124 core::mem::swap(&mut self.width, &mut self.height);
125 }
126 self
127 }
128}
129
130impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Rect<T, PhysicalPx> {
131 fn transformed(self, info: RotationInfo) -> Self {
132 let one: T = T::from(1).unwrap();
133 let mut origin: Point2D = self.origin.transformed(info);
134 let size: Size2D = self.size.transformed(info);
135 if info.orientation.mirror_width() {
136 origin.y = origin.y - (size.height - one);
137 }
138 if info.orientation.mirror_height() {
139 origin.x = origin.x - (size.width - one);
140 }
141 Self::new(origin, size)
142 }
143}
144
145impl<T: Copy> Transform for BorderRadius<T, PhysicalPx> {
146 fn transformed(self, info: RotationInfo) -> Self {
147 match info.orientation {
148 RenderingRotation::NoRotation => self,
149 RenderingRotation::Rotate90 => {
150 Self::new(self.bottom_left, self.top_left, self.top_right, self.bottom_right)
151 }
152 RenderingRotation::Rotate180 => {
153 Self::new(self.bottom_right, self.bottom_left, self.top_left, self.top_right)
154 }
155 RenderingRotation::Rotate270 => {
156 Self::new(self.top_right, self.bottom_right, self.bottom_left, self.top_left)
157 }
158 }
159 }
160}
161
162/// This trait defines a bi-directional interface between Slint and your code to send lines to your screen, when using
163/// the [`SoftwareRenderer::render_by_line`] function.
164///
165/// * Through the associated `TargetPixel` type Slint knows how to create and manipulate pixels without having to know
166/// the exact device-specific binary representation and operations for blending.
167/// * Through the `process_line` function Slint notifies you when a line can be rendered and provides a callback that
168/// you can invoke to fill a slice of pixels for the given line.
169///
170/// See the [`render_by_line`](SoftwareRenderer::render_by_line) documentation for an example.
171pub trait LineBufferProvider {
172 /// The pixel type of the buffer
173 type TargetPixel: TargetPixel;
174
175 /// Called once per line, you will have to call the render_fn back with the buffer.
176 ///
177 /// The `line` is the y position of the line to be drawn.
178 /// The `range` is the range within the line that is going to be rendered (eg, within the dirty region)
179 /// The `render_fn` function should be called to render the line, passing the buffer
180 /// corresponding to the specified line and range.
181 fn process_line(
182 &mut self,
183 line: usize,
184 range: core::ops::Range<usize>,
185 render_fn: impl FnOnce(&mut [Self::TargetPixel]),
186 );
187}
188
189#[cfg(not(cbindgen))]
190const PHYSICAL_REGION_MAX_SIZE: usize = DirtyRegion::MAX_COUNT;
191// cbindgen can't understand associated const correctly, so hardcode the value
192#[cfg(cbindgen)]
193pub const PHYSICAL_REGION_MAX_SIZE: usize = 3;
194const _: () = {
195 assert!(PHYSICAL_REGION_MAX_SIZE == 3);
196 assert!(DirtyRegion::MAX_COUNT == 3);
197};
198
199/// Represents a rectangular region on the screen, used for partial rendering.
200///
201/// The region may be composed of multiple sub-regions.
202#[derive(Clone, Debug, Default)]
203#[repr(C)]
204pub struct PhysicalRegion {
205 rectangles: [euclid::Box2D<i16, PhysicalPx>; PHYSICAL_REGION_MAX_SIZE],
206 count: usize,
207}
208
209impl PhysicalRegion {
210 fn iter_box(&self) -> impl Iterator<Item = euclid::Box2D<i16, PhysicalPx>> + '_ {
211 (0..self.count).map(|x| self.rectangles[x])
212 }
213
214 fn bounding_rect(&self) -> PhysicalRect {
215 if self.count == 0 {
216 return Default::default();
217 }
218 let mut r = self.rectangles[0];
219 for i in 1..self.count {
220 r = r.union(&self.rectangles[i]);
221 }
222 r.to_rect()
223 }
224
225 /// Returns the size of the bounding box of this region.
226 pub fn bounding_box_size(&self) -> crate::api::PhysicalSize {
227 let bb = self.bounding_rect();
228 crate::api::PhysicalSize { width: bb.width() as _, height: bb.height() as _ }
229 }
230 /// Returns the origin of the bounding box of this region.
231 pub fn bounding_box_origin(&self) -> crate::api::PhysicalPosition {
232 let bb = self.bounding_rect();
233 crate::api::PhysicalPosition { x: bb.origin.x as _, y: bb.origin.y as _ }
234 }
235
236 /// Returns an iterator over the rectangles in this region.
237 /// Each rectangle is represented by its position and its size.
238 /// They do not overlap.
239 pub fn iter(
240 &self,
241 ) -> impl Iterator<Item = (crate::api::PhysicalPosition, crate::api::PhysicalSize)> + '_ {
242 let mut line_ranges = Vec::<core::ops::Range<i16>>::new();
243 let mut begin_line = 0;
244 let mut end_line = 0;
245 core::iter::from_fn(move || loop {
246 match line_ranges.pop() {
247 Some(r) => {
248 return Some((
249 crate::api::PhysicalPosition { x: r.start as _, y: begin_line as _ },
250 crate::api::PhysicalSize {
251 width: r.len() as _,
252 height: (end_line - begin_line) as _,
253 },
254 ));
255 }
256 None => {
257 begin_line = end_line;
258 end_line = match region_line_ranges(self, begin_line, &mut line_ranges) {
259 Some(end_line) => end_line,
260 None => return None,
261 };
262 line_ranges.reverse();
263 }
264 }
265 })
266 }
267}
268
269#[test]
270fn region_iter() {
271 let mut region = PhysicalRegion::default();
272 assert_eq!(region.iter().next(), None);
273 region.rectangles[0] =
274 euclid::Box2D::from_origin_and_size(euclid::point2(1, 1), euclid::size2(2, 3));
275 region.rectangles[1] =
276 euclid::Box2D::from_origin_and_size(euclid::point2(6, 2), euclid::size2(3, 20));
277 region.rectangles[2] =
278 euclid::Box2D::from_origin_and_size(euclid::point2(0, 10), euclid::size2(10, 5));
279 assert_eq!(region.iter().next(), None);
280 region.count = 1;
281 let r = |x, y, width, height| {
282 (crate::api::PhysicalPosition { x, y }, crate::api::PhysicalSize { width, height })
283 };
284
285 let mut iter = region.iter();
286 assert_eq!(iter.next(), Some(r(1, 1, 2, 3)));
287 assert_eq!(iter.next(), None);
288 drop(iter);
289
290 region.count = 3;
291 let mut iter = region.iter();
292 assert_eq!(iter.next(), Some(r(1, 1, 2, 1))); // the two first rectangle could have been merged
293 assert_eq!(iter.next(), Some(r(1, 2, 2, 2)));
294 assert_eq!(iter.next(), Some(r(6, 2, 3, 2)));
295 assert_eq!(iter.next(), Some(r(6, 4, 3, 6)));
296 assert_eq!(iter.next(), Some(r(0, 10, 10, 5)));
297 assert_eq!(iter.next(), Some(r(6, 15, 3, 7)));
298 assert_eq!(iter.next(), None);
299}
300
301/// Computes what are the x ranges that intersects the region for specified y line.
302///
303/// This uses a mutable reference to a Vec so that the memory is re-used between calls.
304///
305/// Returns the y position until which this range is valid
306fn region_line_ranges(
307 region: &PhysicalRegion,
308 line: i16,
309 line_ranges: &mut Vec<core::ops::Range<i16>>,
310) -> Option<i16> {
311 line_ranges.clear();
312 let mut next_validity = None::<i16>;
313 for geom in region.iter_box() {
314 if geom.is_empty() {
315 continue;
316 }
317 if geom.y_range().contains(&line) {
318 match &mut next_validity {
319 Some(val) => *val = geom.max.y.min(*val),
320 None => next_validity = Some(geom.max.y),
321 }
322 let mut tmp = Some(geom.x_range());
323 line_ranges.retain_mut(|it| {
324 if let Some(r) = &mut tmp {
325 if it.end < r.start {
326 true
327 } else if it.start <= r.start {
328 if it.end >= r.end {
329 tmp = None;
330 return true;
331 }
332 r.start = it.start;
333 return false;
334 } else if it.start <= r.end {
335 if it.end <= r.end {
336 return false;
337 } else {
338 it.start = r.start;
339 tmp = None;
340 return true;
341 }
342 } else {
343 core::mem::swap(it, r);
344 return true;
345 }
346 } else {
347 true
348 }
349 });
350 if let Some(r) = tmp {
351 line_ranges.push(r);
352 }
353 continue;
354 } else if geom.min.y >= line {
355 match &mut next_validity {
356 Some(val) => *val = geom.min.y.min(*val),
357 None => next_validity = Some(geom.min.y),
358 }
359 }
360 }
361 // check that current items are properly sorted
362 debug_assert!(line_ranges.windows(2).all(|x| x[0].end < x[1].start));
363 next_validity
364}
365
366mod target_pixel_buffer;
367
368#[cfg(feature = "experimental")]
369pub use target_pixel_buffer::{CompositionMode, TargetPixelBuffer, Texture, TexturePixelFormat};
370
371#[cfg(not(feature = "experimental"))]
372use target_pixel_buffer::{CompositionMode, TexturePixelFormat};
373
374struct TargetPixelSlice<'a, T> {
375 data: &'a mut [T],
376 pixel_stride: usize,
377}
378
379impl<'a, T: TargetPixel> target_pixel_buffer::TargetPixelBuffer for TargetPixelSlice<'a, T> {
380 type TargetPixel = T;
381
382 fn line_slice(&mut self, line_number: usize) -> &mut [Self::TargetPixel] {
383 let offset: usize = line_number * self.pixel_stride;
384 &mut self.data[offset..offset + self.pixel_stride]
385 }
386
387 fn num_lines(&self) -> usize {
388 self.data.len() / self.pixel_stride
389 }
390}
391
392/// A Renderer that do the rendering in software
393///
394/// The renderer can remember what items needs to be redrawn from the previous iteration.
395///
396/// There are two kind of possible rendering
397/// 1. Using [`render()`](Self::render()) to render the window in a buffer
398/// 2. Using [`render_by_line()`](Self::render()) to render the window line by line. This
399/// is only useful if the device does not have enough memory to render the whole window
400/// in one single buffer
401pub struct SoftwareRenderer {
402 repaint_buffer_type: Cell<RepaintBufferType>,
403 /// This is the area which was dirty on the previous frame.
404 /// Only used if repaint_buffer_type == RepaintBufferType::SwappedBuffers
405 prev_frame_dirty: Cell<DirtyRegion>,
406 partial_rendering_state: PartialRenderingState,
407 maybe_window_adapter: RefCell<Option<Weak<dyn crate::window::WindowAdapter>>>,
408 rotation: Cell<RenderingRotation>,
409 rendering_metrics_collector: Option<Rc<RenderingMetricsCollector>>,
410}
411
412impl Default for SoftwareRenderer {
413 fn default() -> Self {
414 Self {
415 partial_rendering_state: Default::default(),
416 prev_frame_dirty: Default::default(),
417 maybe_window_adapter: Default::default(),
418 rotation: Default::default(),
419 rendering_metrics_collector: RenderingMetricsCollector::new(winsys_info:"software"),
420 repaint_buffer_type: Default::default(),
421 }
422 }
423}
424
425impl SoftwareRenderer {
426 /// Create a new Renderer
427 pub fn new() -> Self {
428 Default::default()
429 }
430
431 /// Create a new SoftwareRenderer.
432 ///
433 /// The `repaint_buffer_type` parameter specify what kind of buffer are passed to [`Self::render`]
434 pub fn new_with_repaint_buffer_type(repaint_buffer_type: RepaintBufferType) -> Self {
435 let self_ = Self::default();
436 self_.repaint_buffer_type.set(repaint_buffer_type);
437 self_
438 }
439
440 /// Change the what kind of buffer is being passed to [`Self::render`]
441 ///
442 /// This may clear the internal caches
443 pub fn set_repaint_buffer_type(&self, repaint_buffer_type: RepaintBufferType) {
444 if self.repaint_buffer_type.replace(repaint_buffer_type) != repaint_buffer_type {
445 self.partial_rendering_state.clear_cache();
446 }
447 }
448
449 /// Returns the kind of buffer that must be passed to [`Self::render`]
450 pub fn repaint_buffer_type(&self) -> RepaintBufferType {
451 self.repaint_buffer_type.get()
452 }
453
454 /// Set how the window need to be rotated in the buffer.
455 ///
456 /// This is typically used to implement screen rotation in software
457 pub fn set_rendering_rotation(&self, rotation: RenderingRotation) {
458 self.rotation.set(rotation)
459 }
460
461 /// Return the current rotation. See [`Self::set_rendering_rotation()`]
462 pub fn rendering_rotation(&self) -> RenderingRotation {
463 self.rotation.get()
464 }
465
466 /// Render the window to the given frame buffer.
467 ///
468 /// The renderer uses a cache internally and will only render the part of the window
469 /// which are dirty. The `extra_draw_region` is an extra region which will also
470 /// be rendered. (eg: the previous dirty region in case of double buffering)
471 /// This function returns the region that was rendered.
472 ///
473 /// The pixel_stride is the size (in pixels) between two lines in the buffer.
474 /// It is equal `width` if the screen is not rotated, and `height` if the screen is rotated by 90°.
475 /// The buffer needs to be big enough to contain the window, so its size must be at least
476 /// `pixel_stride * height`, or `pixel_stride * width` if the screen is rotated by 90°.
477 ///
478 /// Returns the physical dirty region for this frame, excluding the extra_draw_region,
479 /// in the window frame of reference. It is affected by the screen rotation.
480 pub fn render(&self, buffer: &mut [impl TargetPixel], pixel_stride: usize) -> PhysicalRegion {
481 self.render_buffer_impl(&mut TargetPixelSlice { data: buffer, pixel_stride })
482 }
483
484 /// Render the window to the given frame buffer.
485 ///
486 /// The renderer uses a cache internally and will only render the part of the window
487 /// which are dirty. The `extra_draw_region` is an extra region which will also
488 /// be rendered. (eg: the previous dirty region in case of double buffering)
489 /// This function returns the region that was rendered.
490 ///
491 /// The buffer's line slices need to be wide enough to if the `width` of the screen and the line count the `height`,
492 /// or the `height` and `width` swapped if the screen is rotated by 90°.
493 ///
494 /// Returns the physical dirty region for this frame, excluding the extra_draw_region,
495 /// in the window frame of reference. It is affected by the screen rotation.
496 #[cfg(feature = "experimental")]
497 pub fn render_into_buffer(&self, buffer: &mut impl TargetPixelBuffer) -> PhysicalRegion {
498 self.render_buffer_impl(buffer)
499 }
500
501 fn render_buffer_impl(
502 &self,
503 buffer: &mut impl target_pixel_buffer::TargetPixelBuffer,
504 ) -> PhysicalRegion {
505 let pixels_per_line = buffer.line_slice(0).len();
506 let num_lines = buffer.num_lines();
507 let buffer_pixel_count = num_lines * pixels_per_line;
508
509 let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
510 else {
511 return Default::default();
512 };
513 let window_inner = WindowInner::from_pub(window.window());
514 let factor = ScaleFactor::new(window_inner.scale_factor());
515 let rotation = self.rotation.get();
516 let (size, background) = if let Some(window_item) =
517 window_inner.window_item().as_ref().map(|item| item.as_pin_ref())
518 {
519 (
520 (LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
521 * factor)
522 .cast(),
523 window_item.background(),
524 )
525 } else if rotation.is_transpose() {
526 (euclid::size2(num_lines as _, pixels_per_line as _), Brush::default())
527 } else {
528 (euclid::size2(pixels_per_line as _, num_lines as _), Brush::default())
529 };
530 if size.is_empty() {
531 return Default::default();
532 }
533 assert!(
534 if rotation.is_transpose() {
535 pixels_per_line >= size.height as usize && buffer_pixel_count >= (size.width as usize * pixels_per_line + size.height as usize) - pixels_per_line
536 } else {
537 pixels_per_line >= size.width as usize && buffer_pixel_count >= (size.height as usize * pixels_per_line + size.width as usize) - pixels_per_line
538 },
539 "buffer of size {} with {pixels_per_line} pixels per line is too small to handle a window of size {size:?}", buffer_pixel_count
540 );
541 let buffer_renderer = SceneBuilder::new(
542 size,
543 factor,
544 window_inner,
545 RenderToBuffer { buffer, dirty_range_cache: vec![], dirty_region: Default::default() },
546 rotation,
547 );
548 let mut renderer = self.partial_rendering_state.create_partial_renderer(buffer_renderer);
549 let window_adapter = renderer.window_adapter.clone();
550
551 window_inner
552 .draw_contents(|components| {
553 let logical_size = (size.cast() / factor).cast();
554
555 let dirty_region_of_existing_buffer = match self.repaint_buffer_type.get() {
556 RepaintBufferType::NewBuffer => {
557 Some(LogicalRect::from_size(logical_size).into())
558 }
559 RepaintBufferType::ReusedBuffer => None,
560 RepaintBufferType::SwappedBuffers => Some(self.prev_frame_dirty.take()),
561 };
562
563 let dirty_region_for_this_frame = self.partial_rendering_state.apply_dirty_region(
564 &mut renderer,
565 components,
566 logical_size,
567 dirty_region_of_existing_buffer,
568 );
569
570 if self.repaint_buffer_type.get() == RepaintBufferType::SwappedBuffers {
571 self.prev_frame_dirty.set(dirty_region_for_this_frame);
572 }
573
574 let rotation = RotationInfo { orientation: rotation, screen_size: size };
575 let screen_rect = PhysicalRect::from_size(size);
576 let mut i = renderer.dirty_region.iter().filter_map(|r| {
577 (r.cast() * factor)
578 .to_rect()
579 .round_out()
580 .cast()
581 .intersection(&screen_rect)?
582 .transformed(rotation)
583 .into()
584 });
585 let dirty_region = PhysicalRegion {
586 rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
587 count: renderer.dirty_region.iter().count(),
588 };
589 drop(i);
590
591 renderer.actual_renderer.processor.dirty_region = dirty_region.clone();
592 renderer.actual_renderer.processor.process_rectangle_impl(
593 screen_rect.transformed(rotation),
594 // TODO: gradient background
595 background.color().into(),
596 CompositionMode::Source,
597 );
598
599 for (component, origin) in components {
600 crate::item_rendering::render_component_items(
601 component,
602 &mut renderer,
603 *origin,
604 &window_adapter,
605 );
606 }
607
608 if let Some(metrics) = &self.rendering_metrics_collector {
609 metrics.measure_frame_rendered(&mut renderer);
610 if metrics.refresh_mode() == RefreshMode::FullSpeed {
611 self.partial_rendering_state.force_screen_refresh();
612 }
613 }
614
615 dirty_region
616 })
617 .unwrap_or_default()
618 }
619
620 /// Render the window, line by line, into the line buffer provided by the [`LineBufferProvider`].
621 ///
622 /// The renderer uses a cache internally and will only render the part of the window
623 /// which are dirty, depending on the dirty tracking policy set in [`SoftwareRenderer::new`]
624 /// This function returns the physical region that was rendered considering the rotation.
625 ///
626 /// The [`LineBufferProvider::process_line()`] function will be called for each line and should
627 /// provide a buffer to draw into.
628 ///
629 /// As an example, let's imagine we want to render into a plain buffer.
630 /// (You wouldn't normally use `render_by_line` for that because the [`Self::render`] would
631 /// then be more efficient)
632 ///
633 /// ```rust
634 /// # use i_slint_core::software_renderer::{LineBufferProvider, SoftwareRenderer, Rgb565Pixel};
635 /// # fn xxx<'a>(the_frame_buffer: &'a mut [Rgb565Pixel], display_width: usize, renderer: &SoftwareRenderer) {
636 /// struct FrameBuffer<'a>{ frame_buffer: &'a mut [Rgb565Pixel], stride: usize }
637 /// impl<'a> LineBufferProvider for FrameBuffer<'a> {
638 /// type TargetPixel = Rgb565Pixel;
639 /// fn process_line(
640 /// &mut self,
641 /// line: usize,
642 /// range: core::ops::Range<usize>,
643 /// render_fn: impl FnOnce(&mut [Self::TargetPixel]),
644 /// ) {
645 /// let line_begin = line * self.stride;
646 /// render_fn(&mut self.frame_buffer[line_begin..][range]);
647 /// // The line has been rendered and there could be code here to
648 /// // send the pixel to the display
649 /// }
650 /// }
651 /// renderer.render_by_line(FrameBuffer{ frame_buffer: the_frame_buffer, stride: display_width });
652 /// # }
653 /// ```
654 pub fn render_by_line(&self, line_buffer: impl LineBufferProvider) -> PhysicalRegion {
655 let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
656 else {
657 return Default::default();
658 };
659 let window_inner = WindowInner::from_pub(window.window());
660 let component_rc = window_inner.component();
661 let component = crate::item_tree::ItemTreeRc::borrow_pin(&component_rc);
662 if let Some(window_item) = crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(
663 component.as_ref().get_item_ref(0),
664 ) {
665 let factor = ScaleFactor::new(window_inner.scale_factor());
666 let size = LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
667 * factor;
668 render_window_frame_by_line(
669 window_inner,
670 window_item.background(),
671 size.cast(),
672 self,
673 line_buffer,
674 )
675 } else {
676 PhysicalRegion { ..Default::default() }
677 }
678 }
679}
680
681#[doc(hidden)]
682impl RendererSealed for SoftwareRenderer {
683 fn text_size(
684 &self,
685 font_request: crate::graphics::FontRequest,
686 text: &str,
687 max_width: Option<LogicalLength>,
688 scale_factor: ScaleFactor,
689 text_wrap: TextWrap,
690 ) -> LogicalSize {
691 fonts::text_size(font_request, text, max_width, scale_factor, text_wrap)
692 }
693
694 fn font_metrics(
695 &self,
696 font_request: crate::graphics::FontRequest,
697 scale_factor: ScaleFactor,
698 ) -> crate::items::FontMetrics {
699 fonts::font_metrics(font_request, scale_factor)
700 }
701
702 fn text_input_byte_offset_for_position(
703 &self,
704 text_input: Pin<&crate::items::TextInput>,
705 pos: LogicalPoint,
706 font_request: crate::graphics::FontRequest,
707 scale_factor: ScaleFactor,
708 ) -> usize {
709 let visual_representation = text_input.visual_representation(None);
710
711 let font = fonts::match_font(&font_request, scale_factor);
712
713 let width = (text_input.width().cast() * scale_factor).cast();
714 let height = (text_input.height().cast() * scale_factor).cast();
715
716 let pos = (pos.cast() * scale_factor)
717 .clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast())
718 .cast();
719
720 match font {
721 fonts::Font::PixelFont(pf) => {
722 let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
723
724 let paragraph = TextParagraphLayout {
725 string: &visual_representation.text,
726 layout,
727 max_width: width,
728 max_height: height,
729 horizontal_alignment: text_input.horizontal_alignment(),
730 vertical_alignment: text_input.vertical_alignment(),
731 wrap: text_input.wrap(),
732 overflow: TextOverflow::Clip,
733 single_line: false,
734 };
735
736 visual_representation.map_byte_offset_from_byte_offset_in_visual_text(
737 paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
738 )
739 }
740 #[cfg(feature = "software-renderer-systemfonts")]
741 fonts::Font::VectorFont(vf) => {
742 let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
743
744 let paragraph = TextParagraphLayout {
745 string: &visual_representation.text,
746 layout,
747 max_width: width,
748 max_height: height,
749 horizontal_alignment: text_input.horizontal_alignment(),
750 vertical_alignment: text_input.vertical_alignment(),
751 wrap: text_input.wrap(),
752 overflow: TextOverflow::Clip,
753 single_line: false,
754 };
755
756 visual_representation.map_byte_offset_from_byte_offset_in_visual_text(
757 paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
758 )
759 }
760 }
761 }
762
763 fn text_input_cursor_rect_for_byte_offset(
764 &self,
765 text_input: Pin<&crate::items::TextInput>,
766 byte_offset: usize,
767 font_request: crate::graphics::FontRequest,
768 scale_factor: ScaleFactor,
769 ) -> LogicalRect {
770 let visual_representation = text_input.visual_representation(None);
771
772 let font = fonts::match_font(&font_request, scale_factor);
773
774 let width = (text_input.width().cast() * scale_factor).cast();
775 let height = (text_input.height().cast() * scale_factor).cast();
776
777 let (cursor_position, cursor_height) = match font {
778 fonts::Font::PixelFont(pf) => {
779 let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
780
781 let paragraph = TextParagraphLayout {
782 string: &visual_representation.text,
783 layout,
784 max_width: width,
785 max_height: height,
786 horizontal_alignment: text_input.horizontal_alignment(),
787 vertical_alignment: text_input.vertical_alignment(),
788 wrap: text_input.wrap(),
789 overflow: TextOverflow::Clip,
790 single_line: false,
791 };
792
793 (paragraph.cursor_pos_for_byte_offset(byte_offset), pf.height())
794 }
795 #[cfg(feature = "software-renderer-systemfonts")]
796 fonts::Font::VectorFont(vf) => {
797 let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
798
799 let paragraph = TextParagraphLayout {
800 string: &visual_representation.text,
801 layout,
802 max_width: width,
803 max_height: height,
804 horizontal_alignment: text_input.horizontal_alignment(),
805 vertical_alignment: text_input.vertical_alignment(),
806 wrap: text_input.wrap(),
807 overflow: TextOverflow::Clip,
808 single_line: false,
809 };
810
811 (paragraph.cursor_pos_for_byte_offset(byte_offset), vf.height())
812 }
813 };
814
815 (PhysicalRect::new(
816 PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1),
817 PhysicalSize::from_lengths(
818 (text_input.text_cursor_width().cast() * scale_factor).cast(),
819 cursor_height,
820 ),
821 )
822 .cast()
823 / scale_factor)
824 .cast()
825 }
826
827 fn free_graphics_resources(
828 &self,
829 _component: crate::item_tree::ItemTreeRef,
830 items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
831 ) -> Result<(), crate::platform::PlatformError> {
832 self.partial_rendering_state.free_graphics_resources(items);
833 Ok(())
834 }
835
836 fn mark_dirty_region(&self, region: crate::item_rendering::DirtyRegion) {
837 self.partial_rendering_state.mark_dirty_region(region);
838 }
839
840 fn register_bitmap_font(&self, font_data: &'static crate::graphics::BitmapFont) {
841 fonts::register_bitmap_font(font_data);
842 }
843
844 #[cfg(feature = "software-renderer-systemfonts")]
845 fn register_font_from_memory(
846 &self,
847 data: &'static [u8],
848 ) -> Result<(), std::boxed::Box<dyn std::error::Error>> {
849 self::fonts::systemfonts::register_font_from_memory(data)
850 }
851
852 #[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
853 fn register_font_from_path(
854 &self,
855 path: &std::path::Path,
856 ) -> Result<(), std::boxed::Box<dyn std::error::Error>> {
857 self::fonts::systemfonts::register_font_from_path(path)
858 }
859
860 fn default_font_size(&self) -> LogicalLength {
861 self::fonts::DEFAULT_FONT_SIZE
862 }
863
864 fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
865 *self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
866 self.partial_rendering_state.clear_cache();
867 }
868
869 fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
870 let Some(window_adapter) =
871 self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
872 else {
873 return Err(
874 "SoftwareRenderer's screenshot called without a window adapter present".into()
875 );
876 };
877
878 let window = window_adapter.window();
879 let size = window.size();
880
881 let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
882 else {
883 // Nothing to render
884 return Err("take_snapshot() called on window with invalid size".into());
885 };
886
887 let mut target_buffer = SharedPixelBuffer::<crate::graphics::Rgb8Pixel>::new(width, height);
888
889 self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
890 self.render(target_buffer.make_mut_slice(), width as usize);
891 // ensure that caches are clear for the next call
892 self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
893
894 let mut target_buffer_with_alpha =
895 SharedPixelBuffer::<Rgba8Pixel>::new(target_buffer.width(), target_buffer.height());
896 for (target_pixel, source_pixel) in target_buffer_with_alpha
897 .make_mut_slice()
898 .iter_mut()
899 .zip(target_buffer.as_slice().iter())
900 {
901 *target_pixel.rgb_mut() = *source_pixel;
902 }
903 Ok(target_buffer_with_alpha)
904 }
905}
906
907fn render_window_frame_by_line(
908 window: &WindowInner,
909 background: Brush,
910 size: PhysicalSize,
911 renderer: &SoftwareRenderer,
912 mut line_buffer: impl LineBufferProvider,
913) -> PhysicalRegion {
914 let mut scene = prepare_scene(window, size, renderer);
915
916 let to_draw_tr = scene.dirty_region.bounding_rect();
917
918 let mut background_color = TargetPixel::background();
919 // FIXME gradient
920 TargetPixel::blend(&mut background_color, background.color().into());
921
922 while scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
923 for r in &scene.current_line_ranges {
924 line_buffer.process_line(
925 scene.current_line.get() as usize,
926 r.start as usize..r.end as usize,
927 |line_buffer| {
928 let offset = r.start;
929
930 line_buffer.fill(background_color);
931 for span in scene.items[0..scene.current_items_index].iter().rev() {
932 debug_assert!(scene.current_line >= span.pos.y_length());
933 debug_assert!(
934 scene.current_line < span.pos.y_length() + span.size.height_length(),
935 );
936 if span.pos.x >= r.end {
937 continue;
938 }
939 let begin = r.start.max(span.pos.x);
940 let end = r.end.min(span.pos.x + span.size.width);
941 if begin >= end {
942 continue;
943 }
944
945 let extra_left_clip = begin - span.pos.x;
946 let extra_right_clip = span.pos.x + span.size.width - end;
947 let range_buffer =
948 &mut line_buffer[(begin - offset) as usize..(end - offset) as usize];
949
950 match span.command {
951 SceneCommand::Rectangle { color } => {
952 TargetPixel::blend_slice(range_buffer, color);
953 }
954 SceneCommand::Texture { texture_index } => {
955 let texture = &scene.vectors.textures[texture_index as usize];
956 draw_functions::draw_texture_line(
957 &PhysicalRect { origin: span.pos, size: span.size },
958 scene.current_line,
959 texture,
960 range_buffer,
961 extra_left_clip,
962 extra_right_clip,
963 );
964 }
965 SceneCommand::SharedBuffer { shared_buffer_index } => {
966 let texture = scene.vectors.shared_buffers
967 [shared_buffer_index as usize]
968 .as_texture();
969 draw_functions::draw_texture_line(
970 &PhysicalRect { origin: span.pos, size: span.size },
971 scene.current_line,
972 &texture,
973 range_buffer,
974 extra_left_clip,
975 extra_right_clip,
976 );
977 }
978 SceneCommand::RoundedRectangle { rectangle_index } => {
979 let rr =
980 &scene.vectors.rounded_rectangles[rectangle_index as usize];
981 draw_functions::draw_rounded_rectangle_line(
982 &PhysicalRect { origin: span.pos, size: span.size },
983 scene.current_line,
984 rr,
985 range_buffer,
986 extra_left_clip,
987 extra_right_clip,
988 );
989 }
990 SceneCommand::Gradient { gradient_index } => {
991 let g = &scene.vectors.gradients[gradient_index as usize];
992
993 draw_functions::draw_gradient_line(
994 &PhysicalRect { origin: span.pos, size: span.size },
995 scene.current_line,
996 g,
997 range_buffer,
998 extra_left_clip,
999 );
1000 }
1001 }
1002 }
1003 },
1004 );
1005 }
1006
1007 if scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
1008 scene.next_line();
1009 }
1010 }
1011 scene.dirty_region
1012}
1013
1014fn prepare_scene(
1015 window: &WindowInner,
1016 size: PhysicalSize,
1017 software_renderer: &SoftwareRenderer,
1018) -> Scene {
1019 let factor = ScaleFactor::new(window.scale_factor());
1020 let prepare_scene = SceneBuilder::new(
1021 size,
1022 factor,
1023 window,
1024 PrepareScene::default(),
1025 software_renderer.rotation.get(),
1026 );
1027 let mut renderer =
1028 software_renderer.partial_rendering_state.create_partial_renderer(prepare_scene);
1029 let window_adapter = renderer.window_adapter.clone();
1030
1031 let mut dirty_region = PhysicalRegion::default();
1032 window.draw_contents(|components| {
1033 let logical_size = (size.cast() / factor).cast();
1034
1035 let dirty_region_of_existing_buffer = match software_renderer.repaint_buffer_type.get() {
1036 RepaintBufferType::NewBuffer => Some(LogicalRect::from_size(logical_size).into()),
1037 RepaintBufferType::ReusedBuffer => None,
1038 RepaintBufferType::SwappedBuffers => Some(software_renderer.prev_frame_dirty.take()),
1039 };
1040
1041 let dirty_region_for_this_frame =
1042 software_renderer.partial_rendering_state.apply_dirty_region(
1043 &mut renderer,
1044 components,
1045 logical_size,
1046 dirty_region_of_existing_buffer,
1047 );
1048
1049 if software_renderer.repaint_buffer_type.get() == RepaintBufferType::SwappedBuffers {
1050 software_renderer.prev_frame_dirty.set(dirty_region_for_this_frame);
1051 }
1052
1053 let rotation =
1054 RotationInfo { orientation: software_renderer.rotation.get(), screen_size: size };
1055 let screen_rect = PhysicalRect::from_size(size);
1056 let mut i = renderer.dirty_region.iter().filter_map(|r| {
1057 (r.cast() * factor)
1058 .to_rect()
1059 .round_out()
1060 .cast()
1061 .intersection(&screen_rect)?
1062 .transformed(rotation)
1063 .into()
1064 });
1065 dirty_region = PhysicalRegion {
1066 rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
1067 count: renderer.dirty_region.iter().count(),
1068 };
1069 drop(i);
1070
1071 for (component, origin) in components {
1072 crate::item_rendering::render_component_items(
1073 component,
1074 &mut renderer,
1075 *origin,
1076 &window_adapter,
1077 );
1078 }
1079 });
1080
1081 if let Some(metrics) = &software_renderer.rendering_metrics_collector {
1082 metrics.measure_frame_rendered(&mut renderer);
1083 if metrics.refresh_mode() == RefreshMode::FullSpeed {
1084 software_renderer.partial_rendering_state.force_screen_refresh();
1085 }
1086 }
1087
1088 let prepare_scene = renderer.into_inner();
1089
1090 /* // visualize dirty regions
1091 let mut prepare_scene = prepare_scene;
1092 for rect in dirty_region.iter() {
1093 prepare_scene.processor.process_rounded_rectangle(
1094 euclid::rect(rect.0.x as _, rect.0.y as _, rect.1.width as _, rect.1.height as _),
1095 RoundedRectangle {
1096 radius: BorderRadius::default(),
1097 width: Length::new(1),
1098 border_color: Color::from_argb_u8(128, 255, 0, 0).into(),
1099 inner_color: PremultipliedRgbaColor::default(),
1100 left_clip: Length::default(),
1101 right_clip: Length::default(),
1102 top_clip: Length::default(),
1103 bottom_clip: Length::default(),
1104 },
1105 )
1106 } // */
1107
1108 Scene::new(prepare_scene.processor.items, prepare_scene.processor.vectors, dirty_region)
1109}
1110
1111trait ProcessScene {
1112 fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>);
1113 fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor);
1114 fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle);
1115 fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand);
1116 fn process_gradient(&mut self, geometry: PhysicalRect, gradient: GradientCommand);
1117}
1118
1119struct RenderToBuffer<'a, TargetPixelBuffer> {
1120 buffer: &'a mut TargetPixelBuffer,
1121 dirty_range_cache: Vec<core::ops::Range<i16>>,
1122 dirty_region: PhysicalRegion,
1123}
1124
1125impl<B: target_pixel_buffer::TargetPixelBuffer> RenderToBuffer<'_, B> {
1126 fn foreach_ranges(
1127 &mut self,
1128 geometry: &PhysicalRect,
1129 mut f: impl FnMut(i16, &mut [B::TargetPixel], i16, i16),
1130 ) {
1131 self.foreach_region(geometry, |buffer, rect, extra_left_clip, extra_right_clip| {
1132 for l in rect.y_range() {
1133 f(
1134 l,
1135 &mut buffer.line_slice(l as usize)
1136 [rect.min_x() as usize..rect.max_x() as usize],
1137 extra_left_clip,
1138 extra_right_clip,
1139 );
1140 }
1141 });
1142 }
1143
1144 fn foreach_region(
1145 &mut self,
1146 geometry: &PhysicalRect,
1147 mut f: impl FnMut(&mut B, PhysicalRect, i16, i16),
1148 ) {
1149 let mut line = geometry.min_y();
1150 while let Some(mut next) =
1151 region_line_ranges(&self.dirty_region, line, &mut self.dirty_range_cache)
1152 {
1153 next = next.min(geometry.max_y());
1154 for r in &self.dirty_range_cache {
1155 if geometry.origin.x >= r.end {
1156 continue;
1157 }
1158 let begin = r.start.max(geometry.origin.x);
1159 let end = r.end.min(geometry.origin.x + geometry.size.width);
1160 if begin >= end {
1161 continue;
1162 }
1163 let extra_left_clip = begin - geometry.origin.x;
1164 let extra_right_clip = geometry.origin.x + geometry.size.width - end;
1165
1166 let region = PhysicalRect {
1167 origin: PhysicalPoint::new(begin, line),
1168 size: PhysicalSize::new(end - begin, next - line),
1169 };
1170
1171 f(&mut self.buffer, region, extra_left_clip, extra_right_clip);
1172 }
1173 if next == geometry.max_y() {
1174 break;
1175 }
1176 line = next;
1177 }
1178 }
1179
1180 fn process_texture_impl(&mut self, geometry: PhysicalRect, texture: SceneTexture<'_>) {
1181 self.foreach_region(&geometry, |buffer, rect, extra_left_clip, extra_right_clip| {
1182 let tex_src_off_x = (texture.extra.off_x + Fixed::from_integer(extra_left_clip as u16))
1183 * Fixed::from_fixed(texture.extra.dx);
1184 let tex_src_off_y = (texture.extra.off_y
1185 + Fixed::from_integer((rect.origin.y - geometry.origin.y) as u16))
1186 * Fixed::from_fixed(texture.extra.dy);
1187 if !buffer.draw_texture(
1188 rect.origin.x,
1189 rect.origin.y,
1190 rect.size.width,
1191 rect.size.height,
1192 target_pixel_buffer::Texture {
1193 bytes: texture.data,
1194 pixel_format: texture.format,
1195 pixel_stride: texture.pixel_stride,
1196 width: texture.source_size().width as u16,
1197 height: texture.source_size().height as u16,
1198 delta_x: texture.extra.dx.0,
1199 delta_y: texture.extra.dy.0,
1200 source_offset_x: tex_src_off_x.0,
1201 source_offset_y: tex_src_off_y.0,
1202 },
1203 texture.extra.colorize.as_argb_encoded(),
1204 texture.extra.alpha,
1205 texture.extra.rotation,
1206 CompositionMode::default(),
1207 ) {
1208 let begin = rect.min_x();
1209 let end = rect.max_x();
1210 for l in rect.y_range() {
1211 draw_functions::draw_texture_line(
1212 &geometry,
1213 PhysicalLength::new(l),
1214 &texture,
1215 &mut buffer.line_slice(l as usize)[begin as usize..end as usize],
1216 extra_left_clip,
1217 extra_right_clip,
1218 );
1219 }
1220 }
1221 });
1222 }
1223
1224 fn process_rectangle_impl(
1225 &mut self,
1226 geometry: PhysicalRect,
1227 color: PremultipliedRgbaColor,
1228 composition_mode: CompositionMode,
1229 ) {
1230 self.foreach_region(&geometry, |buffer, rect, _extra_left_clip, _extra_right_clip| {
1231 if !buffer.fill_rectangle(
1232 rect.origin.x,
1233 rect.origin.y,
1234 rect.size.width,
1235 rect.size.height,
1236 color,
1237 composition_mode,
1238 ) {
1239 let begin = rect.min_x();
1240 let end = rect.max_x();
1241
1242 match composition_mode {
1243 CompositionMode::Source => {
1244 let mut fill_col = B::TargetPixel::background();
1245 B::TargetPixel::blend(&mut fill_col, color);
1246 for l in rect.y_range() {
1247 buffer.line_slice(l as usize)[begin as usize..end as usize]
1248 .fill(fill_col)
1249 }
1250 }
1251 CompositionMode::SourceOver => {
1252 for l in rect.y_range() {
1253 <B::TargetPixel>::blend_slice(
1254 &mut buffer.line_slice(l as usize)[begin as usize..end as usize],
1255 color,
1256 )
1257 }
1258 }
1259 }
1260 }
1261 })
1262 }
1263}
1264
1265impl<T: TargetPixel, B: target_pixel_buffer::TargetPixelBuffer<TargetPixel = T>> ProcessScene
1266 for RenderToBuffer<'_, B>
1267{
1268 fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
1269 self.process_texture_impl(geometry, texture)
1270 }
1271
1272 fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand) {
1273 let texture = buffer.as_texture();
1274 self.process_texture_impl(geometry, texture);
1275 }
1276
1277 fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
1278 self.process_rectangle_impl(geometry, color, CompositionMode::default());
1279 }
1280
1281 fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, rr: RoundedRectangle) {
1282 self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
1283 draw_functions::draw_rounded_rectangle_line(
1284 &geometry,
1285 PhysicalLength::new(line),
1286 &rr,
1287 buffer,
1288 extra_left_clip,
1289 extra_right_clip,
1290 );
1291 });
1292 }
1293
1294 fn process_gradient(&mut self, geometry: PhysicalRect, g: GradientCommand) {
1295 self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, _extra_right_clip| {
1296 draw_functions::draw_gradient_line(
1297 &geometry,
1298 PhysicalLength::new(line),
1299 &g,
1300 buffer,
1301 extra_left_clip,
1302 );
1303 });
1304 }
1305}
1306
1307#[derive(Default)]
1308struct PrepareScene {
1309 items: Vec<SceneItem>,
1310 vectors: SceneVectors,
1311}
1312
1313impl ProcessScene for PrepareScene {
1314 fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
1315 let size = geometry.size;
1316 if !size.is_empty() {
1317 let texture_index = self.vectors.textures.len() as u16;
1318 self.vectors.textures.push(texture);
1319 self.items.push(SceneItem {
1320 pos: geometry.origin,
1321 size,
1322 z: self.items.len() as u16,
1323 command: SceneCommand::Texture { texture_index },
1324 });
1325 }
1326 }
1327
1328 fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand) {
1329 let size = geometry.size;
1330 if !size.is_empty() {
1331 let shared_buffer_index = self.vectors.shared_buffers.len() as u16;
1332 self.vectors.shared_buffers.push(buffer);
1333 self.items.push(SceneItem {
1334 pos: geometry.origin,
1335 size,
1336 z: self.items.len() as u16,
1337 command: SceneCommand::SharedBuffer { shared_buffer_index },
1338 });
1339 }
1340 }
1341
1342 fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
1343 let size = geometry.size;
1344 if !size.is_empty() {
1345 let z = self.items.len() as u16;
1346 let pos = geometry.origin;
1347 self.items.push(SceneItem { pos, size, z, command: SceneCommand::Rectangle { color } });
1348 }
1349 }
1350
1351 fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle) {
1352 let size = geometry.size;
1353 if !size.is_empty() {
1354 let rectangle_index = self.vectors.rounded_rectangles.len() as u16;
1355 self.vectors.rounded_rectangles.push(data);
1356 self.items.push(SceneItem {
1357 pos: geometry.origin,
1358 size,
1359 z: self.items.len() as u16,
1360 command: SceneCommand::RoundedRectangle { rectangle_index },
1361 });
1362 }
1363 }
1364
1365 fn process_gradient(&mut self, geometry: PhysicalRect, gradient: GradientCommand) {
1366 let size = geometry.size;
1367 if !size.is_empty() {
1368 let gradient_index = self.vectors.gradients.len() as u16;
1369 self.vectors.gradients.push(gradient);
1370 self.items.push(SceneItem {
1371 pos: geometry.origin,
1372 size,
1373 z: self.items.len() as u16,
1374 command: SceneCommand::Gradient { gradient_index },
1375 });
1376 }
1377 }
1378}
1379
1380struct SceneBuilder<'a, T> {
1381 processor: T,
1382 state_stack: Vec<RenderState>,
1383 current_state: RenderState,
1384 scale_factor: ScaleFactor,
1385 window: &'a WindowInner,
1386 rotation: RotationInfo,
1387}
1388
1389impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
1390 fn new(
1391 screen_size: PhysicalSize,
1392 scale_factor: ScaleFactor,
1393 window: &'a WindowInner,
1394 processor: T,
1395 orientation: RenderingRotation,
1396 ) -> Self {
1397 Self {
1398 processor,
1399 state_stack: vec![],
1400 current_state: RenderState {
1401 alpha: 1.,
1402 offset: LogicalPoint::default(),
1403 clip: LogicalRect::new(
1404 LogicalPoint::default(),
1405 (screen_size.cast() / scale_factor).cast(),
1406 ),
1407 },
1408 scale_factor,
1409 window,
1410 rotation: RotationInfo { orientation, screen_size },
1411 }
1412 }
1413
1414 fn should_draw(&self, rect: &LogicalRect) -> bool {
1415 !rect.size.is_empty()
1416 && self.current_state.alpha > 0.01
1417 && self.current_state.clip.intersects(rect)
1418 }
1419
1420 fn draw_image_impl(
1421 &mut self,
1422 image_inner: &ImageInner,
1423 crate::graphics::FitResult {
1424 clip_rect: source_rect,
1425 source_to_target_x,
1426 source_to_target_y,
1427 size: fit_size,
1428 offset: image_fit_offset,
1429 tiled,
1430 }: crate::graphics::FitResult,
1431 colorize: Color,
1432 ) {
1433 let global_alpha_u16 = (self.current_state.alpha * 255.) as u16;
1434 let offset =
1435 self.current_state.offset.cast() * self.scale_factor + image_fit_offset.to_vector();
1436
1437 let physical_clip =
1438 (self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
1439 * self.scale_factor)
1440 .round()
1441 .cast();
1442
1443 let tiled_off = tiled.unwrap_or_default();
1444
1445 match image_inner {
1446 ImageInner::None => (),
1447 ImageInner::StaticTextures(StaticTextures {
1448 data,
1449 textures,
1450 size,
1451 original_size,
1452 ..
1453 }) => {
1454 let adjust_x = size.width as f32 / original_size.width as f32;
1455 let adjust_y = size.height as f32 / original_size.height as f32;
1456 let source_to_target_x = source_to_target_x / adjust_x;
1457 let source_to_target_y = source_to_target_y / adjust_y;
1458 let source_rect =
1459 source_rect.cast::<f32>().scale(adjust_x, adjust_y).round().cast();
1460 let Some(dx) = Fixed::from_f32(1. / source_to_target_x) else { return };
1461 let Some(dy) = Fixed::from_f32(1. / source_to_target_y) else { return };
1462
1463 for t in textures.as_slice() {
1464 // That's the source rect in the whole image coordinate
1465 let Some(src_rect) = t.rect.intersection(&source_rect) else { continue };
1466
1467 let target_rect = if tiled.is_some() {
1468 // FIXME! there could be gaps between the tiles
1469 euclid::Rect::new(offset, fit_size).round().cast::<i32>()
1470 } else {
1471 // map t.rect to to the target
1472 euclid::Rect::<f32, PhysicalPx>::from_untyped(
1473 &src_rect.translate(-source_rect.origin.to_vector()).cast(),
1474 )
1475 .scale(source_to_target_x, source_to_target_y)
1476 .translate(offset.to_vector())
1477 .round()
1478 .cast::<i32>()
1479 };
1480
1481 let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
1482 continue;
1483 };
1484
1485 let off_x = Fixed::from_integer(tiled_off.x as i32)
1486 + (Fixed::<i32, 8>::from_fixed(dx))
1487 * (clipped_target.origin.x - target_rect.origin.x) as i32;
1488 let off_y = Fixed::from_integer(tiled_off.y as i32)
1489 + (Fixed::<i32, 8>::from_fixed(dy))
1490 * (clipped_target.origin.y - target_rect.origin.y) as i32;
1491
1492 let pixel_stride = t.rect.width() as u16;
1493 let core::ops::Range { start, end } = compute_range_in_buffer(
1494 &PhysicalRect::from_untyped(
1495 &src_rect.translate(-t.rect.origin.to_vector()).cast(),
1496 ),
1497 pixel_stride as usize,
1498 );
1499 let bpp = t.format.bpp();
1500
1501 let color = if colorize.alpha() > 0 { colorize } else { t.color };
1502 let alpha = if colorize.alpha() > 0 || t.format == TexturePixelFormat::AlphaMap
1503 {
1504 color.alpha() as u16 * global_alpha_u16 / 255
1505 } else {
1506 global_alpha_u16
1507 } as u8;
1508
1509 self.processor.process_texture(
1510 clipped_target.cast().transformed(self.rotation),
1511 SceneTexture {
1512 data: &data.as_slice()[t.index..][start * bpp..end * bpp],
1513 pixel_stride,
1514 format: t.format,
1515 extra: SceneTextureExtra {
1516 colorize: color,
1517 alpha,
1518 rotation: self.rotation.orientation,
1519 dx,
1520 dy,
1521 off_x: Fixed::try_from_fixed(off_x).unwrap(),
1522 off_y: Fixed::try_from_fixed(off_y).unwrap(),
1523 },
1524 },
1525 );
1526 }
1527 }
1528
1529 ImageInner::NineSlice(..) => unreachable!(),
1530 _ => {
1531 let target_rect = euclid::Rect::new(offset, fit_size).round().cast();
1532 let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
1533 return;
1534 };
1535 let orig = image_inner.size().cast::<f32>();
1536 let svg_target_size = if tiled.is_some() {
1537 euclid::size2(orig.width * source_to_target_x, orig.height * source_to_target_y)
1538 .cast()
1539 } else {
1540 target_rect.size.cast()
1541 };
1542 if let Some(buffer) = image_inner.render_to_buffer(Some(svg_target_size)) {
1543 let buf_size = buffer.size().cast::<f32>();
1544 let dx =
1545 Fixed::from_f32(buf_size.width / orig.width / source_to_target_x).unwrap();
1546 let dy = Fixed::from_f32(buf_size.height / orig.height / source_to_target_y)
1547 .unwrap();
1548
1549 let off_x = (Fixed::<i32, 8>::from_fixed(dx))
1550 * (clipped_target.origin.x - target_rect.origin.x) as i32
1551 + Fixed::from_f32(tiled_off.x as f32 * buf_size.width / orig.width)
1552 .unwrap();
1553 let off_y = (Fixed::<i32, 8>::from_fixed(dy))
1554 * (clipped_target.origin.y - target_rect.origin.y) as i32
1555 + Fixed::from_f32(tiled_off.y as f32 * buf_size.height / orig.height)
1556 .unwrap();
1557
1558 let alpha = if colorize.alpha() > 0 {
1559 colorize.alpha() as u16 * global_alpha_u16 / 255
1560 } else {
1561 global_alpha_u16
1562 } as u8;
1563
1564 self.processor.process_shared_image_buffer(
1565 clipped_target.cast().transformed(self.rotation),
1566 SharedBufferCommand {
1567 buffer: SharedBufferData::SharedImage(buffer),
1568 source_rect: PhysicalRect::from_untyped(
1569 &source_rect
1570 .cast::<f32>()
1571 .scale(
1572 buf_size.width / orig.width,
1573 buf_size.height / orig.height,
1574 )
1575 .round()
1576 .cast(),
1577 ),
1578
1579 extra: SceneTextureExtra {
1580 colorize,
1581 alpha,
1582 rotation: self.rotation.orientation,
1583 dx,
1584 dy,
1585 off_x: Fixed::try_from_fixed(off_x).unwrap(),
1586 off_y: Fixed::try_from_fixed(off_y).unwrap(),
1587 },
1588 },
1589 );
1590 } else {
1591 unimplemented!("The image cannot be rendered")
1592 }
1593 }
1594 };
1595 }
1596
1597 fn draw_text_paragraph<Font>(
1598 &mut self,
1599 paragraph: &TextParagraphLayout<'_, Font>,
1600 physical_clip: euclid::Rect<f32, PhysicalPx>,
1601 offset: euclid::Vector2D<f32, PhysicalPx>,
1602 color: Color,
1603 selection: Option<SelectionInfo>,
1604 ) where
1605 Font: AbstractFont + crate::textlayout::TextShaper<Length = PhysicalLength> + GlyphRenderer,
1606 {
1607 paragraph
1608 .layout_lines::<()>(
1609 |glyphs, line_x, line_y, _, sel| {
1610 let baseline_y = line_y + paragraph.layout.font.ascent();
1611 if let (Some(sel), Some(selection)) = (sel, &selection) {
1612 let geometry = euclid::rect(
1613 line_x.get() + sel.start.get(),
1614 line_y.get(),
1615 (sel.end - sel.start).get(),
1616 paragraph.layout.font.height().get(),
1617 );
1618 if let Some(clipped_src) = geometry.intersection(&physical_clip.cast()) {
1619 let geometry =
1620 clipped_src.translate(offset.cast()).transformed(self.rotation);
1621 self.processor
1622 .process_rectangle(geometry, selection.selection_background.into());
1623 }
1624 }
1625 let scale_delta = paragraph.layout.font.scale_delta();
1626 for positioned_glyph in glyphs {
1627 let Some(glyph) =
1628 paragraph.layout.font.render_glyph(positioned_glyph.glyph_id)
1629 else {
1630 continue;
1631 };
1632
1633 let gl_x = PhysicalLength::new((-glyph.x).truncate() as i16);
1634 let gl_y = PhysicalLength::new(glyph.y.truncate() as i16);
1635 let target_rect = PhysicalRect::new(
1636 PhysicalPoint::from_lengths(
1637 line_x + positioned_glyph.x - gl_x,
1638 baseline_y - gl_y - glyph.height,
1639 ),
1640 glyph.size(),
1641 )
1642 .cast();
1643
1644 let color = match &selection {
1645 Some(s) if s.selection.contains(&positioned_glyph.text_byte_offset) => {
1646 s.selection_color
1647 }
1648 _ => color,
1649 };
1650
1651 let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
1652 continue;
1653 };
1654 let geometry = clipped_target.translate(offset).round();
1655 let origin = (geometry.origin - offset.round()).round().cast::<i16>();
1656 let off_x = origin.x - target_rect.origin.x as i16;
1657 let off_y = origin.y - target_rect.origin.y as i16;
1658 let pixel_stride = glyph.pixel_stride;
1659 let mut geometry = geometry.cast();
1660 if geometry.size.width > glyph.width.get() - off_x {
1661 geometry.size.width = glyph.width.get() - off_x
1662 }
1663 if geometry.size.height > glyph.height.get() - off_y {
1664 geometry.size.height = glyph.height.get() - off_y
1665 }
1666 let source_size = geometry.size;
1667 if source_size.is_empty() {
1668 continue;
1669 }
1670
1671 match &glyph.alpha_map {
1672 fonts::GlyphAlphaMap::Static(data) => {
1673 let texture = if !glyph.sdf {
1674 SceneTexture {
1675 data,
1676 pixel_stride,
1677 format: TexturePixelFormat::AlphaMap,
1678 extra: SceneTextureExtra {
1679 colorize: color,
1680 // color already is mixed with global alpha
1681 alpha: color.alpha(),
1682 rotation: self.rotation.orientation,
1683 dx: Fixed::from_integer(1),
1684 dy: Fixed::from_integer(1),
1685 off_x: Fixed::from_integer(off_x as u16),
1686 off_y: Fixed::from_integer(off_y as u16),
1687 },
1688 }
1689 } else {
1690 let delta32 = Fixed::<i32, 8>::from_fixed(scale_delta);
1691 let normalize = |x: Fixed<i32, 8>| {
1692 if x < Fixed::from_integer(0) {
1693 x + Fixed::from_integer(1)
1694 } else {
1695 x
1696 }
1697 };
1698 let fract_x = normalize(
1699 (-glyph.x) - Fixed::from_integer(gl_x.get() as _),
1700 );
1701 let off_x = delta32 * off_x as i32 + fract_x;
1702 let fract_y =
1703 normalize(glyph.y - Fixed::from_integer(gl_y.get() as _));
1704 let off_y = delta32 * off_y as i32 + fract_y;
1705 SceneTexture {
1706 data,
1707 pixel_stride,
1708 format: TexturePixelFormat::SignedDistanceField,
1709 extra: SceneTextureExtra {
1710 colorize: color,
1711 // color already is mixed with global alpha
1712 alpha: color.alpha(),
1713 rotation: self.rotation.orientation,
1714 dx: scale_delta,
1715 dy: scale_delta,
1716 off_x: Fixed::try_from_fixed(off_x).unwrap(),
1717 off_y: Fixed::try_from_fixed(off_y).unwrap(),
1718 },
1719 }
1720 };
1721 self.processor
1722 .process_texture(geometry.transformed(self.rotation), texture);
1723 }
1724 fonts::GlyphAlphaMap::Shared(data) => {
1725 let source_rect = euclid::rect(0, 0, glyph.width.0, glyph.height.0);
1726 self.processor.process_shared_image_buffer(
1727 geometry.transformed(self.rotation),
1728 SharedBufferCommand {
1729 buffer: SharedBufferData::AlphaMap {
1730 data: data.clone(),
1731 width: pixel_stride,
1732 },
1733 source_rect,
1734 extra: SceneTextureExtra {
1735 colorize: color,
1736 // color already is mixed with global alpha
1737 alpha: color.alpha(),
1738 rotation: self.rotation.orientation,
1739 dx: Fixed::from_integer(1),
1740 dy: Fixed::from_integer(1),
1741 off_x: Fixed::from_integer(off_x as u16),
1742 off_y: Fixed::from_integer(off_y as u16),
1743 },
1744 },
1745 );
1746 }
1747 }
1748 }
1749 core::ops::ControlFlow::Continue(())
1750 },
1751 selection.as_ref().map(|s| s.selection.clone()),
1752 )
1753 .ok();
1754 }
1755
1756 /// Returns the color, mixed with the current_state's alpha
1757 fn alpha_color(&self, color: Color) -> Color {
1758 if self.current_state.alpha < 1.0 {
1759 Color::from_argb_u8(
1760 (color.alpha() as f32 * self.current_state.alpha) as u8,
1761 color.red(),
1762 color.green(),
1763 color.blue(),
1764 )
1765 } else {
1766 color
1767 }
1768 }
1769}
1770
1771struct SelectionInfo {
1772 selection_color: Color,
1773 selection_background: Color,
1774 selection: core::ops::Range<usize>,
1775}
1776
1777#[derive(Clone, Copy, Debug)]
1778struct RenderState {
1779 alpha: f32,
1780 offset: LogicalPoint,
1781 clip: LogicalRect,
1782}
1783
1784impl<T: ProcessScene> crate::item_rendering::ItemRenderer for SceneBuilder<'_, T> {
1785 #[allow(clippy::unnecessary_cast)] // Coord!
1786 fn draw_rectangle(
1787 &mut self,
1788 rect: Pin<&dyn RenderRectangle>,
1789 _: &ItemRc,
1790 size: LogicalSize,
1791 _cache: &CachedRenderingData,
1792 ) {
1793 let geom = LogicalRect::from(size);
1794 if self.should_draw(&geom) {
1795 let clipped = match geom.intersection(&self.current_state.clip) {
1796 Some(geom) => geom,
1797 None => return,
1798 };
1799
1800 let background = rect.background();
1801 if let Brush::LinearGradient(g) = background {
1802 let geom2 = (geom.cast() * self.scale_factor).transformed(self.rotation);
1803 let clipped2 = (clipped.cast() * self.scale_factor).transformed(self.rotation);
1804 let act_rect = (clipped.translate(self.current_state.offset.to_vector()).cast()
1805 * self.scale_factor)
1806 .round()
1807 .cast()
1808 .transformed(self.rotation);
1809 let axis_angle = (360. - self.rotation.orientation.angle()) % 360.;
1810 let angle = g.angle() - axis_angle;
1811 let tan = angle.to_radians().tan().abs();
1812 let start = if !tan.is_finite() {
1813 255.
1814 } else {
1815 let h = tan * geom2.width() as f32;
1816 255. * h / (h + geom2.height() as f32)
1817 } as u8;
1818 let mut angle = angle as i32 % 360;
1819 if angle < 0 {
1820 angle += 360;
1821 }
1822 let mut stops = g.stops().copied().peekable();
1823 let mut idx = 0;
1824 let stop_count = g.stops().count();
1825 while let (Some(mut s1), Some(mut s2)) = (stops.next(), stops.peek().copied()) {
1826 let mut flags = 0;
1827 if (angle % 180) > 90 {
1828 flags |= 0b1;
1829 }
1830 if angle <= 90 || angle > 270 {
1831 core::mem::swap(&mut s1, &mut s2);
1832 s1.position = 1. - s1.position;
1833 s2.position = 1. - s2.position;
1834 if idx == 0 {
1835 flags |= 0b100;
1836 }
1837 if idx == stop_count - 2 {
1838 flags |= 0b010;
1839 }
1840 } else {
1841 if idx == 0 {
1842 flags |= 0b010;
1843 }
1844 if idx == stop_count - 2 {
1845 flags |= 0b100;
1846 }
1847 }
1848
1849 idx += 1;
1850
1851 let (adjust_left, adjust_right) = if (angle % 180) > 90 {
1852 (
1853 (geom2.width() * s1.position).floor() as i16,
1854 (geom2.width() * (1. - s2.position)).ceil() as i16,
1855 )
1856 } else {
1857 (
1858 (geom2.width() * (1. - s2.position)).ceil() as i16,
1859 (geom2.width() * s1.position).floor() as i16,
1860 )
1861 };
1862
1863 let gr = GradientCommand {
1864 color1: self.alpha_color(s1.color).into(),
1865 color2: self.alpha_color(s2.color).into(),
1866 start,
1867 flags,
1868 top_clip: Length::new(
1869 (clipped2.min_y() - geom2.min_y()) as i16
1870 - (geom2.height() * s1.position).floor() as i16,
1871 ),
1872 bottom_clip: Length::new(
1873 (geom2.max_y() - clipped2.max_y()) as i16
1874 - (geom2.height() * (1. - s2.position)).ceil() as i16,
1875 ),
1876 left_clip: Length::new(
1877 (clipped2.min_x() - geom2.min_x()) as i16 - adjust_left,
1878 ),
1879 right_clip: Length::new(
1880 (geom2.max_x() - clipped2.max_x()) as i16 - adjust_right,
1881 ),
1882 };
1883
1884 let size_y = act_rect.height_length() + gr.top_clip + gr.bottom_clip;
1885 let size_x = act_rect.width_length() + gr.left_clip + gr.right_clip;
1886 if size_x.get() == 0 || size_y.get() == 0 {
1887 // the position are too close to each other
1888 // FIXME: For the first or the last, we should draw a plain color to the end
1889 continue;
1890 }
1891
1892 self.processor.process_gradient(act_rect, gr);
1893 }
1894 return;
1895 }
1896
1897 let color = self.alpha_color(background.color());
1898
1899 if color.alpha() == 0 {
1900 return;
1901 }
1902 let geometry = (clipped.translate(self.current_state.offset.to_vector()).cast()
1903 * self.scale_factor)
1904 .round()
1905 .cast()
1906 .transformed(self.rotation);
1907
1908 self.processor.process_rectangle(geometry, color.into());
1909 }
1910 }
1911
1912 #[allow(clippy::unnecessary_cast)] // Coord
1913 fn draw_border_rectangle(
1914 &mut self,
1915 rect: Pin<&dyn RenderBorderRectangle>,
1916 _: &ItemRc,
1917 size: LogicalSize,
1918 _: &CachedRenderingData,
1919 ) {
1920 let geom = LogicalRect::from(size);
1921 if self.should_draw(&geom) {
1922 let mut border = rect.border_width();
1923 let radius = rect.border_radius();
1924 // FIXME: gradients
1925 let color = self.alpha_color(rect.background().color());
1926 let border_color = if border.get() as f32 > 0.01 {
1927 self.alpha_color(rect.border_color().color())
1928 } else {
1929 Color::default()
1930 };
1931
1932 let mut border_color = PremultipliedRgbaColor::from(border_color);
1933 let color = PremultipliedRgbaColor::from(color);
1934 if border_color.alpha == 0 {
1935 border = LogicalLength::new(0 as _);
1936 } else if border_color.alpha < 255 {
1937 // Find a color for the border which is an equivalent to blend the background and then the border.
1938 // In the end, the resulting of blending the background and the color is
1939 // (A + B) + C, where A is the buffer color, B is the background, and C is the border.
1940 // which expands to (A*(1-Bα) + B*Bα)*(1-Cα) + C*Cα = A*(1-(Bα+Cα-Bα*Cα)) + B*Bα*(1-Cα) + C*Cα
1941 // so let the new alpha be: Nα = Bα+Cα-Bα*Cα, then this is A*(1-Nα) + N*Nα
1942 // with N = (B*Bα*(1-Cα) + C*Cα)/Nα
1943 // N being the equivalent color of the border that mixes the background and the border
1944 // In pre-multiplied space, the formula simplifies further N' = B'*(1-Cα) + C'
1945 let b = border_color;
1946 let b_alpha_16 = b.alpha as u16;
1947 border_color = PremultipliedRgbaColor {
1948 red: ((color.red as u16 * (255 - b_alpha_16)) / 255) as u8 + b.red,
1949 green: ((color.green as u16 * (255 - b_alpha_16)) / 255) as u8 + b.green,
1950 blue: ((color.blue as u16 * (255 - b_alpha_16)) / 255) as u8 + b.blue,
1951 alpha: (color.alpha as u16 + b_alpha_16
1952 - (color.alpha as u16 * b_alpha_16) / 255) as u8,
1953 }
1954 }
1955
1956 if !radius.is_zero() {
1957 let radius = radius
1958 .min(LogicalBorderRadius::from_length(geom.width_length() / 2 as Coord))
1959 .min(LogicalBorderRadius::from_length(geom.height_length() / 2 as Coord));
1960 if let Some(clipped) = geom.intersection(&self.current_state.clip) {
1961 let geom2 = (geom.cast() * self.scale_factor).transformed(self.rotation);
1962 let clipped2 = (clipped.cast() * self.scale_factor).transformed(self.rotation);
1963 let geometry =
1964 (clipped.translate(self.current_state.offset.to_vector()).cast()
1965 * self.scale_factor)
1966 .round()
1967 .cast()
1968 .transformed(self.rotation);
1969 let radius =
1970 (radius.cast() * self.scale_factor).cast().transformed(self.rotation);
1971 // Add a small value to make sure that the clip is always positive despite floating point shenanigans
1972 const E: f32 = 0.00001;
1973
1974 self.processor.process_rounded_rectangle(
1975 geometry,
1976 RoundedRectangle {
1977 radius,
1978 width: (border.cast() * self.scale_factor).cast(),
1979 border_color,
1980 inner_color: color,
1981 top_clip: PhysicalLength::new(
1982 (clipped2.min_y() - geom2.min_y() + E) as _,
1983 ),
1984 bottom_clip: PhysicalLength::new(
1985 (geom2.max_y() - clipped2.max_y() + E) as _,
1986 ),
1987 left_clip: PhysicalLength::new(
1988 (clipped2.min_x() - geom2.min_x() + E) as _,
1989 ),
1990 right_clip: PhysicalLength::new(
1991 (geom2.max_x() - clipped2.max_x() + E) as _,
1992 ),
1993 },
1994 );
1995 }
1996 return;
1997 }
1998
1999 if color.alpha > 0 {
2000 if let Some(r) = geom
2001 .inflate(-border.get(), -border.get())
2002 .intersection(&self.current_state.clip)
2003 {
2004 let geometry = (r.translate(self.current_state.offset.to_vector()).cast()
2005 * self.scale_factor)
2006 .round()
2007 .cast()
2008 .transformed(self.rotation);
2009 self.processor.process_rectangle(geometry, color);
2010 }
2011 }
2012
2013 // FIXME: gradients
2014 if border_color.alpha > 0 {
2015 let mut add_border = |r: LogicalRect| {
2016 if let Some(r) = r.intersection(&self.current_state.clip) {
2017 let geometry =
2018 (r.translate(self.current_state.offset.to_vector()).try_cast()?
2019 * self.scale_factor)
2020 .round()
2021 .try_cast()?
2022 .transformed(self.rotation);
2023 self.processor.process_rectangle(geometry, border_color);
2024 }
2025 Some(())
2026 };
2027 let b = border.get();
2028 let err = || {
2029 panic!(
2030 "invalid border rectangle {geom:?} border={b} state={:?}",
2031 self.current_state
2032 )
2033 };
2034 add_border(euclid::rect(0 as _, 0 as _, geom.width(), b)).unwrap_or_else(err);
2035 add_border(euclid::rect(0 as _, geom.height() - b, geom.width(), b))
2036 .unwrap_or_else(err);
2037 add_border(euclid::rect(0 as _, b, b, geom.height() - b - b)).unwrap_or_else(err);
2038 add_border(euclid::rect(geom.width() - b, b, b, geom.height() - b - b))
2039 .unwrap_or_else(err);
2040 }
2041 }
2042 }
2043
2044 fn draw_window_background(
2045 &mut self,
2046 rect: Pin<&dyn RenderRectangle>,
2047 _self_rc: &ItemRc,
2048 _size: LogicalSize,
2049 _cache: &CachedRenderingData,
2050 ) {
2051 // register a dependency for the partial renderer's dirty tracker. The actual rendering is done earlier in the software renderer.
2052 let _ = rect.background();
2053 }
2054
2055 fn draw_image(
2056 &mut self,
2057 image: Pin<&dyn RenderImage>,
2058 _: &ItemRc,
2059 size: LogicalSize,
2060 _: &CachedRenderingData,
2061 ) {
2062 let geom = LogicalRect::from(size);
2063 if self.should_draw(&geom) {
2064 let source = image.source();
2065
2066 let image_inner: &ImageInner = (&source).into();
2067 if let ImageInner::NineSlice(nine) = image_inner {
2068 let colorize = image.colorize().color();
2069 let source_size = source.size();
2070 for fit in crate::graphics::fit9slice(
2071 source_size,
2072 nine.1,
2073 size.cast() * self.scale_factor,
2074 self.scale_factor,
2075 image.alignment(),
2076 image.tiling(),
2077 ) {
2078 self.draw_image_impl(&nine.0, fit, colorize);
2079 }
2080 return;
2081 }
2082
2083 let source_clip = image.source_clip().map_or_else(
2084 || euclid::Rect::new(Default::default(), source.size().cast()),
2085 |clip| {
2086 clip.intersection(&euclid::Rect::from_size(source.size().cast()))
2087 .unwrap_or_default()
2088 },
2089 );
2090
2091 let phys_size = geom.size_length().cast() * self.scale_factor;
2092 let fit = crate::graphics::fit(
2093 image.image_fit(),
2094 phys_size,
2095 source_clip,
2096 self.scale_factor,
2097 image.alignment(),
2098 image.tiling(),
2099 );
2100 self.draw_image_impl(image_inner, fit, image.colorize().color());
2101 }
2102 }
2103
2104 fn draw_text(
2105 &mut self,
2106 text: Pin<&dyn crate::item_rendering::RenderText>,
2107 _: &ItemRc,
2108 size: LogicalSize,
2109 _cache: &CachedRenderingData,
2110 ) {
2111 let string = text.text();
2112 if string.trim().is_empty() {
2113 return;
2114 }
2115 let geom = LogicalRect::from(size);
2116 if !self.should_draw(&geom) {
2117 return;
2118 }
2119
2120 let font_request = text.font_request(self.window);
2121
2122 let color = self.alpha_color(text.color().color());
2123 let max_size = (geom.size.cast() * self.scale_factor).cast();
2124
2125 // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside
2126 // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below).
2127 // FIXME: we should allow drawing outside of the Text element's boundaries.
2128 let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom)
2129 {
2130 logical_clip.cast() * self.scale_factor
2131 } else {
2132 return; // This should have been caught earlier already
2133 };
2134 let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
2135
2136 let font = fonts::match_font(&font_request, self.scale_factor);
2137
2138 match font {
2139 fonts::Font::PixelFont(pf) => {
2140 let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor);
2141 let (horizontal_alignment, vertical_alignment) = text.alignment();
2142
2143 let paragraph = TextParagraphLayout {
2144 string: &string,
2145 layout,
2146 max_width: max_size.width_length(),
2147 max_height: max_size.height_length(),
2148 horizontal_alignment,
2149 vertical_alignment,
2150 wrap: text.wrap(),
2151 overflow: text.overflow(),
2152 single_line: false,
2153 };
2154
2155 self.draw_text_paragraph(&paragraph, physical_clip, offset, color, None);
2156 }
2157 #[cfg(feature = "software-renderer-systemfonts")]
2158 fonts::Font::VectorFont(vf) => {
2159 let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor);
2160 let (horizontal_alignment, vertical_alignment) = text.alignment();
2161
2162 let paragraph = TextParagraphLayout {
2163 string: &string,
2164 layout,
2165 max_width: max_size.width_length(),
2166 max_height: max_size.height_length(),
2167 horizontal_alignment,
2168 vertical_alignment,
2169 wrap: text.wrap(),
2170 overflow: text.overflow(),
2171 single_line: false,
2172 };
2173
2174 self.draw_text_paragraph(&paragraph, physical_clip, offset, color, None);
2175 }
2176 }
2177 }
2178
2179 fn draw_text_input(
2180 &mut self,
2181 text_input: Pin<&crate::items::TextInput>,
2182 _: &ItemRc,
2183 size: LogicalSize,
2184 ) {
2185 let geom = LogicalRect::from(size);
2186 if !self.should_draw(&geom) {
2187 return;
2188 }
2189
2190 let font_request = text_input.font_request(&self.window.window_adapter());
2191 let max_size = (geom.size.cast() * self.scale_factor).cast();
2192
2193 // Clip glyphs not only against the global clip but also against the Text's geometry to avoid drawing outside
2194 // of its boundaries (that breaks partial rendering and the cast to usize for the item relative coordinate below).
2195 // FIXME: we should allow drawing outside of the Text element's boundaries.
2196 let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom)
2197 {
2198 logical_clip.cast() * self.scale_factor
2199 } else {
2200 return; // This should have been caught earlier already
2201 };
2202 let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
2203
2204 let font = fonts::match_font(&font_request, self.scale_factor);
2205
2206 let text_visual_representation = text_input.visual_representation(None);
2207 let color = self.alpha_color(text_visual_representation.text_color.color());
2208
2209 let selection =
2210 (!text_visual_representation.selection_range.is_empty()).then_some(SelectionInfo {
2211 selection_background: self.alpha_color(text_input.selection_background_color()),
2212 selection_color: self.alpha_color(text_input.selection_foreground_color()),
2213 selection: text_visual_representation.selection_range.clone(),
2214 });
2215
2216 let cursor_pos_and_height = match font {
2217 fonts::Font::PixelFont(pf) => {
2218 let paragraph = TextParagraphLayout {
2219 string: &text_visual_representation.text,
2220 layout: fonts::text_layout_for_font(&pf, &font_request, self.scale_factor),
2221 max_width: max_size.width_length(),
2222 max_height: max_size.height_length(),
2223 horizontal_alignment: text_input.horizontal_alignment(),
2224 vertical_alignment: text_input.vertical_alignment(),
2225 wrap: text_input.wrap(),
2226 overflow: TextOverflow::Clip,
2227 single_line: text_input.single_line(),
2228 };
2229
2230 self.draw_text_paragraph(&paragraph, physical_clip, offset, color, selection);
2231
2232 text_visual_representation.cursor_position.map(|cursor_offset| {
2233 (paragraph.cursor_pos_for_byte_offset(cursor_offset), pf.height())
2234 })
2235 }
2236 #[cfg(feature = "software-renderer-systemfonts")]
2237 fonts::Font::VectorFont(vf) => {
2238 let paragraph = TextParagraphLayout {
2239 string: &text_visual_representation.text,
2240 layout: fonts::text_layout_for_font(&vf, &font_request, self.scale_factor),
2241 max_width: max_size.width_length(),
2242 max_height: max_size.height_length(),
2243 horizontal_alignment: text_input.horizontal_alignment(),
2244 vertical_alignment: text_input.vertical_alignment(),
2245 wrap: text_input.wrap(),
2246 overflow: TextOverflow::Clip,
2247 single_line: text_input.single_line(),
2248 };
2249
2250 self.draw_text_paragraph(&paragraph, physical_clip, offset, color, selection);
2251
2252 text_visual_representation.cursor_position.map(|cursor_offset| {
2253 (paragraph.cursor_pos_for_byte_offset(cursor_offset), vf.height())
2254 })
2255 }
2256 };
2257
2258 if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height {
2259 let cursor_rect = PhysicalRect::new(
2260 PhysicalPoint::from_lengths(cursor_x, cursor_y),
2261 PhysicalSize::from_lengths(
2262 (text_input.text_cursor_width().cast() * self.scale_factor).cast(),
2263 cursor_height,
2264 ),
2265 );
2266
2267 if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) {
2268 let geometry = clipped_src.translate(offset.cast()).transformed(self.rotation);
2269 #[allow(unused_mut)]
2270 let mut cursor_color = text_visual_representation.cursor_color;
2271 #[cfg(all(feature = "std", target_os = "macos"))]
2272 {
2273 // On macOs, the cursor color is different than other platform. Use a hack to pass the screenshot test.
2274 static IS_SCREENSHOT_TEST: std::sync::OnceLock<bool> =
2275 std::sync::OnceLock::new();
2276 if *IS_SCREENSHOT_TEST.get_or_init(|| {
2277 std::env::var_os("CARGO_PKG_NAME").unwrap_or_default()
2278 == "test-driver-screenshots"
2279 }) {
2280 cursor_color = color;
2281 }
2282 }
2283 self.processor.process_rectangle(geometry, self.alpha_color(cursor_color).into());
2284 }
2285 }
2286 }
2287
2288 #[cfg(feature = "std")]
2289 fn draw_path(&mut self, _path: Pin<&crate::items::Path>, _: &ItemRc, _size: LogicalSize) {
2290 // TODO
2291 }
2292
2293 fn draw_box_shadow(
2294 &mut self,
2295 _box_shadow: Pin<&crate::items::BoxShadow>,
2296 _: &ItemRc,
2297 _size: LogicalSize,
2298 ) {
2299 // TODO
2300 }
2301
2302 fn combine_clip(
2303 &mut self,
2304 other: LogicalRect,
2305 _radius: LogicalBorderRadius,
2306 _border_width: LogicalLength,
2307 ) -> bool {
2308 match self.current_state.clip.intersection(&other) {
2309 Some(r) => {
2310 self.current_state.clip = r;
2311 true
2312 }
2313 None => {
2314 self.current_state.clip = LogicalRect::default();
2315 false
2316 }
2317 }
2318 // TODO: handle radius and border
2319 }
2320
2321 fn get_current_clip(&self) -> LogicalRect {
2322 self.current_state.clip
2323 }
2324
2325 fn translate(&mut self, distance: LogicalVector) {
2326 self.current_state.offset += distance;
2327 self.current_state.clip = self.current_state.clip.translate(-distance)
2328 }
2329
2330 fn translation(&self) -> LogicalVector {
2331 self.current_state.offset.to_vector()
2332 }
2333
2334 fn rotate(&mut self, _angle_in_degrees: f32) {
2335 // TODO (#6068)
2336 }
2337
2338 fn apply_opacity(&mut self, opacity: f32) {
2339 self.current_state.alpha *= opacity;
2340 }
2341
2342 fn save_state(&mut self) {
2343 self.state_stack.push(self.current_state);
2344 }
2345
2346 fn restore_state(&mut self) {
2347 self.current_state = self.state_stack.pop().unwrap();
2348 }
2349
2350 fn scale_factor(&self) -> f32 {
2351 self.scale_factor.0
2352 }
2353
2354 fn draw_cached_pixmap(
2355 &mut self,
2356 _item: &ItemRc,
2357 update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
2358 ) {
2359 // FIXME: actually cache the pixmap
2360 update_fn(&mut |width, height, data| {
2361 let img = SharedImageBuffer::RGBA8Premultiplied(SharedPixelBuffer::clone_from_slice(
2362 data, width, height,
2363 ));
2364
2365 let physical_clip = (self.current_state.clip.cast() * self.scale_factor).cast();
2366 let source_rect = euclid::rect(0, 0, width as _, height as _);
2367
2368 if let Some(clipped_src) = source_rect.intersection(&physical_clip) {
2369 let geometry = clipped_src
2370 .translate(
2371 (self.current_state.offset.cast() * self.scale_factor).to_vector().cast(),
2372 )
2373 .round_in();
2374
2375 self.processor.process_shared_image_buffer(
2376 geometry.cast().transformed(self.rotation),
2377 SharedBufferCommand {
2378 buffer: SharedBufferData::SharedImage(img),
2379 source_rect,
2380 extra: SceneTextureExtra {
2381 colorize: Default::default(),
2382 alpha: (self.current_state.alpha * 255.) as u8,
2383 rotation: self.rotation.orientation,
2384 dx: Fixed::from_integer(1),
2385 dy: Fixed::from_integer(1),
2386 off_x: Fixed::from_integer(clipped_src.min_x() as _),
2387 off_y: Fixed::from_integer(clipped_src.min_y() as _),
2388 },
2389 },
2390 );
2391 }
2392 });
2393 }
2394
2395 fn draw_string(&mut self, string: &str, color: Color) {
2396 let font_request = Default::default();
2397 let font = fonts::match_font(&font_request, self.scale_factor);
2398 let clip = self.current_state.clip.cast() * self.scale_factor;
2399
2400 match font {
2401 fonts::Font::PixelFont(pf) => {
2402 let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor);
2403
2404 let paragraph = TextParagraphLayout {
2405 string,
2406 layout,
2407 max_width: clip.width_length().cast(),
2408 max_height: clip.height_length().cast(),
2409 horizontal_alignment: Default::default(),
2410 vertical_alignment: Default::default(),
2411 wrap: Default::default(),
2412 overflow: Default::default(),
2413 single_line: false,
2414 };
2415
2416 self.draw_text_paragraph(&paragraph, clip, Default::default(), color, None);
2417 }
2418 #[cfg(feature = "software-renderer-systemfonts")]
2419 fonts::Font::VectorFont(vf) => {
2420 let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor);
2421
2422 let paragraph = TextParagraphLayout {
2423 string,
2424 layout,
2425 max_width: clip.width_length().cast(),
2426 max_height: clip.height_length().cast(),
2427 horizontal_alignment: Default::default(),
2428 vertical_alignment: Default::default(),
2429 wrap: Default::default(),
2430 overflow: Default::default(),
2431 single_line: false,
2432 };
2433
2434 self.draw_text_paragraph(&paragraph, clip, Default::default(), color, None);
2435 }
2436 }
2437 }
2438
2439 fn draw_image_direct(&mut self, _image: crate::graphics::Image) {
2440 todo!()
2441 }
2442
2443 fn window(&self) -> &crate::window::WindowInner {
2444 self.window
2445 }
2446
2447 fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
2448 None
2449 }
2450}
2451
2452impl<T: ProcessScene> crate::item_rendering::ItemRendererFeatures for SceneBuilder<'_, T> {
2453 const SUPPORTS_TRANSFORMATIONS: bool = false;
2454}
2455