@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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,55 @@ All notable changes to `@logomarks/core` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] — 2026-11-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
Every generic parts module the Logomarks generators still kept locally, moved here (the code unchanged, each part documented and typed), so the site keeps only its marks. Six new subpaths, none re-exported from the root:
|
|
13
|
+
|
|
14
|
+
- **`/iso`:** iso objects on `/parts`' `scene()` (cylinders, frustums, lathes, poles, roofs, shops and `shopMark()`, openings cut on the fitted faces with `isoCut()`, cut-outs square to the viewer drawn by `isoFlat()`, tipped slabs), flat planes hinged and folded into one mark (`planeScene()`, `planeMark()`, `planeGlyph()`, iso or oblique `ISO`/`OBL`), and voxels (`voxRight`, `voxPlan`, `pixDepth`, `slabs`, corner monograms `cornerMark()`). From the tools' isoparts, planeparts and blockparts.
|
|
15
|
+
- **`/letters`:** letters built from geometry: a Didone capital alphabet with its frames, interlace and one-move builds (`face`, `SERIF_GLYPHS`, `serifLetter`, `pairOf`, `split`, `ruled`, `framed`…), blackletter from one 30° nib (`nib`, `parted`, `BLACKLETTERS`, `blackLetter`), condensed pairs on one stem (`pillar`, `stack`), the monogram letters, and the Echo, Dot Stroke, Ink Trap and Stair faces, the last two on one 448 frame `T`, `B`, `H`.
|
|
16
|
+
- **`/font`:** a capital from a font file as a shape (`glyphShape`, `fontSource`, grown mitred to a fitted hairline), its measures (`weights`, `hairline`, `structuralHairline`, `thinnestStroke`, `runsH`…) and the moves placed on it (`underCrossbar`, `armLozenge`, `stemSplit`, `inlineOpen`…). `loadFont()` takes a path or the font's bytes and names no folder: the fonts stay with whoever draws with them.
|
|
17
|
+
- **`/pixels`:** dither tapers (cells, ramps, ordered-dither `taper()`, `sprite()`, free squares) and typed pixel emotes (`emote`).
|
|
18
|
+
- **`/objects`:** devices and code glyphs, blades, dishes, the boxing glove and pixel sprites, cells, faces and mascot features, animals (the gecko build, two-track animals, bold outlines) and rosettes.
|
|
19
|
+
- **`/forms`:** the Bold, Line and Track constructions: needle gaps and knobs, split discs, hooked capsules, jogs and chamfer checkers, tech sparkles and ribbons, split emblems, shatter, turning slats, flow grids, fold seams, swashes, rotation, stripes, guilloche and speed letters, the Track editions' kit, and soft blobs, Rail lines and interlocking locks.
|
|
20
|
+
- **`/sdf`** gains the Y2K parts built from its fields: `pinch`, `ballArm`, `node`, `blob`, `stadium`.
|
|
21
|
+
|
|
22
|
+
Renamed where two modules' names met in one subpath (`blackLetter`, `serifLetter`, `monoBowl`, `DOT_S`, `TRAP_S`, `PILLAR_LEFT`, `waveArm`, `geoDisc`, `discAt`, `hookAt`, `discSide`, `techChevron`, `foldQuad`, `swashQuad`, `evenBands`, `evenRing`, `shardStrip`, `GEO_X0`, `EMBLEM_W`, `HOOK_W`, `DISC_R`, `planeMark`, `planeGlyph`…). Copies of what core already had were dropped for core's own (`GAP`, `rad`, `openRing`, `mitreOffset`, `mitreBand`, `joinPts`, `chain`, `filletClosed`), and the vector helpers the families repeated are written once.
|
|
23
|
+
|
|
24
|
+
### Dependencies
|
|
25
|
+
|
|
26
|
+
- `/font` loads three optional peers on first use: **opentype.js** (reading the font), **clipper-lib** (already a peer for `/edit`: `mitreGrow()` keeps Clipper's integer offset, which the exact `mitreOffset`/`offsetPolygon` can't match byte for byte) and **@resvg/resvg-js** (`thinnestStroke()`'s mask: core's own scanline fill was tried and differs on the edge pixels of 172 of 176 glyphs, so it would change the measured hairlines). Nothing else needs them.
|
|
27
|
+
|
|
28
|
+
Proved on the Logomarks generators: all 206 that import these parts redraw byte-identical with core in place of the local modules. The golden tests cover every new export the behaviour tests don't reach.
|
|
29
|
+
|
|
30
|
+
## [0.1.11] — 2026-11-15
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Exact mitred offsets in `/geometry`:** `mitreOffset(pts, d)` (an open polyline's parallel, corners mitred), `mitreBand(pts, w)` (a solid band along it, ends square) and `offsetPolygon(pts, d)` (a closed polygon grown or shrunk, either winding). The Logomarks tools had them five ways; these are the same arithmetic as the ones there.
|
|
35
|
+
- **Span tiles in `/parts`:** `spanGrid`, `spanTile`, `evenGrid`, `evenCell` and `longSide`: a tile grid where one token may span a block of cells (`tok^2`, `tok>2`, `tok_2`, `tok@2x3`), from the tools' span parts.
|
|
36
|
+
- **Pixel editions in `/parts`:** `rasterRows`, `rasterMark` (a drawn mark sampled into rows of text, `#` a pixel, on spans or square pixels with a phase and hand fixes) and `insideShape` (even-odd point in shape), from the tools' dither parts.
|
|
37
|
+
- **Golden tests:** a hash of a drawing by every geometry and parts export the other tests don't reach (`tests/golden.json`; refresh with `GOLDEN=update npm test`).
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `nest()`'s type: `cut` is a function `(w, h, side) => number`, as the code has always read it (the type said a number would do).
|
|
42
|
+
|
|
43
|
+
Proved on the Logomarks generators: the nineteen drawn with span tiles or pixel rasters, the ten drawn with the tools' mitred offsets and the twelve drawn with distance fields all redraw byte-identical with these in place of their local copies.
|
|
44
|
+
|
|
45
|
+
## [0.1.10] — 2026-11-15
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **`fitMark()` no longer moves a mark that's already fitted.** A finished mark's points sit on the 0.1 grid, so it measured a hair off 448 and was scaled by that hair: points tipped over a rounding edge and arc radii, rounded down, lost 0.1 on every refit. Of the 5,255 Logomarks marks, 24 changed on a refit and 16 never settled (Trio 52.9 → 52.8 → 52.7 …). A mark on the 512 canvas whose longest side and centre are within `REFIT_TOLERANCE` (0.25) keeps its scale and position, and an arc radius within `ARC_SLACK` (0.005) of the next 0.1 step is taken as it. Every live mark now refits to the same numbers (four older files only have their spacing normalised, once). Semicircles still round down.
|
|
50
|
+
- `runIndustry()`'s `fam:` also takes the family as passed, so `fam:Letters · Latch` builds a batch that passes whole families as its trades (it built none).
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`/batch`: running a generator (Node).** `runBatch(batch, marks, options)` draws each mark seeded by its name (and puts `Math.random` back), with `exact()` so the gap is 22 once fitted, into `<workdir>/newraw<N>/`, and writes `_names.json`, `_families.json`, `_styles.json`, and `_variants.json` / `_pairs.json` when used; held marks are left out, and re-running one mark keeps every sidecar whole. Also `batchArgs` and `selected` (`"Name,Name"`, `fam:`, `pair:` and the generator's own modes), `sidecars`, `rawSvg`, `withSeed`, `slug` (the one slug: no hyphen at either end) and `sourceMark` (an edition's source: live first, then the queue; `LOGOMARKS_SVG`, `LOGOMARKS_QUEUE`). See [docs/api.md](docs/api.md#batches).
|
|
55
|
+
- **`/sdf`: signed distance fields**, from the Logomarks tools' Y2K parts: the fields (`sdCircle`, `sdCapsule`, `sdPath`, `sdBox`, `sdEllipse`, `sdRing`, and the new exact `sdPoly`), smooth and sharp combining (`smin`, `smax`, `ssub`, `fmin`, and the new `fmax` and `fsub`), the gap (`gapCut`, `fover`, `flink`) and `shapeOf`, which traces a field into a kit shape. The twelve generators drawn with them redraw byte-identical on it. See [docs/api.md](docs/api.md#distance-fields).
|
|
56
|
+
|
|
8
57
|
## [0.1.9] — 2026-11-08
|
|
9
58
|
|
|
10
59
|
### Added
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Geometry, SVG reading, fitting, analysis and similarity for logomarks on a 512 g
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @logomarks/core
|
|
9
9
|
npm install clipper-lib # only for the shape edits (reshape)
|
|
10
|
+
npm install opentype.js clipper-lib @resvg/resvg-js # only for /font
|
|
10
11
|
```
|
|
11
12
|
|
|
12
13
|
## Quick start
|
|
@@ -25,13 +26,21 @@ checkFit(mark).problems; // []
|
|
|
25
26
|
|
|
26
27
|
- `/geometry`: shapes and real booleans, strokes, tracks, iso solids, badges and letter skeletons
|
|
27
28
|
- `/svg`: one SVG path parser, filled shapes, bounds and rings
|
|
28
|
-
- `/fit`: the 448 fit and its check
|
|
29
|
+
- `/fit`: the 448 fit and its check (refitting a fitted mark gives the same file)
|
|
29
30
|
- `/analyse`: specks, slivers and pin-holes, gap and stroke widths, shape tags
|
|
30
31
|
- `/similar`: outline and letter signatures, IoU and nearest marks
|
|
31
32
|
- `/raster`: scanline fill, components, distance transform
|
|
32
33
|
- `/edit`: the studio's shape edits (weight, soften, gap, detail) and pixelate
|
|
34
|
+
- `/sdf`: shapes as signed distance fields, smooth unions and exact gap cuts, traced to kit shapes
|
|
33
35
|
- `/stitch`: a mark planned as machine embroidery (fill, satin and border stitches), for a renderer to draw as thread
|
|
34
36
|
- `/parts`: the parts the industry symbols are built from (Node)
|
|
37
|
+
- `/iso`: iso objects (lathes, roofs, shops, cut-outs square to the viewer, tipped slabs), folded planes and voxels
|
|
38
|
+
- `/letters`: letters built from geometry: a Didone serif, a blackletter nib, monogram, pillar and stem pairs, and the echo, dot-stroke, ink-trap and stair faces
|
|
39
|
+
- `/font`: a capital from a font file (a path or its bytes) as a shape, its hairline measured and grown, and the house moves placed on it (Node)
|
|
40
|
+
- `/pixels`: dither tapers that break a pixel mark up, and typed pixel emotes
|
|
41
|
+
- `/objects`: devices and code glyphs, blades, dishes, sport, cells, faces, animals and rosettes
|
|
42
|
+
- `/forms`: the Bold, Line and Track forms: discs, hooks, jogs, emblems, shatter, slats, flow grids, folds, swashes, stripes, rails, locks and track editions
|
|
43
|
+
- `/batch`: a generator's runner: seeded marks, the sidecars, the command line, `slug` (Node)
|
|
35
44
|
- `logomarks-skill <dir>`: writes the design skill's `kit.mjs` and `fit.mjs`
|
|
36
45
|
|
|
37
46
|
Every export, repeatable drawings and the skill scripts: [API](https://github.com/Logomarks/core/blob/main/docs/api.md).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logomarks/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Geometry, SVG reading, fitting, analysis and similarity for logomarks on a 512 grid: the kit the Logomarks set is drawn with (shapes, real booleans, strokes, tracks, iso solids, badges, letter skeletons), one SVG path parser, the 448 fit, speck and shape-tag analysis, outline signatures, stitch plans for embroidery, and the
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Geometry, SVG reading, fitting, analysis and similarity for logomarks on a 512 grid: the kit the Logomarks set is drawn with (shapes, real booleans, strokes, tracks, iso solids, badges, letter skeletons), one SVG path parser, the 448 fit, speck and shape-tag analysis, outline signatures, signed distance fields traced to shapes, stitch plans for embroidery, and the parts the generators draw with: iso objects, letters and type, font glyphs, pixel dither, objects and the Bold, Line and Track forms, plus the batch runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"types": "src/index.d.ts",
|
|
@@ -22,6 +22,41 @@
|
|
|
22
22
|
"import": "./src/parts/index.js",
|
|
23
23
|
"default": "./src/parts/index.js"
|
|
24
24
|
},
|
|
25
|
+
"./iso": {
|
|
26
|
+
"types": "./src/iso/index.d.ts",
|
|
27
|
+
"import": "./src/iso/index.js",
|
|
28
|
+
"default": "./src/iso/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./letters": {
|
|
31
|
+
"types": "./src/letters/index.d.ts",
|
|
32
|
+
"import": "./src/letters/index.js",
|
|
33
|
+
"default": "./src/letters/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./font": {
|
|
36
|
+
"types": "./src/font/index.d.ts",
|
|
37
|
+
"import": "./src/font/index.js",
|
|
38
|
+
"default": "./src/font/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./pixels": {
|
|
41
|
+
"types": "./src/pixels/index.d.ts",
|
|
42
|
+
"import": "./src/pixels/index.js",
|
|
43
|
+
"default": "./src/pixels/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./objects": {
|
|
46
|
+
"types": "./src/objects/index.d.ts",
|
|
47
|
+
"import": "./src/objects/index.js",
|
|
48
|
+
"default": "./src/objects/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./forms": {
|
|
51
|
+
"types": "./src/forms/index.d.ts",
|
|
52
|
+
"import": "./src/forms/index.js",
|
|
53
|
+
"default": "./src/forms/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./batch": {
|
|
56
|
+
"types": "./src/batch/index.d.ts",
|
|
57
|
+
"import": "./src/batch/index.js",
|
|
58
|
+
"default": "./src/batch/index.js"
|
|
59
|
+
},
|
|
25
60
|
"./svg": {
|
|
26
61
|
"types": "./src/svg/index.d.ts",
|
|
27
62
|
"import": "./src/svg/index.js",
|
|
@@ -47,6 +82,11 @@
|
|
|
47
82
|
"import": "./src/similar/index.js",
|
|
48
83
|
"default": "./src/similar/index.js"
|
|
49
84
|
},
|
|
85
|
+
"./sdf": {
|
|
86
|
+
"types": "./src/sdf/index.d.ts",
|
|
87
|
+
"import": "./src/sdf/index.js",
|
|
88
|
+
"default": "./src/sdf/index.js"
|
|
89
|
+
},
|
|
50
90
|
"./edit": {
|
|
51
91
|
"types": "./src/edit/index.d.ts",
|
|
52
92
|
"import": "./src/edit/index.js",
|
|
@@ -91,7 +131,12 @@
|
|
|
91
131
|
"fit",
|
|
92
132
|
"similarity",
|
|
93
133
|
"embroidery",
|
|
94
|
-
"satin-stitch"
|
|
134
|
+
"satin-stitch",
|
|
135
|
+
"signed-distance-field",
|
|
136
|
+
"sdf",
|
|
137
|
+
"letters",
|
|
138
|
+
"font",
|
|
139
|
+
"pixel-art"
|
|
95
140
|
],
|
|
96
141
|
"author": "David Sherlock",
|
|
97
142
|
"license": "MIT",
|
|
@@ -112,16 +157,26 @@
|
|
|
112
157
|
"polygon-clipping": "^0.15.7"
|
|
113
158
|
},
|
|
114
159
|
"peerDependencies": {
|
|
115
|
-
"
|
|
160
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
161
|
+
"clipper-lib": "^6.4.2",
|
|
162
|
+
"opentype.js": "^2.0.0"
|
|
116
163
|
},
|
|
117
164
|
"peerDependenciesMeta": {
|
|
165
|
+
"@resvg/resvg-js": {
|
|
166
|
+
"optional": true
|
|
167
|
+
},
|
|
118
168
|
"clipper-lib": {
|
|
119
169
|
"optional": true
|
|
170
|
+
},
|
|
171
|
+
"opentype.js": {
|
|
172
|
+
"optional": true
|
|
120
173
|
}
|
|
121
174
|
},
|
|
122
175
|
"devDependencies": {
|
|
176
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
123
177
|
"@types/node": "^26.6.6",
|
|
124
178
|
"clipper-lib": "^6.4.2",
|
|
179
|
+
"opentype.js": "^2.0.0",
|
|
125
180
|
"typescript": "^6.0.3"
|
|
126
181
|
}
|
|
127
182
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** What a generator was asked to build. */
|
|
2
|
+
export interface BatchArgs {
|
|
3
|
+
/** Where the batch's `newraw<N>/` folder goes. */
|
|
4
|
+
workdir: string;
|
|
5
|
+
/** Only these marks, by name; null for no name list. */
|
|
6
|
+
names: string[] | null;
|
|
7
|
+
/** Only this family's marks; null for any. */
|
|
8
|
+
family: string | null;
|
|
9
|
+
/** Only the monograms of these letter pairs; null for any. */
|
|
10
|
+
pairs: string[] | null;
|
|
11
|
+
/** A mode the generator handles itself (one of its `modes`); null for none. */
|
|
12
|
+
mode: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The work folder and the selection from `node genbatchN.mjs <workdir> ["Name,Name" | "fam:…" | "pair:DS,JS" | mode]`. */
|
|
16
|
+
export function batchArgs(argv?: readonly string[], options?: { modes?: readonly string[] }): BatchArgs;
|
|
17
|
+
/** Whether a mark is in the selection (in no mark when a mode was asked for). */
|
|
18
|
+
export function selected(args: BatchArgs, name: string, mark: { family?: string; pair?: string }): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A generator's command line: `node genbatchN.mjs <workdir> [what]`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* What a generator was asked to build. `what` is one of:
|
|
7
|
+
* - nothing: every mark;
|
|
8
|
+
* - `"Name,Name"`: those marks;
|
|
9
|
+
* - `"fam:<family>"`: one family's marks;
|
|
10
|
+
* - `"pair:DS,JS"`: the monograms of those letter pairs;
|
|
11
|
+
* - one of `modes` (e.g. `"matrix"`): a mode the generator handles itself.
|
|
12
|
+
*
|
|
13
|
+
* @param {readonly string[]} [argv] The arguments after the script (default `process.argv.slice(2)`).
|
|
14
|
+
* @param {{ modes?: readonly string[] }} [options] The generator's own modes.
|
|
15
|
+
* @returns {import('./args.js').BatchArgs} The work folder and the selection.
|
|
16
|
+
* @throws {Error} When there's no work folder.
|
|
17
|
+
*/
|
|
18
|
+
export function batchArgs(argv = process.argv.slice(2), { modes = [] } = {}) {
|
|
19
|
+
const [workdir, what = ''] = argv;
|
|
20
|
+
if (!workdir) throw new Error('usage: node genbatchN.mjs <workdir> ["Name,Name" | "fam:<family>" | "pair:DS,JS"]');
|
|
21
|
+
const list = (/** @type {string} */ s) => s.split(',').map((x) => x.trim()).filter(Boolean);
|
|
22
|
+
return {
|
|
23
|
+
workdir,
|
|
24
|
+
names: what && !/^(fam|pair):/.test(what) && !modes.includes(what) ? list(what) : null,
|
|
25
|
+
family: what.startsWith('fam:') ? what.slice(4).trim() : null,
|
|
26
|
+
pairs: what.startsWith('pair:') ? list(what.slice(5)) : null,
|
|
27
|
+
mode: modes.includes(what) ? what : null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether a mark is in the selection.
|
|
33
|
+
*
|
|
34
|
+
* @param {import('./args.js').BatchArgs} args The selection.
|
|
35
|
+
* @param {string} name The mark's name.
|
|
36
|
+
* @param {{ family?: string, pair?: string }} mark Its family and letter pair.
|
|
37
|
+
* @returns {boolean} True when it should be built.
|
|
38
|
+
*/
|
|
39
|
+
export function selected(args, name, mark) {
|
|
40
|
+
if (args.names) return args.names.includes(name);
|
|
41
|
+
if (args.family) return mark.family === args.family;
|
|
42
|
+
if (args.pairs) return !!mark.pair && args.pairs.includes(mark.pair);
|
|
43
|
+
return !args.mode;
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { slug } from './slug.js';
|
|
2
|
+
export { batchArgs, selected } from './args.js';
|
|
3
|
+
export type { BatchArgs } from './args.js';
|
|
4
|
+
export { sourceMark } from './source.js';
|
|
5
|
+
export type { SourceMark } from './source.js';
|
|
6
|
+
export { runBatch, sidecars, rawSvg, withSeed } from './run.js';
|
|
7
|
+
export type { BatchMark, BatchOptions, BatchResult } from './run.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @logomarks/core/batch
|
|
3
|
+
*
|
|
4
|
+
* Running a generator (Node only): the command line, a mark's slug, reading an existing mark for an edition, and the
|
|
5
|
+
* runner that draws a batch seeded per mark and writes the sidecars the export and the review page read.
|
|
6
|
+
*/
|
|
7
|
+
export { slug } from './slug.js';
|
|
8
|
+
export { batchArgs, selected } from './args.js';
|
|
9
|
+
export { sourceMark } from './source.js';
|
|
10
|
+
export { runBatch, sidecars, rawSvg, withSeed } from './run.js';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Shape } from '../geometry/types.js';
|
|
2
|
+
import type { BatchArgs } from './args.js';
|
|
3
|
+
|
|
4
|
+
/** One mark in a batch. */
|
|
5
|
+
export interface BatchMark {
|
|
6
|
+
/** Its family, e.g. "Letters · Latch" (`_families.json`; `fam:` selects by it). */
|
|
7
|
+
family: string;
|
|
8
|
+
/** Its style, e.g. "Bold" (`_styles.json`). */
|
|
9
|
+
style?: string;
|
|
10
|
+
/** The live mark it is an edition or variation of, by slug (`_variants.json`). */
|
|
11
|
+
variantOf?: string;
|
|
12
|
+
/** Its letter pair, for monograms (`_pairs.json`; `pair:` selects by it). */
|
|
13
|
+
pair?: string;
|
|
14
|
+
/** The drawing: takes the gap (or nothing with `gap: false`) and returns the mark. */
|
|
15
|
+
build: (g: number) => Shape;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** How a batch is drawn. */
|
|
19
|
+
export interface BatchOptions {
|
|
20
|
+
/** Draw each build with the gap and exact(), so the gap is 22 once fitted (default true). */
|
|
21
|
+
gap?: boolean;
|
|
22
|
+
/** Drop the pieces under `piece` and the holes under `hole` square units after drawing. */
|
|
23
|
+
despeck?: { piece?: number; hole?: number };
|
|
24
|
+
/** Names held back for a later drop: built by nobody, left out of the sidecars. */
|
|
25
|
+
held?: readonly string[];
|
|
26
|
+
/** The command line (default `process.argv.slice(2)`). */
|
|
27
|
+
argv?: readonly string[];
|
|
28
|
+
/** The generator's own modes (e.g. "matrix"): asking for one builds no mark here. */
|
|
29
|
+
modes?: readonly string[];
|
|
30
|
+
/** Where progress goes (default console.log). */
|
|
31
|
+
log?: (line: string) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What a batch run did. */
|
|
35
|
+
export interface BatchResult {
|
|
36
|
+
/** The `newraw<N>` folder. */
|
|
37
|
+
out: string;
|
|
38
|
+
/** The command line it read. */
|
|
39
|
+
args: BatchArgs;
|
|
40
|
+
/** The names it drew. */
|
|
41
|
+
built: string[];
|
|
42
|
+
/** The names that threw, and why. */
|
|
43
|
+
failed: { name: string; error: string }[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A raw mark's file: one even-odd path on the 512 canvas. */
|
|
47
|
+
export function rawSvg(shape: Shape): string;
|
|
48
|
+
/** Runs a drawing with Math.random seeded by the name, then puts Math.random back. */
|
|
49
|
+
export function withSeed<T>(name: string, draw: () => T): T;
|
|
50
|
+
/** The batch's sidecar files: file name → contents (`_names`, `_families`, `_styles`, and `_variants` / `_pairs` when used). */
|
|
51
|
+
export function sidecars(marks: Readonly<Record<string, BatchMark>>): Record<string, unknown>;
|
|
52
|
+
/** Draws a batch into `<workdir>/newraw<batch>/`, seeded per mark, and writes its sidecars. */
|
|
53
|
+
export function runBatch(batch: number, marks: Readonly<Record<string, BatchMark>>, options?: BatchOptions): BatchResult;
|
package/src/batch/run.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The batch runner: draws a generator's marks into `<workdir>/newraw<N>/` with the sidecars the export and the review
|
|
3
|
+
* page read (Node only: it writes files).
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { seeded, toPath } from '../geometry/index.js';
|
|
8
|
+
import { exact, despeckShape } from '../parts/frame.js';
|
|
9
|
+
import { batchArgs, selected } from './args.js';
|
|
10
|
+
import { slug } from './slug.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A finished raw mark: one even-odd path on the 512 canvas (normalize fits it).
|
|
14
|
+
*
|
|
15
|
+
* @param {import('../geometry/types.js').Shape} shape The mark.
|
|
16
|
+
* @returns {string} The SVG file's text.
|
|
17
|
+
*/
|
|
18
|
+
export const rawSvg = (shape) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><path fill-rule="evenodd" d="${toPath(shape)}"/></svg>\n`;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runs a drawing with Math.random seeded by the mark's name, putting Math.random back afterwards, so the mark is the
|
|
22
|
+
* same on every run whatever else ran first.
|
|
23
|
+
*
|
|
24
|
+
* @template T
|
|
25
|
+
* @param {string} name The mark's name (the seed).
|
|
26
|
+
* @param {() => T} draw The drawing.
|
|
27
|
+
* @returns {T} What it drew.
|
|
28
|
+
*/
|
|
29
|
+
export function withSeed(name, draw) {
|
|
30
|
+
const random = Math.random;
|
|
31
|
+
Math.random = seeded(name);
|
|
32
|
+
try { return draw(); } finally { Math.random = random; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The sidecars for a batch's marks: `_names.json` (how normalize finds the batch), `_families.json` and `_styles.json`,
|
|
37
|
+
* and `_variants.json` and `_pairs.json` when any mark has a `variantOf` or a `pair`. Each maps slug → value.
|
|
38
|
+
*
|
|
39
|
+
* @param {Readonly<Record<string, import('./run.js').BatchMark>>} marks The batch's marks, by name.
|
|
40
|
+
* @returns {Record<string, unknown>} File name → contents.
|
|
41
|
+
*/
|
|
42
|
+
export function sidecars(marks) {
|
|
43
|
+
const entries = Object.entries(marks);
|
|
44
|
+
const map = (/** @type {keyof import('./run.js').BatchMark} */ key) =>
|
|
45
|
+
Object.fromEntries(entries.filter(([, m]) => m[key] !== undefined).map(([name, m]) => [slug(name), m[key]]));
|
|
46
|
+
/** @type {Record<string, unknown>} */
|
|
47
|
+
const files = { '_names.json': Object.keys(marks), '_families.json': map('family'), '_styles.json': map('style') };
|
|
48
|
+
for (const [key, file] of /** @type {const} */ ([['variantOf', '_variants.json'], ['pair', '_pairs.json']])) {
|
|
49
|
+
const m = map(key);
|
|
50
|
+
if (Object.keys(m).length) files[file] = m;
|
|
51
|
+
}
|
|
52
|
+
return files;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Draws a batch: every mark (or the ones the command line names: `"Name,Name"`, `"fam:<family>"`, `"pair:DS,JS"`)
|
|
57
|
+
* into `<workdir>/newraw<batch>/<slug>.svg`, seeded per mark so a re-run is byte-identical, then writes the sidecars
|
|
58
|
+
* for every mark that isn't held (so re-running one mark never trims them). A mark that throws is reported and the
|
|
59
|
+
* rest still drawn.
|
|
60
|
+
*
|
|
61
|
+
* With `gap: true` (the default) each build takes the gap and is drawn with exact(), so its gap is 22 once fitted;
|
|
62
|
+
* with `gap: false` it takes nothing. `despeck` drops the pieces and holes a boolean left under those areas.
|
|
63
|
+
*
|
|
64
|
+
* @param {number} batch The batch number.
|
|
65
|
+
* @param {Readonly<Record<string, import('./run.js').BatchMark>>} marks The marks, by name.
|
|
66
|
+
* @param {import('./run.js').BatchOptions} [options] The drawing and the command line.
|
|
67
|
+
* @returns {import('./run.js').BatchResult} What was built and what failed.
|
|
68
|
+
*/
|
|
69
|
+
export function runBatch(batch, marks, { gap = true, despeck, held = [], argv, modes, log = console.log } = {}) {
|
|
70
|
+
const args = batchArgs(argv, { modes });
|
|
71
|
+
const out = path.join(args.workdir, `newraw${batch}`);
|
|
72
|
+
fs.mkdirSync(out, { recursive: true });
|
|
73
|
+
const live = Object.fromEntries(Object.entries(marks).filter(([name]) => !held.includes(name)));
|
|
74
|
+
/** @type {string[]} */ const built = [];
|
|
75
|
+
/** @type {{ name: string, error: string }[]} */ const failed = [];
|
|
76
|
+
for (const [name, mark] of Object.entries(live)) {
|
|
77
|
+
if (!selected(args, name, mark)) continue;
|
|
78
|
+
try {
|
|
79
|
+
// exact() redraws up to three times to land the gap: each attempt starts from the same seed
|
|
80
|
+
let shape = withSeed(name, () => (gap ? exact(mark.build, () => { Math.random = seeded(name); }) : mark.build(0)));
|
|
81
|
+
if (despeck) shape = despeckShape(shape, despeck.piece, despeck.hole);
|
|
82
|
+
fs.writeFileSync(path.join(out, `${slug(name)}.svg`), rawSvg(shape));
|
|
83
|
+
built.push(name);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
failed.push({ name, error: e instanceof Error ? e.message : String(e) });
|
|
86
|
+
log(`FAIL ${name}: ${failed[failed.length - 1].error.slice(0, 160)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
for (const [file, data] of Object.entries(sidecars(live))) fs.writeFileSync(path.join(out, file), JSON.stringify(data, null, 1));
|
|
90
|
+
log(`built ${built.length} of ${Object.keys(live).length}${held.length ? ` (${held.length} held)` : ''} into ${out}`);
|
|
91
|
+
return { out, args, built, failed };
|
|
92
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mark's slug: the name its file, its page and every sidecar are keyed by.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A mark's slug from its name: lower case, each run of anything but a–z and 0–9 one hyphen, no hyphen at either end
|
|
7
|
+
* ("Ship's Wheel" → "ship-s-wheel", "Tree (r)" → "tree-r"). The site, normalize and the review tools all use this one.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} name The mark's name.
|
|
10
|
+
* @returns {string} Its slug.
|
|
11
|
+
*/
|
|
12
|
+
export const slug = (name) => name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** An existing mark read for an edition or a variation. */
|
|
2
|
+
export interface SourceMark {
|
|
3
|
+
/** The file's text. */
|
|
4
|
+
svg: string;
|
|
5
|
+
/** Where it was read from. */
|
|
6
|
+
file: string;
|
|
7
|
+
/** The live set, or the review queue. */
|
|
8
|
+
from: 'live' | 'queue';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** An existing mark: the live file first, then the queue's `<prefix>NNN-<slug>.svg` (`LOGOMARKS_SVG` / `LOGOMARKS_QUEUE` replace the folders); null when it's in neither. */
|
|
12
|
+
export function sourceMark(slug: string, folders: { live: string; queue?: string }): SourceMark | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a mark that already exists, for editions and variations drawn from it (Node only).
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An existing mark's SVG: the live file first, then the review queue, where a mark approved for the next drop waits
|
|
9
|
+
* as `<prefix>NNN-<slug>.svg`. `LOGOMARKS_SVG` and `LOGOMARKS_QUEUE` replace the two folders, so a proof can read a
|
|
10
|
+
* snapshot while an export rewrites them.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} slug The mark's slug.
|
|
13
|
+
* @param {{ live: string, queue?: string }} folders The live svg folder and the queue folder.
|
|
14
|
+
* @returns {import('./source.js').SourceMark | null} The file, or null when it's in neither.
|
|
15
|
+
*/
|
|
16
|
+
export function sourceMark(slug, { live, queue }) {
|
|
17
|
+
const liveDir = process.env.LOGOMARKS_SVG || live, queueDir = process.env.LOGOMARKS_QUEUE || queue;
|
|
18
|
+
const file = path.join(liveDir, `${slug}.svg`);
|
|
19
|
+
if (fs.existsSync(file)) return { svg: fs.readFileSync(file, 'utf8'), file, from: 'live' };
|
|
20
|
+
if (!queueDir || !fs.existsSync(queueDir)) return null;
|
|
21
|
+
const queued = fs.readdirSync(queueDir).find((f) => f.replace(/^[a-z]+\d+-/, '') === `${slug}.svg`);
|
|
22
|
+
return queued ? { svg: fs.readFileSync(path.join(queueDir, queued), 'utf8'), file: path.join(queueDir, queued), from: 'queue' } : null;
|
|
23
|
+
}
|
package/src/fit/index.d.ts
CHANGED
|
@@ -21,6 +21,10 @@ export const CANVAS: 512;
|
|
|
21
21
|
export const FIT: 448;
|
|
22
22
|
/** How far size, centre and edges may drift: 0.5. */
|
|
23
23
|
export const FIT_TOLERANCE: 0.5;
|
|
24
|
+
/** How far an already-fitted mark may sit from 448 and the centre and keep its scale on a refit: 0.25. */
|
|
25
|
+
export const REFIT_TOLERANCE: 0.25;
|
|
26
|
+
/** How close an arc radius may sit below the next 0.1 step and still be taken as it: 0.005. */
|
|
27
|
+
export const ARC_SLACK: 0.005;
|
|
24
28
|
/** Markup a finished mark must not contain. */
|
|
25
29
|
export const FORBIDDEN_MARKUP: RegExp;
|
|
26
30
|
/** A mark fitted to the standard; throws when the file has no filled shapes. */
|
package/src/fit/index.js
CHANGED
|
@@ -16,6 +16,10 @@ export const CANVAS = 512;
|
|
|
16
16
|
export const FIT = 448;
|
|
17
17
|
/** How far size, centre and canvas edges may drift before a check fails. */
|
|
18
18
|
export const FIT_TOLERANCE = 0.5;
|
|
19
|
+
/** How far a mark already on the 512 canvas may sit from 448 and the centre and still keep its scale and position on a refit. */
|
|
20
|
+
export const REFIT_TOLERANCE = 0.25;
|
|
21
|
+
/** How close (in units) an arc radius may sit below the next 0.1 step and still be taken as it. */
|
|
22
|
+
export const ARC_SLACK = 0.005;
|
|
19
23
|
/** Markup a finished mark must not contain: flatten to filled paths. */
|
|
20
24
|
export const FORBIDDEN_MARKUP = /<(g|style|mask|clipPath|use|text|image)\b|transform=|stroke=/;
|
|
21
25
|
|
|
@@ -31,7 +35,9 @@ const snap = (v) => Math.round(v * 10) / 10;
|
|
|
31
35
|
* Segments as compact relative path data. Points are snapped to the 0.1 grid
|
|
32
36
|
* first and the relative steps taken between snapped points, so rounding never
|
|
33
37
|
* drifts. Arc radii are rounded down: a semicircle stays exact (a radius a
|
|
34
|
-
* hair too big would bulge the arc).
|
|
38
|
+
* hair too big would bulge the arc). A radius within ARC_SLACK of the next
|
|
39
|
+
* step up is taken as that step, so refitting a fitted mark (its scale a hair
|
|
40
|
+
* under 1) keeps its radii instead of shaving 0.1 off each time.
|
|
35
41
|
*
|
|
36
42
|
* @param {readonly import('./path.js').Segment[]} segs The segments.
|
|
37
43
|
* @param {(x: number, y: number) => number[]} f Where each point goes.
|
|
@@ -60,7 +66,7 @@ function serialise(segs, f, k) {
|
|
|
60
66
|
cx = sx = x; cy = sy = y;
|
|
61
67
|
} else if (s[0] === 'A') {
|
|
62
68
|
const [x, y] = f(s[6], s[7]).map(snap);
|
|
63
|
-
const r = (/** @type {number} */ v) => r1(Math.floor(v * k * 10) / 10);
|
|
69
|
+
const r = (/** @type {number} */ v) => r1(Math.floor(v * k * 10 + ARC_SLACK * 10) / 10);
|
|
64
70
|
d += cmd('a', `${r(s[1])} ${r(s[2])} ${r1(s[3])} ${s[4] ? 1 : 0} ${s[5] ? 1 : 0} ${rel(x, y)}`);
|
|
65
71
|
cx = x; cy = y;
|
|
66
72
|
} else {
|
|
@@ -74,7 +80,24 @@ function serialise(segs, f, k) {
|
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
/**
|
|
77
|
-
*
|
|
83
|
+
* Whether a mark is already fitted: on the 512 canvas, its longest side and centre within REFIT_TOLERANCE. Its
|
|
84
|
+
* points sit on the 0.1 grid, so it measures a hair off 448; scaling it by that hair would tip points and arc radii
|
|
85
|
+
* over a rounding edge, so a refit keeps it as it is.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} svg The file's text.
|
|
88
|
+
* @param {{ x: number, y: number, w: number, h: number }} b Its outline's box.
|
|
89
|
+
* @returns {boolean} True when it is.
|
|
90
|
+
*/
|
|
91
|
+
function isFitted(svg, b) {
|
|
92
|
+
const vb = viewBox(svg);
|
|
93
|
+
if (!vb || vb.join(' ') !== `0 0 ${CANVAS} ${CANVAS}`) return false;
|
|
94
|
+
const near = (/** @type {number} */ a, /** @type {number} */ e) => Math.abs(a - e) <= REFIT_TOLERANCE;
|
|
95
|
+
return near(Math.max(b.w, b.h), FIT) && near(b.x + b.w / 2, CANVAS / 2) && near(b.y + b.h / 2, CANVAS / 2);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A mark fitted to the standard. A mark already fitted keeps its scale and position (see isFitted), so refitting a
|
|
100
|
+
* finished file gives the same file.
|
|
78
101
|
*
|
|
79
102
|
* @param {string} svg The file's text.
|
|
80
103
|
* @returns {import('./index.js').Fitted} The fitted file and the box it had.
|
|
@@ -84,8 +107,9 @@ export function fitMark(svg) {
|
|
|
84
107
|
const all = shapes(svg);
|
|
85
108
|
if (!all.length) throw new Error('no filled shapes found');
|
|
86
109
|
const before = bounds(all);
|
|
87
|
-
const
|
|
88
|
-
const
|
|
110
|
+
const fitted = isFitted(svg, before);
|
|
111
|
+
const k = fitted ? 1 : FIT / Math.max(before.w, before.h);
|
|
112
|
+
const tx = fitted ? 0 : CANVAS / 2 - (before.x + before.w / 2) * k, ty = fitted ? 0 : CANVAS / 2 - (before.y + before.h / 2) * k;
|
|
89
113
|
const f = (/** @type {number} */ x, /** @type {number} */ y) => [x * k + tx, y * k + ty];
|
|
90
114
|
const paths = all.map((s) => `<path${s.evenodd ? ' fill-rule="evenodd"' : ''} d="${serialise(s.segs, f, k)}"/>`).join('');
|
|
91
115
|
return { svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${CANVAS} ${CANVAS}" fill="currentColor">${paths}</svg>\n`, before };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Shape } from '../geometry/types.js';
|
|
2
|
+
import type { GlyphSource } from '../letters/serif.js';
|
|
3
|
+
|
|
4
|
+
/** A font: its file's path, or its bytes. */
|
|
5
|
+
export type FontSource = string | Uint8Array | ArrayBuffer;
|
|
6
|
+
/** A parsed font (opentype.js's Font): what /font reads of it. */
|
|
7
|
+
export interface Font {
|
|
8
|
+
charToGlyph(char: string): { path: { commands: { type: string; x?: number; y?: number; x1?: number; y1?: number; x2?: number; y2?: number }[] } };
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A font, parsed and cached (needs opentype.js). */
|
|
13
|
+
export function loadFont(source: FontSource): Font;
|
|
14
|
+
/** A font's cap height in font units: the top of its H. */
|
|
15
|
+
export function capUnits(font: Font): number;
|
|
16
|
+
/** Path data filled non-zero, as a shape. */
|
|
17
|
+
export function nonzeroShape(d: string, step?: number): Shape;
|
|
18
|
+
/** A font's character at cap height `cap`, its ink from x 0. */
|
|
19
|
+
export function glyphShape(font: FontSource, char: string, o?: { cap?: number; step?: number }): { s: Shape; w: number };
|
|
20
|
+
/** The shape grown by d, its corners mitred (needs clipper-lib). */
|
|
21
|
+
export function mitreGrow(shape: Shape, d: number, limit?: number): Shape;
|
|
22
|
+
/** A font's stem and hairline, from its H, as fractions of the cap. */
|
|
23
|
+
export function weights(font: FontSource): { S: number; h: number };
|
|
24
|
+
/** A font's hairline: a quantile of its letters' stroke depths, as a fraction of the cap. */
|
|
25
|
+
export function hairline(font: FontSource, chars?: string, q?: number): number;
|
|
26
|
+
/** A font's structural hairline, by erosion, as a fraction of the cap (needs @resvg/resvg-js). */
|
|
27
|
+
export function structuralHairline(font: FontSource, chars?: string): number;
|
|
28
|
+
/** A font's character as a glyph source, grown so its hairline fits to `hair`. */
|
|
29
|
+
export function fontSource(font: FontSource, char: string, o?: { hair?: number; h?: number; onGrow?: (t: number) => void }): GlyphSource;
|
|
30
|
+
/** A glyph source with its ink below the baseline trimmed off. */
|
|
31
|
+
export function onBase(src: GlyphSource): GlyphSource;
|