1use std::borrow::Cow;
28
29use hwpforge_foundation::HwpUnit;
30use schemars::JsonSchema;
31use serde::{Deserialize, Serialize};
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
51pub struct ObjectPlacement {
52 pub text_wrap: ObjectTextWrap,
54 pub text_flow: ObjectTextFlow,
56 pub treat_as_char: bool,
58 pub flow_with_text: bool,
60 pub allow_overlap: bool,
62 pub vert_rel_to: ObjectRelativeTo,
64 pub horz_rel_to: ObjectRelativeTo,
66 pub vert_offset: HwpUnit,
68 pub horz_offset: HwpUnit,
70}
71
72impl ObjectPlacement {
73 #[must_use]
93 pub fn legacy_inline_defaults() -> Self {
94 Self {
95 text_wrap: ObjectTextWrap::TopAndBottom,
96 text_flow: ObjectTextFlow::BothSides,
97 treat_as_char: true,
98 flow_with_text: false,
99 allow_overlap: false,
100 vert_rel_to: ObjectRelativeTo::Para,
101 horz_rel_to: ObjectRelativeTo::Para,
102 vert_offset: HwpUnit::ZERO,
103 horz_offset: HwpUnit::ZERO,
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
110#[non_exhaustive]
111pub enum ObjectTextWrap {
112 TopAndBottom,
114 Square,
116 BehindText,
118 InFrontOfText,
120 Tight,
122 Through,
124 Other(String),
126}
127
128impl ObjectTextWrap {
129 pub fn from_hwpx(value: &str) -> Self {
131 match value {
132 "TOP_AND_BOTTOM" => Self::TopAndBottom,
133 "SQUARE" => Self::Square,
134 "BEHIND_TEXT" => Self::BehindText,
135 "IN_FRONT_OF_TEXT" => Self::InFrontOfText,
136 "TIGHT" => Self::Tight,
137 "THROUGH" => Self::Through,
138 other => Self::Other(other.to_string()),
139 }
140 }
141
142 pub fn as_hwpx_str(&self) -> Cow<'_, str> {
144 match self {
145 Self::TopAndBottom => Cow::Borrowed("TOP_AND_BOTTOM"),
146 Self::Square => Cow::Borrowed("SQUARE"),
147 Self::BehindText => Cow::Borrowed("BEHIND_TEXT"),
148 Self::InFrontOfText => Cow::Borrowed("IN_FRONT_OF_TEXT"),
149 Self::Tight => Cow::Borrowed("TIGHT"),
150 Self::Through => Cow::Borrowed("THROUGH"),
151 Self::Other(value) => Cow::Borrowed(value.as_str()),
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
158#[non_exhaustive]
159pub enum ObjectTextFlow {
160 BothSides,
162 LeftOnly,
164 RightOnly,
166 LargestOnly,
168 Other(String),
170}
171
172impl ObjectTextFlow {
173 pub fn from_hwpx(value: &str) -> Self {
175 match value {
176 "BOTH_SIDES" => Self::BothSides,
177 "LEFT_ONLY" => Self::LeftOnly,
178 "RIGHT_ONLY" => Self::RightOnly,
179 "LARGEST_ONLY" => Self::LargestOnly,
180 other => Self::Other(other.to_string()),
181 }
182 }
183
184 pub fn as_hwpx_str(&self) -> Cow<'_, str> {
186 match self {
187 Self::BothSides => Cow::Borrowed("BOTH_SIDES"),
188 Self::LeftOnly => Cow::Borrowed("LEFT_ONLY"),
189 Self::RightOnly => Cow::Borrowed("RIGHT_ONLY"),
190 Self::LargestOnly => Cow::Borrowed("LARGEST_ONLY"),
191 Self::Other(value) => Cow::Borrowed(value.as_str()),
192 }
193 }
194}
195
196#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
198#[non_exhaustive]
199pub enum ObjectRelativeTo {
200 Paper,
202 Page,
204 Para,
206 Column,
208 Character,
210 Line,
212 Other(String),
214}
215
216impl ObjectRelativeTo {
217 pub fn from_hwpx(value: &str) -> Self {
219 match value {
220 "PAPER" => Self::Paper,
221 "PAGE" => Self::Page,
222 "PARA" => Self::Para,
223 "COLUMN" => Self::Column,
224 "CHAR" => Self::Character,
225 "LINE" => Self::Line,
226 other => Self::Other(other.to_string()),
227 }
228 }
229
230 pub fn as_hwpx_str(&self) -> Cow<'_, str> {
232 match self {
233 Self::Paper => Cow::Borrowed("PAPER"),
234 Self::Page => Cow::Borrowed("PAGE"),
235 Self::Para => Cow::Borrowed("PARA"),
236 Self::Column => Cow::Borrowed("COLUMN"),
237 Self::Character => Cow::Borrowed("CHAR"),
238 Self::Line => Cow::Borrowed("LINE"),
239 Self::Other(value) => Cow::Borrowed(value.as_str()),
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn legacy_inline_defaults_are_inline() {
250 let p = ObjectPlacement::legacy_inline_defaults();
251 assert!(p.treat_as_char);
252 assert!(!p.flow_with_text);
253 assert!(!p.allow_overlap);
254 assert_eq!(p.text_wrap, ObjectTextWrap::TopAndBottom);
255 assert_eq!(p.text_flow, ObjectTextFlow::BothSides);
256 assert_eq!(p.vert_rel_to, ObjectRelativeTo::Para);
257 assert_eq!(p.horz_rel_to, ObjectRelativeTo::Para);
258 assert_eq!(p.vert_offset, HwpUnit::ZERO);
259 assert_eq!(p.horz_offset, HwpUnit::ZERO);
260 }
261
262 #[test]
263 fn placement_serde_roundtrip() {
264 let p = ObjectPlacement {
265 text_wrap: ObjectTextWrap::Square,
266 text_flow: ObjectTextFlow::RightOnly,
267 treat_as_char: false,
268 flow_with_text: true,
269 allow_overlap: true,
270 vert_rel_to: ObjectRelativeTo::Paper,
271 horz_rel_to: ObjectRelativeTo::Page,
272 vert_offset: HwpUnit::new(1200).unwrap(),
273 horz_offset: HwpUnit::new(3400).unwrap(),
274 };
275 let json = serde_json::to_string(&p).unwrap();
276 let back: ObjectPlacement = serde_json::from_str(&json).unwrap();
277 assert_eq!(p, back);
278 }
279
280 #[test]
281 fn text_wrap_hwpx_roundtrip() {
282 for (s, v) in [
283 ("TOP_AND_BOTTOM", ObjectTextWrap::TopAndBottom),
284 ("SQUARE", ObjectTextWrap::Square),
285 ("BEHIND_TEXT", ObjectTextWrap::BehindText),
286 ("IN_FRONT_OF_TEXT", ObjectTextWrap::InFrontOfText),
287 ("TIGHT", ObjectTextWrap::Tight),
288 ("THROUGH", ObjectTextWrap::Through),
289 ] {
290 assert_eq!(ObjectTextWrap::from_hwpx(s), v);
291 assert_eq!(v.as_hwpx_str(), s);
292 }
293 assert_eq!(ObjectTextWrap::from_hwpx("WEIRD"), ObjectTextWrap::Other("WEIRD".to_string()));
294 assert_eq!(ObjectTextWrap::Other("WEIRD".to_string()).as_hwpx_str(), "WEIRD");
295 }
296
297 #[test]
298 fn text_flow_hwpx_roundtrip() {
299 for (s, v) in [
300 ("BOTH_SIDES", ObjectTextFlow::BothSides),
301 ("LEFT_ONLY", ObjectTextFlow::LeftOnly),
302 ("RIGHT_ONLY", ObjectTextFlow::RightOnly),
303 ("LARGEST_ONLY", ObjectTextFlow::LargestOnly),
304 ] {
305 assert_eq!(ObjectTextFlow::from_hwpx(s), v);
306 assert_eq!(v.as_hwpx_str(), s);
307 }
308 assert_eq!(ObjectTextFlow::from_hwpx("X"), ObjectTextFlow::Other("X".to_string()));
309 assert_eq!(ObjectTextFlow::Other("X".to_string()).as_hwpx_str(), "X");
310 }
311
312 #[test]
313 fn relative_to_hwpx_roundtrip() {
314 for (s, v) in [
315 ("PAPER", ObjectRelativeTo::Paper),
316 ("PAGE", ObjectRelativeTo::Page),
317 ("PARA", ObjectRelativeTo::Para),
318 ("COLUMN", ObjectRelativeTo::Column),
319 ("CHAR", ObjectRelativeTo::Character),
320 ("LINE", ObjectRelativeTo::Line),
321 ] {
322 assert_eq!(ObjectRelativeTo::from_hwpx(s), v);
323 assert_eq!(v.as_hwpx_str(), s);
324 }
325 assert_eq!(ObjectRelativeTo::from_hwpx("Z"), ObjectRelativeTo::Other("Z".to_string()));
326 assert_eq!(ObjectRelativeTo::Other("Z".to_string()).as_hwpx_str(), "Z");
327 }
328}