-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpage.py
More file actions
160 lines (134 loc) · 6.71 KB
/
Copy pathpage.py
File metadata and controls
160 lines (134 loc) · 6.71 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
#!/usr/bin/env python3
"""Page geometry, shared by wav2png, sheet2pdf and make_print.
One image row is one audio sample and one strip is one second, so the page
decides the sample rate: it is the usable page height in pixels. How many
strips fit across a sheet is the usable width divided by the strip width.
Every tool that touches paper asks here instead of keeping its own copy of
A4, which is how the three of them used to disagree.
--paper takes a name from PAPERS or WxH in millimetres; either way it is
normalised to portrait, and --landscape turns it.
"""
PAPERS = {"a3": (297.0, 420.0), "a4": (210.0, 297.0), "a5": (148.0, 210.0),
"letter": (215.9, 279.4), "legal": (215.9, 355.6)}
def paper(spec):
"""'a4' or '210x297' -> (width, height) in mm, portrait."""
if str(spec).lower() in PAPERS:
return PAPERS[str(spec).lower()]
try:
w, h = (float(v) for v in str(spec).lower().split("x"))
except ValueError:
raise SystemExit("unknown paper %r -- a name (%s) or WxH in mm"
% (spec, ", ".join(sorted(PAPERS))))
if w <= 0 or h <= 0:
raise SystemExit("paper %r has no area" % spec)
return (w, h) if w <= h else (h, w)
def usable(spec, margin, landscape=False):
"""Paper minus the margin on every side, in mm."""
w, h = paper(spec)
if landscape:
w, h = h, w
if w - 2 * margin <= 0 or h - 2 * margin <= 0:
raise SystemExit("margin %g mm leaves no %s page" % (margin, spec))
return w - 2 * margin, h - 2 * margin
def geometry(spec, dpi, width, margin, landscape=False):
"""Page -> (sample rate, strips per sheet). One row per sample, one strip per second."""
use_w, use_h = usable(spec, margin, landscape)
rate = int(use_h * dpi / 25.4)
per_sheet = int(use_w * dpi / 25.4) // width
if per_sheet < 1:
raise SystemExit("a %d px strip is %.1f mm, wider than the %.1f mm of usable page"
% (width, width / dpi * 25.4, use_w))
return rate, per_sheet
def fit(spec, dpi, margin, landscape, nstrips, sheets):
"""`nstrips` strips onto `sheets` pages -> (strips per sheet, widest width).
Widest is the point: spare page width is amplitude resolution thrown away,
and the strips it would buy get filled with silence anyway."""
use_w = int(usable(spec, margin, landscape)[0] * dpi / 25.4)
per_sheet = -(-nstrips // sheets)
width = use_w // per_sheet # floor, so per_sheet of them always fit
if width < 1:
raise SystemExit(
"%d strips will not fit %d sheet%s even one pixel wide.\n"
"Ask for %d sheets or more, or raise --dpi."
% (nstrips, sheets, "" if sheets == 1 else "s", -(-nstrips // use_w)))
return per_sheet, width
def box(w_mm, h_mm, spec, path=""):
"""Smallest orientation of `spec` that holds the sheet, portrait first."""
p = paper(spec)
for b in (p, p[::-1]):
if w_mm <= b[0] + 1e-6 and h_mm <= b[1] + 1e-6:
return b
raise SystemExit(
"%s is %.1f x %.1f mm, which does not fit %s (%.0f x %.0f mm).\n"
"Re-generate it smaller or print at a higher dpi -- scaling it here "
"would destroy the signal." % (path, w_mm, h_mm, spec, p[0], p[1]))
def add_paper_arg(p, default="a4"):
"""--paper alone, for a tool that only places finished sheets."""
p.add_argument("--paper", default=default, metavar="NAME|WxH",
help="paper size: %s, or WxH in mm (default: %s)"
% (", ".join(sorted(PAPERS)), default))
def add_align_arg(p, default="left"):
"""Where a sheet narrower than the rest sits on its page."""
p.add_argument("--align", choices=("left", "center", "right"), default=default,
help="where a short last sheet goes relative to the full ones "
"(default: %s, which keeps every page's strips in the "
"same place)" % default)
def add_page_args(p, dpi=600, margin=9.0):
"""Every flag a sheet-composing tool needs, so all of them take the same ones."""
add_paper_arg(p)
p.add_argument("--dpi", type=int, default=dpi,
help="print resolution (default: %d)" % dpi)
p.add_argument("--margin", type=float, default=margin, metavar="MM",
help="unprintable page margin on every side (default: %g)" % margin)
p.add_argument("--landscape", action="store_true",
help="turn the page: fewer Hz, more seconds per sheet")
def demo():
"""The numbers the whole pipeline hangs off, and the ways they can be wrong."""
assert paper("A4") == (210.0, 297.0) == paper("297x210"), "not normalised to portrait"
assert paper("100x250") == (100.0, 250.0)
for bad in ("a6", "210", "wide x tall", "0x100"):
try:
paper(bad)
assert False, "accepted paper %r" % bad
except SystemExit:
pass
# A4 portrait at 600 dpi with 9 mm margins is the reference geometry
assert geometry("a4", 600, 39, 9) == (6590, 116), geometry("a4", 600, 39, 9)
assert geometry("a4", 600, 39, 9, True) == (4535, 168)
assert geometry("a4", 300, 39, 9) == (3295, 58)
assert geometry("a3", 600, 39, 9) == (9496, 168)
for bad in (("a4", 600, 6000, 9), ("a4", 600, 39, 200)):
try:
geometry(*bad)
assert False, "impossible geometry accepted"
except SystemExit:
pass
# --fit: the widest strip that still fits, and it demonstrably does fit
assert fit("a4", 600, 9, False, 60, 1) == (60, 75)
assert fit("a4", 600, 9, False, 137, 2) == (69, 65)
assert fit("a4", 600, 9, True, 60, 1) == (60, 109)
assert fit("a4", 600, 9, False, 121, 2) == (61, 74), "the 2-sheet case from the field"
for sheets in (1, 2, 3):
for n in (1, 60, 121, 137, 400):
per_sheet, w = fit("a4", 600, 9, False, n, sheets)
assert per_sheet * sheets >= n
assert geometry("a4", 600, w, 9)[1] * sheets >= n, (n, sheets)
# and it is the widest such: one pixel more would not fit
if w < 4535: # unless it already fills the page
assert geometry("a4", 600, w + 1, 9)[1] * sheets < n, (n, sheets)
try:
fit("a4", 600, 9, False, 10000, 1)
assert False, "impossible fit accepted"
except SystemExit:
pass
assert box(191.1, 279.0, "a4") == (210.0, 297.0)
assert box(279.0, 191.1, "a4") == (297.0, 210.0), "should turn the page, not refuse"
assert box(210.0, 297.0, "a4") == (210.0, 297.0), "exactly full page still fits"
try:
box(516.5, 279.0, "a4")
assert False, "oversized sheet not refused"
except SystemExit:
pass
print("demo ok")
if __name__ == "__main__":
demo()