doctextstyle 5.20.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doctextstyle/__init__.py +23 -0
- doctextstyle/__patch__.py +8 -0
- doctextstyle/cli.py +72 -0
- doctextstyle/cluster.py +166 -0
- doctextstyle/extractor.py +178 -0
- doctextstyle/features/__init__.py +51 -0
- doctextstyle/features/blockquote.py +69 -0
- doctextstyle/features/content.py +34 -0
- doctextstyle/features/footnote.py +64 -0
- doctextstyle/features/headline.py +161 -0
- doctextstyle/features/pagesize.py +36 -0
- doctextstyle/features/paragraph.py +62 -0
- doctextstyle/features/textbounding.py +67 -0
- doctextstyle/parser.py +394 -0
- doctextstyle/path.py +14 -0
- doctextstyle/utils.py +88 -0
- doctextstyle/vector/__init__.py +38 -0
- doctextstyle/vector/decide.py +75 -0
- doctextstyle/vector/extract.py +155 -0
- doctextstyle/vector/headlines.py +274 -0
- doctextstyle/vector/prepare.py +112 -0
- doctextstyle-5.20.5.dist-info/METADATA +46 -0
- doctextstyle-5.20.5.dist-info/RECORD +34 -0
- doctextstyle-5.20.5.dist-info/WHEEL +5 -0
- doctextstyle-5.20.5.dist-info/entry_points.txt +3 -0
- doctextstyle-5.20.5.dist-info/licenses/LICENSE +21 -0
- doctextstyle-5.20.5.dist-info/top_level.txt +3 -0
- hey/__init__.py +16 -0
- magic/__init__.py +19 -0
- magic/cli.py +61 -0
- magic/feature/__init__.py +8 -0
- magic/feature/content.py +224 -0
- magic/feature/oneline.py +28 -0
- magic/path.py +18 -0
doctextstyle/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import doctextstyle.__patch__
|
|
13
|
+
import hey
|
|
14
|
+
from doctextstyle.vector.extract import run as extract_headlines # pylint:disable=C0412
|
|
15
|
+
from doctextstyle.vector.extract import run_fromdata as headlines_fromdata # pylint:disable=C0412
|
|
16
|
+
|
|
17
|
+
__version__ = hey.__version__
|
|
18
|
+
|
|
19
|
+
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
20
|
+
PROCESS = 'doctextstyle'
|
|
21
|
+
|
|
22
|
+
JUSTIFIED = 1
|
|
23
|
+
NOT_JUSTIFIED = 0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
doctextstyle/cli.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import serializeraw
|
|
13
|
+
import utilo
|
|
14
|
+
import utilo.cli
|
|
15
|
+
|
|
16
|
+
import doctextstyle
|
|
17
|
+
import doctextstyle.extractor
|
|
18
|
+
|
|
19
|
+
DEFAULT_OUTPUT_FILE = 'doctextstyle__textstyle.yaml'
|
|
20
|
+
|
|
21
|
+
DESCRIPTION = """\
|
|
22
|
+
Extracts common text layout properties for text, headlines(h1, h2, h3),
|
|
23
|
+
pagenumber, footnotes, paragraphs and lists.
|
|
24
|
+
|
|
25
|
+
Requires: `rawmaker__oneline` data to create pagetextnavigators
|
|
26
|
+
`rawmaker__oneline` data to create pagecontenttextnavigators
|
|
27
|
+
|
|
28
|
+
Writes: `doctextstyle__textstyle.yaml`
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@utilo.saveme
|
|
33
|
+
def main() -> int:
|
|
34
|
+
commands = []
|
|
35
|
+
parser = utilo.cli.create_parser(
|
|
36
|
+
todo=commands,
|
|
37
|
+
description=DESCRIPTION,
|
|
38
|
+
config=utilo.ParserConfiguration(
|
|
39
|
+
inputparameter=True,
|
|
40
|
+
multiprocessed=True,
|
|
41
|
+
outputparameter=True,
|
|
42
|
+
pages=True,
|
|
43
|
+
prefix=False,
|
|
44
|
+
),
|
|
45
|
+
version=doctextstyle.__version__,
|
|
46
|
+
prog=doctextstyle.PROCESS,
|
|
47
|
+
)
|
|
48
|
+
args = utilo.parse(parser)
|
|
49
|
+
utilo.log('run doctextstyle')
|
|
50
|
+
selected_pages = utilo.pages_fromargs(args)
|
|
51
|
+
|
|
52
|
+
inpath, outpath = utilo.cli.sources(args, singleinput=True) # pylint:disable=W0632
|
|
53
|
+
inpath = inpath[0]
|
|
54
|
+
|
|
55
|
+
if os.path.isdir(outpath):
|
|
56
|
+
outpath = os.path.join(outpath, DEFAULT_OUTPUT_FILE)
|
|
57
|
+
|
|
58
|
+
# ensure that output directory exists
|
|
59
|
+
parent, _ = os.path.split(outpath)
|
|
60
|
+
os.makedirs(parent, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
extracted = doctextstyle.extractor.extract(inpath, pages=selected_pages)
|
|
64
|
+
except FileNotFoundError as error:
|
|
65
|
+
utilo.error('missing input location')
|
|
66
|
+
utilo.error(f'{error}')
|
|
67
|
+
return utilo.FAILURE
|
|
68
|
+
|
|
69
|
+
dumped = serializeraw.dump_doctextstyle(extracted)
|
|
70
|
+
utilo.file_replace(outpath, dumped)
|
|
71
|
+
utilo.log('completed')
|
|
72
|
+
return utilo.SUCCESS
|
doctextstyle/cluster.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import collections
|
|
11
|
+
import enum
|
|
12
|
+
import functools
|
|
13
|
+
|
|
14
|
+
import configos
|
|
15
|
+
import iamraw
|
|
16
|
+
import utilo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClusterProperty(enum.Enum):
|
|
20
|
+
SIZE = enum.auto()
|
|
21
|
+
FONT = enum.auto()
|
|
22
|
+
BEFORE = enum.auto()
|
|
23
|
+
AFTER = enum.auto()
|
|
24
|
+
YPOS = enum.auto()
|
|
25
|
+
LEFT = enum.auto()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
ClusterPropertySelection = list[ClusterProperty]
|
|
29
|
+
Tol = collections.namedtuple('Tol', 'abs, rel')
|
|
30
|
+
NO_TOLERANCE = Tol(0.0, 0.0)
|
|
31
|
+
|
|
32
|
+
CLUSTER_SIZE_MIN = configos.HV_INT_PLUS(default=5)
|
|
33
|
+
|
|
34
|
+
CLUSTER_SIZE_DIFF_ABS = configos.HV_FLOAT_PLUS(default=0.5)
|
|
35
|
+
|
|
36
|
+
CLUSTER_SIZE_DIFF_REL = configos.HV_FLOAT_PLUS(default=0.1)
|
|
37
|
+
|
|
38
|
+
CLUSTER_AFTER_DIFF_ABS = configos.HV_FLOAT_PLUS(default=2.0)
|
|
39
|
+
|
|
40
|
+
CLUSTER_AFTER_DIFF_REL = configos.HV_FLOAT_PLUS(default=0.1)
|
|
41
|
+
|
|
42
|
+
CLUSTER_BEFORE_DIFF_ABS = configos.HV_FLOAT_PLUS(default=2.0)
|
|
43
|
+
|
|
44
|
+
CLUSTER_BEFORE_DIFF_REL = configos.HV_FLOAT_PLUS(default=0.1)
|
|
45
|
+
|
|
46
|
+
CLUSTER_LEFT_DIFF_ABS = configos.HV_FLOAT_PLUS(default=15.0)
|
|
47
|
+
|
|
48
|
+
CLUSTER_LEFT_DIFF_REL = configos.HV_FLOAT_PLUS(default=0.15)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def cluster(
|
|
52
|
+
items: iamraw.TextProperties,
|
|
53
|
+
selection: ClusterPropertySelection = None,
|
|
54
|
+
validator: callable = None,
|
|
55
|
+
*,
|
|
56
|
+
minsize: int = CLUSTER_SIZE_MIN,
|
|
57
|
+
unique_content: bool = False,
|
|
58
|
+
max_size_diff=Tol(CLUSTER_SIZE_DIFF_ABS, CLUSTER_SIZE_DIFF_REL),
|
|
59
|
+
max_after_diff=Tol(CLUSTER_AFTER_DIFF_ABS, CLUSTER_AFTER_DIFF_REL),
|
|
60
|
+
max_before_diff=Tol(CLUSTER_BEFORE_DIFF_ABS, CLUSTER_BEFORE_DIFF_REL),
|
|
61
|
+
max_left_diff=Tol(CLUSTER_LEFT_DIFF_ABS, CLUSTER_LEFT_DIFF_REL),
|
|
62
|
+
):
|
|
63
|
+
if selection:
|
|
64
|
+
selection = set(selection)
|
|
65
|
+
if validator:
|
|
66
|
+
items = [item for item in items if validator(item)]
|
|
67
|
+
|
|
68
|
+
decider = functools.partial(
|
|
69
|
+
classifier,
|
|
70
|
+
selection=selection,
|
|
71
|
+
max_size_diff=max_size_diff,
|
|
72
|
+
max_after_diff=max_after_diff,
|
|
73
|
+
max_before_diff=max_before_diff,
|
|
74
|
+
max_left_diff=max_left_diff,
|
|
75
|
+
)
|
|
76
|
+
clustered = utilo.determine_cluster(
|
|
77
|
+
todo=items,
|
|
78
|
+
classifier=decider,
|
|
79
|
+
min_elements=minsize,
|
|
80
|
+
)
|
|
81
|
+
if unique_content:
|
|
82
|
+
clustered = [item for item in clustered if iscontent_unique(item)]
|
|
83
|
+
return clustered
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def classifier( # pylint:disable=R1260
|
|
87
|
+
candidat,
|
|
88
|
+
clusteritem,
|
|
89
|
+
selection,
|
|
90
|
+
max_size_diff=None,
|
|
91
|
+
max_after_diff=None,
|
|
92
|
+
max_before_diff=None,
|
|
93
|
+
max_left_diff=None,
|
|
94
|
+
) -> bool:
|
|
95
|
+
if selection is None or ClusterProperty.SIZE in selection:
|
|
96
|
+
if not utilo.pnear(
|
|
97
|
+
candidat.size,
|
|
98
|
+
clusteritem.size,
|
|
99
|
+
abs_tol=max_size_diff.abs,
|
|
100
|
+
rel_tol=max_size_diff.rel,
|
|
101
|
+
):
|
|
102
|
+
return False
|
|
103
|
+
if selection is None or ClusterProperty.BEFORE in selection:
|
|
104
|
+
if not utilo.pnear(
|
|
105
|
+
candidat.before,
|
|
106
|
+
clusteritem.before,
|
|
107
|
+
abs_tol=max_before_diff.abs,
|
|
108
|
+
rel_tol=max_before_diff.rel,
|
|
109
|
+
):
|
|
110
|
+
return False
|
|
111
|
+
if selection is None or ClusterProperty.AFTER in selection:
|
|
112
|
+
if not utilo.pnear(
|
|
113
|
+
candidat.after,
|
|
114
|
+
clusteritem.after,
|
|
115
|
+
abs_tol=max_after_diff.abs,
|
|
116
|
+
rel_tol=max_after_diff.rel,
|
|
117
|
+
):
|
|
118
|
+
return False
|
|
119
|
+
if selection is None or ClusterProperty.LEFT in selection:
|
|
120
|
+
if not utilo.pnear(
|
|
121
|
+
candidat.left,
|
|
122
|
+
clusteritem.left,
|
|
123
|
+
abs_tol=max_left_diff.abs,
|
|
124
|
+
rel_tol=max_left_diff.rel,
|
|
125
|
+
):
|
|
126
|
+
return False
|
|
127
|
+
if selection is None or ClusterProperty.FONT in selection:
|
|
128
|
+
if candidat.font != clusteritem.font:
|
|
129
|
+
return False
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def iscontent_unique(current) -> bool:
|
|
134
|
+
expected = len(current.content)
|
|
135
|
+
current = {item.hashed for item in current.content}
|
|
136
|
+
return len(current) == expected
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def bestmatch(clustered, number: int = 0):
|
|
140
|
+
try:
|
|
141
|
+
largest_cluster_first_item = clustered[number].content[0]
|
|
142
|
+
except IndexError:
|
|
143
|
+
# no clusted content
|
|
144
|
+
return None
|
|
145
|
+
size = largest_cluster_first_item.size
|
|
146
|
+
font = largest_cluster_first_item.font
|
|
147
|
+
# TODO: PLUS INF OR MINUS INF
|
|
148
|
+
before = [item.before for item in clustered[number].content]
|
|
149
|
+
before = [utilo.INF if item is None else item for item in before]
|
|
150
|
+
before = utilo.mode(before)
|
|
151
|
+
after = [item.after for item in clustered[number].content]
|
|
152
|
+
after = [utilo.INF if item is None else item for item in after]
|
|
153
|
+
after = utilo.mode(after)
|
|
154
|
+
length = len(clustered[0])
|
|
155
|
+
|
|
156
|
+
if before is utilo.INF:
|
|
157
|
+
before = None
|
|
158
|
+
if after is utilo.INF:
|
|
159
|
+
after = None
|
|
160
|
+
return (size, font, length, (before, after))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def remove(flats, toremove: list):
|
|
164
|
+
toremove = set(toremove)
|
|
165
|
+
flats = [item for item in flats if item not in toremove]
|
|
166
|
+
return flats
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import contextlib
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
import iamraw
|
|
14
|
+
import serializeraw
|
|
15
|
+
import texmex
|
|
16
|
+
import utilo
|
|
17
|
+
|
|
18
|
+
import doctextstyle
|
|
19
|
+
import doctextstyle.features
|
|
20
|
+
import doctextstyle.features.blockquote
|
|
21
|
+
import doctextstyle.features.content
|
|
22
|
+
import doctextstyle.features.footnote
|
|
23
|
+
import doctextstyle.features.headline
|
|
24
|
+
import doctextstyle.features.pagesize
|
|
25
|
+
import doctextstyle.features.textbounding as dtt
|
|
26
|
+
import doctextstyle.parser
|
|
27
|
+
import doctextstyle.utils
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract(path: str, pages: tuple = None) -> iamraw.DocTextStyle: # pylint:disable=R0914,R0915
|
|
31
|
+
navigator, cnavigators, magic = load_data(path, pages=pages)
|
|
32
|
+
|
|
33
|
+
parsed = doctextstyle.parser.parses(navigator, magic)
|
|
34
|
+
flat = doctextstyle.utils.flatten(parsed)
|
|
35
|
+
|
|
36
|
+
text = doctextstyle.features.text(flat)
|
|
37
|
+
if not text or len(text) < 4:
|
|
38
|
+
utilo.error('not enough text data')
|
|
39
|
+
return iamraw.DocTextStyle()
|
|
40
|
+
text_after = text[3][1]
|
|
41
|
+
|
|
42
|
+
result = iamraw.DocTextStyle(
|
|
43
|
+
text_size=text[0],
|
|
44
|
+
text_distance=text_after,
|
|
45
|
+
text_family=text[1],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
extract_headlines(result, flat)
|
|
49
|
+
|
|
50
|
+
pagenumber = doctextstyle.features.pagenumber(flat)
|
|
51
|
+
if pagenumber:
|
|
52
|
+
result.pagenumber_size = pagenumber[0]
|
|
53
|
+
result.pagenumber_family = pagenumber[1]
|
|
54
|
+
|
|
55
|
+
extract_footnotes(result, flat)
|
|
56
|
+
|
|
57
|
+
pagesizes = doctextstyle.features.pagesize.pagesizes(path, pages=pages)
|
|
58
|
+
|
|
59
|
+
if pagesizes:
|
|
60
|
+
result.page_width, result.page_height = pagesizes[0][0]
|
|
61
|
+
with contextlib.suppress(IndexError):
|
|
62
|
+
result.page_rotated_width, result.page_rotated_height = pagesizes[1][0] # yapf:disable
|
|
63
|
+
else:
|
|
64
|
+
utilo.error('no pagesize, too few pages to run feature')
|
|
65
|
+
|
|
66
|
+
extract_contentborder(result, path, pages)
|
|
67
|
+
extract_textdimension(result, navigator, cnavigators)
|
|
68
|
+
extract_blockquote(result, flat)
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_data(path: str, pages: tuple = None):
|
|
73
|
+
navigator = serializeraw.ptn_frompath(
|
|
74
|
+
path,
|
|
75
|
+
prefix='oneline',
|
|
76
|
+
pages=pages,
|
|
77
|
+
)
|
|
78
|
+
try:
|
|
79
|
+
cnavigators = serializeraw.ptcn_frompath(
|
|
80
|
+
path,
|
|
81
|
+
prefix='oneline',
|
|
82
|
+
pages=pages,
|
|
83
|
+
)
|
|
84
|
+
except FileNotFoundError as error:
|
|
85
|
+
cnavigators = None
|
|
86
|
+
utilo.error(f'missing page text content navigator: {error}')
|
|
87
|
+
|
|
88
|
+
magic = iamraw.path.magic_content(path)
|
|
89
|
+
magic = serializeraw.load_types(magic) if os.path.exists(magic) else []
|
|
90
|
+
return navigator, cnavigators, magic
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def extract_footnotes(result, flat):
|
|
94
|
+
footnotes = doctextstyle.features.footnote.footnote(flat)
|
|
95
|
+
if footnotes:
|
|
96
|
+
footnote_after = footnotes[3][1]
|
|
97
|
+
result.footnote_size = footnotes[0]
|
|
98
|
+
result.footnote_family = footnotes[1]
|
|
99
|
+
result.footnote_distance = footnote_after
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def extract_contentborder(result, path, pages):
|
|
103
|
+
leftright = iamraw.path.groupme_border_leftright(path)
|
|
104
|
+
content = doctextstyle.features.content.content(leftright, pages=pages)
|
|
105
|
+
if content:
|
|
106
|
+
normal = content[0][0]
|
|
107
|
+
result.content_left = normal[0]
|
|
108
|
+
result.content_right = normal[1]
|
|
109
|
+
result.content_top = normal[2]
|
|
110
|
+
result.content_bottom = normal[3]
|
|
111
|
+
if len(content) > 1:
|
|
112
|
+
rotated = content[1][0]
|
|
113
|
+
result.content_rotated_left = rotated[0]
|
|
114
|
+
result.content_rotated_right = rotated[1]
|
|
115
|
+
result.content_rotated_top = rotated[2]
|
|
116
|
+
result.content_rotated_bottom = rotated[3]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def extract_headlines(result, flat):
|
|
120
|
+
headlines = doctextstyle.features.headline.headlines(flat)
|
|
121
|
+
if not headlines:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
result.h1_size = headlines[0][0]
|
|
125
|
+
result.h1_family = headlines[0][1]
|
|
126
|
+
result.h1_before = headlines[0][3][0]
|
|
127
|
+
result.h1_after = headlines[0][3][1]
|
|
128
|
+
|
|
129
|
+
if len(headlines) == 1:
|
|
130
|
+
return
|
|
131
|
+
result.h2_size = headlines[1][0]
|
|
132
|
+
result.h2_family = headlines[1][1]
|
|
133
|
+
result.h2_before = headlines[1][3][0]
|
|
134
|
+
result.h2_after = headlines[1][3][1]
|
|
135
|
+
|
|
136
|
+
if len(headlines) == 2:
|
|
137
|
+
return
|
|
138
|
+
result.h3_size = headlines[2][0]
|
|
139
|
+
result.h3_family = headlines[2][1]
|
|
140
|
+
result.h3_before = headlines[2][3][0]
|
|
141
|
+
result.h3_after = headlines[2][3][1]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def extract_textdimension(result, navigators, cnavigators):
|
|
145
|
+
if not cnavigators:
|
|
146
|
+
return
|
|
147
|
+
twidth = dtt.text_width(cnavigators)
|
|
148
|
+
twidth_min = dtt.text_width_min(cnavigators)
|
|
149
|
+
twidth_max = dtt.text_width_max(cnavigators)
|
|
150
|
+
|
|
151
|
+
result.text_width = twidth
|
|
152
|
+
result.text_width_min = twidth_min
|
|
153
|
+
result.text_width_max = twidth_max
|
|
154
|
+
|
|
155
|
+
result.text_left = texmex.document_textfeed(cnavigators)
|
|
156
|
+
result.text_right = texmex.document_textfeed(navigators, left=False)
|
|
157
|
+
|
|
158
|
+
result.text_left = utilo.roundme(result.text_left)
|
|
159
|
+
result.text_right = utilo.roundme(result.text_right)
|
|
160
|
+
|
|
161
|
+
if result.content_right is None:
|
|
162
|
+
return
|
|
163
|
+
# content_right is None for very short documents, cause there are
|
|
164
|
+
# a minimum number of pages required to extract this properly.
|
|
165
|
+
right = result.page_width - result.content_right
|
|
166
|
+
result.text_alignment = dtt.justified(cnavigators, right)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def extract_blockquote(result, flat):
|
|
170
|
+
style = doctextstyle.features.blockquote.blockquote_style(flat)
|
|
171
|
+
if not style:
|
|
172
|
+
return
|
|
173
|
+
font, size, left, right = style
|
|
174
|
+
|
|
175
|
+
result.block_size = size
|
|
176
|
+
result.block_family = font
|
|
177
|
+
result.block_left = left
|
|
178
|
+
result.block_right = right
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import doctextstyle.cluster
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def text(flats, returncluster: bool = False):
|
|
14
|
+
clustered = doctextstyle.cluster.cluster(
|
|
15
|
+
flats,
|
|
16
|
+
(
|
|
17
|
+
doctextstyle.cluster.ClusterProperty.SIZE,
|
|
18
|
+
doctextstyle.cluster.ClusterProperty.FONT,
|
|
19
|
+
),
|
|
20
|
+
)
|
|
21
|
+
result = doctextstyle.cluster.bestmatch(clustered)
|
|
22
|
+
if not result:
|
|
23
|
+
# too few data to determine text style information
|
|
24
|
+
return None
|
|
25
|
+
if returncluster:
|
|
26
|
+
return result, clustered[0] if clustered else []
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pagenumber(flats, returncluster: bool = False):
|
|
31
|
+
|
|
32
|
+
def validator(item) -> bool:
|
|
33
|
+
if item.top >= 100 and item.bottom >= 100:
|
|
34
|
+
# page number is not in the middle of the page. The page
|
|
35
|
+
# number is located at the top or bottom of the page.
|
|
36
|
+
return False
|
|
37
|
+
return item.length <= 6
|
|
38
|
+
|
|
39
|
+
clustered = doctextstyle.cluster.cluster(
|
|
40
|
+
flats,
|
|
41
|
+
(
|
|
42
|
+
doctextstyle.cluster.ClusterProperty.SIZE,
|
|
43
|
+
doctextstyle.cluster.ClusterProperty.FONT,
|
|
44
|
+
),
|
|
45
|
+
validator=validator,
|
|
46
|
+
)
|
|
47
|
+
# assert len(clustered) == 1, len(clustered)
|
|
48
|
+
result = doctextstyle.cluster.bestmatch(clustered)
|
|
49
|
+
if returncluster:
|
|
50
|
+
return result, clustered[0] if clustered else []
|
|
51
|
+
return result
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import configos
|
|
11
|
+
import utilo
|
|
12
|
+
|
|
13
|
+
BLOCKQUOTE_CLUSTER_SIZE_MIN = configos.HV_INT_PLUS(default=30)
|
|
14
|
+
|
|
15
|
+
BLOCKQUOTE_LEFT_DIFF_MAX = configos.HV_FLOAT_PLUS(default=15.0)
|
|
16
|
+
|
|
17
|
+
BLOCKQUOTE_RIGHT_DIFF_MAX = configos.HV_FLOAT_PLUS(default=15.0)
|
|
18
|
+
|
|
19
|
+
BLOCKQUOTE_TEXT_LENGTH_MIN = configos.HV_INT_PLUS(default=45)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def blockquote_style(flats) -> tuple:
|
|
23
|
+
# TODO: REMOVE TEXT CLUSTER FIRST
|
|
24
|
+
flats = [item for item in flats if item.hashed.count('.') < 10]
|
|
25
|
+
# skip very short text
|
|
26
|
+
flats = [
|
|
27
|
+
item for item in flats if len(item.hashed) > BLOCKQUOTE_TEXT_LENGTH_MIN
|
|
28
|
+
]
|
|
29
|
+
# skip item which start too right
|
|
30
|
+
flats = [item for item in flats if item.left <= 160]
|
|
31
|
+
# cluster text
|
|
32
|
+
clustered = utilo.determine_cluster(
|
|
33
|
+
flats,
|
|
34
|
+
classifier=classifier,
|
|
35
|
+
min_elements=BLOCKQUOTE_CLUSTER_SIZE_MIN,
|
|
36
|
+
strategy=utilo.MatchStrategy.MIN,
|
|
37
|
+
)
|
|
38
|
+
if len(clustered) < 2:
|
|
39
|
+
# too few cluster
|
|
40
|
+
return None
|
|
41
|
+
# largest cluster is text cluster
|
|
42
|
+
blockquote = clustered[1].center
|
|
43
|
+
result = (
|
|
44
|
+
blockquote.font,
|
|
45
|
+
blockquote.size,
|
|
46
|
+
blockquote.left,
|
|
47
|
+
blockquote.right,
|
|
48
|
+
)
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def classifier(candidat, clusteritem):
|
|
53
|
+
# expected
|
|
54
|
+
x0 = clusteritem.left
|
|
55
|
+
x1 = clusteritem.right
|
|
56
|
+
# current
|
|
57
|
+
x00 = candidat.left
|
|
58
|
+
x11 = candidat.right
|
|
59
|
+
# compare font size
|
|
60
|
+
if candidat.size != clusteritem.size:
|
|
61
|
+
return False
|
|
62
|
+
leftdiff = utilo.near(x00, x0, diff=BLOCKQUOTE_LEFT_DIFF_MAX)
|
|
63
|
+
if not leftdiff:
|
|
64
|
+
return False
|
|
65
|
+
rightdiff = utilo.near(x11, x1, diff=BLOCKQUOTE_RIGHT_DIFF_MAX)
|
|
66
|
+
if not rightdiff:
|
|
67
|
+
return False
|
|
68
|
+
# merge candidat into cluster
|
|
69
|
+
return True
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import configos
|
|
11
|
+
import serializeraw
|
|
12
|
+
import utilo
|
|
13
|
+
|
|
14
|
+
CONTENT_DIFF_MAX = configos.HV_FLOAT_PLUS(default=5.0)
|
|
15
|
+
|
|
16
|
+
CONTENT_ELEMENTS_MIN = configos.HV_INT_PLUS(default=3)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def content(path, pages: tuple = None):
|
|
20
|
+
leftright = serializeraw.load_leftright_border(path, pages)
|
|
21
|
+
|
|
22
|
+
def equals(candidat, clusteritem):
|
|
23
|
+
# left, right, top, down
|
|
24
|
+
distance = utilo.norms(candidat, clusteritem)
|
|
25
|
+
return distance < CONTENT_DIFF_MAX
|
|
26
|
+
|
|
27
|
+
clustered = utilo.determine_cluster(
|
|
28
|
+
leftright.values(),
|
|
29
|
+
classifier=equals,
|
|
30
|
+
min_elements=CONTENT_ELEMENTS_MIN,
|
|
31
|
+
)
|
|
32
|
+
# TODO: SUPPORT LEFT AND RIGHT DIFFERENT PAGE?
|
|
33
|
+
result = [(cluster[0], len(cluster)) for cluster in clustered]
|
|
34
|
+
return result
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# C O P Y R I G H T
|
|
3
|
+
# -----------------------------------------------------------------------------
|
|
4
|
+
# Copyright (c) 2020-2022 by Helmut Konrad Fahrendholz. All rights reserved.
|
|
5
|
+
# This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
|
|
6
|
+
# use or distribution is an offensive act against international law and may
|
|
7
|
+
# be prosecuted under federal law. Its content is company confidential.
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
import configos
|
|
11
|
+
import iamraw
|
|
12
|
+
import utilo
|
|
13
|
+
|
|
14
|
+
import doctextstyle.cluster
|
|
15
|
+
import doctextstyle.features
|
|
16
|
+
import doctextstyle.features.headline
|
|
17
|
+
|
|
18
|
+
FOOTNOTES_COUNT_MIN = configos.HV_INT_PLUS(default=10)
|
|
19
|
+
|
|
20
|
+
VALIDATOR_COUNT_MIN = configos.HV_INT_PLUS(default=25)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def footnote(flats: iamraw.TextProperties):
|
|
24
|
+
_text = doctextstyle.features.text(flats, returncluster=True)
|
|
25
|
+
_pagenumber = doctextstyle.features.pagenumber(
|
|
26
|
+
flats,
|
|
27
|
+
returncluster=True,
|
|
28
|
+
)
|
|
29
|
+
_headlines = doctextstyle.features.headline.headlines(
|
|
30
|
+
flats,
|
|
31
|
+
returncluster=True,
|
|
32
|
+
)
|
|
33
|
+
if _text:
|
|
34
|
+
flats = doctextstyle.cluster.remove(flats, _text[1])
|
|
35
|
+
else:
|
|
36
|
+
utilo.debug('footnote: no text style')
|
|
37
|
+
|
|
38
|
+
if _pagenumber:
|
|
39
|
+
flats = doctextstyle.cluster.remove(flats, _pagenumber[1])
|
|
40
|
+
else:
|
|
41
|
+
utilo.debug('footnote: no pagenumber style')
|
|
42
|
+
|
|
43
|
+
if _headlines:
|
|
44
|
+
for item in _headlines[1]:
|
|
45
|
+
flats = doctextstyle.cluster.remove(flats, item)
|
|
46
|
+
else:
|
|
47
|
+
utilo.debug('footnote: no headline style')
|
|
48
|
+
|
|
49
|
+
def validator(item) -> bool:
|
|
50
|
+
# Shrink footnotes to bottom area
|
|
51
|
+
return item.bottom < 150 and item.length >= VALIDATOR_COUNT_MIN
|
|
52
|
+
|
|
53
|
+
clustered = doctextstyle.cluster.cluster(
|
|
54
|
+
flats,
|
|
55
|
+
(doctextstyle.cluster.ClusterProperty.SIZE,),
|
|
56
|
+
validator=validator,
|
|
57
|
+
minsize=FOOTNOTES_COUNT_MIN,
|
|
58
|
+
unique_content=True,
|
|
59
|
+
max_size_diff=doctextstyle.cluster.NO_TOLERANCE,
|
|
60
|
+
max_before_diff=doctextstyle.cluster.NO_TOLERANCE,
|
|
61
|
+
max_after_diff=doctextstyle.cluster.NO_TOLERANCE,
|
|
62
|
+
)
|
|
63
|
+
result = doctextstyle.cluster.bestmatch(clustered)
|
|
64
|
+
return result
|