@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -0
  3. package/package.json +47 -4
  4. package/src/font/glyphs.d.ts +31 -0
  5. package/src/font/glyphs.js +228 -0
  6. package/src/font/index.d.ts +10 -0
  7. package/src/font/index.js +10 -0
  8. package/src/font/measure.d.ts +16 -0
  9. package/src/font/measure.js +201 -0
  10. package/src/font/moves.d.ts +42 -0
  11. package/src/font/moves.js +263 -0
  12. package/src/forms/discs.d.ts +20 -0
  13. package/src/forms/discs.js +114 -0
  14. package/src/forms/emblems.d.ts +22 -0
  15. package/src/forms/emblems.js +109 -0
  16. package/src/forms/flow.d.ts +21 -0
  17. package/src/forms/flow.js +80 -0
  18. package/src/forms/folds.d.ts +18 -0
  19. package/src/forms/folds.js +147 -0
  20. package/src/forms/geo.d.ts +46 -0
  21. package/src/forms/geo.js +233 -0
  22. package/src/forms/hooks.d.ts +40 -0
  23. package/src/forms/hooks.js +204 -0
  24. package/src/forms/index.d.ts +22 -0
  25. package/src/forms/index.js +24 -0
  26. package/src/forms/jog.d.ts +33 -0
  27. package/src/forms/jog.js +162 -0
  28. package/src/forms/lines.d.ts +32 -0
  29. package/src/forms/lines.js +233 -0
  30. package/src/forms/ref.d.ts +41 -0
  31. package/src/forms/ref.js +246 -0
  32. package/src/forms/shatter.d.ts +40 -0
  33. package/src/forms/shatter.js +341 -0
  34. package/src/forms/slats.d.ts +35 -0
  35. package/src/forms/slats.js +78 -0
  36. package/src/forms/swash.d.ts +23 -0
  37. package/src/forms/swash.js +74 -0
  38. package/src/forms/tech.d.ts +40 -0
  39. package/src/forms/tech.js +293 -0
  40. package/src/forms/tracks.d.ts +36 -0
  41. package/src/forms/tracks.js +121 -0
  42. package/src/index.js +2 -1
  43. package/src/iso/index.d.ts +5 -0
  44. package/src/iso/index.js +13 -0
  45. package/src/iso/objects.d.ts +168 -0
  46. package/src/iso/objects.js +1042 -0
  47. package/src/iso/planes.d.ts +27 -0
  48. package/src/iso/planes.js +138 -0
  49. package/src/iso/voxels.d.ts +43 -0
  50. package/src/iso/voxels.js +255 -0
  51. package/src/letters/blackletter.d.ts +31 -0
  52. package/src/letters/blackletter.js +199 -0
  53. package/src/letters/dotstroke.d.ts +16 -0
  54. package/src/letters/dotstroke.js +77 -0
  55. package/src/letters/echo.d.ts +42 -0
  56. package/src/letters/echo.js +246 -0
  57. package/src/letters/frame.d.ts +6 -0
  58. package/src/letters/frame.js +14 -0
  59. package/src/letters/index.d.ts +16 -0
  60. package/src/letters/index.js +18 -0
  61. package/src/letters/inktrap.d.ts +66 -0
  62. package/src/letters/inktrap.js +244 -0
  63. package/src/letters/mono.d.ts +145 -0
  64. package/src/letters/mono.js +684 -0
  65. package/src/letters/pillar.d.ts +31 -0
  66. package/src/letters/pillar.js +257 -0
  67. package/src/letters/serif.d.ts +109 -0
  68. package/src/letters/serif.js +666 -0
  69. package/src/letters/stair.d.ts +8 -0
  70. package/src/letters/stair.js +43 -0
  71. package/src/letters/stem.d.ts +29 -0
  72. package/src/letters/stem.js +205 -0
  73. package/src/objects/animals.d.ts +105 -0
  74. package/src/objects/animals.js +469 -0
  75. package/src/objects/bio.d.ts +14 -0
  76. package/src/objects/bio.js +76 -0
  77. package/src/objects/devglyphs.d.ts +34 -0
  78. package/src/objects/devglyphs.js +226 -0
  79. package/src/objects/devices.d.ts +31 -0
  80. package/src/objects/devices.js +114 -0
  81. package/src/objects/faces.d.ts +94 -0
  82. package/src/objects/faces.js +344 -0
  83. package/src/objects/food.d.ts +12 -0
  84. package/src/objects/food.js +74 -0
  85. package/src/objects/index.d.ts +13 -0
  86. package/src/objects/index.js +18 -0
  87. package/src/objects/rosettes.d.ts +14 -0
  88. package/src/objects/rosettes.js +64 -0
  89. package/src/objects/sport.d.ts +8 -0
  90. package/src/objects/sport.js +37 -0
  91. package/src/objects/weapons.d.ts +31 -0
  92. package/src/objects/weapons.js +179 -0
  93. package/src/pixels/dither.d.ts +78 -0
  94. package/src/pixels/dither.js +399 -0
  95. package/src/pixels/emote.d.ts +6 -0
  96. package/src/pixels/emote.js +76 -0
  97. package/src/pixels/index.d.ts +7 -0
  98. package/src/pixels/index.js +8 -0
  99. package/src/sdf/index.d.ts +2 -0
  100. package/src/sdf/index.js +2 -0
  101. package/src/sdf/y2k.d.ts +12 -0
  102. package/src/sdf/y2k.js +80 -0
  103. package/src/util/vec.d.ts +14 -0
  104. package/src/util/vec.js +61 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Lines, blobs, rails and locks several families share: soft blobs, the Cairo tiling and a centre picker, chamfered
