forked from a4k-openproject/plugin.program.openwizard
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.py
More file actions
286 lines (251 loc) · 9.7 KB
/
Copy pathutils.py
File metadata and controls
286 lines (251 loc) · 9.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 - 2019 -- Lars Heuer - Semagia <http://www.semagia.com/>.
# All rights reserved.
#
# License: BSD License
#
"""\
Utility functions useful for writers or QR Code objects.
"""
from __future__ import absolute_import, unicode_literals
from . import consts
from itertools import chain
try: # pragma: no cover
range = xrange
except NameError: # pragma: no cover
pass
def get_default_border_size(version):
"""\
Returns the default border size (quiet zone) for the provided version.
:param int version: 1 .. 40 or a Micro QR Code version constant.
:rtype: int
"""
return 4 if version > 0 else 2
def get_border(version, border):
"""\
Returns `border` if not ``None``, otherwise the default border size for
the provided QR Code.
:param int version: 1 .. 40 or a Micro QR Code version constant
:param int border: The size of the quiet zone or ``None``.
:rtype: int
"""
return border if border is not None else get_default_border_size(version)
def get_symbol_size(version, scale=1, border=None):
"""\
Returns the symbol size (width x height) with the provided border and
scaling factor.
:param int version: A version constant.
:param scale: Indicates the size of a single module (default: 1).
The size of a module depends on the used output format; i.e.
in a PNG context, a scaling factor of 2 indicates that a module
has a size of 2 x 2 pixel. Some outputs (i.e. SVG) accept
floating point values.
:type scale: int or float
:param int border: The border size or ``None`` to specify the
default quiet zone (4 for QR Codes, 2 for Micro QR Codes).
:rtype: tuple (width, height)
"""
if border is None:
border = get_default_border_size(version)
# M4 = 0, M3 = -1 ...
dim = version * 4 + 17 if version > 0 else (version + 4) * 2 + 9
dim += 2 * border
dim *= scale
return dim, dim
def check_valid_scale(scale):
"""\
Raises a :py:exc:`ValueError` iff `scale` is negative or zero.
:param scale: float or integer indicating a scaling factor.
"""
if scale <= 0:
raise ValueError('The scale must not be negative or zero. '
'Got: "{0}"'.format(scale))
def check_valid_border(border):
"""\
Raises a ValueError iff `border` is negative.
:param int border: Indicating the size of the quiet zone.
"""
if border is not None and (int(border) != border or border < 0):
raise ValueError('The border must not a non-negative integer value. '
'Got: "{0}"'.format(border))
def matrix_to_lines(matrix, x, y, incby=1):
"""\
Converts the `matrix` into an iterable of ((x1, y1), (x2, y2)) tuples which
represent a sequence (horizontal line) of dark modules.
The path starts at the 1st row of the matrix and moves down to the last
row.
:param matrix: An iterable of bytearrays.
:param x: Initial position on the x-axis.
:param y: Initial position on the y-axis.
:param incby: Value to move along the y-axis (default: 1).
:rtype: iterable of (x1, y1), (x2, y2) tuples
"""
y -= incby # Move along y-axis so we can simply increment y in the loop
last_bit = 0x1
for row in matrix:
x1 = x
x2 = x
y += incby
for bit in row:
if last_bit != bit and not bit:
yield (x1, y), (x2, y)
x1 = x2
x2 += 1
if not bit:
x1 += 1
last_bit = bit
if last_bit:
yield (x1, y), (x2, y)
last_bit = 0x0
def matrix_iter(matrix, version, scale=1, border=None):
"""\
Returns an iterator / generator over the provided matrix which includes
the border and the scaling factor.
If either the `scale` or `border` value is invalid, a :py:exc:`ValueError`
is raised.
:param matrix: An iterable of bytearrays.
:param int version: A version constant.
:param int scale: The scaling factor (default: ``1``).
:param int border: The border size or ``None`` to specify the
default quiet zone (4 for QR Codes, 2 for Micro QR Codes).
:raises: :py:exc:`ValueError` if an illegal scale or border value is provided
"""
check_valid_border(border)
scale = int(scale)
check_valid_scale(scale)
border = get_border(version, border)
width, height = get_symbol_size(version, scale=1, border=0)
def get_bit(i, j):
return 0x1 if (0 <= i < height and 0 <= j < width and matrix[i][j]) else 0x0
for i in range(-border, height + border):
for s in range(scale):
yield chain.from_iterable(([get_bit(i, j)] * scale for j in range(-border, width + border)))
# Constants for detailed iterator, see utils.matrix_iter_detail
TYPE_FINDER_PATTERN_LIGHT = 6
"""\
Light finder module
"""
TYPE_FINDER_PATTERN_DARK = TYPE_FINDER_PATTERN_LIGHT << 8
"""\
Dark finder module.
"""
TYPE_SEPARATOR = 8
"""\
Separator around the finder patterns (light module)
"""
TYPE_ALIGNMENT_PATTERN_LIGHT = 10
"""\
Light alignment pattern module.
"""
TYPE_ALIGNMENT_PATTERN_DARK = TYPE_ALIGNMENT_PATTERN_LIGHT << 8
"""\
Dark alignment pattern module.
"""
TYPE_TIMING_LIGHT = 12
"""\
Light timing pattern module.
"""
TYPE_TIMING_DARK = TYPE_TIMING_LIGHT << 8
"""\
Dark timing patten module.
"""
TYPE_FORMAT_LIGHT = 14
"""\
Light format information module.
"""
TYPE_FORMAT_DARK = TYPE_FORMAT_LIGHT << 8
"""\
Dark format information module.
"""
TYPE_VERSION_LIGHT = 16
"""\
Light version information module.
"""
TYPE_VERSION_DARK = TYPE_VERSION_LIGHT << 8
"""\
Dark version information module.
"""
TYPE_DARKMODULE = 512
"""\
A single dark module which occurs in QR Codes (but not in Micro QR Codes).
"""
TYPE_DATA_LIGHT = 4
"""\
Light module in the encoding area (either a data module or an error correction module).
"""
TYPE_DATA_DARK = TYPE_DATA_LIGHT << 8
"""\
Dark module in the encoding area (either a data module or an error correction module).
"""
TYPE_QUIET_ZONE = 18
"""\
Border of light modules.
"""
def matrix_iter_detail(matrix, version, scale=1, border=None):
"""\
Returns an iterator / generator over the provided matrix which includes
the border and the scaling factor.
This iterator / generator returns different values for dark / light modules
and therefor the different parts (like the finder patterns, alignment patterns etc.)
are distinguishable. If this information isn't necessary, use the
:py:func:`matrix_iter()` function because it is much cheaper and faster.
If either the `scale` or `border` value is invalid, a py:exc:`ValueError`
is raised.
:param matrix: An iterable of bytearrays.
:param int version: A version constant.
:param int scale: The scaling factor (default: ``1``).
:param int border: The border size or ``None`` to specify the
default quiet zone (4 for QR Codes, 2 for Micro QR Codes).
:raises: :py:exc:`ValueError` if an illegal scale or border value is provided
"""
from segno import encoder
check_valid_border(border)
scale = int(scale)
check_valid_scale(scale)
border = get_border(version, border)
width, height = get_symbol_size(version, scale=1, border=0)
is_micro = version < 1
# Create an empty matrix with invalid 0x2 values
alignment_matrix = encoder.make_matrix(version, reserve_regions=False, add_timing=False)
encoder.add_alignment_patterns(alignment_matrix, version)
def get_bit(i, j):
# Check if we operate upon the matrix or the "virtual" border
if 0 <= i < height and 0 <= j < width:
val = matrix[i][j]
if not is_micro:
# Alignment pattern
alignment_val = alignment_matrix[i][j]
if alignment_val != 0x2:
return (TYPE_ALIGNMENT_PATTERN_LIGHT, TYPE_ALIGNMENT_PATTERN_DARK)[alignment_val]
if version > 6: # Version information
if i < 6 and width - 12 < j < width - 8 \
or height - 12 < i < height - 8 and j < 6:
return (TYPE_VERSION_LIGHT, TYPE_VERSION_DARK)[val]
# Dark module
if i == height - 8 and j == 8:
return TYPE_DARKMODULE
# Timing - IMPORTANT: Check alignment (see above) in advance!
if not is_micro and ((i == 6 and j > 7 and j < width - 8) or (j == 6 and i > 7 and i < height - 8)) \
or is_micro and (i == 0 and j > 7 or j == 0 and i > 7):
return (TYPE_TIMING_LIGHT, TYPE_TIMING_DARK)[val]
# Format - IMPORTANT: Check timing (see above) in advance!
if i == 8 and (j < 9 or (not is_micro and j > width - 10)) \
or j == 8 and (i < 8 or not is_micro and i > height - 9):
return (TYPE_FORMAT_LIGHT, TYPE_FORMAT_DARK)[val]
# Finder pattern
# top left top right
if i < 7 and (j < 7 or (not is_micro and j > width - 8)) \
or not is_micro and i > height - 8 and j < 7: # bottom left
return (TYPE_FINDER_PATTERN_LIGHT, TYPE_FINDER_PATTERN_DARK)[val]
# Separator
# top left top right
if i < 8 and (j < 8 or (not is_micro and j > width - 9)) \
or not is_micro and (i > height - 9 and j < 8): # bottom left
return TYPE_SEPARATOR
return (TYPE_DATA_LIGHT, TYPE_DATA_DARK)[val]
else:
return TYPE_QUIET_ZONE
for i in range(-border, height + border):
for s in range(scale):
yield chain.from_iterable(([get_bit(i, j)] * scale for j in range(-border, width + border)))