use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TypographyConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line_numbering: Option<LineNumbering>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub baseline_grid: Option<BaselineGrid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hyphenation: Option<HyphenationConfig>,
}
impl TypographyConfig {
#[must_use]
pub fn new() -> Self {
Self {
line_numbering: None,
baseline_grid: None,
hyphenation: None,
}
}
#[must_use]
pub fn with_line_numbering(mut self, ln: LineNumbering) -> Self {
self.line_numbering = Some(ln);
self
}
#[must_use]
pub fn with_baseline_grid(mut self, bg: BaselineGrid) -> Self {
self.baseline_grid = Some(bg);
self
}
#[must_use]
pub fn with_hyphenation(mut self, h: HyphenationConfig) -> Self {
self.hyphenation = Some(h);
self
}
}
impl Default for TypographyConfig {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LineNumbering {
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub start: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interval: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub side: Option<LineNumberingSide>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub restart: Option<LineNumberingRestart>,
}
impl LineNumbering {
#[must_use]
pub fn enabled() -> Self {
Self {
enabled: true,
start: None,
interval: None,
side: None,
restart: None,
}
}
#[must_use]
pub const fn with_start(mut self, start: u32) -> Self {
self.start = Some(start);
self
}
#[must_use]
pub const fn with_interval(mut self, interval: u32) -> Self {
self.interval = Some(interval);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LineNumberingSide {
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LineNumberingRestart {
Page,
Section,
#[serde(rename = "none")]
NoRestart,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BaselineGrid {
pub enabled: bool,
pub line_height: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<String>,
}
impl BaselineGrid {
#[must_use]
pub fn new(line_height: impl Into<String>) -> Self {
Self {
enabled: true,
line_height: line_height.into(),
offset: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HyphenationConfig {
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_word_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_before: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_after: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_consecutive: Option<u32>,
}
impl HyphenationConfig {
#[must_use]
pub fn enabled() -> Self {
Self {
enabled: true,
language: None,
min_word_length: None,
min_before: None,
min_after: None,
max_consecutive: None,
}
}
#[must_use]
pub fn with_language(mut self, lang: impl Into<String>) -> Self {
self.language = Some(lang.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_typography_config_serde() {
let config = TypographyConfig::new()
.with_line_numbering(LineNumbering::enabled().with_interval(5))
.with_hyphenation(HyphenationConfig::enabled().with_language("en-US"));
let json = serde_json::to_string_pretty(&config).unwrap();
let parsed: TypographyConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, config);
}
#[test]
fn test_line_numbering_serde() {
let ln = LineNumbering::enabled().with_start(10).with_interval(5);
let json = serde_json::to_string(&ln).unwrap();
assert!(json.contains("\"start\":10"));
assert!(json.contains("\"interval\":5"));
let parsed: LineNumbering = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, ln);
}
#[test]
fn test_line_numbering_side_serde() {
let side = LineNumberingSide::Left;
let json = serde_json::to_string(&side).unwrap();
assert_eq!(json, "\"left\"");
let right: LineNumberingSide = serde_json::from_str("\"right\"").unwrap();
assert_eq!(right, LineNumberingSide::Right);
}
#[test]
fn test_line_numbering_restart_serde() {
let restart = LineNumberingRestart::NoRestart;
let json = serde_json::to_string(&restart).unwrap();
assert_eq!(json, "\"none\"");
let page: LineNumberingRestart = serde_json::from_str("\"page\"").unwrap();
assert_eq!(page, LineNumberingRestart::Page);
}
#[test]
fn test_baseline_grid_serde() {
let grid = BaselineGrid::new("14pt");
let json = serde_json::to_string(&grid).unwrap();
assert!(json.contains("\"lineHeight\":\"14pt\""));
let parsed: BaselineGrid = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, grid);
}
#[test]
fn test_hyphenation_serde() {
let hyph = HyphenationConfig::enabled().with_language("de");
let json = serde_json::to_string(&hyph).unwrap();
assert!(json.contains("\"language\":\"de\""));
let parsed: HyphenationConfig = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, hyph);
}
#[test]
fn test_typography_defaults() {
let json = "{}";
let config: TypographyConfig = serde_json::from_str(json).unwrap();
assert!(config.line_numbering.is_none());
assert!(config.baseline_grid.is_none());
assert!(config.hyphenation.is_none());
}
}