postext 1.19.3 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/dist/__tests__/comics/art.test.d.ts +2 -0
  2. package/dist/__tests__/comics/art.test.d.ts.map +1 -0
  3. package/dist/__tests__/comics/art.test.js +96 -0
  4. package/dist/__tests__/comics/art.test.js.map +1 -0
  5. package/dist/__tests__/comics/build.test.d.ts +2 -0
  6. package/dist/__tests__/comics/build.test.d.ts.map +1 -0
  7. package/dist/__tests__/comics/build.test.js +215 -0
  8. package/dist/__tests__/comics/build.test.js.map +1 -0
  9. package/dist/__tests__/comics/canvas.test.d.ts +2 -0
  10. package/dist/__tests__/comics/canvas.test.d.ts.map +1 -0
  11. package/dist/__tests__/comics/canvas.test.js +161 -0
  12. package/dist/__tests__/comics/canvas.test.js.map +1 -0
  13. package/dist/__tests__/comics/config.test.d.ts +2 -0
  14. package/dist/__tests__/comics/config.test.d.ts.map +1 -0
  15. package/dist/__tests__/comics/config.test.js +97 -0
  16. package/dist/__tests__/comics/config.test.js.map +1 -0
  17. package/dist/__tests__/comics/geometry.test.d.ts +2 -0
  18. package/dist/__tests__/comics/geometry.test.d.ts.map +1 -0
  19. package/dist/__tests__/comics/geometry.test.js +118 -0
  20. package/dist/__tests__/comics/geometry.test.js.map +1 -0
  21. package/dist/__tests__/comics/html.test.d.ts +2 -0
  22. package/dist/__tests__/comics/html.test.d.ts.map +1 -0
  23. package/dist/__tests__/comics/html.test.js +253 -0
  24. package/dist/__tests__/comics/html.test.js.map +1 -0
  25. package/dist/__tests__/comics/lettering.test.d.ts +2 -0
  26. package/dist/__tests__/comics/lettering.test.d.ts.map +1 -0
  27. package/dist/__tests__/comics/lettering.test.js +241 -0
  28. package/dist/__tests__/comics/lettering.test.js.map +1 -0
  29. package/dist/__tests__/comics/script.test.d.ts +2 -0
  30. package/dist/__tests__/comics/script.test.d.ts.map +1 -0
  31. package/dist/__tests__/comics/script.test.js +147 -0
  32. package/dist/__tests__/comics/script.test.js.map +1 -0
  33. package/dist/__tests__/comics/split.test.d.ts +2 -0
  34. package/dist/__tests__/comics/split.test.d.ts.map +1 -0
  35. package/dist/__tests__/comics/split.test.js +120 -0
  36. package/dist/__tests__/comics/split.test.js.map +1 -0
  37. package/dist/__tests__/comics/spread.test.d.ts +2 -0
  38. package/dist/__tests__/comics/spread.test.d.ts.map +1 -0
  39. package/dist/__tests__/comics/spread.test.js +161 -0
  40. package/dist/__tests__/comics/spread.test.js.map +1 -0
  41. package/dist/__tests__/comics/strip.test.d.ts +2 -0
  42. package/dist/__tests__/comics/strip.test.d.ts.map +1 -0
  43. package/dist/__tests__/comics/strip.test.js +231 -0
  44. package/dist/__tests__/comics/strip.test.js.map +1 -0
  45. package/dist/__tests__/exports.test.js +89 -0
  46. package/dist/__tests__/exports.test.js.map +1 -1
  47. package/dist/__tests__/parse/containers.test.js +1 -1
  48. package/dist/__tests__/parse/containers.test.js.map +1 -1
  49. package/dist/canvas-backend/comic.d.ts +32 -0
  50. package/dist/canvas-backend/comic.d.ts.map +1 -0
  51. package/dist/canvas-backend/comic.js +216 -0
  52. package/dist/canvas-backend/comic.js.map +1 -0
  53. package/dist/canvas-backend/headerFooter.d.ts +4 -1
  54. package/dist/canvas-backend/headerFooter.d.ts.map +1 -1
  55. package/dist/canvas-backend/headerFooter.js +3 -1
  56. package/dist/canvas-backend/headerFooter.js.map +1 -1
  57. package/dist/canvas-backend/index.d.ts.map +1 -1
  58. package/dist/canvas-backend/index.js +7 -1
  59. package/dist/canvas-backend/index.js.map +1 -1
  60. package/dist/comics/art.d.ts +72 -0
  61. package/dist/comics/art.d.ts.map +1 -0
  62. package/dist/comics/art.js +154 -0
  63. package/dist/comics/art.js.map +1 -0
  64. package/dist/comics/fonts.d.ts +20 -0
  65. package/dist/comics/fonts.d.ts.map +1 -0
  66. package/dist/comics/fonts.js +42 -0
  67. package/dist/comics/fonts.js.map +1 -0
  68. package/dist/comics/geometry.d.ts +87 -0
  69. package/dist/comics/geometry.d.ts.map +1 -0
  70. package/dist/comics/geometry.js +272 -0
  71. package/dist/comics/geometry.js.map +1 -0
  72. package/dist/comics/index.d.ts +29 -0
  73. package/dist/comics/index.d.ts.map +1 -0
  74. package/dist/comics/index.js +18 -0
  75. package/dist/comics/index.js.map +1 -0
  76. package/dist/comics/layoutPage.d.ts +156 -0
  77. package/dist/comics/layoutPage.d.ts.map +1 -0
  78. package/dist/comics/layoutPage.js +697 -0
  79. package/dist/comics/layoutPage.js.map +1 -0
  80. package/dist/comics/lettering/__tests__/harness.test.d.ts +2 -0
  81. package/dist/comics/lettering/__tests__/harness.test.d.ts.map +1 -0
  82. package/dist/comics/lettering/__tests__/harness.test.js +273 -0
  83. package/dist/comics/lettering/__tests__/harness.test.js.map +1 -0
  84. package/dist/comics/lettering/__tests__/letterPanel.test.d.ts +2 -0
  85. package/dist/comics/lettering/__tests__/letterPanel.test.d.ts.map +1 -0
  86. package/dist/comics/lettering/__tests__/letterPanel.test.js +224 -0
  87. package/dist/comics/lettering/__tests__/letterPanel.test.js.map +1 -0
  88. package/dist/comics/lettering/__tests__/polish.test.d.ts +2 -0
  89. package/dist/comics/lettering/__tests__/polish.test.d.ts.map +1 -0
  90. package/dist/comics/lettering/__tests__/polish.test.js +357 -0
  91. package/dist/comics/lettering/__tests__/polish.test.js.map +1 -0
  92. package/dist/comics/lettering/__tests__/round2.test.d.ts +2 -0
  93. package/dist/comics/lettering/__tests__/round2.test.d.ts.map +1 -0
  94. package/dist/comics/lettering/__tests__/round2.test.js +289 -0
  95. package/dist/comics/lettering/__tests__/round2.test.js.map +1 -0
  96. package/dist/comics/lettering/__tests__/shapeText.test.d.ts +2 -0
  97. package/dist/comics/lettering/__tests__/shapeText.test.d.ts.map +1 -0
  98. package/dist/comics/lettering/__tests__/shapeText.test.js +98 -0
  99. package/dist/comics/lettering/__tests__/shapeText.test.js.map +1 -0
  100. package/dist/comics/lettering/__tests__/shapes.test.d.ts +2 -0
  101. package/dist/comics/lettering/__tests__/shapes.test.d.ts.map +1 -0
  102. package/dist/comics/lettering/__tests__/shapes.test.js +137 -0
  103. package/dist/comics/lettering/__tests__/shapes.test.js.map +1 -0
  104. package/dist/comics/lettering/__tests__/stubMeasure.d.ts +5 -0
  105. package/dist/comics/lettering/__tests__/stubMeasure.d.ts.map +1 -0
  106. package/dist/comics/lettering/__tests__/stubMeasure.js +47 -0
  107. package/dist/comics/lettering/__tests__/stubMeasure.js.map +1 -0
  108. package/dist/comics/lettering/__tests__/text.test.d.ts +2 -0
  109. package/dist/comics/lettering/__tests__/text.test.d.ts.map +1 -0
  110. package/dist/comics/lettering/__tests__/text.test.js +86 -0
  111. package/dist/comics/lettering/__tests__/text.test.js.map +1 -0
  112. package/dist/comics/lettering/geom.d.ts +78 -0
  113. package/dist/comics/lettering/geom.d.ts.map +1 -0
  114. package/dist/comics/lettering/geom.js +376 -0
  115. package/dist/comics/lettering/geom.js.map +1 -0
  116. package/dist/comics/lettering/index.d.ts +7 -0
  117. package/dist/comics/lettering/index.d.ts.map +1 -0
  118. package/dist/comics/lettering/index.js +8 -0
  119. package/dist/comics/lettering/index.js.map +1 -0
  120. package/dist/comics/lettering/letter.d.ts +7 -0
  121. package/dist/comics/lettering/letter.d.ts.map +1 -0
  122. package/dist/comics/lettering/letter.js +397 -0
  123. package/dist/comics/lettering/letter.js.map +1 -0
  124. package/dist/comics/lettering/placement.d.ts +117 -0
  125. package/dist/comics/lettering/placement.d.ts.map +1 -0
  126. package/dist/comics/lettering/placement.js +889 -0
  127. package/dist/comics/lettering/placement.js.map +1 -0
  128. package/dist/comics/lettering/presets.d.ts +15 -0
  129. package/dist/comics/lettering/presets.d.ts.map +1 -0
  130. package/dist/comics/lettering/presets.js +61 -0
  131. package/dist/comics/lettering/presets.js.map +1 -0
  132. package/dist/comics/lettering/shape-text.d.ts +72 -0
  133. package/dist/comics/lettering/shape-text.d.ts.map +1 -0
  134. package/dist/comics/lettering/shape-text.js +550 -0
  135. package/dist/comics/lettering/shape-text.js.map +1 -0
  136. package/dist/comics/lettering/shapes.d.ts +83 -0
  137. package/dist/comics/lettering/shapes.d.ts.map +1 -0
  138. package/dist/comics/lettering/shapes.js +512 -0
  139. package/dist/comics/lettering/shapes.js.map +1 -0
  140. package/dist/comics/lettering/text.d.ts +79 -0
  141. package/dist/comics/lettering/text.d.ts.map +1 -0
  142. package/dist/comics/lettering/text.js +488 -0
  143. package/dist/comics/lettering/text.js.map +1 -0
  144. package/dist/comics/lettering/types.d.ts +270 -0
  145. package/dist/comics/lettering/types.d.ts.map +1 -0
  146. package/dist/comics/lettering/types.js +9 -0
  147. package/dist/comics/lettering/types.js.map +1 -0
  148. package/dist/comics/page.d.ts +37 -0
  149. package/dist/comics/page.d.ts.map +1 -0
  150. package/dist/comics/page.js +285 -0
  151. package/dist/comics/page.js.map +1 -0
  152. package/dist/comics/paint.d.ts +65 -0
  153. package/dist/comics/paint.d.ts.map +1 -0
  154. package/dist/comics/paint.js +219 -0
  155. package/dist/comics/paint.js.map +1 -0
  156. package/dist/comics/script.d.ts +42 -0
  157. package/dist/comics/script.d.ts.map +1 -0
  158. package/dist/comics/script.js +302 -0
  159. package/dist/comics/script.js.map +1 -0
  160. package/dist/comics/split.d.ts +135 -0
  161. package/dist/comics/split.d.ts.map +1 -0
  162. package/dist/comics/split.js +514 -0
  163. package/dist/comics/split.js.map +1 -0
  164. package/dist/comics/spread.d.ts +36 -0
  165. package/dist/comics/spread.d.ts.map +1 -0
  166. package/dist/comics/spread.js +172 -0
  167. package/dist/comics/spread.js.map +1 -0
  168. package/dist/comics/strip.d.ts +77 -0
  169. package/dist/comics/strip.d.ts.map +1 -0
  170. package/dist/comics/strip.js +89 -0
  171. package/dist/comics/strip.js.map +1 -0
  172. package/dist/comics/transform.d.ts +37 -0
  173. package/dist/comics/transform.d.ts.map +1 -0
  174. package/dist/comics/transform.js +161 -0
  175. package/dist/comics/transform.js.map +1 -0
  176. package/dist/comics/types.d.ts +143 -0
  177. package/dist/comics/types.d.ts.map +1 -0
  178. package/dist/comics/types.js +13 -0
  179. package/dist/comics/types.js.map +1 -0
  180. package/dist/comics/warnings.d.ts +14 -0
  181. package/dist/comics/warnings.d.ts.map +1 -0
  182. package/dist/comics/warnings.js +94 -0
  183. package/dist/comics/warnings.js.map +1 -0
  184. package/dist/configWarnings.d.ts.map +1 -1
  185. package/dist/configWarnings.js +42 -0
  186. package/dist/configWarnings.js.map +1 -1
  187. package/dist/defaults/comics.d.ts +51 -0
  188. package/dist/defaults/comics.d.ts.map +1 -0
  189. package/dist/defaults/comics.js +385 -0
  190. package/dist/defaults/comics.js.map +1 -0
  191. package/dist/defaults/index.d.ts +1 -0
  192. package/dist/defaults/index.d.ts.map +1 -1
  193. package/dist/defaults/index.js +9 -0
  194. package/dist/defaults/index.js.map +1 -1
  195. package/dist/design/layout.d.ts +13 -1
  196. package/dist/design/layout.d.ts.map +1 -1
  197. package/dist/design/layout.js +17 -0
  198. package/dist/design/layout.js.map +1 -1
  199. package/dist/html-backend.d.ts +3 -0
  200. package/dist/html-backend.d.ts.map +1 -1
  201. package/dist/html-backend.js +44 -8
  202. package/dist/html-backend.js.map +1 -1
  203. package/dist/htmlComic.d.ts +69 -0
  204. package/dist/htmlComic.d.ts.map +1 -0
  205. package/dist/htmlComic.js +179 -0
  206. package/dist/htmlComic.js.map +1 -0
  207. package/dist/index.d.ts +8 -2
  208. package/dist/index.d.ts.map +1 -1
  209. package/dist/index.js +4 -0
  210. package/dist/index.js.map +1 -1
  211. package/dist/parse/blockParser.d.ts.map +1 -1
  212. package/dist/parse/blockParser.js +25 -1
  213. package/dist/parse/blockParser.js.map +1 -1
  214. package/dist/parse/indexMarks.d.ts.map +1 -1
  215. package/dist/parse/indexMarks.js +3 -0
  216. package/dist/parse/indexMarks.js.map +1 -1
  217. package/dist/parse/types.d.ts +7 -1
  218. package/dist/parse/types.d.ts.map +1 -1
  219. package/dist/pipeline/build.d.ts.map +1 -1
  220. package/dist/pipeline/build.js +251 -9
  221. package/dist/pipeline/build.js.map +1 -1
  222. package/dist/pipeline/config.d.ts.map +1 -1
  223. package/dist/pipeline/config.js +4 -1
  224. package/dist/pipeline/config.js.map +1 -1
  225. package/dist/pipeline/contentWarnings.d.ts.map +1 -1
  226. package/dist/pipeline/contentWarnings.js +58 -0
  227. package/dist/pipeline/contentWarnings.js.map +1 -1
  228. package/dist/pipeline/floatSlots.js +1 -1
  229. package/dist/pipeline/floatSlots.js.map +1 -1
  230. package/dist/pipeline/headerFooter.d.ts +6 -2
  231. package/dist/pipeline/headerFooter.d.ts.map +1 -1
  232. package/dist/pipeline/headerFooter.js +11 -3
  233. package/dist/pipeline/headerFooter.js.map +1 -1
  234. package/dist/pipeline/pageRoles.d.ts +1 -0
  235. package/dist/pipeline/pageRoles.d.ts.map +1 -1
  236. package/dist/pipeline/pageRoles.js +3 -0
  237. package/dist/pipeline/pageRoles.js.map +1 -1
  238. package/dist/pipeline/placement.d.ts +3 -3
  239. package/dist/pipeline/placement.d.ts.map +1 -1
  240. package/dist/pipeline/placement.js +4 -4
  241. package/dist/pipeline/placement.js.map +1 -1
  242. package/dist/types.d.ts +338 -1
  243. package/dist/types.d.ts.map +1 -1
  244. package/dist/vdt.d.ts +271 -2
  245. package/dist/vdt.d.ts.map +1 -1
  246. package/dist/vdt.js.map +1 -1
  247. package/dist/worker/fonts.d.ts +2 -1
  248. package/dist/worker/fonts.d.ts.map +1 -1
  249. package/dist/worker/fonts.js +11 -1
  250. package/dist/worker/fonts.js.map +1 -1
  251. package/package.json +1 -1
