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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: table-renderer
3
- Version: 0.1.1
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
- libpangocairo-1.0-0 \
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
- libpangocairo-1.0-0 \
171
+ libharfbuzz-subset0 \
163
172
  fonts-noto-cjk \
164
173
  && rm -rf /var/lib/apt/lists/*
165
174
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "table-renderer"
7
- version = "0.1.1"
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"
@@ -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.1.1"
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
- page = pdf[0]
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
- # Convert pdfium bitmap to Pillow Image
182
- pil_image = bitmap.to_pil()
183
- 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
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