use crate::gerber::GerberLayerData;
use crate::{LayerCorners, LayerScale, LayerTransform, LayerType, Pos};
use gerber_parser::gerber_types::{
Aperture, Circle, Command, DCode, FunctionCode, GCode, InterpolationMode, Operation, Unit,
};
use lazy_static::lazy_static;
use std::io::BufReader;
const CHAR_SIZE: u8 = 94;
const TEXT_HEIGHT: f64 = 9.6577;
#[derive(Debug, Clone, PartialEq, Default)]
pub enum HAlign {
#[default]
Left,
Center,
Right,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum VAlign {
#[default]
Bottom,
Middle,
Top,
}
#[derive(Clone)]
pub struct AsciiText {
size: f64,
ratio: f64,
h_align: HAlign,
v_align: VAlign,
}
impl AsciiText {
pub fn new(size: f64) -> Self {
Self {
size,
ratio: 1.0,
h_align: HAlign::default(),
v_align: VAlign::default(),
}
}
pub fn ratio(mut self, ratio: f64) -> Self {
self.ratio = ratio;
self
}
pub fn h_align(mut self, align: HAlign) -> Self {
self.h_align = align;
self
}
pub fn v_align(mut self, align: VAlign) -> Self {
self.v_align = align;
self
}
pub fn build(&self, text: impl AsRef<str>, layer_type: LayerType) -> GerberLayerData {
let Self {
size,
ratio,
h_align,
v_align,
} = self;
let (size, ratio) = (*size, *ratio);
let text = text.as_ref();
let height: f64 = TEXT_HEIGHT / 6.0 * size;
let mut layer = GerberLayerData::empty(layer_type);
layer.apertures.insert(
10,
Aperture::Circle(Circle {
diameter: size / 10.0 * ratio,
hole_diameter: None,
}),
);
layer
.commands
.push(Command::FunctionCode(FunctionCode::DCode(
DCode::SelectAperture(10),
)));
layer
.commands
.push(Command::FunctionCode(FunctionCode::GCode(
GCode::InterpolationMode(InterpolationMode::Linear),
)));
let lines: Vec<&str> = text.lines().collect();
let num_lines = lines.len();
for (line_idx, line) in lines.iter().enumerate() {
let y = num_lines.saturating_sub(1 + line_idx) as f64 * height;
let mut line_cmds: Vec<Command> = Vec::new();
let mut pos = Pos { x: 0.0, y };
for c in line.chars() {
match c {
' ' => pos.x += 0.6 * size,
_ if ('!'..='~').contains(&c) => {
let i = c as usize - '!' as usize;
let mut char = CHARS[i].clone();
(&layer.coordinate_format, &mut char).scale(size / 3.0, size / 3.0);
let width = (&Unit::Millimeters, &char).get_size().width;
(&Unit::Millimeters, &mut char).transform(&pos);
line_cmds.extend(char);
pos.x += width + 0.3 * size;
}
_ => {}
}
}
let (min, max) = (&Unit::Millimeters, &line_cmds).get_corners();
let x_shift = match h_align {
HAlign::Left => -min.x,
HAlign::Center => -(min.x + max.x) / 2.0,
HAlign::Right => -max.x,
};
if x_shift != 0.0 {
(&Unit::Millimeters, &mut line_cmds).transform(&Pos { x: x_shift, y: 0.0 });
}
layer.commands.extend(line_cmds);
}
let total_height = num_lines as f64 * height;
let y_shift = match v_align {
VAlign::Bottom => 0.0,
VAlign::Middle => -total_height / 2.0,
VAlign::Top => -total_height,
};
if y_shift != 0.0 {
layer.transform(&Pos { x: 0.0, y: y_shift });
}
layer
}
}
fn load_ascii() -> Vec<Vec<Command>> {
let raw = include_str!("gerber/kicad_font.gbr");
let reader = BufReader::new(raw.as_bytes());
let data = gerber_parser::parse(reader).unwrap();
let mut stokes = Vec::new();
let mut current_stroke = Vec::new();
for command in data.commands() {
if let Command::FunctionCode(FunctionCode::DCode(DCode::Operation(op))) = command {
match op {
Operation::Interpolate(_, _) | Operation::Flash(_) => {
if !current_stroke.is_empty() {
current_stroke.push(command.clone());
}
}
Operation::Move(_) => {
if !current_stroke.is_empty() {
stokes.push(current_stroke)
}
current_stroke = vec![command.clone()];
}
}
}
}
let mut chars: Vec<Vec<Command>> = vec![Vec::new(); CHAR_SIZE as usize];
for mut stroke in stokes {
let (min, _) = (&Unit::Millimeters, &stroke).get_corners();
let key = ((min.y + 2.4) / TEXT_HEIGHT) as u8;
(&Unit::Millimeters, &mut stroke).transform(&Pos {
x: 0.0,
y: key as f64 * -TEXT_HEIGHT,
});
let key = CHAR_SIZE - key - 1;
chars[key as usize].extend(stroke);
}
for char in chars.iter_mut() {
let x = -(&Unit::Millimeters, &*char).get_corners().0.x;
(&Unit::Millimeters, char).transform(&Pos { x, y: 0.0 });
}
chars
}
lazy_static! {
static ref CHARS: Vec<Vec<Command>> = load_ascii();
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::io::BufWriter;
#[test]
fn test_ascii() -> Result<(), Box<dyn std::error::Error>> {
let layer =
AsciiText::new(3.0).build("~Hello \"World\" 0123456789", LayerType::SilkScreenTop);
fs::create_dir_all("output")?;
let file = fs::File::create("output/ascii.gbr")?;
let mut writer = BufWriter::new(file);
layer.write_to(&mut writer)?;
Ok(())
}
}