table-renderer 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-renderer
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A Python library for rendering structured tables as high-quality images or HTML.
5
5
  Keywords: table,renderer,rendering,image,html,weasyprint,pdfium,report
6
6
  Author: pTaunium
@@ -29,9 +29,10 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  <!-- README-I18N:END -->
31
31
 
32
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
33
33
  [![Python Version](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](pyproject.toml)
34
34
  [![CI](https://github.com/pTaunium/table-renderer/actions/workflows/ci.yml/badge.svg)](https://github.com/pTaunium/table-renderer/actions)
35
+ [![codecov](https://codecov.io/gh/pTaunium/table-renderer/graph/badge.svg)](https://codecov.io/gh/pTaunium/table-renderer)
35
36
 
36
37
  `table-renderer` is a powerful and lightweight Python library designed to convert structured tables into high-quality images (PNG, JPG, WebP) or HTML. By combining the layout power of CSS with an intuitive Python API, it's perfect for automated reporting, Kubernetes CronJobs, or any data visualization task.
37
38
 
@@ -41,6 +42,7 @@ Description-Content-Type: text/markdown
41
42
  - **Business Friendly**: Uses `pypdfium2` (Apache-2.0) and `WeasyPrint` for a 100% MIT-compatible dependency chain.
42
43
  - **High-Quality Rendering**: Powered by WeasyPrint (CSS engine), supporting complex text wrapping, alignment, and cell merging.
43
44
  - **Auto-Crop**: Automatically crops images to the actual table area using visual detection, eliminating unnecessary white space.
45
+ - **Continuous Long Tables**: Automatically stitches multiple PDF pages into a single continuous long image, preventing content from being cut off.
44
46
  - **Object-Oriented API**: Clean `Table` -> `Row/Column` -> `Cell` hierarchy with fluent interface support.
45
47
  - **Cascading Styles**: Manage visual styles effortlessly with "Global Defaults + Local Overrides."
46
48
  - **Multi-language Font Support**: Support for font stacks (e.g., different fonts for English and Chinese).
@@ -81,7 +83,7 @@ from table_renderer import Table
81
83
  # 1. Initialize a 3x3 table
82
84
  table = Table(3, 3)
83
85
  table.set_width(600)
84
- table.set_border(1, "black", "solid")
86
+ table.set_border(width=1, color="black", style="solid")
85
87
 
86
88
  # 2. Set header style (Row 0)
87
89
  header = table.get_row(0)
@@ -135,12 +137,14 @@ table.set_font(family="Roboto-Regular, NotoSansTC-Medium")
135
137
 
136
138
  ### 3. Embedding Images in Cells
137
139
 
138
- You can easily embed local images into any cell.
140
+ You can embed both local images and remote URLs into any cell.
139
141
 
140
142
  ```python
141
- # Set an image with optional width/height constraints
143
+ # Set a local image
142
144
  table.cell(1, 0).set_image("path/to/logo.png", width=50)
143
- table.cell(1, 0).set_text("Company Logo") # Text appears below the image
145
+
146
+ # Set a remote image URL
147
+ table.cell(1, 1).set_image("https://example.com/image.jpg", width=100)
144
148
  ```
145
149
 
146
150
  ### 4. Layout & Sizing
@@ -153,6 +157,15 @@ table.get_column(0).set_width(200) # Fixed 200px
153
157
  table.get_column(1).set_width("auto") # Distribute remaining space
154
158
  ```
155
159
 
160
+ **Handling Extremely Wide Tables:**
161
+ The library dynamically estimates canvas width to prevent clipping. However, if your table contains unbreakable strings (like long URLs) and relies purely on auto-layout, you can manually override the canvas width. The auto-crop feature will automatically trim any excess white space.
162
+
163
+ ```python
164
+ # Force a massive canvas width; auto-crop will cleanly trim the excess
165
+ table.set_width(15000)
166
+ table.to_image("wide_table.png")
167
+ ```
168
+
156
169
  ### 5. DPI, Formats & Padding
157
170
 
158
171
  Fine-tune your output image quality and margins.
@@ -177,9 +190,8 @@ FROM python:3.12-slim
177
190
  # Install system dependencies for WeasyPrint rendering and fonts
178
191
  RUN apt-get update && apt-get install -y --no-install-recommends \
179
192
  libpango-1.0-0 \
180
- libharfbuzz0b \
181
193
  libpangoft2-1.0-0 \
182
- libpangocairo-1.0-0 \
194
+ libharfbuzz-subset0 \
183
195
  fonts-noto-cjk \
184
196
  && rm -rf /var/lib/apt/lists/*
185
197
 
@@ -6,9 +6,10 @@
6
6
 
7
7
  <!-- README-I18N:END -->
8
8
 
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
10
  [![Python Version](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](pyproject.toml)
11
11
  [![CI](https://github.com/pTaunium/table-renderer/actions/workflows/ci.yml/badge.svg)](https://github.com/pTaunium/table-renderer/actions)
12
+ [![codecov](https://codecov.io/gh/pTaunium/table-renderer/graph/badge.svg)](https://codecov.io/gh/pTaunium/table-renderer)
12
13
 
13
14
  `table-renderer` is a powerful and lightweight Python library designed to convert structured tables into high-quality images (PNG, JPG, WebP) or HTML. By combining the layout power of CSS with an intuitive Python API, it's perfect for automated reporting, Kubernetes CronJobs, or any data visualization task.
14
15
 
@@ -18,6 +19,7 @@
18
19
  - **Business Friendly**: Uses `pypdfium2` (Apache-2.0) and `WeasyPrint` for a 100% MIT-compatible dependency chain.
19
20
  - **High-Quality Rendering**: Powered by WeasyPrint (CSS engine), supporting complex text wrapping, alignment, and cell merging.
20
21
  - **Auto-Crop**: Automatically crops images to the actual table area using visual detection, eliminating unnecessary white space.
22
+ - **Continuous Long Tables**: Automatically stitches multiple PDF pages into a single continuous long image, preventing content from being cut off.
21
23
  - **Object-Oriented API**: Clean `Table` -> `Row/Column` -> `Cell` hierarchy with fluent interface support.
22
24
  - **Cascading Styles**: Manage visual styles effortlessly with "Global Defaults + Local Overrides."
23
25
  - **Multi-language Font Support**: Support for font stacks (e.g., different fonts for English and Chinese).
@@ -58,7 +60,7 @@ from table_renderer import Table
58
60
  # 1. Initialize a 3x3 table
59
61
  table = Table(3, 3)
60
62
  table.set_width(600)
61
- table.set_border(1, "black", "solid")
63
+ table.set_border(width=1, color="black", style="solid")
62
64
 
63
65
  # 2. Set header style (Row 0)
64
66
  header = table.get_row(0)
@@ -112,12 +114,14 @@ table.set_font(family="Roboto-Regular, NotoSansTC-Medium")
112
114
 
113
115
  ### 3. Embedding Images in Cells
114
116
 
115
- You can easily embed local images into any cell.
117
+ You can embed both local images and remote URLs into any cell.
116
118
 
117
119
  ```python
118
- # Set an image with optional width/height constraints
120
+ # Set a local image
119
121
  table.cell(1, 0).set_image("path/to/logo.png", width=50)
120
- table.cell(1, 0).set_text("Company Logo") # Text appears below the image
122
+
123
+ # Set a remote image URL
124
+ table.cell(1, 1).set_image("https://example.com/image.jpg", width=100)
121
125
  ```
122
126
 
123
127
  ### 4. Layout & Sizing
@@ -130,6 +134,15 @@ table.get_column(0).set_width(200) # Fixed 200px
130
134
  table.get_column(1).set_width("auto") # Distribute remaining space
131
135
  ```
132
136
 
137
+ **Handling Extremely Wide Tables:**
138
+ The library dynamically estimates canvas width to prevent clipping. However, if your table contains unbreakable strings (like long URLs) and relies purely on auto-layout, you can manually override the canvas width. The auto-crop feature will automatically trim any excess white space.
139
+
140
+ ```python
141
+ # Force a massive canvas width; auto-crop will cleanly trim the excess
142
+ table.set_width(15000)
143
+ table.to_image("wide_table.png")
144
+ ```
145
+
133
146
  ### 5. DPI, Formats & Padding
134
147
 
135
148
  Fine-tune your output image quality and margins.
@@ -154,9 +167,8 @@ FROM python:3.12-slim
154
167
  # Install system dependencies for WeasyPrint rendering and fonts
155
168
  RUN apt-get update && apt-get install -y --no-install-recommends \
156
169
  libpango-1.0-0 \
157
- libharfbuzz0b \
158
170
  libpangoft2-1.0-0 \
159
- libpangocairo-1.0-0 \
171
+ libharfbuzz-subset0 \
160
172
  fonts-noto-cjk \
161
173
  && rm -rf /var/lib/apt/lists/*
162
174
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "table-renderer"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "A Python library for rendering structured tables as high-quality images or HTML."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,11 @@
1
+ import importlib.metadata
2
+
3
+ from .models import Cell, Column, Row, Style, StyledObject, Table
4
+
5
+ try:
6
+ __version__ = importlib.metadata.version("table-renderer")
7
+ except importlib.metadata.PackageNotFoundError:
8
+ # Package is not installed (e.g. during local development without pip install -e .)
9
+ __version__ = "0.2.0"
10
+
11
+ __all__ = ["Cell", "Column", "Row", "Style", "StyledObject", "Table", "__version__"]
@@ -12,6 +12,7 @@ class StyledObject:
12
12
 
13
13
  def set_font(
14
14
  self,
15
+ *,
15
16
  size: int | None = None,
16
17
  color: str | None = None,
17
18
  family: str | None = None,
@@ -50,6 +51,7 @@ class StyledObject:
50
51
 
51
52
  def set_align(
52
53
  self,
54
+ *,
53
55
  horizontal: Literal["left", "center", "right"] | None = None,
54
56
  vertical: Literal["top", "middle", "bottom"] | None = None,
55
57
  ) -> Self:
@@ -69,6 +71,7 @@ class StyledObject:
69
71
 
70
72
  def set_border(
71
73
  self,
74
+ *,
72
75
  width: int | None = None,
73
76
  color: str | None = None,
74
77
  style: Literal["solid", "dashed", "dotted"] | None = None,
@@ -38,7 +38,7 @@ class Cell(StyledObject):
38
38
  return self
39
39
 
40
40
  def set_image(
41
- self, path: str, width: int | None = None, height: int | None = None
41
+ self, path: str, *, width: int | None = None, height: int | None = None
42
42
  ) -> Self:
43
43
  """
44
44
  Set an image to be displayed in the cell.
@@ -56,7 +56,7 @@ class Cell(StyledObject):
56
56
  self.image_height = height
57
57
  return self
58
58
 
59
- def span(self, rows: int = 1, cols: int = 1) -> Self:
59
+ def span(self, *, rows: int = 1, cols: int = 1) -> Self:
60
60
  """
61
61
  Set the row and column span for merging.
62
62
 
@@ -112,7 +112,7 @@ class Table(StyledObject):
112
112
  self.font_files.append(font_path)
113
113
  return self
114
114
 
115
- def to_image(self, output_path: str, dpi: int = 144, padding: int = 10) -> None:
115
+ def to_image(self, output_path: str, *, dpi: int = 144, padding: int = 10) -> None:
116
116
  """
117
117
  Render the table to an image file.
118
118
 
@@ -13,6 +13,7 @@ HTML_TEMPLATE = """
13
13
  <head>
14
14
  <style>
15
15
  @page {
16
+ size: {{ page_width }}px {{ page_height }}px; /* Dynamically estimated size */
16
17
  margin: 0;
17
18
  }
18
19
  body {
@@ -116,16 +117,40 @@ def render_to_html(table: "Table") -> str:
116
117
  # Handle Image URL resolution
117
118
  cell.image_url = ""
118
119
  if cell.image_path:
119
- abs_img_path = os.path.abspath(cell.image_path)
120
- cell.image_url = f"file://{abs_img_path}"
120
+ if cell.image_path.startswith(("http://", "https://")):
121
+ cell.image_url = cell.image_path
122
+ else:
123
+ abs_img_path = os.path.abspath(cell.image_path)
124
+ cell.image_url = f"file://{abs_img_path}"
121
125
 
122
126
  # Attach final style string for template
123
127
  cell.final_style = final_style.to_css()
124
128
  row_data.append(cell)
125
129
  cells_data.append(row_data)
126
130
 
131
+ # Estimate safe canvas width to prevent clipping
132
+ # Base it on the number of columns if explicit widths aren't provided (assume ~100px min per column)
133
+ col_count = len(table._cells[0]) if table._cells else 0
134
+ estimated_width = max(2000, col_count * 100)
135
+
136
+ if isinstance(table.width, int):
137
+ estimated_width = max(estimated_width, table.width + 100)
138
+
139
+ col_width_sum = 0
140
+ for c in table._col_objects:
141
+ if isinstance(c.width, int):
142
+ col_width_sum += c.width
143
+ estimated_width = max(estimated_width, col_width_sum + 100)
144
+
145
+ # Estimate safe canvas height to optimize rendering speed for small tables, capped at 5000px
146
+ # Assume a generous average of 40px per row plus 100px padding
147
+ estimated_height = len(table._cells) * 40 + 100
148
+ estimated_height = min(max(estimated_height, 500), 5000)
149
+
127
150
  template = Template(HTML_TEMPLATE)
128
151
  return template.render(
152
+ page_width=estimated_width,
153
+ page_height=estimated_height,
129
154
  table_width=f"{table.width}px" if isinstance(table.width, int) else table.width,
130
155
  table_style=table.style.to_css(),
131
156
  font_faces=font_faces,
@@ -147,6 +172,7 @@ def render_to_html(table: "Table") -> str:
147
172
  def render_to_image(
148
173
  table: "Table",
149
174
  output_path: str,
175
+ *,
150
176
  dpi: int = 144,
151
177
  padding: int = 10,
152
178
  ) -> None:
@@ -168,16 +194,30 @@ def render_to_image(
168
194
 
169
195
  # Load the PDF from bytes
170
196
  with pdfium.PdfDocument(pdf_bytes) as pdf:
171
- page = pdf[0]
172
-
173
- # Render page to a bitmap
174
- # scale_factor: 72 is the default PDF DPI
197
+ page_images = []
175
198
  scale_factor = dpi / 72
176
- bitmap = page.render(scale=scale_factor)
177
199
 
178
- # Convert pdfium bitmap to Pillow Image
179
- pil_image = bitmap.to_pil()
180
- bitmap.close()
200
+ # Render each page to a PIL Image
201
+ for page in pdf:
202
+ # fill_color=(255, 255, 255, 0) ensures the background is transparent
203
+ bitmap = page.render(scale=scale_factor, fill_color=(255, 255, 255, 0))
204
+ pil_page = bitmap.to_pil()
205
+ page_images.append(pil_page)
206
+ bitmap.close()
207
+
208
+ if not page_images:
209
+ return
210
+
211
+ # Stitch all pages vertically into one long image
212
+ total_width = max(img.width for img in page_images)
213
+ total_height = sum(img.height for img in page_images)
214
+
215
+ # Create a large canvas
216
+ pil_image = Image.new("RGBA", (total_width, total_height), (255, 255, 255, 0))
217
+ y_offset = 0
218
+ for img in page_images:
219
+ pil_image.paste(img, (0, y_offset))
220
+ y_offset += img.height
181
221
 
182
222
  # Visual Auto-Crop: Find the bounding box of non-white pixels
183
223
  # 1. Convert to RGB to ensure we have a standard background to check
@@ -203,6 +243,12 @@ def render_to_image(
203
243
  pil_image = pil_image.crop((left, top, right, bottom))
204
244
 
205
245
  # Save to the requested format (Pillow handles most formats)
246
+ if output_path.lower().endswith((".jpg", ".jpeg")) and pil_image.mode == "RGBA":
247
+ # Create a white background image and paste the RGBA image on it to flatten it
248
+ rgb_image = Image.new("RGB", pil_image.size, (255, 255, 255))
249
+ rgb_image.paste(pil_image, mask=pil_image.split()[3])
250
+ pil_image = rgb_image
251
+
206
252
  pil_image.save(output_path)
207
253
 
208
254
 
@@ -1,3 +0,0 @@
1
- from .models import Cell, Column, Row, Style, StyledObject, Table
2
-
3
- __all__ = ["Cell", "Column", "Row", "Style", "StyledObject", "Table"]