784 lines
18 KiB
Rust
784 lines
18 KiB
Rust
//! This module implements my own bitmap and layout based system.
|
||
//!
|
||
//! I created those as a result to my annoyance of how bad / limited
|
||
//! plotters is at rendering text and creating layouts in a clean manner.
|
||
//!
|
||
//! There's still stuff to be implemented here, like a cache for glyphs and
|
||
//! whatnot, but this does run pretty stably for the b30 renderer.
|
||
// {{{ Imports
|
||
use anyhow::anyhow;
|
||
use freetype::bitmap::PixelMode;
|
||
use freetype::face::{KerningMode, LoadFlag};
|
||
use freetype::ffi::{FT_Set_Var_Design_Coordinates, FT_GLYPH_BBOX_PIXELS};
|
||
use freetype::{Bitmap, BitmapGlyph, Face, Glyph, StrokerLineCap, StrokerLineJoin};
|
||
use image::{GenericImage, RgbImage, RgbaImage};
|
||
use num::traits::Euclid;
|
||
|
||
use crate::assets::FREETYPE_LIB;
|
||
use crate::context::Error;
|
||
// }}}
|
||
|
||
// {{{ Color
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub struct Color(pub u8, pub u8, pub u8, pub u8);
|
||
|
||
impl Color {
|
||
pub const BLACK: Self = Self::from_rgb_int(0x000000);
|
||
pub const WHITE: Self = Self::from_rgb_int(0xffffff);
|
||
|
||
#[inline]
|
||
pub const fn from_rgba_int(i: u32) -> Self {
|
||
Self(
|
||
(i >> 24) as u8,
|
||
((i >> 16) & 0xff) as u8,
|
||
((i >> 8) & 0xff) as u8,
|
||
(i & 0xff) as u8,
|
||
)
|
||
}
|
||
|
||
#[inline]
|
||
pub const fn from_rgb_int(i: u32) -> Self {
|
||
Self::from_rgba_int((i << 8) + 0xff)
|
||
}
|
||
|
||
#[inline]
|
||
pub const fn from_bytes(bytes: [u8; 4]) -> Self {
|
||
Self(bytes[0], bytes[1], bytes[2], bytes[3])
|
||
}
|
||
|
||
#[inline]
|
||
pub fn alpha(mut self, a: u8) -> Self {
|
||
self.3 = a;
|
||
self
|
||
}
|
||
|
||
#[inline]
|
||
pub fn distance(self, other: Self) -> f32 {
|
||
let dr = self.0 as f32 - other.0 as f32;
|
||
let dg = self.1 as f32 - other.1 as f32;
|
||
let db = self.2 as f32 - other.2 as f32;
|
||
(dr * dr + dg * dg + db * db).sqrt()
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Rect
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub struct Rect {
|
||
pub x: i32,
|
||
pub y: i32,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
}
|
||
|
||
impl Rect {
|
||
#[inline]
|
||
pub fn new(x: i32, y: i32, width: u32, height: u32) -> Self {
|
||
Self {
|
||
x,
|
||
y,
|
||
width,
|
||
height,
|
||
}
|
||
}
|
||
|
||
#[inline]
|
||
pub fn from_extremes(x_min: i32, y_min: i32, x_max: i32, y_max: i32) -> Self {
|
||
Self::new(x_min, y_min, (x_max - x_min) as u32, (y_max - y_min) as u32)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn from_image(image: &impl GenericImage) -> Self {
|
||
Self::new(0, 0, image.width(), image.height())
|
||
}
|
||
|
||
#[inline]
|
||
pub fn scaled(&self, scale: u32) -> Self {
|
||
Self::new(self.x, self.y, self.width * scale, self.height * scale)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn align(&self, alignment: (Align, Align), pos: Position) -> Position {
|
||
(
|
||
pos.0 - alignment.0.scale(self.width) as i32,
|
||
pos.1 - alignment.1.scale(self.height) as i32,
|
||
)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn align_whole(&self, alignment: (Align, Align), pos: Position) -> Self {
|
||
let pos = self.align(alignment, pos);
|
||
Self::new(pos.0, pos.1, self.width, self.height)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn center(&self) -> Position {
|
||
(
|
||
self.x + self.width as i32 / 2,
|
||
self.y + self.height as i32 / 2,
|
||
)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn top_left(&self) -> Position {
|
||
(self.x, self.y)
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Align
|
||
#[allow(dead_code)]
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub enum Align {
|
||
Start,
|
||
Center,
|
||
End,
|
||
}
|
||
|
||
impl Align {
|
||
#[inline]
|
||
pub fn scale(self, dist: u32) -> u32 {
|
||
match self {
|
||
Self::Start => 0,
|
||
Self::Center => dist / 2,
|
||
Self::End => dist,
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Other types
|
||
pub type Position = (i32, i32);
|
||
|
||
fn float_to_ft_fixed(f: f32) -> i64 {
|
||
(f * 64.0) as i64
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy)]
|
||
pub struct TextStyle {
|
||
pub size: u32,
|
||
pub weight: Option<u32>,
|
||
pub color: Color,
|
||
pub align: (Align, Align),
|
||
pub stroke: Option<(Color, f32)>,
|
||
pub drop_shadow: Option<(Color, Position)>,
|
||
}
|
||
// }}}
|
||
// {{{ BitmapCanvas
|
||
pub struct BitmapCanvas {
|
||
pub buffer: Box<[u8]>,
|
||
pub width: u32,
|
||
}
|
||
|
||
impl BitmapCanvas {
|
||
#[inline]
|
||
pub fn height(&self) -> u32 {
|
||
self.buffer.len() as u32 / 3 / self.width
|
||
}
|
||
|
||
// {{{ Draw pixel
|
||
#[allow(clippy::identity_op)]
|
||
pub fn set_pixel(&mut self, pos: (u32, u32), color: Color) {
|
||
let index = 3 * (pos.1 * self.width + pos.0) as usize;
|
||
let alpha = color.3 as u32;
|
||
|
||
self.buffer[index + 0] =
|
||
((alpha * color.0 as u32 + (255 - alpha) * self.buffer[index + 0] as u32) / 255) as u8;
|
||
self.buffer[index + 1] =
|
||
((alpha * color.1 as u32 + (255 - alpha) * self.buffer[index + 1] as u32) / 255) as u8;
|
||
self.buffer[index + 2] =
|
||
((alpha * color.2 as u32 + (255 - alpha) * self.buffer[index + 2] as u32) / 255) as u8;
|
||
}
|
||
// }}}
|
||
// {{{ Draw RGB image
|
||
/// Draws a bitmap image with no alpha channel.
|
||
pub fn blit_rbg(&mut self, pos: Position, (iw, ih): (u32, u32), src: &[u8]) {
|
||
let iw = iw as i32;
|
||
let ih = ih as i32;
|
||
let width = self.width as i32;
|
||
let height = self.height() as i32;
|
||
|
||
let x_start = 0.max(-pos.0);
|
||
let y_start = 0.max(-pos.1);
|
||
let x_end = iw.min(width - pos.0);
|
||
let y_end = ih.min(height - pos.1);
|
||
|
||
for dx in x_start..x_end {
|
||
for dy in y_start..y_end {
|
||
let x = pos.0 + dx;
|
||
let y = pos.1 + dy;
|
||
|
||
let r = src[(dx + dy * iw) as usize * 3];
|
||
let g = src[(dx + dy * iw) as usize * 3 + 1];
|
||
let b = src[(dx + dy * iw) as usize * 3 + 2];
|
||
|
||
let color = Color(r, g, b, 0xff);
|
||
|
||
self.set_pixel((x as u32, y as u32), color);
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Draw RGBA image
|
||
/// Draws a bitmap image taking care of the alpha channel.
|
||
pub fn blit_rbga(&mut self, pos: Position, (iw, ih): (u32, u32), src: &[u8]) {
|
||
let iw = iw as i32;
|
||
let ih = ih as i32;
|
||
let width = self.width as i32;
|
||
let height = self.height() as i32;
|
||
|
||
let x_start = 0.max(-pos.0);
|
||
let y_start = 0.max(-pos.1);
|
||
let x_end = iw.min(width - pos.0);
|
||
let y_end = ih.min(height - pos.1);
|
||
|
||
for dx in x_start..x_end {
|
||
for dy in y_start..y_end {
|
||
let x = pos.0 + dx;
|
||
let y = pos.1 + dy;
|
||
|
||
let r = src[(dx + dy * iw) as usize * 4];
|
||
let g = src[(dx + dy * iw) as usize * 4 + 1];
|
||
let b = src[(dx + dy * iw) as usize * 4 + 2];
|
||
let a = src[(dx + dy * iw) as usize * 4 + 3];
|
||
|
||
let color = Color(r, g, b, a);
|
||
|
||
self.set_pixel((x as u32, y as u32), color);
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Draw scaled up RBGA image
|
||
pub fn blit_rbga_scaled_up(
|
||
&mut self,
|
||
pos: Position,
|
||
(iw, ih): (u32, u32),
|
||
src: &[u8],
|
||
scale: u32,
|
||
) {
|
||
let scale = scale as i32;
|
||
|
||
let iw = iw as i32;
|
||
let ih = ih as i32;
|
||
let width = self.width as i32;
|
||
let height = self.height() as i32;
|
||
|
||
let x_start = pos.0.max(0);
|
||
let y_start = pos.1.max(0);
|
||
let x_end = (pos.0 + iw * scale).min(width);
|
||
let y_end = (pos.1 + ih * scale).min(height);
|
||
|
||
for x in x_start..x_end {
|
||
for y in y_start..y_end {
|
||
// NOTE: I could instead keep separate counters.
|
||
// It would introduce an additional if statement,
|
||
// but would not perform division.
|
||
let dx = (x - pos.0) / scale;
|
||
let dy = (y - pos.1) / scale;
|
||
let r = src[(dx + dy * iw) as usize * 4];
|
||
let g = src[(dx + dy * iw) as usize * 4 + 1];
|
||
let b = src[(dx + dy * iw) as usize * 4 + 2];
|
||
let a = src[(dx + dy * iw) as usize * 4 + 3];
|
||
|
||
let color = Color(r, g, b, a);
|
||
|
||
self.set_pixel((x as u32, y as u32), color);
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Fill
|
||
/// Fill with solid color
|
||
pub fn fill(&mut self, pos: Position, (iw, ih): (u32, u32), color: Color) {
|
||
let height = self.height();
|
||
for dx in 0..iw {
|
||
for dy in 0..ih {
|
||
let x = pos.0 + dx as i32;
|
||
let y = pos.1 + dy as i32;
|
||
if x >= 0 && (x as u32) < self.width && y >= 0 && (y as u32) < height {
|
||
self.set_pixel((x as u32, y as u32), color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Draw text
|
||
#[allow(clippy::type_complexity)]
|
||
pub fn plan_text_rendering(
|
||
pos: Position,
|
||
faces: &mut [&mut Face],
|
||
style: TextStyle,
|
||
text: &str,
|
||
) -> Result<(Position, Rect, Vec<(i64, Glyph)>), Error> {
|
||
// {{{ Control weight
|
||
if let Some(weight) = style.weight {
|
||
for face in faces.iter_mut() {
|
||
unsafe {
|
||
let raw = face.raw_mut() as *mut _;
|
||
let slice = [(weight as i64) << 16];
|
||
|
||
// {{{ Debug logging
|
||
// let mut amaster = 0 as *mut FT_MM_Var;
|
||
// FT_Get_MM_Var(raw, &mut amaster as *mut _);
|
||
// println!("{:?}", *amaster);
|
||
// println!("{:?}", *(*amaster).axis);
|
||
// println!("{:?}", *(*amaster).namedstyle);
|
||
// }}}
|
||
|
||
// Set variable weight
|
||
let _err = FT_Set_Var_Design_Coordinates(raw, 3, slice.as_ptr());
|
||
// Some fonts are not variable, so we just ignore errors :/
|
||
// if err != FT_Err_Ok {
|
||
// let err: FtResult<_> = Err(err.into());
|
||
// err?;
|
||
// }
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
|
||
for face in faces.iter_mut() {
|
||
face.set_char_size((style.size << 6) as isize, 0, 0, 0)?;
|
||
}
|
||
|
||
// {{{ Compute layout
|
||
let mut pen_x = 0;
|
||
let kerning: Vec<_> = faces.iter().map(|f| f.has_kerning()).collect();
|
||
let mut previous = None;
|
||
let mut data = Vec::new();
|
||
|
||
for c in text.chars() {
|
||
let c = match c {
|
||
'~' => '~',
|
||
c => c,
|
||
};
|
||
|
||
let (face_index, glyph_index) = faces
|
||
.iter()
|
||
.enumerate()
|
||
.find_map(|(i, face)| {
|
||
let glyph_index = face.get_char_index(c as usize)?;
|
||
Some((i, glyph_index))
|
||
})
|
||
.ok_or_else(|| {
|
||
anyhow!("Could not get glyph index for char '{}' in \"{}\"", c, text)
|
||
})?;
|
||
|
||
let face = &mut faces[face_index];
|
||
if let Some((prev_face_index, prev_glyth_index)) = previous {
|
||
if prev_face_index == face_index && kerning[face_index] {
|
||
let delta = face.get_kerning(
|
||
prev_glyth_index,
|
||
glyph_index,
|
||
KerningMode::KerningDefault,
|
||
)?;
|
||
pen_x += delta.x >> 6; // we shift to get rid of sub-pixel accuracy
|
||
}
|
||
}
|
||
|
||
face.load_glyph(glyph_index, LoadFlag::DEFAULT)?;
|
||
|
||
data.push((pen_x, face.glyph().get_glyph()?));
|
||
pen_x += face.glyph().advance().x >> 6;
|
||
previous = Some((face_index, glyph_index));
|
||
}
|
||
|
||
// }}}
|
||
// {{{ Find bounding box
|
||
let mut x_min = 32000;
|
||
let mut y_min = 32000;
|
||
let mut x_max = -32000;
|
||
let mut y_max = -32000;
|
||
|
||
for (pen_x, glyph) in &data {
|
||
let mut bbox = glyph.get_cbox(FT_GLYPH_BBOX_PIXELS);
|
||
|
||
bbox.xMin += pen_x;
|
||
bbox.xMax += pen_x;
|
||
|
||
if bbox.xMin < x_min {
|
||
x_min = bbox.xMin
|
||
}
|
||
|
||
if bbox.xMax > x_max {
|
||
x_max = bbox.xMax
|
||
}
|
||
|
||
if bbox.yMin < y_min {
|
||
y_min = bbox.yMin
|
||
}
|
||
|
||
if bbox.yMax > y_max {
|
||
y_max = bbox.yMax
|
||
}
|
||
}
|
||
|
||
// Check that we really grew the string bbox
|
||
if x_min > x_max {
|
||
x_min = 0;
|
||
x_max = 0;
|
||
y_min = 0;
|
||
y_max = 0;
|
||
}
|
||
|
||
let bbox = Rect::from_extremes(x_min as i32, y_min as i32, x_max as i32, y_max as i32);
|
||
let pos = bbox.align(style.align, pos);
|
||
// }}}
|
||
|
||
Ok((pos, bbox, data))
|
||
}
|
||
|
||
/// Render text
|
||
pub fn text(
|
||
&mut self,
|
||
pos: Position,
|
||
faces: &mut [&mut Face],
|
||
style: TextStyle,
|
||
text: &str,
|
||
) -> Result<(), Error> {
|
||
let (pos, bbox, data) = Self::plan_text_rendering(pos, faces, style, text)?;
|
||
|
||
// {{{ Render glyphs
|
||
for (pos_x, glyph) in &data {
|
||
let b_glyph = glyph.to_bitmap(freetype::RenderMode::Normal, None)?;
|
||
let bitmap = b_glyph.bitmap();
|
||
let pixel_mode = bitmap.pixel_mode()?;
|
||
assert_eq!(pixel_mode, PixelMode::Gray);
|
||
|
||
let char_pos = (
|
||
pos.0 + *pos_x as i32 - bbox.x,
|
||
pos.1 + bbox.height as i32 + bbox.y,
|
||
);
|
||
|
||
if let Some((shadow_color, offset)) = style.drop_shadow {
|
||
let char_pos = (char_pos.0 + offset.0, char_pos.1 + offset.1);
|
||
self.blit_glyph(&b_glyph, &bitmap, char_pos, shadow_color);
|
||
}
|
||
|
||
if let Some((stroke_color, stroke_width)) = style.stroke {
|
||
// {{{ Create stroke
|
||
let stroker = FREETYPE_LIB.with(|lib| lib.new_stroker())?;
|
||
stroker.set(
|
||
float_to_ft_fixed(stroke_width),
|
||
StrokerLineCap::Round,
|
||
StrokerLineJoin::Round,
|
||
0,
|
||
);
|
||
|
||
let sglyph = glyph.stroke(&stroker)?;
|
||
let sb_glyph = sglyph.to_bitmap(freetype::RenderMode::Normal, None)?;
|
||
let sbitmap = sb_glyph.bitmap();
|
||
let spixel_mode = sbitmap.pixel_mode()?;
|
||
assert_eq!(spixel_mode, PixelMode::Gray);
|
||
// }}}
|
||
|
||
self.blit_glyph(&sb_glyph, &sbitmap, char_pos, stroke_color);
|
||
}
|
||
|
||
self.blit_glyph(&b_glyph, &bitmap, char_pos, style.color);
|
||
}
|
||
// }}}
|
||
|
||
Ok(())
|
||
}
|
||
// }}}
|
||
// {{{ Blit glyph
|
||
pub fn blit_glyph(
|
||
&mut self,
|
||
b_glyph: &BitmapGlyph,
|
||
bitmap: &Bitmap,
|
||
pos: Position,
|
||
color: Color,
|
||
) {
|
||
let iw = bitmap.width();
|
||
let ih = bitmap.rows();
|
||
let height = self.buffer.len() as u32 / 3 / self.width;
|
||
let src = bitmap.buffer();
|
||
|
||
for dx in 0..iw {
|
||
for dy in 0..ih {
|
||
let x = pos.0 + dx + b_glyph.left();
|
||
let y = pos.1 + dy - b_glyph.top();
|
||
|
||
// TODO: gamma correction
|
||
if x >= 0 && (x as u32) < self.width && y >= 0 && (y as u32) < height {
|
||
let gray = src[(dx + dy * iw) as usize];
|
||
|
||
let r = color.0;
|
||
let g = color.1;
|
||
let b = color.2;
|
||
let a = ((color.3 as u32 * gray as u32) / 0xff) as u8;
|
||
|
||
let color = Color(r, g, b, a);
|
||
|
||
self.set_pixel((x as u32, y as u32), color);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// }}}
|
||
|
||
#[inline]
|
||
pub fn new(width: u32, height: u32) -> Self {
|
||
let buffer = vec![u8::MAX; 3 * (width * height) as usize].into_boxed_slice();
|
||
Self { buffer, width }
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Layout types
|
||
#[derive(Clone, Copy, Debug)]
|
||
pub struct LayoutBox {
|
||
relative_to: Option<(LayoutBoxId, i32, i32)>,
|
||
pub width: u32,
|
||
pub height: u32,
|
||
}
|
||
|
||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||
pub struct LayoutBoxId(usize);
|
||
|
||
#[derive(Default, Debug)]
|
||
pub struct LayoutManager {
|
||
boxes: Vec<LayoutBox>,
|
||
}
|
||
|
||
pub struct LayoutDrawer {
|
||
pub layout: LayoutManager,
|
||
pub canvas: BitmapCanvas,
|
||
}
|
||
|
||
impl LayoutManager {
|
||
// {{{ Trivial box creation
|
||
pub fn make_box(&mut self, width: u32, height: u32) -> LayoutBoxId {
|
||
let id = self.boxes.len();
|
||
self.boxes.push(LayoutBox {
|
||
relative_to: None,
|
||
width,
|
||
height,
|
||
});
|
||
|
||
LayoutBoxId(id)
|
||
}
|
||
|
||
pub fn make_relative_box(
|
||
&mut self,
|
||
to: LayoutBoxId,
|
||
x: i32,
|
||
y: i32,
|
||
width: u32,
|
||
height: u32,
|
||
) -> LayoutBoxId {
|
||
let id = self.make_box(width, height);
|
||
self.edit_to_relative(id, to, x, y);
|
||
|
||
id
|
||
}
|
||
// }}}
|
||
// {{{ Chage box to be relative
|
||
pub fn edit_to_relative(
|
||
&mut self,
|
||
id: LayoutBoxId,
|
||
id_relative_to: LayoutBoxId,
|
||
x: i32,
|
||
y: i32,
|
||
) {
|
||
let current = self.boxes[id.0];
|
||
|
||
match current.relative_to {
|
||
Some((current_points_to, dx, dy)) if current_points_to != id_relative_to => {
|
||
self.edit_to_relative(current_points_to, id_relative_to, x - dx, y - dy);
|
||
}
|
||
_ => {
|
||
self.boxes[id.0].relative_to = Some((id_relative_to, x, y));
|
||
}
|
||
}
|
||
|
||
{
|
||
let a = self.lookup(id);
|
||
let b = self.lookup(id_relative_to);
|
||
assert_eq!((a.x - b.x, a.y - b.y), (x, y));
|
||
}
|
||
}
|
||
// }}}
|
||
// {{{ Margins
|
||
#[inline]
|
||
pub fn margin(&mut self, id: LayoutBoxId, t: i32, r: i32, b: i32, l: i32) -> LayoutBoxId {
|
||
let inner = self.boxes[id.0];
|
||
let out = self.make_box(
|
||
(inner.width as i32 + l + r) as u32,
|
||
(inner.height as i32 + t + b) as u32,
|
||
);
|
||
self.edit_to_relative(id, out, l, t);
|
||
out
|
||
}
|
||
|
||
#[inline]
|
||
pub fn margin_xy(&mut self, inner: LayoutBoxId, x: i32, y: i32) -> LayoutBoxId {
|
||
self.margin(inner, y, x, y, x)
|
||
}
|
||
|
||
#[inline]
|
||
pub fn margin_uniform(&mut self, inner: LayoutBoxId, amount: i32) -> LayoutBoxId {
|
||
self.margin(inner, amount, amount, amount, amount)
|
||
}
|
||
// }}}
|
||
// {{{ Glueing
|
||
#[inline]
|
||
pub fn glue_horizontally(
|
||
&mut self,
|
||
first_id: LayoutBoxId,
|
||
second_id: LayoutBoxId,
|
||
) -> LayoutBoxId {
|
||
let first = self.boxes[first_id.0];
|
||
let second = self.boxes[second_id.0];
|
||
let id = self.make_box(first.width.max(second.width), first.height + second.height);
|
||
|
||
self.edit_to_relative(first_id, id, 0, 0);
|
||
self.edit_to_relative(second_id, id, 0, first.height as i32);
|
||
id
|
||
}
|
||
|
||
#[inline]
|
||
pub fn glue_vertically(
|
||
&mut self,
|
||
first_id: LayoutBoxId,
|
||
second_id: LayoutBoxId,
|
||
) -> LayoutBoxId {
|
||
let first = self.boxes[first_id.0];
|
||
let second = self.boxes[second_id.0];
|
||
let id = self.make_box(first.width + second.width, first.height.max(second.height));
|
||
|
||
self.edit_to_relative(first_id, id, 0, 0);
|
||
self.edit_to_relative(second_id, id, first.width as i32, 0);
|
||
id
|
||
}
|
||
// }}}
|
||
// {{{ Repeating
|
||
pub fn repeated_evenly(
|
||
&mut self,
|
||
id: LayoutBoxId,
|
||
amount: (u32, u32),
|
||
) -> (LayoutBoxId, impl Iterator<Item = Position>) {
|
||
let inner = self.boxes[id.0];
|
||
let outer_id = self.make_box(inner.width * amount.0, inner.height * amount.1);
|
||
self.edit_to_relative(id, outer_id, 0, 0);
|
||
|
||
(
|
||
outer_id,
|
||
(0..amount.0 * amount.1).map(move |i| {
|
||
let (y, x) = i.div_rem_euclid(&amount.0);
|
||
((x * inner.width) as i32, (y * inner.height) as i32)
|
||
}),
|
||
)
|
||
}
|
||
// }}}
|
||
// {{{ Lookup box
|
||
pub fn lookup(&self, id: LayoutBoxId) -> Rect {
|
||
let current = self.boxes[id.0];
|
||
if let Some((to, dx, dy)) = current.relative_to {
|
||
let r = self.lookup(to);
|
||
Rect::new(r.x + dx, r.y + dy, current.width, current.height)
|
||
} else {
|
||
Rect::new(0, 0, current.width, current.height)
|
||
}
|
||
}
|
||
|
||
#[inline]
|
||
pub fn width(&self, id: LayoutBoxId) -> u32 {
|
||
self.boxes[id.0].width
|
||
}
|
||
|
||
#[inline]
|
||
pub fn height(&self, id: LayoutBoxId) -> u32 {
|
||
self.boxes[id.0].height
|
||
}
|
||
|
||
// }}}
|
||
// {{{ Alignment
|
||
#[inline]
|
||
pub fn position_relative_to(&self, id: LayoutBoxId, pos: Position) -> Position {
|
||
let current = self.lookup(id);
|
||
((pos.0 + current.x), (pos.1 + current.y))
|
||
}
|
||
|
||
#[inline]
|
||
pub fn align(&self, id: LayoutBoxId, align: (Align, Align), pos: Position) -> Position {
|
||
self.lookup(id).align(align, pos)
|
||
}
|
||
// }}}
|
||
}
|
||
|
||
impl LayoutDrawer {
|
||
#[inline]
|
||
pub fn new(layout: LayoutManager, canvas: BitmapCanvas) -> Self {
|
||
Self { layout, canvas }
|
||
}
|
||
|
||
// {{{ Drawing
|
||
// {{{ Draw pixel
|
||
#[inline]
|
||
pub fn set_pixel(&mut self, id: LayoutBoxId, pos: (u32, u32), color: Color) {
|
||
let pos = self
|
||
.layout
|
||
.position_relative_to(id, (pos.0 as i32, pos.1 as i32));
|
||
self.canvas.set_pixel((pos.0 as u32, pos.1 as u32), color);
|
||
}
|
||
// }}}
|
||
// {{{ Draw images
|
||
/// Draws a bitmap image taking with no alpha channel.
|
||
#[inline]
|
||
pub fn blit_rbg(&mut self, id: LayoutBoxId, pos: Position, image: &RgbImage) {
|
||
let pos = self.layout.position_relative_to(id, pos);
|
||
self.canvas
|
||
.blit_rbg(pos, image.dimensions(), image.as_raw());
|
||
}
|
||
|
||
/// Draws a bitmap image taking care of the alpha channel.
|
||
#[inline]
|
||
pub fn blit_rbga(&mut self, id: LayoutBoxId, pos: Position, image: &RgbaImage) {
|
||
let pos = self.layout.position_relative_to(id, pos);
|
||
self.canvas
|
||
.blit_rbga(pos, image.dimensions(), image.as_raw());
|
||
}
|
||
|
||
#[inline]
|
||
pub fn blit_rbg_scaled_up(
|
||
&mut self,
|
||
id: LayoutBoxId,
|
||
pos: Position,
|
||
dims: (u32, u32),
|
||
src: &[u8],
|
||
scale: u32,
|
||
) {
|
||
let pos = self.layout.position_relative_to(id, pos);
|
||
self.canvas.blit_rbga_scaled_up(pos, dims, src, scale);
|
||
}
|
||
// }}}
|
||
// {{{ Fill
|
||
/// Fills with solid color
|
||
#[inline]
|
||
pub fn fill(&mut self, id: LayoutBoxId, color: Color) {
|
||
let current = self.layout.lookup(id);
|
||
self.canvas.fill(
|
||
(current.x, current.y),
|
||
(current.width, current.height),
|
||
color,
|
||
);
|
||
}
|
||
// }}}
|
||
// {{{ Draw text
|
||
/// Render text
|
||
#[inline]
|
||
pub fn text(
|
||
&mut self,
|
||
id: LayoutBoxId,
|
||
pos: Position,
|
||
faces: &mut [&mut Face],
|
||
style: TextStyle,
|
||
text: &str,
|
||
) -> Result<(), Error> {
|
||
let pos = self.layout.position_relative_to(id, pos);
|
||
self.canvas.text(pos, faces, style, text)
|
||
}
|
||
// }}}
|
||
// }}}
|
||
}
|
||
// }}}
|