@embedpdf/core-geometry 3.0.0-next.13 → 3.0.0-next.15
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/index.cjs +182 -311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -184
- package/dist/index.d.ts +135 -184
- package/dist/index.js +172 -301
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,11 +10,11 @@ function boundsOfRects(rects) {
|
|
|
10
10
|
let y1 = Infinity;
|
|
11
11
|
let x2 = -Infinity;
|
|
12
12
|
let y2 = -Infinity;
|
|
13
|
-
for (const
|
|
14
|
-
x1 = Math.min(x1,
|
|
15
|
-
y1 = Math.min(y1,
|
|
16
|
-
x2 = Math.max(x2,
|
|
17
|
-
y2 = Math.max(y2,
|
|
13
|
+
for (const rect of rects) {
|
|
14
|
+
x1 = Math.min(x1, rect.x);
|
|
15
|
+
y1 = Math.min(y1, rect.y);
|
|
16
|
+
x2 = Math.max(x2, rect.x + rect.width);
|
|
17
|
+
y2 = Math.max(y2, rect.y + rect.height);
|
|
18
18
|
}
|
|
19
19
|
return {
|
|
20
20
|
x: x1,
|
|
@@ -29,14 +29,48 @@ function boundsOfRects(rects) {
|
|
|
29
29
|
* without a null branch. Space-agnostic like {@link boundsOfRects}: view-px
|
|
30
30
|
* viewport ∩ footprint, page-point visibility ∩ tile footprints, ….
|
|
31
31
|
*/
|
|
32
|
-
function intersectRects(
|
|
33
|
-
const x = Math.max(
|
|
34
|
-
const y = Math.max(
|
|
32
|
+
function intersectRects(left, right) {
|
|
33
|
+
const x = Math.max(left.x, right.x);
|
|
34
|
+
const y = Math.max(left.y, right.y);
|
|
35
35
|
return {
|
|
36
36
|
x,
|
|
37
37
|
y,
|
|
38
|
-
width: Math.max(0, Math.min(
|
|
39
|
-
height: Math.max(0, Math.min(
|
|
38
|
+
width: Math.max(0, Math.min(left.x + left.width, right.x + right.width) - x),
|
|
39
|
+
height: Math.max(0, Math.min(left.y + left.height, right.y + right.height) - y)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** The axis-aligned rect spanned by two corners, in any order — a drag box. */
|
|
43
|
+
function rectFromCorners(from, to) {
|
|
44
|
+
return {
|
|
45
|
+
x: Math.min(from.x, to.x),
|
|
46
|
+
y: Math.min(from.y, to.y),
|
|
47
|
+
width: Math.abs(to.x - from.x),
|
|
48
|
+
height: Math.abs(to.y - from.y)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Whether a rect contains a point, edges inclusive. Space-agnostic. */
|
|
52
|
+
function rectContains(rect, point) {
|
|
53
|
+
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
54
|
+
}
|
|
55
|
+
/** Positive-area overlap of two rects — touching edges do not count. */
|
|
56
|
+
function rectsOverlap(left, right) {
|
|
57
|
+
const overlap = intersectRects(left, right);
|
|
58
|
+
return overlap.width > 0 && overlap.height > 0;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The largest rect of `content`'s proportions inside `box`, centred in it:
|
|
62
|
+
* CSS `object-fit: contain`. `box` itself when `content` has no area.
|
|
63
|
+
*/
|
|
64
|
+
function containedRect(content, box) {
|
|
65
|
+
if (content.width <= 0 || content.height <= 0) return box;
|
|
66
|
+
const scale = Math.min(box.width / content.width, box.height / content.height);
|
|
67
|
+
const width = content.width * scale;
|
|
68
|
+
const height = content.height * scale;
|
|
69
|
+
return {
|
|
70
|
+
x: box.x + (box.width - width) / 2,
|
|
71
|
+
y: box.y + (box.height - height) / 2,
|
|
72
|
+
width,
|
|
73
|
+
height
|
|
40
74
|
};
|
|
41
75
|
}
|
|
42
76
|
const NO_FRAME = {
|
|
@@ -50,16 +84,16 @@ function isQuarterTurn(rotation) {
|
|
|
50
84
|
return rotation === 90 || rotation === 270;
|
|
51
85
|
}
|
|
52
86
|
/**
|
|
53
|
-
* Compose two quarter-turn rotations (mod 360).
|
|
54
|
-
* page's
|
|
87
|
+
* Compose two quarter-turn rotations (mod 360). The way the shell resolves a
|
|
88
|
+
* page's total display rotation — `addRotations(page /Rotate, view rotation)`
|
|
55
89
|
* — so the wrap arithmetic is never hand-written (and drift) per call site.
|
|
56
90
|
*/
|
|
57
|
-
function addRotations(
|
|
58
|
-
return (
|
|
91
|
+
function addRotations(left, right) {
|
|
92
|
+
return (left + right) % 360;
|
|
59
93
|
}
|
|
60
94
|
/**
|
|
61
95
|
* Snap a view-px value to the device pixel grid: round to a whole device pixel,
|
|
62
|
-
* back to view px. Used for a page's screen
|
|
96
|
+
* back to view px. Used for a page's screen position so a CSS-rotated page lands
|
|
63
97
|
* on the grid (no sub-pixel anti-aliased fringe). The shell never hand-rounds.
|
|
64
98
|
*/
|
|
65
99
|
function snapToDevice(value, dpr) {
|
|
@@ -76,13 +110,13 @@ function displaySize(content, rotation) {
|
|
|
76
110
|
};
|
|
77
111
|
}
|
|
78
112
|
/**
|
|
79
|
-
* The engine's `kind:'width'` height rule, replicated
|
|
113
|
+
* The engine's `kind:'width'` height rule, replicated exactly so the viewer can
|
|
80
114
|
* predict the rendered bitmap's height up front — the page box is sized to it,
|
|
81
115
|
* so the box matches the bitmap with no reflow and no 1px seam.
|
|
82
116
|
*
|
|
83
117
|
* Mirrors `PageRenderReader.resolveDeviceSize` (engine-services): we render the
|
|
84
|
-
* page
|
|
85
|
-
* here — `pageSize` is the page's own points.
|
|
118
|
+
* page un-rotated (CSS applies the rotation), so there is no width/height swap
|
|
119
|
+
* here — `pageSize` is the page's own points. Must stay in lockstep with that
|
|
86
120
|
* function; the round-trip test in the render plugin asserts they agree.
|
|
87
121
|
*/
|
|
88
122
|
function deviceHeightForWidth(pageSize, deviceWidth) {
|
|
@@ -90,7 +124,7 @@ function deviceHeightForWidth(pageSize, deviceWidth) {
|
|
|
90
124
|
}
|
|
91
125
|
/**
|
|
92
126
|
* Build a page's transform from its intrinsic size + the platform/view scale.
|
|
93
|
-
* `scale` is
|
|
127
|
+
* `scale` is view px per PDF point (the shell composes it: `viewUnitsPerPoint ×
|
|
94
128
|
* contentScale × zoom`); `dpr` is device px per view px. Device dimensions snap
|
|
95
129
|
* to the grid (crisp, no rotation fringe) and follow the engine's width rule
|
|
96
130
|
* (box == bitmap, no reflow).
|
|
@@ -118,13 +152,13 @@ function pageTransform(input) {
|
|
|
118
152
|
];
|
|
119
153
|
const viewMat = rotateScaleMatrix(viewScale, content.width, content.height, rotation);
|
|
120
154
|
const invMat = invert(viewMat);
|
|
121
|
-
const toPixels = (
|
|
155
|
+
const toPixels = (point) => applyPoint(contentMat, point);
|
|
122
156
|
const contentInv = invert(contentMat);
|
|
123
|
-
const fromPixels = (
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
157
|
+
const fromPixels = (point) => applyPoint(contentInv, point);
|
|
158
|
+
const pageToView = (point) => applyPoint(viewMat, point);
|
|
159
|
+
const viewToPage = (point) => applyPoint(invMat, point);
|
|
160
|
+
const pageToViewRect = (rect) => applyRect(viewMat, rect);
|
|
161
|
+
const viewToPageRect = (rect) => applyRect(invMat, rect);
|
|
128
162
|
const cssMatrix = matrixToCss(viewMat);
|
|
129
163
|
return {
|
|
130
164
|
rotation,
|
|
@@ -140,13 +174,22 @@ function pageTransform(input) {
|
|
|
140
174
|
zoom,
|
|
141
175
|
toPixels,
|
|
142
176
|
fromPixels,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
177
|
+
pageToView,
|
|
178
|
+
pageToViewRect,
|
|
179
|
+
viewToPage,
|
|
180
|
+
viewToPageRect,
|
|
147
181
|
cssMatrix
|
|
148
182
|
};
|
|
149
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Whether two transforms place things on the page the same: the same scale, zoom and turn, and
|
|
186
|
+
* the same content box. A Stage builds a new transform on every frame, also for a plain scroll,
|
|
187
|
+
* which moves the page but changes nothing inside it; a layer that compares with this redraws
|
|
188
|
+
* only when what it draws in the page's pixels moved.
|
|
189
|
+
*/
|
|
190
|
+
function samePagePlacement(left, right) {
|
|
191
|
+
return left.viewScale === right.viewScale && left.zoom === right.zoom && left.rotation === right.rotation && left.contentWidth === right.contentWidth && left.contentHeight === right.contentHeight;
|
|
192
|
+
}
|
|
150
193
|
/** The identity transform within a single space. */
|
|
151
194
|
function identity() {
|
|
152
195
|
return [
|
|
@@ -159,13 +202,13 @@ function identity() {
|
|
|
159
202
|
];
|
|
160
203
|
}
|
|
161
204
|
/**
|
|
162
|
-
* Compose two matrices: `compose(
|
|
163
|
-
* product `
|
|
164
|
-
* rejects composing mismatched hops.
|
|
205
|
+
* Compose two matrices: `compose(matrix, inner)` applies `inner` first, then
|
|
206
|
+
* `matrix` (matrix product `matrix · inner`). The shared middle space `B` must
|
|
207
|
+
* line up, so the type system rejects composing mismatched hops.
|
|
165
208
|
*/
|
|
166
|
-
function compose(
|
|
167
|
-
const [a, b, c, d, e, f] =
|
|
168
|
-
const [a2, b2, c2, d2, e2, f2] =
|
|
209
|
+
function compose(matrix, inner) {
|
|
210
|
+
const [a, b, c, d, e, f] = matrix;
|
|
211
|
+
const [a2, b2, c2, d2, e2, f2] = inner;
|
|
169
212
|
return [
|
|
170
213
|
a * a2 + c * b2,
|
|
171
214
|
b * a2 + d * b2,
|
|
@@ -175,9 +218,9 @@ function compose(m, n) {
|
|
|
175
218
|
b * e2 + d * f2 + f
|
|
176
219
|
];
|
|
177
220
|
}
|
|
178
|
-
/** Invert a transform — `invert(
|
|
179
|
-
function invert(
|
|
180
|
-
const [a, b, c, d, e, f] =
|
|
221
|
+
/** Invert a transform — `invert(pageToView)` is `viewToPage`, i.e. hit-testing for free. */
|
|
222
|
+
function invert(matrix) {
|
|
223
|
+
const [a, b, c, d, e, f] = matrix;
|
|
181
224
|
const det = a * d - b * c;
|
|
182
225
|
if (det === 0) throw new Error("Mat2D.invert: non-invertible matrix (det = 0)");
|
|
183
226
|
const ia = d / det;
|
|
@@ -194,35 +237,35 @@ function invert(m) {
|
|
|
194
237
|
];
|
|
195
238
|
}
|
|
196
239
|
/** Apply a transform to a point. */
|
|
197
|
-
function applyPoint(
|
|
198
|
-
const [a, b, c, d, e, f] =
|
|
240
|
+
function applyPoint(matrix, point) {
|
|
241
|
+
const [a, b, c, d, e, f] = matrix;
|
|
199
242
|
return {
|
|
200
|
-
x: a *
|
|
201
|
-
y: b *
|
|
243
|
+
x: a * point.x + c * point.y + e,
|
|
244
|
+
y: b * point.x + d * point.y + f
|
|
202
245
|
};
|
|
203
246
|
}
|
|
204
247
|
/**
|
|
205
248
|
* Apply a transform to a rect, returning the axis-aligned bounding box of the
|
|
206
|
-
* four transformed corners.
|
|
249
|
+
* four transformed corners. Exact for translate/scale/quarter-turn (every page
|
|
207
250
|
* transform `pageGeometry` produces); lossy under skew — use {@link applyQuad}
|
|
208
251
|
* for content that can be rotated off-axis.
|
|
209
252
|
*/
|
|
210
|
-
function applyRect(
|
|
211
|
-
const c0 = applyPoint(
|
|
212
|
-
x:
|
|
213
|
-
y:
|
|
253
|
+
function applyRect(matrix, rect) {
|
|
254
|
+
const c0 = applyPoint(matrix, {
|
|
255
|
+
x: rect.x,
|
|
256
|
+
y: rect.y
|
|
214
257
|
});
|
|
215
|
-
const c1 = applyPoint(
|
|
216
|
-
x:
|
|
217
|
-
y:
|
|
258
|
+
const c1 = applyPoint(matrix, {
|
|
259
|
+
x: rect.x + rect.width,
|
|
260
|
+
y: rect.y
|
|
218
261
|
});
|
|
219
|
-
const c2 = applyPoint(
|
|
220
|
-
x:
|
|
221
|
-
y:
|
|
262
|
+
const c2 = applyPoint(matrix, {
|
|
263
|
+
x: rect.x,
|
|
264
|
+
y: rect.y + rect.height
|
|
222
265
|
});
|
|
223
|
-
const c3 = applyPoint(
|
|
224
|
-
x:
|
|
225
|
-
y:
|
|
266
|
+
const c3 = applyPoint(matrix, {
|
|
267
|
+
x: rect.x + rect.width,
|
|
268
|
+
y: rect.y + rect.height
|
|
226
269
|
});
|
|
227
270
|
const xs = [
|
|
228
271
|
c0.x,
|
|
@@ -245,22 +288,22 @@ function applyRect(m, r) {
|
|
|
245
288
|
height: Math.max(...ys) - minY
|
|
246
289
|
};
|
|
247
290
|
}
|
|
248
|
-
/** Apply a transform to a quad
|
|
249
|
-
function applyQuad(
|
|
291
|
+
/** Apply a transform to a quad: each corner moves and keeps its name. */
|
|
292
|
+
function applyQuad(matrix, quad) {
|
|
250
293
|
return {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
294
|
+
upperLeft: applyPoint(matrix, quad.upperLeft),
|
|
295
|
+
upperRight: applyPoint(matrix, quad.upperRight),
|
|
296
|
+
lowerLeft: applyPoint(matrix, quad.lowerLeft),
|
|
297
|
+
lowerRight: applyPoint(matrix, quad.lowerRight)
|
|
255
298
|
};
|
|
256
299
|
}
|
|
257
300
|
/**
|
|
258
|
-
* A `Mat2D`
|
|
301
|
+
* A `Mat2D` is a CSS `matrix()` — the six numbers are identical. Drop the result
|
|
259
302
|
* on a footprint-space layer (`transform`, `transform-origin: 0 0`) and place
|
|
260
303
|
* children in the source space; the rotation + scale are handled for free.
|
|
261
304
|
*/
|
|
262
|
-
function matrixToCss(
|
|
263
|
-
const [a, b, c, d, e, f] =
|
|
305
|
+
function matrixToCss(matrix) {
|
|
306
|
+
const [a, b, c, d, e, f] = matrix;
|
|
264
307
|
return `matrix(${a}, ${b}, ${c}, ${d}, ${e}, ${f})`;
|
|
265
308
|
}
|
|
266
309
|
/** Translate within a space. */
|
|
@@ -285,40 +328,40 @@ function scale(sx, sy) {
|
|
|
285
328
|
0
|
|
286
329
|
];
|
|
287
330
|
}
|
|
288
|
-
/** Rotate about the space origin (radians; clockwise in y-down
|
|
331
|
+
/** Rotate about the space origin (radians; clockwise in y-down page space). */
|
|
289
332
|
function rotate(rad) {
|
|
290
|
-
const
|
|
291
|
-
const
|
|
333
|
+
const cosine = Math.cos(rad);
|
|
334
|
+
const sine = Math.sin(rad);
|
|
292
335
|
return [
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
-
|
|
296
|
-
|
|
336
|
+
cosine,
|
|
337
|
+
sine,
|
|
338
|
+
-sine,
|
|
339
|
+
cosine,
|
|
297
340
|
0,
|
|
298
341
|
0
|
|
299
342
|
];
|
|
300
343
|
}
|
|
301
344
|
/** Rotate about an arbitrary pivot: `T(c) · R(rad) · T(-c)`. */
|
|
302
|
-
function rotateAbout(
|
|
303
|
-
return compose(translate(
|
|
345
|
+
function rotateAbout(point, rad) {
|
|
346
|
+
return compose(translate(point.x, point.y), compose(rotate(rad), translate(-point.x, -point.y)));
|
|
304
347
|
}
|
|
305
348
|
/** Scale about an arbitrary anchor: `T(a) · S(sx, sy) · T(-a)`. */
|
|
306
|
-
function scaleAbout(
|
|
307
|
-
return compose(translate(
|
|
349
|
+
function scaleAbout(point, sx, sy) {
|
|
350
|
+
return compose(translate(point.x, point.y), compose(scale(sx, sy), translate(-point.x, -point.y)));
|
|
308
351
|
}
|
|
309
352
|
/** The rotation angle (radians) encoded in a matrix — `atan2(b, a)`. Positive is
|
|
310
|
-
* clockwise in y-down
|
|
311
|
-
function angleOf(
|
|
312
|
-
return Math.atan2(
|
|
353
|
+
* clockwise in y-down page space (the inverse of {@link rotate}). */
|
|
354
|
+
function angleOf(matrix) {
|
|
355
|
+
return Math.atan2(matrix[1], matrix[0]);
|
|
313
356
|
}
|
|
314
357
|
/**
|
|
315
|
-
* The page's scale + integer quarter-turn as a `Mat2D`: a
|
|
316
|
-
* its place in the rotated
|
|
317
|
-
* point; `boxW`/`boxH` are the
|
|
358
|
+
* The page's scale + integer quarter-turn as a `Mat2D`: a page point maps to
|
|
359
|
+
* its place in the rotated display box. `scale` is output units per content
|
|
360
|
+
* point; `boxW`/`boxH` are the un-rotated content's extents in those same output
|
|
318
361
|
* units, and the offsets keep the footprint in the box's positive quadrant
|
|
319
362
|
* (origin at the box top-left).
|
|
320
363
|
*
|
|
321
|
-
*
|
|
364
|
+
* The single encoding of the quarter-turn — `pageTransform` (view px),
|
|
322
365
|
* `pageGeometry` (the `content→view` hop), and `pageToWorld` (world units) all
|
|
323
366
|
* build on this one function, so the rotation can never drift between them.
|
|
324
367
|
*/
|
|
@@ -358,112 +401,75 @@ function rotateScaleMatrix(scale, boxW, boxH, rotation) {
|
|
|
358
401
|
];
|
|
359
402
|
}
|
|
360
403
|
}
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
* origin = crop top-left, unscaled points). THE single encoding of the engine→
|
|
364
|
-
* content y-flip + crop offset: `pageGeometry` composes its `pdfToContent` from
|
|
365
|
-
* this, and pure annotation geometry (content-space editing) reuses it too, so
|
|
366
|
-
* the rule can never be hand-written twice and drift.
|
|
367
|
-
*/
|
|
368
|
-
function pdfToContentMatrix(crop) {
|
|
369
|
-
return [
|
|
370
|
-
1,
|
|
371
|
-
0,
|
|
372
|
-
0,
|
|
373
|
-
-1,
|
|
374
|
-
-crop.left,
|
|
375
|
-
crop.top
|
|
376
|
-
];
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Build a page's space matrices from its crop box, rotation, and `userUnit` plus
|
|
380
|
-
* the viewer `zoom`. This is the ONE function that knows the y-flip, crop origin,
|
|
381
|
-
* `userUnit`, and rotation; everything else composes from its output. The engine
|
|
382
|
-
* never emits these — they bake in viewer state (zoom, later scroll/dpr) — so the
|
|
383
|
-
* boundary stays clean: engine owns `Pdf*` truth, the viewer composes the rest.
|
|
384
|
-
*/
|
|
385
|
-
function pageGeometry(input, zoom) {
|
|
386
|
-
const { crop, rotation, userUnit } = input;
|
|
387
|
-
const s = zoom * userUnit;
|
|
388
|
-
const pdfToContent = pdfToContentMatrix(crop);
|
|
389
|
-
const contentToView = rotateScaleMatrix(s, (crop.right - crop.left) * s, (crop.top - crop.bottom) * s, rotation);
|
|
390
|
-
const pdfToView = compose(contentToView, pdfToContent);
|
|
404
|
+
/** An upright quad over a rect (y-down: upper = smaller y). */
|
|
405
|
+
function quadFromRect(rect) {
|
|
391
406
|
return {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
viewToPdf: invert(pdfToView)
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
/** Axis-aligned TextQuad over a rect (y-down: upper = smaller y). */
|
|
399
|
-
function textQuadFromRect(r) {
|
|
400
|
-
return {
|
|
401
|
-
upperStart: {
|
|
402
|
-
x: r.x,
|
|
403
|
-
y: r.y
|
|
407
|
+
upperLeft: {
|
|
408
|
+
x: rect.x,
|
|
409
|
+
y: rect.y
|
|
404
410
|
},
|
|
405
|
-
|
|
406
|
-
x:
|
|
407
|
-
y:
|
|
411
|
+
upperRight: {
|
|
412
|
+
x: rect.x + rect.width,
|
|
413
|
+
y: rect.y
|
|
408
414
|
},
|
|
409
|
-
|
|
410
|
-
x:
|
|
411
|
-
y:
|
|
415
|
+
lowerLeft: {
|
|
416
|
+
x: rect.x,
|
|
417
|
+
y: rect.y + rect.height
|
|
412
418
|
},
|
|
413
|
-
|
|
414
|
-
x:
|
|
415
|
-
y:
|
|
419
|
+
lowerRight: {
|
|
420
|
+
x: rect.x + rect.width,
|
|
421
|
+
y: rect.y + rect.height
|
|
416
422
|
}
|
|
417
423
|
};
|
|
418
424
|
}
|
|
419
|
-
/** The four corners
|
|
420
|
-
function
|
|
425
|
+
/** The four corners: upper-left, upper-right, lower-left, lower-right. */
|
|
426
|
+
function quadCorners(quad) {
|
|
421
427
|
return [
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
428
|
+
quad.upperLeft,
|
|
429
|
+
quad.upperRight,
|
|
430
|
+
quad.lowerLeft,
|
|
431
|
+
quad.lowerRight
|
|
426
432
|
];
|
|
427
433
|
}
|
|
428
|
-
/** The corners as a polygon
|
|
429
|
-
function
|
|
434
|
+
/** The corners as a polygon ring: upper-left, upper-right, lower-right, lower-left. */
|
|
435
|
+
function quadRing(quad) {
|
|
430
436
|
return [
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
437
|
+
quad.upperLeft,
|
|
438
|
+
quad.upperRight,
|
|
439
|
+
quad.lowerRight,
|
|
440
|
+
quad.lowerLeft
|
|
435
441
|
];
|
|
436
442
|
}
|
|
437
443
|
/**
|
|
438
|
-
* One
|
|
439
|
-
*
|
|
444
|
+
* One side edge of the cell, the left (`UL → LL`) or right (`UR → LR`) one,
|
|
445
|
+
* ascent corner first. This is the segment a caret or a selection handle
|
|
440
446
|
* occupies: its length is the glyph's ink height in the text's own frame (not
|
|
441
447
|
* the AABB height, which grows with tilt), and its direction carries the
|
|
442
|
-
* text's rotation. Which side is the selection's
|
|
448
|
+
* text's rotation. Which side is the selection's leading edge is a reading
|
|
443
449
|
* -order question — decide it with `advance`, not with geometry.
|
|
444
450
|
*/
|
|
445
|
-
function
|
|
446
|
-
return side === "
|
|
451
|
+
function quadEdge(quad, side) {
|
|
452
|
+
return side === "left" ? [quad.upperLeft, quad.lowerLeft] : [quad.upperRight, quad.lowerRight];
|
|
447
453
|
}
|
|
448
|
-
/** Corner-wise equality — an AABB comparison would call a quad that
|
|
454
|
+
/** Corner-wise equality — an AABB comparison would call a quad that rotated
|
|
449
455
|
* in place "unchanged"; corners cannot. (Change-detection for quad consumers.) */
|
|
450
|
-
function
|
|
451
|
-
const eq = (
|
|
452
|
-
return eq(
|
|
456
|
+
function quadEquals(left, right) {
|
|
457
|
+
const eq = (point, other) => point.x === other.x && point.y === other.y;
|
|
458
|
+
return eq(left.upperLeft, right.upperLeft) && eq(left.upperRight, right.upperRight) && eq(left.lowerLeft, right.lowerLeft) && eq(left.lowerRight, right.lowerRight);
|
|
453
459
|
}
|
|
454
|
-
/** Axis-aligned bounds of a
|
|
455
|
-
function
|
|
460
|
+
/** Axis-aligned bounds of a quad. */
|
|
461
|
+
function quadBounds(quad) {
|
|
456
462
|
const xs = [
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
463
|
+
quad.upperLeft.x,
|
|
464
|
+
quad.upperRight.x,
|
|
465
|
+
quad.lowerLeft.x,
|
|
466
|
+
quad.lowerRight.x
|
|
461
467
|
];
|
|
462
468
|
const ys = [
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
469
|
+
quad.upperLeft.y,
|
|
470
|
+
quad.upperRight.y,
|
|
471
|
+
quad.lowerLeft.y,
|
|
472
|
+
quad.lowerRight.y
|
|
467
473
|
];
|
|
468
474
|
const minX = Math.min(...xs);
|
|
469
475
|
const minY = Math.min(...ys);
|
|
@@ -474,142 +480,7 @@ function textQuadBounds(t) {
|
|
|
474
480
|
height: Math.max(...ys) - minY
|
|
475
481
|
};
|
|
476
482
|
}
|
|
477
|
-
/** Map a TextQuad through an affine transform (corner semantics ride along). */
|
|
478
|
-
function applyTextQuad(m, t) {
|
|
479
|
-
return {
|
|
480
|
-
upperStart: applyPoint(m, t.upperStart),
|
|
481
|
-
upperEnd: applyPoint(m, t.upperEnd),
|
|
482
|
-
lowerStart: applyPoint(m, t.lowerStart),
|
|
483
|
-
lowerEnd: applyPoint(m, t.lowerEnd)
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
/** Serialize to the positional zigzag convention (`p1..p4` = US, UE, LS, LE). */
|
|
487
|
-
function positionalQuad(t) {
|
|
488
|
-
return {
|
|
489
|
-
p1: t.upperStart,
|
|
490
|
-
p2: t.upperEnd,
|
|
491
|
-
p3: t.lowerStart,
|
|
492
|
-
p4: t.lowerEnd
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
const QUAD_EPSILON = 1e-6;
|
|
496
|
-
function finitePoint(p) {
|
|
497
|
-
return Number.isFinite(p.x) && Number.isFinite(p.y);
|
|
498
|
-
}
|
|
499
|
-
/** Is `[us, ue, ls, le]` a well-formed zigzag reading of a quad? */
|
|
500
|
-
function zigzagWellFormed([us, ue, ls, le]) {
|
|
501
|
-
if (![
|
|
502
|
-
us,
|
|
503
|
-
ue,
|
|
504
|
-
ls,
|
|
505
|
-
le
|
|
506
|
-
].every(finitePoint)) return false;
|
|
507
|
-
const upper = {
|
|
508
|
-
x: ue.x - us.x,
|
|
509
|
-
y: ue.y - us.y
|
|
510
|
-
};
|
|
511
|
-
const lower = {
|
|
512
|
-
x: le.x - ls.x,
|
|
513
|
-
y: le.y - ls.y
|
|
514
|
-
};
|
|
515
|
-
const startSide = {
|
|
516
|
-
x: ls.x - us.x,
|
|
517
|
-
y: ls.y - us.y
|
|
518
|
-
};
|
|
519
|
-
const endSide = {
|
|
520
|
-
x: le.x - ue.x,
|
|
521
|
-
y: le.y - ue.y
|
|
522
|
-
};
|
|
523
|
-
const lenU = Math.hypot(upper.x, upper.y);
|
|
524
|
-
const lenL = Math.hypot(lower.x, lower.y);
|
|
525
|
-
const lenS = Math.hypot(startSide.x, startSide.y);
|
|
526
|
-
const lenE = Math.hypot(endSide.x, endSide.y);
|
|
527
|
-
if (lenU <= QUAD_EPSILON || lenL <= QUAD_EPSILON || lenS <= QUAD_EPSILON || lenE <= QUAD_EPSILON) return false;
|
|
528
|
-
if (upper.x * lower.x + upper.y * lower.y <= 0) return false;
|
|
529
|
-
if (startSide.x * endSide.x + startSide.y * endSide.y <= 0) return false;
|
|
530
|
-
const startArea = upper.x * startSide.y - upper.y * startSide.x;
|
|
531
|
-
const endArea = lower.x * endSide.y - lower.y * endSide.x;
|
|
532
|
-
if (Math.abs(startArea) <= QUAD_EPSILON * lenU * lenS) return false;
|
|
533
|
-
if (Math.abs(endArea) <= QUAD_EPSILON * lenL * lenE) return false;
|
|
534
|
-
return Math.sign(startArea) === Math.sign(endArea);
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Interpret an arbitrary positional quad (an imported `/QuadPoints` entry,
|
|
538
|
-
* already in content space) as a TextQuad — a NORMALIZER, not a cast:
|
|
539
|
-
*
|
|
540
|
-
* 1. the de-facto zigzag order (US, UE, LS, LE) passes through untouched —
|
|
541
|
-
* this covers every well-formed writer, rotated quads included;
|
|
542
|
-
* 2. the common ring order (US, UE, LE, LS) is recognized and repaired;
|
|
543
|
-
* 3. anything else gets a deterministic geometric labeling: corners sorted
|
|
544
|
-
* into a ring around their centroid, "upper" = the edge whose midpoint
|
|
545
|
-
* sits highest on screen (smallest y). The 180° "which side is up"
|
|
546
|
-
* ambiguity is unresolvable from geometry alone — convention decides.
|
|
547
|
-
*
|
|
548
|
-
* Drawing code downstream (`markupScene`) reads corner SEMANTICS, so a
|
|
549
|
-
* mislabeled import can shift an underline to the wrong edge but can never
|
|
550
|
-
* crash or self-intersect.
|
|
551
|
-
*/
|
|
552
|
-
function normalizeQuad(q) {
|
|
553
|
-
if (zigzagWellFormed([
|
|
554
|
-
q.p1,
|
|
555
|
-
q.p2,
|
|
556
|
-
q.p3,
|
|
557
|
-
q.p4
|
|
558
|
-
])) return {
|
|
559
|
-
upperStart: q.p1,
|
|
560
|
-
upperEnd: q.p2,
|
|
561
|
-
lowerStart: q.p3,
|
|
562
|
-
lowerEnd: q.p4
|
|
563
|
-
};
|
|
564
|
-
if (zigzagWellFormed([
|
|
565
|
-
q.p1,
|
|
566
|
-
q.p2,
|
|
567
|
-
q.p4,
|
|
568
|
-
q.p3
|
|
569
|
-
])) return {
|
|
570
|
-
upperStart: q.p1,
|
|
571
|
-
upperEnd: q.p2,
|
|
572
|
-
lowerStart: q.p4,
|
|
573
|
-
lowerEnd: q.p3
|
|
574
|
-
};
|
|
575
|
-
const pts = [
|
|
576
|
-
q.p1,
|
|
577
|
-
q.p2,
|
|
578
|
-
q.p3,
|
|
579
|
-
q.p4
|
|
580
|
-
].map((p) => finitePoint(p) ? p : {
|
|
581
|
-
x: 0,
|
|
582
|
-
y: 0
|
|
583
|
-
});
|
|
584
|
-
const cx = (pts[0].x + pts[1].x + pts[2].x + pts[3].x) / 4;
|
|
585
|
-
const cy = (pts[0].y + pts[1].y + pts[2].y + pts[3].y) / 4;
|
|
586
|
-
const ringSorted = [...pts].sort((a, b) => Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx));
|
|
587
|
-
let upperEdge = 0;
|
|
588
|
-
let bestY = Infinity;
|
|
589
|
-
for (let i = 0; i < 4; i++) {
|
|
590
|
-
const midY = (ringSorted[i].y + ringSorted[(i + 1) % 4].y) / 2;
|
|
591
|
-
if (midY < bestY) {
|
|
592
|
-
bestY = midY;
|
|
593
|
-
upperEdge = i;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
const a = ringSorted[upperEdge];
|
|
597
|
-
const b = ringSorted[(upperEdge + 1) % 4];
|
|
598
|
-
const c = ringSorted[(upperEdge + 2) % 4];
|
|
599
|
-
const d = ringSorted[(upperEdge + 3) % 4];
|
|
600
|
-
return a.x <= b.x ? {
|
|
601
|
-
upperStart: a,
|
|
602
|
-
upperEnd: b,
|
|
603
|
-
lowerStart: d,
|
|
604
|
-
lowerEnd: c
|
|
605
|
-
} : {
|
|
606
|
-
upperStart: b,
|
|
607
|
-
upperEnd: a,
|
|
608
|
-
lowerStart: c,
|
|
609
|
-
lowerEnd: d
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
483
|
//#endregion
|
|
613
|
-
export { NO_FRAME, addRotations, angleOf, applyPoint, applyQuad, applyRect,
|
|
484
|
+
export { NO_FRAME, addRotations, angleOf, applyPoint, applyQuad, applyRect, boundsOfRects, compose, containedRect, deviceHeightForWidth, displaySize, identity, intersectRects, invert, isQuarterTurn, matrixToCss, pageTransform, quadBounds, quadCorners, quadEdge, quadEquals, quadFromRect, quadRing, rectContains, rectFromCorners, rectsOverlap, rotate, rotateAbout, rotateScaleMatrix, samePagePlacement, scale, scaleAbout, snapToDevice, translate };
|
|
614
485
|
|
|
615
486
|
//# sourceMappingURL=index.js.map
|