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.
- {table_renderer-0.1.0 → table_renderer-0.2.0}/PKG-INFO +20 -8
- {table_renderer-0.1.0 → table_renderer-0.2.0}/README.md +19 -7
- {table_renderer-0.1.0 → table_renderer-0.2.0}/pyproject.toml +1 -1
- table_renderer-0.2.0/src/table_renderer/__init__.py +11 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/base.py +3 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/cell.py +2 -2
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/table.py +1 -1
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/renderer.py +56 -10
- table_renderer-0.1.0/src/table_renderer/__init__.py +0 -3
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/__init__.py +0 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/column.py +0 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/row.py +0 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/models/style.py +0 -0
- {table_renderer-0.1.0 → table_renderer-0.2.0}/src/table_renderer/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: table-renderer
|
|
3
|
-
Version: 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)
|
|
33
33
|
[](pyproject.toml)
|
|
34
34
|
[](https://github.com/pTaunium/table-renderer/actions)
|
|
35
|
+
[](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
|
|
140
|
+
You can embed both local images and remote URLs into any cell.
|
|
139
141
|
|
|
140
142
|
```python
|
|
141
|
-
# Set
|
|
143
|
+
# Set a local image
|
|
142
144
|
table.cell(1, 0).set_image("path/to/logo.png", width=50)
|
|
143
|
-
|
|
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
|
-
|
|
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)
|
|
10
10
|
[](pyproject.toml)
|
|
11
11
|
[](https://github.com/pTaunium/table-renderer/actions)
|
|
12
|
+
[](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
|
|
117
|
+
You can embed both local images and remote URLs into any cell.
|
|
116
118
|
|
|
117
119
|
```python
|
|
118
|
-
# Set
|
|
120
|
+
# Set a local image
|
|
119
121
|
table.cell(1, 0).set_image("path/to/logo.png", width=50)
|
|
120
|
-
|
|
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
|
-
|
|
171
|
+
libharfbuzz-subset0 \
|
|
160
172
|
fonts-noto-cjk \
|
|
161
173
|
&& rm -rf /var/lib/apt/lists/*
|
|
162
174
|
|
|
@@ -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
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|