3
+ * polylines, Rails (two tracks along a line, corners mitred sharp), bowed lines and the Y of gaps, interlocking overlaps
4
+ * (each shape in front of the next on one side of a line and behind it on the other), and placing one part exactly a
5
+ * gap clear of another.
6
+ * From the Logomarks tools (shared.mjs's generic half: batches 14, 101, 116, 117, 163, 166, 169, 178, 187, 255).
7
+ */
8
+ import { C, TAU, U, D, I, X, poly, sample, stroke, grown, regular, move, GAP as G, mitreOffset as mitreOff } from '../geometry/index.js';
9
+
10
+ /* ---------- soft blobs ---------- */
11
+
12
+ /**
13
+ * A blob: radius R wobbled by sine terms, each [k, amplitude, phase] (r = 1 + Σ a·sin(k·t + p)); sy squashes it
14
+ * vertically.
15
+ *
16
+ * @param {number} cx The centre.
17
+ * @param {number} cy The centre.
18
+ * @param {number} R The radius.
19
+ * @param {readonly (readonly number[])[]} [terms=[]] The wobble's terms.
20
+ * @param {number} [sy=1] The vertical squash.
21
+ * @param {number} [n=360] Points round it.
22
+ * @returns {import('../geometry/types.js').Polygon} The blob.
23
+ */
24
+ export const blob = (cx, cy, R, terms = [], sy = 1, n = 360) =>
25
+ poly(sample((t) => { let r = 1; for (const [k, a, p] of terms) r += a * Math.sin(k * t + p); return [cx + R * r * Math.cos(t), cy + sy * R * r * Math.sin(t)]; }, 0, TAU, n).slice(0, -1));
26
+
27
+ /**
28
+ * The same blob grown by g all round (near enough), to cut a gap round it.
29
+ *
30
+ * @param {number} cx The centre.
31
+ * @param {number} cy The centre.
32
+ * @param {number} R The radius.
33
+ * @param {readonly (readonly number[])[]} terms The wobble's terms.
34
+ * @param {number} [sy=1] The vertical squash.
35
+ * @param {number} [g=GAP] How far it grows.
36
+ * @returns {import('../geometry/types.js').Polygon} The grown blob.
37
+ */
38
+ export const blobGrown = (cx, cy, R, terms, sy = 1, g = G) => blob(cx, cy, R + g, terms, sy);
39
+
40
+ /* ---------- tiling ---------- */
41
+
42
+ /**
43
+ * The Cairo pentagonal tiling, equilateral, on a 7 × 7 block of unit cells round the origin: square cells alternately
44
+ * crossed by a short horizontal or vertical bar, each bar end joined to the two nearest corners.
45
+ *
46
+ * @param {number} [t=0.2257] The bar's share (0.2257: every edge the same length).
47
+ * @returns {import('../geometry/types.js').Point[][]} The pentagons.
48
+ */
49
+ export const cairo = (t = 0.2257) => {
50
+ const out = [];
51
+ for (let i = -3; i <= 3; i++) for (let j = -3; j <= 3; j++) {
52
+ if ((i + j) % 2 === 0) out.push([[i + t, j + 0.5], [i + 1 - t, j + 0.5], [i + 1, j + 1], [i + 0.5, j + 1 + t], [i, j + 1]], [[i + t, j + 0.5], [i, j], [i + 0.5, j - t], [i + 1, j], [i + 1 - t, j + 0.5]]);
53
+ else out.push([[i + 0.5, j + t], [i + 0.5, j + 1 - t], [i, j + 1], [i - t, j + 0.5], [i, j]], [[i + 0.5, j + t], [i + 1, j], [i + 1 + t, j + 0.5], [i + 1, j + 1], [i + 0.5, j + 1 - t]]);
54
+ }
55
+ return out;
56
+ };
57
+
58
+ /**
59
+ * The polygons whose centres (the mean of their points) lie within r of (cx, cy).
60
+ *
61
+ * @template {readonly (readonly number[])[]} T
62
+ * @param {readonly T[]} list The polygons, as point lists.
63
+ * @param {number} cx The centre.
64
+ * @param {number} cy The centre.
65
+ * @param {number} r The reach.
66
+ * @returns {T[]} Those near it.
67
+ */
68
+ export const near = (list, cx, cy, r) => list.filter((pl) => { const m = pl.reduce((a, p) => [a[0] + p[0] / pl.length, a[1] + p[1] / pl.length], [0, 0]); return Math.hypot(m[0] - cx, m[1] - cy) <= r; });
69
+
70
+ /* ---------- chamfered and mitred lines ---------- */
71
+
72
+ const add2 = (a, b) => [a[0] + b[0], a[1] + b[1]], sub2 = (a, b) => [a[0] - b[0], a[1] - b[1]], mul2 = (a, k) => [a[0] * k, a[1] * k];
73
+ const unit2 = (v) => { const l = Math.hypot(...v) || 1; return [v[0] / l, v[1] / l]; };
74
+
75
+ /**
76
+ * A polyline with every inner corner cut at 45° by c.
77
+ *
78
+ * @param {readonly import('../geometry/types.js').Point[]} pts The polyline.
79
+ * @param {number | readonly number[]} c The cut (or one per inner corner; 0 keeps the corner).
80
+ * @returns {import('../geometry/types.js').Point[]} The chamfered polyline.
81
+ */
82
+ export const chamPath = (pts, c) => {
83
+ const out = [pts[0]];
84
+ for (let i = 1; i < pts.length - 1; i++) {
85
+ const k = Array.isArray(c) ? c[i - 1] : c, p = pts[i];
86
+ if (!k) { out.push(p); continue; } const u = unit2(sub2(p, pts[i - 1])), v = unit2(sub2(pts[i + 1], p)); out.push(sub2(p, mul2(u, k)), add2(p, mul2(v, k)));
87
+ }
88
+ out.push(pts.at(-1)); return out;
89
+ };
90
+
91
+ /**
92
+ * A Rail: two tracks w wide, pitch apart, along an open polyline, every corner mitred sharp and the ends cut flat (the
93
+ * Duo's 26 and 48 by default).
94
+ *
95
+ * @param {readonly import('../geometry/types.js').Point[]} pts The centre line.
96
+ * @param {number} [w=26] Each track's width.
97
+ * @param {number} [pitch=48] Track to track, centre to centre.
98
+ * @returns {import('../geometry/types.js').Shape} The Rail.
99
+ */
100
+ export const railsOf = (pts, w = 26, pitch = 48) => U(stroke(mitreOff(pts, pitch / 2), w, { chunk: 4 }), stroke(mitreOff(pts, -pitch / 2), w, { chunk: 4 }));
101
+
102
+ /**
103
+ * A closed Rail: the two tracks (w wide, pitch apart) of a regular n-gon whose centre path has inradius r, corners
104
+ * mitred sharp.
105
+ *
106
+ * @param {number} cx The centre.
107
+ * @param {number} cy The centre.
108
+ * @param {number} r The centre path's inradius.
109
+ * @param {number} n Sides.
110
+ * @param {number} [deg=-90] The first corner's angle.
111
+ * @param {number} [w=26] Each track's width.
112
+ * @param {number} [pitch=48] Track to track.
113
+ * @returns {import('../geometry/types.js').Shape} The Rail ring.
114
+ */
115
+ export const railRing = (cx, cy, r, n, deg = -90, w = 26, pitch = 48) => {
116
+ const ng = (ri) => regular(cx, cy, ri / Math.cos(Math.PI / n), n, deg);
117
+ return U(D(ng(r + pitch / 2 + w / 2), ng(r + pitch / 2 - w / 2)), D(ng(r - pitch / 2 + w / 2), ng(r - pitch / 2 - w / 2)));
118
+ };
119
+
120
+ /**
121
+ * A closed Rail along any polygon (concave too), every corner mitred sharp.
122
+ *
123
+ * @param {readonly import('../geometry/types.js').Point[]} pts The centre path (open: no repeated point).
124
+ * @param {number} [w=26] Each track's width.
125
+ * @param {number} [pitch=48] Track to track.
126
+ * @returns {import('../geometry/types.js').Shape} The Rail loop.
127
+ */
128
+ export const railLoop = (pts, w = 26, pitch = 48) => {
129
+ const off = (d) => poly(mitreOff([pts.at(-1), ...pts, pts[0]], d).slice(1, -1));
130
+ return U(X(off(pitch / 2 + w / 2), off(pitch / 2 - w / 2)), X(off(-pitch / 2 + w / 2), off(-pitch / 2 - w / 2)));
131
+ };
132
+
133
+ /* ---------- bows, slots and locks ---------- */
134
+
135
+ /**
136
+ * Points from a to b bowed sideways at the middle by k times the length (a parabola; k < 0 bows to the right of a→b,
137
+ * y down).
138
+ *
139
+ * @param {readonly number[]} a The start.
140
+ * @param {readonly number[]} b The end.
141
+ * @param {number} k The bow, as a share of the length.
142
+ * @param {number} [n=24] Steps.
143
+ * @returns {import('../geometry/types.js').Point[]} The bowed line.
144
+ */
145
+ export const bow = (a, b, k, n = 24) => {
146
+ const dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy), nx = dy / L, ny = -dx / L;
147
+ return Array.from({ length: n + 1 }, (_, i) => { const t = i / n, h = k * L * 4 * t * (1 - t); return [a[0] + dx * t + nx * h, a[1] + dy * t + ny * h]; });
148
+ };
149
+
150
+ /**
151
+ * A slot g wide leaving the canvas centre at angle a, from `from` to `to` along it (three of them make the Y of gaps
152
+ * that splits a cube).
153
+ *
154
+ * @param {number} a The angle (degrees, y down).
155
+ * @param {number} g The width.
156
+ * @param {number} [from=-40] Where it starts along the ray.
157
+ * @param {number} [to=400] Where it ends.
158
+ * @returns {import('../geometry/types.js').Polygon} The slot.
159
+ */
160
+ export const slot = (a, g, from = -40, to = 400) => {
161
+ const u = [Math.cos((a * Math.PI) / 180), Math.sin((a * Math.PI) / 180)], v = [-u[1] * g / 2, u[0] * g / 2];
162
+ return poly([[C + u[0] * from + v[0], C + u[1] * from + v[1]], [C + u[0] * to + v[0], C + u[1] * to + v[1]], [C + u[0] * to - v[0], C + u[1] * to - v[1]], [C + u[0] * from - v[0], C + u[1] * from - v[1]]]);
163
+ };
164
+
165
+ /**
166
+ * The two half-planes either side of the line p→q (as large squares).
167
+ *
168
+ * @param {readonly number[]} p A point on the line.
169
+ * @param {readonly number[]} q Another.
170
+ * @returns {import('../geometry/types.js').Polygon[]} [left, right].
171
+ */
172
+ export const halves = (p, q) => {
173
+ const d = [q[0] - p[0], q[1] - p[1]], L = Math.hypot(...d), u = [(d[0] / L) * 2000, (d[1] / L) * 2000], n = [-u[1], u[0]];
174
+ return [1, -1].map((k) => poly([[p[0] - u[0], p[1] - u[1]], [p[0] + u[0], p[1] + u[1]], [p[0] + u[0] + k * n[0], p[1] + u[1] + k * n[1]], [p[0] - u[0] + k * n[0], p[1] - u[1] + k * n[1]]]));
175
+ };
176
+
177
+ /**
178
+ * Shapes in a chain along the line p→q, each overlapping the next: every shape is in front of the next on the left of
179
+ * the line and behind it on the right (a cyclic lock, link by link), the gap g round every crossing.
180
+ *
181
+ * @param {readonly import('../geometry/types.js').Geom[]} list The links, in order.
182
+ * @param {readonly number[]} p A point on the line.
183
+ * @param {readonly number[]} q Another.
184
+ * @param {number} [g=GAP] The gap.
185
+ * @returns {import('../geometry/types.js').Shape} The chain.
186
+ */
187
+ export const lockChain = (list, p, q, g = G) => {
188
+ const [left, right] = halves(p, q);
189
+ return U(...list.map((s, k) => D(s, ...(list[k + 1] ? [grown(I(list[k + 1], right), g)] : []), ...(k ? [grown(I(list[k - 1], left), g)] : []))));
190
+ };
191
+
192
+ /**
193
+ * Two shapes overlapped, a in front on the left of the line p→q, b in front on its right.
194
+ *
195
+ * @param {import('../geometry/types.js').Geom} a The first.
196
+ * @param {import('../geometry/types.js').Geom} b The second.
197
+ * @param {readonly number[]} p A point on the line.
198
+ * @param {readonly number[]} q Another.
199
+ * @param {number} [g=GAP] The gap.
200
+ * @returns {import('../geometry/types.js').Shape} The pair.
201
+ */
202
+ export const lockAlong = (a, b, p, q, g = G) => lockChain([a, b], p, q, g);
203
+
204
+ /* ---------- placing a part a gap clear ---------- */
205
+
206
+ /**
207
+ * The least distance between two shapes, measured between their outline points (dense outlines give it to a fraction).
208
+ *
209
+ * @param {import('../geometry/types.js').Geom} a One shape.
210
+ * @param {import('../geometry/types.js').Geom} b The other.
211
+ * @returns {number} The distance.
212
+ */
213
+ export const gapBetween = (a, b) => {
214
+ const B = U(b).flat(2); let m = Infinity;
215
+ for (const p of U(a).flat(2)) for (const q of B) { const d = (p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2; if (d < m) m = d; } return Math.sqrt(m);
216
+ };
217
+
218
+ /**
219
+ * A part slid along `deg` (0 = right, 90 = down) until it sits exactly g clear of `fixed`, by bisection between 0 and
220
+ * `far`.
221
+ *
222
+ * @param {import('../geometry/types.js').Geom} fixed What it must clear.
223
+ * @param {import('../geometry/types.js').Geom} part The part.
224
+ * @param {number} deg The direction (degrees).
225
+ * @param {number} g The gap.
226
+ * @param {number} [far=400] The farthest it may go.
227
+ * @returns {import('../geometry/types.js').Geom} The moved part.
228
+ */
229
+ export const slideToGap = (fixed, part, deg, g, far = 400) => {
230
+ const u = [Math.cos((deg * Math.PI) / 180), Math.sin((deg * Math.PI) / 180)];
231
+ const at = (t) => move(part, u[0] * t, u[1] * t); let lo = 0, hi = far;
232
+ for (let i = 0; i < 30; i++) { const t = (lo + hi) / 2; if (gapBetween(fixed, at(t)) < g) lo = t; else hi = t; } return at(hi);
233
+ };
@@ -0,0 +1,41 @@
1
+ import type { Point, Polygon, Shape, Geom } from '../geometry/types.js';
2
+
3
+ /** A stripe's edges: each a number or a function of x (y down). */
4
+ export type StripeEdge = number | ((x: number) => number);
5
+
6
+ /** n copies of a shape turned evenly round (cx, cy), the first turned deg0. */
7
+ export function turns(g: Geom, n: number, deg0?: number, cx?: number, cy?: number): Geom[];
8
+ /** A cyclic overlap: every unit cut free by the next one round, crossings parted by g. */
9
+ export function cyclic(units: readonly Geom[], g: number): Shape;
10
+ /** A chevron: two arms w wide meeting at an apex at the origin, opening `spread` degrees, pointing along +x. */
11
+ export function chevron(L: number, w: number, spread: number): Polygon;
12
+ /** Stripes as bands between [top, bottom] edges, sampled every dx from x0 to x1. */
13
+ export function stripeBands(list: readonly (readonly StripeEdge[])[], o?: { x0?: number; x1?: number; dx?: number }): Shape;
14
+ /** n even stripes across [a, b] with a gap gs: [top, bottom] each. */
15
+ export function evenBands(n: number, a: number, b: number, gs: number): [number, number][];
16
+ /** n stripes across [a, b] with a fixed gap gs, widths in the ratio r0 : r1 first to last. */
17
+ export function thinning(n: number, a: number, b: number, gs: number, r0: number, r1: number): [number, number][];
18
+ /** n stripes w wide along a polyline, `pitch` apart square to every run, corners mitred. */
19
+ export function stripesAlong(pts: readonly Point[], n: number, w: number, pitch: number): Shape;
20
+ /** A stripe list shifted by f(x). */
21
+ export function shifted(list: readonly (readonly number[])[], f: (x: number) => number): ((x: number) => number)[][];
22
+ /** Stripes drawn level, then turned deg about the centre. */
23
+ export function turned(list: readonly (readonly StripeEdge[])[], deg: number, o?: { x0?: number; x1?: number; dx?: number }): Shape;
24
+ /** A ring whose radius waves k times round: r = R + A·cos(k·t + ph). */
25
+ export function waveRing(R: number, A: number, k: number, ph?: number, n?: number, cx?: number, cy?: number): Point[];
26
+ /** A glyph slanted deg degrees (tops lean right) about the line y = cy. */
27
+ export function italic(g: Geom, deg?: number, cy?: number): Geom;
28
+ /** A sharp notch: the triangle from edge points a and b to the tip t, `out` past the edge. */
29
+ export function wedge(a: readonly number[], b: readonly number[], t: Point, out?: number): Polygon;
30
+ /** The speed capitals' box: cap line, baseline, left and right edges, stem and bar. */
31
+ export const SPEED_BOX: { T0: number; B0: number; L0: number; R0: number; SW: number; BW: number };
32
+ /** A bar from y0 to y1, across the speed box unless told. */
33
+ export function hz(y0: number, y1: number, x0?: number, x1?: number): Polygon;
34
+ /** An upright from x0 to x1, the speed box's height unless told. */
35
+ export function vt(x0: number, x1: number, y0?: number, y1?: number): Polygon;
36
+ /** The speed slit: a wedge bw wide entering at a, its tip len along dir. */
37
+ export function slit(a: readonly number[], len: number, dir?: number, bw?: number): Polygon;
38
+ /** A speed letter: the glyph less its slit, slanted 17°. */
39
+ export function speed(glyph: Geom, cut: Geom): Geom;
40
+ /** The speed slab capitals (N M Z K X S R A), each returning the upright glyph. */
41
+ export const SPEED_GLYPHS: Record<string, () => Geom>;
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Four constructions from the reference sheets, as small helpers a generator composes (constructions only; no mark's
3
+ * geometry lives here):
4
+ *
5
+ * - rotation ("Bold · Rotational"): turns() sets one unit round a centre n times; cyclic() puts each copy in front of the
6
+ * one before it (a cyclic overlap on any unit), every crossing parted by the gap;
7
+ * - stripes ("Bold · Stripes"): stripeBands() draws stripes as bands between two edge functions of x, so a stripe can
8
+ * ripple or taper; stripesAlong() bends them with mitred corners, widths held square to each run; evenBands() spaces
9
+ * n stripes evenly across a span and thinning() with a fixed gap, their widths easing from one end to the other;
10
+ * - guilloche ("Line · Guilloche"): waveRing() is the closed curve the bold guilloche strands run along (the kit's
11
+ * weave() parts them over and under);
12
+ * - speed italics ("Letters · Speed"): italic() slants a glyph built upright, wedge() is the one sharp notch, and the
13
+ * slab capitals (SPEED_GLYPHS, on SPEED_BOX) take the speed slit (slit()) through speed().
14
+ *
15
+ * From the Logomarks tools (refparts.mjs, batch 237; the speed capitals shared from batch 240).
16
+ */
17
+ import { C, poly, rect, rot, mapPts, U, D, grown, closedCurve, mitreBand as band, mitreOffset as mitreOff } from '../geometry/index.js';
18
+
19
+ /* ---------- rotation ---------- */
20
+
21
+ /**
22
+ * n copies of a shape turned evenly round (cx, cy), the first turned deg0: a list, not yet united.
23
+ *
24
+ * @param {import('../geometry/types.js').Geom} g The unit.
25
+ * @param {number} n How many.
26
+ * @param {number} [deg0=0] The first copy's turn (degrees).
27
+ * @param {number} [cx=C] The centre.
28
+ * @param {number} [cy=C] The centre.
29
+ * @returns {import('../geometry/types.js').Geom[]} The copies.
30
+ */
31
+ export const turns = (g, n, deg0 = 0, cx = C, cy = C) => [...Array(n)].map((_, k) => rot(g, deg0 + (360 * k) / n, cx, cy));
32
+
33
+ /**
34
+ * A cyclic overlap: every unit cut free by the next one round, so each lies in front of one neighbour and behind the
35
+ * other (no real stack does that, which is the move); every crossing is parted by g.
36
+ *
37
+ * @param {readonly import('../geometry/types.js').Geom[]} units The units, in order round.
38
+ * @param {number} g The gap.
39
+ * @returns {import('../geometry/types.js').Shape} The overlap.
40
+ */
41
+ export const cyclic = (units, g) => U(...units.map((u, k) => D(u, grown(units[(k + 1) % units.length], g))));
42
+
43
+ /**
44
+ * A chevron: two arms w wide meeting at a sharp apex at the origin, opening `spread` degrees, pointing along +x, each
45
+ * arm L long (measured along its outer edge). Move and turn it into place.
46
+ *
47
+ * @param {number} L The arms' length.
48
+ * @param {number} w Their width.
49
+ * @param {number} spread The opening (degrees).
50
+ * @returns {import('../geometry/types.js').Polygon} The chevron.
51
+ */
52
+ export function chevron(L, w, spread) {
53
+ const h = (spread / 2) * (Math.PI / 180), s = w / Math.sin(h);
54
+ const arm = (k) => [-L * Math.cos(h), k * L * Math.sin(h)];
55
+ const a = arm(1), b = arm(-1), tipIn = [-s, 0];
56
+ const inner = (p) => [p[0] - s, p[1]];
57
+ return poly([[0, 0], b, inner(b), tipIn, inner(a), a]);
58
+ }
59
+
60
+ /* ---------- stripes ---------- */
61
+
62
+ /**
63
+ * Stripes as bands: each entry [top, bottom], each a number or a function of x (y down), sampled every dx across the
64
+ * canvas and beyond, so a region clips them.
65
+ *
66
+ * @param {readonly (readonly (number | ((x: number) => number))[])[]} list The stripes' [top, bottom] edges.
67
+ * @param {{ x0?: number, x1?: number, dx?: number }} [o] Where the sampling starts (−80) and ends (592), and its step (4).
68
+ * @returns {import('../geometry/types.js').Shape} The stripes.
69
+ */
70
+ export function stripeBands(list, { x0 = -80, x1 = 592, dx = 4 } = {}) {
71
+ const at = (f, x) => (typeof f === 'function' ? f(x) : f), xs = [];
72
+ for (let x = x0; x <= x1 + 1e-9; x += dx) xs.push(x);
73
+ return U(...list.map(([t, b]) => poly([...xs.map((x) => [x, at(t, x)]), ...xs.slice().reverse().map((x) => [x, at(b, x)])])));
74
+ }
75
+
76
+ /**
77
+ * n even stripes across [a, b] with a gap gs between them.
78
+ *
79
+ * @param {number} n How many.
80
+ * @param {number} a Where they start.
81
+ * @param {number} b Where they end.
82
+ * @param {number} gs The gap between two.
83
+ * @returns {[number, number][]} Each stripe's [top, bottom].
84
+ */
85
+ export const evenBands = (n, a, b, gs) => { const w = (b - a - (n - 1) * gs) / n; return [...Array(n)].map((_, k) => [a + k * (w + gs), a + k * (w + gs) + w]); };
86
+
87
+ /**
88
+ * n stripes across [a, b] with a fixed gap gs, their widths in the ratio r0 : r1 from the first to the last (linear).
89
+ *
90
+ * @param {number} n How many.
91
+ * @param {number} a Where they start.
92
+ * @param {number} b Where they end.
93
+ * @param {number} gs The gap between two.
94
+ * @param {number} r0 The first stripe's share.
95
+ * @param {number} r1 The last stripe's share.
96
+ * @returns {[number, number][]} Each stripe's [top, bottom].
97
+ */
98
+ export function thinning(n, a, b, gs, r0, r1) {
99
+ const ws = [...Array(n)].map((_, k) => r0 + ((r1 - r0) * k) / (n - 1)), k = (b - a - (n - 1) * gs) / ws.reduce((s, v) => s + v, 0);
100
+ let y = a; return ws.map((v) => { const out = [y, y + v * k]; y += v * k + gs; return out; });
101
+ }
102
+
103
+ /**
104
+ * n stripes w wide along a polyline, `pitch` apart measured square to every run (corners mitred), so a stripe that
105
+ * bends keeps its width and every gap through the bend.
106
+ *
107
+ * @param {readonly import('../geometry/types.js').Point[]} pts The polyline.
108
+ * @param {number} n How many stripes.
109
+ * @param {number} w Their width.
110
+ * @param {number} pitch Centre to centre.
111
+ * @returns {import('../geometry/types.js').Shape} The stripes.
112
+ */
113
+ export const stripesAlong = (pts, n, w, pitch) => U(...[...Array(n)].map((_, k) => band(mitreOff(pts, (k - (n - 1) / 2) * pitch), w)));
114
+
115
+ /**
116
+ * A stripe list shifted by f(x), so every stripe follows one wave or one pulse.
117
+ *
118
+ * @param {readonly (readonly number[])[]} list The stripes' [top, bottom].
119
+ * @param {(x: number) => number} f The shift.
120
+ * @returns {((x: number) => number)[][]} The stripes as edge functions, for stripeBands().
121
+ */
122
+ export const shifted = (list, f) => list.map(([t, b]) => [(x) => t + f(x), (x) => b + f(x)]);
123
+
124
+ /**
125
+ * Stripes in a turned frame: drawn level, then turned deg about the centre.
126
+ *
127
+ * @param {readonly (readonly (number | ((x: number) => number))[])[]} list The stripes' [top, bottom] edges.
128
+ * @param {number} deg The turn (degrees).
129
+ * @param {{ x0?: number, x1?: number, dx?: number }} [o] stripeBands()'s options.
130
+ * @returns {import('../geometry/types.js').Shape} The stripes.
131
+ */
132
+ export const turned = (list, deg, o) => rot(stripeBands(list, o), deg);
133
+
134
+ /* ---------- guilloche curves ---------- */
135
+
136
+ /**
137
+ * A ring whose radius waves k times round: r = R + A·cos(k·t + ph), sampled at n points.
138
+ *
139
+ * @param {number} R The mean radius.
140
+ * @param {number} A The wave's amplitude.
141
+ * @param {number} k Waves round.
142
+ * @param {number} [ph=0] The phase.
143
+ * @param {number} [n=480] Points.
144
+ * @param {number} [cx=C] The centre.
145
+ * @param {number} [cy=C] The centre.
146
+ * @returns {import('../geometry/types.js').Point[]} The ring.
147
+ */
148
+ export const waveRing = (R, A, k, ph = 0, n = 480, cx = C, cy = C) => closedCurve((t) => { const r = R + A * Math.cos(k * t + ph); return [cx + r * Math.cos(t), cy + r * Math.sin(t)]; }, n);
149
+
150
+ /* ---------- speed italics ---------- */
151
+
152
+ /**
153
+ * A glyph drawn upright, slanted deg degrees (tops lean right) about the line y = cy.
154
+ *
155
+ * @param {import('../geometry/types.js').Geom} g The glyph.
156
+ * @param {number} [deg=17] The slant.
157
+ * @param {number} [cy=C] The line it slants about.
158
+ * @returns {import('../geometry/types.js').Geom} The slanted glyph.
159
+ */
160
+ export const italic = (g, deg = 17, cy = C) => { const t = Math.tan((deg * Math.PI) / 180); return mapPts(g, ([x, y]) => [x + (cy - y) * t, y]); };
161
+
162
+ /**
163
+ * A sharp notch: the triangle from the edge points a and b to the tip t, set `out` past the edge so it cuts clean.
164
+ *
165
+ * @param {readonly number[]} a One edge point.
166
+ * @param {readonly number[]} b The other.
167
+ * @param {import('../geometry/types.js').Point} t The tip.
168
+ * @param {number} [out=6] How far past the edge it starts.
169
+ * @returns {import('../geometry/types.js').Polygon} The notch.
170
+ */
171
+ export const wedge = (a, b, t, out = 6) => {
172
+ const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], v = [m[0] - t[0], m[1] - t[1]], L = Math.hypot(...v);
173
+ const o = [(v[0] / L) * out, (v[1] / L) * out]; return poly([[a[0] + o[0], a[1] + o[1]], [b[0] + o[0], b[1] + o[1]], t]);
174
+ };
175
+
176
+ /* ---------- speed slab capitals ---------- */
177
+ // Heavy slab capitals built upright on a 368 × 400 box (M 432 wide; stems 112, bars 92, sharp corners, flat terminals; S
178
+ // has its two outer turns chamfered so it never reads as a 5), slanted 17°, each with ONE speed slit: a sharp wedge cut
179
+ // in from the trailing (left) edge of one stroke, rising slightly forward.
180
+
181
+ /** The speed capitals' box: cap line T0, baseline B0, left edge L0, right edge R0, stem SW and bar BW. */
182
+ export const SPEED_BOX = { T0: 56, B0: 456, L0: 72, R0: 440, SW: 112, BW: 92 };
183
+ const { T0, B0, L0, R0, SW, BW } = SPEED_BOX;
184
+
185
+ /**
186
+ * A bar from y0 to y1, across the speed box unless told.
187
+ *
188
+ * @param {number} y0 Its top.
189
+ * @param {number} y1 Its bottom.
190
+ * @param {number} [x0] Its left (the box's).
191
+ * @param {number} [x1] Its right (the box's).
192
+ * @returns {import('../geometry/types.js').Polygon} The bar.
193
+ */
194
+ export const hz = (y0, y1, x0 = L0, x1 = R0) => rect(x0, y0, x1 - x0, y1 - y0);
195
+
196
+ /**
197
+ * An upright from x0 to x1, the speed box's full height unless told.
198
+ *
199
+ * @param {number} x0 Its left.
200
+ * @param {number} x1 Its right.
201
+ * @param {number} [y0] Its top (the cap line).
202
+ * @param {number} [y1] Its bottom (the baseline).
203
+ * @returns {import('../geometry/types.js').Polygon} The upright.
204
+ */
205
+ export const vt = (x0, x1, y0 = T0, y1 = B0) => rect(x0, y0, x1 - x0, y1 - y0);
206
+
207
+ /**
208
+ * The speed slit: a wedge `bw` wide where it enters at a, its sharp tip `len` further along `dir`.
209
+ *
210
+ * @param {readonly number[]} a Where it enters.
211
+ * @param {number} len How far the tip runs.
212
+ * @param {number} [dir=-8] Its direction (degrees, y down).
213
+ * @param {number} [bw=46] Its width at the entry.
214
+ * @returns {import('../geometry/types.js').Polygon} The slit.
215
+ */
216
+ export const slit = (a, len, dir = -8, bw = 46) => {
217
+ const r = (dir * Math.PI) / 180, n = [(-Math.sin(r) * bw) / 2, (Math.cos(r) * bw) / 2];
218
+ return wedge([a[0] + n[0], a[1] + n[1]], [a[0] - n[0], a[1] - n[1]], [a[0] + Math.cos(r) * len, a[1] + Math.sin(r) * len]);
219
+ };
220
+
221
+ /**
222
+ * A speed letter: the glyph less its slit, slanted 17°.
223
+ *
224
+ * @param {import('../geometry/types.js').Geom} glyph The upright glyph.
225
+ * @param {import('../geometry/types.js').Geom} cut Its slit.
226
+ * @returns {import('../geometry/types.js').Geom} The letter.
227
+ */
228
+ export const speed = (glyph, cut) => italic(D(glyph, cut), 17);
229
+
230
+ /** The speed slab capitals (N M Z K X S R A), each a function returning the upright glyph on SPEED_BOX. */
231
+ export const SPEED_GLYPHS = {
232
+ N: () => U(vt(L0, L0 + SW), vt(R0 - SW, R0), poly([[L0, T0], [L0 + SW + 18, T0], [R0, B0 - 150], [R0, B0], [R0 - SW - 18, B0], [L0, T0 + 150]])),
233
+ M: () => poly([[40, T0], [150, T0], [C, 262], [362, T0], [472, T0], [472, B0], [368, B0], [368, 232], [C + 30, B0], [C - 30, B0], [144, 232], [144, B0], [40, B0]]),
234
+ Z: () => U(hz(T0, T0 + BW), hz(B0 - BW, B0), poly([[R0 - 140, T0 + BW], [R0, T0 + BW], [L0 + 140, B0 - BW], [L0, B0 - BW]])),
235
+ K: () => {
236
+ const a = [L0 + SW - 10, 372], b = [R0, T0], p = (t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
237
+ return U(vt(L0, L0 + SW), poly([[L0 + SW - 10, 236], [R0 - 128, T0], [R0, T0], a]), poly([p(0.3), p(0.62), [R0, B0], [R0 - 126, B0]]));
238
+ },
239
+ X: () => U(poly([[L0, T0], [L0 + 124, T0], [R0, B0], [R0 - 124, B0]]), poly([[R0 - 124, T0], [R0, T0], [L0 + 124, B0], [L0, B0]])),
240
+ S: () => D(U(hz(T0, T0 + BW), vt(L0, L0 + SW, T0, 302), hz(210, 302), vt(R0 - SW, R0, 210, B0), hz(B0 - BW, B0)), poly([[L0 - 1, T0 - 1], [L0 + 76, T0 - 1], [L0 - 1, T0 + 76]]), poly([[R0 + 1, B0 + 1], [R0 - 76, B0 + 1], [R0 + 1, B0 - 76]])),
241
+ R: () => U(vt(L0, L0 + SW), hz(T0, T0 + BW), vt(R0 - SW, R0, T0, 314), hz(222, 314), poly([[R0 - SW - 70, 314], [R0 - SW + 44, 314], [R0, B0], [R0 - 120, B0]])),
242
+ A: () => {
243
+ const out = poly([[166, T0], [346, T0], [R0, B0], [L0, B0]]), inner = poly([[226, T0 + BW], [286, T0 + BW], [312, 290], [200, 290]]), foot = poly([[178, 372], [334, 372], [358, B0 + 4], [154, B0 + 4]]);
244
+ return D(out, inner, foot);
245
+ },
246
+ };
@@ -0,0 +1,40 @@
1
+ import type { Geom, Point, Ring, Shape } from '../geometry/types.js';
2
+
3
+ /** A rail: s in 0…1 to a point. */
4
+ export type Rail = (s: number) => Point;
5
+ /** A shard of a strip: its ring, its place along the strip and the rail its long side lies on. */
6
+ export interface Shard {
7
+ r: Ring;
8
+ s: number;
9
+ base: 'inner' | 'outer';
10
+ c?: Point;
11
+ }
12
+ /** A shard's shrink, turn and move. */
13
+ export interface ShardMove {
14
+ k?: number;
15
+ deg?: number;
16
+ d?: readonly number[];
17
+ }
18
+
19
+ /** A rail along a polyline, by arc length. */
20
+ export function rail(pts: readonly Point[]): Rail;
21
+ /** The run of a closed outline from the vertex nearest p to the vertex nearest q. */
22
+ export function outlineRun(ring: readonly Point[], p: readonly number[], q: readonly number[]): Point[];
23
+ /** The two rails of a band w wide along a centreline, mitred: [left, right]. */
24
+ export function bandRails(pts: readonly Point[], w: number): [Rail, Rail];
25
+ /** The triangular shards of a band between two rails, smaller down the strip. */
26
+ export function shardStrip(inner: Rail, outer: Rail, o?: { n?: number; ratio?: number; jit?: number; rng?: () => number; arcN?: number }): Shard[];
27
+ /** A shard inset by half a gap, its tips cleared. */
28
+ export function inset(r: readonly Point[], g: number): Ring | undefined;
29
+ /** The form less the band, cut back half a gap along the crack. */
30
+ export function cutBack(shape: Geom, inner: Rail, outer: Rail, i0: readonly number[], g: number, k?: number): Shape;
31
+ /** The shards moved off the body, each pushed on until it is g clear. */
32
+ export function drift(body: Geom, shards: readonly Shard[], o: { move: (p: Shard & { c: Point }) => ShardMove; g: number; step?: number; min: number }): Geom[];
33
+ /** A move spreading the shards out from a point. */
34
+ export function spread(from: readonly number[], grow: readonly number[], shrink?: number, turn?: number): (p: Shard & { c: Point }) => ShardMove;
35
+ /** A form breaking on one side into drifting shards. */
36
+ export function breakOff(shape: Geom, inner: Rail, outer: Rail, o: { n?: number; ratio?: number; jit?: number; all?: boolean; move: (p: Shard & { c: Point }) => ShardMove; g: number; min?: number }): Shape;
37
+ /** The house recipe: breakOff() spread from the centre, as a build of the gap. */
38
+ export function houseBreak(shape: Geom, inner: Rail, outer: Rail, o?: { from?: readonly number[]; grow?: readonly number[]; shrink?: number; turn?: number; n?: number; ratio?: number; jit?: number; all?: boolean; min?: number }): (g: number) => Shape;
39
+ /** The point k of the way from p to q. */
40
+ export function lerpAt(p: readonly number[], q: readonly number[], k: number): Point;