use psdk_father::args::{ArgContext, PsdkArgBase, PsdkEasyArg};
use psdk_father::command::CommandClause;
use psdk_father::errors::{PsdkFatherError, PsdkFatherResult};
use std::sync::{Arc, Mutex};
use crate::marks::{Font, Rotation};
use super::{CImageBytes, CImageBytesOptions};
#[derive(Clone, Debug)]
pub struct CTextCanvasOptions {
pub x: i32,
pub y: i32,
pub image_bytes: Vec<u8>,
pub compress: bool,
}
#[derive(Clone, Debug)]
pub struct CTextCanvasImageOptions {
pub content: String,
pub font: Font,
pub rotation: Rotation,
pub bold: Option<bool>,
pub alpha: i32,
}
#[derive(Clone, Debug)]
pub struct CTextCanvas {
image_bytes: CImageBytes,
}
impl CTextCanvas {
pub fn new(options: CTextCanvasOptions) -> PsdkFatherResult<Self> {
Ok(Self {
image_bytes: CImageBytes::new(CImageBytesOptions {
x: options.x,
y: options.y,
bytes: options.image_bytes,
compress: options.compress,
reverse: false,
gray_threshold: None,
smooth_weight: 0,
edge_dither_mode: None,
edge_dither_depth: Default::default(),
})?,
})
}
pub fn generate_image_bytes(options: CTextCanvasImageOptions) -> PsdkFatherResult<Vec<u8>> {
let alpha = options.alpha.clamp(0, 255) as u8;
let font_height = options.font.get_height().max(7);
let scale = (font_height / 7).max(1);
let char_width = 5 * scale;
let char_height = 7 * scale;
let char_gap = scale;
let padding = 2u32;
let char_count = options.content.chars().count().max(1) as u32;
let width = padding * 2 + char_count * char_width + char_count.saturating_sub(1) * char_gap;
let height = padding * 2 + char_height;
let mut canvas = RgbaCanvas::new(width, height);
for (index, character) in options.content.chars().enumerate() {
let pattern = glyph_pattern(character);
let x = padding + index as u32 * (char_width + char_gap);
draw_glyph(
&mut canvas,
Point { x, y: padding },
scale,
pattern,
options.bold.unwrap_or(false),
alpha,
);
}
let canvas = rotate_rgba(canvas, &options.rotation);
encode_png_rgba(canvas.width, canvas.height, &canvas.rgba)
}
}
struct RgbaCanvas {
width: u32,
height: u32,
rgba: Vec<u8>,
}
impl RgbaCanvas {
fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
rgba: vec![255u8; (width * height * 4) as usize],
}
}
}
#[derive(Clone, Copy)]
struct Point {
x: u32,
y: u32,
}
fn draw_glyph(
canvas: &mut RgbaCanvas,
origin: Point,
scale: u32,
pattern: [u8; 7],
bold: bool,
alpha: u8,
) {
for (row, bits) in pattern.iter().enumerate() {
for column in 0..5 {
if bits & (1 << (4 - column)) == 0 {
continue;
}
let px = origin.x + column * scale;
let py = origin.y + row as u32 * scale;
fill_rect(
canvas,
Point { x: px, y: py },
scale + u32::from(bold),
scale,
alpha,
);
}
}
}
fn fill_rect(canvas: &mut RgbaCanvas, origin: Point, rect_width: u32, rect_height: u32, alpha: u8) {
for py in origin.y..(origin.y + rect_height).min(canvas.height) {
for px in origin.x..(origin.x + rect_width).min(canvas.width) {
let offset = ((py * canvas.width + px) * 4) as usize;
canvas.rgba[offset] = 0;
canvas.rgba[offset + 1] = 0;
canvas.rgba[offset + 2] = 0;
canvas.rgba[offset + 3] = alpha;
}
}
}
fn rotate_rgba(canvas: RgbaCanvas, rotation: &Rotation) -> RgbaCanvas {
match rotation {
Rotation::Rotation0 => canvas,
Rotation::Rotation90 => {
let mut rotated = RgbaCanvas::new(canvas.height, canvas.width);
for y in 0..canvas.height {
for x in 0..canvas.width {
copy_pixel(
&canvas,
&mut rotated,
Point { x, y },
Point {
x: canvas.height - 1 - y,
y: x,
},
);
}
}
rotated
}
Rotation::Rotation180 => {
let mut rotated = RgbaCanvas::new(canvas.width, canvas.height);
for y in 0..canvas.height {
for x in 0..canvas.width {
copy_pixel(
&canvas,
&mut rotated,
Point { x, y },
Point {
x: canvas.width - 1 - x,
y: canvas.height - 1 - y,
},
);
}
}
rotated
}
Rotation::Rotation270 => {
let mut rotated = RgbaCanvas::new(canvas.height, canvas.width);
for y in 0..canvas.height {
for x in 0..canvas.width {
copy_pixel(
&canvas,
&mut rotated,
Point { x, y },
Point {
x: y,
y: canvas.width - 1 - x,
},
);
}
}
rotated
}
}
}
fn copy_pixel(
source: &RgbaCanvas,
target: &mut RgbaCanvas,
source_point: Point,
target_point: Point,
) {
let source_offset = ((source_point.y * source.width + source_point.x) * 4) as usize;
let target_offset = ((target_point.y * target.width + target_point.x) * 4) as usize;
target.rgba[target_offset..target_offset + 4]
.copy_from_slice(&source.rgba[source_offset..source_offset + 4]);
}
fn encode_png_rgba(width: u32, height: u32, rgba: &[u8]) -> PsdkFatherResult<Vec<u8>> {
let mut png_bytes = Vec::new();
{
let mut encoder = png::Encoder::new(&mut png_bytes, width, height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder
.write_header()
.map_err(|error| PsdkFatherError::Image(format!("{:?}", error)))?;
writer
.write_image_data(rgba)
.map_err(|error| PsdkFatherError::Image(format!("{:?}", error)))?;
}
Ok(png_bytes)
}
fn glyph_pattern(character: char) -> [u8; 7] {
match character.to_ascii_uppercase() {
'A' => [
0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
],
'B' => [
0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110,
],
'C' => [
0b01111, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b01111,
],
'D' => [
0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110,
],
'E' => [
0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111,
],
'F' => [
0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000,
],
'G' => [
0b01111, 0b10000, 0b10000, 0b10111, 0b10001, 0b10001, 0b01111,
],
'H' => [
0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001,
],
'I' => [
0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b11111,
],
'J' => [
0b00111, 0b00010, 0b00010, 0b00010, 0b10010, 0b10010, 0b01100,
],
'K' => [
0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001,
],
'L' => [
0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111,
],
'M' => [
0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001,
],
'N' => [
0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001,
],
'O' => [
0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110,
],
'P' => [
0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000,
],
'Q' => [
0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101,
],
'R' => [
0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001,
],
'S' => [
0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110,
],
'T' => [
0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100,
],
'U' => [
0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110,
],
'V' => [
0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100,
],
'W' => [
0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b10101, 0b01010,
],
'X' => [
0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001,
],
'Y' => [
0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100,
],
'Z' => [
0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111,
],
'0' => [
0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110,
],
'1' => [
0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110,
],
'2' => [
0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111,
],
'3' => [
0b11110, 0b00001, 0b00001, 0b01110, 0b00001, 0b00001, 0b11110,
],
'4' => [
0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010,
],
'5' => [
0b11111, 0b10000, 0b10000, 0b11110, 0b00001, 0b00001, 0b11110,
],
'6' => [
0b01110, 0b10000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110,
],
'7' => [
0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000,
],
'8' => [
0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110,
],
'9' => [
0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00001, 0b01110,
],
' ' => [0, 0, 0, 0, 0, 0, 0],
_ => [0b11111, 0b10001, 0b00001, 0b00110, 0b00100, 0, 0b00100],
}
}
impl TryFrom<(i32, i32, Vec<u8>)> for CTextCanvas {
type Error = psdk_father::errors::PsdkFatherError;
fn try_from((x, y, image_bytes): (i32, i32, Vec<u8>)) -> Result<Self, Self::Error> {
Self::new(CTextCanvasOptions {
x,
y,
image_bytes,
compress: false,
})
}
}
impl PsdkEasyArg<String> for CTextCanvas {
fn context(&self) -> Arc<Mutex<ArgContext>> {
self.image_bytes.context()
}
}
impl PsdkArgBase<String> for CTextCanvas {
fn header(&self) -> String {
self.image_bytes.header()
}
fn clause(&self) -> PsdkFatherResult<CommandClause> {
self.image_bytes.clause()
}
}
psdk_father::impl_psdk_arg_to_easy_arg!(String, CTextCanvas, true);