postext 1.19.3 → 1.20.1
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/dist/__tests__/comics/art.test.d.ts +2 -0
- package/dist/__tests__/comics/art.test.d.ts.map +1 -0
- package/dist/__tests__/comics/art.test.js +96 -0
- package/dist/__tests__/comics/art.test.js.map +1 -0
- package/dist/__tests__/comics/build.test.d.ts +2 -0
- package/dist/__tests__/comics/build.test.d.ts.map +1 -0
- package/dist/__tests__/comics/build.test.js +215 -0
- package/dist/__tests__/comics/build.test.js.map +1 -0
- package/dist/__tests__/comics/canvas.test.d.ts +2 -0
- package/dist/__tests__/comics/canvas.test.d.ts.map +1 -0
- package/dist/__tests__/comics/canvas.test.js +161 -0
- package/dist/__tests__/comics/canvas.test.js.map +1 -0
- package/dist/__tests__/comics/config.test.d.ts +2 -0
- package/dist/__tests__/comics/config.test.d.ts.map +1 -0
- package/dist/__tests__/comics/config.test.js +97 -0
- package/dist/__tests__/comics/config.test.js.map +1 -0
- package/dist/__tests__/comics/geometry.test.d.ts +2 -0
- package/dist/__tests__/comics/geometry.test.d.ts.map +1 -0
- package/dist/__tests__/comics/geometry.test.js +118 -0
- package/dist/__tests__/comics/geometry.test.js.map +1 -0
- package/dist/__tests__/comics/html.test.d.ts +2 -0
- package/dist/__tests__/comics/html.test.d.ts.map +1 -0
- package/dist/__tests__/comics/html.test.js +253 -0
- package/dist/__tests__/comics/html.test.js.map +1 -0
- package/dist/__tests__/comics/lettering.test.d.ts +2 -0
- package/dist/__tests__/comics/lettering.test.d.ts.map +1 -0
- package/dist/__tests__/comics/lettering.test.js +241 -0
- package/dist/__tests__/comics/lettering.test.js.map +1 -0
- package/dist/__tests__/comics/script.test.d.ts +2 -0
- package/dist/__tests__/comics/script.test.d.ts.map +1 -0
- package/dist/__tests__/comics/script.test.js +147 -0
- package/dist/__tests__/comics/script.test.js.map +1 -0
- package/dist/__tests__/comics/split.test.d.ts +2 -0
- package/dist/__tests__/comics/split.test.d.ts.map +1 -0
- package/dist/__tests__/comics/split.test.js +120 -0
- package/dist/__tests__/comics/split.test.js.map +1 -0
- package/dist/__tests__/comics/spread.test.d.ts +2 -0
- package/dist/__tests__/comics/spread.test.d.ts.map +1 -0
- package/dist/__tests__/comics/spread.test.js +161 -0
- package/dist/__tests__/comics/spread.test.js.map +1 -0
- package/dist/__tests__/comics/strip.test.d.ts +2 -0
- package/dist/__tests__/comics/strip.test.d.ts.map +1 -0
- package/dist/__tests__/comics/strip.test.js +231 -0
- package/dist/__tests__/comics/strip.test.js.map +1 -0
- package/dist/__tests__/exports.test.js +89 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/parse/containers.test.js +1 -1
- package/dist/__tests__/parse/containers.test.js.map +1 -1
- package/dist/canvas-backend/comic.d.ts +32 -0
- package/dist/canvas-backend/comic.d.ts.map +1 -0
- package/dist/canvas-backend/comic.js +216 -0
- package/dist/canvas-backend/comic.js.map +1 -0
- package/dist/canvas-backend/headerFooter.d.ts +4 -1
- package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
- package/dist/canvas-backend/headerFooter.js +3 -1
- package/dist/canvas-backend/headerFooter.js.map +1 -1
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js +7 -1
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/comics/art.d.ts +72 -0
- package/dist/comics/art.d.ts.map +1 -0
- package/dist/comics/art.js +154 -0
- package/dist/comics/art.js.map +1 -0
- package/dist/comics/fonts.d.ts +20 -0
- package/dist/comics/fonts.d.ts.map +1 -0
- package/dist/comics/fonts.js +42 -0
- package/dist/comics/fonts.js.map +1 -0
- package/dist/comics/geometry.d.ts +87 -0
- package/dist/comics/geometry.d.ts.map +1 -0
- package/dist/comics/geometry.js +272 -0
- package/dist/comics/geometry.js.map +1 -0
- package/dist/comics/index.d.ts +29 -0
- package/dist/comics/index.d.ts.map +1 -0
- package/dist/comics/index.js +18 -0
- package/dist/comics/index.js.map +1 -0
- package/dist/comics/layoutPage.d.ts +156 -0
- package/dist/comics/layoutPage.d.ts.map +1 -0
- package/dist/comics/layoutPage.js +698 -0
- package/dist/comics/layoutPage.js.map +1 -0
- package/dist/comics/lettering/__tests__/harness.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/harness.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/harness.test.js +273 -0
- package/dist/comics/lettering/__tests__/harness.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/letterPanel.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/letterPanel.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/letterPanel.test.js +224 -0
- package/dist/comics/lettering/__tests__/letterPanel.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/polish.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/polish.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/polish.test.js +357 -0
- package/dist/comics/lettering/__tests__/polish.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/round2.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/round2.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/round2.test.js +289 -0
- package/dist/comics/lettering/__tests__/round2.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/round3.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/round3.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/round3.test.js +240 -0
- package/dist/comics/lettering/__tests__/round3.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/shapeText.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/shapeText.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/shapeText.test.js +98 -0
- package/dist/comics/lettering/__tests__/shapeText.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/shapes.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/shapes.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/shapes.test.js +137 -0
- package/dist/comics/lettering/__tests__/shapes.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/stubMeasure.d.ts +5 -0
- package/dist/comics/lettering/__tests__/stubMeasure.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/stubMeasure.js +47 -0
- package/dist/comics/lettering/__tests__/stubMeasure.js.map +1 -0
- package/dist/comics/lettering/__tests__/text.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/text.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/text.test.js +86 -0
- package/dist/comics/lettering/__tests__/text.test.js.map +1 -0
- package/dist/comics/lettering/geom.d.ts +78 -0
- package/dist/comics/lettering/geom.d.ts.map +1 -0
- package/dist/comics/lettering/geom.js +376 -0
- package/dist/comics/lettering/geom.js.map +1 -0
- package/dist/comics/lettering/index.d.ts +7 -0
- package/dist/comics/lettering/index.d.ts.map +1 -0
- package/dist/comics/lettering/index.js +8 -0
- package/dist/comics/lettering/index.js.map +1 -0
- package/dist/comics/lettering/letter.d.ts +7 -0
- package/dist/comics/lettering/letter.d.ts.map +1 -0
- package/dist/comics/lettering/letter.js +496 -0
- package/dist/comics/lettering/letter.js.map +1 -0
- package/dist/comics/lettering/placement.d.ts +133 -0
- package/dist/comics/lettering/placement.d.ts.map +1 -0
- package/dist/comics/lettering/placement.js +1095 -0
- package/dist/comics/lettering/placement.js.map +1 -0
- package/dist/comics/lettering/presets.d.ts +15 -0
- package/dist/comics/lettering/presets.d.ts.map +1 -0
- package/dist/comics/lettering/presets.js +61 -0
- package/dist/comics/lettering/presets.js.map +1 -0
- package/dist/comics/lettering/shape-text.d.ts +72 -0
- package/dist/comics/lettering/shape-text.d.ts.map +1 -0
- package/dist/comics/lettering/shape-text.js +550 -0
- package/dist/comics/lettering/shape-text.js.map +1 -0
- package/dist/comics/lettering/shapes.d.ts +83 -0
- package/dist/comics/lettering/shapes.d.ts.map +1 -0
- package/dist/comics/lettering/shapes.js +512 -0
- package/dist/comics/lettering/shapes.js.map +1 -0
- package/dist/comics/lettering/text.d.ts +79 -0
- package/dist/comics/lettering/text.d.ts.map +1 -0
- package/dist/comics/lettering/text.js +488 -0
- package/dist/comics/lettering/text.js.map +1 -0
- package/dist/comics/lettering/types.d.ts +279 -0
- package/dist/comics/lettering/types.d.ts.map +1 -0
- package/dist/comics/lettering/types.js +9 -0
- package/dist/comics/lettering/types.js.map +1 -0
- package/dist/comics/page.d.ts +37 -0
- package/dist/comics/page.d.ts.map +1 -0
- package/dist/comics/page.js +285 -0
- package/dist/comics/page.js.map +1 -0
- package/dist/comics/paint.d.ts +65 -0
- package/dist/comics/paint.d.ts.map +1 -0
- package/dist/comics/paint.js +219 -0
- package/dist/comics/paint.js.map +1 -0
- package/dist/comics/script.d.ts +42 -0
- package/dist/comics/script.d.ts.map +1 -0
- package/dist/comics/script.js +302 -0
- package/dist/comics/script.js.map +1 -0
- package/dist/comics/split.d.ts +135 -0
- package/dist/comics/split.d.ts.map +1 -0
- package/dist/comics/split.js +514 -0
- package/dist/comics/split.js.map +1 -0
- package/dist/comics/spread.d.ts +36 -0
- package/dist/comics/spread.d.ts.map +1 -0
- package/dist/comics/spread.js +172 -0
- package/dist/comics/spread.js.map +1 -0
- package/dist/comics/strip.d.ts +77 -0
- package/dist/comics/strip.d.ts.map +1 -0
- package/dist/comics/strip.js +89 -0
- package/dist/comics/strip.js.map +1 -0
- package/dist/comics/transform.d.ts +37 -0
- package/dist/comics/transform.d.ts.map +1 -0
- package/dist/comics/transform.js +161 -0
- package/dist/comics/transform.js.map +1 -0
- package/dist/comics/types.d.ts +143 -0
- package/dist/comics/types.d.ts.map +1 -0
- package/dist/comics/types.js +13 -0
- package/dist/comics/types.js.map +1 -0
- package/dist/comics/warnings.d.ts +14 -0
- package/dist/comics/warnings.d.ts.map +1 -0
- package/dist/comics/warnings.js +94 -0
- package/dist/comics/warnings.js.map +1 -0
- package/dist/configWarnings.d.ts.map +1 -1
- package/dist/configWarnings.js +42 -0
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/comics.d.ts +51 -0
- package/dist/defaults/comics.d.ts.map +1 -0
- package/dist/defaults/comics.js +385 -0
- package/dist/defaults/comics.js.map +1 -0
- package/dist/defaults/index.d.ts +1 -0
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +9 -0
- package/dist/defaults/index.js.map +1 -1
- package/dist/design/layout.d.ts +13 -1
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +17 -0
- package/dist/design/layout.js.map +1 -1
- package/dist/html-backend.d.ts +3 -0
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +44 -8
- package/dist/html-backend.js.map +1 -1
- package/dist/htmlComic.d.ts +69 -0
- package/dist/htmlComic.d.ts.map +1 -0
- package/dist/htmlComic.js +179 -0
- package/dist/htmlComic.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +25 -1
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/indexMarks.d.ts.map +1 -1
- package/dist/parse/indexMarks.js +3 -0
- package/dist/parse/indexMarks.js.map +1 -1
- package/dist/parse/types.d.ts +7 -1
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +251 -9
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +4 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +58 -0
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/pipeline/floatSlots.js +1 -1
- package/dist/pipeline/floatSlots.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +6 -2
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +11 -3
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/pageRoles.d.ts +1 -0
- package/dist/pipeline/pageRoles.d.ts.map +1 -1
- package/dist/pipeline/pageRoles.js +3 -0
- package/dist/pipeline/pageRoles.js.map +1 -1
- package/dist/pipeline/placement.d.ts +3 -3
- package/dist/pipeline/placement.d.ts.map +1 -1
- package/dist/pipeline/placement.js +4 -4
- package/dist/pipeline/placement.js.map +1 -1
- package/dist/types.d.ts +338 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +271 -2
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/dist/worker/fonts.d.ts +2 -1
- package/dist/worker/fonts.d.ts.map +1 -1
- package/dist/worker/fonts.js +11 -1
- package/dist/worker/fonts.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// A deterministic text measurer for the lettering tests (no canvas in the
|
|
2
|
+
// node test environment): advances by character class, in ems of the
|
|
3
|
+
// font's px size, close enough to a comic face for shapes to be judged.
|
|
4
|
+
import { clearTextWidthCache } from '../../../measure/canvas';
|
|
5
|
+
const SIZE_RE = /(\d*\.?\d+)px/;
|
|
6
|
+
/** Advance of one character, in em. */
|
|
7
|
+
export function stubAdvance(ch) {
|
|
8
|
+
// Bidi controls are default ignorable: no advance (as a canvas gives).
|
|
9
|
+
if (/[\u2066-\u2069]/.test(ch))
|
|
10
|
+
return 0;
|
|
11
|
+
if (/[ -ヿ㐀-鿿豈-!-⦆]/.test(ch))
|
|
12
|
+
return 1;
|
|
13
|
+
if (/[-ۿ]/.test(ch))
|
|
14
|
+
return 0.42;
|
|
15
|
+
if (/\s/.test(ch))
|
|
16
|
+
return 0.27;
|
|
17
|
+
if (/[A-Z0-9]/.test(ch))
|
|
18
|
+
return 0.6;
|
|
19
|
+
if (/[a-z]/.test(ch))
|
|
20
|
+
return 0.5;
|
|
21
|
+
if (/[.,;:'!|]/.test(ch))
|
|
22
|
+
return 0.25;
|
|
23
|
+
if (ch === '…')
|
|
24
|
+
return 0.8;
|
|
25
|
+
return 0.55;
|
|
26
|
+
}
|
|
27
|
+
class StubCtx {
|
|
28
|
+
font = '16px sans-serif';
|
|
29
|
+
measureText(s) {
|
|
30
|
+
const m = SIZE_RE.exec(this.font);
|
|
31
|
+
const px = m ? parseFloat(m[1]) : 16;
|
|
32
|
+
let w = 0;
|
|
33
|
+
for (const ch of s)
|
|
34
|
+
w += stubAdvance(ch);
|
|
35
|
+
return { width: w * px };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Install the stub as the engine's measuring canvas. */
|
|
39
|
+
export function installStubMeasure() {
|
|
40
|
+
globalThis.OffscreenCanvas = class {
|
|
41
|
+
getContext() {
|
|
42
|
+
return new StubCtx();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
clearTextWidthCache();
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=stubMeasure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stubMeasure.js","sourceRoot":"","sources":["../../../../src/comics/lettering/__tests__/stubMeasure.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,qEAAqE;AACrE,wEAAwE;AAExE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,MAAM,OAAO,GAAG,eAAe,CAAC;AAEhC,uCAAuC;AACvC,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,uEAAuE;IACvE,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IACzC,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,GAAG,CAAC;IACpC,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,GAAG,CAAC;IACjC,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,GAAG,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,OAAO;IACX,IAAI,GAAG,iBAAiB,CAAC;IACzB,WAAW,CAAC,CAAS;QACnB,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,EAAE,IAAI,CAAC;YAAE,CAAC,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;QACzC,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,yDAAyD;AACzD,MAAM,UAAU,kBAAkB;IAC/B,UAAsD,CAAC,eAAe,GAAG;QACxE,UAAU;YACR,OAAO,IAAI,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC;IACF,mBAAmB,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.test.d.ts","sourceRoot":"","sources":["../../../../src/comics/lettering/__tests__/text.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { breakPoints, markupOf, prepareText, readLetteringText } from '../text';
|
|
3
|
+
import { presetLetteringStyles } from '../presets';
|
|
4
|
+
import { parseRichDesignText } from '../../../design/richText';
|
|
5
|
+
const styleFor = (locale, id = 'speech') => presetLetteringStyles({ fontSizePx: 12, locale })[id];
|
|
6
|
+
const prep = (text, locale, extra = {}, map) => prepareText(readLetteringText(text, map), { ...styleFor(locale), ...extra }, { locale, vertical: false });
|
|
7
|
+
const span = (text, bold = false, italic = false, more = {}) => ({ text, bold, italic, ...more });
|
|
8
|
+
describe('lettering text: house rules (SPEC D3.1)', () => {
|
|
9
|
+
it('stands ASCII ! and ? upright in Japanese columns (a pair left to the one-cell rule), not in rows', () => {
|
|
10
|
+
const v = (t) => prepareText(readLetteringText(t, [...t].map((_, i) => 100 + i)), styleFor('ja'), { locale: 'ja', vertical: true });
|
|
11
|
+
const col = v('キャロット!? 本当!');
|
|
12
|
+
// The pair stays ASCII: the vertical layout sets it in one cell with
|
|
13
|
+
// the face's own glyphs (comic faces lack ⁉ ‼).
|
|
14
|
+
expect(col.text).toBe('キャロット!? 本当!');
|
|
15
|
+
expect(col.source[5]).toBe(105);
|
|
16
|
+
expect(col.source[6]).toBe(106);
|
|
17
|
+
expect(v('メン!!!').text).toBe('メン!!!');
|
|
18
|
+
// Chinese columns set every mark full width.
|
|
19
|
+
expect(prepareText(readLetteringText('真的!?', undefined), styleFor('ja'), { locale: 'zh', vertical: true }).text).toBe('真的!?');
|
|
20
|
+
expect(prepareText(readLetteringText('本当!', undefined), styleFor('ja'), { locale: 'ja', vertical: false }).text).toBe('本当!');
|
|
21
|
+
expect(prepareText(readLetteringText('Really!?', undefined), styleFor('en'), { locale: 'en', vertical: true }).text).toBe('REALLY!?');
|
|
22
|
+
});
|
|
23
|
+
it('sets cased scripts in capitals, keeps Arabic and CJK as they are', () => {
|
|
24
|
+
expect(prep('Did you hear that?', 'en').text).toBe('DID YOU HEAR THAT?');
|
|
25
|
+
expect(prep('¿Has oído eso?', 'es').text).toBe('¿HAS OÍDO ESO?');
|
|
26
|
+
expect(prep('هل سمعت ذلك؟', 'ar').text).toBe('هل سمعت ذلك؟');
|
|
27
|
+
expect(prep('いまの音、聞こえた?', 'ja').text).toBe('いまの音、聞こえた?');
|
|
28
|
+
});
|
|
29
|
+
it('upper-cases in the locale rules and keeps a source offset per character', () => {
|
|
30
|
+
const p = prep('straße', 'de', {}, [10, 11, 12, 13, 14, 15]);
|
|
31
|
+
expect(p.text).toBe('STRASSE');
|
|
32
|
+
expect(p.source).toEqual([10, 11, 12, 13, 14, 14, 15]);
|
|
33
|
+
expect(prep('iki', 'tr').text).toBe('İKİ');
|
|
34
|
+
});
|
|
35
|
+
it('normalises ellipses, double dashes and the final stop', () => {
|
|
36
|
+
expect(prep('Well... maybe', 'en', { textTransform: 'none' }).text).toBe('Well… maybe');
|
|
37
|
+
expect(prep('Wait—what?', 'en', { textTransform: 'none', doubleDash: true }).text).toBe('Wait--what?');
|
|
38
|
+
expect(prep('Wait—what?', 'en', { textTransform: 'none' }).text).toBe('Wait—what?');
|
|
39
|
+
expect(prep('なんでもない。もう寝なさい。', 'ja').text).toBe('なんでもない。もう寝なさい');
|
|
40
|
+
expect(prep('沒什麼。', 'zh-Hant').text).toBe('沒什麼');
|
|
41
|
+
expect(prep('沒什麼。', 'zh-Hant', { dropFinalStop: false }).text).toBe('沒什麼。');
|
|
42
|
+
expect(prep('他說…', 'zh-Hant').text).toBe('他說……');
|
|
43
|
+
expect(prep('C’est fini.', 'fr', { textTransform: 'none' }).text).toBe('C’est fini.');
|
|
44
|
+
});
|
|
45
|
+
it('prints emphasis bold italic, bold only in scripts without italics', () => {
|
|
46
|
+
const en = prep([span('I said '), span('now', false, true), span('!')], 'en');
|
|
47
|
+
const i = en.text.indexOf('NOW');
|
|
48
|
+
expect(en.style[i]).toMatchObject({ bold: true, italic: true });
|
|
49
|
+
expect(en.style[0]).toMatchObject({ bold: false, italic: false });
|
|
50
|
+
const ar = prep([span('قلت '), span('الآن', true)], 'ar');
|
|
51
|
+
expect(ar.style[ar.text.length - 1]).toMatchObject({ bold: true, italic: false });
|
|
52
|
+
});
|
|
53
|
+
it('collapses white space and reads forced breaks', () => {
|
|
54
|
+
expect(prep('one two \n three', 'en', { textTransform: 'none' }).text).toBe('one two\nthree');
|
|
55
|
+
expect(prep('one\u2028two', 'en', { textTransform: 'none' }).text).toBe('one\ntwo');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('lettering text: legal breaks', () => {
|
|
59
|
+
it('breaks Latin text at spaces only, cheaper after a sentence end', () => {
|
|
60
|
+
const p = prep('Hi there. Go on', 'en', { textTransform: 'none' });
|
|
61
|
+
const pts = breakPoints(p, 'gb', false);
|
|
62
|
+
expect(pts.map((b) => p.text.slice(0, b.end))).toEqual(['Hi', 'Hi there.', 'Hi there. Go', 'Hi there. Go on']);
|
|
63
|
+
const after = (s) => pts.find((b) => p.text.slice(0, b.end) === s).penalty;
|
|
64
|
+
expect(after('Hi there.')).toBeLessThan(after('Hi'));
|
|
65
|
+
});
|
|
66
|
+
it('keeps Japanese kinsoku: no column opens with a closing mark or a small kana', () => {
|
|
67
|
+
const p = prep('ちょっと待って、ね', 'ja');
|
|
68
|
+
const pts = breakPoints(p, 'ja-very-strict', true).filter((b) => !b.forced);
|
|
69
|
+
for (const b of pts) {
|
|
70
|
+
expect('、ょっ').not.toContain(p.text[b.next]);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it('never breaks inside a tate-chu-yoko run', () => {
|
|
74
|
+
const p = prepareText(readLetteringText([span('あと'), span('12', false, false, { combineUpright: true }), span('分')], undefined), styleFor('ja'), { locale: 'ja', vertical: true });
|
|
75
|
+
const pts = breakPoints(p, 'ja-strict', true);
|
|
76
|
+
const at = p.text.indexOf('12');
|
|
77
|
+
expect(pts.some((b) => b.end === at + 1)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('writes markup that reads back to the same text', () => {
|
|
80
|
+
const p = prep([span('Look '), span('out*_', true), span(' x^2^ now')], 'en', { textTransform: 'none' });
|
|
81
|
+
const markup = markupOf(p, 0, p.text.length);
|
|
82
|
+
const rt = parseRichDesignText(markup, { family: 'X', sizePx: 12, weight: 400, italic: false });
|
|
83
|
+
expect(rt.text).toBe(p.text);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=text.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.test.js","sourceRoot":"","sources":["../../../../src/comics/lettering/__tests__/text.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Point, Rect } from './types';
|
|
2
|
+
/** A 32-bit FNV-1a hash of a string: the seed of every jitter. */
|
|
3
|
+
export declare function hashString(s: string): number;
|
|
4
|
+
/** A deterministic generator of numbers in [0, 1) seeded by `seed`
|
|
5
|
+
* (mulberry32). */
|
|
6
|
+
export declare function seededRandom(seed: number): () => number;
|
|
7
|
+
export declare const dist: (a: Point, b: Point) => number;
|
|
8
|
+
export declare const add: (a: Point, b: Point) => Point;
|
|
9
|
+
export declare const sub: (a: Point, b: Point) => Point;
|
|
10
|
+
export declare const scale: (a: Point, k: number) => Point;
|
|
11
|
+
export declare const lerp: (a: Point, b: Point, t: number) => Point;
|
|
12
|
+
export declare const norm: (a: Point) => Point;
|
|
13
|
+
/** `a` turned a quarter turn (to the left in page coordinates, y down). */
|
|
14
|
+
export declare const perp: (a: Point) => Point;
|
|
15
|
+
export declare function rectOf(x: number, y: number, width: number, height: number): Rect;
|
|
16
|
+
export declare function rectCentre(r: Rect): Point;
|
|
17
|
+
export declare function unionRect(a: Rect | undefined, b: Rect): Rect;
|
|
18
|
+
export declare function boundsOf(points: readonly Point[]): Rect;
|
|
19
|
+
export declare function rectsOverlap(a: Rect, b: Rect, margin?: number): boolean;
|
|
20
|
+
export declare function rectOverlapArea(a: Rect, b: Rect): number;
|
|
21
|
+
export declare function pointInRect(p: Point, r: Rect, margin?: number): boolean;
|
|
22
|
+
/** Signed area of a polygon (positive when clockwise on a y-down page). */
|
|
23
|
+
export declare function signedArea(poly: readonly Point[]): number;
|
|
24
|
+
/** Whether `p` lies inside the convex polygon `poly` (either winding). */
|
|
25
|
+
export declare function pointInConvex(p: Point, poly: readonly Point[]): boolean;
|
|
26
|
+
/** A convex polygon with each edge moved inward by `d` (clipped by the
|
|
27
|
+
* half-planes of the moved edges). Empty when it vanishes. */
|
|
28
|
+
export declare function insetConvex(poly: readonly Point[], d: number): Point[];
|
|
29
|
+
/** The part of `poly` on the side of the line through `p` that `n` points
|
|
30
|
+
* to (Sutherland–Hodgman, one edge). */
|
|
31
|
+
export declare function clipHalfPlane(poly: readonly Point[], p: Point, n: Point): Point[];
|
|
32
|
+
/** The part of a convex `poly` inside the rectangle `r`. */
|
|
33
|
+
export declare function clipToRect(poly: readonly Point[], r: Rect): Point[];
|
|
34
|
+
/** Where the ray from `o` along `dir` first leaves the polygon (the
|
|
35
|
+
* distance along `dir`, normalised), or undefined. */
|
|
36
|
+
export declare function rayExit(o: Point, dir: Point, poly: readonly Point[]): number | undefined;
|
|
37
|
+
/** Distance along `dir` (unit) from `o` to segment `ab`, or undefined. */
|
|
38
|
+
export declare function raySegment(o: Point, dir: Point, a: Point, b: Point): number | undefined;
|
|
39
|
+
/** Whether segments `ab` and `cd` cross. */
|
|
40
|
+
export declare function segmentsCross(a: Point, b: Point, c: Point, d: Point): boolean;
|
|
41
|
+
/** Distance between segments `ab` and `cd` (0 when they cross). */
|
|
42
|
+
export declare function segmentDistance(a: Point, b: Point, c: Point, d: Point): number;
|
|
43
|
+
/** Whether segment `ab` crosses rect `r` (or lies inside it). */
|
|
44
|
+
export declare function segmentHitsRect(a: Point, b: Point, r: Rect): boolean;
|
|
45
|
+
/** The point of the polygon's boundary nearest to `p`. */
|
|
46
|
+
export declare function nearestOnPolygon(p: Point, poly: readonly Point[]): Point;
|
|
47
|
+
/** One command of a path in page px: M L C Z only. */
|
|
48
|
+
export interface PathCmd {
|
|
49
|
+
op: 'M' | 'L' | 'C' | 'Z';
|
|
50
|
+
pts: Point[];
|
|
51
|
+
}
|
|
52
|
+
/** SVG path data of commands (two decimals). */
|
|
53
|
+
export declare function pathData(cmds: readonly PathCmd[]): string;
|
|
54
|
+
/** Commands moved by (dx, dy). */
|
|
55
|
+
export declare function translateCmds(cmds: readonly PathCmd[], dx: number, dy: number): PathCmd[];
|
|
56
|
+
/** A closed polygon (straight segments). */
|
|
57
|
+
export declare function polygonCmds(points: readonly Point[]): PathCmd[];
|
|
58
|
+
/** A closed smooth curve through `points` (Catmull-Rom turned into cubic
|
|
59
|
+
* Beziers). */
|
|
60
|
+
export declare function smoothClosedCmds(points: readonly Point[]): PathCmd[];
|
|
61
|
+
/** An open smooth curve through `points` (Catmull-Rom, ends clamped), as
|
|
62
|
+
* `C` commands that continue from `points[0]`. */
|
|
63
|
+
export declare function smoothOpenCmds(points: readonly Point[]): PathCmd[];
|
|
64
|
+
/** A circle as four cubic arcs. */
|
|
65
|
+
export declare function circleCmds(c: Point, r: number): PathCmd[];
|
|
66
|
+
/** A rectangle with corners rounded by `radius` (cubic arcs). */
|
|
67
|
+
export declare function roundedRectCmds(r: Rect, radius: number): PathCmd[];
|
|
68
|
+
/** Length of the closed polygon through `points`. */
|
|
69
|
+
export declare function perimeter(points: readonly Point[]): number;
|
|
70
|
+
/** Points at `count` equal steps of arc length along the closed polygon
|
|
71
|
+
* `points` (starting at `points[0]`, shifted by `phase` steps), each with
|
|
72
|
+
* the polygon's outward normal there (`cw`: the polygon runs clockwise on
|
|
73
|
+
* the page). */
|
|
74
|
+
export declare function resampleClosed(points: readonly Point[], count: number, phase?: number): {
|
|
75
|
+
p: Point;
|
|
76
|
+
n: Point;
|
|
77
|
+
}[];
|
|
78
|
+
//# sourceMappingURL=geom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geom.d.ts","sourceRoot":"","sources":["../../../src/comics/lettering/geom.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// Small geometry helpers of the lettering: seeded noise, polygons, rects,
|
|
2
|
+
// and SVG path data (M L C Z only, so that every renderer reads it).
|
|
3
|
+
/** A 32-bit FNV-1a hash of a string: the seed of every jitter. */
|
|
4
|
+
export function hashString(s) {
|
|
5
|
+
let h = 0x811c9dc5;
|
|
6
|
+
for (let i = 0; i < s.length; i++) {
|
|
7
|
+
h ^= s.charCodeAt(i);
|
|
8
|
+
h = Math.imul(h, 0x01000193);
|
|
9
|
+
}
|
|
10
|
+
return h >>> 0;
|
|
11
|
+
}
|
|
12
|
+
/** A deterministic generator of numbers in [0, 1) seeded by `seed`
|
|
13
|
+
* (mulberry32). */
|
|
14
|
+
export function seededRandom(seed) {
|
|
15
|
+
let a = seed >>> 0;
|
|
16
|
+
return () => {
|
|
17
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
18
|
+
let t = a;
|
|
19
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
20
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
21
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
25
|
+
export const add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
26
|
+
export const sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
27
|
+
export const scale = (a, k) => ({ x: a.x * k, y: a.y * k });
|
|
28
|
+
export const lerp = (a, b, t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t });
|
|
29
|
+
export const norm = (a) => {
|
|
30
|
+
const l = Math.hypot(a.x, a.y) || 1;
|
|
31
|
+
return { x: a.x / l, y: a.y / l };
|
|
32
|
+
};
|
|
33
|
+
/** `a` turned a quarter turn (to the left in page coordinates, y down). */
|
|
34
|
+
export const perp = (a) => ({ x: a.y, y: -a.x });
|
|
35
|
+
export function rectOf(x, y, width, height) {
|
|
36
|
+
return { x, y, width, height };
|
|
37
|
+
}
|
|
38
|
+
export function rectCentre(r) {
|
|
39
|
+
return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
|
|
40
|
+
}
|
|
41
|
+
export function unionRect(a, b) {
|
|
42
|
+
if (!a)
|
|
43
|
+
return { ...b };
|
|
44
|
+
const x = Math.min(a.x, b.x);
|
|
45
|
+
const y = Math.min(a.y, b.y);
|
|
46
|
+
return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
47
|
+
}
|
|
48
|
+
export function boundsOf(points) {
|
|
49
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
50
|
+
for (const p of points) {
|
|
51
|
+
if (p.x < x0)
|
|
52
|
+
x0 = p.x;
|
|
53
|
+
if (p.y < y0)
|
|
54
|
+
y0 = p.y;
|
|
55
|
+
if (p.x > x1)
|
|
56
|
+
x1 = p.x;
|
|
57
|
+
if (p.y > y1)
|
|
58
|
+
y1 = p.y;
|
|
59
|
+
}
|
|
60
|
+
return points.length ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : { x: 0, y: 0, width: 0, height: 0 };
|
|
61
|
+
}
|
|
62
|
+
export function rectsOverlap(a, b, margin = 0) {
|
|
63
|
+
return a.x < b.x + b.width + margin && b.x < a.x + a.width + margin
|
|
64
|
+
&& a.y < b.y + b.height + margin && b.y < a.y + a.height + margin;
|
|
65
|
+
}
|
|
66
|
+
export function rectOverlapArea(a, b) {
|
|
67
|
+
const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
|
|
68
|
+
const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
|
|
69
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
70
|
+
}
|
|
71
|
+
export function pointInRect(p, r, margin = 0) {
|
|
72
|
+
return p.x >= r.x - margin && p.x <= r.x + r.width + margin && p.y >= r.y - margin && p.y <= r.y + r.height + margin;
|
|
73
|
+
}
|
|
74
|
+
/** Signed area of a polygon (positive when clockwise on a y-down page). */
|
|
75
|
+
export function signedArea(poly) {
|
|
76
|
+
let s = 0;
|
|
77
|
+
for (let i = 0; i < poly.length; i++) {
|
|
78
|
+
const a = poly[i];
|
|
79
|
+
const b = poly[(i + 1) % poly.length];
|
|
80
|
+
s += a.x * b.y - b.x * a.y;
|
|
81
|
+
}
|
|
82
|
+
return s / 2;
|
|
83
|
+
}
|
|
84
|
+
/** Whether `p` lies inside the convex polygon `poly` (either winding). */
|
|
85
|
+
export function pointInConvex(p, poly) {
|
|
86
|
+
if (poly.length < 3)
|
|
87
|
+
return false;
|
|
88
|
+
let sign = 0;
|
|
89
|
+
for (let i = 0; i < poly.length; i++) {
|
|
90
|
+
const a = poly[i];
|
|
91
|
+
const b = poly[(i + 1) % poly.length];
|
|
92
|
+
const c = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
|
|
93
|
+
if (Math.abs(c) < 1e-9)
|
|
94
|
+
continue;
|
|
95
|
+
const s = c > 0 ? 1 : -1;
|
|
96
|
+
if (sign === 0)
|
|
97
|
+
sign = s;
|
|
98
|
+
else if (s !== sign)
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** A convex polygon with each edge moved inward by `d` (clipped by the
|
|
104
|
+
* half-planes of the moved edges). Empty when it vanishes. */
|
|
105
|
+
export function insetConvex(poly, d) {
|
|
106
|
+
if (poly.length < 3)
|
|
107
|
+
return [];
|
|
108
|
+
if (d <= 0)
|
|
109
|
+
return poly.map((p) => ({ ...p }));
|
|
110
|
+
const cw = signedArea(poly) > 0;
|
|
111
|
+
let out = poly.map((p) => ({ ...p }));
|
|
112
|
+
for (let i = 0; i < poly.length && out.length > 0; i++) {
|
|
113
|
+
const a = poly[i];
|
|
114
|
+
const b = poly[(i + 1) % poly.length];
|
|
115
|
+
// Inward normal: on a clockwise (y-down) polygon, to the right of a→b.
|
|
116
|
+
const e = norm(sub(b, a));
|
|
117
|
+
const n = cw ? { x: -e.y, y: e.x } : { x: e.y, y: -e.x };
|
|
118
|
+
const a2 = add(a, scale(n, d));
|
|
119
|
+
out = clipHalfPlane(out, a2, n);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** The part of `poly` on the side of the line through `p` that `n` points
|
|
124
|
+
* to (Sutherland–Hodgman, one edge). */
|
|
125
|
+
export function clipHalfPlane(poly, p, n) {
|
|
126
|
+
const side = (q) => (q.x - p.x) * n.x + (q.y - p.y) * n.y;
|
|
127
|
+
const out = [];
|
|
128
|
+
for (let i = 0; i < poly.length; i++) {
|
|
129
|
+
const a = poly[i];
|
|
130
|
+
const b = poly[(i + 1) % poly.length];
|
|
131
|
+
const sa = side(a);
|
|
132
|
+
const sb = side(b);
|
|
133
|
+
if (sa >= 0)
|
|
134
|
+
out.push(a);
|
|
135
|
+
if ((sa >= 0) !== (sb >= 0))
|
|
136
|
+
out.push(lerp(a, b, sa / (sa - sb)));
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
/** The part of a convex `poly` inside the rectangle `r`. */
|
|
141
|
+
export function clipToRect(poly, r) {
|
|
142
|
+
let out = [...poly];
|
|
143
|
+
out = clipHalfPlane(out, { x: r.x, y: r.y }, { x: 1, y: 0 });
|
|
144
|
+
out = clipHalfPlane(out, { x: r.x + r.width, y: r.y }, { x: -1, y: 0 });
|
|
145
|
+
out = clipHalfPlane(out, { x: r.x, y: r.y }, { x: 0, y: 1 });
|
|
146
|
+
out = clipHalfPlane(out, { x: r.x, y: r.y + r.height }, { x: 0, y: -1 });
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
/** Where the ray from `o` along `dir` first leaves the polygon (the
|
|
150
|
+
* distance along `dir`, normalised), or undefined. */
|
|
151
|
+
export function rayExit(o, dir, poly) {
|
|
152
|
+
let best;
|
|
153
|
+
for (let i = 0; i < poly.length; i++) {
|
|
154
|
+
const a = poly[i];
|
|
155
|
+
const b = poly[(i + 1) % poly.length];
|
|
156
|
+
const t = raySegment(o, dir, a, b);
|
|
157
|
+
if (t !== undefined && t > 1e-9 && (best === undefined || t < best))
|
|
158
|
+
best = t;
|
|
159
|
+
}
|
|
160
|
+
return best;
|
|
161
|
+
}
|
|
162
|
+
/** Distance along `dir` (unit) from `o` to segment `ab`, or undefined. */
|
|
163
|
+
export function raySegment(o, dir, a, b) {
|
|
164
|
+
const ex = b.x - a.x;
|
|
165
|
+
const ey = b.y - a.y;
|
|
166
|
+
const den = dir.x * ey - dir.y * ex;
|
|
167
|
+
if (Math.abs(den) < 1e-12)
|
|
168
|
+
return undefined;
|
|
169
|
+
const t = ((a.x - o.x) * ey - (a.y - o.y) * ex) / den;
|
|
170
|
+
const u = ((a.x - o.x) * dir.y - (a.y - o.y) * dir.x) / den;
|
|
171
|
+
if (u < -1e-9 || u > 1 + 1e-9)
|
|
172
|
+
return undefined;
|
|
173
|
+
return t;
|
|
174
|
+
}
|
|
175
|
+
/** Whether segments `ab` and `cd` cross. */
|
|
176
|
+
export function segmentsCross(a, b, c, d) {
|
|
177
|
+
const o = (p, q, r) => (q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x);
|
|
178
|
+
const d1 = o(c, d, a);
|
|
179
|
+
const d2 = o(c, d, b);
|
|
180
|
+
const d3 = o(a, b, c);
|
|
181
|
+
const d4 = o(a, b, d);
|
|
182
|
+
return ((d1 > 0) !== (d2 > 0)) && ((d3 > 0) !== (d4 > 0)) && d1 !== 0 && d2 !== 0 && d3 !== 0 && d4 !== 0;
|
|
183
|
+
}
|
|
184
|
+
/** Distance between segments `ab` and `cd` (0 when they cross). */
|
|
185
|
+
export function segmentDistance(a, b, c, d) {
|
|
186
|
+
if (segmentsCross(a, b, c, d))
|
|
187
|
+
return 0;
|
|
188
|
+
const toSeg = (p, s, e) => {
|
|
189
|
+
const vx = e.x - s.x;
|
|
190
|
+
const vy = e.y - s.y;
|
|
191
|
+
const l2 = vx * vx + vy * vy;
|
|
192
|
+
const t = l2 > 0 ? Math.max(0, Math.min(1, ((p.x - s.x) * vx + (p.y - s.y) * vy) / l2)) : 0;
|
|
193
|
+
return Math.hypot(p.x - (s.x + t * vx), p.y - (s.y + t * vy));
|
|
194
|
+
};
|
|
195
|
+
return Math.min(toSeg(a, c, d), toSeg(b, c, d), toSeg(c, a, b), toSeg(d, a, b));
|
|
196
|
+
}
|
|
197
|
+
/** Whether segment `ab` crosses rect `r` (or lies inside it). */
|
|
198
|
+
export function segmentHitsRect(a, b, r) {
|
|
199
|
+
if (pointInRect(a, r) || pointInRect(b, r))
|
|
200
|
+
return true;
|
|
201
|
+
const c = [
|
|
202
|
+
{ x: r.x, y: r.y }, { x: r.x + r.width, y: r.y },
|
|
203
|
+
{ x: r.x + r.width, y: r.y + r.height }, { x: r.x, y: r.y + r.height },
|
|
204
|
+
];
|
|
205
|
+
for (let i = 0; i < 4; i++)
|
|
206
|
+
if (segmentsCross(a, b, c[i], c[(i + 1) % 4]))
|
|
207
|
+
return true;
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
/** The point of the polygon's boundary nearest to `p`. */
|
|
211
|
+
export function nearestOnPolygon(p, poly) {
|
|
212
|
+
let best = poly[0] ?? p;
|
|
213
|
+
let bestD = Infinity;
|
|
214
|
+
for (let i = 0; i < poly.length; i++) {
|
|
215
|
+
const a = poly[i];
|
|
216
|
+
const b = poly[(i + 1) % poly.length];
|
|
217
|
+
const ab = sub(b, a);
|
|
218
|
+
const l2 = ab.x * ab.x + ab.y * ab.y || 1;
|
|
219
|
+
const t = Math.max(0, Math.min(1, ((p.x - a.x) * ab.x + (p.y - a.y) * ab.y) / l2));
|
|
220
|
+
const q = add(a, scale(ab, t));
|
|
221
|
+
const d = dist(p, q);
|
|
222
|
+
if (d < bestD) {
|
|
223
|
+
bestD = d;
|
|
224
|
+
best = q;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return best;
|
|
228
|
+
}
|
|
229
|
+
const num = (v) => {
|
|
230
|
+
const r = Math.round(v * 100) / 100;
|
|
231
|
+
return Object.is(r, -0) ? '0' : String(r);
|
|
232
|
+
};
|
|
233
|
+
const pt = (p) => `${num(p.x)} ${num(p.y)}`;
|
|
234
|
+
/** SVG path data of commands (two decimals). */
|
|
235
|
+
export function pathData(cmds) {
|
|
236
|
+
let d = '';
|
|
237
|
+
for (const c of cmds) {
|
|
238
|
+
if (c.op === 'Z')
|
|
239
|
+
d += 'Z';
|
|
240
|
+
else if (c.op === 'C')
|
|
241
|
+
d += `C${pt(c.pts[0])} ${pt(c.pts[1])} ${pt(c.pts[2])}`;
|
|
242
|
+
else
|
|
243
|
+
d += `${c.op}${pt(c.pts[0])}`;
|
|
244
|
+
}
|
|
245
|
+
return d;
|
|
246
|
+
}
|
|
247
|
+
/** Commands moved by (dx, dy). */
|
|
248
|
+
export function translateCmds(cmds, dx, dy) {
|
|
249
|
+
return cmds.map((c) => ({ op: c.op, pts: c.pts.map((p) => ({ x: p.x + dx, y: p.y + dy })) }));
|
|
250
|
+
}
|
|
251
|
+
/** A closed polygon (straight segments). */
|
|
252
|
+
export function polygonCmds(points) {
|
|
253
|
+
if (points.length === 0)
|
|
254
|
+
return [];
|
|
255
|
+
return [
|
|
256
|
+
{ op: 'M', pts: [{ ...points[0] }] },
|
|
257
|
+
...points.slice(1).map((p) => ({ op: 'L', pts: [{ ...p }] })),
|
|
258
|
+
{ op: 'Z', pts: [] },
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
/** A closed smooth curve through `points` (Catmull-Rom turned into cubic
|
|
262
|
+
* Beziers). */
|
|
263
|
+
export function smoothClosedCmds(points) {
|
|
264
|
+
const n = points.length;
|
|
265
|
+
if (n < 3)
|
|
266
|
+
return polygonCmds(points);
|
|
267
|
+
const out = [{ op: 'M', pts: [{ ...points[0] }] }];
|
|
268
|
+
for (let i = 0; i < n; i++) {
|
|
269
|
+
const p0 = points[(i - 1 + n) % n];
|
|
270
|
+
const p1 = points[i];
|
|
271
|
+
const p2 = points[(i + 1) % n];
|
|
272
|
+
const p3 = points[(i + 2) % n];
|
|
273
|
+
out.push({
|
|
274
|
+
op: 'C',
|
|
275
|
+
pts: [
|
|
276
|
+
{ x: p1.x + (p2.x - p0.x) / 6, y: p1.y + (p2.y - p0.y) / 6 },
|
|
277
|
+
{ x: p2.x - (p3.x - p1.x) / 6, y: p2.y - (p3.y - p1.y) / 6 },
|
|
278
|
+
{ ...p2 },
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
out.push({ op: 'Z', pts: [] });
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
/** An open smooth curve through `points` (Catmull-Rom, ends clamped), as
|
|
286
|
+
* `C` commands that continue from `points[0]`. */
|
|
287
|
+
export function smoothOpenCmds(points) {
|
|
288
|
+
const n = points.length;
|
|
289
|
+
const out = [];
|
|
290
|
+
for (let i = 0; i + 1 < n; i++) {
|
|
291
|
+
const p0 = points[Math.max(0, i - 1)];
|
|
292
|
+
const p1 = points[i];
|
|
293
|
+
const p2 = points[i + 1];
|
|
294
|
+
const p3 = points[Math.min(n - 1, i + 2)];
|
|
295
|
+
out.push({
|
|
296
|
+
op: 'C',
|
|
297
|
+
pts: [
|
|
298
|
+
{ x: p1.x + (p2.x - p0.x) / 6, y: p1.y + (p2.y - p0.y) / 6 },
|
|
299
|
+
{ x: p2.x - (p3.x - p1.x) / 6, y: p2.y - (p3.y - p1.y) / 6 },
|
|
300
|
+
{ ...p2 },
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
/** A circle as four cubic arcs. */
|
|
307
|
+
export function circleCmds(c, r) {
|
|
308
|
+
const k = 0.5522847498 * r;
|
|
309
|
+
const P = (x, y) => ({ x: c.x + x, y: c.y + y });
|
|
310
|
+
return [
|
|
311
|
+
{ op: 'M', pts: [P(r, 0)] },
|
|
312
|
+
{ op: 'C', pts: [P(r, k), P(k, r), P(0, r)] },
|
|
313
|
+
{ op: 'C', pts: [P(-k, r), P(-r, k), P(-r, 0)] },
|
|
314
|
+
{ op: 'C', pts: [P(-r, -k), P(-k, -r), P(0, -r)] },
|
|
315
|
+
{ op: 'C', pts: [P(k, -r), P(r, -k), P(r, 0)] },
|
|
316
|
+
{ op: 'Z', pts: [] },
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
/** A rectangle with corners rounded by `radius` (cubic arcs). */
|
|
320
|
+
export function roundedRectCmds(r, radius) {
|
|
321
|
+
const rad = Math.max(0, Math.min(radius, r.width / 2, r.height / 2));
|
|
322
|
+
const { x, y, width: w, height: h } = r;
|
|
323
|
+
if (rad <= 0.01)
|
|
324
|
+
return polygonCmds([{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }]);
|
|
325
|
+
const k = 0.5522847498 * rad;
|
|
326
|
+
return [
|
|
327
|
+
{ op: 'M', pts: [{ x: x + rad, y }] },
|
|
328
|
+
{ op: 'L', pts: [{ x: x + w - rad, y }] },
|
|
329
|
+
{ op: 'C', pts: [{ x: x + w - rad + k, y }, { x: x + w, y: y + rad - k }, { x: x + w, y: y + rad }] },
|
|
330
|
+
{ op: 'L', pts: [{ x: x + w, y: y + h - rad }] },
|
|
331
|
+
{ op: 'C', pts: [{ x: x + w, y: y + h - rad + k }, { x: x + w - rad + k, y: y + h }, { x: x + w - rad, y: y + h }] },
|
|
332
|
+
{ op: 'L', pts: [{ x: x + rad, y: y + h }] },
|
|
333
|
+
{ op: 'C', pts: [{ x: x + rad - k, y: y + h }, { x, y: y + h - rad + k }, { x, y: y + h - rad }] },
|
|
334
|
+
{ op: 'L', pts: [{ x, y: y + rad }] },
|
|
335
|
+
{ op: 'C', pts: [{ x, y: y + rad - k }, { x: x + rad - k, y }, { x: x + rad, y }] },
|
|
336
|
+
{ op: 'Z', pts: [] },
|
|
337
|
+
];
|
|
338
|
+
}
|
|
339
|
+
/** Length of the closed polygon through `points`. */
|
|
340
|
+
export function perimeter(points) {
|
|
341
|
+
let s = 0;
|
|
342
|
+
for (let i = 0; i < points.length; i++)
|
|
343
|
+
s += dist(points[i], points[(i + 1) % points.length]);
|
|
344
|
+
return s;
|
|
345
|
+
}
|
|
346
|
+
/** Points at `count` equal steps of arc length along the closed polygon
|
|
347
|
+
* `points` (starting at `points[0]`, shifted by `phase` steps), each with
|
|
348
|
+
* the polygon's outward normal there (`cw`: the polygon runs clockwise on
|
|
349
|
+
* the page). */
|
|
350
|
+
export function resampleClosed(points, count, phase = 0) {
|
|
351
|
+
const n = points.length;
|
|
352
|
+
const cum = [0];
|
|
353
|
+
for (let i = 0; i < n; i++)
|
|
354
|
+
cum.push(cum[i] + dist(points[i], points[(i + 1) % n]));
|
|
355
|
+
const total = cum[n];
|
|
356
|
+
const cw = signedArea(points) > 0;
|
|
357
|
+
const out = [];
|
|
358
|
+
let seg = 0;
|
|
359
|
+
for (let j = 0; j < count; j++) {
|
|
360
|
+
let s = ((j + phase) / count) * total;
|
|
361
|
+
s = ((s % total) + total) % total;
|
|
362
|
+
while (seg > 0 && cum[seg] > s)
|
|
363
|
+
seg--;
|
|
364
|
+
while (seg < n - 1 && cum[seg + 1] < s)
|
|
365
|
+
seg++;
|
|
366
|
+
const a = points[seg];
|
|
367
|
+
const b = points[(seg + 1) % n];
|
|
368
|
+
const t = (s - cum[seg]) / Math.max(1e-9, cum[seg + 1] - cum[seg]);
|
|
369
|
+
const e = norm(sub(b, a));
|
|
370
|
+
// Outward: left of the direction on a clockwise (y-down) polygon.
|
|
371
|
+
const nn = cw ? { x: e.y, y: -e.x } : { x: -e.y, y: e.x };
|
|
372
|
+
out.push({ p: lerp(a, b, t), n: nn });
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=geom.js.map
|