table-renderer 0.1.1__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.1 → table_renderer-0.2.0}/PKG-INFO +13 -4
- {table_renderer-0.1.1 → table_renderer-0.2.0}/README.md +12 -3
- {table_renderer-0.1.1 → table_renderer-0.2.0}/pyproject.toml +1 -1
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/__init__.py +1 -1
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/base.py +3 -0
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/cell.py +2 -2
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/table.py +1 -1
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/renderer.py +51 -8
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/__init__.py +0 -0
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/column.py +0 -0
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/row.py +0 -0
- {table_renderer-0.1.1 → table_renderer-0.2.0}/src/table_renderer/models/style.py +0 -0
- {table_renderer-0.1.1 → 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
|
|
@@ -42,6 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
- **Business Friendly**: Uses `pypdfium2` (Apache-2.0) and `WeasyPrint` for a 100% MIT-compatible dependency chain.
|
|
43
43
|
- **High-Quality Rendering**: Powered by WeasyPrint (CSS engine), supporting complex text wrapping, alignment, and cell merging.
|
|
44
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.
|
|
45
46
|
- **Object-Oriented API**: Clean `Table` -> `Row/Column` -> `Cell` hierarchy with fluent interface support.
|
|
46
47
|
- **Cascading Styles**: Manage visual styles effortlessly with "Global Defaults + Local Overrides."
|
|
47
48
|
- **Multi-language Font Support**: Support for font stacks (e.g., different fonts for English and Chinese).
|
|
@@ -82,7 +83,7 @@ from table_renderer import Table
|
|
|
82
83
|
# 1. Initialize a 3x3 table
|
|
83
84
|
table = Table(3, 3)
|
|
84
85
|
table.set_width(600)
|
|
85
|
-
table.set_border(1, "black", "solid")
|
|
86
|
+
table.set_border(width=1, color="black", style="solid")
|
|
86
87
|
|
|
87
88
|
# 2. Set header style (Row 0)
|
|
88
89
|
header = table.get_row(0)
|
|
@@ -156,6 +157,15 @@ table.get_column(0).set_width(200) # Fixed 200px
|
|
|
156
157
|
table.get_column(1).set_width("auto") # Distribute remaining space
|
|
157
158
|
```
|
|
158
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
|
+
|
|
159
169
|
### 5. DPI, Formats & Padding
|
|
160
170
|
|
|
161
171
|
Fine-tune your output image quality and margins.
|
|
@@ -180,9 +190,8 @@ FROM python:3.12-slim
|
|
|
180
190
|
# Install system dependencies for WeasyPrint rendering and fonts
|
|
181
191
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
182
192
|
libpango-1.0-0 \
|
|
183
|
-
libharfbuzz0b \
|
|
184
193
|
libpangoft2-1.0-0 \
|
|
185
|
-
|
|
194
|
+
libharfbuzz-subset0 \
|
|
186
195
|
fonts-noto-cjk \
|
|
187
196
|
&& rm -rf /var/lib/apt/lists/*
|
|
188
197
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- **Business Friendly**: Uses `pypdfium2` (Apache-2.0) and `WeasyPrint` for a 100% MIT-compatible dependency chain.
|
|
20
20
|
- **High-Quality Rendering**: Powered by WeasyPrint (CSS engine), supporting complex text wrapping, alignment, and cell merging.
|
|
21
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.
|
|
22
23
|
- **Object-Oriented API**: Clean `Table` -> `Row/Column` -> `Cell` hierarchy with fluent interface support.
|
|
23
24
|
- **Cascading Styles**: Manage visual styles effortlessly with "Global Defaults + Local Overrides."
|
|
24
25
|
- **Multi-language Font Support**: Support for font stacks (e.g., different fonts for English and Chinese).
|
|
@@ -59,7 +60,7 @@ from table_renderer import Table
|
|
|
59
60
|
# 1. Initialize a 3x3 table
|
|
60
61
|
table = Table(3, 3)
|
|
61
62
|
table.set_width(600)
|
|
62
|
-
table.set_border(1, "black", "solid")
|
|
63
|
+
table.set_border(width=1, color="black", style="solid")
|
|
63
64
|
|
|
64
65
|
# 2. Set header style (Row 0)
|
|
65
66
|
header = table.get_row(0)
|
|
@@ -133,6 +134,15 @@ table.get_column(0).set_width(200) # Fixed 200px
|
|
|
133
134
|
table.get_column(1).set_width("auto") # Distribute remaining space
|
|
134
135
|
```
|
|
135
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
|
+
|
|
136
146
|
### 5. DPI, Formats & Padding
|
|
137
147
|
|
|
138
148
|
Fine-tune your output image quality and margins.
|
|
@@ -157,9 +167,8 @@ FROM python:3.12-slim
|
|
|
157
167
|
# Install system dependencies for WeasyPrint rendering and fonts
|
|
158
168
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
159
169
|
libpango-1.0-0 \
|
|
160
|
-
libharfbuzz0b \
|
|
161
170
|
libpangoft2-1.0-0 \
|
|
162
|
-
|
|
171
|
+
libharfbuzz-subset0 \
|
|
163
172
|
fonts-noto-cjk \
|
|
164
173
|
&& rm -rf /var/lib/apt/lists/*
|
|
165
174
|
|
|
@@ -6,6 +6,6 @@ try:
|
|
|
6
6
|
__version__ = importlib.metadata.version("table-renderer")
|
|
7
7
|
except importlib.metadata.PackageNotFoundError:
|
|
8
8
|
# Package is not installed (e.g. during local development without pip install -e .)
|
|
9
|
-
__version__ = "0.
|
|
9
|
+
__version__ = "0.2.0"
|
|
10
10
|
|
|
11
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 {
|
|
@@ -127,8 +128,29 @@ def render_to_html(table: "Table") -> str:
|
|
|
127
128
|
row_data.append(cell)
|
|
128
129
|
cells_data.append(row_data)
|
|
129
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
|
+
|
|
130
150
|
template = Template(HTML_TEMPLATE)
|
|
131
151
|
return template.render(
|
|
152
|
+
page_width=estimated_width,
|
|
153
|
+
page_height=estimated_height,
|
|
132
154
|
table_width=f"{table.width}px" if isinstance(table.width, int) else table.width,
|
|
133
155
|
table_style=table.style.to_css(),
|
|
134
156
|
font_faces=font_faces,
|
|
@@ -150,6 +172,7 @@ def render_to_html(table: "Table") -> str:
|
|
|
150
172
|
def render_to_image(
|
|
151
173
|
table: "Table",
|
|
152
174
|
output_path: str,
|
|
175
|
+
*,
|
|
153
176
|
dpi: int = 144,
|
|
154
177
|
padding: int = 10,
|
|
155
178
|
) -> None:
|
|
@@ -171,16 +194,30 @@ def render_to_image(
|
|
|
171
194
|
|
|
172
195
|
# Load the PDF from bytes
|
|
173
196
|
with pdfium.PdfDocument(pdf_bytes) as pdf:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Render page to a bitmap
|
|
177
|
-
# scale_factor: 72 is the default PDF DPI
|
|
197
|
+
page_images = []
|
|
178
198
|
scale_factor = dpi / 72
|
|
179
|
-
bitmap = page.render(scale=scale_factor)
|
|
180
199
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
184
221
|
|
|
185
222
|
# Visual Auto-Crop: Find the bounding box of non-white pixels
|
|
186
223
|
# 1. Convert to RGB to ensure we have a standard background to check
|
|
@@ -206,6 +243,12 @@ def render_to_image(
|
|
|
206
243
|
pil_image = pil_image.crop((left, top, right, bottom))
|
|
207
244
|
|
|
208
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
|
+
|
|
209
252
|
pil_image.save(output_path)
|
|
210
253
|
|
|
211
254
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|