@logomarks/core 0.1.9 → 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 +49 -0
- package/README.md +10 -1
- package/package.json +59 -4
- package/src/batch/args.d.ts +18 -0
- package/src/batch/args.js +44 -0
- package/src/batch/index.d.ts +7 -0
- package/src/batch/index.js +10 -0
- package/src/batch/run.d.ts +53 -0
- package/src/batch/run.js +92 -0
- package/src/batch/slug.d.ts +2 -0
- package/src/batch/slug.js +12 -0
- package/src/batch/source.d.ts +12 -0
- package/src/batch/source.js +23 -0
- package/src/fit/index.d.ts +4 -0
- package/src/fit/index.js +29 -5
- 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/geometry/index.d.ts +1 -0
- package/src/geometry/index.js +1 -0
- package/src/geometry/mitre.d.ts +8 -0
- package/src/geometry/mitre.js +64 -0
- package/src/geometry/modular.d.ts +1 -1
- 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/parts/index.d.ts +2 -0
- package/src/parts/index.js +2 -0
- package/src/parts/pixels.d.ts +11 -0
- package/src/parts/pixels.js +74 -0
- package/src/parts/run.js +4 -2
- package/src/parts/span.d.ts +13 -0
- package/src/parts/span.js +100 -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/fields.js +135 -0
- package/src/sdf/gap.js +42 -0
- package/src/sdf/index.d.ts +58 -0
- package/src/sdf/index.js +14 -0
- package/src/sdf/ops.js +102 -0
- package/src/sdf/trace.js +90 -0
- package/src/sdf/y2k.d.ts +12 -0
- package/src/sdf/y2k.js +80 -0
- package/src/skill/bundle.js +1 -1
- package/src/util/vec.d.ts +14 -0
- package/src/util/vec.js +61 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stair letters: a letter on a coarse pixel grid (4–5 cells across, one cell per stroke), its diagonals stepping cell to
|
|
3
|
+
* cell corner to corner. Each cell's outer corners round a little; wherever two cells meet only at a corner, a straight
|
|
4
|
+
* neck bridges them along the diagonal, so the steps pinch but read as one stroke at 32 px.
|
|
5
|
+
* From the Logomarks tools (stairparts.mjs, batches 311 and 319).
|
|
6
|
+
*/
|
|
7
|
+
import { U, poly, pixels, soften } from '../geometry/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The grid: P the cell (fitted, 90 for five rows to 112 for four); NECK the neck's width across the diagonal (fitted
|
|
11
|
+
* 28–34); EDGE each cell's outer corner radius; FIL the neck roots' fillet. The neck must stay wider than 2·EDGE or the
|
|
12
|
+
* rounding pinches it off; a closing to round the corners instead swells every neck to most of a cell (a blobby pixel face).
|
|
13
|
+
*/
|
|
14
|
+
export const STAIR = { P: 110, NECK: 34, EDGE: 14, FIL: 10 };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A bar NECK wide along each corner-only contact's diagonal, 2.4 NECK long, so its ends sit inside the two cells.
|
|
18
|
+
*
|
|
19
|
+
* @param {readonly string[]} rows The grid, '#' a cell.
|
|
20
|
+
* @param {{ P?: number, NECK?: number }} [o] The cell and neck (STAIR's by default).
|
|
21
|
+
* @returns {import('../geometry/types.js').Polygon[]} One bar per corner contact.
|
|
22
|
+
*/
|
|
23
|
+
export const necks = (rows, { P = STAIR.P, NECK = STAIR.NECK } = {}) => {
|
|
24
|
+
const out = [], at = (r, c) => rows[r] && rows[r][c] === '#';
|
|
25
|
+
for (let r = 0; r + 1 < rows.length; r++) for (let c = 0; c < rows[r].length; c++) for (const d of [-1, 1]) {
|
|
26
|
+
if (!at(r, c) || !at(r + 1, c + d) || at(r, c + d) || at(r + 1, c)) continue;
|
|
27
|
+
const x = (c + (d > 0 ? 1 : 0)) * P, y = (r + 1) * P, ux = d / Math.SQRT2, uy = 1 / Math.SQRT2, L = NECK * 1.2, h = NECK / 2;
|
|
28
|
+
out.push(poly([[x - ux * L - uy * h, y - uy * L + ux * h], [x + ux * L - uy * h, y + uy * L + ux * h], [x + ux * L + uy * h, y + uy * L - ux * h], [x - ux * L + uy * h, y - uy * L - ux * h]]));
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A stair glyph: the rows' cells ('#') as one solid, corners rounded, corner contacts necked.
|
|
35
|
+
*
|
|
36
|
+
* @param {readonly string[]} rows The grid, '#' a cell.
|
|
37
|
+
* @param {{ P?: number, NECK?: number, EDGE?: number, FIL?: number }} [o] The grid (STAIR's by default).
|
|
38
|
+
* @returns {() => import('../geometry/types.js').Shape} The build (it takes no gap: exact() only measures it).
|
|
39
|
+
*/
|
|
40
|
+
export const stair = (rows, o = {}) => () => {
|
|
41
|
+
const { P, EDGE, FIL } = { ...STAIR, ...o };
|
|
42
|
+
return soften(U(soften(pixels(rows, P), 0, EDGE), ...necks(rows, { ...STAIR, ...o })), FIL, 0);
|
|
43
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Shape } from '../geometry/types.js';
|
|
2
|
+
import type { Face } from './serif.js';
|
|
3
|
+
|
|
4
|
+
/** The stem face: a serif face whose arms reach W past the stem. */
|
|
5
|
+
export type StemFace = Face & { W: number };
|
|
6
|
+
/** A part drawn on the stem: its ink and the stem corners it takes. */
|
|
7
|
+
export interface StemPart {
|
|
8
|
+
s: Shape;
|
|
9
|
+
at: Record<string, number | boolean>;
|
|
10
|
+
}
|
|
11
|
+
/** One letter's part of a stem ligature. */
|
|
12
|
+
export interface StemPlacement {
|
|
13
|
+
k: string;
|
|
14
|
+
side?: string;
|
|
15
|
+
ya: number;
|
|
16
|
+
yb?: number;
|
|
17
|
+
o?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The stem face for hairline h. */
|
|
21
|
+
export function sface(h: number, o?: { S?: number; W?: number }): StemFace;
|
|
22
|
+
/** Right-side letters on a band of the stem. */
|
|
23
|
+
export const STEM_RIGHT: Record<string, (m: StemFace, ya: number, yb: number, o?: Record<string, unknown>) => StemPart>;
|
|
24
|
+
/** Lowercase on the band from the x-height to the foot. */
|
|
25
|
+
export const STEM_LOWER: Record<string, (m: StemFace, xh: number, o?: Record<string, unknown>) => StemPart>;
|
|
26
|
+
/** Left-side parts on a band of the stem. */
|
|
27
|
+
export const STEM_LEFT: Record<string, (m: StemFace, ya: number, yb: number, o?: Record<string, unknown>) => StemPart>;
|
|
28
|
+
/** A stem ligature, its hairline fitted to 31. */
|
|
29
|
+
export function stack(parts: readonly StemPlacement[], o?: { S?: number; W?: number }): (g: number) => Shape;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stem monograms: two letters stacked on ONE thick Didone stem, the upper letter's arms or bowls on the stem's top part,
|
|
3
|
+
* the lower letter's on its foot; every arm and bowl off ONE side of the stem (or a hook off the other side, at the other
|
|
4
|
+
* end), each arm with ONE free end, and that end alone flared into a sharp wedge (never the double-barred cross). Drawn on
|
|
5
|
+
* the serif face's parts (foot serifs and brackets, slants, stressed rings, D bowls, balls). From the Logomarks tools
|
|
6
|
+
* (stemparts.mjs, batches 309 and 329).
|
|
7
|
+
*/
|
|
8
|
+
import { U, I, rect, poly, move, bbox } from '../geometry/index.js';
|
|
9
|
+
import { despeckShape } from '../parts/index.js';
|
|
10
|
+
import { foot, slant, ringArc, ball, bracket, dBowl } from './serif.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A rectangle from its corners.
|
|
14
|
+
*
|
|
15
|
+
* @param {number} x0 Left.
|
|
16
|
+
* @param {number} y0 Top.
|
|
17
|
+
* @param {number} x1 Right.
|
|
18
|
+
* @param {number} y1 Bottom.
|
|
19
|
+
* @returns {import('../geometry/types.js').Polygon} The rectangle.
|
|
20
|
+
*/
|
|
21
|
+
const R = (x0, y0, x1, y1) => rect(x0, y0, x1 - x0, y1 - y0);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A vector made unit length.
|
|
25
|
+
*
|
|
26
|
+
* @param {readonly number[]} v The vector.
|
|
27
|
+
* @returns {number[]} Its direction.
|
|
28
|
+
*/
|
|
29
|
+
const unit = ([x, y]) => { const l = Math.hypot(x, y) || 1; return [x / l, y / l]; };
|
|
30
|
+
|
|
31
|
+
/** The stem's height T: the stem is x 0…S, y 0…T. A part is drawn on a local band [ya, yb] of the stem. */
|
|
32
|
+
const T = 640;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The stem face for hairline h: stem S (0.15 T unless `o.S`), arms reaching W past the stem (0.46 T unless `o.W`).
|
|
36
|
+
*
|
|
37
|
+
* @param {number} h The hairline.
|
|
38
|
+
* @param {{ S?: number, W?: number }} [o] The stem and the arms' reach, as fractions of the stem's height.
|
|
39
|
+
* @returns {import('./stem.js').StemFace} The face.
|
|
40
|
+
*/
|
|
41
|
+
export const sface = (h, o = {}) => {
|
|
42
|
+
const S = Math.round(T * (o.S ?? 0.15));
|
|
43
|
+
return { H: T, S, h, ov: Math.round(T * 0.045), br: Math.round(T * 0.03), bw: Math.round(h * 1.25), W: Math.round(T * (o.W ?? 0.46)) };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The face m with its cap height c (for the serif face's foot(), which reads m.H and m.h).
|
|
48
|
+
*
|
|
49
|
+
* @param {import('./stem.js').StemFace} m The face.
|
|
50
|
+
* @param {number} c The band's height.
|
|
51
|
+
* @returns {import('./stem.js').StemFace} The face on the band.
|
|
52
|
+
*/
|
|
53
|
+
const band = (m, c) => ({ ...m, H: c });
|
|
54
|
+
|
|
55
|
+
/** The power of a wedge's concave sides. */
|
|
56
|
+
const PW = 2.4;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A hairline arm off the stem's right edge from x0 to xe, its top edge at y: the free end flares into a sharp wedge `up`
|
|
60
|
+
* above and `dn` below over the last `len`, its sides concave (a power curve tangent to the arm), its end square. The root
|
|
61
|
+
* gets a small bracket on each open side.
|
|
62
|
+
*
|
|
63
|
+
* @param {import('./stem.js').StemFace} m The face.
|
|
64
|
+
* @param {number} x0 The root.
|
|
65
|
+
* @param {number} xe The free end.
|
|
66
|
+
* @param {number} y The arm's top edge.
|
|
67
|
+
* @param {{ up?: number, dn?: number, len?: number, root?: number[] }} [o] The wedge and the root's brackets.
|
|
68
|
+
* @returns {import('../geometry/types.js').Shape} The arm.
|
|
69
|
+
*/
|
|
70
|
+
const arm = (m, x0, xe, y, { up = 0, dn = 0, len = (xe - x0) * 0.4, root = [1, 1] } = {}) => {
|
|
71
|
+
const n = 28, top = [], bot = [];
|
|
72
|
+
for (let i = 0; i <= n; i++) { const t = i / n, x = xe - len + len * t; top.push([x, y - up * t ** PW]); bot.push([x, y + m.h + dn * t ** PW]); }
|
|
73
|
+
const s = poly([[x0 - 1, y], ...top, ...bot.reverse(), [x0 - 1, y + m.h]]);
|
|
74
|
+
return U(s, root[0] ? bracket([x0, y], [1, 0], [0, -1], m.br * 0.7) : [], root[1] ? bracket([x0, y + m.h], [1, 0], [0, 1], m.br * 0.7) : []);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The same arm to the LEFT of the stem (x1 its root, xe < x1 its free end).
|
|
79
|
+
*
|
|
80
|
+
* @param {import('./stem.js').StemFace} m The face.
|
|
81
|
+
* @param {number} x1 The root.
|
|
82
|
+
* @param {number} xe The free end.
|
|
83
|
+
* @param {number} y The arm's top edge.
|
|
84
|
+
* @param {{ up?: number, dn?: number, len?: number, root?: number[] }} [o] As arm().
|
|
85
|
+
* @returns {import('../geometry/types.js').Shape} The arm.
|
|
86
|
+
*/
|
|
87
|
+
const armL = (m, x1, xe, y, o = {}) => { const a = arm(m, -x1, -xe, y, o); return a.map((pg) => pg.map((r) => r.map(([x, yy]) => [-x, yy]).reverse())); };
|
|
88
|
+
|
|
89
|
+
/** The wedge depth, in hairlines. */
|
|
90
|
+
const WD = 3;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Right-side letters on the band [ya, yb]: `(m, ya, yb, o) => { s, at }`, with `at` the stem's corners it takes (tr: the
|
|
94
|
+
* top-right serif, br: the foot's right serif) when the band reaches the stem's ends. F L P B R K N H.
|
|
95
|
+
*/
|
|
96
|
+
export const STEM_RIGHT = {
|
|
97
|
+
F(m, ya, yb, o = {}) {
|
|
98
|
+
const c = yb - ya, W = m.W * (o.k ?? 1), ym = ya + c * (o.mid ?? 0.46) - m.h / 2;
|
|
99
|
+
return { s: U(arm(m, m.S, m.S + W, ya, { dn: m.h * WD, root: [0, 1] }), arm(m, m.S, m.S + W * 0.84, ym, { up: m.h * WD * 0.62, dn: m.h * WD * 0.62 })), at: { tr: ya === 0 } };
|
|
100
|
+
},
|
|
101
|
+
L(m, ya, yb, o = {}) {
|
|
102
|
+
const W = m.W * (o.k ?? 1.04);
|
|
103
|
+
return { s: arm(m, m.S, m.S + W, yb - m.h, { up: m.h * WD, root: [1, 0] }), at: { br: yb === T } };
|
|
104
|
+
},
|
|
105
|
+
// P: a D bowl on the stem from the band's top, closing on the stem at `cl` of the band
|
|
106
|
+
P(m, ya, yb, o = {}) {
|
|
107
|
+
const c = yb - ya, y1 = ya + c * (o.cl ?? 0.56);
|
|
108
|
+
return { s: dBowl(m, 0, ya, y1, m.S + m.W * (o.k ?? 0.92), m.S * 0.96, c * 0.04), at: { tr: ya === 0 } };
|
|
109
|
+
},
|
|
110
|
+
B(m, ya, yb, o = {}) {
|
|
111
|
+
const c = yb - ya, yu = ya + c * 0.47 + m.h / 2;
|
|
112
|
+
return { s: U(dBowl(m, 0, ya, yu, m.S + m.W * 0.84, m.S * 0.9, c * 0.03), dBowl(m, 0, yu - m.h, yb, m.S + m.W * 0.98, m.S * 0.98, c * 0.04)), at: { tr: ya === 0, br: yb === T } };
|
|
113
|
+
},
|
|
114
|
+
// R: P's bowl and a thick leg slanting from under it to the band's baseline, with a foot serif
|
|
115
|
+
R(m, ya, yb, o = {}) {
|
|
116
|
+
const c = yb - ya, yr = ya + c * 0.53, xr = m.S + m.W * 0.86, Wl = m.S + m.W * 1.06, mb = band(m, c);
|
|
117
|
+
const bowl = dBowl(m, 0, ya, yr, xr, m.S * 0.94, c * 0.04), wedge = U(R(-10, yr - m.h, Wl + 50, yb), bowl);
|
|
118
|
+
const leg = slant(m.S + m.W * 0.1, Wl - m.S * 1.05, m.S, ya + (yr - ya) * 0.45, yb);
|
|
119
|
+
const lf = move(foot(mb, leg.at(yb)[0], leg.at(yb)[1], 'base', { da: leg.up, db: leg.up, brk: 0 }), 0, ya);
|
|
120
|
+
return { s: U(bowl, I(leg.s, wedge), lf), at: { tr: ya === 0 } };
|
|
121
|
+
},
|
|
122
|
+
// K: a hairline arm from the stem up to the band's cap line and a thick leg from the arm to the baseline
|
|
123
|
+
K(m, ya, yb, o = {}) {
|
|
124
|
+
const c = yb - ya, mb = band(m, c), Wk = m.S + m.W * (o.k ?? 1.0);
|
|
125
|
+
const sx = m.S, arm_ = slant(Wk - m.ov * 1.2 - m.h * 1.3, sx - m.h * 1.2, m.h, 0, c * 0.72), armS = I(arm_.s, R(sx - 1, -1, Wk + 50, c));
|
|
126
|
+
const y0 = c * 0.4, P = [(arm_.at(y0)[0] + arm_.at(y0)[1]) / 2, y0], B = [Wk - m.ov - m.S * 1.1, c], u = unit([B[0] - P[0], B[1] - P[1]]);
|
|
127
|
+
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];
|
|
128
|
+
const leg = poly([P, Q, [B[0] + wb, c], B]), lu = unit([P[0] - B[0], P[1] - B[1]]);
|
|
129
|
+
// the arm's free end, cut level with the band's cap line, flares out to the right into a sharp wedge (no slab serif)
|
|
130
|
+
const fl = m.h * WD * (o.fl ?? 0.38), ln = c * 0.13, n = 24, xt = arm_.at(0)[0];
|
|
131
|
+
const wedge = poly([[xt, 0], ...[...Array(n + 1)].map((_, i) => { const t = i / n, y = ln * t; return [arm_.at(y)[1] + fl * (1 - t) ** PW, y]; }), [arm_.at(ln)[0], ln]]);
|
|
132
|
+
const s = U(armS, wedge, leg, foot(mb, B[0], B[0] + wb, 'base', { da: lu, db: lu, brk: 0 }));
|
|
133
|
+
return { s: move(s, 0, ya), at: {} };
|
|
134
|
+
},
|
|
135
|
+
// N: a thick diagonal from the stem at the band's top down to a hairline right stem's foot; that stem's free top flares
|
|
136
|
+
// right into a wedge (as the K's arm)
|
|
137
|
+
N(m, ya, yb, o = {}) {
|
|
138
|
+
const c = yb - ya, W = m.S + m.W * (o.k ?? 0.82), rx = W - m.h, wd = m.S * (o.t ?? 0.95);
|
|
139
|
+
const d = slant(0, W - (wd * Math.hypot(rx, c)) / c, wd, 0, c), fl = m.h * WD * 0.38, ln = c * 0.16, n = 24;
|
|
140
|
+
const wedge = poly([[rx, 0], ...[...Array(n + 1)].map((_, i) => { const t = i / n; return [W + fl * (1 - t) ** PW, ln * t]; }), [rx, ln]]);
|
|
141
|
+
return { s: move(U(I(d.s, R(0, 0, W + 10, c)), R(rx, 0, W, c), wedge), 0, ya), at: {} };
|
|
142
|
+
},
|
|
143
|
+
// H: a hairline crossbar from the stem to a thick right stem with foot serifs top and base
|
|
144
|
+
H(m, ya, yb, o = {}) {
|
|
145
|
+
const c = yb - ya, mb = band(m, c), b = m.S + m.W * (o.k ?? 0.5), W = b + m.S * (o.t ?? 0.9), yc = c * (o.bar ?? 0.5) - m.h / 2;
|
|
146
|
+
return { s: move(U(R(b, 0, W, c), foot(mb, b, W, 'top'), foot(mb, b, W, 'base'), R(m.S - 1, yc, b + 1, yc + m.h)), 0, ya), at: {} };
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Lowercase: `(m, xh, o) => { s, at }` on the band from the x-height xh to the stem's foot. f k.
|
|
152
|
+
*/
|
|
153
|
+
export const STEM_LOWER = {
|
|
154
|
+
// f: the stem's top curls right over into a ball (a stressed ring's left side is the stem), and a hairline bar off ONE side
|
|
155
|
+
// of the stem at `xh`, its free end wedged (`right`: off the right side)
|
|
156
|
+
f(m, xh, o = {}) {
|
|
157
|
+
const rx = m.S * 0.5 + m.W * (o.k ?? 0.5), ry = T * 0.13, cx = rx, cy = ry, a1 = o.a1 ?? -38, r = ringArc(cx, cy, rx, ry, m.S, m.h, -180, a1), e = r.end(a1);
|
|
158
|
+
const bar = o.right ? arm(m, m.S, m.S + m.W * (o.bar ?? 0.42), xh, { up: m.h * WD * 0.5, dn: m.h * WD * 0.5 }) : armL(m, 0, -m.W * (o.bar ?? 0.42), xh, { up: m.h * WD * 0.5, dn: m.h * WD * 0.5 });
|
|
159
|
+
return { s: U(r.s, ball(e.o, [cx, cy], m.S * 0.52), R(0, cy, m.S, cy + 2), bar), at: { tl: 1, tr: 1 } };
|
|
160
|
+
},
|
|
161
|
+
// k: K's arm and leg on the band [xh, T]
|
|
162
|
+
k(m, xh, o = {}) { return STEM_RIGHT.K(m, xh, T, o); },
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Left-side parts on the band [ya, yb]: `(m, ya, yb, o) => { s, at }`. J (the stem's foot hooks left; `at.foot` is where
|
|
167
|
+
* the stem ends) and A (the stem is its upright right leg).
|
|
168
|
+
*/
|
|
169
|
+
export const STEM_LEFT = {
|
|
170
|
+
// J: the stem's foot runs on into a hook curling left along the baseline, ending in a ball
|
|
171
|
+
J(m, ya, yb) {
|
|
172
|
+
const c = yb - ya, rx = m.S * 0.5 + m.W * 0.5, ry = c * 0.34, cx = m.S - rx, cy = yb - ry, r = ringArc(cx, cy, rx, ry, m.S, m.h, 0, 160), e = r.end(160);
|
|
173
|
+
return { s: U(r.s, ball(e.o, [cx, cy - ry * 0.4], m.S * 0.5)), at: { bl: 1, br: 1, foot: cy } };
|
|
174
|
+
},
|
|
175
|
+
// A: the stem is the A's upright right leg; a thick diagonal leaves the stem's left edge at the band's top and runs down
|
|
176
|
+
// left to a foot serif, a hairline bar back to the stem at `bar` of the band
|
|
177
|
+
A(m, ya, yb, o = {}) {
|
|
178
|
+
const c = yb - ya, mb = band(m, c), Wa = m.W * (o.k ?? 0.9), t = m.S * (o.t ?? 0.66), w = (t * Math.hypot(Wa, c)) / c;
|
|
179
|
+
const d = I(poly([[0, 0], [w, 0], [-Wa + w, c], [-Wa, c]]), R(-Wa - 10, 0, m.S, c)), xIn = (y) => w - (Wa * y) / c, yb_ = c * (o.bar ?? 0.6);
|
|
180
|
+
const bar = R(xIn(yb_ + m.h) - 1, yb_, 0.5, yb_ + m.h), dir = unit([Wa, -c]);
|
|
181
|
+
// the slab runs on past the slanted stroke's inner side at its top (foot() measures the reach from the foot's corner)
|
|
182
|
+
const sl = (m.h * Wa) / c, slab = U(foot(mb, -Wa, -Wa + w, 'base', { da: dir, db: dir }), R(-Wa + w, c - m.h, -Wa + w + sl + m.ov * 0.8, c));
|
|
183
|
+
return { s: move(U(d, bar, slab), 0, ya), at: {} };
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* A stem ligature: the parts drawn on one stem, each `{ k, side, ya, yb, o }` (side 'r' STEM_RIGHT, 'L' STEM_LEFT, 'l'
|
|
189
|
+
* STEM_LOWER from ya; ya and yb as fractions of the stem). The stem's serifs go where no part takes the corner. The
|
|
190
|
+
* hairline is redrawn so it fits to 31.
|
|
191
|
+
*
|
|
192
|
+
* @param {readonly { k: string, side?: string, ya: number, yb?: number, o?: Record<string, unknown> }[]} parts The parts.
|
|
193
|
+
* @param {{ S?: number, W?: number }} [o] sface()'s stem and reach.
|
|
194
|
+
* @returns {(g: number) => import('../geometry/types.js').Shape} The build, for exact().
|
|
195
|
+
*/
|
|
196
|
+
export const stack = (parts, { S, W } = {}) => (g) => {
|
|
197
|
+
const draw = (h) => {
|
|
198
|
+
const m = sface(h, { S, W }); let at = {}, s = [];
|
|
199
|
+
for (const p of parts) { const r = p.side === 'l' ? STEM_LOWER[p.k](m, p.ya * T, p.o || {}) : (p.side === 'L' ? STEM_LEFT : STEM_RIGHT)[p.k](m, p.ya * T, p.yb * T, p.o || {}); s = U(s, r.s); at = { ...at, ...r.at }; }
|
|
200
|
+
const yEnd = at.foot ?? T, stem = R(0, 0, m.S, yEnd + 1);
|
|
201
|
+
const serifs = U(at.tl ? [] : foot(m, 0, m.S, 'top', { l: 1, r: at.tr ? 0 : 1 }), at.foot ? [] : foot(m, 0, m.S, 'base', { l: at.bl ? 0 : 1, r: at.br ? 0 : 1 }));
|
|
202
|
+
const mk = U(stem, s, serifs), [X0, Y0, X1, Y1] = bbox(mk); return move(mk, 256 - (X0 + X1) / 2, 256 - (Y0 + Y1) / 2);
|
|
203
|
+
};
|
|
204
|
+
const [x0, y0, x1, y1] = bbox(draw(32)); return despeckShape(draw(Math.ceil((31 * Math.max(x1 - x0, y1 - y0)) / 448)), 200, 30);
|
|
205
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Geom, Point, Polygon, Shape } from '../geometry/types.js';
|
|
2
|
+
import type { TrackSet } from '../parts/lines.js';
|
|
3
|
+
|
|
4
|
+
type Pts = readonly (readonly number[])[];
|
|
5
|
+
|
|
6
|
+
/** sidePhones()'s options: the head and how the headphones sit on it. */
|
|
7
|
+
export interface SidePhonesOptions {
|
|
8
|
+
/** The head (ears in front of the band included), or a build of it from the gap. */
|
|
9
|
+
head: Geom | ((g: number) => Geom);
|
|
10
|
+
/** The outline the band clears (default: the head). */
|
|
11
|
+
rimOn?: Geom;
|
|
12
|
+
/** Where the band lands on the forehead, x. */
|
|
13
|
+
x1: number;
|
|
14
|
+
cupY?: number;
|
|
15
|
+
cupC?: number[];
|
|
16
|
+
cupIn?: number;
|
|
17
|
+
cw?: number;
|
|
18
|
+
ch?: number;
|
|
19
|
+
tilt?: number;
|
|
20
|
+
bw?: number;
|
|
21
|
+
/** The band's lift off the head, in gaps. */
|
|
22
|
+
lift?: number;
|
|
23
|
+
holes?: (g: number) => Geom[];
|
|
24
|
+
parts?: (g: number) => Geom[];
|
|
25
|
+
behind?: (g: number) => Geom[];
|
|
26
|
+
/** Batch 308's even run and level cut. */
|
|
27
|
+
even?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A round eye hole. */
|
|
31
|
+
export function eye(x: number, y: number, r?: number): Polygon;
|
|
32
|
+
/** A foot of n splayed toes, each ending in a round pad. */
|
|
33
|
+
export function toes(x: number, y: number, deg: number, n?: number, len?: number, w?: number, pad?: number, fanDeg?: number): Shape;
|
|
34
|
+
/** Gecko Wall's width along the body (t 0 → 1). */
|
|
35
|
+
export function geckoW(t: number): number;
|
|
36
|
+
/** A gecko from above: body along a spine, head, eyes and padded feet. */
|
|
37
|
+
export function gecko(spinePts: Pts, legs: readonly Pts[], o?: { w?: (t: number) => number; legW?: number; toe?: { n?: number; len?: number; w?: number; pad?: number }; head?: number[]; eyeAt?: number[] }): Shape;
|
|
38
|
+
/** The house two tracks for the Track animals (26 wide, 48 apart). */
|
|
39
|
+
export const DUO: TrackSet;
|
|
40
|
+
/** Points drawn k times smaller about (300, 300). */
|
|
41
|
+
export function sc(pts: Pts, k: number): Point[];
|
|
42
|
+
/** A solid drawn k times smaller about (300, 300). */
|
|
43
|
+
export function scaleS(g: Geom, k: number): Shape;
|
|
44
|
+
/** Two tracks round a filleted outline. */
|
|
45
|
+
export function loop2(o: Pts): Shape;
|
|
46
|
+
/** Two tracks along an open line, round ends. */
|
|
47
|
+
export function line2(pts: Pts): Shape;
|
|
48
|
+
/** Where solids may sit inside a two-track loop. */
|
|
49
|
+
export function inside(o: Pts): Shape;
|
|
50
|
+
/** An ellipse head at the end of a line: [shape, centre, heading]. */
|
|
51
|
+
export function endHead(pts: Pts, len: number, wid: number, ahead: number): [Polygon, Point, number];
|
|
52
|
+
/** An eye as near (x, y) as the loop allows. */
|
|
53
|
+
export function eyeIn(o: Pts, at: readonly number[], r?: number): Polygon;
|
|
54
|
+
/** A two-track loop round a stadium. */
|
|
55
|
+
export function stadium2(cx: number, cy: number, len: number, wid: number, deg?: number): Shape;
|
|
56
|
+
/** A cloud loop's centreline from a ring of circles [x, y, r]. */
|
|
57
|
+
export function cloud(cs: Pts, r?: number): Point[];
|
|
58
|
+
/** A smooth closed outline through control points. */
|
|
59
|
+
export function S(pts: Pts, n?: number): Polygon;
|
|
60
|
+
/** A tapering round-ended stroke along a smooth curve. */
|
|
61
|
+
export function taper(pts: Pts, w0: number, w1: number, n?: number): Shape;
|
|
62
|
+
/** A cut g wide along a smooth curve, round ends. */
|
|
63
|
+
export function seam(pts: Pts, g: number): Shape;
|
|
64
|
+
/** A symmetric outline about x = cx from its right half. */
|
|
65
|
+
export function mirror(half: Pts, cx?: number): Point[];
|
|
66
|
+
/** Points on the circular arc from P to Q bulging h to the left. */
|
|
67
|
+
export function bow(P: readonly number[], Q: readonly number[], h: number, n?: number): Point[];
|
|
68
|
+
/** A sickle wing from root to tip and back. */
|
|
69
|
+
export function sickle(p0: readonly number[], tip: readonly number[], p1: readonly number[], h0: number, h1: number): Polygon;
|
|
70
|
+
/** The near wing over the body, parted by a straight cut across its root. */
|
|
71
|
+
export function rootCut(body: Geom, wing: Geom, a: readonly number[], b: readonly number[], g: number): Shape;
|
|
72
|
+
/** Cuts g wide square across a curve at fractions of its length. */
|
|
73
|
+
export function bands(pts: Pts, ts: readonly number[], reach: number, g: number): Shape;
|
|
74
|
+
/** A leg standing to the ground, its foot squared off flat. */
|
|
75
|
+
export function flatLeg(hip: readonly number[], gy: number, w?: number, dir?: number, toe?: number, r?: number): Shape;
|
|
76
|
+
/** A dog's bone, turned deg about its centre. */
|
|
77
|
+
export function bone(cx: number, cy: number, L: number, w: number, r: number, deg?: number): Shape;
|
|
78
|
+
/** A paw print: heel pad and four toe pads. */
|
|
79
|
+
export function pawPrint(cx: number, cy: number, k?: number): Shape;
|
|
80
|
+
/** A solid kept a gap clear of a loop's two tracks. */
|
|
81
|
+
export function clearOf(solid: Geom, o: Pts): Shape;
|
|
82
|
+
/** A shape moved by (dx, dy). */
|
|
83
|
+
export function moved(sh: Shape, dx: number, dy: number): Shape;
|
|
84
|
+
/** A closed polyline resampled to points `step` apart. */
|
|
85
|
+
export function evenPts(pts: Pts, step: number): Point[];
|
|
86
|
+
/** A ribbon loop's centreline round a spine, wf(t) wide. */
|
|
87
|
+
export function ribbon(spine: Pts, wf: (t: number) => number, n?: number): Point[];
|
|
88
|
+
/** Whether a point is inside a shape (even-odd). */
|
|
89
|
+
export function inShape(sh: Geom, p: readonly number[]): boolean;
|
|
90
|
+
/** A head side-on in headphones, as a build from the gap. */
|
|
91
|
+
export function sidePhones(o: SidePhonesOptions): (g: number) => Shape;
|
|
92
|
+
/** sidePhones() scaled, turned and moved, the gap kept. */
|
|
93
|
+
export function wearPhones(o: SidePhonesOptions, k: number, dx: number, dy: number, deg?: number): (g: number) => Shape;
|
|
94
|
+
/** Layers back to front, each laid over() the ones before by the gap. */
|
|
95
|
+
export function layered(g: number, ...layers: Geom[]): Shape;
|
|
96
|
+
/** A cat's or fox's head side-on, for sidePhones(). */
|
|
97
|
+
export function pointyHead(s: number): { head: Shape; rimOn: Polygon; x1: number };
|
|
98
|
+
/** ∪ : a shut eye for an animal side-on. */
|
|
99
|
+
export function shut(x: number, y: number, r: number, w: number): Shape;
|
|
100
|
+
/** A long head side-on (a goat's, a donkey's). */
|
|
101
|
+
export function longBase(s?: number): Polygon;
|
|
102
|
+
/** A bird's head side-on, facing right. */
|
|
103
|
+
export const birdHead: Polygon;
|
|
104
|
+
/** Four standing legs, each pair a leg's width plus the gap apart. */
|
|
105
|
+
export function legs4(xb: number, xf: number, y0: number, y1: number, w: number, g: number): Shape;
|