branding-engine 0.1.0 → 0.2.1
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.
- package/README.md +428 -73
- package/bin/cli.mjs +8 -5
- package/examples/severino-labs/README.md +95 -0
- package/examples/severino-labs/brand.json +30 -0
- package/examples/severino-labs/generated/cards/social-card.png +0 -0
- package/examples/severino-labs/generated/severino-labs/README.md +49 -0
- package/examples/severino-labs/generated/severino-labs/icons/apple-touch-icon.png +0 -0
- package/examples/severino-labs/generated/severino-labs/icons/favicon-192.png +0 -0
- package/examples/severino-labs/generated/severino-labs/icons/favicon-32.png +0 -0
- package/examples/severino-labs/generated/severino-labs/icons/favicon.ico +0 -0
- package/examples/severino-labs/generated/severino-labs/icons/favicon.svg +1 -0
- package/examples/severino-labs/generated/severino-labs/mark/mark-1024.png +0 -0
- package/examples/severino-labs/generated/severino-labs/mark/mark-512.png +0 -0
- package/examples/severino-labs/generated/severino-labs/mark/mark-transparent-dark.png +0 -0
- package/examples/severino-labs/generated/severino-labs/mark/mark-transparent-light.png +0 -0
- package/examples/severino-labs/generated/severino-labs/mark/mark.svg +1 -0
- package/examples/severino-labs/generated/severino-labs/sheet/overview.png +0 -0
- package/examples/severino-labs/generated/severino-labs/sheet/palette.png +0 -0
- package/examples/severino-labs/generated/severino-labs/sheet/sheet-mark.png +0 -0
- package/examples/severino-labs/generated/severino-labs/sheet/type-specimen.png +0 -0
- package/examples/severino-labs/generated/severino-labs/web/head.html +6 -0
- package/examples/severino-labs/generated/severino-labs/web/site.webmanifest +19 -0
- package/examples/severino-labs/generated/severino-labs/web/tokens.css +7 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps-dark.png +0 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps-light.png +0 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-caps.svg +1 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-dark.png +0 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark-light.png +0 -0
- package/examples/severino-labs/generated/severino-labs/wordmark/wordmark.svg +1 -0
- package/examples/severino-labs/studio.jpg +0 -0
- package/index.mjs +1 -0
- package/package.json +13 -6
- package/src/build.mjs +8 -6
- package/src/lib/extract-glyphs.mjs +65 -0
- package/src/lib/glyphs.mjs +6 -19
- package/src/lib/identity.mjs +11 -0
- package/src/lib/mark.mjs +28 -8
- package/src/lib/render.mjs +11 -2
- package/src/lib/wordmark.mjs +6 -1
- package/src/make-mark.mjs +2 -0
- package/src/make-sheet.mjs +2 -0
- package/src/make-web.mjs +2 -0
- package/src/make-wordmark.mjs +3 -1
- package/src/site.mjs +3 -1
- package/requirements.txt +0 -3
- package/src/lib/extract-glyphs.py +0 -75
package/src/make-mark.mjs
CHANGED
|
@@ -8,10 +8,12 @@ import path from 'node:path';
|
|
|
8
8
|
import sharp from 'sharp';
|
|
9
9
|
import { markSvg } from './lib/mark.mjs';
|
|
10
10
|
import { normalizeHex } from './lib/color.mjs';
|
|
11
|
+
import { normalizeGlyph } from './lib/identity.mjs';
|
|
11
12
|
import { pngsToIco } from './lib/ico.mjs';
|
|
12
13
|
|
|
13
14
|
export async function makeMark({ slug, hex, glyph = 'JS', outDir }) {
|
|
14
15
|
const fill = normalizeHex(hex);
|
|
16
|
+
glyph = normalizeGlyph(glyph);
|
|
15
17
|
const iconsDir = path.join(outDir, slug, 'icons');
|
|
16
18
|
const markDir = path.join(outDir, slug, 'mark');
|
|
17
19
|
fs.mkdirSync(iconsDir, { recursive: true });
|
package/src/make-sheet.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import sharp from 'sharp';
|
|
9
9
|
import { markSvg } from './lib/mark.mjs';
|
|
10
|
+
import { normalizeGlyph } from './lib/identity.mjs';
|
|
10
11
|
import { fontPath } from './lib/font.mjs';
|
|
11
12
|
import { darken, normalizeHex } from './lib/color.mjs';
|
|
12
13
|
import { esc } from './lib/html.mjs';
|
|
@@ -21,6 +22,7 @@ function prettyFont(file) {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export async function makeSheet({ slug, hex, glyph = 'JS', wordmark, deep, browser, outDir }) {
|
|
25
|
+
glyph = normalizeGlyph(glyph);
|
|
24
26
|
const fill = normalizeHex(hex);
|
|
25
27
|
const deepShade = deep ? normalizeHex(deep) : darken(fill);
|
|
26
28
|
const title = wordmark || glyph;
|
package/src/make-web.mjs
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { darken, normalizeHex } from './lib/color.mjs';
|
|
8
|
+
import { normalizeGlyph } from './lib/identity.mjs';
|
|
8
9
|
|
|
9
10
|
export function makeWeb({ slug, hex, glyph = 'JS', name, deep, onColor, outDir }) {
|
|
10
11
|
const accent = normalizeHex(hex);
|
|
12
|
+
glyph = normalizeGlyph(glyph);
|
|
11
13
|
const deepShade = deep ? normalizeHex(deep) : darken(accent);
|
|
12
14
|
const onAccent = onColor ? normalizeHex(onColor) : '#ffffff';
|
|
13
15
|
const label = name || slug;
|
package/src/make-wordmark.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import sharp from 'sharp';
|
|
9
9
|
import { wordmarkSvg } from './lib/wordmark.mjs';
|
|
10
10
|
import { WORDMARK_CHARS, ensureGlyphs, wordmarkGlyphFile } from './lib/glyphs.mjs';
|
|
11
|
+
import { normalizeGlyph } from './lib/identity.mjs';
|
|
11
12
|
|
|
12
13
|
const INK = { light: '#0b0620', dark: '#ffffff' }; // ink for light / dark backgrounds
|
|
13
14
|
const PNG_HEIGHT = 512; // raster export height; SVG stays the source of truth
|
|
@@ -17,13 +18,14 @@ const VARIANTS = [
|
|
|
17
18
|
];
|
|
18
19
|
|
|
19
20
|
export async function makeWordmark({ slug, hex, text, glyph = 'JS', weight = 700, outDir }) {
|
|
21
|
+
glyph = normalizeGlyph(glyph);
|
|
20
22
|
const dir = path.join(outDir, slug, 'wordmark');
|
|
21
23
|
mkdirSync(dir, { recursive: true });
|
|
22
24
|
|
|
23
25
|
// The build pre-extracts, but a one-off kit lands straight here, so make sure
|
|
24
26
|
// the cache exists. It bundles the whole alphabet (WORDMARK_CHARS) so a one-off
|
|
25
27
|
// never overwrites the shared set with just its own name's letters.
|
|
26
|
-
ensureGlyphs({ file: wordmarkGlyphFile(), weight, chars: WORDMARK_CHARS, label: 'wordmark glyphs' });
|
|
28
|
+
await ensureGlyphs({ file: wordmarkGlyphFile(), weight, chars: WORDMARK_CHARS, label: 'wordmark glyphs' });
|
|
27
29
|
|
|
28
30
|
const toPng = (svg) => sharp(Buffer.from(svg)).resize({ height: PNG_HEIGHT }).png().toBuffer();
|
|
29
31
|
for (const { caps, base } of VARIANTS) {
|
package/src/site.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'node:path';
|
|
|
11
11
|
import sharp from 'sharp';
|
|
12
12
|
import { markSvg } from './lib/mark.mjs';
|
|
13
13
|
import { darken, normalizeHex } from './lib/color.mjs';
|
|
14
|
+
import { normalizeGlyph } from './lib/identity.mjs';
|
|
14
15
|
import { pngsToIco } from './lib/ico.mjs';
|
|
15
16
|
|
|
16
17
|
const DEFAULT_CONFIG = {
|
|
@@ -27,6 +28,7 @@ function headSnippet(accent) {
|
|
|
27
28
|
'<link rel="icon" type="image/svg+xml" href="/favicon.svg" />',
|
|
28
29
|
'<link rel="apple-touch-icon" href="/apple-touch-icon.png" />',
|
|
29
30
|
'<link rel="manifest" href="/site.webmanifest" />',
|
|
31
|
+
'<link rel="stylesheet" href="/brand-tokens.css" />',
|
|
30
32
|
`<meta name="theme-color" content="${accent}" />`,
|
|
31
33
|
].join('\n');
|
|
32
34
|
}
|
|
@@ -69,7 +71,7 @@ export async function generateSite({ config, publicDir = 'public', cwd = process
|
|
|
69
71
|
const accent = normalizeHex(cfg.accent);
|
|
70
72
|
const onAccent = cfg.onColor ? normalizeHex(cfg.onColor) : '#ffffff';
|
|
71
73
|
const deep = cfg.deep ? normalizeHex(cfg.deep) : darken(accent);
|
|
72
|
-
const glyph = cfg.glyph || 'JS';
|
|
74
|
+
const glyph = normalizeGlyph(cfg.glyph || 'JS');
|
|
73
75
|
const name = cfg.name || 'Site';
|
|
74
76
|
|
|
75
77
|
const pub = path.resolve(cwd, publicDir);
|
package/requirements.txt
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Extract glyph outlines as SVG path data for the brand mark.
|
|
3
|
-
|
|
4
|
-
Defaults to the bundled Inter variable woff2, but any font works: pass a path to
|
|
5
|
-
a .ttf/.otf/.woff2. Variable fonts are instantiated at the given weight; static
|
|
6
|
-
fonts use their single master. Output is JSON the node generators consume, so
|
|
7
|
-
they stay pure-node.
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
python3 bin/lib/extract-glyphs.py "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 800
|
|
11
|
-
python3 bin/lib/extract-glyphs.py "AB" 700 ~/fonts/SomeFont.ttf
|
|
12
|
-
python3 bin/lib/extract-glyphs.py "AB" 700 ~/fonts/SomeFont.ttf myfont-glyphs.json
|
|
13
|
-
|
|
14
|
-
The default run writes brand-glyphs.json (the kit's glyph cache). A custom font
|
|
15
|
-
writes <font-stem>-glyphs.json unless you name the output, so the brand cache is
|
|
16
|
-
never clobbered by accident. Point a generator at a non-default file with
|
|
17
|
-
BRAND_GLYPHS=<file> (e.g. BRAND_GLYPHS=somefont-glyphs.json ./kit.sh ...).
|
|
18
|
-
"""
|
|
19
|
-
import json
|
|
20
|
-
import sys
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
|
-
from fontTools.ttLib import TTFont
|
|
24
|
-
from fontTools.varLib.instancer import instantiateVariableFont
|
|
25
|
-
from fontTools.pens.svgPathPen import SVGPathPen
|
|
26
|
-
from fontTools.pens.boundsPen import BoundsPen
|
|
27
|
-
|
|
28
|
-
here = Path(__file__).resolve().parent
|
|
29
|
-
root = here.parents[1]
|
|
30
|
-
default_font = root / "assets/fonts/inter/inter-variable-latin.woff2"
|
|
31
|
-
|
|
32
|
-
chars = sys.argv[1] if len(sys.argv) > 1 else "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
33
|
-
weight = float(sys.argv[2]) if len(sys.argv) > 2 else 800.0
|
|
34
|
-
font_path = Path(sys.argv[3]).expanduser() if len(sys.argv) > 3 else default_font
|
|
35
|
-
|
|
36
|
-
if len(sys.argv) > 4:
|
|
37
|
-
out_path = Path(sys.argv[4])
|
|
38
|
-
if not out_path.is_absolute():
|
|
39
|
-
out_path = here / out_path
|
|
40
|
-
elif len(sys.argv) > 3:
|
|
41
|
-
# Custom font, no explicit output: derive a name so Inter is never clobbered.
|
|
42
|
-
out_path = here / f"{font_path.stem}-glyphs.json"
|
|
43
|
-
else:
|
|
44
|
-
out_path = here / "brand-glyphs.json"
|
|
45
|
-
|
|
46
|
-
font = TTFont(str(font_path))
|
|
47
|
-
if "fvar" in font:
|
|
48
|
-
instantiateVariableFont(font, {"wght": weight}, inplace=True)
|
|
49
|
-
|
|
50
|
-
glyph_set = font.getGlyphSet()
|
|
51
|
-
cmap = font.getBestCmap()
|
|
52
|
-
upem = font["head"].unitsPerEm
|
|
53
|
-
|
|
54
|
-
glyphs = {}
|
|
55
|
-
for ch in dict.fromkeys(chars):
|
|
56
|
-
name = cmap[ord(ch)]
|
|
57
|
-
g = glyph_set[name]
|
|
58
|
-
pen = SVGPathPen(glyph_set)
|
|
59
|
-
g.draw(pen)
|
|
60
|
-
bp = BoundsPen(glyph_set)
|
|
61
|
-
g.draw(bp)
|
|
62
|
-
# Blank glyphs (e.g. the space) have no contours, so BoundsPen leaves
|
|
63
|
-
# bounds None; they still carry an advance width the layout needs.
|
|
64
|
-
xmin, ymin, xmax, ymax = bp.bounds or (0, 0, 0, 0)
|
|
65
|
-
glyphs[ch] = {
|
|
66
|
-
"path": pen.getCommands(),
|
|
67
|
-
"advance": g.width,
|
|
68
|
-
"bounds": {"xMin": xmin, "yMin": ymin, "xMax": xmax, "yMax": ymax},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
out_path.write_text(json.dumps(
|
|
72
|
-
{"font": font_path.name, "unitsPerEm": upem, "weight": weight, "glyphs": glyphs},
|
|
73
|
-
indent=2,
|
|
74
|
-
))
|
|
75
|
-
print(f"wrote {out_path.name} from {font_path.name} — upem={upem} weight={int(weight)} chars={''.join(glyphs)}")
|