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