use std::borrow::Cow;
pub use color::{
AnyColor, Cmyk8, DeviceN8, Gray8, NCOMPS, Pixel, PixelMode, Rgb8, Rgba8, TransferLut,
convert::*,
};
pub const AA_SIZE: i32 = 4;
pub const MAX_CURVE_SPLITS: i32 = 1024;
pub const BEZIER_CIRCLE: f64 = 0.552_284_75;
pub const SPOT_NCOMPS: usize = 4;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum ThinLineMode {
#[default]
Default,
Solid,
Shape,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum LineCap {
#[default]
Butt,
Round,
Projecting,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum LineJoin {
#[default]
Miter,
Round,
Bevel,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum ScreenType {
#[default]
Dispersed,
Clustered,
StochasticClustered,
}
#[derive(Copy, Clone, Debug)]
pub struct ScreenParams {
pub kind: ScreenType,
pub size: i32,
pub dot_radius: i32,
}
impl Default for ScreenParams {
fn default() -> Self {
Self {
kind: ScreenType::Dispersed,
size: 2,
dot_radius: 2,
}
}
}
impl ScreenParams {
pub const fn validate(&self) -> Result<(), Cow<'static, str>> {
if self.size < 2 {
return Err(Cow::Borrowed("ScreenParams::size must be >= 2"));
}
#[expect(
clippy::cast_sign_loss,
reason = "size >= 2 has been asserted above; the value is positive, \
so the cast to u32 loses no bits"
)]
let size_u32 = self.size as u32;
if !size_u32.is_power_of_two() {
return Err(Cow::Borrowed("ScreenParams::size must be a power of two"));
}
if self.dot_radius < 1 {
return Err(Cow::Borrowed("ScreenParams::dot_radius must be >= 1"));
}
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum BlendMode {
#[default]
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn screen_params_default_matches_cpp() {
let p = ScreenParams::default();
assert_eq!(p.kind, ScreenType::Dispersed);
assert_eq!(p.size, 2);
assert_eq!(p.dot_radius, 2);
assert!(p.validate().is_ok());
}
#[test]
fn screen_params_validate_rejects_non_power_of_two() {
let p = ScreenParams {
size: 3,
..ScreenParams::default()
};
assert!(p.validate().is_err());
}
#[test]
fn screen_params_validate_rejects_size_less_than_2() {
let p = ScreenParams {
size: 1,
..ScreenParams::default()
};
assert!(p.validate().is_err());
let p = ScreenParams {
size: 0,
..ScreenParams::default()
};
assert!(p.validate().is_err());
let p = ScreenParams {
size: -1,
..ScreenParams::default()
};
assert!(p.validate().is_err());
}
#[test]
fn screen_params_validate_rejects_zero_dot_radius() {
let p = ScreenParams {
dot_radius: 0,
..ScreenParams::default()
};
assert!(p.validate().is_err());
}
#[test]
fn screen_params_validate_errors_are_borrowed() {
use std::borrow::Cow;
let size_small = ScreenParams {
size: 1,
..ScreenParams::default()
};
assert!(
matches!(size_small.validate(), Err(Cow::Borrowed(_))),
"size < 2 error should be Cow::Borrowed"
);
let size_non_pow2 = ScreenParams {
size: 3,
..ScreenParams::default()
};
assert!(
matches!(size_non_pow2.validate(), Err(Cow::Borrowed(_))),
"non-power-of-two error should be Cow::Borrowed"
);
let bad_radius = ScreenParams {
dot_radius: 0,
..ScreenParams::default()
};
assert!(
matches!(bad_radius.validate(), Err(Cow::Borrowed(_))),
"dot_radius < 1 error should be Cow::Borrowed"
);
}
#[test]
fn screen_params_validate_error_messages_mention_field() {
let size_small = ScreenParams {
size: 0,
..ScreenParams::default()
};
let msg = size_small.validate().unwrap_err();
assert!(
msg.contains("size"),
"error for small size should mention 'size', got: {msg}"
);
let size_non_pow2 = ScreenParams {
size: 3,
..ScreenParams::default()
};
let msg = size_non_pow2.validate().unwrap_err();
assert!(
msg.contains("size"),
"error for non-power-of-two should mention 'size', got: {msg}"
);
let bad_radius = ScreenParams {
dot_radius: 0,
..ScreenParams::default()
};
let msg = bad_radius.validate().unwrap_err();
assert!(
msg.contains("dot_radius"),
"error for bad radius should mention 'dot_radius', got: {msg}"
);
}
#[test]
fn screen_params_validate_accepts_valid_power_of_two_sizes() {
for exp in 1u32..=6 {
let size = 1i32 << exp; let p = ScreenParams {
size,
dot_radius: 1,
..ScreenParams::default()
};
assert!(p.validate().is_ok(), "size={size} should be valid");
}
}
#[test]
fn spot_ncomps_matches_cpp() {
assert_eq!(SPOT_NCOMPS, 4);
}
#[test]
fn aa_size_matches_cpp() {
assert_eq!(AA_SIZE, 4);
}
#[test]
fn max_curve_splits_matches_cpp() {
assert_eq!(MAX_CURVE_SPLITS, 1 << 10);
}
#[test]
fn bezier_circle_matches_cpp() {
assert!((BEZIER_CIRCLE - 0.552_284_75_f64).abs() < 1e-9);
}
}