Remote reportlab backend and make reportlab a test-only dependency

This commit is contained in:
James R. Barlow
2023-11-19 23:51:27 -08:00
parent 94615f7ad4
commit 8d1e75017e
8 changed files with 44 additions and 209 deletions
+25 -28
View File
@@ -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
@@ -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()
@@ -1,3 +0,0 @@
from __future__ import annotations
from ._base import Canvas, Text
@@ -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
@@ -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)
+13
View File
@@ -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)