use crate::{Instance, InstanceBuffer};
use densa_core::{Color, Point, Rect};
use densa_text::{quantize_x, Font, GlyphCache, GlyphId, GlyphRun};
const GLYPH_REACH_EMS: f32 = 4.0;
#[derive(Debug)]
pub struct Canvas<'a> {
cache: &'a mut GlyphCache,
out: &'a mut InstanceBuffer,
state: CanvasState,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CanvasState {
offset: Point,
clip: Rect,
}
impl<'a> Canvas<'a> {
pub fn begin(cache: &'a mut GlyphCache, out: &'a mut InstanceBuffer) -> Self {
cache.begin_frame();
out.clear();
Self {
cache,
out,
state: CanvasState {
offset: Point::ORIGIN,
clip: Rect::EVERYTHING,
},
}
}
pub fn state(&self) -> CanvasState {
self.state
}
pub fn restore(&mut self, state: CanvasState) {
self.state = state;
}
pub fn translate(&mut self, dx: f32, dy: f32) {
self.state.offset = self.state.offset.offset(dx, dy);
}
pub fn clip(&mut self, rect: Rect) {
let rect = rect.translate(self.state.offset);
self.state.clip = self.state.clip.intersect(rect);
}
pub fn clip_rect(&self) -> Rect {
let offset = self.state.offset;
let back = Point::new(-offset.x, -offset.y);
self.state.clip.translate(back)
}
pub fn fill_rect(&mut self, rect: Rect, color: Color) {
let rect = rect.translate(self.state.offset).intersect(self.state.clip);
if !rect.is_empty() {
self.out.push(Instance::solid(rect.to_xywh(), color));
}
}
pub fn draw_glyph(&mut self, font: &Font, glyph: GlyphId, px: f32, at: Point, color: Color) {
let at = at.offset(self.state.offset.x, self.state.offset.y);
let reach = GLYPH_REACH_EMS * px;
let near = Rect::from_edges(at.x - reach, at.y - reach, at.x + reach, at.y + reach);
if near.intersect(self.state.clip).is_empty() {
return;
}
let (pixel, bin) = quantize_x(at.x);
let Ok(cached) = self.cache.get(font, glyph, px, bin) else {
return;
};
if cached.size[0] == 0.0 {
return; }
let quad = Rect::new(
pixel as f32 + cached.offset[0],
at.y.round() + cached.offset[1],
cached.size[0],
cached.size[1],
);
let shown = quad.intersect(self.state.clip);
if shown.is_empty() {
return;
}
let uv = clipped_uv(quad, shown, cached.atlas_rect);
self.out.push(Instance::glyph(shown.to_xywh(), uv, color));
}
pub fn draw_run(&mut self, run: GlyphRun<'_>, origin: Point) {
for glyph in run.glyphs {
let at = origin.offset(glyph.x, glyph.y);
self.draw_glyph(run.font, glyph.id, run.px, at, run.color);
}
}
pub fn cache(&self) -> &GlyphCache {
self.cache
}
pub fn instances(&self) -> &InstanceBuffer {
self.out
}
}
fn clipped_uv(quad: Rect, shown: Rect, uv: [f32; 4]) -> [f32; 4] {
if shown == quad {
return uv;
}
let along = |edge: f32, from: f32, length: f32, start: f32, span: f32| {
start + (edge - from) / length * span
};
let (du, dv) = (uv[2] - uv[0], uv[3] - uv[1]);
[
along(shown.left(), quad.left(), quad.width(), uv[0], du),
along(shown.top(), quad.top(), quad.height(), uv[1], dv),
along(shown.right(), quad.left(), quad.width(), uv[0], du),
along(shown.bottom(), quad.top(), quad.height(), uv[1], dv),
]
}