diff --git a/backend/app/services/pdf_generator_service.py b/backend/app/services/pdf_generator_service.py index b294523..44a59c4 100644 --- a/backend/app/services/pdf_generator_service.py +++ b/backend/app/services/pdf_generator_service.py @@ -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})") diff --git a/openspec/changes/pdf-layout-restoration/tasks.md b/openspec/changes/pdf-layout-restoration/tasks.md index 88623bd..adb9ff7 100644 --- a/openspec/changes/pdf-layout-restoration/tasks.md +++ b/openspec/changes/pdf-layout-restoration/tasks.md @@ -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)