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 return None # Cell rendering is done directly on the page
def _render_cell_content(self, x: int, y: int, width: int, height: int): def _render_cell_content(self, x: int, y: int, width: int, height: int):
"""Render the content inside the cell (text and images).""" """Render the content inside the cell (text and images) with line wrapping."""
from PIL import ImageFont from pyWebLayout.concrete.text import Line, Text
from pyWebLayout.style.fonts import Font
from pyWebLayout.style import FontWeight, Alignment
current_y = y + 2 current_y = y + 2
available_height = height - 4 # Account for top/bottom padding
# Get font # Create font for the cell
try: font_size = 12
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
if self._is_header_section and self._style.header_text_bold: if self._is_header_section and self._style.header_text_bold:
font = ImageFont.truetype( font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 12)
else: font = Font(
font = ImageFont.truetype( font_path=font_path,
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12) font_size=font_size,
except BaseException: weight=FontWeight.BOLD if self._is_header_section and self._style.header_text_bold else FontWeight.NORMAL
font = ImageFont.load_default() )
# 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 # Render each block in the cell
for block in self._cell.blocks(): for block in self._cell.blocks():
@ -131,38 +144,83 @@ class TableCellRenderer(Box):
current_y = self._render_image_in_cell( current_y = self._render_image_in_cell(
block, x, current_y, width, height - (current_y - y)) block, x, current_y, width, height - (current_y - y))
elif isinstance(block, (Paragraph, Heading)): elif isinstance(block, (Paragraph, Heading)):
# Extract and render text # Get words from the block
words = []
word_items = block.words() if callable(block.words) else block.words word_items = block.words() if callable(block.words) else block.words
for word in word_items: words = list(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)
if words: if not words:
text = " ".join(words) continue
if current_y <= y + height - 15:
self._draw.text((x + 2, current_y), text, # Layout words using Line objects with wrapping
fill=(0, 0, 0), font=font) word_index = 0
current_y += 16 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 if current_y > y + height - 10: # Don't overflow cell
break break
# If no structured content, try to get any text representation # If no structured content, try to get any text representation
if current_y == y + 2 and hasattr(self._cell, '_text_content'): 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( self._draw.text(
(x + 2, (x + 2, current_y),
current_y),
self._cell._text_content, self._cell._text_content,
fill=( fill=(0, 0, 0),
0, font=pil_font
0, )
0),
font=font)
def _render_image_in_cell(self, image_block: AbstractImage, x: int, y: int, def _render_image_in_cell(self, image_block: AbstractImage, x: int, y: int,
max_width: int, max_height: int) -> int: max_width: int, max_height: int) -> int: