Metadata-Version: 2.2
Name: pkgprint
Version: 0.2.1
Summary: Print and packaging math utilities for developers
Author: Rishabh
License: MIT
Project-URL: Homepage, https://github.com/Rishabh55122/pkgprint
Project-URL: Bug Tracker, https://github.com/Rishabh55122/pkgprint/issues
Keywords: print,packaging,design,cmyk,dpi,bleed,typography
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Intended Audience :: Developers
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Pillow>=9.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"

# pkgprint

**Print and packaging math — straight to your Python project.**

[![PyPI version](https://badge.fury.io/py/pkgprint.svg)](https://pypi.org/project/pkgprint/)
[![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://www.python.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Tests](https://img.shields.io/badge/tests-220%20passing-brightgreen.svg)](#testing--quality)

`pkgprint` is a Python library for the fiddly arithmetic behind print and packaging production: unit conversions, colour model translations, standard paper and box sizes, bleed calculations, print-readiness file checking, WCAG colour contrast validation, and box dieline layout. It's built for developers who work with print pipelines, packaging design tools, or any application that touches commercial print — people who need correct, well-tested answers, not approximations. The core math is zero-dependency; Pillow is only pulled in when you actually check image files.

---

## Installation

```bash
pip install pkgprint
```

---

## Quick Start

```python
import pkgprint

# Look up a standard size and convert to inches
w, h = pkgprint.paper_size("A4")                    # (210, 297)
pkgprint.mm_to_inches(w)                            # 8.267716535433072

# Add 3 mm bleed to a US business card
pkgprint.add_bleed(89, 51)                          # (95, 57)

# Translate a brand colour between colour models
pkgprint.cmyk_to_rgb(0, 100, 100, 0)               # (255, 0, 0)
pkgprint.rgb_to_hex(255, 0, 0)                      # '#FF0000'

# Check text contrast against WCAG AA
pkgprint.contrast_ratio((0, 0, 0), (255, 255, 255)) # 21.0
result = pkgprint.check_wcag_compliance((140, 140, 140), (255, 255, 255))
result.passed                                        # False  (3.36:1 < 4.5)

# Calculate flat sheet size for a shipping box (RSC style)
pkgprint.rsc_flat_size(300, 200, 150)               # (1035.0, 350)
```

---

## Why pkgprint?

- **Zero-dependency core.** Unit conversions, colour math, paper sizes, bleed calculations — none of it needs an external package. Pillow is only imported when you call `check_dpi`, `check_color_mode`, or `check_dimensions`.
- **Formulas from the field.** The WCAG relative luminance uses the exact sRGB gamma correction; RSC flat-size formulas match industry practice; paper sizes reference ISO 216 and ANSI standards. Shortcuts are flagged explicitly in docstrings.
- **Rigorous tests.** 220+ tests covering normal cases, edge cases, and error paths. The suite caught a real production edge case during development: `2480 × 3508 px` — the widely-cited "A4 at 300 DPI" pixel count — actually resolves to 299.97 DPI due to mm→inch rounding, and fails a 300 DPI check. `pkgprint` ships the correct `2481 px` threshold.
- **MIT licensed, commercially free.** Use it in client projects, SaaS products, or internal tooling without restriction.

---

## Feature Overview

| Module | Description | Functions |
|---|---|---|
| `units` | mm ↔ inches ↔ points, DPI ↔ PPI conversions | 6 |
| `color` | CMYK ↔ RGB ↔ hex colour model conversions | 4 |
| `paper` | Standard paper sizes (ISO, ANSI, US) and common box dimensions | 3 |
| `print_specs` | Bleed, safe margin, and trim size calculations | 3 |
| `check` | Image file print-readiness: DPI, colour mode, and dimension checks | 4 + 2 types |
| `accessibility` | WCAG 2.1 contrast ratio checking and colour adjustment | 4 + 1 type |
| `dieline` | Flat sheet and named panel layout for RSC and Mailer box styles | 3 + 1 type |

---

## Module Reference

<details>
<summary><strong>📏 units — Measurement Conversions</strong></summary>

Converts between the measurement units used throughout print and design workflows. DPI and PPI functions are semantic aliases — numerically identical, provided for clarity in code that mixes screen and print contexts.

| Function | Converts |
|---|---|
| `mm_to_inches(mm)` | millimetres → inches |
| `inches_to_mm(inches)` | inches → millimetres |
| `mm_to_points(mm)` | millimetres → PostScript points (1 pt = 1/72 in) |
| `points_to_mm(points)` | PostScript points → millimetres |
| `dpi_to_ppi(dpi)` | DPI → PPI (identity, semantic alias) |
| `ppi_to_dpi(ppi)` | PPI → DPI (identity, semantic alias) |

```python
import pkgprint

pkgprint.mm_to_inches(25.4)     # 1.0
pkgprint.mm_to_inches(210)      # 8.267716535433072  (A4 width)
pkgprint.inches_to_mm(1)        # 25.4
pkgprint.mm_to_points(25.4)     # 72.0  (1 inch = 72 pt)
```

</details>

---

<details>
<summary><strong>🎨 color — Colour Model Conversions</strong></summary>

Converts between the colour models used in print (CMYK) and screen/web (RGB, hex) workflows. CMYK channels use the 0–100 scale (percentage); RGB channels use 0–255.

| Function | Converts |
|---|---|
| `cmyk_to_rgb(c, m, y, k)` | CMYK (0–100 each) → RGB (0–255 each) |
| `rgb_to_cmyk(r, g, b)` | RGB (0–255 each) → CMYK (0–100 each) |
| `hex_to_rgb(hex_str)` | Hex colour string → RGB tuple |
| `rgb_to_hex(r, g, b)` | RGB tuple → hex colour string |

```python
import pkgprint

pkgprint.cmyk_to_rgb(0, 100, 100, 0)    # (255, 0, 0)   — pure red
pkgprint.cmyk_to_rgb(0, 0, 0, 100)      # (0, 0, 0)     — process black
pkgprint.rgb_to_cmyk(255, 0, 0)         # (0.0, 100.0, 100.0, 0.0)
pkgprint.rgb_to_hex(255, 0, 0)          # '#FF0000'
pkgprint.hex_to_rgb('#FF0000')          # (255, 0, 0)
```

</details>

---

<details>
<summary><strong>📄 paper — Standard Paper & Box Sizes</strong></summary>

Looks up standard paper sizes (ISO A-series, B-series, US Letter/Legal/Tabloid, business cards) and common shipping box sizes. All dimensions are in millimetres. Lookups are case-insensitive.

| Function | Returns |
|---|---|
| `paper_size(name)` | `(width_mm, height_mm)` for a named paper size |
| `list_paper_sizes()` | Sorted list of all available paper size names |
| `box_size(name)` | `(length_mm, width_mm, height_mm)` for a named box size |

```python
import pkgprint

pkgprint.paper_size("A4")                 # (210, 297)
pkgprint.paper_size("Letter")             # (216, 279)
pkgprint.paper_size("Business Card US")   # (89, 51)
pkgprint.paper_size("Business Card ISO")  # (85, 55)
pkgprint.box_size("RSC Medium")           # (305, 229, 152)

pkgprint.list_paper_sizes()[:4]
# ['A0', 'A1', 'A2', 'A3']
```

> **Note on business card sizes:** ISO/European business cards are 85×55 mm; US business cards are 89×51 mm (3.5"×2"). Both are available under distinct names.

</details>

---

<details>
<summary><strong>✂️ print_specs — Bleed, Margin & Trim Calculations</strong></summary>

Calculates the three key dimension sets in any print-ready file: the bleed size (artwork canvas with bleed added), the safe area (the zone guaranteed not to be trimmed), and the trim size (finished size recovered from a bled-up file).

| Function | Purpose |
|---|---|
| `add_bleed(w, h, bleed_mm=3)` | Add bleed to both dimensions; returns `(bleed_w, bleed_h)` |
| `safe_margin(w, h, margin_mm=5)` | Subtract safe margin; returns `(safe_w, safe_h)` |
| `trim_size(w, h, bleed_mm=3)` | Recover trim size from a bled-up file; returns `(trim_w, trim_h)` |

```python
import pkgprint

# US business card: 89×51 mm trim, 3 mm bleed → canvas size
pkgprint.add_bleed(89, 51)               # (95, 57)

# A4 with 5 mm bleed
pkgprint.add_bleed(210, 297, bleed_mm=5) # (220, 307)

# Safe text area inside A4 (5 mm margin from each edge)
pkgprint.safe_margin(210, 297)           # (200, 287)

# Recover trim size from a 95×57 mm bled-up business card
pkgprint.trim_size(95, 57)               # (89, 51)
```

</details>

---

<details>
<summary><strong>✅ check — Print-Readiness File Checker</strong></summary>

Inspects an actual image file and reports whether it meets commercial print requirements. Requires **Pillow** (installed automatically with `pip install pkgprint`).

Each check returns a `CheckResult` with `.passed`, `.actual`, `.expected`, and `.message`. `full_report()` collects all three checks without raising on individual failures.

| Function | Checks |
|---|---|
| `check_dpi(path, w_mm, h_mm, min_dpi=300)` | Effective DPI at the target print size |
| `check_color_mode(path, required_mode="CMYK")` | Colour mode (CMYK, RGB, Grayscale…) |
| `check_dimensions(path, w_mm, h_mm, tolerance_mm=1)` | Physical size at native DPI vs target |
| `full_report(path, w_mm, h_mm, min_dpi=300, required_color_mode="CMYK")` | All three checks combined |

```python
import pkgprint

report = pkgprint.full_report("artwork.tif", 210, 297)

print(report.summary)
# DPI check:        PASS — Effective DPI 304.8 ≥ 300 ...
# Colour mode:      FAIL — Colour mode is 'RGB' but 'CMYK' is required ...
# Dimensions check: PASS — Dimensions 210.0×297.0 mm match target ...
# Overall:          NOT PRINT-READY

report.passed          # False
report.dpi.passed      # True
report.color_mode.passed  # False
report.dimensions.actual  # (210.0, 297.0)

# Individual checks
result = pkgprint.check_dpi("artwork.tif", 210, 297, min_dpi=300)
result.passed    # True / False
result.actual    # effective DPI on the worst axis
result.message   # human-readable diagnostic
```

**Result types:** `CheckResult(passed, actual, expected, message)` and `PrintReport(dpi, color_mode, dimensions, passed, summary)`.

</details>

---

<details>
<summary><strong>♿ accessibility — WCAG Colour Contrast Checker</strong></summary>

Checks foreground/background colour pairs for legibility using the WCAG 2.1 contrast formulas — useful for packaging with text, label design, or any print piece with brand colour over a background.

WCAG thresholds: AA normal text = 4.5:1, AA large text = 3.0:1, AAA normal = 7.0:1, AAA large = 4.5:1.

| Function | Purpose |
|---|---|
| `relative_luminance(r, g, b)` | WCAG sRGB relative luminance (0.0–1.0) |
| `contrast_ratio(rgb1, rgb2)` | WCAG contrast ratio (1.0–21.0) |
| `check_wcag_compliance(rgb1, rgb2, level="AA", text_size="normal")` | Pass/fail + ratio vs WCAG threshold |
| `suggest_darker_or_lighter(rgb_text, rgb_bg, target_ratio=4.5)` | Adjusted text colour that meets the target ratio |

```python
import pkgprint

# Pure black on white: maximum contrast
pkgprint.contrast_ratio((0, 0, 0), (255, 255, 255))    # 21.0

# Mid-grey on white: fails AA normal text
result = pkgprint.check_wcag_compliance((140, 140, 140), (255, 255, 255))
result.passed          # False
result.ratio           # 3.36
result.required_ratio  # 4.5
result.message         # 'Contrast ratio 3.36:1 does NOT meet WCAG AA ...'

# Get an adjusted colour that passes
adjusted = pkgprint.suggest_darker_or_lighter(
    (140, 140, 140), (255, 255, 255), target_ratio=4.5
)
adjusted                                               # (118, 118, 118)
pkgprint.contrast_ratio(adjusted, (255, 255, 255))    # 4.54
```

**Result type:** `ContrastResult(passed, ratio, required_ratio, level, text_size, message)`.

</details>

---

<details>
<summary><strong>📦 dieline — Flat Panel Layout Calculator</strong></summary>

Calculates the flat sheet dimensions and named panel sizes for common box styles, so designers know how large their artwork canvas needs to be before structural work starts.

> **Planning formulas only.** These are standard industry approximations. They do not account for material thickness, flute direction, or supplier-specific tuck geometry. Confirm with your structural engineer or printer's dieline template before production.

Supported styles in v0.2.0: `"RSC"` (Regular Slotted Container) and `"Mailer"` (tuck-end mailer). More styles planned — see [Roadmap](#roadmap).

| Function | Returns |
|---|---|
| `rsc_flat_size(length, width, height)` | `(flat_width_mm, flat_height_mm)` for an RSC box |
| `mailer_flat_size(length, width, height)` | `(flat_width_mm, flat_height_mm)` for a tuck mailer |
| `panel_layout(length, width, height, style="RSC")` | List of `Panel` objects with name, width_mm, height_mm |

```python
import pkgprint

# Flat canvas for a 300×200×150 mm RSC shipping box
pkgprint.rsc_flat_size(300, 200, 150)        # (1035.0, 350)
# flat_width  = 2×(300+200) + 35 mm glue flap = 1035 mm
# flat_height = 150 + 200 (top + bottom flaps each = W/2) = 350 mm

# Named panels for the same box
panels = pkgprint.panel_layout(300, 200, 150)
len(panels)       # 13  (4 body + 8 flaps + 1 glue flap)
panels[0]         # Panel(name='front', width_mm=300, height_mm=150)
panels[1]         # Panel(name='right side', width_mm=200, height_mm=150)

# Tuck mailer: 200×100×50 mm
pkgprint.mailer_flat_size(200, 100, 50)      # (635.0, 300.0)
pkgprint.panel_layout(200, 100, 50, style="Mailer")
# 7 panels: front, right side, back, left side,
#           top tuck flap, bottom tuck flap, glue flap
```

**Result type:** `Panel(name, width_mm, height_mm)`.

</details>

---

## Real-World Use Case

**Scenario: Preparing a client's business card artwork for print.**

A client uploads a logo file intended for their US business card (89×51 mm). Before sending to print, you need to verify the file meets your printer's spec, add bleed, and check that their brand colour — a warm grey — will actually be legible as text over the card's white background. With `pkgprint`, you look up the standard size with `paper_size("Business Card US")`, then run `full_report()` on the uploaded file: it instantly tells you the effective DPI at that print size, whether the file is CMYK or RGB (most printers require CMYK), and whether the physical dimensions match. You then call `add_bleed(89, 51)` to get the 95×57 mm canvas size your designer needs. Finally, `check_wcag_compliance(brand_grey, (255, 255, 255))` flags that the grey is only 3.36:1 contrast — failing AA — and `suggest_darker_or_lighter()` proposes an adjusted value that passes, keeping the hue intact. The whole check pipeline runs in under a second, before anything goes to the print queue.

---

## Testing & Quality

`pkgprint` ships with **220 tests** across 7 test files, one per module. The suite covers:

- Normal operation, edge cases (zero bleed, exact threshold values), and error paths (wrong types, out-of-range inputs, missing files)
- Both WCAG AA and AAA thresholds for all four normal/large text combinations
- Programmatically generated image fixtures for `check.py` — no binary assets committed to the repo

**A note on rigor:** During development, the test suite caught a subtle real-world edge case. `2480 × 3508 px` is widely cited as "A4 at 300 DPI", but `2480 ÷ (210 mm / 25.4) = 299.97 DPI` — fractionally below 300 due to mm→inch conversion. The correct pixel count is `2481 × 3508`. The test suite uses the accurate value and the `check_dpi` function enforces the correct threshold.

---

## Roadmap

- **SVG dieline output** — generate production-ready flat dieline artwork directly from `panel_layout()` (v0.3.0)
- **Extended box styles** — crash-lock bottom, auto-bottom, five-panel wrap, and more
- **Expanded paper standards** — JIS B-series, Japanese Shiroku-ban, additional envelope sizes
- **CLI tool** — `pkgprint check artwork.tif --size A4 --dpi 300` for quick pre-flight from the terminal

---

## Contributing

Bug reports, feature requests, and pull requests are welcome at [github.com/Rishabh55122/pkgprint/issues](https://github.com/Rishabh55122/pkgprint/issues). Please open an issue before starting significant work so we can align on approach.

---

## License

MIT — see [LICENSE](LICENSE). Free to use in commercial and open-source projects.
