postext 1.25.5 → 1.26.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__/cjk/headingNumberMark.test.d.ts +2 -0
- package/dist/__tests__/cjk/headingNumberMark.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/headingNumberMark.test.js +108 -0
- package/dist/__tests__/cjk/headingNumberMark.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseDash.test.d.ts +2 -0
- package/dist/__tests__/cjk/japaneseDash.test.d.ts.map +1 -0
- package/dist/__tests__/cjk/japaneseDash.test.js +250 -0
- package/dist/__tests__/cjk/japaneseDash.test.js.map +1 -0
- package/dist/__tests__/cjk/japaneseSpacing.test.js +1 -1
- package/dist/__tests__/cjk/punctuation.test.js +5 -3
- package/dist/__tests__/cjk/punctuation.test.js.map +1 -1
- package/dist/__tests__/columnRuleBands.test.js +47 -0
- package/dist/__tests__/columnRuleBands.test.js.map +1 -1
- package/dist/__tests__/comics/canvas.test.js +38 -1
- package/dist/__tests__/comics/canvas.test.js.map +1 -1
- package/dist/__tests__/comics/html.test.js +16 -3
- package/dist/__tests__/comics/html.test.js.map +1 -1
- package/dist/__tests__/design/cjkDesignDash.test.js +7 -4
- package/dist/__tests__/design/cjkDesignDash.test.js.map +1 -1
- package/dist/__tests__/englishApostropheHyphenation.test.d.ts +2 -0
- package/dist/__tests__/englishApostropheHyphenation.test.d.ts.map +1 -0
- package/dist/__tests__/englishApostropheHyphenation.test.js +47 -0
- package/dist/__tests__/englishApostropheHyphenation.test.js.map +1 -0
- package/dist/__tests__/exports.test.js +1 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/leaderMinRepeats.test.d.ts +2 -0
- package/dist/__tests__/leaderMinRepeats.test.d.ts.map +1 -0
- package/dist/__tests__/leaderMinRepeats.test.js +109 -0
- package/dist/__tests__/leaderMinRepeats.test.js.map +1 -0
- package/dist/__tests__/pipeline/footnoteRuntTracking.test.d.ts +2 -0
- package/dist/__tests__/pipeline/footnoteRuntTracking.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/footnoteRuntTracking.test.js +90 -0
- package/dist/__tests__/pipeline/footnoteRuntTracking.test.js.map +1 -0
- package/dist/__tests__/pipeline/orderedNumberAlign.test.d.ts +2 -0
- package/dist/__tests__/pipeline/orderedNumberAlign.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/orderedNumberAlign.test.js +108 -0
- package/dist/__tests__/pipeline/orderedNumberAlign.test.js.map +1 -0
- package/dist/__tests__/pipeline/trailingCapColumnNote.test.d.ts +2 -0
- package/dist/__tests__/pipeline/trailingCapColumnNote.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/trailingCapColumnNote.test.js +63 -0
- package/dist/__tests__/pipeline/trailingCapColumnNote.test.js.map +1 -0
- package/dist/__tests__/pipeline/trailingCapFloatHead.test.d.ts +2 -0
- package/dist/__tests__/pipeline/trailingCapFloatHead.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline/trailingCapFloatHead.test.js +91 -0
- package/dist/__tests__/pipeline/trailingCapFloatHead.test.js.map +1 -0
- package/dist/__tests__/rtl/arabicLeadingNumbers.test.d.ts +2 -0
- package/dist/__tests__/rtl/arabicLeadingNumbers.test.d.ts.map +1 -0
- package/dist/__tests__/rtl/arabicLeadingNumbers.test.js +186 -0
- package/dist/__tests__/rtl/arabicLeadingNumbers.test.js.map +1 -0
- package/dist/__tests__/rtl/listMarkerDirection.test.d.ts +2 -0
- package/dist/__tests__/rtl/listMarkerDirection.test.d.ts.map +1 -0
- package/dist/__tests__/rtl/listMarkerDirection.test.js +197 -0
- package/dist/__tests__/rtl/listMarkerDirection.test.js.map +1 -0
- package/dist/__tests__/trackedLigatures.test.d.ts +2 -0
- package/dist/__tests__/trackedLigatures.test.d.ts.map +1 -0
- package/dist/__tests__/trackedLigatures.test.js +587 -0
- package/dist/__tests__/trackedLigatures.test.js.map +1 -0
- package/dist/__tests__/vectorRasterScale.test.d.ts +2 -0
- package/dist/__tests__/vectorRasterScale.test.d.ts.map +1 -0
- package/dist/__tests__/vectorRasterScale.test.js +304 -0
- package/dist/__tests__/vectorRasterScale.test.js.map +1 -0
- package/dist/bidi.d.ts +43 -3
- package/dist/bidi.d.ts.map +1 -1
- package/dist/bidi.js +29 -7
- package/dist/bidi.js.map +1 -1
- package/dist/canvas-backend/blockRender.d.ts.map +1 -1
- package/dist/canvas-backend/blockRender.js +43 -5
- package/dist/canvas-backend/blockRender.js.map +1 -1
- package/dist/canvas-backend/comic.d.ts +8 -4
- package/dist/canvas-backend/comic.d.ts.map +1 -1
- package/dist/canvas-backend/comic.js +13 -6
- package/dist/canvas-backend/comic.js.map +1 -1
- package/dist/canvas-backend/deviceScale.d.ts +31 -0
- package/dist/canvas-backend/deviceScale.d.ts.map +1 -0
- package/dist/canvas-backend/deviceScale.js +45 -0
- package/dist/canvas-backend/deviceScale.js.map +1 -0
- package/dist/canvas-backend/index.d.ts +5 -1
- package/dist/canvas-backend/index.d.ts.map +1 -1
- package/dist/canvas-backend/index.js.map +1 -1
- package/dist/canvas-backend/mirrorFrame.d.ts +14 -1
- package/dist/canvas-backend/mirrorFrame.d.ts.map +1 -1
- package/dist/canvas-backend/mirrorFrame.js +51 -7
- package/dist/canvas-backend/mirrorFrame.js.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.d.ts +5 -3
- package/dist/canvas-backend/renderResourceBlock.d.ts.map +1 -1
- package/dist/canvas-backend/renderResourceBlock.js +39 -6
- package/dist/canvas-backend/renderResourceBlock.js.map +1 -1
- package/dist/canvas-backend/trackedText.d.ts +72 -0
- package/dist/canvas-backend/trackedText.d.ts.map +1 -0
- package/dist/canvas-backend/trackedText.js +166 -0
- package/dist/canvas-backend/trackedText.js.map +1 -0
- package/dist/canvas-backend/wordRuns.d.ts +6 -0
- package/dist/canvas-backend/wordRuns.d.ts.map +1 -1
- package/dist/canvas-backend/wordRuns.js +11 -1
- package/dist/canvas-backend/wordRuns.js.map +1 -1
- package/dist/columnRule.d.ts +2 -1
- package/dist/columnRule.d.ts.map +1 -1
- package/dist/columnRule.js +11 -3
- package/dist/columnRule.js.map +1 -1
- package/dist/comics/layoutPage.d.ts.map +1 -1
- package/dist/comics/layoutPage.js +54 -2
- package/dist/comics/layoutPage.js.map +1 -1
- package/dist/comics/lettering/__tests__/ellipsisFace.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/ellipsisFace.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/ellipsisFace.test.js +54 -0
- package/dist/comics/lettering/__tests__/ellipsisFace.test.js.map +1 -0
- package/dist/comics/lettering/__tests__/polish.test.js +8 -0
- package/dist/comics/lettering/__tests__/polish.test.js.map +1 -1
- package/dist/comics/lettering/__tests__/round4.test.d.ts +2 -0
- package/dist/comics/lettering/__tests__/round4.test.d.ts.map +1 -0
- package/dist/comics/lettering/__tests__/round4.test.js +212 -0
- package/dist/comics/lettering/__tests__/round4.test.js.map +1 -0
- package/dist/comics/lettering/letter.js +2 -2
- package/dist/comics/lettering/letter.js.map +1 -1
- package/dist/comics/lettering/placement.d.ts +10 -3
- package/dist/comics/lettering/placement.d.ts.map +1 -1
- package/dist/comics/lettering/placement.js +162 -22
- package/dist/comics/lettering/placement.js.map +1 -1
- package/dist/comics/lettering/text.d.ts.map +1 -1
- package/dist/comics/lettering/text.js +25 -3
- package/dist/comics/lettering/text.js.map +1 -1
- package/dist/comics/lettering/types.d.ts +29 -2
- package/dist/comics/lettering/types.d.ts.map +1 -1
- package/dist/defaults/orderedLists.d.ts +1 -0
- package/dist/defaults/orderedLists.d.ts.map +1 -1
- package/dist/defaults/orderedLists.js +7 -0
- package/dist/defaults/orderedLists.js.map +1 -1
- package/dist/design/bidiText.d.ts +5 -2
- package/dist/design/bidiText.d.ts.map +1 -1
- package/dist/design/bidiText.js +7 -4
- package/dist/design/bidiText.js.map +1 -1
- package/dist/design/layout.d.ts +5 -0
- package/dist/design/layout.d.ts.map +1 -1
- package/dist/design/layout.js +1 -1
- package/dist/design/layout.js.map +1 -1
- package/dist/html-backend.d.ts.map +1 -1
- package/dist/html-backend.js +32 -6
- package/dist/html-backend.js.map +1 -1
- package/dist/htmlComic.d.ts +2 -1
- package/dist/htmlComic.d.ts.map +1 -1
- package/dist/htmlComic.js +14 -1
- package/dist/htmlComic.js.map +1 -1
- package/dist/hyphenate.d.ts.map +1 -1
- package/dist/hyphenate.js +52 -0
- package/dist/hyphenate.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/math/rasterCache.d.ts +7 -1
- package/dist/math/rasterCache.d.ts.map +1 -1
- package/dist/math/rasterCache.js +23 -11
- package/dist/math/rasterCache.js.map +1 -1
- package/dist/measure/bidiLines.d.ts +8 -0
- package/dist/measure/bidiLines.d.ts.map +1 -1
- package/dist/measure/bidiLines.js +13 -0
- package/dist/measure/bidiLines.js.map +1 -1
- package/dist/measure/cache.d.ts.map +1 -1
- package/dist/measure/cache.js +5 -3
- package/dist/measure/cache.js.map +1 -1
- package/dist/measure/cjkCompose.d.ts +10 -0
- package/dist/measure/cjkCompose.d.ts.map +1 -1
- package/dist/measure/cjkCompose.js +95 -19
- package/dist/measure/cjkCompose.js.map +1 -1
- package/dist/measure/cjkPunctuation.d.ts +10 -0
- package/dist/measure/cjkPunctuation.d.ts.map +1 -1
- package/dist/measure/cjkPunctuation.js +18 -1
- package/dist/measure/cjkPunctuation.js.map +1 -1
- package/dist/measure/hardBreaks.d.ts.map +1 -1
- package/dist/measure/hardBreaks.js +4 -1
- package/dist/measure/hardBreaks.js.map +1 -1
- package/dist/measure/leader.d.ts +6 -1
- package/dist/measure/leader.d.ts.map +1 -1
- package/dist/measure/leader.js +8 -3
- package/dist/measure/leader.js.map +1 -1
- package/dist/measure/plain.d.ts.map +1 -1
- package/dist/measure/plain.js +4 -2
- package/dist/measure/plain.js.map +1 -1
- package/dist/measure/rich.d.ts.map +1 -1
- package/dist/measure/rich.js +7 -4
- package/dist/measure/rich.js.map +1 -1
- package/dist/measure/types.d.ts +7 -0
- package/dist/measure/types.d.ts.map +1 -1
- package/dist/measure/types.js.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +38 -5
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/buildBlockKind.d.ts +5 -0
- package/dist/pipeline/buildBlockKind.d.ts.map +1 -1
- package/dist/pipeline/buildBlockKind.js +1 -0
- package/dist/pipeline/buildBlockKind.js.map +1 -1
- package/dist/pipeline/calloutLayout.d.ts.map +1 -1
- package/dist/pipeline/calloutLayout.js +8 -4
- package/dist/pipeline/calloutLayout.js.map +1 -1
- package/dist/pipeline/citations.d.ts.map +1 -1
- package/dist/pipeline/citations.js +52 -6
- package/dist/pipeline/citations.js.map +1 -1
- package/dist/pipeline/config.d.ts +6 -0
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +12 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/headerFooter.d.ts +2 -0
- package/dist/pipeline/headerFooter.d.ts.map +1 -1
- package/dist/pipeline/headerFooter.js +3 -1
- package/dist/pipeline/headerFooter.js.map +1 -1
- package/dist/pipeline/lists.d.ts +8 -0
- package/dist/pipeline/lists.d.ts.map +1 -1
- package/dist/pipeline/lists.js +18 -1
- package/dist/pipeline/lists.js.map +1 -1
- package/dist/pipeline/measureContentBlock.d.ts.map +1 -1
- package/dist/pipeline/measureContentBlock.js +3 -0
- package/dist/pipeline/measureContentBlock.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vdt.d.ts +9 -0
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// balloon cannot be placed cleanly the fallbacks are tried in order: break
|
|
11
11
|
// the border (when allowed), cover avoid zones (never faces), reshape the
|
|
12
12
|
// text block; then it is placed anyway and reported.
|
|
13
|
-
import { boundsOf, clipToRect, dist, insetConvex, nearestOnPolygon, norm, pointInConvex, pointInRect, rayExit, rectsOverlap, segmentHitsRect, segmentDistance, sub, add, scale, } from './geom';
|
|
13
|
+
import { boundsOf, clipToRect, dist, insetConvex, nearestOnPolygon, norm, pointInConvex, pointInRect, rayExit, rectsOverlap, segmentHitsRect, segmentDistance, segmentsCross, sub, add, scale, } from './geom';
|
|
14
14
|
import { tailLength } from './shapes';
|
|
15
15
|
const W = {
|
|
16
16
|
face: 60,
|
|
@@ -66,10 +66,13 @@ function sceneOf(panel) {
|
|
|
66
66
|
fwd: panel.direction === 'rtl' ? -1 : 1,
|
|
67
67
|
buttPoly: buttPolygon(panel),
|
|
68
68
|
neighbours: [...(panel.neighbours ?? [])],
|
|
69
|
+
over: [...(panel.over ?? [])],
|
|
69
70
|
...(panel.limit ? { limit: panel.limit } : {}),
|
|
70
71
|
...(trim ? { edge: insetRect(trim, panel.insetPx), edgeSfx: panel.sheet ?? trim } : {}),
|
|
71
72
|
bleeds: panel.limit !== undefined && panel.polygon.some((p) => !pointInRect(p, panel.limit, 0.5)),
|
|
72
73
|
foreign: [...(panel.foreign ?? [])],
|
|
74
|
+
foreignTails: [...(panel.foreignTails ?? [])],
|
|
75
|
+
foreignArt: [...(panel.foreignArt ?? [])],
|
|
73
76
|
keepOut: [...(panel.keepOut ?? [])],
|
|
74
77
|
live,
|
|
75
78
|
};
|
|
@@ -113,6 +116,18 @@ function coveredArea(samples, rect, area) {
|
|
|
113
116
|
n++;
|
|
114
117
|
return (n / Math.max(1, samples.length)) * area;
|
|
115
118
|
}
|
|
119
|
+
/** Area of the ellipse `rect` holds the samples fall in. */
|
|
120
|
+
function coveredEllipse(samples, rect, area) {
|
|
121
|
+
const cx = rect.x + rect.width / 2;
|
|
122
|
+
const cy = rect.y + rect.height / 2;
|
|
123
|
+
const rx = rect.width / 2;
|
|
124
|
+
const ry = rect.height / 2;
|
|
125
|
+
let n = 0;
|
|
126
|
+
for (const s of samples)
|
|
127
|
+
if (((s.x - cx) / rx) ** 2 + ((s.y - cy) / ry) ** 2 <= 1)
|
|
128
|
+
n++;
|
|
129
|
+
return (n / Math.max(1, samples.length)) * area;
|
|
130
|
+
}
|
|
116
131
|
/** Whether a face fills a good part of its panel (a close shot): its box
|
|
117
132
|
* then has corners of hair a balloon may cover (in a long shot they are a
|
|
118
133
|
* few pixels beside the face). */
|
|
@@ -136,6 +151,20 @@ function coveredOval(samples, rect, area) {
|
|
|
136
151
|
}
|
|
137
152
|
return (n / Math.max(1, samples.length)) * area;
|
|
138
153
|
}
|
|
154
|
+
/** Area of a face or avoid zone of another panel the samples fall in:
|
|
155
|
+
* only what the page shows of it counts (inside its own panel, under no
|
|
156
|
+
* panel laid over that one). */
|
|
157
|
+
function coveredForeign(samples, art, area) {
|
|
158
|
+
let n = 0;
|
|
159
|
+
for (const s of samples) {
|
|
160
|
+
if (!pointInRect(s, art.rect) || !pointInConvex(s, art.within))
|
|
161
|
+
continue;
|
|
162
|
+
if (art.under?.some((poly) => pointInConvex(s, poly)))
|
|
163
|
+
continue;
|
|
164
|
+
n++;
|
|
165
|
+
}
|
|
166
|
+
return (n / Math.max(1, samples.length)) * area;
|
|
167
|
+
}
|
|
139
168
|
function areaOf(v) {
|
|
140
169
|
// The bodies' boxes, as an ellipse fills them.
|
|
141
170
|
return v.bodies.reduce((s, b) => s + b.bbox.width * b.bbox.height * 0.8, 0);
|
|
@@ -232,6 +261,25 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
232
261
|
}
|
|
233
262
|
}
|
|
234
263
|
}
|
|
264
|
+
// What the other panels' pictures protect, for a balloon that leaves
|
|
265
|
+
// its panel: their faces as its own, their avoid zones as its own (#671).
|
|
266
|
+
for (const art of scene.foreignArt) {
|
|
267
|
+
if (!rectsOverlap(box, art.rect))
|
|
268
|
+
continue;
|
|
269
|
+
const a = coveredForeign(samples, art, area);
|
|
270
|
+
if (a <= 0)
|
|
271
|
+
continue;
|
|
272
|
+
if (art.kind === 'face') {
|
|
273
|
+
H('face', (W.face * a) / em2 + W.face);
|
|
274
|
+
reasons.add('face');
|
|
275
|
+
}
|
|
276
|
+
else if (level.coverAvoid)
|
|
277
|
+
S(art.guard ? 'coverAvoid' : 'coverArt', ((art.guard ? 2 : 6) * a) / em2);
|
|
278
|
+
else {
|
|
279
|
+
H('avoid', (W.avoid * a) / em2 + W.avoid);
|
|
280
|
+
reasons.add('avoid');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
235
283
|
for (const r of scene.soft) {
|
|
236
284
|
if (!rectsOverlap(box, r))
|
|
237
285
|
continue;
|
|
@@ -275,6 +323,12 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
275
323
|
else if (scene.neighbours.some((r) => pointInRect(p, r)))
|
|
276
324
|
intrude++;
|
|
277
325
|
}
|
|
326
|
+
else if (scene.over.some((poly) => pointInConvex(p, poly))) {
|
|
327
|
+
// Over an inset laid on this panel: out of the panel, in another's
|
|
328
|
+
// (#671).
|
|
329
|
+
out++;
|
|
330
|
+
intrude++;
|
|
331
|
+
}
|
|
278
332
|
}
|
|
279
333
|
const mayIntrude = intended && (unit.kind === 'sfx' || unit.breakBorder === true);
|
|
280
334
|
// Off the sheet is the worst; into another panel (its art, its
|
|
@@ -298,7 +352,8 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
298
352
|
// another panel's (drawn sound may; so may a balloon its author
|
|
299
353
|
// pinned). A pinned balloon (no `break`) slides back in when it can.
|
|
300
354
|
S('breakBorder', 3 * f + (unit.kind !== 'sfx' && !unit.breakBorder ? 6 : 0));
|
|
301
|
-
|
|
355
|
+
const middle = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
356
|
+
if (unit.kind !== 'sfx' && !unit.pin && (!pointInConvex(middle, scene.live) || scene.over.some((poly) => pointInConvex(middle, poly)))) {
|
|
302
357
|
H('outside', W.outside * f + 10);
|
|
303
358
|
reasons.add('outside');
|
|
304
359
|
}
|
|
@@ -320,7 +375,12 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
320
375
|
for (const r of scene.foreign) {
|
|
321
376
|
if (!rectsOverlap(box, r, 0.3 * em))
|
|
322
377
|
continue;
|
|
323
|
-
|
|
378
|
+
// (An oval, a cloud or a burst is taken as the oval its box holds: the
|
|
379
|
+
// corners of the box are no part of it. This unit's own outline, where
|
|
380
|
+
// it grows past its core, the spikes of a burst, counts by its points.)
|
|
381
|
+
const g = grow(r, 0.3 * em);
|
|
382
|
+
const a = (r.round ? coveredEllipse(samples, g, area) : coveredArea(samples, g, area))
|
|
383
|
+
|| (r.round ? coveredEllipse(rim, r, em2) : coveredArea(rim, r, em2));
|
|
324
384
|
if (a > 0) {
|
|
325
385
|
H('balloon', (W.balloon * a) / em2 + W.balloon);
|
|
326
386
|
reasons.add('balloon');
|
|
@@ -329,6 +389,24 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
329
389
|
// Other balloons: overlap is a fault, a near miss (a tangent) a blemish.
|
|
330
390
|
const centre = at;
|
|
331
391
|
const target = tailTargetOf(unit, scene, centre);
|
|
392
|
+
// Where the tail leaves the body, and the tail as drawn: once for all
|
|
393
|
+
// the terms below (each is a ray cast against the body's outline).
|
|
394
|
+
const leave = target ? edgePoint(v, at, target) : undefined;
|
|
395
|
+
const mine = target && leave ? tailFrom(unit, leave, target) : undefined;
|
|
396
|
+
// The tails of the other panels' balloons are theirs too: no body over
|
|
397
|
+
// one, and no tail of this panel through a balloon of another (#671).
|
|
398
|
+
for (const [a, b] of scene.foreignTails) {
|
|
399
|
+
if (throughBody(a, b, v, at)) {
|
|
400
|
+
H('foreignTail', W.balloon);
|
|
401
|
+
reasons.add('balloon');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (mine && scene.foreign.length > 0) {
|
|
405
|
+
if (scene.foreign.some((r) => segmentHitsRect(mine[0], mine[1], shrink(r, 0.12)))) {
|
|
406
|
+
H('tailThroughForeign', W.balloon);
|
|
407
|
+
reasons.add('balloon');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
332
410
|
for (const o of others) {
|
|
333
411
|
const ov = o.unit.variants[o.variant];
|
|
334
412
|
const obox = movedRect(ov.bbox, o.at);
|
|
@@ -360,9 +438,9 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
360
438
|
}
|
|
361
439
|
// Tails that cross (as drawn: from the wall to the tip), tails
|
|
362
440
|
// through another balloon.
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
const theirs =
|
|
441
|
+
const other = tailOf(scene, o);
|
|
442
|
+
const otarget = other?.target;
|
|
443
|
+
const theirs = other?.drawn;
|
|
366
444
|
if (mine && theirs) {
|
|
367
445
|
// Crossing tails; tails that all but touch read as crossed too
|
|
368
446
|
// (unless they run to one speaker).
|
|
@@ -372,24 +450,31 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
372
450
|
else if (gapTails < 0.6 * em && dist(target, otarget) > em)
|
|
373
451
|
S('cross', 0.4 * W.cross);
|
|
374
452
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
453
|
+
// A tail through another body covers its lettering as a balloon over
|
|
454
|
+
// it would: a fault (#672). Through the corner of its box, clear of its
|
|
455
|
+
// outline, a cost.
|
|
456
|
+
for (const [tail, w, p] of [[mine, ov, o.at], [theirs, v, at]]) {
|
|
457
|
+
if (!tail)
|
|
458
|
+
continue;
|
|
459
|
+
if (throughBody(tail[0], tail[1], w, p)) {
|
|
460
|
+
H('tailThrough', W.balloon);
|
|
461
|
+
reasons.add('balloon');
|
|
462
|
+
}
|
|
463
|
+
else if (w.bodies.some((b) => segmentHitsRect(tail[0], tail[1], shrink(movedRect(b.bbox, p), 0.12))))
|
|
464
|
+
S('tailThrough', W.tailThrough);
|
|
465
|
+
}
|
|
379
466
|
}
|
|
380
467
|
// The tail leaves the first body clear of the group's other bodies, by
|
|
381
468
|
// more than its own half width: a tail grazing another body of its group
|
|
382
469
|
// leaves a notch between them that the doubled outline fills with ink.
|
|
383
|
-
if (target && v.bodies.length > 1) {
|
|
384
|
-
const edge = edgePoint(v, at, target);
|
|
470
|
+
if (target && leave && v.bodies.length > 1) {
|
|
385
471
|
for (const b of v.bodies.slice(1))
|
|
386
|
-
if (segmentHitsRect(
|
|
472
|
+
if (segmentHitsRect(leave, target, grow(movedRect(b.bbox, at), 0.8 * em)))
|
|
387
473
|
S('ownTail', W.ownTail);
|
|
388
474
|
}
|
|
389
475
|
// Near the speaker, a little above; tails not over faces.
|
|
390
|
-
if (target && unit.target.kind === 'point') {
|
|
391
|
-
const
|
|
392
|
-
const gap = dist(edge, target);
|
|
476
|
+
if (target && leave && mine && unit.target.kind === 'point') {
|
|
477
|
+
const gap = dist(leave, target);
|
|
393
478
|
const h = v.bodies[0].bbox.height;
|
|
394
479
|
// Room for a tail of a few ems: too close is worse than too far.
|
|
395
480
|
const ideal = Math.max(2 * em, 0.7 * h);
|
|
@@ -406,16 +491,16 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
406
491
|
S('below', (W.below * (centre.y - target.y)) / em);
|
|
407
492
|
// A tail over another character's face (or the top of the head)
|
|
408
493
|
// reads as theirs.
|
|
409
|
-
const tip = add(
|
|
494
|
+
const tip = add(leave, scale(norm(sub(target, leave)), gap * 0.55));
|
|
410
495
|
for (const f of scene.faces) {
|
|
411
496
|
const head = { x: f.x, y: f.y - 0.3 * f.height, width: f.width, height: 1.3 * f.height };
|
|
412
|
-
if (!segmentHitsRect(
|
|
497
|
+
if (!segmentHitsRect(leave, tip, shrink(head, 0.1)))
|
|
413
498
|
continue;
|
|
414
499
|
S('otherFace', pointInRect(target, head) ? 1 : W.otherFace);
|
|
415
500
|
}
|
|
416
501
|
// A tail drawn across another figure (the body under a marked face):
|
|
417
502
|
// the eye follows it through the wrong character.
|
|
418
|
-
const drawn =
|
|
503
|
+
const drawn = mine;
|
|
419
504
|
for (const a of scene.panel.anchors) {
|
|
420
505
|
if (!a.face || a.mouth === target || a.head === target)
|
|
421
506
|
continue;
|
|
@@ -425,11 +510,11 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
425
510
|
S('tailOverFigure', W.tailOverFigure);
|
|
426
511
|
}
|
|
427
512
|
}
|
|
428
|
-
else if (unit.target.kind === 'offPanel' && target) {
|
|
513
|
+
else if (unit.target.kind === 'offPanel' && target && leave) {
|
|
429
514
|
// An off-panel speaker's balloon sits by the border its voice comes
|
|
430
515
|
// from, with a short tail to it (the letterer's practice): a tail
|
|
431
516
|
// across the panel reads as an arrow, so the balloon moves instead.
|
|
432
|
-
const reach = dist(
|
|
517
|
+
const reach = dist(leave, target);
|
|
433
518
|
S('side', (W.side * Math.max(0, reach - 1.2 * em) + 3 * W.side * Math.max(0, reach - 3 * em)) / em);
|
|
434
519
|
}
|
|
435
520
|
if (unit.kind === 'sfx' && unit.near)
|
|
@@ -447,6 +532,22 @@ function evaluate(scene, unit, vi, at, others, level, firstOrder, terms) {
|
|
|
447
532
|
}
|
|
448
533
|
return { hard, soft, reasons, order, cover, coverArt };
|
|
449
534
|
}
|
|
535
|
+
/** Whether the segment a–b runs through a body of `v` at `at`: across
|
|
536
|
+
* its outline (the spikes of a burst and the bumps of a cloud are part of
|
|
537
|
+
* it; a turned or leaned sound effect's is its turned box) or inside it. */
|
|
538
|
+
function throughBody(a, b, v, at) {
|
|
539
|
+
if (!segmentHitsRect(a, b, movedRect(v.bbox, at)))
|
|
540
|
+
return false;
|
|
541
|
+
const turned = (v.rotate || v.skew) && v.bodies.length === 1;
|
|
542
|
+
return v.bodies.some((body) => {
|
|
543
|
+
const outline = moved(turned ? v.rim : body.hull, at);
|
|
544
|
+
for (let i = 0; i < outline.length; i++) {
|
|
545
|
+
if (segmentsCross(a, b, outline[i], outline[(i + 1) % outline.length]))
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
return !turned && pointInConvex(a, moved(body.core, at));
|
|
549
|
+
});
|
|
550
|
+
}
|
|
450
551
|
/** A rectangle grown by `d` px on every side. */
|
|
451
552
|
function grow(r, d) {
|
|
452
553
|
return { x: r.x - d, y: r.y - d, width: r.width + 2 * d, height: r.height + 2 * d };
|
|
@@ -454,10 +555,26 @@ function grow(r, d) {
|
|
|
454
555
|
function shrink(r, f) {
|
|
455
556
|
return { x: r.x + r.width * f, y: r.y + r.height * f, width: r.width * (1 - 2 * f), height: r.height * (1 - 2 * f) };
|
|
456
557
|
}
|
|
558
|
+
/** The tail of a placed unit, kept with it: a unit that stays where it is
|
|
559
|
+
* is asked for its tail at every spot tried for the others. (A placed
|
|
560
|
+
* unit is never changed in place; a move makes a new one.) */
|
|
561
|
+
const tailsOf = new WeakMap();
|
|
562
|
+
function tailOf(scene, o) {
|
|
563
|
+
let t = tailsOf.get(o);
|
|
564
|
+
if (t === undefined) {
|
|
565
|
+
const target = tailTargetOf(o.unit, scene, o.at);
|
|
566
|
+
t = target ? { target, drawn: tailSegment(o.unit, o.unit.variants[o.variant], o.at, target) } : null;
|
|
567
|
+
tailsOf.set(o, t);
|
|
568
|
+
}
|
|
569
|
+
return t ?? undefined;
|
|
570
|
+
}
|
|
457
571
|
/** A unit's tail as drawn, for the cost: from where it leaves the first
|
|
458
572
|
* body to its tip ({@link tailLength} of the gap). */
|
|
459
573
|
function tailSegment(unit, v, at, target) {
|
|
460
|
-
|
|
574
|
+
return tailFrom(unit, edgePoint(v, at, target), target);
|
|
575
|
+
}
|
|
576
|
+
/** The same from the point the tail leaves the body at. */
|
|
577
|
+
function tailFrom(unit, edge, target) {
|
|
461
578
|
const gap = dist(edge, target);
|
|
462
579
|
const len = Math.max(Math.min(gap, 0.8 * unit.em), tailLength(gap, unit.em, {}, unit.target.kind === 'offPanel'));
|
|
463
580
|
return [edge, add(edge, scale(norm(sub(target, edge)), len))];
|
|
@@ -470,8 +587,20 @@ function edgePoint(v, at, target) {
|
|
|
470
587
|
const t = rayExit(c, u, moved(b.core, at));
|
|
471
588
|
return t === undefined ? c : add(c, scale(u, t));
|
|
472
589
|
}
|
|
590
|
+
/** The candidates of each unit's variants: they depend on the unit and
|
|
591
|
+
* the panel alone, and the search asks for them at every step. */
|
|
592
|
+
const candidatesOf = new WeakMap();
|
|
473
593
|
/** Candidate anchor positions of a unit (deterministic order). */
|
|
474
594
|
function candidates(scene, unit, vi) {
|
|
595
|
+
let byVariant = candidatesOf.get(unit);
|
|
596
|
+
if (!byVariant)
|
|
597
|
+
candidatesOf.set(unit, (byVariant = new Map()));
|
|
598
|
+
let list = byVariant.get(vi);
|
|
599
|
+
if (!list)
|
|
600
|
+
byVariant.set(vi, (list = candidatesFresh(scene, unit, vi)));
|
|
601
|
+
return list;
|
|
602
|
+
}
|
|
603
|
+
function candidatesFresh(scene, unit, vi) {
|
|
475
604
|
const v = unit.variants[vi];
|
|
476
605
|
const out = [];
|
|
477
606
|
const seen = new Set();
|
|
@@ -631,8 +760,16 @@ function bestFor(scene, unit, others, firstOrder, fixed) {
|
|
|
631
760
|
// its poorer shape, and is tried before covering an avoid zone (a
|
|
632
761
|
// letterer reshapes the text before covering a hand).
|
|
633
762
|
const penalty = (vi) => (unit.variants[vi].reshaped ? RESHAPE_COST : 0);
|
|
763
|
+
// Whether any spot tried at the level before met an avoid zone: when
|
|
764
|
+
// none did, a level that only adds covering them finds the same spots at
|
|
765
|
+
// the same costs, and is not searched again.
|
|
766
|
+
const breaks = (l) => l.breakBorder && unit.breakBorder === true;
|
|
767
|
+
let before;
|
|
634
768
|
for (const li of levels) {
|
|
635
769
|
const level = LEVELS[li];
|
|
770
|
+
if (level.coverAvoid && before && !before.metAvoid && before.breaks === breaks(level))
|
|
771
|
+
continue;
|
|
772
|
+
let metAvoid = false;
|
|
636
773
|
let best;
|
|
637
774
|
for (const vi of variants) {
|
|
638
775
|
const cands = unit.pin
|
|
@@ -643,11 +780,14 @@ function bestFor(scene, unit, others, firstOrder, fixed) {
|
|
|
643
780
|
: candidates(scene, unit, vi).map((at) => ({ at, extra: 0 }));
|
|
644
781
|
for (const { at, extra } of cands) {
|
|
645
782
|
const ev = evaluate(scene, unit, vi, at, others, level, firstOrder);
|
|
783
|
+
if (ev.reasons.has('avoid'))
|
|
784
|
+
metAvoid = true;
|
|
646
785
|
const score = ev.hard * 1000 + ev.soft + penalty(vi) + extra;
|
|
647
786
|
if (!best || score < best.score - 1e-9)
|
|
648
787
|
best = { variant: vi, level: li, at, ev, score };
|
|
649
788
|
}
|
|
650
789
|
}
|
|
790
|
+
before = { breaks: breaks(level), metAvoid: metAvoid || level.coverAvoid };
|
|
651
791
|
if (!best)
|
|
652
792
|
continue;
|
|
653
793
|
const choice = { variant: best.variant, level: best.level, at: best.at, ev: best.ev };
|