@embedpdf/engine-core 3.0.0-next.1 → 3.0.0-next.11
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/{AbortError-CjSasIlL.js → AbortError-8-9JAX1Y.js} +2 -2
- package/dist/{AbortError-CjSasIlL.js.map → AbortError-8-9JAX1Y.js.map} +1 -1
- package/dist/{AbortError-ByBflvEl.cjs → AbortError-CRSooKsk.cjs} +2 -2
- package/dist/{AbortError-ByBflvEl.cjs.map → AbortError-CRSooKsk.cjs.map} +1 -1
- package/dist/{AnnotationListSnapshot-W0RCsyDE.d.cts → AnnotationListSnapshot-C1U3XUxP.d.cts} +846 -127
- package/dist/{AnnotationListSnapshot-W0RCsyDE.d.ts → AnnotationListSnapshot-C1U3XUxP.d.ts} +846 -127
- package/dist/{DocumentSecurityService-Df79fgpe.d.cts → DocumentSecurityService-6rhztZlz.d.cts} +33 -2
- package/dist/{DocumentSecurityService-B-tULKNY.d.ts → DocumentSecurityService-7nL9aY75.d.ts} +33 -2
- package/dist/{Engine-B7Oe7f5i.d.ts → Engine-CWnAKccD.d.ts} +62 -19
- package/dist/{Engine-D9loFZc1.d.cts → Engine-D3868xIU.d.cts} +62 -19
- package/dist/{subtype-qejjVr6h.cjs → PageInsertBlankInput-CzShuSJI.cjs} +184 -1
- package/dist/PageInsertBlankInput-CzShuSJI.cjs.map +1 -0
- package/dist/{subtype-BvAzLCGC.js → PageInsertBlankInput-WKY0tr6S.js} +149 -2
- package/dist/PageInsertBlankInput-WKY0tr6S.js.map +1 -0
- package/dist/{builders-C548U47M.js → builders-Cdz34nbF.js} +566 -164
- package/dist/builders-Cdz34nbF.js.map +1 -0
- package/dist/{builders-BvikWRuO.cjs → builders-Do6_4KnS.cjs} +628 -220
- package/dist/builders-Do6_4KnS.cjs.map +1 -0
- package/dist/conformance.cjs +1039 -204
- package/dist/conformance.cjs.map +1 -1
- package/dist/conformance.d.cts +130 -6
- package/dist/conformance.d.ts +130 -6
- package/dist/conformance.js +994 -163
- package/dist/conformance.js.map +1 -1
- package/dist/{fold-CB4fAOOy.js → fold-BfyxxwS6.js} +162 -3
- package/dist/fold-BfyxxwS6.js.map +1 -0
- package/dist/{fold-IaMph3CO.cjs → fold-C7I5GpBz.cjs} +235 -4
- package/dist/fold-C7I5GpBz.cjs.map +1 -0
- package/dist/index.cjs +38 -19
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/{resolver-DiPMTfcy.js → resolver-CHG4waXL.js} +2 -1
- package/dist/resolver-CHG4waXL.js.map +1 -0
- package/dist/{resolver-DAZWVoJL.cjs → resolver-D-jumvgM.cjs} +2 -1
- package/dist/resolver-D-jumvgM.cjs.map +1 -0
- package/dist/runtime.cjs +43 -24
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +103 -5
- package/dist/runtime.d.ts +103 -5
- package/dist/runtime.js +6 -6
- package/dist/{schemas-B_1VyFsm.cjs → schemas-CS5iPpX5.cjs} +305 -111
- package/dist/schemas-CS5iPpX5.cjs.map +1 -0
- package/dist/{schemas-9OpSSeM6.js → schemas-XGrNVMDd.js} +250 -92
- package/dist/schemas-XGrNVMDd.js.map +1 -0
- package/dist/wire.cjs +131 -4
- package/dist/wire.cjs.map +1 -1
- package/dist/wire.d.cts +277 -20
- package/dist/wire.d.ts +277 -20
- package/dist/wire.js +120 -5
- package/dist/wire.js.map +1 -1
- package/package.json +1 -1
- package/dist/builders-BvikWRuO.cjs.map +0 -1
- package/dist/builders-C548U47M.js.map +0 -1
- package/dist/fold-CB4fAOOy.js.map +0 -1
- package/dist/fold-IaMph3CO.cjs.map +0 -1
- package/dist/resolver-DAZWVoJL.cjs.map +0 -1
- package/dist/resolver-DiPMTfcy.js.map +0 -1
- package/dist/schemas-9OpSSeM6.js.map +0 -1
- package/dist/schemas-B_1VyFsm.cjs.map +0 -1
- package/dist/subtype-BvAzLCGC.js.map +0 -1
- package/dist/subtype-qejjVr6h.cjs.map +0 -1
|
@@ -1,126 +1,35 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
//#region src/
|
|
1
|
+
const require_fold = require("./fold-C7I5GpBz.cjs");
|
|
2
|
+
const require_PageInsertBlankInput = require("./PageInsertBlankInput-CzShuSJI.cjs");
|
|
3
|
+
//#region src/dto/PdfAction.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* may carry them in any order). THE enforcement point for the "PdfRect is always
|
|
8
|
-
* normalized" rule: every producer that reads a raw box from PDFium passes it
|
|
9
|
-
* through here, so the invariant holds once, centrally — downstream `width =
|
|
10
|
-
* right - left` is never negative.
|
|
5
|
+
* The one Table-240 decode, shared by the reader, the schema's producers,
|
|
6
|
+
* and tests — so bit semantics cannot fork.
|
|
11
7
|
*/
|
|
12
|
-
|
|
8
|
+
const decodeSubmitFormFlags = (raw) => {
|
|
9
|
+
const bit = (n) => (raw & 1 << n - 1) !== 0;
|
|
10
|
+
const format = bit(9) ? "pdf" : bit(6) ? "xfdf" : bit(3) ? "html" : "fdf";
|
|
13
11
|
return {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
raw,
|
|
13
|
+
exclude: bit(1),
|
|
14
|
+
includeNoValueFields: bit(2),
|
|
15
|
+
format,
|
|
16
|
+
method: bit(4) && (format === "html" || format === "pdf") ? "get" : "post",
|
|
17
|
+
submitCoordinates: bit(5),
|
|
18
|
+
includeAppendSaves: bit(7),
|
|
19
|
+
includeAnnotations: bit(8),
|
|
20
|
+
canonicalFormat: bit(10),
|
|
21
|
+
exclNonUserAnnots: bit(11),
|
|
22
|
+
exclFKey: bit(12),
|
|
23
|
+
embedForm: bit(14)
|
|
18
24
|
};
|
|
19
|
-
}
|
|
20
|
-
function pdfRectWidth(r) {
|
|
21
|
-
return r.right - r.left;
|
|
22
|
-
}
|
|
23
|
-
function pdfRectHeight(r) {
|
|
24
|
-
return r.top - r.bottom;
|
|
25
|
-
}
|
|
26
|
-
function pdfRectSize(r) {
|
|
27
|
-
return {
|
|
28
|
-
width: r.right - r.left,
|
|
29
|
-
height: r.top - r.bottom
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** Convert edges -> origin+size, staying y-up (origin at bottom-left). */
|
|
33
|
-
function pdfRectToOriginSize(r) {
|
|
34
|
-
return {
|
|
35
|
-
x: r.left,
|
|
36
|
-
y: r.bottom,
|
|
37
|
-
width: r.right - r.left,
|
|
38
|
-
height: r.top - r.bottom
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/** Convert y-up origin+size back to edges. */
|
|
42
|
-
function pdfRectFromOriginSize(o) {
|
|
43
|
-
return {
|
|
44
|
-
left: o.x,
|
|
45
|
-
bottom: o.y,
|
|
46
|
-
right: o.x + o.width,
|
|
47
|
-
top: o.y + o.height
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Enclosing axis-aligned box of a quad, in PDF user space. Orientation
|
|
52
|
-
* agnostic (correct for rotated/skewed quads) — this is what highlight
|
|
53
|
-
* rendering and hit-testing want.
|
|
54
|
-
*/
|
|
55
|
-
function pdfQuadBounds(q) {
|
|
56
|
-
const xs = [
|
|
57
|
-
q.p1.x,
|
|
58
|
-
q.p2.x,
|
|
59
|
-
q.p3.x,
|
|
60
|
-
q.p4.x
|
|
61
|
-
];
|
|
62
|
-
const ys = [
|
|
63
|
-
q.p1.y,
|
|
64
|
-
q.p2.y,
|
|
65
|
-
q.p3.y,
|
|
66
|
-
q.p4.y
|
|
67
|
-
];
|
|
68
|
-
return {
|
|
69
|
-
left: Math.min(...xs),
|
|
70
|
-
right: Math.max(...xs),
|
|
71
|
-
bottom: Math.min(...ys),
|
|
72
|
-
top: Math.max(...ys)
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Axis-aligned named-corner interpretation of a quad.
|
|
77
|
-
*
|
|
78
|
-
* VALID ONLY for upright (non-rotated, non-skewed) quads: it is derived from
|
|
79
|
-
* the enclosing bounds, so for rotated/skewed quads it returns the bounding
|
|
80
|
-
* box corners, not the true geometric corners. For arbitrary quads use the
|
|
81
|
-
* positional `p1..p4` or `pdfQuadBounds` directly.
|
|
82
|
-
*/
|
|
83
|
-
function pdfQuadCorners(q) {
|
|
84
|
-
const b = pdfQuadBounds(q);
|
|
85
|
-
return {
|
|
86
|
-
topLeft: {
|
|
87
|
-
x: b.left,
|
|
88
|
-
y: b.top
|
|
89
|
-
},
|
|
90
|
-
topRight: {
|
|
91
|
-
x: b.right,
|
|
92
|
-
y: b.top
|
|
93
|
-
},
|
|
94
|
-
bottomLeft: {
|
|
95
|
-
x: b.left,
|
|
96
|
-
y: b.bottom
|
|
97
|
-
},
|
|
98
|
-
bottomRight: {
|
|
99
|
-
x: b.right,
|
|
100
|
-
y: b.bottom
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Build a quad from named corners, in PDFium `FS_QUADPOINTSF` slot order
|
|
106
|
-
* (`p1 = topLeft`, `p2 = topRight`, `p3 = bottomLeft`, `p4 = bottomRight`).
|
|
107
|
-
* The inverse of `pdfQuadCorners` for upright quads.
|
|
108
|
-
*/
|
|
109
|
-
function pdfQuadFromCorners(c) {
|
|
110
|
-
return {
|
|
111
|
-
p1: c.topLeft,
|
|
112
|
-
p2: c.topRight,
|
|
113
|
-
p3: c.bottomLeft,
|
|
114
|
-
p4: c.bottomRight
|
|
115
|
-
};
|
|
116
|
-
}
|
|
25
|
+
};
|
|
117
26
|
//#endregion
|
|
118
27
|
//#region src/dto/PageRender.ts
|
|
119
28
|
function createPageImageHandle(result, blobSource) {
|
|
120
29
|
return {
|
|
121
30
|
...result,
|
|
122
31
|
async objectUrl(signal) {
|
|
123
|
-
if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new require_fold.EngineError(
|
|
32
|
+
if (typeof Blob === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.RuntimeUnavailable, "Object URLs are not available in this environment");
|
|
124
33
|
const blob = await blobSource.blob(signal);
|
|
125
34
|
const url = URL.createObjectURL(blob);
|
|
126
35
|
return {
|
|
@@ -344,7 +253,7 @@ function parseJpeg(buffer) {
|
|
|
344
253
|
async function normalizeAttachmentFileSource(file, key) {
|
|
345
254
|
const resolved = await resolveBinarySource(file.data);
|
|
346
255
|
const name = file.name ?? resolved.name;
|
|
347
|
-
if (!name) throw new require_fold.EngineError(
|
|
256
|
+
if (!name) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.InvalidArg, "attachment file requires a name — pass { data, name } or a File whose name is set");
|
|
348
257
|
const mimeType = file.mimeType ?? resolved.mimeType;
|
|
349
258
|
return {
|
|
350
259
|
wireFile: {
|
|
@@ -382,7 +291,7 @@ async function normalizeFileAttachmentDraft(draft, allocateKey) {
|
|
|
382
291
|
async function resolveStampSource(source) {
|
|
383
292
|
const resolved = await resolveBinarySource(source);
|
|
384
293
|
const meta = sniffBinaryMetadata(resolved.bytes);
|
|
385
|
-
if (!meta) throw new require_fold.EngineError(
|
|
294
|
+
if (!meta) throw new require_fold.EngineError(require_PageInsertBlankInput.EngineErrorCode.InvalidArg, "stamp source must be PNG, JPEG, or single-page PDF bytes (format is detected from the data, not the declared mime type)");
|
|
386
295
|
return {
|
|
387
296
|
...resolved,
|
|
388
297
|
mimeType: meta.mimeType
|
|
@@ -524,6 +433,164 @@ function buildThreads(annotations) {
|
|
|
524
433
|
return threads;
|
|
525
434
|
}
|
|
526
435
|
//#endregion
|
|
436
|
+
//#region src/annotation/comments.ts
|
|
437
|
+
/** Subtypes that never participate in comment threads. `unsupported`
|
|
438
|
+
* covers `/Popup` dictionaries (raw code 16) and unreadable foreign
|
|
439
|
+
* blobs — neither makes a meaningful comment card. */
|
|
440
|
+
const EXCLUDED_SUBTYPES = /* @__PURE__ */ new Set([
|
|
441
|
+
"widget",
|
|
442
|
+
"link",
|
|
443
|
+
"unsupported"
|
|
444
|
+
]);
|
|
445
|
+
const REVIEW_STATES = /* @__PURE__ */ new Set([
|
|
446
|
+
"accepted",
|
|
447
|
+
"rejected",
|
|
448
|
+
"cancelled",
|
|
449
|
+
"completed",
|
|
450
|
+
"none"
|
|
451
|
+
]);
|
|
452
|
+
const MARKED_STATES = /* @__PURE__ */ new Set(["marked", "unmarked"]);
|
|
453
|
+
const nonEmpty = (v) => typeof v === "string" && v !== "";
|
|
454
|
+
/**
|
|
455
|
+
* An ISO 32000 §12.5.6.3 state annotation: a text annotation carrying a
|
|
456
|
+
* non-empty `/State` or `/StateModel`. Classification never relies on
|
|
457
|
+
* flags — producers disagree on how state annotations are flagged.
|
|
458
|
+
*/
|
|
459
|
+
function isStateAnnotation(a) {
|
|
460
|
+
return a.subtype === "text" && (nonEmpty(a.state) || nonEmpty(a.stateModel));
|
|
461
|
+
}
|
|
462
|
+
function buildCommentThreads(annotations, opts = {}) {
|
|
463
|
+
const eligible = annotations.filter((a) => !EXCLUDED_SUBTYPES.has(a.subtype));
|
|
464
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
465
|
+
for (const a of eligible) {
|
|
466
|
+
const key = refKey(a.ref);
|
|
467
|
+
if (!byKey.has(key)) byKey.set(key, a);
|
|
468
|
+
if (a.nm) {
|
|
469
|
+
const aliasKey = refKey({
|
|
470
|
+
kind: "nm",
|
|
471
|
+
pageObjectNumber: a.pageObjectNumber,
|
|
472
|
+
nm: a.nm
|
|
473
|
+
});
|
|
474
|
+
if (!byKey.has(aliasKey)) byKey.set(aliasKey, a);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const children = /* @__PURE__ */ new Map();
|
|
478
|
+
for (const a of eligible) {
|
|
479
|
+
if (!a.inReplyTo) continue;
|
|
480
|
+
const parent = byKey.get(refKey(a.inReplyTo));
|
|
481
|
+
if (!parent) continue;
|
|
482
|
+
const parentKey = refKey(parent.ref);
|
|
483
|
+
const list = children.get(parentKey);
|
|
484
|
+
if (list) list.push(a);
|
|
485
|
+
else children.set(parentKey, [a]);
|
|
486
|
+
}
|
|
487
|
+
const visited = /* @__PURE__ */ new Set();
|
|
488
|
+
const threads = [];
|
|
489
|
+
const walk = (root) => {
|
|
490
|
+
const replies = [];
|
|
491
|
+
const groupedParts = [];
|
|
492
|
+
const states = [];
|
|
493
|
+
visited.add(refKey(root.ref));
|
|
494
|
+
const stack = [...children.get(refKey(root.ref)) ?? []];
|
|
495
|
+
while (stack.length > 0) {
|
|
496
|
+
const a = stack.pop();
|
|
497
|
+
const key = refKey(a.ref);
|
|
498
|
+
if (visited.has(key)) continue;
|
|
499
|
+
visited.add(key);
|
|
500
|
+
if (isStateAnnotation(a)) states.push(a);
|
|
501
|
+
else if (classifyRelation(a) === "grouped-subordinate") groupedParts.push(a);
|
|
502
|
+
else replies.push(a);
|
|
503
|
+
stack.push(...children.get(key) ?? []);
|
|
504
|
+
}
|
|
505
|
+
replies.sort(chronological);
|
|
506
|
+
states.sort(chronological);
|
|
507
|
+
threads.push({
|
|
508
|
+
root,
|
|
509
|
+
pageObjectNumber: root.pageObjectNumber,
|
|
510
|
+
replies,
|
|
511
|
+
groupedParts,
|
|
512
|
+
review: computeReview(states, opts)
|
|
513
|
+
});
|
|
514
|
+
};
|
|
515
|
+
for (const a of eligible) if (!a.inReplyTo && !isStateAnnotation(a)) walk(a);
|
|
516
|
+
for (const a of eligible) {
|
|
517
|
+
if (visited.has(refKey(a.ref))) continue;
|
|
518
|
+
if (isStateAnnotation(a)) continue;
|
|
519
|
+
walk(a);
|
|
520
|
+
}
|
|
521
|
+
return threads;
|
|
522
|
+
}
|
|
523
|
+
/** `created ?? modified` ascending; undated last; z-order tiebreak. */
|
|
524
|
+
function chronological(a, b) {
|
|
525
|
+
const at = a.created ?? a.modified;
|
|
526
|
+
const bt = b.created ?? b.modified;
|
|
527
|
+
if (at !== null && bt !== null && at !== bt) return at < bt ? -1 : 1;
|
|
528
|
+
if (at !== null && bt === null) return -1;
|
|
529
|
+
if (at === null && bt !== null) return 1;
|
|
530
|
+
return a.index - b.index;
|
|
531
|
+
}
|
|
532
|
+
function computeReview(states, opts) {
|
|
533
|
+
const byReviewer = {};
|
|
534
|
+
const markedLatest = {};
|
|
535
|
+
let lastChange = null;
|
|
536
|
+
for (const a of states) {
|
|
537
|
+
const status = toReviewStatus(a);
|
|
538
|
+
if (!status) continue;
|
|
539
|
+
if (status.stateModel === "marked") {
|
|
540
|
+
if (status.by !== null && isNewer(status, markedLatest[status.by])) markedLatest[status.by] = status;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (status.by !== null && isNewer(status, byReviewer[status.by])) byReviewer[status.by] = status;
|
|
544
|
+
if (isNewer(status, lastChange)) lastChange = status;
|
|
545
|
+
}
|
|
546
|
+
const markedBy = Object.keys(markedLatest).filter((key) => markedLatest[key].state === "marked").sort();
|
|
547
|
+
const review = {
|
|
548
|
+
byReviewer,
|
|
549
|
+
lastChange,
|
|
550
|
+
markedBy,
|
|
551
|
+
statusRefs: states.map((a) => a.ref)
|
|
552
|
+
};
|
|
553
|
+
if (opts.currentUserId !== void 0) review.mine = byReviewer[opts.currentUserId] ?? null;
|
|
554
|
+
return review;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Derive a `ReviewStatus` from one state annotation, or null when nothing
|
|
558
|
+
* classifiable can be derived. ISO defaulting happens HERE, not in the
|
|
559
|
+
* engine reader: a known model with an absent state means `none` /
|
|
560
|
+
* `unmarked`; an absent model infers from a known state; a custom model
|
|
561
|
+
* with no state has no derivable status.
|
|
562
|
+
*/
|
|
563
|
+
function toReviewStatus(a) {
|
|
564
|
+
if (a.subtype !== "text") return null;
|
|
565
|
+
const rawState = nonEmpty(a.state) ? a.state : null;
|
|
566
|
+
let stateModel = nonEmpty(a.stateModel) ? a.stateModel : null;
|
|
567
|
+
if (stateModel === null && rawState !== null) if (REVIEW_STATES.has(rawState)) stateModel = "review";
|
|
568
|
+
else if (MARKED_STATES.has(rawState)) stateModel = "marked";
|
|
569
|
+
else return null;
|
|
570
|
+
let state = rawState;
|
|
571
|
+
if (state === null) if (stateModel === "review") state = "none";
|
|
572
|
+
else if (stateModel === "marked") state = "unmarked";
|
|
573
|
+
else return null;
|
|
574
|
+
if (stateModel === null) return null;
|
|
575
|
+
return {
|
|
576
|
+
state,
|
|
577
|
+
stateModel,
|
|
578
|
+
by: a.userId ?? (nonEmpty(a.author) ? a.author : null),
|
|
579
|
+
at: a.modified ?? a.created ?? null,
|
|
580
|
+
ref: a.ref
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/** `a` newer than (or supersedes) `b`. `at` decides; dated beats undated.
|
|
584
|
+
* States arrive pre-sorted by `chronological` (date, then z-order), so on
|
|
585
|
+
* equal or missing dates the later entry wins via `>=` / `true`. */
|
|
586
|
+
function isNewer(a, b) {
|
|
587
|
+
if (!b) return true;
|
|
588
|
+
if (a.at !== null && b.at !== null) return a.at >= b.at;
|
|
589
|
+
if (a.at !== null) return true;
|
|
590
|
+
if (b.at !== null) return false;
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
//#endregion
|
|
527
594
|
//#region src/mutation/AnnotationMutationImpactPolicy.ts
|
|
528
595
|
function changesAnnotationList(_kind) {
|
|
529
596
|
return true;
|
|
@@ -547,14 +614,19 @@ function invalidatesWeakIndexRefs(kind, weakStateBefore) {
|
|
|
547
614
|
const EPSILON = .001;
|
|
548
615
|
/**
|
|
549
616
|
* Keys that never affect `/AP` on any kind: the discriminator, behavioral
|
|
550
|
-
* flags, reply relationships, and
|
|
617
|
+
* flags, reply relationships, grouping, and the conversation-plane
|
|
618
|
+
* entries (`/Subj` subject line, `/State` + `/StateModel` review status —
|
|
619
|
+
* dictionary-only per ISO 32000 §12.5.6.3, never painted).
|
|
551
620
|
*/
|
|
552
621
|
const INERT_KEYS = /* @__PURE__ */ new Set([
|
|
553
622
|
"subtype",
|
|
554
623
|
"flags",
|
|
555
624
|
"inReplyTo",
|
|
556
625
|
"replyType",
|
|
557
|
-
"groupId"
|
|
626
|
+
"groupId",
|
|
627
|
+
"subject",
|
|
628
|
+
"state",
|
|
629
|
+
"stateModel"
|
|
558
630
|
]);
|
|
559
631
|
/**
|
|
560
632
|
* Kinds whose `/AP` paints `/Contents`. Everywhere else `contents` is popup
|
|
@@ -1007,37 +1079,275 @@ function buildSnippet(text, range, context = 48) {
|
|
|
1007
1079
|
};
|
|
1008
1080
|
}
|
|
1009
1081
|
//#endregion
|
|
1010
|
-
//#region src/
|
|
1011
|
-
/**
|
|
1012
|
-
* Match-range → merged line rects, in PDF user space (y-up).
|
|
1013
|
-
*
|
|
1014
|
-
* This is the same line-merge text selection uses (adapted from Chromium's
|
|
1015
|
-
* pdf/pdfium/pdfium_range.cc `MergeAdjacentRects`, BSD-licensed, Copyright
|
|
1016
|
-
* 2010 The Chromium Authors), operating directly on the engine's
|
|
1017
|
-
* `PageGeometrySnapshot`: glyph loose boxes → sub-runs (split on big
|
|
1018
|
-
* intra-run gaps) → one rect per visual line. Search highlights therefore
|
|
1019
|
-
* look exactly like selections — never a rect per glyph (the v2 bug).
|
|
1020
|
-
*
|
|
1021
|
-
* It lives in engine-core because BOTH sides need it: the server computes
|
|
1022
|
-
* rects here for `'rects'`-mode responses (geometry never leaves the trust
|
|
1023
|
-
* boundary), and the local engine computes them in the worker.
|
|
1024
|
-
*/
|
|
1082
|
+
//#region src/text/layout.ts
|
|
1025
1083
|
const FLAG_EMPTY = 2;
|
|
1084
|
+
const isBoundary = (flags) => (flags & 3) !== 0;
|
|
1085
|
+
const isEmpty = (flags) => (flags & FLAG_EMPTY) !== 0;
|
|
1026
1086
|
const CHAR_DISTANCE_FACTOR = 2.5;
|
|
1027
1087
|
const FONT_SIZE_RATIO_THRESHOLD = 1.5;
|
|
1028
1088
|
const VERTICAL_OVERLAP_THRESHOLD = .8;
|
|
1089
|
+
const LINE_OVERLAP_THRESHOLD = .5;
|
|
1090
|
+
/** Baseline directions within ~0.5° share a frame. */
|
|
1091
|
+
const FRAME_DOT_TOLERANCE = Math.cos(.0087);
|
|
1092
|
+
const EDGE_EPSILON = 1e-6;
|
|
1093
|
+
const IDENTITY_FRAME = {
|
|
1094
|
+
baseline: {
|
|
1095
|
+
x: 1,
|
|
1096
|
+
y: 0
|
|
1097
|
+
},
|
|
1098
|
+
ascent: {
|
|
1099
|
+
x: 0,
|
|
1100
|
+
y: 1
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
const toFrame = (f, p) => ({
|
|
1104
|
+
x: p.x * f.baseline.x + p.y * f.baseline.y,
|
|
1105
|
+
y: p.x * f.ascent.x + p.y * f.ascent.y
|
|
1106
|
+
});
|
|
1107
|
+
const fromFrame = (f, p) => ({
|
|
1108
|
+
x: f.baseline.x * p.x + f.ascent.x * p.y,
|
|
1109
|
+
y: f.baseline.y * p.x + f.ascent.y * p.y
|
|
1110
|
+
});
|
|
1111
|
+
const ZERO_RECT = {
|
|
1112
|
+
left: 0,
|
|
1113
|
+
bottom: 0,
|
|
1114
|
+
right: 0,
|
|
1115
|
+
top: 0
|
|
1116
|
+
};
|
|
1117
|
+
/** Frame-local AABB of a page-space cell (exact under the frame's rotation;
|
|
1118
|
+
* shear residue is the documented in-frame envelope). */
|
|
1119
|
+
function frameBoxOfQuad(f, q) {
|
|
1120
|
+
const pts = [
|
|
1121
|
+
q.p1,
|
|
1122
|
+
q.p2,
|
|
1123
|
+
q.p3,
|
|
1124
|
+
q.p4
|
|
1125
|
+
].map((p) => toFrame(f, p));
|
|
1126
|
+
const xs = pts.map((p) => p.x);
|
|
1127
|
+
const ys = pts.map((p) => p.y);
|
|
1128
|
+
return {
|
|
1129
|
+
left: Math.min(...xs),
|
|
1130
|
+
bottom: Math.min(...ys),
|
|
1131
|
+
right: Math.max(...xs),
|
|
1132
|
+
top: Math.max(...ys)
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
/** Find-or-create the frame for a rotated run, keyed by the baseline
|
|
1136
|
+
* direction + ascent handedness of its first classifiable glyph's edges. */
|
|
1137
|
+
function frameForRun(frames, run) {
|
|
1138
|
+
const seed = run.glyphs.find((g) => !isEmpty(g.flags));
|
|
1139
|
+
if (!seed) return 0;
|
|
1140
|
+
const q = seed.looseQuad;
|
|
1141
|
+
const bx = q.p2.x - q.p1.x;
|
|
1142
|
+
const by = q.p2.y - q.p1.y;
|
|
1143
|
+
const len = Math.hypot(bx, by);
|
|
1144
|
+
if (len <= EDGE_EPSILON) return 0;
|
|
1145
|
+
const baseline = {
|
|
1146
|
+
x: bx / len,
|
|
1147
|
+
y: by / len
|
|
1148
|
+
};
|
|
1149
|
+
const ax = q.p1.x - q.p3.x;
|
|
1150
|
+
const ay = q.p1.y - q.p3.y;
|
|
1151
|
+
const sign = baseline.x * ay - baseline.y * ax >= 0 ? 1 : -1;
|
|
1152
|
+
const ascent = {
|
|
1153
|
+
x: -baseline.y * sign,
|
|
1154
|
+
y: baseline.x * sign
|
|
1155
|
+
};
|
|
1156
|
+
for (let i = 0; i < frames.length; i++) {
|
|
1157
|
+
const f = frames[i];
|
|
1158
|
+
const dot = f.baseline.x * baseline.x + f.baseline.y * baseline.y;
|
|
1159
|
+
const sameHandedness = f.ascent.x * ascent.x + f.ascent.y * ascent.y > 0;
|
|
1160
|
+
if (dot >= FRAME_DOT_TOLERANCE && sameHandedness) return i;
|
|
1161
|
+
}
|
|
1162
|
+
frames.push({
|
|
1163
|
+
baseline,
|
|
1164
|
+
ascent
|
|
1165
|
+
});
|
|
1166
|
+
return frames.length - 1;
|
|
1167
|
+
}
|
|
1029
1168
|
/**
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1169
|
+
* Flatten a page's geometry snapshot into the canonical layout. Upright runs
|
|
1170
|
+
* copy their wire boxes verbatim (frame 0 — byte-identical to the
|
|
1171
|
+
* pre-orientation behavior, wire run rects included); rotated runs project
|
|
1172
|
+
* their glyph cells into their cluster's frame.
|
|
1033
1173
|
*/
|
|
1034
|
-
function
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1038
|
-
const subRuns = [];
|
|
1174
|
+
function buildPageTextLayout(snapshot) {
|
|
1175
|
+
const glyphs = [];
|
|
1176
|
+
const runs = [];
|
|
1177
|
+
const frames = [IDENTITY_FRAME];
|
|
1039
1178
|
for (const run of snapshot.runs) {
|
|
1040
|
-
|
|
1179
|
+
if (!require_fold.isRotatedGeometryRun(run)) {
|
|
1180
|
+
for (const g of run.glyphs) glyphs.push({
|
|
1181
|
+
loose: g.looseBox,
|
|
1182
|
+
tight: g.tightBox,
|
|
1183
|
+
flags: g.flags
|
|
1184
|
+
});
|
|
1185
|
+
runs.push({
|
|
1186
|
+
charStart: run.charStart,
|
|
1187
|
+
count: run.glyphs.length,
|
|
1188
|
+
rect: run.rect,
|
|
1189
|
+
fontSize: run.fontSize,
|
|
1190
|
+
frame: 0
|
|
1191
|
+
});
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const frame = frameForRun(frames, run);
|
|
1195
|
+
const f = frames[frame];
|
|
1196
|
+
let runBox = null;
|
|
1197
|
+
for (const g of run.glyphs) {
|
|
1198
|
+
if (isEmpty(g.flags)) {
|
|
1199
|
+
glyphs.push({
|
|
1200
|
+
loose: ZERO_RECT,
|
|
1201
|
+
flags: g.flags
|
|
1202
|
+
});
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
const loose = frameBoxOfQuad(f, g.looseQuad);
|
|
1206
|
+
const tight = g.tightQuad ? frameBoxOfQuad(f, g.tightQuad) : void 0;
|
|
1207
|
+
glyphs.push({
|
|
1208
|
+
loose,
|
|
1209
|
+
tight,
|
|
1210
|
+
flags: g.flags
|
|
1211
|
+
});
|
|
1212
|
+
runBox = runBox ? {
|
|
1213
|
+
left: Math.min(runBox.left, loose.left),
|
|
1214
|
+
bottom: Math.min(runBox.bottom, loose.bottom),
|
|
1215
|
+
right: Math.max(runBox.right, loose.right),
|
|
1216
|
+
top: Math.max(runBox.top, loose.top)
|
|
1217
|
+
} : loose;
|
|
1218
|
+
}
|
|
1219
|
+
runs.push({
|
|
1220
|
+
charStart: run.charStart,
|
|
1221
|
+
count: run.glyphs.length,
|
|
1222
|
+
rect: runBox ?? ZERO_RECT,
|
|
1223
|
+
fontSize: run.fontSize,
|
|
1224
|
+
frame
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
return {
|
|
1228
|
+
glyphs,
|
|
1229
|
+
runs,
|
|
1230
|
+
frames
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
const inRect = (r, p) => p.x >= r.left && p.x <= r.right && p.y >= r.bottom && p.y <= r.top;
|
|
1234
|
+
function avgGlyphHeight(layout) {
|
|
1235
|
+
let total = 0;
|
|
1236
|
+
let count = 0;
|
|
1237
|
+
for (const g of layout.glyphs) {
|
|
1238
|
+
if (isEmpty(g.flags)) continue;
|
|
1239
|
+
total += g.loose.top - g.loose.bottom;
|
|
1240
|
+
count++;
|
|
1241
|
+
}
|
|
1242
|
+
return count === 0 ? 0 : total / count;
|
|
1243
|
+
}
|
|
1244
|
+
/** The point expressed in every frame (index-aligned with `layout.frames`).
|
|
1245
|
+
* Frame 0 returns the point itself — no float round-trip on the fast path. */
|
|
1246
|
+
function pointPerFrame(layout, p) {
|
|
1247
|
+
return layout.frames.map((f, i) => i === 0 ? p : toFrame(f, p));
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* The glyph (char) index at a page-space point, or null when nothing is near
|
|
1251
|
+
* (so callers can show the pointer cursor off-text). PDFium `GetIndexAtPos`:
|
|
1252
|
+
* exact tight-box containment first, then a tolerance pass (closest by
|
|
1253
|
+
* Manhattan distance within `toleranceFactor × average glyph height`). Each
|
|
1254
|
+
* run tests the point in ITS OWN frame, so rotated text hit-tests exactly.
|
|
1255
|
+
*/
|
|
1256
|
+
function textGlyphAt(layout, point, toleranceFactor = 1.5) {
|
|
1257
|
+
const pts = pointPerFrame(layout, point);
|
|
1258
|
+
for (const run of layout.runs) {
|
|
1259
|
+
const q = pts[run.frame];
|
|
1260
|
+
if (!inRect(run.rect, q)) continue;
|
|
1261
|
+
for (let i = 0; i < run.count; i++) {
|
|
1262
|
+
const g = layout.glyphs[run.charStart + i];
|
|
1263
|
+
if (inRect(g.tight ?? g.loose, q)) return run.charStart + i;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (toleranceFactor <= 0) return null;
|
|
1267
|
+
const half = avgGlyphHeight(layout) * toleranceFactor / 2;
|
|
1268
|
+
let best = -1;
|
|
1269
|
+
let bestDist = Infinity;
|
|
1270
|
+
for (const run of layout.runs) {
|
|
1271
|
+
const q = pts[run.frame];
|
|
1272
|
+
const r = run.rect;
|
|
1273
|
+
if (q.x < r.left - half || q.x > r.right + half || q.y < r.bottom - half || q.y > r.top + half) continue;
|
|
1274
|
+
for (let i = 0; i < run.count; i++) {
|
|
1275
|
+
const g = layout.glyphs[run.charStart + i];
|
|
1276
|
+
if (isEmpty(g.flags)) continue;
|
|
1277
|
+
const b = g.tight ?? g.loose;
|
|
1278
|
+
if (q.x < b.left - half || q.x > b.right + half || q.y < b.bottom - half || q.y > b.top + half) continue;
|
|
1279
|
+
const dx = Math.min(Math.abs(q.x - b.left), Math.abs(q.x - b.right));
|
|
1280
|
+
const dy = Math.min(Math.abs(q.y - b.bottom), Math.abs(q.y - b.top));
|
|
1281
|
+
if (dx + dy < bestDist) {
|
|
1282
|
+
bestDist = dx + dy;
|
|
1283
|
+
best = run.charStart + i;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return best >= 0 ? best : null;
|
|
1288
|
+
}
|
|
1289
|
+
/** Double-click: the word around `glyph` (walk to space/empty glyphs both ways). */
|
|
1290
|
+
function expandTextRangeToWord(layout, glyph) {
|
|
1291
|
+
const n = layout.glyphs.length;
|
|
1292
|
+
if (glyph < 0 || glyph >= n) return [glyph, glyph];
|
|
1293
|
+
let from = glyph;
|
|
1294
|
+
while (from > 0 && !isBoundary(layout.glyphs[from - 1].flags)) from--;
|
|
1295
|
+
let to = glyph;
|
|
1296
|
+
while (to < n - 1 && !isBoundary(layout.glyphs[to + 1].flags)) to++;
|
|
1297
|
+
return [from, to];
|
|
1298
|
+
}
|
|
1299
|
+
/** Triple-click: the full visual line — SAME-FRAME runs whose vertical extent
|
|
1300
|
+
* overlaps the anchor run's. A differently-oriented run is a line boundary. */
|
|
1301
|
+
function expandTextRangeToLine(layout, glyph) {
|
|
1302
|
+
const ri = layout.runs.findIndex((r) => glyph >= r.charStart && glyph < r.charStart + r.count);
|
|
1303
|
+
if (ri < 0) return [glyph, glyph];
|
|
1304
|
+
const anchor = layout.runs[ri];
|
|
1305
|
+
const bottom = anchor.rect.bottom;
|
|
1306
|
+
const top = anchor.rect.top;
|
|
1307
|
+
let from = anchor.charStart;
|
|
1308
|
+
let to = anchor.charStart + anchor.count - 1;
|
|
1309
|
+
for (let r = ri - 1; r >= 0; r--) {
|
|
1310
|
+
const run = layout.runs[r];
|
|
1311
|
+
if (isZero(run.rect)) continue;
|
|
1312
|
+
if (run.frame !== anchor.frame) break;
|
|
1313
|
+
if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
|
|
1314
|
+
from = run.charStart;
|
|
1315
|
+
}
|
|
1316
|
+
for (let r = ri + 1; r < layout.runs.length; r++) {
|
|
1317
|
+
const run = layout.runs[r];
|
|
1318
|
+
if (isZero(run.rect)) continue;
|
|
1319
|
+
if (run.frame !== anchor.frame) break;
|
|
1320
|
+
if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
|
|
1321
|
+
to = run.charStart + run.count - 1;
|
|
1322
|
+
}
|
|
1323
|
+
return [from, to];
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* A single glyph's oriented cell in page space, or null for degenerate
|
|
1327
|
+
* glyphs — the anchor for caret placement at a selection boundary.
|
|
1328
|
+
*/
|
|
1329
|
+
function textGlyphQuad(layout, glyph) {
|
|
1330
|
+
const g = layout.glyphs[glyph];
|
|
1331
|
+
if (!g || isEmpty(g.flags)) return null;
|
|
1332
|
+
const b = g.loose;
|
|
1333
|
+
if (b.right <= b.left || b.top <= b.bottom) return null;
|
|
1334
|
+
const run = layout.runs.find((r) => glyph >= r.charStart && glyph < r.charStart + r.count);
|
|
1335
|
+
const f = layout.frames[run?.frame ?? 0];
|
|
1336
|
+
return quadOfFrameRect(f, run?.frame ?? 0, b);
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Merged visual-line segments for the half-open char range
|
|
1340
|
+
* `[charStart, charStart + charCount)` — THE canonical segmentation. Chars
|
|
1341
|
+
* outside the layout are ignored.
|
|
1342
|
+
*/
|
|
1343
|
+
function textSegmentsForRange(layout, charStart, charCount) {
|
|
1344
|
+
if (charCount <= 0 || layout.glyphs.length === 0) return [];
|
|
1345
|
+
const lo = Math.max(0, charStart);
|
|
1346
|
+
const hi = Math.min(layout.glyphs.length - 1, charStart + charCount - 1);
|
|
1347
|
+
if (hi < lo) return [];
|
|
1348
|
+
const subRuns = [];
|
|
1349
|
+
for (const run of layout.runs) {
|
|
1350
|
+
const runEnd = run.charStart + run.count - 1;
|
|
1041
1351
|
if (runEnd < lo || run.charStart > hi) continue;
|
|
1042
1352
|
const s = Math.max(lo, run.charStart);
|
|
1043
1353
|
const e = Math.min(hi, runEnd);
|
|
@@ -1045,63 +1355,141 @@ function searchRectsForRange(snapshot, charStart, charCount) {
|
|
|
1045
1355
|
let right = -Infinity;
|
|
1046
1356
|
let bottom = Infinity;
|
|
1047
1357
|
let top = -Infinity;
|
|
1048
|
-
let
|
|
1358
|
+
let charCountAcc = 0;
|
|
1049
1359
|
let widthSum = 0;
|
|
1050
1360
|
let prevRight = -Infinity;
|
|
1361
|
+
let firstX = 0;
|
|
1362
|
+
let lastX = 0;
|
|
1051
1363
|
const flush = () => {
|
|
1052
|
-
if (
|
|
1364
|
+
if (charCountAcc > 0 && left !== Infinity) subRuns.push({
|
|
1053
1365
|
rect: {
|
|
1054
1366
|
left,
|
|
1055
1367
|
bottom,
|
|
1056
1368
|
right,
|
|
1057
1369
|
top
|
|
1058
1370
|
},
|
|
1059
|
-
charCount:
|
|
1060
|
-
fontSize: run.fontSize
|
|
1371
|
+
charCount: charCountAcc,
|
|
1372
|
+
fontSize: run.fontSize,
|
|
1373
|
+
frame: run.frame,
|
|
1374
|
+
firstX,
|
|
1375
|
+
lastX
|
|
1061
1376
|
});
|
|
1062
1377
|
left = Infinity;
|
|
1063
1378
|
right = -Infinity;
|
|
1064
1379
|
bottom = Infinity;
|
|
1065
1380
|
top = -Infinity;
|
|
1066
|
-
|
|
1381
|
+
charCountAcc = 0;
|
|
1067
1382
|
widthSum = 0;
|
|
1068
1383
|
prevRight = -Infinity;
|
|
1069
1384
|
};
|
|
1070
1385
|
for (let ci = s; ci <= e; ci++) {
|
|
1071
|
-
const g =
|
|
1072
|
-
if ((g.flags
|
|
1073
|
-
const b = g.
|
|
1074
|
-
if (
|
|
1075
|
-
const avg = widthSum /
|
|
1386
|
+
const g = layout.glyphs[ci];
|
|
1387
|
+
if (isEmpty(g.flags)) continue;
|
|
1388
|
+
const b = g.loose;
|
|
1389
|
+
if (charCountAcc > 0 && prevRight > -Infinity) {
|
|
1390
|
+
const avg = widthSum / charCountAcc;
|
|
1076
1391
|
if (avg > 0 && Math.abs(b.left - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
|
|
1077
1392
|
}
|
|
1393
|
+
const centerX = (b.left + b.right) / 2;
|
|
1394
|
+
if (charCountAcc === 0) firstX = centerX;
|
|
1395
|
+
lastX = centerX;
|
|
1078
1396
|
left = Math.min(left, b.left);
|
|
1079
1397
|
right = Math.max(right, b.right);
|
|
1080
1398
|
bottom = Math.min(bottom, b.bottom);
|
|
1081
1399
|
top = Math.max(top, b.top);
|
|
1082
|
-
|
|
1400
|
+
charCountAcc++;
|
|
1083
1401
|
widthSum += b.right - b.left;
|
|
1084
1402
|
prevRight = b.right;
|
|
1085
1403
|
}
|
|
1086
1404
|
flush();
|
|
1087
1405
|
}
|
|
1088
|
-
return
|
|
1406
|
+
return mergeAdjacentSubRuns(subRuns).map((m) => materializeSegment(layout, m));
|
|
1089
1407
|
}
|
|
1090
|
-
function
|
|
1408
|
+
function mergeAdjacentSubRuns(runs) {
|
|
1091
1409
|
const out = [];
|
|
1092
1410
|
let prev = null;
|
|
1093
1411
|
let cur = null;
|
|
1094
1412
|
for (const run of runs) {
|
|
1095
|
-
if (prev && cur && shouldMerge(prev, run)) cur =
|
|
1413
|
+
if (prev && cur && prev.frame === run.frame && shouldMerge(prev, run)) cur = {
|
|
1414
|
+
rect: {
|
|
1415
|
+
left: Math.min(cur.rect.left, run.rect.left),
|
|
1416
|
+
bottom: Math.min(cur.rect.bottom, run.rect.bottom),
|
|
1417
|
+
right: Math.max(cur.rect.right, run.rect.right),
|
|
1418
|
+
top: Math.max(cur.rect.top, run.rect.top)
|
|
1419
|
+
},
|
|
1420
|
+
frame: cur.frame,
|
|
1421
|
+
firstX: cur.firstX,
|
|
1422
|
+
lastX: run.lastX
|
|
1423
|
+
};
|
|
1096
1424
|
else {
|
|
1097
1425
|
if (cur) out.push(cur);
|
|
1098
|
-
cur =
|
|
1426
|
+
cur = {
|
|
1427
|
+
rect: run.rect,
|
|
1428
|
+
frame: run.frame,
|
|
1429
|
+
firstX: run.firstX,
|
|
1430
|
+
lastX: run.lastX
|
|
1431
|
+
};
|
|
1099
1432
|
}
|
|
1100
1433
|
prev = run;
|
|
1101
1434
|
}
|
|
1102
|
-
if (cur) out.push(cur);
|
|
1435
|
+
if (cur && cur.rect.right > cur.rect.left && cur.rect.top > cur.rect.bottom) out.push(cur);
|
|
1103
1436
|
return out;
|
|
1104
1437
|
}
|
|
1438
|
+
/** Segment quad for a frame-local rect (frame 0: the rect's own corners,
|
|
1439
|
+
* no float round-trip). Slot order US, UE, LS, LE; frame is y-up, so the
|
|
1440
|
+
* upper corners sit at the frame TOP. */
|
|
1441
|
+
function quadOfFrameRect(f, frameIndex, r) {
|
|
1442
|
+
if (frameIndex === 0) return {
|
|
1443
|
+
p1: {
|
|
1444
|
+
x: r.left,
|
|
1445
|
+
y: r.top
|
|
1446
|
+
},
|
|
1447
|
+
p2: {
|
|
1448
|
+
x: r.right,
|
|
1449
|
+
y: r.top
|
|
1450
|
+
},
|
|
1451
|
+
p3: {
|
|
1452
|
+
x: r.left,
|
|
1453
|
+
y: r.bottom
|
|
1454
|
+
},
|
|
1455
|
+
p4: {
|
|
1456
|
+
x: r.right,
|
|
1457
|
+
y: r.bottom
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
return {
|
|
1461
|
+
p1: fromFrame(f, {
|
|
1462
|
+
x: r.left,
|
|
1463
|
+
y: r.top
|
|
1464
|
+
}),
|
|
1465
|
+
p2: fromFrame(f, {
|
|
1466
|
+
x: r.right,
|
|
1467
|
+
y: r.top
|
|
1468
|
+
}),
|
|
1469
|
+
p3: fromFrame(f, {
|
|
1470
|
+
x: r.left,
|
|
1471
|
+
y: r.bottom
|
|
1472
|
+
}),
|
|
1473
|
+
p4: fromFrame(f, {
|
|
1474
|
+
x: r.right,
|
|
1475
|
+
y: r.bottom
|
|
1476
|
+
})
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function materializeSegment(layout, m) {
|
|
1480
|
+
const frame = layout.frames[m.frame];
|
|
1481
|
+
const quad = quadOfFrameRect(frame, m.frame, m.rect);
|
|
1482
|
+
return {
|
|
1483
|
+
quad,
|
|
1484
|
+
rect: m.frame === 0 ? m.rect : {
|
|
1485
|
+
left: Math.min(quad.p1.x, quad.p2.x, quad.p3.x, quad.p4.x),
|
|
1486
|
+
bottom: Math.min(quad.p1.y, quad.p2.y, quad.p3.y, quad.p4.y),
|
|
1487
|
+
right: Math.max(quad.p1.x, quad.p2.x, quad.p3.x, quad.p4.x),
|
|
1488
|
+
top: Math.max(quad.p1.y, quad.p2.y, quad.p3.y, quad.p4.y)
|
|
1489
|
+
},
|
|
1490
|
+
advance: m.lastX >= m.firstX ? 1 : -1
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1105
1493
|
function shouldMerge(a, b) {
|
|
1106
1494
|
if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
|
|
1107
1495
|
if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
|
|
@@ -1114,14 +1502,6 @@ function shouldMerge(a, b) {
|
|
|
1114
1502
|
const bL = b.rect.left - bw;
|
|
1115
1503
|
return aL < b.rect.right + bw && aR > bL;
|
|
1116
1504
|
}
|
|
1117
|
-
function union(a, b) {
|
|
1118
|
-
return {
|
|
1119
|
-
left: Math.min(a.left, b.left),
|
|
1120
|
-
bottom: Math.min(a.bottom, b.bottom),
|
|
1121
|
-
right: Math.max(a.right, b.right),
|
|
1122
|
-
top: Math.max(a.top, b.top)
|
|
1123
|
-
};
|
|
1124
|
-
}
|
|
1125
1505
|
function verticalOverlap(a, b) {
|
|
1126
1506
|
const ah = a.top - a.bottom;
|
|
1127
1507
|
const bh = b.top - b.bottom;
|
|
@@ -1130,6 +1510,28 @@ function verticalOverlap(a, b) {
|
|
|
1130
1510
|
if (u === ah || u === bh) return 1;
|
|
1131
1511
|
return Math.max(0, Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)) / u;
|
|
1132
1512
|
}
|
|
1513
|
+
const isZero = (r) => r.right === r.left && r.top === r.bottom;
|
|
1514
|
+
function overlapV(bottom1, top1, bottom2, top2) {
|
|
1515
|
+
const u = Math.max(top1, top2) - Math.min(bottom1, bottom2);
|
|
1516
|
+
if (u === 0) return false;
|
|
1517
|
+
return Math.max(0, Math.min(top1, top2) - Math.max(bottom1, bottom2)) / u >= LINE_OVERLAP_THRESHOLD;
|
|
1518
|
+
}
|
|
1519
|
+
//#endregion
|
|
1520
|
+
//#region src/search/rects.ts
|
|
1521
|
+
/**
|
|
1522
|
+
* Match-range → canonical visual-line segments. Thin wrappers over the ONE
|
|
1523
|
+
* text layout (`src/text/layout.ts`) — search owns matching and cursors,
|
|
1524
|
+
* never segmentation, so a match highlights exactly like a selection of the
|
|
1525
|
+
* same characters. No frame or merge logic lives here.
|
|
1526
|
+
*/
|
|
1527
|
+
/** Canonical segments for `[charStart, charStart + charCount)`. */
|
|
1528
|
+
function searchSegmentsForRange(snapshot, charStart, charCount) {
|
|
1529
|
+
return textSegmentsForRange(buildPageTextLayout(snapshot), charStart, charCount);
|
|
1530
|
+
}
|
|
1531
|
+
/** The segments' AABBs — for consumers that genuinely want boxes. */
|
|
1532
|
+
function searchRectsForRange(snapshot, charStart, charCount) {
|
|
1533
|
+
return searchSegmentsForRange(snapshot, charStart, charCount).map((s) => s.rect);
|
|
1534
|
+
}
|
|
1133
1535
|
//#endregion
|
|
1134
1536
|
//#region src/search/epoch.ts
|
|
1135
1537
|
/**
|
|
@@ -1404,6 +1806,18 @@ Object.defineProperty(exports, "appearanceImpactOf", {
|
|
|
1404
1806
|
return appearanceImpactOf;
|
|
1405
1807
|
}
|
|
1406
1808
|
});
|
|
1809
|
+
Object.defineProperty(exports, "buildCommentThreads", {
|
|
1810
|
+
enumerable: true,
|
|
1811
|
+
get: function() {
|
|
1812
|
+
return buildCommentThreads;
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
Object.defineProperty(exports, "buildPageTextLayout", {
|
|
1816
|
+
enumerable: true,
|
|
1817
|
+
get: function() {
|
|
1818
|
+
return buildPageTextLayout;
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1407
1821
|
Object.defineProperty(exports, "buildSnippet", {
|
|
1408
1822
|
enumerable: true,
|
|
1409
1823
|
get: function() {
|
|
@@ -1470,6 +1884,12 @@ Object.defineProperty(exports, "decodeStableIdKey", {
|
|
|
1470
1884
|
return decodeStableIdKey;
|
|
1471
1885
|
}
|
|
1472
1886
|
});
|
|
1887
|
+
Object.defineProperty(exports, "decodeSubmitFormFlags", {
|
|
1888
|
+
enumerable: true,
|
|
1889
|
+
get: function() {
|
|
1890
|
+
return decodeSubmitFormFlags;
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1473
1893
|
Object.defineProperty(exports, "encodeFieldRefKey", {
|
|
1474
1894
|
enumerable: true,
|
|
1475
1895
|
get: function() {
|
|
@@ -1482,6 +1902,18 @@ Object.defineProperty(exports, "encodeStableIdKey", {
|
|
|
1482
1902
|
return encodeStableIdKey;
|
|
1483
1903
|
}
|
|
1484
1904
|
});
|
|
1905
|
+
Object.defineProperty(exports, "expandTextRangeToLine", {
|
|
1906
|
+
enumerable: true,
|
|
1907
|
+
get: function() {
|
|
1908
|
+
return expandTextRangeToLine;
|
|
1909
|
+
}
|
|
1910
|
+
});
|
|
1911
|
+
Object.defineProperty(exports, "expandTextRangeToWord", {
|
|
1912
|
+
enumerable: true,
|
|
1913
|
+
get: function() {
|
|
1914
|
+
return expandTextRangeToWord;
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1485
1917
|
Object.defineProperty(exports, "foldOptionsFor", {
|
|
1486
1918
|
enumerable: true,
|
|
1487
1919
|
get: function() {
|
|
@@ -1494,6 +1926,12 @@ Object.defineProperty(exports, "invalidatesWeakIndexRefs", {
|
|
|
1494
1926
|
return invalidatesWeakIndexRefs;
|
|
1495
1927
|
}
|
|
1496
1928
|
});
|
|
1929
|
+
Object.defineProperty(exports, "isStateAnnotation", {
|
|
1930
|
+
enumerable: true,
|
|
1931
|
+
get: function() {
|
|
1932
|
+
return isStateAnnotation;
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1497
1935
|
Object.defineProperty(exports, "isValidPageObjectNumber", {
|
|
1498
1936
|
enumerable: true,
|
|
1499
1937
|
get: function() {
|
|
@@ -1548,66 +1986,12 @@ Object.defineProperty(exports, "normalizeAttachmentFileSource", {
|
|
|
1548
1986
|
return normalizeAttachmentFileSource;
|
|
1549
1987
|
}
|
|
1550
1988
|
});
|
|
1551
|
-
Object.defineProperty(exports, "normalizePdfRect", {
|
|
1552
|
-
enumerable: true,
|
|
1553
|
-
get: function() {
|
|
1554
|
-
return normalizePdfRect;
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1557
1989
|
Object.defineProperty(exports, "pdfPermissions", {
|
|
1558
1990
|
enumerable: true,
|
|
1559
1991
|
get: function() {
|
|
1560
1992
|
return pdfPermissions;
|
|
1561
1993
|
}
|
|
1562
1994
|
});
|
|
1563
|
-
Object.defineProperty(exports, "pdfQuadBounds", {
|
|
1564
|
-
enumerable: true,
|
|
1565
|
-
get: function() {
|
|
1566
|
-
return pdfQuadBounds;
|
|
1567
|
-
}
|
|
1568
|
-
});
|
|
1569
|
-
Object.defineProperty(exports, "pdfQuadCorners", {
|
|
1570
|
-
enumerable: true,
|
|
1571
|
-
get: function() {
|
|
1572
|
-
return pdfQuadCorners;
|
|
1573
|
-
}
|
|
1574
|
-
});
|
|
1575
|
-
Object.defineProperty(exports, "pdfQuadFromCorners", {
|
|
1576
|
-
enumerable: true,
|
|
1577
|
-
get: function() {
|
|
1578
|
-
return pdfQuadFromCorners;
|
|
1579
|
-
}
|
|
1580
|
-
});
|
|
1581
|
-
Object.defineProperty(exports, "pdfRectFromOriginSize", {
|
|
1582
|
-
enumerable: true,
|
|
1583
|
-
get: function() {
|
|
1584
|
-
return pdfRectFromOriginSize;
|
|
1585
|
-
}
|
|
1586
|
-
});
|
|
1587
|
-
Object.defineProperty(exports, "pdfRectHeight", {
|
|
1588
|
-
enumerable: true,
|
|
1589
|
-
get: function() {
|
|
1590
|
-
return pdfRectHeight;
|
|
1591
|
-
}
|
|
1592
|
-
});
|
|
1593
|
-
Object.defineProperty(exports, "pdfRectSize", {
|
|
1594
|
-
enumerable: true,
|
|
1595
|
-
get: function() {
|
|
1596
|
-
return pdfRectSize;
|
|
1597
|
-
}
|
|
1598
|
-
});
|
|
1599
|
-
Object.defineProperty(exports, "pdfRectToOriginSize", {
|
|
1600
|
-
enumerable: true,
|
|
1601
|
-
get: function() {
|
|
1602
|
-
return pdfRectToOriginSize;
|
|
1603
|
-
}
|
|
1604
|
-
});
|
|
1605
|
-
Object.defineProperty(exports, "pdfRectWidth", {
|
|
1606
|
-
enumerable: true,
|
|
1607
|
-
get: function() {
|
|
1608
|
-
return pdfRectWidth;
|
|
1609
|
-
}
|
|
1610
|
-
});
|
|
1611
1995
|
Object.defineProperty(exports, "refKey", {
|
|
1612
1996
|
enumerable: true,
|
|
1613
1997
|
get: function() {
|
|
@@ -1638,6 +2022,12 @@ Object.defineProperty(exports, "searchRectsForRange", {
|
|
|
1638
2022
|
return searchRectsForRange;
|
|
1639
2023
|
}
|
|
1640
2024
|
});
|
|
2025
|
+
Object.defineProperty(exports, "searchSegmentsForRange", {
|
|
2026
|
+
enumerable: true,
|
|
2027
|
+
get: function() {
|
|
2028
|
+
return searchSegmentsForRange;
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
1641
2031
|
Object.defineProperty(exports, "semanticEqual", {
|
|
1642
2032
|
enumerable: true,
|
|
1643
2033
|
get: function() {
|
|
@@ -1656,6 +2046,24 @@ Object.defineProperty(exports, "sniffBinaryMetadata", {
|
|
|
1656
2046
|
return sniffBinaryMetadata;
|
|
1657
2047
|
}
|
|
1658
2048
|
});
|
|
2049
|
+
Object.defineProperty(exports, "textGlyphAt", {
|
|
2050
|
+
enumerable: true,
|
|
2051
|
+
get: function() {
|
|
2052
|
+
return textGlyphAt;
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
Object.defineProperty(exports, "textGlyphQuad", {
|
|
2056
|
+
enumerable: true,
|
|
2057
|
+
get: function() {
|
|
2058
|
+
return textGlyphQuad;
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
Object.defineProperty(exports, "textSegmentsForRange", {
|
|
2062
|
+
enumerable: true,
|
|
2063
|
+
get: function() {
|
|
2064
|
+
return textSegmentsForRange;
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
1659
2067
|
Object.defineProperty(exports, "validateSearchQuery", {
|
|
1660
2068
|
enumerable: true,
|
|
1661
2069
|
get: function() {
|
|
@@ -1681,4 +2089,4 @@ Object.defineProperty(exports, "wordBefore", {
|
|
|
1681
2089
|
}
|
|
1682
2090
|
});
|
|
1683
2091
|
|
|
1684
|
-
//# sourceMappingURL=builders-
|
|
2092
|
+
//# sourceMappingURL=builders-Do6_4KnS.cjs.map
|