forked from originalankur/maptoposter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpresets.py
More file actions
89 lines (76 loc) · 2.64 KB
/
Copy pathpresets.py
File metadata and controls
89 lines (76 loc) · 2.64 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
"""Output size presets and resolution handling."""
from dataclasses import dataclass
MAX_PIXELS_PER_SIDE = 12000
DEFAULT_DPI = 300
DEFAULT_WIDTH_IN = 12.0
DEFAULT_HEIGHT_IN = 16.0
# name: (width_px, height_px) at DEFAULT_DPI
PRESETS = {
"mobile": (1080, 1920),
"hd": (1920, 1080),
"4k": (3840, 2160),
"4k-portrait": (2160, 3840),
"square": (2048, 2048),
"instagram-post": (1080, 1350),
"instagram-story": (1080, 1920),
"a4": (2480, 3508),
"a3": (3508, 4961),
"a2": (4961, 7016),
"18x24": (5400, 7200),
"24x36": (7200, 10800),
}
@dataclass(frozen=True)
class SizeSpec:
"""Resolved output size. exact_pixels=True means save without tight bbox."""
width_in: float
height_in: float
dpi: int
exact_pixels: bool
def parse_pixels(value: str) -> tuple[int, int]:
"""Parse a 'WIDTHxHEIGHT' pixel string, e.g. '1920x1080'."""
parts = value.lower().replace("×", "x").split("x")
if len(parts) != 2:
raise ValueError(
f"Invalid --pixels value '{value}'. Expected WIDTHxHEIGHT, e.g. 1920x1080."
)
try:
width, height = int(parts[0]), int(parts[1])
except ValueError as exc:
raise ValueError(
f"Invalid --pixels value '{value}'. Expected WIDTHxHEIGHT, e.g. 1920x1080."
) from exc
if width <= 0 or height <= 0:
raise ValueError(f"--pixels dimensions must be positive, got '{value}'.")
return width, height
def _apply_orientation(width, height, orientation):
if orientation == "portrait" and width > height:
return height, width
if orientation == "landscape" and height > width:
return height, width
return width, height
def resolve_size(
preset=None,
pixels=None,
width_in=DEFAULT_WIDTH_IN,
height_in=DEFAULT_HEIGHT_IN,
dpi=DEFAULT_DPI,
orientation=None,
) -> SizeSpec:
"""Resolve output size. Precedence: pixels > preset > explicit inches."""
if pixels:
px_w, px_h = parse_pixels(pixels)
elif preset:
if preset not in PRESETS:
raise ValueError(
f"Unknown preset '{preset}'. Available: {', '.join(sorted(PRESETS))}"
)
px_w, px_h = PRESETS[preset]
else:
w, h = _apply_orientation(width_in, height_in, orientation)
return SizeSpec(w, h, dpi, exact_pixels=False)
px_w, px_h = _apply_orientation(px_w, px_h, orientation)
if px_w > MAX_PIXELS_PER_SIDE or px_h > MAX_PIXELS_PER_SIDE:
raise ValueError(
f"Pixel size {px_w}x{px_h} exceeds the maximum of {MAX_PIXELS_PER_SIDE} px per side."
)
return SizeSpec(px_w / dpi, px_h / dpi, dpi, exact_pixels=True)