#![warn(missing_docs)]
mod draw_functions;
mod fixed;
mod fonts;
mod minimal_software_window;
mod scene;
use self::fonts::GlyphRenderer;
pub use self::minimal_software_window::MinimalSoftwareWindow;
use self::scene::*;
use crate::api::PlatformError;
use crate::graphics::rendering_metrics_collector::{RefreshMode, RenderingMetricsCollector};
use crate::graphics::{
BorderRadius, PixelFormat, Rgba8Pixel, SharedImageBuffer, SharedPixelBuffer,
};
use crate::item_rendering::{
CachedRenderingData, DirtyRegion, PartialRenderingState, RenderBorderRectangle, RenderImage,
};
use crate::items::{ItemRc, TextOverflow, TextWrap};
use crate::lengths::{
LogicalBorderRadius, LogicalLength, LogicalPoint, LogicalRect, LogicalSize, LogicalVector,
PhysicalPx, PointLengths, RectLengths, ScaleFactor, SizeLengths,
};
use crate::renderer::RendererSealed;
use crate::textlayout::{AbstractFont, FontMetrics, TextParagraphLayout};
use crate::window::{WindowAdapter, WindowInner};
use crate::{Brush, Color, Coord, ImageInner, StaticTextures};
use alloc::rc::{Rc, Weak};
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use core::cell::{Cell, RefCell};
use core::pin::Pin;
use euclid::Length;
use fixed::Fixed;
#[allow(unused)]
use num_traits::Float;
use num_traits::NumCast;
pub use draw_functions::{PremultipliedRgbaColor, Rgb565Pixel, TargetPixel};
type PhysicalLength = euclid::Length<i16, PhysicalPx>;
type PhysicalRect = euclid::Rect<i16, PhysicalPx>;
type PhysicalSize = euclid::Size2D<i16, PhysicalPx>;
type PhysicalPoint = euclid::Point2D<i16, PhysicalPx>;
type PhysicalBorderRadius = BorderRadius<i16, PhysicalPx>;
pub use crate::item_rendering::RepaintBufferType;
#[non_exhaustive]
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
pub enum RenderingRotation {
#[default]
NoRotation,
Rotate90,
Rotate180,
Rotate270,
}
impl RenderingRotation {
fn is_transpose(self) -> bool {
matches!(self, Self::Rotate90 | Self::Rotate270)
}
fn mirror_width(self) -> bool {
matches!(self, Self::Rotate270 | Self::Rotate180)
}
fn mirror_height(self) -> bool {
matches!(self, Self::Rotate90 | Self::Rotate180)
}
fn angle(self) -> f32 {
match self {
RenderingRotation::NoRotation => 0.,
RenderingRotation::Rotate90 => 90.,
RenderingRotation::Rotate180 => 180.,
RenderingRotation::Rotate270 => 270.,
}
}
}
#[derive(Copy, Clone)]
struct RotationInfo {
orientation: RenderingRotation,
screen_size: PhysicalSize,
}
trait Transform {
#[must_use]
fn transformed(self, info: RotationInfo) -> Self;
}
impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Point2D<T, PhysicalPx> {
fn transformed(mut self, info: RotationInfo) -> Self {
if info.orientation.mirror_width() {
self.x = T::from(info.screen_size.width).unwrap() - self.x - T::from(1).unwrap()
}
if info.orientation.mirror_height() {
self.y = T::from(info.screen_size.height).unwrap() - self.y - T::from(1).unwrap()
}
if info.orientation.is_transpose() {
core::mem::swap(&mut self.x, &mut self.y);
}
self
}
}
impl<T: Copy> Transform for euclid::Size2D<T, PhysicalPx> {
fn transformed(mut self, info: RotationInfo) -> Self {
if info.orientation.is_transpose() {
core::mem::swap(&mut self.width, &mut self.height);
}
self
}
}
impl<T: Copy + NumCast + core::ops::Sub<Output = T>> Transform for euclid::Rect<T, PhysicalPx> {
fn transformed(self, info: RotationInfo) -> Self {
let one = T::from(1).unwrap();
let mut origin = self.origin.transformed(info);
let size = self.size.transformed(info);
if info.orientation.mirror_width() {
origin.y = origin.y - (size.height - one);
}
if info.orientation.mirror_height() {
origin.x = origin.x - (size.width - one);
}
Self::new(origin, size)
}
}
impl<T: Copy> Transform for BorderRadius<T, PhysicalPx> {
fn transformed(self, info: RotationInfo) -> Self {
match info.orientation {
RenderingRotation::NoRotation => self,
RenderingRotation::Rotate90 => {
Self::new(self.bottom_left, self.top_left, self.top_right, self.bottom_right)
}
RenderingRotation::Rotate180 => {
Self::new(self.bottom_right, self.bottom_left, self.top_left, self.top_right)
}
RenderingRotation::Rotate270 => {
Self::new(self.top_right, self.bottom_right, self.bottom_left, self.top_left)
}
}
}
}
pub trait LineBufferProvider {
type TargetPixel: TargetPixel;
fn process_line(
&mut self,
line: usize,
range: core::ops::Range<usize>,
render_fn: impl FnOnce(&mut [Self::TargetPixel]),
);
}
#[cfg(not(cbindgen))]
const PHYSICAL_REGION_MAX_SIZE: usize = DirtyRegion::MAX_COUNT;
#[cfg(cbindgen)]
pub const PHYSICAL_REGION_MAX_SIZE: usize = 3;
const _: () = {
assert!(PHYSICAL_REGION_MAX_SIZE == 3);
assert!(DirtyRegion::MAX_COUNT == 3);
};
#[derive(Clone, Debug, Default)]
#[repr(C)]
pub struct PhysicalRegion {
rectangles: [euclid::Box2D<i16, PhysicalPx>; PHYSICAL_REGION_MAX_SIZE],
count: usize,
}
impl PhysicalRegion {
fn iter_box(&self) -> impl Iterator<Item = euclid::Box2D<i16, PhysicalPx>> + '_ {
(0..self.count).map(|x| self.rectangles[x])
}
fn bounding_rect(&self) -> PhysicalRect {
if self.count == 0 {
return Default::default();
}
let mut r = self.rectangles[0];
for i in 1..self.count {
r = r.union(&self.rectangles[i]);
}
r.to_rect()
}
pub fn bounding_box_size(&self) -> crate::api::PhysicalSize {
let bb = self.bounding_rect();
crate::api::PhysicalSize { width: bb.width() as _, height: bb.height() as _ }
}
pub fn bounding_box_origin(&self) -> crate::api::PhysicalPosition {
let bb = self.bounding_rect();
crate::api::PhysicalPosition { x: bb.origin.x as _, y: bb.origin.y as _ }
}
pub fn iter(
&self,
) -> impl Iterator<Item = (crate::api::PhysicalPosition, crate::api::PhysicalSize)> + '_ {
let mut line_ranges = Vec::<core::ops::Range<i16>>::new();
let mut begin_line = 0;
let mut end_line = 0;
core::iter::from_fn(move || loop {
match line_ranges.pop() {
Some(r) => {
return Some((
crate::api::PhysicalPosition { x: r.start as _, y: begin_line as _ },
crate::api::PhysicalSize {
width: r.len() as _,
height: (end_line - begin_line) as _,
},
));
}
None => {
begin_line = end_line;
end_line = match region_line_ranges(self, begin_line, &mut line_ranges) {
Some(end_line) => end_line,
None => return None,
};
line_ranges.reverse();
}
}
})
}
}
#[test]
fn region_iter() {
let mut region = PhysicalRegion::default();
assert_eq!(region.iter().next(), None);
region.rectangles[0] =
euclid::Box2D::from_origin_and_size(euclid::point2(1, 1), euclid::size2(2, 3));
region.rectangles[1] =
euclid::Box2D::from_origin_and_size(euclid::point2(6, 2), euclid::size2(3, 20));
region.rectangles[2] =
euclid::Box2D::from_origin_and_size(euclid::point2(0, 10), euclid::size2(10, 5));
assert_eq!(region.iter().next(), None);
region.count = 1;
let r = |x, y, width, height| {
(crate::api::PhysicalPosition { x, y }, crate::api::PhysicalSize { width, height })
};
let mut iter = region.iter();
assert_eq!(iter.next(), Some(r(1, 1, 2, 3)));
assert_eq!(iter.next(), None);
drop(iter);
region.count = 3;
let mut iter = region.iter();
assert_eq!(iter.next(), Some(r(1, 1, 2, 1))); assert_eq!(iter.next(), Some(r(1, 2, 2, 2)));
assert_eq!(iter.next(), Some(r(6, 2, 3, 2)));
assert_eq!(iter.next(), Some(r(6, 4, 3, 6)));
assert_eq!(iter.next(), Some(r(0, 10, 10, 5)));
assert_eq!(iter.next(), Some(r(6, 15, 3, 7)));
assert_eq!(iter.next(), None);
}
fn region_line_ranges(
region: &PhysicalRegion,
line: i16,
line_ranges: &mut Vec<core::ops::Range<i16>>,
) -> Option<i16> {
line_ranges.clear();
let mut next_validity = None::<i16>;
for geom in region.iter_box() {
if geom.is_empty() {
continue;
}
if geom.y_range().contains(&line) {
match &mut next_validity {
Some(val) => *val = geom.max.y.min(*val),
None => next_validity = Some(geom.max.y),
}
let mut tmp = Some(geom.x_range());
line_ranges.retain_mut(|it| {
if let Some(r) = &mut tmp {
if it.end < r.start {
return true;
} else if it.start <= r.start {
if it.end >= r.end {
tmp = None;
return true;
}
r.start = it.start;
return false;
} else if it.start <= r.end {
if it.end <= r.end {
return false;
} else {
it.start = r.start;
tmp = None;
return true;
}
} else {
core::mem::swap(it, r);
return true;
}
} else {
return true;
}
});
if let Some(r) = tmp {
line_ranges.push(r);
}
continue;
} else {
if geom.min.y >= line {
match &mut next_validity {
Some(val) => *val = geom.min.y.min(*val),
None => next_validity = Some(geom.min.y),
}
}
}
}
debug_assert!(line_ranges.windows(2).all(|x| x[0].end < x[1].start));
next_validity
}
pub struct SoftwareRenderer {
partial_rendering_state: PartialRenderingState,
maybe_window_adapter: RefCell<Option<Weak<dyn crate::window::WindowAdapter>>>,
rotation: Cell<RenderingRotation>,
rendering_metrics_collector: Option<Rc<RenderingMetricsCollector>>,
}
impl Default for SoftwareRenderer {
fn default() -> Self {
Self {
partial_rendering_state: Default::default(),
maybe_window_adapter: Default::default(),
rotation: Default::default(),
rendering_metrics_collector: RenderingMetricsCollector::new("software"),
}
}
}
impl SoftwareRenderer {
pub fn new() -> Self {
Default::default()
}
pub fn new_with_repaint_buffer_type(repaint_buffer_type: RepaintBufferType) -> Self {
let self_ = Self::default();
self_.partial_rendering_state.set_repaint_buffer_type(repaint_buffer_type);
self_
}
pub fn set_repaint_buffer_type(&self, repaint_buffer_type: RepaintBufferType) {
self.partial_rendering_state.set_repaint_buffer_type(repaint_buffer_type);
}
pub fn repaint_buffer_type(&self) -> RepaintBufferType {
self.partial_rendering_state.repaint_buffer_type()
}
pub fn set_rendering_rotation(&self, rotation: RenderingRotation) {
self.rotation.set(rotation)
}
pub fn rendering_rotation(&self) -> RenderingRotation {
self.rotation.get()
}
pub fn render(&self, buffer: &mut [impl TargetPixel], pixel_stride: usize) -> PhysicalRegion {
let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
else {
return Default::default();
};
let window_inner = WindowInner::from_pub(window.window());
let factor = ScaleFactor::new(window_inner.scale_factor());
let rotation = self.rotation.get();
let (size, background) = if let Some(window_item) =
window_inner.window_item().as_ref().map(|item| item.as_pin_ref())
{
(
(LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
* factor)
.cast(),
window_item.background(),
)
} else if rotation.is_transpose() {
(euclid::size2((buffer.len() / pixel_stride) as _, pixel_stride as _), Brush::default())
} else {
(euclid::size2(pixel_stride as _, (buffer.len() / pixel_stride) as _), Brush::default())
};
if size.is_empty() {
return Default::default();
}
assert!(
if rotation.is_transpose() {
pixel_stride >= size.height as usize && buffer.len() >= (size.width as usize * pixel_stride + size.height as usize) - pixel_stride
} else {
pixel_stride >= size.width as usize && buffer.len() >= (size.height as usize * pixel_stride + size.width as usize) - pixel_stride
},
"buffer of size {} with stride {pixel_stride} is too small to handle a window of size {size:?}", buffer.len()
);
let buffer_renderer = SceneBuilder::new(
size,
factor,
window_inner,
RenderToBuffer {
buffer,
stride: pixel_stride,
dirty_range_cache: vec![],
dirty_region: Default::default(),
},
rotation,
);
let mut renderer = self.partial_rendering_state.create_partial_renderer(buffer_renderer);
window_inner
.draw_contents(|components| {
let logical_size = (size.cast() / factor).cast();
self.partial_rendering_state.apply_dirty_region(
&mut renderer,
components,
logical_size,
);
let rotation = RotationInfo { orientation: rotation, screen_size: size };
let screen_rect = PhysicalRect::from_size(size);
let mut i = renderer.dirty_region.iter().filter_map(|r| {
(r.cast() * factor)
.to_rect()
.round_out()
.cast()
.intersection(&screen_rect)?
.transformed(rotation)
.into()
});
let dirty_region = PhysicalRegion {
rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
count: renderer.dirty_region.iter().count(),
};
drop(i);
let mut bg = TargetPixel::background();
TargetPixel::blend(&mut bg, background.color().into());
let mut line = 0;
while let Some(next) = region_line_ranges(
&dirty_region,
line,
&mut renderer.actual_renderer.processor.dirty_range_cache,
) {
for l in line..next {
for r in &renderer.actual_renderer.processor.dirty_range_cache {
renderer.actual_renderer.processor.buffer[l as usize * pixel_stride..]
[r.start as usize..r.end as usize]
.fill(bg);
}
}
line = next;
}
renderer.actual_renderer.processor.dirty_region = dirty_region.clone();
for (component, origin) in components {
crate::item_rendering::render_component_items(
component,
&mut renderer,
*origin,
);
}
if let Some(metrics) = &self.rendering_metrics_collector {
metrics.measure_frame_rendered(&mut renderer);
if metrics.refresh_mode() == RefreshMode::FullSpeed {
self.partial_rendering_state.force_screen_refresh();
}
}
dirty_region
})
.unwrap_or_default()
}
pub fn render_by_line(&self, line_buffer: impl LineBufferProvider) -> PhysicalRegion {
let Some(window) = self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
else {
return Default::default();
};
let window_inner = WindowInner::from_pub(window.window());
let component_rc = window_inner.component();
let component = crate::item_tree::ItemTreeRc::borrow_pin(&component_rc);
if let Some(window_item) = crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(
component.as_ref().get_item_ref(0),
) {
let factor = ScaleFactor::new(window_inner.scale_factor());
let size = LogicalSize::from_lengths(window_item.width(), window_item.height()).cast()
* factor;
render_window_frame_by_line(
window_inner,
window_item.background(),
size.cast(),
self,
line_buffer,
)
} else {
PhysicalRegion { ..Default::default() }
}
}
}
#[doc(hidden)]
impl RendererSealed for SoftwareRenderer {
fn text_size(
&self,
font_request: crate::graphics::FontRequest,
text: &str,
max_width: Option<LogicalLength>,
scale_factor: ScaleFactor,
text_wrap: TextWrap,
) -> LogicalSize {
fonts::text_size(font_request, text, max_width, scale_factor, text_wrap)
}
fn font_metrics(
&self,
font_request: crate::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> crate::items::FontMetrics {
fonts::font_metrics(font_request, scale_factor)
}
fn text_input_byte_offset_for_position(
&self,
text_input: Pin<&crate::items::TextInput>,
pos: LogicalPoint,
font_request: crate::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> usize {
let visual_representation = text_input.visual_representation(None);
let font = fonts::match_font(&font_request, scale_factor);
let width = (text_input.width().cast() * scale_factor).cast();
let height = (text_input.height().cast() * scale_factor).cast();
let pos = (pos.cast() * scale_factor)
.clamp(euclid::point2(0., 0.), euclid::point2(i16::MAX, i16::MAX).cast())
.cast();
match font {
fonts::Font::PixelFont(pf) => {
let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
let paragraph = TextParagraphLayout {
string: &visual_representation.text,
layout,
max_width: width,
max_height: height,
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: false,
};
visual_representation.map_byte_offset_from_byte_offset_in_visual_text(
paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
)
}
#[cfg(feature = "software-renderer-systemfonts")]
fonts::Font::VectorFont(vf) => {
let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
let paragraph = TextParagraphLayout {
string: &visual_representation.text,
layout,
max_width: width,
max_height: height,
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: false,
};
visual_representation.map_byte_offset_from_byte_offset_in_visual_text(
paragraph.byte_offset_for_position((pos.x_length(), pos.y_length())),
)
}
}
}
fn text_input_cursor_rect_for_byte_offset(
&self,
text_input: Pin<&crate::items::TextInput>,
byte_offset: usize,
font_request: crate::graphics::FontRequest,
scale_factor: ScaleFactor,
) -> LogicalRect {
let visual_representation = text_input.visual_representation(None);
let font = fonts::match_font(&font_request, scale_factor);
let width = (text_input.width().cast() * scale_factor).cast();
let height = (text_input.height().cast() * scale_factor).cast();
let (cursor_position, cursor_height) = match font {
fonts::Font::PixelFont(pf) => {
let layout = fonts::text_layout_for_font(&pf, &font_request, scale_factor);
let paragraph = TextParagraphLayout {
string: &visual_representation.text,
layout,
max_width: width,
max_height: height,
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: false,
};
(paragraph.cursor_pos_for_byte_offset(byte_offset), pf.height())
}
#[cfg(feature = "software-renderer-systemfonts")]
fonts::Font::VectorFont(vf) => {
let layout = fonts::text_layout_for_font(&vf, &font_request, scale_factor);
let paragraph = TextParagraphLayout {
string: &visual_representation.text,
layout,
max_width: width,
max_height: height,
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: false,
};
(paragraph.cursor_pos_for_byte_offset(byte_offset), vf.height())
}
};
(PhysicalRect::new(
PhysicalPoint::from_lengths(cursor_position.0, cursor_position.1),
PhysicalSize::from_lengths(
(text_input.text_cursor_width().cast() * scale_factor).cast(),
cursor_height,
),
)
.cast()
/ scale_factor)
.cast()
}
fn free_graphics_resources(
&self,
_component: crate::item_tree::ItemTreeRef,
items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
) -> Result<(), crate::platform::PlatformError> {
self.partial_rendering_state.free_graphics_resources(items);
Ok(())
}
fn mark_dirty_region(&self, region: crate::item_rendering::DirtyRegion) {
self.partial_rendering_state.mark_dirty_region(region);
}
fn register_bitmap_font(&self, font_data: &'static crate::graphics::BitmapFont) {
fonts::register_bitmap_font(font_data);
}
#[cfg(feature = "software-renderer-systemfonts")]
fn register_font_from_memory(
&self,
data: &'static [u8],
) -> Result<(), Box<dyn std::error::Error>> {
self::fonts::systemfonts::register_font_from_memory(data)
}
#[cfg(all(feature = "software-renderer-systemfonts", not(target_arch = "wasm32")))]
fn register_font_from_path(
&self,
path: &std::path::Path,
) -> Result<(), Box<dyn std::error::Error>> {
self::fonts::systemfonts::register_font_from_path(path)
}
fn default_font_size(&self) -> LogicalLength {
self::fonts::DEFAULT_FONT_SIZE
}
fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
*self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
self.partial_rendering_state.clear_cache();
}
fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
let Some(window_adapter) =
self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade())
else {
return Err(
"SoftwareRenderer's screenshot called without a window adapter present".into()
);
};
let window = window_adapter.window();
let size = window.size();
let Some((width, height)) = size.width.try_into().ok().zip(size.height.try_into().ok())
else {
return Err("take_snapshot() called on window with invalid size".into());
};
let mut target_buffer = SharedPixelBuffer::<crate::graphics::Rgb8Pixel>::new(width, height);
self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
self.render(target_buffer.make_mut_slice(), width as usize);
self.set_repaint_buffer_type(RepaintBufferType::NewBuffer);
let mut target_buffer_with_alpha =
SharedPixelBuffer::<Rgba8Pixel>::new(target_buffer.width(), target_buffer.height());
for (target_pixel, source_pixel) in target_buffer_with_alpha
.make_mut_slice()
.iter_mut()
.zip(target_buffer.as_slice().iter())
{
*target_pixel.rgb_mut() = *source_pixel;
}
Ok(target_buffer_with_alpha)
}
}
fn render_window_frame_by_line(
window: &WindowInner,
background: Brush,
size: PhysicalSize,
renderer: &SoftwareRenderer,
mut line_buffer: impl LineBufferProvider,
) -> PhysicalRegion {
let mut scene = prepare_scene(window, size, renderer);
let to_draw_tr = scene.dirty_region.bounding_rect();
let mut background_color = TargetPixel::background();
TargetPixel::blend(&mut background_color, background.color().into());
while scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
for r in &scene.current_line_ranges {
line_buffer.process_line(
scene.current_line.get() as usize,
r.start as usize..r.end as usize,
|line_buffer| {
let offset = r.start;
line_buffer.fill(background_color);
for span in scene.items[0..scene.current_items_index].iter().rev() {
debug_assert!(scene.current_line >= span.pos.y_length());
debug_assert!(
scene.current_line < span.pos.y_length() + span.size.height_length(),
);
if span.pos.x >= r.end {
continue;
}
let begin = r.start.max(span.pos.x);
let end = r.end.min(span.pos.x + span.size.width);
if begin >= end {
continue;
}
let extra_left_clip = begin - span.pos.x;
let extra_right_clip = span.pos.x + span.size.width - end;
let range_buffer =
&mut line_buffer[(begin - offset) as usize..(end - offset) as usize];
match span.command {
SceneCommand::Rectangle { color } => {
TargetPixel::blend_slice(range_buffer, color);
}
SceneCommand::Texture { texture_index } => {
let texture = &scene.vectors.textures[texture_index as usize];
draw_functions::draw_texture_line(
&PhysicalRect { origin: span.pos, size: span.size },
scene.current_line,
texture,
range_buffer,
extra_left_clip,
extra_right_clip,
);
}
SceneCommand::SharedBuffer { shared_buffer_index } => {
let texture = scene.vectors.shared_buffers
[shared_buffer_index as usize]
.as_texture();
draw_functions::draw_texture_line(
&PhysicalRect { origin: span.pos, size: span.size },
scene.current_line,
&texture,
range_buffer,
extra_left_clip,
extra_right_clip,
);
}
SceneCommand::RoundedRectangle { rectangle_index } => {
let rr =
&scene.vectors.rounded_rectangles[rectangle_index as usize];
draw_functions::draw_rounded_rectangle_line(
&PhysicalRect { origin: span.pos, size: span.size },
scene.current_line,
rr,
range_buffer,
extra_left_clip,
extra_right_clip,
);
}
SceneCommand::Gradient { gradient_index } => {
let g = &scene.vectors.gradients[gradient_index as usize];
draw_functions::draw_gradient_line(
&PhysicalRect { origin: span.pos, size: span.size },
scene.current_line,
g,
range_buffer,
extra_left_clip,
);
}
}
}
},
);
}
if scene.current_line < to_draw_tr.origin.y_length() + to_draw_tr.size.height_length() {
scene.next_line();
}
}
scene.dirty_region
}
fn prepare_scene(
window: &WindowInner,
size: PhysicalSize,
software_renderer: &SoftwareRenderer,
) -> Scene {
let factor = ScaleFactor::new(window.scale_factor());
let prepare_scene = SceneBuilder::new(
size,
factor,
window,
PrepareScene::default(),
software_renderer.rotation.get(),
);
let mut renderer =
software_renderer.partial_rendering_state.create_partial_renderer(prepare_scene);
let mut dirty_region = PhysicalRegion::default();
window.draw_contents(|components| {
let logical_size = (size.cast() / factor).cast();
software_renderer.partial_rendering_state.apply_dirty_region(
&mut renderer,
components,
logical_size,
);
let rotation =
RotationInfo { orientation: software_renderer.rotation.get(), screen_size: size };
let screen_rect = PhysicalRect::from_size(size);
let mut i = renderer.dirty_region.iter().filter_map(|r| {
(r.cast() * factor)
.to_rect()
.round_out()
.cast()
.intersection(&screen_rect)?
.transformed(rotation)
.into()
});
dirty_region = PhysicalRegion {
rectangles: core::array::from_fn(|_| i.next().unwrap_or_default().to_box2d()),
count: renderer.dirty_region.iter().count(),
};
drop(i);
for (component, origin) in components {
crate::item_rendering::render_component_items(component, &mut renderer, *origin);
}
});
if let Some(metrics) = &software_renderer.rendering_metrics_collector {
metrics.measure_frame_rendered(&mut renderer);
if metrics.refresh_mode() == RefreshMode::FullSpeed {
software_renderer.partial_rendering_state.force_screen_refresh();
}
}
let prepare_scene = renderer.into_inner();
Scene::new(prepare_scene.processor.items, prepare_scene.processor.vectors, dirty_region)
}
trait ProcessScene {
fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>);
fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor);
fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle);
fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand);
fn process_gradient(&mut self, geometry: PhysicalRect, gradient: GradientCommand);
}
struct RenderToBuffer<'a, TargetPixel> {
buffer: &'a mut [TargetPixel],
stride: usize,
dirty_range_cache: Vec<core::ops::Range<i16>>,
dirty_region: PhysicalRegion,
}
impl<'a, T: TargetPixel> RenderToBuffer<'a, T> {
fn foreach_ranges(
&mut self,
geometry: &PhysicalRect,
mut f: impl FnMut(i16, &mut [T], i16, i16),
) {
let mut line = geometry.min_y();
while let Some(mut next) =
region_line_ranges(&self.dirty_region, line, &mut self.dirty_range_cache)
{
next = next.min(geometry.max_y());
for r in &self.dirty_range_cache {
if geometry.origin.x >= r.end {
continue;
}
let begin = r.start.max(geometry.origin.x);
let end = r.end.min(geometry.origin.x + geometry.size.width);
if begin >= end {
continue;
}
let extra_left_clip = begin - geometry.origin.x;
let extra_right_clip = geometry.origin.x + geometry.size.width - end;
for l in line..next {
f(
l,
&mut self.buffer[l as usize * self.stride..][begin as usize..end as usize],
extra_left_clip,
extra_right_clip,
);
}
}
if next == geometry.max_y() {
break;
}
line = next;
}
}
fn process_texture_impl(&mut self, geometry: PhysicalRect, texture: SceneTexture<'_>) {
self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
draw_functions::draw_texture_line(
&geometry,
PhysicalLength::new(line),
&texture,
buffer,
extra_left_clip,
extra_right_clip,
);
});
}
}
impl<'a, T: TargetPixel> ProcessScene for RenderToBuffer<'a, T> {
fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
self.process_texture_impl(geometry, texture)
}
fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand) {
let texture = buffer.as_texture();
self.process_texture_impl(geometry, texture);
}
fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
self.foreach_ranges(&geometry, |_line, buffer, _extra_left_clip, _extra_right_clip| {
TargetPixel::blend_slice(buffer, color);
});
}
fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, rr: RoundedRectangle) {
self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, extra_right_clip| {
draw_functions::draw_rounded_rectangle_line(
&geometry,
PhysicalLength::new(line),
&rr,
buffer,
extra_left_clip,
extra_right_clip,
);
});
}
fn process_gradient(&mut self, geometry: PhysicalRect, g: GradientCommand) {
self.foreach_ranges(&geometry, |line, buffer, extra_left_clip, _extra_right_clip| {
draw_functions::draw_gradient_line(
&geometry,
PhysicalLength::new(line),
&g,
buffer,
extra_left_clip,
);
});
}
}
#[derive(Default)]
struct PrepareScene {
items: Vec<SceneItem>,
vectors: SceneVectors,
}
impl ProcessScene for PrepareScene {
fn process_texture(&mut self, geometry: PhysicalRect, texture: SceneTexture<'static>) {
let size = geometry.size;
if !size.is_empty() {
let texture_index = self.vectors.textures.len() as u16;
self.vectors.textures.push(texture);
self.items.push(SceneItem {
pos: geometry.origin,
size,
z: self.items.len() as u16,
command: SceneCommand::Texture { texture_index },
});
}
}
fn process_shared_image_buffer(&mut self, geometry: PhysicalRect, buffer: SharedBufferCommand) {
let size = geometry.size;
if !size.is_empty() {
let shared_buffer_index = self.vectors.shared_buffers.len() as u16;
self.vectors.shared_buffers.push(buffer);
self.items.push(SceneItem {
pos: geometry.origin,
size,
z: self.items.len() as u16,
command: SceneCommand::SharedBuffer { shared_buffer_index },
});
}
}
fn process_rectangle(&mut self, geometry: PhysicalRect, color: PremultipliedRgbaColor) {
let size = geometry.size;
if !size.is_empty() {
let z = self.items.len() as u16;
let pos = geometry.origin;
self.items.push(SceneItem { pos, size, z, command: SceneCommand::Rectangle { color } });
}
}
fn process_rounded_rectangle(&mut self, geometry: PhysicalRect, data: RoundedRectangle) {
let size = geometry.size;
if !size.is_empty() {
let rectangle_index = self.vectors.rounded_rectangles.len() as u16;
self.vectors.rounded_rectangles.push(data);
self.items.push(SceneItem {
pos: geometry.origin,
size,
z: self.items.len() as u16,
command: SceneCommand::RoundedRectangle { rectangle_index },
});
}
}
fn process_gradient(&mut self, geometry: PhysicalRect, gradient: GradientCommand) {
let size = geometry.size;
if !size.is_empty() {
let gradient_index = self.vectors.gradients.len() as u16;
self.vectors.gradients.push(gradient);
self.items.push(SceneItem {
pos: geometry.origin,
size,
z: self.items.len() as u16,
command: SceneCommand::Gradient { gradient_index },
});
}
}
}
struct SceneBuilder<'a, T> {
processor: T,
state_stack: Vec<RenderState>,
current_state: RenderState,
scale_factor: ScaleFactor,
window: &'a WindowInner,
rotation: RotationInfo,
}
impl<'a, T: ProcessScene> SceneBuilder<'a, T> {
fn new(
screen_size: PhysicalSize,
scale_factor: ScaleFactor,
window: &'a WindowInner,
processor: T,
orientation: RenderingRotation,
) -> Self {
Self {
processor,
state_stack: vec![],
current_state: RenderState {
alpha: 1.,
offset: LogicalPoint::default(),
clip: LogicalRect::new(
LogicalPoint::default(),
(screen_size.cast() / scale_factor).cast(),
),
},
scale_factor,
window,
rotation: RotationInfo { orientation, screen_size },
}
}
fn should_draw(&self, rect: &LogicalRect) -> bool {
!rect.size.is_empty()
&& self.current_state.alpha > 0.01
&& self.current_state.clip.intersects(rect)
}
fn draw_image_impl(
&mut self,
image_inner: &ImageInner,
crate::graphics::FitResult {
clip_rect: source_rect,
source_to_target_x,
source_to_target_y,
size: fit_size,
offset: image_fit_offset,
tiled,
}: crate::graphics::FitResult,
colorize: Color,
) {
let global_alpha_u16 = (self.current_state.alpha * 255.) as u16;
let offset =
self.current_state.offset.cast() * self.scale_factor + image_fit_offset.to_vector();
let physical_clip =
(self.current_state.clip.translate(self.current_state.offset.to_vector()).cast()
* self.scale_factor)
.round()
.cast();
let tiled_off = tiled.unwrap_or_default();
match image_inner {
ImageInner::None => (),
ImageInner::StaticTextures(StaticTextures {
data,
textures,
size,
original_size,
..
}) => {
let adjust_x = size.width as f32 / original_size.width as f32;
let adjust_y = size.height as f32 / original_size.height as f32;
let source_to_target_x = source_to_target_x / adjust_x;
let source_to_target_y = source_to_target_y / adjust_y;
let source_rect =
source_rect.cast::<f32>().scale(adjust_x, adjust_y).round().cast();
let dx = Fixed::from_f32(1. / source_to_target_x).unwrap();
let dy = Fixed::from_f32(1. / source_to_target_y).unwrap();
for t in textures.as_slice() {
let Some(src_rect) = t.rect.intersection(&source_rect) else { continue };
let target_rect = if tiled.is_some() {
euclid::Rect::new(offset, fit_size).round().cast::<i32>()
} else {
euclid::Rect::<f32, PhysicalPx>::from_untyped(
&src_rect.translate(-source_rect.origin.to_vector()).cast(),
)
.scale(source_to_target_x, source_to_target_y)
.translate(offset.to_vector())
.round()
.cast::<i32>()
};
let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
continue;
};
let off_x = Fixed::from_integer(tiled_off.x as i32)
+ (Fixed::<i32, 8>::from_fixed(dx))
* (clipped_target.origin.x - target_rect.origin.x) as i32;
let off_y = Fixed::from_integer(tiled_off.y as i32)
+ (Fixed::<i32, 8>::from_fixed(dy))
* (clipped_target.origin.y - target_rect.origin.y) as i32;
let pixel_stride = t.rect.width() as u16;
let core::ops::Range { start, end } = compute_range_in_buffer(
&PhysicalRect::from_untyped(
&src_rect.translate(-t.rect.origin.to_vector()).cast(),
),
pixel_stride as usize,
);
let bpp = t.format.bpp();
let color = if colorize.alpha() > 0 { colorize } else { t.color };
let alpha = if colorize.alpha() > 0 || t.format == PixelFormat::AlphaMap {
color.alpha() as u16 * global_alpha_u16 / 255
} else {
global_alpha_u16
} as u8;
self.processor.process_texture(
clipped_target.cast().transformed(self.rotation),
SceneTexture {
data: &data.as_slice()[t.index..][start * bpp..end * bpp],
pixel_stride,
format: t.format,
extra: SceneTextureExtra {
colorize: color,
alpha,
rotation: self.rotation.orientation,
dx,
dy,
off_x: Fixed::try_from_fixed(off_x).unwrap(),
off_y: Fixed::try_from_fixed(off_y).unwrap(),
},
},
);
}
}
ImageInner::NineSlice(..) => unreachable!(),
_ => {
let target_rect = euclid::Rect::new(offset, fit_size).round().cast();
let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
return;
};
let orig = image_inner.size().cast::<f32>();
let svg_target_size = if tiled.is_some() {
euclid::size2(orig.width * source_to_target_x, orig.height * source_to_target_y)
.cast()
} else {
target_rect.size.cast()
};
if let Some(buffer) = image_inner.render_to_buffer(Some(svg_target_size)) {
let buf_size = buffer.size().cast::<f32>();
let dx =
Fixed::from_f32(buf_size.width / orig.width / source_to_target_x).unwrap();
let dy = Fixed::from_f32(buf_size.height / orig.height / source_to_target_y)
.unwrap();
let off_x = (Fixed::<i32, 8>::from_fixed(dx))
* (clipped_target.origin.x - target_rect.origin.x) as i32
+ Fixed::from_f32(tiled_off.x as f32 * buf_size.width / orig.width)
.unwrap();
let off_y = (Fixed::<i32, 8>::from_fixed(dy))
* (clipped_target.origin.y - target_rect.origin.y) as i32
+ Fixed::from_f32(tiled_off.y as f32 * buf_size.height / orig.height)
.unwrap();
let alpha = if colorize.alpha() > 0 {
colorize.alpha() as u16 * global_alpha_u16 / 255
} else {
global_alpha_u16
} as u8;
self.processor.process_shared_image_buffer(
clipped_target.cast().transformed(self.rotation),
SharedBufferCommand {
buffer: SharedBufferData::SharedImage(buffer),
source_rect: PhysicalRect::from_untyped(
&source_rect
.cast::<f32>()
.scale(
buf_size.width / orig.width,
buf_size.height / orig.height,
)
.round()
.cast(),
),
extra: SceneTextureExtra {
colorize,
alpha,
rotation: self.rotation.orientation,
dx,
dy,
off_x: Fixed::try_from_fixed(off_x).unwrap(),
off_y: Fixed::try_from_fixed(off_y).unwrap(),
},
},
);
} else {
unimplemented!("The image cannot be rendered")
}
}
};
}
fn draw_text_paragraph<Font>(
&mut self,
paragraph: &TextParagraphLayout<'_, Font>,
physical_clip: euclid::Rect<f32, PhysicalPx>,
offset: euclid::Vector2D<f32, PhysicalPx>,
color: Color,
selection: Option<SelectionInfo>,
) where
Font: AbstractFont + crate::textlayout::TextShaper<Length = PhysicalLength> + GlyphRenderer,
{
paragraph
.layout_lines::<()>(
|glyphs, line_x, line_y, _, sel| {
let baseline_y = line_y + paragraph.layout.font.ascent();
if let (Some(sel), Some(selection)) = (sel, &selection) {
let geometry = euclid::rect(
line_x.get() + sel.start.get(),
line_y.get(),
(sel.end - sel.start).get(),
paragraph.layout.font.height().get(),
);
if let Some(clipped_src) = geometry.intersection(&physical_clip.cast()) {
let geometry =
clipped_src.translate(offset.cast()).transformed(self.rotation);
self.processor
.process_rectangle(geometry, selection.selection_background.into());
}
}
let scale_delta = paragraph.layout.font.scale_delta();
for positioned_glyph in glyphs {
let Some(glyph) =
paragraph.layout.font.render_glyph(positioned_glyph.glyph_id)
else {
continue;
};
let gl_x = PhysicalLength::new((-glyph.x).truncate() as i16);
let gl_y = PhysicalLength::new(glyph.y.truncate() as i16);
let target_rect = PhysicalRect::new(
PhysicalPoint::from_lengths(
line_x + positioned_glyph.x - gl_x,
baseline_y - gl_y - glyph.height,
),
glyph.size(),
)
.cast();
let color = match &selection {
Some(s) if s.selection.contains(&positioned_glyph.text_byte_offset) => {
s.selection_color
}
_ => color,
};
let Some(clipped_target) = physical_clip.intersection(&target_rect) else {
continue;
};
let geometry = clipped_target.translate(offset).round();
let origin = (geometry.origin - offset.round()).round().cast::<i16>();
let off_x = origin.x - target_rect.origin.x as i16;
let off_y = origin.y - target_rect.origin.y as i16;
let pixel_stride = glyph.pixel_stride;
let mut geometry = geometry.cast();
if geometry.size.width > glyph.width.get() - off_x {
geometry.size.width = glyph.width.get() - off_x
}
if geometry.size.height > glyph.height.get() - off_y {
geometry.size.height = glyph.height.get() - off_y
}
let source_size = geometry.size;
if source_size.is_empty() {
continue;
}
match &glyph.alpha_map {
fonts::GlyphAlphaMap::Static(data) => {
let texture = if !glyph.sdf {
SceneTexture {
data,
pixel_stride,
format: PixelFormat::AlphaMap,
extra: SceneTextureExtra {
colorize: color,
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(off_x as u16),
off_y: Fixed::from_integer(off_y as u16),
},
}
} else {
let delta32 = Fixed::<i32, 8>::from_fixed(scale_delta);
let normalize = |x: Fixed<i32, 8>| {
if x < Fixed::from_integer(0) {
x + Fixed::from_integer(1)
} else {
x
}
};
let fract_x = normalize(
(-glyph.x) - Fixed::from_integer(gl_x.get() as _),
);
let off_x = delta32 * off_x as i32 + fract_x;
let fract_y =
normalize(glyph.y - Fixed::from_integer(gl_y.get() as _));
let off_y = delta32 * off_y as i32 + fract_y;
SceneTexture {
data,
pixel_stride,
format: PixelFormat::SignedDistanceField,
extra: SceneTextureExtra {
colorize: color,
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: scale_delta,
dy: scale_delta,
off_x: Fixed::try_from_fixed(off_x).unwrap(),
off_y: Fixed::try_from_fixed(off_y).unwrap(),
},
}
};
self.processor
.process_texture(geometry.transformed(self.rotation), texture);
}
fonts::GlyphAlphaMap::Shared(data) => {
let source_rect = euclid::rect(0, 0, glyph.width.0, glyph.height.0);
self.processor.process_shared_image_buffer(
geometry.transformed(self.rotation),
SharedBufferCommand {
buffer: SharedBufferData::AlphaMap {
data: data.clone(),
width: pixel_stride,
},
source_rect,
extra: SceneTextureExtra {
colorize: color,
alpha: color.alpha(),
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(off_x as u16),
off_y: Fixed::from_integer(off_y as u16),
},
},
);
}
}
}
core::ops::ControlFlow::Continue(())
},
selection.as_ref().map(|s| s.selection.clone()),
)
.ok();
}
fn alpha_color(&self, color: Color) -> Color {
if self.current_state.alpha < 1.0 {
Color::from_argb_u8(
(color.alpha() as f32 * self.current_state.alpha) as u8,
color.red(),
color.green(),
color.blue(),
)
} else {
color
}
}
}
struct SelectionInfo {
selection_color: Color,
selection_background: Color,
selection: core::ops::Range<usize>,
}
#[derive(Clone, Copy, Debug)]
struct RenderState {
alpha: f32,
offset: LogicalPoint,
clip: LogicalRect,
}
impl<'a, T: ProcessScene> crate::item_rendering::ItemRenderer for SceneBuilder<'a, T> {
#[allow(clippy::unnecessary_cast)] fn draw_rectangle(
&mut self,
rect: Pin<&crate::items::Rectangle>,
_: &ItemRc,
size: LogicalSize,
) {
let geom = LogicalRect::from(size);
if self.should_draw(&geom) {
let clipped = match geom.intersection(&self.current_state.clip) {
Some(geom) => geom,
None => return,
};
let background = rect.background();
if let Brush::LinearGradient(g) = background {
let geom2 = (geom.cast() * self.scale_factor).transformed(self.rotation);
let clipped2 = (clipped.cast() * self.scale_factor).transformed(self.rotation);
let act_rect = (clipped.translate(self.current_state.offset.to_vector()).cast()
* self.scale_factor)
.round()
.cast()
.transformed(self.rotation);
let axis_angle = (360. - self.rotation.orientation.angle()) % 360.;
let angle = g.angle() - axis_angle;
let tan = angle.to_radians().tan().abs();
let start = if !tan.is_finite() {
255.
} else {
let h = tan * geom2.width() as f32;
255. * h / (h + geom2.height() as f32)
} as u8;
let mut angle = angle as i32 % 360;
if angle < 0 {
angle += 360;
}
let mut stops = g.stops().copied().peekable();
let mut idx = 0;
let stop_count = g.stops().count();
while let (Some(mut s1), Some(mut s2)) = (stops.next(), stops.peek().copied()) {
let mut flags = 0;
if (angle % 180) > 90 {
flags |= 0b1;
}
if angle <= 90 || angle > 270 {
core::mem::swap(&mut s1, &mut s2);
s1.position = 1. - s1.position;
s2.position = 1. - s2.position;
if idx == 0 {
flags |= 0b100;
}
if idx == stop_count - 2 {
flags |= 0b010;
}
} else {
if idx == 0 {
flags |= 0b010;
}
if idx == stop_count - 2 {
flags |= 0b100;
}
}
idx += 1;
let (adjust_left, adjust_right) = if (angle % 180) > 90 {
(
(geom2.width() * s1.position).floor() as i16,
(geom2.width() * (1. - s2.position)).ceil() as i16,
)
} else {
(
(geom2.width() * (1. - s2.position)).ceil() as i16,
(geom2.width() * s1.position).floor() as i16,
)
};
let gr = GradientCommand {
color1: self.alpha_color(s1.color).into(),
color2: self.alpha_color(s2.color).into(),
start,
flags,
top_clip: Length::new(
(clipped2.min_y() - geom2.min_y()) as i16
- (geom2.height() * s1.position).floor() as i16,
),
bottom_clip: Length::new(
(geom2.max_y() - clipped2.max_y()) as i16
- (geom2.height() * (1. - s2.position)).ceil() as i16,
),
left_clip: Length::new(
(clipped2.min_x() - geom2.min_x()) as i16 - adjust_left,
),
right_clip: Length::new(
(geom2.max_x() - clipped2.max_x()) as i16 - adjust_right,
),
};
let size_y = act_rect.height_length() + gr.top_clip + gr.bottom_clip;
let size_x = act_rect.width_length() + gr.left_clip + gr.right_clip;
if size_x.get() == 0 || size_y.get() == 0 {
continue;
}
self.processor.process_gradient(act_rect, gr);
}
return;
}
let color = self.alpha_color(background.color());
if color.alpha() == 0 {
return;
}
let geometry = (clipped.translate(self.current_state.offset.to_vector()).cast()
* self.scale_factor)
.round()
.cast()
.transformed(self.rotation);
self.processor.process_rectangle(geometry, color.into());
}
}
#[allow(clippy::unnecessary_cast)] fn draw_border_rectangle(
&mut self,
rect: Pin<&dyn RenderBorderRectangle>,
_: &ItemRc,
size: LogicalSize,
_: &CachedRenderingData,
) {
let geom = LogicalRect::from(size);
if self.should_draw(&geom) {
let mut border = rect.border_width();
let radius = rect.border_radius();
let color = self.alpha_color(rect.background().color());
let border_color = if border.get() as f32 > 0.01 {
self.alpha_color(rect.border_color().color())
} else {
Color::default()
};
let mut border_color = PremultipliedRgbaColor::from(border_color);
let color = PremultipliedRgbaColor::from(color);
if border_color.alpha == 0 {
border = LogicalLength::new(0 as _);
} else if border_color.alpha < 255 {
let b = border_color;
let b_alpha_16 = b.alpha as u16;
border_color = PremultipliedRgbaColor {
red: ((color.red as u16 * (255 - b_alpha_16)) / 255) as u8 + b.red,
green: ((color.green as u16 * (255 - b_alpha_16)) / 255) as u8 + b.green,
blue: ((color.blue as u16 * (255 - b_alpha_16)) / 255) as u8 + b.blue,
alpha: (color.alpha as u16 + b_alpha_16
- (color.alpha as u16 * b_alpha_16) / 255) as u8,
}
}
if !radius.is_zero() {
let radius = radius
.min(LogicalBorderRadius::from_length(geom.width_length() / 2 as Coord))
.min(LogicalBorderRadius::from_length(geom.height_length() / 2 as Coord));
if let Some(clipped) = geom.intersection(&self.current_state.clip) {
let geom2 = (geom.cast() * self.scale_factor).transformed(self.rotation);
let clipped2 = (clipped.cast() * self.scale_factor).transformed(self.rotation);
let geometry =
(clipped.translate(self.current_state.offset.to_vector()).cast()
* self.scale_factor)
.round()
.cast()
.transformed(self.rotation);
let radius =
(radius.cast() * self.scale_factor).cast().transformed(self.rotation);
const E: f32 = 0.00001;
self.processor.process_rounded_rectangle(
geometry,
RoundedRectangle {
radius,
width: (border.cast() * self.scale_factor).cast(),
border_color,
inner_color: color,
top_clip: PhysicalLength::new(
(clipped2.min_y() - geom2.min_y() + E) as _,
),
bottom_clip: PhysicalLength::new(
(geom2.max_y() - clipped2.max_y() + E) as _,
),
left_clip: PhysicalLength::new(
(clipped2.min_x() - geom2.min_x() + E) as _,
),
right_clip: PhysicalLength::new(
(geom2.max_x() - clipped2.max_x() + E) as _,
),
},
);
}
return;
}
if color.alpha > 0 {
if let Some(r) = geom
.inflate(-border.get(), -border.get())
.intersection(&self.current_state.clip)
{
let geometry = (r.translate(self.current_state.offset.to_vector()).cast()
* self.scale_factor)
.round()
.cast()
.transformed(self.rotation);
self.processor.process_rectangle(geometry, color);
}
}
if border_color.alpha > 0 {
let mut add_border = |r: LogicalRect| {
if let Some(r) = r.intersection(&self.current_state.clip) {
let geometry =
(r.translate(self.current_state.offset.to_vector()).try_cast()?
* self.scale_factor)
.round()
.try_cast()?
.transformed(self.rotation);
self.processor.process_rectangle(geometry, border_color);
}
Some(())
};
let b = border.get();
let err = || {
panic!(
"invalid border rectangle {geom:?} border={b} state={:?}",
self.current_state
)
};
add_border(euclid::rect(0 as _, 0 as _, geom.width(), b)).unwrap_or_else(err);
add_border(euclid::rect(0 as _, geom.height() - b, geom.width(), b))
.unwrap_or_else(err);
add_border(euclid::rect(0 as _, b, b, geom.height() - b - b)).unwrap_or_else(err);
add_border(euclid::rect(geom.width() - b, b, b, geom.height() - b - b))
.unwrap_or_else(err);
}
}
}
fn draw_image(
&mut self,
image: Pin<&dyn RenderImage>,
_: &ItemRc,
size: LogicalSize,
_: &CachedRenderingData,
) {
let geom = LogicalRect::from(size);
if self.should_draw(&geom) {
let source = image.source();
let image_inner: &ImageInner = (&source).into();
if let ImageInner::NineSlice(nine) = image_inner {
let colorize = image.colorize().color();
let source_size = source.size();
for fit in crate::graphics::fit9slice(
source_size,
nine.1,
size.cast() * self.scale_factor,
self.scale_factor,
image.alignment(),
image.tiling(),
) {
self.draw_image_impl(&nine.0, fit, colorize);
}
return;
}
let source_clip = image.source_clip().map_or_else(
|| euclid::Rect::new(Default::default(), source.size().cast()),
|clip| {
clip.intersection(&euclid::Rect::from_size(source.size().cast()))
.unwrap_or_default()
},
);
let phys_size = geom.size_length().cast() * self.scale_factor;
let fit = crate::graphics::fit(
image.image_fit(),
phys_size,
source_clip,
self.scale_factor,
image.alignment(),
image.tiling(),
);
self.draw_image_impl(image_inner, fit, image.colorize().color());
}
}
fn draw_text(
&mut self,
text: Pin<&dyn crate::item_rendering::RenderText>,
_: &ItemRc,
size: LogicalSize,
_cache: &CachedRenderingData,
) {
let string = text.text();
if string.trim().is_empty() {
return;
}
let geom = LogicalRect::from(size);
if !self.should_draw(&geom) {
return;
}
let font_request = text.font_request(self.window);
let color = self.alpha_color(text.color().color());
let max_size = (geom.size.cast() * self.scale_factor).cast();
let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom)
{
logical_clip.cast() * self.scale_factor
} else {
return; };
let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
let font = fonts::match_font(&font_request, self.scale_factor);
match font {
fonts::Font::PixelFont(pf) => {
let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor);
let (horizontal_alignment, vertical_alignment) = text.alignment();
let paragraph = TextParagraphLayout {
string: &string,
layout,
max_width: max_size.width_length(),
max_height: max_size.height_length(),
horizontal_alignment,
vertical_alignment,
wrap: text.wrap(),
overflow: text.overflow(),
single_line: false,
};
self.draw_text_paragraph(¶graph, physical_clip, offset, color, None);
}
#[cfg(feature = "software-renderer-systemfonts")]
fonts::Font::VectorFont(vf) => {
let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor);
let (horizontal_alignment, vertical_alignment) = text.alignment();
let paragraph = TextParagraphLayout {
string: &string,
layout,
max_width: max_size.width_length(),
max_height: max_size.height_length(),
horizontal_alignment,
vertical_alignment,
wrap: text.wrap(),
overflow: text.overflow(),
single_line: false,
};
self.draw_text_paragraph(¶graph, physical_clip, offset, color, None);
}
}
}
fn draw_text_input(
&mut self,
text_input: Pin<&crate::items::TextInput>,
_: &ItemRc,
size: LogicalSize,
) {
let geom = LogicalRect::from(size);
if !self.should_draw(&geom) {
return;
}
let font_request = text_input.font_request(&self.window.window_adapter());
let max_size = (geom.size.cast() * self.scale_factor).cast();
let physical_clip = if let Some(logical_clip) = self.current_state.clip.intersection(&geom)
{
logical_clip.cast() * self.scale_factor
} else {
return; };
let offset = self.current_state.offset.to_vector().cast() * self.scale_factor;
let font = fonts::match_font(&font_request, self.scale_factor);
let text_visual_representation = text_input.visual_representation(None);
let color = self.alpha_color(text_visual_representation.text_color.color());
let selection =
(!text_visual_representation.selection_range.is_empty()).then_some(SelectionInfo {
selection_background: self.alpha_color(text_input.selection_background_color()),
selection_color: self.alpha_color(text_input.selection_foreground_color()),
selection: text_visual_representation.selection_range.clone(),
});
let cursor_pos_and_height = match font {
fonts::Font::PixelFont(pf) => {
let paragraph = TextParagraphLayout {
string: &text_visual_representation.text,
layout: fonts::text_layout_for_font(&pf, &font_request, self.scale_factor),
max_width: max_size.width_length(),
max_height: max_size.height_length(),
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: text_input.single_line(),
};
self.draw_text_paragraph(¶graph, physical_clip, offset, color, selection);
text_visual_representation.cursor_position.map(|cursor_offset| {
(paragraph.cursor_pos_for_byte_offset(cursor_offset), pf.height())
})
}
#[cfg(feature = "software-renderer-systemfonts")]
fonts::Font::VectorFont(vf) => {
let paragraph = TextParagraphLayout {
string: &text_visual_representation.text,
layout: fonts::text_layout_for_font(&vf, &font_request, self.scale_factor),
max_width: max_size.width_length(),
max_height: max_size.height_length(),
horizontal_alignment: text_input.horizontal_alignment(),
vertical_alignment: text_input.vertical_alignment(),
wrap: text_input.wrap(),
overflow: TextOverflow::Clip,
single_line: text_input.single_line(),
};
self.draw_text_paragraph(¶graph, physical_clip, offset, color, selection);
text_visual_representation.cursor_position.map(|cursor_offset| {
(paragraph.cursor_pos_for_byte_offset(cursor_offset), vf.height())
})
}
};
if let Some(((cursor_x, cursor_y), cursor_height)) = cursor_pos_and_height {
let cursor_rect = PhysicalRect::new(
PhysicalPoint::from_lengths(cursor_x, cursor_y),
PhysicalSize::from_lengths(
(text_input.text_cursor_width().cast() * self.scale_factor).cast(),
cursor_height,
),
);
if let Some(clipped_src) = cursor_rect.intersection(&physical_clip.cast()) {
let geometry = clipped_src.translate(offset.cast()).transformed(self.rotation);
#[allow(unused_mut)]
let mut cursor_color = text_visual_representation.cursor_color;
#[cfg(all(feature = "std", target_os = "macos"))]
{
static IS_SCREENSHOT_TEST: std::sync::OnceLock<bool> =
std::sync::OnceLock::new();
if *IS_SCREENSHOT_TEST.get_or_init(|| {
std::env::var_os("CARGO_PKG_NAME").unwrap_or_default()
== "test-driver-screenshots"
}) {
cursor_color = color;
}
}
self.processor.process_rectangle(geometry, self.alpha_color(cursor_color).into());
}
}
}
#[cfg(feature = "std")]
fn draw_path(&mut self, _path: Pin<&crate::items::Path>, _: &ItemRc, _size: LogicalSize) {
}
fn draw_box_shadow(
&mut self,
_box_shadow: Pin<&crate::items::BoxShadow>,
_: &ItemRc,
_size: LogicalSize,
) {
}
fn combine_clip(
&mut self,
other: LogicalRect,
_radius: LogicalBorderRadius,
_border_width: LogicalLength,
) -> bool {
match self.current_state.clip.intersection(&other) {
Some(r) => {
self.current_state.clip = r;
true
}
None => {
self.current_state.clip = LogicalRect::default();
false
}
}
}
fn get_current_clip(&self) -> LogicalRect {
self.current_state.clip
}
fn translate(&mut self, distance: LogicalVector) {
self.current_state.offset += distance;
self.current_state.clip = self.current_state.clip.translate(-distance)
}
fn translation(&self) -> LogicalVector {
self.current_state.offset.to_vector()
}
fn rotate(&mut self, _angle_in_degrees: f32) {
}
fn apply_opacity(&mut self, opacity: f32) {
self.current_state.alpha *= opacity;
}
fn save_state(&mut self) {
self.state_stack.push(self.current_state);
}
fn restore_state(&mut self) {
self.current_state = self.state_stack.pop().unwrap();
}
fn scale_factor(&self) -> f32 {
self.scale_factor.0
}
fn draw_cached_pixmap(
&mut self,
_item: &ItemRc,
update_fn: &dyn Fn(&mut dyn FnMut(u32, u32, &[u8])),
) {
update_fn(&mut |width, height, data| {
let img = SharedImageBuffer::RGBA8Premultiplied(SharedPixelBuffer::clone_from_slice(
data, width, height,
));
let physical_clip = (self.current_state.clip.cast() * self.scale_factor).cast();
let source_rect = euclid::rect(0, 0, width as _, height as _);
if let Some(clipped_src) = source_rect.intersection(&physical_clip) {
let geometry = clipped_src
.translate(
(self.current_state.offset.cast() * self.scale_factor).to_vector().cast(),
)
.round_in();
self.processor.process_shared_image_buffer(
geometry.cast().transformed(self.rotation),
SharedBufferCommand {
buffer: SharedBufferData::SharedImage(img),
source_rect,
extra: SceneTextureExtra {
colorize: Default::default(),
alpha: (self.current_state.alpha * 255.) as u8,
rotation: self.rotation.orientation,
dx: Fixed::from_integer(1),
dy: Fixed::from_integer(1),
off_x: Fixed::from_integer(clipped_src.min_x() as _),
off_y: Fixed::from_integer(clipped_src.min_y() as _),
},
},
);
}
});
}
fn draw_string(&mut self, string: &str, color: Color) {
let font_request = Default::default();
let font = fonts::match_font(&font_request, self.scale_factor);
let clip = self.current_state.clip.cast() * self.scale_factor;
match font {
fonts::Font::PixelFont(pf) => {
let layout = fonts::text_layout_for_font(&pf, &font_request, self.scale_factor);
let paragraph = TextParagraphLayout {
string,
layout,
max_width: clip.width_length().cast(),
max_height: clip.height_length().cast(),
horizontal_alignment: Default::default(),
vertical_alignment: Default::default(),
wrap: Default::default(),
overflow: Default::default(),
single_line: false,
};
self.draw_text_paragraph(¶graph, clip, Default::default(), color, None);
}
#[cfg(feature = "software-renderer-systemfonts")]
fonts::Font::VectorFont(vf) => {
let layout = fonts::text_layout_for_font(&vf, &font_request, self.scale_factor);
let paragraph = TextParagraphLayout {
string,
layout,
max_width: clip.width_length().cast(),
max_height: clip.height_length().cast(),
horizontal_alignment: Default::default(),
vertical_alignment: Default::default(),
wrap: Default::default(),
overflow: Default::default(),
single_line: false,
};
self.draw_text_paragraph(¶graph, clip, Default::default(), color, None);
}
}
}
fn draw_image_direct(&mut self, _image: crate::graphics::Image) {
todo!()
}
fn window(&self) -> &crate::window::WindowInner {
self.window
}
fn as_any(&mut self) -> Option<&mut dyn core::any::Any> {
None
}
}