@logomarks/core 0.1.11 → 0.2.0
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/CHANGELOG.md +22 -0
- package/README.md +7 -0
- package/package.json +47 -4
- package/src/font/glyphs.d.ts +31 -0
- package/src/font/glyphs.js +228 -0
- package/src/font/index.d.ts +10 -0
- package/src/font/index.js +10 -0
- package/src/font/measure.d.ts +16 -0
- package/src/font/measure.js +201 -0
- package/src/font/moves.d.ts +42 -0
- package/src/font/moves.js +263 -0
- package/src/forms/discs.d.ts +20 -0
- package/src/forms/discs.js +114 -0
- package/src/forms/emblems.d.ts +22 -0
- package/src/forms/emblems.js +109 -0
- package/src/forms/flow.d.ts +21 -0
- package/src/forms/flow.js +80 -0
- package/src/forms/folds.d.ts +18 -0
- package/src/forms/folds.js +147 -0
- package/src/forms/geo.d.ts +46 -0
- package/src/forms/geo.js +233 -0
- package/src/forms/hooks.d.ts +40 -0
- package/src/forms/hooks.js +204 -0
- package/src/forms/index.d.ts +22 -0
- package/src/forms/index.js +24 -0
- package/src/forms/jog.d.ts +33 -0
- package/src/forms/jog.js +162 -0
- package/src/forms/lines.d.ts +32 -0
- package/src/forms/lines.js +233 -0
- package/src/forms/ref.d.ts +41 -0
- package/src/forms/ref.js +246 -0
- package/src/forms/shatter.d.ts +40 -0
- package/src/forms/shatter.js +341 -0
- package/src/forms/slats.d.ts +35 -0
- package/src/forms/slats.js +78 -0
- package/src/forms/swash.d.ts +23 -0
- package/src/forms/swash.js +74 -0
- package/src/forms/tech.d.ts +40 -0
- package/src/forms/tech.js +293 -0
- package/src/forms/tracks.d.ts +36 -0
- package/src/forms/tracks.js +121 -0
- package/src/index.js +2 -1
- package/src/iso/index.d.ts +5 -0
- package/src/iso/index.js +13 -0
- package/src/iso/objects.d.ts +168 -0
- package/src/iso/objects.js +1042 -0
- package/src/iso/planes.d.ts +27 -0
- package/src/iso/planes.js +138 -0
- package/src/iso/voxels.d.ts +43 -0
- package/src/iso/voxels.js +255 -0
- package/src/letters/blackletter.d.ts +31 -0
- package/src/letters/blackletter.js +199 -0
- package/src/letters/dotstroke.d.ts +16 -0
- package/src/letters/dotstroke.js +77 -0
- package/src/letters/echo.d.ts +42 -0
- package/src/letters/echo.js +246 -0
- package/src/letters/frame.d.ts +6 -0
- package/src/letters/frame.js +14 -0
- package/src/letters/index.d.ts +16 -0
- package/src/letters/index.js +18 -0
- package/src/letters/inktrap.d.ts +66 -0
- package/src/letters/inktrap.js +244 -0
- package/src/letters/mono.d.ts +145 -0
- package/src/letters/mono.js +684 -0
- package/src/letters/pillar.d.ts +31 -0
- package/src/letters/pillar.js +257 -0
- package/src/letters/serif.d.ts +109 -0
- package/src/letters/serif.js +666 -0
- package/src/letters/stair.d.ts +8 -0
- package/src/letters/stair.js +43 -0
- package/src/letters/stem.d.ts +29 -0
- package/src/letters/stem.js +205 -0
- package/src/objects/animals.d.ts +105 -0
- package/src/objects/animals.js +469 -0
- package/src/objects/bio.d.ts +14 -0
- package/src/objects/bio.js +76 -0
- package/src/objects/devglyphs.d.ts +34 -0
- package/src/objects/devglyphs.js +226 -0
- package/src/objects/devices.d.ts +31 -0
- package/src/objects/devices.js +114 -0
- package/src/objects/faces.d.ts +94 -0
- package/src/objects/faces.js +344 -0
- package/src/objects/food.d.ts +12 -0
- package/src/objects/food.js +74 -0
- package/src/objects/index.d.ts +13 -0
- package/src/objects/index.js +18 -0
- package/src/objects/rosettes.d.ts +14 -0
- package/src/objects/rosettes.js +64 -0
- package/src/objects/sport.d.ts +8 -0
- package/src/objects/sport.js +37 -0
- package/src/objects/weapons.d.ts +31 -0
- package/src/objects/weapons.js +179 -0
- package/src/pixels/dither.d.ts +78 -0
- package/src/pixels/dither.js +399 -0
- package/src/pixels/emote.d.ts +6 -0
- package/src/pixels/emote.js +76 -0
- package/src/pixels/index.d.ts +7 -0
- package/src/pixels/index.js +8 -0
- package/src/sdf/index.d.ts +2 -0
- package/src/sdf/index.js +2 -0
- package/src/sdf/y2k.d.ts +12 -0
- package/src/sdf/y2k.js +80 -0
- package/src/util/vec.d.ts +14 -0
- package/src/util/vec.js +61 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Shape } from '../geometry/types.js';
|
|
2
|
+
import type { Face } from './serif.js';
|
|
3
|
+
|
|
4
|
+
/** The pillar face: a condensed serif face with its counter width cw. */
|
|
5
|
+
export type PillarFace = Face & { cw: number };
|
|
6
|
+
/** A letter drawn beside the pillar: its ink, the pillar corners it joins, and (for a follow) its far edge. */
|
|
7
|
+
export interface PillarPart {
|
|
8
|
+
s: Shape;
|
|
9
|
+
at: Record<string, number>;
|
|
10
|
+
w?: number;
|
|
11
|
+
}
|
|
12
|
+
/** pillarPair()'s options: a T lead's bar and an H lead's crossbar. */
|
|
13
|
+
export interface PillarOptions {
|
|
14
|
+
tOver?: number;
|
|
15
|
+
tLeft?: number;
|
|
16
|
+
tBeak?: number;
|
|
17
|
+
hBar?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The condensed face at cap H with hairline h. */
|
|
21
|
+
export function pface(H: number, h: number): PillarFace;
|
|
22
|
+
/** A condensed bowl on a stem, its right corners rounded by quarter ellipses. */
|
|
23
|
+
export function cBowl(m: Face, x0: number, y0: number, y1: number, xr: number, t: number, ry: number): Shape;
|
|
24
|
+
/** The leads, drawn left of the pillar. */
|
|
25
|
+
export const PILLAR_LEFT: Record<string, (m: PillarFace, o?: PillarOptions) => PillarPart>;
|
|
26
|
+
/** The follows, drawn right of the pillar (t: under a T lead's bar). */
|
|
27
|
+
export const PILLAR_RIGHT: Record<string, (m: PillarFace, t?: boolean) => PillarPart>;
|
|
28
|
+
/** The pair a + b on one pillar at cap H with hairline h. */
|
|
29
|
+
export function pillarPair(a: string, b: string, H: number, h: number, o?: PillarOptions): { s: Shape; m: PillarFace };
|
|
30
|
+
/** The pillar mark: the pair over a short bar a gap below, optionally knocked out of a disc. */
|
|
31
|
+
export function pillar(a: string, b: string, o?: PillarOptions & { bar?: number; bh?: number; disc?: boolean; pad?: number }): (g: number) => Shape;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pillar monograms: two tall condensed Didone capitals sharing ONE stem, the pillar, over a short bar a gap below,
|
|
3
|
+
* optionally knocked out of a disc. A geometric condensed Didone on the serif face's parts (foot serifs, brackets,
|
|
4
|
+
* slants, stressed rings): the first letter is drawn left of the pillar (x < 0), the second right of it (x > S). Leads
|
|
5
|
+
* (PILLAR_LEFT): J M N D A H, and a T lead is the pillar's own top. Follows (PILLAR_RIGHT): D P B H F R N K A E L. From the
|
|
6
|
+
* Logomarks tools (pillarparts.mjs, batches 292, 300 and 329).
|
|
7
|
+
*/
|
|
8
|
+
import { U, D, I, rect, ellipse, circle, move, bbox, poly } from '../geometry/index.js';
|
|
9
|
+
import { despeckShape } from '../parts/index.js';
|
|
10
|
+
import { face, foot, slant, ringArc, ball, bracket } from './serif.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A vector made unit length.
|
|
14
|
+
*
|
|
15
|
+
* @param {readonly number[]} v The vector.
|
|
16
|
+
* @returns {number[]} Its direction.
|
|
17
|
+
*/
|
|
18
|
+
const unit = ([x, y]) => { const l = Math.hypot(x, y) || 1; return [x / l, y / l]; };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The condensed face: cap H, stems S (the pillar, 0.19 H), hairlines h (set so they fit to 31), serifs reaching ov,
|
|
22
|
+
* counters cw wide (facing serifs keep 40+ between them). The pillar is x 0…S.
|
|
23
|
+
*
|
|
24
|
+
* @param {number} H The cap height.
|
|
25
|
+
* @param {number} h The hairline.
|
|
26
|
+
* @returns {import('./serif.js').Face & { cw: number }} The face.
|
|
27
|
+
*/
|
|
28
|
+
export const pface = (H, h) => ({ ...face(H, { S: Math.round(H * 0.19), h, ov: Math.round(H * 0.06), br: Math.round(H * 0.04), beak: Math.round(H * 0.16) }),
|
|
29
|
+
cw: Math.round(H * 0.24) });
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A rectangle from its corners.
|
|
33
|
+
*
|
|
34
|
+
* @param {number} x0 Left.
|
|
35
|
+
* @param {number} y0 Top.
|
|
36
|
+
* @param {number} x1 Right.
|
|
37
|
+
* @param {number} y1 Bottom.
|
|
38
|
+
* @returns {import('../geometry/types.js').Polygon} The rectangle.
|
|
39
|
+
*/
|
|
40
|
+
const R = (x0, y0, x1, y1) => rect(x0, y0, x1 - x0, y1 - y0);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A condensed bowl on a stem whose left edge is x0, from y0 to y1, reaching right to xr: straight hairlines top and
|
|
44
|
+
* bottom, its right side t thick and straight, the right corners rounded by quarter ellipses (ry down). Includes the
|
|
45
|
+
* stem.
|
|
46
|
+
*
|
|
47
|
+
* @param {import('./serif.js').Face} m The face.
|
|
48
|
+
* @param {number} x0 The stem's left edge.
|
|
49
|
+
* @param {number} y0 The bowl's top.
|
|
50
|
+
* @param {number} y1 Its bottom.
|
|
51
|
+
* @param {number} xr Its right edge.
|
|
52
|
+
* @param {number} t Its right side's thickness.
|
|
53
|
+
* @param {number} ry The corners' radius down.
|
|
54
|
+
* @returns {import('../geometry/types.js').Shape} The bowl.
|
|
55
|
+
*/
|
|
56
|
+
export const cBowl = (m, x0, y0, y1, xr, t, ry) => {
|
|
57
|
+
ry = Math.min(ry, (y1 - y0) / 2); const rx = Math.min(xr - x0 - m.S - 2, (xr - x0 - m.S) * 0.9);
|
|
58
|
+
const rr = (a, b, c, d, rx, ry) => { const cx = c - rx; return U(R(a, b, cx + 0.5, d), I(U(ellipse(cx, b + ry, rx, ry, 0, 200), ellipse(cx, d - ry, rx, ry, 0, 200), R(cx, b + ry, c, d - ry)), R(cx, b - 1, c + 1, d + 1))); };
|
|
59
|
+
return D(rr(x0, y0, xr, y1, rx, ry), rr(x0 + m.S, y0 + m.h, xr - t, y1 - m.h, Math.max(4, rx - t), Math.max(4, ry - m.h)));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A beak serif: a hairline-wide drop of `len` at the end x of a bar along the cap line (dir −1: the bar runs left of x).
|
|
64
|
+
*
|
|
65
|
+
* @param {import('./serif.js').Face} m The face.
|
|
66
|
+
* @param {number} x The bar's end.
|
|
67
|
+
* @param {number} dir Which way the bar runs from x.
|
|
68
|
+
* @param {number} len The drop.
|
|
69
|
+
* @returns {import('../geometry/types.js').Shape} The beak.
|
|
70
|
+
*/
|
|
71
|
+
const beak = (m, x, dir, len) => dir > 0
|
|
72
|
+
? U(R(x - m.bw, 0, x, len), bracket([x - m.bw, m.h], [-1, 0], [0, 1], m.br * 0.8))
|
|
73
|
+
: U(R(x, 0, x + m.bw, len), bracket([x + m.bw, m.h], [1, 0], [0, 1], m.br * 0.8));
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A beak serif up from the baseline: a hairline-wide rise of `len` at the free end x of a foot bar running right.
|
|
77
|
+
*
|
|
78
|
+
* @param {import('./serif.js').Face} m The face.
|
|
79
|
+
* @param {number} x The bar's free end.
|
|
80
|
+
* @param {number} len The rise.
|
|
81
|
+
* @returns {import('../geometry/types.js').Shape} The beak.
|
|
82
|
+
*/
|
|
83
|
+
const beakUp = (m, x, len) => U(R(x - m.bw, m.H - len, x, m.H), bracket([x - m.bw, m.H - m.h], [-1, 0], [0, -1], m.br * 0.8));
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The pillar's top as a T's bar: from xl to xr along the cap line, beaks at the ends, bracketed under into the pillar.
|
|
87
|
+
*
|
|
88
|
+
* @param {import('./serif.js').Face} m The face.
|
|
89
|
+
* @param {number} xl The bar's left end.
|
|
90
|
+
* @param {number} xr Its right end.
|
|
91
|
+
* @param {{ bl?: number, brk?: number }} [o] Whether each end has its beak.
|
|
92
|
+
* @returns {import('../geometry/types.js').Shape} The bar.
|
|
93
|
+
*/
|
|
94
|
+
const tBar = (m, xl, xr, { bl = 1, brk = 1 } = {}) => U(R(xl, 0, xr, m.h), bl ? beak(m, xl, -1, m.beak) : [], brk ? beak(m, xr, 1, m.beak) : [],
|
|
95
|
+
bracket([0, m.h], [-1, 0], [0, 1], m.br), bracket([m.S, m.h], [1, 0], [0, 1], m.br));
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* An A with one upright leg (the pillar) and one thick diagonal: side −1 the diagonal runs down-left from the pillar's
|
|
99
|
+
* top-left corner (A leads), +1 down-right from its top-right (A follows).
|
|
100
|
+
*
|
|
101
|
+
* @param {import('./serif.js').Face} m The face.
|
|
102
|
+
* @param {number} side −1 or 1.
|
|
103
|
+
* @param {{ W?: number, t?: number, yb?: number }} [o] The A's width at the baseline past the pillar, the diagonal's
|
|
104
|
+
* thickness and its bar's height.
|
|
105
|
+
* @returns {{ s: import('../geometry/types.js').Shape, at: Record<string, number> }} The A and the pillar corners it joins.
|
|
106
|
+
*/
|
|
107
|
+
const aSide = (m, side, { W = m.H * 0.5, t = m.S * 0.92, yb = m.H * 0.64 } = {}) => {
|
|
108
|
+
// the diagonal's outer edge runs from the pillar's outer top corner to the foot; its inner edge t across
|
|
109
|
+
const xo = side < 0 ? 0 : m.S, xf = side < 0 ? -W : m.S + W, dy = m.H, w = (t * Math.hypot(xf - xo, dy)) / dy;
|
|
110
|
+
const d = side < 0 ? poly([[xo, 0], [xo + w, 0], [xf + w, m.H], [xf, m.H]]) : poly([[xo - w, 0], [xo, 0], [xf, m.H], [xf - w, m.H]]);
|
|
111
|
+
const dk = I(d, side < 0 ? R(-W - w - 10, 0, m.S, m.H) : R(0, 0, m.S + W + w + 10, m.H));
|
|
112
|
+
const xIn = (y) => (side < 0 ? xo + w + ((xf - xo) * y) / dy : xo - w + ((xf - xo) * y) / dy); // the diagonal's inner edge at y
|
|
113
|
+
const bar = side < 0 ? R(xIn(yb + m.h) - 1, yb, 0.5, yb + m.h) : R(m.S - 0.5, yb, xIn(yb + m.h) + 1, yb + m.h);
|
|
114
|
+
const dir = unit([xo - xf, -dy]);
|
|
115
|
+
const ft = side < 0 ? foot(m, xf, xf + w, 'base', { da: dir, db: dir }) : foot(m, xf - w, xf, 'base', { da: dir, db: dir });
|
|
116
|
+
return { s: U(dk, bar, ft), at: side < 0 ? { tl: 1 } : { tr: 1 } };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The leads, drawn left of the pillar: `(m, o) => { s, at }`, with `at` the pillar's corners it joins (tl, bl), which get
|
|
121
|
+
* no serif. H takes `o.hBar`, its crossbar's height as a fraction of the cap (0.47).
|
|
122
|
+
*/
|
|
123
|
+
export const PILLAR_LEFT = {
|
|
124
|
+
// J: the pillar runs on into a hook curling left along the baseline, ending in a ball
|
|
125
|
+
J(m) {
|
|
126
|
+
const rx = (m.S + m.cw) / 2 + 20, ry = m.H * 0.22, cx = m.S - rx, cy = m.H - ry, r = ringArc(cx, cy, rx, ry, m.S, m.h, 0, 160), e = r.end(160);
|
|
127
|
+
return { s: U(r.s, ball(e.o, [cx, cy - ry * 0.4], m.S * 0.5)), at: { bl: 1 } };
|
|
128
|
+
},
|
|
129
|
+
// M: a hairline left stem, a thick diagonal down to the vertex, a hairline diagonal up into the pillar's top
|
|
130
|
+
M(m) {
|
|
131
|
+
const W = m.H * 0.62, x0 = -W, vx = -W * 0.3, wd = m.S * 1.0;
|
|
132
|
+
const dl = slant(x0, vx - (wd * Math.hypot(vx - x0, m.H)) / m.H, wd, 0, m.H), dr = slant(-m.h * 0.4, vx - m.h * 1.1, m.h, 0, m.H);
|
|
133
|
+
return { s: U(R(x0, 0, x0 + m.h, m.H), foot(m, x0, x0 + m.h, 'top', { r: 0 }), foot(m, x0, x0 + m.h, 'base'), I(U(dl.s, dr.s), R(x0 - 10, 0, 0.5, m.H))), at: { tl: 1 } };
|
|
134
|
+
},
|
|
135
|
+
// N: a hairline left stem and a thick diagonal from its top down into the pillar's foot
|
|
136
|
+
N(m) {
|
|
137
|
+
const W = m.H * 0.46, x0 = -W, wd = m.S * 1.0; let xb = 0;
|
|
138
|
+
for (let i = 0; i < 4; i++) { const w = (wd * Math.hypot(xb - x0, m.H)) / m.H; xb = m.S - w; }
|
|
139
|
+
const d = slant(x0, xb, wd, 0, m.H);
|
|
140
|
+
return { s: U(R(x0, 0, x0 + m.h, m.H), foot(m, x0, x0 + m.h, 'top', { r: 0 }), foot(m, x0, x0 + m.h, 'base'), I(d.s, R(x0 - 10, 0, m.S, m.H))), at: { bl: 1 } };
|
|
141
|
+
},
|
|
142
|
+
// D: a thick left stem and hairlines top and bottom into the pillar, the counter's corners rounded on the pillar's side
|
|
143
|
+
D(m) {
|
|
144
|
+
const x0 = -(m.S + m.cw), ry = m.H * 0.2, rx = m.cw * 0.7;
|
|
145
|
+
const inner = U(R(x0 + m.S, m.h, -rx, m.H - m.h), I(U(ellipse(-rx, m.h + ry, rx, ry, 0, 200), ellipse(-rx, m.H - m.h - ry, rx, ry, 0, 200), R(-rx, m.h + ry, 0, m.H - m.h - ry)), R(-rx, 0, 0, m.H)));
|
|
146
|
+
return { s: U(D(R(x0, 0, 0.5, m.H), inner), foot(m, x0, x0 + m.S, 'top', { r: 0 }), foot(m, x0, x0 + m.S, 'base', { r: 0 })), at: { tl: 1, bl: 1 } };
|
|
147
|
+
},
|
|
148
|
+
// A: the pillar is its upright right leg, the thick diagonal runs down-left from the pillar's top
|
|
149
|
+
A(m) { return aSide(m, -1); },
|
|
150
|
+
// H: a thick left stem and a hairline crossbar at `o.hBar` of the cap (0.47) into the pillar, its right stem
|
|
151
|
+
H(m, o = {}) {
|
|
152
|
+
const x0 = -(m.S + m.cw), yc = m.H * (o.hBar ?? 0.47) - m.h / 2;
|
|
153
|
+
return { s: U(R(x0, 0, x0 + m.S, m.H), foot(m, x0, x0 + m.S, 'top'), foot(m, x0, x0 + m.S, 'base'), R(x0 + m.S - 1, yc, 1, yc + m.h)), at: {} };
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The follows, drawn right of the pillar: `(m, t) => { s, at, w? }`, with `at` the pillar's corners it joins (tr, br),
|
|
159
|
+
* `t` true under a T lead's bar, and `w` its far edge.
|
|
160
|
+
*/
|
|
161
|
+
export const PILLAR_RIGHT = {
|
|
162
|
+
D(m) { return { s: cBowl(m, 0, 0, m.H, m.S + m.cw + m.S * 1.02, m.S * 1.02, m.H * 0.24), at: { tr: 1, br: 1 } }; },
|
|
163
|
+
P(m) { return { s: cBowl(m, 0, 0, m.H * 0.56, m.S + m.cw * 0.95 + m.S * 0.96, m.S * 0.96, m.H * 0.16), at: { tr: 1 } }; },
|
|
164
|
+
B(m) {
|
|
165
|
+
const yu = m.H * 0.47 + m.h / 2;
|
|
166
|
+
return { s: U(cBowl(m, 0, 0, yu, m.S + m.cw * 0.8 + m.S * 0.9, m.S * 0.9, m.H * 0.13), cBowl(m, 0, yu - m.h, m.H, m.S + m.cw * 0.95 + m.S * 0.98, m.S * 0.98, m.H * 0.15)), at: { tr: 1, br: 1 } };
|
|
167
|
+
},
|
|
168
|
+
// H: the crossbar from the pillar to a right stem. `t`: the T's bar runs on over the H and its right stem rises into it.
|
|
169
|
+
H(m, t) {
|
|
170
|
+
const b = m.S + m.cw, yc = m.H * 0.47 - m.h / 2;
|
|
171
|
+
return { s: U(R(b, 0, b + m.S, m.H), t ? [] : foot(m, b, b + m.S, 'top'), foot(m, b, b + m.S, 'base'), R(m.S - 1, yc, b + 1, yc + m.h)), at: {}, w: b + m.S };
|
|
172
|
+
},
|
|
173
|
+
F(m) {
|
|
174
|
+
const W = m.S + m.cw + m.H * 0.06, ym = m.H * 0.47 - m.h / 2, xm = W - m.H * 0.07, bk = (x, len) => U(R(x - m.bw, 0, x, len), bracket([x - m.bw, m.h], [-1, 0], [0, 1], m.br * 0.8));
|
|
175
|
+
return { s: U(R(0, 0, W, m.h), bk(W, m.beak), R(0, ym, xm, ym + m.h), R(xm - m.bw, ym - m.beak * 0.3, xm, ym + m.h + m.beak * 0.3),
|
|
176
|
+
bracket([xm - m.bw, ym], [-1, 0], [0, -1], m.br * 0.6), bracket([xm - m.bw, ym + m.h], [-1, 0], [0, 1], m.br * 0.6)), at: { tr: 1 } };
|
|
177
|
+
},
|
|
178
|
+
// R: P's bowl, a little shorter, and a thick leg slanting from under it to the baseline
|
|
179
|
+
R(m) {
|
|
180
|
+
const yr = m.H * 0.53, xr = m.S + m.cw * 0.95 + m.S * 0.94, W = xr + m.H * 0.05;
|
|
181
|
+
const bowl = cBowl(m, 0, 0, yr, xr, m.S * 0.94, m.H * 0.15), leg = slant(m.S + m.cw * 0.25, W - m.S * 1.05, m.S, yr - m.h, m.H);
|
|
182
|
+
return { s: U(bowl, I(leg.s, R(0, yr - m.h, W + 50, m.H)), foot(m, leg.at(m.H)[0], leg.at(m.H)[1], 'base', { da: leg.up, db: leg.up })), at: { tr: 1 } };
|
|
183
|
+
},
|
|
184
|
+
// N: a thick diagonal from the pillar's top down to a hairline right stem's foot; under a T lead's bar its right stem
|
|
185
|
+
// rises into the bar, no top serif
|
|
186
|
+
N(m, t) {
|
|
187
|
+
const W = m.H * 0.46, rx = m.S + W - m.h, wd = m.S * 1.0;
|
|
188
|
+
const d = slant(0, rx + m.h - (wd * Math.hypot(rx - 0, m.H)) / m.H, wd, 0, m.H);
|
|
189
|
+
return { s: U(I(d.s, R(0, 0, rx + m.h + 10, m.H)), R(rx, 0, rx + m.h, m.H), t ? [] : foot(m, rx, rx + m.h, 'top')), at: { tr: 1 }, w: rx + m.h };
|
|
190
|
+
},
|
|
191
|
+
// K: a hairline arm from the pillar up to the right and a thick leg from the arm down to the baseline (the serif K, condensed)
|
|
192
|
+
K(m, t) {
|
|
193
|
+
const W = m.S + m.H * 0.5, sx = m.S, yTop = t ? m.h - 1 : 0;
|
|
194
|
+
const arm = slant(W - m.ov - m.h * 1.3, sx - m.h * 1.2, m.h, yTop, m.H * 0.7), armS = I(arm.s, R(sx - 0.5, -1, W + 50, m.H));
|
|
195
|
+
const y0 = m.H * 0.4, P = [(arm.at(y0)[0] + arm.at(y0)[1]) / 2, y0], B = [W - m.S * 0.95, m.H], u = unit([B[0] - P[0], B[1] - P[1]]);
|
|
196
|
+
const ad = arm.up, cr = Math.abs(ad[0] * u[1] - ad[1] * u[0]), Q = [P[0] + (ad[0] * m.S) / cr, P[1] + (ad[1] * m.S) / cr], wb = m.S / u[1];
|
|
197
|
+
const leg = I(poly([P, Q, [B[0] + wb, m.H], B]), R(sx - 0.5, 0, W + 80, m.H)), lu = unit([P[0] - B[0], P[1] - B[1]]);
|
|
198
|
+
const top = t ? [] : foot(m, arm.at(0)[0], arm.at(0)[1], 'top', { da: arm.down, db: arm.down, brk: 0 });
|
|
199
|
+
return { s: U(armS, leg, top, foot(m, B[0], B[0] + wb, 'base', { da: lu, db: lu })), at: { tr: 1 }, w: arm.at(yTop)[1] };
|
|
200
|
+
},
|
|
201
|
+
// A: the pillar is its upright left leg, the thick diagonal runs down-right from the pillar's top
|
|
202
|
+
A(m) { return aSide(m, 1); },
|
|
203
|
+
// E: the pillar is its stem; a top bar with a beak down, a shorter middle arm with a serif, a longer foot bar with a beak up
|
|
204
|
+
E(m) {
|
|
205
|
+
const W = m.S + m.cw + m.H * 0.06, ym = m.H * 0.47 - m.h / 2, xm = W - m.H * 0.07, Wb = W + m.H * 0.03;
|
|
206
|
+
return { s: U(R(0, 0, W, m.h), beak(m, W, 1, m.beak), R(0, ym, xm, ym + m.h), R(xm - m.bw, ym - m.beak * 0.3, xm, ym + m.h + m.beak * 0.3),
|
|
207
|
+
bracket([xm - m.bw, ym], [-1, 0], [0, -1], m.br * 0.6), bracket([xm - m.bw, ym + m.h], [-1, 0], [0, 1], m.br * 0.6),
|
|
208
|
+
R(0, m.H - m.h, Wb, m.H), beakUp(m, Wb, m.beak)), at: { tr: 1, br: 1 }, w: W };
|
|
209
|
+
},
|
|
210
|
+
// L: the pillar is its stem; a foot bar with a beak up
|
|
211
|
+
L(m) { const Wb = m.S + m.cw + m.H * 0.09; return { s: U(R(0, m.H - m.h, Wb, m.H), beakUp(m, Wb, m.beak)), at: { br: 1 } }; },
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The pair a + b on one pillar, at cap H with hairline h. A T lead is the pillar's own top: a bar both ways, its left arm
|
|
216
|
+
* `o.tLeft` long (as far as the right arm by default), its right arm out to the second letter's far edge plus `o.tOver`.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} a The lead (a PILLAR_LEFT key, or 'T').
|
|
219
|
+
* @param {string} b The follow (a PILLAR_RIGHT key).
|
|
220
|
+
* @param {number} H The cap height.
|
|
221
|
+
* @param {number} h The hairline.
|
|
222
|
+
* @param {{ tOver?: number, tLeft?: number, tBeak?: number, hBar?: number }} [o] A T lead's bar, and an H lead's crossbar.
|
|
223
|
+
* @returns {{ s: import('../geometry/types.js').Shape, m: import('./serif.js').Face & { cw: number } }} The pair and its face.
|
|
224
|
+
*/
|
|
225
|
+
export const pillarPair = (a, b, H, h, o = {}) => {
|
|
226
|
+
const m = pface(H, h);
|
|
227
|
+
if (a === 'T') {
|
|
228
|
+
const Rt = PILLAR_RIGHT[b](m, true), xr = Rt.w + (o.tOver ?? 0), xl = -(o.tLeft ?? (xr - m.S));
|
|
229
|
+
const s = U(R(0, 0, m.S, m.H), Rt.s, tBar(m, xl, xr, { brk: o.tBeak ?? 1 }), foot(m, 0, m.S, 'base', { l: 1, r: Rt.at.br ? 0 : 1 }));
|
|
230
|
+
return { s, m };
|
|
231
|
+
}
|
|
232
|
+
const L = PILLAR_LEFT[a](m, o), Rt = PILLAR_RIGHT[b](m), at = { ...L.at, ...Rt.at };
|
|
233
|
+
const s = U(R(0, 0, m.S, m.H), L.s, Rt.s, foot(m, 0, m.S, 'top', { l: at.tl ? 0 : 1, r: at.tr ? 0 : 1 }), foot(m, 0, m.S, 'base', { l: at.bl ? 0 : 1, r: at.br ? 0 : 1 }));
|
|
234
|
+
return { s, m };
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The pillar mark: the pair over a short bar (`bar` of the pair's width, `bh` of the stem thick) a gap below; `disc`:
|
|
239
|
+
* knocked out of a disc whose edge stays `pad` gaps clear of the ink. The hairline is redrawn so it fits to 31.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} a The lead.
|
|
242
|
+
* @param {string} b The follow.
|
|
243
|
+
* @param {{ bar?: number, bh?: number, disc?: boolean, pad?: number, tOver?: number, tLeft?: number, tBeak?: number, hBar?: number }} [o]
|
|
244
|
+
* The bar (0.5 wide, 0.6 thick), the disc (none; 2 gaps clear), and pillarPair()'s options.
|
|
245
|
+
* @returns {(g: number) => import('../geometry/types.js').Shape} The build, for exact().
|
|
246
|
+
*/
|
|
247
|
+
export const pillar = (a, b, { bar = 0.5, bh = 0.6, disc = false, pad = 2, ...o } = {}) => (g) => {
|
|
248
|
+
const draw = (h) => {
|
|
249
|
+
const H = 380, { s, m } = pillarPair(a, b, H, h, o), [x0, , x1] = bbox(s), xc = (x0 + x1) / 2, bw = (x1 - x0) * bar;
|
|
250
|
+
let mk = U(s, R(xc - bw / 2, H + g, xc + bw / 2, H + g + m.S * bh));
|
|
251
|
+
const [X0, Y0, X1, Y1] = bbox(mk); mk = move(mk, 256 - (X0 + X1) / 2, 256 - (Y0 + Y1) / 2);
|
|
252
|
+
if (!disc) return mk;
|
|
253
|
+
const rmax = Math.max(...mk.flatMap((pg) => pg[0].map(([x, y]) => Math.hypot(x - 256, y - 256))));
|
|
254
|
+
return D(circle(256, 256, rmax + g * pad, 360), mk);
|
|
255
|
+
};
|
|
256
|
+
const [x0, y0, x1, y1] = bbox(draw(32)); return despeckShape(draw(Math.ceil((31 * Math.max(x1 - x0, y1 - y0)) / 448)), 200, 30);
|
|
257
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Geom, Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** A serif face's metrics: cap H, stem S, hairline h, serif reach ov, bracket br, beak length and beak width bw. */
|
|
4
|
+
export interface Face {
|
|
5
|
+
H: number;
|
|
6
|
+
S: number;
|
|
7
|
+
h: number;
|
|
8
|
+
ov: number;
|
|
9
|
+
br: number;
|
|
10
|
+
beak: number;
|
|
11
|
+
bw: number;
|
|
12
|
+
[key: string]: number;
|
|
13
|
+
}
|
|
14
|
+
/** A glyph: its ink (from x 0) and its width. */
|
|
15
|
+
export interface Glyph {
|
|
16
|
+
s: Shape;
|
|
17
|
+
w: number;
|
|
18
|
+
}
|
|
19
|
+
/** A glyph from a source: its ink, width and face (a grown font glyph's m carries t and dy too). */
|
|
20
|
+
export interface SourceGlyph extends Glyph {
|
|
21
|
+
m: Face & { t?: number; dy?: number };
|
|
22
|
+
}
|
|
23
|
+
/** A glyph source: the capital at cap H, given face options and the gap g. */
|
|
24
|
+
export type GlyphSource = (H: number, o?: { g?: number; S?: number; h?: number; [key: string]: number | undefined }) => SourceGlyph;
|
|
25
|
+
/** A straight stroke: its shape, its sides at a height, its width along a flat end and its directions. */
|
|
26
|
+
export interface Slant {
|
|
27
|
+
s: Polygon;
|
|
28
|
+
at: (y: number) => [number, number];
|
|
29
|
+
w: number;
|
|
30
|
+
up: number[];
|
|
31
|
+
down: number[];
|
|
32
|
+
}
|
|
33
|
+
/** A stressed ring and the points of its cut at an angle. */
|
|
34
|
+
export interface RingArc {
|
|
35
|
+
s: Shape;
|
|
36
|
+
end: (a: number) => { o: number[]; i: number[] };
|
|
37
|
+
}
|
|
38
|
+
/** A crossing of two shapes and its centre. */
|
|
39
|
+
export interface Crossing {
|
|
40
|
+
pg: Polygon;
|
|
41
|
+
c: number[];
|
|
42
|
+
}
|
|
43
|
+
/** Which shape is in front at each crossing. */
|
|
44
|
+
export type Order = string | { under?: number[][] };
|
|
45
|
+
/** A move's build, for exact(). */
|
|
46
|
+
export type Build = (g: number) => Shape;
|
|
47
|
+
/** Where a move cuts or places, from the face, the width, the gap and the glyph. */
|
|
48
|
+
export type Placer<T> = (m: Face, w: number, g: number, L: SourceGlyph) => T;
|
|
49
|
+
|
|
50
|
+
/** The metrics of a serif face with cap height H. */
|
|
51
|
+
export function face(H?: number, o?: Partial<Face>): Face;
|
|
52
|
+
/** The concave fillet of radius r in the corner K between two edges along d1 and d2. */
|
|
53
|
+
export function bracket(K: readonly number[], d1: readonly number[], d2: readonly number[], r: number): Shape;
|
|
54
|
+
/** A hairline serif slab on a stroke's end [a, b] at the cap line or the baseline. */
|
|
55
|
+
export function foot(m: Face, a: number, b: number, at: 'top' | 'base', o?: { l?: number; r?: number; da?: readonly number[]; db?: readonly number[]; brk?: number }): Shape;
|
|
56
|
+
/** A straight stroke with flat ends, t thick across, its left side from (xt, y0) to (xb, y1). */
|
|
57
|
+
export function slant(xt: number, xb: number, t: number, y0: number, y1: number): Slant;
|
|
58
|
+
/** A D-bowl on a stem from y0 to y1 reaching right to xr, its side t thick. */
|
|
59
|
+
export function dBowl(m: Face, x0: number, y0: number, y1: number, xr: number, t: number, flat: number): Shape;
|
|
60
|
+
/** A stressed ring kept from a0 to a1 degrees, cut along rays. */
|
|
61
|
+
export function ringArc(cx: number, cy: number, rx: number, ry: number, t: number, h: number, a0?: number, a1?: number): RingArc;
|
|
62
|
+
/** A ball terminal of radius rb hung inside a curve at o, toward its centre c. */
|
|
63
|
+
export function ball(o: readonly number[], c: readonly number[], rb: number): Polygon;
|
|
64
|
+
/** The serif capitals, each `(m) => { s, w }`. */
|
|
65
|
+
export const SERIF_GLYPHS: Record<string, (m: Face) => Glyph>;
|
|
66
|
+
/** The serif capital k in face m, its ink from x and its cap line at y. */
|
|
67
|
+
export function serifLetter(k: string, m: Face, x?: number, y?: number): Glyph;
|
|
68
|
+
/** The serif capital k in face m, centred on (cx, cy). */
|
|
69
|
+
export function serifLetterAt(k: string, m: Face, cx: number, cy: number): Shape;
|
|
70
|
+
/** Where two shapes cross, larger than min, sorted by key. */
|
|
71
|
+
export function crossings(a: Geom, b: Geom, min?: number, key?: (c: number[]) => number): Crossing[];
|
|
72
|
+
/** Two shapes laid over each other, at each crossing one in front and the other cut back by the gap. */
|
|
73
|
+
export function interlace(a: Geom, b: Geom, order: Order, g?: number, o?: { reach?: number; key?: (c: number[]) => number; min?: number }): { s: Shape; n: number; xs: Crossing[] };
|
|
74
|
+
/** A tucked serif pair, its hairline fitted to 31. */
|
|
75
|
+
export function pairOf(a: string, b: string, o?: { H?: number; kb?: number; fx?: number; fy?: number; order?: Order }): Build;
|
|
76
|
+
/** A stressed oval frame. */
|
|
77
|
+
export function ovalFrame(cx: number, cy: number, rx: number, ry: number, t: number, h: number): Shape;
|
|
78
|
+
/** A rectangle with its corners scooped out by quarter circles. */
|
|
79
|
+
export function cartouche(cx: number, cy: number, w: number, h: number, r: number): Shape;
|
|
80
|
+
/** A cartouche's outline, t thick. */
|
|
81
|
+
export function cartoucheRule(cx: number, cy: number, w: number, h: number, r: number, t: number): Shape;
|
|
82
|
+
/** A hairline rule, its ends tapered to a point over `taper`. */
|
|
83
|
+
export function rule(x0: number, x1: number, y: number, h: number, taper?: number): Polygon;
|
|
84
|
+
/** A rectangle with its corners chamfered c. */
|
|
85
|
+
export function octagon(cx: number, cy: number, w: number, h: number, c: number): Polygon;
|
|
86
|
+
/** An octagonal plaque's outline, t thick. */
|
|
87
|
+
export function octagonRule(cx: number, cy: number, w: number, h: number, c: number, t: number): Shape;
|
|
88
|
+
/** This face's capital k as a glyph source. */
|
|
89
|
+
export function drawn(k: string): GlyphSource;
|
|
90
|
+
/** A five-point star of radius R, its inner radius R·k. */
|
|
91
|
+
export function star5(cx: number, cy: number, R: number, k?: number): Polygon;
|
|
92
|
+
/** The capital alone, centred. */
|
|
93
|
+
export function plain(src: GlyphSource, H?: number): Build;
|
|
94
|
+
/** A capital a gap above a tapered hairline rule. */
|
|
95
|
+
export function ruled(src: GlyphSource, H: number, over?: number): Build;
|
|
96
|
+
/** A capital cut once across by the gap. */
|
|
97
|
+
export function split(src: GlyphSource, H: number, cut: Placer<Geom>): Build;
|
|
98
|
+
/** A capital with a ball or a star placed by `at`. */
|
|
99
|
+
export function dotted(src: GlyphSource, H: number, at: Placer<number[]>, kind?: 'dot' | 'star'): Build;
|
|
100
|
+
/** A capital on one hairline plinth. */
|
|
101
|
+
export function plinth(src: GlyphSource, H: number, over?: number): Build;
|
|
102
|
+
/** A capital in a frame. */
|
|
103
|
+
export function framed(src: GlyphSource, H: number, frame: (g: number) => Geom, o?: { dx?: number; dy?: number }): Build;
|
|
104
|
+
/** A capital in a double frame. */
|
|
105
|
+
export function crest(src: GlyphSource, H: number, outer: (g: number) => Geom, inner: (g: number) => Geom, o?: { dx?: number; dy?: number }): Build;
|
|
106
|
+
/** A capital knocked out of a plaque with a hairline inner rule. */
|
|
107
|
+
export function plaque(src: GlyphSource, H: number, shape: () => Geom, ruleFn: (g: number) => Geom): Build;
|
|
108
|
+
/** The fat-face inline: a g-wide channel down every thick stroke. */
|
|
109
|
+
export function inline(src: GlyphSource, H?: number): Build;
|