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;