@flighthq/clip 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clipRegion.d.ts +25 -0
- package/dist/clipRegion.d.ts.map +1 -0
- package/dist/clipRegion.js +510 -0
- package/dist/clipRegion.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/clipRegion.test.ts +598 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClipRegion, MatrixLike, Path, PathWinding, RectangleLike } from '@flighthq/types';
|
|
2
|
+
export declare function acquireClipRegion(): ClipRegion;
|
|
3
|
+
export declare function clipRegionContainsPoint(clip: Readonly<ClipRegion>, x: number, y: number): boolean;
|
|
4
|
+
export declare function clipRegionContainsRectangle(clip: Readonly<ClipRegion>, rectangle: Readonly<RectangleLike>): boolean;
|
|
5
|
+
export declare function clipRegionIntersectsRectangle(clip: Readonly<ClipRegion>, rectangle: Readonly<RectangleLike>): boolean;
|
|
6
|
+
export declare function clipRegionsEqual(a: Readonly<ClipRegion>, b: Readonly<ClipRegion>): boolean;
|
|
7
|
+
export declare function cloneClipRegion(clip: Readonly<ClipRegion>): ClipRegion;
|
|
8
|
+
export declare function copyClipRegion(out: ClipRegion, source: Readonly<ClipRegion>): void;
|
|
9
|
+
export declare function createClipRegionFromCircle(x: number, y: number, radius: number, tolerance?: number): ClipRegion;
|
|
10
|
+
export declare function createClipRegionFromContours(contours: Readonly<ReadonlyArray<ReadonlyArray<number>>>, winding: PathWinding): ClipRegion;
|
|
11
|
+
export declare function createClipRegionFromEllipse(rectangle: Readonly<RectangleLike>, tolerance?: number): ClipRegion;
|
|
12
|
+
export declare function createClipRegionFromPath(path: Readonly<Path>, tolerance?: number): ClipRegion;
|
|
13
|
+
export declare function createClipRegionFromRectangle(rectangle: Readonly<RectangleLike>): ClipRegion;
|
|
14
|
+
export declare function createClipRegionFromRoundedRectangle(rectangle: Readonly<RectangleLike>, radius: number, tolerance?: number): ClipRegion;
|
|
15
|
+
export declare function getClipRegionBounds(out: RectangleLike, clip: Readonly<ClipRegion>): void;
|
|
16
|
+
export declare function intersectClipRegions(out: ClipRegion, a: Readonly<ClipRegion>, b: Readonly<ClipRegion>): void;
|
|
17
|
+
export declare function invalidateClipRegion(clip: ClipRegion): void;
|
|
18
|
+
export declare function isClipRegionEmpty(clip: Readonly<ClipRegion>): boolean;
|
|
19
|
+
export declare function isClipRegionRectangular(clip: Readonly<ClipRegion>): boolean;
|
|
20
|
+
export declare function normalizeClipRegion(out: ClipRegion, clip: Readonly<ClipRegion>): void;
|
|
21
|
+
export declare function releaseClipRegion(clip: ClipRegion): void;
|
|
22
|
+
export declare function setClipRegionToRectangle(out: ClipRegion, rectangle: Readonly<RectangleLike>): void;
|
|
23
|
+
export declare function transformClipRegion(out: ClipRegion, clip: Readonly<ClipRegion>, matrix: Readonly<MatrixLike>): void;
|
|
24
|
+
export declare function unionClipRegions(out: ClipRegion, a: Readonly<ClipRegion>, b: Readonly<ClipRegion>): void;
|
|
25
|
+
//# sourceMappingURL=clipRegion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipRegion.d.ts","sourceRoot":"","sources":["../src/clipRegion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { cloneRectangle, containsRectanglePointXY, copyRectangle, createRectangle, enclosesRectangle, intersectsRectangle, isEmptyRectangle, matrixTransformRectangle, mergeRectangle, } from '@flighthq/geometry';
|
|
2
|
+
import { appendPathCubicCurveTo, appendPathLineTo, appendPathMoveTo, createPath, flattenPath } from '@flighthq/path';
|
|
3
|
+
// Returns a ClipRegion from the pool (as an empty rectangular region at the origin).
|
|
4
|
+
// Every acquireClipRegion must have a matching releaseClipRegion — treat them as paired brackets.
|
|
5
|
+
// Callers should initialize the acquired region immediately before use.
|
|
6
|
+
export function acquireClipRegion() {
|
|
7
|
+
const region = clipRegionPool.pop();
|
|
8
|
+
if (region !== undefined) {
|
|
9
|
+
region.rect.x = 0;
|
|
10
|
+
region.rect.y = 0;
|
|
11
|
+
region.rect.width = 0;
|
|
12
|
+
region.rect.height = 0;
|
|
13
|
+
region.contours = null;
|
|
14
|
+
region.winding = 'nonZero';
|
|
15
|
+
region.version = 0;
|
|
16
|
+
return region;
|
|
17
|
+
}
|
|
18
|
+
return makeEmptyClipRegion();
|
|
19
|
+
}
|
|
20
|
+
// Returns true if a point (x, y) lies within the clip region (in clip-local space).
|
|
21
|
+
// For the rectangle form uses the rectangle bounds; for the contour form applies
|
|
22
|
+
// the exact winding rule (even-odd or non-zero point-in-polygon).
|
|
23
|
+
export function clipRegionContainsPoint(clip, x, y) {
|
|
24
|
+
if (!containsRectanglePointXY(clip.rect, x, y))
|
|
25
|
+
return false;
|
|
26
|
+
if (clip.contours === null)
|
|
27
|
+
return true;
|
|
28
|
+
return pointInContours(clip.contours, clip.winding, x, y);
|
|
29
|
+
}
|
|
30
|
+
// Returns true when the clip region fully contains the given rectangle.
|
|
31
|
+
// Rectangle form: exact containment; contour form: bounding-box approximation (conservative).
|
|
32
|
+
export function clipRegionContainsRectangle(clip, rectangle) {
|
|
33
|
+
return enclosesRectangle(clip.rect, rectangle);
|
|
34
|
+
}
|
|
35
|
+
// Returns true when the given rectangle overlaps the clip region.
|
|
36
|
+
// Rectangle form uses exact rect-vs-rect check; contour form falls back to bounding box (conservative).
|
|
37
|
+
export function clipRegionIntersectsRectangle(clip, rectangle) {
|
|
38
|
+
return intersectsRectangle(clip.rect, rectangle);
|
|
39
|
+
}
|
|
40
|
+
// Structural equality check. Does not use the version counter — compares geometry directly.
|
|
41
|
+
// Useful for cache reuse independent of manual invalidation. Contour comparison is exact (point-by-point).
|
|
42
|
+
export function clipRegionsEqual(a, b) {
|
|
43
|
+
if (a === b)
|
|
44
|
+
return true;
|
|
45
|
+
if (a.winding !== b.winding)
|
|
46
|
+
return false;
|
|
47
|
+
const ar = a.rect;
|
|
48
|
+
const br = b.rect;
|
|
49
|
+
if (ar.x !== br.x || ar.y !== br.y || ar.width !== br.width || ar.height !== br.height)
|
|
50
|
+
return false;
|
|
51
|
+
if (a.contours === null && b.contours === null)
|
|
52
|
+
return true;
|
|
53
|
+
if (a.contours === null || b.contours === null)
|
|
54
|
+
return false;
|
|
55
|
+
const ac = a.contours;
|
|
56
|
+
const bc = b.contours;
|
|
57
|
+
if (ac.length !== bc.length)
|
|
58
|
+
return false;
|
|
59
|
+
for (let i = 0; i < ac.length; i++) {
|
|
60
|
+
const ai = ac[i];
|
|
61
|
+
const bi = bc[i];
|
|
62
|
+
if (ai.length !== bi.length)
|
|
63
|
+
return false;
|
|
64
|
+
for (let j = 0; j < ai.length; j++) {
|
|
65
|
+
if (ai[j] !== bi[j])
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
// Deep copy of a clip region (rect, contours arrays, winding). Version is preserved from the
|
|
72
|
+
// source so the caller can still detect change; call invalidateClipRegion after mutation.
|
|
73
|
+
export function cloneClipRegion(clip) {
|
|
74
|
+
const rect = cloneRectangle(clip.rect);
|
|
75
|
+
const contours = clip.contours === null ? null : clip.contours.map((c) => c.slice());
|
|
76
|
+
return { contours, rect, version: clip.version, winding: clip.winding };
|
|
77
|
+
}
|
|
78
|
+
// Copies source into out in place; does nothing when out === source. Bumps out.version
|
|
79
|
+
// (treats a retargeted region as changed so backends re-derive state).
|
|
80
|
+
export function copyClipRegion(out, source) {
|
|
81
|
+
if (out === source)
|
|
82
|
+
return;
|
|
83
|
+
copyRectangle(out.rect, source.rect);
|
|
84
|
+
out.contours = source.contours === null ? null : source.contours.map((c) => c.slice());
|
|
85
|
+
out.winding = source.winding;
|
|
86
|
+
out.version = (out.version + 1) >>> 0;
|
|
87
|
+
}
|
|
88
|
+
// Builds a clip region from a circle. Internally approximates via cubic Bezier curves and flattens.
|
|
89
|
+
export function createClipRegionFromCircle(x, y, radius, tolerance = 0.25) {
|
|
90
|
+
const path = createPath('nonZero');
|
|
91
|
+
appendCircleToPath(path, x, y, radius);
|
|
92
|
+
return createClipRegionFromPath(path, tolerance);
|
|
93
|
+
}
|
|
94
|
+
// Builds a clip region from raw flattened contours (x,y pairs per sub-path) and a winding rule.
|
|
95
|
+
// The caller is responsible for providing valid, closed contours. The bounding rect is computed
|
|
96
|
+
// from the contour data; pass an empty array for an empty region. The contours are deep-copied so
|
|
97
|
+
// later edits to the caller's array do not mutate the region — symmetry with the rest of create*.
|
|
98
|
+
export function createClipRegionFromContours(contours, winding) {
|
|
99
|
+
const rect = createRectangle();
|
|
100
|
+
setRectangleToContoursBounds(rect, contours);
|
|
101
|
+
const owned = contours.map((c) => c.slice());
|
|
102
|
+
return { contours: owned, rect, version: 0, winding };
|
|
103
|
+
}
|
|
104
|
+
// Builds a clip region from an axis-aligned ellipse bounded by the given rectangle.
|
|
105
|
+
// Uses cubic Bezier approximation (kappa constant) and flattens via path.
|
|
106
|
+
export function createClipRegionFromEllipse(rectangle, tolerance = 0.25) {
|
|
107
|
+
const path = createPath('nonZero');
|
|
108
|
+
appendEllipseToPath(path, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
|
|
109
|
+
return createClipRegionFromPath(path, tolerance);
|
|
110
|
+
}
|
|
111
|
+
// Builds a clip region from arbitrary path geometry. The path is flattened to contours now (cached on
|
|
112
|
+
// the region; re-create to change), and the region's rect is set to their bounding box for culling and
|
|
113
|
+
// the stencil cover quad. Realized by stencil-then-cover, so it handles concavity, holes, and
|
|
114
|
+
// self-intersection per the path's own winding rule.
|
|
115
|
+
export function createClipRegionFromPath(path, tolerance = 0.25) {
|
|
116
|
+
const contours = flattenPath(path, tolerance);
|
|
117
|
+
const rect = createRectangle();
|
|
118
|
+
setRectangleToContoursBounds(rect, contours);
|
|
119
|
+
return { contours, rect, version: 0, winding: path.winding };
|
|
120
|
+
}
|
|
121
|
+
// Builds a rectangular clip region — the allocation-light, scissor-eligible form. The rectangle is
|
|
122
|
+
// copied so later edits to the caller's rectangle do not mutate the region; bump via invalidateClipRegion.
|
|
123
|
+
export function createClipRegionFromRectangle(rectangle) {
|
|
124
|
+
return { contours: null, rect: cloneRectangle(rectangle), version: 0, winding: 'nonZero' };
|
|
125
|
+
}
|
|
126
|
+
// Builds a clip region from a rounded rectangle with a uniform corner radius.
|
|
127
|
+
// Falls back to a plain rect clip when radius <= 0.
|
|
128
|
+
export function createClipRegionFromRoundedRectangle(rectangle, radius, tolerance = 0.25) {
|
|
129
|
+
if (radius <= 0)
|
|
130
|
+
return createClipRegionFromRectangle(rectangle);
|
|
131
|
+
const path = createPath('nonZero');
|
|
132
|
+
appendRoundedRectToPath(path, rectangle.x, rectangle.y, rectangle.width, rectangle.height, radius);
|
|
133
|
+
return createClipRegionFromPath(path, tolerance);
|
|
134
|
+
}
|
|
135
|
+
// Returns the bounding rect of a clip region in the given out rectangle.
|
|
136
|
+
export function getClipRegionBounds(out, clip) {
|
|
137
|
+
const r = clip.rect;
|
|
138
|
+
out.x = r.x;
|
|
139
|
+
out.y = r.y;
|
|
140
|
+
out.width = r.width;
|
|
141
|
+
out.height = r.height;
|
|
142
|
+
}
|
|
143
|
+
// Computes the intersection of two clip regions into out (alias-safe: out may be a or b).
|
|
144
|
+
// Rectangle∩Rectangle: exact scissor-eligible intersection via computeRectangleIntersection.
|
|
145
|
+
// All mixed or contour forms: bounding-box intersection (conservative — the renderer's stencil
|
|
146
|
+
// covers any finer geometry). Bumps out.version.
|
|
147
|
+
export function intersectClipRegions(out, a, b) {
|
|
148
|
+
// Read all input values into locals first (alias-safe for out === a or out === b).
|
|
149
|
+
const aRect = a.rect;
|
|
150
|
+
const bRect = b.rect;
|
|
151
|
+
const ax = aRect.x, ay = aRect.y, aw = aRect.width, ah = aRect.height;
|
|
152
|
+
const bx = bRect.x, by = bRect.y, bw = bRect.width, bh = bRect.height;
|
|
153
|
+
const aContours = a.contours;
|
|
154
|
+
const bContours = b.contours;
|
|
155
|
+
const aWinding = a.winding;
|
|
156
|
+
const bWinding = b.winding;
|
|
157
|
+
// Compute intersection rect (reusing local vars for clarity).
|
|
158
|
+
const x0 = Math.max(ax, bx);
|
|
159
|
+
const y0 = Math.max(ay, by);
|
|
160
|
+
const x1 = Math.min(ax + aw, bx + bw);
|
|
161
|
+
const y1 = Math.min(ay + ah, by + bh);
|
|
162
|
+
if (x1 <= x0 || y1 <= y0) {
|
|
163
|
+
// Disjoint: result is empty.
|
|
164
|
+
out.rect.x = 0;
|
|
165
|
+
out.rect.y = 0;
|
|
166
|
+
out.rect.width = 0;
|
|
167
|
+
out.rect.height = 0;
|
|
168
|
+
out.contours = null;
|
|
169
|
+
out.winding = 'nonZero';
|
|
170
|
+
out.version = (out.version + 1) >>> 0;
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
out.rect.x = x0;
|
|
174
|
+
out.rect.y = y0;
|
|
175
|
+
out.rect.width = x1 - x0;
|
|
176
|
+
out.rect.height = y1 - y0;
|
|
177
|
+
if (aContours === null && bContours === null) {
|
|
178
|
+
// Both rectangular: result is scissor-eligible.
|
|
179
|
+
out.contours = null;
|
|
180
|
+
out.winding = 'nonZero';
|
|
181
|
+
}
|
|
182
|
+
else if (aContours !== null && bContours === null) {
|
|
183
|
+
// Contours ∩ rect: keep contours, clipped bounds computed above.
|
|
184
|
+
out.contours = aContours.map((c) => c.slice());
|
|
185
|
+
out.winding = aWinding;
|
|
186
|
+
}
|
|
187
|
+
else if (aContours === null && bContours !== null) {
|
|
188
|
+
out.contours = bContours.map((c) => c.slice());
|
|
189
|
+
out.winding = bWinding;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Both contours: conservative — keep the finer of the two (larger number of contours) and
|
|
193
|
+
// use its winding. The renderer's stencil-then-cover handles the true intersection.
|
|
194
|
+
const keepA = aContours.length >= bContours.length;
|
|
195
|
+
out.contours = (keepA ? aContours : bContours).map((c) => c.slice());
|
|
196
|
+
out.winding = keepA ? aWinding : bWinding;
|
|
197
|
+
}
|
|
198
|
+
out.version = (out.version + 1) >>> 0;
|
|
199
|
+
}
|
|
200
|
+
// Marks the region's geometry changed so backends re-derive cached state. Mirrors invalidateImageResource.
|
|
201
|
+
export function invalidateClipRegion(clip) {
|
|
202
|
+
clip.version = (clip.version + 1) >>> 0;
|
|
203
|
+
}
|
|
204
|
+
// Returns true if no area passes through the clip — either the bounding rect is empty or
|
|
205
|
+
// the contour array exists but has no entries.
|
|
206
|
+
export function isClipRegionEmpty(clip) {
|
|
207
|
+
if (isEmptyRectangle(clip.rect))
|
|
208
|
+
return true;
|
|
209
|
+
if (clip.contours !== null && clip.contours.length === 0)
|
|
210
|
+
return true;
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
// Returns true when the clip is in the scissor-eligible rectangle form (contours === null).
|
|
214
|
+
export function isClipRegionRectangular(clip) {
|
|
215
|
+
return clip.contours === null;
|
|
216
|
+
}
|
|
217
|
+
// Canonicalizes a contour region back to the scissor-eligible rect form when the contour set is
|
|
218
|
+
// exactly (within NORMALIZE_EPSILON) an axis-aligned rectangle. This lightweight check works only
|
|
219
|
+
// on single-contour, 4-point (8 coordinate) contours — it does not require a full polygon-clipping
|
|
220
|
+
// kernel. When the contour is detected as a rectangular axis-aligned quad, the rect form is
|
|
221
|
+
// restored so downstream backends use the cheaper scissor path instead of stencil-then-cover.
|
|
222
|
+
// Otherwise out receives a copy of clip unchanged. Bumps out.version in all cases.
|
|
223
|
+
// Already-rectangular clips (contours === null) are copied through without modification.
|
|
224
|
+
export function normalizeClipRegion(out, clip) {
|
|
225
|
+
const inContours = clip.contours;
|
|
226
|
+
const inRect = clip.rect;
|
|
227
|
+
const inWinding = clip.winding;
|
|
228
|
+
if (inContours === null) {
|
|
229
|
+
// Already rect form: copy straight through.
|
|
230
|
+
copyRectangle(out.rect, inRect);
|
|
231
|
+
out.contours = null;
|
|
232
|
+
out.winding = inWinding;
|
|
233
|
+
out.version = (out.version + 1) >>> 0;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Only attempt rect detection for a single contour with exactly 4 points (8 xy values).
|
|
237
|
+
if (inContours.length === 1 && inContours[0].length === 8) {
|
|
238
|
+
const c = inContours[0];
|
|
239
|
+
const e = NORMALIZE_EPSILON;
|
|
240
|
+
// Compute bounding box of the 4 points.
|
|
241
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
242
|
+
for (let i = 0; i < 8; i += 2) {
|
|
243
|
+
const cx = c[i];
|
|
244
|
+
const cy = c[i + 1];
|
|
245
|
+
if (cx < minX)
|
|
246
|
+
minX = cx;
|
|
247
|
+
if (cx > maxX)
|
|
248
|
+
maxX = cx;
|
|
249
|
+
if (cy < minY)
|
|
250
|
+
minY = cy;
|
|
251
|
+
if (cy > maxY)
|
|
252
|
+
maxY = cy;
|
|
253
|
+
}
|
|
254
|
+
// Each point must sit at a corner of the bounding box.
|
|
255
|
+
let isAxisAligned = true;
|
|
256
|
+
for (let i = 0; i < 8; i += 2) {
|
|
257
|
+
const cx = c[i];
|
|
258
|
+
const cy = c[i + 1];
|
|
259
|
+
if (!(Math.abs(cx - minX) <= e || Math.abs(cx - maxX) <= e)) {
|
|
260
|
+
isAxisAligned = false;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
if (!(Math.abs(cy - minY) <= e || Math.abs(cy - maxY) <= e)) {
|
|
264
|
+
isAxisAligned = false;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (isAxisAligned) {
|
|
269
|
+
out.rect.x = minX;
|
|
270
|
+
out.rect.y = minY;
|
|
271
|
+
out.rect.width = maxX - minX;
|
|
272
|
+
out.rect.height = maxY - minY;
|
|
273
|
+
out.contours = null;
|
|
274
|
+
out.winding = 'nonZero';
|
|
275
|
+
out.version = (out.version + 1) >>> 0;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Contours are not a simple axis-aligned rectangle: copy through unchanged.
|
|
280
|
+
copyRectangle(out.rect, inRect);
|
|
281
|
+
out.contours = inContours.map((c) => c.slice());
|
|
282
|
+
out.winding = inWinding;
|
|
283
|
+
out.version = (out.version + 1) >>> 0;
|
|
284
|
+
}
|
|
285
|
+
// Returns a ClipRegion to the pool. After release the caller must not use the region.
|
|
286
|
+
// Every acquireClipRegion must have a matching releaseClipRegion.
|
|
287
|
+
export function releaseClipRegion(clip) {
|
|
288
|
+
clipRegionPool.push(clip);
|
|
289
|
+
}
|
|
290
|
+
// Retargets an existing region to a rectangle form in place, avoiding per-frame allocation
|
|
291
|
+
// in animated-clip scenarios. Bumps version.
|
|
292
|
+
export function setClipRegionToRectangle(out, rectangle) {
|
|
293
|
+
copyRectangle(out.rect, rectangle);
|
|
294
|
+
out.contours = null;
|
|
295
|
+
out.winding = 'nonZero';
|
|
296
|
+
out.version = (out.version + 1) >>> 0;
|
|
297
|
+
}
|
|
298
|
+
// Applies a 2D affine matrix to a clip region and writes the result into out (alias-safe).
|
|
299
|
+
// Rectangle form: when the matrix is axis-aligned (b === 0 && c === 0) the result is scissor-eligible;
|
|
300
|
+
// otherwise, the rectangle is promoted to a 4-point contour quad so the scissor-eligibility invariant
|
|
301
|
+
// is maintained (a rotated/skewed rectangle is no longer axis-aligned). The bounding rect of
|
|
302
|
+
// the transformed geometry is recomputed for culling. Bumps version.
|
|
303
|
+
export function transformClipRegion(out, clip, matrix) {
|
|
304
|
+
const ma = matrix.a;
|
|
305
|
+
const mb = matrix.b;
|
|
306
|
+
const mc = matrix.c;
|
|
307
|
+
const md = matrix.d;
|
|
308
|
+
const mtx = matrix.tx;
|
|
309
|
+
const mty = matrix.ty;
|
|
310
|
+
const inContours = clip.contours;
|
|
311
|
+
const inRect = clip.rect;
|
|
312
|
+
const inWinding = clip.winding;
|
|
313
|
+
if (inContours === null) {
|
|
314
|
+
const axisAligned = mb === 0 && mc === 0;
|
|
315
|
+
if (axisAligned) {
|
|
316
|
+
// Rectangle stays scissor-eligible: apply transform to the rect.
|
|
317
|
+
matrixTransformRectangle(out.rect, matrix, inRect);
|
|
318
|
+
out.contours = null;
|
|
319
|
+
out.winding = 'nonZero';
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Rotation or skew: promote to 4-point quad contour.
|
|
323
|
+
const rx = inRect.x;
|
|
324
|
+
const ry = inRect.y;
|
|
325
|
+
const rw = inRect.width;
|
|
326
|
+
const rh = inRect.height;
|
|
327
|
+
// Transform all four corners.
|
|
328
|
+
const tlX = ma * rx + mc * ry + mtx;
|
|
329
|
+
const tlY = mb * rx + md * ry + mty;
|
|
330
|
+
const trX = ma * (rx + rw) + mc * ry + mtx;
|
|
331
|
+
const trY = mb * (rx + rw) + md * ry + mty;
|
|
332
|
+
const brX = ma * (rx + rw) + mc * (ry + rh) + mtx;
|
|
333
|
+
const brY = mb * (rx + rw) + md * (ry + rh) + mty;
|
|
334
|
+
const blX = ma * rx + mc * (ry + rh) + mtx;
|
|
335
|
+
const blY = mb * rx + md * (ry + rh) + mty;
|
|
336
|
+
const quad = [tlX, tlY, trX, trY, brX, brY, blX, blY];
|
|
337
|
+
out.contours = [quad];
|
|
338
|
+
out.winding = 'nonZero';
|
|
339
|
+
setRectangleToContoursBounds(out.rect, [quad]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Transform every contour point.
|
|
344
|
+
const newContours = new Array(inContours.length);
|
|
345
|
+
for (let c = 0; c < inContours.length; c++) {
|
|
346
|
+
const src = inContours[c];
|
|
347
|
+
const dst = new Array(src.length);
|
|
348
|
+
for (let i = 0; i < src.length; i += 2) {
|
|
349
|
+
const ox = src[i];
|
|
350
|
+
const oy = src[i + 1];
|
|
351
|
+
dst[i] = ma * ox + mc * oy + mtx;
|
|
352
|
+
dst[i + 1] = mb * ox + md * oy + mty;
|
|
353
|
+
}
|
|
354
|
+
newContours[c] = dst;
|
|
355
|
+
}
|
|
356
|
+
out.contours = newContours;
|
|
357
|
+
out.winding = inWinding;
|
|
358
|
+
setRectangleToContoursBounds(out.rect, newContours);
|
|
359
|
+
}
|
|
360
|
+
out.version = (out.version + 1) >>> 0;
|
|
361
|
+
}
|
|
362
|
+
// Computes the bounding-box union of two clip regions into out (conservative for contour forms).
|
|
363
|
+
// Both rectangular: exact mergeRectangle; any contour involved: union of their bounding rects,
|
|
364
|
+
// contours from the input with more sub-paths (heuristic), same winding. Bumps version.
|
|
365
|
+
export function unionClipRegions(out, a, b) {
|
|
366
|
+
// Read inputs into locals (alias-safe).
|
|
367
|
+
const aRect = a.rect;
|
|
368
|
+
const bRect = b.rect;
|
|
369
|
+
const aContours = a.contours;
|
|
370
|
+
const bContours = b.contours;
|
|
371
|
+
const aWinding = a.winding;
|
|
372
|
+
const bWinding = b.winding;
|
|
373
|
+
mergeRectangle(out.rect, aRect, bRect);
|
|
374
|
+
if (aContours === null && bContours === null) {
|
|
375
|
+
out.contours = null;
|
|
376
|
+
out.winding = 'nonZero';
|
|
377
|
+
}
|
|
378
|
+
else if (aContours !== null && bContours === null) {
|
|
379
|
+
out.contours = aContours.map((c) => c.slice());
|
|
380
|
+
out.winding = aWinding;
|
|
381
|
+
}
|
|
382
|
+
else if (aContours === null && bContours !== null) {
|
|
383
|
+
out.contours = bContours.map((c) => c.slice());
|
|
384
|
+
out.winding = bWinding;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
const keepA = aContours.length >= bContours.length;
|
|
388
|
+
out.contours = (keepA ? aContours : bContours).map((c) => c.slice());
|
|
389
|
+
out.winding = keepA ? aWinding : bWinding;
|
|
390
|
+
}
|
|
391
|
+
out.version = (out.version + 1) >>> 0;
|
|
392
|
+
}
|
|
393
|
+
// -- module-level pools, constants, and scratch helpers --
|
|
394
|
+
// Tolerance used when comparing floats in normalizeClipRegion.
|
|
395
|
+
const NORMALIZE_EPSILON = 1e-6;
|
|
396
|
+
// Kappa constant for circle/ellipse cubic Bezier approximation.
|
|
397
|
+
const KAPPA = 0.5522847498;
|
|
398
|
+
const clipRegionPool = [];
|
|
399
|
+
function makeEmptyClipRegion() {
|
|
400
|
+
return { contours: null, rect: createRectangle(), version: 0, winding: 'nonZero' };
|
|
401
|
+
}
|
|
402
|
+
// Returns true if (px, py) is inside the contours according to the given winding rule.
|
|
403
|
+
// Uses a horizontal ray-cast to count crossings. Handles both 'nonZero' and 'evenOdd'.
|
|
404
|
+
function pointInContours(contours, winding, px, py) {
|
|
405
|
+
let windingNumber = 0;
|
|
406
|
+
for (let c = 0; c < contours.length; c++) {
|
|
407
|
+
const contour = contours[c];
|
|
408
|
+
const n = contour.length;
|
|
409
|
+
if (n < 4)
|
|
410
|
+
continue;
|
|
411
|
+
for (let i = 0; i < n; i += 2) {
|
|
412
|
+
const x0 = contour[i];
|
|
413
|
+
const y0 = contour[i + 1];
|
|
414
|
+
const x1 = contour[(i + 2) % n];
|
|
415
|
+
const y1 = contour[(i + 3) % n];
|
|
416
|
+
if (y0 <= py) {
|
|
417
|
+
if (y1 > py) {
|
|
418
|
+
// Upward crossing: is point to left of edge?
|
|
419
|
+
if ((x1 - x0) * (py - y0) - (px - x0) * (y1 - y0) > 0) {
|
|
420
|
+
windingNumber++;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
if (y1 <= py) {
|
|
426
|
+
// Downward crossing: is point to right of edge?
|
|
427
|
+
if ((x1 - x0) * (py - y0) - (px - x0) * (y1 - y0) < 0) {
|
|
428
|
+
windingNumber--;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (winding === 'evenOdd') {
|
|
435
|
+
return (windingNumber & 1) !== 0;
|
|
436
|
+
}
|
|
437
|
+
return windingNumber !== 0;
|
|
438
|
+
}
|
|
439
|
+
function appendCircleToPath(path, cx, cy, r) {
|
|
440
|
+
const k = r * KAPPA;
|
|
441
|
+
appendPathMoveTo(path, cx, cy - r);
|
|
442
|
+
appendPathCubicCurveTo(path, cx + k, cy - r, cx + r, cy - k, cx + r, cy);
|
|
443
|
+
appendPathCubicCurveTo(path, cx + r, cy + k, cx + k, cy + r, cx, cy + r);
|
|
444
|
+
appendPathCubicCurveTo(path, cx - k, cy + r, cx - r, cy + k, cx - r, cy);
|
|
445
|
+
appendPathCubicCurveTo(path, cx - r, cy - k, cx - k, cy - r, cx, cy - r);
|
|
446
|
+
}
|
|
447
|
+
function appendEllipseToPath(path, x, y, w, h) {
|
|
448
|
+
const cx = x + w / 2;
|
|
449
|
+
const cy = y + h / 2;
|
|
450
|
+
const rx = w / 2;
|
|
451
|
+
const ry = h / 2;
|
|
452
|
+
const kx = rx * KAPPA;
|
|
453
|
+
const ky = ry * KAPPA;
|
|
454
|
+
appendPathMoveTo(path, cx, cy - ry);
|
|
455
|
+
appendPathCubicCurveTo(path, cx + kx, cy - ry, cx + rx, cy - ky, cx + rx, cy);
|
|
456
|
+
appendPathCubicCurveTo(path, cx + rx, cy + ky, cx + kx, cy + ry, cx, cy + ry);
|
|
457
|
+
appendPathCubicCurveTo(path, cx - kx, cy + ry, cx - rx, cy + ky, cx - rx, cy);
|
|
458
|
+
appendPathCubicCurveTo(path, cx - rx, cy - ky, cx - kx, cy - ry, cx, cy - ry);
|
|
459
|
+
}
|
|
460
|
+
function appendRoundedRectToPath(path, x, y, w, h, r) {
|
|
461
|
+
const maxR = Math.min(w, h) / 2;
|
|
462
|
+
const cr = Math.min(r, maxR);
|
|
463
|
+
const k = cr * KAPPA;
|
|
464
|
+
const x1 = x + cr;
|
|
465
|
+
const x2 = x + w - cr;
|
|
466
|
+
const y1 = y + cr;
|
|
467
|
+
const y2 = y + h - cr;
|
|
468
|
+
appendPathMoveTo(path, x1, y);
|
|
469
|
+
appendPathLineTo(path, x2, y);
|
|
470
|
+
appendPathCubicCurveTo(path, x2 + k, y, x + w, y1 - k, x + w, y1);
|
|
471
|
+
appendPathLineTo(path, x + w, y2);
|
|
472
|
+
appendPathCubicCurveTo(path, x + w, y2 + k, x2 + k, y + h, x2, y + h);
|
|
473
|
+
appendPathLineTo(path, x1, y + h);
|
|
474
|
+
appendPathCubicCurveTo(path, x1 - k, y + h, x, y2 + k, x, y2);
|
|
475
|
+
appendPathLineTo(path, x, y1);
|
|
476
|
+
appendPathCubicCurveTo(path, x, y1 - k, x1 - k, y, x1, y);
|
|
477
|
+
}
|
|
478
|
+
function setRectangleToContoursBounds(out, contours) {
|
|
479
|
+
let minX = Infinity;
|
|
480
|
+
let minY = Infinity;
|
|
481
|
+
let maxX = -Infinity;
|
|
482
|
+
let maxY = -Infinity;
|
|
483
|
+
for (let c = 0; c < contours.length; c++) {
|
|
484
|
+
const contour = contours[c];
|
|
485
|
+
for (let i = 0; i < contour.length; i += 2) {
|
|
486
|
+
const x = contour[i];
|
|
487
|
+
const y = contour[i + 1];
|
|
488
|
+
if (x < minX)
|
|
489
|
+
minX = x;
|
|
490
|
+
if (x > maxX)
|
|
491
|
+
maxX = x;
|
|
492
|
+
if (y < minY)
|
|
493
|
+
minY = y;
|
|
494
|
+
if (y > maxY)
|
|
495
|
+
maxY = y;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (minX > maxX) {
|
|
499
|
+
out.x = 0;
|
|
500
|
+
out.y = 0;
|
|
501
|
+
out.width = 0;
|
|
502
|
+
out.height = 0;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
out.x = minX;
|
|
506
|
+
out.y = minY;
|
|
507
|
+
out.width = maxX - minX;
|
|
508
|
+
out.height = maxY - minY;
|
|
509
|
+
}
|
|
510
|
+
//# sourceMappingURL=clipRegion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipRegion.js","sourceRoot":"","sources":["../src/clipRegion.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/clip",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/geometry": "0.1.0",
|
|
31
|
+
"@flighthq/path": "0.1.0",
|
|
32
|
+
"@flighthq/types": "0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.3.0"
|
|
36
|
+
},
|
|
37
|
+
"description": "ClipRegion operations: hard geometric clip regions as plain data — constructors, composition, queries, transform, and a pool bracket",
|
|
38
|
+
"sideEffects": false
|
|
39
|
+
}
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import { createMatrix, createRectangle } from '@flighthq/geometry';
|
|
2
|
+
import { appendPathLineTo, appendPathMoveTo, createPath } from '@flighthq/path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
acquireClipRegion,
|
|
6
|
+
clipRegionContainsPoint,
|
|
7
|
+
clipRegionContainsRectangle,
|
|
8
|
+
clipRegionIntersectsRectangle,
|
|
9
|
+
clipRegionsEqual,
|
|
10
|
+
cloneClipRegion,
|
|
11
|
+
copyClipRegion,
|
|
12
|
+
createClipRegionFromCircle,
|
|
13
|
+
createClipRegionFromContours,
|
|
14
|
+
createClipRegionFromEllipse,
|
|
15
|
+
createClipRegionFromPath,
|
|
16
|
+
createClipRegionFromRectangle,
|
|
17
|
+
createClipRegionFromRoundedRectangle,
|
|
18
|
+
getClipRegionBounds,
|
|
19
|
+
intersectClipRegions,
|
|
20
|
+
invalidateClipRegion,
|
|
21
|
+
isClipRegionEmpty,
|
|
22
|
+
isClipRegionRectangular,
|
|
23
|
+
normalizeClipRegion,
|
|
24
|
+
releaseClipRegion,
|
|
25
|
+
setClipRegionToRectangle,
|
|
26
|
+
transformClipRegion,
|
|
27
|
+
unionClipRegions,
|
|
28
|
+
} from './clipRegion';
|
|
29
|
+
|
|
30
|
+
describe('acquireClipRegion', () => {
|
|
31
|
+
it('returns a valid empty rectangular clip region', () => {
|
|
32
|
+
const clip = acquireClipRegion();
|
|
33
|
+
expect(clip.contours).toBeNull();
|
|
34
|
+
expect(clip.rect.width).toBe(0);
|
|
35
|
+
expect(clip.rect.height).toBe(0);
|
|
36
|
+
expect(clip.winding).toBe('nonZero');
|
|
37
|
+
expect(clip.version).toBe(0);
|
|
38
|
+
releaseClipRegion(clip);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns a fresh object when the pool is empty', () => {
|
|
42
|
+
const a = acquireClipRegion();
|
|
43
|
+
const b = acquireClipRegion();
|
|
44
|
+
expect(a).not.toBe(b);
|
|
45
|
+
releaseClipRegion(a);
|
|
46
|
+
releaseClipRegion(b);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('reuses a released region from the pool', () => {
|
|
50
|
+
const first = acquireClipRegion();
|
|
51
|
+
releaseClipRegion(first);
|
|
52
|
+
const second = acquireClipRegion();
|
|
53
|
+
expect(second).toBe(first);
|
|
54
|
+
releaseClipRegion(second);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('resets the region state on reuse', () => {
|
|
58
|
+
const clip = acquireClipRegion();
|
|
59
|
+
clip.rect.x = 99;
|
|
60
|
+
clip.rect.width = 50;
|
|
61
|
+
clip.contours = [[1, 2, 3, 4]];
|
|
62
|
+
clip.version = 5;
|
|
63
|
+
releaseClipRegion(clip);
|
|
64
|
+
|
|
65
|
+
const reused = acquireClipRegion();
|
|
66
|
+
expect(reused.rect.x).toBe(0);
|
|
67
|
+
expect(reused.rect.width).toBe(0);
|
|
68
|
+
expect(reused.contours).toBeNull();
|
|
69
|
+
expect(reused.version).toBe(0);
|
|
70
|
+
releaseClipRegion(reused);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('clipRegionContainsPoint', () => {
|
|
75
|
+
it('returns true when the point is inside a rectangular clip', () => {
|
|
76
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
77
|
+
expect(clipRegionContainsPoint(clip, 5, 5)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns false when the point is outside a rectangular clip', () => {
|
|
81
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
82
|
+
expect(clipRegionContainsPoint(clip, 15, 5)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns true for a point inside a triangular contour clip (nonZero)', () => {
|
|
86
|
+
const path = createPath('nonZero');
|
|
87
|
+
appendPathMoveTo(path, 0, 0);
|
|
88
|
+
appendPathLineTo(path, 10, 0);
|
|
89
|
+
appendPathLineTo(path, 5, 10);
|
|
90
|
+
const clip = createClipRegionFromPath(path);
|
|
91
|
+
expect(clipRegionContainsPoint(clip, 5, 4)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns false for a point outside a triangular contour clip', () => {
|
|
95
|
+
const path = createPath('nonZero');
|
|
96
|
+
appendPathMoveTo(path, 0, 0);
|
|
97
|
+
appendPathLineTo(path, 10, 0);
|
|
98
|
+
appendPathLineTo(path, 5, 10);
|
|
99
|
+
const clip = createClipRegionFromPath(path);
|
|
100
|
+
expect(clipRegionContainsPoint(clip, 0, 9)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('clipRegionContainsRectangle', () => {
|
|
105
|
+
it('returns true when a rectangle is fully inside the clip', () => {
|
|
106
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 100, 100));
|
|
107
|
+
expect(clipRegionContainsRectangle(clip, createRectangle(10, 10, 20, 20))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('returns false when a rectangle extends outside the clip', () => {
|
|
111
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
112
|
+
expect(clipRegionContainsRectangle(clip, createRectangle(5, 5, 20, 20))).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('clipRegionIntersectsRectangle', () => {
|
|
117
|
+
it('returns true for an overlapping rectangle', () => {
|
|
118
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
119
|
+
expect(clipRegionIntersectsRectangle(clip, createRectangle(5, 5, 10, 10))).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('returns false for a disjoint rectangle', () => {
|
|
123
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
124
|
+
expect(clipRegionIntersectsRectangle(clip, createRectangle(20, 20, 5, 5))).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('clipRegionsEqual', () => {
|
|
129
|
+
it('returns true for two identical rectangular clips', () => {
|
|
130
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
131
|
+
const b = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
132
|
+
expect(clipRegionsEqual(a, b)).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns false when rects differ', () => {
|
|
136
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
137
|
+
const b = createClipRegionFromRectangle(createRectangle(0, 0, 20, 10));
|
|
138
|
+
expect(clipRegionsEqual(a, b)).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns true for two identical path clips', () => {
|
|
142
|
+
const path = createPath('nonZero');
|
|
143
|
+
appendPathMoveTo(path, 0, 0);
|
|
144
|
+
appendPathLineTo(path, 10, 0);
|
|
145
|
+
appendPathLineTo(path, 10, 10);
|
|
146
|
+
appendPathLineTo(path, 0, 10);
|
|
147
|
+
const a = createClipRegionFromPath(path, 1);
|
|
148
|
+
const b = createClipRegionFromPath(path, 1);
|
|
149
|
+
expect(clipRegionsEqual(a, b)).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('returns true for the same object reference', () => {
|
|
153
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
154
|
+
expect(clipRegionsEqual(a, a)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('returns false when one is rectangular and the other has contours', () => {
|
|
158
|
+
const rect = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
159
|
+
const path = createPath();
|
|
160
|
+
appendPathMoveTo(path, 0, 0);
|
|
161
|
+
appendPathLineTo(path, 10, 0);
|
|
162
|
+
appendPathLineTo(path, 10, 10);
|
|
163
|
+
const contour = createClipRegionFromPath(path);
|
|
164
|
+
expect(clipRegionsEqual(rect, contour)).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('cloneClipRegion', () => {
|
|
169
|
+
it('produces a deep copy of a rectangular clip', () => {
|
|
170
|
+
const original = createClipRegionFromRectangle(createRectangle(5, 6, 20, 30));
|
|
171
|
+
const clone = cloneClipRegion(original);
|
|
172
|
+
expect(clone).not.toBe(original);
|
|
173
|
+
expect(clone.rect).not.toBe(original.rect);
|
|
174
|
+
expect(clone.rect.x).toBe(5);
|
|
175
|
+
expect(clone.rect.y).toBe(6);
|
|
176
|
+
expect(clone.rect.width).toBe(20);
|
|
177
|
+
expect(clone.rect.height).toBe(30);
|
|
178
|
+
expect(clone.contours).toBeNull();
|
|
179
|
+
expect(clone.winding).toBe('nonZero');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('produces independent contour arrays', () => {
|
|
183
|
+
const path = createPath('evenOdd');
|
|
184
|
+
appendPathMoveTo(path, 0, 0);
|
|
185
|
+
appendPathLineTo(path, 10, 0);
|
|
186
|
+
appendPathLineTo(path, 10, 10);
|
|
187
|
+
appendPathLineTo(path, 0, 10);
|
|
188
|
+
const original = createClipRegionFromPath(path, 1);
|
|
189
|
+
const clone = cloneClipRegion(original);
|
|
190
|
+
expect(clone.contours).not.toBe(original.contours);
|
|
191
|
+
expect(clone.winding).toBe('evenOdd');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('copyClipRegion', () => {
|
|
196
|
+
it('overwrites out with source data and bumps the version', () => {
|
|
197
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
198
|
+
const src = createClipRegionFromRectangle(createRectangle(5, 6, 20, 30));
|
|
199
|
+
copyClipRegion(out, src);
|
|
200
|
+
expect(out.rect.x).toBe(5);
|
|
201
|
+
expect(out.rect.y).toBe(6);
|
|
202
|
+
expect(out.rect.width).toBe(20);
|
|
203
|
+
expect(out.rect.height).toBe(30);
|
|
204
|
+
expect(out.version).toBe(1);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('is a no-op when out === source', () => {
|
|
208
|
+
const clip = createClipRegionFromRectangle(createRectangle(1, 2, 3, 4));
|
|
209
|
+
const versionBefore = clip.version;
|
|
210
|
+
copyClipRegion(clip, clip);
|
|
211
|
+
expect(clip.version).toBe(versionBefore);
|
|
212
|
+
expect(clip.rect.x).toBe(1);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('createClipRegionFromCircle', () => {
|
|
217
|
+
it('creates a contour clip whose bounding box approximates the circle', () => {
|
|
218
|
+
const clip = createClipRegionFromCircle(50, 50, 20);
|
|
219
|
+
expect(clip.contours).not.toBeNull();
|
|
220
|
+
expect(clip.rect.x).toBeCloseTo(30, 0);
|
|
221
|
+
expect(clip.rect.y).toBeCloseTo(30, 0);
|
|
222
|
+
expect(clip.rect.width).toBeCloseTo(40, 0);
|
|
223
|
+
expect(clip.rect.height).toBeCloseTo(40, 0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('createClipRegionFromContours', () => {
|
|
228
|
+
it('deep-copies the provided contours and computes bounds', () => {
|
|
229
|
+
const contours = [[0, 0, 10, 0, 10, 10, 0, 10]];
|
|
230
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
231
|
+
expect(clip.contours).not.toBe(contours);
|
|
232
|
+
expect(clip.contours?.[0]).not.toBe(contours[0]);
|
|
233
|
+
expect(clip.contours).toEqual(contours);
|
|
234
|
+
expect(clip.rect.x).toBe(0);
|
|
235
|
+
expect(clip.rect.y).toBe(0);
|
|
236
|
+
expect(clip.rect.width).toBe(10);
|
|
237
|
+
expect(clip.rect.height).toBe(10);
|
|
238
|
+
expect(clip.winding).toBe('nonZero');
|
|
239
|
+
expect(clip.version).toBe(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('does not observe later edits to the caller array', () => {
|
|
243
|
+
const contours = [[0, 0, 10, 0, 10, 10, 0, 10]];
|
|
244
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
245
|
+
contours[0][2] = 999;
|
|
246
|
+
expect(clip.contours?.[0][2]).toBe(10);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('produces an empty region for an empty contours array', () => {
|
|
250
|
+
const clip = createClipRegionFromContours([], 'evenOdd');
|
|
251
|
+
expect(isClipRegionEmpty(clip)).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe('createClipRegionFromEllipse', () => {
|
|
256
|
+
it('creates a contour clip whose bounding box matches the source rectangle', () => {
|
|
257
|
+
const rect = createRectangle(10, 20, 40, 30);
|
|
258
|
+
const clip = createClipRegionFromEllipse(rect);
|
|
259
|
+
expect(clip.contours).not.toBeNull();
|
|
260
|
+
expect(clip.rect.x).toBeCloseTo(10, 0);
|
|
261
|
+
expect(clip.rect.y).toBeCloseTo(20, 0);
|
|
262
|
+
expect(clip.rect.width).toBeCloseTo(40, 0);
|
|
263
|
+
expect(clip.rect.height).toBeCloseTo(30, 0);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('createClipRegionFromPath', () => {
|
|
268
|
+
it('flattens to contours and bounds the region by their extent', () => {
|
|
269
|
+
const path = createPath();
|
|
270
|
+
appendPathMoveTo(path, 0, 0);
|
|
271
|
+
appendPathLineTo(path, 10, 0);
|
|
272
|
+
appendPathLineTo(path, 10, 10);
|
|
273
|
+
appendPathLineTo(path, 0, 10);
|
|
274
|
+
const clip = createClipRegionFromPath(path);
|
|
275
|
+
expect(clip.contours).not.toBeNull();
|
|
276
|
+
expect(clip.rect.x).toBeCloseTo(0);
|
|
277
|
+
expect(clip.rect.y).toBeCloseTo(0);
|
|
278
|
+
expect(clip.rect.width).toBeCloseTo(10);
|
|
279
|
+
expect(clip.rect.height).toBeCloseTo(10);
|
|
280
|
+
expect(clip.version).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('createClipRegionFromRectangle', () => {
|
|
285
|
+
it('copies the rectangle, leaves contours null, and starts at version 0', () => {
|
|
286
|
+
const rectangle = createRectangle(5, 6, 20, 30);
|
|
287
|
+
const clip = createClipRegionFromRectangle(rectangle);
|
|
288
|
+
expect(clip.contours).toBeNull();
|
|
289
|
+
expect(clip.rect).not.toBe(rectangle);
|
|
290
|
+
expect(clip.rect.x).toBe(5);
|
|
291
|
+
expect(clip.rect.y).toBe(6);
|
|
292
|
+
expect(clip.rect.width).toBe(20);
|
|
293
|
+
expect(clip.rect.height).toBe(30);
|
|
294
|
+
expect(clip.version).toBe(0);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('createClipRegionFromRoundedRectangle', () => {
|
|
299
|
+
it('creates a contour clip for positive radius', () => {
|
|
300
|
+
const rect = createRectangle(0, 0, 100, 60);
|
|
301
|
+
const clip = createClipRegionFromRoundedRectangle(rect, 10);
|
|
302
|
+
expect(clip.contours).not.toBeNull();
|
|
303
|
+
expect(clip.rect.x).toBeCloseTo(0, 0);
|
|
304
|
+
expect(clip.rect.y).toBeCloseTo(0, 0);
|
|
305
|
+
expect(clip.rect.width).toBeCloseTo(100, 0);
|
|
306
|
+
expect(clip.rect.height).toBeCloseTo(60, 0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('falls back to a rectangular clip when radius is 0', () => {
|
|
310
|
+
const rect = createRectangle(0, 0, 50, 50);
|
|
311
|
+
const clip = createClipRegionFromRoundedRectangle(rect, 0);
|
|
312
|
+
expect(clip.contours).toBeNull();
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('getClipRegionBounds', () => {
|
|
317
|
+
it('copies the clip rect into the out rectangle', () => {
|
|
318
|
+
const clip = createClipRegionFromRectangle(createRectangle(3, 4, 15, 25));
|
|
319
|
+
const out = createRectangle();
|
|
320
|
+
getClipRegionBounds(out, clip);
|
|
321
|
+
expect(out.x).toBe(3);
|
|
322
|
+
expect(out.y).toBe(4);
|
|
323
|
+
expect(out.width).toBe(15);
|
|
324
|
+
expect(out.height).toBe(25);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('intersectClipRegions', () => {
|
|
329
|
+
it('produces a scissor-eligible rect when both inputs are rectangular', () => {
|
|
330
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 20, 20));
|
|
331
|
+
const b = createClipRegionFromRectangle(createRectangle(10, 10, 20, 20));
|
|
332
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
333
|
+
intersectClipRegions(out, a, b);
|
|
334
|
+
expect(out.contours).toBeNull();
|
|
335
|
+
expect(out.rect.x).toBe(10);
|
|
336
|
+
expect(out.rect.y).toBe(10);
|
|
337
|
+
expect(out.rect.width).toBe(10);
|
|
338
|
+
expect(out.rect.height).toBe(10);
|
|
339
|
+
expect(out.version).toBe(1);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('produces an empty region for disjoint inputs', () => {
|
|
343
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
344
|
+
const b = createClipRegionFromRectangle(createRectangle(20, 20, 10, 10));
|
|
345
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
346
|
+
intersectClipRegions(out, a, b);
|
|
347
|
+
expect(isClipRegionEmpty(out)).toBe(true);
|
|
348
|
+
expect(out.contours).toBeNull();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('is alias-safe when out === a', () => {
|
|
352
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 20, 20));
|
|
353
|
+
const b = createClipRegionFromRectangle(createRectangle(10, 10, 20, 20));
|
|
354
|
+
intersectClipRegions(out, out, b);
|
|
355
|
+
expect(out.rect.x).toBe(10);
|
|
356
|
+
expect(out.rect.y).toBe(10);
|
|
357
|
+
expect(out.rect.width).toBe(10);
|
|
358
|
+
expect(out.rect.height).toBe(10);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('is alias-safe when out === b', () => {
|
|
362
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 20, 20));
|
|
363
|
+
const out = createClipRegionFromRectangle(createRectangle(10, 10, 20, 20));
|
|
364
|
+
intersectClipRegions(out, a, out);
|
|
365
|
+
expect(out.rect.x).toBe(10);
|
|
366
|
+
expect(out.rect.y).toBe(10);
|
|
367
|
+
expect(out.rect.width).toBe(10);
|
|
368
|
+
expect(out.rect.height).toBe(10);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('keeps contours when one input is a contour form', () => {
|
|
372
|
+
const path = createPath();
|
|
373
|
+
appendPathMoveTo(path, 0, 0);
|
|
374
|
+
appendPathLineTo(path, 20, 0);
|
|
375
|
+
appendPathLineTo(path, 20, 20);
|
|
376
|
+
appendPathLineTo(path, 0, 20);
|
|
377
|
+
const contourClip = createClipRegionFromPath(path);
|
|
378
|
+
const rectClip = createClipRegionFromRectangle(createRectangle(10, 10, 20, 20));
|
|
379
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
380
|
+
intersectClipRegions(out, contourClip, rectClip);
|
|
381
|
+
expect(out.contours).not.toBeNull();
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe('invalidateClipRegion', () => {
|
|
386
|
+
it('increments the version and wraps with >>> 0', () => {
|
|
387
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
388
|
+
invalidateClipRegion(clip);
|
|
389
|
+
expect(clip.version).toBe(1);
|
|
390
|
+
clip.version = 0xffffffff;
|
|
391
|
+
invalidateClipRegion(clip);
|
|
392
|
+
expect(clip.version).toBe(0);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe('isClipRegionEmpty', () => {
|
|
397
|
+
it('returns true for a zero-size rectangular clip', () => {
|
|
398
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 0, 0));
|
|
399
|
+
expect(isClipRegionEmpty(clip)).toBe(true);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('returns false for a non-empty rectangular clip', () => {
|
|
403
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
404
|
+
expect(isClipRegionEmpty(clip)).toBe(false);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('returns true for a contour clip with no contours', () => {
|
|
408
|
+
const clip = createClipRegionFromContours([], 'nonZero');
|
|
409
|
+
expect(isClipRegionEmpty(clip)).toBe(true);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('isClipRegionRectangular', () => {
|
|
414
|
+
it('returns true for a rectangular clip', () => {
|
|
415
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
416
|
+
expect(isClipRegionRectangular(clip)).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('returns false for a path clip', () => {
|
|
420
|
+
const path = createPath();
|
|
421
|
+
appendPathMoveTo(path, 0, 0);
|
|
422
|
+
appendPathLineTo(path, 10, 0);
|
|
423
|
+
appendPathLineTo(path, 10, 10);
|
|
424
|
+
const clip = createClipRegionFromPath(path);
|
|
425
|
+
expect(isClipRegionRectangular(clip)).toBe(false);
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
describe('normalizeClipRegion', () => {
|
|
430
|
+
it('copies a rectangular clip unchanged and bumps version', () => {
|
|
431
|
+
const clip = createClipRegionFromRectangle(createRectangle(5, 10, 20, 30));
|
|
432
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
433
|
+
normalizeClipRegion(out, clip);
|
|
434
|
+
expect(out.contours).toBeNull();
|
|
435
|
+
expect(out.rect.x).toBe(5);
|
|
436
|
+
expect(out.rect.y).toBe(10);
|
|
437
|
+
expect(out.rect.width).toBe(20);
|
|
438
|
+
expect(out.rect.height).toBe(30);
|
|
439
|
+
expect(out.version).toBe(1);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('promotes a 4-point axis-aligned quad contour back to rect form', () => {
|
|
443
|
+
// 4-corner axis-aligned rectangle stored as contours (e.g. from transformClipRegion with identity)
|
|
444
|
+
const contours = [[0, 0, 10, 0, 10, 10, 0, 10]];
|
|
445
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
446
|
+
expect(clip.contours).not.toBeNull();
|
|
447
|
+
|
|
448
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
449
|
+
normalizeClipRegion(out, clip);
|
|
450
|
+
|
|
451
|
+
expect(out.contours).toBeNull();
|
|
452
|
+
expect(out.rect.x).toBeCloseTo(0);
|
|
453
|
+
expect(out.rect.y).toBeCloseTo(0);
|
|
454
|
+
expect(out.rect.width).toBeCloseTo(10);
|
|
455
|
+
expect(out.rect.height).toBeCloseTo(10);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('preserves contours that are not axis-aligned rectangles', () => {
|
|
459
|
+
// Triangle: 3 points (6 coords) — not a rectangle.
|
|
460
|
+
const contours = [[0, 0, 10, 0, 5, 10]];
|
|
461
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
462
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
463
|
+
normalizeClipRegion(out, clip);
|
|
464
|
+
expect(out.contours).not.toBeNull();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('preserves contours when more than one sub-path is present', () => {
|
|
468
|
+
const contours = [
|
|
469
|
+
[0, 0, 10, 0, 10, 10, 0, 10],
|
|
470
|
+
[20, 20, 30, 20, 30, 30, 20, 30],
|
|
471
|
+
];
|
|
472
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
473
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
474
|
+
normalizeClipRegion(out, clip);
|
|
475
|
+
// Two contours → cannot detect as a simple rect.
|
|
476
|
+
expect(out.contours).not.toBeNull();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('normalizes a quad produced by transformClipRegion with a 90-degree rotation back to rect', () => {
|
|
480
|
+
// A 10x10 rect rotated 90 degrees produces a quad with corners at exactly axis-aligned positions.
|
|
481
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
482
|
+
const rotated = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
483
|
+
const matrix = { a: 0, b: 1, c: -1, d: 0, tx: 10, ty: 0 }; // 90-degree rotation
|
|
484
|
+
transformClipRegion(rotated, clip, matrix);
|
|
485
|
+
expect(rotated.contours).not.toBeNull();
|
|
486
|
+
|
|
487
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
488
|
+
normalizeClipRegion(out, rotated);
|
|
489
|
+
// A 90-degree-rotated rectangle remains an axis-aligned rectangle, so it normalizes.
|
|
490
|
+
expect(out.contours).toBeNull();
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe('releaseClipRegion', () => {
|
|
495
|
+
it('returns a region to the pool for reuse', () => {
|
|
496
|
+
const clip = acquireClipRegion();
|
|
497
|
+
releaseClipRegion(clip);
|
|
498
|
+
const reused = acquireClipRegion();
|
|
499
|
+
expect(reused).toBe(clip);
|
|
500
|
+
releaseClipRegion(reused);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('setClipRegionToRectangle', () => {
|
|
505
|
+
it('retargets a contour clip to a rectangle form and bumps version', () => {
|
|
506
|
+
const path = createPath();
|
|
507
|
+
appendPathMoveTo(path, 0, 0);
|
|
508
|
+
appendPathLineTo(path, 10, 0);
|
|
509
|
+
appendPathLineTo(path, 10, 10);
|
|
510
|
+
const clip = createClipRegionFromPath(path);
|
|
511
|
+
setClipRegionToRectangle(clip, createRectangle(5, 5, 20, 30));
|
|
512
|
+
expect(clip.contours).toBeNull();
|
|
513
|
+
expect(clip.rect.x).toBe(5);
|
|
514
|
+
expect(clip.rect.y).toBe(5);
|
|
515
|
+
expect(clip.rect.width).toBe(20);
|
|
516
|
+
expect(clip.rect.height).toBe(30);
|
|
517
|
+
expect(clip.version).toBe(1);
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
describe('transformClipRegion', () => {
|
|
522
|
+
it('keeps a rectangular clip scissor-eligible for axis-aligned transforms', () => {
|
|
523
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
524
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
525
|
+
// Scale by 2, translate by 5, 5
|
|
526
|
+
const matrix = createMatrix(2, 0, 0, 2, 5, 5);
|
|
527
|
+
transformClipRegion(out, clip, matrix);
|
|
528
|
+
expect(out.contours).toBeNull();
|
|
529
|
+
expect(out.rect.x).toBeCloseTo(5);
|
|
530
|
+
expect(out.rect.y).toBeCloseTo(5);
|
|
531
|
+
expect(out.rect.width).toBeCloseTo(20);
|
|
532
|
+
expect(out.rect.height).toBeCloseTo(20);
|
|
533
|
+
expect(out.version).toBe(1);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('promotes a rectangle to a quad contour when the matrix rotates', () => {
|
|
537
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
538
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
539
|
+
// 45 degree rotation matrix
|
|
540
|
+
const cos45 = Math.cos(Math.PI / 4);
|
|
541
|
+
const sin45 = Math.sin(Math.PI / 4);
|
|
542
|
+
const matrix = createMatrix(cos45, sin45, -sin45, cos45, 0, 0);
|
|
543
|
+
transformClipRegion(out, clip, matrix);
|
|
544
|
+
expect(out.contours).not.toBeNull();
|
|
545
|
+
expect(out.contours!.length).toBe(1);
|
|
546
|
+
expect(out.contours![0].length).toBe(8); // 4 corner pairs
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('transforms contour points correctly', () => {
|
|
550
|
+
const contours = [[0, 0, 10, 0, 10, 10, 0, 10]];
|
|
551
|
+
const clip = createClipRegionFromContours(contours, 'nonZero');
|
|
552
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
553
|
+
// Translate by 5, 5
|
|
554
|
+
const matrix = createMatrix(1, 0, 0, 1, 5, 5);
|
|
555
|
+
transformClipRegion(out, clip, matrix);
|
|
556
|
+
expect(out.contours).not.toBeNull();
|
|
557
|
+
expect(out.rect.x).toBeCloseTo(5);
|
|
558
|
+
expect(out.rect.y).toBeCloseTo(5);
|
|
559
|
+
expect(out.rect.width).toBeCloseTo(10);
|
|
560
|
+
expect(out.rect.height).toBeCloseTo(10);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('is alias-safe when out === clip (rect form, axis-aligned)', () => {
|
|
564
|
+
const clip = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
565
|
+
const matrix = createMatrix(1, 0, 0, 1, 5, 5);
|
|
566
|
+
transformClipRegion(clip, clip, matrix);
|
|
567
|
+
expect(clip.rect.x).toBeCloseTo(5);
|
|
568
|
+
expect(clip.rect.y).toBeCloseTo(5);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
describe('unionClipRegions', () => {
|
|
573
|
+
it('produces the bounding union of two rectangular clips', () => {
|
|
574
|
+
const a = createClipRegionFromRectangle(createRectangle(0, 0, 10, 10));
|
|
575
|
+
const b = createClipRegionFromRectangle(createRectangle(5, 5, 15, 15));
|
|
576
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
577
|
+
unionClipRegions(out, a, b);
|
|
578
|
+
expect(out.contours).toBeNull();
|
|
579
|
+
expect(out.rect.x).toBe(0);
|
|
580
|
+
expect(out.rect.y).toBe(0);
|
|
581
|
+
expect(out.rect.width).toBe(20);
|
|
582
|
+
expect(out.rect.height).toBe(20);
|
|
583
|
+
expect(out.version).toBe(1);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('preserves contours from the richer input', () => {
|
|
587
|
+
const path = createPath();
|
|
588
|
+
appendPathMoveTo(path, 0, 0);
|
|
589
|
+
appendPathLineTo(path, 10, 0);
|
|
590
|
+
appendPathLineTo(path, 10, 10);
|
|
591
|
+
appendPathLineTo(path, 0, 10);
|
|
592
|
+
const contourClip = createClipRegionFromPath(path);
|
|
593
|
+
const rectClip = createClipRegionFromRectangle(createRectangle(5, 5, 20, 20));
|
|
594
|
+
const out = createClipRegionFromRectangle(createRectangle(0, 0, 1, 1));
|
|
595
|
+
unionClipRegions(out, contourClip, rectClip);
|
|
596
|
+
expect(out.contours).not.toBeNull();
|
|
597
|
+
});
|
|
598
|
+
});
|