use cranpose_render_common::{
graph_scene::RenderDiagnostics,
text_canvas::{TextPixelWindow, TextPixels},
text_raster::{self, TextRasterRequest, paint_text},
};
use cranpose_ui::text::TextMotion;
use cranpose_ui_graphics::{Point, Rect};
use super::{
Target, TextRaster, blit_image_between_pixels, clip_rect_to_bounds, on_pixel_grid,
snap_delta_for_anchor,
};
use crate::{pixel::Pixel, scene::TextDraw};
pub(super) fn draw_text<P: Pixel>(
target: &mut Target<'_, P>,
draw: &TextDraw,
diagnostics: &RenderDiagnostics,
text: &TextRaster,
) {
let Some(raster_rect) = text_raster_rect(draw) else {
return;
};
let reach = draw.glyph_reach(raster_rect);
let part = if raster_rect.x.fract() == 0.0 && raster_rect.y.fract() == 0.0 {
let Some(part) = clip_rect_to_bounds(reach, None, target) else {
return;
};
if paint_text_over_target(draw, (raster_rect, reach), target, text) {
return;
}
Rect {
x: part.min_x as f32,
y: part.min_y as f32,
width: (part.max_x - part.min_x) as f32,
height: (part.max_y - part.min_y) as f32,
}
} else {
reach
};
draw_text_image(target, draw, (raster_rect, part), diagnostics, text);
}
#[inline(never)]
fn draw_text_image<P: Pixel>(
target: &mut Target<'_, P>,
draw: &TextDraw,
(raster_rect, part): (Rect, Rect),
diagnostics: &RenderDiagnostics,
text: &TextRaster,
) {
if let Some((pixels, rect)) = rasterize_text_part(draw, raster_rect, part, text) {
blit_rasterized_text(target, rect, draw.clip, pixels, diagnostics);
}
}
fn paint_text_over_target<P: Pixel>(
draw: &TextDraw,
(raster_rect, reach): (Rect, Rect),
target: &mut Target<'_, P>,
text: &TextRaster,
) -> bool {
let Some(bounds) = clip_rect_to_bounds(reach, draw.clip, target) else {
return true;
};
let first = (bounds.min_y - target.top) as usize * target.width as usize
+ (bounds.min_x - target.left) as usize;
let stride = target.width as usize;
let Some(pixels) = target.pixels.get_mut(first..) else {
return true;
};
let window = TextPixelWindow {
pixels: P::text_rows(pixels),
stride,
width: (bounds.max_x - bounds.min_x) as u32,
height: (bounds.max_y - bounds.min_y) as u32,
};
let mut glyphs = text.glyphs.try_borrow_mut().ok();
let covered = Rect {
x: bounds.min_x as f32,
y: bounds.min_y as f32,
width: window.width as f32,
height: window.height as f32,
};
paint_text(
raster_request(draw, covered, raster_rect),
glyphs.as_deref_mut(),
window,
)
}
fn raster_request(draw: &TextDraw, part: Rect, raster_rect: Rect) -> TextRasterRequest<'_> {
TextRasterRequest::new(
&draw.layout,
draw.text.as_ref(),
part,
&draw.text_style,
draw.color,
draw.scale,
)
.with_origin(Point::new(raster_rect.x, raster_rect.y))
.with_ink(draw.ink)
}
pub(crate) fn rasterize_text(draw: &TextDraw, text: &TextRaster) -> Option<(TextPixels, Rect)> {
let raster_rect = text_raster_rect(draw)?;
rasterize_text_part(draw, raster_rect, draw.glyph_reach(raster_rect), text)
}
pub(super) fn rasterize_text_part(
draw: &TextDraw,
raster_rect: Rect,
part: Rect,
text: &TextRaster,
) -> Option<(TextPixels, Rect)> {
let mut glyphs = text.glyphs.try_borrow_mut().ok();
let pixels = text_raster::rasterize_text(
raster_request(draw, part, raster_rect),
glyphs.as_deref_mut(),
)?;
let rect = Rect {
x: part.x,
y: part.y,
width: pixels.width as f32,
height: pixels.height as f32,
};
Some((pixels, rect))
}
pub(super) fn is_static_text(draw: &TextDraw) -> bool {
draw.text_style
.paragraph_style
.text_motion
.unwrap_or(TextMotion::Static)
== TextMotion::Static
}
pub(super) fn text_raster_rect(draw: &TextDraw) -> Option<Rect> {
if draw.scale.max(0.0) == 0.0 {
return None;
}
if !is_static_text(draw) {
return Some(draw.rect);
}
let snap_delta = draw
.snap_anchor
.map(snap_delta_for_anchor)
.unwrap_or_default();
Some(on_pixel_grid(
draw.rect.translate(snap_delta.x, snap_delta.y),
))
}
fn blit_rasterized_text<P: Pixel>(
target: &mut Target<'_, P>,
rect: Rect,
clip: Option<Rect>,
text: TextPixels,
diagnostics: &RenderDiagnostics,
) {
if rect.width <= 0.0 || rect.height <= 0.0 || text.width == 0 || text.height == 0 {
return;
}
let Some(clip_bounds) = clip_rect_to_bounds(rect, clip, target) else {
return;
};
if rect.x.fract() != 0.0 || rect.y.fract() != 0.0 {
if let Some(image) = text.into_image() {
blit_image_between_pixels(target, rect, clip_bounds, &image, diagnostics);
}
return;
}
let source_pixels = text.pixels.as_chunks::<4>().0;
for py in clip_bounds.min_y..clip_bounds.max_y {
let row = target.row(py, clip_bounds.min_x, clip_bounds.max_x);
let first = (py as f32 - rect.y) as usize * text.width as usize
+ (clip_bounds.min_x as f32 - rect.x) as usize;
let source = source_pixels
.get(first..first + row.len())
.unwrap_or_default();
for (dst, src) in row.iter_mut().zip(source) {
dst.blend_bytes(*src);
}
}
}