Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docling/backend/asciidoc_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
TableData,
TextItem,
)
from typing_extensions import override

from docling.backend.abstract_backend import DeclarativeDocumentBackend
from docling.backend.utils.image_resource_loader import ImageResourceLoader
Expand Down Expand Up @@ -84,20 +85,25 @@ def __init__(
) from e
return

@override
def is_valid(self) -> bool:
return self.valid

@classmethod
@override
def supports_pagination(cls) -> bool:
return False

@override
def unload(self):
return

@classmethod
@override
def supported_formats(cls) -> set[InputFormat]:
return {InputFormat.ASCIIDOC}

@override
def convert(self) -> DoclingDocument:
"""
Parses the ASCII into a structured document model.
Expand Down
6 changes: 6 additions & 0 deletions docling/backend/csv_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Final, Optional, Set, Union

from docling_core.types.doc import DoclingDocument, DocumentOrigin, TableCell, TableData
from typing_extensions import override

from docling.backend.abstract_backend import DeclarativeDocumentBackend
from docling.datamodel.backend_options import CsvBackendOptions
Expand Down Expand Up @@ -76,22 +77,27 @@ def __init__(
) from e
return

@override
def is_valid(self) -> bool:
return self.valid

@classmethod
@override
def supports_pagination(cls) -> bool:
return False

@override
def unload(self):
if isinstance(self.path_or_stream, BytesIO):
self.path_or_stream.close()
self.path_or_stream = None

@classmethod
@override
def supported_formats(cls) -> Set[InputFormat]:
return {InputFormat.CSV}

@override
def convert(self) -> DoclingDocument:
"""Parse the CSV content into a DoclingDocument.

Expand Down
21 changes: 21 additions & 0 deletions docling/backend/docling_parse_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
TextCell,
)
from PIL import Image
from typing_extensions import override

from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend
from docling.datamodel.accelerator_options import AcceleratorOptions
Expand Down Expand Up @@ -130,15 +131,19 @@ def __init__(self, result: PageParseResult, rendered: bool = True):
self._seg_page: Optional[SegmentedPdfPage] = None

@property
@override
def page_no(self) -> int:
return self._result.page_number

@override
def is_valid(self) -> bool:
return self._result.success

@override
def get_error_message(self) -> str:
return self._result.error_message

@override
def get_text_in_rect(self, bbox: BoundingBox) -> str:
segmented_page = self.get_segmented_page()
if segmented_page is None:
Expand All @@ -155,6 +160,7 @@ def get_text_in_rect(self, bbox: BoundingBox) -> str:

return text_piece

@override
def get_segmented_page(self) -> Optional[SegmentedPdfPage]:
if not self.is_valid():
return None
Expand All @@ -170,18 +176,21 @@ def get_segmented_page(self) -> Optional[SegmentedPdfPage]:
self._seg_page = seg_page
return self._seg_page

@override
def get_text_cells(self) -> Iterable[TextCell]:
segmented_page = self.get_segmented_page()
if segmented_page is None:
return []
return segmented_page.textline_cells

@override
def get_visible_text_cells(self) -> Optional[list[TextCell]]:
segmented_page = self.get_segmented_page()
if segmented_page is None:
return []
return _visible_text_cells(segmented_page.textline_cells)

@override
def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]:
segmented_page = self.get_segmented_page()
if segmented_page is None:
Expand All @@ -197,6 +206,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]:
cropboxes.append(cropbox.scaled(scale=scale))
return cropboxes

@override
def has_content_in(
self,
*,
Expand All @@ -211,6 +221,7 @@ def has_content_in(
bbox=bbox, chars=chars, shapes=shapes, bitmaps=bitmaps
)

@override
def get_shape_lines(
self,
*,
Expand All @@ -229,6 +240,7 @@ def get_shape_lines(
)
]

@override
def get_connected_shape_bounding_boxes(
self, *, tolerance: float = 0.0
) -> Optional[list[BoundingBox]]:
Expand All @@ -243,6 +255,7 @@ def get_connected_shape_bounding_boxes(
)
]

@override
def get_page_image(
self, scale: float = 1, cropbox: Optional[BoundingBox] = None
) -> Image.Image:
Expand All @@ -253,9 +266,11 @@ def get_page_image(
)
return self._result.get_image(scale=scale, cropbox=cropbox).convert("RGB")

@override
def get_size(self) -> Size:
return Size(width=self._result.page_width, height=self._result.page_height)

@override
def unload(self) -> None:
return None

Expand Down Expand Up @@ -344,29 +359,35 @@ def __init__(
f"docling-parse could not load document {self.document_hash}."
) from e

@override
def is_valid(self) -> bool:
return not self._closed and self.page_count() > 0

@override
def page_count(self) -> int:
return self.parser.page_count(self.doc_key)

@override
def get_document_outline(self) -> list[_PdfOutlineItem]:
"""Extract the outline from the threaded parser's document annotations."""
annotations = self.parser.get_annotations(self.doc_key)
toc = annotations.table_of_contents if annotations is not None else None
return extract_outline_from_docling_parse(toc)

@override
def load_page(self, page_no: int) -> PdfPageBackend:
raise NotImplementedError(
"ThreadedDoclingParseDocumentBackend only supports iter_pages()."
)

@override
def iter_pages(self) -> Iterator[ThreadedDoclingParsePageBackend]:
self._iterating = True
for result in self.parser.iterate_results():
yield ThreadedDoclingParsePageBackend(result, rendered=self._render_pages)
self._iterating = False

