pkgprint 0.1.0__tar.gz
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.
- pkgprint-0.1.0/LICENSE +21 -0
- pkgprint-0.1.0/PKG-INFO +199 -0
- pkgprint-0.1.0/README.md +171 -0
- pkgprint-0.1.0/pkgprint/__init__.py +42 -0
- pkgprint-0.1.0/pkgprint/color.py +200 -0
- pkgprint-0.1.0/pkgprint/paper.py +174 -0
- pkgprint-0.1.0/pkgprint/print_specs.py +181 -0
- pkgprint-0.1.0/pkgprint/units.py +163 -0
- pkgprint-0.1.0/pkgprint.egg-info/PKG-INFO +199 -0
- pkgprint-0.1.0/pkgprint.egg-info/SOURCES.txt +17 -0
- pkgprint-0.1.0/pkgprint.egg-info/dependency_links.txt +1 -0
- pkgprint-0.1.0/pkgprint.egg-info/requires.txt +5 -0
- pkgprint-0.1.0/pkgprint.egg-info/top_level.txt +1 -0
- pkgprint-0.1.0/pyproject.toml +40 -0
- pkgprint-0.1.0/setup.cfg +4 -0
- pkgprint-0.1.0/tests/test_color.py +146 -0
- pkgprint-0.1.0/tests/test_paper.py +100 -0
- pkgprint-0.1.0/tests/test_print_specs.py +137 -0
- pkgprint-0.1.0/tests/test_units.py +144 -0
pkgprint-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rishabh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pkgprint-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: pkgprint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Print and packaging math utilities for developers
|
|
5
|
+
Author: Rishabh
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Rishabh55122/pkgprint
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/Rishabh55122/pkgprint/issues
|
|
9
|
+
Keywords: print,packaging,design,cmyk,dpi,bleed,typography
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: build; extra == "dev"
|
|
27
|
+
Requires-Dist: twine; extra == "dev"
|
|
28
|
+
|
|
29
|
+
# pkgprint
|
|
30
|
+
|
|
31
|
+
**Print and packaging math utilities for Python developers.**
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/pkgprint/)
|
|
34
|
+
[](https://www.python.org/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
`pkgprint` is a zero-dependency Python library that handles the fiddly maths behind print and packaging production: unit conversions, colour model translations, standard paper and box sizes, and bleed/margin arithmetic.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install pkgprint
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import pkgprint
|
|
53
|
+
|
|
54
|
+
# Get A4 dimensions and convert to inches
|
|
55
|
+
w, h = pkgprint.paper_size("A4")
|
|
56
|
+
print(pkgprint.mm_to_inches(w), pkgprint.mm_to_inches(h))
|
|
57
|
+
# → 8.267716535433071 11.692913385826772
|
|
58
|
+
|
|
59
|
+
# Add 3 mm bleed to a business card
|
|
60
|
+
bleed_w, bleed_h = pkgprint.add_bleed(89, 51, bleed_mm=3)
|
|
61
|
+
print(bleed_w, bleed_h)
|
|
62
|
+
# → 95 57
|
|
63
|
+
|
|
64
|
+
# Convert a brand colour from CMYK to RGB
|
|
65
|
+
rgb = pkgprint.cmyk_to_rgb(0, 100, 100, 0) # Pantone-ish red
|
|
66
|
+
print(rgb)
|
|
67
|
+
# → (255, 0, 0)
|
|
68
|
+
|
|
69
|
+
# Convert back from hex to RGB
|
|
70
|
+
print(pkgprint.hex_to_rgb("#FF5733"))
|
|
71
|
+
# → (255, 87, 51)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
### `units` — Measurement Conversions
|
|
79
|
+
|
|
80
|
+
| Function | Description |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `mm_to_inches(mm)` | Millimetres → inches |
|
|
83
|
+
| `inches_to_mm(inches)` | Inches → millimetres |
|
|
84
|
+
| `mm_to_points(mm)` | Millimetres → typographic points (1 pt = 1/72 in) |
|
|
85
|
+
| `points_to_mm(points)` | Points → millimetres |
|
|
86
|
+
| `dpi_to_ppi(dpi)` | DPI → PPI (semantic alias, same unit) |
|
|
87
|
+
| `ppi_to_dpi(ppi)` | PPI → DPI (semantic alias, same unit) |
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import pkgprint
|
|
91
|
+
|
|
92
|
+
pkgprint.mm_to_inches(25.4) # → 1.0
|
|
93
|
+
pkgprint.inches_to_mm(8.5) # → 215.9 (US Letter width)
|
|
94
|
+
pkgprint.mm_to_points(210) # → 595.28 (A4 width in points)
|
|
95
|
+
pkgprint.points_to_mm(72) # → 25.4
|
|
96
|
+
pkgprint.dpi_to_ppi(300) # → 300
|
|
97
|
+
pkgprint.ppi_to_dpi(96) # → 96
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### `color` — Colour Model Conversions
|
|
103
|
+
|
|
104
|
+
| Function | Description |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `cmyk_to_rgb(c, m, y, k)` | CMYK → RGB. Accepts 0–100 **or** 0–1 inputs. |
|
|
107
|
+
| `rgb_to_cmyk(r, g, b)` | RGB → approximate CMYK (device-independent). |
|
|
108
|
+
| `hex_to_rgb(hex_code)` | CSS hex string → RGB tuple. Supports `#fff` shorthand. |
|
|
109
|
+
| `rgb_to_hex(r, g, b)` | RGB tuple → uppercase CSS hex string. |
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import pkgprint
|
|
113
|
+
|
|
114
|
+
pkgprint.cmyk_to_rgb(0, 100, 100, 0) # → (255, 0, 0) red
|
|
115
|
+
pkgprint.cmyk_to_rgb(0, 1, 1, 0) # → (255, 0, 0) same, 0–1 range
|
|
116
|
+
pkgprint.rgb_to_cmyk(0, 0, 255) # → (100.0, 100.0, 0.0, 0.0) blue
|
|
117
|
+
pkgprint.hex_to_rgb("#FF5733") # → (255, 87, 51)
|
|
118
|
+
pkgprint.hex_to_rgb("#fff") # → (255, 255, 255) shorthand ok
|
|
119
|
+
pkgprint.rgb_to_hex(255, 87, 51) # → '#FF5733'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `paper` — Standard Sizes
|
|
125
|
+
|
|
126
|
+
| Function | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `paper_size(name)` | `(width, height)` in mm for a named paper size. Case-insensitive. |
|
|
129
|
+
| `list_paper_sizes()` | Sorted list of all supported paper size names. |
|
|
130
|
+
| `box_size(style)` | `(length, width, height)` in mm for a named packaging box. Case-insensitive. |
|
|
131
|
+
|
|
132
|
+
**Supported paper sizes:** ISO A0–A8, B0–B6, C4–C6 (envelopes), Letter, Legal, Tabloid, Executive, ANSI A–E, Business Card, Postcard, and more.
|
|
133
|
+
|
|
134
|
+
**Supported box styles:** RSC Small/Medium/Large, Mailer A4/A5, Gift Small/Medium/Large, Tuck Small/Medium/Large.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import pkgprint
|
|
138
|
+
|
|
139
|
+
pkgprint.paper_size("A4") # → (210, 297)
|
|
140
|
+
pkgprint.paper_size("letter") # → (216, 279) case-insensitive
|
|
141
|
+
pkgprint.box_size("RSC Medium") # → (305, 229, 152)
|
|
142
|
+
pkgprint.list_paper_sizes() # → ['A0', 'A1', ..., 'Tabloid', ...]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### `print_specs` — Print Production Math
|
|
148
|
+
|
|
149
|
+
| Function | Description |
|
|
150
|
+
|---|---|
|
|
151
|
+
| `add_bleed(width, height, bleed_mm=3)` | Trim size → artwork size with bleed on all four sides. |
|
|
152
|
+
| `safe_margin(width, height, margin_mm=5)` | Trim size → safe content area (removes margins). |
|
|
153
|
+
| `trim_size(width, height, bleed_mm=3)` | Artwork size (with bleed) → trim size. |
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import pkgprint
|
|
157
|
+
|
|
158
|
+
# Business card: 89 × 51 mm → with 3 mm bleed → 95 × 57 mm
|
|
159
|
+
pkgprint.add_bleed(89, 51) # → (95, 57)
|
|
160
|
+
|
|
161
|
+
# A4 safe content area with 5 mm margins
|
|
162
|
+
pkgprint.safe_margin(210, 297, margin_mm=5) # → (200, 287)
|
|
163
|
+
|
|
164
|
+
# Reverse: what is the trim size of a 220 × 307 mm file with 5 mm bleed?
|
|
165
|
+
pkgprint.trim_size(220, 307, bleed_mm=5) # → (210, 297)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Build & Publish
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Install build tools
|
|
174
|
+
pip install build twine
|
|
175
|
+
|
|
176
|
+
# Build source + wheel
|
|
177
|
+
python -m build
|
|
178
|
+
|
|
179
|
+
# Test publish (TestPyPI)
|
|
180
|
+
twine upload --repository testpypi dist/*
|
|
181
|
+
|
|
182
|
+
# Real publish
|
|
183
|
+
twine upload dist/*
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Running Tests
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
pip install pytest
|
|
192
|
+
pytest
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT — see [LICENSE](LICENSE).
|
pkgprint-0.1.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# pkgprint
|
|
2
|
+
|
|
3
|
+
**Print and packaging math utilities for Python developers.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pkgprint/)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
`pkgprint` is a zero-dependency Python library that handles the fiddly maths behind print and packaging production: unit conversions, colour model translations, standard paper and box sizes, and bleed/margin arithmetic.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install pkgprint
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import pkgprint
|
|
25
|
+
|
|
26
|
+
# Get A4 dimensions and convert to inches
|
|
27
|
+
w, h = pkgprint.paper_size("A4")
|
|
28
|
+
print(pkgprint.mm_to_inches(w), pkgprint.mm_to_inches(h))
|
|
29
|
+
# → 8.267716535433071 11.692913385826772
|
|
30
|
+
|
|
31
|
+
# Add 3 mm bleed to a business card
|
|
32
|
+
bleed_w, bleed_h = pkgprint.add_bleed(89, 51, bleed_mm=3)
|
|
33
|
+
print(bleed_w, bleed_h)
|
|
34
|
+
# → 95 57
|
|
35
|
+
|
|
36
|
+
# Convert a brand colour from CMYK to RGB
|
|
37
|
+
rgb = pkgprint.cmyk_to_rgb(0, 100, 100, 0) # Pantone-ish red
|
|
38
|
+
print(rgb)
|
|
39
|
+
# → (255, 0, 0)
|
|
40
|
+
|
|
41
|
+
# Convert back from hex to RGB
|
|
42
|
+
print(pkgprint.hex_to_rgb("#FF5733"))
|
|
43
|
+
# → (255, 87, 51)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## API Reference
|
|
49
|
+
|
|
50
|
+
### `units` — Measurement Conversions
|
|
51
|
+
|
|
52
|
+
| Function | Description |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `mm_to_inches(mm)` | Millimetres → inches |
|
|
55
|
+
| `inches_to_mm(inches)` | Inches → millimetres |
|
|
56
|
+
| `mm_to_points(mm)` | Millimetres → typographic points (1 pt = 1/72 in) |
|
|
57
|
+
| `points_to_mm(points)` | Points → millimetres |
|
|
58
|
+
| `dpi_to_ppi(dpi)` | DPI → PPI (semantic alias, same unit) |
|
|
59
|
+
| `ppi_to_dpi(ppi)` | PPI → DPI (semantic alias, same unit) |
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import pkgprint
|
|
63
|
+
|
|
64
|
+
pkgprint.mm_to_inches(25.4) # → 1.0
|
|
65
|
+
pkgprint.inches_to_mm(8.5) # → 215.9 (US Letter width)
|
|
66
|
+
pkgprint.mm_to_points(210) # → 595.28 (A4 width in points)
|
|
67
|
+
pkgprint.points_to_mm(72) # → 25.4
|
|
68
|
+
pkgprint.dpi_to_ppi(300) # → 300
|
|
69
|
+
pkgprint.ppi_to_dpi(96) # → 96
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
### `color` — Colour Model Conversions
|
|
75
|
+
|
|
76
|
+
| Function | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `cmyk_to_rgb(c, m, y, k)` | CMYK → RGB. Accepts 0–100 **or** 0–1 inputs. |
|
|
79
|
+
| `rgb_to_cmyk(r, g, b)` | RGB → approximate CMYK (device-independent). |
|
|
80
|
+
| `hex_to_rgb(hex_code)` | CSS hex string → RGB tuple. Supports `#fff` shorthand. |
|
|
81
|
+
| `rgb_to_hex(r, g, b)` | RGB tuple → uppercase CSS hex string. |
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import pkgprint
|
|
85
|
+
|
|
86
|
+
pkgprint.cmyk_to_rgb(0, 100, 100, 0) # → (255, 0, 0) red
|
|
87
|
+
pkgprint.cmyk_to_rgb(0, 1, 1, 0) # → (255, 0, 0) same, 0–1 range
|
|
88
|
+
pkgprint.rgb_to_cmyk(0, 0, 255) # → (100.0, 100.0, 0.0, 0.0) blue
|
|
89
|
+
pkgprint.hex_to_rgb("#FF5733") # → (255, 87, 51)
|
|
90
|
+
pkgprint.hex_to_rgb("#fff") # → (255, 255, 255) shorthand ok
|
|
91
|
+
pkgprint.rgb_to_hex(255, 87, 51) # → '#FF5733'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `paper` — Standard Sizes
|
|
97
|
+
|
|
98
|
+
| Function | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `paper_size(name)` | `(width, height)` in mm for a named paper size. Case-insensitive. |
|
|
101
|
+
| `list_paper_sizes()` | Sorted list of all supported paper size names. |
|
|
102
|
+
| `box_size(style)` | `(length, width, height)` in mm for a named packaging box. Case-insensitive. |
|
|
103
|
+
|
|
104
|
+
**Supported paper sizes:** ISO A0–A8, B0–B6, C4–C6 (envelopes), Letter, Legal, Tabloid, Executive, ANSI A–E, Business Card, Postcard, and more.
|
|
105
|
+
|
|
106
|
+
**Supported box styles:** RSC Small/Medium/Large, Mailer A4/A5, Gift Small/Medium/Large, Tuck Small/Medium/Large.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import pkgprint
|
|
110
|
+
|
|
111
|
+
pkgprint.paper_size("A4") # → (210, 297)
|
|
112
|
+
pkgprint.paper_size("letter") # → (216, 279) case-insensitive
|
|
113
|
+
pkgprint.box_size("RSC Medium") # → (305, 229, 152)
|
|
114
|
+
pkgprint.list_paper_sizes() # → ['A0', 'A1', ..., 'Tabloid', ...]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### `print_specs` — Print Production Math
|
|
120
|
+
|
|
121
|
+
| Function | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `add_bleed(width, height, bleed_mm=3)` | Trim size → artwork size with bleed on all four sides. |
|
|
124
|
+
| `safe_margin(width, height, margin_mm=5)` | Trim size → safe content area (removes margins). |
|
|
125
|
+
| `trim_size(width, height, bleed_mm=3)` | Artwork size (with bleed) → trim size. |
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import pkgprint
|
|
129
|
+
|
|
130
|
+
# Business card: 89 × 51 mm → with 3 mm bleed → 95 × 57 mm
|
|
131
|
+
pkgprint.add_bleed(89, 51) # → (95, 57)
|
|
132
|
+
|
|
133
|
+
# A4 safe content area with 5 mm margins
|
|
134
|
+
pkgprint.safe_margin(210, 297, margin_mm=5) # → (200, 287)
|
|
135
|
+
|
|
136
|
+
# Reverse: what is the trim size of a 220 × 307 mm file with 5 mm bleed?
|
|
137
|
+
pkgprint.trim_size(220, 307, bleed_mm=5) # → (210, 297)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Build & Publish
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Install build tools
|
|
146
|
+
pip install build twine
|
|
147
|
+
|
|
148
|
+
# Build source + wheel
|
|
149
|
+
python -m build
|
|
150
|
+
|
|
151
|
+
# Test publish (TestPyPI)
|
|
152
|
+
twine upload --repository testpypi dist/*
|
|
153
|
+
|
|
154
|
+
# Real publish
|
|
155
|
+
twine upload dist/*
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Running Tests
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
pip install pytest
|
|
164
|
+
pytest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkgprint — Print & packaging math utilities for developers.
|
|
3
|
+
|
|
4
|
+
Provides unit conversions, color model conversions, standard paper/box sizes,
|
|
5
|
+
and bleed/margin calculations useful in print, packaging, and design workflows.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> import pkgprint
|
|
9
|
+
>>> pkgprint.mm_to_inches(210)
|
|
10
|
+
8.267716535433071
|
|
11
|
+
>>> pkgprint.paper_size("A4")
|
|
12
|
+
(210, 297)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .units import mm_to_inches, inches_to_mm, mm_to_points, points_to_mm, dpi_to_ppi, ppi_to_dpi
|
|
16
|
+
from .color import cmyk_to_rgb, rgb_to_cmyk, hex_to_rgb, rgb_to_hex
|
|
17
|
+
from .paper import paper_size, list_paper_sizes, box_size
|
|
18
|
+
from .print_specs import add_bleed, safe_margin, trim_size
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
__all__ = [
|
|
22
|
+
# units
|
|
23
|
+
"mm_to_inches",
|
|
24
|
+
"inches_to_mm",
|
|
25
|
+
"mm_to_points",
|
|
26
|
+
"points_to_mm",
|
|
27
|
+
"dpi_to_ppi",
|
|
28
|
+
"ppi_to_dpi",
|
|
29
|
+
# color
|
|
30
|
+
"cmyk_to_rgb",
|
|
31
|
+
"rgb_to_cmyk",
|
|
32
|
+
"hex_to_rgb",
|
|
33
|
+
"rgb_to_hex",
|
|
34
|
+
# paper
|
|
35
|
+
"paper_size",
|
|
36
|
+
"list_paper_sizes",
|
|
37
|
+
"box_size",
|
|
38
|
+
# print specs
|
|
39
|
+
"add_bleed",
|
|
40
|
+
"safe_margin",
|
|
41
|
+
"trim_size",
|
|
42
|
+
]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
color.py — Color model conversions for print and screen workflows.
|
|
3
|
+
|
|
4
|
+
Conversion references:
|
|
5
|
+
- CMYK → RGB: standard ICC-style formula using key (K) channel.
|
|
6
|
+
- RGB → CMYK: inverse of the above (approximate, device-independent).
|
|
7
|
+
- Hex ↔ RGB: CSS hex notation, 6-digit only (with optional '#' prefix).
|
|
8
|
+
|
|
9
|
+
CMYK inputs are accepted in **both** 0–100 and 0–1 ranges and are
|
|
10
|
+
auto-detected: values > 1 are treated as percentage (0–100).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _normalise_cmyk(c: float, m: float, y: float, k: float) -> tuple[float, float, float, float]:
|
|
17
|
+
"""Return CMYK values normalised to the 0–1 range."""
|
|
18
|
+
values = (c, m, y, k)
|
|
19
|
+
if any(v > 1 for v in values):
|
|
20
|
+
# Assume 0–100 percentage input
|
|
21
|
+
c, m, y, k = c / 100, m / 100, y / 100, k / 100
|
|
22
|
+
return c, m, y, k
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def cmyk_to_rgb(c: float, m: float, y: float, k: float) -> tuple[int, int, int]:
|
|
26
|
+
"""Convert CMYK colour values to RGB.
|
|
27
|
+
|
|
28
|
+
Inputs are accepted in either 0–100 (percentage) or 0–1 range.
|
|
29
|
+
If any value exceeds 1, all four are treated as percentages.
|
|
30
|
+
|
|
31
|
+
Formula (ICC standard):
|
|
32
|
+
R = 255 × (1 − C) × (1 − K)
|
|
33
|
+
G = 255 × (1 − M) × (1 − K)
|
|
34
|
+
B = 255 × (1 − Y) × (1 − K)
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
c: Cyan component (0–100 or 0–1).
|
|
38
|
+
m: Magenta component (0–100 or 0–1).
|
|
39
|
+
y: Yellow component (0–100 or 0–1).
|
|
40
|
+
k: Key (Black) component (0–100 or 0–1).
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Tuple of (R, G, B) integers in the range 0–255.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If any normalised value falls outside 0–1.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> cmyk_to_rgb(0, 100, 100, 0) # pure red in CMYK
|
|
50
|
+
(255, 0, 0)
|
|
51
|
+
>>> cmyk_to_rgb(0, 0, 0, 0) # white
|
|
52
|
+
(255, 255, 255)
|
|
53
|
+
>>> cmyk_to_rgb(0, 0, 0, 100) # black
|
|
54
|
+
(0, 0, 0)
|
|
55
|
+
"""
|
|
56
|
+
for name, val in zip(("c", "m", "y", "k"), (c, m, y, k)):
|
|
57
|
+
if not isinstance(val, (int, float)):
|
|
58
|
+
raise TypeError(f"{name!r} must be a number, got {type(val).__name__!r}")
|
|
59
|
+
|
|
60
|
+
c, m, y, k = _normalise_cmyk(c, m, y, k)
|
|
61
|
+
|
|
62
|
+
for name, val in zip(("c", "m", "y", "k"), (c, m, y, k)):
|
|
63
|
+
if not (0.0 <= val <= 1.0):
|
|
64
|
+
raise ValueError(f"{name!r} must be in range 0–1 (or 0–100), got {val}")
|
|
65
|
+
|
|
66
|
+
r = round(255 * (1 - c) * (1 - k))
|
|
67
|
+
g = round(255 * (1 - m) * (1 - k))
|
|
68
|
+
b = round(255 * (1 - y) * (1 - k))
|
|
69
|
+
return (r, g, b)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def rgb_to_cmyk(r: int, g: int, b: int) -> tuple[float, float, float, float]:
|
|
73
|
+
"""Convert RGB colour values to approximate CMYK.
|
|
74
|
+
|
|
75
|
+
This is a device-independent approximation (no ICC profile).
|
|
76
|
+
Output values are in the 0–100 percentage range, rounded to 2 dp.
|
|
77
|
+
|
|
78
|
+
Formula:
|
|
79
|
+
K = 1 − max(R, G, B) / 255
|
|
80
|
+
C = (1 − R/255 − K) / (1 − K) [0 when K=1]
|
|
81
|
+
M = (1 − G/255 − K) / (1 − K)
|
|
82
|
+
Y = (1 − B/255 − K) / (1 − K)
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
r: Red channel (0–255).
|
|
86
|
+
g: Green channel (0–255).
|
|
87
|
+
b: Blue channel (0–255).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Tuple of (C, M, Y, K) floats in the range 0.0–100.0.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If any channel is outside 0–255.
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> rgb_to_cmyk(255, 0, 0) # red
|
|
97
|
+
(0.0, 100.0, 100.0, 0.0)
|
|
98
|
+
>>> rgb_to_cmyk(0, 0, 0) # black
|
|
99
|
+
(0.0, 0.0, 0.0, 100.0)
|
|
100
|
+
>>> rgb_to_cmyk(255, 255, 255) # white
|
|
101
|
+
(0.0, 0.0, 0.0, 0.0)
|
|
102
|
+
"""
|
|
103
|
+
for name, val in zip(("r", "g", "b"), (r, g, b)):
|
|
104
|
+
if not isinstance(val, (int, float)):
|
|
105
|
+
raise TypeError(f"{name!r} must be a number, got {type(val).__name__!r}")
|
|
106
|
+
if not (0 <= val <= 255):
|
|
107
|
+
raise ValueError(f"{name!r} must be in range 0–255, got {val}")
|
|
108
|
+
|
|
109
|
+
r_, g_, b_ = r / 255, g / 255, b / 255
|
|
110
|
+
k = 1 - max(r_, g_, b_)
|
|
111
|
+
|
|
112
|
+
if k == 1.0:
|
|
113
|
+
# Pure black — avoid division by zero
|
|
114
|
+
return (0.0, 0.0, 0.0, 100.0)
|
|
115
|
+
|
|
116
|
+
c = (1 - r_ - k) / (1 - k)
|
|
117
|
+
m = (1 - g_ - k) / (1 - k)
|
|
118
|
+
y = (1 - b_ - k) / (1 - k)
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
round(c * 100, 2),
|
|
122
|
+
round(m * 100, 2),
|
|
123
|
+
round(y * 100, 2),
|
|
124
|
+
round(k * 100, 2),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def hex_to_rgb(hex_code: str) -> tuple[int, int, int]:
|
|
129
|
+
"""Convert a CSS hex colour string to an RGB tuple.
|
|
130
|
+
|
|
131
|
+
Accepts 6-digit hex strings with or without a leading '#'.
|
|
132
|
+
3-digit shorthand (e.g. ``#fff``) is also supported and expanded.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
hex_code: Hex colour string, e.g. ``"#FF5733"`` or ``"ff5733"``.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Tuple of (R, G, B) integers in the range 0–255.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If *hex_code* is not a valid 3- or 6-digit hex colour.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> hex_to_rgb("#FF5733")
|
|
145
|
+
(255, 87, 51)
|
|
146
|
+
>>> hex_to_rgb("ffffff")
|
|
147
|
+
(255, 255, 255)
|
|
148
|
+
>>> hex_to_rgb("#000")
|
|
149
|
+
(0, 0, 0)
|
|
150
|
+
"""
|
|
151
|
+
if not isinstance(hex_code, str):
|
|
152
|
+
raise TypeError(f"hex_code must be a string, got {type(hex_code).__name__!r}")
|
|
153
|
+
|
|
154
|
+
code = hex_code.strip().lstrip("#")
|
|
155
|
+
|
|
156
|
+
if len(code) == 3:
|
|
157
|
+
code = "".join(ch * 2 for ch in code)
|
|
158
|
+
|
|
159
|
+
if len(code) != 6:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Invalid hex colour {hex_code!r}: expected 3 or 6 hex digits (with optional '#')."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
r = int(code[0:2], 16)
|
|
166
|
+
g = int(code[2:4], 16)
|
|
167
|
+
b = int(code[4:6], 16)
|
|
168
|
+
except ValueError:
|
|
169
|
+
raise ValueError(f"Invalid hex colour {hex_code!r}: contains non-hex characters.")
|
|
170
|
+
|
|
171
|
+
return (r, g, b)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def rgb_to_hex(r: int, g: int, b: int) -> str:
|
|
175
|
+
"""Convert RGB integers to a CSS hex colour string (uppercase, with '#').
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
r: Red channel (0–255).
|
|
179
|
+
g: Green channel (0–255).
|
|
180
|
+
b: Blue channel (0–255).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Uppercase hex string, e.g. ``"#FF5733"``.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValueError: If any channel is outside 0–255.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> rgb_to_hex(255, 87, 51)
|
|
190
|
+
'#FF5733'
|
|
191
|
+
>>> rgb_to_hex(0, 0, 0)
|
|
192
|
+
'#000000'
|
|
193
|
+
"""
|
|
194
|
+
for name, val in zip(("r", "g", "b"), (r, g, b)):
|
|
195
|
+
if not isinstance(val, (int, float)):
|
|
196
|
+
raise TypeError(f"{name!r} must be a number, got {type(val).__name__!r}")
|
|
197
|
+
if not (0 <= val <= 255):
|
|
198
|
+
raise ValueError(f"{name!r} must be in range 0–255, got {val}")
|
|
199
|
+
|
|
200
|
+
return "#{:02X}{:02X}{:02X}".format(int(r), int(g), int(b))
|