Skip to main content

office2pdf/ir/
elements.rs

1use std::collections::BTreeMap;
2
3use super::style::{Alignment, Color, ParagraphStyle, TabLeader, TextStyle};
4
5/// Header or footer content for flow pages.
6#[derive(Debug, Clone)]
7pub struct HeaderFooter {
8    pub paragraphs: Vec<HeaderFooterParagraph>,
9    /// Distance in points from the page edge, as specified by the section page margins.
10    pub distance_from_edge: Option<f64>,
11}
12
13/// A paragraph within a header or footer.
14#[derive(Debug, Clone)]
15pub struct HeaderFooterParagraph {
16    pub style: ParagraphStyle,
17    pub elements: Vec<HFInline>,
18    pub border: Option<CellBorder>,
19    pub frame: Option<HeaderFooterFrame>,
20}
21
22/// Page- or margin-relative positioning for a header/footer paragraph frame.
23#[derive(Debug, Clone, PartialEq)]
24pub struct HeaderFooterFrame {
25    pub x: Option<f64>,
26    pub y: Option<f64>,
27    pub width: Option<f64>,
28    pub height: Option<f64>,
29    pub horizontal_anchor: FrameAnchor,
30    pub vertical_anchor: FrameAnchor,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum FrameAnchor {
35    Page,
36    Margin,
37    #[default]
38    Text,
39}
40
41/// A position-relative tab (`w:ptab`) inside header/footer content.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct PositionedTab {
44    pub alignment: PositionedTabAlignment,
45    pub relative_to: PositionedTabRelativeTo,
46    pub leader: TabLeader,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
50pub enum PositionedTabAlignment {
51    Center,
52    #[default]
53    Left,
54    Right,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub enum PositionedTabRelativeTo {
59    Indent,
60    #[default]
61    Margin,
62}
63
64/// An inline element within a header or footer paragraph.
65#[derive(Debug, Clone)]
66pub enum HFInline {
67    /// A text run with styling.
68    Run(Run),
69    /// An inline image embedded in the header or footer part.
70    Image(ImageData),
71    /// Current page number field.
72    PageNumber,
73    /// Total page count field.
74    TotalPages,
75    /// Alignment tab positioned relative to the paragraph indent or page margin.
76    PositionedTab(PositionedTab),
77}
78
79/// Block-level content elements.
80#[derive(Debug, Clone)]
81pub enum Block {
82    Paragraph(Paragraph),
83    Table(Table),
84    Image(ImageData),
85    /// Consecutive inline images from one flow paragraph.
86    InlineImages(Vec<ImageData>),
87    FloatingImage(FloatingImage),
88    FloatingTextBox(FloatingTextBox),
89    FloatingShape(FloatingShape),
90    List(List),
91    MathEquation(MathEquation),
92    Chart(Chart),
93    PageBreak,
94    ColumnBreak,
95}
96
97/// A chart extracted from an embedded chart object.
98#[derive(Debug, Clone)]
99pub struct Chart {
100    /// The type of chart (bar, line, pie, etc.).
101    pub chart_type: ChartType,
102    /// Optional chart title.
103    pub title: Option<String>,
104    /// Category labels (x-axis or pie slice names).
105    pub categories: Vec<String>,
106    /// Data series.
107    pub series: Vec<ChartSeries>,
108}
109
110/// The type of chart.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum ChartType {
113    Bar,
114    Column,
115    Line,
116    Pie,
117    Area,
118    Scatter,
119    Other(String),
120}
121
122/// A data series within a chart.
123#[derive(Debug, Clone)]
124pub struct ChartSeries {
125    /// Optional series name.
126    pub name: Option<String>,
127    /// Data values for this series.
128    pub values: Vec<f64>,
129}
130
131/// A math equation (from OMML or similar).
132#[derive(Debug, Clone)]
133pub struct MathEquation {
134    /// Typst math notation content (without surrounding `$` delimiters).
135    pub content: String,
136    /// Whether this is a display equation (centered, on its own line) vs inline.
137    pub display: bool,
138}
139
140/// How text wraps around a floating image.
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum WrapMode {
143    /// Text wraps around the image on both sides (square bounding box).
144    Square,
145    /// Text wraps tightly around the image contour.
146    Tight,
147    /// Text appears above and below the image only (no side wrapping).
148    TopAndBottom,
149    /// Image is behind the text (no wrapping, text flows over).
150    Behind,
151    /// Image is in front of the text (no wrapping, image covers text).
152    InFront,
153    /// No text wrapping.
154    None,
155}
156
157/// A floating image with positioning and text wrap mode.
158#[derive(Debug, Clone)]
159pub struct FloatingImage {
160    pub image: ImageData,
161    pub wrap_mode: WrapMode,
162    /// Horizontal offset in points from the anchor reference.
163    pub offset_x: f64,
164    /// Vertical offset in points from the anchor reference.
165    pub offset_y: f64,
166}
167
168/// A floating text box with positioning, size, and text wrap mode.
169#[derive(Debug, Clone)]
170pub struct FloatingTextBox {
171    pub content: Vec<Block>,
172    pub wrap_mode: WrapMode,
173    pub width: f64,
174    pub height: f64,
175    pub padding: Insets,
176    pub vertical_align: TextBoxVerticalAlign,
177    /// Horizontal offset in points from the anchor reference.
178    pub offset_x: f64,
179    /// Vertical offset in points from the anchor reference.
180    pub offset_y: f64,
181}
182
183/// A floating geometric shape (rectangle, line/arrow, ellipse, …) positioned
184/// with an anchor offset. Used for DrawingML word-processing shapes (`wps:wsp`)
185/// that carry geometry but no text box — these have no docx-rs representation
186/// and would otherwise be dropped (issue #176).
187#[derive(Debug, Clone)]
188pub struct FloatingShape {
189    pub shape: Shape,
190    /// On-page bounding-box width in points (from `wp:extent`).
191    pub width: f64,
192    /// On-page bounding-box height in points (from `wp:extent`).
193    pub height: f64,
194    /// Horizontal offset in points from the anchor reference.
195    pub offset_x: f64,
196    /// Vertical offset in points from the anchor reference.
197    pub offset_y: f64,
198    pub wrap_mode: WrapMode,
199}
200
201/// Vertical alignment for fixed text box content.
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
203pub enum TextBoxVerticalAlign {
204    #[default]
205    Top,
206    Center,
207    Bottom,
208}
209
210/// A fixed-position text box with content padding and vertical alignment.
211#[derive(Debug, Clone)]
212pub struct TextBoxData {
213    pub content: Vec<Block>,
214    pub padding: Insets,
215    pub vertical_align: TextBoxVerticalAlign,
216    /// Background fill color for the text box.
217    pub fill: Option<Color>,
218    /// Opacity from 0.0 (fully transparent) to 1.0 (fully opaque).
219    pub opacity: Option<f64>,
220    /// Border stroke for the text box.
221    pub stroke: Option<BorderSide>,
222    /// Shape geometry when the text box originates from a non-rectangular shape
223    /// (e.g., `roundRect`, `homePlate`). `None` means default rectangle.
224    pub shape_kind: Option<ShapeKind>,
225    /// When true, text should not wrap — the content width is unconstrained.
226    /// Corresponds to `<a:bodyPr wrap="none"/>` in OOXML.
227    pub no_wrap: bool,
228    /// Whether the source requested PowerPoint autofit behavior for this box.
229    pub auto_fit: bool,
230    /// Clockwise text rotation from `<a:bodyPr vert>` ("vert" = 90°,
231    /// "vert270" = 270°); the box geometry itself stays unrotated.
232    pub text_rotation_deg: Option<f64>,
233}
234
235/// The kind of list: ordered (numbered) or unordered (bulleted).
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum ListKind {
238    Ordered,
239    Unordered,
240}
241
242/// Numbering configuration for a specific list level.
243#[derive(Debug, Clone, PartialEq)]
244pub struct ListLevelStyle {
245    pub kind: ListKind,
246    /// Optional Typst numbering pattern derived from Word's lvlText/numFmt.
247    pub numbering_pattern: Option<String>,
248    /// Whether parent numbers should be shown for nested ordered lists.
249    pub full_numbering: bool,
250    /// Optional concrete marker text for unordered PPTX bullet lists.
251    pub marker_text: Option<String>,
252    /// Optional concrete marker presentation resolved from the source format.
253    pub marker_style: Option<TextStyle>,
254}
255
256/// A list block containing items at various indent levels.
257#[derive(Debug, Clone)]
258pub struct List {
259    pub kind: ListKind,
260    pub items: Vec<ListItem>,
261    /// Per-level list style overrides. Levels not present fall back to `kind`.
262    pub level_styles: BTreeMap<u32, ListLevelStyle>,
263}
264
265/// A single list item with content and indent level.
266#[derive(Debug, Clone)]
267pub struct ListItem {
268    pub content: Vec<Paragraph>,
269    pub level: u32,
270    /// Ordered list item number when this item begins a new numbering run.
271    pub start_at: Option<u32>,
272}
273
274/// A paragraph consisting of styled text runs.
275#[derive(Debug, Clone)]
276pub struct Paragraph {
277    pub style: ParagraphStyle,
278    pub runs: Vec<Run>,
279}
280
281/// A run of text with uniform formatting.
282#[derive(Debug, Clone)]
283pub struct Run {
284    pub text: String,
285    pub style: TextStyle,
286    /// Optional hyperlink URL. When present, the run is rendered as a clickable link.
287    pub href: Option<String>,
288    /// Optional footnote/endnote content. When present, a footnote marker is emitted and
289    /// the content is rendered at the bottom of the page.
290    pub footnote: Option<String>,
291}
292
293/// A table.
294#[derive(Debug, Clone, Default)]
295pub struct Table {
296    pub rows: Vec<TableRow>,
297    pub column_widths: Vec<f64>,
298    /// Number of leading rows that should repeat as the table header.
299    pub header_row_count: usize,
300    /// Optional block alignment for the table within the flow.
301    pub alignment: Option<Alignment>,
302    /// Default cell padding applied by the table when cells don't override it.
303    pub default_cell_padding: Option<Insets>,
304    /// When true, row heights should be derived from content instead of forced to
305    /// the exact source row sizes. PowerPoint often renders slide tables this way.
306    pub use_content_driven_row_heights: bool,
307    /// Default vertical alignment for cells that don't override it.
308    /// Excel prints cells bottom-aligned by default; Word/PowerPoint keep
309    /// the renderer default (top).
310    pub default_vertical_align: Option<CellVerticalAlign>,
311}
312
313/// A table row.
314#[derive(Debug, Clone)]
315pub struct TableRow {
316    pub cells: Vec<TableCell>,
317    pub height: Option<f64>,
318}
319
320/// A data bar rendering within a cell (conditional formatting).
321#[derive(Debug, Clone)]
322pub struct DataBarInfo {
323    /// Bar color.
324    pub color: Color,
325    /// Fill percentage from 0.0 to 1.0.
326    pub fill_pct: f64,
327}
328
329/// Vertical alignment within a table cell.
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum CellVerticalAlign {
332    Top,
333    Center,
334    Bottom,
335}
336
337/// Insets/padding in points.
338#[derive(Debug, Clone, Copy, PartialEq, Default)]
339pub struct Insets {
340    pub top: f64,
341    pub right: f64,
342    pub bottom: f64,
343    pub left: f64,
344}
345
346/// A table cell.
347#[derive(Debug, Clone)]
348pub struct TableCell {
349    pub content: Vec<Block>,
350    pub col_span: u32,
351    pub row_span: u32,
352    pub border: Option<CellBorder>,
353    pub background: Option<Color>,
354    /// DataBar conditional formatting render info.
355    pub data_bar: Option<DataBarInfo>,
356    /// IconSet text symbol prepended to cell content.
357    pub icon_text: Option<String>,
358    /// Fill color of the IconSet symbol (Excel draws icons in band colors).
359    pub icon_color: Option<Color>,
360    /// Excel text spill: total width in points the content may paint across
361    /// (own column plus consecutive empty columns to the right). Content is
362    /// laid out on one line and clipped to this width instead of wrapping.
363    pub spill_width: Option<f64>,
364    /// Vertical alignment of cell content.
365    pub vertical_align: Option<CellVerticalAlign>,
366    /// Optional cell padding override in points.
367    pub padding: Option<Insets>,
368}
369
370impl Default for TableCell {
371    fn default() -> Self {
372        Self {
373            content: Vec::new(),
374            col_span: 1,
375            row_span: 1,
376            border: None,
377            background: None,
378            data_bar: None,
379            icon_text: None,
380            icon_color: None,
381            spill_width: None,
382            vertical_align: None,
383            padding: None,
384        }
385    }
386}
387
388/// Cell border specification.
389#[derive(Debug, Clone, Default)]
390pub struct CellBorder {
391    pub top: Option<BorderSide>,
392    pub bottom: Option<BorderSide>,
393    pub left: Option<BorderSide>,
394    pub right: Option<BorderSide>,
395}
396
397/// Border line style (dash pattern).
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
399pub enum BorderLineStyle {
400    #[default]
401    Solid,
402    Dashed,
403    Dotted,
404    DashDot,
405    DashDotDot,
406    Double,
407    None,
408}
409
410/// A single border side.
411#[derive(Debug, Clone)]
412pub struct BorderSide {
413    pub width: f64,
414    pub color: Color,
415    pub style: BorderLineStyle,
416}
417
418/// Fractions of the source image cropped away from each edge.
419#[derive(Debug, Clone, Copy, PartialEq, Default)]
420pub struct ImageCrop {
421    pub left: f64,
422    pub top: f64,
423    pub right: f64,
424    pub bottom: f64,
425}
426
427impl ImageCrop {
428    pub fn is_empty(&self) -> bool {
429        self.left == 0.0 && self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0
430    }
431}
432
433/// Image data.
434#[derive(Debug, Clone)]
435pub struct ImageData {
436    pub data: Vec<u8>,
437    pub format: ImageFormat,
438    pub width: Option<f64>,
439    pub height: Option<f64>,
440    pub crop: Option<ImageCrop>,
441    /// Optional border stroke around the image.
442    pub stroke: Option<BorderSide>,
443    /// Horizontal placement inherited from the containing paragraph
444    /// (flow documents); None renders at the flow default (left).
445    pub alignment: Option<Alignment>,
446    /// Clip geometry from the picture's `<a:prstGeom>` (crop to shape).
447    pub clip_shape: Option<ImageClipShape>,
448    /// Outer shadow effect (`a:effectLst/a:outerShdw` on `p:pic`).
449    pub shadow: Option<Shadow>,
450}
451
452/// Supported picture clip geometries (PowerPoint "crop to shape").
453#[derive(Debug, Clone, Copy, PartialEq)]
454pub enum ImageClipShape {
455    /// Rounded rectangle with the corner radius as a fraction of the
456    /// shorter side (PowerPoint's roundRect `adj`, default 1/6 ≈ 0.1667).
457    RoundedRect(f64),
458    Ellipse,
459}
460
461/// Supported image formats.
462#[derive(Debug, Clone, Copy, PartialEq, Eq)]
463pub enum ImageFormat {
464    Png,
465    Jpeg,
466    Gif,
467    Bmp,
468    Tiff,
469    Svg,
470}
471
472impl ImageFormat {
473    /// Return the file extension for this image format.
474    pub fn extension(&self) -> &'static str {
475        match self {
476            Self::Png => "png",
477            Self::Jpeg => "jpeg",
478            Self::Gif => "gif",
479            Self::Bmp => "bmp",
480            Self::Tiff => "tiff",
481            Self::Svg => "svg",
482        }
483    }
484}
485
486/// A node in a SmartArt diagram with hierarchy depth.
487#[derive(Debug, Clone, PartialEq)]
488pub struct SmartArtNode {
489    /// The text content of this node.
490    pub text: String,
491    /// Depth in the hierarchy (0 = top-level node).
492    pub depth: usize,
493}
494
495/// SmartArt diagram content extracted from a presentation.
496///
497/// Contains nodes extracted from the SmartArt data model with hierarchy
498/// information derived from the connection list.
499/// Rendered as an indented tree or numbered steps since full SmartArt
500/// layout engines are not feasible in a pure-Rust converter.
501#[derive(Debug, Clone)]
502pub struct SmartArt {
503    /// Nodes extracted from SmartArt data points with hierarchy depth.
504    pub items: Vec<SmartArtNode>,
505}
506
507/// A single stop in a gradient fill.
508#[derive(Debug, Clone)]
509pub struct GradientStop {
510    /// Position along the gradient axis, from 0.0 (start) to 1.0 (end).
511    pub position: f64,
512    /// Color at this stop.
513    pub color: Color,
514}
515
516/// A linear gradient fill.
517#[derive(Debug, Clone)]
518pub struct GradientFill {
519    /// Gradient color stops, ordered by position.
520    pub stops: Vec<GradientStop>,
521    /// Angle of the linear gradient in degrees (0 = left-to-right, 90 = top-to-bottom).
522    pub angle: f64,
523}
524
525/// An outer shadow effect on a shape.
526#[derive(Debug, Clone)]
527pub struct Shadow {
528    /// Blur radius in points.
529    pub blur_radius: f64,
530    /// Distance from the shape in points.
531    pub distance: f64,
532    /// Direction angle in degrees (0 = right, 90 = down, 180 = left, 270 = up).
533    pub direction: f64,
534    /// Shadow color.
535    pub color: Color,
536    /// Opacity from 0.0 (fully transparent) to 1.0 (fully opaque).
537    pub opacity: f64,
538}
539
540/// Basic geometric shape.
541#[derive(Debug, Clone)]
542pub struct Shape {
543    pub kind: ShapeKind,
544    pub fill: Option<Color>,
545    /// Gradient fill for the shape (takes precedence over solid fill when present).
546    pub gradient_fill: Option<GradientFill>,
547    pub stroke: Option<BorderSide>,
548    /// Rotation angle in degrees (clockwise).
549    pub rotation_deg: Option<f64>,
550    /// Opacity from 0.0 (fully transparent) to 1.0 (fully opaque).
551    pub opacity: Option<f64>,
552    /// Outer shadow effect.
553    pub shadow: Option<Shadow>,
554}
555
556/// Shape types.
557#[derive(Debug, Clone)]
558pub enum ShapeKind {
559    Rectangle,
560    Ellipse,
561    /// Straight line from `(x1,y1)` to `(x2,y2)` in points, relative to element's top-left.
562    Line {
563        x1: f64,
564        y1: f64,
565        x2: f64,
566        y2: f64,
567        head_end: ArrowHead,
568        tail_end: ArrowHead,
569    },
570    /// Multi-segment polyline in points, relative to element's top-left.
571    Polyline {
572        points: Vec<(f64, f64)>,
573        head_end: ArrowHead,
574        tail_end: ArrowHead,
575    },
576    /// Rectangle with rounded corners. `radius_fraction` is relative to `min(width, height)`.
577    RoundedRectangle {
578        radius_fraction: f64,
579    },
580    /// Arbitrary polygon defined by vertices normalized to 0.0–1.0 relative to the bounding box.
581    Polygon {
582        vertices: Vec<(f64, f64)>,
583    },
584}
585
586/// Arrowhead decoration on a line endpoint.
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
588pub enum ArrowHead {
589    #[default]
590    None,
591    Triangle,
592}
593
594#[cfg(test)]
595#[path = "elements_tests.rs"]
596mod tests;