use std::ops::Range;
use smudge_core::geometry::{Color, Corners, PixelRect, Point, Rect};
use smudge_core::paint::{DisplayItem, GlyphRun, PaintOp};
use crate::glyph_cache::{GlyphCache, MAX_GLYPH_SIZE, MaskKey};
const PLACEMENT_LIMIT: f32 = 1.0e9;
#[derive(Copy, Clone)]
pub(crate) struct Surface {
pub(crate) scale: f32,
pub(crate) width: u32,
pub(crate) height: u32,
}
fn maybe_translated(rect: Rect, offset: Point) -> Rect {
if offset == Point::default() {
rect
} else {
rect.translated(offset)
}
}
pub(crate) fn for_each_op(
mut on_op: impl FnMut(usize, &PaintOp, PixelRect, Point),
clips: &mut Vec<Option<PixelRect>>,
offsets: &mut Vec<Point>,
items: &[DisplayItem],
surface: Surface,
root: PixelRect,
) {
clips.clear();
clips.push(Some(root));
offsets.clear();
let mut current = Some(root);
let mut offset = Point::default();
for (index, item) in items.iter().enumerate() {
match item {
DisplayItem::Op(op) => {
if let Some(clip) = current
&& let Some(area) = PixelRect::from_logical(
maybe_translated(op.bounds(), offset),
surface.scale,
surface.width,
surface.height,
)
.and_then(|area| area.intersect(clip))
{
on_op(index, op, area, offset);
}
}
DisplayItem::PushClip(rect) => {
let inner = current.and_then(|outer| {
PixelRect::from_logical(
maybe_translated(*rect, offset),
surface.scale,
surface.width,
surface.height,
)
.and_then(|inner| inner.intersect(outer))
});
clips.push(inner);
current = inner;
}
DisplayItem::PopClip => {
if clips.len() > 1 {
clips.pop();
current = clips.last().copied().flatten();
}
}
DisplayItem::PushOffset(delta) => {
offsets.push(offset);
offset = Point {
x: offset.x + delta.x,
y: offset.y + delta.y,
};
}
DisplayItem::PopOffset => {
offset = offsets.pop().unwrap_or(offset);
}
}
}
}
#[derive(Copy, Clone)]
pub(crate) struct PlannedOp {
pub(crate) index: usize,
pub(crate) area: PixelRect,
pub(crate) offset: Point,
}
pub(crate) struct RasterPlan {
ops: Vec<PlannedOp>,
pub(crate) clips: Vec<Option<PixelRect>>,
pub(crate) offsets: Vec<Point>,
}
impl RasterPlan {
pub(crate) fn new() -> RasterPlan {
RasterPlan {
ops: Vec::new(),
clips: Vec::new(),
offsets: Vec::new(),
}
}
pub(crate) fn build(&mut self, items: &[DisplayItem], scale: f32, width: u32, height: u32) {
self.ops.clear();
for_each_op(
|index, _op, area, offset| {
self.ops.push(PlannedOp {
index,
area,
offset,
})
},
&mut self.clips,
&mut self.offsets,
items,
Surface {
scale,
width,
height,
},
PixelRect {
x: 0,
y: 0,
width,
height,
},
);
}
pub(crate) fn ops(&self) -> &[PlannedOp] {
&self.ops
}
}
pub(crate) struct Canvas<'a> {
pub(crate) pixels: &'a mut [u8],
pub(crate) width: u32,
pub(crate) scale: f32,
pub(crate) glyphs: &'a mut GlyphCache,
}
impl Canvas<'_> {
pub(crate) fn clear(&mut self, rect: PixelRect, color: Color) {
let alpha = u32::from(color.a);
let pixel = [
premultiply(color.r, alpha),
premultiply(color.g, alpha),
premultiply(color.b, alpha),
color.a,
];
for y in rect.y..rect.y + rect.height {
let start = self.index(rect.x, y);
let row = &mut self.pixels[start..start + rect.width as usize * 4];
row.as_chunks_mut::<4>().0.fill(pixel);
}
}
pub(crate) fn draw(&mut self, op: &PaintOp, area: PixelRect, offset: Point) {
match *op {
PaintOp::FillRect {
rect,
radius,
color,
} => {
if let Some(shape) = Shape::new(maybe_translated(rect, offset), radius, self.scale)
{
self.fill(
area,
color,
|x, y| shape.coverage(x, y),
|y| shape.steady(y),
);
}
}
PaintOp::StrokeRect {
rect,
radius,
width,
color,
} => {
if !width.is_finite() || width <= 0.0 {
return;
}
let Some(outer) = Shape::new(maybe_translated(rect, offset), radius, self.scale)
else {
return;
};
match outer.inset(width * self.scale) {
Some(inner) => self.fill(
area,
color,
|x, y| (outer.coverage(x, y) - inner.coverage(x, y)).max(0.0),
|y| inner.steady(y),
),
None => self.fill(
area,
color,
|x, y| outer.coverage(x, y),
|y| outer.steady(y),
),
}
}
PaintOp::Glyphs(ref run) => self.draw_glyphs(run.as_ref(), area, offset),
}
}
fn draw_glyphs(&mut self, run: &GlyphRun, area: PixelRect, offset: Point) {
let size_px = run.size * self.scale;
if !run.size.is_finite()
|| run.size <= 0.0
|| !size_px.is_finite()
|| size_px <= 0.0
|| size_px > MAX_GLYPH_SIZE
{
return;
}
let size = (size_px * 64.0).round() as i32;
if size <= 0 {
return;
}
let Canvas {
pixels,
width,
scale,
glyphs,
..
} = self;
let scale = *scale;
let shift_x = offset.x * scale;
let shift_y = offset.y * scale;
for glyph in &run.glyphs {
let x = glyph.x * scale + shift_x;
let y = (glyph.y * scale + shift_y).round();
if !(-PLACEMENT_LIMIT..=PLACEMENT_LIMIT).contains(&x)
|| !(-PLACEMENT_LIMIT..=PLACEMENT_LIMIT).contains(&y)
{
continue;
}
let origin_x = x.floor();
let subpixel = ((x - origin_x) * 4.0) as u32 % 4;
let key = MaskKey {
font_id: run.font.id,
font_index: run.font.index,
glyph_id: glyph.id,
size,
subpixel,
};
let Some(entry) = glyphs.entry(key, &run.font) else {
continue;
};
let (mask_width, mask_height) = (entry.width(), entry.height());
if mask_width == 0 || mask_height == 0 {
continue;
}
let x0 = origin_x as i64 + i64::from(entry.left());
let y0 = y as i64 + i64::from(entry.top());
let x1 = x0 + i64::from(mask_width);
let y1 = y0 + i64::from(mask_height);
let ix0 = x0.max(i64::from(area.x));
let iy0 = y0.max(i64::from(area.y));
let ix1 = x1.min(i64::from(area.x) + i64::from(area.width));
let iy1 = y1.min(i64::from(area.y) + i64::from(area.height));
if ix0 >= ix1 || iy0 >= iy1 {
continue;
}
let mut mask_offset = ((iy0 - y0) as usize) * mask_width as usize + (ix0 - x0) as usize;
let row_bytes = (ix1 - ix0) as usize * 4;
for y in iy0..iy1 {
let start = (y as usize * *width as usize + ix0 as usize) * 4;
let row = &mut pixels[start..start + row_bytes];
let coverage = &entry.data()[mask_offset..mask_offset + (ix1 - ix0) as usize];
for (target, &coverage) in row.as_chunks_mut::<4>().0.iter_mut().zip(coverage) {
if coverage == 0 {
continue;
}
let alpha = (u32::from(coverage) * u32::from(run.color.a) + 127) / 255;
blend(target, run.color, alpha);
}
mask_offset += mask_width as usize;
}
}
}
fn fill(
&mut self,
area: PixelRect,
color: Color,
coverage: impl Fn(u32, u32) -> f32,
steady: impl Fn(u32) -> Range<u32>,
) {
if color.a == 0 {
return;
}
let to_alpha = |coverage: f32| (coverage * f32::from(color.a) + 0.5) as u32;
let right = area.x + area.width;
for y in area.y..area.y + area.height {
let run = steady(y);
let start = run.start.clamp(area.x, right);
let end = run.end.clamp(start, right);
let row_start = self.index(area.x, y);
let row = &mut self.pixels[row_start..row_start + area.width as usize * 4];
let (before, rest) = row.split_at_mut((start - area.x) as usize * 4);
let (middle, after) = rest.split_at_mut((end - start) as usize * 4);
for (x, target) in (area.x..).zip(before.as_chunks_mut::<4>().0) {
put(target, color, to_alpha(coverage(x, y)));
}
if start < end {
let middle = middle.as_chunks_mut::<4>().0;
match to_alpha(coverage(start, y)) {
0 => {}
255 => middle.fill([color.r, color.g, color.b, 255]),
alpha => middle
.iter_mut()
.for_each(|target| blend(target, color, alpha)),
}
}
for (x, target) in (end..).zip(after.as_chunks_mut::<4>().0) {
put(target, color, to_alpha(coverage(x, y)));
}
}
}
fn index(&self, x: u32, y: u32) -> usize {
(y as usize * self.width as usize + x as usize) * 4
}
}
#[derive(Copy, Clone)]
struct Shape {
x0: f32,
y0: f32,
x1: f32,
y1: f32,
radius: Corners,
}
impl Shape {
fn new(rect: Rect, radius: Corners, scale: f32) -> Option<Shape> {
let scaled = Corners {
top_left: radius.top_left * scale,
top_right: radius.top_right * scale,
bottom_right: radius.bottom_right * scale,
bottom_left: radius.bottom_left * scale,
};
Shape::fitted(
rect.x0 * scale,
rect.y0 * scale,
rect.x1 * scale,
rect.y1 * scale,
scaled,
)
}
fn inset(&self, by: f32) -> Option<Shape> {
let radius = self.radius;
Shape::fitted(
self.x0 + by,
self.y0 + by,
self.x1 - by,
self.y1 - by,
Corners {
top_left: radius.top_left - by,
top_right: radius.top_right - by,
bottom_right: radius.bottom_right - by,
bottom_left: radius.bottom_left - by,
},
)
}
fn fitted(x0: f32, y0: f32, x1: f32, y1: f32, radius: Corners) -> Option<Shape> {
let finite = x0.is_finite() && y0.is_finite() && x1.is_finite() && y1.is_finite();
if !finite || x0 >= x1 || y0 >= y1 {
return None;
}
let limit = (x1 - x0).min(y1 - y0) / 2.0;
let fit = |radius: f32| radius.max(0.0).min(limit);
Some(Shape {
x0,
y0,
x1,
y1,
radius: Corners {
top_left: fit(radius.top_left),
top_right: fit(radius.top_right),
bottom_right: fit(radius.bottom_right),
bottom_left: fit(radius.bottom_left),
},
})
}
fn steady(&self, y: u32) -> Range<u32> {
let top = y as f32;
let down = self.y1.min(top + 1.0) - self.y0.max(top);
if down <= 0.0 {
return 0..0;
}
let cy = top + 0.5;
let Corners {
top_left,
top_right,
bottom_right,
bottom_left,
} = self.radius;
let band = |upper: f32, lower: f32| {
if cy < self.y0 + upper {
upper
} else if cy > self.y1 - lower {
lower
} else {
0.0
}
};
let start = (self.x0 + band(top_left, bottom_left)).ceil() as u32;
let end = (self.x1 - band(top_right, bottom_right)).floor() as u32;
start..end.max(start)
}
fn coverage(&self, x: u32, y: u32) -> f32 {
let (left, top) = (x as f32, y as f32);
let across = (self.x1.min(left + 1.0) - self.x0.max(left)).clamp(0.0, 1.0);
let down = (self.y1.min(top + 1.0) - self.y0.max(top)).clamp(0.0, 1.0);
let area = across * down;
if area == 0.0 {
return 0.0;
}
let (cx, cy) = (left + 0.5, top + 0.5);
let Corners {
top_left,
top_right,
bottom_right,
bottom_left,
} = self.radius;
let corner = if cx < self.x0 + top_left && cy < self.y0 + top_left {
(top_left, self.x0 + top_left, self.y0 + top_left)
} else if cx > self.x1 - top_right && cy < self.y0 + top_right {
(top_right, self.x1 - top_right, self.y0 + top_right)
} else if cx > self.x1 - bottom_right && cy > self.y1 - bottom_right {
(bottom_right, self.x1 - bottom_right, self.y1 - bottom_right)
} else if cx < self.x0 + bottom_left && cy > self.y1 - bottom_left {
(bottom_left, self.x0 + bottom_left, self.y1 - bottom_left)
} else {
return area;
};
let (radius, ox, oy) = corner;
let distance = (cx - ox).hypot(cy - oy);
area.min((radius - distance + 0.5).clamp(0.0, 1.0))
}
}
fn put(target: &mut [u8; 4], color: Color, alpha: u32) {
match alpha {
0 => {}
255 => *target = [color.r, color.g, color.b, 255],
_ => blend(target, color, alpha),
}
}
fn blend(target: &mut [u8; 4], color: Color, alpha: u32) {
let rest = 255 - alpha;
let mix = |source: u8, target: u8| {
((u32::from(source) * alpha + u32::from(target) * rest + 127) / 255) as u8
};
target[0] = mix(color.r, target[0]);
target[1] = mix(color.g, target[1]);
target[2] = mix(color.b, target[2]);
target[3] = mix(255, target[3]);
}
fn premultiply(channel: u8, alpha: u32) -> u8 {
((u32::from(channel) * alpha + 127) / 255) as u8
}