@override
def unload(self) -> None:
if self._closed:
return
Expand Down
4 changes: 4 additions & 0 deletions docling/backend/docx/latex/omml.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import lxml.etree as ET
from lxml.etree import _Element
from typing_extensions import override

try: # pragma: no cover - import-time guard
from pylatexenc.latexencode import UnicodeToLatexEncoder
Expand Down Expand Up @@ -243,6 +244,7 @@ def __init__(self, elm: _Element):
self.__innerdict = {}
self.text = self.process_children(elm)

@override
def __str__(self) -> str:
"""Return string representation."""
return self.text
Expand Down Expand Up @@ -318,6 +320,7 @@ def __init__(self, element: _Element):
)
self._latex = self.process_children(element)

@override
def __str__(self) -> str:
"""Return LaTeX string with normalized spacing."""
return self.latex.replace(" ", " ")
Expand All @@ -326,6 +329,7 @@ def __unicode__(self) -> str:
"""Return unicode representation."""
return self.__str__()

@override
def process_unknow(self, elm: _Element, stag: str) -> Any | None:
"""Handle unknown element types.

Expand Down
5 changes: 5 additions & 0 deletions docling/backend/email_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from pathlib import Path

from docling_core.types.doc import DocItemLabel, DoclingDocument, DocumentOrigin
from typing_extensions import override

from docling.backend.abstract_backend import DeclarativeDocumentBackend
from docling.backend.html_backend import HTMLDocumentBackend
Expand Down Expand Up @@ -200,14 +201,17 @@ def _msg_to_rfc822_bytes(data: bytes) -> bytes:

return email_message.as_bytes()

@override
def is_valid(self) -> bool:
return self.valid

@classmethod
@override
def supports_pagination(cls) -> bool:
return False

@classmethod
@override
def supported_formats(cls) -> set[InputFormat]:
return {InputFormat.EMAIL}

Expand Down Expand Up @@ -316,6 +320,7 @@ def _get_attachment_labels(self) -> list[str]:
labels.append(f"{filename} ({content_type})" if content_type else filename)
return labels

@override
def convert(self) -> DoclingDocument:
if not self.is_valid() or self.mail is None:
raise RuntimeError(
Expand Down
16 changes: 16 additions & 0 deletions docling/backend/image_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
TextCell,
)
from PIL import Image, ImageOps
from typing_extensions import override

from docling.backend.abstract_backend import AbstractDocumentBackend
from docling.backend.pdf_backend import PdfDocumentBackend, PdfPageBackend
Expand Down Expand Up @@ -93,16 +94,20 @@ def __init__(
self.valid: bool = self._image is not None

@property
@override
def page_no(self) -> int:
return self._page_no + 1

@override
def is_valid(self) -> bool:
return self.valid

@override
def get_text_in_rect(self, bbox: BoundingBox) -> str:
# No text extraction from raw images without OCR
return ""

@override
def get_segmented_page(self) -> SegmentedPdfPage:
# Return empty segmented page with proper dimensions for raw images
assert self._image is not None
Expand Down Expand Up @@ -134,10 +139,12 @@ def get_segmented_page(self) -> SegmentedPdfPage:
has_lines=False,
)

@override
def get_text_cells(self) -> Iterable[TextCell]:
# No text cells on raw images
return []

@override
def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]:
# For raw images, the entire page is a bitmap
assert self._image is not None
Expand All @@ -153,6 +160,7 @@ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]:
full_page_bbox = full_page_bbox.scaled(scale=scale)
yield full_page_bbox

@override
def get_page_image(
self, scale: float = 1, cropbox: BoundingBox | None = None
) -> Image.Image:
Expand Down Expand Up @@ -194,13 +202,15 @@ def get_page_image(
box=source_box,
)

@override
def get_size(self) -> Size:
assert self._image is not None
return Size(
width=self._image.width * _POINTS_PER_INCH / self._dpi[0],
height=self._image.height * _POINTS_PER_INCH / self._dpi[1],
)

@override
def unload(self):
# Help GC and free memory
self._image = None
Expand Down Expand Up @@ -263,12 +273,15 @@ def __init__(
f"Could not load image for document {self.file}"
) from e

@override
def is_valid(self) -> bool:
return len(self._frames) > 0

@override
def page_count(self) -> int:
return len(self._frames)

@override
def load_page(self, page_no: int) -> _ImagePageBackend:
if not (0 <= page_no < len(self._frames)):
raise IndexError(f"Page index out of range: {page_no}")
Expand All @@ -277,14 +290,17 @@ def load_page(self, page_no: int) -> _ImagePageBackend:
)

@classmethod
@override
def supported_formats(cls) -> set[InputFormat]:
# Only IMAGE here; PDF handling remains in PDF-oriented backends
return {InputFormat.IMAGE}

@classmethod
@override
def supports_pagination(cls) -> bool:
return True

@override
def unload(self):
for frame in self._frames:
frame.close()
Expand Down
2 changes: 2 additions & 0 deletions docling/backend/iwork/keynote_iwa.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import zipfile

from docling_core.types.doc import DocItemLabel
from typing_extensions import override

from docling.backend.iwork.archives import (
SHAPE_STORAGE_FIELD,
Expand Down Expand Up @@ -348,6 +349,7 @@ def _blocks(
for block in self._drawable_blocks(identifier)
], []

@override
def _drawable_blocks(self, identifier: int) -> list[Block]:
"""Read a drawable, reading a chart here and deferring anything else.

Expand Down
Loading
Loading