Metadata-Version: 2.4
Name: ambhora-brandkit
Version: 1.0.0
Summary: Generate every distribution variant of the Ambhora brand assets
Author-email: Jayesh Badwaik <j.badwaik@fz-juelich.de>
Project-URL: Homepage, https://github.com/ambhora/ambhora-brandkit
Project-URL: Source, https://github.com/ambhora/ambhora-brandkit
Keywords: brand,logo,svg,design,assets
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: lxml>=4.9
Provides-Extra: render
Requires-Dist: cairosvg>=2.7; extra == "render"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pillow>=10; extra == "dev"

<!--
- SPDX-License-Identifier: CC-BY-4.0
- Copyright (C) 2025 Jayesh Badwaik <j.badwaik@fz-juelich.de>
-->

# ambhora-brandkit

Generates every distribution variant of the Ambhora brand assets from the
pristine sources, which are never modified.

```
pip install .[render]
brandkit build -o dist
```

## No Inkscape required

The original plan was to shell out to Inkscape for everything. That turned out
to be unnecessary. The work splits into four parts, and only one of them
actually needs a renderer:

| Step | How it is done | Dependency |
|------|----------------|------------|
| Recolour, recanvas, background plate | `lxml` tree surgery | `lxml` |
| Ink bounding box (for padding) | analytic geometry, `svgbbox.py` | none |
| SVG output | serialised straight from lxml | none |
| PNG and PDF output | a pluggable backend | one of three |

So SVG-only builds run with nothing but `lxml`. PNG and PDF need exactly one of:

- **`cairosvg`** — a pip-installable library, so `pip install .[render]` gives a
  complete install with no external binaries. Writes true vector PDFs. Default.
- **`rsvg-convert`** — from librsvg. Excellent SVG conformance, very fast, and
  already present on most Linux systems and via Homebrew.
- **`inkscape`** — still supported. It is the slowest of the three, but it is
  the only backend that can measure and render **live text**, which makes it
  the right choice for the `source` set (see below).

`brandkit info` reports which backends are usable on the current machine, and
`--engine` picks one explicitly.

### Why the bounding box did not need Inkscape either

Padding has to be measured from the *ink*, not from the page, so the one thing
genuinely worth calling `inkscape --query-all` for was the bounding box.
`svgbbox.py` computes it directly instead: Bézier segments are bounded by their
real stationary points, elliptical arcs are converted to cubics first, and
affine transforms are composed down the tree and applied to control points
*before* extrema are solved — which keeps the result exact under rotation and
skew. Against a 4000-point resampling of random cubics it agrees to 1.5e-5.

On the real assets it reproduces the page geometry exactly: the icon measures
1600×1600 to the last decimal.

The one thing it cannot do is measure live `<text>`, because that needs font
metrics and shaping. This only affects the `source` set. When it happens the
build says so and falls back to the document page, or you can pass
`--engine inkscape` on a machine with the brand font installed.

## The variant matrix

Three marks × three inks × three backgrounds × three formats, minus the two
illegible pairings (black on black, white on white):

|            | transparent | white plate | black plate |
|------------|:-----------:|:-----------:|:-----------:|
| **colour** | yes | yes | yes |
| **white**  | yes | — | yes |
| **black**  | yes | yes | — |

Seven legible combinations per mark, per padding rule, in `svg`, `pdf` and
`png` (at each requested raster width). With the default two padding rules and
three PNG widths that is **210 files**.

Output is laid out as `dist/<mark>/<format>/<name>`, with self-describing
names:

```
dist/icon/svg/ambhora-icon-white-on-black-clearspace.svg
dist/icon/png/ambhora-icon-color-on-transparent-none-512px.png
dist/wordmark/pdf/ambhora-wordmark-black-on-white-clearspace.pdf
```

A `dist/manifest.json` records every file with its size, SHA-256, canvas
geometry and the digest of the pristine source it came from, so a build is
auditable and reproducible.

## Padding

Yes, padded variants work — and they are exact, because padding is applied as
a change of *window*, not a transform of the artwork. The `viewBox` is widened
around the ink box; not a single coordinate in the drawing moves. A test
asserts this by re-measuring the ink after padding and comparing it to the
original.

Clear space is expressed as a fraction of the mark's **ink height**, applied
equally on all four sides. Height is the right reference here because the
wordmark is roughly 7:1 — a width-derived margin would swallow it, whereas a
height-derived margin is how exclusion zones are specified in practice, as a
multiple of the cap height.

| Rule | Margin | Use |
|------|--------|-----|
| `none` | — | Original page, framed exactly like the pristine file. Default. |
| `tight` | 10% of ink height | App icon / favicon safe area; artwork at ~80% of canvas. |
| `clearspace` | 25% of ink height | Minimum brand exclusion zone. Default. |
| `generous` | 50% of ink height | Hero placements, covers, title slides. |
| `avatar` | 10%, then squared | Profile pictures. Intended for `icon`; squaring the wide `logo` lockup leaves a lot of empty canvas. |

`brandkit paddings` prints this at the terminal. Select rules with repeated
`--padding` flags.

## The `share` directory

The pristine assets live in `share/ambhora-brandkit/{primary,source}` and are
byte-for-byte identical to the originals. They install as **shared data**, to
`<prefix>/share/ambhora-brandkit/`, rather than being buried inside the Python
package — so a Makefile, a LaTeX build or a shell script can reach them just as
easily as Python can.

At runtime they are looked up in this order:

1. `$AMBHORA_BRANDKIT_SHARE`
2. `<sys.prefix>/share/ambhora-brandkit` — a normal or virtualenv install
3. `<site.USER_BASE>/share/...` — a `pip install --user`
4. `/usr/local/share/...`, `/usr/share/...` — system packages
5. `share/ambhora-brandkit` above the source tree — editable installs and plain
   checkouts, so the tool works straight from a clone

`primary` has all type converted to outlines and is what you should build from.
`source` keeps editable text and needs the ADLaM Display brand font installed
to render correctly.

## Usage

```
brandkit info                          # assets, palette, backend status
brandkit list                          # print the variant matrix
brandkit paddings                      # explain the clear-space rules
brandkit build -o dist                 # everything
brandkit build -n                      # dry run, writes nothing

brandkit build --asset icon --format png --padding avatar --png-width 512
brandkit build --format svg            # needs no rendering backend at all
brandkit build --engine rsvg -j 8
brandkit build --asset-set source --engine inkscape
```

Also available as `python -m ambhora_brandkit`.

## Layout

```
pyproject.toml
share/ambhora-brandkit/     pristine assets, installed to <prefix>/share/
  primary/                  type outlined — build from this
  source/                   live text — needs the brand font
src/ambhora_brandkit/
  geometry.py               affine matrices, rectangles, transform parsing
  svgbbox.py                analytic ink bounding boxes
  svgdoc.py                 recolour, recanvas, background plate
  padding.py                clear-space rules
  spec.py                   the variant matrix and naming scheme
  engines.py                cairosvg / rsvg / inkscape backends
  build.py                  planning, execution, manifest
  cli.py                    command line interface
tests/
```

## Licensing

The brand assets under `share/` carry their original `CC-BY-4.0` headers and
are unchanged. The Python code is marked `MIT`, since CC licences are not
intended for software — change it if you would rather it matched the assets.
