use std::path::Path;
use log::debug;
use image::{Luma};
use barcoders::sym::code39::Code39;
use qrcode::QrCode;
#[cfg(feature = "structopt")]
use structopt::StructOpt;
use embedded_graphics::prelude::*;
use embedded_text::prelude::*;
use embedded_graphics::{
pixelcolor::BinaryColor,
};
#[cfg(feature = "preview")]
use embedded_graphics_simulator::{
BinaryColorTheme, OutputSettingsBuilder, SimulatorDisplay, Window,
};
use crate::Error;
pub mod display;
pub use display::*;
pub mod ops;
pub use ops::*;
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "structopt", derive(StructOpt))]
pub struct RenderConfig {
pub min_x: usize,
pub max_x: usize,
pub y: usize,
}
impl Default for RenderConfig {
fn default() -> Self {
Self {
min_x: 32,
max_x: 10 * 1024,
y: 64,
}
}
}
pub struct Render {
cfg: RenderConfig,
display: Display,
}
impl Render {
pub fn new(cfg: RenderConfig) -> Self {
let display = Display::new(cfg.y as usize, cfg.min_x as usize);
Self { cfg, display }
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), anyhow::Error> {
let size = self.display.size();
let i = image::DynamicImage::new_luma8(size.width, size.height);
let mut i = i.into_luma8();
for x in 0..size.width {
for y in 0..size.height {
let p = self.display.get(x as usize, y as usize)?;
if !p {
i.put_pixel(x, y, Luma([0xff]));
}
}
}
i.save(path)?;
Ok(())
}
pub fn render(&mut self, ops: &[Op]) -> Result<&Self, Error> {
let mut x = 0;
for operation in ops {
x += match operation {
Op::Text { text, opts } => self.render_text(x, text, opts)?,
Op::Pad{ count } => self.pad(x, *count)?,
Op::Qr{ code } => self.render_qrcode(x, code)?,
Op::Barcode{ code, opts } => self.render_barcode(x, code, opts)?,
Op::Image{ file, opts } => self.render_image(x, file, opts)?,
}
}
Ok(self)
}
fn render_text(&mut self, start_x: usize, value: &str, opts: &TextOptions) -> Result<usize, Error> {
use embedded_graphics::fonts::*;
use embedded_text::style::vertical_overdraw::Hidden;
let value = value.replace("\\n", "\n");
let max_line_x = value
.split("\n")
.map(|line| opts.font.char_width() * line.len() + 1)
.max()
.unwrap();
let max_x = self.cfg.max_x.min(start_x + max_line_x);
let tb = TextBox::new(
&value,
Rectangle::new(
Point::new(start_x as i32, 0 as i32),
Point::new(max_x as i32, self.cfg.y as i32),
),
);
debug!("Textbox: {:?}", tb);
#[cfg(nope)]
let a = match opts.h_align {
HAlign::Centre => CenterAligned,
HAlign::Left => LeftAligned,
HAlign::Right => RightAligned,
HAlign::Justify => Justified,
};
#[cfg(nope)]
let v = match opts.v_align {
VAlign::Centre => CenterAligned,
VAlign::Top => TopAligned,
VAlign::Bottom => BottomAligned,
};
let a = CenterAligned;
let v = CenterAligned;
let h = Exact(Hidden);
let l = 4;
let res = match opts.font {
FontKind::Font6x6 => {
let ts = TextBoxStyleBuilder::new(Font6x6)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
FontKind::Font6x8 => {
let ts = TextBoxStyleBuilder::new(Font6x8)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
FontKind::Font6x12 => {
let ts = TextBoxStyleBuilder::new(Font6x12)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
FontKind::Font8x16 => {
let ts = TextBoxStyleBuilder::new(Font8x16)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
FontKind::Font12x16 => {
let ts = TextBoxStyleBuilder::new(Font12x16)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
FontKind::Font24x32 => {
let ts = TextBoxStyleBuilder::new(Font24x32)
.text_color(BinaryColor::On)
.height_mode(h)
.alignment(a)
.line_spacing(l)
.vertical_alignment(v)
.build();
let tb = tb.into_styled(ts);
tb.draw(&mut self.display).unwrap();
tb.size()
}
};
Ok(res.width as usize)
}
fn pad(&mut self, x: usize, columns: usize) -> Result<usize, Error> {
self.display
.draw_pixel(Pixel(Point::new((x + columns) as i32, 0), BinaryColor::Off))?;
Ok(columns)
}
fn render_qrcode(&mut self, x_start: usize, value: &str) -> Result<usize, Error> {
let qr = QrCode::new(value).unwrap();
let img = qr.render()
.dark_color(image::Rgb([0, 0, 0]))
.light_color(image::Rgb([255, 255, 255]))
.quiet_zone(false)
.max_dimensions(self.cfg.y as u32, self.cfg.y as u32)
.build();
let y_offset = (self.cfg.y as i32 - img.height() as i32) / 2;
let x_offset = x_start as i32 + y_offset;
for (x, y, v) in img.enumerate_pixels() {
let c = match v {
image::Rgb([0, 0, 0]) => BinaryColor::On,
_ => BinaryColor::Off,
};
let p = Pixel(Point::new(x_offset + x as i32, y_offset + y as i32), c);
self.display.draw_pixel(p)?
}
Ok(img.width() as usize + x_offset as usize)
}
fn render_barcode(&mut self, x_start: usize, value: &str, opts: &BarcodeOptions) -> Result<usize, Error> {
let barcode = Code39::new(value).unwrap();
let encoded: Vec<u8> = barcode.encode();
let x_offset = x_start as i32;
for i in 0..encoded.len() {
for y in opts.y_offset..self.cfg.y-opts.y_offset {
let c = match encoded[i] != 0 {
true => BinaryColor::On,
false => BinaryColor::Off,
};
let p = Pixel(Point::new(x_offset + i as i32, y as i32), c);
self.display.draw_pixel(p)?
}
}
Ok(encoded.len() + x_offset as usize)
}
fn render_image(&mut self, x_start: usize, file: &str, _opts: &ImageOptions) -> Result<usize, Error> {
let img = image::io::Reader::open(file)?.decode()?;
let i = img.clone().into_luma8();
let d = i.dimensions();
let x_offset = x_start as i32;
let y_offset = (self.cfg.y / 2) as i32 - (d.1 as usize / 2) as i32;
for x in 0..d.0 as i32 {
for y in 0..d.1 as i32 {
let p = i.get_pixel(x as u32, y as u32);
let c = match p.0[0] == 0 {
true => BinaryColor::On,
false => BinaryColor::Off,
};
let p = Pixel(Point::new(x_offset + x as i32, y_offset + y as i32), c);
self.display.draw_pixel(p)?
}
}
Ok(d.0 as usize + x_offset as usize)
}
pub fn raster(&self, margins: (usize, usize, usize)) -> Result<Vec<[u8; 16]>, anyhow::Error> {
self.display.raster(margins)
}
#[cfg(feature = "preview")]
pub fn show(&self) -> Result<(), anyhow::Error> {
let s = self.display.size();
debug!("Render display size: {:?}", s);
let mut sim_display: SimulatorDisplay<BinaryColor> = SimulatorDisplay::new(s);
for y in 0..s.height as usize {
for x in 0..s.width as usize {
let p = self.display.get_pixel(x, y)?;
sim_display.draw_pixel(p)?;
}
}
let output_settings = OutputSettingsBuilder::new()
.theme(BinaryColorTheme::LcdWhite)
.build();
let name = format!("Label preview ({}, {})", s.width, s.height);
Window::new(&name, &output_settings).show_static(&sim_display);
Ok(())
}
}