#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PaperStock {
pub name: &'static str,
pub thickness_mm: f32,
}
impl PaperStock {
pub const fn custom(name: &'static str, thickness_mm: f32) -> Self {
Self { name, thickness_mm }
}
pub fn binding_compensation_mm(&self) -> f32 {
1.0
}
}
const STOCKS: &[PaperStock] = &[
PaperStock::custom("bible_70gsm", 0.060),
PaperStock::custom("uncoated_70gsm", 0.085),
PaperStock::custom("uncoated_80gsm", 0.100),
PaperStock::custom("uncoated_90gsm", 0.115),
PaperStock::custom("uncoated_100gsm", 0.130),
PaperStock::custom("uncoated_120gsm", 0.160),
PaperStock::custom("coated_matte_80gsm", 0.080),
PaperStock::custom("coated_matte_100gsm", 0.105),
PaperStock::custom("coated_gloss_100gsm", 0.100),
PaperStock::custom("cover_250gsm", 0.300),
PaperStock::custom("cover_300gsm", 0.360),
];
pub const DEFAULT_INTERIOR: &str = "uncoated_80gsm";
pub const DEFAULT_COVER: &str = "cover_250gsm";
pub fn paper_stock(name: &str) -> Option<PaperStock> {
let n = name.trim().to_ascii_lowercase();
STOCKS.iter().copied().find(|s| s.name == n)
}
pub fn stock_names() -> impl Iterator<Item = &'static str> {
STOCKS.iter().map(|s| s.name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lookup_is_case_insensitive_and_trims() {
assert_eq!(paper_stock("uncoated_80gsm").unwrap().thickness_mm, 0.100);
assert_eq!(paper_stock(" Uncoated_80GSM ").unwrap().thickness_mm, 0.100);
assert!(paper_stock("papyrus").is_none());
}
#[test]
fn defaults_exist_and_are_ordered() {
let interior = paper_stock(DEFAULT_INTERIOR).unwrap();
let cover = paper_stock(DEFAULT_COVER).unwrap();
assert!(cover.thickness_mm > interior.thickness_mm * 2.0);
assert!(cover.binding_compensation_mm() > 0.0);
}
#[test]
fn preset_names_are_unique() {
let mut names: Vec<&str> = stock_names().collect();
let n = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), n, "duplicate stock name in the preset table");
}
#[test]
fn custom_override() {
let s = PaperStock::custom("exotic", 0.42);
assert_eq!(s.thickness_mm, 0.42);
}
}