@embedpdf/engine-core 3.0.0-next.2 → 3.0.0-next.4
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-ByBflvEl.cjs → AbortError-BFwCIKC6.cjs} +2 -2
- package/dist/{AbortError-ByBflvEl.cjs.map → AbortError-BFwCIKC6.cjs.map} +1 -1
- package/dist/{AbortError-CjSasIlL.js → AbortError-D0w0N4pJ.js} +2 -2
- package/dist/{AbortError-CjSasIlL.js.map → AbortError-D0w0N4pJ.js.map} +1 -1
- package/dist/{AnnotationListSnapshot-W0RCsyDE.d.cts → AnnotationListSnapshot-_Z600IzD.d.cts} +216 -20
- package/dist/{AnnotationListSnapshot-W0RCsyDE.d.ts → AnnotationListSnapshot-_Z600IzD.d.ts} +216 -20
- package/dist/{DocumentSecurityService-B-tULKNY.d.ts → DocumentSecurityService-BR4tesJK.d.ts} +2 -2
- package/dist/{DocumentSecurityService-Df79fgpe.d.cts → DocumentSecurityService-CoHQf0Ju.d.cts} +2 -2
- package/dist/{Engine-D9loFZc1.d.cts → Engine-LHgbW5z-.d.cts} +4 -4
- package/dist/{Engine-B7Oe7f5i.d.ts → Engine-h_6P4pjM.d.ts} +4 -4
- package/dist/{builders-C548U47M.js → builders-BWgXkLKd.js} +382 -167
- package/dist/builders-BWgXkLKd.js.map +1 -0
- package/dist/{builders-BvikWRuO.cjs → builders-CBK8k_cn.cjs} +423 -220
- package/dist/builders-CBK8k_cn.cjs.map +1 -0
- package/dist/conformance.cjs +303 -135
- package/dist/conformance.cjs.map +1 -1
- package/dist/conformance.d.cts +37 -3
- package/dist/conformance.d.ts +37 -3
- package/dist/conformance.js +303 -136
- package/dist/conformance.js.map +1 -1
- package/dist/{fold-CB4fAOOy.js → fold-D3_ZiKOg.js} +162 -3
- package/dist/fold-D3_ZiKOg.js.map +1 -0
- package/dist/{fold-IaMph3CO.cjs → fold-M6iEyY8X.cjs} +233 -2
- package/dist/fold-M6iEyY8X.cjs.map +1 -0
- package/dist/index.cjs +22 -12
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/runtime.cjs +23 -13
- package/dist/runtime.d.cts +4 -4
- package/dist/runtime.d.ts +4 -4
- package/dist/runtime.js +5 -5
- package/dist/{schemas-9OpSSeM6.js → schemas-C32v81cE.js} +29 -4
- package/dist/schemas-C32v81cE.js.map +1 -0
- package/dist/{schemas-B_1VyFsm.cjs → schemas-CLByd6ze.cjs} +29 -4
- package/dist/schemas-CLByd6ze.cjs.map +1 -0
- package/dist/{subtype-qejjVr6h.cjs → subtype-C4XA7C16.cjs} +9 -1
- package/dist/subtype-C4XA7C16.cjs.map +1 -0
- package/dist/{subtype-BvAzLCGC.js → subtype-DsxZbJtP.js} +9 -1
- package/dist/subtype-DsxZbJtP.js.map +1 -0
- package/dist/wire.cjs +14 -1
- package/dist/wire.cjs.map +1 -1
- package/dist/wire.d.cts +13 -4
- package/dist/wire.d.ts +13 -4
- package/dist/wire.js +14 -2
- 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/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,120 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
//#region src/geometry/convert.ts
|
|
4
|
-
/**
|
|
5
|
-
* Normalize a `PdfRect` to the y-up invariant (`left <= right`, `bottom <= top`),
|
|
6
|
-
* regardless of how the PDF ordered the corners (a `/Rect` array or `FS_RECTF`
|
|
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.
|
|
11
|
-
*/
|
|
12
|
-
function normalizePdfRect(r) {
|
|
13
|
-
return {
|
|
14
|
-
left: Math.min(r.left, r.right),
|
|
15
|
-
right: Math.max(r.left, r.right),
|
|
16
|
-
bottom: Math.min(r.top, r.bottom),
|
|
17
|
-
top: Math.max(r.top, r.bottom)
|
|
18
|
-
};
|
|
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
|
-
}
|
|
117
|
-
//#endregion
|
|
1
|
+
const require_fold = require("./fold-M6iEyY8X.cjs");
|
|
2
|
+
const require_subtype = require("./subtype-C4XA7C16.cjs");
|
|
118
3
|
//#region src/dto/PageRender.ts
|
|
119
4
|
function createPageImageHandle(result, blobSource) {
|
|
120
5
|
return {
|
|
@@ -1007,37 +892,275 @@ function buildSnippet(text, range, context = 48) {
|
|
|
1007
892
|
};
|
|
1008
893
|
}
|
|
1009
894
|
//#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
|
-
*/
|
|
895
|
+
//#region src/text/layout.ts
|
|
1025
896
|
const FLAG_EMPTY = 2;
|
|
897
|
+
const isBoundary = (flags) => (flags & 3) !== 0;
|
|
898
|
+
const isEmpty = (flags) => (flags & FLAG_EMPTY) !== 0;
|
|
1026
899
|
const CHAR_DISTANCE_FACTOR = 2.5;
|
|
1027
900
|
const FONT_SIZE_RATIO_THRESHOLD = 1.5;
|
|
1028
901
|
const VERTICAL_OVERLAP_THRESHOLD = .8;
|
|
902
|
+
const LINE_OVERLAP_THRESHOLD = .5;
|
|
903
|
+
/** Baseline directions within ~0.5° share a frame. */
|
|
904
|
+
const FRAME_DOT_TOLERANCE = Math.cos(.0087);
|
|
905
|
+
const EDGE_EPSILON = 1e-6;
|
|
906
|
+
const IDENTITY_FRAME = {
|
|
907
|
+
baseline: {
|
|
908
|
+
x: 1,
|
|
909
|
+
y: 0
|
|
910
|
+
},
|
|
911
|
+
ascent: {
|
|
912
|
+
x: 0,
|
|
913
|
+
y: 1
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
const toFrame = (f, p) => ({
|
|
917
|
+
x: p.x * f.baseline.x + p.y * f.baseline.y,
|
|
918
|
+
y: p.x * f.ascent.x + p.y * f.ascent.y
|
|
919
|
+
});
|
|
920
|
+
const fromFrame = (f, p) => ({
|
|
921
|
+
x: f.baseline.x * p.x + f.ascent.x * p.y,
|
|
922
|
+
y: f.baseline.y * p.x + f.ascent.y * p.y
|
|
923
|
+
});
|
|
924
|
+
const ZERO_RECT = {
|
|
925
|
+
left: 0,
|
|
926
|
+
bottom: 0,
|
|
927
|
+
right: 0,
|
|
928
|
+
top: 0
|
|
929
|
+
};
|
|
930
|
+
/** Frame-local AABB of a page-space cell (exact under the frame's rotation;
|
|
931
|
+
* shear residue is the documented in-frame envelope). */
|
|
932
|
+
function frameBoxOfQuad(f, q) {
|
|
933
|
+
const pts = [
|
|
934
|
+
q.p1,
|
|
935
|
+
q.p2,
|
|
936
|
+
q.p3,
|
|
937
|
+
q.p4
|
|
938
|
+
].map((p) => toFrame(f, p));
|
|
939
|
+
const xs = pts.map((p) => p.x);
|
|
940
|
+
const ys = pts.map((p) => p.y);
|
|
941
|
+
return {
|
|
942
|
+
left: Math.min(...xs),
|
|
943
|
+
bottom: Math.min(...ys),
|
|
944
|
+
right: Math.max(...xs),
|
|
945
|
+
top: Math.max(...ys)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
/** Find-or-create the frame for a rotated run, keyed by the baseline
|
|
949
|
+
* direction + ascent handedness of its first classifiable glyph's edges. */
|
|
950
|
+
function frameForRun(frames, run) {
|
|
951
|
+
const seed = run.glyphs.find((g) => !isEmpty(g.flags));
|
|
952
|
+
if (!seed) return 0;
|
|
953
|
+
const q = seed.looseQuad;
|
|
954
|
+
const bx = q.p2.x - q.p1.x;
|
|
955
|
+
const by = q.p2.y - q.p1.y;
|
|
956
|
+
const len = Math.hypot(bx, by);
|
|
957
|
+
if (len <= EDGE_EPSILON) return 0;
|
|
958
|
+
const baseline = {
|
|
959
|
+
x: bx / len,
|
|
960
|
+
y: by / len
|
|
961
|
+
};
|
|
962
|
+
const ax = q.p1.x - q.p3.x;
|
|
963
|
+
const ay = q.p1.y - q.p3.y;
|
|
964
|
+
const sign = baseline.x * ay - baseline.y * ax >= 0 ? 1 : -1;
|
|
965
|
+
const ascent = {
|
|
966
|
+
x: -baseline.y * sign,
|
|
967
|
+
y: baseline.x * sign
|
|
968
|
+
};
|
|
969
|
+
for (let i = 0; i < frames.length; i++) {
|
|
970
|
+
const f = frames[i];
|
|
971
|
+
const dot = f.baseline.x * baseline.x + f.baseline.y * baseline.y;
|
|
972
|
+
const sameHandedness = f.ascent.x * ascent.x + f.ascent.y * ascent.y > 0;
|
|
973
|
+
if (dot >= FRAME_DOT_TOLERANCE && sameHandedness) return i;
|
|
974
|
+
}
|
|
975
|
+
frames.push({
|
|
976
|
+
baseline,
|
|
977
|
+
ascent
|
|
978
|
+
});
|
|
979
|
+
return frames.length - 1;
|
|
980
|
+
}
|
|
1029
981
|
/**
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
982
|
+
* Flatten a page's geometry snapshot into the canonical layout. Upright runs
|
|
983
|
+
* copy their wire boxes verbatim (frame 0 — byte-identical to the
|
|
984
|
+
* pre-orientation behavior, wire run rects included); rotated runs project
|
|
985
|
+
* their glyph cells into their cluster's frame.
|
|
1033
986
|
*/
|
|
1034
|
-
function
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1038
|
-
const subRuns = [];
|
|
987
|
+
function buildPageTextLayout(snapshot) {
|
|
988
|
+
const glyphs = [];
|
|
989
|
+
const runs = [];
|
|
990
|
+
const frames = [IDENTITY_FRAME];
|
|
1039
991
|
for (const run of snapshot.runs) {
|
|
1040
|
-
|
|
992
|
+
if (!require_fold.isRotatedGeometryRun(run)) {
|
|
993
|
+
for (const g of run.glyphs) glyphs.push({
|
|
994
|
+
loose: g.looseBox,
|
|
995
|
+
tight: g.tightBox,
|
|
996
|
+
flags: g.flags
|
|
997
|
+
});
|
|
998
|
+
runs.push({
|
|
999
|
+
charStart: run.charStart,
|
|
1000
|
+
count: run.glyphs.length,
|
|
1001
|
+
rect: run.rect,
|
|
1002
|
+
fontSize: run.fontSize,
|
|
1003
|
+
frame: 0
|
|
1004
|
+
});
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const frame = frameForRun(frames, run);
|
|
1008
|
+
const f = frames[frame];
|
|
1009
|
+
let runBox = null;
|
|
1010
|
+
for (const g of run.glyphs) {
|
|
1011
|
+
if (isEmpty(g.flags)) {
|
|
1012
|
+
glyphs.push({
|
|
1013
|
+
loose: ZERO_RECT,
|
|
1014
|
+
flags: g.flags
|
|
1015
|
+
});
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
const loose = frameBoxOfQuad(f, g.looseQuad);
|
|
1019
|
+
const tight = g.tightQuad ? frameBoxOfQuad(f, g.tightQuad) : void 0;
|
|
1020
|
+
glyphs.push({
|
|
1021
|
+
loose,
|
|
1022
|
+
tight,
|
|
1023
|
+
flags: g.flags
|
|
1024
|
+
});
|
|
1025
|
+
runBox = runBox ? {
|
|
1026
|
+
left: Math.min(runBox.left, loose.left),
|
|
1027
|
+
bottom: Math.min(runBox.bottom, loose.bottom),
|
|
1028
|
+
right: Math.max(runBox.right, loose.right),
|
|
1029
|
+
top: Math.max(runBox.top, loose.top)
|
|
1030
|
+
} : loose;
|
|
1031
|
+
}
|
|
1032
|
+
runs.push({
|
|
1033
|
+
charStart: run.charStart,
|
|
1034
|
+
count: run.glyphs.length,
|
|
1035
|
+
rect: runBox ?? ZERO_RECT,
|
|
1036
|
+
fontSize: run.fontSize,
|
|
1037
|
+
frame
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
glyphs,
|
|
1042
|
+
runs,
|
|
1043
|
+
frames
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
const inRect = (r, p) => p.x >= r.left && p.x <= r.right && p.y >= r.bottom && p.y <= r.top;
|
|
1047
|
+
function avgGlyphHeight(layout) {
|
|
1048
|
+
let total = 0;
|
|
1049
|
+
let count = 0;
|
|
1050
|
+
for (const g of layout.glyphs) {
|
|
1051
|
+
if (isEmpty(g.flags)) continue;
|
|
1052
|
+
total += g.loose.top - g.loose.bottom;
|
|
1053
|
+
count++;
|
|
1054
|
+
}
|
|
1055
|
+
return count === 0 ? 0 : total / count;
|
|
1056
|
+
}
|
|
1057
|
+
/** The point expressed in every frame (index-aligned with `layout.frames`).
|
|
1058
|
+
* Frame 0 returns the point itself — no float round-trip on the fast path. */
|
|
1059
|
+
function pointPerFrame(layout, p) {
|
|
1060
|
+
return layout.frames.map((f, i) => i === 0 ? p : toFrame(f, p));
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* The glyph (char) index at a page-space point, or null when nothing is near
|
|
1064
|
+
* (so callers can show the pointer cursor off-text). PDFium `GetIndexAtPos`:
|
|
1065
|
+
* exact tight-box containment first, then a tolerance pass (closest by
|
|
1066
|
+
* Manhattan distance within `toleranceFactor × average glyph height`). Each
|
|
1067
|
+
* run tests the point in ITS OWN frame, so rotated text hit-tests exactly.
|
|
1068
|
+
*/
|
|
1069
|
+
function textGlyphAt(layout, point, toleranceFactor = 1.5) {
|
|
1070
|
+
const pts = pointPerFrame(layout, point);
|
|
1071
|
+
for (const run of layout.runs) {
|
|
1072
|
+
const q = pts[run.frame];
|
|
1073
|
+
if (!inRect(run.rect, q)) continue;
|
|
1074
|
+
for (let i = 0; i < run.count; i++) {
|
|
1075
|
+
const g = layout.glyphs[run.charStart + i];
|
|
1076
|
+
if (inRect(g.tight ?? g.loose, q)) return run.charStart + i;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (toleranceFactor <= 0) return null;
|
|
1080
|
+
const half = avgGlyphHeight(layout) * toleranceFactor / 2;
|
|
1081
|
+
let best = -1;
|
|
1082
|
+
let bestDist = Infinity;
|
|
1083
|
+
for (const run of layout.runs) {
|
|
1084
|
+
const q = pts[run.frame];
|
|
1085
|
+
const r = run.rect;
|
|
1086
|
+
if (q.x < r.left - half || q.x > r.right + half || q.y < r.bottom - half || q.y > r.top + half) continue;
|
|
1087
|
+
for (let i = 0; i < run.count; i++) {
|
|
1088
|
+
const g = layout.glyphs[run.charStart + i];
|
|
1089
|
+
if (isEmpty(g.flags)) continue;
|
|
1090
|
+
const b = g.tight ?? g.loose;
|
|
1091
|
+
if (q.x < b.left - half || q.x > b.right + half || q.y < b.bottom - half || q.y > b.top + half) continue;
|
|
1092
|
+
const dx = Math.min(Math.abs(q.x - b.left), Math.abs(q.x - b.right));
|
|
1093
|
+
const dy = Math.min(Math.abs(q.y - b.bottom), Math.abs(q.y - b.top));
|
|
1094
|
+
if (dx + dy < bestDist) {
|
|
1095
|
+
bestDist = dx + dy;
|
|
1096
|
+
best = run.charStart + i;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return best >= 0 ? best : null;
|
|
1101
|
+
}
|
|
1102
|
+
/** Double-click: the word around `glyph` (walk to space/empty glyphs both ways). */
|
|
1103
|
+
function expandTextRangeToWord(layout, glyph) {
|
|
1104
|
+
const n = layout.glyphs.length;
|
|
1105
|
+
if (glyph < 0 || glyph >= n) return [glyph, glyph];
|
|
1106
|
+
let from = glyph;
|
|
1107
|
+
while (from > 0 && !isBoundary(layout.glyphs[from - 1].flags)) from--;
|
|
1108
|
+
let to = glyph;
|
|
1109
|
+
while (to < n - 1 && !isBoundary(layout.glyphs[to + 1].flags)) to++;
|
|
1110
|
+
return [from, to];
|
|
1111
|
+
}
|
|
1112
|
+
/** Triple-click: the full visual line — SAME-FRAME runs whose vertical extent
|
|
1113
|
+
* overlaps the anchor run's. A differently-oriented run is a line boundary. */
|
|
1114
|
+
function expandTextRangeToLine(layout, glyph) {
|
|
1115
|
+
const ri = layout.runs.findIndex((r) => glyph >= r.charStart && glyph < r.charStart + r.count);
|
|
1116
|
+
if (ri < 0) return [glyph, glyph];
|
|
1117
|
+
const anchor = layout.runs[ri];
|
|
1118
|
+
const bottom = anchor.rect.bottom;
|
|
1119
|
+
const top = anchor.rect.top;
|
|
1120
|
+
let from = anchor.charStart;
|
|
1121
|
+
let to = anchor.charStart + anchor.count - 1;
|
|
1122
|
+
for (let r = ri - 1; r >= 0; r--) {
|
|
1123
|
+
const run = layout.runs[r];
|
|
1124
|
+
if (isZero(run.rect)) continue;
|
|
1125
|
+
if (run.frame !== anchor.frame) break;
|
|
1126
|
+
if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
|
|
1127
|
+
from = run.charStart;
|
|
1128
|
+
}
|
|
1129
|
+
for (let r = ri + 1; r < layout.runs.length; r++) {
|
|
1130
|
+
const run = layout.runs[r];
|
|
1131
|
+
if (isZero(run.rect)) continue;
|
|
1132
|
+
if (run.frame !== anchor.frame) break;
|
|
1133
|
+
if (!overlapV(run.rect.bottom, run.rect.top, bottom, top)) break;
|
|
1134
|
+
to = run.charStart + run.count - 1;
|
|
1135
|
+
}
|
|
1136
|
+
return [from, to];
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* A single glyph's oriented cell in page space, or null for degenerate
|
|
1140
|
+
* glyphs — the anchor for caret placement at a selection boundary.
|
|
1141
|
+
*/
|
|
1142
|
+
function textGlyphQuad(layout, glyph) {
|
|
1143
|
+
const g = layout.glyphs[glyph];
|
|
1144
|
+
if (!g || isEmpty(g.flags)) return null;
|
|
1145
|
+
const b = g.loose;
|
|
1146
|
+
if (b.right <= b.left || b.top <= b.bottom) return null;
|
|
1147
|
+
const run = layout.runs.find((r) => glyph >= r.charStart && glyph < r.charStart + r.count);
|
|
1148
|
+
const f = layout.frames[run?.frame ?? 0];
|
|
1149
|
+
return quadOfFrameRect(f, run?.frame ?? 0, b);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Merged visual-line segments for the half-open char range
|
|
1153
|
+
* `[charStart, charStart + charCount)` — THE canonical segmentation. Chars
|
|
1154
|
+
* outside the layout are ignored.
|
|
1155
|
+
*/
|
|
1156
|
+
function textSegmentsForRange(layout, charStart, charCount) {
|
|
1157
|
+
if (charCount <= 0 || layout.glyphs.length === 0) return [];
|
|
1158
|
+
const lo = Math.max(0, charStart);
|
|
1159
|
+
const hi = Math.min(layout.glyphs.length - 1, charStart + charCount - 1);
|
|
1160
|
+
if (hi < lo) return [];
|
|
1161
|
+
const subRuns = [];
|
|
1162
|
+
for (const run of layout.runs) {
|
|
1163
|
+
const runEnd = run.charStart + run.count - 1;
|
|
1041
1164
|
if (runEnd < lo || run.charStart > hi) continue;
|
|
1042
1165
|
const s = Math.max(lo, run.charStart);
|
|
1043
1166
|
const e = Math.min(hi, runEnd);
|
|
@@ -1045,63 +1168,141 @@ function searchRectsForRange(snapshot, charStart, charCount) {
|
|
|
1045
1168
|
let right = -Infinity;
|
|
1046
1169
|
let bottom = Infinity;
|
|
1047
1170
|
let top = -Infinity;
|
|
1048
|
-
let
|
|
1171
|
+
let charCountAcc = 0;
|
|
1049
1172
|
let widthSum = 0;
|
|
1050
1173
|
let prevRight = -Infinity;
|
|
1174
|
+
let firstX = 0;
|
|
1175
|
+
let lastX = 0;
|
|
1051
1176
|
const flush = () => {
|
|
1052
|
-
if (
|
|
1177
|
+
if (charCountAcc > 0 && left !== Infinity) subRuns.push({
|
|
1053
1178
|
rect: {
|
|
1054
1179
|
left,
|
|
1055
1180
|
bottom,
|
|
1056
1181
|
right,
|
|
1057
1182
|
top
|
|
1058
1183
|
},
|
|
1059
|
-
charCount:
|
|
1060
|
-
fontSize: run.fontSize
|
|
1184
|
+
charCount: charCountAcc,
|
|
1185
|
+
fontSize: run.fontSize,
|
|
1186
|
+
frame: run.frame,
|
|
1187
|
+
firstX,
|
|
1188
|
+
lastX
|
|
1061
1189
|
});
|
|
1062
1190
|
left = Infinity;
|
|
1063
1191
|
right = -Infinity;
|
|
1064
1192
|
bottom = Infinity;
|
|
1065
1193
|
top = -Infinity;
|
|
1066
|
-
|
|
1194
|
+
charCountAcc = 0;
|
|
1067
1195
|
widthSum = 0;
|
|
1068
1196
|
prevRight = -Infinity;
|
|
1069
1197
|
};
|
|
1070
1198
|
for (let ci = s; ci <= e; ci++) {
|
|
1071
|
-
const g =
|
|
1072
|
-
if ((g.flags
|
|
1073
|
-
const b = g.
|
|
1074
|
-
if (
|
|
1075
|
-
const avg = widthSum /
|
|
1199
|
+
const g = layout.glyphs[ci];
|
|
1200
|
+
if (isEmpty(g.flags)) continue;
|
|
1201
|
+
const b = g.loose;
|
|
1202
|
+
if (charCountAcc > 0 && prevRight > -Infinity) {
|
|
1203
|
+
const avg = widthSum / charCountAcc;
|
|
1076
1204
|
if (avg > 0 && Math.abs(b.left - prevRight) > CHAR_DISTANCE_FACTOR * avg) flush();
|
|
1077
1205
|
}
|
|
1206
|
+
const centerX = (b.left + b.right) / 2;
|
|
1207
|
+
if (charCountAcc === 0) firstX = centerX;
|
|
1208
|
+
lastX = centerX;
|
|
1078
1209
|
left = Math.min(left, b.left);
|
|
1079
1210
|
right = Math.max(right, b.right);
|
|
1080
1211
|
bottom = Math.min(bottom, b.bottom);
|
|
1081
1212
|
top = Math.max(top, b.top);
|
|
1082
|
-
|
|
1213
|
+
charCountAcc++;
|
|
1083
1214
|
widthSum += b.right - b.left;
|
|
1084
1215
|
prevRight = b.right;
|
|
1085
1216
|
}
|
|
1086
1217
|
flush();
|
|
1087
1218
|
}
|
|
1088
|
-
return
|
|
1219
|
+
return mergeAdjacentSubRuns(subRuns).map((m) => materializeSegment(layout, m));
|
|
1089
1220
|
}
|
|
1090
|
-
function
|
|
1221
|
+
function mergeAdjacentSubRuns(runs) {
|
|
1091
1222
|
const out = [];
|
|
1092
1223
|
let prev = null;
|
|
1093
1224
|
let cur = null;
|
|
1094
1225
|
for (const run of runs) {
|
|
1095
|
-
if (prev && cur && shouldMerge(prev, run)) cur =
|
|
1226
|
+
if (prev && cur && prev.frame === run.frame && shouldMerge(prev, run)) cur = {
|
|
1227
|
+
rect: {
|
|
1228
|
+
left: Math.min(cur.rect.left, run.rect.left),
|
|
1229
|
+
bottom: Math.min(cur.rect.bottom, run.rect.bottom),
|
|
1230
|
+
right: Math.max(cur.rect.right, run.rect.right),
|
|
1231
|
+
top: Math.max(cur.rect.top, run.rect.top)
|
|
1232
|
+
},
|
|
1233
|
+
frame: cur.frame,
|
|
1234
|
+
firstX: cur.firstX,
|
|
1235
|
+
lastX: run.lastX
|
|
1236
|
+
};
|
|
1096
1237
|
else {
|
|
1097
1238
|
if (cur) out.push(cur);
|
|
1098
|
-
cur =
|
|
1239
|
+
cur = {
|
|
1240
|
+
rect: run.rect,
|
|
1241
|
+
frame: run.frame,
|
|
1242
|
+
firstX: run.firstX,
|
|
1243
|
+
lastX: run.lastX
|
|
1244
|
+
};
|
|
1099
1245
|
}
|
|
1100
1246
|
prev = run;
|
|
1101
1247
|
}
|
|
1102
|
-
if (cur) out.push(cur);
|
|
1248
|
+
if (cur && cur.rect.right > cur.rect.left && cur.rect.top > cur.rect.bottom) out.push(cur);
|
|
1103
1249
|
return out;
|
|
1104
1250
|
}
|
|
1251
|
+
/** Segment quad for a frame-local rect (frame 0: the rect's own corners,
|
|
1252
|
+
* no float round-trip). Slot order US, UE, LS, LE; frame is y-up, so the
|
|
1253
|
+
* upper corners sit at the frame TOP. */
|
|
1254
|
+
function quadOfFrameRect(f, frameIndex, r) {
|
|
1255
|
+
if (frameIndex === 0) return {
|
|
1256
|
+
p1: {
|
|
1257
|
+
x: r.left,
|
|
1258
|
+
y: r.top
|
|
1259
|
+
},
|
|
1260
|
+
p2: {
|
|
1261
|
+
x: r.right,
|
|
1262
|
+
y: r.top
|
|
1263
|
+
},
|
|
1264
|
+
p3: {
|
|
1265
|
+
x: r.left,
|
|
1266
|
+
y: r.bottom
|
|
1267
|
+
},
|
|
1268
|
+
p4: {
|
|
1269
|
+
x: r.right,
|
|
1270
|
+
y: r.bottom
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
return {
|
|
1274
|
+
p1: fromFrame(f, {
|
|
1275
|
+
x: r.left,
|
|
1276
|
+
y: r.top
|
|
1277
|
+
}),
|
|
1278
|
+
p2: fromFrame(f, {
|
|
1279
|
+
x: r.right,
|
|
1280
|
+
y: r.top
|
|
1281
|
+
}),
|
|
1282
|
+
p3: fromFrame(f, {
|
|
1283
|
+
x: r.left,
|
|
1284
|
+
y: r.bottom
|
|
1285
|
+
}),
|
|
1286
|
+
p4: fromFrame(f, {
|
|
1287
|
+
x: r.right,
|
|
1288
|
+
y: r.bottom
|
|
1289
|
+
})
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function materializeSegment(layout, m) {
|
|
1293
|
+
const frame = layout.frames[m.frame];
|
|
1294
|
+
const quad = quadOfFrameRect(frame, m.frame, m.rect);
|
|
1295
|
+
return {
|
|
1296
|
+
quad,
|
|
1297
|
+
rect: m.frame === 0 ? m.rect : {
|
|
1298
|
+
left: Math.min(quad.p1.x, quad.p2.x, quad.p3.x, quad.p4.x),
|
|
1299
|
+
bottom: Math.min(quad.p1.y, quad.p2.y, quad.p3.y, quad.p4.y),
|
|
1300
|
+
right: Math.max(quad.p1.x, quad.p2.x, quad.p3.x, quad.p4.x),
|
|
1301
|
+
top: Math.max(quad.p1.y, quad.p2.y, quad.p3.y, quad.p4.y)
|
|
1302
|
+
},
|
|
1303
|
+
advance: m.lastX >= m.firstX ? 1 : -1
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1105
1306
|
function shouldMerge(a, b) {
|
|
1106
1307
|
if (a.fontSize != null && b.fontSize != null && a.fontSize > 0 && b.fontSize > 0) {
|
|
1107
1308
|
if (Math.max(a.fontSize, b.fontSize) / Math.min(a.fontSize, b.fontSize) > FONT_SIZE_RATIO_THRESHOLD) return false;
|
|
@@ -1114,14 +1315,6 @@ function shouldMerge(a, b) {
|
|
|
1114
1315
|
const bL = b.rect.left - bw;
|
|
1115
1316
|
return aL < b.rect.right + bw && aR > bL;
|
|
1116
1317
|
}
|
|
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
1318
|
function verticalOverlap(a, b) {
|
|
1126
1319
|
const ah = a.top - a.bottom;
|
|
1127
1320
|
const bh = b.top - b.bottom;
|
|
@@ -1130,6 +1323,28 @@ function verticalOverlap(a, b) {
|
|
|
1130
1323
|
if (u === ah || u === bh) return 1;
|
|
1131
1324
|
return Math.max(0, Math.min(a.top, b.top) - Math.max(a.bottom, b.bottom)) / u;
|
|
1132
1325
|
}
|
|
1326
|
+
const isZero = (r) => r.right === r.left && r.top === r.bottom;
|
|
1327
|
+
function overlapV(bottom1, top1, bottom2, top2) {
|
|
1328
|
+
const u = Math.max(top1, top2) - Math.min(bottom1, bottom2);
|
|
1329
|
+
if (u === 0) return false;
|
|
1330
|
+
return Math.max(0, Math.min(top1, top2) - Math.max(bottom1, bottom2)) / u >= LINE_OVERLAP_THRESHOLD;
|
|
1331
|
+
}
|
|
1332
|
+
//#endregion
|
|
1333
|
+
//#region src/search/rects.ts
|
|
1334
|
+
/**
|
|
1335
|
+
* Match-range → canonical visual-line segments. Thin wrappers over the ONE
|
|
1336
|
+
* text layout (`src/text/layout.ts`) — search owns matching and cursors,
|
|
1337
|
+
* never segmentation, so a match highlights exactly like a selection of the
|
|
1338
|
+
* same characters. No frame or merge logic lives here.
|
|
1339
|
+
*/
|
|
1340
|
+
/** Canonical segments for `[charStart, charStart + charCount)`. */
|
|
1341
|
+
function searchSegmentsForRange(snapshot, charStart, charCount) {
|
|
1342
|
+
return textSegmentsForRange(buildPageTextLayout(snapshot), charStart, charCount);
|
|
1343
|
+
}
|
|
1344
|
+
/** The segments' AABBs — for consumers that genuinely want boxes. */
|
|
1345
|
+
function searchRectsForRange(snapshot, charStart, charCount) {
|
|
1346
|
+
return searchSegmentsForRange(snapshot, charStart, charCount).map((s) => s.rect);
|
|
1347
|
+
}
|
|
1133
1348
|
//#endregion
|
|
1134
1349
|
//#region src/search/epoch.ts
|
|
1135
1350
|
/**
|
|
@@ -1404,6 +1619,12 @@ Object.defineProperty(exports, "appearanceImpactOf", {
|
|
|
1404
1619
|
return appearanceImpactOf;
|
|
1405
1620
|
}
|
|
1406
1621
|
});
|
|
1622
|
+
Object.defineProperty(exports, "buildPageTextLayout", {
|
|
1623
|
+
enumerable: true,
|
|
1624
|
+
get: function() {
|
|
1625
|
+
return buildPageTextLayout;
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1407
1628
|
Object.defineProperty(exports, "buildSnippet", {
|
|
1408
1629
|
enumerable: true,
|
|
1409
1630
|
get: function() {
|
|
@@ -1482,6 +1703,18 @@ Object.defineProperty(exports, "encodeStableIdKey", {
|
|
|
1482
1703
|
return encodeStableIdKey;
|
|
1483
1704
|
}
|
|
1484
1705
|
});
|
|
1706
|
+
Object.defineProperty(exports, "expandTextRangeToLine", {
|
|
1707
|
+
enumerable: true,
|
|
1708
|
+
get: function() {
|
|
1709
|
+
return expandTextRangeToLine;
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
Object.defineProperty(exports, "expandTextRangeToWord", {
|
|
1713
|
+
enumerable: true,
|
|
1714
|
+
get: function() {
|
|
1715
|
+
return expandTextRangeToWord;
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1485
1718
|
Object.defineProperty(exports, "foldOptionsFor", {
|
|
1486
1719
|
enumerable: true,
|
|
1487
1720
|
get: function() {
|
|
@@ -1548,66 +1781,12 @@ Object.defineProperty(exports, "normalizeAttachmentFileSource", {
|
|
|
1548
1781
|
return normalizeAttachmentFileSource;
|
|
1549
1782
|
}
|
|
1550
1783
|
});
|
|
1551
|
-
Object.defineProperty(exports, "normalizePdfRect", {
|
|
1552
|
-
enumerable: true,
|
|
1553
|
-
get: function() {
|
|
1554
|
-
return normalizePdfRect;
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1557
1784
|
Object.defineProperty(exports, "pdfPermissions", {
|
|
1558
1785
|
enumerable: true,
|
|
1559
1786
|
get: function() {
|
|
1560
1787
|
return pdfPermissions;
|
|
1561
1788
|
}
|
|
1562
1789
|
});
|
|
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
1790
|
Object.defineProperty(exports, "refKey", {
|
|
1612
1791
|
enumerable: true,
|
|
1613
1792
|
get: function() {
|
|
@@ -1638,6 +1817,12 @@ Object.defineProperty(exports, "searchRectsForRange", {
|
|
|
1638
1817
|
return searchRectsForRange;
|
|
1639
1818
|
}
|
|
1640
1819
|
});
|
|
1820
|
+
Object.defineProperty(exports, "searchSegmentsForRange", {
|
|
1821
|
+
enumerable: true,
|
|
1822
|
+
get: function() {
|
|
1823
|
+
return searchSegmentsForRange;
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1641
1826
|
Object.defineProperty(exports, "semanticEqual", {
|
|
1642
1827
|
enumerable: true,
|
|
1643
1828
|
get: function() {
|
|
@@ -1656,6 +1841,24 @@ Object.defineProperty(exports, "sniffBinaryMetadata", {
|
|
|
1656
1841
|
return sniffBinaryMetadata;
|
|
1657
1842
|
}
|
|
1658
1843
|
});
|
|
1844
|
+
Object.defineProperty(exports, "textGlyphAt", {
|
|
1845
|
+
enumerable: true,
|
|
1846
|
+
get: function() {
|
|
1847
|
+
return textGlyphAt;
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
Object.defineProperty(exports, "textGlyphQuad", {
|
|
1851
|
+
enumerable: true,
|
|
1852
|
+
get: function() {
|
|
1853
|
+
return textGlyphQuad;
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
Object.defineProperty(exports, "textSegmentsForRange", {
|
|
1857
|
+
enumerable: true,
|
|
1858
|
+
get: function() {
|
|
1859
|
+
return textSegmentsForRange;
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1659
1862
|
Object.defineProperty(exports, "validateSearchQuery", {
|
|
1660
1863
|
enumerable: true,
|
|
1661
1864
|
get: function() {
|
|
@@ -1681,4 +1884,4 @@ Object.defineProperty(exports, "wordBefore", {
|
|
|
1681
1884
|
}
|
|
1682
1885
|
});
|
|
1683
1886
|
|
|
1684
|
-
//# sourceMappingURL=builders-
|
|
1887
|
+
//# sourceMappingURL=builders-CBK8k_cn.cjs.map
|