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