lopdf_table/
lib.rs

1//! A composable table drawing library for PDFs built on lopdf
2//!
3//! This library provides an ergonomic API for creating tables in PDF documents
4//! with support for automatic sizing, custom styling, and flexible layouts.
5
6use lopdf::{Document, Object, ObjectId};
7use tracing::{debug, instrument, trace};
8
9mod constants;
10mod drawing;
11mod drawing_utils;
12pub mod error;
13pub mod layout;
14pub mod style;
15pub mod table;
16mod text;
17
18// Re-export constants for public use
19pub use constants::*;
20
21pub use error::{Result, TableError};
22pub use style::{
23    Alignment, BorderStyle, CellStyle, Color, RowStyle, TableStyle, VerticalAlignment,
24};
25pub use table::{Cell, ColumnWidth, Row, Table};
26
27/// Result of drawing a paginated table
28#[derive(Debug, Clone)]
29pub struct PagedTableResult {
30    /// Page IDs where table parts were drawn
31    pub page_ids: Vec<ObjectId>,
32    /// Total number of pages used
33    pub total_pages: usize,
34    /// Final position after drawing (x, y on last page)
35    pub final_position: (f32, f32),
36}
37
38/// Extension trait for lopdf::Document to add table drawing capabilities
39pub trait TableDrawing {
40    /// Draw a table at the specified position on a page
41    ///
42    /// # Arguments
43    /// * `page_id` - The object ID of the page to draw on
44    /// * `table` - The table to draw
45    /// * `position` - The (x, y) position of the table's top-left corner
46    ///
47    /// # Returns
48    /// Returns Ok(()) on success, or an error if the table cannot be drawn
49    fn draw_table(&mut self, page_id: ObjectId, table: Table, position: (f32, f32)) -> Result<()>;
50
51    /// Add a table to a page with automatic positioning
52    ///
53    /// This method will find an appropriate position on the page for the table
54    fn add_table_to_page(&mut self, page_id: ObjectId, table: Table) -> Result<()>;
55
56    /// Create table content operations without adding to document
57    ///
58    /// Useful for custom positioning or combining with other content
59    fn create_table_content(&self, table: &Table, position: (f32, f32)) -> Result<Vec<Object>>;
60
61    /// Draw a table with automatic page wrapping
62    ///
63    /// This method will automatically create new pages as needed when the table
64    /// exceeds the available space on the current page. Header rows will be
65    /// repeated on each new page if configured.
66    ///
67    /// # Arguments
68    /// * `page_id` - The object ID of the starting page
69    /// * `table` - The table to draw
70    /// * `position` - The (x, y) position of the table's top-left corner
71    ///
72    /// # Returns
73    /// Returns a PagedTableResult with information about pages used
74    fn draw_table_with_pagination(
75        &mut self,
76        page_id: ObjectId,
77        table: Table,
78        position: (f32, f32),
79    ) -> Result<PagedTableResult>;
80}
81
82impl TableDrawing for Document {
83    #[instrument(skip(self, table), fields(table_rows = table.rows.len()))]
84    fn draw_table(&mut self, page_id: ObjectId, table: Table, position: (f32, f32)) -> Result<()> {
85        debug!("Drawing table at position {:?}", position);
86
87        // Calculate layout
88        let layout = layout::calculate_layout(&table)?;
89        trace!("Calculated layout: {:?}", layout);
90
91        // Generate drawing operations
92        let operations = drawing::generate_table_operations(&table, &layout, position)?;
93
94        // Add content to page
95        drawing::add_operations_to_page(self, page_id, operations)?;
96
97        Ok(())
98    }
99
100    #[instrument(skip(self, table))]
101    fn add_table_to_page(&mut self, page_id: ObjectId, table: Table) -> Result<()> {
102        // For now, default to top-left with some margin
103        let position = (DEFAULT_MARGIN, A4_HEIGHT - DEFAULT_MARGIN - 50.0);
104        self.draw_table(page_id, table, position)
105    }
106
107    fn create_table_content(&self, table: &Table, position: (f32, f32)) -> Result<Vec<Object>> {
108        let layout = layout::calculate_layout(table)?;
109        drawing::generate_table_operations(table, &layout, position)
110    }
111
112    #[instrument(skip(self, table), fields(table_rows = table.rows.len()))]
113    fn draw_table_with_pagination(
114        &mut self,
115        page_id: ObjectId,
116        table: Table,
117        position: (f32, f32),
118    ) -> Result<PagedTableResult> {
119        debug!("Drawing paginated table at position {:?}", position);
120
121        // Calculate layout
122        let layout = layout::calculate_layout(&table)?;
123        trace!("Calculated layout: {:?}", layout);
124
125        // Generate paginated drawing operations
126        let result = drawing::draw_table_paginated(self, page_id, &table, &layout, position)?;
127
128        Ok(result)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_basic_table_creation() {
138        let table = Table::new()
139            .add_row(Row::new(vec![Cell::new("Header 1"), Cell::new("Header 2")]))
140            .add_row(Row::new(vec![Cell::new("Data 1"), Cell::new("Data 2")]));
141
142        assert_eq!(table.rows.len(), 2);
143        assert_eq!(table.rows[0].cells.len(), 2);
144    }
145}