feat: implement Task 7 span-level rendering for inline styling
Added support for preserving and rendering inline style variations within text elements (e.g., bold/italic/color changes mid-line). Span Extraction (direct_extraction_engine.py): 1. Parse PyMuPDF span data with font, size, flags, color per span 2. Create DocumentElement children for each span with StyleInfo 3. Store spans in element.children for downstream rendering 4. Extract span-specific bbox from PyMuPDF (lines 434-453) Span Rendering (pdf_generator_service.py): 1. Implement _draw_text_with_spans() method (lines 1685-1734) - Iterate through span children - Apply per-span styling via _apply_text_style - Track X position and calculate widths - Return total rendered width 2. Integrate in _draw_text_element_direct() (lines 1822-1823, 1905-1914) - Check for element.children (has_spans flag) - Use span rendering for first line - Fall back to normal rendering for list items 3. Add span count to debug logging Features: - Inline font changes (Arial → Times → Courier) - Inline size changes (12pt → 14pt → 10pt) - Inline style changes (normal → bold → italic) - Inline color changes (black → red → blue) Limitations (future work): - Currently renders all spans on first line only - Multi-line span support requires line breaking logic - List items use single-style rendering (compatibility) Direct track only (OCR track has no span information). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -409,9 +409,11 @@ class DirectExtractionEngine:
|
||||
y1=bbox_data[3]
|
||||
)
|
||||
|
||||
# Extract text content
|
||||
# Extract text content and span information
|
||||
text_parts = []
|
||||
styles = []
|
||||
span_children = [] # Store span-level children for inline styling
|
||||
span_counter = 0
|
||||
|
||||
for line in block.get("lines", []):
|
||||
for span in line.get("spans", []):
|
||||
@@ -429,6 +431,27 @@ class DirectExtractionEngine:
|
||||
)
|
||||
styles.append(style)
|
||||
|
||||
# Create span child element for inline styling
|
||||
span_bbox_data = span.get("bbox", bbox_data)
|
||||
span_bbox = BoundingBox(
|
||||
x0=span_bbox_data[0],
|
||||
y0=span_bbox_data[1],
|
||||
x1=span_bbox_data[2],
|
||||
y1=span_bbox_data[3]
|
||||
)
|
||||
|
||||
span_element = DocumentElement(
|
||||
element_id=f"span_{page_num}_{counter}_{span_counter}",
|
||||
type=ElementType.TEXT, # Spans are always text
|
||||
content=text,
|
||||
bbox=span_bbox,
|
||||
style=style,
|
||||
confidence=1.0,
|
||||
metadata={"span_index": span_counter}
|
||||
)
|
||||
span_children.append(span_element)
|
||||
span_counter += 1
|
||||
|
||||
if not text_parts:
|
||||
return None
|
||||
|
||||
@@ -449,7 +472,8 @@ class DirectExtractionEngine:
|
||||
content=full_text,
|
||||
bbox=bbox,
|
||||
style=block_style,
|
||||
confidence=1.0 # Direct extraction has perfect confidence
|
||||
confidence=1.0, # Direct extraction has perfect confidence
|
||||
children=span_children # Store span children for inline styling
|
||||
)
|
||||
|
||||
def _infer_element_type(self, text: str, styles: List[StyleInfo]) -> ElementType:
|
||||
|
||||
Reference in New Issue
Block a user