use std::borrow::Cow;
use hwpforge_foundation::HwpUnit;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ObjectPlacement {
pub text_wrap: ObjectTextWrap,
pub text_flow: ObjectTextFlow,
pub treat_as_char: bool,
pub flow_with_text: bool,
pub allow_overlap: bool,
pub vert_rel_to: ObjectRelativeTo,
pub horz_rel_to: ObjectRelativeTo,
pub vert_offset: HwpUnit,
pub horz_offset: HwpUnit,
}
impl ObjectPlacement {
#[must_use]
pub fn legacy_inline_defaults() -> Self {
Self {
text_wrap: ObjectTextWrap::TopAndBottom,
text_flow: ObjectTextFlow::BothSides,
treat_as_char: true,
flow_with_text: false,
allow_overlap: false,
vert_rel_to: ObjectRelativeTo::Para,
horz_rel_to: ObjectRelativeTo::Para,
vert_offset: HwpUnit::ZERO,
horz_offset: HwpUnit::ZERO,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[non_exhaustive]
pub enum ObjectTextWrap {
TopAndBottom,
Square,
BehindText,
InFrontOfText,
Tight,
Through,
Other(String),
}
impl ObjectTextWrap {
pub fn from_hwpx(value: &str) -> Self {
match value {
"TOP_AND_BOTTOM" => Self::TopAndBottom,
"SQUARE" => Self::Square,
"BEHIND_TEXT" => Self::BehindText,
"IN_FRONT_OF_TEXT" => Self::InFrontOfText,
"TIGHT" => Self::Tight,
"THROUGH" => Self::Through,
other => Self::Other(other.to_string()),
}
}
pub fn as_hwpx_str(&self) -> Cow<'_, str> {
match self {
Self::TopAndBottom => Cow::Borrowed("TOP_AND_BOTTOM"),
Self::Square => Cow::Borrowed("SQUARE"),
Self::BehindText => Cow::Borrowed("BEHIND_TEXT"),
Self::InFrontOfText => Cow::Borrowed("IN_FRONT_OF_TEXT"),
Self::Tight => Cow::Borrowed("TIGHT"),
Self::Through => Cow::Borrowed("THROUGH"),
Self::Other(value) => Cow::Borrowed(value.as_str()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[non_exhaustive]
pub enum ObjectTextFlow {
BothSides,
LeftOnly,
RightOnly,
LargestOnly,
Other(String),
}
impl ObjectTextFlow {
pub fn from_hwpx(value: &str) -> Self {
match value {
"BOTH_SIDES" => Self::BothSides,
"LEFT_ONLY" => Self::LeftOnly,
"RIGHT_ONLY" => Self::RightOnly,
"LARGEST_ONLY" => Self::LargestOnly,
other => Self::Other(other.to_string()),
}
}
pub fn as_hwpx_str(&self) -> Cow<'_, str> {
match self {
Self::BothSides => Cow::Borrowed("BOTH_SIDES"),
Self::LeftOnly => Cow::Borrowed("LEFT_ONLY"),
Self::RightOnly => Cow::Borrowed("RIGHT_ONLY"),
Self::LargestOnly => Cow::Borrowed("LARGEST_ONLY"),
Self::Other(value) => Cow::Borrowed(value.as_str()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[non_exhaustive]
pub enum ObjectRelativeTo {
Paper,
Page,
Para,
Column,
Character,
Line,
Other(String),
}
impl ObjectRelativeTo {
pub fn from_hwpx(value: &str) -> Self {
match value {
"PAPER" => Self::Paper,
"PAGE" => Self::Page,
"PARA" => Self::Para,
"COLUMN" => Self::Column,
"CHAR" => Self::Character,
"LINE" => Self::Line,
other => Self::Other(other.to_string()),
}
}
pub fn as_hwpx_str(&self) -> Cow<'_, str> {
match self {
Self::Paper => Cow::Borrowed("PAPER"),
Self::Page => Cow::Borrowed("PAGE"),
Self::Para => Cow::Borrowed("PARA"),
Self::Column => Cow::Borrowed("COLUMN"),
Self::Character => Cow::Borrowed("CHAR"),
Self::Line => Cow::Borrowed("LINE"),
Self::Other(value) => Cow::Borrowed(value.as_str()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn legacy_inline_defaults_are_inline() {
let p = ObjectPlacement::legacy_inline_defaults();
assert!(p.treat_as_char);
assert!(!p.flow_with_text);
assert!(!p.allow_overlap);
assert_eq!(p.text_wrap, ObjectTextWrap::TopAndBottom);
assert_eq!(p.text_flow, ObjectTextFlow::BothSides);
assert_eq!(p.vert_rel_to, ObjectRelativeTo::Para);
assert_eq!(p.horz_rel_to, ObjectRelativeTo::Para);
assert_eq!(p.vert_offset, HwpUnit::ZERO);
assert_eq!(p.horz_offset, HwpUnit::ZERO);
}
#[test]
fn placement_serde_roundtrip() {
let p = ObjectPlacement {
text_wrap: ObjectTextWrap::Square,
text_flow: ObjectTextFlow::RightOnly,
treat_as_char: false,
flow_with_text: true,
allow_overlap: true,
vert_rel_to: ObjectRelativeTo::Paper,
horz_rel_to: ObjectRelativeTo::Page,
vert_offset: HwpUnit::new(1200).unwrap(),
horz_offset: HwpUnit::new(3400).unwrap(),
};
let json = serde_json::to_string(&p).unwrap();
let back: ObjectPlacement = serde_json::from_str(&json).unwrap();
assert_eq!(p, back);
}
#[test]
fn text_wrap_hwpx_roundtrip() {
for (s, v) in [
("TOP_AND_BOTTOM", ObjectTextWrap::TopAndBottom),
("SQUARE", ObjectTextWrap::Square),
("BEHIND_TEXT", ObjectTextWrap::BehindText),
("IN_FRONT_OF_TEXT", ObjectTextWrap::InFrontOfText),
("TIGHT", ObjectTextWrap::Tight),
("THROUGH", ObjectTextWrap::Through),
] {
assert_eq!(ObjectTextWrap::from_hwpx(s), v);
assert_eq!(v.as_hwpx_str(), s);
}
assert_eq!(ObjectTextWrap::from_hwpx("WEIRD"), ObjectTextWrap::Other("WEIRD".to_string()));
assert_eq!(ObjectTextWrap::Other("WEIRD".to_string()).as_hwpx_str(), "WEIRD");
}
#[test]
fn text_flow_hwpx_roundtrip() {
for (s, v) in [
("BOTH_SIDES", ObjectTextFlow::BothSides),
("LEFT_ONLY", ObjectTextFlow::LeftOnly),
("RIGHT_ONLY", ObjectTextFlow::RightOnly),
("LARGEST_ONLY", ObjectTextFlow::LargestOnly),
] {
assert_eq!(ObjectTextFlow::from_hwpx(s), v);
assert_eq!(v.as_hwpx_str(), s);
}
assert_eq!(ObjectTextFlow::from_hwpx("X"), ObjectTextFlow::Other("X".to_string()));
assert_eq!(ObjectTextFlow::Other("X".to_string()).as_hwpx_str(), "X");
}
#[test]
fn relative_to_hwpx_roundtrip() {
for (s, v) in [
("PAPER", ObjectRelativeTo::Paper),
("PAGE", ObjectRelativeTo::Page),
("PARA", ObjectRelativeTo::Para),
("COLUMN", ObjectRelativeTo::Column),
("CHAR", ObjectRelativeTo::Character),
("LINE", ObjectRelativeTo::Line),
] {
assert_eq!(ObjectRelativeTo::from_hwpx(s), v);
assert_eq!(v.as_hwpx_str(), s);
}
assert_eq!(ObjectRelativeTo::from_hwpx("Z"), ObjectRelativeTo::Other("Z".to_string()));
assert_eq!(ObjectRelativeTo::Other("Z".to_string()).as_hwpx_str(), "Z");
}
}