@mlightcad/cad-pdf-plugin 1.8.0-preview.2 → 1.8.0-preview.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/LICENSE +21 -21
- package/README.md +280 -186
- package/dist/cad-pdf-plugin-register.js +29 -11
- package/dist/cad-pdf-plugin-register.umd.cjs +1 -1
- package/dist/cad-pdf-plugin.js +17568 -8428
- package/dist/cad-pdf-plugin.umd.cjs +847 -13
- package/lib/AcApConvertToPdfCmd.d.ts.map +1 -1
- package/lib/AcApConvertToPdfCmd.js +14 -3
- package/lib/AcApConvertToPdfCmd.js.map +1 -1
- package/lib/AcApImportPdfCmd.d.ts +5 -3
- package/lib/AcApImportPdfCmd.d.ts.map +1 -1
- package/lib/AcApImportPdfCmd.js +5 -3
- package/lib/AcApImportPdfCmd.js.map +1 -1
- package/lib/AcApImportPdfPromptCmd.d.ts +16 -0
- package/lib/AcApImportPdfPromptCmd.d.ts.map +1 -0
- package/lib/AcApImportPdfPromptCmd.js +89 -0
- package/lib/AcApImportPdfPromptCmd.js.map +1 -0
- package/lib/AcApPdfBlockCmd.d.ts +15 -0
- package/lib/AcApPdfBlockCmd.d.ts.map +1 -0
- package/lib/AcApPdfBlockCmd.js +75 -0
- package/lib/AcApPdfBlockCmd.js.map +1 -0
- package/lib/AcApPdfDimCmd.d.ts +20 -0
- package/lib/AcApPdfDimCmd.d.ts.map +1 -0
- package/lib/AcApPdfDimCmd.js +413 -0
- package/lib/AcApPdfDimCmd.js.map +1 -0
- package/lib/AcApPdfDimSession.d.ts +82 -0
- package/lib/AcApPdfDimSession.d.ts.map +1 -0
- package/lib/AcApPdfDimSession.js +252 -0
- package/lib/AcApPdfDimSession.js.map +1 -0
- package/lib/AcApPdfFontResolver.d.ts.map +1 -1
- package/lib/AcApPdfFontResolver.js +2 -5
- package/lib/AcApPdfFontResolver.js.map +1 -1
- package/lib/AcApPdfHatchCmd.d.ts +33 -0
- package/lib/AcApPdfHatchCmd.d.ts.map +1 -0
- package/lib/AcApPdfHatchCmd.js +411 -0
- package/lib/AcApPdfHatchCmd.js.map +1 -0
- package/lib/AcApPdfHatchSession.d.ts +122 -0
- package/lib/AcApPdfHatchSession.d.ts.map +1 -0
- package/lib/AcApPdfHatchSession.js +357 -0
- package/lib/AcApPdfHatchSession.js.map +1 -0
- package/lib/AcApPdfImportConvertor.d.ts +47 -5
- package/lib/AcApPdfImportConvertor.d.ts.map +1 -1
- package/lib/AcApPdfImportConvertor.js +999 -108
- package/lib/AcApPdfImportConvertor.js.map +1 -1
- package/lib/AcApPdfImportOptions.d.ts +116 -0
- package/lib/AcApPdfImportOptions.d.ts.map +1 -0
- package/lib/AcApPdfImportOptions.js +31 -0
- package/lib/AcApPdfImportOptions.js.map +1 -0
- package/lib/AcApPdfImportResources.d.ts +41 -0
- package/lib/AcApPdfImportResources.d.ts.map +1 -0
- package/lib/AcApPdfImportResources.js +52 -0
- package/lib/AcApPdfImportResources.js.map +1 -0
- package/lib/AcApPdfPlugin.d.ts +3 -3
- package/lib/AcApPdfPlugin.d.ts.map +1 -1
- package/lib/AcApPdfPlugin.js +23 -5
- package/lib/AcApPdfPlugin.js.map +1 -1
- package/lib/AcApPdfScaleCmd.d.ts +22 -0
- package/lib/AcApPdfScaleCmd.d.ts.map +1 -0
- package/lib/AcApPdfScaleCmd.js +185 -0
- package/lib/AcApPdfScaleCmd.js.map +1 -0
- package/lib/AcApPdfTextCmd.d.ts +73 -0
- package/lib/AcApPdfTextCmd.d.ts.map +1 -0
- package/lib/AcApPdfTextCmd.js +777 -0
- package/lib/AcApPdfTextCmd.js.map +1 -0
- package/lib/AcApPdfTextSession.d.ts +232 -0
- package/lib/AcApPdfTextSession.d.ts.map +1 -0
- package/lib/AcApPdfTextSession.js +826 -0
- package/lib/AcApPdfTextSession.js.map +1 -0
- package/lib/acapPdfI18n.d.ts +11 -0
- package/lib/acapPdfI18n.d.ts.map +1 -0
- package/lib/acapPdfI18n.js +57 -0
- package/lib/acapPdfI18n.js.map +1 -0
- package/lib/acapPdfPrompts.d.ts +12 -0
- package/lib/acapPdfPrompts.d.ts.map +1 -0
- package/lib/acapPdfPrompts.js +100 -0
- package/lib/acapPdfPrompts.js.map +1 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -1
- package/lib/pdfDeprecatedMatching.d.ts +27 -0
- package/lib/pdfDeprecatedMatching.d.ts.map +1 -0
- package/lib/pdfDeprecatedMatching.js +20 -0
- package/lib/pdfDeprecatedMatching.js.map +1 -0
- package/lib/pdfDimStrokeTexts.d.ts +55 -0
- package/lib/pdfDimStrokeTexts.d.ts.map +1 -0
- package/lib/pdfDimStrokeTexts.js +274 -0
- package/lib/pdfDimStrokeTexts.js.map +1 -0
- package/lib/pdfDimensionConvert.d.ts +80 -0
- package/lib/pdfDimensionConvert.d.ts.map +1 -0
- package/lib/pdfDimensionConvert.js +566 -0
- package/lib/pdfDimensionConvert.js.map +1 -0
- package/lib/pdfEntityStrokes.d.ts +13 -0
- package/lib/pdfEntityStrokes.d.ts.map +1 -0
- package/lib/pdfEntityStrokes.js +86 -0
- package/lib/pdfEntityStrokes.js.map +1 -0
- package/lib/pdfHatchRecover.d.ts +57 -0
- package/lib/pdfHatchRecover.d.ts.map +1 -0
- package/lib/pdfHatchRecover.js +315 -0
- package/lib/pdfHatchRecover.js.map +1 -0
- package/lib/pdfImportAppend.d.ts +24 -0
- package/lib/pdfImportAppend.d.ts.map +1 -0
- package/lib/pdfImportAppend.js +35 -0
- package/lib/pdfImportAppend.js.map +1 -0
- package/lib/pdfImportEntities.d.ts +63 -0
- package/lib/pdfImportEntities.d.ts.map +1 -0
- package/lib/pdfImportEntities.js +307 -0
- package/lib/pdfImportEntities.js.map +1 -0
- package/lib/pdfImportImages.d.ts +90 -0
- package/lib/pdfImportImages.d.ts.map +1 -0
- package/lib/pdfImportImages.js +294 -0
- package/lib/pdfImportImages.js.map +1 -0
- package/lib/pdfImportShapes.d.ts +63 -0
- package/lib/pdfImportShapes.d.ts.map +1 -0
- package/lib/pdfImportShapes.js +248 -0
- package/lib/pdfImportShapes.js.map +1 -0
- package/lib/pdfVectorImport.d.ts +218 -5
- package/lib/pdfVectorImport.d.ts.map +1 -1
- package/lib/pdfVectorImport.js +566 -98
- package/lib/pdfVectorImport.js.map +1 -1
- package/lib/recognition/pdfCurveRecognition.d.ts +91 -0
- package/lib/recognition/pdfCurveRecognition.d.ts.map +1 -0
- package/lib/recognition/pdfCurveRecognition.js +506 -0
- package/lib/recognition/pdfCurveRecognition.js.map +1 -0
- package/lib/recognition/pdfDimDigitRecognition.d.ts +18 -0
- package/lib/recognition/pdfDimDigitRecognition.d.ts.map +1 -0
- package/lib/recognition/pdfDimDigitRecognition.js +520 -0
- package/lib/recognition/pdfDimDigitRecognition.js.map +1 -0
- package/lib/recognition/pdfDimensionPattern.d.ts +173 -0
- package/lib/recognition/pdfDimensionPattern.d.ts.map +1 -0
- package/lib/recognition/pdfDimensionPattern.js +1023 -0
- package/lib/recognition/pdfDimensionPattern.js.map +1 -0
- package/lib/recognition/pdfFormBlocks.d.ts +58 -0
- package/lib/recognition/pdfFormBlocks.d.ts.map +1 -0
- package/lib/recognition/pdfFormBlocks.js +260 -0
- package/lib/recognition/pdfFormBlocks.js.map +1 -0
- package/lib/recognition/pdfGlyphClustering.d.ts +81 -0
- package/lib/recognition/pdfGlyphClustering.d.ts.map +1 -0
- package/lib/recognition/pdfGlyphClustering.js +1005 -0
- package/lib/recognition/pdfGlyphClustering.js.map +1 -0
- package/lib/recognition/pdfGlyphDictionary.d.ts +49 -0
- package/lib/recognition/pdfGlyphDictionary.d.ts.map +1 -0
- package/lib/recognition/pdfGlyphDictionary.js +188 -0
- package/lib/recognition/pdfGlyphDictionary.js.map +1 -0
- package/lib/recognition/pdfHatchFaces.d.ts +84 -0
- package/lib/recognition/pdfHatchFaces.d.ts.map +1 -0
- package/lib/recognition/pdfHatchFaces.js +1285 -0
- package/lib/recognition/pdfHatchFaces.js.map +1 -0
- package/lib/recognition/pdfHatchRecognition.d.ts +222 -0
- package/lib/recognition/pdfHatchRecognition.d.ts.map +1 -0
- package/lib/recognition/pdfHatchRecognition.js +3781 -0
- package/lib/recognition/pdfHatchRecognition.js.map +1 -0
- package/lib/recognition/pdfRecGeometry.d.ts +67 -0
- package/lib/recognition/pdfRecGeometry.d.ts.map +1 -0
- package/lib/recognition/pdfRecGeometry.js +365 -0
- package/lib/recognition/pdfRecGeometry.js.map +1 -0
- package/lib/recognition/pdfTextFontKind.d.ts +16 -0
- package/lib/recognition/pdfTextFontKind.d.ts.map +1 -0
- package/lib/recognition/pdfTextFontKind.js +51 -0
- package/lib/recognition/pdfTextFontKind.js.map +1 -0
- package/lib/recognition/pdfTextMerge.d.ts +71 -0
- package/lib/recognition/pdfTextMerge.d.ts.map +1 -0
- package/lib/recognition/pdfTextMerge.js +382 -0
- package/lib/recognition/pdfTextMerge.js.map +1 -0
- package/lib/recognition/pdfTextRunMerge.d.ts +28 -0
- package/lib/recognition/pdfTextRunMerge.d.ts.map +1 -0
- package/lib/recognition/pdfTextRunMerge.js +90 -0
- package/lib/recognition/pdfTextRunMerge.js.map +1 -0
- package/lib/register.d.ts +8 -2
- package/lib/register.d.ts.map +1 -1
- package/lib/register.js +29 -4
- package/lib/register.js.map +1 -1
- package/lib/ui/AcUiPdfDimPanel.d.ts +55 -0
- package/lib/ui/AcUiPdfDimPanel.d.ts.map +1 -0
- package/lib/ui/AcUiPdfDimPanel.js +347 -0
- package/lib/ui/AcUiPdfDimPanel.js.map +1 -0
- package/lib/ui/AcUiPdfHatchPanel.d.ts +57 -0
- package/lib/ui/AcUiPdfHatchPanel.d.ts.map +1 -0
- package/lib/ui/AcUiPdfHatchPanel.js +420 -0
- package/lib/ui/AcUiPdfHatchPanel.js.map +1 -0
- package/lib/ui/AcUiPdfImportDialog.d.ts +120 -0
- package/lib/ui/AcUiPdfImportDialog.d.ts.map +1 -0
- package/lib/ui/AcUiPdfImportDialog.js +673 -0
- package/lib/ui/AcUiPdfImportDialog.js.map +1 -0
- package/lib/ui/AcUiPdfTextPanel.d.ts +95 -0
- package/lib/ui/AcUiPdfTextPanel.d.ts.map +1 -0
- package/lib/ui/AcUiPdfTextPanel.js +775 -0
- package/lib/ui/AcUiPdfTextPanel.js.map +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Groups exploded text strokes (SHX line text, outlined fonts) into text
|
|
3
|
+
* line candidates with a CAD placement.
|
|
4
|
+
*
|
|
5
|
+
* Pure geometry: strokes are plain point lists, so the algorithm is
|
|
6
|
+
* unit-testable without the data model. The `pdftext` command samples
|
|
7
|
+
* entities into strokes and turns candidates into `AcDbText`.
|
|
8
|
+
*/
|
|
9
|
+
var __values = (this && this.__values) || function(o) {
|
|
10
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
11
|
+
if (m) return m.call(o);
|
|
12
|
+
if (o && typeof o.length === "number") return {
|
|
13
|
+
next: function () {
|
|
14
|
+
if (o && i >= o.length) o = void 0;
|
|
15
|
+
return { value: o && o[i++], done: !o };
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
19
|
+
};
|
|
20
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
21
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
22
|
+
if (!m) return o;
|
|
23
|
+
var i = m.call(o), r, ar = [], e;
|
|
24
|
+
try {
|
|
25
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
26
|
+
}
|
|
27
|
+
catch (error) { e = { error: error }; }
|
|
28
|
+
finally {
|
|
29
|
+
try {
|
|
30
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
31
|
+
}
|
|
32
|
+
finally { if (e) throw e.error; }
|
|
33
|
+
}
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
37
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
38
|
+
if (ar || !(i in from)) {
|
|
39
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
40
|
+
ar[i] = from[i];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
44
|
+
};
|
|
45
|
+
function strokeBox(stroke) {
|
|
46
|
+
var e_1, _a;
|
|
47
|
+
var minX = Infinity;
|
|
48
|
+
var minY = Infinity;
|
|
49
|
+
var maxX = -Infinity;
|
|
50
|
+
var maxY = -Infinity;
|
|
51
|
+
try {
|
|
52
|
+
for (var _b = __values(stroke.points), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
53
|
+
var p = _c.value;
|
|
54
|
+
minX = Math.min(minX, p.x);
|
|
55
|
+
minY = Math.min(minY, p.y);
|
|
56
|
+
maxX = Math.max(maxX, p.x);
|
|
57
|
+
maxY = Math.max(maxY, p.y);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
61
|
+
finally {
|
|
62
|
+
try {
|
|
63
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
64
|
+
}
|
|
65
|
+
finally { if (e_1) throw e_1.error; }
|
|
66
|
+
}
|
|
67
|
+
return { minX: minX, minY: minY, maxX: maxX, maxY: maxY };
|
|
68
|
+
}
|
|
69
|
+
function boxSize(box) {
|
|
70
|
+
return Math.max(box.maxX - box.minX, box.maxY - box.minY);
|
|
71
|
+
}
|
|
72
|
+
function boxGap(a, b) {
|
|
73
|
+
var dx = Math.max(0, a.minX - b.maxX, b.minX - a.maxX);
|
|
74
|
+
var dy = Math.max(0, a.minY - b.maxY, b.minY - a.maxY);
|
|
75
|
+
return Math.hypot(dx, dy);
|
|
76
|
+
}
|
|
77
|
+
function percentile(sorted, q) {
|
|
78
|
+
if (sorted.length === 0)
|
|
79
|
+
return 0;
|
|
80
|
+
var index = Math.min(sorted.length - 1, Math.max(0, Math.round(q * (sorted.length - 1))));
|
|
81
|
+
return sorted[index];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Estimates text height from the sizes of glyph parts. Parts far above the
|
|
85
|
+
* median are treated as non-text so a frame in the selection does not skew
|
|
86
|
+
* the result.
|
|
87
|
+
*
|
|
88
|
+
* Strokes chained end to end are measured together: some PDFs store a glyph
|
|
89
|
+
* stroke as many tiny separate segments, each far smaller than the text.
|
|
90
|
+
*/
|
|
91
|
+
export function estimatePdfTextHeight(strokes) {
|
|
92
|
+
var sized = strokes.filter(function (stroke) { return boxSize(strokeBox(stroke)) > 0; });
|
|
93
|
+
if (sized.length === 0)
|
|
94
|
+
return 0;
|
|
95
|
+
var strokeMedian = percentile(sized.map(function (stroke) { return boxSize(strokeBox(stroke)); }).sort(byNumber), 0.5);
|
|
96
|
+
var sizes = chainStrokes(sized, strokeMedian * 0.01)
|
|
97
|
+
.map(function (group) {
|
|
98
|
+
return boxSize(strokeBox({ id: 0, points: group.flatMap(function (i) { return sized[i].points; }) }));
|
|
99
|
+
})
|
|
100
|
+
.sort(byNumber);
|
|
101
|
+
var median = percentile(sizes, 0.5);
|
|
102
|
+
var textLike = sizes.filter(function (size) { return size <= median * 4; });
|
|
103
|
+
return percentile(textLike, 0.9);
|
|
104
|
+
}
|
|
105
|
+
function byNumber(a, b) {
|
|
106
|
+
return a - b;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Groups strokes whose end points meet within `tolerance` (a stroke split
|
|
110
|
+
* into consecutive segments), using a hash grid of end points.
|
|
111
|
+
*
|
|
112
|
+
* @param canJoin - Optional extra condition for joining two strokes.
|
|
113
|
+
*/
|
|
114
|
+
function chainStrokes(strokes, tolerance, canJoin) {
|
|
115
|
+
var uf = new UnionFind(strokes.length);
|
|
116
|
+
var cell = Math.max(tolerance, 1e-12);
|
|
117
|
+
var grid = new Map();
|
|
118
|
+
strokes.forEach(function (stroke, index) {
|
|
119
|
+
var e_2, _a, e_3, _b;
|
|
120
|
+
var _c;
|
|
121
|
+
var ends = [stroke.points[0], stroke.points[stroke.points.length - 1]];
|
|
122
|
+
try {
|
|
123
|
+
for (var ends_1 = __values(ends), ends_1_1 = ends_1.next(); !ends_1_1.done; ends_1_1 = ends_1.next()) {
|
|
124
|
+
var p = ends_1_1.value;
|
|
125
|
+
var gx = Math.floor(p.x / cell);
|
|
126
|
+
var gy = Math.floor(p.y / cell);
|
|
127
|
+
for (var dx = -1; dx <= 1; dx++) {
|
|
128
|
+
for (var dy = -1; dy <= 1; dy++) {
|
|
129
|
+
try {
|
|
130
|
+
for (var _d = (e_3 = void 0, __values((_c = grid.get("".concat(gx + dx, ",").concat(gy + dy))) !== null && _c !== void 0 ? _c : [])), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
131
|
+
var other = _e.value;
|
|
132
|
+
if (Math.hypot(other.p.x - p.x, other.p.y - p.y) <= tolerance &&
|
|
133
|
+
(!canJoin || canJoin(index, other.index))) {
|
|
134
|
+
uf.union(index, other.index);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
139
|
+
finally {
|
|
140
|
+
try {
|
|
141
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
142
|
+
}
|
|
143
|
+
finally { if (e_3) throw e_3.error; }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
var key = "".concat(gx, ",").concat(gy);
|
|
148
|
+
var bucket = grid.get(key);
|
|
149
|
+
if (bucket)
|
|
150
|
+
bucket.push({ index: index, p: p });
|
|
151
|
+
else
|
|
152
|
+
grid.set(key, [{ index: index, p: p }]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
156
|
+
finally {
|
|
157
|
+
try {
|
|
158
|
+
if (ends_1_1 && !ends_1_1.done && (_a = ends_1.return)) _a.call(ends_1);
|
|
159
|
+
}
|
|
160
|
+
finally { if (e_2) throw e_2.error; }
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
var groups = new Map();
|
|
164
|
+
strokes.forEach(function (_, i) {
|
|
165
|
+
var root = uf.find(i);
|
|
166
|
+
var group = groups.get(root);
|
|
167
|
+
if (group)
|
|
168
|
+
group.push(i);
|
|
169
|
+
else
|
|
170
|
+
groups.set(root, [i]);
|
|
171
|
+
});
|
|
172
|
+
return __spreadArray([], __read(groups.values()), false);
|
|
173
|
+
}
|
|
174
|
+
/** Sine of the largest angle between segments still treated as collinear. */
|
|
175
|
+
var COLLINEAR_SINE = Math.sin((2 * Math.PI) / 180);
|
|
176
|
+
/** Unit direction of a straight stroke, or `undefined` for a curved one. */
|
|
177
|
+
function straightDirection(stroke, tolerance) {
|
|
178
|
+
var e_4, _a;
|
|
179
|
+
var a = stroke.points[0];
|
|
180
|
+
var b = stroke.points[stroke.points.length - 1];
|
|
181
|
+
var length = Math.hypot(b.x - a.x, b.y - a.y);
|
|
182
|
+
if (!(length > 0))
|
|
183
|
+
return undefined;
|
|
184
|
+
var ux = (b.x - a.x) / length;
|
|
185
|
+
var uy = (b.y - a.y) / length;
|
|
186
|
+
try {
|
|
187
|
+
for (var _b = __values(stroke.points), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
188
|
+
var p = _c.value;
|
|
189
|
+
if (Math.abs((p.x - a.x) * uy - (p.y - a.y) * ux) > tolerance) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
195
|
+
finally {
|
|
196
|
+
try {
|
|
197
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
198
|
+
}
|
|
199
|
+
finally { if (e_4) throw e_4.error; }
|
|
200
|
+
}
|
|
201
|
+
return { x: ux, y: uy };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Size of the straight line each stroke belongs to: straight strokes that
|
|
205
|
+
* meet end to end in the same direction are measured together. PDFs often
|
|
206
|
+
* split table grids and frames into segments shorter than the text, which
|
|
207
|
+
* would otherwise pass as glyph strokes and glue table cells together.
|
|
208
|
+
*/
|
|
209
|
+
function straightRunSizes(strokes, tolerance) {
|
|
210
|
+
var e_5, _a;
|
|
211
|
+
var directions = strokes.map(function (stroke) { return straightDirection(stroke, tolerance); });
|
|
212
|
+
var runs = chainStrokes(strokes, tolerance, function (i, j) {
|
|
213
|
+
var a = directions[i];
|
|
214
|
+
var b = directions[j];
|
|
215
|
+
return !!a && !!b && Math.abs(a.x * b.y - a.y * b.x) <= COLLINEAR_SINE;
|
|
216
|
+
});
|
|
217
|
+
var sizes = new Array(strokes.length);
|
|
218
|
+
var _loop_1 = function (run) {
|
|
219
|
+
var size = boxSize(strokeBox({ id: 0, points: run.flatMap(function (i) { return strokes[i].points; }) }));
|
|
220
|
+
run.forEach(function (i) { return (sizes[i] = size); });
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
for (var runs_1 = __values(runs), runs_1_1 = runs_1.next(); !runs_1_1.done; runs_1_1 = runs_1.next()) {
|
|
224
|
+
var run = runs_1_1.value;
|
|
225
|
+
_loop_1(run);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
229
|
+
finally {
|
|
230
|
+
try {
|
|
231
|
+
if (runs_1_1 && !runs_1_1.done && (_a = runs_1.return)) _a.call(runs_1);
|
|
232
|
+
}
|
|
233
|
+
finally { if (e_5) throw e_5.error; }
|
|
234
|
+
}
|
|
235
|
+
return sizes;
|
|
236
|
+
}
|
|
237
|
+
var UnionFind = /** @class */ (function () {
|
|
238
|
+
function UnionFind(n) {
|
|
239
|
+
this.parent = Array.from({ length: n }, function (_, i) { return i; });
|
|
240
|
+
}
|
|
241
|
+
UnionFind.prototype.find = function (i) {
|
|
242
|
+
while (this.parent[i] !== i) {
|
|
243
|
+
this.parent[i] = this.parent[this.parent[i]];
|
|
244
|
+
i = this.parent[i];
|
|
245
|
+
}
|
|
246
|
+
return i;
|
|
247
|
+
};
|
|
248
|
+
UnionFind.prototype.union = function (a, b) {
|
|
249
|
+
var ra = this.find(a);
|
|
250
|
+
var rb = this.find(b);
|
|
251
|
+
if (ra !== rb)
|
|
252
|
+
this.parent[ra] = rb;
|
|
253
|
+
};
|
|
254
|
+
return UnionFind;
|
|
255
|
+
}());
|
|
256
|
+
/** Joins items whose boxes are within `gap`, using a uniform grid. */
|
|
257
|
+
function clusterBoxes(boxes, gap) {
|
|
258
|
+
var uf = new UnionFind(boxes.length);
|
|
259
|
+
var cell = Math.max(gap * 2, 1e-9);
|
|
260
|
+
var grid = new Map();
|
|
261
|
+
boxes.forEach(function (box, i) {
|
|
262
|
+
var e_6, _a;
|
|
263
|
+
var x0 = Math.floor((box.minX - gap) / cell);
|
|
264
|
+
var x1 = Math.floor((box.maxX + gap) / cell);
|
|
265
|
+
var y0 = Math.floor((box.minY - gap) / cell);
|
|
266
|
+
var y1 = Math.floor((box.maxY + gap) / cell);
|
|
267
|
+
var seen = new Set();
|
|
268
|
+
for (var gx = x0; gx <= x1; gx++) {
|
|
269
|
+
for (var gy = y0; gy <= y1; gy++) {
|
|
270
|
+
var key = "".concat(gx, ",").concat(gy);
|
|
271
|
+
var bucket = grid.get(key);
|
|
272
|
+
if (bucket) {
|
|
273
|
+
try {
|
|
274
|
+
for (var bucket_1 = (e_6 = void 0, __values(bucket)), bucket_1_1 = bucket_1.next(); !bucket_1_1.done; bucket_1_1 = bucket_1.next()) {
|
|
275
|
+
var j = bucket_1_1.value;
|
|
276
|
+
if (seen.has(j))
|
|
277
|
+
continue;
|
|
278
|
+
seen.add(j);
|
|
279
|
+
if (boxGap(box, boxes[j]) <= gap)
|
|
280
|
+
uf.union(i, j);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
284
|
+
finally {
|
|
285
|
+
try {
|
|
286
|
+
if (bucket_1_1 && !bucket_1_1.done && (_a = bucket_1.return)) _a.call(bucket_1);
|
|
287
|
+
}
|
|
288
|
+
finally { if (e_6) throw e_6.error; }
|
|
289
|
+
}
|
|
290
|
+
bucket.push(i);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
grid.set(key, [i]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
var groups = new Map();
|
|
299
|
+
boxes.forEach(function (_, i) {
|
|
300
|
+
var root = uf.find(i);
|
|
301
|
+
var group = groups.get(root);
|
|
302
|
+
if (group)
|
|
303
|
+
group.push(i);
|
|
304
|
+
else
|
|
305
|
+
groups.set(root, [i]);
|
|
306
|
+
});
|
|
307
|
+
return __spreadArray([], __read(groups.values()), false);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Baseline direction of a text cluster, in `(-π/2, π/2]`.
|
|
311
|
+
*
|
|
312
|
+
* Uses the direction of the minimum-height oriented bounding box (rotating
|
|
313
|
+
* calipers over the convex hull): along a text line the extent across the
|
|
314
|
+
* baseline is smallest. Principal axes would be skewed by asymmetric glyphs.
|
|
315
|
+
* Returns `undefined` when the cluster is not elongated enough to tell.
|
|
316
|
+
*/
|
|
317
|
+
function principalAngle(points) {
|
|
318
|
+
var e_7, _a;
|
|
319
|
+
var hull = convexHull(points);
|
|
320
|
+
if (hull.length < 2)
|
|
321
|
+
return undefined;
|
|
322
|
+
var bestAngle = 0;
|
|
323
|
+
var bestHeight = Infinity;
|
|
324
|
+
var bestLength = 0;
|
|
325
|
+
for (var i = 0; i < hull.length; i++) {
|
|
326
|
+
var a = hull[i];
|
|
327
|
+
var b = hull[(i + 1) % hull.length];
|
|
328
|
+
var len = Math.hypot(b.x - a.x, b.y - a.y);
|
|
329
|
+
if (len === 0)
|
|
330
|
+
continue;
|
|
331
|
+
var ux = (b.x - a.x) / len;
|
|
332
|
+
var uy = (b.y - a.y) / len;
|
|
333
|
+
var minU = Infinity;
|
|
334
|
+
var maxU = -Infinity;
|
|
335
|
+
var minV = Infinity;
|
|
336
|
+
var maxV = -Infinity;
|
|
337
|
+
try {
|
|
338
|
+
for (var hull_1 = (e_7 = void 0, __values(hull)), hull_1_1 = hull_1.next(); !hull_1_1.done; hull_1_1 = hull_1.next()) {
|
|
339
|
+
var p = hull_1_1.value;
|
|
340
|
+
var u = p.x * ux + p.y * uy;
|
|
341
|
+
var v = -p.x * uy + p.y * ux;
|
|
342
|
+
minU = Math.min(minU, u);
|
|
343
|
+
maxU = Math.max(maxU, u);
|
|
344
|
+
minV = Math.min(minV, v);
|
|
345
|
+
maxV = Math.max(maxV, v);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
349
|
+
finally {
|
|
350
|
+
try {
|
|
351
|
+
if (hull_1_1 && !hull_1_1.done && (_a = hull_1.return)) _a.call(hull_1);
|
|
352
|
+
}
|
|
353
|
+
finally { if (e_7) throw e_7.error; }
|
|
354
|
+
}
|
|
355
|
+
var height = maxV - minV;
|
|
356
|
+
if (height < bestHeight - 1e-12) {
|
|
357
|
+
bestHeight = height;
|
|
358
|
+
bestLength = maxU - minU;
|
|
359
|
+
bestAngle = Math.atan2(uy, ux);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// A single glyph or a square block of text has no reliable direction.
|
|
363
|
+
if (!(bestLength > bestHeight * 1.8))
|
|
364
|
+
return undefined;
|
|
365
|
+
return normalizeTextAngle(bestAngle);
|
|
366
|
+
}
|
|
367
|
+
/** Fewest glyph blobs for {@link neighborAngle} to vote on a direction. */
|
|
368
|
+
var NEIGHBOR_MIN_BLOBS = 3;
|
|
369
|
+
/** Width of one direction histogram bin, in radians. */
|
|
370
|
+
var NEIGHBOR_BIN = Math.PI / 180;
|
|
371
|
+
/**
|
|
372
|
+
* Baseline direction of a phrase from the direction between each glyph and
|
|
373
|
+
* its nearest neighbor, in `(-π/2, π/2]`.
|
|
374
|
+
*
|
|
375
|
+
* Characters of a line are closer to each other than lines are, so the most
|
|
376
|
+
* common nearest-neighbor direction is the baseline. Unlike the phrase
|
|
377
|
+
* outline, this also works for blocks taller than wide: a narrow column of
|
|
378
|
+
* table cells or a paragraph of short lines.
|
|
379
|
+
*
|
|
380
|
+
* @returns The direction, or `undefined` when the phrase has fewer than
|
|
381
|
+
* {@link NEIGHBOR_MIN_BLOBS} glyph blobs.
|
|
382
|
+
*/
|
|
383
|
+
function neighborAngle(strokes, textHeight, wordGap) {
|
|
384
|
+
var e_8, _a;
|
|
385
|
+
// Blobs: touching strokes, roughly one glyph or glyph part each.
|
|
386
|
+
var boxes = strokes.map(strokeBox);
|
|
387
|
+
var blobs = clusterBoxes(boxes, textHeight * 0.02).map(function (group) {
|
|
388
|
+
var e_9, _a;
|
|
389
|
+
var minX = Infinity;
|
|
390
|
+
var minY = Infinity;
|
|
391
|
+
var maxX = -Infinity;
|
|
392
|
+
var maxY = -Infinity;
|
|
393
|
+
try {
|
|
394
|
+
for (var group_1 = __values(group), group_1_1 = group_1.next(); !group_1_1.done; group_1_1 = group_1.next()) {
|
|
395
|
+
var i = group_1_1.value;
|
|
396
|
+
minX = Math.min(minX, boxes[i].minX);
|
|
397
|
+
minY = Math.min(minY, boxes[i].minY);
|
|
398
|
+
maxX = Math.max(maxX, boxes[i].maxX);
|
|
399
|
+
maxY = Math.max(maxY, boxes[i].maxY);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
403
|
+
finally {
|
|
404
|
+
try {
|
|
405
|
+
if (group_1_1 && !group_1_1.done && (_a = group_1.return)) _a.call(group_1);
|
|
406
|
+
}
|
|
407
|
+
finally { if (e_9) throw e_9.error; }
|
|
408
|
+
}
|
|
409
|
+
return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
|
|
410
|
+
});
|
|
411
|
+
if (blobs.length < NEIGHBOR_MIN_BLOBS)
|
|
412
|
+
return undefined;
|
|
413
|
+
var cell = Math.max(textHeight, 1e-9);
|
|
414
|
+
var grid = new Map();
|
|
415
|
+
blobs.forEach(function (blob, i) {
|
|
416
|
+
var key = "".concat(Math.floor(blob.x / cell), ",").concat(Math.floor(blob.y / cell));
|
|
417
|
+
var bucket = grid.get(key);
|
|
418
|
+
if (bucket)
|
|
419
|
+
bucket.push(i);
|
|
420
|
+
else
|
|
421
|
+
grid.set(key, [i]);
|
|
422
|
+
});
|
|
423
|
+
var reach = Math.ceil(Math.max(wordGap, 1));
|
|
424
|
+
var bins = Math.round(Math.PI / NEIGHBOR_BIN);
|
|
425
|
+
var votes = new Array(bins).fill(0);
|
|
426
|
+
var angles = [];
|
|
427
|
+
blobs.forEach(function (blob, i) {
|
|
428
|
+
var e_10, _a;
|
|
429
|
+
var _b;
|
|
430
|
+
var gx = Math.floor(blob.x / cell);
|
|
431
|
+
var gy = Math.floor(blob.y / cell);
|
|
432
|
+
var best = -1;
|
|
433
|
+
var bestDistance = Infinity;
|
|
434
|
+
for (var dx = -reach; dx <= reach; dx++) {
|
|
435
|
+
for (var dy = -reach; dy <= reach; dy++) {
|
|
436
|
+
try {
|
|
437
|
+
for (var _c = (e_10 = void 0, __values((_b = grid.get("".concat(gx + dx, ",").concat(gy + dy))) !== null && _b !== void 0 ? _b : [])), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
438
|
+
var j = _d.value;
|
|
439
|
+
if (j === i)
|
|
440
|
+
continue;
|
|
441
|
+
var d = Math.hypot(blobs[j].x - blob.x, blobs[j].y - blob.y);
|
|
442
|
+
if (d > 0 && d < bestDistance) {
|
|
443
|
+
bestDistance = d;
|
|
444
|
+
best = j;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
449
|
+
finally {
|
|
450
|
+
try {
|
|
451
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
452
|
+
}
|
|
453
|
+
finally { if (e_10) throw e_10.error; }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (best < 0)
|
|
458
|
+
return;
|
|
459
|
+
var angle = Math.atan2(blobs[best].y - blob.y, blobs[best].x - blob.x);
|
|
460
|
+
if (angle < 0)
|
|
461
|
+
angle += Math.PI;
|
|
462
|
+
if (angle >= Math.PI)
|
|
463
|
+
angle -= Math.PI;
|
|
464
|
+
angles.push(angle);
|
|
465
|
+
votes[Math.min(bins - 1, Math.floor(angle / NEIGHBOR_BIN))]++;
|
|
466
|
+
});
|
|
467
|
+
if (angles.length === 0)
|
|
468
|
+
return undefined;
|
|
469
|
+
// Mode of the histogram, smoothed over neighboring bins (circular).
|
|
470
|
+
var bestBin = 0;
|
|
471
|
+
var bestScore = -1;
|
|
472
|
+
for (var b = 0; b < bins; b++) {
|
|
473
|
+
var score = 0;
|
|
474
|
+
for (var k = -2; k <= 2; k++)
|
|
475
|
+
score += votes[(b + k + bins) % bins];
|
|
476
|
+
if (score > bestScore) {
|
|
477
|
+
bestScore = score;
|
|
478
|
+
bestBin = b;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Refine: mean of the votes near the mode, unwrapped around it.
|
|
482
|
+
var center = (bestBin + 0.5) * NEIGHBOR_BIN;
|
|
483
|
+
var sum = 0;
|
|
484
|
+
var count = 0;
|
|
485
|
+
try {
|
|
486
|
+
for (var angles_1 = __values(angles), angles_1_1 = angles_1.next(); !angles_1_1.done; angles_1_1 = angles_1.next()) {
|
|
487
|
+
var angle = angles_1_1.value;
|
|
488
|
+
var delta = angle - center;
|
|
489
|
+
if (delta > Math.PI / 2)
|
|
490
|
+
delta -= Math.PI;
|
|
491
|
+
if (delta < -Math.PI / 2)
|
|
492
|
+
delta += Math.PI;
|
|
493
|
+
if (Math.abs(delta) <= 3 * NEIGHBOR_BIN) {
|
|
494
|
+
sum += delta;
|
|
495
|
+
count++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
500
|
+
finally {
|
|
501
|
+
try {
|
|
502
|
+
if (angles_1_1 && !angles_1_1.done && (_a = angles_1.return)) _a.call(angles_1);
|
|
503
|
+
}
|
|
504
|
+
finally { if (e_8) throw e_8.error; }
|
|
505
|
+
}
|
|
506
|
+
return normalizeTextAngle(center + (count > 0 ? sum / count : 0));
|
|
507
|
+
}
|
|
508
|
+
/** Candidates scoring within this fraction of the best count as a tie. */
|
|
509
|
+
var ANGLE_TIE = 0.05;
|
|
510
|
+
/**
|
|
511
|
+
* How much better a tilted direction must score than 0° / 90° to be used:
|
|
512
|
+
* drawings are mostly axis-aligned, and a few glyphs (`Q1`) can make a
|
|
513
|
+
* slight tilt score marginally better.
|
|
514
|
+
*/
|
|
515
|
+
var TILT_MARGIN = 0.2;
|
|
516
|
+
/** How much better than every other direction a clear direction scores. */
|
|
517
|
+
var ANGLE_CLEAR = 0.25;
|
|
518
|
+
/** Lines shorter than this many times their thickness are not real lines. */
|
|
519
|
+
var MIN_LINE_ASPECT = 1.5;
|
|
520
|
+
/**
|
|
521
|
+
* Baseline direction of a phrase: among 0°, 90°, the nearest-neighbor vote
|
|
522
|
+
* and the outline direction, the one whose lines have the tightest glyph
|
|
523
|
+
* spacing.
|
|
524
|
+
*
|
|
525
|
+
* Along the baseline, glyphs of a line are close together. Read across it,
|
|
526
|
+
* "lines" are columns of characters whose gaps are the line spacing, or
|
|
527
|
+
* single glyphs / single characters (not elongated) that count as a word
|
|
528
|
+
* gap. This holds for single lines, paragraphs, widely spaced titles and
|
|
529
|
+
* narrow table columns alike, where the phrase outline or nearest neighbors
|
|
530
|
+
* (often parts of one character) fail. Ties keep the earlier candidate, so
|
|
531
|
+
* horizontal text stays exactly horizontal.
|
|
532
|
+
*
|
|
533
|
+
* @returns The direction, and whether it is clear. It is not when the
|
|
534
|
+
* phrase reads as fewer than three glyphs, or no direction scores clearly
|
|
535
|
+
* better than the others: short words like `Q1` look the same as one
|
|
536
|
+
* character read sideways, so only the rest of the drawing can tell.
|
|
537
|
+
*/
|
|
538
|
+
function bestPhraseAngle(strokes, textHeight, wordGap, glyphGap) {
|
|
539
|
+
var e_11, _a;
|
|
540
|
+
var candidates = [
|
|
541
|
+
0,
|
|
542
|
+
Math.PI / 2,
|
|
543
|
+
neighborAngle(strokes, textHeight, wordGap),
|
|
544
|
+
principalAngle(strokes.flatMap(function (stroke) { return stroke.points; }))
|
|
545
|
+
].filter(function (angle, i, all) {
|
|
546
|
+
return angle !== undefined &&
|
|
547
|
+
all
|
|
548
|
+
.slice(0, i)
|
|
549
|
+
.every(function (other) {
|
|
550
|
+
return other === undefined ||
|
|
551
|
+
Math.abs(normalizeTextAngle(other) - normalizeTextAngle(angle)) >
|
|
552
|
+
NEIGHBOR_BIN;
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
var best = candidates[0];
|
|
556
|
+
var bestScore = Infinity;
|
|
557
|
+
var bestGlyphs = 0;
|
|
558
|
+
var scores = [];
|
|
559
|
+
var _loop_2 = function (angle) {
|
|
560
|
+
var e_12, _b, e_13, _c;
|
|
561
|
+
var gaps = 0;
|
|
562
|
+
var count = 0;
|
|
563
|
+
var glyphCount = 0;
|
|
564
|
+
var local = localStrokes(strokes, angle);
|
|
565
|
+
try {
|
|
566
|
+
for (var _d = (e_12 = void 0, __values(splitByGaps(local, 'v', textHeight * 0.1))), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
567
|
+
var row = _e.value;
|
|
568
|
+
var _loop_3 = function (line) {
|
|
569
|
+
var glyphs = splitByGaps(line, 'u', glyphGap * textHeight);
|
|
570
|
+
glyphCount += glyphs.length;
|
|
571
|
+
// One glyph, or one character read sideways (its stacked parts),
|
|
572
|
+
// counts as a word gap.
|
|
573
|
+
var band = lineBand(line);
|
|
574
|
+
if (glyphs.length < 2 ||
|
|
575
|
+
band.maxU - band.minU < (band.maxV - band.minV) * MIN_LINE_ASPECT) {
|
|
576
|
+
gaps += wordGap * textHeight;
|
|
577
|
+
count++;
|
|
578
|
+
return "continue";
|
|
579
|
+
}
|
|
580
|
+
var reach = -Infinity;
|
|
581
|
+
glyphs.forEach(function (glyph, i) {
|
|
582
|
+
var minU = Math.min.apply(Math, __spreadArray([], __read(glyph.map(function (item) { return item.minU; })), false));
|
|
583
|
+
if (i > 0) {
|
|
584
|
+
gaps += minU - reach;
|
|
585
|
+
count++;
|
|
586
|
+
}
|
|
587
|
+
reach = Math.max.apply(Math, __spreadArray([], __read(glyph.map(function (item) { return item.maxU; })), false));
|
|
588
|
+
});
|
|
589
|
+
};
|
|
590
|
+
try {
|
|
591
|
+
for (var _f = (e_13 = void 0, __values(splitByGaps(row, 'u', wordGap * textHeight))), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
592
|
+
var line = _g.value;
|
|
593
|
+
_loop_3(line);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (e_13_1) { e_13 = { error: e_13_1 }; }
|
|
597
|
+
finally {
|
|
598
|
+
try {
|
|
599
|
+
if (_g && !_g.done && (_c = _f.return)) _c.call(_f);
|
|
600
|
+
}
|
|
601
|
+
finally { if (e_13) throw e_13.error; }
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
606
|
+
finally {
|
|
607
|
+
try {
|
|
608
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
609
|
+
}
|
|
610
|
+
finally { if (e_12) throw e_12.error; }
|
|
611
|
+
}
|
|
612
|
+
var score = count > 0 ? gaps / count : Infinity;
|
|
613
|
+
scores.push(score);
|
|
614
|
+
var axis = Math.abs(Math.sin(2 * angle)) < 1e-9;
|
|
615
|
+
if (score < bestScore * (1 - (axis ? ANGLE_TIE : TILT_MARGIN))) {
|
|
616
|
+
best = angle;
|
|
617
|
+
bestScore = score;
|
|
618
|
+
bestGlyphs = glyphCount;
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
try {
|
|
622
|
+
for (var candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next(); !candidates_1_1.done; candidates_1_1 = candidates_1.next()) {
|
|
623
|
+
var angle = candidates_1_1.value;
|
|
624
|
+
_loop_2(angle);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
catch (e_11_1) { e_11 = { error: e_11_1 }; }
|
|
628
|
+
finally {
|
|
629
|
+
try {
|
|
630
|
+
if (candidates_1_1 && !candidates_1_1.done && (_a = candidates_1.return)) _a.call(candidates_1);
|
|
631
|
+
}
|
|
632
|
+
finally { if (e_11) throw e_11.error; }
|
|
633
|
+
}
|
|
634
|
+
var runnerUp = Math.min.apply(Math, __spreadArray(__spreadArray([], __read(scores.filter(function (_, i) { return candidates[i] !== best; })), false), [Infinity], false));
|
|
635
|
+
return {
|
|
636
|
+
angle: normalizeTextAngle(best),
|
|
637
|
+
// Two glyphs cannot tell a line from a column.
|
|
638
|
+
clear: bestGlyphs >= 3 && bestScore <= runnerUp * (1 - ANGLE_CLEAR)
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/** Andrew's monotone chain convex hull (counterclockwise). */
|
|
642
|
+
function convexHull(points) {
|
|
643
|
+
var e_14, _a;
|
|
644
|
+
var pts = __spreadArray([], __read(points), false).sort(function (a, b) { return a.x - b.x || a.y - b.y; });
|
|
645
|
+
if (pts.length < 3)
|
|
646
|
+
return pts;
|
|
647
|
+
var cross = function (o, a, b) {
|
|
648
|
+
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
649
|
+
};
|
|
650
|
+
var lower = [];
|
|
651
|
+
try {
|
|
652
|
+
for (var pts_1 = __values(pts), pts_1_1 = pts_1.next(); !pts_1_1.done; pts_1_1 = pts_1.next()) {
|
|
653
|
+
var p = pts_1_1.value;
|
|
654
|
+
while (lower.length >= 2 &&
|
|
655
|
+
cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
|
|
656
|
+
lower.pop();
|
|
657
|
+
}
|
|
658
|
+
lower.push(p);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch (e_14_1) { e_14 = { error: e_14_1 }; }
|
|
662
|
+
finally {
|
|
663
|
+
try {
|
|
664
|
+
if (pts_1_1 && !pts_1_1.done && (_a = pts_1.return)) _a.call(pts_1);
|
|
665
|
+
}
|
|
666
|
+
finally { if (e_14) throw e_14.error; }
|
|
667
|
+
}
|
|
668
|
+
var upper = [];
|
|
669
|
+
for (var i = pts.length - 1; i >= 0; i--) {
|
|
670
|
+
var p = pts[i];
|
|
671
|
+
while (upper.length >= 2 &&
|
|
672
|
+
cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
|
|
673
|
+
upper.pop();
|
|
674
|
+
}
|
|
675
|
+
upper.push(p);
|
|
676
|
+
}
|
|
677
|
+
lower.pop();
|
|
678
|
+
upper.pop();
|
|
679
|
+
return lower.concat(upper);
|
|
680
|
+
}
|
|
681
|
+
/** Folds an angle into `(-π/2, π/2]` and snaps near-orthogonal angles. */
|
|
682
|
+
function normalizeTextAngle(angle) {
|
|
683
|
+
var a = angle;
|
|
684
|
+
while (a <= -Math.PI / 2)
|
|
685
|
+
a += Math.PI;
|
|
686
|
+
while (a > Math.PI / 2)
|
|
687
|
+
a -= Math.PI;
|
|
688
|
+
var snapped = Math.round(a / (Math.PI / 2)) * (Math.PI / 2);
|
|
689
|
+
if (Math.abs(a - snapped) < (2 * Math.PI) / 180)
|
|
690
|
+
a = snapped;
|
|
691
|
+
if (a <= -Math.PI / 2 + 1e-12)
|
|
692
|
+
a += Math.PI;
|
|
693
|
+
return a;
|
|
694
|
+
}
|
|
695
|
+
function toLocal(p, angle) {
|
|
696
|
+
var c = Math.cos(angle);
|
|
697
|
+
var s = Math.sin(angle);
|
|
698
|
+
return { u: p.x * c + p.y * s, v: -p.x * s + p.y * c };
|
|
699
|
+
}
|
|
700
|
+
function toWorld(u, v, angle) {
|
|
701
|
+
var c = Math.cos(angle);
|
|
702
|
+
var s = Math.sin(angle);
|
|
703
|
+
return { x: u * c - v * s, y: u * s + v * c };
|
|
704
|
+
}
|
|
705
|
+
function localStrokes(strokes, angle) {
|
|
706
|
+
return strokes.map(function (stroke) {
|
|
707
|
+
var e_15, _a;
|
|
708
|
+
var minU = Infinity;
|
|
709
|
+
var maxU = -Infinity;
|
|
710
|
+
var minV = Infinity;
|
|
711
|
+
var maxV = -Infinity;
|
|
712
|
+
try {
|
|
713
|
+
for (var _b = __values(stroke.points), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
714
|
+
var p = _c.value;
|
|
715
|
+
var _d = toLocal(p, angle), u = _d.u, v = _d.v;
|
|
716
|
+
minU = Math.min(minU, u);
|
|
717
|
+
maxU = Math.max(maxU, u);
|
|
718
|
+
minV = Math.min(minV, v);
|
|
719
|
+
maxV = Math.max(maxV, v);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
catch (e_15_1) { e_15 = { error: e_15_1 }; }
|
|
723
|
+
finally {
|
|
724
|
+
try {
|
|
725
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
726
|
+
}
|
|
727
|
+
finally { if (e_15) throw e_15.error; }
|
|
728
|
+
}
|
|
729
|
+
return { stroke: stroke, minU: minU, maxU: maxU, minV: minV, maxV: maxV };
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
/** Splits strokes into groups separated by gaps along one local axis. */
|
|
733
|
+
function splitByGaps(items, axis, gap) {
|
|
734
|
+
var e_16, _a;
|
|
735
|
+
var lo = axis === 'u' ? 'minU' : 'minV';
|
|
736
|
+
var hi = axis === 'u' ? 'maxU' : 'maxV';
|
|
737
|
+
var sorted = __spreadArray([], __read(items), false).sort(function (a, b) { return a[lo] - b[lo]; });
|
|
738
|
+
var groups = [];
|
|
739
|
+
var current = [];
|
|
740
|
+
var reach = -Infinity;
|
|
741
|
+
try {
|
|
742
|
+
for (var sorted_1 = __values(sorted), sorted_1_1 = sorted_1.next(); !sorted_1_1.done; sorted_1_1 = sorted_1.next()) {
|
|
743
|
+
var item = sorted_1_1.value;
|
|
744
|
+
if (current.length > 0 && item[lo] - reach > gap) {
|
|
745
|
+
groups.push(current);
|
|
746
|
+
current = [];
|
|
747
|
+
reach = -Infinity;
|
|
748
|
+
}
|
|
749
|
+
current.push(item);
|
|
750
|
+
reach = Math.max(reach, item[hi]);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (e_16_1) { e_16 = { error: e_16_1 }; }
|
|
754
|
+
finally {
|
|
755
|
+
try {
|
|
756
|
+
if (sorted_1_1 && !sorted_1_1.done && (_a = sorted_1.return)) _a.call(sorted_1);
|
|
757
|
+
}
|
|
758
|
+
finally { if (e_16) throw e_16.error; }
|
|
759
|
+
}
|
|
760
|
+
if (current.length > 0)
|
|
761
|
+
groups.push(current);
|
|
762
|
+
return groups;
|
|
763
|
+
}
|
|
764
|
+
function median(values) {
|
|
765
|
+
var sorted = __spreadArray([], __read(values), false).sort(function (a, b) { return a - b; });
|
|
766
|
+
return percentile(sorted, 0.5);
|
|
767
|
+
}
|
|
768
|
+
function lineBand(items) {
|
|
769
|
+
return {
|
|
770
|
+
minU: Math.min.apply(Math, __spreadArray([], __read(items.map(function (item) { return item.minU; })), false)),
|
|
771
|
+
maxU: Math.max.apply(Math, __spreadArray([], __read(items.map(function (item) { return item.maxU; })), false)),
|
|
772
|
+
minV: Math.min.apply(Math, __spreadArray([], __read(items.map(function (item) { return item.minV; })), false)),
|
|
773
|
+
maxV: Math.max.apply(Math, __spreadArray([], __read(items.map(function (item) { return item.maxV; })), false))
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Whether a stroke fits a text line: inside its band (with a quarter-height
|
|
778
|
+
* margin) and no larger than about the line height.
|
|
779
|
+
*/
|
|
780
|
+
function fitsBand(item, band) {
|
|
781
|
+
var height = band.maxV - band.minV;
|
|
782
|
+
var margin = height * 0.25;
|
|
783
|
+
var size = Math.max(item.maxU - item.minU, item.maxV - item.minV);
|
|
784
|
+
return (size <= height * 1.5 &&
|
|
785
|
+
item.minU >= band.minU - margin &&
|
|
786
|
+
item.maxU <= band.maxU + margin &&
|
|
787
|
+
item.minV >= band.minV - margin &&
|
|
788
|
+
item.maxV <= band.maxV + margin);
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Whether a rejected stroke can join a text line taller than the estimated
|
|
792
|
+
* height: across the baseline inside its band (quarter-height margin), along
|
|
793
|
+
* it within a word gap, and no larger than about the line height.
|
|
794
|
+
*
|
|
795
|
+
* @param size - Size to check: the full straight line a segment belongs to,
|
|
796
|
+
* so pieces of grid lines next to text are not attached.
|
|
797
|
+
*/
|
|
798
|
+
function joinsBand(item, band, size, wordGap) {
|
|
799
|
+
var height = band.maxV - band.minV;
|
|
800
|
+
var margin = height * 0.25;
|
|
801
|
+
var gap = height * wordGap;
|
|
802
|
+
return (size <= height * 1.5 &&
|
|
803
|
+
item.maxU >= band.minU - gap &&
|
|
804
|
+
item.minU <= band.maxU + gap &&
|
|
805
|
+
item.minV >= band.minV - margin &&
|
|
806
|
+
item.maxV <= band.maxV + margin);
|
|
807
|
+
}
|
|
808
|
+
function extendBand(band, item) {
|
|
809
|
+
band.minU = Math.min(band.minU, item.minU);
|
|
810
|
+
band.maxU = Math.max(band.maxU, item.maxU);
|
|
811
|
+
band.minV = Math.min(band.minV, item.minV);
|
|
812
|
+
band.maxV = Math.max(band.maxV, item.maxV);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Merges rows lying inside the band of a taller row into it: pieces of a
|
|
816
|
+
* large glyph that were split off where a long stroke, rejected at first,
|
|
817
|
+
* joined them to the rest.
|
|
818
|
+
*/
|
|
819
|
+
function mergeFragments(rows) {
|
|
820
|
+
var height = function (row) {
|
|
821
|
+
var band = lineBand(row.items);
|
|
822
|
+
return band.maxV - band.minV;
|
|
823
|
+
};
|
|
824
|
+
var order = __spreadArray([], __read(rows), false).sort(function (a, b) { return height(a) - height(b); });
|
|
825
|
+
var removed = new Set();
|
|
826
|
+
order.forEach(function (row, i) {
|
|
827
|
+
var _a;
|
|
828
|
+
var strokes = row.items.map(function (item) { return item.stroke; });
|
|
829
|
+
for (var j = order.length - 1; j > i; j--) {
|
|
830
|
+
var target = order[j];
|
|
831
|
+
if (!(height(target) > height(row)))
|
|
832
|
+
continue;
|
|
833
|
+
var local = localStrokes(strokes, target.angle);
|
|
834
|
+
if (!fitsBand(lineBand(local), lineBand(target.items)))
|
|
835
|
+
continue;
|
|
836
|
+
(_a = target.items).push.apply(_a, __spreadArray([], __read(local), false));
|
|
837
|
+
removed.add(row);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
return rows.filter(function (row) { return !removed.has(row); });
|
|
842
|
+
}
|
|
843
|
+
function buildLine(items, angle, textHeight, glyphGap) {
|
|
844
|
+
var glyphs = splitByGaps(items, 'u', glyphGap * textHeight).map(function (group) { return ({
|
|
845
|
+
strokes: group.map(function (item) { return item.stroke; }),
|
|
846
|
+
minU: Math.min.apply(Math, __spreadArray([], __read(group.map(function (item) { return item.minU; })), false)),
|
|
847
|
+
maxU: Math.max.apply(Math, __spreadArray([], __read(group.map(function (item) { return item.maxU; })), false)),
|
|
848
|
+
minV: Math.min.apply(Math, __spreadArray([], __read(group.map(function (item) { return item.minV; })), false)),
|
|
849
|
+
maxV: Math.max.apply(Math, __spreadArray([], __read(group.map(function (item) { return item.maxV; })), false))
|
|
850
|
+
}); });
|
|
851
|
+
// Descenders (g, p, y) hang below the baseline: most glyphs sit on it.
|
|
852
|
+
var baseline = median(glyphs.map(function (glyph) { return glyph.minV; }));
|
|
853
|
+
var tops = glyphs.map(function (glyph) { return glyph.maxV; }).sort(function (a, b) { return a - b; });
|
|
854
|
+
var top = percentile(tops, 0.9);
|
|
855
|
+
var minU = Math.min.apply(Math, __spreadArray([], __read(glyphs.map(function (glyph) { return glyph.minU; })), false));
|
|
856
|
+
var maxU = Math.max.apply(Math, __spreadArray([], __read(glyphs.map(function (glyph) { return glyph.maxU; })), false));
|
|
857
|
+
return {
|
|
858
|
+
strokes: items.map(function (item) { return item.stroke; }),
|
|
859
|
+
glyphs: glyphs,
|
|
860
|
+
rotation: angle,
|
|
861
|
+
position: toWorld(minU, baseline, angle),
|
|
862
|
+
height: Math.max(top - baseline, 1e-9),
|
|
863
|
+
width: maxU - minU
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Clusters strokes into text line candidates.
|
|
868
|
+
*
|
|
869
|
+
* 1. Strokes much larger than text are rejected.
|
|
870
|
+
* 2. Strokes closer than `wordGap · height` join into phrases.
|
|
871
|
+
* 3. Each phrase gets a baseline direction (minimum-height bounding box,
|
|
872
|
+
* or the dominant direction when the phrase is a single glyph).
|
|
873
|
+
* 4. Each phrase is split into rows at gaps across the baseline, and rows
|
|
874
|
+
* into lines at gaps along it wider than `wordGap · height`.
|
|
875
|
+
*
|
|
876
|
+
* @param strokes - Candidate strokes in world coordinates.
|
|
877
|
+
* @param options - Height hint and gap factors.
|
|
878
|
+
*/
|
|
879
|
+
export function clusterPdfTextStrokes(strokes, options) {
|
|
880
|
+
var _a, _b, _c;
|
|
881
|
+
if (options === void 0) { options = {}; }
|
|
882
|
+
var usable = strokes.filter(function (stroke) { return stroke.points.length > 0; });
|
|
883
|
+
var textHeight = (_a = options.textHeight) !== null && _a !== void 0 ? _a : estimatePdfTextHeight(usable);
|
|
884
|
+
var wordGap = (_b = options.wordGap) !== null && _b !== void 0 ? _b : 1.2;
|
|
885
|
+
var glyphGap = (_c = options.glyphGap) !== null && _c !== void 0 ? _c : 0.04;
|
|
886
|
+
if (!(textHeight > 0)) {
|
|
887
|
+
return { lines: [], rejected: usable, textHeight: 0 };
|
|
888
|
+
}
|
|
889
|
+
// Straight lines split into short segments (table grids, frames) are
|
|
890
|
+
// judged by their full length; long strokes of a taller title rejected
|
|
891
|
+
// here are attached back to their line below.
|
|
892
|
+
var runSizes = straightRunSizes(usable, textHeight * 0.01);
|
|
893
|
+
var accepted = [];
|
|
894
|
+
var rejected = [];
|
|
895
|
+
var rejectedSize = new Map();
|
|
896
|
+
usable.forEach(function (stroke, i) {
|
|
897
|
+
var size = Math.max(boxSize(strokeBox(stroke)), runSizes[i]);
|
|
898
|
+
if (size > textHeight * 1.5) {
|
|
899
|
+
rejected.push(stroke);
|
|
900
|
+
rejectedSize.set(stroke, size);
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
accepted.push(stroke);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
// Phrases: strokes within a word gap. Lines of a paragraph may join into
|
|
907
|
+
// one phrase; they are split into rows below once the direction is known.
|
|
908
|
+
var boxes = accepted.map(strokeBox);
|
|
909
|
+
var phrases = clusterBoxes(boxes, wordGap * textHeight).map(function (group) {
|
|
910
|
+
return group.map(function (i) { return accepted[i]; });
|
|
911
|
+
});
|
|
912
|
+
// Direction per phrase: the candidate with the tightest glyph spacing.
|
|
913
|
+
// Phrases too small or too ambiguous to tell (one glyph, `Q1`, or a title
|
|
914
|
+
// whose long strokes are attached only later) borrow the dominant
|
|
915
|
+
// direction of the clear ones.
|
|
916
|
+
var judged = phrases.map(function (phrase) {
|
|
917
|
+
return boxSize(strokeBox({ id: 0, points: phrase.flatMap(function (s) { return s.points; }) })) <
|
|
918
|
+
textHeight * 2
|
|
919
|
+
? undefined
|
|
920
|
+
: bestPhraseAngle(phrase, textHeight, wordGap, glyphGap);
|
|
921
|
+
});
|
|
922
|
+
var known = judged.flatMap(function (result) { return ((result === null || result === void 0 ? void 0 : result.clear) ? [result.angle] : []); });
|
|
923
|
+
var dominant = known.length > 0 ? median(known) : undefined;
|
|
924
|
+
var angles = phrases.map(function (phrase, i) {
|
|
925
|
+
var _a;
|
|
926
|
+
var result = judged[i];
|
|
927
|
+
if (result === null || result === void 0 ? void 0 : result.clear)
|
|
928
|
+
return result.angle;
|
|
929
|
+
return ((_a = dominant !== null && dominant !== void 0 ? dominant : result === null || result === void 0 ? void 0 : result.angle) !== null && _a !== void 0 ? _a : principalAngle(phrase.flatMap(function (stroke) { return stroke.points; })));
|
|
930
|
+
});
|
|
931
|
+
var merged = [];
|
|
932
|
+
phrases.forEach(function (phrase, i) {
|
|
933
|
+
var e_17, _a, e_18, _b;
|
|
934
|
+
var _c;
|
|
935
|
+
var angle = (_c = angles[i]) !== null && _c !== void 0 ? _c : 0;
|
|
936
|
+
var local = localStrokes(phrase, angle);
|
|
937
|
+
try {
|
|
938
|
+
// Rows: gaps across the baseline. Lines: word gaps along it.
|
|
939
|
+
for (var _d = __values(splitByGaps(local, 'v', textHeight * 0.1)), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
940
|
+
var row = _e.value;
|
|
941
|
+
try {
|
|
942
|
+
for (var _f = (e_18 = void 0, __values(splitByGaps(row, 'u', wordGap * textHeight))), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
943
|
+
var line = _g.value;
|
|
944
|
+
merged.push({ angle: angle, items: line });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
catch (e_18_1) { e_18 = { error: e_18_1 }; }
|
|
948
|
+
finally {
|
|
949
|
+
try {
|
|
950
|
+
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
|
|
951
|
+
}
|
|
952
|
+
finally { if (e_18) throw e_18.error; }
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
catch (e_17_1) { e_17 = { error: e_17_1 }; }
|
|
957
|
+
finally {
|
|
958
|
+
try {
|
|
959
|
+
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
960
|
+
}
|
|
961
|
+
finally { if (e_17) throw e_17.error; }
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
// Strokes too long for the estimated height may belong to taller text (a
|
|
965
|
+
// title above body text): attach those lying in the band of such a line.
|
|
966
|
+
// Bands grow as strokes join, so repeat until nothing changes.
|
|
967
|
+
var bands = merged.map(function (row) { return lineBand(row.items); });
|
|
968
|
+
var stillRejected = rejected;
|
|
969
|
+
var _loop_4 = function (changed) {
|
|
970
|
+
changed = false;
|
|
971
|
+
stillRejected = stillRejected.filter(function (stroke) {
|
|
972
|
+
var _a;
|
|
973
|
+
var size = (_a = rejectedSize.get(stroke)) !== null && _a !== void 0 ? _a : 0;
|
|
974
|
+
for (var i = 0; i < merged.length; i++) {
|
|
975
|
+
var item = localStrokes([stroke], merged[i].angle)[0];
|
|
976
|
+
if (!joinsBand(item, bands[i], size, wordGap))
|
|
977
|
+
continue;
|
|
978
|
+
merged[i].items.push(item);
|
|
979
|
+
extendBand(bands[i], item);
|
|
980
|
+
changed = true;
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
return true;
|
|
984
|
+
});
|
|
985
|
+
out_changed_1 = changed;
|
|
986
|
+
};
|
|
987
|
+
var out_changed_1;
|
|
988
|
+
for (var changed = true; changed && stillRejected.length > 0;) {
|
|
989
|
+
_loop_4(changed);
|
|
990
|
+
changed = out_changed_1;
|
|
991
|
+
}
|
|
992
|
+
var rows = mergeFragments(merged);
|
|
993
|
+
var lines = rows.map(function (row) {
|
|
994
|
+
return buildLine(row.items, row.angle, textHeight, glyphGap);
|
|
995
|
+
});
|
|
996
|
+
// Reading order: top to bottom, then left to right.
|
|
997
|
+
lines.sort(function (a, b) {
|
|
998
|
+
var dy = b.position.y - a.position.y;
|
|
999
|
+
if (Math.abs(dy) > Math.min(a.height, b.height) * 0.5)
|
|
1000
|
+
return dy;
|
|
1001
|
+
return a.position.x - b.position.x;
|
|
1002
|
+
});
|
|
1003
|
+
return { lines: lines, rejected: stillRejected, textHeight: textHeight };
|
|
1004
|
+
}
|
|
1005
|
+
//# sourceMappingURL=pdfGlyphClustering.js.map
|