use crate::dms::config::{CfgError, MultiCfg, SignCfg, VmsCfg};
use crate::dms::font::{FontError, FontTable};
use crate::dms::graphic::{GraphicError, GraphicTable};
use crate::dms::multi::{ColorCtx, ColorScheme, Rectangle};
#[derive(Debug, thiserror::Error)]
pub enum SignError {
#[error("Config error: {0}")]
CfgValidation(#[from] CfgError),
#[error("Font error: {0}")]
FontValidation(#[from] FontError),
#[error("Graphic error: {0}")]
GraphicValidation(#[from] GraphicError),
}
#[derive(Clone, Default)]
pub struct DmsBuilder<const C: usize, const F: usize, const G: usize> {
sign_cfg: SignCfg,
vms_cfg: VmsCfg,
font_definition: FontTable<C, F>,
multi_cfg: MultiCfg,
graphic_definition: GraphicTable<G>,
}
#[derive(Clone)]
pub struct Dms<const C: usize, const F: usize, const G: usize> {
pub(crate) sign_cfg: SignCfg,
pub(crate) vms_cfg: VmsCfg,
pub(crate) font_definition: FontTable<C, F>,
pub(crate) multi_cfg: MultiCfg,
pub(crate) graphic_definition: GraphicTable<G>,
}
impl<const C: usize, const F: usize, const G: usize> DmsBuilder<C, F, G> {
pub fn with_sign_cfg(mut self, cfg: SignCfg) -> Self {
self.sign_cfg = cfg;
self
}
pub fn with_vms_cfg(mut self, cfg: VmsCfg) -> Self {
self.vms_cfg = cfg;
self
}
pub fn with_font_definition(mut self, fonts: FontTable<C, F>) -> Self {
self.font_definition = fonts;
self
}
pub fn with_multi_cfg(mut self, cfg: MultiCfg) -> Self {
self.multi_cfg = cfg;
self
}
pub fn with_graphic_definition(
mut self,
graphics: GraphicTable<{ G }>,
) -> Self {
self.graphic_definition = graphics;
self
}
pub fn build(mut self) -> Result<Dms<C, F, G>, SignError> {
self.sign_cfg.validate(&self.vms_cfg)?;
self.multi_cfg.validate()?;
self.font_definition.validate()?;
let width = self.vms_cfg.sign_width_pixels;
let height = self.vms_cfg.sign_height_pixels;
self.graphic_definition.retain(width, height);
self.graphic_definition.validate(width, height)?;
Ok(Dms {
sign_cfg: self.sign_cfg,
vms_cfg: self.vms_cfg,
font_definition: self.font_definition,
multi_cfg: self.multi_cfg,
graphic_definition: self.graphic_definition,
})
}
}
impl<const C: usize, const F: usize, const G: usize> Dms<C, F, G> {
pub fn builder() -> DmsBuilder<C, F, G> {
DmsBuilder::default()
}
pub fn into_builder(self) -> DmsBuilder<C, F, G> {
DmsBuilder {
sign_cfg: self.sign_cfg,
vms_cfg: self.vms_cfg,
font_definition: self.font_definition,
multi_cfg: self.multi_cfg,
graphic_definition: self.graphic_definition,
}
}
pub fn font_definition(&self) -> &FontTable<C, F> {
&self.font_definition
}
pub fn graphic_definition(&self) -> &GraphicTable<{ G }> {
&self.graphic_definition
}
pub fn face_width_mm(&self) -> f32 {
self.sign_cfg.sign_width as f32
}
pub fn pixel_width(&self) -> u16 {
self.vms_cfg.sign_width_pixels
}
fn border_horiz_mm(&self) -> f32 {
let pix = self.pixel_width() + self.gap_char_count();
let min_width = (pix as f32) * self.pitch_horiz_mm();
let extra = (self.face_width_mm() - min_width).max(0.0);
if self.gap_char_count() > 0 {
let border = self.sign_cfg.horizontal_border as f32;
border.min(extra / 2.0)
} else {
extra / 2.0
}
}
fn pixel_width_mm(&self) -> f32 {
self.face_width_mm() - self.border_horiz_mm() * 2.0
}
fn pitch_horiz(&self) -> u8 {
self.vms_cfg.horizontal_pitch
}
fn pitch_horiz_mm(&self) -> f32 {
let pitch = self.pitch_horiz() as f32;
let pix = self.pixel_width() + self.gap_char_count();
if pix > 0 {
pitch.min(self.face_width_mm() / pix as f32)
} else {
pitch
}
}
fn gap_char_count(&self) -> u16 {
let cw = self.char_width().into();
if cw > 1 && self.pixel_width() > cw {
(self.pixel_width() - 1) / cw
} else {
0
}
}
pub fn pixel_x(&self, x: u32, width: u32) -> f32 {
let border = self.border_horiz_mm();
let offset = self.char_offset_mm(x);
let x = x as f32 + 0.5; let pos = border + offset + x * self.pitch_horiz_mm();
width as f32 * pos / self.face_width_mm()
}
fn char_offset_mm(&self, x: u32) -> f32 {
let cw = u32::from(self.char_width());
if cw > 1 {
let char_num = (x / cw) as f32;
char_num * self.gap_char_mm()
} else {
0.0
}
}
fn gap_char_mm(&self) -> f32 {
let gaps = self.gap_char_count();
if gaps > 0 {
self.excess_char_mm() / gaps as f32
} else {
0.0
}
}
fn excess_char_mm(&self) -> f32 {
let pix_mm = self.pitch_horiz_mm() * self.pixel_width() as f32;
(self.pixel_width_mm() - pix_mm).max(0.0)
}
pub fn face_height_mm(&self) -> f32 {
self.sign_cfg.sign_height as f32
}
pub fn pixel_height(&self) -> u16 {
self.vms_cfg.sign_height_pixels
}
fn border_vert_mm(&self) -> f32 {
let pix = self.pixel_height() + self.gap_line_count();
let min_height = (pix as f32) * self.pitch_vert_mm();
let extra = (self.face_height_mm() - min_height).max(0.0);
if self.gap_line_count() > 0 {
let border = self.sign_cfg.vertical_border as f32;
border.min(extra / 2.0)
} else {
extra / 2.0
}
}
fn pixel_height_mm(&self) -> f32 {
self.face_height_mm() - self.border_vert_mm() * 2.0
}
fn pitch_vert(&self) -> u8 {
self.vms_cfg.vertical_pitch
}
fn pitch_vert_mm(&self) -> f32 {
let pitch = self.pitch_vert() as f32;
let pix = self.pixel_height() + self.gap_line_count();
if pix > 0 {
pitch.min(self.face_height_mm() / pix as f32)
} else {
pitch
}
}
fn gap_line_count(&self) -> u16 {
let ch = self.char_height().into();
if ch > 1 && self.pixel_height() > ch {
(self.pixel_height() - 1) / ch
} else {
0
}
}
pub fn pixel_y(&self, y: u32, height: u32) -> f32 {
let border = self.border_vert_mm();
let offset = self.line_offset_mm(y);
let y = y as f32 + 0.5; let pos = border + offset + y * self.pitch_vert_mm();
height as f32 * pos / self.face_height_mm()
}
fn line_offset_mm(&self, y: u32) -> f32 {
let ch = u32::from(self.char_height());
if ch > 1 {
let line_num = (y / ch) as f32;
line_num * self.gap_line_mm()
} else {
0.0
}
}
fn gap_line_mm(&self) -> f32 {
let gaps = self.gap_line_count();
if gaps > 0 {
self.excess_line_mm() / gaps as f32
} else {
0.0
}
}
fn excess_line_mm(&self) -> f32 {
let pix_mm = self.pitch_vert_mm() * self.pixel_height() as f32;
(self.pixel_height_mm() - pix_mm).max(0.0)
}
pub(crate) fn char_width(&self) -> u8 {
self.vms_cfg.char_width_pixels
}
pub(crate) fn char_height(&self) -> u8 {
self.vms_cfg.char_height_pixels
}
fn color_scheme(&self) -> ColorScheme {
self.multi_cfg.color_scheme
}
fn foreground_default_rgb(&self) -> (u8, u8, u8) {
match self.color_scheme() {
ColorScheme::ColorClassic | ColorScheme::Color24Bit => {
self.multi_cfg.default_foreground_rgb.rgb()
}
_ => {
let mono = &self.vms_cfg.monochrome_color;
(mono[0], mono[1], mono[2])
}
}
}
fn background_default_rgb(&self) -> (u8, u8, u8) {
match self.color_scheme() {
ColorScheme::ColorClassic | ColorScheme::Color24Bit => {
self.multi_cfg.default_background_rgb.rgb()
}
_ => {
let mono = &self.vms_cfg.monochrome_color;
(mono[3], mono[4], mono[5])
}
}
}
pub(crate) fn color_ctx(&self) -> ColorCtx {
let color_scheme = self.color_scheme();
let fg_default = self.foreground_default_rgb();
let bg_default = self.background_default_rgb();
ColorCtx::new(color_scheme, fg_default, bg_default)
}
pub(crate) fn full_rect(&self) -> Rectangle {
Rectangle::new(1, 1, self.pixel_width(), self.pixel_height())
}
pub(crate) fn rect_lines(&self, rect: Rectangle, font_num: u8) -> usize {
let mut n_lines = 0;
if let Some(font) = self.font_definition().font(font_num) {
let font_height = usize::from(font.height);
let char_height = usize::from(self.char_height());
let (line_height, line_spacing) = if char_height == 0 {
(font_height, usize::from(font.line_spacing))
} else {
if font_height > char_height {
return 0;
}
(char_height, 0)
};
while line_height * (n_lines + 1) + line_spacing * n_lines
<= usize::from(rect.height)
{
n_lines += 1;
}
}
n_lines
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn dms_size() {
assert_eq!(std::mem::size_of::<Dms<128, 16, 32>>(), 70_072);
assert_eq!(std::mem::size_of::<Dms<256, 24, 32>>(), 201_720);
}
}