@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,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weapons and blades: the parts a knife, a sword, an axe, an archery or a shooting mark is drawn from. Every part is drawn
|
|
3
|
+
* about the origin (y down), pointing right (+x) unless it says otherwise, and returns its pieces separately so a mark can
|
|
4
|
+
* part them with the house gap (`chain()`, `over()`). From the Logomarks tools (weaponparts.mjs, batch 173).
|
|
5
|
+
*/
|
|
6
|
+
import { U, poly, circle, rect, rr, sample, over, move, mapPts, annulus, joinPts as run } from '../geometry/index.js';
|
|
7
|
+
import { turn } from '../parts/index.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Turns a shape drawn about the origin by `deg` (clockwise on screen), then moves it by (dx, dy).
|
|
11
|
+
*
|
|
12
|
+
* @param {import('../geometry/types.js').Geom} s The shape.
|
|
13
|
+
* @param {number} deg The turn, in degrees.
|
|
14
|
+
* @param {number} [dx=0] The move, x.
|
|
15
|
+
* @param {number} [dy=0] The move, y.
|
|
16
|
+
* @returns {import('../geometry/types.js').Shape} The placed shape.
|
|
17
|
+
*/
|
|
18
|
+
export const place = (s, deg, dx = 0, dy = 0) => move(turn(s, deg), dx, dy);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mirrors a shape left to right about x = 0.
|
|
22
|
+
*
|
|
23
|
+
* @param {import('../geometry/types.js').Geom} s The shape.
|
|
24
|
+
* @returns {import('../geometry/types.js').Shape} The mirrored shape.
|
|
25
|
+
*/
|
|
26
|
+
export const flip = (s) => mapPts(s, ([x, y]) => [-x, y]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A quadratic Bézier from p0 through the control point c to p1, as n + 1 points.
|
|
30
|
+
*
|
|
31
|
+
* @param {readonly number[]} p0 The start.
|
|
32
|
+
* @param {readonly number[]} c The control point.
|
|
33
|
+
* @param {readonly number[]} p1 The end.
|
|
34
|
+
* @param {number} [n=24] The steps.
|
|
35
|
+
* @returns {import('../geometry/types.js').Point[]} The curve.
|
|
36
|
+
*/
|
|
37
|
+
export const bez = (p0, c, p1, n = 24) => sample((t) => [0, 1].map((d) => (1 - t) ** 2 * p0[d] + 2 * (1 - t) * t * c[d] + t * t * p1[d]), 0, 1, n);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A blade from the heel (x = 0) to the tip (x = L), spine on top, H deep at the heel, as a polygon's points: 'chef' (the
|
|
41
|
+
* spine dropping and the edge's belly rising to a tip a quarter down), 'santoku' (a flat edge, the spine rounding down to
|
|
42
|
+
* it), 'drop' (spine and edge meeting on the centre line), 'cleaver' (a tall square blade).
|
|
43
|
+
*
|
|
44
|
+
* @param {'chef' | 'santoku' | 'drop' | 'cleaver'} kind The blade.
|
|
45
|
+
* @param {number} L Its length.
|
|
46
|
+
* @param {number} H Its depth at the heel.
|
|
47
|
+
* @returns {import('../geometry/types.js').Point[]} The outline.
|
|
48
|
+
* @throws {Error} For a kind it doesn't know.
|
|
49
|
+
*/
|
|
50
|
+
export const bladePts = (kind, L, H) => {
|
|
51
|
+
const t = -H / 2, b = H / 2;
|
|
52
|
+
if (kind === 'chef') return run([[0, b], [0, t], [L * 0.55, t]], bez([L * 0.55, t], [L * 0.86, t], [L, t + H * 0.28]), bez([L, t + H * 0.28], [L * 0.8, b], [L * 0.42, b]));
|
|
53
|
+
if (kind === 'santoku') return run([[0, b], [0, t], [L * 0.6, t]], bez([L * 0.6, t], [L, t], [L, b]));
|
|
54
|
+
if (kind === 'drop') return run([[0, b], [0, t], [L * 0.6, t]], bez([L * 0.6, t], [L * 0.9, t + H * 0.1], [L, H * 0.06]), bez([L, H * 0.06], [L * 0.82, b], [L * 0.5, b]));
|
|
55
|
+
if (kind === 'cleaver') return [[0, b], [0, t], [L, t], [L, b - 18], [L - 18, b]];
|
|
56
|
+
throw new Error('blade ' + kind);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A knife's grip: a handle from x = x1 back to x = x1 − L, h deep, its top on y = top, the butt rounded.
|
|
61
|
+
*
|
|
62
|
+
* @param {number} x1 Its front end.
|
|
63
|
+
* @param {number} L Its length.
|
|
64
|
+
* @param {number} top Its top.
|
|
65
|
+
* @param {number} h Its depth.
|
|
66
|
+
* @param {number} [r=h/2] The butt's radius.
|
|
67
|
+
* @returns {import('../geometry/types.js').Polygon} The grip.
|
|
68
|
+
*/
|
|
69
|
+
export const grip = (x1, L, top, h, r = h / 2) => rr(x1 - L, top, L, h, [r, 8, 8, r]);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A knife pointing right: the `kind` blade (L long, H deep) and its handle (HL long, HH deep, sitting `hy` below the
|
|
73
|
+
* spine), parted by the gap.
|
|
74
|
+
*
|
|
75
|
+
* @param {number} g The gap.
|
|
76
|
+
* @param {{ kind?: 'chef' | 'santoku' | 'drop' | 'cleaver', L?: number, H?: number, HL?: number, HH?: number, hy?: number }} [o] The blade and handle.
|
|
77
|
+
* @returns {{ blade: import('../geometry/types.js').Polygon, handle: import('../geometry/types.js').Polygon }} The pieces.
|
|
78
|
+
*/
|
|
79
|
+
export const knife = (g, { kind = 'chef', L = 280, H = 100, HL = 170, HH = 56, hy = 6 } = {}) => ({
|
|
80
|
+
blade: poly(bladePts(kind, L, H)), handle: grip(-g, HL, -H / 2 + hy, HH) });
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A straight sword pointing up: a blade W wide (L long, its last `tip` a point), a crossguard bar, a grip and a round
|
|
84
|
+
* pommel, each parted from the next by the gap, the guard's top on y = 0.
|
|
85
|
+
*
|
|
86
|
+
* @param {number} g The gap.
|
|
87
|
+
* @param {{ L?: number, W?: number, tip?: number, guard?: number[], hilt?: number[], pommel?: number, taper?: number }} [o] The sizes: guard and hilt are [width, height].
|
|
88
|
+
* @returns {{ blade: import('../geometry/types.js').Polygon, guard: import('../geometry/types.js').Polygon, hilt: import('../geometry/types.js').Polygon, pommel: import('../geometry/types.js').Polygon }} The pieces.
|
|
89
|
+
*/
|
|
90
|
+
export const sword = (g, { L = 300, W = 56, tip = 70, guard = [176, 30], hilt = [36, 92], pommel = 26, taper = 0.78 } = {}) => {
|
|
91
|
+
const w2 = W / 2, w1 = (W * taper) / 2, blade = poly([[-w2, -g], [-w1, -g - L + tip], [0, -g - L], [w1, -g - L + tip], [w2, -g]]);
|
|
92
|
+
const gd = rr(-guard[0] / 2, 0, guard[0], guard[1], guard[1] / 2), y1 = guard[1] + g;
|
|
93
|
+
const h = rect(-hilt[0] / 2, y1, hilt[0], hilt[1]), p = circle(0, y1 + hilt[1] + g + pommel, pommel, 120);
|
|
94
|
+
return { blade, guard: gd, hilt: h, pommel: p };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Two copies of a shape drawn pointing up, crossed in an X at ±deg about the origin, the right-leaning one in front, cut
|
|
99
|
+
* free by the gap.
|
|
100
|
+
*
|
|
101
|
+
* @param {import('../geometry/types.js').Geom} s The shape.
|
|
102
|
+
* @param {number} deg The lean, in degrees.
|
|
103
|
+
* @param {number} g The gap.
|
|
104
|
+
* @returns {import('../geometry/types.js').Shape} The pair.
|
|
105
|
+
*/
|
|
106
|
+
export const crossed = (s, deg, g) => over(turn(flip(s), -deg), turn(s, deg), g);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A katana pointing right along a gentle curve (the tip rising): the grip (tsuka), the guard (tsuba, a plate seen edge on)
|
|
110
|
+
* and either the drawn blade with its angled point or the scabbard (saya), each parted by the gap. `bend` is how far the
|
|
111
|
+
* ends rise over the middle.
|
|
112
|
+
*
|
|
113
|
+
* @param {number} g The gap.
|
|
114
|
+
* @param {{ sheathed?: boolean, L?: number, W?: number, tsukaL?: number, tsuba?: number[], bend?: number }} [o] The sword.
|
|
115
|
+
* @returns {{ tsuka: import('../geometry/types.js').Shape, tsuba: import('../geometry/types.js').Shape, blade: import('../geometry/types.js').Shape }} The pieces.
|
|
116
|
+
*/
|
|
117
|
+
export const katana = (g, { sheathed = false, L = 448, W = 46, tsukaL = 128, tsuba = [30, 104], bend = 40 } = {}) => {
|
|
118
|
+
const x0 = -L / 2, xt = x0 + tsukaL, xb = xt + g + tsuba[0] + g, w2 = W / 2;
|
|
119
|
+
const tsuka = rr(x0, -w2 + 2, tsukaL, W - 4, [20, 4, 4, 20]), guard = rr(xt + g, -tsuba[1] / 2, tsuba[0], tsuba[1], 12);
|
|
120
|
+
const len = L / 2 - xb, sw = sheathed ? W + 6 : W;
|
|
121
|
+
const blade = sheathed ? rr(xb, -sw / 2, len, sw, [4, sw / 2, sw / 2, 4])
|
|
122
|
+
: poly([[xb, -w2], [L / 2, -w2], ...bez([L / 2, -w2], [L / 2 - 14, w2 - 6], [L / 2 - 64, w2]).slice(1), [xb, w2]]);
|
|
123
|
+
const curve = (s) => mapPts(s, ([x, y]) => [x, y - bend * ((2 * x) / L) ** 2]);
|
|
124
|
+
return { tsuka: curve(tsuka), tsuba: curve(guard), blade: curve(blade) };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A felling axe pointing up: a haft W wide from y = bottom up into the head, and the head (poll on the left, the bit
|
|
129
|
+
* flaring to a convex edge on the right), the haft parted from the head by the gap.
|
|
130
|
+
*
|
|
131
|
+
* @param {number} g The gap.
|
|
132
|
+
* @param {{ W?: number, poll?: number, bit?: number, top?: number, bottom?: number, edge?: number }} [o] The sizes.
|
|
133
|
+
* @returns {{ head: import('../geometry/types.js').Polygon, haft: import('../geometry/types.js').Polygon }} The pieces.
|
|
134
|
+
*/
|
|
135
|
+
export const axe = (g, { W = 46, poll = 64, bit = 196, top = -224, bottom = 224, edge = 1 } = {}) => {
|
|
136
|
+
const hy = top + 44, head = poly(run([[-poll, hy - 40], [W / 2 + 26, hy - 34]], bez([W / 2 + 26, hy - 34], [bit * 0.62, hy - 30], [bit, top]),
|
|
137
|
+
bez([bit, top], [bit + 36 * edge, hy + 70], [bit - 8, hy + 168]), bez([bit - 8, hy + 168], [bit * 0.6, hy + 84], [W / 2 + 26, hy + 56]), [[-poll, hy + 50]]));
|
|
138
|
+
const haft = rr(-W / 2, hy - 20, W, bottom - hy + 20, [0, 0, W / 2, W / 2]);
|
|
139
|
+
return { head, haft };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The axe as one mark: the head in front of the haft, cut free by the gap.
|
|
144
|
+
*
|
|
145
|
+
* @param {number} g The gap.
|
|
146
|
+
* @param {{ W?: number, poll?: number, bit?: number, top?: number, bottom?: number, edge?: number }} [o] axe()'s sizes.
|
|
147
|
+
* @returns {import('../geometry/types.js').Shape} The axe.
|
|
148
|
+
*/
|
|
149
|
+
export const axeCut = (g, o) => { const { head, haft } = axe(g, o); return over(haft, head, g); };
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* An arrow pointing right from x = x0 to x = x1: a shaft w wide, a head `hl` long and `hw` wide parted from the shaft by
|
|
153
|
+
* the gap, and the fletching, two swept vanes on the tail.
|
|
154
|
+
*
|
|
155
|
+
* @param {number} g The gap.
|
|
156
|
+
* @param {number} x0 The tail.
|
|
157
|
+
* @param {number} x1 The tip.
|
|
158
|
+
* @param {{ w?: number, hl?: number, hw?: number, vl?: number, vw?: number }} [o] The shaft's width, the head's length and width, the vanes' length and reach.
|
|
159
|
+
* @returns {{ shaft: import('../geometry/types.js').Polygon, head: import('../geometry/types.js').Polygon, vanes: import('../geometry/types.js').Shape }} The pieces.
|
|
160
|
+
*/
|
|
161
|
+
export const arrow = (g, x0, x1, { w = 22, hl = 74, hw = 70, vl = 92, vw = 34 } = {}) => {
|
|
162
|
+
const head = poly([[x1, 0], [x1 - hl, -hw / 2], [x1 - hl * 0.8, 0], [x1 - hl, hw / 2]]);
|
|
163
|
+
const shaft = rect(x0, -w / 2, x1 - hl * 0.8 - g - x0, w);
|
|
164
|
+
const vane = (s) => poly([[x0 + vl, s * w / 2 - 1], [x0 + vl - 34, s * (w / 2 + vw)], [x0, s * (w / 2 + vw)], [x0 + 34, s * w / 2 - 1]]);
|
|
165
|
+
return { shaft, head, vanes: U(vane(-1), vane(1)) };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Target rings about the origin: a bull of radius r0, then n rings `w` wide, each a gap out from the last.
|
|
170
|
+
*
|
|
171
|
+
* @param {number} g The gap.
|
|
172
|
+
* @param {{ r0?: number, w?: number, n?: number }} [o] The bull's radius, the rings' width and how many.
|
|
173
|
+
* @returns {import('../geometry/types.js').Shape} The target.
|
|
174
|
+
*/
|
|
175
|
+
export const rings = (g, { r0 = 60, w = 50, n = 2 } = {}) => {
|
|
176
|
+
const out = [circle(0, 0, r0, 240)]; let r = r0;
|
|
177
|
+
for (let i = 0; i < n; i++) { r += g; out.push(annulus(0, 0, r + w / 2, w)); r += w; }
|
|
178
|
+
return U(...out);
|
|
179
|
+
};
|
package/src/parts/index.d.ts
CHANGED
package/src/parts/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Geom } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** A span of pixels: [from, to, n], or [null, null, n] for n empty pixels. */
|
|
4
|
+
export type PixelSpan = readonly [number | null, number | null, number];
|
|
5
|
+
|
|
6
|
+
/** Whether a point is inside a kit shape (even-odd). */
|
|
7
|
+
export function insideShape(shape: Geom, x: number, y: number): boolean;
|
|
8
|
+
/** A mark as rows of text ('#' a pixel) over spans of pixels on each axis. */
|
|
9
|
+
export function rasterRows(shape: Geom, xs: readonly PixelSpan[], ys: readonly PixelSpan[], options?: { min?: number; k?: number }): string[];
|
|
10
|
+
/** A whole mark as rows of text on square pixels, n along its longest side, phase (fx, fy), cells to toggle. */
|
|
11
|
+
export function rasterMark(shape: Geom, n: number, fx?: number, fy?: number, options?: { min?: number; fix?: readonly (readonly number[])[] }): string[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pixel editions: a drawn mark sampled into rows of text ('#' a pixel), the format PIX7 and the pixel animals use. From
|
|
3
|
+
* the Logomarks tools' dither parts (batches 210 and 234).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A kit shape as a polygon set (the kit's primitives return one polygon, its booleans a set).
|
|
8
|
+
*
|
|
9
|
+
* @param {import('../geometry/types.js').Geom} shape The shape.
|
|
10
|
+
* @returns {import('../geometry/types.js').Shape} A polygon set.
|
|
11
|
+
*/
|
|
12
|
+
const polys = (shape) => /** @type {import('../geometry/types.js').Shape} */ (typeof shape[0][0][0] === 'number' ? [shape] : shape);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a point is inside a kit shape, even-odd over every ring.
|
|
16
|
+
*
|
|
17
|
+
* @param {import('../geometry/types.js').Geom} shape The shape.
|
|
18
|
+
* @param {number} x The point.
|
|
19
|
+
* @param {number} y The point.
|
|
20
|
+
* @returns {boolean} True when it is.
|
|
21
|
+
*/
|
|
22
|
+
export const insideShape = (shape, x, y) => {
|
|
23
|
+
let k = false;
|
|
24
|
+
for (const pg of polys(shape)) for (const r of pg) for (let a = 0, b = r.length - 1; a < r.length; b = a++) {
|
|
25
|
+
const [xa, ya] = r[a], [xb, yb] = r[b];
|
|
26
|
+
if ((ya > y) !== (yb > y) && x < xa + ((y - ya) * (xb - xa)) / (yb - ya)) k = !k;
|
|
27
|
+
}
|
|
28
|
+
return k;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A mark on the 512 canvas as rows of text. Each axis is a list of spans `[from, to, n]`: n pixels across from..to, or
|
|
33
|
+
* `[null, null, n]` for n empty pixels. A tiled mark gives each tile its own span and each gap one empty pixel, so the
|
|
34
|
+
* gaps stay whole pixels (Cove Rise on a 2 × 2 grid: `[[32, 245, 7], [null, null, 1], [267, 480, 7]]`).
|
|
35
|
+
*
|
|
36
|
+
* @param {import('../geometry/types.js').Geom} shape The mark.
|
|
37
|
+
* @param {readonly (readonly (number | null)[])[]} xs The spans across.
|
|
38
|
+
* @param {readonly (readonly (number | null)[])[]} ys The spans down.
|
|
39
|
+
* @param {{ min?: number, k?: number }} [options] A cell is set when the mark covers `min` (0.5) of a k × k (6) sample.
|
|
40
|
+
* @returns {string[]} The rows, '#' a pixel and '.' none.
|
|
41
|
+
*/
|
|
42
|
+
export const rasterRows = (shape, xs, ys, { min = 0.5, k = 6 } = {}) => {
|
|
43
|
+
const sh = polys(shape);
|
|
44
|
+
const cells = (/** @type {readonly (readonly (number | null)[])[]} */ spans) => spans.flatMap(([a, b, n]) => [...Array(/** @type {number} */ (n))].map((_, i) =>
|
|
45
|
+
(a == null ? null : [a + ((/** @type {number} */ (b) - a) * i) / /** @type {number} */ (n), a + ((/** @type {number} */ (b) - a) * (i + 1)) / /** @type {number} */ (n)])));
|
|
46
|
+
const X = cells(xs), Y = cells(ys);
|
|
47
|
+
return Y.map((y) => X.map((x) => {
|
|
48
|
+
if (!x || !y) return '.';
|
|
49
|
+
let n = 0;
|
|
50
|
+
for (let a = 0; a < k; a++) for (let b = 0; b < k; b++) n += +insideShape(sh, x[0] + ((x[1] - x[0]) * (a + 0.5)) / k, y[0] + ((y[1] - y[0]) * (b + 0.5)) / k);
|
|
51
|
+
return n / (k * k) >= min ? '#' : '.';
|
|
52
|
+
}).join(''));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A whole mark as rows of text on square pixels, for the pixel edition of a non-tiled mark: n pixels along its longest
|
|
57
|
+
* side, the grid starting (fx, fy) pixels before its top-left (sweep this phase so the gaps land on whole empty pixels),
|
|
58
|
+
* then each `fix` cell [i, j] toggled by hand (to fill a corner-only contact or clear a stray).
|
|
59
|
+
*
|
|
60
|
+
* @param {import('../geometry/types.js').Geom} shape The mark.
|
|
61
|
+
* @param {number} n Pixels along its longest side.
|
|
62
|
+
* @param {number} [fx] The grid's phase across, in pixels (0).
|
|
63
|
+
* @param {number} [fy] Its phase down (0).
|
|
64
|
+
* @param {{ min?: number, fix?: readonly (readonly number[])[] }} [options] The coverage a pixel needs (0.5), and cells to toggle.
|
|
65
|
+
* @returns {string[]} The rows.
|
|
66
|
+
*/
|
|
67
|
+
export const rasterMark = (shape, n, fx = 0, fy = 0, { min = 0.5, fix = [] } = {}) => {
|
|
68
|
+
const pts = polys(shape).flat(2), xs = pts.map((p) => p[0]), ys = pts.map((p) => p[1]);
|
|
69
|
+
const x0 = Math.min(...xs), x1 = Math.max(...xs), y0 = Math.min(...ys), y1 = Math.max(...ys), p = Math.max(x1 - x0, y1 - y0) / n;
|
|
70
|
+
const nx = Math.ceil((x1 - x0) / p + fx), ny = Math.ceil((y1 - y0) / p + fy), ox = x0 - fx * p, oy = y0 - fy * p;
|
|
71
|
+
const rows = rasterRows(shape, [[ox, ox + nx * p, nx]], [[oy, oy + ny * p, ny]], { min }).map((r) => [...r]);
|
|
72
|
+
for (const [i, j] of fix) rows[j][i] = rows[j][i] === '#' ? '.' : '#';
|
|
73
|
+
return rows.map((r) => r.join(''));
|
|
74
|
+
};
|
package/src/parts/run.js
CHANGED
|
@@ -7,12 +7,14 @@ import { seeded, toPath } from '../geometry/index.js';
|
|
|
7
7
|
import { exact } from './frame.js';
|
|
8
8
|
|
|
9
9
|
/** Writes a batch: every mark in `M` ({ name: { trade, style, fn(g) } }) drawn with exact() so its gap is 22 once fitted,
|
|
10
|
-
* seeded per mark (a re-run is byte-identical); `node genbatchN.mjs <workdir> ["Name,Name" | "fam:Industry · Law"]`.
|
|
10
|
+
* seeded per mark (a re-run is byte-identical); `node genbatchN.mjs <workdir> ["Name,Name" | "fam:Industry · Law"]`.
|
|
11
|
+
* `fam:` also takes the trade as written, for batches that pass a whole family as the trade ("fam:Letters · Latch").
|
|
12
|
+
* New batches use `runBatch` from `@logomarks/core/batch`. */
|
|
11
13
|
export const runIndustry = (batch, M) => {
|
|
12
14
|
const [S, only = ''] = process.argv.slice(2), OUT = path.join(S, `newraw${batch}`), slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
13
15
|
fs.mkdirSync(OUT, { recursive: true }); let n = 0;
|
|
14
16
|
for (const [name, { trade, fn }] of Object.entries(M)) {
|
|
15
|
-
if (only.startsWith('fam:') ? 'Industry · ' + trade
|
|
17
|
+
if (only.startsWith('fam:') ? !['Industry · ' + trade, trade].includes(only.slice(4)) : only && !only.split(',').includes(name)) continue;
|
|
16
18
|
try { const s = exact(fn, () => { Math.random = seeded(name); });
|
|
17
19
|
fs.writeFileSync(path.join(OUT, slug(name) + '.svg'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><path fill-rule="evenodd" d="${toPath(s)}"/></svg>\n`); n++; }
|
|
18
20
|
catch (e) { console.log('FAIL', name, e.message.slice(0, 160)); } }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Geom, Shape } from '../geometry/types.js';
|
|
2
|
+
import type { Cell } from './tiles.js';
|
|
3
|
+
|
|
4
|
+
/** One tile token over a w × h block of cells, the gaps inside it filled (oblong blocks: S, t<k>, d<k>, h<k>, q<k>). */
|
|
5
|
+
export function spanTile(tok: string, cs: readonly Cell[], n: number, i: number, j: number, w: number, h: number): Geom;
|
|
6
|
+
/** A grid of tiles from rows of tokens, a token spanning cells as tok^2, tok>2, tok_2 or tok@2x3. */
|
|
7
|
+
export function spanGrid(rows: readonly string[], s: number): Shape;
|
|
8
|
+
/** A shape's longest side. */
|
|
9
|
+
export function longSide(sh: Geom): number;
|
|
10
|
+
/** The cell size that draws the rows 422 at their longest with the gap 22. */
|
|
11
|
+
export function evenCell(rows: readonly string[]): number;
|
|
12
|
+
/** The span grid at evenCell(rows). */
|
|
13
|
+
export function evenGrid(rows: readonly string[]): Shape;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Span tiles: a grid of tile() tokens on the 422 span with the house gap, where one token may span a block of cells (the
|
|
3
|
+
* gaps inside it filled), so a big tile sits among unit tiles. From the Logomarks tools (batch 269, shared since 280).
|
|
4
|
+
*/
|
|
5
|
+
import { rr, rect, poly, bbox, ellipse, I, U } from '../geometry/index.js';
|
|
6
|
+
import { gridCells, tile } from './tiles.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One tile token over a w × h block of cells, the gaps inside the block filled. A square block takes every tile() token;
|
|
10
|
+
* an oblong one takes `S` (a slab), `t<k>` (a right triangle keeping corner k: tl, tr, br, bl), `d<k>` (a slab with side
|
|
11
|
+
* k fully round: t, r, b, l; `dp` a pill), `h<k>` (a half ellipse filling the block, flat on side k) and `q<k>` (a
|
|
12
|
+
* quarter ellipse centred on corner k).
|
|
13
|
+
*
|
|
14
|
+
* @param {string} tok The token.
|
|
15
|
+
* @param {readonly import('./tiles.js').Cell[]} cs The grid's cells (gridCells), row by row.
|
|
16
|
+
* @param {number} n Cells across.
|
|
17
|
+
* @param {number} i The block's first column.
|
|
18
|
+
* @param {number} j Its first row.
|
|
19
|
+
* @param {number} w Its width, in cells.
|
|
20
|
+
* @param {number} h Its height, in cells.
|
|
21
|
+
* @returns {import('../geometry/types.js').Geom} The tile.
|
|
22
|
+
* @throws {Error} For a token an oblong block doesn't take.
|
|
23
|
+
*/
|
|
24
|
+
export const spanTile = (tok, cs, n, i, j, w, h) => {
|
|
25
|
+
const a = cs[j * n + i], b = cs[(j + h - 1) * n + (i + w - 1)], x = a.x, y = a.y, W = b.x + b.s - a.x, H = b.y + b.s - a.y;
|
|
26
|
+
if (Math.abs(W - H) < 1e-6) return tile(tok, { i, j, x, y, s: W });
|
|
27
|
+
if (tok === 'S') return rect(x, y, W, H);
|
|
28
|
+
if (tok[0] === 't') {
|
|
29
|
+
/** @type {Record<string, number[]>} */ const K = { tl: [x, y], tr: [x + W, y], br: [x + W, y + H], bl: [x, y + H] };
|
|
30
|
+
const O = ['tl', 'tr', 'br', 'bl'], q = O.indexOf(tok.slice(1));
|
|
31
|
+
return poly([K[O[(q + 3) % 4]], K[O[q]], K[O[(q + 1) % 4]]]);
|
|
32
|
+
}
|
|
33
|
+
if (tok[0] === 'd') {
|
|
34
|
+
const r = Math.min(W, H) / 2, k = tok[1];
|
|
35
|
+
return rr(x, y, W, H, /** @type {Record<string, number[]>} */ ({ t: [r, r, 0, 0], r: [0, r, r, 0], b: [0, 0, r, r], l: [r, 0, 0, r], p: [r, r, r, r] })[k]);
|
|
36
|
+
}
|
|
37
|
+
if (tok[0] === 'h') {
|
|
38
|
+
const k = tok[1], box = rect(x, y, W, H), v = k === 't' || k === 'b';
|
|
39
|
+
const [cx, cy] = /** @type {Record<string, number[]>} */ ({ t: [x + W / 2, y], b: [x + W / 2, y + H], l: [x, y + H / 2], r: [x + W, y + H / 2] })[k];
|
|
40
|
+
return I(ellipse(cx, cy, v ? W / 2 : W, v ? H : H / 2, 0, 400), box);
|
|
41
|
+
}
|
|
42
|
+
if (tok[0] === 'q') {
|
|
43
|
+
const k = tok.slice(1), cx = k[1] === 'r' ? x + W : x, cy = k[0] === 'b' ? y + H : y;
|
|
44
|
+
return I(ellipse(cx, cy, W, H, 0, 400), rect(x, y, W, H));
|
|
45
|
+
}
|
|
46
|
+
throw new Error('span tile ' + tok);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A grid of tiles from rows of tokens, where a token may span cells: `tok^2` a 2 × 2 block (`tok^3` 3 × 3), `tok>2` two
|
|
51
|
+
* cells across, `tok_2` two cells down, `tok@2x3` any block (2 across, 3 down). The cells a block covers are written `-`,
|
|
52
|
+
* an empty cell `.`.
|
|
53
|
+
*
|
|
54
|
+
* @param {readonly string[]} rows The rows of tokens.
|
|
55
|
+
* @param {number} s The cell's size.
|
|
56
|
+
* @returns {import('../geometry/types.js').Shape} The grid.
|
|
57
|
+
*/
|
|
58
|
+
export const spanGrid = (rows, s) => {
|
|
59
|
+
const g = rows.map((r) => r.trim().split(/\s+/)), n = Math.max(...g.map((r) => r.length)), m = g.length, cs = gridCells(n, m, { s }), out = [];
|
|
60
|
+
g.forEach((row, j) => row.forEach((t, i) => {
|
|
61
|
+
if (t === '.' || t === '-') return;
|
|
62
|
+
const bx = t.match(/^(.+?)@(\d)x(\d)$/);
|
|
63
|
+
if (bx) { out.push(spanTile(bx[1], cs, n, i, j, +bx[2], +bx[3])); return; }
|
|
64
|
+
const mm = t.match(/^(.+?)(?:\^(\d)|>(\d)|_(\d))$/);
|
|
65
|
+
if (!mm) { out.push(tile(t, cs[j * n + i])); return; }
|
|
66
|
+
const [, tok, sq, wd, ht] = mm, w = +(sq || wd || 1), h = +(sq || ht || 1);
|
|
67
|
+
out.push(spanTile(tok, cs, n, i, j, w, h));
|
|
68
|
+
}));
|
|
69
|
+
return U(...out);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A shape's longest side.
|
|
74
|
+
*
|
|
75
|
+
* @param {import('../geometry/types.js').Geom} sh The shape.
|
|
76
|
+
* @returns {number} Its longer box side.
|
|
77
|
+
*/
|
|
78
|
+
export const longSide = (sh) => { const [x0, y0, x1, y1] = bbox(sh); return Math.max(x1 - x0, y1 - y0); };
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The cell size that draws `rows` so the mark (not the grid) is 422 at its longest with the gap 22, so a mark whose
|
|
82
|
+
* tiles stop short of the grid's edge fits to 448 with the family's gap. The longest side is linear in the cell, so
|
|
83
|
+
* two trial builds find it.
|
|
84
|
+
*
|
|
85
|
+
* @param {readonly string[]} rows The rows of tokens.
|
|
86
|
+
* @returns {number} The cell size.
|
|
87
|
+
*/
|
|
88
|
+
export const evenCell = (rows) => {
|
|
89
|
+
const n = Math.max(...rows.map((r) => r.trim().split(/\s+/).length), rows.length), s0 = (422 - (n - 1) * 22) / n;
|
|
90
|
+
const a = longSide(spanGrid(rows, s0)), b = longSide(spanGrid(rows, s0 * 1.2));
|
|
91
|
+
return s0 + ((422 - a) * 0.2 * s0) / (b - a);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The span grid at evenCell(rows).
|
|
96
|
+
*
|
|
97
|
+
* @param {readonly string[]} rows The rows of tokens.
|
|
98
|
+
* @returns {import('../geometry/types.js').Shape} The grid.
|
|
99
|
+
*/
|
|
100
|
+
export const evenGrid = (rows) => spanGrid(rows, evenCell(rows));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Box, Geom, Shape } from '../geometry/types.js';
|
|
2
|
+
|
|
3
|
+
/** Cells: [i, j], or tapered [i, j, f] / [i, j, f, deg] (f the size as a share of the pitch). */
|
|
4
|
+
export type Cells = readonly (readonly number[])[];
|
|
5
|
+
/** A ramp: a cell centre (grid units) to t (≤ 0 solid, 1 gone). */
|
|
6
|
+
export type Ramp = (x: number, y: number) => number;
|
|
7
|
+
/** A threshold mask: (i, j) to a value in (0, 1). */
|
|
8
|
+
export type Mask = (i: number, j: number) => number;
|
|
9
|
+
/** taper()'s settings. */
|
|
10
|
+
export interface TaperOptions {
|
|
11
|
+
ramp: Ramp;
|
|
12
|
+
n?: number;
|
|
13
|
+
size?: boolean;
|
|
14
|
+
density?: number;
|
|
15
|
+
mask?: string | Mask;
|
|
16
|
+
gamma?: number;
|
|
17
|
+
min?: number;
|
|
18
|
+
turn?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The smallest dither cell once fitted (16). */
|
|
22
|
+
export const MIN_CELL: number;
|
|
23
|
+
/** The smallest gap between dither cells once fitted (22). */
|
|
24
|
+
export const MIN_GAP: number;
|
|
25
|
+
/** The fitted span (448). */
|
|
26
|
+
export const SPAN: number;
|
|
27
|
+
/** The n × n Bayer matrix as thresholds in (0, 1). */
|
|
28
|
+
export function bayer(n: number): number[][];
|
|
29
|
+
/** Threshold masks: bayer4, bayer8 (ordered dither) and r2 (low-discrepancy). */
|
|
30
|
+
export const MASKS: Record<'bayer4' | 'bayer8' | 'r2', Mask>;
|
|
31
|
+
/** The cells a shape (in grid units) covers by at least `min` of a k × k sample. */
|
|
32
|
+
export function raster(shape: Geom, o?: { min?: number; k?: number }): [number, number][];
|
|
33
|
+
/** Cells from rows of text ('#' or 'X' a cell), top-left at (i0, j0). */
|
|
34
|
+
export function fromRows(rows: readonly string[], i0?: number, j0?: number): [number, number][];
|
|
35
|
+
/** A w × h block of cells, top-left at (i, j). */
|
|
36
|
+
export function block(i: number, j: number, w: number, h: number): [number, number][];
|
|
37
|
+
/** The cells of a not in b. */
|
|
38
|
+
export function minus(a: Cells, b: Cells): number[][];
|
|
39
|
+
/** A square frame n cells across, walls w thick, top-left at (i, j). */
|
|
40
|
+
export function squareFrame(i: number, j: number, n: number, w: number): number[][];
|
|
41
|
+
/** A comet: the head's cells and a trail behind them, with the ramp that fades it. */
|
|
42
|
+
export function comet(head: Cells, deg: number, len: number, o?: { narrow?: number; lanes?: boolean; edge?: number | null }): { cells: number[][]; ramp: Ramp };
|
|
43
|
+
/** The cells within `depth` steps of a shape's cells, the shape included. */
|
|
44
|
+
export function dilate(cells: Cells, depth?: number): number[][];
|
|
45
|
+
/** A shape's cells less every cell within `depth` steps of its outside. */
|
|
46
|
+
export function erode(cells: Cells, depth?: number): number[][];
|
|
47
|
+
/** A linear ramp: 0 at distance a along deg from (cx, cy), 1 at distance b. */
|
|
48
|
+
export function linear(deg: number, a: number, b: number, cx?: number, cy?: number): Ramp;
|
|
49
|
+
/** A ramp solid (−1) on the `solid` cells and `ramp` elsewhere. */
|
|
50
|
+
export function solidOn(solid: Cells, ramp: Ramp): Ramp;
|
|
51
|
+
/** A ramp by Chebyshev steps from a core: 0 on it, 1 at `depth` steps out. */
|
|
52
|
+
export function steps(core: Cells, depth: number): Ramp;
|
|
53
|
+
/** The dither zone's cell sizes (shares of the pitch p), largest first, in n steps. */
|
|
54
|
+
export function levels(p: number, n: number, min?: number): number[];
|
|
55
|
+
/** The pitch a set of cells gets once its longest side is fitted to 448. */
|
|
56
|
+
export function pitchOf(cells: Cells): number;
|
|
57
|
+
/** Cells tapered by a ramp: [i, j, f] (or [i, j, f, deg]) per kept cell. */
|
|
58
|
+
export function taper(cells: Cells, o: TaperOptions): number[][];
|
|
59
|
+
/** Cells drawn by hand as rows ('#' full, '1'–'9' a dither size, or a legend's [size, deg]). */
|
|
60
|
+
export function mapCells(rows: readonly string[], o?: { n?: number; min?: number; legend?: Record<string, number[]>; i0?: number; j0?: number }): number[][];
|
|
61
|
+
/** Full cells touching only at a corner: each 2 × 2 checker's top-left. */
|
|
62
|
+
export function corners(cells: Cells): number[][];
|
|
63
|
+
/** Tapered cells drawn on the 512 canvas, fitted to 448, every gap checked. */
|
|
64
|
+
export function sprite(cells: Cells, o?: { round?: boolean; r?: number; push?: number[]; allowCorners?: boolean }): Shape;
|
|
65
|
+
/** The upright box of a square of side s turned by deg. */
|
|
66
|
+
export function turnBox(s: number, deg?: number): number;
|
|
67
|
+
/** The side of a square turned by deg whose upright box is b across. */
|
|
68
|
+
export function turnedSide(b: number, deg?: number): number;
|
|
69
|
+
/** Squares [x, y, s, deg] as one shape. */
|
|
70
|
+
export function squares(list: Cells): Shape;
|
|
71
|
+
/** The bounding box of a square list. */
|
|
72
|
+
export function extent(list: Cells): Box;
|
|
73
|
+
/** A square list moved so its box is centred on the canvas. */
|
|
74
|
+
export function centred(list: Cells): number[][];
|
|
75
|
+
/** A resolution run: columns of 1, 2, 3 … squares, H solved so the run is `span` wide. */
|
|
76
|
+
export function resRun(counts: readonly number[], o?: { span?: number; gap?: number }): { list: number[][]; H: number; side: number };
|
|
77
|
+
/** A single file of squares [side, deg] along x, MIN_GAP apart, on one line. */
|
|
78
|
+
export function file(items: Cells, o?: { gap?: number; align?: 'top' | 'bottom' | 'centre'; span?: number }): number[][];
|