Skip to main content

rust_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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
453#[serde(rename_all = "camelCase")]
454pub struct ImagePropertyAttribute {
455    pub name: String,
456    pub value: String,
457}
458
459/// Ordered presentation data, including extension properties not rendered by the viewer.
460#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct ImagePropertyNode {
463    pub name: String,
464    #[serde(default, skip_serializing_if = "Vec::is_empty")]
465    pub attributes: Vec<ImagePropertyAttribute>,
466    #[serde(default, skip_serializing_if = "Vec::is_empty")]
467    pub children: Vec<ImagePropertyNode>,
468    #[serde(default, skip_serializing_if = "Option::is_none")]
469    pub text: Option<String>,
470}
471
472#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
473#[serde(rename_all = "camelCase")]
474pub struct ImageAssetReference {
475    pub element: String,
476    pub kind: String,
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub relationship_id: Option<String>,
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub target: Option<String>,
481    pub embedded_asset_available: bool,
482}
483
484#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
485#[serde(rename_all = "camelCase")]
486pub struct ImageTransform {
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub rotation_degrees: Option<f64>,
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub flip_horizontal: Option<bool>,
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub flip_vertical: Option<bool>,
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub offset_x_emu: Option<f64>,
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub offset_y_emu: Option<f64>,
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub width_emu: Option<f64>,
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub height_emu: Option<f64>,
501}
502
503#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
504#[serde(rename_all = "camelCase")]
505pub struct ImageTile {
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub offset_x_emu: Option<f64>,
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub offset_y_emu: Option<f64>,
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub scale_x: Option<f64>,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub scale_y: Option<f64>,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub alignment: Option<String>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub flip: Option<String>,
518}
519
520/// Parsed properties describe source intent independently of rendering coverage.
521#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct ImagePresentation {
524    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
525    pub transform_applied: bool,
526    #[serde(default, skip_serializing_if = "Vec::is_empty")]
527    pub assets: Vec<ImageAssetReference>,
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub transform: Option<ImageTransform>,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub source_rectangle: Option<ImageCrop>,
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub fill_rectangle: Option<ImageCrop>,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub tile: Option<ImageTile>,
536    #[serde(default, skip_serializing_if = "Vec::is_empty")]
537    pub properties: Vec<ImagePropertyNode>,
538}
539
540/// Mirrors TypeScript `ImageRunNode`.
541#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct ImageRunNode {
544    #[serde(default, skip_serializing_if = "Option::is_none")]
545    pub presentation: Option<Box<ImagePresentation>>,
546    #[serde(default, skip_serializing_if = "Option::is_none")]
547    pub chart_part_name: Option<String>,
548    #[serde(default, skip_serializing_if = "Option::is_none")]
549    pub chart_data: Option<Box<crate::ChartData>>,
550    #[serde(default, skip_serializing_if = "Option::is_none")]
551    pub paint_bounds: Option<Box<ImagePaintBounds>>,
552    #[serde(default, skip_serializing_if = "Option::is_none")]
553    pub math_ml: Option<String>,
554    #[serde(default, skip_serializing_if = "Option::is_none")]
555    pub content_nodes: Option<Vec<DocNode>>,
556    #[serde(default, skip_serializing_if = "Option::is_none")]
557    pub semantic_kind: Option<String>,
558    #[serde(default, skip_serializing_if = "Option::is_none")]
559    pub block_id: Option<String>,
560    #[serde(default = "default_image_run_type", rename = "type")]
561    pub r#type: ImageRunNodeType,
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub src: Option<String>,
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub alt: Option<String>,
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub width_px: Option<f64>,
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub height_px: Option<f64>,
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub part_name: Option<String>,
572    #[serde(skip_serializing_if = "Option::is_none")]
573    pub content_type: Option<String>,
574    #[serde(default, skip_serializing_if = "Option::is_none")]
575    pub data: Option<Vec<u8>>,
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub source_xml: Option<String>,
578    #[serde(skip_serializing_if = "Option::is_none")]
579    pub crop: Option<ImageCrop>,
580    #[serde(skip_serializing_if = "Option::is_none")]
581    pub css_filter: Option<String>,
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub css_opacity: Option<f64>,
584    #[serde(skip_serializing_if = "Option::is_none")]
585    pub floating: Option<ImageFloating>,
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub synthetic_text_box: Option<bool>,
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub text_box_text: Option<String>,
590}
591
592/// Discriminator for `ImageRunNode`.
593#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
594pub enum ImageRunNodeType {
595    #[serde(rename = "image")]
596    Image,
597}
598
599/// Mirrors TypeScript `FormFieldType`.
600#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
601#[serde(rename_all = "camelCase")]
602pub enum FormFieldType {
603    #[serde(rename = "checkbox")]
604    Checkbox,
605    #[serde(rename = "text")]
606    Text,
607    #[serde(rename = "date")]
608    Date,
609    #[serde(rename = "dropdown")]
610    Dropdown,
611}
612
613/// Mirrors TypeScript `FormFieldSourceKind`.
614#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
615pub enum FormFieldSourceKind {
616    #[serde(rename = "sdt")]
617    Sdt,
618    #[serde(rename = "legacy")]
619    Legacy,
620}
621
622/// Mirrors TypeScript `FormFieldOption`.
623#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct FormFieldOption {
626    pub display_text: String,
627    #[serde(skip_serializing_if = "Option::is_none")]
628    pub value: Option<String>,
629}
630
631/// Mirrors TypeScript `FormFieldTextWidgetSettings`.
632#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
633#[serde(rename_all = "camelCase")]
634pub struct FormFieldTextWidgetSettings {
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub input_type: Option<String>,
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub default_text: Option<String>,
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub max_length: Option<i64>,
641    #[serde(skip_serializing_if = "Option::is_none")]
642    pub text_format: Option<String>,
643}
644
645/// Mirrors TypeScript `FormFieldCheckboxWidgetSettings`.
646#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
647#[serde(rename_all = "camelCase")]
648pub struct FormFieldCheckboxWidgetSettings {
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub default_checked: Option<bool>,
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub size_mode: Option<FormFieldCheckboxSizeMode>,
653    #[serde(skip_serializing_if = "Option::is_none")]
654    pub size_pt: Option<f64>,
655}
656
657/// Mirrors TypeScript `FormFieldCheckboxWidgetSettings.sizeMode`.
658#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
659pub enum FormFieldCheckboxSizeMode {
660    #[serde(rename = "auto")]
661    Auto,
662    #[serde(rename = "exact")]
663    Exact,
664}
665
666/// Mirrors TypeScript `FormFieldDropdownWidgetSettings`.
667#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
668#[serde(rename_all = "camelCase")]
669pub struct FormFieldDropdownWidgetSettings {
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub default_value: Option<String>,
672}
673
674/// Mirrors TypeScript `FormFieldWidgetSettings`.
675#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
676#[serde(rename_all = "camelCase")]
677pub struct FormFieldWidgetSettings {
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub name: Option<String>,
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub enabled: Option<bool>,
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub calc_on_exit: Option<bool>,
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub text: Option<FormFieldTextWidgetSettings>,
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub checkbox: Option<FormFieldCheckboxWidgetSettings>,
688    #[serde(skip_serializing_if = "Option::is_none")]
689    pub dropdown: Option<FormFieldDropdownWidgetSettings>,
690}
691
692#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
693#[serde(rename_all = "camelCase")]
694pub struct FormFieldSourcePreview {
695    pub relationship_id: String,
696    pub shape_id: String,
697    pub checked: bool,
698    pub image: Box<ImageRunNode>,
699}
700
701/// Mirrors TypeScript `FormFieldRunNode`.
702#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
703#[serde(rename_all = "camelCase")]
704pub struct FormFieldRunNode {
705    #[serde(default = "default_form_field_run_type", rename = "type")]
706    pub r#type: FormFieldRunNodeType,
707    pub field_type: FormFieldType,
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub source_kind: Option<FormFieldSourceKind>,
710    #[serde(skip_serializing_if = "Option::is_none")]
711    pub id: Option<i64>,
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub tag: Option<String>,
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub title: Option<String>,
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub placeholder: Option<String>,
718    #[serde(skip_serializing_if = "Option::is_none")]
719    pub checked: Option<bool>,
720    #[serde(skip_serializing_if = "Option::is_none")]
721    pub value: Option<String>,
722    #[serde(skip_serializing_if = "Option::is_none")]
723    pub options: Option<Vec<FormFieldOption>>,
724    #[serde(skip_serializing_if = "Option::is_none")]
725    pub widget: Option<FormFieldWidgetSettings>,
726    #[serde(skip_serializing_if = "Option::is_none")]
727    pub checked_symbol: Option<String>,
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub unchecked_symbol: Option<String>,
730    #[serde(skip_serializing_if = "Option::is_none")]
731    pub style: Option<SharedTextStyle>,
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub link: Option<String>,
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub source_xml: Option<String>,
736    #[serde(default, skip_serializing_if = "Option::is_none")]
737    pub source_preview: Option<Box<FormFieldSourcePreview>>,
738}
739
740/// Discriminator for `FormFieldRunNode`.
741#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
742pub enum FormFieldRunNodeType {
743    #[serde(rename = "form-field")]
744    FormField,
745}
746
747/// Mirrors TypeScript `ParagraphChildNode`.
748#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
749#[serde(untagged)]
750pub enum ParagraphChildNode {
751    Text(TextRunNode),
752    Image(Box<ImageRunNode>),
753    FormField(Box<FormFieldRunNode>),
754}
755
756/// Mirrors TypeScript `ParagraphNumbering`.
757#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
758#[serde(rename_all = "camelCase")]
759pub struct ParagraphNumbering {
760    pub num_id: i64,
761    pub ilvl: i64,
762}
763
764/// Mirrors TypeScript `ParagraphSpacing.lineRule`.
765#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
766pub enum ParagraphLineRule {
767    #[serde(rename = "auto")]
768    Auto,
769    #[serde(rename = "exact")]
770    Exact,
771    #[serde(rename = "atLeast")]
772    AtLeast,
773}
774
775/// Mirrors TypeScript `ParagraphSpacing`.
776#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
777#[serde(rename_all = "camelCase")]
778pub struct ParagraphSpacing {
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub before_twips: Option<i64>,
781    #[serde(skip_serializing_if = "Option::is_none")]
782    pub after_twips: Option<i64>,
783    #[serde(skip_serializing_if = "Option::is_none")]
784    pub before_auto_spacing: Option<bool>,
785    #[serde(skip_serializing_if = "Option::is_none")]
786    pub after_auto_spacing: Option<bool>,
787    #[serde(skip_serializing_if = "Option::is_none")]
788    pub line_twips: Option<i64>,
789    #[serde(skip_serializing_if = "Option::is_none")]
790    pub line_rule: Option<ParagraphLineRule>,
791}
792
793/// Mirrors TypeScript `ParagraphIndent`.
794#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
795#[serde(rename_all = "camelCase")]
796pub struct ParagraphIndent {
797    #[serde(skip_serializing_if = "Option::is_none")]
798    pub left_twips: Option<i64>,
799    #[serde(skip_serializing_if = "Option::is_none")]
800    pub right_twips: Option<i64>,
801    #[serde(skip_serializing_if = "Option::is_none")]
802    pub first_line_twips: Option<i64>,
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub hanging_twips: Option<i64>,
805}
806
807/// Mirrors TypeScript `ParagraphBorderStyle`.
808#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
809#[serde(rename_all = "camelCase")]
810pub struct ParagraphBorderStyle {
811    #[serde(rename = "type")]
812    pub border_type: String,
813    #[serde(skip_serializing_if = "Option::is_none")]
814    pub color: Option<String>,
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub size_eighth_pt: Option<f64>,
817    #[serde(skip_serializing_if = "Option::is_none")]
818    pub space_pt: Option<f64>,
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub frame: Option<bool>,
821    #[serde(skip_serializing_if = "Option::is_none")]
822    pub shadow: Option<bool>,
823}
824
825/// Mirrors TypeScript `ParagraphBorderSet`.
826#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
827#[serde(rename_all = "camelCase")]
828pub struct ParagraphBorderSet {
829    #[serde(skip_serializing_if = "Option::is_none")]
830    pub top: Option<ParagraphBorderStyle>,
831    #[serde(skip_serializing_if = "Option::is_none")]
832    pub right: Option<ParagraphBorderStyle>,
833    #[serde(skip_serializing_if = "Option::is_none")]
834    pub bottom: Option<ParagraphBorderStyle>,
835    #[serde(skip_serializing_if = "Option::is_none")]
836    pub left: Option<ParagraphBorderStyle>,
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub between: Option<ParagraphBorderStyle>,
839    #[serde(skip_serializing_if = "Option::is_none")]
840    pub bar: Option<ParagraphBorderStyle>,
841}
842
843/// Mirrors TypeScript `ParagraphTabStop.alignment`.
844#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
845#[serde(rename_all = "camelCase")]
846pub enum ParagraphTabStopAlignment {
847    Clear,
848    Left,
849    Center,
850    Right,
851    Decimal,
852    Bar,
853}
854
855/// Mirrors TypeScript `ParagraphTabStop.leader`.
856#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
857pub enum ParagraphTabStopLeader {
858    #[serde(rename = "none")]
859    LeaderNone,
860    #[serde(rename = "dot")]
861    Dot,
862    #[serde(rename = "hyphen")]
863    Hyphen,
864    #[serde(rename = "underscore")]
865    Underscore,
866    #[serde(rename = "middleDot")]
867    MiddleDot,
868}
869
870/// Mirrors TypeScript `ParagraphTabStop`.
871#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
872#[serde(rename_all = "camelCase")]
873pub struct ParagraphTabStop {
874    #[serde(skip_serializing_if = "Option::is_none")]
875    pub alignment: Option<ParagraphTabStopAlignment>,
876    #[serde(skip_serializing_if = "Option::is_none")]
877    pub leader: Option<ParagraphTabStopLeader>,
878    #[serde(skip_serializing_if = "Option::is_none")]
879    pub position_twips: Option<i64>,
880}
881
882/// Mirrors TypeScript `ParagraphStyle.dropCap.type`.
883#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
884pub enum ParagraphDropCapType {
885    #[serde(rename = "drop")]
886    Drop,
887    #[serde(rename = "margin")]
888    Margin,
889}
890
891/// Mirrors TypeScript `ParagraphStyle.dropCap`.
892#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
893#[serde(rename_all = "camelCase")]
894pub struct ParagraphDropCap {
895    #[serde(rename = "type")]
896    pub drop_cap_type: ParagraphDropCapType,
897    #[serde(skip_serializing_if = "Option::is_none")]
898    pub lines: Option<i64>,
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub wrap: Option<String>,
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub horizontal_anchor: Option<String>,
903    #[serde(skip_serializing_if = "Option::is_none")]
904    pub vertical_anchor: Option<String>,
905    #[serde(skip_serializing_if = "Option::is_none")]
906    pub x_twips: Option<i64>,
907    #[serde(skip_serializing_if = "Option::is_none")]
908    pub y_twips: Option<i64>,
909    #[serde(skip_serializing_if = "Option::is_none")]
910    pub horizontal_space_twips: Option<i64>,
911    #[serde(skip_serializing_if = "Option::is_none")]
912    pub vertical_space_twips: Option<i64>,
913}
914
915#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
916#[serde(rename_all = "camelCase")]
917pub enum ParagraphTextAlignment {
918    Auto,
919    Top,
920    Center,
921    Baseline,
922    Bottom,
923}
924
925/// Mirrors TypeScript `ParagraphStyle`.
926#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
927#[serde(rename_all = "camelCase")]
928pub struct ParagraphStyle {
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub source_before_auto_spacing: Option<bool>,
931    #[serde(skip_serializing_if = "Option::is_none")]
932    pub source_after_auto_spacing: Option<bool>,
933    #[serde(skip_serializing_if = "Option::is_none")]
934    pub align: Option<ParagraphAlignment>,
935    #[serde(skip_serializing_if = "Option::is_none")]
936    pub snap_to_grid: Option<bool>,
937    #[serde(skip_serializing_if = "Option::is_none")]
938    pub source_snap_to_grid: Option<bool>,
939    #[serde(skip_serializing_if = "Option::is_none")]
940    pub source_has_snap_to_grid: Option<bool>,
941    #[serde(skip_serializing_if = "Option::is_none")]
942    pub source_inherited_snap_to_grid: Option<bool>,
943    #[serde(skip_serializing_if = "Option::is_none")]
944    pub source_table_snap_to_grid: Option<bool>,
945    #[serde(skip_serializing_if = "Option::is_none")]
946    pub text_alignment: Option<ParagraphTextAlignment>,
947    #[serde(skip_serializing_if = "Option::is_none")]
948    pub source_text_alignment: Option<ParagraphTextAlignment>,
949    #[serde(skip_serializing_if = "Option::is_none")]
950    pub source_has_text_alignment: Option<bool>,
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub source_inherited_text_alignment: Option<ParagraphTextAlignment>,
953    #[serde(skip_serializing_if = "Option::is_none")]
954    pub source_table_text_alignment: Option<ParagraphTextAlignment>,
955    #[serde(skip_serializing_if = "Option::is_none")]
956    pub heading_level: Option<HeadingLevel>,
957    #[serde(skip_serializing_if = "Option::is_none")]
958    pub style_id: Option<String>,
959    #[serde(skip_serializing_if = "Option::is_none")]
960    pub style_name: Option<String>,
961    #[serde(skip_serializing_if = "Option::is_none")]
962    pub numbering: Option<ParagraphNumbering>,
963    #[serde(skip_serializing_if = "Option::is_none")]
964    pub spacing: Option<ParagraphSpacing>,
965    #[serde(skip_serializing_if = "Option::is_none")]
966    pub indent: Option<ParagraphIndent>,
967    #[serde(skip_serializing_if = "Option::is_none")]
968    pub background_color: Option<String>,
969    #[serde(skip_serializing_if = "Option::is_none")]
970    pub borders: Option<ParagraphBorderSet>,
971    #[serde(skip_serializing_if = "Option::is_none")]
972    pub tab_stops: Option<Vec<ParagraphTabStop>>,
973    #[serde(skip_serializing_if = "Option::is_none")]
974    pub contextual_spacing: Option<bool>,
975    #[serde(skip_serializing_if = "Option::is_none")]
976    pub keep_next: Option<bool>,
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub keep_lines: Option<bool>,
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub widow_control: Option<bool>,
981    #[serde(skip_serializing_if = "Option::is_none")]
982    pub page_break_before: Option<bool>,
983    #[serde(skip_serializing_if = "Option::is_none")]
984    pub drop_cap: Option<ParagraphDropCap>,
985}
986
987/// Text-independent run provenance for a validated surgical source XML patch.
988#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
989#[serde(rename_all = "camelCase")]
990pub struct ParagraphSourceTextPatchRun {
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub style: Option<SharedTextStyle>,
993    #[serde(skip_serializing_if = "Option::is_none")]
994    pub link: Option<String>,
995    #[serde(skip_serializing_if = "Option::is_none")]
996    pub note_reference: Option<NoteReference>,
997}
998
999#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1000#[serde(rename_all = "camelCase")]
1001pub struct ParagraphSourceTextPatch {
1002    pub runs: Vec<ParagraphSourceTextPatchRun>,
1003}
1004
1005/// Imported paragraph-mark default, character-style, and direct layers.
1006#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1007#[serde(rename_all = "camelCase")]
1008pub struct ParagraphMarkFormattingSource {
1009    #[serde(skip_serializing_if = "Option::is_none")]
1010    pub default_style: Option<SharedTextStyle>,
1011    #[serde(skip_serializing_if = "Option::is_none")]
1012    pub character_style: Option<SharedTextStyle>,
1013    #[serde(skip_serializing_if = "Option::is_none")]
1014    pub direct_style: Option<SharedTextStyle>,
1015}
1016
1017/// Mirrors TypeScript `ParagraphNode`.
1018#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct ParagraphNode {
1021    #[serde(default, skip_serializing_if = "Option::is_none")]
1022    pub block_id: Option<String>,
1023    #[serde(default = "default_paragraph_node_type", rename = "type")]
1024    pub r#type: ParagraphNodeType,
1025    pub children: Vec<ParagraphChildNode>,
1026    #[serde(skip_serializing_if = "Option::is_none")]
1027    pub style: Option<SharedParagraphStyle>,
1028    #[serde(skip_serializing_if = "Option::is_none")]
1029    pub paragraph_mark_style: Option<SharedTextStyle>,
1030    #[serde(skip_serializing_if = "Option::is_none")]
1031    pub source_paragraph_mark_style: Option<SharedTextStyle>,
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub source_paragraph_mark_formatting: Option<ParagraphMarkFormattingSource>,
1034    #[serde(skip_serializing_if = "Option::is_none")]
1035    pub source_paragraph_mark_properties_xml: Option<String>,
1036    #[serde(skip_serializing_if = "Option::is_none")]
1037    pub paragraph_mark_deleted: Option<bool>,
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    pub source_xml: Option<String>,
1040    #[serde(skip_serializing_if = "Option::is_none")]
1041    pub source_text_patch: Option<ParagraphSourceTextPatch>,
1042    /// Immutable import-time run metadata used to prove that a later surgical
1043    /// annotation edit is still targeting the exact parsed run state.
1044    #[serde(skip_serializing_if = "Option::is_none")]
1045    pub source_run_provenance: Option<ParagraphSourceTextPatch>,
1046}
1047
1048/// Discriminator for `ParagraphNode`.
1049#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1050pub enum ParagraphNodeType {
1051    #[serde(rename = "paragraph")]
1052    Paragraph,
1053}
1054
1055/// Mirrors TypeScript `TableBoxSpacing`.
1056#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1057#[serde(rename_all = "camelCase")]
1058pub struct TableBoxSpacing {
1059    #[serde(skip_serializing_if = "Option::is_none")]
1060    pub top_twips: Option<i64>,
1061    #[serde(skip_serializing_if = "Option::is_none")]
1062    pub right_twips: Option<i64>,
1063    #[serde(skip_serializing_if = "Option::is_none")]
1064    pub bottom_twips: Option<i64>,
1065    #[serde(skip_serializing_if = "Option::is_none")]
1066    pub left_twips: Option<i64>,
1067}
1068
1069/// Mirrors TypeScript `TableBorderStyle`.
1070#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1071#[serde(rename_all = "camelCase")]
1072pub struct TableBorderStyle {
1073    #[serde(rename = "type")]
1074    pub border_type: String,
1075    #[serde(skip_serializing_if = "Option::is_none")]
1076    pub color: Option<String>,
1077    #[serde(skip_serializing_if = "Option::is_none")]
1078    pub size_eighth_pt: Option<f64>,
1079}
1080
1081/// Mirrors TypeScript `TableBorderSet`.
1082#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1083#[serde(rename_all = "camelCase")]
1084pub struct TableBorderSet {
1085    #[serde(skip_serializing_if = "Option::is_none")]
1086    pub top: Option<TableBorderStyle>,
1087    #[serde(skip_serializing_if = "Option::is_none")]
1088    pub right: Option<TableBorderStyle>,
1089    #[serde(skip_serializing_if = "Option::is_none")]
1090    pub bottom: Option<TableBorderStyle>,
1091    #[serde(skip_serializing_if = "Option::is_none")]
1092    pub left: Option<TableBorderStyle>,
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    pub inside_h: Option<TableBorderStyle>,
1095    #[serde(skip_serializing_if = "Option::is_none")]
1096    pub inside_v: Option<TableBorderStyle>,
1097    #[serde(skip_serializing_if = "Option::is_none")]
1098    pub tl2br: Option<TableBorderStyle>,
1099    #[serde(skip_serializing_if = "Option::is_none")]
1100    pub tr2bl: Option<TableBorderStyle>,
1101}
1102
1103/// Mirrors TypeScript `TableCellStyle.verticalAlign`.
1104#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1105#[serde(rename_all = "camelCase")]
1106pub enum TableCellVerticalAlign {
1107    Top,
1108    Center,
1109    Bottom,
1110}
1111
1112/// Preferred width with an explicit measurement kind.
1113#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1114#[serde(tag = "type", rename_all = "camelCase")]
1115pub enum TablePreferredWidth {
1116    Dxa { value: f64 },
1117    Pct { value: f64 },
1118    Auto,
1119    Nil,
1120}
1121
1122/// Imported width baseline used to distinguish edits to either width API.
1123#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1124#[serde(rename_all = "camelCase")]
1125pub struct TableWidthSource {
1126    #[serde(skip_serializing_if = "Option::is_none")]
1127    pub preferred_width: Option<TablePreferredWidth>,
1128    #[serde(skip_serializing_if = "Option::is_none")]
1129    pub width_twips: Option<i64>,
1130    #[serde(skip_serializing_if = "Option::is_none")]
1131    pub inherited_preferred_width: Option<TablePreferredWidth>,
1132}
1133
1134#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1135#[serde(rename_all = "camelCase")]
1136pub enum TableAlignment {
1137    Left,
1138    Center,
1139    Right,
1140}
1141
1142/// Mirrors TypeScript `TableCellStyle`.
1143#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1144#[serde(rename_all = "camelCase")]
1145pub struct TableCellStyle {
1146    #[serde(skip_serializing_if = "Option::is_none")]
1147    pub background_color: Option<String>,
1148    #[serde(skip_serializing_if = "Option::is_none")]
1149    pub grid_span: Option<i64>,
1150    #[serde(skip_serializing_if = "Option::is_none")]
1151    pub row_span: Option<i64>,
1152    #[serde(skip_serializing_if = "Option::is_none")]
1153    pub v_merge_continuation: Option<bool>,
1154    #[serde(skip_serializing_if = "Option::is_none")]
1155    pub width_twips: Option<i64>,
1156    #[serde(skip_serializing_if = "Option::is_none")]
1157    pub preferred_width: Option<TablePreferredWidth>,
1158    #[serde(skip_serializing_if = "Option::is_none")]
1159    pub source_width: Option<TableWidthSource>,
1160    #[serde(skip_serializing_if = "Option::is_none")]
1161    pub margin_twips: Option<TableBoxSpacing>,
1162    #[serde(skip_serializing_if = "Option::is_none")]
1163    pub vertical_align: Option<TableCellVerticalAlign>,
1164    #[serde(skip_serializing_if = "Option::is_none")]
1165    pub text_direction: Option<String>,
1166    #[serde(skip_serializing_if = "Option::is_none")]
1167    pub source_text_direction: Option<String>,
1168    #[serde(skip_serializing_if = "Option::is_none")]
1169    pub no_wrap: Option<bool>,
1170    #[serde(skip_serializing_if = "Option::is_none")]
1171    pub source_no_wrap: Option<bool>,
1172    #[serde(skip_serializing_if = "Option::is_none")]
1173    pub borders: Option<TableBorderSet>,
1174}
1175
1176/// Mirrors TypeScript `TableCellContentNode`.
1177#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1178#[serde(untagged)]
1179pub enum TableCellContentNode {
1180    Paragraph(ParagraphNode),
1181    Table(Box<TableNode>),
1182}
1183
1184/// Mirrors TypeScript `TableCellNode`.
1185#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1186#[serde(rename_all = "camelCase")]
1187pub struct TableCellNode {
1188    #[serde(default = "default_table_cell_node_type", rename = "type")]
1189    pub r#type: TableCellNodeType,
1190    pub nodes: Vec<TableCellContentNode>,
1191    #[serde(skip_serializing_if = "Option::is_none")]
1192    pub style: Option<TableCellStyle>,
1193}
1194
1195/// Discriminator for `TableCellNode`.
1196#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1197pub enum TableCellNodeType {
1198    #[serde(rename = "table-cell")]
1199    TableCell,
1200}
1201
1202/// Mirrors TypeScript `TableRowStyle.heightRule`.
1203#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1204pub enum TableRowHeightRule {
1205    #[serde(rename = "auto")]
1206    Auto,
1207    #[serde(rename = "atLeast")]
1208    AtLeast,
1209    #[serde(rename = "exact")]
1210    Exact,
1211}
1212
1213/// Imported row geometry used to distinguish a clear from an omitted legacy field.
1214#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct TableRowGeometrySource {
1217    #[serde(skip_serializing_if = "Option::is_none")]
1218    pub grid_before: Option<i64>,
1219    #[serde(skip_serializing_if = "Option::is_none")]
1220    pub grid_after: Option<i64>,
1221    #[serde(skip_serializing_if = "Option::is_none")]
1222    pub width_before: Option<TablePreferredWidth>,
1223    #[serde(skip_serializing_if = "Option::is_none")]
1224    pub width_after: Option<TablePreferredWidth>,
1225}
1226
1227/// Mirrors TypeScript `TableRowStyle`.
1228#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1229#[serde(rename_all = "camelCase")]
1230pub struct TableRowStyle {
1231    #[serde(skip_serializing_if = "Option::is_none")]
1232    pub grid_before: Option<i64>,
1233    #[serde(skip_serializing_if = "Option::is_none")]
1234    pub grid_after: Option<i64>,
1235    #[serde(skip_serializing_if = "Option::is_none")]
1236    pub width_before: Option<TablePreferredWidth>,
1237    #[serde(skip_serializing_if = "Option::is_none")]
1238    pub width_after: Option<TablePreferredWidth>,
1239    #[serde(skip_serializing_if = "Option::is_none")]
1240    pub source_row_geometry: Option<TableRowGeometrySource>,
1241    #[serde(skip_serializing_if = "Option::is_none")]
1242    pub background_color: Option<String>,
1243    #[serde(skip_serializing_if = "Option::is_none")]
1244    pub height_twips: Option<i64>,
1245    #[serde(skip_serializing_if = "Option::is_none")]
1246    pub height_rule: Option<TableRowHeightRule>,
1247    #[serde(skip_serializing_if = "Option::is_none")]
1248    pub cant_split: Option<bool>,
1249    #[serde(skip_serializing_if = "Option::is_none")]
1250    pub is_header: Option<bool>,
1251}
1252
1253/// Mirrors TypeScript `TableRowNode`.
1254#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1255#[serde(rename_all = "camelCase")]
1256pub struct TableRowNode {
1257    #[serde(default = "default_table_row_node_type", rename = "type")]
1258    pub r#type: TableRowNodeType,
1259    pub cells: Vec<TableCellNode>,
1260    #[serde(skip_serializing_if = "Option::is_none")]
1261    pub style: Option<TableRowStyle>,
1262}
1263
1264/// Discriminator for `TableRowNode`.
1265#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1266pub enum TableRowNodeType {
1267    #[serde(rename = "table-row")]
1268    TableRow,
1269}
1270
1271/// Mirrors TypeScript `TableStyle.layout`.
1272#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1273pub enum TableLayout {
1274    #[serde(rename = "fixed")]
1275    Fixed,
1276    #[serde(rename = "autofit")]
1277    Autofit,
1278}
1279
1280/// Mirrors TypeScript `TableStyle.floating`.
1281#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1282#[serde(rename_all = "camelCase")]
1283pub struct TableFloating {
1284    #[serde(skip_serializing_if = "Option::is_none")]
1285    pub x_twips: Option<i64>,
1286    #[serde(skip_serializing_if = "Option::is_none")]
1287    pub y_twips: Option<i64>,
1288    #[serde(skip_serializing_if = "Option::is_none")]
1289    pub left_from_text_twips: Option<i64>,
1290    #[serde(skip_serializing_if = "Option::is_none")]
1291    pub right_from_text_twips: Option<i64>,
1292    #[serde(skip_serializing_if = "Option::is_none")]
1293    pub top_from_text_twips: Option<i64>,
1294    #[serde(skip_serializing_if = "Option::is_none")]
1295    pub bottom_from_text_twips: Option<i64>,
1296    #[serde(skip_serializing_if = "Option::is_none")]
1297    pub horizontal_anchor: Option<String>,
1298    #[serde(skip_serializing_if = "Option::is_none")]
1299    pub vertical_anchor: Option<String>,
1300    #[serde(skip_serializing_if = "Option::is_none")]
1301    pub horizontal_align: Option<ImageHorizontalAlign>,
1302    #[serde(skip_serializing_if = "Option::is_none")]
1303    pub vertical_align: Option<ImageVerticalAlign>,
1304}
1305
1306/// Mirrors TypeScript `TableStyle`.
1307#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1308#[serde(rename_all = "camelCase")]
1309pub struct TableStyle {
1310    #[serde(skip_serializing_if = "Option::is_none")]
1311    pub style_id: Option<String>,
1312    #[serde(skip_serializing_if = "Option::is_none")]
1313    pub source_style_id: Option<String>,
1314    #[serde(skip_serializing_if = "Option::is_none")]
1315    pub width_twips: Option<i64>,
1316    #[serde(skip_serializing_if = "Option::is_none")]
1317    pub preferred_width: Option<TablePreferredWidth>,
1318    #[serde(skip_serializing_if = "Option::is_none")]
1319    pub source_width: Option<TableWidthSource>,
1320    #[serde(skip_serializing_if = "Option::is_none")]
1321    pub alignment: Option<TableAlignment>,
1322    #[serde(skip_serializing_if = "Option::is_none")]
1323    pub source_alignment: Option<TableAlignment>,
1324    #[serde(skip_serializing_if = "Option::is_none")]
1325    pub source_inherited_alignment: Option<TableAlignment>,
1326    #[serde(skip_serializing_if = "Option::is_none")]
1327    pub bidi_visual: Option<bool>,
1328    #[serde(skip_serializing_if = "Option::is_none")]
1329    pub source_bidi_visual: Option<bool>,
1330    #[serde(skip_serializing_if = "Option::is_none")]
1331    pub source_inherited_bidi_visual: Option<bool>,
1332    #[serde(skip_serializing_if = "Option::is_none")]
1333    pub indent_twips: Option<i64>,
1334    #[serde(skip_serializing_if = "Option::is_none")]
1335    pub layout: Option<TableLayout>,
1336    #[serde(skip_serializing_if = "Option::is_none")]
1337    pub cell_spacing_twips: Option<i64>,
1338    #[serde(skip_serializing_if = "Option::is_none")]
1339    pub cell_margin_twips: Option<TableBoxSpacing>,
1340    #[serde(skip_serializing_if = "Option::is_none")]
1341    pub column_widths_twips: Option<Vec<i64>>,
1342    /// Logical intervals with coincident physical boundaries. Project these
1343    /// columns for layout and materialize them before structural edits.
1344    #[serde(skip_serializing_if = "Option::is_none")]
1345    pub collapsed_grid_columns: Option<Vec<usize>>,
1346    #[serde(skip_serializing_if = "Option::is_none")]
1347    pub borders: Option<TableBorderSet>,
1348    #[serde(skip_serializing_if = "Option::is_none")]
1349    pub floating: Option<TableFloating>,
1350}
1351
1352/// Text-only paragraph replacement retained inside an otherwise untouched table.
1353#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1354#[serde(rename_all = "camelCase")]
1355pub struct TableSourceTextPatch {
1356    pub source_paragraph_xml: String,
1357    pub paragraph: ParagraphNode,
1358}
1359
1360/// Mirrors TypeScript `TableNode`.
1361#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1362#[serde(rename_all = "camelCase")]
1363pub struct TableNode {
1364    #[serde(default, skip_serializing_if = "Option::is_none")]
1365    pub block_id: Option<String>,
1366    #[serde(default = "default_table_node_type", rename = "type")]
1367    pub r#type: TableNodeType,
1368    pub rows: Vec<TableRowNode>,
1369    #[serde(skip_serializing_if = "Option::is_none")]
1370    pub style: Option<TableStyle>,
1371    #[serde(skip_serializing_if = "Option::is_none")]
1372    pub source_xml: Option<String>,
1373    #[serde(skip_serializing_if = "Option::is_none")]
1374    pub source_text_patches: Option<Vec<TableSourceTextPatch>>,
1375}
1376
1377/// Discriminator for `TableNode`.
1378#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1379pub enum TableNodeType {
1380    #[serde(rename = "table")]
1381    Table,
1382}
1383
1384/// Mirrors TypeScript `DocNode`.
1385#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1386#[serde(untagged)]
1387pub enum DocNode {
1388    Paragraph(ParagraphNode),
1389    Table(TableNode),
1390}
1391
1392/// Mirrors TypeScript `HeaderSection`.
1393#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1394#[serde(rename_all = "camelCase")]
1395pub struct HeaderSection {
1396    pub part_name: String,
1397    #[serde(skip_serializing_if = "Option::is_none")]
1398    pub reference_type: Option<String>,
1399    pub nodes: Vec<DocNode>,
1400}
1401
1402/// Mirrors TypeScript `FooterSection`.
1403#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1404#[serde(rename_all = "camelCase")]
1405pub struct FooterSection {
1406    pub part_name: String,
1407    #[serde(skip_serializing_if = "Option::is_none")]
1408    pub reference_type: Option<String>,
1409    pub nodes: Vec<DocNode>,
1410}
1411
1412/// Mirrors TypeScript `DocumentSection`.
1413#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1414#[serde(rename_all = "camelCase")]
1415pub struct DocumentSection {
1416    pub start_node_index: i64,
1417    #[serde(skip_serializing_if = "Option::is_none")]
1418    pub section_properties_xml: Option<String>,
1419    pub header_sections: Vec<HeaderSection>,
1420    pub footer_sections: Vec<FooterSection>,
1421}
1422
1423/// Mirrors TypeScript `ParagraphStyleDefinition`.
1424#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1425#[serde(rename_all = "camelCase")]
1426pub struct ParagraphStyleDefinition {
1427    pub id: String,
1428    pub name: String,
1429    #[serde(skip_serializing_if = "Option::is_none")]
1430    pub based_on_id: Option<String>,
1431    #[serde(skip_serializing_if = "Option::is_none")]
1432    pub next_style_id: Option<String>,
1433    #[serde(skip_serializing_if = "Option::is_none")]
1434    pub align: Option<ParagraphAlignment>,
1435    #[serde(skip_serializing_if = "Option::is_none")]
1436    pub snap_to_grid: Option<bool>,
1437    #[serde(skip_serializing_if = "Option::is_none")]
1438    pub source_has_snap_to_grid: Option<bool>,
1439    #[serde(skip_serializing_if = "Option::is_none")]
1440    pub text_alignment: Option<ParagraphTextAlignment>,
1441    #[serde(skip_serializing_if = "Option::is_none")]
1442    pub source_has_text_alignment: Option<bool>,
1443    #[serde(skip_serializing_if = "Option::is_none")]
1444    pub heading_level: Option<HeadingLevel>,
1445    #[serde(skip_serializing_if = "Option::is_none")]
1446    pub numbering: Option<ParagraphNumbering>,
1447    #[serde(skip_serializing_if = "Option::is_none")]
1448    pub spacing: Option<ParagraphSpacing>,
1449    #[serde(skip_serializing_if = "Option::is_none")]
1450    pub indent: Option<ParagraphIndent>,
1451    #[serde(skip_serializing_if = "Option::is_none")]
1452    pub background_color: Option<String>,
1453    #[serde(skip_serializing_if = "Option::is_none")]
1454    pub borders: Option<ParagraphBorderSet>,
1455    #[serde(skip_serializing_if = "Option::is_none")]
1456    pub tab_stops: Option<Vec<ParagraphTabStop>>,
1457    #[serde(skip_serializing_if = "Option::is_none")]
1458    pub contextual_spacing: Option<bool>,
1459    #[serde(skip_serializing_if = "Option::is_none")]
1460    pub keep_next: Option<bool>,
1461    #[serde(skip_serializing_if = "Option::is_none")]
1462    pub keep_lines: Option<bool>,
1463    #[serde(skip_serializing_if = "Option::is_none")]
1464    pub widow_control: Option<bool>,
1465    #[serde(skip_serializing_if = "Option::is_none")]
1466    pub page_break_before: Option<bool>,
1467    #[serde(skip_serializing_if = "Option::is_none")]
1468    pub run_style: Option<TextStyle>,
1469    #[serde(skip_serializing_if = "Option::is_none")]
1470    pub ui_priority: Option<i64>,
1471    #[serde(skip_serializing_if = "Option::is_none")]
1472    pub is_default: Option<bool>,
1473    #[serde(skip_serializing_if = "Option::is_none")]
1474    pub is_primary: Option<bool>,
1475}
1476
1477/// Mirrors TypeScript `NumberingLevelDefinition.suffix`.
1478#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1479pub enum NumberingLevelSuffix {
1480    #[serde(rename = "tab")]
1481    Tab,
1482    #[serde(rename = "space")]
1483    Space,
1484    #[serde(rename = "nothing")]
1485    Nothing,
1486}
1487
1488/// Mirrors TypeScript `NumberingLevelDefinition`.
1489#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1490#[serde(rename_all = "camelCase")]
1491pub struct NumberingLevelDefinition {
1492    pub ilvl: i64,
1493    #[serde(skip_serializing_if = "Option::is_none")]
1494    pub start: Option<i64>,
1495    #[serde(skip_serializing_if = "Option::is_none")]
1496    pub format: Option<String>,
1497    #[serde(skip_serializing_if = "Option::is_none")]
1498    pub text: Option<String>,
1499    #[serde(skip_serializing_if = "Option::is_none")]
1500    pub suffix: Option<NumberingLevelSuffix>,
1501    /// `w:lvlRestart`: one-based level whose use restarts this level; 0 means
1502    /// this level never restarts. Absent means the OOXML default (restart when
1503    /// any shallower level is used).
1504    #[serde(skip_serializing_if = "Option::is_none")]
1505    pub lvl_restart: Option<i64>,
1506    /// `w:isLgl`: legal numbering — every placeholder in this level's text
1507    /// renders as decimal regardless of the referenced levels' formats.
1508    #[serde(skip_serializing_if = "Option::is_none")]
1509    pub is_lgl: Option<bool>,
1510    #[serde(skip_serializing_if = "Option::is_none")]
1511    pub indent: Option<ParagraphIndent>,
1512    #[serde(skip_serializing_if = "Option::is_none")]
1513    pub run_style: Option<TextStyle>,
1514    #[serde(skip_serializing_if = "Option::is_none")]
1515    pub bullet_font_family: Option<String>,
1516    #[serde(skip_serializing_if = "Option::is_none")]
1517    pub bullet_color: Option<String>,
1518    #[serde(skip_serializing_if = "Option::is_none")]
1519    pub picture_bullet_id: Option<i64>,
1520    #[serde(skip_serializing_if = "Option::is_none")]
1521    pub picture_bullet: Option<NumberingPictureBulletDefinition>,
1522}
1523
1524/// Mirrors TypeScript `NumberingAbstractDefinition`.
1525#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1526#[serde(rename_all = "camelCase")]
1527pub struct NumberingAbstractDefinition {
1528    pub abstract_num_id: i64,
1529    pub levels: Vec<NumberingLevelDefinition>,
1530}
1531
1532/// Mirrors TypeScript `NumberingInstanceDefinition`.
1533#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1534#[serde(rename_all = "camelCase")]
1535pub struct NumberingInstanceDefinition {
1536    pub num_id: i64,
1537    pub abstract_num_id: i64,
1538    #[serde(skip_serializing_if = "Option::is_none")]
1539    pub level_start_overrides: Option<HashMap<String, i64>>,
1540    #[serde(skip_serializing_if = "Option::is_none")]
1541    pub level_overrides: Option<Vec<NumberingLevelDefinition>>,
1542}
1543
1544/// Mirrors TypeScript `NumberingDefinitionSet`.
1545#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1546#[serde(rename_all = "camelCase")]
1547pub struct NumberingDefinitionSet {
1548    pub abstracts: Vec<NumberingAbstractDefinition>,
1549    pub instances: Vec<NumberingInstanceDefinition>,
1550}
1551
1552/// Mirrors TypeScript `NumberingPictureBulletDefinition`.
1553#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1554#[serde(rename_all = "camelCase")]
1555pub struct NumberingPictureBulletDefinition {
1556    pub num_pic_bullet_id: i64,
1557    #[serde(skip_serializing_if = "Option::is_none")]
1558    pub src: Option<String>,
1559    #[serde(skip_serializing_if = "Option::is_none")]
1560    pub width_px: Option<f64>,
1561    #[serde(skip_serializing_if = "Option::is_none")]
1562    pub height_px: Option<f64>,
1563    #[serde(skip_serializing_if = "Option::is_none")]
1564    pub part_name: Option<String>,
1565    #[serde(skip_serializing_if = "Option::is_none")]
1566    pub content_type: Option<String>,
1567}
1568
1569/// Mirrors TypeScript `DocumentNoteDefinition`.
1570#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1571#[serde(rename_all = "camelCase")]
1572pub struct DocumentNoteDefinition {
1573    pub id: i64,
1574    pub text: String,
1575    #[serde(skip_serializing_if = "Option::is_none")]
1576    pub nodes: Option<Vec<DocNode>>,
1577}
1578
1579/// Mirrors TypeScript `DocumentCommentDefinition`.
1580#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1581#[serde(rename_all = "camelCase")]
1582pub struct DocumentCommentDefinition {
1583    #[serde(default, skip_serializing_if = "Option::is_none")]
1584    pub nodes: Option<Vec<DocNode>>,
1585    pub id: i64,
1586    #[serde(skip_serializing_if = "Option::is_none")]
1587    pub author: Option<String>,
1588    #[serde(skip_serializing_if = "Option::is_none")]
1589    pub initials: Option<String>,
1590    #[serde(skip_serializing_if = "Option::is_none")]
1591    pub date: Option<String>,
1592    pub text: String,
1593    #[serde(skip_serializing_if = "Option::is_none")]
1594    pub parent_id: Option<i64>,
1595    #[serde(skip_serializing_if = "Option::is_none")]
1596    pub resolved: Option<bool>,
1597    #[serde(skip_serializing_if = "Option::is_none")]
1598    pub source_xml: Option<String>,
1599    #[serde(skip_serializing_if = "Option::is_none")]
1600    pub extended_paragraph_id: Option<String>,
1601    #[serde(skip_serializing_if = "Option::is_none")]
1602    pub source_resolved: Option<bool>,
1603    #[serde(skip_serializing_if = "Option::is_none")]
1604    pub resolution_dirty: Option<bool>,
1605    #[serde(skip_serializing_if = "Option::is_none")]
1606    pub is_new: Option<bool>,
1607}
1608
1609/// Mirrors TypeScript `DocumentCompatibilitySettings`.
1610#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1611#[serde(rename_all = "camelCase")]
1612pub struct DocumentCompatibilitySettings {
1613    #[serde(skip_serializing_if = "Option::is_none")]
1614    pub adjust_line_height_in_table: Option<bool>,
1615    #[serde(skip_serializing_if = "Option::is_none")]
1616    pub no_leading: Option<bool>,
1617    #[serde(
1618        rename = "suppressTopSpacingWP",
1619        skip_serializing_if = "Option::is_none"
1620    )]
1621    pub suppress_top_spacing_wp: Option<bool>,
1622    #[serde(
1623        rename = "truncateFontHeightsLikeWP6",
1624        skip_serializing_if = "Option::is_none"
1625    )]
1626    pub truncate_font_heights_like_wp6: Option<bool>,
1627    #[serde(skip_serializing_if = "Option::is_none")]
1628    pub no_extra_line_spacing: Option<bool>,
1629    #[serde(rename = "spaceForUL", skip_serializing_if = "Option::is_none")]
1630    pub space_for_ul: Option<bool>,
1631    #[serde(skip_serializing_if = "Option::is_none")]
1632    pub suppress_bottom_spacing: Option<bool>,
1633    #[serde(skip_serializing_if = "Option::is_none")]
1634    pub compatibility_mode: Option<i64>,
1635    #[serde(
1636        rename = "enableOpenTypeFeatures",
1637        skip_serializing_if = "Option::is_none"
1638    )]
1639    pub enable_open_type_features: Option<bool>,
1640    #[serde(skip_serializing_if = "Option::is_none")]
1641    pub suppress_spacing_before_after_page_break: Option<bool>,
1642    #[serde(skip_serializing_if = "Option::is_none")]
1643    pub use_printer_metrics: Option<bool>,
1644    #[serde(skip_serializing_if = "Option::is_none")]
1645    pub use_fixed_html_paragraph_spacing: Option<bool>,
1646    #[serde(skip_serializing_if = "Option::is_none")]
1647    pub do_not_break_wrapped_tables: Option<bool>,
1648    #[serde(skip_serializing_if = "Option::is_none")]
1649    pub do_not_break_constrained_forced_table: Option<bool>,
1650    #[serde(skip_serializing_if = "Option::is_none")]
1651    pub even_and_odd_headers: Option<bool>,
1652}
1653
1654/// Mirrors TypeScript `DocModel.metadata`.
1655#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1656#[serde(rename_all = "camelCase")]
1657pub struct DocModelMetadata {
1658    #[serde(default, skip_serializing_if = "Option::is_none")]
1659    pub document_id: Option<String>,
1660    #[serde(default, skip_serializing_if = "Option::is_none")]
1661    pub source_part_names: Option<std::collections::BTreeMap<String, String>>,
1662    pub source_parts: i64,
1663    pub warnings: Vec<String>,
1664    #[serde(skip_serializing_if = "Option::is_none")]
1665    pub document_page_count: Option<i64>,
1666    #[serde(skip_serializing_if = "Option::is_none")]
1667    pub document_open_tag: Option<String>,
1668    #[serde(skip_serializing_if = "Option::is_none")]
1669    pub document_background_color: Option<String>,
1670    #[serde(skip_serializing_if = "Option::is_none")]
1671    pub section_properties_xml: Option<String>,
1672    #[serde(skip_serializing_if = "Option::is_none")]
1673    pub sections: Option<Vec<DocumentSection>>,
1674    pub header_sections: Vec<HeaderSection>,
1675    pub footer_sections: Vec<FooterSection>,
1676    pub paragraph_styles: Vec<ParagraphStyleDefinition>,
1677    #[serde(skip_serializing_if = "Option::is_none")]
1678    pub default_paragraph_style_id: Option<String>,
1679    #[serde(skip_serializing_if = "Option::is_none")]
1680    pub default_tab_stop_twips: Option<i64>,
1681    #[serde(skip_serializing_if = "Option::is_none")]
1682    pub document_settings_imported: Option<bool>,
1683    #[serde(skip_serializing_if = "Option::is_none")]
1684    pub line_spacing_compatibility_imported: Option<bool>,
1685    #[serde(skip_serializing_if = "Option::is_none")]
1686    pub numbering_definitions: Option<NumberingDefinitionSet>,
1687    #[serde(skip_serializing_if = "Option::is_none")]
1688    pub compatibility: Option<DocumentCompatibilitySettings>,
1689    #[serde(skip_serializing_if = "Option::is_none")]
1690    pub footnotes: Option<Vec<DocumentNoteDefinition>>,
1691    #[serde(skip_serializing_if = "Option::is_none")]
1692    pub endnotes: Option<Vec<DocumentNoteDefinition>>,
1693    #[serde(skip_serializing_if = "Option::is_none")]
1694    pub comments: Option<Vec<DocumentCommentDefinition>>,
1695}
1696
1697/// Mirrors TypeScript `DocModel`.
1698#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1699#[serde(rename_all = "camelCase")]
1700pub struct DocModel {
1701    pub nodes: Vec<DocNode>,
1702    pub metadata: DocModelMetadata,
1703}
1704
1705#[cfg(test)]
1706mod deserialize_tests {
1707    use super::{DocModel, DocNode, HeadingLevel, ParagraphAlignment};
1708
1709    #[test]
1710    fn deserializes_minimal_js_doc_model() {
1711        let json = r#"{"nodes":[{"type":"paragraph","children":[{"type":"text","text":"Hi"}]}],"metadata":{"sourceParts":1,"warnings":[],"headerSections":[],"footerSections":[],"paragraphStyles":[]}}"#;
1712        let model: DocModel = serde_json::from_str(json).expect("should deserialize");
1713        assert_eq!(model.nodes.len(), 1);
1714    }
1715
1716    #[test]
1717    fn deserializes_heading_level_two() {
1718        let json = r#"{"type":"paragraph","style":{"headingLevel":2},"children":[{"type":"text","text":"Added"}]}"#;
1719        let node: DocNode = serde_json::from_str(json).expect("heading level 2");
1720        let DocNode::Paragraph(paragraph) = node else {
1721            panic!("expected paragraph");
1722        };
1723        assert_eq!(
1724            paragraph.style.and_then(|style| style.heading_level),
1725            Some(HeadingLevel::Two)
1726        );
1727    }
1728
1729    #[test]
1730    fn deserializes_align_center() {
1731        let json = r#"{"type":"paragraph","style":{"align":"center"},"children":[{"type":"text","text":"Added"}]}"#;
1732        let node: DocNode = serde_json::from_str(json).expect("align center");
1733        let DocNode::Paragraph(paragraph) = node else {
1734            panic!("expected paragraph");
1735        };
1736        assert_eq!(
1737            paragraph.style.and_then(|style| style.align),
1738            Some(ParagraphAlignment::Center)
1739        );
1740    }
1741}
1742
1743/// Fully resolved formatting shared until a caller edits its value.
1744#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
1745#[serde(transparent)]
1746pub struct SharedParagraphStyle(std::sync::Arc<ParagraphStyle>);
1747
1748impl SharedParagraphStyle {
1749    pub fn into_owned(self) -> ParagraphStyle {
1750        std::sync::Arc::unwrap_or_clone(self.0)
1751    }
1752}
1753
1754impl From<ParagraphStyle> for SharedParagraphStyle {
1755    fn from(style: ParagraphStyle) -> Self {
1756        Self(std::sync::Arc::new(style))
1757    }
1758}
1759
1760impl std::ops::Deref for SharedParagraphStyle {
1761    type Target = ParagraphStyle;
1762
1763    fn deref(&self) -> &ParagraphStyle {
1764        &self.0
1765    }
1766}
1767
1768impl std::ops::DerefMut for SharedParagraphStyle {
1769    fn deref_mut(&mut self) -> &mut ParagraphStyle {
1770        std::sync::Arc::make_mut(&mut self.0)
1771    }
1772}