added line wrapping to table

This commit is contained in:
Duncan Tourolle 2025-11-10 14:33:30 +01:00
parent 303179865d
commit 5afad2ca07

View File

@ -108,21 +108,34 @@ class TableCellRenderer(Box):
return None # Cell rendering is done directly on the page
def _render_cell_content(self, x: int, y: int, width: int, height: int):
"""Render the content inside the cell (text and images)."""
from PIL import ImageFont
"""Render the content inside the cell (text and images) with line wrapping."""
from pyWebLayout.concrete.text import Line, Text
from pyWebLayout.style.fonts import Font
from pyWebLayout.style import FontWeight, Alignment
current_y = y + 2
available_height = height - 4 # Account for top/bottom padding
# Get font
try:
# Create font for the cell
font_size = 12
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
if self._is_header_section and self._style.header_text_bold:
font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 12)
else:
font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
except BaseException:
font = ImageFont.load_default()
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
font = Font(
font_path=font_path,
font_size=font_size,
weight=FontWeight.BOLD if self._is_header_section and self._style.header_text_bold else FontWeight.NORMAL
)
# Word spacing constraints (min, max)
min_spacing = int(font_size * 0.25)
max_spacing = int(font_size * 0.5)
word_spacing = (min_spacing, max_spacing)
# Line height (baseline spacing)
line_height = font_size + 4
ascent, descent = font.font.getmetrics()
# Render each block in the cell
for block in self._cell.blocks():
@ -131,38 +144,83 @@ class TableCellRenderer(Box):
current_y = self._render_image_in_cell(
block, x, current_y, width, height - (current_y - y))
elif isinstance(block, (Paragraph, Heading)):
# Extract and render text
words = []
# Get words from the block
word_items = block.words() if callable(block.words) else block.words
for word in word_items:
if hasattr(word, 'text'):
words.append(word.text)
elif isinstance(word, tuple) and len(word) >= 2:
word_obj = word[1]
if hasattr(word_obj, 'text'):
words.append(word_obj.text)
words = list(word_items)
if words:
text = " ".join(words)
if current_y <= y + height - 15:
self._draw.text((x + 2, current_y), text,
fill=(0, 0, 0), font=font)
current_y += 16
if not words:
continue
# Layout words using Line objects with wrapping
word_index = 0
pretext = None
while word_index < len(words):
# Check if we have space for another line
if current_y + ascent + descent > y + available_height:
break # No more space in cell
# Create a new line
line = Line(
spacing=word_spacing,
origin=(x + 2, current_y),
size=(width - 4, line_height),
draw=self._draw,
font=font,
halign=Alignment.LEFT
)
# Add words to this line until it's full
line_has_content = False
while word_index < len(words):
word = words[word_index]
# Handle word tuples (index, word_obj)
if isinstance(word, tuple) and len(word) >= 2:
word_obj = word[1]
else:
word_obj = word
# Try to add word to line
success, overflow = line.add_word(word_obj, pretext)
pretext = None # Clear pretext after use
if success:
line_has_content = True
if overflow:
# Word was hyphenated, carry over to next line
pretext = overflow
word_index += 1
else:
# Word doesn't fit on this line
if not line_has_content:
# Even first word doesn't fit, force it anyway to avoid infinite loop
word_index += 1
break
# Render the line if it has content
if line_has_content or len(line.text_objects) > 0:
line.render()
current_y += line_height
if current_y > y + height - 10: # Don't overflow cell
break
# If no structured content, try to get any text representation
if current_y == y + 2 and hasattr(self._cell, '_text_content'):
# Use simple text rendering for fallback case
from PIL import ImageFont
try:
pil_font = ImageFont.truetype(font_path, font_size)
except BaseException:
pil_font = ImageFont.load_default()
self._draw.text(
(x + 2,
current_y),
(x + 2, current_y),
self._cell._text_content,
fill=(
0,
0,
0),
font=font)
fill=(0, 0, 0),
font=pil_font
)
def _render_image_in_cell(self, image_block: AbstractImage, x: int, y: int,
max_width: int, max_height: int) -> int: