Correcting Matrix logic helps

The good: don't have to do inverse and intermediate transforms.

The bad: skew looks bad, partly because the hOCR coordinate system is inconsistent around skew?
This commit is contained in:
James R. Barlow
2023-11-19 23:51:27 -08:00
parent 491b6bdb1f
commit d7680cae27
+36 -41
View File
@@ -19,7 +19,7 @@ from pathlib import Path
from typing import Any, NamedTuple
from xml.etree import ElementTree
from pikepdf import PdfMatrix
from pikepdf import Matrix, Rectangle
from ocrmypdf.hocrtransform._canvas import PikepdfCanvas
from ocrmypdf.hocrtransform.color import (
@@ -305,44 +305,47 @@ class HocrTransform:
):
if line is None:
return
pxl_line_coords = self.element_coordinates(line)
line_box = self.pt_from_pixel(pxl_line_coords, bottomup=True)
line_box = self.element_coordinates(line)
assert line_box.y2 > line_box.y1
# Baseline is a polynomial (usually straight line) in the coordinate system
# of the line
slope, pxl_intercept = self.baseline(line)
slope, intercept = self.baseline(line)
if abs(slope) < 0.005:
slope = 0.0
angle = atan(slope)
intercept = pxl_intercept / self.dpi * INCH
# Setup a new coordinate system on the line box's intercept and rotated by
# its slope
canvas.push()
line_matrix = (
PdfMatrix()
.translated(line_box.x1, line_box.y1 - intercept)
.rotated(-angle / pi * 180)
Matrix()
.translated(0, self.height)
.scaled(1, -1)
.scaled(INCH / self.dpi, INCH / self.dpi)
.translated(-line_box.x1, -line_box.y1)
.translated(0, -intercept)
.rotated(angle / pi * 180)
.translated(0, intercept)
.translated(line_box.x1, line_box.y1)
)
canvas.cm(*line_matrix.shorthand)
cm_line_box = line_box.transform(line_matrix, inverse=True)
cm_line_height = cm_line_box.y2 - cm_line_box.y1
print(line_matrix)
text = canvas.begin_text()
# Don't allow the font to break out of the bounding box. Division by
# cos_a accounts for extra clearance between the glyph's vertical axis
# on a sloped baseline and the edge of the bounding box.
fontsize = cm_line_height
line_box_height = abs(line_box.y2 - line_box.y1)
fontsize = line_box_height + intercept
text.set_font(fontname, fontsize)
if invisible_text or True:
text.set_render_mode(3) # Invisible (indicates OCR text)
self._do_debug_line_bbox(canvas, cm_line_box)
self._do_debug_baseline(canvas, 0, cm_line_box, 0)
self._do_debug_line_bbox(canvas, line_box)
self._do_debug_baseline(
canvas, line_box.y2 + intercept, line_box, line_box.y2 + intercept
)
canvas.set_fill_color(BLACK) # text in black
elements = line.findall(self._child_xpath('span', elemclass))
@@ -351,9 +354,8 @@ class HocrTransform:
canvas,
fontname,
interword_spaces,
cm_line_height,
line_matrix,
cm_line_box,
line_box.y2 - line_box.y1,
line_box,
text,
fontsize,
elem,
@@ -368,8 +370,7 @@ class HocrTransform:
fontname,
interword_spaces,
line_height,
line_matrix,
cm_line_box,
line_box,
text,
fontsize,
elem,
@@ -380,31 +381,25 @@ class HocrTransform:
if elemtxt == '':
return
pxl_coords = self.element_coordinates(elem)
box = self.pt_from_pixel(pxl_coords, bottomup=True)
cm_box = box.transform(line_matrix, inverse=True)
box_width = cm_box.x2 - cm_box.x1
box = self.element_coordinates(elem)
box_width = box.x2 - box.x1
font_width = canvas.string_width(elemtxt, fontname, fontsize)
# Debug sketches
self._do_debug_word_triangle(canvas, cm_box)
self._do_debug_word_bbox(canvas, line_height, cm_line_box, cm_box, box_width)
self._do_debug_word_triangle(canvas, box)
self._do_debug_word_bbox(canvas, line_height, line_box, box, box_width)
# If this word is 0 units wide, our best bet seems to be to suppress this text
if font_width > 0:
text.set_text_transform(1, 0, 0, 1, cm_box.x1, cm_line_box.y1)
text.set_text_transform(1, 0, 0, 1, box.x1, line_box.y2)
text.set_horiz_scale(100 * box_width / font_width)
text.show(elemtxt)
if interword_spaces and next_elem is not None:
next_box = self.pt_from_pixel(
self.element_coordinates(next_elem), bottomup=True
)
next_cm_box = next_box.transform(line_matrix, inverse=True)
space_box = Rect(cm_box.x2, cm_line_box.y1, next_cm_box.x1, cm_line_box.y2)
next_box = self.element_coordinates(next_elem)
space_box = Rect(box.x2, line_box.y1, next_box.x1, line_box.y2)
self._do_debug_space_bbox(canvas, space_box)
text.set_text_transform(1, 0, 0, 1, space_box.x1, cm_line_box.y1)
text.set_text_transform(1, 0, 0, 1, space_box.x1, line_box.y2)
space_width = canvas.string_width(' ', fontname, fontsize)
box_width = space_box.x2 - space_box.x1
text.set_horiz_scale(100 * box_width / space_width)
@@ -429,7 +424,7 @@ class HocrTransform:
def _do_debug_word_triangle(
self,
canvas,
cm_box,
box,
):
if not self.render_options.render_triangle: # pragma: no cover
return
@@ -438,19 +433,19 @@ class HocrTransform:
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
canvas.line(cm_box.x2, cm_box.y1, cm_box.x1, cm_box.y2) # diagonal
canvas.line(cm_box.x1, cm_box.y1, cm_box.x1, cm_box.y2) # rise
canvas.line(box.x1, box.y1, box.x2, box.y1) # across bottom
canvas.line(box.x2, box.y1, box.x1, box.y2) # diagonal
canvas.line(box.x1, box.y1, box.x1, box.y2) # rise
canvas.pop()
def _do_debug_word_bbox(self, canvas, line_height, cm_line_box, cm_box, box_width):
def _do_debug_word_bbox(self, canvas, line_height, line_box, box, box_width):
if not self.render_options.render_word_bbox: # pragma: no cover
return
canvas.push()
canvas.set_dashes()
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.rect(box.x1, line_box.y1, box_width, line_height, fill=0)
canvas.pop()
def _do_debug_space_bbox(self, canvas, box):