@swf2es/player 0.1.0 → 0.1.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/package.json +7 -7
- package/dist/audio.d.ts +0 -22
- package/dist/audio.d.ts.map +0 -1
- package/dist/audio.js +0 -106
- package/dist/audio.js.map +0 -1
- package/dist/bitmap-filters.d.ts +0 -29
- package/dist/bitmap-filters.d.ts.map +0 -1
- package/dist/bitmap-filters.js +0 -906
- package/dist/bitmap-filters.js.map +0 -1
- package/dist/bitmap-ops.d.ts +0 -113
- package/dist/bitmap-ops.d.ts.map +0 -1
- package/dist/bitmap-ops.js +0 -725
- package/dist/bitmap-ops.js.map +0 -1
- package/dist/bitmap.d.ts +0 -98
- package/dist/bitmap.d.ts.map +0 -1
- package/dist/bitmap.js +0 -314
- package/dist/bitmap.js.map +0 -1
- package/dist/bounds.d.ts +0 -26
- package/dist/bounds.d.ts.map +0 -1
- package/dist/bounds.js +0 -300
- package/dist/bounds.js.map +0 -1
- package/dist/color.d.ts +0 -7
- package/dist/color.d.ts.map +0 -1
- package/dist/color.js +0 -42
- package/dist/color.js.map +0 -1
- package/dist/display.d.ts +0 -425
- package/dist/display.d.ts.map +0 -1
- package/dist/display.js +0 -1274
- package/dist/display.js.map +0 -1
- package/dist/drawing.d.ts +0 -52
- package/dist/drawing.d.ts.map +0 -1
- package/dist/drawing.js +0 -204
- package/dist/drawing.js.map +0 -1
- package/dist/filters.d.ts +0 -54
- package/dist/filters.d.ts.map +0 -1
- package/dist/filters.js +0 -155
- package/dist/filters.js.map +0 -1
- package/dist/fonts.d.ts +0 -23
- package/dist/fonts.d.ts.map +0 -1
- package/dist/fonts.js +0 -91
- package/dist/fonts.js.map +0 -1
- package/dist/geometry.d.ts +0 -21
- package/dist/geometry.d.ts.map +0 -1
- package/dist/geometry.js +0 -76
- package/dist/geometry.js.map +0 -1
- package/dist/gradients.d.ts +0 -29
- package/dist/gradients.d.ts.map +0 -1
- package/dist/gradients.js +0 -116
- package/dist/gradients.js.map +0 -1
- package/dist/images.d.ts +0 -22
- package/dist/images.d.ts.map +0 -1
- package/dist/images.js +0 -147
- package/dist/images.js.map +0 -1
- package/dist/input.d.ts +0 -79
- package/dist/input.d.ts.map +0 -1
- package/dist/input.js +0 -366
- package/dist/input.js.map +0 -1
- package/dist/keyboard.d.ts +0 -97
- package/dist/keyboard.d.ts.map +0 -1
- package/dist/keyboard.js +0 -583
- package/dist/keyboard.js.map +0 -1
- package/dist/morph.d.ts +0 -17
- package/dist/morph.d.ts.map +0 -1
- package/dist/morph.js +0 -218
- package/dist/morph.js.map +0 -1
- package/dist/pixi-blend.d.ts +0 -7
- package/dist/pixi-blend.d.ts.map +0 -1
- package/dist/pixi-blend.js +0 -253
- package/dist/pixi-blend.js.map +0 -1
- package/dist/pixi-color.d.ts +0 -54
- package/dist/pixi-color.d.ts.map +0 -1
- package/dist/pixi-color.js +0 -336
- package/dist/pixi-color.js.map +0 -1
- package/dist/pixi-filters.d.ts +0 -40
- package/dist/pixi-filters.d.ts.map +0 -1
- package/dist/pixi-filters.js +0 -953
- package/dist/pixi-filters.js.map +0 -1
- package/dist/pixi-resolve.d.ts +0 -4
- package/dist/pixi-resolve.d.ts.map +0 -1
- package/dist/pixi-resolve.js +0 -76
- package/dist/pixi-resolve.js.map +0 -1
- package/dist/pixi.d.ts +0 -219
- package/dist/pixi.d.ts.map +0 -1
- package/dist/pixi.js +0 -2169
- package/dist/pixi.js.map +0 -1
- package/dist/playerglobal/flash/filters/filters.d.ts +0 -26
- package/dist/playerglobal/flash/filters/filters.d.ts.map +0 -1
- package/dist/playerglobal/flash/filters/filters.js +0 -287
- package/dist/playerglobal/flash/filters/filters.js.map +0 -1
- package/dist/playerglobal/flash/system/FSCommand.d.ts +0 -4
- package/dist/playerglobal/flash/system/FSCommand.d.ts.map +0 -1
- package/dist/playerglobal/flash/system/FSCommand.js +0 -18
- package/dist/playerglobal/flash/system/FSCommand.js.map +0 -1
- package/dist/playerglobal/natives.d.ts +0 -10
- package/dist/playerglobal/natives.d.ts.map +0 -1
- package/dist/playerglobal/natives.js +0 -53
- package/dist/playerglobal/natives.js.map +0 -1
- package/dist/png.d.ts +0 -4
- package/dist/png.d.ts.map +0 -1
- package/dist/png.js +0 -140
- package/dist/png.js.map +0 -1
- package/dist/render/art.d.ts +0 -148
- package/dist/render/art.d.ts.map +0 -1
- package/dist/render/art.js +0 -575
- package/dist/render/art.js.map +0 -1
- package/dist/render/batchers.d.ts +0 -23
- package/dist/render/batchers.d.ts.map +0 -1
- package/dist/render/batchers.js +0 -52
- package/dist/render/batchers.js.map +0 -1
- package/dist/render/bitmaps.d.ts +0 -117
- package/dist/render/bitmaps.d.ts.map +0 -1
- package/dist/render/bitmaps.js +0 -422
- package/dist/render/bitmaps.js.map +0 -1
- package/dist/render/blend.d.ts +0 -37
- package/dist/render/blend.d.ts.map +0 -1
- package/dist/render/blend.js +0 -195
- package/dist/render/blend.js.map +0 -1
- package/dist/render/color.d.ts +0 -111
- package/dist/render/color.d.ts.map +0 -1
- package/dist/render/color.js +0 -533
- package/dist/render/color.js.map +0 -1
- package/dist/render/compositing.d.ts +0 -77
- package/dist/render/compositing.d.ts.map +0 -1
- package/dist/render/compositing.js +0 -188
- package/dist/render/compositing.js.map +0 -1
- package/dist/render/filters.d.ts +0 -98
- package/dist/render/filters.d.ts.map +0 -1
- package/dist/render/filters.js +0 -1326
- package/dist/render/filters.js.map +0 -1
- package/dist/render/hooks.d.ts +0 -4
- package/dist/render/hooks.d.ts.map +0 -1
- package/dist/render/hooks.js +0 -48
- package/dist/render/hooks.js.map +0 -1
- package/dist/render/node.d.ts +0 -139
- package/dist/render/node.d.ts.map +0 -1
- package/dist/render/node.js +0 -59
- package/dist/render/node.js.map +0 -1
- package/dist/render/patches.d.ts +0 -54
- package/dist/render/patches.d.ts.map +0 -1
- package/dist/render/patches.js +0 -154
- package/dist/render/patches.js.map +0 -1
- package/dist/render/planes.d.ts +0 -74
- package/dist/render/planes.d.ts.map +0 -1
- package/dist/render/planes.js +0 -267
- package/dist/render/planes.js.map +0 -1
- package/dist/render/pointer.d.ts +0 -5
- package/dist/render/pointer.d.ts.map +0 -1
- package/dist/render/pointer.js +0 -279
- package/dist/render/pointer.js.map +0 -1
- package/dist/render/pools.d.ts +0 -122
- package/dist/render/pools.d.ts.map +0 -1
- package/dist/render/pools.js +0 -238
- package/dist/render/pools.js.map +0 -1
- package/dist/render/resolve.d.ts +0 -4
- package/dist/render/resolve.d.ts.map +0 -1
- package/dist/render/resolve.js +0 -76
- package/dist/render/resolve.js.map +0 -1
- package/dist/render/saffron.d.ts +0 -135
- package/dist/render/saffron.d.ts.map +0 -1
- package/dist/render/saffron.js +0 -725
- package/dist/render/saffron.js.map +0 -1
- package/dist/render/settle.d.ts +0 -60
- package/dist/render/settle.d.ts.map +0 -1
- package/dist/render/settle.js +0 -128
- package/dist/render/settle.js.map +0 -1
- package/dist/render/shared.d.ts +0 -25
- package/dist/render/shared.d.ts.map +0 -1
- package/dist/render/shared.js +0 -33
- package/dist/render/shared.js.map +0 -1
- package/dist/render/snapshot.d.ts +0 -21
- package/dist/render/snapshot.d.ts.map +0 -1
- package/dist/render/snapshot.js +0 -91
- package/dist/render/snapshot.js.map +0 -1
- package/dist/render/spares.d.ts +0 -56
- package/dist/render/spares.d.ts.map +0 -1
- package/dist/render/spares.js +0 -160
- package/dist/render/spares.js.map +0 -1
- package/dist/render/strokes.d.ts +0 -107
- package/dist/render/strokes.d.ts.map +0 -1
- package/dist/render/strokes.js +0 -448
- package/dist/render/strokes.js.map +0 -1
- package/dist/render/table.d.ts +0 -213
- package/dist/render/table.d.ts.map +0 -1
- package/dist/render/table.js +0 -795
- package/dist/render/table.js.map +0 -1
- package/dist/render/tessellate.d.ts +0 -89
- package/dist/render/tessellate.d.ts.map +0 -1
- package/dist/render/tessellate.js +0 -386
- package/dist/render/tessellate.js.map +0 -1
- package/dist/render/text.d.ts +0 -40
- package/dist/render/text.d.ts.map +0 -1
- package/dist/render/text.js +0 -309
- package/dist/render/text.js.map +0 -1
- package/dist/render/three.d.ts +0 -83
- package/dist/render/three.d.ts.map +0 -1
- package/dist/render/three.js +0 -511
- package/dist/render/three.js.map +0 -1
- package/dist/render/triangles.d.ts +0 -28
- package/dist/render/triangles.d.ts.map +0 -1
- package/dist/render/triangles.js +0 -189
- package/dist/render/triangles.js.map +0 -1
- package/dist/render/view.d.ts +0 -238
- package/dist/render/view.d.ts.map +0 -1
- package/dist/render/view.js +0 -1173
- package/dist/render/view.js.map +0 -1
- package/dist/render/worker/bitmaps.d.ts +0 -43
- package/dist/render/worker/bitmaps.d.ts.map +0 -1
- package/dist/render/worker/bitmaps.js +0 -151
- package/dist/render/worker/bitmaps.js.map +0 -1
- package/dist/render/worker/drawing.d.ts +0 -21
- package/dist/render/worker/drawing.d.ts.map +0 -1
- package/dist/render/worker/drawing.js +0 -356
- package/dist/render/worker/drawing.js.map +0 -1
- package/dist/render/worker/filters.d.ts +0 -7
- package/dist/render/worker/filters.d.ts.map +0 -1
- package/dist/render/worker/filters.js +0 -125
- package/dist/render/worker/filters.js.map +0 -1
- package/dist/render/worker/frames.d.ts +0 -32
- package/dist/render/worker/frames.d.ts.map +0 -1
- package/dist/render/worker/frames.js +0 -124
- package/dist/render/worker/frames.js.map +0 -1
- package/dist/render/worker/host.d.ts +0 -60
- package/dist/render/worker/host.d.ts.map +0 -1
- package/dist/render/worker/host.js +0 -297
- package/dist/render/worker/host.js.map +0 -1
- package/dist/render/worker/libraries.d.ts +0 -36
- package/dist/render/worker/libraries.d.ts.map +0 -1
- package/dist/render/worker/libraries.js +0 -153
- package/dist/render/worker/libraries.js.map +0 -1
- package/dist/render/worker/presentation.d.ts +0 -18
- package/dist/render/worker/presentation.d.ts.map +0 -1
- package/dist/render/worker/presentation.js +0 -47
- package/dist/render/worker/presentation.js.map +0 -1
- package/dist/render/worker/renderer.d.ts +0 -4
- package/dist/render/worker/renderer.d.ts.map +0 -1
- package/dist/render/worker/renderer.js +0 -21
- package/dist/render/worker/renderer.js.map +0 -1
- package/dist/render/worker/scene.d.ts +0 -32
- package/dist/render/worker/scene.d.ts.map +0 -1
- package/dist/render/worker/scene.js +0 -611
- package/dist/render/worker/scene.js.map +0 -1
- package/dist/render/worker/text.d.ts +0 -47
- package/dist/render/worker/text.d.ts.map +0 -1
- package/dist/render/worker/text.js +0 -261
- package/dist/render/worker/text.js.map +0 -1
- package/dist/render/worker/thread.d.ts +0 -23
- package/dist/render/worker/thread.d.ts.map +0 -1
- package/dist/render/worker/thread.js +0 -107
- package/dist/render/worker/thread.js.map +0 -1
- package/dist/render/worker/values.d.ts +0 -18
- package/dist/render/worker/values.d.ts.map +0 -1
- package/dist/render/worker/values.js +0 -64
- package/dist/render/worker/values.js.map +0 -1
- package/dist/sha256.d.ts +0 -5
- package/dist/sha256.d.ts.map +0 -1
- package/dist/sha256.js +0 -96
- package/dist/sha256.js.map +0 -1
- package/dist/shapes.d.ts +0 -71
- package/dist/shapes.d.ts.map +0 -1
- package/dist/shapes.js +0 -340
- package/dist/shapes.js.map +0 -1
- package/dist/static-text.d.ts +0 -33
- package/dist/static-text.d.ts.map +0 -1
- package/dist/static-text.js +0 -105
- package/dist/static-text.js.map +0 -1
- package/dist/text-layout.d.ts +0 -66
- package/dist/text-layout.d.ts.map +0 -1
- package/dist/text-layout.js +0 -315
- package/dist/text-layout.js.map +0 -1
- package/dist/text.d.ts +0 -67
- package/dist/text.d.ts.map +0 -1
- package/dist/text.js +0 -383
- package/dist/text.js.map +0 -1
- package/dist/timeline.d.ts +0 -150
- package/dist/timeline.d.ts.map +0 -1
- package/dist/timeline.js +0 -266
- package/dist/timeline.js.map +0 -1
package/dist/pixi.js
DELETED
|
@@ -1,2169 +0,0 @@
|
|
|
1
|
-
import { BufferImageSource, CanvasTextMetrics, fontStringFromTextStyle, Graphics, GraphicsContext, Matrix, Container as PixiContainer, Rectangle, RendererType, RenderTexture, Sprite, Text, Texture, } from "pixi.js";
|
|
2
|
-
import { BitmapStore } from "./bitmap.js";
|
|
3
|
-
import { toStage } from "./bounds.js";
|
|
4
|
-
import { concatColor, multipliesOnly, sameColor } from "./color.js";
|
|
5
|
-
import { BitmapObject, CHILDREN, CLEAN, Clips, CONTENT, Container, PIXELS, ShapeObject, StaticTextObject, TextObject, TRANSFORM, } from "./display.js";
|
|
6
|
-
import { deviceMetrics, fontFamily } from "./fonts.js";
|
|
7
|
-
import { shifted } from "./geometry.js";
|
|
8
|
-
import { RADIAL_MAX, radialPixels, ramp } from "./gradients.js";
|
|
9
|
-
import { blendLayers, droppedLayers } from "./morph.js";
|
|
10
|
-
import { blendFilters } from "./pixi-blend.js";
|
|
11
|
-
import { dropBatchedCopy, setFlashColor, showFor } from "./pixi-color.js";
|
|
12
|
-
import { displayFilters, FilterChain, rgbaOf } from "./pixi-filters.js";
|
|
13
|
-
import { boundedResolves } from "./pixi-resolve.js";
|
|
14
|
-
import { CUBIC, flatten, inside, LINE, MOVE, orientation, pointsOf, shapeLayers, } from "./shapes.js";
|
|
15
|
-
import { GUTTER, shownLines } from "./text-layout.js";
|
|
16
|
-
/** The contours as a tree, each under the smallest one that holds it. */
|
|
17
|
-
function containment(paths) {
|
|
18
|
-
const regions = paths.map((path) => {
|
|
19
|
-
const points = flatten(path);
|
|
20
|
-
let area = 0;
|
|
21
|
-
for (let i = 0, j = points.length - 2; i < points.length; j = i, i += 2) {
|
|
22
|
-
area += points[j] * points[i + 1] - points[i] * points[j + 1];
|
|
23
|
-
}
|
|
24
|
-
return { path, points, parent: null, children: [], area: Math.abs(area) / 2 };
|
|
25
|
-
});
|
|
26
|
-
for (const inner of regions) {
|
|
27
|
-
for (const outer of regions) {
|
|
28
|
-
if (outer !== inner &&
|
|
29
|
-
outer.area > inner.area &&
|
|
30
|
-
encloses(outer.points, inner.points) &&
|
|
31
|
-
(!inner.parent || outer.area < inner.parent.area)) {
|
|
32
|
-
inner.parent = outer;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const r of regions) {
|
|
37
|
-
r.parent?.children.push(r);
|
|
38
|
-
}
|
|
39
|
-
return regions.filter((r) => !r.parent);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Whether the polygon `outer` holds `inner`, two contours that do not
|
|
43
|
-
* cross, by a point of `inner` off `outer`'s outline: a pixel font's
|
|
44
|
-
* contours touch, and a corner they share is inside one and out of the
|
|
45
|
-
* other as the parity rule rounds it. Its corners, else its edges'
|
|
46
|
-
* middles; a contour lying wholly along the other's outline goes by its first.
|
|
47
|
-
*/
|
|
48
|
-
function encloses(outer, inner) {
|
|
49
|
-
for (const middles of [false, true]) {
|
|
50
|
-
for (let i = 0, j = inner.length - 2; i < inner.length; j = i, i += 2) {
|
|
51
|
-
const x = middles ? (inner[i] + inner[j]) / 2 : inner[i];
|
|
52
|
-
const y = middles ? (inner[i + 1] + inner[j + 1]) / 2 : inner[i + 1];
|
|
53
|
-
if (!onOutline(outer, x, y)) {
|
|
54
|
-
return inside(outer, x, y);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return inside(outer, inner[0], inner[1]);
|
|
59
|
-
}
|
|
60
|
-
/** Whether (px, py) lies on one of the polygon's edges, to within a millionth of a pixel. */
|
|
61
|
-
function onOutline(points, px, py) {
|
|
62
|
-
const near = 1e-6;
|
|
63
|
-
for (let i = 0, j = points.length - 2; i < points.length; j = i, i += 2) {
|
|
64
|
-
const [x0, y0, x1, y1] = [points[j], points[j + 1], points[i], points[i + 1]];
|
|
65
|
-
if (px >= Math.min(x0, x1) - near &&
|
|
66
|
-
px <= Math.max(x0, x1) + near &&
|
|
67
|
-
py >= Math.min(y0, y1) - near &&
|
|
68
|
-
py <= Math.max(y0, y1) + near &&
|
|
69
|
-
Math.abs((x1 - x0) * (py - y0) - (y1 - y0) * (px - x0)) <=
|
|
70
|
-
near * Math.max(1, Math.hypot(x1 - x0, y1 - y0))) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* How far, in pixels, a flattened curve may stray from the curve. Pixi's
|
|
78
|
-
* own flattening allows a fraction of the curve's length, which leaves
|
|
79
|
-
* Flash's long curves visibly polygonal; this holds up to a few times scale.
|
|
80
|
-
*/
|
|
81
|
-
const FLATNESS = 0.02;
|
|
82
|
-
function trace(context, path) {
|
|
83
|
-
let x = 0;
|
|
84
|
-
let y = 0;
|
|
85
|
-
for (let i = 0; i < path.length;) {
|
|
86
|
-
const command = path[i];
|
|
87
|
-
if (command === MOVE || command === LINE) {
|
|
88
|
-
x = path[i + 1];
|
|
89
|
-
y = path[i + 2];
|
|
90
|
-
if (command === MOVE) {
|
|
91
|
-
context.moveTo(x, y);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
context.lineTo(x, y);
|
|
95
|
-
}
|
|
96
|
-
i += 3;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (command === CUBIC) {
|
|
100
|
-
// A cubic's chords stray at most 3|Δ²| / 4n² for the larger of its two second differences.
|
|
101
|
-
const [c1x, c1y, c2x, c2y, ax, ay] = path.slice(i + 1, i + 7);
|
|
102
|
-
const bend = Math.max(Math.hypot(x - 2 * c1x + c2x, y - 2 * c1y + c2y), Math.hypot(c1x - 2 * c2x + ax, c1y - 2 * c2y + ay));
|
|
103
|
-
const n = Math.min(256, Math.max(1, Math.ceil(Math.sqrt((3 * bend) / (4 * FLATNESS)))));
|
|
104
|
-
for (let k = 1; k < n; k++) {
|
|
105
|
-
const t = k / n;
|
|
106
|
-
const u = 1 - t;
|
|
107
|
-
context.lineTo(u * u * u * x + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ax, u * u * u * y + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ay);
|
|
108
|
-
}
|
|
109
|
-
context.lineTo(ax, ay);
|
|
110
|
-
x = ax;
|
|
111
|
-
y = ay;
|
|
112
|
-
i += 7;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
// A quadratic's chords of parameter step 1/n stray at most |p0 - 2c + p2| / 4n².
|
|
116
|
-
const cx = path[i + 1];
|
|
117
|
-
const cy = path[i + 2];
|
|
118
|
-
const ax = path[i + 3];
|
|
119
|
-
const ay = path[i + 4];
|
|
120
|
-
const bend = Math.hypot(x - 2 * cx + ax, y - 2 * cy + ay);
|
|
121
|
-
const n = Math.min(256, Math.max(1, Math.ceil(Math.sqrt(bend / (4 * FLATNESS)))));
|
|
122
|
-
for (let k = 1; k < n; k++) {
|
|
123
|
-
const t = k / n;
|
|
124
|
-
const u = 1 - t;
|
|
125
|
-
context.lineTo(u * u * x + 2 * u * t * cx + t * t * ax, u * u * y + 2 * u * t * cy + t * t * ay);
|
|
126
|
-
}
|
|
127
|
-
context.lineTo(ax, ay);
|
|
128
|
-
x = ax;
|
|
129
|
-
y = ay;
|
|
130
|
-
i += 5;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* A fill's paint, but a resolved bitmap's, which the view gives: a solid
|
|
135
|
-
* colour; a gradient as its first colour, for now; and a bitmap fill of a
|
|
136
|
-
* bitmap the SWF lacks red, as Flash draws one.
|
|
137
|
-
*/
|
|
138
|
-
function paint(fill) {
|
|
139
|
-
const argb = fill.type === "solid"
|
|
140
|
-
? fill.color
|
|
141
|
-
: fill.type === "bitmap"
|
|
142
|
-
? 0xffff0000
|
|
143
|
-
: (fill.gradient.stops[0]?.color ?? 0xff000000);
|
|
144
|
-
return { color: argb & 0xffffff, alpha: (argb >>> 24) / 255 };
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Make a texture source report "clamp" for addressMode: Pixi turns a
|
|
148
|
-
* fill's texture that says clamp-to-edge to repeating, while WebGL reads
|
|
149
|
-
* each axis's mode, which stays clamped.
|
|
150
|
-
*/
|
|
151
|
-
function keepClamped(source) {
|
|
152
|
-
Object.defineProperty(source.style, "addressMode", { get: () => "clamp", set: () => { } });
|
|
153
|
-
}
|
|
154
|
-
/** What each fill context holds of the textures its gradients use, given back as it is destroyed. */
|
|
155
|
-
const holds = new WeakMap();
|
|
156
|
-
/** A context destroyed, the textures it held given back first: a fill's, or lines', which hold none. */
|
|
157
|
-
function destroyContext(context) {
|
|
158
|
-
for (const release of holds.get(context) ?? []) {
|
|
159
|
-
release();
|
|
160
|
-
}
|
|
161
|
-
holds.delete(context);
|
|
162
|
-
dropBatchedCopy(context);
|
|
163
|
-
context.destroy();
|
|
164
|
-
}
|
|
165
|
-
/** The bounds of a fill's contours, whole pixels out. */
|
|
166
|
-
function regionOf(contours) {
|
|
167
|
-
let x0 = Number.POSITIVE_INFINITY;
|
|
168
|
-
let y0 = Number.POSITIVE_INFINITY;
|
|
169
|
-
let x1 = Number.NEGATIVE_INFINITY;
|
|
170
|
-
let y1 = Number.NEGATIVE_INFINITY;
|
|
171
|
-
for (const path of contours) {
|
|
172
|
-
const points = flatten(path);
|
|
173
|
-
for (let i = 0; i < points.length; i += 2) {
|
|
174
|
-
x0 = Math.min(x0, points[i]);
|
|
175
|
-
x1 = Math.max(x1, points[i]);
|
|
176
|
-
y0 = Math.min(y0, points[i + 1]);
|
|
177
|
-
y1 = Math.max(y1, points[i + 1]);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
if (!(x1 >= x0 && y1 >= y0)) {
|
|
181
|
-
return { x: 0, y: 0, width: 1, height: 1 };
|
|
182
|
-
}
|
|
183
|
-
const x = Math.floor(x0);
|
|
184
|
-
const y = Math.floor(y0);
|
|
185
|
-
return { x, y, width: Math.max(1, Math.ceil(x1) - x), height: Math.max(1, Math.ceil(y1) - y) };
|
|
186
|
-
}
|
|
187
|
-
/** The store a SWF bitmap's fills draw from, one for all its shapes; null until its image is decoded. */
|
|
188
|
-
function characterStore(character) {
|
|
189
|
-
if (!character.pixels) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
character.store ??= BitmapStore.of(character.pixels);
|
|
193
|
-
return character.store;
|
|
194
|
-
}
|
|
195
|
-
const UNIT = [1, 0, 0, 1];
|
|
196
|
-
function sameLinear(p, q) {
|
|
197
|
-
return p[0] === q[0] && p[1] === q[1] && p[2] === q[2] && p[3] === q[3];
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* A line's width on the stage. Flash strokes a transformed shape with one
|
|
201
|
-
* width all along, not the local width stretched by the transform: the
|
|
202
|
-
* width scaled by the transform's (a + c, b + d) as a line's scale mode
|
|
203
|
-
* allows, never under `least`, a pixel of the screen it is shown on, which
|
|
204
|
-
* is also how wide a hairline is.
|
|
205
|
-
*/
|
|
206
|
-
function screenWidth(line, m, least) {
|
|
207
|
-
const sx = Math.abs(m[0] + m[2]);
|
|
208
|
-
const sy = Math.abs(m[1] + m[3]);
|
|
209
|
-
const scale = line.noHScale
|
|
210
|
-
? line.noVScale
|
|
211
|
-
? 1
|
|
212
|
-
: sy
|
|
213
|
-
: line.noVScale
|
|
214
|
-
? sx
|
|
215
|
-
: Math.sqrt((sx * sx + sy * sy) / 2);
|
|
216
|
-
return Math.max(least, (line.width / 20) * scale);
|
|
217
|
-
}
|
|
218
|
-
function stroke(line, m, least) {
|
|
219
|
-
const caps = ["round", "butt", "square"];
|
|
220
|
-
const joins = ["round", "bevel", "miter"];
|
|
221
|
-
return {
|
|
222
|
-
width: screenWidth(line, m, least),
|
|
223
|
-
color: line.color & 0xffffff,
|
|
224
|
-
alpha: (line.color >>> 24) / 255,
|
|
225
|
-
cap: caps[line.startCap] ?? "round",
|
|
226
|
-
join: joins[line.join] ?? "round",
|
|
227
|
-
miterLimit: line.miterLimit,
|
|
228
|
-
alignment: 0.5,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function transformPath(path, m) {
|
|
232
|
-
const out = path.slice();
|
|
233
|
-
for (let i = 0; i < out.length;) {
|
|
234
|
-
const points = pointsOf(out[i]);
|
|
235
|
-
for (let k = 0; k < points; k++) {
|
|
236
|
-
const x = out[i + 1 + 2 * k];
|
|
237
|
-
const y = out[i + 2 + 2 * k];
|
|
238
|
-
out[i + 1 + 2 * k] = m[0] * x + m[2] * y;
|
|
239
|
-
out[i + 2 + 2 * k] = m[1] * x + m[3] * y;
|
|
240
|
-
}
|
|
241
|
-
i += 1 + 2 * points;
|
|
242
|
-
}
|
|
243
|
-
return out;
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* A context for a shape's fills or lines, drawn on its own, never batched.
|
|
247
|
-
* A batch holds its vertices on the stage, so Pixi repacks and uploads
|
|
248
|
-
* them all again whenever anything in the render group changes structure,
|
|
249
|
-
* as a timeline does on most frames; and between batches and the large
|
|
250
|
-
* shapes Pixi draws alone anyway it switches programs. Alone, a shape's
|
|
251
|
-
* geometry is uploaded once and only its transform changes.
|
|
252
|
-
*/
|
|
253
|
-
function shapeContext() {
|
|
254
|
-
const context = new GraphicsContext();
|
|
255
|
-
context.batchMode = "no-batch";
|
|
256
|
-
return context;
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* A layer's fills, which are the same for every instance of the shape.
|
|
260
|
-
* Each contour's region is inside or not by the fill's rule, by its depth
|
|
261
|
-
* in the containment for even-odd, by the sum of orientations around it
|
|
262
|
-
* for non-zero: a region inside where its parent is not is filled, with
|
|
263
|
-
* the first regions below it that are not cut out as holes, and so on in.
|
|
264
|
-
*/
|
|
265
|
-
function fillContext(layer, painter) {
|
|
266
|
-
const context = shapeContext();
|
|
267
|
-
const held = [];
|
|
268
|
-
holds.set(context, held);
|
|
269
|
-
for (const { fill, contours, winding } of layer.fills) {
|
|
270
|
-
// The region is a radial gradient's alone to need: flattening the contours is not free.
|
|
271
|
-
const style = painter(fill, () => regionOf(contours), (release) => held.push(release));
|
|
272
|
-
const inside = (depth, sum) => winding === "nonZero" ? sum !== 0 : depth % 2 === 0;
|
|
273
|
-
// A region's holes are cut together, then the islands in them filled.
|
|
274
|
-
// Pixi's cut() goes to the fill before the last too once the last has a
|
|
275
|
-
// hole, so a second cut, or one after an island, would land a hole in
|
|
276
|
-
// another region, which then fills across to it.
|
|
277
|
-
const fillRegion = (region, depth, sum) => {
|
|
278
|
-
context.beginPath();
|
|
279
|
-
trace(context, region.path);
|
|
280
|
-
context.closePath().fill(style);
|
|
281
|
-
const cut = [];
|
|
282
|
-
holes(region, depth, sum, cut);
|
|
283
|
-
if (cut.length === 0) {
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
context.beginPath();
|
|
287
|
-
for (const [hole] of cut) {
|
|
288
|
-
trace(context, hole.path);
|
|
289
|
-
context.closePath();
|
|
290
|
-
}
|
|
291
|
-
context.cut();
|
|
292
|
-
for (const [hole, d, s] of cut) {
|
|
293
|
-
islands(hole, d, s);
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
const holes = (region, depth, sum, cut) => {
|
|
297
|
-
for (const child of region.children) {
|
|
298
|
-
const s = sum + orientation(child.points);
|
|
299
|
-
if (inside(depth + 1, s)) {
|
|
300
|
-
holes(child, depth + 1, s, cut);
|
|
301
|
-
}
|
|
302
|
-
else {
|
|
303
|
-
cut.push([child, depth + 1, s]);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
const islands = (region, depth, sum) => {
|
|
308
|
-
for (const child of region.children) {
|
|
309
|
-
const s = sum + orientation(child.points);
|
|
310
|
-
if (inside(depth + 1, s)) {
|
|
311
|
-
fillRegion(child, depth + 1, s);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
islands(child, depth + 1, s);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
for (const root of containment(contours)) {
|
|
319
|
-
const s = orientation(root.points);
|
|
320
|
-
if (inside(0, s)) {
|
|
321
|
-
fillRegion(root, 0, s);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
islands(root, 0, s);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return context;
|
|
329
|
-
}
|
|
330
|
-
/** A layer's lines as seen through `m`, drawn in its space so that their width is even. */
|
|
331
|
-
function strokeContext(layer, m, least) {
|
|
332
|
-
const context = shapeContext();
|
|
333
|
-
for (const { line, paths } of layer.strokes) {
|
|
334
|
-
context.beginPath();
|
|
335
|
-
for (const path of paths) {
|
|
336
|
-
trace(context, transformPath(path, m));
|
|
337
|
-
}
|
|
338
|
-
context.stroke(stroke(line, m, least));
|
|
339
|
-
}
|
|
340
|
-
return context;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* How long, in milliseconds, lines' contexts no instance uses are kept for
|
|
344
|
-
* one to come back to, as a loop does, and how many at most. By age, not
|
|
345
|
-
* count: a loop longer than a count would find each context gone just
|
|
346
|
-
* before it came round to it; and by time, not renders, which a host may
|
|
347
|
-
* make many of between a SWF's frames.
|
|
348
|
-
*/
|
|
349
|
-
const IDLE_MS = 5000;
|
|
350
|
-
const IDLE_MOST = 4096;
|
|
351
|
-
/**
|
|
352
|
-
* The most objects off the list kept whole: enough for a pool or a panel,
|
|
353
|
-
* while a timeline that makes its children anew on every frame lets its
|
|
354
|
-
* last frames' go, whose Graphics in thousands made long collector pauses.
|
|
355
|
-
*/
|
|
356
|
-
const PARKED_MOST = 1024;
|
|
357
|
-
/** First-time detached groups get one chance to return without letting churn fill the batch cache. */
|
|
358
|
-
const PARKED_FIRST_GROUPS_MOST = 64;
|
|
359
|
-
/**
|
|
360
|
-
* Lines' contexts by shape layer and the linear transform they are seen
|
|
361
|
-
* through, shared by every instance that sees a character's layer alike:
|
|
362
|
-
* instances of one creature in step, or one instance on its loop's next
|
|
363
|
-
* turn. Contexts are counted as they are taken and given back, a removed
|
|
364
|
-
* object's too; one no longer taken waits among the idle, destroyed after
|
|
365
|
-
* IDLE_MS or, the oldest first, past IDLE_MOST. Only a character's layers
|
|
366
|
-
* are shared, which never change, and a blend's while its morph keeps it:
|
|
367
|
-
* one it has dropped is never drawn from again, so its lines go at once.
|
|
368
|
-
*/
|
|
369
|
-
class StrokeContexts {
|
|
370
|
-
counts;
|
|
371
|
-
byLayer = new WeakMap();
|
|
372
|
-
uses = new Map();
|
|
373
|
-
keys = new Map();
|
|
374
|
-
/** Contexts no one holds, oldest first, with the time each went idle. */
|
|
375
|
-
idle = new Map();
|
|
376
|
-
/** The idle ones of blends, which go as soon as their morph drops the blend. */
|
|
377
|
-
idleBlends = new Set();
|
|
378
|
-
constructor(counts) {
|
|
379
|
-
this.counts = counts;
|
|
380
|
-
}
|
|
381
|
-
/** The layer's lines under `key` if they are kept, not taken: for a view drawn once to borrow. */
|
|
382
|
-
peek(layer, key) {
|
|
383
|
-
return this.byLayer.get(layer)?.get(key) ?? null;
|
|
384
|
-
}
|
|
385
|
-
/** The layer's lines seen through `m`, taken: made or found. */
|
|
386
|
-
take(layer, m, least, key = linesKey(m, least)) {
|
|
387
|
-
let contexts = this.byLayer.get(layer);
|
|
388
|
-
if (!contexts) {
|
|
389
|
-
contexts = new Map();
|
|
390
|
-
this.byLayer.set(layer, contexts);
|
|
391
|
-
}
|
|
392
|
-
let context = contexts.get(key);
|
|
393
|
-
if (context) {
|
|
394
|
-
this.counts.strokeReuses++;
|
|
395
|
-
this.idle.delete(context);
|
|
396
|
-
this.idleBlends.delete(context);
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
context = linesContext(layer, m, least);
|
|
400
|
-
this.counts.strokeContexts++;
|
|
401
|
-
contexts.set(key, context);
|
|
402
|
-
this.keys.set(context, [layer, key]);
|
|
403
|
-
}
|
|
404
|
-
this.uses.set(context, (this.uses.get(context) ?? 0) + 1);
|
|
405
|
-
return context;
|
|
406
|
-
}
|
|
407
|
-
/** Give a context back: one of these goes idle when no one holds it; any other is destroyed. */
|
|
408
|
-
give(context) {
|
|
409
|
-
const uses = this.uses.get(context);
|
|
410
|
-
if (uses === undefined) {
|
|
411
|
-
destroyContext(context);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
if (uses > 1) {
|
|
415
|
-
this.uses.set(context, uses - 1);
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
this.uses.delete(context);
|
|
419
|
-
const layer = this.keys.get(context)[0];
|
|
420
|
-
if (droppedLayers.has(layer)) {
|
|
421
|
-
this.drop(context);
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
this.idle.set(context, performance.now());
|
|
425
|
-
if (blendLayers.has(layer)) {
|
|
426
|
-
this.idleBlends.add(context);
|
|
427
|
-
}
|
|
428
|
-
if (this.idle.size > IDLE_MOST) {
|
|
429
|
-
this.drop(this.idle.keys().next().value);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
/** A frame prepared: the contexts idle too long go, and those of blends their morph dropped. */
|
|
433
|
-
tick() {
|
|
434
|
-
for (const context of this.idleBlends) {
|
|
435
|
-
if (droppedLayers.has(this.keys.get(context)[0])) {
|
|
436
|
-
this.drop(context);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
const now = performance.now();
|
|
440
|
-
for (const [context, since] of this.idle) {
|
|
441
|
-
if (now - since < IDLE_MS) {
|
|
442
|
-
break;
|
|
443
|
-
}
|
|
444
|
-
this.drop(context);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
drop(context) {
|
|
448
|
-
this.idle.delete(context);
|
|
449
|
-
this.idleBlends.delete(context);
|
|
450
|
-
const [layer, key] = this.keys.get(context);
|
|
451
|
-
this.keys.delete(context);
|
|
452
|
-
this.byLayer.get(layer)?.delete(key);
|
|
453
|
-
destroyContext(context);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* A morph's blends' fills, shared as a shape's are by every instance drawn
|
|
458
|
-
* at that blend: a crowd in step, and a timeline that places the morph
|
|
459
|
-
* again at a ratio it has drawn, tessellate each blend once. Counted as
|
|
460
|
-
* nodes take and give them back; one no one holds goes as its morph drops
|
|
461
|
-
* the blend, which is then never drawn from again, or after IDLE_MS.
|
|
462
|
-
*/
|
|
463
|
-
class BlendFills {
|
|
464
|
-
held = new Map();
|
|
465
|
-
/** The blends no one holds, in the order they went idle, with the time each did. */
|
|
466
|
-
idle = new Map();
|
|
467
|
-
/** The blend's fills if they are kept, not taken: for a view drawn once to borrow. */
|
|
468
|
-
peek(blend) {
|
|
469
|
-
return this.held.get(blend)?.fills ?? null;
|
|
470
|
-
}
|
|
471
|
-
/** The blend's fills, taken: found, or built by `build`. */
|
|
472
|
-
take(blend, build) {
|
|
473
|
-
let entry = this.held.get(blend);
|
|
474
|
-
if (!entry) {
|
|
475
|
-
entry = { fills: build(), uses: 0 };
|
|
476
|
-
this.held.set(blend, entry);
|
|
477
|
-
}
|
|
478
|
-
entry.uses++;
|
|
479
|
-
this.idle.delete(blend);
|
|
480
|
-
return entry.fills;
|
|
481
|
-
}
|
|
482
|
-
give(blend) {
|
|
483
|
-
const entry = this.held.get(blend);
|
|
484
|
-
if (!entry) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
entry.uses--;
|
|
488
|
-
if (entry.uses > 0) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
if (blend.layers.some((layer) => droppedLayers.has(layer))) {
|
|
492
|
-
this.drop(blend);
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
this.idle.set(blend, performance.now());
|
|
496
|
-
}
|
|
497
|
-
/** A frame prepared: the idle blends their morph dropped go, and those idle too long. */
|
|
498
|
-
tick() {
|
|
499
|
-
const now = performance.now();
|
|
500
|
-
for (const [blend, since] of this.idle) {
|
|
501
|
-
if (now - since >= IDLE_MS || blend.layers.some((layer) => droppedLayers.has(layer))) {
|
|
502
|
-
this.drop(blend);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
drop(blend) {
|
|
507
|
-
this.idle.delete(blend);
|
|
508
|
-
for (const context of this.held.get(blend)?.fills ?? []) {
|
|
509
|
-
destroyContext(context);
|
|
510
|
-
}
|
|
511
|
-
this.held.delete(blend);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
/** The key a layer's lines seen through `m`, at least `least` wide, are kept by. */
|
|
515
|
-
function linesKey(m, least) {
|
|
516
|
-
return `${m[0]},${m[1]},${m[2]},${m[3]},${least}`;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* A Graphics of a context it does not own and that never changes once
|
|
520
|
-
* built: a shape's or a drawing's fills, a glyph's, or lines, whose context
|
|
521
|
-
* is swapped for the transform's as the object turns. It does not listen on
|
|
522
|
-
* the context, which is destroyed only once no one holds it: a shared
|
|
523
|
-
* one's listeners, one an instance or a glyph, made each swap and each
|
|
524
|
-
* destroy search them all, so that a text of n glyphs of one font took
|
|
525
|
-
* O(n²) to go.
|
|
526
|
-
*/
|
|
527
|
-
class SharedGraphics extends Graphics {
|
|
528
|
-
/** The context it stands for, which the cache counts: what it draws, or that drawn as a batched copy (showFor). */
|
|
529
|
-
shared;
|
|
530
|
-
constructor(context) {
|
|
531
|
-
super(context);
|
|
532
|
-
this.shared = this.context;
|
|
533
|
-
this.context.off("update", this.onViewUpdate, this);
|
|
534
|
-
this.context.off("unload", this.unload, this);
|
|
535
|
-
}
|
|
536
|
-
/** Stand for `context` instead, drawn as its colour transform needs. */
|
|
537
|
-
swap(context) {
|
|
538
|
-
this.shared = context;
|
|
539
|
-
showFor(this, this.flashColor ?? null);
|
|
540
|
-
}
|
|
541
|
-
/** Draw `context` without listening on it: a shared context would gather a listener for every instance. */
|
|
542
|
-
show(context) {
|
|
543
|
-
if (context !== this.context) {
|
|
544
|
-
this._context = context;
|
|
545
|
-
this.onViewUpdate();
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
/** A layer's lines seen through `m`, nothing where `m` flattens them. */
|
|
550
|
-
function linesContext(layer, m, least) {
|
|
551
|
-
return m[0] * m[3] - m[1] * m[2] === 0 ? new GraphicsContext() : strokeContext(layer, m, least);
|
|
552
|
-
}
|
|
553
|
-
const NO_RECORDS = [];
|
|
554
|
-
/** Only a partner outside the branch prevents it owning a separate instruction set. */
|
|
555
|
-
function maskLink(o, partner, links) {
|
|
556
|
-
if (partner && !o.encloses(partner)) {
|
|
557
|
-
links.push(partner.ref);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
export class PixiView {
|
|
561
|
-
renderer;
|
|
562
|
-
fresh;
|
|
563
|
-
source;
|
|
564
|
-
stage = new PixiContainer();
|
|
565
|
-
/** What the view has built since it was made, for measuring: lines' contexts made and reused. */
|
|
566
|
-
counts = { strokeContexts: 0, strokeReuses: 0 };
|
|
567
|
-
lines = new StrokeContexts(this.counts);
|
|
568
|
-
nodes = new WeakMap();
|
|
569
|
-
fills = new Map();
|
|
570
|
-
blends = new BlendFills();
|
|
571
|
-
/**
|
|
572
|
-
* A fill painted: a bitmap's from its store's texture sampled as the fill
|
|
573
|
-
* samples, in global texture space so that its matrix maps the bitmap's
|
|
574
|
-
* pixels to the shape's; nothing for a bitmap that has no texture.
|
|
575
|
-
*/
|
|
576
|
-
painter = (fill, region, hold) => {
|
|
577
|
-
if (fill.type === "gradient") {
|
|
578
|
-
const m = fill.matrix;
|
|
579
|
-
if (m.a * m.d - m.b * m.c === 0) {
|
|
580
|
-
return { color: 0, alpha: 0 };
|
|
581
|
-
}
|
|
582
|
-
const held = gpuBitmaps(this.renderer).gradientTexture(fill, fill.radial ? region() : null);
|
|
583
|
-
hold(held.release);
|
|
584
|
-
return { texture: held.texture, matrix: held.matrix, textureSpace: "global" };
|
|
585
|
-
}
|
|
586
|
-
if (fill.type !== "image") {
|
|
587
|
-
return paint(fill);
|
|
588
|
-
}
|
|
589
|
-
const store = fill.image instanceof BitmapStore ? fill.image : characterStore(fill.image);
|
|
590
|
-
const texture = store && gpuBitmaps(this.renderer).fillTexture(store, fill.repeat, fill.smooth);
|
|
591
|
-
if (!texture) {
|
|
592
|
-
return { color: 0, alpha: 0 };
|
|
593
|
-
}
|
|
594
|
-
const m = fill.matrix;
|
|
595
|
-
return { texture, matrix: new Matrix(m.a, m.b, m.c, m.d, m.tx, m.ty), textureSpace: "global" };
|
|
596
|
-
};
|
|
597
|
-
/** Whether the renderer was found to lack the back buffer that filter-backed blends read. */
|
|
598
|
-
backBufferChecked = false;
|
|
599
|
-
/**
|
|
600
|
-
* A filter-backed blend reads Pixi's back buffer, which WebGL has only
|
|
601
|
-
* with `useBackBuffer: true`; without it that blend draws as normal.
|
|
602
|
-
*/
|
|
603
|
-
checkBackBuffer() {
|
|
604
|
-
if (this.backBufferChecked) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
this.backBufferChecked = true;
|
|
608
|
-
const gl = this.renderer;
|
|
609
|
-
if (gl.backBuffer && !gl.backBuffer.useBackBuffer) {
|
|
610
|
-
console.warn("swf2es: a blend mode draws as normal: make the Pixi renderer with useBackBuffer: true");
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
/** What a fresh view built itself, which it destroys; what it borrowed from `source` stays. */
|
|
614
|
-
built = new Set();
|
|
615
|
-
/** What a fresh view borrowed from its source's caches, which it neither gives back nor destroys. */
|
|
616
|
-
borrowed = new WeakSet();
|
|
617
|
-
/**
|
|
618
|
-
* The released nodes that drew something, oldest first, with the time
|
|
619
|
-
* each left the list: kept whole for IDLE_MS, at most PARKED_MOST of them,
|
|
620
|
-
* as a pool's objects or a panel shown and hidden come back, then emptied.
|
|
621
|
-
*/
|
|
622
|
-
parked = new Map();
|
|
623
|
-
/** Retain only batches, not detached display trees, while a group waits for reuse. */
|
|
624
|
-
parkedGroups = new Map();
|
|
625
|
-
parkedFirstGroups = new Set();
|
|
626
|
-
/** The filters a fresh view made, which it destroys with the rest. */
|
|
627
|
-
builtFilters = [];
|
|
628
|
-
/** The objects `mask` was found set on, for the masks to be placed that are not under the root. */
|
|
629
|
-
maskees = new Set();
|
|
630
|
-
/** Where those masks are placed, beside the root. */
|
|
631
|
-
offList = new PixiContainer();
|
|
632
|
-
/**
|
|
633
|
-
* `fresh` makes a view that draws every object as new and leaves the
|
|
634
|
-
* objects' dirty flags as they were, for a one-off render such as
|
|
635
|
-
* BitmapData.draw's, so the stage's own view still sees each change.
|
|
636
|
-
*/
|
|
637
|
-
/**
|
|
638
|
-
* How many screen pixels a stage pixel covers, for the thinnest line,
|
|
639
|
-
* which Flash draws a pixel of the screen wide however far the stage is
|
|
640
|
-
* zoomed. The renderer's resolution unless set: a host that renders
|
|
641
|
-
* finer than the screen to average down, as the test page does, sets it
|
|
642
|
-
* to what the screen shows. A fresh view draws a BitmapData's pixels,
|
|
643
|
-
* which are the stage's, so its thinnest line is a pixel.
|
|
644
|
-
*/
|
|
645
|
-
screenScale = null;
|
|
646
|
-
/** The thinnest line it last drew the stage's lines with; a change draws them all again. */
|
|
647
|
-
strokedAt = 0;
|
|
648
|
-
/** The root the last prepare synced, to know whether an object is on the list. */
|
|
649
|
-
root = null;
|
|
650
|
-
/** Whether this prepare draws every line again, the screen's scale having changed. */
|
|
651
|
-
rescaled = false;
|
|
652
|
-
constructor(renderer, fresh = false,
|
|
653
|
-
/** The stage's view, whose geometry and textures a fresh view borrows where they are current. */
|
|
654
|
-
source = null) {
|
|
655
|
-
this.renderer = renderer;
|
|
656
|
-
this.fresh = fresh;
|
|
657
|
-
this.source = source;
|
|
658
|
-
if (renderer.type === RendererType.WEBGL) {
|
|
659
|
-
boundedResolves(renderer);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Where a pointer event is on the SWF's stage. CSS `object-fit` shows the
|
|
664
|
-
* canvas's pixels in a box of their own within its content box, letterboxed
|
|
665
|
-
* by `contain`, cropped by `cover`: Pixi maps a point over the whole element
|
|
666
|
-
* as `fill` would, so the point is taken within that box instead, centred
|
|
667
|
-
* as the default `object-position` puts it, and then from the canvas's
|
|
668
|
-
* pixels to Pixi's screen and the stage. `style` is the canvas's live
|
|
669
|
-
* computed style, null where there is no DOM.
|
|
670
|
-
*/
|
|
671
|
-
stagePoint(player, e, style) {
|
|
672
|
-
const screen = this.renderer.screen;
|
|
673
|
-
const toStage = (x, y) => [
|
|
674
|
-
(x * player.width) / screen.width,
|
|
675
|
-
(y * player.height) / screen.height,
|
|
676
|
-
];
|
|
677
|
-
const canvas = this.renderer.canvas;
|
|
678
|
-
if (!canvas?.getBoundingClientRect || !style) {
|
|
679
|
-
return toStage(e.global.x, e.global.y);
|
|
680
|
-
}
|
|
681
|
-
// The content box: the element's rectangle within its borders and padding.
|
|
682
|
-
const px = (v) => Number.parseFloat(v ?? "") || 0;
|
|
683
|
-
const rect = canvas.getBoundingClientRect();
|
|
684
|
-
let left = rect.left + px(style.borderLeftWidth) + px(style.paddingLeft);
|
|
685
|
-
let top = rect.top + px(style.borderTopWidth) + px(style.paddingTop);
|
|
686
|
-
const width = rect.width -
|
|
687
|
-
px(style.borderLeftWidth) -
|
|
688
|
-
px(style.paddingLeft) -
|
|
689
|
-
px(style.borderRightWidth) -
|
|
690
|
-
px(style.paddingRight);
|
|
691
|
-
const height = rect.height -
|
|
692
|
-
px(style.borderTopWidth) -
|
|
693
|
-
px(style.paddingTop) -
|
|
694
|
-
px(style.borderBottomWidth) -
|
|
695
|
-
px(style.paddingBottom);
|
|
696
|
-
let sx = width / canvas.width;
|
|
697
|
-
let sy = height / canvas.height;
|
|
698
|
-
const fit = style.objectFit;
|
|
699
|
-
if (fit === "contain" || fit === "scale-down" || fit === "cover" || fit === "none") {
|
|
700
|
-
let scale = fit === "cover" ? Math.max(sx, sy) : fit === "none" ? 1 : Math.min(sx, sy);
|
|
701
|
-
if (fit === "scale-down") {
|
|
702
|
-
scale = Math.min(scale, 1);
|
|
703
|
-
}
|
|
704
|
-
left += (width - canvas.width * scale) / 2;
|
|
705
|
-
top += (height - canvas.height * scale) / 2;
|
|
706
|
-
sx = scale;
|
|
707
|
-
sy = scale;
|
|
708
|
-
}
|
|
709
|
-
const resolution = this.renderer.resolution || 1;
|
|
710
|
-
return toStage((e.clientX - left) / sx / resolution, (e.clientY - top) / sy / resolution);
|
|
711
|
-
}
|
|
712
|
-
/** Let Pixi normalize browser coordinates; Flash's display list chooses the target. */
|
|
713
|
-
bindPointer(player) {
|
|
714
|
-
this.stage.eventMode = "static";
|
|
715
|
-
this.stage.hitArea = new Rectangle(0, 0, this.renderer.screen.width, this.renderer.screen.height);
|
|
716
|
-
// A move is posted, handled when the player next advances or before the
|
|
717
|
-
// next press, release, leave or key; a frame callback of its own handles
|
|
718
|
-
// it where the host does not advance (a paused player, say).
|
|
719
|
-
let frame = 0;
|
|
720
|
-
const canvas = this.renderer.canvas;
|
|
721
|
-
// Live: read once, it follows the element's style as the page changes it.
|
|
722
|
-
const style = canvas?.getBoundingClientRect && typeof getComputedStyle === "function"
|
|
723
|
-
? getComputedStyle(canvas)
|
|
724
|
-
: null;
|
|
725
|
-
const send = (type) => (e) => {
|
|
726
|
-
const [x, y] = this.stagePoint(player, e, style);
|
|
727
|
-
const p = {
|
|
728
|
-
x,
|
|
729
|
-
y,
|
|
730
|
-
button: e.button,
|
|
731
|
-
buttons: e.buttons,
|
|
732
|
-
altKey: e.altKey,
|
|
733
|
-
ctrlKey: e.ctrlKey,
|
|
734
|
-
shiftKey: e.shiftKey,
|
|
735
|
-
time: e.timeStamp,
|
|
736
|
-
};
|
|
737
|
-
if (type === "move" && player.pointer && typeof requestAnimationFrame === "function") {
|
|
738
|
-
player.pointer.post(p);
|
|
739
|
-
frame ||= requestAnimationFrame(() => {
|
|
740
|
-
frame = 0;
|
|
741
|
-
player.pointer?.flush();
|
|
742
|
-
});
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
player.pointer?.handle(type, p);
|
|
746
|
-
};
|
|
747
|
-
const move = send("move");
|
|
748
|
-
const down = send("down");
|
|
749
|
-
const up = send("up");
|
|
750
|
-
const leave = send("leave");
|
|
751
|
-
this.stage.on("pointermove", move);
|
|
752
|
-
this.stage.on("pointerdown", down);
|
|
753
|
-
this.stage.on("pointerup", up);
|
|
754
|
-
this.stage.on("pointerupoutside", up);
|
|
755
|
-
this.stage.on("pointerleave", leave);
|
|
756
|
-
// Pixi sets the canvas's cursor on every move, from its target's, which
|
|
757
|
-
// is this stage: the player's cursor goes there, and through Pixi's own
|
|
758
|
-
// setter now, which keeps Pixi's record of it right.
|
|
759
|
-
const events = this.renderer.events;
|
|
760
|
-
// Pixi's "default" is "inherit", which shows the page's cursor where the
|
|
761
|
-
// page sets one; Flash's arrow, auto's or a forced one, is the arrow.
|
|
762
|
-
const pixiDefault = events?.cursorStyles.default;
|
|
763
|
-
if (events) {
|
|
764
|
-
events.cursorStyles.default = "default";
|
|
765
|
-
}
|
|
766
|
-
const show = (cursor) => {
|
|
767
|
-
this.stage.cursor = cursor;
|
|
768
|
-
if (events) {
|
|
769
|
-
events.setCursor(cursor);
|
|
770
|
-
}
|
|
771
|
-
else if (canvas?.style) {
|
|
772
|
-
canvas.style.cursor = cursor;
|
|
773
|
-
}
|
|
774
|
-
};
|
|
775
|
-
const pointer = player.pointer;
|
|
776
|
-
if (pointer) {
|
|
777
|
-
pointer.onCursor = show;
|
|
778
|
-
show(pointer.cursor());
|
|
779
|
-
}
|
|
780
|
-
return () => {
|
|
781
|
-
// Pixi's own default back first, then the canvas left to it, as before the bind.
|
|
782
|
-
if (events) {
|
|
783
|
-
events.cursorStyles.default = pixiDefault;
|
|
784
|
-
}
|
|
785
|
-
if (pointer) {
|
|
786
|
-
pointer.onCursor = null;
|
|
787
|
-
this.stage.cursor = "default";
|
|
788
|
-
if (canvas?.style) {
|
|
789
|
-
canvas.style.cursor = "";
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
if (frame) {
|
|
793
|
-
cancelAnimationFrame(frame);
|
|
794
|
-
frame = 0;
|
|
795
|
-
}
|
|
796
|
-
player.pointer?.flush();
|
|
797
|
-
this.stage.off("pointermove", move);
|
|
798
|
-
this.stage.off("pointerdown", down);
|
|
799
|
-
this.stage.off("pointerup", up);
|
|
800
|
-
this.stage.off("pointerupoutside", up);
|
|
801
|
-
this.stage.off("pointerleave", leave);
|
|
802
|
-
this.stage.eventMode = "passive";
|
|
803
|
-
this.stage.hitArea = null;
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* The source view's node for `o`, if what it drew is still `o`'s: built
|
|
808
|
-
* since the content last changed. Its lines are current for its own
|
|
809
|
-
* `world` alone.
|
|
810
|
-
*/
|
|
811
|
-
current(o) {
|
|
812
|
-
const node = this.source?.nodes.get(o);
|
|
813
|
-
return node && !(o.dirty & CONTENT) ? node : null;
|
|
814
|
-
}
|
|
815
|
-
/** Destroy what this fresh view built, and its containers; what it borrowed stays its owner's. */
|
|
816
|
-
dispose(root) {
|
|
817
|
-
root.destroy({ children: true });
|
|
818
|
-
for (const context of this.built) {
|
|
819
|
-
destroyContext(context);
|
|
820
|
-
}
|
|
821
|
-
// A container destroyed lets go of its filters without destroying them.
|
|
822
|
-
for (const filter of this.builtFilters) {
|
|
823
|
-
filter.destroy();
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
node(o) {
|
|
827
|
-
let node = this.nodes.get(o);
|
|
828
|
-
if (!node) {
|
|
829
|
-
const art = new PixiContainer();
|
|
830
|
-
node = {
|
|
831
|
-
container: new PixiContainer(),
|
|
832
|
-
world: [0, 0, 0, 0],
|
|
833
|
-
art,
|
|
834
|
-
layers: [],
|
|
835
|
-
fills: [],
|
|
836
|
-
ownFills: false,
|
|
837
|
-
blended: null,
|
|
838
|
-
strokes: [],
|
|
839
|
-
sharedLines: false,
|
|
840
|
-
kids: [],
|
|
841
|
-
released: false,
|
|
842
|
-
reused: false,
|
|
843
|
-
emptied: false,
|
|
844
|
-
strokedAt: 0,
|
|
845
|
-
bitmap: null,
|
|
846
|
-
lines: [],
|
|
847
|
-
masking: false,
|
|
848
|
-
groups: [],
|
|
849
|
-
scroll: null,
|
|
850
|
-
clipped: false,
|
|
851
|
-
color: null,
|
|
852
|
-
inherited: null,
|
|
853
|
-
blend: "normal",
|
|
854
|
-
filterRecords: NO_RECORDS,
|
|
855
|
-
filters: [],
|
|
856
|
-
draws: 0,
|
|
857
|
-
singleDraw: 0,
|
|
858
|
-
directBlend: null,
|
|
859
|
-
childIsolation: false,
|
|
860
|
-
maskLinks: [],
|
|
861
|
-
};
|
|
862
|
-
node.container.addChild(art);
|
|
863
|
-
this.nodes.set(o, node);
|
|
864
|
-
}
|
|
865
|
-
return node;
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* What `o` itself draws, built anew: a shape's layers, whose fills are
|
|
869
|
-
* shared by every instance of the character, or its drawing's, which are
|
|
870
|
-
* its own and change.
|
|
871
|
-
*/
|
|
872
|
-
redraw(o, node) {
|
|
873
|
-
const done = this.clear(node);
|
|
874
|
-
// Given back after the new ones are made, so that a texture or a blend they share is kept, not
|
|
875
|
-
// made again.
|
|
876
|
-
try {
|
|
877
|
-
this.draw(o, node);
|
|
878
|
-
}
|
|
879
|
-
finally {
|
|
880
|
-
done();
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Empty the node of what it drew, and return what gives back its fills
|
|
885
|
-
* and lines: a drawing's fills, a blend's, and every node's lines; a
|
|
886
|
-
* Graphics frees only a context it made.
|
|
887
|
-
*/
|
|
888
|
-
clear(node) {
|
|
889
|
-
// A Bitmap's texture is its store's: only the sprite goes.
|
|
890
|
-
node.bitmap?.destroy();
|
|
891
|
-
node.bitmap = null;
|
|
892
|
-
const old = node.ownFills && !this.fresh ? node.fills : [];
|
|
893
|
-
const oldLines = this.fresh ? [] : node.strokes.map((g) => g?.shared);
|
|
894
|
-
const blend = node.blended;
|
|
895
|
-
// A text's characters are in a container of their own; their shared glyph fills stay, not being theirs.
|
|
896
|
-
for (const child of node.art.removeChildren()) {
|
|
897
|
-
if (!child.destroyed) {
|
|
898
|
-
child.destroy({ children: true });
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
node.layers = [];
|
|
902
|
-
node.fills = [];
|
|
903
|
-
node.ownFills = false;
|
|
904
|
-
node.blended = null;
|
|
905
|
-
node.strokes = [];
|
|
906
|
-
node.lines = [];
|
|
907
|
-
return () => {
|
|
908
|
-
for (const context of old) {
|
|
909
|
-
destroyContext(context);
|
|
910
|
-
}
|
|
911
|
-
for (const context of oldLines) {
|
|
912
|
-
if (context) {
|
|
913
|
-
this.lines.give(context);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
if (blend) {
|
|
917
|
-
this.blends.give(blend);
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
/** What `o` itself draws, into its node emptied of what it drew before. */
|
|
922
|
-
draw(o, node) {
|
|
923
|
-
const current = this.current(o);
|
|
924
|
-
if (o instanceof BitmapObject) {
|
|
925
|
-
this.drawBitmap(o, node);
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
if (o instanceof TextObject) {
|
|
929
|
-
drawText(o, node.art);
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
if (o instanceof StaticTextObject) {
|
|
933
|
-
drawStaticText(o, node.art);
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
const shape = o instanceof ShapeObject ? o.drawn() : null;
|
|
937
|
-
node.layers = o.drawing?.layers ?? shape?.layers ?? [];
|
|
938
|
-
const build = (layer) => {
|
|
939
|
-
const context = fillContext(layer, this.painter);
|
|
940
|
-
if (this.fresh) {
|
|
941
|
-
this.built.add(context);
|
|
942
|
-
}
|
|
943
|
-
return context;
|
|
944
|
-
};
|
|
945
|
-
let fills;
|
|
946
|
-
node.ownFills = false;
|
|
947
|
-
if (current && current.layers === node.layers && current.fills.length === node.layers.length) {
|
|
948
|
-
fills = current.fills;
|
|
949
|
-
}
|
|
950
|
-
else if (shape && !o.drawing && !(o instanceof ShapeObject && o.morph)) {
|
|
951
|
-
// A fresh view borrows the stage's, which its object, off the list, may no longer hold.
|
|
952
|
-
fills = this.fills.get(shape) ?? this.source?.fills.get(shape) ?? node.layers.map(build);
|
|
953
|
-
this.fills.set(shape, fills);
|
|
954
|
-
}
|
|
955
|
-
else if (shape && !o.drawing && !this.fresh) {
|
|
956
|
-
// A morph's blend, one of as many as its ratios: not with the shapes', which the view keeps.
|
|
957
|
-
fills = this.blends.take(shape, () => node.layers.map(build));
|
|
958
|
-
node.blended = shape;
|
|
959
|
-
}
|
|
960
|
-
else if (shape && !o.drawing && this.source?.blends.peek(shape)) {
|
|
961
|
-
fills = this.source.blends.peek(shape);
|
|
962
|
-
}
|
|
963
|
-
else {
|
|
964
|
-
fills = node.layers.map(build);
|
|
965
|
-
node.ownFills = true;
|
|
966
|
-
}
|
|
967
|
-
node.fills = fills;
|
|
968
|
-
// A blend's layers never change, so its lines are shared as a shape's: instances in step stroke
|
|
969
|
-
// once.
|
|
970
|
-
node.sharedLines = !o.drawing && shape !== null;
|
|
971
|
-
const lines = current &&
|
|
972
|
-
sameLinear(current.world, node.world) &&
|
|
973
|
-
this.source?.leastWidth === this.leastWidth
|
|
974
|
-
? current.strokes
|
|
975
|
-
: null;
|
|
976
|
-
node.layers.forEach((layer, i) => {
|
|
977
|
-
node.art.addChild(new SharedGraphics(fills[i]));
|
|
978
|
-
const borrowed = lines?.[i];
|
|
979
|
-
const strokes = layer.strokes.length
|
|
980
|
-
? borrowed
|
|
981
|
-
? new SharedGraphics(borrowed.shared)
|
|
982
|
-
: new SharedGraphics()
|
|
983
|
-
: null;
|
|
984
|
-
if (strokes) {
|
|
985
|
-
if (borrowed) {
|
|
986
|
-
strokes.setFromMatrix(borrowed.localTransform);
|
|
987
|
-
}
|
|
988
|
-
node.art.addChild(strokes);
|
|
989
|
-
node.lines.push(strokes);
|
|
990
|
-
}
|
|
991
|
-
node.strokes.push(borrowed ? null : strokes);
|
|
992
|
-
});
|
|
993
|
-
this.restroke(node);
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* A Bitmap as a sprite over its store's texture, which every Bitmap of
|
|
997
|
-
* the store shares; nothing for no store, one disposed, one of 0 by 0
|
|
998
|
-
* that Flash could not read, or one too large for a texture.
|
|
999
|
-
*/
|
|
1000
|
-
drawBitmap(o, node) {
|
|
1001
|
-
const texture = o.store && gpuBitmaps(this.renderer).texture(o.store, o.smoothing);
|
|
1002
|
-
if (!texture) {
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
node.bitmap = new Sprite(texture);
|
|
1006
|
-
node.art.addChild(node.bitmap);
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* An object off the list, and all below it: their lines given back, so
|
|
1010
|
-
* that the cache can let them go, and drawn again if they come back.
|
|
1011
|
-
*/
|
|
1012
|
-
release(o) {
|
|
1013
|
-
const node = this.nodes.get(o);
|
|
1014
|
-
if (!node || node.released) {
|
|
1015
|
-
return;
|
|
1016
|
-
}
|
|
1017
|
-
node.released = true;
|
|
1018
|
-
const group = node.container.isRenderGroup ? node.container.renderGroup : null;
|
|
1019
|
-
if (group) {
|
|
1020
|
-
const uid = group.instructionSet.uid;
|
|
1021
|
-
this.parkedGroups.delete(uid);
|
|
1022
|
-
this.parkedGroups.set(uid, { since: performance.now(), group: new WeakRef(group) });
|
|
1023
|
-
if (!node.reused) {
|
|
1024
|
-
this.parkedFirstGroups.add(uid);
|
|
1025
|
-
if (this.parkedFirstGroups.size > PARKED_FIRST_GROUPS_MOST) {
|
|
1026
|
-
const oldest = this.parkedFirstGroups.values().next().value;
|
|
1027
|
-
if (oldest !== undefined) {
|
|
1028
|
-
this.emptyGroup(oldest, this.parkedGroups.get(oldest)?.group.deref());
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
const chain = node.filters[0];
|
|
1034
|
-
if (chain instanceof FilterChain) {
|
|
1035
|
-
chain.forget();
|
|
1036
|
-
}
|
|
1037
|
-
// What it drew is kept a while for it to come back to, as a pool's
|
|
1038
|
-
// objects do, then goes: Pixi keeps a Graphics it has drawn, with its
|
|
1039
|
-
// geometry, for a minute after it was last drawn, which a timeline that
|
|
1040
|
-
// makes its children anew on every frame turned into gigabytes.
|
|
1041
|
-
if (node.art.children.length > 0) {
|
|
1042
|
-
this.parked.delete(node);
|
|
1043
|
-
this.parked.set(node, performance.now());
|
|
1044
|
-
if (this.parked.size > PARKED_MOST) {
|
|
1045
|
-
this.empty(this.parked.keys().next().value);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
this.empty(node);
|
|
1050
|
-
}
|
|
1051
|
-
// Those it last drew, which may since have left it too, and any it has
|
|
1052
|
-
// now; not one it last drew that has moved to another parent on the
|
|
1053
|
-
// list, which draws it.
|
|
1054
|
-
const kids = new Set(node.kids);
|
|
1055
|
-
if (o instanceof Container) {
|
|
1056
|
-
for (const child of o.children) {
|
|
1057
|
-
kids.add(child);
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
for (const kid of kids) {
|
|
1061
|
-
if (kid.parent === o || this.left(kid, o)) {
|
|
1062
|
-
this.release(kid);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
/**
|
|
1067
|
-
* Whether `kid`, which `from` last drew, is off the list: taken out, or
|
|
1068
|
-
* moved to a parent that is off it too, which no sync will release.
|
|
1069
|
-
*/
|
|
1070
|
-
left(kid, from) {
|
|
1071
|
-
return kid.parent === null || (kid.parent !== from && !this.root?.encloses(kid));
|
|
1072
|
-
}
|
|
1073
|
-
/** A released node's art emptied, its fills and lines given back: drawn again if it comes back. */
|
|
1074
|
-
empty(node) {
|
|
1075
|
-
this.parked.delete(node);
|
|
1076
|
-
node.emptied = true;
|
|
1077
|
-
this.clear(node)();
|
|
1078
|
-
}
|
|
1079
|
-
/** Return a parked group's batches without changing its nested group hierarchy. */
|
|
1080
|
-
emptyGroup(uid, group) {
|
|
1081
|
-
this.parkedGroups.delete(uid);
|
|
1082
|
-
this.parkedFirstGroups.delete(uid);
|
|
1083
|
-
retireBatchers(this.renderer, uid);
|
|
1084
|
-
if (group) {
|
|
1085
|
-
group.instructionSet.destroy();
|
|
1086
|
-
group.structureDidChange = true;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* Draw the lines again for the object's transform on the stage: in the
|
|
1091
|
-
* stage's axes, under the inverse of that transform's linear part.
|
|
1092
|
-
*/
|
|
1093
|
-
restroke(node) {
|
|
1094
|
-
const m = node.world;
|
|
1095
|
-
const det = m[0] * m[3] - m[1] * m[2];
|
|
1096
|
-
const least = this.leastWidth;
|
|
1097
|
-
node.strokedAt = least;
|
|
1098
|
-
// One key and one inverse for all its layers.
|
|
1099
|
-
const key = linesKey(m, least);
|
|
1100
|
-
const inverse = det === 0 ? null : new Matrix(m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, 0, 0);
|
|
1101
|
-
node.layers.forEach((layer, i) => {
|
|
1102
|
-
const strokes = node.strokes[i];
|
|
1103
|
-
if (!strokes) {
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
// The new context goes in before the old one goes back, as it may be the same.
|
|
1107
|
-
const previous = strokes.shared;
|
|
1108
|
-
// A fresh view borrows the stage's lines where it draws them alike.
|
|
1109
|
-
const kept = this.fresh && node.sharedLines && this.source?.leastWidth === least
|
|
1110
|
-
? this.source.lines.peek(layer, key)
|
|
1111
|
-
: null;
|
|
1112
|
-
if (kept) {
|
|
1113
|
-
strokes.swap(kept);
|
|
1114
|
-
this.borrowed.add(kept);
|
|
1115
|
-
}
|
|
1116
|
-
else if (node.sharedLines && !this.fresh) {
|
|
1117
|
-
strokes.swap(this.lines.take(layer, m, least, key));
|
|
1118
|
-
}
|
|
1119
|
-
else {
|
|
1120
|
-
strokes.swap(linesContext(layer, m, least));
|
|
1121
|
-
this.counts.strokeContexts++;
|
|
1122
|
-
if (this.fresh) {
|
|
1123
|
-
this.built.add(strokes.shared);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
if (!this.borrowed.has(previous) && !this.built.has(previous)) {
|
|
1127
|
-
this.lines.give(previous);
|
|
1128
|
-
}
|
|
1129
|
-
if (inverse) {
|
|
1130
|
-
strokes.setFromMatrix(inverse);
|
|
1131
|
-
}
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Bring `o`'s mirror up to date where it changed, and return it. `parent`
|
|
1136
|
-
* is the linear part of its parent's transform on the stage, and `moved`
|
|
1137
|
-
* whether that changed, which changes the lines of every shape below.
|
|
1138
|
-
* `own` stands in for `o`'s own matrix, as a draw's matrix does.
|
|
1139
|
-
*/
|
|
1140
|
-
sync(o, parent, moved, own = o.placed, inMask = false, tint = null, isolated = false) {
|
|
1141
|
-
const node = this.node(o);
|
|
1142
|
-
const { container } = node;
|
|
1143
|
-
let dirty = this.fresh ? TRANSFORM | CHILDREN | CONTENT : o.dirty;
|
|
1144
|
-
if (node.released) {
|
|
1145
|
-
// Back from off the list: drawn again if its art was emptied, its transform as if unknown, so
|
|
1146
|
-
// that it and all below draw their lines again; a parked one has kept its own. Its children,
|
|
1147
|
-
// if it has or had any, are arranged again, as those that were emptied with it are drawn
|
|
1148
|
-
// again.
|
|
1149
|
-
node.released = false;
|
|
1150
|
-
node.reused = true;
|
|
1151
|
-
this.parked.delete(node);
|
|
1152
|
-
const group = node.container.renderGroup;
|
|
1153
|
-
if (group) {
|
|
1154
|
-
this.parkedGroups.delete(group.instructionSet.uid);
|
|
1155
|
-
this.parkedFirstGroups.delete(group.instructionSet.uid);
|
|
1156
|
-
}
|
|
1157
|
-
const kids = o instanceof Container && (o.children.length > 0 || node.kids.length > 0);
|
|
1158
|
-
dirty |= TRANSFORM | (kids ? CHILDREN : 0);
|
|
1159
|
-
if (node.emptied) {
|
|
1160
|
-
node.emptied = false;
|
|
1161
|
-
node.world = [Number.NaN, Number.NaN, Number.NaN, Number.NaN];
|
|
1162
|
-
dirty |= CONTENT;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
// A mask is drawn, whatever its visibility, alpha and colour, by its fills alone.
|
|
1166
|
-
const masking = inMask || o.maskOf !== null || o.clipDepth > 0;
|
|
1167
|
-
const remask = masking !== node.masking;
|
|
1168
|
-
node.masking = masking;
|
|
1169
|
-
if (remask) {
|
|
1170
|
-
dirty |= TRANSFORM;
|
|
1171
|
-
}
|
|
1172
|
-
if (dirty & TRANSFORM) {
|
|
1173
|
-
const m = own;
|
|
1174
|
-
container.setFromMatrix(new Matrix(m.a, m.b, m.c, m.d, m.tx, m.ty));
|
|
1175
|
-
container.visible = o.visible || masking;
|
|
1176
|
-
// A blend mode composites the object as a layer (pixi-blend.ts); a mask is its fills alone.
|
|
1177
|
-
// Its filters, then its blend: adl filters the object, then blends what they make.
|
|
1178
|
-
const blend = masking ? "normal" : o.blendMode;
|
|
1179
|
-
const records = masking ? NO_RECORDS : o.filters;
|
|
1180
|
-
if (blend !== node.blend || records !== node.filterRecords) {
|
|
1181
|
-
node.blend = blend;
|
|
1182
|
-
node.filterRecords = records;
|
|
1183
|
-
for (const f of node.filters) {
|
|
1184
|
-
f.destroy();
|
|
1185
|
-
}
|
|
1186
|
-
// Flash's filters are WebGL's alone; Pixi skips a chain with one it
|
|
1187
|
-
// cannot run, so under WebGPU they are left out and the blend kept.
|
|
1188
|
-
const chain = this.renderer.type === RendererType.WEBGPU
|
|
1189
|
-
? []
|
|
1190
|
-
: displayFilters(records, (map) => {
|
|
1191
|
-
// A displacement map's map: its store's texture, brought up to date as it is read.
|
|
1192
|
-
const store = map.$store;
|
|
1193
|
-
return store && !store.disposed
|
|
1194
|
-
? gpuBitmaps(this.renderer).texture(store, false)
|
|
1195
|
-
: null;
|
|
1196
|
-
});
|
|
1197
|
-
// A view drawn once keeps no output.
|
|
1198
|
-
node.filters = chain.length > 0 ? [new FilterChain(chain, container, !this.fresh)] : [];
|
|
1199
|
-
if (this.fresh) {
|
|
1200
|
-
this.builtFilters.push(...node.filters);
|
|
1201
|
-
}
|
|
1202
|
-
const blending = blendFilters(blend);
|
|
1203
|
-
container.filters =
|
|
1204
|
-
node.filters.length > 0 || blending ? [...node.filters, ...(blending ?? [])] : null;
|
|
1205
|
-
}
|
|
1206
|
-
// Most objects have neither: they pay one test.
|
|
1207
|
-
if (o.mask || o.scroll || node.clipped) {
|
|
1208
|
-
if (this.clip(o, node) && o instanceof Container) {
|
|
1209
|
-
dirty |= CHILDREN;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
// The colour transform from the stage down. One that only multiplies is
|
|
1214
|
-
// Pixi's tint and alpha, which Pixi composes down the tree itself; any
|
|
1215
|
-
// other is what is drawn's own (pixi-color.ts), and the tint stays white.
|
|
1216
|
-
let recolor = false;
|
|
1217
|
-
if (dirty & TRANSFORM || tint !== node.inherited) {
|
|
1218
|
-
node.inherited = tint;
|
|
1219
|
-
const ct = o.colorTransform;
|
|
1220
|
-
const color = masking ? null : ct ? (tint ? concatColor(tint, ct) : ct) : tint;
|
|
1221
|
-
if (!sameColor(color, node.color)) {
|
|
1222
|
-
node.color = color;
|
|
1223
|
-
recolor = true;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
const flash = node.color && !multipliesOnly(node.color) ? node.color : null;
|
|
1227
|
-
if (dirty & TRANSFORM || recolor) {
|
|
1228
|
-
const ct = flash || masking ? null : o.colorTransform;
|
|
1229
|
-
container.alpha = ct ? Math.max(0, Math.min(1, ct.aMul)) : 1;
|
|
1230
|
-
container.tint = ct
|
|
1231
|
-
? (Math.round(Math.max(0, Math.min(1, ct.rMul)) * 255) << 16) |
|
|
1232
|
-
(Math.round(Math.max(0, Math.min(1, ct.gMul)) * 255) << 8) |
|
|
1233
|
-
Math.round(Math.max(0, Math.min(1, ct.bMul)) * 255)
|
|
1234
|
-
: 0xffffff;
|
|
1235
|
-
}
|
|
1236
|
-
if (moved || dirty & TRANSFORM) {
|
|
1237
|
-
const m = own;
|
|
1238
|
-
const world = [
|
|
1239
|
-
parent[0] * m.a + parent[2] * m.b,
|
|
1240
|
-
parent[1] * m.a + parent[3] * m.b,
|
|
1241
|
-
parent[0] * m.c + parent[2] * m.d,
|
|
1242
|
-
parent[1] * m.c + parent[3] * m.d,
|
|
1243
|
-
];
|
|
1244
|
-
moved = !sameLinear(world, node.world);
|
|
1245
|
-
node.world = world;
|
|
1246
|
-
}
|
|
1247
|
-
if (dirty & CONTENT) {
|
|
1248
|
-
this.redraw(o, node);
|
|
1249
|
-
}
|
|
1250
|
-
else if ((moved || node.strokedAt !== this.leastWidth) && node.strokes.some((g) => g)) {
|
|
1251
|
-
this.restroke(node);
|
|
1252
|
-
}
|
|
1253
|
-
if (dirty & CONTENT || remask) {
|
|
1254
|
-
for (const lines of node.lines) {
|
|
1255
|
-
lines.visible = !masking;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
if (dirty & CONTENT || recolor) {
|
|
1259
|
-
for (const leaf of node.art.children) {
|
|
1260
|
-
setFlashColor(leaf, flash);
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
if (dirty & PIXELS && !(dirty & CONTENT)) {
|
|
1264
|
-
// Pixels set since: the textures brought up to date, uploaded where the CPU changed them.
|
|
1265
|
-
const bitmaps = gpuBitmaps(this.renderer);
|
|
1266
|
-
if (node.bitmap && o instanceof BitmapObject && o.store) {
|
|
1267
|
-
bitmaps.texture(o.store, o.smoothing);
|
|
1268
|
-
}
|
|
1269
|
-
for (const layer of node.layers) {
|
|
1270
|
-
for (const { fill } of layer.fills) {
|
|
1271
|
-
if (fill.type === "image" && fill.image instanceof BitmapStore) {
|
|
1272
|
-
bitmaps.fillTexture(fill.image, fill.repeat, fill.smooth);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
// What its filters' kept output was drawn from changed, but for a move.
|
|
1278
|
-
// (A mask from outside it does not count: adl keeps the output, clipped as it was.)
|
|
1279
|
-
const chain = node.filters[0];
|
|
1280
|
-
if (chain instanceof FilterChain &&
|
|
1281
|
-
(dirty & ~TRANSFORM ||
|
|
1282
|
-
moved ||
|
|
1283
|
-
remask ||
|
|
1284
|
-
recolor ||
|
|
1285
|
-
this.rescaled ||
|
|
1286
|
-
// A scroll moves what is drawn within the input; any move of a scrolled object counts.
|
|
1287
|
-
(dirty & TRANSFORM && (o.scroll || node.scroll)) ||
|
|
1288
|
-
(o instanceof Container && o.descendantsDirty))) {
|
|
1289
|
-
chain.changed();
|
|
1290
|
-
}
|
|
1291
|
-
const childIsolation = isolated || node.blend !== "normal" || node.filters.length > 0 || node.clipped;
|
|
1292
|
-
const isolationChanged = childIsolation !== node.childIsolation;
|
|
1293
|
-
node.childIsolation = childIsolation;
|
|
1294
|
-
if (o instanceof Container) {
|
|
1295
|
-
if (dirty & CHILDREN) {
|
|
1296
|
-
this.arrange(o, node, moved, masking, childIsolation);
|
|
1297
|
-
}
|
|
1298
|
-
else if (moved ||
|
|
1299
|
-
remask ||
|
|
1300
|
-
recolor ||
|
|
1301
|
-
this.rescaled ||
|
|
1302
|
-
isolationChanged ||
|
|
1303
|
-
o.descendantsDirty) {
|
|
1304
|
-
for (const child of o.children) {
|
|
1305
|
-
if (moved ||
|
|
1306
|
-
remask ||
|
|
1307
|
-
this.rescaled ||
|
|
1308
|
-
recolor ||
|
|
1309
|
-
isolationChanged ||
|
|
1310
|
-
child.dirty !== CLEAN ||
|
|
1311
|
-
(child instanceof Container && child.descendantsDirty)) {
|
|
1312
|
-
this.sync(child, node.world, moved, child.placed, masking, node.color, childIsolation);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
if (!this.fresh) {
|
|
1317
|
-
o.descendantsDirty = false;
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
if (!this.fresh) {
|
|
1321
|
-
this.group(o, node);
|
|
1322
|
-
// Fixed-function multiply needs an opaque backdrop; screen's equation
|
|
1323
|
-
// also holds over transparent pixels. Add does not preserve layer alpha.
|
|
1324
|
-
let direct = null;
|
|
1325
|
-
if (node.singleDraw === 1) {
|
|
1326
|
-
if (node.blend === "screen") {
|
|
1327
|
-
direct = "screen";
|
|
1328
|
-
}
|
|
1329
|
-
else if (node.blend === "multiply" && this.renderer.background.alpha === 1) {
|
|
1330
|
-
direct = "multiply";
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
if (isolated) {
|
|
1334
|
-
direct = null;
|
|
1335
|
-
}
|
|
1336
|
-
if (direct !== node.directBlend || (direct && container.filters)) {
|
|
1337
|
-
container.blendMode = direct ?? "inherit";
|
|
1338
|
-
const blending = direct ? null : blendFilters(node.blend);
|
|
1339
|
-
container.filters =
|
|
1340
|
-
node.filters.length > 0 || blending ? [...node.filters, ...(blending ?? [])] : null;
|
|
1341
|
-
node.directBlend = direct;
|
|
1342
|
-
}
|
|
1343
|
-
o.dirty = CLEAN;
|
|
1344
|
-
}
|
|
1345
|
-
if (node.blend !== "normal" && node.blend !== "layer" && !node.directBlend) {
|
|
1346
|
-
this.checkBackBuffer();
|
|
1347
|
-
}
|
|
1348
|
-
return container;
|
|
1349
|
-
}
|
|
1350
|
-
/** Keep an animated branch from rebuilding all the stage's batches when its children change. */
|
|
1351
|
-
group(o, node) {
|
|
1352
|
-
let draws = node.art.children.length;
|
|
1353
|
-
let singleDraw = 0;
|
|
1354
|
-
if (node.art.children.length === 1) {
|
|
1355
|
-
const art = node.art.children[0];
|
|
1356
|
-
singleDraw =
|
|
1357
|
-
art instanceof SharedGraphics &&
|
|
1358
|
-
art.context.instructions.length === 1 &&
|
|
1359
|
-
art.context.instructions[0].action === "fill"
|
|
1360
|
-
? 1
|
|
1361
|
-
: 2;
|
|
1362
|
-
}
|
|
1363
|
-
else if (node.art.children.length > 1) {
|
|
1364
|
-
singleDraw = 2;
|
|
1365
|
-
}
|
|
1366
|
-
const links = node.maskLinks;
|
|
1367
|
-
links.length = 0;
|
|
1368
|
-
maskLink(o, o.mask, links);
|
|
1369
|
-
maskLink(o, o.maskOf, links);
|
|
1370
|
-
if (o instanceof Container) {
|
|
1371
|
-
for (const child of o.children) {
|
|
1372
|
-
const part = this.nodes.get(child);
|
|
1373
|
-
if (part) {
|
|
1374
|
-
for (const partner of part.maskLinks) {
|
|
1375
|
-
maskLink(o, partner.deref() ?? null, links);
|
|
1376
|
-
}
|
|
1377
|
-
draws += part.container.isRenderGroup ? 0 : part.draws;
|
|
1378
|
-
if (part.singleDraw !== 0) {
|
|
1379
|
-
singleDraw =
|
|
1380
|
-
singleDraw === 0 &&
|
|
1381
|
-
part.singleDraw === 1 &&
|
|
1382
|
-
part.blend === "normal" &&
|
|
1383
|
-
!part.container.isRenderGroup
|
|
1384
|
-
? 1
|
|
1385
|
-
: 2;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
node.draws = draws;
|
|
1391
|
-
node.singleDraw =
|
|
1392
|
-
links.length === 0 && !node.masking && !node.clipped && node.filters.length === 0
|
|
1393
|
-
? singleDraw
|
|
1394
|
-
: 2;
|
|
1395
|
-
// A stencil mask's geometry is collected into the masked object's instructions. Until both
|
|
1396
|
-
// belong to this branch, it must share its parent's group. A timeline mask itself also stays
|
|
1397
|
-
// with its siblings, though their common parent may form a group.
|
|
1398
|
-
if ((links.length > 0 || node.masking) && node.container.isRenderGroup) {
|
|
1399
|
-
this.disableRenderGroup(node.container);
|
|
1400
|
-
}
|
|
1401
|
-
else if (links.length === 0 &&
|
|
1402
|
-
!node.masking &&
|
|
1403
|
-
o.parent &&
|
|
1404
|
-
draws >= 64 &&
|
|
1405
|
-
!node.container.isRenderGroup) {
|
|
1406
|
-
// Keep the group when its animation gets smaller, avoiding repeated batcher destruction.
|
|
1407
|
-
node.container.enableRenderGroup();
|
|
1408
|
-
const set = node.container.renderGroup?.instructionSet;
|
|
1409
|
-
if (set) {
|
|
1410
|
-
viewGroups.add(set);
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
/** Retire Pixi's per-group batches before it returns the group to its pool. */
|
|
1415
|
-
disableRenderGroup(container) {
|
|
1416
|
-
const set = container.renderGroup?.instructionSet;
|
|
1417
|
-
if (set) {
|
|
1418
|
-
// Pixi pools the group, set included, for any root it renders next.
|
|
1419
|
-
viewGroups.delete(set);
|
|
1420
|
-
retireBatchers(this.renderer, set.uid);
|
|
1421
|
-
set.destroy();
|
|
1422
|
-
}
|
|
1423
|
-
container.disableRenderGroup();
|
|
1424
|
-
}
|
|
1425
|
-
/**
|
|
1426
|
-
* The children's containers in render order, under the object's art,
|
|
1427
|
-
* those a timeline's mask clips in a container the mask masks, nested as
|
|
1428
|
-
* the masks are; the masks themselves among them, where their place in
|
|
1429
|
-
* the tree puts them, though not drawn.
|
|
1430
|
-
*/
|
|
1431
|
-
arrange(o, node, moved, masking, isolated) {
|
|
1432
|
-
const content = node.scroll?.content ?? node.container;
|
|
1433
|
-
for (const group of node.groups) {
|
|
1434
|
-
group.mask = null;
|
|
1435
|
-
group.destroy();
|
|
1436
|
-
}
|
|
1437
|
-
node.groups = [];
|
|
1438
|
-
content.removeChildren();
|
|
1439
|
-
content.addChild(node.art);
|
|
1440
|
-
// Those that left the list give their lines back, all the way down; one
|
|
1441
|
-
// moved to another parent on the list is drawn there, perhaps already this frame.
|
|
1442
|
-
if (!this.fresh) {
|
|
1443
|
-
for (const kid of node.kids) {
|
|
1444
|
-
if (this.left(kid, o)) {
|
|
1445
|
-
this.release(kid);
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
node.kids = [...o.children];
|
|
1449
|
-
}
|
|
1450
|
-
const clips = new Clips();
|
|
1451
|
-
const open = [];
|
|
1452
|
-
for (const child of o.children) {
|
|
1453
|
-
open.length = clips.enter(child);
|
|
1454
|
-
const into = open.length > 0 ? open[open.length - 1] : content;
|
|
1455
|
-
const container = this.sync(child, node.world, moved, child.placed, masking, node.color, isolated);
|
|
1456
|
-
into.addChild(container);
|
|
1457
|
-
if (child.clipDepth > 0) {
|
|
1458
|
-
const group = new PixiContainer();
|
|
1459
|
-
group.mask = container;
|
|
1460
|
-
into.addChild(group);
|
|
1461
|
-
open.push(group);
|
|
1462
|
-
node.groups.push(group);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
/**
|
|
1467
|
-
* The object's mask, and its scroll: true where the scroll's clip came
|
|
1468
|
-
* or went, which moves the children. A mask's own mask, or one that is
|
|
1469
|
-
* the object or above it, Pixi could not draw: it is left out.
|
|
1470
|
-
*/
|
|
1471
|
-
clip(o, node) {
|
|
1472
|
-
const mask = o.mask && !o.maskOf && !o.mask.encloses(o) ? o.mask : null;
|
|
1473
|
-
node.container.mask = mask ? this.node(mask).container : null;
|
|
1474
|
-
if (mask) {
|
|
1475
|
-
this.maskees.add(o.ref);
|
|
1476
|
-
}
|
|
1477
|
-
const moved = this.scrollClip(o, node);
|
|
1478
|
-
node.clipped = mask !== null || node.scroll !== null;
|
|
1479
|
-
return moved;
|
|
1480
|
-
}
|
|
1481
|
-
/**
|
|
1482
|
-
* The object's scroll, as a clip of its art and children to the
|
|
1483
|
-
* rectangle, which its matrix's shift has moved to its place; true where
|
|
1484
|
-
* the clip came or went, which moves the children.
|
|
1485
|
-
*/
|
|
1486
|
-
scrollClip(o, node) {
|
|
1487
|
-
const r = o.scroll;
|
|
1488
|
-
const scroll = node.scroll;
|
|
1489
|
-
if (!r) {
|
|
1490
|
-
if (!scroll) {
|
|
1491
|
-
return false;
|
|
1492
|
-
}
|
|
1493
|
-
scroll.content.mask = null;
|
|
1494
|
-
node.container.addChild(...scroll.content.removeChildren());
|
|
1495
|
-
scroll.clip.destroy();
|
|
1496
|
-
scroll.content.destroy();
|
|
1497
|
-
node.scroll = null;
|
|
1498
|
-
return true;
|
|
1499
|
-
}
|
|
1500
|
-
const clip = scroll?.clip ?? new Graphics();
|
|
1501
|
-
clip
|
|
1502
|
-
.clear()
|
|
1503
|
-
.rect(r.xMin, r.yMin, r.xMax - r.xMin, r.yMax - r.yMin)
|
|
1504
|
-
.fill(0xffffff);
|
|
1505
|
-
if (scroll) {
|
|
1506
|
-
return false;
|
|
1507
|
-
}
|
|
1508
|
-
const content = new PixiContainer();
|
|
1509
|
-
const children = node.container.removeChildren();
|
|
1510
|
-
if (children.length > 0) {
|
|
1511
|
-
content.addChild(...children);
|
|
1512
|
-
}
|
|
1513
|
-
content.mask = clip;
|
|
1514
|
-
node.container.addChild(clip, content);
|
|
1515
|
-
node.scroll = { clip, content };
|
|
1516
|
-
return true;
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* The masks `mask` set that are not under `root`, off the display list
|
|
1520
|
-
* or elsewhere on it, each in `holder` at its place in the stage's space
|
|
1521
|
-
* (the space a draw draws into, for a draw's), as Flash places such a
|
|
1522
|
-
* mask; the objects they mask synced already.
|
|
1523
|
-
*/
|
|
1524
|
-
placeMasks(root, holder) {
|
|
1525
|
-
holder.removeChildren();
|
|
1526
|
-
for (const ref of this.maskees) {
|
|
1527
|
-
const o = ref.deref();
|
|
1528
|
-
const mask = o?.mask;
|
|
1529
|
-
if (!o || !mask) {
|
|
1530
|
-
this.maskees.delete(ref);
|
|
1531
|
-
continue;
|
|
1532
|
-
}
|
|
1533
|
-
if (root.encloses(o) && !root.encloses(mask)) {
|
|
1534
|
-
holder.addChild(this.sync(mask, UNIT, false, toStage(mask, null), true));
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
/** Bring the stage up to date with `root`'s display list, without drawing. */
|
|
1539
|
-
/** The thinnest line, in stage pixels: a pixel of the screen. */
|
|
1540
|
-
get leastWidth() {
|
|
1541
|
-
return this.fresh ? 1 : 1 / (this.screenScale ?? this.renderer.resolution ?? 1);
|
|
1542
|
-
}
|
|
1543
|
-
prepare(root) {
|
|
1544
|
-
this.root = root;
|
|
1545
|
-
this.lines.tick();
|
|
1546
|
-
this.blends.tick();
|
|
1547
|
-
const now = performance.now();
|
|
1548
|
-
for (const [node, since] of this.parked) {
|
|
1549
|
-
if (now - since < IDLE_MS) {
|
|
1550
|
-
break;
|
|
1551
|
-
}
|
|
1552
|
-
this.empty(node);
|
|
1553
|
-
}
|
|
1554
|
-
for (const [uid, entry] of this.parkedGroups) {
|
|
1555
|
-
if (now - entry.since < IDLE_MS) {
|
|
1556
|
-
break;
|
|
1557
|
-
}
|
|
1558
|
-
this.emptyGroup(uid, entry.group.deref());
|
|
1559
|
-
}
|
|
1560
|
-
this.rescaled = this.leastWidth !== this.strokedAt;
|
|
1561
|
-
this.strokedAt = this.leastWidth;
|
|
1562
|
-
const node = this.sync(root, UNIT, false);
|
|
1563
|
-
this.rescaled = false;
|
|
1564
|
-
if (node.parent !== this.stage) {
|
|
1565
|
-
this.stage.removeChildren();
|
|
1566
|
-
this.stage.addChild(node, this.offList);
|
|
1567
|
-
}
|
|
1568
|
-
this.placeMasks(root, this.offList);
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* `o` drawn alone, as BitmapData.draw takes a display object: through `m`
|
|
1572
|
-
* into a w x h texture, rendered at `samples` a side and averaged on the
|
|
1573
|
-
* GPU, as Flash covers edges (4 at its high quality). A fresh view does
|
|
1574
|
-
* it, borrowing the stage's current geometry, so the stage's is untouched.
|
|
1575
|
-
*/
|
|
1576
|
-
sampled(o, m, width, height, samples) {
|
|
1577
|
-
const n = samples;
|
|
1578
|
-
const view = new PixiView(this.renderer, true, this);
|
|
1579
|
-
// Built at the draw's own scale, lines included, as the stage's are,
|
|
1580
|
-
// then rendered n times larger: the curves are no finer than on the
|
|
1581
|
-
// stage, and widths and hairlines scale with the samples.
|
|
1582
|
-
const node = view.sync(o, [1, 0, 0, 1], true, o.scroll ? shifted(m, o.scroll) : m);
|
|
1583
|
-
const scaled = new PixiContainer();
|
|
1584
|
-
const masks = new PixiContainer();
|
|
1585
|
-
scaled.addChild(node, masks);
|
|
1586
|
-
view.placeMasks(o, masks);
|
|
1587
|
-
scaled.scale.set(n);
|
|
1588
|
-
let target = RenderTexture.create({ width: width * n, height: height * n });
|
|
1589
|
-
this.renderer.render({ container: scaled, target, clear: true });
|
|
1590
|
-
view.dispose(scaled);
|
|
1591
|
-
// Halved until a sample a pixel: a linear sample at the corner four texels share is their mean.
|
|
1592
|
-
for (let k = n; k > 1; k /= 2) {
|
|
1593
|
-
const half = RenderTexture.create({ width: (width * k) / 2, height: (height * k) / 2 });
|
|
1594
|
-
target.source.scaleMode = "linear";
|
|
1595
|
-
const sprite = new Sprite(target);
|
|
1596
|
-
sprite.scale.set(0.5);
|
|
1597
|
-
this.renderer.render({ container: sprite, target: half, clear: true });
|
|
1598
|
-
sprite.destroy();
|
|
1599
|
-
target.destroy(true);
|
|
1600
|
-
target = half;
|
|
1601
|
-
}
|
|
1602
|
-
return target;
|
|
1603
|
-
}
|
|
1604
|
-
/** `o` drawn alone, as `sampled`, read back as premultiplied ARGB. */
|
|
1605
|
-
snapshot(o, m, width, height, samples = 4) {
|
|
1606
|
-
const target = this.sampled(o, m, width, height, samples);
|
|
1607
|
-
const pixels = argbOf(this.renderer.extract.pixels(target).pixels);
|
|
1608
|
-
target.destroy(true);
|
|
1609
|
-
return pixels;
|
|
1610
|
-
}
|
|
1611
|
-
/**
|
|
1612
|
-
* `o` drawn into `store` on the GPU, as `sampled`, source over at (x, y)
|
|
1613
|
-
* with nothing read back: the store is then newer on the GPU. False,
|
|
1614
|
-
* having done nothing, where the store can have no texture.
|
|
1615
|
-
*/
|
|
1616
|
-
drawInto(store, o, m, x, y, width, height, samples = 4) {
|
|
1617
|
-
const bitmaps = gpuBitmaps(this.renderer);
|
|
1618
|
-
const texture = bitmaps.texture(store);
|
|
1619
|
-
if (!texture) {
|
|
1620
|
-
return false;
|
|
1621
|
-
}
|
|
1622
|
-
// Into a texture of its own first: the object may show the store itself.
|
|
1623
|
-
const drawn = this.sampled(o, m, width, height, samples);
|
|
1624
|
-
const sprite = new Sprite(drawn);
|
|
1625
|
-
sprite.position.set(x, y);
|
|
1626
|
-
this.renderer.render({ container: sprite, target: texture, clear: false });
|
|
1627
|
-
sprite.destroy();
|
|
1628
|
-
drawn.destroy(true);
|
|
1629
|
-
bitmaps.drawn(store);
|
|
1630
|
-
return true;
|
|
1631
|
-
}
|
|
1632
|
-
/** Draw `root`'s display list, synced first. */
|
|
1633
|
-
render(root) {
|
|
1634
|
-
this.prepare(root);
|
|
1635
|
-
this.renderer.render(this.stage);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
/**
|
|
1639
|
-
* Whether text laid out at (x, y), `width` by `height`, reaches outside its
|
|
1640
|
-
* field, which then clips it: where it lies, as a margin or an indent puts
|
|
1641
|
-
* it, not its size alone.
|
|
1642
|
-
*/
|
|
1643
|
-
export function overruns(field, x, y, width, height) {
|
|
1644
|
-
return (x < field.left ||
|
|
1645
|
-
y < field.top ||
|
|
1646
|
-
x + width > field.left + field.width ||
|
|
1647
|
-
y + height > field.top + field.height);
|
|
1648
|
-
}
|
|
1649
|
-
/** Glyphs' fills, made once and shared by every character drawn in them; null for an empty glyph. */
|
|
1650
|
-
const glyphFills = new WeakMap();
|
|
1651
|
-
/** A glyph's outline as a shape's fill, white for a tint to colour, in the font's units over 20, as shapeLayers takes twips. */
|
|
1652
|
-
function glyphFill(glyph) {
|
|
1653
|
-
let context = glyphFills.get(glyph);
|
|
1654
|
-
if (context === undefined) {
|
|
1655
|
-
const shape = {
|
|
1656
|
-
id: 0,
|
|
1657
|
-
bounds: { xMin: 0, xMax: 0, yMin: 0, yMax: 0 },
|
|
1658
|
-
edgeBounds: null,
|
|
1659
|
-
fills: [{ type: "solid", color: 0xffffffff }],
|
|
1660
|
-
lines: [],
|
|
1661
|
-
records: glyph.records,
|
|
1662
|
-
truncated: false,
|
|
1663
|
-
};
|
|
1664
|
-
const layers = shapeLayers(shape);
|
|
1665
|
-
context = layers.length > 0 ? fillContext(layers[0], (fill) => paint(fill)) : null;
|
|
1666
|
-
glyphFills.set(glyph, context);
|
|
1667
|
-
}
|
|
1668
|
-
return context;
|
|
1669
|
-
}
|
|
1670
|
-
/**
|
|
1671
|
-
* A TextField drawn from its layout (text-layout.ts): its background and
|
|
1672
|
-
* border, then each line from the first scrolled to, a character of an
|
|
1673
|
-
* embedded font as its glyph's shape, a run of a device font as one Pixi
|
|
1674
|
-
* Text on the line's baseline, each in its own colour, clipped to the
|
|
1675
|
-
* field where the text runs over it.
|
|
1676
|
-
*/
|
|
1677
|
-
function drawText(o, art) {
|
|
1678
|
-
if (o.background || o.border) {
|
|
1679
|
-
const box = new Graphics();
|
|
1680
|
-
if (o.background) {
|
|
1681
|
-
box.rect(o.left, o.top, o.width, o.height).fill({ color: o.backgroundColor & 0xffffff });
|
|
1682
|
-
}
|
|
1683
|
-
// A border covers the pixels at both edges, x and x + width, as adl draws it.
|
|
1684
|
-
if (o.border) {
|
|
1685
|
-
box
|
|
1686
|
-
.rect(o.left + 0.5, o.top + 0.5, o.width, o.height)
|
|
1687
|
-
.stroke({ color: o.borderColor & 0xffffff, width: 1 });
|
|
1688
|
-
}
|
|
1689
|
-
art.addChild(box);
|
|
1690
|
-
}
|
|
1691
|
-
if (!o.model.text) {
|
|
1692
|
-
drawCaret(o, art);
|
|
1693
|
-
return;
|
|
1694
|
-
}
|
|
1695
|
-
const layout = o.layout;
|
|
1696
|
-
const first = Math.min(Math.max(0, o.scrollV - 1), layout.lines.length - 1);
|
|
1697
|
-
// The lines that fit the field from there, one at least: adl draws no line part of the way.
|
|
1698
|
-
const last = first + shownLines(o, first) - 1;
|
|
1699
|
-
const dx = o.left * 20 - o.scrollH * 20;
|
|
1700
|
-
const dy = o.top * 20 - (layout.lines[first].y - GUTTER);
|
|
1701
|
-
const text = new PixiContainer();
|
|
1702
|
-
let bottom = 0;
|
|
1703
|
-
let right = 0;
|
|
1704
|
-
let left = Number.POSITIVE_INFINITY;
|
|
1705
|
-
for (let l = first; l <= last; l++) {
|
|
1706
|
-
const line = layout.lines[l];
|
|
1707
|
-
const baseline = (dy + line.y + line.ascent) / 20;
|
|
1708
|
-
let run = [];
|
|
1709
|
-
const flush = () => {
|
|
1710
|
-
if (run.length > 0) {
|
|
1711
|
-
text.addChild(deviceRun(o, run, dx, baseline));
|
|
1712
|
-
run = [];
|
|
1713
|
-
}
|
|
1714
|
-
};
|
|
1715
|
-
for (const c of line.chars) {
|
|
1716
|
-
if (!c.shown) {
|
|
1717
|
-
continue;
|
|
1718
|
-
}
|
|
1719
|
-
if (c.font) {
|
|
1720
|
-
flush();
|
|
1721
|
-
const fill = c.glyph && glyphFill(c.glyph);
|
|
1722
|
-
if (fill) {
|
|
1723
|
-
const g = new SharedGraphics(fill);
|
|
1724
|
-
const scale = (Math.max(0, c.format.size) * 20) / c.font.em;
|
|
1725
|
-
g.scale.set(scale);
|
|
1726
|
-
g.position.set((dx + c.x + c.kern) / 20, baseline);
|
|
1727
|
-
g.tint = c.format.color & 0xffffff;
|
|
1728
|
-
text.addChild(g);
|
|
1729
|
-
}
|
|
1730
|
-
}
|
|
1731
|
-
else if (run.length > 0 && run[0].format !== c.format) {
|
|
1732
|
-
flush();
|
|
1733
|
-
run.push(c);
|
|
1734
|
-
}
|
|
1735
|
-
else {
|
|
1736
|
-
run.push(c);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
flush();
|
|
1740
|
-
bottom = Math.max(bottom, dy + line.y + line.ascent + line.descent);
|
|
1741
|
-
right = Math.max(right, dx + line.x + line.width);
|
|
1742
|
-
left = Math.min(left, dx + line.x);
|
|
1743
|
-
}
|
|
1744
|
-
art.addChild(text);
|
|
1745
|
-
// Clipped inside the gutter, as adl clips it: 2 pixels in from each edge.
|
|
1746
|
-
const inner = {
|
|
1747
|
-
left: o.left + GUTTER / 20,
|
|
1748
|
-
top: o.top + GUTTER / 20,
|
|
1749
|
-
width: o.width - (2 * GUTTER) / 20,
|
|
1750
|
-
height: o.height - (2 * GUTTER) / 20,
|
|
1751
|
-
};
|
|
1752
|
-
// Where the text lies, scrolled: a scroll left of the gutter clips as one past the right does.
|
|
1753
|
-
const top = (dy + layout.lines[first].y) / 20;
|
|
1754
|
-
if (overruns(inner, left / 20, top, (right - left) / 20, bottom / 20 - top)) {
|
|
1755
|
-
const clip = new Graphics()
|
|
1756
|
-
.rect(inner.left, inner.top, Math.max(0, inner.width), Math.max(0, inner.height))
|
|
1757
|
-
.fill({ color: 0xffffff });
|
|
1758
|
-
art.addChild(clip);
|
|
1759
|
-
text.mask = clip;
|
|
1760
|
-
}
|
|
1761
|
-
drawCaret(o, art);
|
|
1762
|
-
}
|
|
1763
|
-
/** Static text: each glyph's shared fill, at its height and in its colour, under the text's matrix. */
|
|
1764
|
-
function drawStaticText(o, art) {
|
|
1765
|
-
const m = o.definition.definition.matrix;
|
|
1766
|
-
const text = new PixiContainer();
|
|
1767
|
-
text.setFromMatrix(new Matrix(m.a, m.b, m.c, m.d, m.tx / 20, m.ty / 20));
|
|
1768
|
-
for (const placed of o.glyphs.glyphs) {
|
|
1769
|
-
const fill = glyphFill(placed.glyph);
|
|
1770
|
-
if (!fill) {
|
|
1771
|
-
continue;
|
|
1772
|
-
}
|
|
1773
|
-
// The fill is in the font's units over 20: a height in twips over the em puts it in pixels.
|
|
1774
|
-
const g = new SharedGraphics(fill);
|
|
1775
|
-
g.scale.set(placed.height / placed.font.em);
|
|
1776
|
-
g.position.set(placed.x / 20, placed.y / 20);
|
|
1777
|
-
g.tint = placed.color & 0xffffff;
|
|
1778
|
-
g.alpha = (placed.color >>> 24) / 255;
|
|
1779
|
-
text.addChild(g);
|
|
1780
|
-
}
|
|
1781
|
-
art.addChild(text);
|
|
1782
|
-
}
|
|
1783
|
-
/**
|
|
1784
|
-
* A focused input field's caret, a pixel wide and its line's height, in
|
|
1785
|
-
* the colour of the text before it, and its selection shaded under it.
|
|
1786
|
-
*/
|
|
1787
|
-
function drawCaret(o, art) {
|
|
1788
|
-
if (!o.focused || !(o.type === "input" || o.selectable)) {
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
const layout = o.layout;
|
|
1792
|
-
const lines = layout.lines;
|
|
1793
|
-
if (lines.length === 0) {
|
|
1794
|
-
return;
|
|
1795
|
-
}
|
|
1796
|
-
const first = Math.min(Math.max(0, o.scrollV - 1), lines.length - 1);
|
|
1797
|
-
const last = first + shownLines(o, first) - 1;
|
|
1798
|
-
const dx = o.left * 20 - o.scrollH * 20;
|
|
1799
|
-
const dy = o.top * 20 - (lines[first].y - GUTTER);
|
|
1800
|
-
// Inside the gutter, as the text is clipped: a caret scrolled out of view is not drawn.
|
|
1801
|
-
const inner = {
|
|
1802
|
-
left: o.left + GUTTER / 20,
|
|
1803
|
-
right: o.left + o.width - GUTTER / 20,
|
|
1804
|
-
};
|
|
1805
|
-
// Where index i is drawn: its line, and its x, the line's end past its last character.
|
|
1806
|
-
const place = (i) => {
|
|
1807
|
-
const line = lines.find((l) => i < l.end) ?? lines[lines.length - 1];
|
|
1808
|
-
const c = line.chars[i - line.start];
|
|
1809
|
-
const shown = line.chars.filter((ch) => ch.shown);
|
|
1810
|
-
const last = shown[shown.length - 1];
|
|
1811
|
-
const x = c ? c.x : last ? last.x + last.advance : line.x;
|
|
1812
|
-
return { line, x: (dx + x) / 20 };
|
|
1813
|
-
};
|
|
1814
|
-
const g = new Graphics();
|
|
1815
|
-
const [begin, end] = o.selection;
|
|
1816
|
-
if (begin < end) {
|
|
1817
|
-
const from = place(begin);
|
|
1818
|
-
const to = place(end);
|
|
1819
|
-
const a = lines.indexOf(from.line);
|
|
1820
|
-
const b = lines.indexOf(to.line);
|
|
1821
|
-
// Each shown line the selection covers, from its start or to its end where it goes on.
|
|
1822
|
-
for (let i = Math.max(a, first); i <= Math.min(b, last); i++) {
|
|
1823
|
-
const line = lines[i];
|
|
1824
|
-
const left = i === a ? from.x : (dx + line.x) / 20;
|
|
1825
|
-
const shown = line.chars.filter((ch) => ch.shown);
|
|
1826
|
-
const end = shown.length > 0 ? shown[shown.length - 1] : null;
|
|
1827
|
-
const right = i === b ? to.x : (dx + (end ? end.x + end.advance : line.x)) / 20;
|
|
1828
|
-
// Within the gutter, as the text is: a line wider than the field is clipped there.
|
|
1829
|
-
const l = Math.max(left, inner.left);
|
|
1830
|
-
const r = Math.min(right, inner.right);
|
|
1831
|
-
const top = (dy + line.y) / 20;
|
|
1832
|
-
const height = (line.ascent + line.descent) / 20;
|
|
1833
|
-
g.rect(l, top, Math.max(0, r - l), height).fill({ color: 0x3399ff, alpha: 0.4 });
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
const at = place(o.caret);
|
|
1837
|
-
const index = lines.indexOf(at.line);
|
|
1838
|
-
if (o.type === "input" &&
|
|
1839
|
-
index >= first &&
|
|
1840
|
-
index <= last &&
|
|
1841
|
-
at.x >= inner.left &&
|
|
1842
|
-
at.x <= inner.right) {
|
|
1843
|
-
const format = o.model.formats[Math.max(0, o.caret - 1)] ?? o.model.defaultFormat;
|
|
1844
|
-
const top = (dy + at.line.y) / 20;
|
|
1845
|
-
const height = Math.max(1, (at.line.ascent + at.line.descent) / 20);
|
|
1846
|
-
g.rect(at.x, top, 1, height).fill({ color: format.color & 0xffffff });
|
|
1847
|
-
}
|
|
1848
|
-
art.addChild(g);
|
|
1849
|
-
}
|
|
1850
|
-
/** A run of a device font's characters in one format, as Pixi Text from where the layout put its first, on the baseline. */
|
|
1851
|
-
function deviceRun(o, run, dx, baseline) {
|
|
1852
|
-
const f = run[0].format;
|
|
1853
|
-
const chars = run.map((c) => (o.displayAsPassword ? "*" : o.model.text[c.index])).join("");
|
|
1854
|
-
const style = {
|
|
1855
|
-
fontFamily: fontFamily(f.font),
|
|
1856
|
-
fontSize: f.size,
|
|
1857
|
-
fill: f.color & 0xffffff,
|
|
1858
|
-
fontWeight: f.bold ? "bold" : "normal",
|
|
1859
|
-
fontStyle: f.italic ? "italic" : "normal",
|
|
1860
|
-
letterSpacing: f.letterSpacing,
|
|
1861
|
-
};
|
|
1862
|
-
const t = new Text({ text: chars, style });
|
|
1863
|
-
// Pixi's Text puts its top at its font's ascent above the baseline; without a DOM (node) there is no font to measure.
|
|
1864
|
-
const ascent = typeof document === "undefined"
|
|
1865
|
-
? deviceMetrics(f.font, f.size, f.bold, f.italic).ascent
|
|
1866
|
-
: CanvasTextMetrics.measureFont(fontStringFromTextStyle(t.style)).ascent;
|
|
1867
|
-
t.position.set((dx + run[0].x) / 20, baseline - ascent);
|
|
1868
|
-
return t;
|
|
1869
|
-
}
|
|
1870
|
-
/** RGBA bytes read from a texture as premultiplied ARGB. */
|
|
1871
|
-
function argbOf(bytes) {
|
|
1872
|
-
const out = new Uint32Array(bytes.length / 4);
|
|
1873
|
-
for (let i = 0; i < out.length; i++) {
|
|
1874
|
-
const j = i * 4;
|
|
1875
|
-
out[i] = ((bytes[j + 3] << 24) | (bytes[j] << 16) | (bytes[j + 1] << 8) | bytes[j + 2]) >>> 0;
|
|
1876
|
-
}
|
|
1877
|
-
return out;
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* A store's texture: its premultiplied ARGB exactly, uploaded as it is and
|
|
1881
|
-
* rendered into by draws. Pixi's collector never unloads it, as content a
|
|
1882
|
-
* draw left there could not be uploaded again.
|
|
1883
|
-
*/
|
|
1884
|
-
class StoreTexture {
|
|
1885
|
-
renderer;
|
|
1886
|
-
bitmaps;
|
|
1887
|
-
source;
|
|
1888
|
-
texture;
|
|
1889
|
-
/** The store's version the texture holds. */
|
|
1890
|
-
version = -1;
|
|
1891
|
-
/**
|
|
1892
|
-
* The same pixels sampled otherwise, copied on the GPU as the texture
|
|
1893
|
-
* changes: linearly for a smoothed Bitmap, and repeating or clamped,
|
|
1894
|
-
* either way, for bitmap fills; by "linear repeat".
|
|
1895
|
-
*/
|
|
1896
|
-
variants = new Map();
|
|
1897
|
-
constructor(renderer, bitmaps, width, height) {
|
|
1898
|
-
this.renderer = renderer;
|
|
1899
|
-
this.bitmaps = bitmaps;
|
|
1900
|
-
this.source = new BufferImageSource({
|
|
1901
|
-
// Bytes decide the format, 8 bits a channel; none allocate a float buffer.
|
|
1902
|
-
resource: new Uint8Array(0),
|
|
1903
|
-
width,
|
|
1904
|
-
height,
|
|
1905
|
-
alphaMode: "premultiplied-alpha",
|
|
1906
|
-
scaleMode: "nearest",
|
|
1907
|
-
autoGarbageCollect: false,
|
|
1908
|
-
});
|
|
1909
|
-
// No bytes until the CPU's are uploaded: the first use allocates the
|
|
1910
|
-
// texture without an upload, and a store of one colour is cleared to it.
|
|
1911
|
-
this.source.resource = null;
|
|
1912
|
-
this.texture = new Texture({ source: this.source });
|
|
1913
|
-
}
|
|
1914
|
-
read() {
|
|
1915
|
-
return argbOf(this.renderer.extract.pixels(this.texture).pixels);
|
|
1916
|
-
}
|
|
1917
|
-
/** The store's pixels on the GPU: cleared to their colour where they are all one, as a new bitmap's are, else uploaded. */
|
|
1918
|
-
upload(pixels) {
|
|
1919
|
-
const first = pixels[0];
|
|
1920
|
-
let uniform = true;
|
|
1921
|
-
for (let i = 1; i < pixels.length; i++) {
|
|
1922
|
-
if (pixels[i] !== first) {
|
|
1923
|
-
uniform = false;
|
|
1924
|
-
break;
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
if (!uniform) {
|
|
1928
|
-
this.source.resource = rgbaOf(pixels);
|
|
1929
|
-
this.source.update();
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
// Already premultiplied, as the texture holds them; a byte divided by 255 comes back exactly.
|
|
1933
|
-
this.renderer.render({
|
|
1934
|
-
container: EMPTY,
|
|
1935
|
-
target: this.texture,
|
|
1936
|
-
clear: true,
|
|
1937
|
-
clearColor: [
|
|
1938
|
-
((first >>> 16) & 0xff) / 255,
|
|
1939
|
-
((first >>> 8) & 0xff) / 255,
|
|
1940
|
-
(first & 0xff) / 255,
|
|
1941
|
-
(first >>> 24) / 255,
|
|
1942
|
-
],
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
/**
|
|
1946
|
-
* The texture sampled linearly or not, repeating or clamped: a
|
|
1947
|
-
* texture's sampling is its source's in Pixi, so each way is a copy.
|
|
1948
|
-
*/
|
|
1949
|
-
sampled(linear, repeat) {
|
|
1950
|
-
const key = `${linear} ${repeat}`;
|
|
1951
|
-
let variant = this.variants.get(key);
|
|
1952
|
-
if (!variant) {
|
|
1953
|
-
const texture = RenderTexture.create({
|
|
1954
|
-
width: this.source.width,
|
|
1955
|
-
height: this.source.height,
|
|
1956
|
-
scaleMode: linear ? "linear" : "nearest",
|
|
1957
|
-
addressMode: repeat ? "repeat" : "clamp-to-edge",
|
|
1958
|
-
autoGarbageCollect: false,
|
|
1959
|
-
});
|
|
1960
|
-
if (!repeat) {
|
|
1961
|
-
keepClamped(texture.source);
|
|
1962
|
-
}
|
|
1963
|
-
variant = { texture, version: -1 };
|
|
1964
|
-
this.variants.set(key, variant);
|
|
1965
|
-
}
|
|
1966
|
-
if (variant.version !== this.version) {
|
|
1967
|
-
const sprite = new Sprite(this.texture);
|
|
1968
|
-
this.renderer.render({ container: sprite, target: variant.texture, clear: true });
|
|
1969
|
-
sprite.destroy();
|
|
1970
|
-
variant.version = this.version;
|
|
1971
|
-
}
|
|
1972
|
-
return variant.texture;
|
|
1973
|
-
}
|
|
1974
|
-
destroy() {
|
|
1975
|
-
this.bitmaps.forget(this);
|
|
1976
|
-
this.texture.destroy(true);
|
|
1977
|
-
for (const { texture } of this.variants.values()) {
|
|
1978
|
-
texture.destroy(true);
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* The textures of a renderer's stores, one a store, made as a Bitmap shows
|
|
1984
|
-
* one or a draw renders into it. Each renderer keeps its own: a store
|
|
1985
|
-
* shown by two has a copy in each, and one a draw wrote is read back
|
|
1986
|
-
* through the renderer that drew it before another uploads it.
|
|
1987
|
-
*/
|
|
1988
|
-
class GpuBitmaps {
|
|
1989
|
-
renderer;
|
|
1990
|
-
limit;
|
|
1991
|
-
copies = new WeakMap();
|
|
1992
|
-
collected = new FinalizationRegistry((copy) => copy.destroy());
|
|
1993
|
-
constructor(renderer) {
|
|
1994
|
-
this.renderer = renderer;
|
|
1995
|
-
const gl = renderer.gl;
|
|
1996
|
-
this.limit = gl ? gl.getParameter(gl.MAX_TEXTURE_SIZE) : 8192;
|
|
1997
|
-
}
|
|
1998
|
-
/**
|
|
1999
|
-
* The store's texture, made if it has none and uploaded if the CPU
|
|
2000
|
-
* changed the store since; null for one disposed, of 0 by 0, or larger
|
|
2001
|
-
* than a texture may be. Smoothed, it is a linearly sampled copy.
|
|
2002
|
-
*/
|
|
2003
|
-
texture(store, smoothing) {
|
|
2004
|
-
if (store.disposed ||
|
|
2005
|
-
store.width === 0 ||
|
|
2006
|
-
store.width > this.limit ||
|
|
2007
|
-
store.height > this.limit) {
|
|
2008
|
-
return null;
|
|
2009
|
-
}
|
|
2010
|
-
let copy = this.copies.get(store);
|
|
2011
|
-
if (!copy) {
|
|
2012
|
-
copy = new StoreTexture(this.renderer, this, store.width, store.height);
|
|
2013
|
-
this.copies.set(store, copy);
|
|
2014
|
-
store.copies.add(copy);
|
|
2015
|
-
// Unregistered by the copy itself, on dispose.
|
|
2016
|
-
this.collected.register(store, copy, copy);
|
|
2017
|
-
}
|
|
2018
|
-
// Behind the store: its pixels, read back first if another renderer's draw wrote them.
|
|
2019
|
-
if (copy.version !== store.version) {
|
|
2020
|
-
copy.upload(store.pixels);
|
|
2021
|
-
copy.version = store.version;
|
|
2022
|
-
}
|
|
2023
|
-
return smoothing ? copy.sampled(true, false) : copy.texture;
|
|
2024
|
-
}
|
|
2025
|
-
/** The store's texture for a bitmap fill: repeating or clamped, smoothed or not, up to date. */
|
|
2026
|
-
fillTexture(store, repeat, smooth) {
|
|
2027
|
-
if (!this.texture(store)) {
|
|
2028
|
-
return null;
|
|
2029
|
-
}
|
|
2030
|
-
return this.copies.get(store).sampled(smooth, repeat);
|
|
2031
|
-
}
|
|
2032
|
-
/** A draw rendered into this renderer's copy of the store, which now holds the store as it is. */
|
|
2033
|
-
drawn(store) {
|
|
2034
|
-
const copy = this.copies.get(store);
|
|
2035
|
-
if (copy) {
|
|
2036
|
-
store.drawnOnGpu(copy);
|
|
2037
|
-
copy.version = store.version;
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
forget(copy) {
|
|
2041
|
-
this.collected.unregister(copy);
|
|
2042
|
-
}
|
|
2043
|
-
/**
|
|
2044
|
-
* Each gradient's textures by region: one for a linear gradient, one for
|
|
2045
|
-
* each region a radial one fills, which `drawPath` and `copyFrom` can make
|
|
2046
|
-
* several of, each held by the contexts that draw with it.
|
|
2047
|
-
*/
|
|
2048
|
-
gradients = new WeakMap();
|
|
2049
|
-
gradientsCollected = new FinalizationRegistry((t) => t.destroy(true));
|
|
2050
|
-
/**
|
|
2051
|
-
* A gradient's texture and the matrix from its texels to the shape, made
|
|
2052
|
-
* once for each region and held: given back with `release` by each
|
|
2053
|
-
* context that took it, and freed when the last does, or when the fill is
|
|
2054
|
-
* collected. A linear one is its ramp of 256 colours, sampled nearest and
|
|
2055
|
-
* spread as the texture wraps, moved half a pixel so that a pixel's
|
|
2056
|
-
* centre reads what Flash reads at its corner. A radial one is computed
|
|
2057
|
-
* over the region it fills, a texel a pixel (up to RADIAL_MAX a side),
|
|
2058
|
-
* sampled linearly.
|
|
2059
|
-
*/
|
|
2060
|
-
gradientTexture(fill, area) {
|
|
2061
|
-
const region = area ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
2062
|
-
const key = area ? `${area.x} ${area.y} ${area.width} ${area.height}` : "";
|
|
2063
|
-
let made = this.gradients.get(fill);
|
|
2064
|
-
if (!made) {
|
|
2065
|
-
made = new Map();
|
|
2066
|
-
this.gradients.set(fill, made);
|
|
2067
|
-
}
|
|
2068
|
-
const known = made.get(key);
|
|
2069
|
-
if (known) {
|
|
2070
|
-
known.uses++;
|
|
2071
|
-
return known;
|
|
2072
|
-
}
|
|
2073
|
-
const colors = ramp(fill.stops, fill.linearRgb);
|
|
2074
|
-
const m = fill.matrix;
|
|
2075
|
-
const columns = Math.min(RADIAL_MAX, region.width);
|
|
2076
|
-
const rows = Math.min(RADIAL_MAX, region.height);
|
|
2077
|
-
const clamped = fill.radial || fill.spread === 0;
|
|
2078
|
-
const source = new BufferImageSource({
|
|
2079
|
-
resource: rgbaOf(fill.radial
|
|
2080
|
-
? radialPixels(colors, m, fill.focal, fill.spread, region, columns, rows)
|
|
2081
|
-
: colors),
|
|
2082
|
-
width: fill.radial ? columns : 256,
|
|
2083
|
-
height: fill.radial ? rows : 1,
|
|
2084
|
-
alphaMode: "premultiplied-alpha",
|
|
2085
|
-
scaleMode: fill.radial ? "linear" : "nearest",
|
|
2086
|
-
addressMode: clamped ? "clamp-to-edge" : fill.spread === 1 ? "mirror-repeat" : "repeat",
|
|
2087
|
-
autoGarbageCollect: false,
|
|
2088
|
-
});
|
|
2089
|
-
if (clamped) {
|
|
2090
|
-
keepClamped(source);
|
|
2091
|
-
}
|
|
2092
|
-
const texture = new Texture({ source });
|
|
2093
|
-
const matrix = fill.radial
|
|
2094
|
-
? new Matrix(region.width / columns, 0, 0, region.height / rows, region.x, region.y)
|
|
2095
|
-
: new Matrix(1, 0, 0, 1, 0.5, 0.5)
|
|
2096
|
-
.append(new Matrix(m.a, m.b, m.c, m.d, m.tx, m.ty))
|
|
2097
|
-
// Texels to the gradient square, -819.2 to 819.2 a side.
|
|
2098
|
-
.append(new Matrix(1638.4 / 256, 0, 0, 1638.4, -819.2, -819.2));
|
|
2099
|
-
const byRegion = made;
|
|
2100
|
-
const entry = {
|
|
2101
|
-
texture,
|
|
2102
|
-
matrix,
|
|
2103
|
-
uses: 1,
|
|
2104
|
-
release: () => {
|
|
2105
|
-
if (--entry.uses === 0) {
|
|
2106
|
-
byRegion.delete(key);
|
|
2107
|
-
this.gradientsCollected.unregister(entry);
|
|
2108
|
-
texture.destroy(true);
|
|
2109
|
-
}
|
|
2110
|
-
},
|
|
2111
|
-
};
|
|
2112
|
-
made.set(key, entry);
|
|
2113
|
-
this.gradientsCollected.register(fill, texture, entry);
|
|
2114
|
-
return entry;
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
|
-
/** Retired groups' batchers kept, at most this many groups', for new groups to build with. */
|
|
2118
|
-
const SPARE_BATCHERS_MOST = 16;
|
|
2119
|
-
const spareBatchersOf = new WeakMap();
|
|
2120
|
-
/**
|
|
2121
|
-
* The instruction sets of groups a view made, the only ones given spare batchers: a root rendered
|
|
2122
|
-
* once, as BitmapData.draw's, is destroyed with whatever it was given.
|
|
2123
|
-
*/
|
|
2124
|
-
const viewGroups = new WeakSet();
|
|
2125
|
-
/**
|
|
2126
|
-
* Take a retired instruction set's batchers from Pixi, which keeps them by ID even after its group
|
|
2127
|
-
* is gone. A branch made anew each frame would otherwise allocate a group's buffers as an older
|
|
2128
|
-
* one's are freed, which costs more than keeping them all; a new group takes them over instead.
|
|
2129
|
-
*/
|
|
2130
|
-
function retireBatchers(renderer, uid) {
|
|
2131
|
-
const pipe = renderer.renderPipes?.batch;
|
|
2132
|
-
const batchers = pipe?._batchersByInstructionSet?.[uid];
|
|
2133
|
-
if (!pipe || !batchers) {
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
delete pipe._batchersByInstructionSet[uid];
|
|
2137
|
-
let spare = spareBatchersOf.get(renderer);
|
|
2138
|
-
if (!spare) {
|
|
2139
|
-
const list = [];
|
|
2140
|
-
const buildStart = pipe.buildStart;
|
|
2141
|
-
pipe.buildStart = function (set) {
|
|
2142
|
-
if (!this._batchersByInstructionSet[set.uid] && list.length > 0 && viewGroups.has(set)) {
|
|
2143
|
-
this._batchersByInstructionSet[set.uid] = list.pop();
|
|
2144
|
-
}
|
|
2145
|
-
buildStart.call(this, set);
|
|
2146
|
-
};
|
|
2147
|
-
spare = list;
|
|
2148
|
-
spareBatchersOf.set(renderer, spare);
|
|
2149
|
-
}
|
|
2150
|
-
if (spare.length < SPARE_BATCHERS_MOST) {
|
|
2151
|
-
spare.push(batchers);
|
|
2152
|
-
return;
|
|
2153
|
-
}
|
|
2154
|
-
for (const batcher of Object.values(batchers)) {
|
|
2155
|
-
batcher.destroy();
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
const gpuBitmapsOf = new WeakMap();
|
|
2159
|
-
/** Nothing, rendered to clear a texture. */
|
|
2160
|
-
const EMPTY = new PixiContainer();
|
|
2161
|
-
function gpuBitmaps(renderer) {
|
|
2162
|
-
let bitmaps = gpuBitmapsOf.get(renderer);
|
|
2163
|
-
if (!bitmaps) {
|
|
2164
|
-
bitmaps = new GpuBitmaps(renderer);
|
|
2165
|
-
gpuBitmapsOf.set(renderer, bitmaps);
|
|
2166
|
-
}
|
|
2167
|
-
return bitmaps;
|
|
2168
|
-
}
|
|
2169
|
-
//# sourceMappingURL=pixi.js.map
|