fix: implement actual list item spacing with Y offset adjustment
Previous implementation only expanded bbox_height which had no visual effect. New implementation properly applies spacing_after between list items. Changes: 1. Track cumulative Y offset in _draw_list_elements_direct 2. Calculate actual gap between adjacent list items 3. If actual gap < desired spacing_after, add offset to push next item down 4. Pass y_offset parameter to _draw_text_element_direct 5. Apply y_offset when calculating pdf_y coordinate Implementation details: - Default 3pt spacing_after for list items (except last item in group) - Compare actual_gap (next.bbox.y0 - current.bbox.y1) with desired spacing - Cumulative offset ensures spacing compounds across multiple items - Negative offset in PDF coordinates (Y increases upward) - Debug logging shows when additional spacing is applied This now creates actual visual spacing between list items in the PDF output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1636,6 +1636,9 @@ class PDFGeneratorService:
|
||||
list_type = 'unordered'
|
||||
|
||||
# Draw each item in the group with proper spacing
|
||||
# Track cumulative Y offset to apply spacing_after between items
|
||||
cumulative_y_offset = 0
|
||||
|
||||
for item_idx, item in enumerate(group):
|
||||
# Prepare list marker based on type
|
||||
if list_type == 'ordered':
|
||||
@@ -1654,21 +1657,37 @@ class PDFGeneratorService:
|
||||
|
||||
# Add default list item spacing if not specified
|
||||
# This ensures consistent spacing between list items
|
||||
if 'spacing_after' not in item.metadata or item.metadata.get('spacing_after', 0) == 0:
|
||||
# Default list item spacing: 3 points between items
|
||||
item.metadata['spacing_after'] = 3.0
|
||||
desired_spacing_after = item.metadata.get('spacing_after', 0)
|
||||
if desired_spacing_after == 0:
|
||||
# Default list item spacing: 3 points between items (except last item)
|
||||
if item_idx < len(group) - 1:
|
||||
desired_spacing_after = 3.0
|
||||
item.metadata['spacing_after'] = desired_spacing_after
|
||||
|
||||
# Mark this as requiring spacing application
|
||||
item.metadata['_apply_spacing_after'] = True
|
||||
# Draw the list item with cumulative Y offset
|
||||
self._draw_text_element_direct(pdf_canvas, item, page_height, y_offset=cumulative_y_offset)
|
||||
|
||||
# Draw the list item using text element renderer
|
||||
self._draw_text_element_direct(pdf_canvas, item, page_height)
|
||||
# Calculate spacing to add after this item
|
||||
if item_idx < len(group) - 1 and desired_spacing_after > 0:
|
||||
next_item = group[item_idx + 1]
|
||||
|
||||
# Calculate actual vertical gap between items (in document coordinates)
|
||||
# Note: Y increases downward in document coordinates
|
||||
actual_gap = next_item.bbox.y0 - item.bbox.y1
|
||||
|
||||
# If actual gap is less than desired spacing, add offset to push next item down
|
||||
if actual_gap < desired_spacing_after:
|
||||
additional_spacing = desired_spacing_after - actual_gap
|
||||
cumulative_y_offset -= additional_spacing # Negative because PDF Y increases upward
|
||||
logger.debug(f"Adding {additional_spacing:.1f}pt spacing after list item {item.element_id} "
|
||||
f"(actual_gap={actual_gap:.1f}pt, desired={desired_spacing_after:.1f}pt)")
|
||||
|
||||
def _draw_text_element_direct(
|
||||
self,
|
||||
pdf_canvas: canvas.Canvas,
|
||||
element: 'DocumentElement',
|
||||
page_height: float
|
||||
page_height: float,
|
||||
y_offset: float = 0
|
||||
):
|
||||
"""
|
||||
Draw text element with Direct track rich formatting.
|
||||
@@ -1679,6 +1698,7 @@ class PDFGeneratorService:
|
||||
pdf_canvas: ReportLab canvas object
|
||||
element: DocumentElement with text content
|
||||
page_height: Page height for coordinate transformation
|
||||
y_offset: Optional Y coordinate offset (for list spacing), in PDF coordinates
|
||||
"""
|
||||
try:
|
||||
text_content = element.get_text()
|
||||
@@ -1693,7 +1713,7 @@ class PDFGeneratorService:
|
||||
|
||||
# Transform coordinates (top-left origin → bottom-left origin)
|
||||
pdf_x = bbox.x0
|
||||
pdf_y = page_height - bbox.y1 # Use bottom of bbox
|
||||
pdf_y = page_height - bbox.y1 + y_offset # Use bottom of bbox + apply offset
|
||||
|
||||
bbox_width = bbox.x1 - bbox.x0
|
||||
bbox_height = bbox.y1 - bbox.y0
|
||||
@@ -1743,10 +1763,9 @@ class PDFGeneratorService:
|
||||
|
||||
# Get paragraph spacing
|
||||
# spacing_before: Applied by adjusting starting Y position (pdf_y)
|
||||
# spacing_after: Applied for list items marked with _apply_spacing_after
|
||||
# spacing_after: Applied via y_offset in _draw_list_elements_direct for list items
|
||||
paragraph_spacing_before = element.metadata.get('spacing_before', 0) if element.metadata else 0
|
||||
paragraph_spacing_after = element.metadata.get('spacing_after', 0) if element.metadata else 0
|
||||
apply_spacing_after = element.metadata.get('_apply_spacing_after', False) if element.metadata else False
|
||||
|
||||
# Handle line breaks
|
||||
lines = text_content.split('\n')
|
||||
@@ -1761,13 +1780,6 @@ class PDFGeneratorService:
|
||||
# Apply paragraph spacing before (shift starting position up)
|
||||
pdf_y += paragraph_spacing_before
|
||||
|
||||
# Apply list item spacing after by expanding bbox height
|
||||
# This creates visual space between list items
|
||||
if apply_spacing_after and paragraph_spacing_after > 0:
|
||||
# Adjust bbox to include spacing_after
|
||||
# This is done by conceptually expanding the element's vertical space
|
||||
bbox_height += paragraph_spacing_after
|
||||
|
||||
# Draw each line with alignment
|
||||
for i, line in enumerate(lines):
|
||||
if not line.strip():
|
||||
@@ -1844,12 +1856,12 @@ class PDFGeneratorService:
|
||||
actual_text_height = len(lines) * line_height
|
||||
bbox_bottom_margin = bbox_height - actual_text_height - paragraph_spacing_before
|
||||
|
||||
# Note: For list items with _apply_spacing_after, spacing_after is added to bbox_height
|
||||
# Note: For list items, spacing_after is applied via y_offset in _draw_list_elements_direct
|
||||
# For other elements, spacing is inherent in element positioning (bbox-based layout)
|
||||
list_info = f", list={list_type}, level={list_level}" if is_list_item else ""
|
||||
spacing_applied = f", spacing_after_applied={apply_spacing_after}" if is_list_item else ""
|
||||
y_offset_info = f", y_offset={y_offset:.1f}pt" if y_offset != 0 else ""
|
||||
logger.debug(f"Drew text element: {text_content[:30]}... "
|
||||
f"({len(lines)} lines, align={alignment}, indent={indent}{list_info}{spacing_applied}, "
|
||||
f"({len(lines)} lines, align={alignment}, indent={indent}{list_info}{y_offset_info}, "
|
||||
f"spacing_before={paragraph_spacing_before}, spacing_after={paragraph_spacing_after}, "
|
||||
f"actual_height={actual_text_height:.1f}, bbox_bottom_margin={bbox_bottom_margin:.1f})")
|
||||
|
||||
|
||||
@@ -115,10 +115,11 @@
|
||||
- [x] Calculate marker width before rendering (line 1758)
|
||||
- [x] Add marker_width to subsequent line indentation (lines 1770-1772)
|
||||
- [x] 6.2.5 Remove original markers from text content (lines 1716-1723)
|
||||
- [x] 6.2.6 Dedicated list item spacing (lines 1655-1662, 1764-1769)
|
||||
- [x] Default 3pt spacing_after for list items
|
||||
- [x] Applied by expanding bbox_height (line 1769)
|
||||
- [x] Marked with _apply_spacing_after flag
|
||||
- [x] 6.2.6 Dedicated list item spacing (lines 1658-1683)
|
||||
- [x] Default 3pt spacing_after for list items (except last item)
|
||||
- [x] Calculate actual gap between adjacent items (line 1676)
|
||||
- [x] Apply cumulative Y offset to push items down if gap < desired (lines 1678-1683)
|
||||
- [x] Pass y_offset to _draw_text_element_direct (line 1668, 1690, 1716)
|
||||
- [x] 6.2.7 Maintain list grouping via proximity (max_gap=30pt, lines 1597-1607)
|
||||
|
||||
### 7. Span-Level Rendering (Advanced)
|
||||
|
||||
Reference in New Issue
Block a user