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