Skip to main content

extend_docx/
model.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5fn default_text_run_type() -> TextRunNodeType {
6    TextRunNodeType::Text
7}
8
9fn default_image_run_type() -> ImageRunNodeType {
10    ImageRunNodeType::Image
11}
12
13fn default_form_field_run_type() -> FormFieldRunNodeType {
14    FormFieldRunNodeType::FormField
15}
16
17fn default_paragraph_node_type() -> ParagraphNodeType {
18    ParagraphNodeType::Paragraph
19}
20
21fn default_table_cell_node_type() -> TableCellNodeType {
22    TableCellNodeType::TableCell
23}
24
25fn default_table_row_node_type() -> TableRowNodeType {
26    TableRowNodeType::TableRow
27}
28
29fn default_table_node_type() -> TableNodeType {
30    TableNodeType::Table
31}
32
33/// Mirrors TypeScript `ParagraphAlignment`.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub enum ParagraphAlignment {
37    Left,
38    Center,
39    Right,
40    Justify,
41}
42
43/// Mirrors TypeScript `HeadingLevel`.
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
45#[serde(into = "i64")]
46pub enum HeadingLevel {
47    One = 1,
48    Two = 2,
49    Three = 3,
50    Four = 4,
51    Five = 5,
52    Six = 6,
53    Seven = 7,
54    Eight = 8,
55    Nine = 9,
56}
57
58impl<'de> Deserialize<'de> for HeadingLevel {
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60    where
61        D: serde::Deserializer<'de>,
62    {
63        let value = i64::deserialize(deserializer)?;
64        match value {
65            1 => Ok(HeadingLevel::One),
66            2 => Ok(HeadingLevel::Two),
67            3 => Ok(HeadingLevel::Three),
68            4 => Ok(HeadingLevel::Four),
69            5 => Ok(HeadingLevel::Five),
70            6 => Ok(HeadingLevel::Six),
71            7 => Ok(HeadingLevel::Seven),
72            8 => Ok(HeadingLevel::Eight),
73            9 => Ok(HeadingLevel::Nine),
74            _ => Err(serde::de::Error::custom(format!(
75                "invalid heading level {value}"
76            ))),
77        }
78    }
79}
80
81impl From<HeadingLevel> for i64 {
82    fn from(value: HeadingLevel) -> Self {
83        value as i64
84    }
85}
86
87/// Mirrors TypeScript `TextRunBorderStyle`.
88#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct TextRunBorderStyle {
91    #[serde(rename = "type")]
92    pub border_type: String,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub color: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub size_eighth_pt: Option<f64>,
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub space_pt: Option<f64>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub frame: Option<bool>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub shadow: Option<bool>,
103}
104
105/// Mirrors TypeScript `VerticalAlign`.
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub enum VerticalAlign {
109    #[serde(rename = "superscript")]
110    Superscript,
111    #[serde(rename = "subscript")]
112    Subscript,
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub enum TextLigatures {
118    None,
119    Standard,
120    Contextual,
121    Historical,
122    Discretional,
123    StandardContextual,
124    StandardHistorical,
125    ContextualHistorical,
126    StandardDiscretional,
127    ContextualDiscretional,
128    HistoricalDiscretional,
129    StandardContextualHistorical,
130    StandardContextualDiscretional,
131    StandardHistoricalDiscretional,
132    ContextualHistoricalDiscretional,
133    All,
134}
135
136impl TextLigatures {
137    pub fn from_xml_value(value: &str) -> Option<Self> {
138        match value {
139            "none" => Some(Self::None),
140            "standard" => Some(Self::Standard),
141            "contextual" => Some(Self::Contextual),
142            "historical" => Some(Self::Historical),
143            "discretional" => Some(Self::Discretional),
144            "standardContextual" => Some(Self::StandardContextual),
145            "standardHistorical" => Some(Self::StandardHistorical),
146            "contextualHistorical" => Some(Self::ContextualHistorical),
147            "standardDiscretional" => Some(Self::StandardDiscretional),
148            "contextualDiscretional" => Some(Self::ContextualDiscretional),
149            "historicalDiscretional" => Some(Self::HistoricalDiscretional),
150            "standardContextualHistorical" => Some(Self::StandardContextualHistorical),
151            "standardContextualDiscretional" => Some(Self::StandardContextualDiscretional),
152            "standardHistoricalDiscretional" => Some(Self::StandardHistoricalDiscretional),
153            "contextualHistoricalDiscretional" => Some(Self::ContextualHistoricalDiscretional),
154            "all" => Some(Self::All),
155            _ => None,
156        }
157    }
158
159    pub fn xml_value(self) -> &'static str {
160        match self {
161            Self::None => "none",
162            Self::Standard => "standard",
163            Self::Contextual => "contextual",
164            Self::Historical => "historical",
165            Self::Discretional => "discretional",
166            Self::StandardContextual => "standardContextual",
167            Self::StandardHistorical => "standardHistorical",
168            Self::ContextualHistorical => "contextualHistorical",
169            Self::StandardDiscretional => "standardDiscretional",
170            Self::ContextualDiscretional => "contextualDiscretional",
171            Self::HistoricalDiscretional => "historicalDiscretional",
172            Self::StandardContextualHistorical => "standardContextualHistorical",
173            Self::StandardContextualDiscretional => "standardContextualDiscretional",
174            Self::StandardHistoricalDiscretional => "standardHistoricalDiscretional",
175            Self::ContextualHistoricalDiscretional => "contextualHistoricalDiscretional",
176            Self::All => "all",
177        }
178    }
179}
180
181/// Mirrors TypeScript `TextStyle`.
182#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct TextStyle {
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub bold: Option<bool>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub italic: Option<bool>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub underline: Option<bool>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub strike: Option<bool>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub all_caps: Option<bool>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub small_caps: Option<bool>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub color: Option<String>,
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub highlight: Option<String>,
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub background_color: Option<String>,
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub font_size_pt: Option<f64>,
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub font_size_cs_pt: Option<f64>,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub font_family: Option<String>,
209    /// Import-time `font_family` value used to distinguish a later edit through
210    /// the backwards-compatible single-family API from the preserved slots.
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub source_font_family: Option<String>,
213    /// Explicit OOXML `w:rFonts` slots. `font_family` remains the resolved
214    /// single-family fallback used by existing renderers and callers.
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub font_family_ascii: Option<String>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub font_family_h_ansi: Option<String>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub font_family_east_asia: Option<String>,
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub font_family_cs: Option<String>,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub font_theme_ascii: Option<String>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub font_theme_h_ansi: Option<String>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub font_theme_east_asia: Option<String>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub font_theme_cs: Option<String>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub resolved_font_family_ascii: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub resolved_font_family_h_ansi: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub resolved_font_family_east_asia: Option<String>,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub resolved_font_family_cs: Option<String>,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub font_hint: Option<String>,
241    /// OOXML `w:lang` slots plus run-direction flags that participate in font
242    /// selection for complex-script text.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub language: Option<String>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub language_east_asia: Option<String>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub language_bidi: Option<String>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub right_to_left: Option<bool>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub complex_script: Option<bool>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub character_spacing_twips: Option<i64>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub character_scale_percent: Option<u16>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub source_character_scale: Option<String>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub kerning_min_font_size_pt: Option<f64>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub ligatures: Option<TextLigatures>,
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub contextual_alternates: Option<bool>,
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub vertical_align: Option<VerticalAlign>,
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub run_border: Option<TextRunBorderStyle>,
269}
270
271/// Fully resolved formatting shared until a caller edits its value.
272#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
273#[serde(transparent)]
274pub struct SharedTextStyle(std::sync::Arc<TextStyle>);
275
276impl SharedTextStyle {
277    pub fn into_owned(self) -> TextStyle {
278        std::sync::Arc::unwrap_or_clone(self.0)
279    }
280}
281
282impl From<TextStyle> for SharedTextStyle {
283    fn from(style: TextStyle) -> Self {
284        Self(std::sync::Arc::new(style))
285    }
286}
287
288impl std::ops::Deref for SharedTextStyle {
289    type Target = TextStyle;
290
291    fn deref(&self) -> &TextStyle {
292        &self.0
293    }
294}
295
296impl std::ops::DerefMut for SharedTextStyle {
297    fn deref_mut(&mut self) -> &mut TextStyle {
298        std::sync::Arc::make_mut(&mut self.0)
299    }
300}
301
302/// Mirrors TypeScript `TextRunNode.noteReference`.
303#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
304#[serde(rename_all = "camelCase")]
305pub struct NoteReference {
306    pub kind: NoteReferenceKind,
307    pub id: i64,
308}
309
310/// Mirrors TypeScript `TextRunNode.noteReference.kind`.
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
312pub enum NoteReferenceKind {
313    #[serde(rename = "footnote")]
314    Footnote,
315    #[serde(rename = "endnote")]
316    Endnote,
317}
318
319/// Mirrors TypeScript `TextRunNode`.
320#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct TextRunNode {
323    #[serde(default = "default_text_run_type", rename = "type")]
324    pub r#type: TextRunNodeType,
325    pub text: String,
326    #[serde(skip_serializing_if = "Option::is_none")]
327    pub style: Option<SharedTextStyle>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub link: Option<String>,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub note_reference: Option<NoteReference>,
332}
333
334/// Discriminator for `TextRunNode`.
335#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
336pub enum TextRunNodeType {
337    #[serde(rename = "text")]
338    Text,
339}
340
341/// Mirrors TypeScript `ImageRunNode.crop`.
342#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct ImageCrop {
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub left_fraction: Option<f64>,
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub top_fraction: Option<f64>,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub right_fraction: Option<f64>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub bottom_fraction: Option<f64>,
353}
354
355/// Mirrors TypeScript `ImageRunNode.floating.horizontalAlign`.
356#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub enum ImageHorizontalAlign {
359    Left,
360    Center,
361    Right,
362    Inside,
363    Outside,
364}
365
366/// Mirrors TypeScript `ImageRunNode.floating.verticalAlign`.
367#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub enum ImageVerticalAlign {
370    Top,
371    Center,
372    Bottom,
373    Inside,
374    Outside,
375}
376
377/// Mirrors TypeScript `ImageRunNode.floating.wrapType`.
378#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub enum ImageWrapType {
381    #[serde(rename = "none")]
382    WrapNone,
383    #[serde(rename = "square")]
384    Square,
385    #[serde(rename = "tight")]
386    Tight,
387    #[serde(rename = "through")]
388    Through,
389    #[serde(rename = "topAndBottom")]
390    TopAndBottom,
391}
392
393/// Mirrors TypeScript `ImageRunNode.floating.wrapText`.
394#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub enum ImageWrapText {
397    #[serde(rename = "bothSides")]
398    BothSides,
399    #[serde(rename = "left")]
400    Left,
401    #[serde(rename = "right")]
402    Right,
403    #[serde(rename = "largest")]
404    Largest,
405}
406
407/// Mirrors TypeScript `ImageRunNode.floating`.
408#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct ImageFloating {
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub x_px: Option<f64>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub y_px: Option<f64>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub horizontal_align: Option<ImageHorizontalAlign>,
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub vertical_align: Option<ImageVerticalAlign>,
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub horizontal_relative_to: Option<String>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub vertical_relative_to: Option<String>,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub dist_l_px: Option<f64>,
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub dist_r_px: Option<f64>,
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub dist_t_px: Option<f64>,
429    #[serde(skip_serializing_if = "Option::is_none")]
430    pub dist_b_px: Option<f64>,
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub wrap_type: Option<ImageWrapType>,
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub wrap_text: Option<ImageWrapText>,
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub behind_document: Option<bool>,
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub z_index: Option<i64>,
439}
440
441#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct ImagePaintBounds {
444    pub x_px: f64,
445    pub y_px: f64,
446    pub width_px: f64,
447    pub height_px: f64,
448    pub layout_width_px: f64,
449    pub layout_height_px: f64,
450}
451
452/// Mirrors TypeScript `ImageRunNode`.
453#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
454#[serde(rename_all = "camelCase")]
455pub struct ImageRunNode {
456    #[serde(default, skip_serializing_if = "Option::is_none")]
457    pub paint_bounds: Option<Box<ImagePaintBounds>>,
458    #[serde(default, skip_serializing_if = "Option::is_none")]
459    pub math_ml: Option<String>,
460    #[serde(default, skip_serializing_if = "Option::is_none")]
461    pub content_nodes: Option<Vec<DocNode>>,
462    #[serde(default, skip_serializing_if = "Option::is_none")]
463    pub semantic_kind: Option<String>,
464    #[serde(default, skip_serializing_if = "Option::is_none")]
465    pub block_id: Option<String>,
466    #[serde(default = "default_image_run_type", rename = "type")]
467    pub r#type: ImageRunNodeType,
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub src: Option<String>,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub alt: Option<String>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub width_px: Option<f64>,
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub height_px: Option<f64>,
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub part_name: Option<String>,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub content_type: Option<String>,
480    #[serde(default, skip_serializing_if = "Option::is_none")]
481    pub data: Option<Vec<u8>>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub source_xml: Option<String>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub crop: Option<ImageCrop>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub css_filter: Option<String>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub css_opacity: Option<f64>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub floating: Option<ImageFloating>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub synthetic_text_box: Option<bool>,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub text_box_text: Option<String>,
496}
497
498/// Discriminator for `ImageRunNode`.
499#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
500pub enum ImageRunNodeType {
501    #[serde(rename = "image")]
502    Image,
503}
504
505/// Mirrors TypeScript `FormFieldType`.
506#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
507#[serde(rename_all = "camelCase")]
508pub enum FormFieldType {
509    #[serde(rename = "checkbox")]
510    Checkbox,
511    #[serde(rename = "text")]
512    Text,
513    #[serde(rename = "date")]
514    Date,
515    #[serde(rename = "dropdown")]
516    Dropdown,
517}
518
519/// Mirrors TypeScript `FormFieldSourceKind`.
520#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
521pub enum FormFieldSourceKind {
522    #[serde(rename = "sdt")]
523    Sdt,
524    #[serde(rename = "legacy")]
525    Legacy,
526}
527
528/// Mirrors TypeScript `FormFieldOption`.
529#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
530#[serde(rename_all = "camelCase")]
531pub struct FormFieldOption {
532    pub display_text: String,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub value: Option<String>,
535}
536
537/// Mirrors TypeScript `FormFieldTextWidgetSettings`.
538#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
539#[serde(rename_all = "camelCase")]
540pub struct FormFieldTextWidgetSettings {
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub input_type: Option<String>,
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub default_text: Option<String>,
545    #[serde(skip_serializing_if = "Option::is_none")]
546    pub max_length: Option<i64>,
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub text_format: Option<String>,
549}
550
551/// Mirrors TypeScript `FormFieldCheckboxWidgetSettings`.
552#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct FormFieldCheckboxWidgetSettings {
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub default_checked: Option<bool>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub size_mode: Option<FormFieldCheckboxSizeMode>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub size_pt: Option<f64>,
561}
562
563/// Mirrors TypeScript `FormFieldCheckboxWidgetSettings.sizeMode`.
564#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
565pub enum FormFieldCheckboxSizeMode {
566    #[serde(rename = "auto")]
567    Auto,
568    #[serde(rename = "exact")]
569    Exact,
570}
571
572/// Mirrors TypeScript `FormFieldDropdownWidgetSettings`.
573#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
574#[serde(rename_all = "camelCase")]
575pub struct FormFieldDropdownWidgetSettings {
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub default_value: Option<String>,
578}
579
580/// Mirrors TypeScript `FormFieldWidgetSettings`.
581#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct FormFieldWidgetSettings {
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub name: Option<String>,
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub enabled: Option<bool>,
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub calc_on_exit: Option<bool>,
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub text: Option<FormFieldTextWidgetSettings>,
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub checkbox: Option<FormFieldCheckboxWidgetSettings>,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub dropdown: Option<FormFieldDropdownWidgetSettings>,
596}
597
598#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase")]
600pub struct FormFieldSourcePreview {
601    pub relationship_id: String,
602    pub shape_id: String,
603    pub checked: bool,
604    pub image: Box<ImageRunNode>,
605}
606
607/// Mirrors TypeScript `FormFieldRunNode`.
608#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610pub struct FormFieldRunNode {
611    #[serde(default = "default_form_field_run_type", rename = "type")]
612    pub r#type: FormFieldRunNodeType,
613    pub field_type: FormFieldType,
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub source_kind: Option<FormFieldSourceKind>,
616    #[serde(skip_serializing_if = "Option::is_none")]
617    pub id: Option<i64>,
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub tag: Option<String>,
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub title: Option<String>,
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub placeholder: Option<String>,
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub checked: Option<bool>,
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub value: Option<String>,
628    #[serde(skip_serializing_if = "Option::is_none")]
629    pub options: Option<Vec<FormFieldOption>>,
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub widget: Option<FormFieldWidgetSettings>,
632    #[serde(skip_serializing_if = "Option::is_none")]
633    pub checked_symbol: Option<String>,
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub unchecked_symbol: Option<String>,
636    #[serde(skip_serializing_if = "Option::is_none")]
637    pub style: Option<SharedTextStyle>,
638    #[serde(skip_serializing_if = "Option::is_none")]
639    pub link: Option<String>,
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub source_xml: Option<String>,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    pub source_preview: Option<Box<FormFieldSourcePreview>>,
644}
645
646/// Discriminator for `FormFieldRunNode`.
647#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
648pub enum FormFieldRunNodeType {
649    #[serde(rename = "form-field")]
650    FormField,
651}
652
653/// Mirrors TypeScript `ParagraphChildNode`.
654#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
655#[serde(untagged)]
656pub enum ParagraphChildNode {
657    Text(TextRunNode),
658    Image(Box<ImageRunNode>),
659    FormField(Box<FormFieldRunNode>),
660}
661
662/// Mirrors TypeScript `ParagraphNumbering`.
663#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
664#[serde(rename_all = "camelCase")]
665pub struct ParagraphNumbering {
666    pub num_id: i64,
667    pub ilvl: i64,
668}
669
670/// Mirrors TypeScript `ParagraphSpacing.lineRule`.
671#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
672pub enum ParagraphLineRule {
673    #[serde(rename = "auto")]
674    Auto,
675    #[serde(rename = "exact")]
676    Exact,
677    #[serde(rename = "atLeast")]
678    AtLeast,
679}
680
681/// Mirrors TypeScript `ParagraphSpacing`.
682#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
683#[serde(rename_all = "camelCase")]
684pub struct ParagraphSpacing {
685    #[serde(skip_serializing_if = "Option::is_none")]
686    pub before_twips: Option<i64>,
687    #[serde(skip_serializing_if = "Option::is_none")]
688    pub after_twips: Option<i64>,
689    #[serde(skip_serializing_if = "Option::is_none")]
690    pub before_auto_spacing: Option<bool>,
691    #[serde(skip_serializing_if = "Option::is_none")]
692    pub after_auto_spacing: Option<bool>,
693    #[serde(skip_serializing_if = "Option::is_none")]
694    pub line_twips: Option<i64>,
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub line_rule: Option<ParagraphLineRule>,
697}
698
699/// Mirrors TypeScript `ParagraphIndent`.
700#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
701#[serde(rename_all = "camelCase")]
702pub struct ParagraphIndent {
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub left_twips: Option<i64>,
705    #[serde(skip_serializing_if = "Option::is_none")]
706    pub right_twips: Option<i64>,
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub first_line_twips: Option<i64>,
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub hanging_twips: Option<i64>,
711}
712
713/// Mirrors TypeScript `ParagraphBorderStyle`.
714#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
715#[serde(rename_all = "camelCase")]
716pub struct ParagraphBorderStyle {
717    #[serde(rename = "type")]
718    pub border_type: String,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub color: Option<String>,
721    #[serde(skip_serializing_if = "Option::is_none")]
722    pub size_eighth_pt: Option<f64>,
723    #[serde(skip_serializing_if = "Option::is_none")]
724    pub space_pt: Option<f64>,
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub frame: Option<bool>,
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub shadow: Option<bool>,
729}
730
731/// Mirrors TypeScript `ParagraphBorderSet`.
732#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
733#[serde(rename_all = "camelCase")]
734pub struct ParagraphBorderSet {
735    #[serde(skip_serializing_if = "Option::is_none")]
736    pub top: Option<ParagraphBorderStyle>,
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub right: Option<ParagraphBorderStyle>,
739    #[serde(skip_serializing_if = "Option::is_none")]
740    pub bottom: Option<ParagraphBorderStyle>,
741    #[serde(skip_serializing_if = "Option::is_none")]
742    pub left: Option<ParagraphBorderStyle>,
743    #[serde(skip_serializing_if = "Option::is_none")]
744    pub between: Option<ParagraphBorderStyle>,
745    #[serde(skip_serializing_if = "Option::is_none")]
746    pub bar: Option<ParagraphBorderStyle>,
747}
748
749/// Mirrors TypeScript `ParagraphTabStop.alignment`.
750#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
751#[serde(rename_all = "camelCase")]
752pub enum ParagraphTabStopAlignment {
753    Clear,
754    Left,
755    Center,
756    Right,
757    Decimal,
758    Bar,
759}
760
761/// Mirrors TypeScript `ParagraphTabStop.leader`.
762#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
763pub enum ParagraphTabStopLeader {
764    #[serde(rename = "none")]
765    LeaderNone,
766    #[serde(rename = "dot")]
767    Dot,
768    #[serde(rename = "hyphen")]
769    Hyphen,
770    #[serde(rename = "underscore")]
771    Underscore,
772    #[serde(rename = "middleDot")]
773    MiddleDot,
774}
775
776/// Mirrors TypeScript `ParagraphTabStop`.
777#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct ParagraphTabStop {
780    #[serde(skip_serializing_if = "Option::is_none")]
781    pub alignment: Option<ParagraphTabStopAlignment>,
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub leader: Option<ParagraphTabStopLeader>,
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub position_twips: Option<i64>,
786}
787
788/// Mirrors TypeScript `ParagraphStyle.dropCap.type`.
789#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
790pub enum ParagraphDropCapType {
791    #[serde(rename = "drop")]
792    Drop,
793    #[serde(rename = "margin")]
794    Margin,
795}
796
797/// Mirrors TypeScript `ParagraphStyle.dropCap`.
798#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
799#[serde(rename_all = "camelCase")]
800pub struct ParagraphDropCap {
801    #[serde(rename = "type")]
802    pub drop_cap_type: ParagraphDropCapType,
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub lines: Option<i64>,
805    #[serde(skip_serializing_if = "Option::is_none")]
806    pub wrap: Option<String>,
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub horizontal_anchor: Option<String>,
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub vertical_anchor: Option<String>,
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub x_twips: Option<i64>,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub y_twips: Option<i64>,
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub horizontal_space_twips: Option<i64>,
817    #[serde(skip_serializing_if = "Option::is_none")]
818    pub vertical_space_twips: Option<i64>,
819}
820
821#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
822#[serde(rename_all = "camelCase")]
823pub enum ParagraphTextAlignment {
824    Auto,
825    Top,
826    Center,
827    Baseline,
828    Bottom,
829}
830
831/// Mirrors TypeScript `ParagraphStyle`.
832#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
833#[serde(rename_all = "camelCase")]
834pub struct ParagraphStyle {
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub source_before_auto_spacing: Option<bool>,
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub source_after_auto_spacing: Option<bool>,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub align: Option<ParagraphAlignment>,
841    #[serde(skip_serializing_if = "Option::is_none")]
842    pub snap_to_grid: Option<bool>,
843    #[serde(skip_serializing_if = "Option::is_none")]
844    pub source_snap_to_grid: Option<bool>,
845    #[serde(skip_serializing_if = "Option::is_none")]
846    pub source_has_snap_to_grid: Option<bool>,
847    #[serde(skip_serializing_if = "Option::is_none")]
848    pub source_inherited_snap_to_grid: Option<bool>,
849    #[serde(skip_serializing_if = "Option::is_none")]
850    pub source_table_snap_to_grid: Option<bool>,
851    #[serde(skip_serializing_if = "Option::is_none")]
852    pub text_alignment: Option<ParagraphTextAlignment>,
853    #[serde(skip_serializing_if = "Option::is_none")]
854    pub source_text_alignment: Option<ParagraphTextAlignment>,
855    #[serde(skip_serializing_if = "Option::is_none")]
856    pub source_has_text_alignment: Option<bool>,
857    #[serde(skip_serializing_if = "Option::is_none")]
858    pub source_inherited_text_alignment: Option<ParagraphTextAlignment>,
859    #[serde(skip_serializing_if = "Option::is_none")]
860    pub source_table_text_alignment: Option<ParagraphTextAlignment>,
861    #[serde(skip_serializing_if = "Option::is_none")]
862    pub heading_level: Option<HeadingLevel>,
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub style_id: Option<String>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub style_name: Option<String>,
867    #[serde(skip_serializing_if = "Option::is_none")]
868    pub numbering: Option<ParagraphNumbering>,
869    #[serde(skip_serializing_if = "Option::is_none")]
870    pub spacing: Option<ParagraphSpacing>,
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub indent: Option<ParagraphIndent>,
873    #[serde(skip_serializing_if = "Option::is_none")]
874    pub background_color: Option<String>,
875    #[serde(skip_serializing_if = "Option::is_none")]
876    pub borders: Option<ParagraphBorderSet>,
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub tab_stops: Option<Vec<ParagraphTabStop>>,
879    #[serde(skip_serializing_if = "Option::is_none")]
880    pub contextual_spacing: Option<bool>,
881    #[serde(skip_serializing_if = "Option::is_none")]
882    pub keep_next: Option<bool>,
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub keep_lines: Option<bool>,
885    #[serde(skip_serializing_if = "Option::is_none")]
886    pub widow_control: Option<bool>,
887    #[serde(skip_serializing_if = "Option::is_none")]
888    pub page_break_before: Option<bool>,
889    #[serde(skip_serializing_if = "Option::is_none")]
890    pub drop_cap: Option<ParagraphDropCap>,
891}
892
893/// Text-independent run provenance for a validated surgical source XML patch.
894#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
895#[serde(rename_all = "camelCase")]
896pub struct ParagraphSourceTextPatchRun {
897    #[serde(skip_serializing_if = "Option::is_none")]
898    pub style: Option<SharedTextStyle>,
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub link: Option<String>,
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub note_reference: Option<NoteReference>,
903}
904
905#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
906#[serde(rename_all = "camelCase")]
907pub struct ParagraphSourceTextPatch {
908    pub runs: Vec<ParagraphSourceTextPatchRun>,
909}
910
911/// Imported paragraph-mark default, character-style, and direct layers.
912#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
913#[serde(rename_all = "camelCase")]
914pub struct ParagraphMarkFormattingSource {
915    #[serde(skip_serializing_if = "Option::is_none")]
916    pub default_style: Option<SharedTextStyle>,
917    #[serde(skip_serializing_if = "Option::is_none")]
918    pub character_style: Option<SharedTextStyle>,
919    #[serde(skip_serializing_if = "Option::is_none")]
920    pub direct_style: Option<SharedTextStyle>,
921}
922
923/// Mirrors TypeScript `ParagraphNode`.
924#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
925#[serde(rename_all = "camelCase")]
926pub struct ParagraphNode {
927    #[serde(default, skip_serializing_if = "Option::is_none")]
928    pub block_id: Option<String>,
929    #[serde(default = "default_paragraph_node_type", rename = "type")]
930    pub r#type: ParagraphNodeType,
931    pub children: Vec<ParagraphChildNode>,
932    #[serde(skip_serializing_if = "Option::is_none")]
933    pub style: Option<SharedParagraphStyle>,
934    #[serde(skip_serializing_if = "Option::is_none")]
935    pub paragraph_mark_style: Option<SharedTextStyle>,
936    #[serde(skip_serializing_if = "Option::is_none")]
937    pub source_paragraph_mark_style: Option<SharedTextStyle>,
938    #[serde(skip_serializing_if = "Option::is_none")]
939    pub source_paragraph_mark_formatting: Option<ParagraphMarkFormattingSource>,
940    #[serde(skip_serializing_if = "Option::is_none")]
941    pub source_paragraph_mark_properties_xml: Option<String>,
942    #[serde(skip_serializing_if = "Option::is_none")]
943    pub paragraph_mark_deleted: Option<bool>,
944    #[serde(skip_serializing_if = "Option::is_none")]
945    pub source_xml: Option<String>,
946    #[serde(skip_serializing_if = "Option::is_none")]
947    pub source_text_patch: Option<ParagraphSourceTextPatch>,
948    /// Immutable import-time run metadata used to prove that a later surgical
949    /// annotation edit is still targeting the exact parsed run state.
950    #[serde(skip_serializing_if = "Option::is_none")]
951    pub source_run_provenance: Option<ParagraphSourceTextPatch>,
952}
953
954/// Discriminator for `ParagraphNode`.
955#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
956pub enum ParagraphNodeType {
957    #[serde(rename = "paragraph")]
958    Paragraph,
959}
960
961/// Mirrors TypeScript `TableBoxSpacing`.
962#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
963#[serde(rename_all = "camelCase")]
964pub struct TableBoxSpacing {
965    #[serde(skip_serializing_if = "Option::is_none")]
966    pub top_twips: Option<i64>,
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub right_twips: Option<i64>,
969    #[serde(skip_serializing_if = "Option::is_none")]
970    pub bottom_twips: Option<i64>,
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub left_twips: Option<i64>,
973}
974
975/// Mirrors TypeScript `TableBorderStyle`.
976#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
977#[serde(rename_all = "camelCase")]
978pub struct TableBorderStyle {
979    #[serde(rename = "type")]
980    pub border_type: String,
981    #[serde(skip_serializing_if = "Option::is_none")]
982    pub color: Option<String>,
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub size_eighth_pt: Option<f64>,
985}
986
987/// Mirrors TypeScript `TableBorderSet`.
988#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
989#[serde(rename_all = "camelCase")]
990pub struct TableBorderSet {
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub top: Option<TableBorderStyle>,
993    #[serde(skip_serializing_if = "Option::is_none")]
994    pub right: Option<TableBorderStyle>,
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub bottom: Option<TableBorderStyle>,
997    #[serde(skip_serializing_if = "Option::is_none")]
998    pub left: Option<TableBorderStyle>,
999    #[serde(skip_serializing_if = "Option::is_none")]
1000    pub inside_h: Option<TableBorderStyle>,
1001    #[serde(skip_serializing_if = "Option::is_none")]
1002    pub inside_v: Option<TableBorderStyle>,
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    pub tl2br: Option<TableBorderStyle>,
1005    #[serde(skip_serializing_if = "Option::is_none")]
1006    pub tr2bl: Option<TableBorderStyle>,
1007}
1008
1009/// Mirrors TypeScript `TableCellStyle.verticalAlign`.
1010#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1011#[serde(rename_all = "camelCase")]
1012pub enum TableCellVerticalAlign {
1013    Top,
1014    Center,
1015    Bottom,
1016}
1017
1018/// Preferred width with an explicit measurement kind.
1019#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1020#[serde(tag = "type", rename_all = "camelCase")]
1021pub enum TablePreferredWidth {
1022    Dxa { value: f64 },
1023    Pct { value: f64 },
1024    Auto,
1025    Nil,
1026}
1027
1028/// Imported width baseline used to distinguish edits to either width API.
1029#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1030#[serde(rename_all = "camelCase")]
1031pub struct TableWidthSource {
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub preferred_width: Option<TablePreferredWidth>,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    pub width_twips: Option<i64>,
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub inherited_preferred_width: Option<TablePreferredWidth>,
1038}
1039
1040#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1041#[serde(rename_all = "camelCase")]
1042pub enum TableAlignment {
1043    Left,
1044    Center,
1045    Right,
1046}
1047
1048/// Mirrors TypeScript `TableCellStyle`.
1049#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1050#[serde(rename_all = "camelCase")]
1051pub struct TableCellStyle {
1052    #[serde(skip_serializing_if = "Option::is_none")]
1053    pub background_color: Option<String>,
1054    #[serde(skip_serializing_if = "Option::is_none")]
1055    pub grid_span: Option<i64>,
1056    #[serde(skip_serializing_if = "Option::is_none")]
1057    pub row_span: Option<i64>,
1058    #[serde(skip_serializing_if = "Option::is_none")]
1059    pub v_merge_continuation: Option<bool>,
1060    #[serde(skip_serializing_if = "Option::is_none")]
1061    pub width_twips: Option<i64>,
1062    #[serde(skip_serializing_if = "Option::is_none")]
1063    pub preferred_width: Option<TablePreferredWidth>,
1064    #[serde(skip_serializing_if = "Option::is_none")]
1065    pub source_width: Option<TableWidthSource>,
1066    #[serde(skip_serializing_if = "Option::is_none")]
1067    pub margin_twips: Option<TableBoxSpacing>,
1068    #[serde(skip_serializing_if = "Option::is_none")]
1069    pub vertical_align: Option<TableCellVerticalAlign>,
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    pub text_direction: Option<String>,
1072    #[serde(skip_serializing_if = "Option::is_none")]
1073    pub source_text_direction: Option<String>,
1074    #[serde(skip_serializing_if = "Option::is_none")]
1075    pub no_wrap: Option<bool>,
1076    #[serde(skip_serializing_if = "Option::is_none")]
1077    pub source_no_wrap: Option<bool>,
1078    #[serde(skip_serializing_if = "Option::is_none")]
1079    pub borders: Option<TableBorderSet>,
1080}
1081
1082/// Mirrors TypeScript `TableCellContentNode`.
1083#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1084#[serde(untagged)]
1085pub enum TableCellContentNode {
1086    Paragraph(ParagraphNode),
1087    Table(Box<TableNode>),
1088}
1089
1090/// Mirrors TypeScript `TableCellNode`.
1091#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1092#[serde(rename_all = "camelCase")]
1093pub struct TableCellNode {
1094    #[serde(default = "default_table_cell_node_type", rename = "type")]
1095    pub r#type: TableCellNodeType,
1096    pub nodes: Vec<TableCellContentNode>,
1097    #[serde(skip_serializing_if = "Option::is_none")]
1098    pub style: Option<TableCellStyle>,
1099}
1100
1101/// Discriminator for `TableCellNode`.
1102#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1103pub enum TableCellNodeType {
1104    #[serde(rename = "table-cell")]
1105    TableCell,
1106}
1107
1108/// Mirrors TypeScript `TableRowStyle.heightRule`.
1109#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1110pub enum TableRowHeightRule {
1111    #[serde(rename = "auto")]
1112    Auto,
1113    #[serde(rename = "atLeast")]
1114    AtLeast,
1115    #[serde(rename = "exact")]
1116    Exact,
1117}
1118
1119/// Imported row geometry used to distinguish a clear from an omitted legacy field.
1120#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1121#[serde(rename_all = "camelCase")]
1122pub struct TableRowGeometrySource {
1123    #[serde(skip_serializing_if = "Option::is_none")]
1124    pub grid_before: Option<i64>,
1125    #[serde(skip_serializing_if = "Option::is_none")]
1126    pub grid_after: Option<i64>,
1127    #[serde(skip_serializing_if = "Option::is_none")]
1128    pub width_before: Option<TablePreferredWidth>,
1129    #[serde(skip_serializing_if = "Option::is_none")]
1130    pub width_after: Option<TablePreferredWidth>,
1131}
1132
1133/// Mirrors TypeScript `TableRowStyle`.
1134#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1135#[serde(rename_all = "camelCase")]
1136pub struct TableRowStyle {
1137    #[serde(skip_serializing_if = "Option::is_none")]
1138    pub grid_before: Option<i64>,
1139    #[serde(skip_serializing_if = "Option::is_none")]
1140    pub grid_after: Option<i64>,
1141    #[serde(skip_serializing_if = "Option::is_none")]
1142    pub width_before: Option<TablePreferredWidth>,
1143    #[serde(skip_serializing_if = "Option::is_none")]
1144    pub width_after: Option<TablePreferredWidth>,
1145    #[serde(skip_serializing_if = "Option::is_none")]
1146    pub source_row_geometry: Option<TableRowGeometrySource>,
1147    #[serde(skip_serializing_if = "Option::is_none")]
1148    pub background_color: Option<String>,
1149    #[serde(skip_serializing_if = "Option::is_none")]
1150    pub height_twips: Option<i64>,
1151    #[serde(skip_serializing_if = "Option::is_none")]
1152    pub height_rule: Option<TableRowHeightRule>,
1153    #[serde(skip_serializing_if = "Option::is_none")]
1154    pub cant_split: Option<bool>,
1155    #[serde(skip_serializing_if = "Option::is_none")]
1156    pub is_header: Option<bool>,
1157}
1158
1159/// Mirrors TypeScript `TableRowNode`.
1160#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1161#[serde(rename_all = "camelCase")]
1162pub struct TableRowNode {
1163    #[serde(default = "default_table_row_node_type", rename = "type")]
1164    pub r#type: TableRowNodeType,
1165    pub cells: Vec<TableCellNode>,
1166    #[serde(skip_serializing_if = "Option::is_none")]
1167    pub style: Option<TableRowStyle>,
1168}
1169
1170/// Discriminator for `TableRowNode`.
1171#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1172pub enum TableRowNodeType {
1173    #[serde(rename = "table-row")]
1174    TableRow,
1175}
1176
1177/// Mirrors TypeScript `TableStyle.layout`.
1178#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1179pub enum TableLayout {
1180    #[serde(rename = "fixed")]
1181    Fixed,
1182    #[serde(rename = "autofit")]
1183    Autofit,
1184}
1185
1186/// Mirrors TypeScript `TableStyle.floating`.
1187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1188#[serde(rename_all = "camelCase")]
1189pub struct TableFloating {
1190    #[serde(skip_serializing_if = "Option::is_none")]
1191    pub x_twips: Option<i64>,
1192    #[serde(skip_serializing_if = "Option::is_none")]
1193    pub y_twips: Option<i64>,
1194    #[serde(skip_serializing_if = "Option::is_none")]
1195    pub left_from_text_twips: Option<i64>,
1196    #[serde(skip_serializing_if = "Option::is_none")]
1197    pub right_from_text_twips: Option<i64>,
1198    #[serde(skip_serializing_if = "Option::is_none")]
1199    pub top_from_text_twips: Option<i64>,
1200    #[serde(skip_serializing_if = "Option::is_none")]
1201    pub bottom_from_text_twips: Option<i64>,
1202    #[serde(skip_serializing_if = "Option::is_none")]
1203    pub horizontal_anchor: Option<String>,
1204    #[serde(skip_serializing_if = "Option::is_none")]
1205    pub vertical_anchor: Option<String>,
1206    #[serde(skip_serializing_if = "Option::is_none")]
1207    pub horizontal_align: Option<ImageHorizontalAlign>,
1208    #[serde(skip_serializing_if = "Option::is_none")]
1209    pub vertical_align: Option<ImageVerticalAlign>,
1210}
1211
1212/// Mirrors TypeScript `TableStyle`.
1213#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1214#[serde(rename_all = "camelCase")]
1215pub struct TableStyle {
1216    #[serde(skip_serializing_if = "Option::is_none")]
1217    pub style_id: Option<String>,
1218    #[serde(skip_serializing_if = "Option::is_none")]
1219    pub source_style_id: Option<String>,
1220    #[serde(skip_serializing_if = "Option::is_none")]
1221    pub width_twips: Option<i64>,
1222    #[serde(skip_serializing_if = "Option::is_none")]
1223    pub preferred_width: Option<TablePreferredWidth>,
1224    #[serde(skip_serializing_if = "Option::is_none")]
1225    pub source_width: Option<TableWidthSource>,
1226    #[serde(skip_serializing_if = "Option::is_none")]
1227    pub alignment: Option<TableAlignment>,
1228    #[serde(skip_serializing_if = "Option::is_none")]
1229    pub source_alignment: Option<TableAlignment>,
1230    #[serde(skip_serializing_if = "Option::is_none")]
1231    pub source_inherited_alignment: Option<TableAlignment>,
1232    #[serde(skip_serializing_if = "Option::is_none")]
1233    pub bidi_visual: Option<bool>,
1234    #[serde(skip_serializing_if = "Option::is_none")]
1235    pub source_bidi_visual: Option<bool>,
1236    #[serde(skip_serializing_if = "Option::is_none")]
1237    pub source_inherited_bidi_visual: Option<bool>,
1238    #[serde(skip_serializing_if = "Option::is_none")]
1239    pub indent_twips: Option<i64>,
1240    #[serde(skip_serializing_if = "Option::is_none")]
1241    pub layout: Option<TableLayout>,
1242    #[serde(skip_serializing_if = "Option::is_none")]
1243    pub cell_spacing_twips: Option<i64>,
1244    #[serde(skip_serializing_if = "Option::is_none")]
1245    pub cell_margin_twips: Option<TableBoxSpacing>,
1246    #[serde(skip_serializing_if = "Option::is_none")]
1247    pub column_widths_twips: Option<Vec<i64>>,
1248    /// Logical intervals with coincident physical boundaries. Project these
1249    /// columns for layout and materialize them before structural edits.
1250    #[serde(skip_serializing_if = "Option::is_none")]
1251    pub collapsed_grid_columns: Option<Vec<usize>>,
1252    #[serde(skip_serializing_if = "Option::is_none")]
1253    pub borders: Option<TableBorderSet>,
1254    #[serde(skip_serializing_if = "Option::is_none")]
1255    pub floating: Option<TableFloating>,
1256}
1257
1258/// Text-only paragraph replacement retained inside an otherwise untouched table.
1259#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1260#[serde(rename_all = "camelCase")]
1261pub struct TableSourceTextPatch {
1262    pub source_paragraph_xml: String,
1263    pub paragraph: ParagraphNode,
1264}
1265
1266/// Mirrors TypeScript `TableNode`.
1267#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1268#[serde(rename_all = "camelCase")]
1269pub struct TableNode {
1270    #[serde(default, skip_serializing_if = "Option::is_none")]
1271    pub block_id: Option<String>,
1272    #[serde(default = "default_table_node_type", rename = "type")]
1273    pub r#type: TableNodeType,
1274    pub rows: Vec<TableRowNode>,
1275    #[serde(skip_serializing_if = "Option::is_none")]
1276    pub style: Option<TableStyle>,
1277    #[serde(skip_serializing_if = "Option::is_none")]
1278    pub source_xml: Option<String>,
1279    #[serde(skip_serializing_if = "Option::is_none")]
1280    pub source_text_patches: Option<Vec<TableSourceTextPatch>>,
1281}
1282
1283/// Discriminator for `TableNode`.
1284#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1285pub enum TableNodeType {
1286    #[serde(rename = "table")]
1287    Table,
1288}
1289
1290/// Mirrors TypeScript `DocNode`.
1291#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1292#[serde(untagged)]
1293pub enum DocNode {
1294    Paragraph(ParagraphNode),
1295    Table(TableNode),
1296}
1297
1298/// Mirrors TypeScript `HeaderSection`.
1299#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1300#[serde(rename_all = "camelCase")]
1301pub struct HeaderSection {
1302    pub part_name: String,
1303    #[serde(skip_serializing_if = "Option::is_none")]
1304    pub reference_type: Option<String>,
1305    pub nodes: Vec<DocNode>,
1306}
1307
1308/// Mirrors TypeScript `FooterSection`.
1309#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1310#[serde(rename_all = "camelCase")]
1311pub struct FooterSection {
1312    pub part_name: String,
1313    #[serde(skip_serializing_if = "Option::is_none")]
1314    pub reference_type: Option<String>,
1315    pub nodes: Vec<DocNode>,
1316}
1317
1318/// Mirrors TypeScript `DocumentSection`.
1319#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1320#[serde(rename_all = "camelCase")]
1321pub struct DocumentSection {
1322    pub start_node_index: i64,
1323    #[serde(skip_serializing_if = "Option::is_none")]
1324    pub section_properties_xml: Option<String>,
1325    pub header_sections: Vec<HeaderSection>,
1326    pub footer_sections: Vec<FooterSection>,
1327}
1328
1329/// Mirrors TypeScript `ParagraphStyleDefinition`.
1330#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1331#[serde(rename_all = "camelCase")]
1332pub struct ParagraphStyleDefinition {
1333    pub id: String,
1334    pub name: String,
1335    #[serde(skip_serializing_if = "Option::is_none")]
1336    pub based_on_id: Option<String>,
1337    #[serde(skip_serializing_if = "Option::is_none")]
1338    pub next_style_id: Option<String>,
1339    #[serde(skip_serializing_if = "Option::is_none")]
1340    pub align: Option<ParagraphAlignment>,
1341    #[serde(skip_serializing_if = "Option::is_none")]
1342    pub snap_to_grid: Option<bool>,
1343    #[serde(skip_serializing_if = "Option::is_none")]
1344    pub source_has_snap_to_grid: Option<bool>,
1345    #[serde(skip_serializing_if = "Option::is_none")]
1346    pub text_alignment: Option<ParagraphTextAlignment>,
1347    #[serde(skip_serializing_if = "Option::is_none")]
1348    pub source_has_text_alignment: Option<bool>,
1349    #[serde(skip_serializing_if = "Option::is_none")]
1350    pub heading_level: Option<HeadingLevel>,
1351    #[serde(skip_serializing_if = "Option::is_none")]
1352    pub numbering: Option<ParagraphNumbering>,
1353    #[serde(skip_serializing_if = "Option::is_none")]
1354    pub spacing: Option<ParagraphSpacing>,
1355    #[serde(skip_serializing_if = "Option::is_none")]
1356    pub indent: Option<ParagraphIndent>,
1357    #[serde(skip_serializing_if = "Option::is_none")]
1358    pub background_color: Option<String>,
1359    #[serde(skip_serializing_if = "Option::is_none")]
1360    pub borders: Option<ParagraphBorderSet>,
1361    #[serde(skip_serializing_if = "Option::is_none")]
1362    pub tab_stops: Option<Vec<ParagraphTabStop>>,
1363    #[serde(skip_serializing_if = "Option::is_none")]
1364    pub contextual_spacing: Option<bool>,
1365    #[serde(skip_serializing_if = "Option::is_none")]
1366    pub keep_next: Option<bool>,
1367    #[serde(skip_serializing_if = "Option::is_none")]
1368    pub keep_lines: Option<bool>,
1369    #[serde(skip_serializing_if = "Option::is_none")]
1370    pub widow_control: Option<bool>,
1371    #[serde(skip_serializing_if = "Option::is_none")]
1372    pub page_break_before: Option<bool>,
1373    #[serde(skip_serializing_if = "Option::is_none")]
1374    pub run_style: Option<TextStyle>,
1375    #[serde(skip_serializing_if = "Option::is_none")]
1376    pub ui_priority: Option<i64>,
1377    #[serde(skip_serializing_if = "Option::is_none")]
1378    pub is_default: Option<bool>,
1379    #[serde(skip_serializing_if = "Option::is_none")]
1380    pub is_primary: Option<bool>,
1381}
1382
1383/// Mirrors TypeScript `NumberingLevelDefinition.suffix`.
1384#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1385pub enum NumberingLevelSuffix {
1386    #[serde(rename = "tab")]
1387    Tab,
1388    #[serde(rename = "space")]
1389    Space,
1390    #[serde(rename = "nothing")]
1391    Nothing,
1392}
1393
1394/// Mirrors TypeScript `NumberingLevelDefinition`.
1395#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1396#[serde(rename_all = "camelCase")]
1397pub struct NumberingLevelDefinition {
1398    pub ilvl: i64,
1399    #[serde(skip_serializing_if = "Option::is_none")]
1400    pub start: Option<i64>,
1401    #[serde(skip_serializing_if = "Option::is_none")]
1402    pub format: Option<String>,
1403    #[serde(skip_serializing_if = "Option::is_none")]
1404    pub text: Option<String>,
1405    #[serde(skip_serializing_if = "Option::is_none")]
1406    pub suffix: Option<NumberingLevelSuffix>,
1407    /// `w:lvlRestart`: one-based level whose use restarts this level; 0 means
1408    /// this level never restarts. Absent means the OOXML default (restart when
1409    /// any shallower level is used).
1410    #[serde(skip_serializing_if = "Option::is_none")]
1411    pub lvl_restart: Option<i64>,
1412    /// `w:isLgl`: legal numbering — every placeholder in this level's text
1413    /// renders as decimal regardless of the referenced levels' formats.
1414    #[serde(skip_serializing_if = "Option::is_none")]
1415    pub is_lgl: Option<bool>,
1416    #[serde(skip_serializing_if = "Option::is_none")]
1417    pub indent: Option<ParagraphIndent>,
1418    #[serde(skip_serializing_if = "Option::is_none")]
1419    pub run_style: Option<TextStyle>,
1420    #[serde(skip_serializing_if = "Option::is_none")]
1421    pub bullet_font_family: Option<String>,
1422    #[serde(skip_serializing_if = "Option::is_none")]
1423    pub bullet_color: Option<String>,
1424    #[serde(skip_serializing_if = "Option::is_none")]
1425    pub picture_bullet_id: Option<i64>,
1426    #[serde(skip_serializing_if = "Option::is_none")]
1427    pub picture_bullet: Option<NumberingPictureBulletDefinition>,
1428}
1429
1430/// Mirrors TypeScript `NumberingAbstractDefinition`.
1431#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1432#[serde(rename_all = "camelCase")]
1433pub struct NumberingAbstractDefinition {
1434    pub abstract_num_id: i64,
1435    pub levels: Vec<NumberingLevelDefinition>,
1436}
1437
1438/// Mirrors TypeScript `NumberingInstanceDefinition`.
1439#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1440#[serde(rename_all = "camelCase")]
1441pub struct NumberingInstanceDefinition {
1442    pub num_id: i64,
1443    pub abstract_num_id: i64,
1444    #[serde(skip_serializing_if = "Option::is_none")]
1445    pub level_start_overrides: Option<HashMap<String, i64>>,
1446    #[serde(skip_serializing_if = "Option::is_none")]
1447    pub level_overrides: Option<Vec<NumberingLevelDefinition>>,
1448}
1449
1450/// Mirrors TypeScript `NumberingDefinitionSet`.
1451#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1452#[serde(rename_all = "camelCase")]
1453pub struct NumberingDefinitionSet {
1454    pub abstracts: Vec<NumberingAbstractDefinition>,
1455    pub instances: Vec<NumberingInstanceDefinition>,
1456}
1457
1458/// Mirrors TypeScript `NumberingPictureBulletDefinition`.
1459#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1460#[serde(rename_all = "camelCase")]
1461pub struct NumberingPictureBulletDefinition {
1462    pub num_pic_bullet_id: i64,
1463    #[serde(skip_serializing_if = "Option::is_none")]
1464    pub src: Option<String>,
1465    #[serde(skip_serializing_if = "Option::is_none")]
1466    pub width_px: Option<f64>,
1467    #[serde(skip_serializing_if = "Option::is_none")]
1468    pub height_px: Option<f64>,
1469    #[serde(skip_serializing_if = "Option::is_none")]
1470    pub part_name: Option<String>,
1471    #[serde(skip_serializing_if = "Option::is_none")]
1472    pub content_type: Option<String>,
1473}
1474
1475/// Mirrors TypeScript `DocumentNoteDefinition`.
1476#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1477#[serde(rename_all = "camelCase")]
1478pub struct DocumentNoteDefinition {
1479    pub id: i64,
1480    pub text: String,
1481    #[serde(skip_serializing_if = "Option::is_none")]
1482    pub nodes: Option<Vec<DocNode>>,
1483}
1484
1485/// Mirrors TypeScript `DocumentCommentDefinition`.
1486#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1487#[serde(rename_all = "camelCase")]
1488pub struct DocumentCommentDefinition {
1489    #[serde(default, skip_serializing_if = "Option::is_none")]
1490    pub nodes: Option<Vec<DocNode>>,
1491    pub id: i64,
1492    #[serde(skip_serializing_if = "Option::is_none")]
1493    pub author: Option<String>,
1494    #[serde(skip_serializing_if = "Option::is_none")]
1495    pub initials: Option<String>,
1496    #[serde(skip_serializing_if = "Option::is_none")]
1497    pub date: Option<String>,
1498    pub text: String,
1499    #[serde(skip_serializing_if = "Option::is_none")]
1500    pub parent_id: Option<i64>,
1501    #[serde(skip_serializing_if = "Option::is_none")]
1502    pub resolved: Option<bool>,
1503    #[serde(skip_serializing_if = "Option::is_none")]
1504    pub source_xml: Option<String>,
1505    #[serde(skip_serializing_if = "Option::is_none")]
1506    pub extended_paragraph_id: Option<String>,
1507    #[serde(skip_serializing_if = "Option::is_none")]
1508    pub source_resolved: Option<bool>,
1509    #[serde(skip_serializing_if = "Option::is_none")]
1510    pub resolution_dirty: Option<bool>,
1511    #[serde(skip_serializing_if = "Option::is_none")]
1512    pub is_new: Option<bool>,
1513}
1514
1515/// Mirrors TypeScript `DocumentCompatibilitySettings`.
1516#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1517#[serde(rename_all = "camelCase")]
1518pub struct DocumentCompatibilitySettings {
1519    #[serde(skip_serializing_if = "Option::is_none")]
1520    pub adjust_line_height_in_table: Option<bool>,
1521    #[serde(skip_serializing_if = "Option::is_none")]
1522    pub no_leading: Option<bool>,
1523    #[serde(
1524        rename = "suppressTopSpacingWP",
1525        skip_serializing_if = "Option::is_none"
1526    )]
1527    pub suppress_top_spacing_wp: Option<bool>,
1528    #[serde(
1529        rename = "truncateFontHeightsLikeWP6",
1530        skip_serializing_if = "Option::is_none"
1531    )]
1532    pub truncate_font_heights_like_wp6: Option<bool>,
1533    #[serde(skip_serializing_if = "Option::is_none")]
1534    pub no_extra_line_spacing: Option<bool>,
1535    #[serde(rename = "spaceForUL", skip_serializing_if = "Option::is_none")]
1536    pub space_for_ul: Option<bool>,
1537    #[serde(skip_serializing_if = "Option::is_none")]
1538    pub suppress_bottom_spacing: Option<bool>,
1539    #[serde(skip_serializing_if = "Option::is_none")]
1540    pub compatibility_mode: Option<i64>,
1541    #[serde(
1542        rename = "enableOpenTypeFeatures",
1543        skip_serializing_if = "Option::is_none"
1544    )]
1545    pub enable_open_type_features: Option<bool>,
1546    #[serde(skip_serializing_if = "Option::is_none")]
1547    pub suppress_spacing_before_after_page_break: Option<bool>,
1548    #[serde(skip_serializing_if = "Option::is_none")]
1549    pub use_printer_metrics: Option<bool>,
1550    #[serde(skip_serializing_if = "Option::is_none")]
1551    pub use_fixed_html_paragraph_spacing: Option<bool>,
1552    #[serde(skip_serializing_if = "Option::is_none")]
1553    pub do_not_break_wrapped_tables: Option<bool>,
1554    #[serde(skip_serializing_if = "Option::is_none")]
1555    pub do_not_break_constrained_forced_table: Option<bool>,
1556    #[serde(skip_serializing_if = "Option::is_none")]
1557    pub even_and_odd_headers: Option<bool>,
1558}
1559
1560/// Mirrors TypeScript `DocModel.metadata`.
1561#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1562#[serde(rename_all = "camelCase")]
1563pub struct DocModelMetadata {
1564    #[serde(default, skip_serializing_if = "Option::is_none")]
1565    pub document_id: Option<String>,
1566    #[serde(default, skip_serializing_if = "Option::is_none")]
1567    pub source_part_names: Option<std::collections::BTreeMap<String, String>>,
1568    pub source_parts: i64,
1569    pub warnings: Vec<String>,
1570    #[serde(skip_serializing_if = "Option::is_none")]
1571    pub document_page_count: Option<i64>,
1572    #[serde(skip_serializing_if = "Option::is_none")]
1573    pub document_open_tag: Option<String>,
1574    #[serde(skip_serializing_if = "Option::is_none")]
1575    pub document_background_color: Option<String>,
1576    #[serde(skip_serializing_if = "Option::is_none")]
1577    pub section_properties_xml: Option<String>,
1578    #[serde(skip_serializing_if = "Option::is_none")]
1579    pub sections: Option<Vec<DocumentSection>>,
1580    pub header_sections: Vec<HeaderSection>,
1581    pub footer_sections: Vec<FooterSection>,
1582    pub paragraph_styles: Vec<ParagraphStyleDefinition>,
1583    #[serde(skip_serializing_if = "Option::is_none")]
1584    pub default_paragraph_style_id: Option<String>,
1585    #[serde(skip_serializing_if = "Option::is_none")]
1586    pub default_tab_stop_twips: Option<i64>,
1587    #[serde(skip_serializing_if = "Option::is_none")]
1588    pub document_settings_imported: Option<bool>,
1589    #[serde(skip_serializing_if = "Option::is_none")]
1590    pub line_spacing_compatibility_imported: Option<bool>,
1591    #[serde(skip_serializing_if = "Option::is_none")]
1592    pub numbering_definitions: Option<NumberingDefinitionSet>,
1593    #[serde(skip_serializing_if = "Option::is_none")]
1594    pub compatibility: Option<DocumentCompatibilitySettings>,
1595    #[serde(skip_serializing_if = "Option::is_none")]
1596    pub footnotes: Option<Vec<DocumentNoteDefinition>>,
1597    #[serde(skip_serializing_if = "Option::is_none")]
1598    pub endnotes: Option<Vec<DocumentNoteDefinition>>,
1599    #[serde(skip_serializing_if = "Option::is_none")]
1600    pub comments: Option<Vec<DocumentCommentDefinition>>,
1601}
1602
1603/// Mirrors TypeScript `DocModel`.
1604#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1605#[serde(rename_all = "camelCase")]
1606pub struct DocModel {
1607    pub nodes: Vec<DocNode>,
1608    pub metadata: DocModelMetadata,
1609}
1610
1611#[cfg(test)]
1612mod deserialize_tests {
1613    use super::{DocModel, DocNode, HeadingLevel, ParagraphAlignment};
1614
1615    #[test]
1616    fn deserializes_minimal_js_doc_model() {
1617        let json = r#"{"nodes":[{"type":"paragraph","children":[{"type":"text","text":"Hi"}]}],"metadata":{"sourceParts":1,"warnings":[],"headerSections":[],"footerSections":[],"paragraphStyles":[]}}"#;
1618        let model: DocModel = serde_json::from_str(json).expect("should deserialize");
1619        assert_eq!(model.nodes.len(), 1);
1620    }
1621
1622    #[test]
1623    fn deserializes_heading_level_two() {
1624        let json = r#"{"type":"paragraph","style":{"headingLevel":2},"children":[{"type":"text","text":"Added"}]}"#;
1625        let node: DocNode = serde_json::from_str(json).expect("heading level 2");
1626        let DocNode::Paragraph(paragraph) = node else {
1627            panic!("expected paragraph");
1628        };
1629        assert_eq!(
1630            paragraph.style.and_then(|style| style.heading_level),
1631            Some(HeadingLevel::Two)
1632        );
1633    }
1634
1635    #[test]
1636    fn deserializes_align_center() {
1637        let json = r#"{"type":"paragraph","style":{"align":"center"},"children":[{"type":"text","text":"Added"}]}"#;
1638        let node: DocNode = serde_json::from_str(json).expect("align center");
1639        let DocNode::Paragraph(paragraph) = node else {
1640            panic!("expected paragraph");
1641        };
1642        assert_eq!(
1643            paragraph.style.and_then(|style| style.align),
1644            Some(ParagraphAlignment::Center)
1645        );
1646    }
1647}
1648
1649/// Fully resolved formatting shared until a caller edits its value.
1650#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1651#[serde(transparent)]
1652pub struct SharedParagraphStyle(std::sync::Arc<ParagraphStyle>);
1653
1654impl SharedParagraphStyle {
1655    pub fn into_owned(self) -> ParagraphStyle {
1656        std::sync::Arc::unwrap_or_clone(self.0)
1657    }
1658}
1659
1660impl From<ParagraphStyle> for SharedParagraphStyle {
1661    fn from(style: ParagraphStyle) -> Self {
1662        Self(std::sync::Arc::new(style))
1663    }
1664}
1665
1666impl std::ops::Deref for SharedParagraphStyle {
1667    type Target = ParagraphStyle;
1668
1669    fn deref(&self) -> &ParagraphStyle {
1670        &self.0
1671    }
1672}
1673
1674impl std::ops::DerefMut for SharedParagraphStyle {
1675    fn deref_mut(&mut self) -> &mut ParagraphStyle {
1676        std::sync::Arc::make_mut(&mut self.0)
1677    }
1678}
1679