') # Close main container
return "\n".join(parts)
def _render_extracted(
self,
content: Dict[str, Any],
options: Dict[str, Any],
field_config: Dict[str, Any]
) -> str:
"""
Render pre-extracted PDF content as HTML.
This is used when the PDF has already been processed by the
format handler and we want to display the extracted text.
"""
field_key = field_config.get("key", "pdf")
max_height = options.get("max_height", 700)
# Check if this is FormatOutput-style content
if "rendered_html" in content:
inner_html = content["rendered_html"]
elif "text" in content:
# Fall back to plain text
inner_html = f'
{html.escape(content["text"])}
'
else:
inner_html = '
No content available
'
# Build container
style_str = f"max-height: {max_height}px; overflow-y: auto;" if max_height else ""
# Add metadata if available
metadata_html = ""
if "metadata" in content:
meta = content["metadata"]
if "total_pages" in meta:
metadata_html = f'
Pages: {meta["total_pages"]}
'
return f'''
{metadata_html}
{inner_html}
'''
def _render_pdfjs_bbox(
self,
pdf_source: str,
options: Dict[str, Any],
field_config: Dict[str, Any]
) -> str:
"""
Render PDF with bounding box annotation support.
Uses paginated view mode by default for better bbox drawing experience.
"""
field_key = field_config.get("key", "pdf")
# Force paginated mode for bounding box annotation
view_mode = "paginated"
max_height = options.get("max_height", 700)
max_width = options.get("max_width")
show_controls = options.get("show_page_controls", True)
zoom = options.get("zoom", "page-fit")
initial_page = options.get("initial_page", 1)
bbox_min_size = options.get("bbox_min_size", 10)
show_labels = options.get("show_bbox_labels", True)
# Build style string
styles = []
if max_height:
styles.append(f"max-height: {max_height}px")
if max_width:
styles.append(f"max-width: {max_width}px")
style_str = "; ".join(styles) if styles else ""
parts = []
# Container div with bbox annotation mode
parts.append(
f'
'
)
# Enhanced page controls for paginated navigation
if show_controls:
parts.append('''
Page of
-
''')
# Canvas container for PDF rendering and bbox overlay
parts.append('''
''')
# Bounding box info panel
parts.append('''
Boxes on page: 0
Total boxes: 0
''')
# Loading indicator
parts.append('''
Loading PDF...
''')
# Error display
parts.append('')
# Hidden input that carries drawn boxes through the standard save
# pipeline (collected as "{name}:::_data", restored by the server). F-040.
parts.append(
f''
)
parts.append('
') # Close main container
return "\n".join(parts)
def _render_extracted_bbox(
self,
content: Dict[str, Any],
options: Dict[str, Any],
field_config: Dict[str, Any]
) -> str:
"""
Render pre-extracted PDF content with bounding box support.
For pre-extracted content, we render page images for bbox annotation.
"""
field_key = field_config.get("key", "pdf")
max_height = options.get("max_height", 700)
bbox_min_size = options.get("bbox_min_size", 10)
show_labels = options.get("show_bbox_labels", True)
metadata = content.get("metadata", {})
total_pages = metadata.get("total_pages", 1)
pages = metadata.get("pages", [])
style_str = f"max-height: {max_height}px; overflow-y: auto;" if max_height else ""
parts = []
parts.append(
f'
'
)
# Page navigation
parts.append(f'''
Page of
{total_pages}
''')
# Render each page with bbox overlay
if "rendered_html" in content:
# Content has pre-rendered HTML
parts.append('
')
else:
# Render pages from metadata
parts.append('
')
for i, page_meta in enumerate(pages, start=1):
page_width = page_meta.get("width", 612)
page_height = page_meta.get("height", 792)
parts.append(
f'
'
)
parts.append(f'
Page {i}
')
parts.append('
')
parts.append(f'')
parts.append('
')
parts.append('
')
# Bounding box info
parts.append('''
Boxes on page: 0
Total boxes: 0
''')
# Hidden input that carries drawn boxes through the standard save
# pipeline (collected as "{name}:::_data", restored by the server). F-040.
parts.append(
f''
)
parts.append('
')
return "\n".join(parts)
def get_css_classes(self, field_config: Dict[str, Any]) -> List[str]:
"""Get CSS classes for the display container."""
classes = super().get_css_classes(field_config)
options = self.get_display_options(field_config)
if field_config.get("span_target"):
classes.append("span-target-pdf")
view_mode = options.get("view_mode", "scroll")
classes.append(f"pdf-mode-{view_mode}")
annotation_mode = options.get("annotation_mode", "span")
if annotation_mode == "bounding_box":
classes.append("pdf-bbox-annotation")
return classes
def get_data_attributes(
self,
field_config: Dict[str, Any],
data: Any
) -> Dict[str, str]:
"""Get data attributes for JavaScript initialization."""
attrs = super().get_data_attributes(field_config, data)
options = self.get_display_options(field_config)
attrs["view-mode"] = options.get("view_mode", "scroll")
attrs["text-layer"] = str(options.get("text_layer", True)).lower()
return attrs
def get_js_init(self) -> Optional[str]:
"""
Return JavaScript initialization code for PDF.js.
"""
return '''
// PDF display initialization is handled by pdf-viewer.js
// which is loaded as a separate script
if (typeof initPDFViewers === 'function') {
initPDFViewers();
}
'''
def validate_config(self, field_config: Dict[str, Any]) -> List[str]:
"""Validate the field configuration."""
errors = super().validate_config(field_config)
options = field_config.get("display_options", {})
# Validate view_mode
valid_modes = ["scroll", "paginated", "side-by-side"]
view_mode = options.get("view_mode", "scroll")
if view_mode not in valid_modes:
errors.append(
f"Invalid view_mode '{view_mode}'. "
f"Must be one of: {', '.join(valid_modes)}"
)
# Validate annotation_mode
valid_annotation_modes = ["span", "bounding_box"]
annotation_mode = options.get("annotation_mode", "span")
if annotation_mode not in valid_annotation_modes:
errors.append(
f"Invalid annotation_mode '{annotation_mode}'. "
f"Must be one of: {', '.join(valid_annotation_modes)}"
)
# Validate zoom
zoom = options.get("zoom", "auto")
valid_zoom = ["auto", "page-fit", "page-width"]
if zoom not in valid_zoom:
try:
float(zoom)
except (TypeError, ValueError):
errors.append(
f"Invalid zoom '{zoom}'. "
f"Must be one of {valid_zoom} or a number"
)
return errors