use hwpforge_foundation::HwpUnit;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[non_exhaustive]
pub enum ColumnType {
#[default]
Newspaper,
Parallel,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[non_exhaustive]
pub enum ColumnLayoutMode {
#[default]
Left,
Right,
Mirror,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ColumnDef {
pub width: HwpUnit,
pub gap: HwpUnit,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ColumnSettings {
pub column_type: ColumnType,
pub layout_mode: ColumnLayoutMode,
pub columns: Vec<ColumnDef>,
}
impl ColumnSettings {
pub fn equal_columns(count: u32, gap: HwpUnit) -> Result<Self, &'static str> {
if count < 2 {
return Err("column count must be >= 2 (use None for single column)");
}
let columns: Vec<ColumnDef> = (0..count)
.map(|i| ColumnDef {
width: HwpUnit::ZERO, gap: if i < count - 1 { gap } else { HwpUnit::ZERO },
})
.collect();
Ok(Self {
column_type: ColumnType::Newspaper,
layout_mode: ColumnLayoutMode::Left,
columns,
})
}
pub fn custom(columns: Vec<ColumnDef>) -> Result<Self, &'static str> {
if columns.len() < 2 {
return Err("column count must be >= 2 (use None for single column)");
}
Ok(Self {
column_type: ColumnType::Newspaper,
layout_mode: ColumnLayoutMode::Left,
columns,
})
}
pub fn count(&self) -> usize {
self.columns.len()
}
pub fn is_equal_width(&self) -> bool {
if self.columns.is_empty() {
return true;
}
let first = self.columns[0].width;
self.columns.iter().all(|c| c.width == first)
}
}
impl std::fmt::Display for ColumnSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ColumnSettings({} columns, {:?})", self.columns.len(), self.column_type)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equal_columns_2() {
let gap = HwpUnit::new(1134).unwrap();
let cs = ColumnSettings::equal_columns(2, gap).unwrap();
assert_eq!(cs.count(), 2);
assert_eq!(cs.column_type, ColumnType::Newspaper);
assert_eq!(cs.layout_mode, ColumnLayoutMode::Left);
assert_eq!(cs.columns[0].gap, gap);
assert_eq!(cs.columns[1].gap, HwpUnit::ZERO);
assert!(cs.is_equal_width());
}
#[test]
fn equal_columns_3() {
let gap = HwpUnit::new(1134).unwrap();
let cs = ColumnSettings::equal_columns(3, gap).unwrap();
assert_eq!(cs.count(), 3);
assert_eq!(cs.columns[0].gap, gap);
assert_eq!(cs.columns[1].gap, gap);
assert_eq!(cs.columns[2].gap, HwpUnit::ZERO);
}
#[test]
fn equal_columns_returns_error_on_1() {
let result = ColumnSettings::equal_columns(1, HwpUnit::ZERO);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "column count must be >= 2 (use None for single column)");
}
#[test]
fn equal_columns_returns_error_on_0() {
let result = ColumnSettings::equal_columns(0, HwpUnit::ZERO);
assert!(result.is_err());
}
#[test]
fn custom_columns() {
let cs = ColumnSettings::custom(vec![
ColumnDef { width: HwpUnit::new(14000).unwrap(), gap: HwpUnit::new(1134).unwrap() },
ColumnDef { width: HwpUnit::new(27000).unwrap(), gap: HwpUnit::ZERO },
])
.unwrap();
assert_eq!(cs.count(), 2);
assert!(!cs.is_equal_width());
assert_eq!(cs.columns[0].width.as_i32(), 14000);
assert_eq!(cs.columns[1].width.as_i32(), 27000);
}
#[test]
fn custom_returns_error_on_1() {
let result =
ColumnSettings::custom(vec![ColumnDef { width: HwpUnit::ZERO, gap: HwpUnit::ZERO }]);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "column count must be >= 2 (use None for single column)");
}
#[test]
fn serde_roundtrip() {
let cs = ColumnSettings::equal_columns(2, HwpUnit::new(1134).unwrap()).unwrap();
let json = serde_json::to_string(&cs).unwrap();
let back: ColumnSettings = serde_json::from_str(&json).unwrap();
assert_eq!(cs, back);
}
#[test]
fn serde_roundtrip_custom() {
let cs = ColumnSettings::custom(vec![
ColumnDef { width: HwpUnit::new(14000).unwrap(), gap: HwpUnit::new(1134).unwrap() },
ColumnDef { width: HwpUnit::new(27000).unwrap(), gap: HwpUnit::ZERO },
])
.unwrap();
let json = serde_json::to_string(&cs).unwrap();
let back: ColumnSettings = serde_json::from_str(&json).unwrap();
assert_eq!(cs, back);
}
#[test]
fn display() {
let cs = ColumnSettings::equal_columns(2, HwpUnit::new(1134).unwrap()).unwrap();
let s = cs.to_string();
assert!(s.contains("2 columns"), "display: {s}");
assert!(s.contains("Newspaper"), "display: {s}");
}
#[test]
fn default_types() {
assert_eq!(ColumnType::default(), ColumnType::Newspaper);
assert_eq!(ColumnLayoutMode::default(), ColumnLayoutMode::Left);
}
#[test]
fn parallel_type() {
let mut cs = ColumnSettings::equal_columns(2, HwpUnit::ZERO).unwrap();
cs.column_type = ColumnType::Parallel;
assert_eq!(cs.column_type, ColumnType::Parallel);
}
#[test]
fn mirror_layout() {
let mut cs = ColumnSettings::equal_columns(2, HwpUnit::ZERO).unwrap();
cs.layout_mode = ColumnLayoutMode::Mirror;
assert_eq!(cs.layout_mode, ColumnLayoutMode::Mirror);
}
#[test]
fn is_equal_width_with_zero_widths() {
let cs = ColumnSettings::equal_columns(3, HwpUnit::new(1134).unwrap()).unwrap();
assert!(cs.is_equal_width());
}
#[test]
fn clone_independence() {
let cs = ColumnSettings::equal_columns(2, HwpUnit::new(1134).unwrap()).unwrap();
let mut cloned = cs.clone();
cloned.column_type = ColumnType::Parallel;
assert_eq!(cs.column_type, ColumnType::Newspaper);
assert_eq!(cloned.column_type, ColumnType::Parallel);
}
#[test]
fn column_settings_serde_roundtrip() {
let cs = ColumnSettings::equal_columns(2, HwpUnit::new(1134).unwrap()).unwrap();
let json = serde_json::to_string(&cs).unwrap();
let back: ColumnSettings = serde_json::from_str(&json).unwrap();
assert_eq!(cs, back);
}
}