@@ -0,0 +1,137 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildBody, buildNeck, buildTail, fitSuperellipse, tailLength, translateBody } from '../shapes';
3
+ import { pathData, pointInConvex, pointInRect, dist } from '../geom';
4
+ import { presetLetteringStyles } from '../presets';
5
+ const EM = 12;
6
+ const styles = presetLetteringStyles({ fontSizePx: EM, locale: 'en', fontFamily: 'Test' });
7
+ const speech = styles.speech;
8
+ // A diamond text block: three lines, the middle one longest.
9
+ const INK = [
10
+ { x: 20, y: 0, width: 60, height: 11 },
11
+ { x: 0, y: 14, width: 100, height: 11 },
12
+ { x: 15, y: 28, width: 70, height: 11 },
13
+ ];
14
+ const corners = (r) => [
15
+ { x: r.x, y: r.y }, { x: r.x + r.width, y: r.y }, { x: r.x + r.width, y: r.y + r.height }, { x: r.x, y: r.y + r.height },
16
+ ];
17
+ /** Distance from `p` to the polygon's boundary. */
18
+ function edgeDistance(p, poly) {
19
+ let best = Infinity;
20
+ for (let i = 0; i < poly.length; i++) {
21
+ const a = poly[i];
22
+ const b = poly[(i + 1) % poly.length];
23
+ const abx = b.x - a.x;
24
+ const aby = b.y - a.y;
25
+ const t = Math.max(0, Math.min(1, ((p.x - a.x) * abx + (p.y - a.y) * aby) / (abx * abx + aby * aby || 1)));
26
+ best = Math.min(best, dist(p, { x: a.x + abx * t, y: a.y + aby * t }));
27
+ }
28
+ return best;
29
+ }
30
+ const isClosed = (d) => d.startsWith('M') && d.endsWith('Z') && /^[MLCZ0-9 .\-]+$/.test(d);
31
+ describe('balloon bodies (SPEC D3.2)', () => {
32
+ for (const shape of ['oval', 'cloud', 'burst', 'wavy', 'electric', 'rounded', 'rectangle']) {
33
+ it(`${shape}: a closed outline holding the text with its air`, () => {
34
+ const style = { ...speech, shape };
35
+ const body = buildBody(INK, EM, style, 'b1');
36
+ const d = pathData(body.cmds);
37
+ expect(isClosed(d)).toBe(true);
38
+ for (const r of INK) {
39
+ for (const c of corners(r)) {
40
+ expect(pointInConvex(c, body.core)).toBe(true);
41
+ // Constant air: no ink corner closer to the outline than the
42
+ // padding (2 % for the sampled outline).
43
+ expect(edgeDistance(c, body.core)).toBeGreaterThanOrEqual(style.padding * 0.98 - 0.5);
44
+ }
45
+ }
46
+ const b = body.bbox;
47
+ expect(b.x).toBeLessThan(0 - style.padding + 1);
48
+ expect(b.x + b.width).toBeGreaterThan(100 + style.padding - 1);
49
+ });
50
+ }
51
+ it('hugs a diamond: an oval no wider than the text plus generous air', () => {
52
+ const body = buildBody(INK, EM, speech, 'b1');
53
+ expect(body.bbox.width).toBeLessThan(100 + 2 * speech.padding + 0.6 * 100);
54
+ expect(body.bbox.height).toBeLessThan(39 + 2 * speech.padding + 0.8 * 39);
55
+ });
56
+ it('fits the smallest superellipse around points', () => {
57
+ const pts = [{ x: -10, y: -5 }, { x: 10, y: 5 }, { x: 10, y: -5 }, { x: -10, y: 5 }];
58
+ const { a, b } = fitSuperellipse(pts, { x: 0, y: 0 }, 2);
59
+ expect(a * b).toBeCloseTo(100, 0); // a = 10√2, b = 5√2 for an ellipse
60
+ });
61
+ it('jitters bursts and clouds from the seed only', () => {
62
+ const burst = { ...speech, shape: 'burst' };
63
+ const a = pathData(buildBody(INK, EM, burst, 'x').cmds);
64
+ const b = pathData(buildBody(INK, EM, burst, 'x').cmds);
65
+ const c = pathData(buildBody(INK, EM, burst, 'y').cmds);
66
+ expect(a).toBe(b);
67
+ expect(a).not.toBe(c);
68
+ const wobbly = (seed) => pathData(buildBody(INK, EM, { ...speech, wobble: 0.04 }, seed).cmds);
69
+ expect(wobbly('s')).toBe(wobbly('s'));
70
+ expect(wobbly('s')).not.toBe(wobbly('t'));
71
+ });
72
+ it('points every burst spike away from the centre', () => {
73
+ const body = buildBody(INK, EM, { ...speech, shape: 'burst' }, 'z');
74
+ for (let i = 1; i < body.hull.length; i += 2) {
75
+ expect(dist(body.hull[i], body.centre)).toBeGreaterThan(dist(body.hull[i - 1], body.centre));
76
+ }
77
+ });
78
+ });
79
+ describe('tails (SPEC D3.2)', () => {
80
+ const body = translateBody(buildBody(INK, EM, speech, 't'), 200, 100);
81
+ const target = { x: 160, y: 260 };
82
+ it('leaves the body, aims at the target and stops at the reach', () => {
83
+ const tail = buildTail(body, target, speech, { strokeWidth: speech.strokeWidth });
84
+ expect(tail).toBeDefined();
85
+ expect(isClosed(pathData(tail.cmds))).toBe(true);
86
+ const gap = dist(tail.base, target);
87
+ // 0.55 of the gap, the tip never farther than 2.5 em from the target.
88
+ expect(dist(tail.base, tail.tip)).toBeCloseTo(gap - Math.min(0.45 * gap, 2.5 * EM), 0);
89
+ expect(dist(tail.tip, target)).toBeGreaterThanOrEqual(0.5 * EM);
90
+ expect(dist(tail.tip, target)).toBeLessThanOrEqual(2.5 * EM + 0.5);
91
+ // The tip lies on the line from the exit to the target.
92
+ const ux = (target.x - tail.base.x) / gap;
93
+ const uy = (target.y - tail.base.y) / gap;
94
+ const along = (tail.tip.x - tail.base.x) * ux + (tail.tip.y - tail.base.y) * uy;
95
+ expect(along).toBeCloseTo(dist(tail.base, tail.tip), 3);
96
+ // It starts inside the body (the layer method merges the outlines).
97
+ const first = tail.cmds[0].pts[0];
98
+ expect(pointInConvex(first, body.core)).toBe(true);
99
+ });
100
+ it('ends about as near its target whatever the gap (consistent reach)', () => {
101
+ expect(tailLength(4 * EM, EM, speech, false)).toBeCloseTo(2.2 * EM, 6);
102
+ // A far balloon: the tip still ends 2.5 em from the mouth.
103
+ expect(tailLength(20 * EM, EM, speech, false)).toBeCloseTo(17.5 * EM, 6);
104
+ // Close: never nearer than tailGap (0.5 em).
105
+ expect(tailLength(EM, EM, speech, false)).toBeCloseTo(0.5 * EM, 6);
106
+ expect(tailLength(7 * EM, EM, speech, true)).toBe(7 * EM);
107
+ });
108
+ it('stops short of the speaker\'s face', () => {
109
+ const face = { x: 120, y: 185, width: 110, height: 100 };
110
+ const tail = buildTail(body, target, speech, { strokeWidth: 1, face });
111
+ expect(pointInRect(tail.tip, face)).toBe(false);
112
+ const free = buildTail(body, target, speech, { strokeWidth: 1 });
113
+ expect(pointInRect(free.tip, face)).toBe(true);
114
+ });
115
+ it('runs to the border for an off-panel speaker', () => {
116
+ const border = { x: 200, y: 400 };
117
+ const tail = buildTail(body, border, speech, { strokeWidth: 1, offPanel: true });
118
+ expect(dist(tail.tip, border)).toBeLessThan(0.01);
119
+ });
120
+ it('draws thought bubbles, a lightning bolt and no tail', () => {
121
+ const bubbles = buildTail(body, target, { ...speech, tail: 'bubbles' }, { strokeWidth: 1 });
122
+ expect(bubbles.cmds.filter((c) => c.op === 'M')).toHaveLength(3);
123
+ const zig = buildTail(body, target, { ...speech, tail: 'zigzag' }, { strokeWidth: 1 });
124
+ expect(zig.cmds.every((c) => c.op !== 'C')).toBe(true);
125
+ expect(buildTail(body, target, { ...speech, tail: 'none' }, { strokeWidth: 1 })).toBeUndefined();
126
+ // A target inside the body has no tail.
127
+ expect(buildTail(body, body.centre, speech, { strokeWidth: 1 })).toBeUndefined();
128
+ });
129
+ it('joins two bodies with a neck that starts inside both', () => {
130
+ const other = translateBody(buildBody(INK, EM, speech, 'u'), 260, 190);
131
+ const neck = buildNeck(body, other, 1);
132
+ expect(isClosed(pathData(neck))).toBe(true);
133
+ const start = neck[0].pts[0];
134
+ expect(pointInConvex(start, body.core)).toBe(true);
135
+ });
136
+ });
137
+ //# sourceMappingURL=shapes.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapes.test.js","sourceRoot":"","sources":["../../../../src/comics/lettering/__tests__/shapes.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Advance of one character, in em. */
2
+ export declare function stubAdvance(ch: string): number;
3
+ /** Install the stub as the engine's measuring canvas. */
4
+ export declare function installStubMeasure(): void;
5
+ //# sourceMappingURL=stubMeasure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stubMeasure.d.ts","sourceRoot":"","sources":["../../../../src/comics/lettering/__tests__/stubMeasure.ts"],"names":[],"mappings":"AAQA,uCAAuC;AACvC,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAW9C;AAaD,yDAAyD;AACzD,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=text.test.d.ts.map
@@ -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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"}