2019-10-31 08:22:58 +00:00
|
|
|
import heapq
|
2019-11-10 11:18:49 +00:00
|
|
|
import logging
|
2019-11-07 20:12:34 +00:00
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
from .utils import INF
|
2014-06-26 09:12:39 +00:00
|
|
|
from .utils import Plane
|
2019-11-07 20:12:34 +00:00
|
|
|
from .utils import apply_matrix_pt
|
2014-06-26 09:12:39 +00:00
|
|
|
from .utils import bbox2str
|
2019-11-07 20:12:34 +00:00
|
|
|
from .utils import fsplit
|
|
|
|
from .utils import get_bound
|
2014-06-26 09:12:39 +00:00
|
|
|
from .utils import matrix2str
|
2019-11-07 20:12:34 +00:00
|
|
|
from .utils import uniq
|
2009-03-28 17:23:53 +00:00
|
|
|
|
2019-11-10 11:18:49 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2009-03-28 17:23:53 +00:00
|
|
|
|
2011-05-14 06:51:40 +00:00
|
|
|
class IndexAssigner(object):
|
|
|
|
|
|
|
|
def __init__(self, index=0):
|
|
|
|
self.index = index
|
|
|
|
return
|
|
|
|
|
|
|
|
def run(self, obj):
|
|
|
|
if isinstance(obj, LTTextBox):
|
|
|
|
obj.index = self.index
|
|
|
|
self.index += 1
|
|
|
|
elif isinstance(obj, LTTextGroup):
|
|
|
|
for x in obj:
|
2011-05-14 07:32:09 +00:00
|
|
|
self.run(x)
|
2011-05-14 06:51:40 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
|
2009-07-21 07:55:19 +00:00
|
|
|
class LAParams(object):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Parameters for layout analysis
|
|
|
|
|
|
|
|
:param line_overlap: If two characters have more overlap than this they
|
|
|
|
are considered to be on the same line. The overlap is specified
|
|
|
|
relative to the minimum height of both characters.
|
|
|
|
:param char_margin: If two characters are closer together than this
|
|
|
|
margin they are considered to be part of the same word. If
|
|
|
|
characters are on the same line but not part of the same word, an
|
|
|
|
intermediate space is inserted. The margin is specified relative to
|
|
|
|
the width of the character.
|
|
|
|
:param word_margin: If two words are are closer together than this
|
|
|
|
margin they are considered to be part of the same line. A space is
|
|
|
|
added in between for readability. The margin is specified relative
|
|
|
|
to the width of the word.
|
|
|
|
:param line_margin: If two lines are are close together they are
|
|
|
|
considered to be part of the same paragraph. The margin is
|
|
|
|
specified relative to the height of a line.
|
|
|
|
:param boxes_flow: Specifies how much a horizontal and vertical position
|
2019-11-10 11:18:49 +00:00
|
|
|
of a text matters when determining the order of text boxes. The value
|
2019-11-07 20:12:34 +00:00
|
|
|
should be within the range of -1.0 (only horizontal position
|
|
|
|
matters) to +1.0 (only vertical position matters).
|
|
|
|
:param detect_vertical: If vertical text should be considered during
|
|
|
|
layout analysis
|
|
|
|
:param all_texts: If layout analysis should be performed on text in
|
|
|
|
figures.
|
|
|
|
"""
|
2009-10-24 04:41:59 +00:00
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
line_overlap=0.5,
|
2010-10-17 05:13:39 +00:00
|
|
|
char_margin=2.0,
|
2009-10-24 04:41:59 +00:00
|
|
|
line_margin=0.5,
|
2010-04-10 11:29:30 +00:00
|
|
|
word_margin=0.1,
|
2011-02-14 14:41:23 +00:00
|
|
|
boxes_flow=0.5,
|
2011-02-02 14:09:34 +00:00
|
|
|
detect_vertical=False,
|
2010-04-10 11:29:30 +00:00
|
|
|
all_texts=False):
|
2009-10-24 04:41:59 +00:00
|
|
|
self.line_overlap = line_overlap
|
|
|
|
self.char_margin = char_margin
|
|
|
|
self.line_margin = line_margin
|
|
|
|
self.word_margin = word_margin
|
2010-12-26 08:26:39 +00:00
|
|
|
self.boxes_flow = boxes_flow
|
2011-02-02 14:09:34 +00:00
|
|
|
self.detect_vertical = detect_vertical
|
2010-04-10 11:29:30 +00:00
|
|
|
self.all_texts = all_texts
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2019-12-29 20:20:20 +00:00
|
|
|
return '<LAParams: char_margin=%.1f, line_margin=%.1f, ' \
|
|
|
|
'word_margin=%.1f all_texts=%r>' % \
|
|
|
|
(self.char_margin, self.line_margin, self.word_margin,
|
|
|
|
self.all_texts)
|
2009-05-04 08:29:36 +00:00
|
|
|
|
|
|
|
|
2010-04-10 11:29:30 +00:00
|
|
|
class LTItem(object):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Interface for things that can be analyzed"""
|
2009-05-05 12:26:29 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
def analyze(self, laparams):
|
|
|
|
"""Perform the layout analysis."""
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
class LTText(object):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Interface for things that have text"""
|
2011-05-14 06:45:08 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return ('<%s %r>' %
|
|
|
|
(self.__class__.__name__, self.get_text()))
|
|
|
|
|
|
|
|
def get_text(self):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Text contained in this object"""
|
2011-05-14 06:45:08 +00:00
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
class LTComponent(LTItem):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Object with a bounding box"""
|
2011-05-14 06:45:08 +00:00
|
|
|
|
2009-10-24 04:41:59 +00:00
|
|
|
def __init__(self, bbox):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTItem.__init__(self)
|
2009-10-24 04:41:59 +00:00
|
|
|
self.set_bbox(bbox)
|
|
|
|
return
|
|
|
|
|
2009-11-06 15:06:59 +00:00
|
|
|
def __repr__(self):
|
2010-10-17 05:13:39 +00:00
|
|
|
return ('<%s %s>' %
|
|
|
|
(self.__class__.__name__, bbox2str(self.bbox)))
|
2009-11-06 15:06:59 +00:00
|
|
|
|
2014-06-26 08:41:31 +00:00
|
|
|
# Disable comparison.
|
|
|
|
def __lt__(self, _):
|
|
|
|
raise ValueError
|
2019-11-07 20:12:34 +00:00
|
|
|
|
2014-06-26 08:41:31 +00:00
|
|
|
def __le__(self, _):
|
|
|
|
raise ValueError
|
2019-11-07 20:12:34 +00:00
|
|
|
|
2014-06-26 08:41:31 +00:00
|
|
|
def __gt__(self, _):
|
|
|
|
raise ValueError
|
2019-11-07 20:12:34 +00:00
|
|
|
|
2014-06-26 08:41:31 +00:00
|
|
|
def __ge__(self, _):
|
|
|
|
raise ValueError
|
|
|
|
|
2014-06-15 03:12:13 +00:00
|
|
|
def set_bbox(self, bbox):
|
|
|
|
(x0, y0, x1, y1) = bbox
|
2009-10-24 04:41:59 +00:00
|
|
|
self.x0 = x0
|
|
|
|
self.y0 = y0
|
|
|
|
self.x1 = x1
|
|
|
|
self.y1 = y1
|
|
|
|
self.width = x1-x0
|
|
|
|
self.height = y1-y0
|
2014-06-15 03:12:13 +00:00
|
|
|
self.bbox = bbox
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
2011-03-01 11:47:20 +00:00
|
|
|
|
|
|
|
def is_empty(self):
|
|
|
|
return self.width <= 0 or self.height <= 0
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2009-11-14 11:29:40 +00:00
|
|
|
def is_hoverlap(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
return obj.x0 <= self.x1 and self.x0 <= obj.x1
|
2009-11-14 11:29:40 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
def hdistance(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
if self.is_hoverlap(obj):
|
2009-10-24 04:41:59 +00:00
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return min(abs(self.x0-obj.x1), abs(self.x1-obj.x0))
|
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
def hoverlap(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
if self.is_hoverlap(obj):
|
|
|
|
return min(abs(self.x0-obj.x1), abs(self.x1-obj.x0))
|
2009-11-14 11:29:40 +00:00
|
|
|
else:
|
2010-03-20 05:43:34 +00:00
|
|
|
return 0
|
2009-11-14 11:29:40 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
def is_voverlap(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
return obj.y0 <= self.y1 and self.y0 <= obj.y1
|
|
|
|
|
|
|
|
def vdistance(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
if self.is_voverlap(obj):
|
2009-10-24 04:41:59 +00:00
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return min(abs(self.y0-obj.y1), abs(self.y1-obj.y0))
|
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
def voverlap(self, obj):
|
2017-05-29 07:06:09 +00:00
|
|
|
assert isinstance(obj, LTComponent), str(type(obj))
|
2010-03-20 05:43:34 +00:00
|
|
|
if self.is_voverlap(obj):
|
|
|
|
return min(abs(self.y0-obj.y1), abs(self.y1-obj.y0))
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
2009-06-20 10:44:00 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTCurve(LTComponent):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""A generic Bezier curve"""
|
2009-05-15 14:25:32 +00:00
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
def __init__(self, linewidth, pts, stroke=False, fill=False, evenodd=False,
|
|
|
|
stroking_color=None, non_stroking_color=None):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTComponent.__init__(self, get_bound(pts))
|
2009-12-20 02:38:01 +00:00
|
|
|
self.pts = pts
|
2009-10-24 04:41:59 +00:00
|
|
|
self.linewidth = linewidth
|
2016-11-08 19:01:58 +00:00
|
|
|
self.stroke = stroke
|
|
|
|
self.fill = fill
|
|
|
|
self.evenodd = evenodd
|
|
|
|
self.stroking_color = stroking_color
|
|
|
|
self.non_stroking_color = non_stroking_color
|
2009-12-20 02:38:01 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def get_pts(self):
|
2013-11-07 08:35:04 +00:00
|
|
|
return ','.join('%.3f,%.3f' % p for p in self.pts)
|
2009-12-20 02:38:01 +00:00
|
|
|
|
|
|
|
|
2011-04-20 13:05:25 +00:00
|
|
|
class LTLine(LTCurve):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""A single straight line.
|
|
|
|
|
|
|
|
Could be used for separating text or figures.
|
|
|
|
"""
|
2009-12-20 02:38:01 +00:00
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
def __init__(self, linewidth, p0, p1, stroke=False, fill=False,
|
|
|
|
evenodd=False, stroking_color=None, non_stroking_color=None):
|
|
|
|
LTCurve.__init__(self, linewidth, [p0, p1], stroke, fill, evenodd,
|
|
|
|
stroking_color, non_stroking_color)
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
2009-05-15 14:25:32 +00:00
|
|
|
|
|
|
|
|
2011-04-20 13:05:25 +00:00
|
|
|
class LTRect(LTCurve):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""A rectangle.
|
|
|
|
|
|
|
|
Could be used for framing another pictures or figures.
|
|
|
|
"""
|
2009-05-15 14:25:32 +00:00
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
def __init__(self, linewidth, bbox, stroke=False, fill=False,
|
|
|
|
evenodd=False, stroking_color=None, non_stroking_color=None):
|
2014-06-15 03:12:13 +00:00
|
|
|
(x0, y0, x1, y1) = bbox
|
2019-12-29 20:20:20 +00:00
|
|
|
LTCurve.__init__(self, linewidth,
|
|
|
|
[(x0, y0), (x1, y0), (x1, y1), (x0, y1)], stroke,
|
|
|
|
fill, evenodd, stroking_color, non_stroking_color)
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
2009-05-15 14:25:32 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTImage(LTComponent):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""An image object.
|
|
|
|
|
|
|
|
Embedded images can be in JPEG, Bitmap or JBIG2.
|
|
|
|
"""
|
2010-01-30 07:30:01 +00:00
|
|
|
|
2010-04-10 11:05:02 +00:00
|
|
|
def __init__(self, name, stream, bbox):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTComponent.__init__(self, bbox)
|
2010-01-30 07:30:01 +00:00
|
|
|
self.name = name
|
2010-04-10 11:05:02 +00:00
|
|
|
self.stream = stream
|
|
|
|
self.srcsize = (stream.get_any(('W', 'Width')),
|
|
|
|
stream.get_any(('H', 'Height')))
|
|
|
|
self.imagemask = stream.get_any(('IM', 'ImageMask'))
|
|
|
|
self.bits = stream.get_any(('BPC', 'BitsPerComponent'), 1)
|
|
|
|
self.colorspace = stream.get_any(('CS', 'ColorSpace'))
|
|
|
|
if not isinstance(self.colorspace, list):
|
2010-04-10 11:30:03 +00:00
|
|
|
self.colorspace = [self.colorspace]
|
2010-01-30 07:30:01 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2011-05-14 06:45:08 +00:00
|
|
|
return ('<%s(%s) %s %r>' %
|
2010-10-17 05:13:39 +00:00
|
|
|
(self.__class__.__name__, self.name,
|
2011-05-14 06:45:08 +00:00
|
|
|
bbox2str(self.bbox), self.srcsize))
|
2010-01-30 07:30:01 +00:00
|
|
|
|
|
|
|
|
2013-11-11 10:17:45 +00:00
|
|
|
class LTAnno(LTItem, LTText):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Actual letter in the text as a Unicode string.
|
|
|
|
|
|
|
|
Note that, while a LTChar object has actual boundaries, LTAnno objects does
|
|
|
|
not, as these are "virtual" characters, inserted by a layout analyzer
|
|
|
|
according to the relationship between two characters (e.g. a space).
|
|
|
|
"""
|
2009-06-20 10:44:00 +00:00
|
|
|
|
2009-10-24 04:41:59 +00:00
|
|
|
def __init__(self, text):
|
2011-05-14 06:45:08 +00:00
|
|
|
self._text = text
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
2009-06-20 10:44:00 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
def get_text(self):
|
|
|
|
return self._text
|
2009-06-20 10:44:00 +00:00
|
|
|
|
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTChar(LTComponent, LTText):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Actual letter in the text as a Unicode string."""
|
2009-10-24 04:41:59 +00:00
|
|
|
|
2013-11-11 11:46:30 +00:00
|
|
|
def __init__(self, matrix, font, fontsize, scaling, rise,
|
2018-03-06 10:11:31 +00:00
|
|
|
text, textwidth, textdisp, ncs, graphicstate):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTText.__init__(self)
|
|
|
|
self._text = text
|
2010-11-14 10:07:41 +00:00
|
|
|
self.matrix = matrix
|
|
|
|
self.fontname = font.fontname
|
2018-03-06 10:11:31 +00:00
|
|
|
self.ncs = ncs
|
|
|
|
self.graphicstate = graphicstate
|
2010-11-14 10:07:41 +00:00
|
|
|
self.adv = textwidth * fontsize * scaling
|
2010-02-27 03:59:25 +00:00
|
|
|
# compute the boundary rectangle.
|
2010-11-14 10:07:41 +00:00
|
|
|
if font.is_vertical():
|
2010-02-27 03:59:25 +00:00
|
|
|
# vertical
|
2010-08-29 06:39:31 +00:00
|
|
|
width = font.get_width() * fontsize
|
2013-11-07 08:35:04 +00:00
|
|
|
(vx, vy) = textdisp
|
2010-08-29 06:39:31 +00:00
|
|
|
if vx is None:
|
2014-06-13 14:05:04 +00:00
|
|
|
vx = width * 0.5
|
2010-08-29 06:39:31 +00:00
|
|
|
else:
|
|
|
|
vx = vx * fontsize * .001
|
|
|
|
vy = (1000 - vy) * fontsize * .001
|
|
|
|
tx = -vx
|
|
|
|
ty = vy + rise
|
|
|
|
bll = (tx, ty+self.adv)
|
|
|
|
bur = (tx+width, ty)
|
2010-02-27 03:59:25 +00:00
|
|
|
else:
|
|
|
|
# horizontal
|
2010-08-29 06:39:31 +00:00
|
|
|
height = font.get_height() * fontsize
|
2010-02-27 03:59:25 +00:00
|
|
|
descent = font.get_descent() * fontsize
|
2010-08-29 06:39:31 +00:00
|
|
|
ty = descent + rise
|
|
|
|
bll = (0, ty)
|
|
|
|
bur = (self.adv, ty+height)
|
2013-11-07 08:35:04 +00:00
|
|
|
(a, b, c, d, e, f) = self.matrix
|
2010-08-29 06:39:31 +00:00
|
|
|
self.upright = (0 < a*d*scaling and b*c <= 0)
|
2013-11-07 08:35:04 +00:00
|
|
|
(x0, y0) = apply_matrix_pt(self.matrix, bll)
|
|
|
|
(x1, y1) = apply_matrix_pt(self.matrix, bur)
|
2010-11-09 10:40:05 +00:00
|
|
|
if x1 < x0:
|
2013-11-07 08:35:04 +00:00
|
|
|
(x0, x1) = (x1, x0)
|
2010-11-09 10:40:05 +00:00
|
|
|
if y1 < y0:
|
2013-11-07 08:35:04 +00:00
|
|
|
(y0, y1) = (y1, y0)
|
|
|
|
LTComponent.__init__(self, (x0, y0, x1, y1))
|
2010-11-14 10:07:41 +00:00
|
|
|
if font.is_vertical():
|
2010-11-09 10:39:48 +00:00
|
|
|
self.size = self.width
|
|
|
|
else:
|
|
|
|
self.size = self.height
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2011-05-14 06:45:08 +00:00
|
|
|
return ('<%s %s matrix=%s font=%r adv=%s text=%r>' %
|
2013-11-07 07:14:53 +00:00
|
|
|
(self.__class__.__name__, bbox2str(self.bbox),
|
2011-05-14 06:45:08 +00:00
|
|
|
matrix2str(self.matrix), self.fontname, self.adv,
|
|
|
|
self.get_text()))
|
|
|
|
|
|
|
|
def get_text(self):
|
|
|
|
return self._text
|
2009-10-24 04:41:59 +00:00
|
|
|
|
2010-10-17 05:13:39 +00:00
|
|
|
def is_compatible(self, obj):
|
2010-11-09 10:40:05 +00:00
|
|
|
"""Returns True if two characters can coexist in the same line."""
|
2010-10-17 05:13:39 +00:00
|
|
|
return True
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTContainer(LTComponent):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Object that can be extended and analyzed"""
|
2009-10-24 04:41:59 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def __init__(self, bbox):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTComponent.__init__(self, bbox)
|
2010-11-09 10:40:05 +00:00
|
|
|
self._objs = []
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
2010-04-10 11:29:30 +00:00
|
|
|
def __iter__(self):
|
2010-11-09 10:40:05 +00:00
|
|
|
return iter(self._objs)
|
2010-04-10 11:29:30 +00:00
|
|
|
|
|
|
|
def __len__(self):
|
2010-11-09 10:40:05 +00:00
|
|
|
return len(self._objs)
|
2010-04-10 11:29:30 +00:00
|
|
|
|
|
|
|
def add(self, obj):
|
2010-10-17 05:13:33 +00:00
|
|
|
self._objs.append(obj)
|
2010-04-10 11:29:30 +00:00
|
|
|
return
|
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def extend(self, objs):
|
|
|
|
for obj in objs:
|
|
|
|
self.add(obj)
|
2010-04-10 11:29:30 +00:00
|
|
|
return
|
|
|
|
|
2011-05-14 05:17:39 +00:00
|
|
|
def analyze(self, laparams):
|
|
|
|
for obj in self._objs:
|
|
|
|
obj.analyze(laparams)
|
|
|
|
return
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
|
|
|
|
class LTExpandableContainer(LTContainer):
|
|
|
|
def __init__(self):
|
2013-11-07 08:35:04 +00:00
|
|
|
LTContainer.__init__(self, (+INF, +INF, -INF, -INF))
|
2010-04-10 11:29:30 +00:00
|
|
|
return
|
2009-05-16 04:16:00 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def add(self, obj):
|
|
|
|
LTContainer.add(self, obj)
|
|
|
|
self.set_bbox((min(self.x0, obj.x0), min(self.y0, obj.y0),
|
|
|
|
max(self.x1, obj.x1), max(self.y1, obj.y1)))
|
|
|
|
return
|
|
|
|
|
2009-05-16 04:16:00 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTTextContainer(LTExpandableContainer, LTText):
|
|
|
|
def __init__(self):
|
|
|
|
LTText.__init__(self)
|
|
|
|
LTExpandableContainer.__init__(self)
|
|
|
|
return
|
|
|
|
|
|
|
|
def get_text(self):
|
2019-12-29 20:20:20 +00:00
|
|
|
return ''.join(obj.get_text() for obj in self
|
|
|
|
if isinstance(obj, LTText))
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
|
|
|
|
class LTTextLine(LTTextContainer):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Contains a list of LTChar objects that represent a single text line.
|
|
|
|
|
|
|
|
The characters are aligned either horizontally or vertically, depending on
|
|
|
|
the text's writing mode.
|
|
|
|
"""
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def __init__(self, word_margin):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTTextContainer.__init__(self)
|
2010-11-09 10:40:05 +00:00
|
|
|
self.word_margin = word_margin
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2010-10-17 05:13:39 +00:00
|
|
|
return ('<%s %s %r>' %
|
2011-05-14 06:45:08 +00:00
|
|
|
(self.__class__.__name__, bbox2str(self.bbox),
|
|
|
|
self.get_text()))
|
2009-10-24 04:41:59 +00:00
|
|
|
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTTextContainer.analyze(self, laparams)
|
2013-11-11 10:17:45 +00:00
|
|
|
LTContainer.add(self, LTAnno('\n'))
|
2011-05-14 05:17:39 +00:00
|
|
|
return
|
2009-10-24 04:41:59 +00:00
|
|
|
|
2010-03-21 02:21:37 +00:00
|
|
|
def find_neighbors(self, plane, ratio):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
class LTTextLineHorizontal(LTTextLine):
|
2010-11-09 10:40:05 +00:00
|
|
|
def __init__(self, word_margin):
|
|
|
|
LTTextLine.__init__(self, word_margin)
|
|
|
|
self._x1 = +INF
|
|
|
|
return
|
|
|
|
|
|
|
|
def add(self, obj):
|
|
|
|
if isinstance(obj, LTChar) and self.word_margin:
|
2013-11-05 09:25:19 +00:00
|
|
|
margin = self.word_margin * max(obj.width, obj.height)
|
2010-11-09 10:40:05 +00:00
|
|
|
if self._x1 < obj.x0-margin:
|
2013-11-11 10:17:45 +00:00
|
|
|
LTContainer.add(self, LTAnno(' '))
|
2010-11-09 10:40:05 +00:00
|
|
|
self._x1 = obj.x1
|
|
|
|
LTTextLine.add(self, obj)
|
2010-03-20 05:43:34 +00:00
|
|
|
return
|
|
|
|
|
2010-03-21 02:21:37 +00:00
|
|
|
def find_neighbors(self, plane, ratio):
|
2011-08-14 00:44:21 +00:00
|
|
|
d = ratio*self.height
|
|
|
|
objs = plane.find((self.x0, self.y0-d, self.x1, self.y1+d))
|
2013-11-07 08:35:04 +00:00
|
|
|
return [obj for obj in objs
|
|
|
|
if (isinstance(obj, LTTextLineHorizontal) and
|
|
|
|
abs(obj.height-self.height) < d and
|
|
|
|
(abs(obj.x0-self.x0) < d or
|
|
|
|
abs(obj.x1-self.x1) < d))]
|
|
|
|
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
class LTTextLineVertical(LTTextLine):
|
2010-11-09 10:40:05 +00:00
|
|
|
def __init__(self, word_margin):
|
|
|
|
LTTextLine.__init__(self, word_margin)
|
|
|
|
self._y0 = -INF
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def add(self, obj):
|
|
|
|
if isinstance(obj, LTChar) and self.word_margin:
|
2013-11-05 09:25:19 +00:00
|
|
|
margin = self.word_margin * max(obj.width, obj.height)
|
2010-11-09 10:40:05 +00:00
|
|
|
if obj.y1+margin < self._y0:
|
2013-11-11 10:17:45 +00:00
|
|
|
LTContainer.add(self, LTAnno(' '))
|
2010-11-09 10:40:05 +00:00
|
|
|
self._y0 = obj.y0
|
|
|
|
LTTextLine.add(self, obj)
|
|
|
|
return
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2010-03-21 02:21:37 +00:00
|
|
|
def find_neighbors(self, plane, ratio):
|
2011-08-14 00:44:21 +00:00
|
|
|
d = ratio*self.width
|
|
|
|
objs = plane.find((self.x0-d, self.y0, self.x1+d, self.y1))
|
2013-11-07 08:35:04 +00:00
|
|
|
return [obj for obj in objs
|
|
|
|
if (isinstance(obj, LTTextLineVertical) and
|
|
|
|
abs(obj.width-self.width) < d and
|
|
|
|
(abs(obj.y0-self.y0) < d or
|
|
|
|
abs(obj.y1-self.y1) < d))]
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTTextBox(LTTextContainer):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Represents a group of text chunks in a rectangular area.
|
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
Note that this box is created by geometric analysis and does not
|
|
|
|
necessarily represents a logical boundary of the text. It contains a list
|
|
|
|
of LTTextLine objects.
|
2019-11-07 20:12:34 +00:00
|
|
|
"""
|
2009-05-05 12:26:29 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
def __init__(self):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTTextContainer.__init__(self)
|
2011-08-14 00:44:21 +00:00
|
|
|
self.index = -1
|
2009-10-24 04:41:59 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2011-05-14 05:17:39 +00:00
|
|
|
return ('<%s(%s) %s %r>' %
|
2011-05-14 06:45:08 +00:00
|
|
|
(self.__class__.__name__,
|
|
|
|
self.index, bbox2str(self.bbox), self.get_text()))
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
class LTTextBoxHorizontal(LTTextBox):
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2011-05-14 05:17:39 +00:00
|
|
|
LTTextBox.analyze(self, laparams)
|
2018-04-10 23:03:32 +00:00
|
|
|
self._objs.sort(key=lambda obj: -obj.y1)
|
2011-05-14 05:17:39 +00:00
|
|
|
return
|
2009-05-04 08:29:36 +00:00
|
|
|
|
2010-11-14 10:07:41 +00:00
|
|
|
def get_writing_mode(self):
|
|
|
|
return 'lr-tb'
|
|
|
|
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2010-03-20 05:43:34 +00:00
|
|
|
class LTTextBoxVertical(LTTextBox):
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2011-05-14 05:17:39 +00:00
|
|
|
LTTextBox.analyze(self, laparams)
|
2018-04-11 00:15:32 +00:00
|
|
|
self._objs.sort(key=lambda obj: -obj.x1)
|
2011-05-14 05:17:39 +00:00
|
|
|
return
|
2009-05-05 12:26:29 +00:00
|
|
|
|
2010-11-14 10:07:41 +00:00
|
|
|
def get_writing_mode(self):
|
|
|
|
return 'tb-rl'
|
|
|
|
|
2009-07-21 07:55:19 +00:00
|
|
|
|
2011-05-14 06:45:08 +00:00
|
|
|
class LTTextGroup(LTTextContainer):
|
2010-03-21 02:21:37 +00:00
|
|
|
def __init__(self, objs):
|
2011-05-14 06:45:08 +00:00
|
|
|
LTTextContainer.__init__(self)
|
2010-11-09 10:40:05 +00:00
|
|
|
self.extend(objs)
|
2010-03-21 02:21:37 +00:00
|
|
|
return
|
|
|
|
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2010-03-25 11:38:47 +00:00
|
|
|
class LTTextGroupLRTB(LTTextGroup):
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2011-05-14 05:17:39 +00:00
|
|
|
LTTextGroup.analyze(self, laparams)
|
2010-03-21 02:21:37 +00:00
|
|
|
# reorder the objects from top-left to bottom-right.
|
2019-12-29 20:20:20 +00:00
|
|
|
self._objs.sort(
|
|
|
|
key=lambda obj: (1 - laparams.boxes_flow) * obj.x0
|
|
|
|
- (1 + laparams.boxes_flow) * (obj.y0 + obj.y1))
|
2011-05-14 05:17:39 +00:00
|
|
|
return
|
2010-03-21 02:21:37 +00:00
|
|
|
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2010-03-25 11:38:47 +00:00
|
|
|
class LTTextGroupTBRL(LTTextGroup):
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2011-05-14 05:17:39 +00:00
|
|
|
LTTextGroup.analyze(self, laparams)
|
2010-03-21 02:21:37 +00:00
|
|
|
# reorder the objects from top-right to bottom-left.
|
2019-12-29 20:20:20 +00:00
|
|
|
self._objs.sort(
|
|
|
|
key=lambda obj: - (1 + laparams.boxes_flow) * (obj.x0 + obj.x1)
|
|
|
|
- (1 - laparams.boxes_flow) * obj.y1)
|
2011-05-14 05:17:39 +00:00
|
|
|
return
|
2010-03-21 02:21:37 +00:00
|
|
|
|
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
class LTLayoutContainer(LTContainer):
|
2010-12-26 07:56:21 +00:00
|
|
|
def __init__(self, bbox):
|
2010-11-09 10:40:05 +00:00
|
|
|
LTContainer.__init__(self, bbox)
|
2011-05-14 06:51:40 +00:00
|
|
|
self.groups = None
|
2010-03-21 02:21:37 +00:00
|
|
|
return
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2013-11-09 06:10:14 +00:00
|
|
|
# group_objects: group text object to textlines.
|
|
|
|
def group_objects(self, laparams, objs):
|
2010-10-17 05:13:33 +00:00
|
|
|
obj0 = None
|
|
|
|
line = None
|
|
|
|
for obj1 in objs:
|
2010-10-17 05:13:39 +00:00
|
|
|
if obj0 is not None:
|
2013-11-11 11:46:30 +00:00
|
|
|
# halign: obj0 and obj1 is horizontally aligned.
|
|
|
|
#
|
|
|
|
# +------+ - - -
|
|
|
|
# | obj0 | - - +------+ -
|
|
|
|
# | | | obj1 | | (line_overlap)
|
|
|
|
# +------+ - - | | -
|
|
|
|
# - - - +------+
|
|
|
|
#
|
|
|
|
# |<--->|
|
|
|
|
# (char_margin)
|
2019-12-29 20:20:20 +00:00
|
|
|
halign = \
|
|
|
|
obj0.is_compatible(obj1) \
|
|
|
|
and obj0.is_voverlap(obj1) \
|
|
|
|
and min(obj0.height, obj1.height) * laparams.line_overlap \
|
|
|
|
< obj0.voverlap(obj1) \
|
|
|
|
and obj0.hdistance(obj1) \
|
|
|
|
< max(obj0.width, obj1.width) * laparams.char_margin
|
2016-11-08 19:01:58 +00:00
|
|
|
|
2013-11-11 11:46:30 +00:00
|
|
|
# valign: obj0 and obj1 is vertically aligned.
|
|
|
|
#
|
|
|
|
# +------+
|
|
|
|
# | obj0 |
|
|
|
|
# | |
|
|
|
|
# +------+ - - -
|
|
|
|
# | | | (char_margin)
|
|
|
|
# +------+ - -
|
|
|
|
# | obj1 |
|
|
|
|
# | |
|
|
|
|
# +------+
|
|
|
|
#
|
|
|
|
# |<-->|
|
|
|
|
# (line_overlap)
|
2019-12-29 20:20:20 +00:00
|
|
|
valign = \
|
|
|
|
laparams.detect_vertical \
|
|
|
|
and obj0.is_compatible(obj1) \
|
|
|
|
and obj0.is_hoverlap(obj1) \
|
|
|
|
and min(obj0.width, obj1.width) * laparams.line_overlap \
|
|
|
|
< obj0.hoverlap(obj1) \
|
|
|
|
and obj0.vdistance(obj1) \
|
|
|
|
< max(obj0.height, obj1.height) * laparams.char_margin
|
2016-11-08 19:01:58 +00:00
|
|
|
|
2013-11-11 11:46:30 +00:00
|
|
|
if ((halign and isinstance(line, LTTextLineHorizontal)) or
|
2019-12-29 20:20:20 +00:00
|
|
|
(valign and isinstance(line, LTTextLineVertical))):
|
2019-11-10 11:18:49 +00:00
|
|
|
|
2010-10-17 05:13:33 +00:00
|
|
|
line.add(obj1)
|
2010-10-17 05:13:39 +00:00
|
|
|
elif line is not None:
|
2011-05-14 05:17:39 +00:00
|
|
|
yield line
|
2010-10-17 05:13:33 +00:00
|
|
|
line = None
|
2010-10-17 05:13:39 +00:00
|
|
|
else:
|
2013-11-11 11:46:30 +00:00
|
|
|
if valign and not halign:
|
2010-12-26 07:56:21 +00:00
|
|
|
line = LTTextLineVertical(laparams.word_margin)
|
2010-10-17 05:13:39 +00:00
|
|
|
line.add(obj0)
|
|
|
|
line.add(obj1)
|
2013-11-11 11:46:30 +00:00
|
|
|
elif halign and not valign:
|
2010-12-26 07:56:21 +00:00
|
|
|
line = LTTextLineHorizontal(laparams.word_margin)
|
2010-10-17 05:13:39 +00:00
|
|
|
line.add(obj0)
|
|
|
|
line.add(obj1)
|
|
|
|
else:
|
2010-12-26 07:56:21 +00:00
|
|
|
line = LTTextLineHorizontal(laparams.word_margin)
|
2010-10-17 05:13:39 +00:00
|
|
|
line.add(obj0)
|
2011-05-14 05:17:39 +00:00
|
|
|
yield line
|
2010-10-17 05:13:39 +00:00
|
|
|
line = None
|
|
|
|
obj0 = obj1
|
2010-10-17 05:13:33 +00:00
|
|
|
if line is None:
|
2010-12-26 07:56:21 +00:00
|
|
|
line = LTTextLineHorizontal(laparams.word_margin)
|
2010-10-17 05:13:39 +00:00
|
|
|
line.add(obj0)
|
2011-05-14 05:17:39 +00:00
|
|
|
yield line
|
2010-10-17 05:13:33 +00:00
|
|
|
return
|
|
|
|
|
2013-11-09 06:10:14 +00:00
|
|
|
# group_textlines: group neighboring lines to textboxes.
|
|
|
|
def group_textlines(self, laparams, lines):
|
2013-10-22 09:24:39 +00:00
|
|
|
plane = Plane(self.bbox)
|
|
|
|
plane.extend(lines)
|
2010-11-09 10:40:05 +00:00
|
|
|
boxes = {}
|
|
|
|
for line in lines:
|
2010-12-26 07:56:21 +00:00
|
|
|
neighbors = line.find_neighbors(plane, laparams.line_margin)
|
2019-12-29 20:20:20 +00:00
|
|
|
if line not in neighbors:
|
|
|
|
continue
|
2010-11-09 10:40:05 +00:00
|
|
|
members = []
|
2010-10-17 05:13:33 +00:00
|
|
|
for obj1 in neighbors:
|
2010-11-09 10:40:05 +00:00
|
|
|
members.append(obj1)
|
|
|
|
if obj1 in boxes:
|
|
|
|
members.extend(boxes.pop(obj1))
|
2010-10-17 05:13:33 +00:00
|
|
|
if isinstance(line, LTTextLineHorizontal):
|
2010-11-09 10:40:05 +00:00
|
|
|
box = LTTextBoxHorizontal()
|
2010-10-17 05:13:33 +00:00
|
|
|
else:
|
2010-11-09 10:40:05 +00:00
|
|
|
box = LTTextBoxVertical()
|
|
|
|
for obj in uniq(members):
|
|
|
|
box.add(obj)
|
|
|
|
boxes[obj] = box
|
2010-10-17 05:13:33 +00:00
|
|
|
done = set()
|
|
|
|
for line in lines:
|
2019-12-29 20:20:20 +00:00
|
|
|
if line not in boxes:
|
|
|
|
continue
|
2010-11-09 10:40:05 +00:00
|
|
|
box = boxes[line]
|
2013-11-07 08:35:04 +00:00
|
|
|
if box in done:
|
|
|
|
continue
|
2010-11-09 10:40:05 +00:00
|
|
|
done.add(box)
|
2013-10-09 13:13:22 +00:00
|
|
|
if not box.is_empty():
|
|
|
|
yield box
|
2010-10-17 05:13:33 +00:00
|
|
|
return
|
|
|
|
|
2010-12-26 07:56:21 +00:00
|
|
|
def group_textboxes(self, laparams, boxes):
|
2019-10-31 08:22:58 +00:00
|
|
|
"""Group textboxes hierarchically.
|
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
Get pair-wise distances, via dist func defined below, and then merge
|
|
|
|
from the closest textbox pair. Once obj1 and obj2 are merged /
|
|
|
|
grouped, the resulting group is considered as a new object, and its
|
|
|
|
distances to other objects & groups are added to the process queue.
|
2019-10-31 08:22:58 +00:00
|
|
|
|
2019-12-29 20:20:20 +00:00
|
|
|
For performance reason, pair-wise distances and object pair info are
|
|
|
|
maintained in a heap of (idx, dist, id(obj1), id(obj2), obj1, obj2)
|
|
|
|
tuples. It ensures quick access to the smallest element. Note that
|
|
|
|
since comparison operators, e.g., __lt__, are disabled for
|
|
|
|
LTComponent, id(obj) has to appear before obj in element tuples.
|
2019-10-31 08:22:58 +00:00
|
|
|
|
|
|
|
:param laparams: LAParams object.
|
|
|
|
:param boxes: All textbox objects to be grouped.
|
|
|
|
:return: a list that has only one element, the final top level textbox.
|
|
|
|
"""
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2011-05-14 05:17:39 +00:00
|
|
|
def dist(obj1, obj2):
|
2010-06-19 03:56:50 +00:00
|
|
|
"""A distance function between two TextBoxes.
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2010-06-19 03:56:50 +00:00
|
|
|
Consider the bounding rectangle for obj1 and obj2.
|
2013-11-07 07:14:53 +00:00
|
|
|
Return its area less the areas of obj1 and obj2,
|
2010-06-19 03:56:50 +00:00
|
|
|
shown as 'www' below. This value may be negative.
|
2013-11-07 08:35:04 +00:00
|
|
|
+------+..........+ (x1, y1)
|
2011-02-14 14:41:23 +00:00
|
|
|
| obj1 |wwwwwwwwww:
|
|
|
|
+------+www+------+
|
|
|
|
:wwwwwwwwww| obj2 |
|
2013-11-07 08:35:04 +00:00
|
|
|
(x0, y0) +..........+------+
|
2010-06-19 03:56:50 +00:00
|
|
|
"""
|
2013-11-07 08:35:04 +00:00
|
|
|
x0 = min(obj1.x0, obj2.x0)
|
|
|
|
y0 = min(obj1.y0, obj2.y0)
|
|
|
|
x1 = max(obj1.x1, obj2.x1)
|
|
|
|
y1 = max(obj1.y1, obj2.y1)
|
2019-12-29 20:20:20 +00:00
|
|
|
return (x1 - x0) * (y1 - y0) \
|
|
|
|
- obj1.width*obj1.height - obj2.width*obj2.height
|
2013-11-07 08:35:04 +00:00
|
|
|
|
2011-05-14 16:22:19 +00:00
|
|
|
def isany(obj1, obj2):
|
2019-10-31 08:22:58 +00:00
|
|
|
"""Check if there's any other object between obj1 and obj2."""
|
2013-11-07 08:35:04 +00:00
|
|
|
x0 = min(obj1.x0, obj2.x0)
|
|
|
|
y0 = min(obj1.y0, obj2.y0)
|
|
|
|
x1 = max(obj1.x1, obj2.x1)
|
|
|
|
y1 = max(obj1.y1, obj2.y1)
|
|
|
|
objs = set(plane.find((x0, y0, x1, y1)))
|
|
|
|
return objs.difference((obj1, obj2))
|
2014-06-26 08:41:31 +00:00
|
|
|
|
2019-10-31 08:22:58 +00:00
|
|
|
dists = []
|
2014-09-03 13:26:08 +00:00
|
|
|
for i in range(len(boxes)):
|
2011-05-14 05:17:39 +00:00
|
|
|
obj1 = boxes[i]
|
2014-09-03 13:26:08 +00:00
|
|
|
for j in range(i+1, len(boxes)):
|
2011-05-14 05:17:39 +00:00
|
|
|
obj2 = boxes[j]
|
2019-11-10 11:18:49 +00:00
|
|
|
dists.append((False, dist(obj1, obj2), id(obj1), id(obj2),
|
|
|
|
obj1, obj2))
|
2019-10-31 08:22:58 +00:00
|
|
|
heapq.heapify(dists)
|
|
|
|
|
2013-10-22 09:24:39 +00:00
|
|
|
plane = Plane(self.bbox)
|
|
|
|
plane.extend(boxes)
|
2019-10-31 08:22:58 +00:00
|
|
|
done = set()
|
|
|
|
while len(dists) > 0:
|
2019-11-10 11:18:49 +00:00
|
|
|
(skip_isany, d, id1, id2, obj1, obj2) = heapq.heappop(dists)
|
2019-10-31 08:22:58 +00:00
|
|
|
# Skip objects that are already merged
|
|
|
|
if (id1 not in done) and (id2 not in done):
|
2019-11-10 11:18:49 +00:00
|
|
|
if skip_isany and isany(obj1, obj2):
|
|
|
|
heapq.heappush(dists, (True, d, id1, id2, obj1, obj2))
|
2019-10-31 08:22:58 +00:00
|
|
|
continue
|
|
|
|
if isinstance(obj1, (LTTextBoxVertical, LTTextGroupTBRL)) or \
|
|
|
|
isinstance(obj2, (LTTextBoxVertical, LTTextGroupTBRL)):
|
|
|
|
group = LTTextGroupTBRL([obj1, obj2])
|
|
|
|
else:
|
|
|
|
group = LTTextGroupLRTB([obj1, obj2])
|
|
|
|
plane.remove(obj1)
|
|
|
|
plane.remove(obj2)
|
|
|
|
done.update([id1, id2])
|
|
|
|
|
|
|
|
for other in plane:
|
2019-12-29 20:20:20 +00:00
|
|
|
heapq.heappush(dists, (False, dist(group, other),
|
|
|
|
id(group), id(other), group, other))
|
2019-10-31 08:22:58 +00:00
|
|
|
plane.add(group)
|
2011-05-14 06:51:40 +00:00
|
|
|
return list(plane)
|
2013-11-07 07:14:53 +00:00
|
|
|
|
2011-05-14 16:22:19 +00:00
|
|
|
def analyze(self, laparams):
|
|
|
|
# textobjs is a list of LTChar objects, i.e.
|
|
|
|
# it has all the individual characters in the page.
|
2019-12-29 20:20:20 +00:00
|
|
|
(textobjs, otherobjs) = fsplit(lambda obj: isinstance(obj, LTChar),
|
|
|
|
self)
|
2011-05-14 16:22:19 +00:00
|
|
|
for obj in otherobjs:
|
|
|
|
obj.analyze(laparams)
|
2013-11-07 08:35:04 +00:00
|
|
|
if not textobjs:
|
|
|
|
return
|
2013-11-09 06:10:14 +00:00
|
|
|
textlines = list(self.group_objects(laparams, textobjs))
|
2011-05-14 16:22:19 +00:00
|
|
|
(empties, textlines) = fsplit(lambda obj: obj.is_empty(), textlines)
|
|
|
|
for obj in empties:
|
|
|
|
obj.analyze(laparams)
|
2013-11-09 06:10:14 +00:00
|
|
|
textboxes = list(self.group_textlines(laparams, textlines))
|
2019-12-29 20:20:20 +00:00
|
|
|
if -1 <= laparams.boxes_flow and laparams.boxes_flow <= +1 \
|
|
|
|
and textboxes:
|
2013-10-09 13:13:22 +00:00
|
|
|
self.groups = self.group_textboxes(laparams, textboxes)
|
|
|
|
assigner = IndexAssigner()
|
|
|
|
for group in self.groups:
|
|
|
|
group.analyze(laparams)
|
|
|
|
assigner.run(group)
|
2013-11-07 08:35:04 +00:00
|
|
|
textboxes.sort(key=lambda box: box.index)
|
2016-09-26 09:06:34 +00:00
|
|
|
else:
|
|
|
|
def getkey(box):
|
|
|
|
if isinstance(box, LTTextBoxVertical):
|
|
|
|
return (0, -box.x1, box.y0)
|
|
|
|
else:
|
|
|
|
return (1, box.y0, box.x0)
|
|
|
|
textboxes.sort(key=getkey)
|
2011-05-14 16:22:19 +00:00
|
|
|
self._objs = textboxes + otherobjs + empties
|
|
|
|
return
|
|
|
|
|
2010-04-10 11:30:03 +00:00
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
class LTFigure(LTLayoutContainer):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Represents an area used by PDF Form objects.
|
|
|
|
|
|
|
|
PDF Forms can be used to present figures or pictures by embedding yet
|
|
|
|
another PDF document within a page. Note that LTFigure objects can appear
|
|
|
|
recursively.
|
|
|
|
"""
|
2010-04-10 11:30:03 +00:00
|
|
|
|
2010-12-26 07:56:21 +00:00
|
|
|
def __init__(self, name, bbox, matrix):
|
2010-08-29 06:39:31 +00:00
|
|
|
self.name = name
|
|
|
|
self.matrix = matrix
|
2013-11-07 08:35:04 +00:00
|
|
|
(x, y, w, h) = bbox
|
2019-12-29 20:20:20 +00:00
|
|
|
bounds = ((x, y), (x + w, y), (x, y + h), (x + w, y + h))
|
|
|
|
bbox = get_bound(apply_matrix_pt(matrix, (p, q)) for (p, q) in bounds)
|
2010-12-26 07:56:21 +00:00
|
|
|
LTLayoutContainer.__init__(self, bbox)
|
2010-04-10 11:30:03 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2010-10-17 05:13:39 +00:00
|
|
|
return ('<%s(%s) %s matrix=%s>' %
|
|
|
|
(self.__class__.__name__, self.name,
|
|
|
|
bbox2str(self.bbox), matrix2str(self.matrix)))
|
2010-04-10 11:30:03 +00:00
|
|
|
|
2010-12-26 07:56:21 +00:00
|
|
|
def analyze(self, laparams):
|
2013-11-07 08:35:04 +00:00
|
|
|
if not laparams.all_texts:
|
|
|
|
return
|
2011-05-14 05:17:39 +00:00
|
|
|
LTLayoutContainer.analyze(self, laparams)
|
2013-11-07 07:14:53 +00:00
|
|
|
return
|
2010-04-10 11:30:03 +00:00
|
|
|
|
|
|
|
|
2010-11-09 10:40:05 +00:00
|
|
|
class LTPage(LTLayoutContainer):
|
2019-11-07 20:12:34 +00:00
|
|
|
"""Represents an entire page.
|
|
|
|
|
|
|
|
May contain child objects like LTTextBox, LTFigure, LTImage, LTRect,
|
|
|
|
LTCurve and LTLine.
|
|
|
|
"""
|
2010-04-10 11:30:03 +00:00
|
|
|
|
2010-12-26 07:56:21 +00:00
|
|
|
def __init__(self, pageid, bbox, rotate=0):
|
|
|
|
LTLayoutContainer.__init__(self, bbox)
|
2010-04-10 11:30:03 +00:00
|
|
|
self.pageid = pageid
|
|
|
|
self.rotate = rotate
|
|
|
|
return
|
|
|
|
|
|
|
|
def __repr__(self):
|
2010-10-17 05:13:39 +00:00
|
|
|
return ('<%s(%r) %s rotate=%r>' %
|
|
|
|
(self.__class__.__name__, self.pageid,
|
|
|
|
bbox2str(self.bbox), self.rotate))
|