|
@@ -0,0 +1,224 @@
|
|
|
|
+use std::cell::RefCell;
|
|
|
|
+use std::rc::Rc;
|
|
|
|
+
|
|
|
|
+use patina::prelude::*;
|
|
|
|
+use patina::geom::*;
|
|
|
|
+use patina::platform as pp;
|
|
|
|
+
|
|
|
|
+/// ABGR32 surface (assumes little-endian CPU)
|
|
|
|
+///
|
|
|
|
+/// Colourmasks:
|
|
|
|
+/// - 0x000000ff: alpha
|
|
|
|
+/// - 0x0000ff00: blue
|
|
|
|
+/// - 0x00ff0000: green
|
|
|
|
+/// - 0xff000000: red
|
|
|
|
+pub struct UnaccelSurface {
|
|
|
|
+ data: Vec<u32>,
|
|
|
|
+ width: usize,
|
|
|
|
+ height: usize,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl UnaccelSurface {
|
|
|
|
+ pub fn new(width: usize, height: usize) -> Self {
|
|
|
|
+ let mut data = vec![];
|
|
|
|
+ data.resize(width * height, 0);
|
|
|
|
+ Self {
|
|
|
|
+ width,
|
|
|
|
+ height,
|
|
|
|
+ data,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
|
|
|
|
+ self.data[x + y * self.width]
|
|
|
|
+ }
|
|
|
|
+ pub fn set_pixel(&mut self, x: usize, y: usize, val: u32) {
|
|
|
|
+ self.data[x + y * self.width] = val;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub fn raw_argb32_data(&self) -> &[u8] {
|
|
|
|
+ let buf = self.data.as_slice().as_ptr();
|
|
|
|
+ let buflen = self.data.len() * 4;
|
|
|
|
+ unsafe { std::slice::from_raw_parts(buf as *const u8, buflen) }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl pp::render::Surface for UnaccelSurface {
|
|
|
|
+ fn size(&self) -> IVector {
|
|
|
|
+ IVector::new(self.width as i32, self.height as i32)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ type Painter<'l> = UnaccelPainter<'l>;
|
|
|
|
+ fn painter(&mut self) -> Option<Self::Painter<'_>> {
|
|
|
|
+ Some(UnaccelPainter { surf: self })
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub struct UnaccelPainter<'l> {
|
|
|
|
+ surf: &'l mut UnaccelSurface,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<'l> pp::render::Painter for UnaccelPainter<'l> {
|
|
|
|
+ type Surface = UnaccelSurface;
|
|
|
|
+
|
|
|
|
+ fn blit(&mut self, source: &Self::Surface, source_area: IRect, target: IPoint) {
|
|
|
|
+ for y in 0..source_area.height() {
|
|
|
|
+ for x in 0..source_area.width() {
|
|
|
|
+ let data = source.get_pixel(
|
|
|
|
+ (source_area.left() + x) as usize,
|
|
|
|
+ (source_area.top() + y) as usize,
|
|
|
|
+ );
|
|
|
|
+ let tx = x + target.x;
|
|
|
|
+ let ty = y + target.y;
|
|
|
|
+ if tx < 0 || ty < 0 || tx >= self.surf.width as i32 || ty >= self.surf.height as i32
|
|
|
|
+ {
|
|
|
|
+ continue;
|
|
|
|
+ } else {
|
|
|
|
+ self.surf.set_pixel(tx as usize, ty as usize, data);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn fill(&mut self, area: IRect, col: pp::render::Colour) {
|
|
|
|
+ let rgba = col.to_rgba();
|
|
|
|
+ for yoff in 0..area.height() {
|
|
|
|
+ for xoff in 0..area.width() {
|
|
|
|
+ self.surf.set_pixel(
|
|
|
|
+ (xoff + area.left()) as usize,
|
|
|
|
+ (yoff + area.top()) as usize,
|
|
|
|
+ rgba,
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ fn outline_rect(&mut self, rect: IRect, col: pp::render::Colour) {
|
|
|
|
+ todo!()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// text rendering via cosmic-text
|
|
|
|
+pub struct UnaccelText {
|
|
|
|
+ font: Rc<schrift_rs::Font>,
|
|
|
|
+ cached: RefCell<schrift_rs::CachedSchrift>,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl UnaccelText {
|
|
|
|
+ pub fn new() -> Self {
|
|
|
|
+ let font =
|
|
|
|
+ schrift_rs::Font::load_from_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
|
|
|
|
+ .unwrap();
|
|
|
|
+ let cached = schrift_rs::CachedSchrift::new(
|
|
|
|
+ schrift_rs::Schrift::build(font.clone())
|
|
|
|
+ .with_scale(16.0)
|
|
|
|
+ .flag_y_downwards()
|
|
|
|
+ .build(),
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ Self {
|
|
|
|
+ font,
|
|
|
|
+ cached: cached.into(),
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl pp::TextInterface for UnaccelText {
|
|
|
|
+ type Surface = UnaccelSurface;
|
|
|
|
+
|
|
|
|
+ fn render_text<'l>(&'l self, what: &pp::text::TextBuffer, sz: f32) -> Self::Surface {
|
|
|
|
+ let line = what.line_iter().next().unwrap();
|
|
|
|
+
|
|
|
|
+ let mut sch = self.cached.borrow_mut();
|
|
|
|
+
|
|
|
|
+ let lm = sch.line_metrics();
|
|
|
|
+ let ascender_offset = lm.ascender;
|
|
|
|
+ let descender_offset = lm.descender;
|
|
|
|
+
|
|
|
|
+ // two passes: first, compute the needed width
|
|
|
|
+ let mut cumulative_width = 0;
|
|
|
|
+ // let mut max_height = 0;
|
|
|
|
+ let glyphs = line
|
|
|
|
+ .chars()
|
|
|
|
+ .map(|v| sch.glyph_lookup(v))
|
|
|
|
+ .collect::<Vec<_>>();
|
|
|
|
+ for glyph in glyphs.iter() {
|
|
|
|
+ let gm = sch.glyph_metrics(*glyph);
|
|
|
|
+ cumulative_width += gm.advance.round() as usize;
|
|
|
|
+ }
|
|
|
|
+ // add one pixel of padding
|
|
|
|
+ cumulative_width += 1;
|
|
|
|
+ // use ascender and descender sizes for image height, with one pixel of padding
|
|
|
|
+ let height = (ascender_offset.abs().round() + descender_offset.abs().round() + 1.0) as usize;
|
|
|
|
+
|
|
|
|
+ let mut surf = UnaccelSurface::new(cumulative_width, height);
|
|
|
|
+
|
|
|
|
+ // second pass: copy the glyph images over
|
|
|
|
+ let mut cursor = 0;
|
|
|
|
+ let mut last_glyph = None;
|
|
|
|
+ for glyph in glyphs.iter() {
|
|
|
|
+ let kerning_offset = match last_glyph.take() {
|
|
|
|
+ Some(lglyph) => {
|
|
|
|
+ let gk = sch.glyph_kerning(lglyph, *glyph);
|
|
|
|
+ (gk.x_shift, gk.y_shift)
|
|
|
|
+ }
|
|
|
|
+ None => {
|
|
|
|
+ (0., 0.)
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ let (left_bearing, advance, y_offset) = {
|
|
|
|
+ let gm = sch.glyph_metrics(*glyph);
|
|
|
|
+ (gm.left_bearing, gm.advance, gm.y_offset + (kerning_offset.1 + ascender_offset).round() as i32)
|
|
|
|
+ };
|
|
|
|
+ let img = sch.glyph_image(*glyph);
|
|
|
|
+ let x_offset = (left_bearing + kerning_offset.0).round() as i32;
|
|
|
|
+ for x in 0..img.1 {
|
|
|
|
+ for y in 0..img.2 {
|
|
|
|
+ let intensity = img.0[(y * img.1 + x) as usize] as u32;
|
|
|
|
+ surf.set_pixel(
|
|
|
|
+ ((cursor + x).checked_add_signed(x_offset).unwrap()) as usize,
|
|
|
|
+ (y as i32 + y_offset) as usize,
|
|
|
|
+ // 0xffffff00 | intensity,
|
|
|
|
+ intensity
|
|
|
|
+ // 0x00ffffff | (intensity << 24)
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ cursor += advance.round() as u32;
|
|
|
|
+ last_glyph = Some(*glyph);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ surf
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[cfg(test)]
|
|
|
|
+#[test]
|
|
|
|
+fn test_unaccel_text() {
|
|
|
|
+ use pp::render::Surface;
|
|
|
|
+ use pp::TextInterface;
|
|
|
|
+ let ut = UnaccelText::new();
|
|
|
|
+
|
|
|
|
+ let tb = pp::text::TextBuffer::new("unaccelerated text rendering ahoy");
|
|
|
|
+
|
|
|
|
+ let surf = ut.render_text(&tb, 16.0);
|
|
|
|
+ println!("{:?}", surf.size());
|
|
|
|
+
|
|
|
|
+ let mut pgm = String::new();
|
|
|
|
+ pgm.push_str("P2\n");
|
|
|
|
+ pgm.push_str(format!("{} {}\n", surf.size().x, surf.size().y).as_str());
|
|
|
|
+ pgm.push_str("255\n");
|
|
|
|
+ for y in 0..surf.size().y {
|
|
|
|
+ for x in 0..surf.size().x {
|
|
|
|
+ pgm.push_str(format!(" {}", surf.get_pixel(x as usize, y as usize) & 0xff).as_str());
|
|
|
|
+ }
|
|
|
|
+ pgm.push('\n');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ std::fs::write("render_output.pgm", pgm).unwrap();
|
|
|
|
+
|
|
|
|
+ // surf
|
|
|
|
+
|
|
|
|
+ // ductr::AnymapImage::ppm(data, 255, height, width);
|
|
|
|
+
|
|
|
|
+ // let rendered = write_as_ascii("unaccelerated.ppm").unwrap();
|
|
|
|
+}
|