use std::num::NonZeroU32;
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum ResizingDimension {
#[default]
Width,
Height,
}
impl ResizingDimension {
pub fn calculate_dimensions(
target_size: u32,
height: u32,
width: u32,
scale: f32,
border: bool,
dimension: ResizingDimension,
) -> (u32, u32, u32, u32) {
match dimension {
ResizingDimension::Width => {
let mut columns = if width > target_size {
target_size
} else {
width
};
if border {
columns = columns.saturating_sub(2).max(1); }
let tile_width = width / columns;
let tile_height = (tile_width as f32 / scale).floor() as u32;
let rows = height / tile_height;
(columns.max(1), rows.max(1), tile_width, tile_height)
}
ResizingDimension::Height => {
let mut rows = if height > target_size {
target_size - 1
} else {
height
};
let tile_height = height / rows;
let tile_width = (tile_height as f32 * scale).ceil() as u32;
let mut columns = width / tile_width;
if border {
columns = columns.saturating_sub(2);
rows = rows.saturating_sub(2);
}
(columns.max(1), rows.max(1), tile_width, tile_height)
}
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TargetType {
#[default]
Shell,
AnsiFile,
HtmlFile,
File,
Svg,
}
impl TargetType {
pub(crate) fn supports_color(&self) -> bool {
self != &TargetType::File
}
pub(crate) fn supports_background_colors(&self) -> bool {
match self {
TargetType::Shell | TargetType::HtmlFile | TargetType::Svg => true,
TargetType::AnsiFile | TargetType::File => false,
}
}
}
#[derive(Debug, PartialEq)]
pub struct Config {
pub characters: String,
pub scale: f32,
pub target_size: u32,
pub color: bool,
pub background_color: bool,
pub invert: bool,
pub border: bool,
pub dimension: ResizingDimension,
pub transform_x: bool,
pub transform_y: bool,
pub center_x: bool,
pub center_y: bool,
pub outline: bool,
pub hysteresis: bool,
pub target: TargetType,
}
impl Config {
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
pub(crate) fn color(&self) -> bool {
self.color && self.target.supports_color()
}
pub(crate) fn background_color(&self) -> bool {
self.background_color && self.target.supports_background_colors()
}
}
impl Default for Config {
fn default() -> Self {
Self {
characters: r#"MWNXK0Okxdolc:;,'... "#.to_string(),
scale: 0.42f32,
target_size: 80,
color: true,
background_color: false,
invert: Default::default(),
border: Default::default(),
dimension: Default::default(),
transform_x: Default::default(),
transform_y: Default::default(),
center_x: Default::default(),
center_y: Default::default(),
outline: Default::default(),
hysteresis: Default::default(),
target: Default::default(),
}
}
}
#[cfg(test)]
mod test_option {
use super::*;
#[test]
fn builder_default() {
assert_eq!(
ConfigBuilder {
characters: r#"MWNXK0Okxdolc:;,'... "#.to_string(),
scale: 0.42f32,
target_size: 80,
color: true,
background_color: false,
invert: false,
border: false,
dimension: ResizingDimension::Width,
transform_x: false,
transform_y: false,
center_x: false,
center_y: false,
outline: false,
hysteresis: false,
target: TargetType::default(),
},
Config::builder()
);
}
}
#[derive(PartialEq, Debug)]
pub struct ConfigBuilder {
characters: String,
scale: f32,
target_size: u32,
color: bool,
background_color: bool,
invert: bool,
border: bool,
dimension: ResizingDimension,
transform_x: bool,
transform_y: bool,
center_x: bool,
center_y: bool,
outline: bool,
hysteresis: bool,
target: TargetType,
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self {
characters: r#"MWNXK0Okxdolc:;,'... "#.to_string(),
scale: 0.42f32,
target_size: 80,
color: true,
background_color: false,
invert: Default::default(),
border: Default::default(),
dimension: Default::default(),
transform_x: Default::default(),
transform_y: Default::default(),
center_x: Default::default(),
center_y: Default::default(),
outline: Default::default(),
hysteresis: Default::default(),
target: Default::default(),
}
}
}
macro_rules! property {
($(#[$attr:meta])* => $field:ident, $field_type:ty) => {
$(#[$attr])*
pub fn $field(&mut self, $field: $field_type) -> &mut Self {
self.$field = $field;
self
}
};
($(#[$attr:meta])* => $field:ident, $field_type:ty, $func:ident) => {
$(#[$attr])*
pub fn $field(&mut self, $field: $field_type) -> &mut Self {
self.$field = $field.$func();
self
}
};
}
impl ConfigBuilder {
pub fn new() -> ConfigBuilder {
Config::builder()
}
pub fn characters(&mut self, characters: String) -> &mut Self {
if !characters.is_empty() {
self.characters = characters;
}
self
}
property! {
=> scale, f32
}
property! {
=> target_size, NonZeroU32, get
}
property! {
=> color, bool
}
property! {
=> background_color, bool
}
property! {
=> invert, bool
}
property! {
=> border, bool
}
property! {
=> dimension, ResizingDimension
}
property! {
=> transform_x, bool
}
property! {
=> transform_y, bool
}
property! {
=> center_x, bool
}
property! {
=> center_y, bool
}
property! {
=> outline, bool
}
property! {
=> hysteresis, bool
}
property! {
=> target, TargetType
}
pub fn build(&self) -> Config {
Config {
characters: self.characters.to_owned(),
scale: self.scale,
target_size: self.target_size,
color: self.color,
background_color: self.background_color,
invert: self.invert,
border: self.border,
dimension: self.dimension,
transform_x: self.transform_x,
transform_y: self.transform_y,
center_x: self.center_x,
center_y: self.center_y,
outline: self.outline,
hysteresis: self.hysteresis,
target: self.target,
}
}
}
#[cfg(test)]
mod test_conversion_configuration_builder {
use super::*;
#[test]
fn build_default() {
assert_eq!(
Config {
characters: r#"MWNXK0Okxdolc:;,'... "#.to_string(),
scale: 0.42f32,
target_size: 80,
color: true,
background_color: false,
invert: false,
border: false,
dimension: ResizingDimension::Width,
transform_x: false,
transform_y: false,
center_x: false,
center_y: false,
outline: false,
hysteresis: false,
target: TargetType::default(),
},
ConfigBuilder::new().build()
);
}
}
#[cfg(test)]
mod test_calculate_dimensions {
use super::*;
#[test]
fn calculate_dimensions_width() {
assert_eq!(
(100, 46, 5, 11),
ResizingDimension::calculate_dimensions(
100,
512,
512,
0.42,
false,
ResizingDimension::Width
)
);
}
#[test]
fn calculate_dimensions_width_119() {
assert_eq!(
(119, 56, 4, 9),
ResizingDimension::calculate_dimensions(
119,
512,
512,
0.42,
false,
ResizingDimension::Width
)
);
}
#[test]
fn calculate_dimensions_height() {
assert_eq!(
(170, 99, 3, 5),
ResizingDimension::calculate_dimensions(
100,
512,
512,
0.42,
false,
ResizingDimension::Height
)
);
}
#[test]
fn calculate_dimensions_height_1x1_img() {
assert_eq!(
(1, 1, 1, 1),
ResizingDimension::calculate_dimensions(
100,
1,
1,
0.42,
false,
ResizingDimension::Height
)
);
}
#[test]
fn calculate_dimensions_width_1x1_img() {
assert_eq!(
(1, 1, 1, 2),
ResizingDimension::calculate_dimensions(
100,
1,
1,
0.42,
false,
ResizingDimension::Width
)
);
}
#[test]
#[should_panic]
fn calculate_dimensions_height_zero() {
ResizingDimension::calculate_dimensions(
0,
512,
512,
0.42,
false,
ResizingDimension::Height,
);
}
#[test]
#[should_panic]
fn calculate_dimensions_width_zero() {
ResizingDimension::calculate_dimensions(0, 512, 512, 0.42, false, ResizingDimension::Width);
}
#[test]
#[should_panic]
fn calculate_dimensions_img_width_zero() {
ResizingDimension::calculate_dimensions(100, 512, 0, 0.42, false, ResizingDimension::Width);
}
#[test]
#[should_panic]
fn calculate_dimensions_img_height_zero() {
ResizingDimension::calculate_dimensions(
100,
0,
512,
0.42,
false,
ResizingDimension::Height,
);
}
#[test]
#[should_panic]
fn calculate_dimensions_img_width_height_zero() {
ResizingDimension::calculate_dimensions(100, 0, 0, 0.42, false, ResizingDimension::Height);
}
#[test]
fn calculate_dimensions_scale_zero() {
assert_eq!(
(100, 1, 5, 4294967295),
ResizingDimension::calculate_dimensions(
100,
512,
512,
0f32,
false,
ResizingDimension::Width
)
);
}
#[test]
fn calculate_border_smaller_columns() {
assert_eq!(
(98, 1, 5, 4294967295),
ResizingDimension::calculate_dimensions(
100,
512,
512,
0f32,
true,
ResizingDimension::Width
)
);
}
}