diff --git a/pyproject.toml b/pyproject.toml index 7c8db814..ff82cef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ dependencies = [ "pdfminer.six>=20220319", "pikepdf>=8.7.1", "pluggy>=0.13.0", - "reportlab>=3.6.8", "rich>=13", "typing-extensions>=4;python_version<'3.10'", ] @@ -58,6 +57,7 @@ test = [ "pytest-cov>=3.0.0", "pytest-xdist>=2.5.0", "python-xmp-toolkit==2.0.1", # also requires apt-get install libexempi3 + "reportlab>=3.6.8", "types-Pillow", "types-humanfriendly", ] diff --git a/src/ocrmypdf/_graft.py b/src/ocrmypdf/_graft.py index 10c6108a..256db368 100644 --- a/src/ocrmypdf/_graft.py +++ b/src/ocrmypdf/_graft.py @@ -313,7 +313,7 @@ class OcrGrafter: strip_invisible_text(self.pdf_base, base_page) base_page.contents_add( - new_text_layer, prepend=self.render_mode == RenderMode.UNDERNEATH + new_text_layer, prepend=self.render_mode == RenderMode.ON_TOP ) _update_resources(obj=base_page.obj, font=font, font_key=font_key) diff --git a/src/ocrmypdf/hocrtransform/__init__.py b/src/ocrmypdf/hocrtransform/__init__.py index c21ee23e..bb346558 100755 --- a/src/ocrmypdf/hocrtransform/__init__.py +++ b/src/ocrmypdf/hocrtransform/__init__.py @@ -21,21 +21,18 @@ from xml.etree import ElementTree from pikepdf import PdfMatrix -from ocrmypdf.hocrtransform.backends import ( - Canvas, -) -from ocrmypdf.hocrtransform.backends.pikepdf import PikepdfCanvas -from ocrmypdf.hocrtransform.backends.reportlab import ( - ReportlabCanvas, - black, - blue, - cyan, - green, - inch, - magenta, - red, +from ocrmypdf.hocrtransform._canvas import PikepdfCanvas +from ocrmypdf.hocrtransform.color import ( + BLACK, + BLUE, + CYAN, + GREEN, + MAGENTA, + RED, ) +INCH = 72.0 + # According to Wikipedia these languages are supported in the ISO-8859-1 character # set, meaning reportlab can generate them and they are compatible with hocr, # assuming Tesseract has the necessary languages installed. Note that there may @@ -231,15 +228,15 @@ class HocrTransform: if bottomup: return Rect._make( [ - (pxl.x1 / self.dpi * inch), - self.height - (pxl.y2 / self.dpi * inch), # swap y1/y2 - (pxl.x2 / self.dpi * inch), - self.height - (pxl.y1 / self.dpi * inch), + (pxl.x1 / self.dpi * INCH), + self.height - (pxl.y2 / self.dpi * INCH), # swap y1/y2 + (pxl.x2 / self.dpi * INCH), + self.height - (pxl.y1 / self.dpi * INCH), ] ) else: return Rect._make( - (c / self.dpi * inch) for c in (pxl.x1, pxl.y1, pxl.x2, pxl.y2) + (c / self.dpi * INCH) for c in (pxl.x1, pxl.y1, pxl.x2, pxl.y2) ) def _child_xpath(self, html_tag: str, html_class: str | None = None) -> str: @@ -292,7 +289,7 @@ class HocrTransform: """ # create the PDF file # page size in points (1/72 in.) - canvas = PikepdfCanvas( # ReportlabCanvas( + canvas = PikepdfCanvas( out_filename, page_size=(self.width, self.height), ) @@ -307,7 +304,7 @@ class HocrTransform: # draw cyan box around paragraph if self.render_options.render_paragraph_bbox: # pragma: no cover - canvas.set_stroke_color(cyan) + canvas.set_stroke_color(CYAN) canvas.set_line_width(0.1) # no line for bounding box canvas.rect(pt.x1, pt.y2, pt.x2 - pt.x1, pt.y2 - pt.y1, fill=0) @@ -355,7 +352,7 @@ class HocrTransform: def _do_line( self, - canvas: Canvas, + canvas: PikepdfCanvas, line: Element | None, elemclass: str, fontname: str, @@ -375,7 +372,7 @@ class HocrTransform: if abs(slope) < 0.005: slope = 0.0 angle = atan(slope) - intercept = pxl_intercept / self.dpi * inch + intercept = pxl_intercept / self.dpi * INCH # Setup a new coordinate system on the line box's intercept and rotated by # its slope @@ -402,7 +399,7 @@ class HocrTransform: self._do_debug_line_bbox(canvas, cm_line_box) self._do_debug_baseline(canvas, 0, cm_line_box, 0) - canvas.set_fill_color(black) # text in black + canvas.set_fill_color(BLACK) # text in black elements = line.findall(self._child_xpath('span', elemclass)) for elem, next_elem in pairwise(elements + [None]): @@ -474,7 +471,7 @@ class HocrTransform: return canvas.push() canvas.set_dashes() - canvas.set_stroke_color(blue) + canvas.set_stroke_color(BLUE) canvas.set_line_width(0.15) canvas.rect( line_box.x1, @@ -494,7 +491,7 @@ class HocrTransform: return canvas.push() canvas.set_dashes() - canvas.set_stroke_color(red) + canvas.set_stroke_color(RED) canvas.set_line_width(0.1) # Draw a triangle that conveys word height and drawing direction canvas.line(cm_box.x1, cm_box.y1, cm_box.x2, cm_box.y1) # across bottom @@ -507,7 +504,7 @@ class HocrTransform: return canvas.push() canvas.set_dashes() - canvas.set_stroke_color(green) + canvas.set_stroke_color(GREEN) canvas.set_line_width(0.1) canvas.rect(cm_box.x1, cm_line_box.y1, box_width, line_height, fill=0) canvas.pop() @@ -517,7 +514,7 @@ class HocrTransform: return canvas.push() canvas.set_dashes() - canvas.set_fill_color(green) + canvas.set_fill_color(GREEN) canvas.set_line_width(0.1) canvas.rect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1, fill=1) canvas.pop() @@ -527,7 +524,7 @@ class HocrTransform: return # draw the baseline in magenta, dashed canvas.set_dashes() - canvas.set_stroke_color(magenta) + canvas.set_stroke_color(MAGENTA) canvas.set_line_width(0.25) # negate slope because it is defined as a rise/run in pixel # coordinates and page coordinates have the y axis flipped diff --git a/src/ocrmypdf/hocrtransform/backends/pikepdf.py b/src/ocrmypdf/hocrtransform/_canvas.py similarity index 97% rename from src/ocrmypdf/hocrtransform/backends/pikepdf.py rename to src/ocrmypdf/hocrtransform/_canvas.py index 7b182ef5..ebc00c11 100644 --- a/src/ocrmypdf/hocrtransform/backends/pikepdf.py +++ b/src/ocrmypdf/hocrtransform/_canvas.py @@ -13,9 +13,6 @@ from pikepdf import ( unparse_content_stream, ) -from ocrmypdf.hocrtransform.backends._base import Canvas as BaseCanvas -from ocrmypdf.hocrtransform.backends._base import Text as BaseText - GLYPHLESS_FONT_NAME = 'pdf.ttf' GLYPHLESS_FONT = (package_files('ocrmypdf.data') / GLYPHLESS_FONT_NAME).read_bytes() @@ -247,9 +244,10 @@ class ContentStreamBuilder: return self._instructions -class PikepdfCanvas(BaseCanvas): +class PikepdfCanvas: def __init__(self, path, *, page_size): - super().__init__(path, page_size=page_size) + self.path = path + self.page_size = page_size self._pdf = Pdf.new() self._page = self._pdf.add_blank_page(page_size=page_size) self._cs = ContentStreamBuilder() @@ -317,7 +315,7 @@ class PikepdfCanvas(BaseCanvas): self._pdf.save(self.path) -class PikepdfText(BaseText): +class PikepdfText: def __init__(self, x=0, y=0, direction=None): self._cs = ContentStreamBuilder() self._cs.begin_text() diff --git a/src/ocrmypdf/hocrtransform/backends/__init__.py b/src/ocrmypdf/hocrtransform/backends/__init__.py deleted file mode 100644 index 7774a489..00000000 --- a/src/ocrmypdf/hocrtransform/backends/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from __future__ import annotations - -from ._base import Canvas, Text diff --git a/src/ocrmypdf/hocrtransform/backends/_base.py b/src/ocrmypdf/hocrtransform/backends/_base.py deleted file mode 100644 index 5df45f29..00000000 --- a/src/ocrmypdf/hocrtransform/backends/_base.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from pathlib import Path - - -class Canvas(ABC): - def __init__(self, path: Path, *, page_size): - self.path = path - self.page_size = page_size - - @abstractmethod - def set_stroke_color(color): - pass - - @abstractmethod - def set_fill_color(color): - pass - - @abstractmethod - def set_line_width(width): - pass - - @abstractmethod - def line(x1, y1, x2, y2): - pass - - @abstractmethod - def begin_text(self) -> Text: - pass - - @abstractmethod - def draw_text(self, text: Text): - pass - - @abstractmethod - def draw_image(self, image: Path, x, y, width, height): - pass - - @abstractmethod - def string_width(self, text, fontname, fontsize): - pass - - @abstractmethod - def set_dashes(self, array, phase): - pass - - @abstractmethod - def save(self): - pass - - -class Text(ABC): - # @abstractmethod - # def begin_marked_content(self, mctype, mcid): - # pass - - # @abstractmethod - # def end_marked_content(self): - # pass - - @abstractmethod - def set_render_mode(self, mode): - pass - - @abstractmethod - def set_font(self, font, size): - pass - - @abstractmethod - def set_text_transform(self, a, b, c, d, e, f): - pass - - @abstractmethod - def show(self, text): - pass - - @abstractmethod - def set_horiz_scale(self, scale): - pass - - @abstractmethod - def get_start_of_line(self): - pass - - @abstractmethod - def move_cursor(self, x, y): - pass diff --git a/src/ocrmypdf/hocrtransform/backends/reportlab.py b/src/ocrmypdf/hocrtransform/backends/reportlab.py deleted file mode 100644 index 1024ed41..00000000 --- a/src/ocrmypdf/hocrtransform/backends/reportlab.py +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import annotations - -import os -import warnings -from pathlib import Path - -with warnings.catch_warnings(): - # reportlab uses deprecated load_module - # shim can be removed when we require reportlab >= 3.7 - warnings.filterwarnings( - 'ignore', category=DeprecationWarning, message=r".*load_module.*" - ) - from reportlab.lib.colors import black, blue, cyan, green, magenta, red - from reportlab.lib.units import inch - from reportlab.pdfgen.canvas import Canvas - from reportlab.pdfgen.textobject import PDFTextObject - -from ocrmypdf.hocrtransform.backends._base import Canvas as BaseCanvas -from ocrmypdf.hocrtransform.backends._base import Text as BaseText - - -class ReportlabCanvas(BaseCanvas): - def __init__(self, path, *, page_size): - super().__init__(path, page_size=page_size) - self._canvas = Canvas(os.fspath(path), pagesize=page_size, pageCompression=1) - - def set_stroke_color(self, color): - self._canvas.setStrokeColor(color) - - def set_fill_color(self, color): - self._canvas.setFillColor(color) - - def set_line_width(self, width): - self._canvas.setLineWidth(width) - - def line(self, x1, y1, x2, y2): - self._canvas.line(x1, y1, x2, y2) - - def begin_text(self, x=0, y=0, direction=None): - return ReportlabText(self._canvas.beginText(x, y, direction)) - - def draw_text(self, text): - self._canvas.drawText(text._text) - - def draw_image(self, image: Path, x, y, width, height): - self._canvas.drawImage(os.fspath(image), x, y, width, height) - - def string_width(self, text, fontname, fontsize): - return self._canvas.stringWidth(text, fontname, fontsize) - - def set_dashes(self, dashes): - self._canvas.setDash(*dashes) - - def save(self): - self._canvas.showPage() - self._canvas.save() - - -class ReportlabText(BaseText): - def __init__(self, text: PDFTextObject): - self._text = text - - def set_font(self, font, size): - self._text.setFont(font, size) - - def set_render_mode(self, mode): - self._text.setTextRenderMode(mode) - - def set_text_transform(self, a, b, c, d, e, f): - return self._text.setTextTransform(a, b, c, d, e, f) - - def show(self, text): - return self._text.textOut(text) - - def set_horiz_scale(self, scale): - return self._text.setHorizScale(scale) - - def get_start_of_line(self): - return self._text.getStartOfLine() - - def move_cursor(self, x, y): - return self._text.moveCursor(x, y) diff --git a/src/ocrmypdf/hocrtransform/color.py b/src/ocrmypdf/hocrtransform/color.py new file mode 100644 index 00000000..e8f2f52b --- /dev/null +++ b/src/ocrmypdf/hocrtransform/color.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from collections import namedtuple + +Color = namedtuple('Color', ['red', 'green', 'blue', 'alpha']) + +BLACK = Color(0, 0, 0, 1) +WHITE = Color(1, 1, 1, 1) +BLUE = Color(0, 0, 1, 1) +CYAN = Color(0, 1, 1, 1) +GREEN = Color(0, 1, 0, 1) +MAGENTA = Color(1, 0, 1, 1) +RED = Color(1, 0, 0, 1)