@mlightcad/cad-pdf-plugin 1.8.0-preview.3 → 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,3781 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovers pattern hatches from the loose stroke segments a PDF holds.
|
|
3
|
+
*
|
|
4
|
+
* CAD programs (AutoCAD included) write a pattern hatch to PDF as plain
|
|
5
|
+
* strokes that are already clipped to the hatch boundary, so an import sees
|
|
6
|
+
* thousands of short lines. This module finds groups of segments that form
|
|
7
|
+
* a regular pattern, matches them against the `acadiso.pat` library (or a
|
|
8
|
+
* user-defined single / double pattern), recovers a boundary and accepts
|
|
9
|
+
* the hatch only when re-drawing it reproduces the source segments.
|
|
10
|
+
*
|
|
11
|
+
* Pure data in, pure data out: entity construction happens in
|
|
12
|
+
* `AcApPdfImportConvertor`.
|
|
13
|
+
*/
|
|
14
|
+
var __assign = (this && this.__assign) || function () {
|
|
15
|
+
__assign = Object.assign || function(t) {
|
|
16
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
17
|
+
s = arguments[i];
|
|
18
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
19
|
+
t[p] = s[p];
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
};
|
|
23
|
+
return __assign.apply(this, arguments);
|
|
24
|
+
};
|
|
25
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
26
|
+
var t = {};
|
|
27
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
28
|
+
t[p] = s[p];
|
|
29
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
30
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
31
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
32
|
+
t[p[i]] = s[p[i]];
|
|
33
|
+
}
|
|
34
|
+
return t;
|
|
35
|
+
};
|
|
36
|
+
var __values = (this && this.__values) || function(o) {
|
|
37
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
38
|
+
if (m) return m.call(o);
|
|
39
|
+
if (o && typeof o.length === "number") return {
|
|
40
|
+
next: function () {
|
|
41
|
+
if (o && i >= o.length) o = void 0;
|
|
42
|
+
return { value: o && o[i++], done: !o };
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
46
|
+
};
|
|
47
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
48
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
49
|
+
if (!m) return o;
|
|
50
|
+
var i = m.call(o), r, ar = [], e;
|
|
51
|
+
try {
|
|
52
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
53
|
+
}
|
|
54
|
+
catch (error) { e = { error: error }; }
|
|
55
|
+
finally {
|
|
56
|
+
try {
|
|
57
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
58
|
+
}
|
|
59
|
+
finally { if (e) throw e.error; }
|
|
60
|
+
}
|
|
61
|
+
return ar;
|
|
62
|
+
};
|
|
63
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
64
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
65
|
+
if (ar || !(i in from)) {
|
|
66
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
67
|
+
ar[i] = from[i];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
71
|
+
};
|
|
72
|
+
import { AcDbPredefinedAcadIsoPat } from '@mlightcad/data-model';
|
|
73
|
+
import { tessellateHatchPattern } from '@mlightcad/pdf-renderer';
|
|
74
|
+
import { acapPdfFaceArrangement, acapPdfFaceBoundaryIn, acapPdfFaceBoundaryParts, acapPdfShapePolyline } from './pdfHatchFaces';
|
|
75
|
+
/**
|
|
76
|
+
* Recognizes pattern hatches among placed shape items.
|
|
77
|
+
*
|
|
78
|
+
* Only unfilled, undashed straight lines take part. Segments a hatch does
|
|
79
|
+
* not reproduce stay as they are, so a failed recognition changes nothing.
|
|
80
|
+
*
|
|
81
|
+
* @param items - Placed shapes in paint order (from `buildPdfImportShapes`).
|
|
82
|
+
* @param options - Tolerances and thresholds.
|
|
83
|
+
* @param clipPaths - Clip loops by {@link PdfImportShapeItem.clipPath} id,
|
|
84
|
+
* placed like the items. A clip around a hatch is a boundary candidate.
|
|
85
|
+
*/
|
|
86
|
+
export function acapPdfRecognizeHatches(items, options, clipPaths) {
|
|
87
|
+
var e_1, _a;
|
|
88
|
+
var opts = resolveOptions(options);
|
|
89
|
+
var hatches = [];
|
|
90
|
+
var consumed = new Set();
|
|
91
|
+
var library = patternSignatures(opts.patterns);
|
|
92
|
+
var closedShapes = collectClosedShapes(items);
|
|
93
|
+
var context = {
|
|
94
|
+
library: library,
|
|
95
|
+
closedShapes: closedShapes,
|
|
96
|
+
clipPaths: clipPaths,
|
|
97
|
+
items: items,
|
|
98
|
+
itemIndex: new ItemIndex(items),
|
|
99
|
+
opts: opts,
|
|
100
|
+
faces: new FaceCache()
|
|
101
|
+
};
|
|
102
|
+
var _loop_1 = function (group) {
|
|
103
|
+
var accept = function (region) {
|
|
104
|
+
var e_2, _a, e_3, _b;
|
|
105
|
+
var any = false;
|
|
106
|
+
try {
|
|
107
|
+
for (var _c = (e_2 = void 0, __values(recognizeRegion(region, group.all, context))), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
108
|
+
var found = _d.value;
|
|
109
|
+
var minSegments = found.minSegments, hatch = __rest(found, ["minSegments"]);
|
|
110
|
+
var kept = hatch.items.filter(function (index) { return !consumed.has(index); });
|
|
111
|
+
if (kept.length < (minSegments !== null && minSegments !== void 0 ? minSegments : opts.minSegments))
|
|
112
|
+
continue;
|
|
113
|
+
try {
|
|
114
|
+
for (var kept_1 = (e_3 = void 0, __values(kept)), kept_1_1 = kept_1.next(); !kept_1_1.done; kept_1_1 = kept_1.next()) {
|
|
115
|
+
var index = kept_1_1.value;
|
|
116
|
+
consumed.add(index);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
120
|
+
finally {
|
|
121
|
+
try {
|
|
122
|
+
if (kept_1_1 && !kept_1_1.done && (_b = kept_1.return)) _b.call(kept_1);
|
|
123
|
+
}
|
|
124
|
+
finally { if (e_3) throw e_3.error; }
|
|
125
|
+
}
|
|
126
|
+
var first = items[kept[0]];
|
|
127
|
+
hatches.push(__assign(__assign({}, hatch), { items: kept, layerName: first.layerName, color: first.color, lineWidth: first.lineWidth }));
|
|
128
|
+
any = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
132
|
+
finally {
|
|
133
|
+
try {
|
|
134
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
135
|
+
}
|
|
136
|
+
finally { if (e_2) throw e_2.error; }
|
|
137
|
+
}
|
|
138
|
+
return any;
|
|
139
|
+
};
|
|
140
|
+
var regions = findRegions(group.long, opts);
|
|
141
|
+
opts.trace('regions', {
|
|
142
|
+
candidates: group.all.length,
|
|
143
|
+
long: group.long.length,
|
|
144
|
+
sizes: regions.map(function (region) { return region.length; })
|
|
145
|
+
});
|
|
146
|
+
var queue = __spreadArray([], __read(regions), false);
|
|
147
|
+
var patchesFrom = regions.length;
|
|
148
|
+
for (var k = 0; k < queue.length; k++) {
|
|
149
|
+
var region = queue[k];
|
|
150
|
+
if (region.some(function (seg) { return consumed.has(seg.index); }))
|
|
151
|
+
continue;
|
|
152
|
+
if (accept(region))
|
|
153
|
+
continue;
|
|
154
|
+
// Hatches of one pattern spread over the drawing (AR-CONC in several
|
|
155
|
+
// rooms) form one region too large to bound; try each patch, once.
|
|
156
|
+
if (k < patchesFrom && region.length >= MIN_PATCH_REGION) {
|
|
157
|
+
var patches = spatialPatches(region, opts);
|
|
158
|
+
if (patches.length > 1) {
|
|
159
|
+
queue.push.apply(queue, __spreadArray([], __read(patches), false));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Hatches of one colour that touch can end up in one region; peel
|
|
164
|
+
// off one regular run of parallel lines at a time.
|
|
165
|
+
var rest = region;
|
|
166
|
+
var _loop_2 = function (round) {
|
|
167
|
+
var run = dominantProgression(rest, opts);
|
|
168
|
+
// The whole region already failed as one piece.
|
|
169
|
+
if (!run || (round === 0 && run.length === region.length))
|
|
170
|
+
return "break";
|
|
171
|
+
accept(run);
|
|
172
|
+
var taken = new Set(run);
|
|
173
|
+
rest = rest.filter(function (seg) { return !taken.has(seg) && !consumed.has(seg.index); });
|
|
174
|
+
if (rest.length < opts.minSegments)
|
|
175
|
+
return "break";
|
|
176
|
+
};
|
|
177
|
+
for (var round = 0; round < MAX_SPLITS; round++) {
|
|
178
|
+
var state_1 = _loop_2(round);
|
|
179
|
+
if (state_1 === "break")
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
for (var _b = __values(groupCandidates(items, opts.tolerance)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
186
|
+
var group = _c.value;
|
|
187
|
+
_loop_1(group);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
191
|
+
finally {
|
|
192
|
+
try {
|
|
193
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
194
|
+
}
|
|
195
|
+
finally { if (e_1) throw e_1.error; }
|
|
196
|
+
}
|
|
197
|
+
return { hatches: hatches, consumed: consumed };
|
|
198
|
+
}
|
|
199
|
+
var NO_TRACE = function () { };
|
|
200
|
+
function resolveOptions(options) {
|
|
201
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
202
|
+
var tolerance = options === null || options === void 0 ? void 0 : options.tolerance;
|
|
203
|
+
return {
|
|
204
|
+
tolerance: tolerance !== undefined && tolerance > 0 ? tolerance : 0.05,
|
|
205
|
+
minSegments: Math.max(2, (_a = options === null || options === void 0 ? void 0 : options.minSegments) !== null && _a !== void 0 ? _a : 8),
|
|
206
|
+
minLines: Math.max(2, (_b = options === null || options === void 0 ? void 0 : options.minLines) !== null && _b !== void 0 ? _b : 3),
|
|
207
|
+
minRecall: (_c = options === null || options === void 0 ? void 0 : options.minRecall) !== null && _c !== void 0 ? _c : 0.9,
|
|
208
|
+
minPrecision: (_d = options === null || options === void 0 ? void 0 : options.minPrecision) !== null && _d !== void 0 ? _d : 0.95,
|
|
209
|
+
patterns: (_e = options === null || options === void 0 ? void 0 : options.patterns) !== null && _e !== void 0 ? _e : AcDbPredefinedAcadIsoPat.patterns,
|
|
210
|
+
customPatterns: (_f = options === null || options === void 0 ? void 0 : options.customPatterns) !== null && _f !== void 0 ? _f : false,
|
|
211
|
+
faceBoundaries: (_g = options === null || options === void 0 ? void 0 : options.faceBoundaries) !== null && _g !== void 0 ? _g : true,
|
|
212
|
+
boundaries: (_h = options === null || options === void 0 ? void 0 : options.boundaries) !== null && _h !== void 0 ? _h : [],
|
|
213
|
+
trace: (_j = options === null || options === void 0 ? void 0 : options.trace) !== null && _j !== void 0 ? _j : NO_TRACE
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** Segments too short for a reliable direction (corner stubs). */
|
|
217
|
+
var SHORT_FACTOR = 4;
|
|
218
|
+
/** Angle gap that splits two direction clusters (radians). */
|
|
219
|
+
var ANGLE_GAP = (0.5 * Math.PI) / 180;
|
|
220
|
+
/** Most translation hypotheses scored per pattern. */
|
|
221
|
+
var MAX_TRANSLATIONS = 400;
|
|
222
|
+
/** Segments sampled to score a translation. */
|
|
223
|
+
var SCORE_SAMPLE = 64;
|
|
224
|
+
/** Lowest sample score worth a full verification. */
|
|
225
|
+
var MIN_SCORE = 0.8;
|
|
226
|
+
function groupCandidates(items, tolerance) {
|
|
227
|
+
var groups = new Map();
|
|
228
|
+
items.forEach(function (item, index) {
|
|
229
|
+
var _a, _b, _c;
|
|
230
|
+
var shape = item.shape;
|
|
231
|
+
if (shape.kind !== 'line' || item.filled || item.dashPattern)
|
|
232
|
+
return;
|
|
233
|
+
var dx = shape.end.x - shape.start.x;
|
|
234
|
+
var dy = shape.end.y - shape.start.y;
|
|
235
|
+
var len = Math.hypot(dx, dy);
|
|
236
|
+
if (!(len > tolerance * 0.1))
|
|
237
|
+
return;
|
|
238
|
+
var theta = Math.atan2(dy, dx);
|
|
239
|
+
if (theta < 0)
|
|
240
|
+
theta += Math.PI;
|
|
241
|
+
if (theta >= Math.PI)
|
|
242
|
+
theta -= Math.PI;
|
|
243
|
+
var seg = { index: index, a: shape.start, b: shape.end, len: len, theta: theta };
|
|
244
|
+
// A hatch has one layer, colour and pen width.
|
|
245
|
+
var key = "".concat((_a = item.layerName) !== null && _a !== void 0 ? _a : '', "|").concat((_b = item.color) !== null && _b !== void 0 ? _b : '', "|").concat(((_c = item.lineWidth) !== null && _c !== void 0 ? _c : 0).toFixed(3));
|
|
246
|
+
var group = groups.get(key);
|
|
247
|
+
if (!group) {
|
|
248
|
+
group = { long: [], all: [] };
|
|
249
|
+
groups.set(key, group);
|
|
250
|
+
}
|
|
251
|
+
group.all.push(seg);
|
|
252
|
+
if (len >= tolerance * SHORT_FACTOR)
|
|
253
|
+
group.long.push(seg);
|
|
254
|
+
});
|
|
255
|
+
return __spreadArray([], __read(groups.values()), false);
|
|
256
|
+
}
|
|
257
|
+
function clusterByAngle(segs) {
|
|
258
|
+
var e_4, _a;
|
|
259
|
+
var sorted = __spreadArray([], __read(segs), false).sort(function (p, q) { return p.theta - q.theta; });
|
|
260
|
+
var clusters = [];
|
|
261
|
+
try {
|
|
262
|
+
for (var sorted_1 = __values(sorted), sorted_1_1 = sorted_1.next(); !sorted_1_1.done; sorted_1_1 = sorted_1.next()) {
|
|
263
|
+
var seg = sorted_1_1.value;
|
|
264
|
+
var last = clusters[clusters.length - 1];
|
|
265
|
+
if (last && seg.theta - last[last.length - 1].theta <= ANGLE_GAP) {
|
|
266
|
+
last.push(seg);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
clusters.push([seg]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
274
|
+
finally {
|
|
275
|
+
try {
|
|
276
|
+
if (sorted_1_1 && !sorted_1_1.done && (_a = sorted_1.return)) _a.call(sorted_1);
|
|
277
|
+
}
|
|
278
|
+
finally { if (e_4) throw e_4.error; }
|
|
279
|
+
}
|
|
280
|
+
// 0 and π are the same direction.
|
|
281
|
+
if (clusters.length > 1) {
|
|
282
|
+
var first = clusters[0];
|
|
283
|
+
var last = clusters[clusters.length - 1];
|
|
284
|
+
if (first[0].theta + Math.PI - last[last.length - 1].theta <= ANGLE_GAP) {
|
|
285
|
+
clusters[0] = __spreadArray(__spreadArray([], __read(last), false), __read(first), false);
|
|
286
|
+
clusters.pop();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return clusters.map(function (list) { return ({ theta: meanAngle(list), segs: list }); });
|
|
290
|
+
}
|
|
291
|
+
/** Length-weighted mean direction in `[0, π)` (doubled-angle average). */
|
|
292
|
+
function meanAngle(segs) {
|
|
293
|
+
var e_5, _a;
|
|
294
|
+
var sx = 0;
|
|
295
|
+
var sy = 0;
|
|
296
|
+
try {
|
|
297
|
+
for (var segs_1 = __values(segs), segs_1_1 = segs_1.next(); !segs_1_1.done; segs_1_1 = segs_1.next()) {
|
|
298
|
+
var seg = segs_1_1.value;
|
|
299
|
+
sx += seg.len * Math.cos(2 * seg.theta);
|
|
300
|
+
sy += seg.len * Math.sin(2 * seg.theta);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
304
|
+
finally {
|
|
305
|
+
try {
|
|
306
|
+
if (segs_1_1 && !segs_1_1.done && (_a = segs_1.return)) _a.call(segs_1);
|
|
307
|
+
}
|
|
308
|
+
finally { if (e_5) throw e_5.error; }
|
|
309
|
+
}
|
|
310
|
+
var theta = Math.atan2(sy, sx) / 2;
|
|
311
|
+
if (theta < 0)
|
|
312
|
+
theta += Math.PI;
|
|
313
|
+
return theta;
|
|
314
|
+
}
|
|
315
|
+
/** Groups parallel segments into lines by their perpendicular offset. */
|
|
316
|
+
function linesOf(segs, theta, tol) {
|
|
317
|
+
var e_6, _a, e_7, _b;
|
|
318
|
+
var ux = Math.cos(theta);
|
|
319
|
+
var uy = Math.sin(theta);
|
|
320
|
+
var entries = segs.map(function (seg) {
|
|
321
|
+
var c = -uy * (seg.a.x + seg.b.x) * 0.5 + ux * (seg.a.y + seg.b.y) * 0.5;
|
|
322
|
+
var p = ux * seg.a.x + uy * seg.a.y;
|
|
323
|
+
var q = ux * seg.b.x + uy * seg.b.y;
|
|
324
|
+
return { seg: seg, c: c, s0: Math.min(p, q), s1: Math.max(p, q) };
|
|
325
|
+
});
|
|
326
|
+
entries.sort(function (p, q) { return p.c - q.c; });
|
|
327
|
+
var lines = [];
|
|
328
|
+
var sum = 0;
|
|
329
|
+
var lastC = -Infinity;
|
|
330
|
+
try {
|
|
331
|
+
for (var entries_1 = __values(entries), entries_1_1 = entries_1.next(); !entries_1_1.done; entries_1_1 = entries_1.next()) {
|
|
332
|
+
var entry = entries_1_1.value;
|
|
333
|
+
var last = lines[lines.length - 1];
|
|
334
|
+
var item = { seg: entry.seg, s0: entry.s0, s1: entry.s1 };
|
|
335
|
+
// Chain neighbours: PDF rounding spreads one line over a few offsets.
|
|
336
|
+
if (last && entry.c - lastC <= tol) {
|
|
337
|
+
last.segs.push(item);
|
|
338
|
+
sum += entry.c;
|
|
339
|
+
last.c = sum / last.segs.length;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
lines.push({ c: entry.c, segs: [item] });
|
|
343
|
+
sum = entry.c;
|
|
344
|
+
}
|
|
345
|
+
lastC = entry.c;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
349
|
+
finally {
|
|
350
|
+
try {
|
|
351
|
+
if (entries_1_1 && !entries_1_1.done && (_a = entries_1.return)) _a.call(entries_1);
|
|
352
|
+
}
|
|
353
|
+
finally { if (e_6) throw e_6.error; }
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
for (var lines_1 = __values(lines), lines_1_1 = lines_1.next(); !lines_1_1.done; lines_1_1 = lines_1.next()) {
|
|
357
|
+
var line = lines_1_1.value;
|
|
358
|
+
line.segs.sort(function (p, q) { return p.s0 - q.s0; });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
362
|
+
finally {
|
|
363
|
+
try {
|
|
364
|
+
if (lines_1_1 && !lines_1_1.done && (_b = lines_1.return)) _b.call(lines_1);
|
|
365
|
+
}
|
|
366
|
+
finally { if (e_7) throw e_7.error; }
|
|
367
|
+
}
|
|
368
|
+
return lines;
|
|
369
|
+
}
|
|
370
|
+
var UnionFind = /** @class */ (function () {
|
|
371
|
+
function UnionFind(size) {
|
|
372
|
+
this.parent = Array.from({ length: size }, function (_, i) { return i; });
|
|
373
|
+
}
|
|
374
|
+
UnionFind.prototype.find = function (i) {
|
|
375
|
+
while (this.parent[i] !== i) {
|
|
376
|
+
this.parent[i] = this.parent[this.parent[i]];
|
|
377
|
+
i = this.parent[i];
|
|
378
|
+
}
|
|
379
|
+
return i;
|
|
380
|
+
};
|
|
381
|
+
UnionFind.prototype.union = function (a, b) {
|
|
382
|
+
var ra = this.find(a);
|
|
383
|
+
var rb = this.find(b);
|
|
384
|
+
if (ra !== rb)
|
|
385
|
+
this.parent[ra] = rb;
|
|
386
|
+
};
|
|
387
|
+
return UnionFind;
|
|
388
|
+
}());
|
|
389
|
+
/** Lines scanned past a segment for its nearest overlapping neighbour. */
|
|
390
|
+
var NEIGHBOUR_SCAN = 256;
|
|
391
|
+
/** Largest jump between neighbouring gaps that still links (gap ratio). */
|
|
392
|
+
var GAP_RATIO = 2.5;
|
|
393
|
+
/** Share of the shorter segment two neighbours must overlap to link. */
|
|
394
|
+
var MIN_OVERLAP = 0.3;
|
|
395
|
+
/** Segments shorter than this share of the median length do not link. */
|
|
396
|
+
var SHORT_LINK = 0.25;
|
|
397
|
+
/**
|
|
398
|
+
* Spatially connected runs of parallel segments within one cluster.
|
|
399
|
+
*
|
|
400
|
+
* Several hatches of one colour interleave in offset order, so each segment
|
|
401
|
+
* links to its nearest parallel neighbour whose extent overlaps, not to the
|
|
402
|
+
* next line overall. A link is kept only when its gap fits the gaps around
|
|
403
|
+
* it, so a lone parallel line far from a hatch does not join it.
|
|
404
|
+
*/
|
|
405
|
+
function clusterComponents(cluster, tol) {
|
|
406
|
+
var e_8, _a, e_9, _b, e_10, _c, e_11, _d, e_12, _e, e_13, _f, e_14, _g, e_15, _h;
|
|
407
|
+
var lines = linesOf(cluster.segs, cluster.theta, tol);
|
|
408
|
+
var rows = [];
|
|
409
|
+
var id = 0;
|
|
410
|
+
try {
|
|
411
|
+
for (var lines_2 = __values(lines), lines_2_1 = lines_2.next(); !lines_2_1.done; lines_2_1 = lines_2.next()) {
|
|
412
|
+
var line = lines_2_1.value;
|
|
413
|
+
rows.push(line.segs.map(function (e) { return (__assign(__assign({}, e), { id: id++ })); }));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
417
|
+
finally {
|
|
418
|
+
try {
|
|
419
|
+
if (lines_2_1 && !lines_2_1.done && (_a = lines_2.return)) _a.call(lines_2);
|
|
420
|
+
}
|
|
421
|
+
finally { if (e_8) throw e_8.error; }
|
|
422
|
+
}
|
|
423
|
+
// Gaps to the nearest neighbour that overlaps (same hatch) and to the
|
|
424
|
+
// nearest one that merely touches (staggered dashes such as bricks).
|
|
425
|
+
var fwd = new Array(id).fill(Infinity);
|
|
426
|
+
var bwd = new Array(id).fill(Infinity);
|
|
427
|
+
var touchFwd = new Array(id).fill(Infinity);
|
|
428
|
+
var touchBwd = new Array(id).fill(Infinity);
|
|
429
|
+
var links = [];
|
|
430
|
+
// Corner stubs overlap a neighbouring hatch as much as their own, so
|
|
431
|
+
// they do not link; verification picks them up afterwards.
|
|
432
|
+
var lengths = cluster.segs.map(function (seg) { return seg.len; }).sort(function (p, q) { return p - q; });
|
|
433
|
+
var minLinkLength = lengths[Math.floor(lengths.length / 2)] * SHORT_LINK;
|
|
434
|
+
var linkable = function (e) { return e.s1 - e.s0 >= minLinkLength; };
|
|
435
|
+
for (var i = 0; i < rows.length; i++) {
|
|
436
|
+
var _loop_3 = function (entry) {
|
|
437
|
+
var e_16, _r, e_17, _s;
|
|
438
|
+
if (!linkable(entry))
|
|
439
|
+
return "continue";
|
|
440
|
+
var touched = false;
|
|
441
|
+
for (var j = i + 1; j < rows.length && j - i <= NEIGHBOUR_SCAN; j++) {
|
|
442
|
+
var gap = lines[j].c - lines[i].c;
|
|
443
|
+
if (!touched) {
|
|
444
|
+
var slack = gap * 2 + tol;
|
|
445
|
+
var near = overlapping(rows[j], entry.s0 - slack, entry.s1 + slack);
|
|
446
|
+
if (near.length > 0) {
|
|
447
|
+
touched = true;
|
|
448
|
+
touchFwd[entry.id] = gap;
|
|
449
|
+
try {
|
|
450
|
+
for (var near_1 = (e_16 = void 0, __values(near)), near_1_1 = near_1.next(); !near_1_1.done; near_1_1 = near_1.next()) {
|
|
451
|
+
var hit = near_1_1.value;
|
|
452
|
+
touchBwd[hit.id] = Math.min(touchBwd[hit.id], gap);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (e_16_1) { e_16 = { error: e_16_1 }; }
|
|
456
|
+
finally {
|
|
457
|
+
try {
|
|
458
|
+
if (near_1_1 && !near_1_1.done && (_r = near_1.return)) _r.call(near_1);
|
|
459
|
+
}
|
|
460
|
+
finally { if (e_16) throw e_16.error; }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Lines of one hatch overlap along their length; lines of two
|
|
465
|
+
// hatches that only meet at a shared edge do not.
|
|
466
|
+
var hits = overlapping(rows[j], entry.s0, entry.s1).filter(function (hit) {
|
|
467
|
+
return linkable(hit) &&
|
|
468
|
+
Math.min(hit.s1, entry.s1) - Math.max(hit.s0, entry.s0) >=
|
|
469
|
+
MIN_OVERLAP * Math.min(hit.s1 - hit.s0, entry.s1 - entry.s0);
|
|
470
|
+
});
|
|
471
|
+
if (hits.length === 0)
|
|
472
|
+
continue;
|
|
473
|
+
fwd[entry.id] = gap;
|
|
474
|
+
try {
|
|
475
|
+
for (var hits_1 = (e_17 = void 0, __values(hits)), hits_1_1 = hits_1.next(); !hits_1_1.done; hits_1_1 = hits_1.next()) {
|
|
476
|
+
var hit = hits_1_1.value;
|
|
477
|
+
bwd[hit.id] = Math.min(bwd[hit.id], gap);
|
|
478
|
+
links.push({ a: entry.id, b: hit.id, gap: gap });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch (e_17_1) { e_17 = { error: e_17_1 }; }
|
|
482
|
+
finally {
|
|
483
|
+
try {
|
|
484
|
+
if (hits_1_1 && !hits_1_1.done && (_s = hits_1.return)) _s.call(hits_1);
|
|
485
|
+
}
|
|
486
|
+
finally { if (e_17) throw e_17.error; }
|
|
487
|
+
}
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
try {
|
|
492
|
+
for (var _j = (e_9 = void 0, __values(rows[i])), _k = _j.next(); !_k.done; _k = _j.next()) {
|
|
493
|
+
var entry = _k.value;
|
|
494
|
+
_loop_3(entry);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
498
|
+
finally {
|
|
499
|
+
try {
|
|
500
|
+
if (_k && !_k.done && (_b = _j.return)) _b.call(_j);
|
|
501
|
+
}
|
|
502
|
+
finally { if (e_9) throw e_9.error; }
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
var uf = new UnionFind(id);
|
|
506
|
+
try {
|
|
507
|
+
for (var links_1 = __values(links), links_1_1 = links_1.next(); !links_1_1.done; links_1_1 = links_1.next()) {
|
|
508
|
+
var _l = links_1_1.value, a = _l.a, b = _l.b, gap = _l.gap;
|
|
509
|
+
var around = Math.min(bwd[a], fwd[b]);
|
|
510
|
+
if (!Number.isFinite(around) || gap <= around * GAP_RATIO)
|
|
511
|
+
uf.union(a, b);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
515
|
+
finally {
|
|
516
|
+
try {
|
|
517
|
+
if (links_1_1 && !links_1_1.done && (_c = links_1.return)) _c.call(links_1);
|
|
518
|
+
}
|
|
519
|
+
finally { if (e_10) throw e_10.error; }
|
|
520
|
+
}
|
|
521
|
+
// Each stub joins the component of its nearest overlapping long segment:
|
|
522
|
+
// one attachment cannot bridge two hatches.
|
|
523
|
+
for (var i = 0; i < rows.length; i++) {
|
|
524
|
+
try {
|
|
525
|
+
for (var _m = (e_11 = void 0, __values(rows[i])), _o = _m.next(); !_o.done; _o = _m.next()) {
|
|
526
|
+
var entry = _o.value;
|
|
527
|
+
if (linkable(entry))
|
|
528
|
+
continue;
|
|
529
|
+
var best = void 0;
|
|
530
|
+
try {
|
|
531
|
+
for (var _p = (e_12 = void 0, __values([-1, 1])), _q = _p.next(); !_q.done; _q = _p.next()) {
|
|
532
|
+
var dir = _q.value;
|
|
533
|
+
for (var j = i + dir; j >= 0 && j < rows.length && Math.abs(j - i) <= NEIGHBOUR_SCAN; j += dir) {
|
|
534
|
+
var gap = Math.abs(lines[j].c - lines[i].c);
|
|
535
|
+
if (best && gap >= best.gap)
|
|
536
|
+
break;
|
|
537
|
+
var hit = overlapping(rows[j], entry.s0 - tol, entry.s1 + tol).find(linkable);
|
|
538
|
+
if (hit) {
|
|
539
|
+
best = { id: hit.id, gap: gap };
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
546
|
+
finally {
|
|
547
|
+
try {
|
|
548
|
+
if (_q && !_q.done && (_e = _p.return)) _e.call(_p);
|
|
549
|
+
}
|
|
550
|
+
finally { if (e_12) throw e_12.error; }
|
|
551
|
+
}
|
|
552
|
+
if (best)
|
|
553
|
+
uf.union(entry.id, best.id);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (e_11_1) { e_11 = { error: e_11_1 }; }
|
|
557
|
+
finally {
|
|
558
|
+
try {
|
|
559
|
+
if (_o && !_o.done && (_d = _m.return)) _d.call(_m);
|
|
560
|
+
}
|
|
561
|
+
finally { if (e_11) throw e_11.error; }
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
var localGap = function (e) {
|
|
565
|
+
var own = Math.min(fwd[e.id], bwd[e.id]);
|
|
566
|
+
return Number.isFinite(own) ? own : Math.min(touchFwd[e.id], touchBwd[e.id]);
|
|
567
|
+
};
|
|
568
|
+
try {
|
|
569
|
+
// Dashes of one line belong together when their gaps match the spacing.
|
|
570
|
+
for (var rows_1 = __values(rows), rows_1_1 = rows_1.next(); !rows_1_1.done; rows_1_1 = rows_1.next()) {
|
|
571
|
+
var row = rows_1_1.value;
|
|
572
|
+
for (var k = 0; k + 1 < row.length; k++) {
|
|
573
|
+
var p = row[k];
|
|
574
|
+
var q = row[k + 1];
|
|
575
|
+
var local = Math.max(localGap(p), localGap(q));
|
|
576
|
+
if (Number.isFinite(local) && q.s0 - p.s1 <= local * GAP_RATIO + tol) {
|
|
577
|
+
uf.union(p.id, q.id);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch (e_13_1) { e_13 = { error: e_13_1 }; }
|
|
583
|
+
finally {
|
|
584
|
+
try {
|
|
585
|
+
if (rows_1_1 && !rows_1_1.done && (_f = rows_1.return)) _f.call(rows_1);
|
|
586
|
+
}
|
|
587
|
+
finally { if (e_13) throw e_13.error; }
|
|
588
|
+
}
|
|
589
|
+
var byRoot = new Map();
|
|
590
|
+
try {
|
|
591
|
+
for (var rows_2 = __values(rows), rows_2_1 = rows_2.next(); !rows_2_1.done; rows_2_1 = rows_2.next()) {
|
|
592
|
+
var row = rows_2_1.value;
|
|
593
|
+
try {
|
|
594
|
+
for (var row_1 = (e_15 = void 0, __values(row)), row_1_1 = row_1.next(); !row_1_1.done; row_1_1 = row_1.next()) {
|
|
595
|
+
var entry = row_1_1.value;
|
|
596
|
+
var root = uf.find(entry.id);
|
|
597
|
+
var list = byRoot.get(root);
|
|
598
|
+
if (list)
|
|
599
|
+
list.push(entry.seg);
|
|
600
|
+
else
|
|
601
|
+
byRoot.set(root, [entry.seg]);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch (e_15_1) { e_15 = { error: e_15_1 }; }
|
|
605
|
+
finally {
|
|
606
|
+
try {
|
|
607
|
+
if (row_1_1 && !row_1_1.done && (_h = row_1.return)) _h.call(row_1);
|
|
608
|
+
}
|
|
609
|
+
finally { if (e_15) throw e_15.error; }
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
catch (e_14_1) { e_14 = { error: e_14_1 }; }
|
|
614
|
+
finally {
|
|
615
|
+
try {
|
|
616
|
+
if (rows_2_1 && !rows_2_1.done && (_g = rows_2.return)) _g.call(rows_2);
|
|
617
|
+
}
|
|
618
|
+
finally { if (e_14) throw e_14.error; }
|
|
619
|
+
}
|
|
620
|
+
return __spreadArray([], __read(byRoot.values()), false);
|
|
621
|
+
}
|
|
622
|
+
/** Entries of a row (sorted by `s0`) whose extent meets `[lo, hi]`. */
|
|
623
|
+
function overlapping(row, lo, hi) {
|
|
624
|
+
var e_18, _a;
|
|
625
|
+
var out = [];
|
|
626
|
+
try {
|
|
627
|
+
for (var row_2 = __values(row), row_2_1 = row_2.next(); !row_2_1.done; row_2_1 = row_2.next()) {
|
|
628
|
+
var entry = row_2_1.value;
|
|
629
|
+
if (entry.s0 > hi)
|
|
630
|
+
break;
|
|
631
|
+
if (entry.s1 >= lo)
|
|
632
|
+
out.push(entry);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch (e_18_1) { e_18 = { error: e_18_1 }; }
|
|
636
|
+
finally {
|
|
637
|
+
try {
|
|
638
|
+
if (row_2_1 && !row_2_1.done && (_a = row_2.return)) _a.call(row_2);
|
|
639
|
+
}
|
|
640
|
+
finally { if (e_18) throw e_18.error; }
|
|
641
|
+
}
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
function segsBox(segs) {
|
|
645
|
+
var e_19, _a, e_20, _b;
|
|
646
|
+
var box = {
|
|
647
|
+
minX: Infinity,
|
|
648
|
+
minY: Infinity,
|
|
649
|
+
maxX: -Infinity,
|
|
650
|
+
maxY: -Infinity
|
|
651
|
+
};
|
|
652
|
+
try {
|
|
653
|
+
for (var segs_2 = __values(segs), segs_2_1 = segs_2.next(); !segs_2_1.done; segs_2_1 = segs_2.next()) {
|
|
654
|
+
var seg = segs_2_1.value;
|
|
655
|
+
try {
|
|
656
|
+
for (var _c = (e_20 = void 0, __values([seg.a, seg.b])), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
657
|
+
var p = _d.value;
|
|
658
|
+
if (p.x < box.minX)
|
|
659
|
+
box.minX = p.x;
|
|
660
|
+
if (p.y < box.minY)
|
|
661
|
+
box.minY = p.y;
|
|
662
|
+
if (p.x > box.maxX)
|
|
663
|
+
box.maxX = p.x;
|
|
664
|
+
if (p.y > box.maxY)
|
|
665
|
+
box.maxY = p.y;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
catch (e_20_1) { e_20 = { error: e_20_1 }; }
|
|
669
|
+
finally {
|
|
670
|
+
try {
|
|
671
|
+
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
|
|
672
|
+
}
|
|
673
|
+
finally { if (e_20) throw e_20.error; }
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
catch (e_19_1) { e_19 = { error: e_19_1 }; }
|
|
678
|
+
finally {
|
|
679
|
+
try {
|
|
680
|
+
if (segs_2_1 && !segs_2_1.done && (_a = segs_2.return)) _a.call(segs_2);
|
|
681
|
+
}
|
|
682
|
+
finally { if (e_19) throw e_19.error; }
|
|
683
|
+
}
|
|
684
|
+
return box;
|
|
685
|
+
}
|
|
686
|
+
function boxArea(box, pad) {
|
|
687
|
+
return Math.max(box.maxX - box.minX, pad) * Math.max(box.maxY - box.minY, pad);
|
|
688
|
+
}
|
|
689
|
+
function overlapArea(p, q) {
|
|
690
|
+
var w = Math.min(p.maxX, q.maxX) - Math.max(p.minX, q.minX);
|
|
691
|
+
var h = Math.min(p.maxY, q.maxY) - Math.max(p.minY, q.minY);
|
|
692
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Candidate hatch regions: components of every direction cluster, merged
|
|
696
|
+
* across directions when their boxes overlap (cross-hatch, bricks, ...).
|
|
697
|
+
*/
|
|
698
|
+
function findRegions(segs, opts) {
|
|
699
|
+
var e_21, _a, e_22, _b, e_23, _c;
|
|
700
|
+
if (segs.length < opts.minSegments)
|
|
701
|
+
return [];
|
|
702
|
+
var components = [];
|
|
703
|
+
try {
|
|
704
|
+
for (var _d = __values(clusterByAngle(segs)), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
705
|
+
var cluster = _e.value;
|
|
706
|
+
if (cluster.segs.length < 2)
|
|
707
|
+
continue;
|
|
708
|
+
try {
|
|
709
|
+
for (var _f = (e_22 = void 0, __values(clusterComponents(cluster, opts.tolerance))), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
710
|
+
var comp = _g.value;
|
|
711
|
+
if (comp.length < 2)
|
|
712
|
+
continue;
|
|
713
|
+
components.push({ segs: comp, box: segsBox(comp) });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
catch (e_22_1) { e_22 = { error: e_22_1 }; }
|
|
717
|
+
finally {
|
|
718
|
+
try {
|
|
719
|
+
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
|
|
720
|
+
}
|
|
721
|
+
finally { if (e_22) throw e_22.error; }
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (e_21_1) { e_21 = { error: e_21_1 }; }
|
|
726
|
+
finally {
|
|
727
|
+
try {
|
|
728
|
+
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
729
|
+
}
|
|
730
|
+
finally { if (e_21) throw e_21.error; }
|
|
731
|
+
}
|
|
732
|
+
components.sort(function (p, q) { return q.segs.length - p.segs.length; });
|
|
733
|
+
var regions = [];
|
|
734
|
+
var _loop_4 = function (comp) {
|
|
735
|
+
var _h;
|
|
736
|
+
var pad = opts.tolerance;
|
|
737
|
+
var target = regions.find(function (region) {
|
|
738
|
+
var shared = overlapArea(region.box, comp.box);
|
|
739
|
+
return (shared >=
|
|
740
|
+
0.5 * Math.min(boxArea(region.box, pad), boxArea(comp.box, pad)));
|
|
741
|
+
});
|
|
742
|
+
if (target) {
|
|
743
|
+
(_h = target.segs).push.apply(_h, __spreadArray([], __read(comp.segs), false));
|
|
744
|
+
target.box = segsBox(target.segs);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
regions.push({ segs: __spreadArray([], __read(comp.segs), false), box: comp.box });
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
try {
|
|
751
|
+
for (var components_1 = __values(components), components_1_1 = components_1.next(); !components_1_1.done; components_1_1 = components_1.next()) {
|
|
752
|
+
var comp = components_1_1.value;
|
|
753
|
+
_loop_4(comp);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch (e_23_1) { e_23 = { error: e_23_1 }; }
|
|
757
|
+
finally {
|
|
758
|
+
try {
|
|
759
|
+
if (components_1_1 && !components_1_1.done && (_c = components_1.return)) _c.call(components_1);
|
|
760
|
+
}
|
|
761
|
+
finally { if (e_23) throw e_23.error; }
|
|
762
|
+
}
|
|
763
|
+
return regions
|
|
764
|
+
.map(function (region) { return region.segs; })
|
|
765
|
+
.filter(function (region) { return region.length >= opts.minSegments; });
|
|
766
|
+
}
|
|
767
|
+
/** Smallest failed region worth splitting into patches. */
|
|
768
|
+
var MIN_PATCH_REGION = 200;
|
|
769
|
+
/** Most grid cells a region is split with. */
|
|
770
|
+
var MAX_PATCH_CELLS = 4000000;
|
|
771
|
+
/**
|
|
772
|
+
* Spatially separate patches of a region: strokes in the same or touching
|
|
773
|
+
* grid cells, one family spacing wide, belong together.
|
|
774
|
+
*/
|
|
775
|
+
function spatialPatches(region, opts) {
|
|
776
|
+
var e_24, _a, e_25, _b;
|
|
777
|
+
var families = measureFamilies(region, opts.tolerance);
|
|
778
|
+
var main = families[0];
|
|
779
|
+
if (!main)
|
|
780
|
+
return [];
|
|
781
|
+
// The spacing of the pattern's own families; sparse ones are line work.
|
|
782
|
+
var link = Math.max.apply(Math, __spreadArray([], __read(families
|
|
783
|
+
.filter(function (f) { return f.segs.length >= main.segs.length * 0.3; })
|
|
784
|
+
.map(function (f) { return f.period; })), false));
|
|
785
|
+
if (!(link > 0))
|
|
786
|
+
return [];
|
|
787
|
+
var box = segsBox(region);
|
|
788
|
+
var nx = Math.floor((box.maxX - box.minX) / link) + 1;
|
|
789
|
+
var ny = Math.floor((box.maxY - box.minY) / link) + 1;
|
|
790
|
+
if (nx * ny > MAX_PATCH_CELLS)
|
|
791
|
+
return [];
|
|
792
|
+
var cellOf = function (p) {
|
|
793
|
+
return Math.floor((p.x - box.minX) / link) * ny +
|
|
794
|
+
Math.floor((p.y - box.minY) / link);
|
|
795
|
+
};
|
|
796
|
+
// Every stroke joins the cells of its ends and midpoint; cells that hold
|
|
797
|
+
// strokes and touch join each other.
|
|
798
|
+
var first = new Map();
|
|
799
|
+
var uf = new UnionFind(region.length);
|
|
800
|
+
region.forEach(function (seg, i) {
|
|
801
|
+
var e_26, _a;
|
|
802
|
+
try {
|
|
803
|
+
for (var _b = __values([seg.a, seg.b, midpoint(seg)]), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
804
|
+
var p = _c.value;
|
|
805
|
+
var c = cellOf(p);
|
|
806
|
+
var j = first.get(c);
|
|
807
|
+
if (j === undefined)
|
|
808
|
+
first.set(c, i);
|
|
809
|
+
else
|
|
810
|
+
uf.union(i, j);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (e_26_1) { e_26 = { error: e_26_1 }; }
|
|
814
|
+
finally {
|
|
815
|
+
try {
|
|
816
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
817
|
+
}
|
|
818
|
+
finally { if (e_26) throw e_26.error; }
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
try {
|
|
822
|
+
for (var first_1 = __values(first), first_1_1 = first_1.next(); !first_1_1.done; first_1_1 = first_1.next()) {
|
|
823
|
+
var _c = __read(first_1_1.value, 2), c = _c[0], i = _c[1];
|
|
824
|
+
var cx = Math.floor(c / ny);
|
|
825
|
+
var cy = c % ny;
|
|
826
|
+
try {
|
|
827
|
+
for (var _d = (e_25 = void 0, __values([
|
|
828
|
+
[1, -1],
|
|
829
|
+
[1, 0],
|
|
830
|
+
[1, 1],
|
|
831
|
+
[0, 1]
|
|
832
|
+
])), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
833
|
+
var _f = __read(_e.value, 2), dx = _f[0], dy = _f[1];
|
|
834
|
+
var x = cx + dx;
|
|
835
|
+
var y = cy + dy;
|
|
836
|
+
if (x >= nx || y < 0 || y >= ny)
|
|
837
|
+
continue;
|
|
838
|
+
var j = first.get(x * ny + y);
|
|
839
|
+
if (j !== undefined)
|
|
840
|
+
uf.union(i, j);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
catch (e_25_1) { e_25 = { error: e_25_1 }; }
|
|
844
|
+
finally {
|
|
845
|
+
try {
|
|
846
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
847
|
+
}
|
|
848
|
+
finally { if (e_25) throw e_25.error; }
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
catch (e_24_1) { e_24 = { error: e_24_1 }; }
|
|
853
|
+
finally {
|
|
854
|
+
try {
|
|
855
|
+
if (first_1_1 && !first_1_1.done && (_a = first_1.return)) _a.call(first_1);
|
|
856
|
+
}
|
|
857
|
+
finally { if (e_24) throw e_24.error; }
|
|
858
|
+
}
|
|
859
|
+
var byRoot = new Map();
|
|
860
|
+
region.forEach(function (seg, i) {
|
|
861
|
+
var root = uf.find(i);
|
|
862
|
+
var list = byRoot.get(root);
|
|
863
|
+
if (list)
|
|
864
|
+
list.push(seg);
|
|
865
|
+
else
|
|
866
|
+
byRoot.set(root, [seg]);
|
|
867
|
+
});
|
|
868
|
+
return __spreadArray([], __read(byRoot.values()), false).filter(function (patch) { return patch.length >= opts.minSegments; });
|
|
869
|
+
}
|
|
870
|
+
/** Most runs peeled off one mixed region. */
|
|
871
|
+
var MAX_SPLITS = 4;
|
|
872
|
+
/**
|
|
873
|
+
* Segments of the largest direction cluster that lie on its most common
|
|
874
|
+
* arithmetic progression of offsets (one hatch out of several mixed ones).
|
|
875
|
+
*/
|
|
876
|
+
function dominantProgression(region, opts) {
|
|
877
|
+
var tol = opts.tolerance;
|
|
878
|
+
var cluster = clusterByAngle(region).sort(function (p, q) { return q.segs.length - p.segs.length; })[0];
|
|
879
|
+
if (!cluster || cluster.segs.length < opts.minSegments)
|
|
880
|
+
return undefined;
|
|
881
|
+
var lines = linesOf(cluster.segs, cluster.theta, tol);
|
|
882
|
+
var period = votedSpacing(lines, tol);
|
|
883
|
+
if (!period)
|
|
884
|
+
return undefined;
|
|
885
|
+
// Grow chains of lines one spacing apart (re-anchored at every line, so
|
|
886
|
+
// rounding does not drift) and keep the longest.
|
|
887
|
+
var reach = Math.max(tol * 2, period * 0.02);
|
|
888
|
+
var find = function (target) {
|
|
889
|
+
var lo = 0;
|
|
890
|
+
var hi = lines.length - 1;
|
|
891
|
+
while (lo <= hi) {
|
|
892
|
+
var mid = (lo + hi) >> 1;
|
|
893
|
+
if (lines[mid].c < target - reach)
|
|
894
|
+
lo = mid + 1;
|
|
895
|
+
else if (lines[mid].c > target + reach)
|
|
896
|
+
hi = mid - 1;
|
|
897
|
+
else
|
|
898
|
+
return mid;
|
|
899
|
+
}
|
|
900
|
+
return -1;
|
|
901
|
+
};
|
|
902
|
+
var walk = function (start, dir) {
|
|
903
|
+
var out = [];
|
|
904
|
+
var current = lines[start].c;
|
|
905
|
+
for (;;) {
|
|
906
|
+
var next = -1;
|
|
907
|
+
for (var m = 1; m <= 3 && next < 0; m++)
|
|
908
|
+
next = find(current + dir * m * period);
|
|
909
|
+
if (next < 0)
|
|
910
|
+
return out;
|
|
911
|
+
out.push(next);
|
|
912
|
+
current = lines[next].c;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
var chain = [];
|
|
916
|
+
var step = Math.max(1, Math.floor(lines.length / 12));
|
|
917
|
+
for (var seed = 0; seed < lines.length; seed += step) {
|
|
918
|
+
var candidate = __spreadArray(__spreadArray(__spreadArray([], __read(walk(seed, -1).reverse()), false), [seed], false), __read(walk(seed, 1)), false);
|
|
919
|
+
if (candidate.length > chain.length)
|
|
920
|
+
chain = candidate;
|
|
921
|
+
}
|
|
922
|
+
if (chain.length < opts.minLines)
|
|
923
|
+
return undefined;
|
|
924
|
+
// Keep the pieces of each chain line that overlap the neighbouring chain
|
|
925
|
+
// lines: another hatch may share an offset somewhere else.
|
|
926
|
+
var rows = chain.map(function (i) { return lines[i].segs; });
|
|
927
|
+
var run = [];
|
|
928
|
+
rows.forEach(function (row, r) {
|
|
929
|
+
var e_27, _a;
|
|
930
|
+
var _b, _c;
|
|
931
|
+
var neighbours = __spreadArray(__spreadArray([], __read(((_b = rows[r - 1]) !== null && _b !== void 0 ? _b : [])), false), __read(((_c = rows[r + 1]) !== null && _c !== void 0 ? _c : [])), false);
|
|
932
|
+
var _loop_5 = function (entry) {
|
|
933
|
+
var len = entry.s1 - entry.s0;
|
|
934
|
+
var linked = neighbours.some(function (other) {
|
|
935
|
+
return Math.min(other.s1, entry.s1) - Math.max(other.s0, entry.s0) >=
|
|
936
|
+
MIN_OVERLAP * Math.min(len, other.s1 - other.s0);
|
|
937
|
+
});
|
|
938
|
+
if (linked)
|
|
939
|
+
run.push(entry.seg);
|
|
940
|
+
};
|
|
941
|
+
try {
|
|
942
|
+
for (var row_3 = __values(row), row_3_1 = row_3.next(); !row_3_1.done; row_3_1 = row_3.next()) {
|
|
943
|
+
var entry = row_3_1.value;
|
|
944
|
+
_loop_5(entry);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
catch (e_27_1) { e_27 = { error: e_27_1 }; }
|
|
948
|
+
finally {
|
|
949
|
+
try {
|
|
950
|
+
if (row_3_1 && !row_3_1.done && (_a = row_3.return)) _a.call(row_3);
|
|
951
|
+
}
|
|
952
|
+
finally { if (e_27) throw e_27.error; }
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
return run.length >= opts.minSegments ? run : undefined;
|
|
956
|
+
}
|
|
957
|
+
/** Most common spacing among nearby parallel lines (mean of its votes). */
|
|
958
|
+
function votedSpacing(lines, tol) {
|
|
959
|
+
var e_28, _a;
|
|
960
|
+
var _b, _c, _d;
|
|
961
|
+
var bin = tol * 2;
|
|
962
|
+
var votes = new Map();
|
|
963
|
+
for (var i = 0; i < lines.length; i++) {
|
|
964
|
+
for (var j = i + 1; j < lines.length && j <= i + 6; j++) {
|
|
965
|
+
var d = lines[j].c - lines[i].c;
|
|
966
|
+
if (d <= bin)
|
|
967
|
+
continue;
|
|
968
|
+
var key = Math.round(d / bin);
|
|
969
|
+
var list = votes.get(key);
|
|
970
|
+
if (list)
|
|
971
|
+
list.push(d);
|
|
972
|
+
else
|
|
973
|
+
votes.set(key, [d]);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
var best;
|
|
977
|
+
try {
|
|
978
|
+
for (var _e = __values(votes.keys()), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
979
|
+
var key = _f.value;
|
|
980
|
+
// Neighbouring bins share one spacing split by rounding.
|
|
981
|
+
var merged = __spreadArray(__spreadArray(__spreadArray([], __read(((_b = votes.get(key - 1)) !== null && _b !== void 0 ? _b : [])), false), __read(((_c = votes.get(key)) !== null && _c !== void 0 ? _c : [])), false), __read(((_d = votes.get(key + 1)) !== null && _d !== void 0 ? _d : [])), false);
|
|
982
|
+
if (!best || merged.length > best.length)
|
|
983
|
+
best = merged;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
catch (e_28_1) { e_28 = { error: e_28_1 }; }
|
|
987
|
+
finally {
|
|
988
|
+
try {
|
|
989
|
+
if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
|
|
990
|
+
}
|
|
991
|
+
finally { if (e_28) throw e_28.error; }
|
|
992
|
+
}
|
|
993
|
+
if (!best || best.length === 0)
|
|
994
|
+
return undefined;
|
|
995
|
+
return best.reduce(function (sum, d) { return sum + d; }, 0) / best.length;
|
|
996
|
+
}
|
|
997
|
+
function measureFamilies(region, tol) {
|
|
998
|
+
var e_29, _a;
|
|
999
|
+
var box = segsBox(region);
|
|
1000
|
+
var cx = (box.minX + box.maxX) / 2;
|
|
1001
|
+
var cy = (box.minY + box.maxY) / 2;
|
|
1002
|
+
// Segments near the centre are least likely clipped by the boundary.
|
|
1003
|
+
var centerDistance = function (seg) {
|
|
1004
|
+
return Math.hypot((seg.a.x + seg.b.x) / 2 - cx, (seg.a.y + seg.b.y) / 2 - cy);
|
|
1005
|
+
};
|
|
1006
|
+
var families = [];
|
|
1007
|
+
try {
|
|
1008
|
+
for (var _b = __values(clusterByAngle(region)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
1009
|
+
var cluster = _c.value;
|
|
1010
|
+
var offsets = linesOf(cluster.segs, cluster.theta, tol).map(function (l) { return l.c; });
|
|
1011
|
+
if (offsets.length < 2)
|
|
1012
|
+
continue;
|
|
1013
|
+
var period = fundamentalPeriod(offsets, tol);
|
|
1014
|
+
if (!period)
|
|
1015
|
+
continue;
|
|
1016
|
+
families.push({
|
|
1017
|
+
theta: cluster.theta,
|
|
1018
|
+
segs: cluster.segs,
|
|
1019
|
+
offsets: offsets,
|
|
1020
|
+
period: period,
|
|
1021
|
+
residues: countResidues(offsets, period, tol),
|
|
1022
|
+
byCenter: cluster.segs
|
|
1023
|
+
.map(function (seg) { return ({ seg: seg, d: centerDistance(seg) }); })
|
|
1024
|
+
.sort(function (p, q) { return p.d - q.d; })
|
|
1025
|
+
.map(function (entry) { return entry.seg; })
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
catch (e_29_1) { e_29 = { error: e_29_1 }; }
|
|
1030
|
+
finally {
|
|
1031
|
+
try {
|
|
1032
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
1033
|
+
}
|
|
1034
|
+
finally { if (e_29) throw e_29.error; }
|
|
1035
|
+
}
|
|
1036
|
+
return families.sort(function (p, q) { return q.segs.length - p.segs.length; });
|
|
1037
|
+
}
|
|
1038
|
+
/** Smallest period that maps most offsets onto other offsets. */
|
|
1039
|
+
function fundamentalPeriod(offsets, tol) {
|
|
1040
|
+
var e_30, _a, e_31, _b;
|
|
1041
|
+
var n = offsets.length;
|
|
1042
|
+
var has = function (value) {
|
|
1043
|
+
var lo = 0;
|
|
1044
|
+
var hi = n - 1;
|
|
1045
|
+
while (lo <= hi) {
|
|
1046
|
+
var mid = (lo + hi) >> 1;
|
|
1047
|
+
if (offsets[mid] < value - tol)
|
|
1048
|
+
lo = mid + 1;
|
|
1049
|
+
else if (offsets[mid] > value + tol)
|
|
1050
|
+
hi = mid - 1;
|
|
1051
|
+
else
|
|
1052
|
+
return true;
|
|
1053
|
+
}
|
|
1054
|
+
return false;
|
|
1055
|
+
};
|
|
1056
|
+
var ref = Math.floor(n / 2);
|
|
1057
|
+
var candidates = [];
|
|
1058
|
+
for (var m = 1; m <= 8; m++) {
|
|
1059
|
+
if (ref + m < n)
|
|
1060
|
+
candidates.push(offsets[ref + m] - offsets[ref]);
|
|
1061
|
+
if (ref - m >= 0)
|
|
1062
|
+
candidates.push(offsets[ref] - offsets[ref - m]);
|
|
1063
|
+
}
|
|
1064
|
+
candidates.sort(function (p, q) { return p - q; });
|
|
1065
|
+
var last = offsets[n - 1];
|
|
1066
|
+
try {
|
|
1067
|
+
for (var candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next(); !candidates_1_1.done; candidates_1_1 = candidates_1.next()) {
|
|
1068
|
+
var period = candidates_1_1.value;
|
|
1069
|
+
if (!(period > tol * 2))
|
|
1070
|
+
continue;
|
|
1071
|
+
var tested = 0;
|
|
1072
|
+
var hit = 0;
|
|
1073
|
+
try {
|
|
1074
|
+
for (var offsets_1 = (e_31 = void 0, __values(offsets)), offsets_1_1 = offsets_1.next(); !offsets_1_1.done; offsets_1_1 = offsets_1.next()) {
|
|
1075
|
+
var c = offsets_1_1.value;
|
|
1076
|
+
if (c + period > last + tol)
|
|
1077
|
+
break;
|
|
1078
|
+
tested++;
|
|
1079
|
+
if (has(c + period))
|
|
1080
|
+
hit++;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
catch (e_31_1) { e_31 = { error: e_31_1 }; }
|
|
1084
|
+
finally {
|
|
1085
|
+
try {
|
|
1086
|
+
if (offsets_1_1 && !offsets_1_1.done && (_b = offsets_1.return)) _b.call(offsets_1);
|
|
1087
|
+
}
|
|
1088
|
+
finally { if (e_31) throw e_31.error; }
|
|
1089
|
+
}
|
|
1090
|
+
if (tested > 0 && hit / tested >= 0.8) {
|
|
1091
|
+
return refinePeriod(offsets, period, tol);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
catch (e_30_1) { e_30 = { error: e_30_1 }; }
|
|
1096
|
+
finally {
|
|
1097
|
+
try {
|
|
1098
|
+
if (candidates_1_1 && !candidates_1_1.done && (_a = candidates_1.return)) _a.call(candidates_1);
|
|
1099
|
+
}
|
|
1100
|
+
finally { if (e_30) throw e_30.error; }
|
|
1101
|
+
}
|
|
1102
|
+
return undefined;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Least-squares period over every line on the main residue: one gap is
|
|
1106
|
+
* off by the PDF rounding, which would add up across a large hatch.
|
|
1107
|
+
*/
|
|
1108
|
+
function refinePeriod(offsets, period, tol) {
|
|
1109
|
+
var e_32, _a;
|
|
1110
|
+
var ref = offsets[Math.floor(offsets.length / 2)];
|
|
1111
|
+
var n = 0;
|
|
1112
|
+
var sk = 0;
|
|
1113
|
+
var sc = 0;
|
|
1114
|
+
var skk = 0;
|
|
1115
|
+
var skc = 0;
|
|
1116
|
+
try {
|
|
1117
|
+
for (var offsets_2 = __values(offsets), offsets_2_1 = offsets_2.next(); !offsets_2_1.done; offsets_2_1 = offsets_2.next()) {
|
|
1118
|
+
var c = offsets_2_1.value;
|
|
1119
|
+
var k = Math.round((c - ref) / period);
|
|
1120
|
+
if (Math.abs(c - ref - k * period) > tol * 2)
|
|
1121
|
+
continue;
|
|
1122
|
+
n++;
|
|
1123
|
+
sk += k;
|
|
1124
|
+
sc += c;
|
|
1125
|
+
skk += k * k;
|
|
1126
|
+
skc += k * c;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
catch (e_32_1) { e_32 = { error: e_32_1 }; }
|
|
1130
|
+
finally {
|
|
1131
|
+
try {
|
|
1132
|
+
if (offsets_2_1 && !offsets_2_1.done && (_a = offsets_2.return)) _a.call(offsets_2);
|
|
1133
|
+
}
|
|
1134
|
+
finally { if (e_32) throw e_32.error; }
|
|
1135
|
+
}
|
|
1136
|
+
var den = n * skk - sk * sk;
|
|
1137
|
+
if (n < 3 || !(Math.abs(den) > 0))
|
|
1138
|
+
return period;
|
|
1139
|
+
var slope = (n * skc - sk * sc) / den;
|
|
1140
|
+
return slope > 0 && Math.abs(slope - period) <= period * 0.05 ? slope : period;
|
|
1141
|
+
}
|
|
1142
|
+
function countResidues(offsets, period, tol) {
|
|
1143
|
+
var e_33, _a;
|
|
1144
|
+
var residues = offsets
|
|
1145
|
+
.map(function (c) { return ((c % period) + period) % period; })
|
|
1146
|
+
.sort(function (p, q) { return p - q; });
|
|
1147
|
+
var count = 0;
|
|
1148
|
+
var last = -Infinity;
|
|
1149
|
+
try {
|
|
1150
|
+
for (var residues_1 = __values(residues), residues_1_1 = residues_1.next(); !residues_1_1.done; residues_1_1 = residues_1.next()) {
|
|
1151
|
+
var r = residues_1_1.value;
|
|
1152
|
+
if (r - last > tol * 2) {
|
|
1153
|
+
count++;
|
|
1154
|
+
last = r;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
catch (e_33_1) { e_33 = { error: e_33_1 }; }
|
|
1159
|
+
finally {
|
|
1160
|
+
try {
|
|
1161
|
+
if (residues_1_1 && !residues_1_1.done && (_a = residues_1.return)) _a.call(residues_1);
|
|
1162
|
+
}
|
|
1163
|
+
finally { if (e_33) throw e_33.error; }
|
|
1164
|
+
}
|
|
1165
|
+
// The first and last residue may be one value split by the wrap.
|
|
1166
|
+
if (count > 1 &&
|
|
1167
|
+
residues[0] + period - residues[residues.length - 1] <= tol * 2) {
|
|
1168
|
+
count--;
|
|
1169
|
+
}
|
|
1170
|
+
return Math.max(1, count);
|
|
1171
|
+
}
|
|
1172
|
+
var DEG = Math.PI / 180;
|
|
1173
|
+
function patternSignatures(patterns) {
|
|
1174
|
+
var out = [];
|
|
1175
|
+
patterns.forEach(function (pattern, order) {
|
|
1176
|
+
var e_34, _a, e_35, _b;
|
|
1177
|
+
// Dots (zero dashes) are allowed: PDF writers often drop them, and a
|
|
1178
|
+
// hatch re-draws them as points that verification does not count.
|
|
1179
|
+
if (pattern.lines.length === 0)
|
|
1180
|
+
return;
|
|
1181
|
+
var groups = [];
|
|
1182
|
+
var _loop_6 = function (line) {
|
|
1183
|
+
var angle = normalizeDirection(line.angle * DEG);
|
|
1184
|
+
var group = groups.find(function (g) { return directionDelta(g.angle, angle) < 1e-4; });
|
|
1185
|
+
if (group)
|
|
1186
|
+
group.lines.push(line);
|
|
1187
|
+
else
|
|
1188
|
+
groups.push({ angle: angle, lines: [line] });
|
|
1189
|
+
};
|
|
1190
|
+
try {
|
|
1191
|
+
for (var _c = __values(pattern.lines), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
1192
|
+
var line = _d.value;
|
|
1193
|
+
_loop_6(line);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
catch (e_34_1) { e_34 = { error: e_34_1 }; }
|
|
1197
|
+
finally {
|
|
1198
|
+
try {
|
|
1199
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
1200
|
+
}
|
|
1201
|
+
finally { if (e_34) throw e_34.error; }
|
|
1202
|
+
}
|
|
1203
|
+
var signature = [];
|
|
1204
|
+
var _loop_7 = function (group) {
|
|
1205
|
+
var period = Math.max.apply(Math, __spreadArray([], __read(group.lines.map(function (l) { return Math.abs(l.deltaY); })), false));
|
|
1206
|
+
if (!(period > 0))
|
|
1207
|
+
return { value: void 0 };
|
|
1208
|
+
// Perpendicular offset of each line's origin, in the group direction.
|
|
1209
|
+
var nx = -Math.sin(group.angle);
|
|
1210
|
+
var ny = Math.cos(group.angle);
|
|
1211
|
+
var offsets = group.lines.map(function (l) {
|
|
1212
|
+
var c = nx * l.originX + ny * l.originY;
|
|
1213
|
+
return ((c % period) + period) % period;
|
|
1214
|
+
});
|
|
1215
|
+
var lattice = latticePeriod(offsets, period);
|
|
1216
|
+
signature.push({
|
|
1217
|
+
angle: group.angle,
|
|
1218
|
+
period: lattice,
|
|
1219
|
+
residues: countResidues(offsets.map(function (c) { return c % lattice; }), lattice, lattice * 1e-3),
|
|
1220
|
+
dotsOnly: group.lines.every(function (l) { return isDotsOnly(l.dashes); })
|
|
1221
|
+
});
|
|
1222
|
+
};
|
|
1223
|
+
try {
|
|
1224
|
+
for (var groups_1 = __values(groups), groups_1_1 = groups_1.next(); !groups_1_1.done; groups_1_1 = groups_1.next()) {
|
|
1225
|
+
var group = groups_1_1.value;
|
|
1226
|
+
var state_2 = _loop_7(group);
|
|
1227
|
+
if (typeof state_2 === "object")
|
|
1228
|
+
return state_2.value;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
catch (e_35_1) { e_35 = { error: e_35_1 }; }
|
|
1232
|
+
finally {
|
|
1233
|
+
try {
|
|
1234
|
+
if (groups_1_1 && !groups_1_1.done && (_b = groups_1.return)) _b.call(groups_1);
|
|
1235
|
+
}
|
|
1236
|
+
finally { if (e_35) throw e_35.error; }
|
|
1237
|
+
}
|
|
1238
|
+
// A pattern of dots only leaves nothing to match.
|
|
1239
|
+
if (signature.every(function (g) { return g.dotsOnly; }))
|
|
1240
|
+
return;
|
|
1241
|
+
out.push({
|
|
1242
|
+
pattern: pattern,
|
|
1243
|
+
order: order,
|
|
1244
|
+
groups: signature,
|
|
1245
|
+
dashed: pattern.lines.some(function (line) { return line.dashes.length > 0; })
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
return out;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Smallest period of the line positions of one pattern group.
|
|
1252
|
+
*
|
|
1253
|
+
* Strokes are measured by where their lines lie, not by what the lines
|
|
1254
|
+
* draw, so a group whose lines sit evenly within the pattern repeat (ANSI35:
|
|
1255
|
+
* a solid and a dashed line half a repeat apart) is measured with a shorter
|
|
1256
|
+
* period than the repeat. The signature must use the same period.
|
|
1257
|
+
*
|
|
1258
|
+
* @param offsets - Line offsets in `[0, period)`.
|
|
1259
|
+
* @param period - Repeat of the group.
|
|
1260
|
+
*/
|
|
1261
|
+
function latticePeriod(offsets, period) {
|
|
1262
|
+
var eps = period * 1e-3;
|
|
1263
|
+
var has = function (value) {
|
|
1264
|
+
return offsets.some(function (c) {
|
|
1265
|
+
var d = Math.abs(c - value);
|
|
1266
|
+
return Math.min(d, period - d) <= eps;
|
|
1267
|
+
});
|
|
1268
|
+
};
|
|
1269
|
+
var _loop_8 = function (k) {
|
|
1270
|
+
var step = period / k;
|
|
1271
|
+
if (offsets.every(function (c) { return has((c + step) % period); }))
|
|
1272
|
+
return { value: step };
|
|
1273
|
+
};
|
|
1274
|
+
for (var k = offsets.length; k > 1; k--) {
|
|
1275
|
+
var state_3 = _loop_8(k);
|
|
1276
|
+
if (typeof state_3 === "object")
|
|
1277
|
+
return state_3.value;
|
|
1278
|
+
}
|
|
1279
|
+
return period;
|
|
1280
|
+
}
|
|
1281
|
+
/** A dash list that draws dots only (zeros and gaps, no dash). */
|
|
1282
|
+
function isDotsOnly(dashes) {
|
|
1283
|
+
return dashes.length > 0 && dashes.every(function (d) { return d <= 0; });
|
|
1284
|
+
}
|
|
1285
|
+
function normalizeDirection(angle) {
|
|
1286
|
+
var a = angle % Math.PI;
|
|
1287
|
+
if (a < 0)
|
|
1288
|
+
a += Math.PI;
|
|
1289
|
+
return a;
|
|
1290
|
+
}
|
|
1291
|
+
/** Smallest difference between two directions (mod π). */
|
|
1292
|
+
function directionDelta(p, q) {
|
|
1293
|
+
var d = Math.abs(normalizeDirection(p) - normalizeDirection(q));
|
|
1294
|
+
return Math.min(d, Math.PI - d);
|
|
1295
|
+
}
|
|
1296
|
+
function relClose(p, q, rel, abs) {
|
|
1297
|
+
return Math.abs(p - q) <= Math.max(rel * Math.max(p, q), abs);
|
|
1298
|
+
}
|
|
1299
|
+
function matchPattern(sig, families, tol, extent) {
|
|
1300
|
+
var e_36, _a;
|
|
1301
|
+
// Extra families are line work around the hatch (its boundary, other
|
|
1302
|
+
// drawing lines); every pattern group that draws dashes still needs a
|
|
1303
|
+
// family of its own. Dot-only groups leave no strokes to measure.
|
|
1304
|
+
var required = sig.groups.filter(function (g) { return !g.dotsOnly; });
|
|
1305
|
+
if (required.length > families.length)
|
|
1306
|
+
return [];
|
|
1307
|
+
var anchor = families[0];
|
|
1308
|
+
var out = [];
|
|
1309
|
+
var _loop_9 = function (group) {
|
|
1310
|
+
var e_37, _b;
|
|
1311
|
+
if (group.residues !== anchor.residues)
|
|
1312
|
+
return "continue";
|
|
1313
|
+
var scale = anchor.period / group.period;
|
|
1314
|
+
var base = anchor.theta - group.angle;
|
|
1315
|
+
// A dashed pattern turned half a turn is a different drawing.
|
|
1316
|
+
var rotations = sig.dashed ? [base, base + Math.PI] : [base];
|
|
1317
|
+
var _loop_10 = function (rotation) {
|
|
1318
|
+
var e_38, _c;
|
|
1319
|
+
var mapping = new Map();
|
|
1320
|
+
var ok = true;
|
|
1321
|
+
var _loop_11 = function (g) {
|
|
1322
|
+
var family = families.find(function (f) {
|
|
1323
|
+
return directionDelta(f.theta, g.angle + rotation) < ANGLE_GAP &&
|
|
1324
|
+
f.residues === g.residues &&
|
|
1325
|
+
relClose(f.period, g.period * scale, 0.03, tol * 2);
|
|
1326
|
+
});
|
|
1327
|
+
if (!family || __spreadArray([], __read(mapping.values()), false).includes(family)) {
|
|
1328
|
+
ok = false;
|
|
1329
|
+
return "break";
|
|
1330
|
+
}
|
|
1331
|
+
mapping.set(g, family);
|
|
1332
|
+
};
|
|
1333
|
+
try {
|
|
1334
|
+
for (var required_2 = (e_38 = void 0, __values(required)), required_2_1 = required_2.next(); !required_2_1.done; required_2_1 = required_2.next()) {
|
|
1335
|
+
var g = required_2_1.value;
|
|
1336
|
+
var state_4 = _loop_11(g);
|
|
1337
|
+
if (state_4 === "break")
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
catch (e_38_1) { e_38 = { error: e_38_1 }; }
|
|
1342
|
+
finally {
|
|
1343
|
+
try {
|
|
1344
|
+
if (required_2_1 && !required_2_1.done && (_c = required_2.return)) _c.call(required_2);
|
|
1345
|
+
}
|
|
1346
|
+
finally { if (e_38) throw e_38.error; }
|
|
1347
|
+
}
|
|
1348
|
+
if (!ok)
|
|
1349
|
+
return "continue";
|
|
1350
|
+
out.push({
|
|
1351
|
+
name: sig.pattern.name,
|
|
1352
|
+
order: sig.order,
|
|
1353
|
+
rotation: snapAngle(rotation, tol, extent),
|
|
1354
|
+
scale: snapScale(scale, tol, extent),
|
|
1355
|
+
lines: sig.pattern.lines,
|
|
1356
|
+
families: sig.pattern.lines.map(function (line) {
|
|
1357
|
+
var angle = normalizeDirection(line.angle * DEG);
|
|
1358
|
+
var g = sig.groups.find(function (gr) { return directionDelta(gr.angle, angle) < 1e-4; });
|
|
1359
|
+
// Dot-only lines have no family (see PatternGroup.dotsOnly).
|
|
1360
|
+
return mapping.get(g);
|
|
1361
|
+
})
|
|
1362
|
+
});
|
|
1363
|
+
};
|
|
1364
|
+
try {
|
|
1365
|
+
for (var rotations_1 = (e_37 = void 0, __values(rotations)), rotations_1_1 = rotations_1.next(); !rotations_1_1.done; rotations_1_1 = rotations_1.next()) {
|
|
1366
|
+
var rotation = rotations_1_1.value;
|
|
1367
|
+
_loop_10(rotation);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
catch (e_37_1) { e_37 = { error: e_37_1 }; }
|
|
1371
|
+
finally {
|
|
1372
|
+
try {
|
|
1373
|
+
if (rotations_1_1 && !rotations_1_1.done && (_b = rotations_1.return)) _b.call(rotations_1);
|
|
1374
|
+
}
|
|
1375
|
+
finally { if (e_37) throw e_37.error; }
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
try {
|
|
1379
|
+
for (var required_1 = __values(required), required_1_1 = required_1.next(); !required_1_1.done; required_1_1 = required_1.next()) {
|
|
1380
|
+
var group = required_1_1.value;
|
|
1381
|
+
_loop_9(group);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
catch (e_36_1) { e_36 = { error: e_36_1 }; }
|
|
1385
|
+
finally {
|
|
1386
|
+
try {
|
|
1387
|
+
if (required_1_1 && !required_1_1.done && (_a = required_1.return)) _a.call(required_1);
|
|
1388
|
+
}
|
|
1389
|
+
finally { if (e_36) throw e_36.error; }
|
|
1390
|
+
}
|
|
1391
|
+
return out;
|
|
1392
|
+
}
|
|
1393
|
+
/** Single or crossed parallel lines that no library pattern explains. */
|
|
1394
|
+
function userDefinedHypotheses(families, tol, extent) {
|
|
1395
|
+
var first = families[0];
|
|
1396
|
+
if (!first || first.residues !== 1)
|
|
1397
|
+
return [];
|
|
1398
|
+
var out = [];
|
|
1399
|
+
// A perpendicular family of the same spacing makes it a cross-hatch.
|
|
1400
|
+
var second = families.find(function (f) {
|
|
1401
|
+
return f !== first &&
|
|
1402
|
+
f.residues === 1 &&
|
|
1403
|
+
Math.abs(directionDelta(first.theta, f.theta) - Math.PI / 2) <
|
|
1404
|
+
ANGLE_GAP &&
|
|
1405
|
+
relClose(first.period, f.period, 0.03, tol * 2);
|
|
1406
|
+
});
|
|
1407
|
+
if (second)
|
|
1408
|
+
out.push(userDefinedPair(first, second, tol, extent));
|
|
1409
|
+
out.push(userDefinedSingle(first, tol, extent));
|
|
1410
|
+
return out;
|
|
1411
|
+
}
|
|
1412
|
+
function userDefinedLine(angle) {
|
|
1413
|
+
return {
|
|
1414
|
+
angle: angle,
|
|
1415
|
+
originX: 0,
|
|
1416
|
+
originY: 0,
|
|
1417
|
+
deltaX: 0,
|
|
1418
|
+
deltaY: 1,
|
|
1419
|
+
dashes: [],
|
|
1420
|
+
sourceLine: 0
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function userDefinedSingle(first, tol, extent) {
|
|
1424
|
+
return {
|
|
1425
|
+
order: Infinity,
|
|
1426
|
+
rotation: snapAngle(first.theta, tol, extent),
|
|
1427
|
+
scale: first.period,
|
|
1428
|
+
lines: [userDefinedLine(0)],
|
|
1429
|
+
families: [first],
|
|
1430
|
+
userDefined: { double: false, spacing: first.period }
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function userDefinedPair(first, second, tol, extent) {
|
|
1434
|
+
var lower = first.theta <= second.theta ? first : second;
|
|
1435
|
+
var upper = lower === first ? second : first;
|
|
1436
|
+
return {
|
|
1437
|
+
order: Infinity,
|
|
1438
|
+
rotation: snapAngle(lower.theta, tol, extent),
|
|
1439
|
+
scale: lower.period,
|
|
1440
|
+
lines: [userDefinedLine(0), userDefinedLine(90)],
|
|
1441
|
+
families: [lower, upper],
|
|
1442
|
+
userDefined: { double: true, spacing: lower.period }
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Rounds to a whole or half degree when the turn moves no point of the
|
|
1447
|
+
* region (size `extent`) by more than half the tolerance.
|
|
1448
|
+
*/
|
|
1449
|
+
function snapAngle(angle, tol, extent) {
|
|
1450
|
+
var a = angle % (Math.PI * 2);
|
|
1451
|
+
if (a < 0)
|
|
1452
|
+
a += Math.PI * 2;
|
|
1453
|
+
var half = Math.round((a / DEG) * 2) / 2;
|
|
1454
|
+
if (Math.abs(half * DEG - a) * extent <= tol * 0.5)
|
|
1455
|
+
a = (half % 360) * DEG;
|
|
1456
|
+
return a;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Rounds to 2 or 3 significant digits when the change shifts no line of
|
|
1460
|
+
* the region (size `extent`) by more than half the tolerance.
|
|
1461
|
+
*/
|
|
1462
|
+
function snapScale(scale, tol, extent) {
|
|
1463
|
+
var e_39, _a;
|
|
1464
|
+
try {
|
|
1465
|
+
for (var _b = __values([2, 3]), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
1466
|
+
var digits = _c.value;
|
|
1467
|
+
var rounded = Number(scale.toPrecision(digits));
|
|
1468
|
+
if ((Math.abs(rounded - scale) / scale) * extent <= tol * 0.5) {
|
|
1469
|
+
return rounded;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
catch (e_39_1) { e_39 = { error: e_39_1 }; }
|
|
1474
|
+
finally {
|
|
1475
|
+
try {
|
|
1476
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
1477
|
+
}
|
|
1478
|
+
finally { if (e_39) throw e_39.error; }
|
|
1479
|
+
}
|
|
1480
|
+
return scale;
|
|
1481
|
+
}
|
|
1482
|
+
/** World definition lines of a hypothesis, translated by `t`. */
|
|
1483
|
+
function definitionLines(hyp, t) {
|
|
1484
|
+
var cr = Math.cos(hyp.rotation);
|
|
1485
|
+
var sr = Math.sin(hyp.rotation);
|
|
1486
|
+
return hyp.lines.map(function (line) {
|
|
1487
|
+
var turn = Math.PI * 2;
|
|
1488
|
+
var angle = (((line.angle * DEG + hyp.rotation) % turn) + turn) % turn;
|
|
1489
|
+
var ox = line.originX * hyp.scale;
|
|
1490
|
+
var oy = line.originY * hyp.scale;
|
|
1491
|
+
var dx = line.deltaX * hyp.scale;
|
|
1492
|
+
var dy = line.deltaY * hyp.scale;
|
|
1493
|
+
var c = Math.cos(angle);
|
|
1494
|
+
var s = Math.sin(angle);
|
|
1495
|
+
return {
|
|
1496
|
+
angle: angle,
|
|
1497
|
+
base: { x: cr * ox - sr * oy + t.x, y: sr * ox + cr * oy + t.y },
|
|
1498
|
+
offset: { x: c * dx - s * dy, y: s * dx + c * dy },
|
|
1499
|
+
dashLengths: line.dashes.map(function (d) { return d * hyp.scale; })
|
|
1500
|
+
};
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
function lineFrame(line) {
|
|
1504
|
+
var e_40, _a;
|
|
1505
|
+
var ux = Math.cos(line.angle);
|
|
1506
|
+
var uy = Math.sin(line.angle);
|
|
1507
|
+
// Offset in line space (matches tessellateHatchPattern).
|
|
1508
|
+
var lx = ux * line.offset.x + uy * line.offset.y;
|
|
1509
|
+
var ly = -uy * line.offset.x + ux * line.offset.y;
|
|
1510
|
+
var spacing = Math.abs(ly) > 1e-9 ? ly : Math.hypot(lx, ly);
|
|
1511
|
+
var dashes = [];
|
|
1512
|
+
var cursor = 0;
|
|
1513
|
+
try {
|
|
1514
|
+
for (var _b = __values(line.dashLengths), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
1515
|
+
var value = _c.value;
|
|
1516
|
+
if (value > 0)
|
|
1517
|
+
dashes.push({ start: cursor, length: value });
|
|
1518
|
+
cursor += Math.abs(value);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
catch (e_40_1) { e_40 = { error: e_40_1 }; }
|
|
1522
|
+
finally {
|
|
1523
|
+
try {
|
|
1524
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
1525
|
+
}
|
|
1526
|
+
finally { if (e_40) throw e_40.error; }
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
ux: ux,
|
|
1530
|
+
uy: uy,
|
|
1531
|
+
spacing: spacing,
|
|
1532
|
+
shiftPerLine: Math.abs(ly) > 1e-9 ? lx : 0,
|
|
1533
|
+
period: cursor,
|
|
1534
|
+
dashes: dashes,
|
|
1535
|
+
dotsOnly: isDotsOnly(line.dashLengths)
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
/** True when `p` lies on a drawn part of `line` (within `tol`). */
|
|
1539
|
+
function onLine(p, line, frame, tol) {
|
|
1540
|
+
var e_41, _a, e_42, _b;
|
|
1541
|
+
var dx = p.x - line.base.x;
|
|
1542
|
+
var dy = p.y - line.base.y;
|
|
1543
|
+
var x = frame.ux * dx + frame.uy * dy;
|
|
1544
|
+
var y = -frame.uy * dx + frame.ux * dy;
|
|
1545
|
+
var k = Math.round(y / frame.spacing);
|
|
1546
|
+
if (Math.abs(y - k * frame.spacing) > tol)
|
|
1547
|
+
return false;
|
|
1548
|
+
// Dots are not strokes; a dot-only line never explains one.
|
|
1549
|
+
if (frame.dotsOnly)
|
|
1550
|
+
return false;
|
|
1551
|
+
if (frame.dashes.length === 0 || !(frame.period > 0))
|
|
1552
|
+
return true;
|
|
1553
|
+
var pos = x - k * frame.shiftPerLine;
|
|
1554
|
+
var phase = ((pos % frame.period) + frame.period) % frame.period;
|
|
1555
|
+
try {
|
|
1556
|
+
for (var _c = __values(frame.dashes), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
1557
|
+
var dash = _d.value;
|
|
1558
|
+
try {
|
|
1559
|
+
for (var _e = (e_42 = void 0, __values([phase, phase - frame.period, phase + frame.period])), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
1560
|
+
var shifted = _f.value;
|
|
1561
|
+
if (shifted >= dash.start - tol &&
|
|
1562
|
+
shifted <= dash.start + dash.length + tol) {
|
|
1563
|
+
return true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
catch (e_42_1) { e_42 = { error: e_42_1 }; }
|
|
1568
|
+
finally {
|
|
1569
|
+
try {
|
|
1570
|
+
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
|
1571
|
+
}
|
|
1572
|
+
finally { if (e_42) throw e_42.error; }
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
catch (e_41_1) { e_41 = { error: e_41_1 }; }
|
|
1577
|
+
finally {
|
|
1578
|
+
try {
|
|
1579
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
1580
|
+
}
|
|
1581
|
+
finally { if (e_41) throw e_41.error; }
|
|
1582
|
+
}
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
function segOnPattern(seg, lines, frames, tol) {
|
|
1586
|
+
var mid = { x: (seg.a.x + seg.b.x) / 2, y: (seg.a.y + seg.b.y) / 2 };
|
|
1587
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1588
|
+
if (directionDelta(lines[i].angle, seg.theta) > ANGLE_GAP)
|
|
1589
|
+
continue;
|
|
1590
|
+
if (onLine(mid, lines[i], frames[i], tol) &&
|
|
1591
|
+
onLine(seg.a, lines[i], frames[i], tol) &&
|
|
1592
|
+
onLine(seg.b, lines[i], frames[i], tol)) {
|
|
1593
|
+
return true;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return false;
|
|
1597
|
+
}
|
|
1598
|
+
function sampleSegs(segs, count) {
|
|
1599
|
+
if (segs.length <= count)
|
|
1600
|
+
return __spreadArray([], __read(segs), false);
|
|
1601
|
+
var step = segs.length / count;
|
|
1602
|
+
var out = [];
|
|
1603
|
+
for (var i = 0; i < count; i++)
|
|
1604
|
+
out.push(segs[Math.floor(i * step)]);
|
|
1605
|
+
return out;
|
|
1606
|
+
}
|
|
1607
|
+
/** Best translation for a hypothesis, with its sample score. */
|
|
1608
|
+
function estimateTranslation(hyp, region, tol) {
|
|
1609
|
+
var e_43, _a, e_44, _b, e_45, _c;
|
|
1610
|
+
var lines0 = definitionLines(hyp, { x: 0, y: 0 });
|
|
1611
|
+
var halves = [];
|
|
1612
|
+
var fulls = [];
|
|
1613
|
+
lines0.forEach(function (line, i) {
|
|
1614
|
+
var e_46, _a, e_47, _b;
|
|
1615
|
+
var family = hyp.families[i];
|
|
1616
|
+
// Dot-only lines left no strokes to place.
|
|
1617
|
+
if (!family)
|
|
1618
|
+
return;
|
|
1619
|
+
var frame = lineFrame(line);
|
|
1620
|
+
var nx = -frame.uy;
|
|
1621
|
+
var ny = frame.ux;
|
|
1622
|
+
var ordered = family.byCenter;
|
|
1623
|
+
var startOf = function (seg) {
|
|
1624
|
+
return frame.ux * seg.a.x + frame.uy * seg.a.y <=
|
|
1625
|
+
frame.ux * seg.b.x + frame.uy * seg.b.y
|
|
1626
|
+
? seg.a
|
|
1627
|
+
: seg.b;
|
|
1628
|
+
};
|
|
1629
|
+
// Put the segment on line k = 0: its perpendicular offset fixes n·t.
|
|
1630
|
+
var perp = function (seg) {
|
|
1631
|
+
return nx * ((seg.a.x + seg.b.x) / 2 - line.base.x) +
|
|
1632
|
+
ny * ((seg.a.y + seg.b.y) / 2 - line.base.y);
|
|
1633
|
+
};
|
|
1634
|
+
if (frame.dashes.length === 0) {
|
|
1635
|
+
try {
|
|
1636
|
+
for (var _c = __values(ordered.slice(0, 3)), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
1637
|
+
var seg = _d.value;
|
|
1638
|
+
halves.push({ nx: nx, ny: ny, value: perp(seg) });
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
catch (e_46_1) { e_46 = { error: e_46_1 }; }
|
|
1642
|
+
finally {
|
|
1643
|
+
try {
|
|
1644
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
1645
|
+
}
|
|
1646
|
+
finally { if (e_46) throw e_46.error; }
|
|
1647
|
+
}
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
var _loop_12 = function (dash) {
|
|
1651
|
+
var e_48, _g;
|
|
1652
|
+
var matches = ordered
|
|
1653
|
+
.filter(function (seg) { return Math.abs(seg.len - dash.length) <= tol * 2; })
|
|
1654
|
+
.slice(0, 2);
|
|
1655
|
+
try {
|
|
1656
|
+
for (var matches_1 = (e_48 = void 0, __values(matches)), matches_1_1 = matches_1.next(); !matches_1_1.done; matches_1_1 = matches_1.next()) {
|
|
1657
|
+
var seg = matches_1_1.value;
|
|
1658
|
+
var start = startOf(seg);
|
|
1659
|
+
var along = frame.ux * (start.x - line.base.x) +
|
|
1660
|
+
frame.uy * (start.y - line.base.y) -
|
|
1661
|
+
dash.start;
|
|
1662
|
+
var across = perp(seg);
|
|
1663
|
+
fulls.push({
|
|
1664
|
+
x: frame.ux * along + nx * across,
|
|
1665
|
+
y: frame.uy * along + ny * across
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
catch (e_48_1) { e_48 = { error: e_48_1 }; }
|
|
1670
|
+
finally {
|
|
1671
|
+
try {
|
|
1672
|
+
if (matches_1_1 && !matches_1_1.done && (_g = matches_1.return)) _g.call(matches_1);
|
|
1673
|
+
}
|
|
1674
|
+
finally { if (e_48) throw e_48.error; }
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
try {
|
|
1678
|
+
// A whole dash also fixes where along the line the pattern starts.
|
|
1679
|
+
for (var _e = __values(frame.dashes), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
1680
|
+
var dash = _f.value;
|
|
1681
|
+
_loop_12(dash);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
catch (e_47_1) { e_47 = { error: e_47_1 }; }
|
|
1685
|
+
finally {
|
|
1686
|
+
try {
|
|
1687
|
+
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
|
1688
|
+
}
|
|
1689
|
+
finally { if (e_47) throw e_47.error; }
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
var candidates = __spreadArray([], __read(fulls), false);
|
|
1693
|
+
for (var i = 0; i < halves.length && candidates.length < MAX_TRANSLATIONS; i++) {
|
|
1694
|
+
for (var j = i + 1; j < halves.length; j++) {
|
|
1695
|
+
var p = halves[i];
|
|
1696
|
+
var q = halves[j];
|
|
1697
|
+
var det = p.nx * q.ny - p.ny * q.nx;
|
|
1698
|
+
if (Math.abs(det) < 1e-6)
|
|
1699
|
+
continue;
|
|
1700
|
+
candidates.push({
|
|
1701
|
+
x: (p.value * q.ny - q.value * p.ny) / det,
|
|
1702
|
+
y: (p.nx * q.value - q.nx * p.value) / det
|
|
1703
|
+
});
|
|
1704
|
+
if (candidates.length >= MAX_TRANSLATIONS)
|
|
1705
|
+
break;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
// Parallel continuous lines only: the along-line shift does not matter.
|
|
1709
|
+
if (candidates.length === 0) {
|
|
1710
|
+
try {
|
|
1711
|
+
for (var halves_1 = __values(halves), halves_1_1 = halves_1.next(); !halves_1_1.done; halves_1_1 = halves_1.next()) {
|
|
1712
|
+
var half = halves_1_1.value;
|
|
1713
|
+
candidates.push({ x: half.nx * half.value, y: half.ny * half.value });
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
catch (e_43_1) { e_43 = { error: e_43_1 }; }
|
|
1717
|
+
finally {
|
|
1718
|
+
try {
|
|
1719
|
+
if (halves_1_1 && !halves_1_1.done && (_a = halves_1.return)) _a.call(halves_1);
|
|
1720
|
+
}
|
|
1721
|
+
finally { if (e_43) throw e_43.error; }
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
var sample = sampleSegs(region, SCORE_SAMPLE);
|
|
1725
|
+
var best;
|
|
1726
|
+
try {
|
|
1727
|
+
for (var _d = __values(candidates.slice(0, MAX_TRANSLATIONS)), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
1728
|
+
var t = _e.value;
|
|
1729
|
+
var lines = definitionLines(hyp, t);
|
|
1730
|
+
var frames_1 = lines.map(lineFrame);
|
|
1731
|
+
var hit = 0;
|
|
1732
|
+
try {
|
|
1733
|
+
for (var sample_1 = (e_45 = void 0, __values(sample)), sample_1_1 = sample_1.next(); !sample_1_1.done; sample_1_1 = sample_1.next()) {
|
|
1734
|
+
var seg = sample_1_1.value;
|
|
1735
|
+
if (segOnPattern(seg, lines, frames_1, tol))
|
|
1736
|
+
hit++;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
catch (e_45_1) { e_45 = { error: e_45_1 }; }
|
|
1740
|
+
finally {
|
|
1741
|
+
try {
|
|
1742
|
+
if (sample_1_1 && !sample_1_1.done && (_c = sample_1.return)) _c.call(sample_1);
|
|
1743
|
+
}
|
|
1744
|
+
finally { if (e_45) throw e_45.error; }
|
|
1745
|
+
}
|
|
1746
|
+
var score = hit / sample.length;
|
|
1747
|
+
if (!best || score > best.score)
|
|
1748
|
+
best = { t: t, score: score };
|
|
1749
|
+
if (score === 1)
|
|
1750
|
+
break;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
catch (e_44_1) { e_44 = { error: e_44_1 }; }
|
|
1754
|
+
finally {
|
|
1755
|
+
try {
|
|
1756
|
+
if (_e && !_e.done && (_b = _d.return)) _b.call(_d);
|
|
1757
|
+
}
|
|
1758
|
+
finally { if (e_44) throw e_44.error; }
|
|
1759
|
+
}
|
|
1760
|
+
return best;
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* Places every pattern line on its own family's strokes.
|
|
1764
|
+
*
|
|
1765
|
+
* One translation places all lines only when they share a lattice (ANSI
|
|
1766
|
+
* and most patterns). Lines such as AR-CONC's each have their own spacing
|
|
1767
|
+
* and stagger, so the strokes near the region fix each line separately.
|
|
1768
|
+
* The hatch keeps explicit lines, so it draws the same either way. Dot-only
|
|
1769
|
+
* lines left no strokes and follow the first placed line.
|
|
1770
|
+
*/
|
|
1771
|
+
function placeLinesSeparately(hyp, region, tol) {
|
|
1772
|
+
if (hyp.custom || hyp.userDefined || hyp.lines.length < 2)
|
|
1773
|
+
return undefined;
|
|
1774
|
+
var lines0 = definitionLines(hyp, { x: 0, y: 0 });
|
|
1775
|
+
var shifts = lines0.map(function (_, i) {
|
|
1776
|
+
var _a;
|
|
1777
|
+
var family = hyp.families[i];
|
|
1778
|
+
if (!family)
|
|
1779
|
+
return undefined;
|
|
1780
|
+
var single = __assign(__assign({}, hyp), { lines: [hyp.lines[i]], families: [family] });
|
|
1781
|
+
return (_a = estimateTranslation(single, family.segs, tol)) === null || _a === void 0 ? void 0 : _a.t;
|
|
1782
|
+
});
|
|
1783
|
+
var fallback = shifts.find(function (t) { return t !== undefined; });
|
|
1784
|
+
if (!fallback)
|
|
1785
|
+
return undefined;
|
|
1786
|
+
var lines = lines0.map(function (line, i) {
|
|
1787
|
+
var _a;
|
|
1788
|
+
var t = (_a = shifts[i]) !== null && _a !== void 0 ? _a : fallback;
|
|
1789
|
+
return __assign(__assign({}, line), { base: { x: line.base.x + t.x, y: line.base.y + t.y } });
|
|
1790
|
+
});
|
|
1791
|
+
var frames = lines.map(lineFrame);
|
|
1792
|
+
var sample = sampleSegs(region, SCORE_SAMPLE);
|
|
1793
|
+
if (sample.length === 0)
|
|
1794
|
+
return undefined;
|
|
1795
|
+
var hit = sample.filter(function (seg) { return segOnPattern(seg, lines, frames, tol); });
|
|
1796
|
+
return { lines: lines, score: hit.length / sample.length };
|
|
1797
|
+
}
|
|
1798
|
+
/** Closed outlines that can bound a hatch. */
|
|
1799
|
+
function collectClosedShapes(items) {
|
|
1800
|
+
var out = [];
|
|
1801
|
+
items.forEach(function (item, index) {
|
|
1802
|
+
var outline = shapeOutline(item.shape);
|
|
1803
|
+
if (!outline || outline.length < 3)
|
|
1804
|
+
return;
|
|
1805
|
+
out.push({ index: index, shape: item.shape, outline: outline, box: pointsBox(outline) });
|
|
1806
|
+
});
|
|
1807
|
+
return out;
|
|
1808
|
+
}
|
|
1809
|
+
var CURVE_STEPS = 72;
|
|
1810
|
+
/** Polygon of a closed shape, or `undefined` for open shapes. */
|
|
1811
|
+
function shapeOutline(shape) {
|
|
1812
|
+
switch (shape.kind) {
|
|
1813
|
+
case 'circle': {
|
|
1814
|
+
var out = [];
|
|
1815
|
+
for (var i = 0; i < CURVE_STEPS; i++) {
|
|
1816
|
+
var a = (i / CURVE_STEPS) * Math.PI * 2;
|
|
1817
|
+
out.push({
|
|
1818
|
+
x: shape.center.x + shape.radius * Math.cos(a),
|
|
1819
|
+
y: shape.center.y + shape.radius * Math.sin(a)
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
return out;
|
|
1823
|
+
}
|
|
1824
|
+
case 'ellipse': {
|
|
1825
|
+
if (Math.abs(shape.endAngle - shape.startAngle - Math.PI * 2) > 1e-9) {
|
|
1826
|
+
return undefined;
|
|
1827
|
+
}
|
|
1828
|
+
var mx = shape.majorAxis.x;
|
|
1829
|
+
var my = shape.majorAxis.y;
|
|
1830
|
+
var out = [];
|
|
1831
|
+
for (var i = 0; i < CURVE_STEPS; i++) {
|
|
1832
|
+
var a = (i / CURVE_STEPS) * Math.PI * 2;
|
|
1833
|
+
var cos = Math.cos(a);
|
|
1834
|
+
var sin = Math.sin(a) * shape.radiusRatio;
|
|
1835
|
+
out.push({
|
|
1836
|
+
x: shape.center.x + mx * cos - my * sin,
|
|
1837
|
+
y: shape.center.y + my * cos + mx * sin
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
return out;
|
|
1841
|
+
}
|
|
1842
|
+
case 'polyline': {
|
|
1843
|
+
if (!shape.closed || shape.vertices.length < 2)
|
|
1844
|
+
return undefined;
|
|
1845
|
+
var v = shape.vertices;
|
|
1846
|
+
var out = [];
|
|
1847
|
+
for (var i = 0; i < v.length; i++) {
|
|
1848
|
+
var a = v[i];
|
|
1849
|
+
var b = v[(i + 1) % v.length];
|
|
1850
|
+
out.push({ x: a.x, y: a.y });
|
|
1851
|
+
if (Math.abs(a.bulge) > 1e-12) {
|
|
1852
|
+
// Sample the bulge arc between a and b.
|
|
1853
|
+
var sweep = 4 * Math.atan(a.bulge);
|
|
1854
|
+
var chord = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1855
|
+
var radius = chord / (2 * Math.sin(Math.abs(sweep) / 2));
|
|
1856
|
+
var mx = (a.x + b.x) / 2;
|
|
1857
|
+
var my = (a.y + b.y) / 2;
|
|
1858
|
+
var h = radius * Math.cos(Math.abs(sweep) / 2) * Math.sign(a.bulge);
|
|
1859
|
+
var cx = mx - (h * (b.y - a.y)) / chord;
|
|
1860
|
+
var cy = my + (h * (b.x - a.x)) / chord;
|
|
1861
|
+
var start = Math.atan2(a.y - cy, a.x - cx);
|
|
1862
|
+
var steps = Math.max(2, Math.ceil((Math.abs(sweep) / Math.PI) * 18));
|
|
1863
|
+
for (var k = 1; k < steps; k++) {
|
|
1864
|
+
var t = start + (sweep * k) / steps;
|
|
1865
|
+
out.push({
|
|
1866
|
+
x: cx + radius * Math.cos(t),
|
|
1867
|
+
y: cy + radius * Math.sin(t)
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return out.length >= 3 ? out : undefined;
|
|
1873
|
+
}
|
|
1874
|
+
default:
|
|
1875
|
+
return undefined;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
function pointsBox(points) {
|
|
1879
|
+
var e_49, _a;
|
|
1880
|
+
var box = {
|
|
1881
|
+
minX: Infinity,
|
|
1882
|
+
minY: Infinity,
|
|
1883
|
+
maxX: -Infinity,
|
|
1884
|
+
maxY: -Infinity
|
|
1885
|
+
};
|
|
1886
|
+
try {
|
|
1887
|
+
for (var points_1 = __values(points), points_1_1 = points_1.next(); !points_1_1.done; points_1_1 = points_1.next()) {
|
|
1888
|
+
var p = points_1_1.value;
|
|
1889
|
+
if (p.x < box.minX)
|
|
1890
|
+
box.minX = p.x;
|
|
1891
|
+
if (p.y < box.minY)
|
|
1892
|
+
box.minY = p.y;
|
|
1893
|
+
if (p.x > box.maxX)
|
|
1894
|
+
box.maxX = p.x;
|
|
1895
|
+
if (p.y > box.maxY)
|
|
1896
|
+
box.maxY = p.y;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
catch (e_49_1) { e_49 = { error: e_49_1 }; }
|
|
1900
|
+
finally {
|
|
1901
|
+
try {
|
|
1902
|
+
if (points_1_1 && !points_1_1.done && (_a = points_1.return)) _a.call(points_1);
|
|
1903
|
+
}
|
|
1904
|
+
finally { if (e_49) throw e_49.error; }
|
|
1905
|
+
}
|
|
1906
|
+
return box;
|
|
1907
|
+
}
|
|
1908
|
+
function distanceToSegment(p, a, b) {
|
|
1909
|
+
var dx = b.x - a.x;
|
|
1910
|
+
var dy = b.y - a.y;
|
|
1911
|
+
var len2 = dx * dx + dy * dy;
|
|
1912
|
+
var t = len2 > 0 ? ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2 : 0;
|
|
1913
|
+
t = Math.max(0, Math.min(1, t));
|
|
1914
|
+
return Math.hypot(p.x - (a.x + dx * t), p.y - (a.y + dy * t));
|
|
1915
|
+
}
|
|
1916
|
+
function onOutline(p, outline, tol) {
|
|
1917
|
+
for (var i = 0; i < outline.length; i++) {
|
|
1918
|
+
if (distanceToSegment(p, outline[i], outline[(i + 1) % outline.length]) <= tol) {
|
|
1919
|
+
return true;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
/** Closed shapes on which the region's segments end. */
|
|
1925
|
+
function boundaryFromShapes(region, shapes, tol) {
|
|
1926
|
+
var e_50, _a, e_51, _b, e_52, _c;
|
|
1927
|
+
var _d;
|
|
1928
|
+
var box = segsBox(region);
|
|
1929
|
+
var near = shapes.filter(function (s) {
|
|
1930
|
+
return s.box.maxX >= box.minX - tol &&
|
|
1931
|
+
s.box.minX <= box.maxX + tol &&
|
|
1932
|
+
s.box.maxY >= box.minY - tol &&
|
|
1933
|
+
s.box.minY <= box.maxY + tol;
|
|
1934
|
+
});
|
|
1935
|
+
if (near.length === 0)
|
|
1936
|
+
return undefined;
|
|
1937
|
+
var hits = new Map();
|
|
1938
|
+
try {
|
|
1939
|
+
for (var _e = __values(sampleSegs(region, 400)), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
1940
|
+
var seg = _f.value;
|
|
1941
|
+
try {
|
|
1942
|
+
for (var _g = (e_51 = void 0, __values([seg.a, seg.b])), _h = _g.next(); !_h.done; _h = _g.next()) {
|
|
1943
|
+
var p = _h.value;
|
|
1944
|
+
try {
|
|
1945
|
+
for (var near_2 = (e_52 = void 0, __values(near)), near_2_1 = near_2.next(); !near_2_1.done; near_2_1 = near_2.next()) {
|
|
1946
|
+
var s = near_2_1.value;
|
|
1947
|
+
if (p.x < s.box.minX - tol ||
|
|
1948
|
+
p.x > s.box.maxX + tol ||
|
|
1949
|
+
p.y < s.box.minY - tol ||
|
|
1950
|
+
p.y > s.box.maxY + tol) {
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (onOutline(p, s.outline, tol)) {
|
|
1954
|
+
hits.set(s, ((_d = hits.get(s)) !== null && _d !== void 0 ? _d : 0) + 1);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
catch (e_52_1) { e_52 = { error: e_52_1 }; }
|
|
1959
|
+
finally {
|
|
1960
|
+
try {
|
|
1961
|
+
if (near_2_1 && !near_2_1.done && (_c = near_2.return)) _c.call(near_2);
|
|
1962
|
+
}
|
|
1963
|
+
finally { if (e_52) throw e_52.error; }
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
catch (e_51_1) { e_51 = { error: e_51_1 }; }
|
|
1968
|
+
finally {
|
|
1969
|
+
try {
|
|
1970
|
+
if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
|
|
1971
|
+
}
|
|
1972
|
+
finally { if (e_51) throw e_51.error; }
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
catch (e_50_1) { e_50 = { error: e_50_1 }; }
|
|
1977
|
+
finally {
|
|
1978
|
+
try {
|
|
1979
|
+
if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
|
|
1980
|
+
}
|
|
1981
|
+
finally { if (e_50) throw e_50.error; }
|
|
1982
|
+
}
|
|
1983
|
+
var chosen = __spreadArray([], __read(hits.entries()), false).filter(function (_a) {
|
|
1984
|
+
var _b = __read(_a, 2), count = _b[1];
|
|
1985
|
+
return count >= 3;
|
|
1986
|
+
})
|
|
1987
|
+
.map(function (_a) {
|
|
1988
|
+
var _b = __read(_a, 1), shape = _b[0];
|
|
1989
|
+
return shape;
|
|
1990
|
+
});
|
|
1991
|
+
if (chosen.length === 0)
|
|
1992
|
+
return undefined;
|
|
1993
|
+
var outer = pointsBox(chosen.flatMap(function (s) { return s.outline; }));
|
|
1994
|
+
if (outer.minX > box.minX + tol ||
|
|
1995
|
+
outer.minY > box.minY + tol ||
|
|
1996
|
+
outer.maxX < box.maxX - tol ||
|
|
1997
|
+
outer.maxY < box.maxY - tol) {
|
|
1998
|
+
return undefined;
|
|
1999
|
+
}
|
|
2000
|
+
return {
|
|
2001
|
+
shapes: chosen.map(function (s) { return s.shape; }),
|
|
2002
|
+
polygons: chosen.map(function (s) { return s.outline; })
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
function polygonsToBoundary(polygons) {
|
|
2006
|
+
return {
|
|
2007
|
+
shapes: polygons.map(function (polygon) { return ({
|
|
2008
|
+
kind: 'polyline',
|
|
2009
|
+
vertices: polygon.map(function (p) { return ({ x: p.x, y: p.y, bulge: 0 }); }),
|
|
2010
|
+
closed: true
|
|
2011
|
+
}); }),
|
|
2012
|
+
polygons: polygons
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
/** Clip path every segment of the region was painted under. */
|
|
2016
|
+
function boundaryFromClip(region, items, clipPaths) {
|
|
2017
|
+
var _a, _b;
|
|
2018
|
+
if (!clipPaths)
|
|
2019
|
+
return undefined;
|
|
2020
|
+
var id = (_a = items[region[0].index]) === null || _a === void 0 ? void 0 : _a.clipPath;
|
|
2021
|
+
if (id === undefined)
|
|
2022
|
+
return undefined;
|
|
2023
|
+
if (region.some(function (seg) { var _a; return ((_a = items[seg.index]) === null || _a === void 0 ? void 0 : _a.clipPath) !== id; }))
|
|
2024
|
+
return undefined;
|
|
2025
|
+
var loops = (_b = clipPaths[id]) === null || _b === void 0 ? void 0 : _b.filter(function (loop) { return loop.length >= 3; });
|
|
2026
|
+
if (!loops || loops.length === 0)
|
|
2027
|
+
return undefined;
|
|
2028
|
+
return polygonsToBoundary(loops.map(function (loop) { return __spreadArray([], __read(loop), false); }));
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Boundary traced through the segment ends of one continuous family. Only
|
|
2032
|
+
* for regions every line crosses once (no holes, convex in that direction).
|
|
2033
|
+
*/
|
|
2034
|
+
function boundaryFromEnds(hyp, lines, tol) {
|
|
2035
|
+
var e_53, _a, e_54, _b;
|
|
2036
|
+
var index = lines.findIndex(function (line) { return line.dashLengths.length === 0; });
|
|
2037
|
+
if (index < 0)
|
|
2038
|
+
return undefined;
|
|
2039
|
+
var family = hyp.families[index];
|
|
2040
|
+
if (!family)
|
|
2041
|
+
return undefined;
|
|
2042
|
+
var theta = normalizeDirection(lines[index].angle);
|
|
2043
|
+
var rows = linesOf(family.segs, theta, tol);
|
|
2044
|
+
if (rows.length < 2)
|
|
2045
|
+
return undefined;
|
|
2046
|
+
var ux = Math.cos(theta);
|
|
2047
|
+
var uy = Math.sin(theta);
|
|
2048
|
+
var starts = [];
|
|
2049
|
+
var ends = [];
|
|
2050
|
+
try {
|
|
2051
|
+
for (var rows_3 = __values(rows), rows_3_1 = rows_3.next(); !rows_3_1.done; rows_3_1 = rows_3.next()) {
|
|
2052
|
+
var row = rows_3_1.value;
|
|
2053
|
+
// Touching collinear pieces form one crossing (segments are sorted).
|
|
2054
|
+
var s0 = row.segs[0].s0;
|
|
2055
|
+
var s1 = row.segs[0].s1;
|
|
2056
|
+
try {
|
|
2057
|
+
for (var _c = (e_54 = void 0, __values(row.segs.slice(1))), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
2058
|
+
var entry = _d.value;
|
|
2059
|
+
if (entry.s0 > s1 + tol)
|
|
2060
|
+
return undefined;
|
|
2061
|
+
s1 = Math.max(s1, entry.s1);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
catch (e_54_1) { e_54 = { error: e_54_1 }; }
|
|
2065
|
+
finally {
|
|
2066
|
+
try {
|
|
2067
|
+
if (_d && !_d.done && (_b = _c.return)) _b.call(_c);
|
|
2068
|
+
}
|
|
2069
|
+
finally { if (e_54) throw e_54.error; }
|
|
2070
|
+
}
|
|
2071
|
+
// Half a tolerance past the ends: lines of another family that run
|
|
2072
|
+
// exactly along the edge then stay inside instead of on it.
|
|
2073
|
+
var a = s0 - tol / 2;
|
|
2074
|
+
var b = s1 + tol / 2;
|
|
2075
|
+
starts.push({ x: ux * a - uy * row.c, y: uy * a + ux * row.c });
|
|
2076
|
+
ends.push({ x: ux * b - uy * row.c, y: uy * b + ux * row.c });
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
catch (e_53_1) { e_53 = { error: e_53_1 }; }
|
|
2080
|
+
finally {
|
|
2081
|
+
try {
|
|
2082
|
+
if (rows_3_1 && !rows_3_1.done && (_a = rows_3.return)) _a.call(rows_3);
|
|
2083
|
+
}
|
|
2084
|
+
finally { if (e_53) throw e_53.error; }
|
|
2085
|
+
}
|
|
2086
|
+
// Cap half a spacing past the outer lines so they are not on the edge
|
|
2087
|
+
// (no line of this family lies in that strip).
|
|
2088
|
+
var half = family.period / 2;
|
|
2089
|
+
var nx = -uy;
|
|
2090
|
+
var ny = ux;
|
|
2091
|
+
var shift = function (p, k) { return ({
|
|
2092
|
+
x: p.x + nx * half * k,
|
|
2093
|
+
y: p.y + ny * half * k
|
|
2094
|
+
}); };
|
|
2095
|
+
var last = rows.length - 1;
|
|
2096
|
+
var polygon = simplifyPolygon(__spreadArray(__spreadArray(__spreadArray(__spreadArray([
|
|
2097
|
+
shift(starts[0], -1)
|
|
2098
|
+
], __read(starts), false), [
|
|
2099
|
+
shift(starts[last], 1),
|
|
2100
|
+
shift(ends[last], 1)
|
|
2101
|
+
], false), __read(__spreadArray([], __read(ends), false).reverse()), false), [
|
|
2102
|
+
shift(ends[0], -1)
|
|
2103
|
+
], false), tol * 0.1);
|
|
2104
|
+
if (polygon.length < 3)
|
|
2105
|
+
return undefined;
|
|
2106
|
+
return polygonsToBoundary([polygon]);
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* The caller's boundaries around the region: those holding its strokes,
|
|
2110
|
+
* plus the ones nested inside them (holes).
|
|
2111
|
+
*/
|
|
2112
|
+
function boundaryFromGiven(region, given) {
|
|
2113
|
+
if (given.length === 0)
|
|
2114
|
+
return undefined;
|
|
2115
|
+
var mids = region.map(midpoint);
|
|
2116
|
+
var polygons = given.filter(function (p) { return p.length >= 3; }).map(function (p) { return __spreadArray([], __read(p), false); });
|
|
2117
|
+
var boxes = polygons.map(pointsBox);
|
|
2118
|
+
var chosen = new Set();
|
|
2119
|
+
polygons.forEach(function (polygon, i) {
|
|
2120
|
+
if (mids.some(function (m) { return inBox(m, boxes[i]) && pointInPolygon(m, polygon); })) {
|
|
2121
|
+
chosen.add(i);
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
if (chosen.size === 0)
|
|
2125
|
+
return undefined;
|
|
2126
|
+
polygons.forEach(function (polygon, i) {
|
|
2127
|
+
if (chosen.has(i))
|
|
2128
|
+
return;
|
|
2129
|
+
var inside = __spreadArray([], __read(chosen), false).some(function (j) {
|
|
2130
|
+
return boxInside(boxes[i], boxes[j]) && pointInPolygon(polygon[0], polygons[j]);
|
|
2131
|
+
});
|
|
2132
|
+
if (inside)
|
|
2133
|
+
chosen.add(i);
|
|
2134
|
+
});
|
|
2135
|
+
var picked = __spreadArray([], __read(chosen), false).map(function (i) { return polygons[i]; });
|
|
2136
|
+
var covered = mids.filter(function (m) { return picked.filter(function (polygon) { return pointInPolygon(m, polygon); }).length % 2 === 1; }).length;
|
|
2137
|
+
if (covered < mids.length * 0.9)
|
|
2138
|
+
return undefined;
|
|
2139
|
+
return polygonsToBoundary(picked);
|
|
2140
|
+
}
|
|
2141
|
+
/** Most edge segments a face arrangement is built from. */
|
|
2142
|
+
var MAX_FACE_SEGMENTS = 20000;
|
|
2143
|
+
/**
|
|
2144
|
+
* Face arrangements and sampled polylines, shared by the hypotheses and
|
|
2145
|
+
* regions of one recognition run.
|
|
2146
|
+
*/
|
|
2147
|
+
var FaceCache = /** @class */ (function () {
|
|
2148
|
+
function FaceCache() {
|
|
2149
|
+
/** Recent arrangements by their sorted edge item indices. */
|
|
2150
|
+
this.recent = [];
|
|
2151
|
+
this.polylines = new Map();
|
|
2152
|
+
this.groups = new WeakMap();
|
|
2153
|
+
}
|
|
2154
|
+
/** Candidate segments of a group by item index. */
|
|
2155
|
+
FaceCache.prototype.segsOf = function (all) {
|
|
2156
|
+
var map = this.groups.get(all);
|
|
2157
|
+
if (!map) {
|
|
2158
|
+
map = new Map(all.map(function (seg) { return [seg.index, seg]; }));
|
|
2159
|
+
this.groups.set(all, map);
|
|
2160
|
+
}
|
|
2161
|
+
return map;
|
|
2162
|
+
};
|
|
2163
|
+
FaceCache.prototype.polyline = function (context, index) {
|
|
2164
|
+
var polyline = this.polylines.get(index);
|
|
2165
|
+
if (!polyline) {
|
|
2166
|
+
polyline = acapPdfShapePolyline(context.items[index].shape, context.opts.tolerance * 0.25);
|
|
2167
|
+
this.polylines.set(index, polyline);
|
|
2168
|
+
}
|
|
2169
|
+
return polyline;
|
|
2170
|
+
};
|
|
2171
|
+
/**
|
|
2172
|
+
* The arrangement of the given items. Hypotheses of one region mostly
|
|
2173
|
+
* leave the same strokes out, so the line work repeats.
|
|
2174
|
+
*/
|
|
2175
|
+
FaceCache.prototype.arrangement = function (context, edges) {
|
|
2176
|
+
var e_55, _a;
|
|
2177
|
+
var hit = this.recent.find(function (entry) { return sameIndices(entry.edges, edges); });
|
|
2178
|
+
if (hit)
|
|
2179
|
+
return hit.arrangement;
|
|
2180
|
+
var polylines = [];
|
|
2181
|
+
try {
|
|
2182
|
+
for (var edges_1 = __values(edges), edges_1_1 = edges_1.next(); !edges_1_1.done; edges_1_1 = edges_1.next()) {
|
|
2183
|
+
var index = edges_1_1.value;
|
|
2184
|
+
var polyline = this.polyline(context, index);
|
|
2185
|
+
if (polyline.length >= 2)
|
|
2186
|
+
polylines.push(polyline);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
catch (e_55_1) { e_55 = { error: e_55_1 }; }
|
|
2190
|
+
finally {
|
|
2191
|
+
try {
|
|
2192
|
+
if (edges_1_1 && !edges_1_1.done && (_a = edges_1.return)) _a.call(edges_1);
|
|
2193
|
+
}
|
|
2194
|
+
finally { if (e_55) throw e_55.error; }
|
|
2195
|
+
}
|
|
2196
|
+
var arrangement = acapPdfFaceArrangement(polylines, {
|
|
2197
|
+
tolerance: context.opts.tolerance,
|
|
2198
|
+
maxSegments: MAX_FACE_SEGMENTS
|
|
2199
|
+
});
|
|
2200
|
+
this.recent.unshift({ edges: edges, arrangement: arrangement });
|
|
2201
|
+
if (this.recent.length > 4)
|
|
2202
|
+
this.recent.pop();
|
|
2203
|
+
return arrangement;
|
|
2204
|
+
};
|
|
2205
|
+
return FaceCache;
|
|
2206
|
+
}());
|
|
2207
|
+
function sameIndices(p, q) {
|
|
2208
|
+
if (p.length !== q.length)
|
|
2209
|
+
return false;
|
|
2210
|
+
for (var i = 0; i < p.length; i++)
|
|
2211
|
+
if (p[i] !== q[i])
|
|
2212
|
+
return false;
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Faces of the line work around the region that hold its strokes. The
|
|
2217
|
+
* strokes the pattern re-draws are left out of the line work.
|
|
2218
|
+
*/
|
|
2219
|
+
function boundaryFromFaces(region, all, lines, context) {
|
|
2220
|
+
var e_56, _a, e_57, _b, e_58, _c;
|
|
2221
|
+
var _d;
|
|
2222
|
+
var tol = context.opts.tolerance;
|
|
2223
|
+
var box = segsBox(region);
|
|
2224
|
+
var frames = lines.map(lineFrame);
|
|
2225
|
+
// The strokes of a sparse pattern (AR-CONC) can stop a whole repeat
|
|
2226
|
+
// short of the boundary: reach one repeat past them.
|
|
2227
|
+
var pad = tol * 4;
|
|
2228
|
+
try {
|
|
2229
|
+
for (var frames_2 = __values(frames), frames_2_1 = frames_2.next(); !frames_2_1.done; frames_2_1 = frames_2.next()) {
|
|
2230
|
+
var frame = frames_2_1.value;
|
|
2231
|
+
if (frame.dotsOnly)
|
|
2232
|
+
continue;
|
|
2233
|
+
pad = Math.max(pad, Math.abs(frame.spacing), frame.period);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
catch (e_56_1) { e_56 = { error: e_56_1 }; }
|
|
2237
|
+
finally {
|
|
2238
|
+
try {
|
|
2239
|
+
if (frames_2_1 && !frames_2_1.done && (_a = frames_2.return)) _a.call(frames_2);
|
|
2240
|
+
}
|
|
2241
|
+
finally { if (e_56) throw e_56.error; }
|
|
2242
|
+
}
|
|
2243
|
+
var query = {
|
|
2244
|
+
minX: box.minX - pad,
|
|
2245
|
+
minY: box.minY - pad,
|
|
2246
|
+
maxX: box.maxX + pad,
|
|
2247
|
+
maxY: box.maxY + pad
|
|
2248
|
+
};
|
|
2249
|
+
var near = context.itemIndex.query(query);
|
|
2250
|
+
// Each item gives one segment or more: too much line work is refused
|
|
2251
|
+
// before anything is sampled.
|
|
2252
|
+
if (near.length - region.length > MAX_FACE_SEGMENTS)
|
|
2253
|
+
return undefined;
|
|
2254
|
+
var strokes = new Set();
|
|
2255
|
+
try {
|
|
2256
|
+
for (var region_1 = __values(region), region_1_1 = region_1.next(); !region_1_1.done; region_1_1 = region_1.next()) {
|
|
2257
|
+
var seg = region_1_1.value;
|
|
2258
|
+
strokes.add(seg.index);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
catch (e_57_1) { e_57 = { error: e_57_1 }; }
|
|
2262
|
+
finally {
|
|
2263
|
+
try {
|
|
2264
|
+
if (region_1_1 && !region_1_1.done && (_b = region_1.return)) _b.call(region_1);
|
|
2265
|
+
}
|
|
2266
|
+
finally { if (e_57) throw e_57.error; }
|
|
2267
|
+
}
|
|
2268
|
+
var candidates = context.faces.segsOf(all);
|
|
2269
|
+
var edges = [];
|
|
2270
|
+
try {
|
|
2271
|
+
for (var near_3 = __values(near), near_3_1 = near_3.next(); !near_3_1.done; near_3_1 = near_3.next()) {
|
|
2272
|
+
var index = near_3_1.value;
|
|
2273
|
+
if (strokes.has(index))
|
|
2274
|
+
continue;
|
|
2275
|
+
var seg = candidates.get(index);
|
|
2276
|
+
if (seg && segOnPattern(seg, lines, frames, tol))
|
|
2277
|
+
continue;
|
|
2278
|
+
edges.push(index);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
catch (e_58_1) { e_58 = { error: e_58_1 }; }
|
|
2282
|
+
finally {
|
|
2283
|
+
try {
|
|
2284
|
+
if (near_3_1 && !near_3_1.done && (_c = near_3.return)) _c.call(near_3);
|
|
2285
|
+
}
|
|
2286
|
+
finally { if (e_58) throw e_58.error; }
|
|
2287
|
+
}
|
|
2288
|
+
edges.sort(function (p, q) { return p - q; });
|
|
2289
|
+
var arrangement = context.faces.arrangement(context, edges);
|
|
2290
|
+
// Hatches of one pattern that touch are told apart by their strokes,
|
|
2291
|
+
// which stop on the shared edge instead of crossing it.
|
|
2292
|
+
var parts = arrangement
|
|
2293
|
+
? acapPdfFaceBoundaryParts(arrangement, region.map(midpoint), {
|
|
2294
|
+
strokes: region.map(function (seg) { return [seg.a, seg.b]; })
|
|
2295
|
+
})
|
|
2296
|
+
: undefined;
|
|
2297
|
+
context.opts.trace('faces', {
|
|
2298
|
+
near: near.length,
|
|
2299
|
+
strokes: strokes.size,
|
|
2300
|
+
edges: edges.length,
|
|
2301
|
+
arranged: !!arrangement,
|
|
2302
|
+
parts: (_d = parts === null || parts === void 0 ? void 0 : parts.length) !== null && _d !== void 0 ? _d : 0
|
|
2303
|
+
});
|
|
2304
|
+
return parts === null || parts === void 0 ? void 0 : parts.map(polygonsToBoundary);
|
|
2305
|
+
}
|
|
2306
|
+
function midpoint(seg) {
|
|
2307
|
+
return { x: (seg.a.x + seg.b.x) / 2, y: (seg.a.y + seg.b.y) / 2 };
|
|
2308
|
+
}
|
|
2309
|
+
function inBox(p, box) {
|
|
2310
|
+
return (p.x >= box.minX && p.x <= box.maxX && p.y >= box.minY && p.y <= box.maxY);
|
|
2311
|
+
}
|
|
2312
|
+
function boxInside(inner, outer) {
|
|
2313
|
+
return (inner.minX >= outer.minX &&
|
|
2314
|
+
inner.minY >= outer.minY &&
|
|
2315
|
+
inner.maxX <= outer.maxX &&
|
|
2316
|
+
inner.maxY <= outer.maxY);
|
|
2317
|
+
}
|
|
2318
|
+
function boxTouches(p, q) {
|
|
2319
|
+
return !(p.maxX < q.minX ||
|
|
2320
|
+
q.maxX < p.minX ||
|
|
2321
|
+
p.maxY < q.minY ||
|
|
2322
|
+
q.maxY < p.minY);
|
|
2323
|
+
}
|
|
2324
|
+
function pointInPolygon(p, polygon) {
|
|
2325
|
+
var inside = false;
|
|
2326
|
+
for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
|
2327
|
+
var a = polygon[i];
|
|
2328
|
+
var b = polygon[j];
|
|
2329
|
+
if (a.y > p.y !== b.y > p.y &&
|
|
2330
|
+
p.x < ((b.x - a.x) * (p.y - a.y)) / (b.y - a.y) + a.x) {
|
|
2331
|
+
inside = !inside;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return inside;
|
|
2335
|
+
}
|
|
2336
|
+
/** Uniform grid over the item boxes, built on first use. */
|
|
2337
|
+
var ItemIndex = /** @class */ (function () {
|
|
2338
|
+
function ItemIndex(items) {
|
|
2339
|
+
this.items = items;
|
|
2340
|
+
this.stride = 1;
|
|
2341
|
+
this.boxes = [];
|
|
2342
|
+
this.originX = 0;
|
|
2343
|
+
this.originY = 0;
|
|
2344
|
+
this.size = 1;
|
|
2345
|
+
}
|
|
2346
|
+
/** Items whose box meets `box`. */
|
|
2347
|
+
ItemIndex.prototype.query = function (box) {
|
|
2348
|
+
var e_59, _a;
|
|
2349
|
+
var _this = this;
|
|
2350
|
+
var _b;
|
|
2351
|
+
var cells = this.build();
|
|
2352
|
+
// Cells lie in [0, stride - 1]; clamping keeps numeric keys unique.
|
|
2353
|
+
var last = this.stride - 1;
|
|
2354
|
+
var cellOf = function (v, origin) {
|
|
2355
|
+
return Math.min(last, Math.max(0, Math.floor((v - origin) / _this.size)));
|
|
2356
|
+
};
|
|
2357
|
+
var x0 = cellOf(box.minX, this.originX);
|
|
2358
|
+
var x1 = cellOf(box.maxX, this.originX);
|
|
2359
|
+
var y0 = cellOf(box.minY, this.originY);
|
|
2360
|
+
var y1 = cellOf(box.maxY, this.originY);
|
|
2361
|
+
var out = new Set();
|
|
2362
|
+
if ((x1 - x0 + 1) * (y1 - y0 + 1) > cells.size) {
|
|
2363
|
+
// A query wider than the grid: scan the boxes instead.
|
|
2364
|
+
this.boxes.forEach(function (b, i) {
|
|
2365
|
+
if (boxTouches(b, box))
|
|
2366
|
+
out.add(i);
|
|
2367
|
+
});
|
|
2368
|
+
return __spreadArray([], __read(out), false);
|
|
2369
|
+
}
|
|
2370
|
+
for (var x = x0; x <= x1; x++) {
|
|
2371
|
+
for (var y = y0; y <= y1; y++) {
|
|
2372
|
+
try {
|
|
2373
|
+
for (var _c = (e_59 = void 0, __values((_b = cells.get(x * this.stride + y)) !== null && _b !== void 0 ? _b : [])), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
2374
|
+
var i = _d.value;
|
|
2375
|
+
if (!out.has(i) && boxTouches(this.boxes[i], box))
|
|
2376
|
+
out.add(i);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
catch (e_59_1) { e_59 = { error: e_59_1 }; }
|
|
2380
|
+
finally {
|
|
2381
|
+
try {
|
|
2382
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
2383
|
+
}
|
|
2384
|
+
finally { if (e_59) throw e_59.error; }
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
return __spreadArray([], __read(out), false);
|
|
2389
|
+
};
|
|
2390
|
+
ItemIndex.prototype.build = function () {
|
|
2391
|
+
var e_60, _a;
|
|
2392
|
+
var _this = this;
|
|
2393
|
+
if (this.cells)
|
|
2394
|
+
return this.cells;
|
|
2395
|
+
this.boxes = this.items.map(function (item) { return shapeBox(item.shape); });
|
|
2396
|
+
var minX = Infinity;
|
|
2397
|
+
var minY = Infinity;
|
|
2398
|
+
var maxX = -Infinity;
|
|
2399
|
+
var maxY = -Infinity;
|
|
2400
|
+
try {
|
|
2401
|
+
for (var _b = __values(this.boxes), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
2402
|
+
var b = _c.value;
|
|
2403
|
+
if (!Number.isFinite(b.minX))
|
|
2404
|
+
continue;
|
|
2405
|
+
minX = Math.min(minX, b.minX);
|
|
2406
|
+
minY = Math.min(minY, b.minY);
|
|
2407
|
+
maxX = Math.max(maxX, b.maxX);
|
|
2408
|
+
maxY = Math.max(maxY, b.maxY);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
catch (e_60_1) { e_60 = { error: e_60_1 }; }
|
|
2412
|
+
finally {
|
|
2413
|
+
try {
|
|
2414
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
2415
|
+
}
|
|
2416
|
+
finally { if (e_60) throw e_60.error; }
|
|
2417
|
+
}
|
|
2418
|
+
var cells = new Map();
|
|
2419
|
+
this.cells = cells;
|
|
2420
|
+
if (!Number.isFinite(minX))
|
|
2421
|
+
return cells;
|
|
2422
|
+
var n = Math.max(1, Math.ceil(Math.sqrt(this.boxes.length / 4)));
|
|
2423
|
+
this.originX = minX;
|
|
2424
|
+
this.originY = minY;
|
|
2425
|
+
this.size = Math.max((maxX - minX) / n, (maxY - minY) / n, 1e-9);
|
|
2426
|
+
this.stride = n + 2;
|
|
2427
|
+
this.boxes.forEach(function (b, i) {
|
|
2428
|
+
if (!Number.isFinite(b.minX))
|
|
2429
|
+
return;
|
|
2430
|
+
var x0 = Math.floor((b.minX - minX) / _this.size);
|
|
2431
|
+
var x1 = Math.floor((b.maxX - minX) / _this.size);
|
|
2432
|
+
var y0 = Math.floor((b.minY - minY) / _this.size);
|
|
2433
|
+
var y1 = Math.floor((b.maxY - minY) / _this.size);
|
|
2434
|
+
for (var x = x0; x <= x1; x++) {
|
|
2435
|
+
for (var y = y0; y <= y1; y++) {
|
|
2436
|
+
var key = x * _this.stride + y;
|
|
2437
|
+
var list = cells.get(key);
|
|
2438
|
+
if (list)
|
|
2439
|
+
list.push(i);
|
|
2440
|
+
else
|
|
2441
|
+
cells.set(key, [i]);
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
});
|
|
2445
|
+
return cells;
|
|
2446
|
+
};
|
|
2447
|
+
return ItemIndex;
|
|
2448
|
+
}());
|
|
2449
|
+
function shapeBox(shape) {
|
|
2450
|
+
switch (shape.kind) {
|
|
2451
|
+
case 'line':
|
|
2452
|
+
return pointsBox([shape.start, shape.end]);
|
|
2453
|
+
case 'circle':
|
|
2454
|
+
case 'arc':
|
|
2455
|
+
return {
|
|
2456
|
+
minX: shape.center.x - shape.radius,
|
|
2457
|
+
minY: shape.center.y - shape.radius,
|
|
2458
|
+
maxX: shape.center.x + shape.radius,
|
|
2459
|
+
maxY: shape.center.y + shape.radius
|
|
2460
|
+
};
|
|
2461
|
+
case 'ellipse': {
|
|
2462
|
+
var r = Math.hypot(shape.majorAxis.x, shape.majorAxis.y);
|
|
2463
|
+
return {
|
|
2464
|
+
minX: shape.center.x - r,
|
|
2465
|
+
minY: shape.center.y - r,
|
|
2466
|
+
maxX: shape.center.x + r,
|
|
2467
|
+
maxY: shape.center.y + r
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
case 'polyline': {
|
|
2471
|
+
var box = pointsBox(shape.vertices);
|
|
2472
|
+
// Bulges can bow past the vertices; half the box is a safe margin.
|
|
2473
|
+
if (!shape.vertices.some(function (v) { return Math.abs(v.bulge) > 1e-12; }))
|
|
2474
|
+
return box;
|
|
2475
|
+
var pad = Math.max(box.maxX - box.minX, box.maxY - box.minY) / 2;
|
|
2476
|
+
return {
|
|
2477
|
+
minX: box.minX - pad,
|
|
2478
|
+
minY: box.minY - pad,
|
|
2479
|
+
maxX: box.maxX + pad,
|
|
2480
|
+
maxY: box.maxY + pad
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
case 'spline':
|
|
2484
|
+
return pointsBox(shape.controlPoints);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Drops vertices that lie on the line between their kept neighbours
|
|
2489
|
+
* (one pass with a stack, then the seam where the ring closes).
|
|
2490
|
+
*/
|
|
2491
|
+
function simplifyPolygon(points, tol) {
|
|
2492
|
+
var e_61, _a;
|
|
2493
|
+
var out = [];
|
|
2494
|
+
try {
|
|
2495
|
+
for (var points_2 = __values(points), points_2_1 = points_2.next(); !points_2_1.done; points_2_1 = points_2.next()) {
|
|
2496
|
+
var p = points_2_1.value;
|
|
2497
|
+
while (out.length >= 2 &&
|
|
2498
|
+
distanceToSegment(out[out.length - 1], out[out.length - 2], p) <= tol) {
|
|
2499
|
+
out.pop();
|
|
2500
|
+
}
|
|
2501
|
+
out.push(p);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
catch (e_61_1) { e_61 = { error: e_61_1 }; }
|
|
2505
|
+
finally {
|
|
2506
|
+
try {
|
|
2507
|
+
if (points_2_1 && !points_2_1.done && (_a = points_2.return)) _a.call(points_2);
|
|
2508
|
+
}
|
|
2509
|
+
finally { if (e_61) throw e_61.error; }
|
|
2510
|
+
}
|
|
2511
|
+
var changed = true;
|
|
2512
|
+
while (changed && out.length > 3) {
|
|
2513
|
+
changed = false;
|
|
2514
|
+
var n = out.length;
|
|
2515
|
+
if (distanceToSegment(out[n - 1], out[n - 2], out[0]) <= tol) {
|
|
2516
|
+
out.pop();
|
|
2517
|
+
changed = true;
|
|
2518
|
+
}
|
|
2519
|
+
else if (distanceToSegment(out[0], out[n - 1], out[1]) <= tol) {
|
|
2520
|
+
out.shift();
|
|
2521
|
+
changed = true;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return out;
|
|
2525
|
+
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Every pattern line the segment lies on (both ends within `tol`), keyed by
|
|
2528
|
+
* definition line and line index, with its extent along that line.
|
|
2529
|
+
*/
|
|
2530
|
+
function patternSpans(a, b, lines, frames, tol) {
|
|
2531
|
+
var out = [];
|
|
2532
|
+
for (var i = 0; i < lines.length; i++) {
|
|
2533
|
+
var base = lines[i].base;
|
|
2534
|
+
var _a = frames[i], ux = _a.ux, uy = _a.uy, spacing = _a.spacing;
|
|
2535
|
+
var ya = -uy * (a.x - base.x) + ux * (a.y - base.y);
|
|
2536
|
+
var yb = -uy * (b.x - base.x) + ux * (b.y - base.y);
|
|
2537
|
+
var k = Math.round((ya + yb) / 2 / spacing);
|
|
2538
|
+
if (Math.abs(ya - k * spacing) > tol || Math.abs(yb - k * spacing) > tol) {
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
var xa = ux * (a.x - base.x) + uy * (a.y - base.y);
|
|
2542
|
+
var xb = ux * (b.x - base.x) + uy * (b.y - base.y);
|
|
2543
|
+
out.push({ key: "".concat(i, ":").concat(k), s0: Math.min(xa, xb), s1: Math.max(xa, xb) });
|
|
2544
|
+
}
|
|
2545
|
+
return out;
|
|
2546
|
+
}
|
|
2547
|
+
/** Merged intervals per pattern line, for coverage queries. */
|
|
2548
|
+
var SpanIndex = /** @class */ (function () {
|
|
2549
|
+
function SpanIndex(spans, gap) {
|
|
2550
|
+
var e_62, _a, e_63, _b, e_64, _c;
|
|
2551
|
+
this.merged = new Map();
|
|
2552
|
+
var byKey = new Map();
|
|
2553
|
+
try {
|
|
2554
|
+
for (var spans_1 = __values(spans), spans_1_1 = spans_1.next(); !spans_1_1.done; spans_1_1 = spans_1.next()) {
|
|
2555
|
+
var span = spans_1_1.value;
|
|
2556
|
+
var list = byKey.get(span.key);
|
|
2557
|
+
if (list)
|
|
2558
|
+
list.push([span.s0, span.s1]);
|
|
2559
|
+
else
|
|
2560
|
+
byKey.set(span.key, [[span.s0, span.s1]]);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
catch (e_62_1) { e_62 = { error: e_62_1 }; }
|
|
2564
|
+
finally {
|
|
2565
|
+
try {
|
|
2566
|
+
if (spans_1_1 && !spans_1_1.done && (_a = spans_1.return)) _a.call(spans_1);
|
|
2567
|
+
}
|
|
2568
|
+
finally { if (e_62) throw e_62.error; }
|
|
2569
|
+
}
|
|
2570
|
+
try {
|
|
2571
|
+
for (var byKey_1 = __values(byKey), byKey_1_1 = byKey_1.next(); !byKey_1_1.done; byKey_1_1 = byKey_1.next()) {
|
|
2572
|
+
var _d = __read(byKey_1_1.value, 2), key = _d[0], list = _d[1];
|
|
2573
|
+
list.sort(function (p, q) { return p[0] - q[0]; });
|
|
2574
|
+
var merged = [];
|
|
2575
|
+
try {
|
|
2576
|
+
for (var list_1 = (e_64 = void 0, __values(list)), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) {
|
|
2577
|
+
var _e = __read(list_1_1.value, 2), s0 = _e[0], s1 = _e[1];
|
|
2578
|
+
var last = merged[merged.length - 1];
|
|
2579
|
+
if (last && s0 <= last[1] + gap)
|
|
2580
|
+
last[1] = Math.max(last[1], s1);
|
|
2581
|
+
else
|
|
2582
|
+
merged.push([s0, s1]);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
catch (e_64_1) { e_64 = { error: e_64_1 }; }
|
|
2586
|
+
finally {
|
|
2587
|
+
try {
|
|
2588
|
+
if (list_1_1 && !list_1_1.done && (_c = list_1.return)) _c.call(list_1);
|
|
2589
|
+
}
|
|
2590
|
+
finally { if (e_64) throw e_64.error; }
|
|
2591
|
+
}
|
|
2592
|
+
this.merged.set(key, merged);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
catch (e_63_1) { e_63 = { error: e_63_1 }; }
|
|
2596
|
+
finally {
|
|
2597
|
+
try {
|
|
2598
|
+
if (byKey_1_1 && !byKey_1_1.done && (_b = byKey_1.return)) _b.call(byKey_1);
|
|
2599
|
+
}
|
|
2600
|
+
finally { if (e_63) throw e_63.error; }
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
/** Length of `span` the indexed intervals cover. */
|
|
2604
|
+
SpanIndex.prototype.covered = function (span) {
|
|
2605
|
+
var list = this.merged.get(span.key);
|
|
2606
|
+
if (!list)
|
|
2607
|
+
return 0;
|
|
2608
|
+
// First interval that may end after s0.
|
|
2609
|
+
var lo = 0;
|
|
2610
|
+
var hi = list.length;
|
|
2611
|
+
while (lo < hi) {
|
|
2612
|
+
var mid = (lo + hi) >> 1;
|
|
2613
|
+
if (list[mid][1] < span.s0)
|
|
2614
|
+
lo = mid + 1;
|
|
2615
|
+
else
|
|
2616
|
+
hi = mid;
|
|
2617
|
+
}
|
|
2618
|
+
var total = 0;
|
|
2619
|
+
for (var i = lo; i < list.length && list[i][0] < span.s1; i++) {
|
|
2620
|
+
total += Math.max(0, Math.min(list[i][1], span.s1) - Math.max(list[i][0], span.s0));
|
|
2621
|
+
}
|
|
2622
|
+
return total;
|
|
2623
|
+
};
|
|
2624
|
+
/** Best covered length over the spans of one segment. */
|
|
2625
|
+
SpanIndex.prototype.coveredBest = function (spans) {
|
|
2626
|
+
var e_65, _a;
|
|
2627
|
+
var best = 0;
|
|
2628
|
+
try {
|
|
2629
|
+
for (var spans_2 = __values(spans), spans_2_1 = spans_2.next(); !spans_2_1.done; spans_2_1 = spans_2.next()) {
|
|
2630
|
+
var span = spans_2_1.value;
|
|
2631
|
+
best = Math.max(best, this.covered(span));
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
catch (e_65_1) { e_65 = { error: e_65_1 }; }
|
|
2635
|
+
finally {
|
|
2636
|
+
try {
|
|
2637
|
+
if (spans_2_1 && !spans_2_1.done && (_a = spans_2.return)) _a.call(spans_2);
|
|
2638
|
+
}
|
|
2639
|
+
finally { if (e_65) throw e_65.error; }
|
|
2640
|
+
}
|
|
2641
|
+
return best;
|
|
2642
|
+
};
|
|
2643
|
+
return SpanIndex;
|
|
2644
|
+
}());
|
|
2645
|
+
function verify(lines, boundary, region, all, opts) {
|
|
2646
|
+
var tol = opts.tolerance;
|
|
2647
|
+
var generated = generateStrokes(lines, boundary.polygons, tol);
|
|
2648
|
+
if (generated.length === 0 || generated.length > region.length * 4 + 100) {
|
|
2649
|
+
opts.trace('verify', { generated: generated.length, region: region.length });
|
|
2650
|
+
return undefined;
|
|
2651
|
+
}
|
|
2652
|
+
var match = measureMatch(lines, generated, region, all, tol);
|
|
2653
|
+
if (match.precision < opts.minPrecision) {
|
|
2654
|
+
opts.trace('verify', { precision: match.precision });
|
|
2655
|
+
return undefined;
|
|
2656
|
+
}
|
|
2657
|
+
opts.trace('verify', { precision: match.precision, recall: match.recall });
|
|
2658
|
+
if (match.recall < opts.minRecall)
|
|
2659
|
+
return undefined;
|
|
2660
|
+
return { matched: match.items };
|
|
2661
|
+
}
|
|
2662
|
+
/** Strokes a hatch draws inside `polygons`, without hairline pieces. */
|
|
2663
|
+
function generateStrokes(lines, polygons, tol) {
|
|
2664
|
+
return tessellateHatchPattern(polygons, lines.map(function (line) { return (__assign(__assign({}, line), { base: __assign({}, line.base), offset: __assign({}, line.offset) })); }), 0).filter(function (seg) { return Math.hypot(seg[1].x - seg[0].x, seg[1].y - seg[0].y) > tol; });
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* How well `generated` (the strokes a hatch draws) and `region` (the source
|
|
2668
|
+
* strokes) cover each other, and which segments of `all` the hatch re-draws
|
|
2669
|
+
* completely.
|
|
2670
|
+
*/
|
|
2671
|
+
function measureMatch(lines, generated, region, all, tol) {
|
|
2672
|
+
var e_66, _a;
|
|
2673
|
+
if (generated.length === 0 || region.length === 0) {
|
|
2674
|
+
return { precision: 0, recall: 0, items: [] };
|
|
2675
|
+
}
|
|
2676
|
+
var frames = lines.map(lineFrame);
|
|
2677
|
+
var spansOf = function (a, b) {
|
|
2678
|
+
return patternSpans(a, b, lines, frames, tol);
|
|
2679
|
+
};
|
|
2680
|
+
var genSpans = generated.map(function (_a) {
|
|
2681
|
+
var _b = __read(_a, 2), a = _b[0], b = _b[1];
|
|
2682
|
+
return spansOf(a, b);
|
|
2683
|
+
});
|
|
2684
|
+
var regionSpans = region.map(function (seg) { return spansOf(seg.a, seg.b); });
|
|
2685
|
+
// PDF rounding leaves hairline gaps between pieces of one line.
|
|
2686
|
+
var sourceIndex = new SpanIndex(regionSpans.flat(), tol);
|
|
2687
|
+
var genIndex = new SpanIndex(genSpans.flat(), tol);
|
|
2688
|
+
var genLength = 0;
|
|
2689
|
+
var genCovered = 0;
|
|
2690
|
+
generated.forEach(function (_a, i) {
|
|
2691
|
+
var _b = __read(_a, 2), a = _b[0], b = _b[1];
|
|
2692
|
+
var len = Math.hypot(b.x - a.x, b.y - a.y);
|
|
2693
|
+
genLength += len;
|
|
2694
|
+
genCovered += Math.min(len, sourceIndex.coveredBest(genSpans[i]));
|
|
2695
|
+
});
|
|
2696
|
+
var precision = genLength > 0 ? genCovered / genLength : 0;
|
|
2697
|
+
var srcLength = 0;
|
|
2698
|
+
var srcCovered = 0;
|
|
2699
|
+
region.forEach(function (seg, i) {
|
|
2700
|
+
srcLength += seg.len;
|
|
2701
|
+
srcCovered += Math.min(seg.len, genIndex.coveredBest(regionSpans[i]));
|
|
2702
|
+
});
|
|
2703
|
+
var recall = srcLength > 0 ? srcCovered / srcLength : 0;
|
|
2704
|
+
// Every segment the hatch re-draws goes, short corner stubs included.
|
|
2705
|
+
var box = segsBox(region);
|
|
2706
|
+
var pad = tol * 2;
|
|
2707
|
+
var matched = [];
|
|
2708
|
+
try {
|
|
2709
|
+
for (var all_1 = __values(all), all_1_1 = all_1.next(); !all_1_1.done; all_1_1 = all_1.next()) {
|
|
2710
|
+
var seg = all_1_1.value;
|
|
2711
|
+
if (Math.max(seg.a.x, seg.b.x) < box.minX - pad ||
|
|
2712
|
+
Math.min(seg.a.x, seg.b.x) > box.maxX + pad ||
|
|
2713
|
+
Math.max(seg.a.y, seg.b.y) < box.minY - pad ||
|
|
2714
|
+
Math.min(seg.a.y, seg.b.y) > box.maxY + pad) {
|
|
2715
|
+
continue;
|
|
2716
|
+
}
|
|
2717
|
+
var covered = genIndex.coveredBest(spansOf(seg.a, seg.b));
|
|
2718
|
+
if (covered >= seg.len - tol * 2)
|
|
2719
|
+
matched.push(seg.index);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
catch (e_66_1) { e_66 = { error: e_66_1 }; }
|
|
2723
|
+
finally {
|
|
2724
|
+
try {
|
|
2725
|
+
if (all_1_1 && !all_1_1.done && (_a = all_1.return)) _a.call(all_1);
|
|
2726
|
+
}
|
|
2727
|
+
finally { if (e_66) throw e_66.error; }
|
|
2728
|
+
}
|
|
2729
|
+
return { precision: precision, recall: recall, items: matched.sort(function (p, q) { return p - q; }) };
|
|
2730
|
+
}
|
|
2731
|
+
/** Fewest strokes of a part split off a recognized region. */
|
|
2732
|
+
var MIN_PART_SEGMENTS = 3;
|
|
2733
|
+
/** Library hypotheses tried with a boundary, best score first. */
|
|
2734
|
+
var MAX_VERIFIED = 8;
|
|
2735
|
+
function recognizeRegion(region, all, context) {
|
|
2736
|
+
var e_67, _a, e_68, _b, e_69, _c;
|
|
2737
|
+
var _d, _e;
|
|
2738
|
+
var opts = context.opts;
|
|
2739
|
+
var tol = opts.tolerance;
|
|
2740
|
+
var families = measureFamilies(region, tol);
|
|
2741
|
+
opts.trace('families', {
|
|
2742
|
+
segments: region.length,
|
|
2743
|
+
families: families.map(function (f) { return ({
|
|
2744
|
+
angle: f.theta / DEG,
|
|
2745
|
+
lines: f.offsets.length,
|
|
2746
|
+
period: f.period,
|
|
2747
|
+
residues: f.residues,
|
|
2748
|
+
segments: f.segs.length
|
|
2749
|
+
}); })
|
|
2750
|
+
});
|
|
2751
|
+
if (families.length === 0)
|
|
2752
|
+
return [];
|
|
2753
|
+
if (families[0].offsets.length < opts.minLines)
|
|
2754
|
+
return [];
|
|
2755
|
+
var regionBox = segsBox(region);
|
|
2756
|
+
var extent = Math.hypot(regionBox.maxX - regionBox.minX, regionBox.maxY - regionBox.minY);
|
|
2757
|
+
var hypotheses = [];
|
|
2758
|
+
try {
|
|
2759
|
+
for (var _f = __values(context.library), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
2760
|
+
var sig = _g.value;
|
|
2761
|
+
hypotheses.push.apply(hypotheses, __spreadArray([], __read(matchPattern(sig, families, tol, extent)), false));
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
catch (e_67_1) { e_67 = { error: e_67_1 }; }
|
|
2765
|
+
finally {
|
|
2766
|
+
try {
|
|
2767
|
+
if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
|
|
2768
|
+
}
|
|
2769
|
+
finally { if (e_67) throw e_67.error; }
|
|
2770
|
+
}
|
|
2771
|
+
hypotheses.push.apply(hypotheses, __spreadArray([], __read(userDefinedHypotheses(families, tol, extent)), false));
|
|
2772
|
+
var scored = [];
|
|
2773
|
+
try {
|
|
2774
|
+
for (var hypotheses_1 = __values(hypotheses), hypotheses_1_1 = hypotheses_1.next(); !hypotheses_1_1.done; hypotheses_1_1 = hypotheses_1.next()) {
|
|
2775
|
+
var hyp = hypotheses_1_1.value;
|
|
2776
|
+
var segs = hypothesisSegs(hyp);
|
|
2777
|
+
var best = estimateTranslation(hyp, segs, tol);
|
|
2778
|
+
if (best && best.score >= MIN_SCORE) {
|
|
2779
|
+
scored.push(__assign({ hyp: hyp, segs: segs }, best));
|
|
2780
|
+
continue;
|
|
2781
|
+
}
|
|
2782
|
+
var placed = placeLinesSeparately(hyp, segs, tol);
|
|
2783
|
+
if (placed && placed.score >= MIN_SCORE) {
|
|
2784
|
+
scored.push(__assign({ hyp: hyp, segs: segs, t: { x: 0, y: 0 } }, placed));
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
catch (e_68_1) { e_68 = { error: e_68_1 }; }
|
|
2789
|
+
finally {
|
|
2790
|
+
try {
|
|
2791
|
+
if (hypotheses_1_1 && !hypotheses_1_1.done && (_b = hypotheses_1.return)) _b.call(hypotheses_1);
|
|
2792
|
+
}
|
|
2793
|
+
finally { if (e_68) throw e_68.error; }
|
|
2794
|
+
}
|
|
2795
|
+
scored.sort(function (p, q) { return compareScored(p, q, region.length); });
|
|
2796
|
+
var tries = scored.slice(0, MAX_VERIFIED);
|
|
2797
|
+
// A measured pattern is the last resort, tried after every library and
|
|
2798
|
+
// user-defined candidate.
|
|
2799
|
+
var custom = opts.customPatterns
|
|
2800
|
+
? customHypothesis(families, opts)
|
|
2801
|
+
: undefined;
|
|
2802
|
+
if (custom) {
|
|
2803
|
+
var segs = hypothesisSegs(custom);
|
|
2804
|
+
var lines_3 = definitionLines(custom, { x: 0, y: 0 });
|
|
2805
|
+
var frames_3 = lines_3.map(lineFrame);
|
|
2806
|
+
var sample = sampleSegs(segs, SCORE_SAMPLE);
|
|
2807
|
+
var hit = sample.filter(function (seg) { return segOnPattern(seg, lines_3, frames_3, tol); });
|
|
2808
|
+
tries.push({
|
|
2809
|
+
hyp: custom,
|
|
2810
|
+
segs: segs,
|
|
2811
|
+
t: { x: 0, y: 0 },
|
|
2812
|
+
score: hit.length / sample.length
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
opts.trace('hypotheses', {
|
|
2816
|
+
tried: hypotheses.map(function (h) { var _a; return (_a = h.name) !== null && _a !== void 0 ? _a : 'USER'; }),
|
|
2817
|
+
scored: tries.map(function (s) {
|
|
2818
|
+
var _a;
|
|
2819
|
+
return ({
|
|
2820
|
+
name: (_a = s.hyp.name) !== null && _a !== void 0 ? _a : (s.hyp.custom ? 'CUSTOM' : 'USER'),
|
|
2821
|
+
score: s.score,
|
|
2822
|
+
scale: s.hyp.scale,
|
|
2823
|
+
angle: s.hyp.rotation / DEG
|
|
2824
|
+
});
|
|
2825
|
+
})
|
|
2826
|
+
});
|
|
2827
|
+
var _loop_13 = function (entry) {
|
|
2828
|
+
var e_70, _h, e_71, _j;
|
|
2829
|
+
var hyp = entry.hyp, t = entry.t, segs = entry.segs;
|
|
2830
|
+
var lines = (_d = entry.lines) !== null && _d !== void 0 ? _d : definitionLines(hyp, t);
|
|
2831
|
+
var boundaries = [
|
|
2832
|
+
function () { return boundaryFromGiven(segs, opts.boundaries); },
|
|
2833
|
+
function () { return boundaryFromShapes(segs, context.closedShapes, tol); },
|
|
2834
|
+
function () { return boundaryFromClip(segs, context.items, context.clipPaths); },
|
|
2835
|
+
function () { return boundaryFromEnds(hyp, lines, tol); },
|
|
2836
|
+
function () {
|
|
2837
|
+
return opts.faceBoundaries
|
|
2838
|
+
? boundaryFromFaces(segs, all, lines, context)
|
|
2839
|
+
: undefined;
|
|
2840
|
+
}
|
|
2841
|
+
];
|
|
2842
|
+
try {
|
|
2843
|
+
for (var _k = (e_70 = void 0, __values(boundaries.entries())), _l = _k.next(); !_l.done; _l = _k.next()) {
|
|
2844
|
+
var _m = __read(_l.value, 2), source = _m[0], make = _m[1];
|
|
2845
|
+
var made = make();
|
|
2846
|
+
opts.trace('boundary', {
|
|
2847
|
+
name: (_e = hyp.name) !== null && _e !== void 0 ? _e : (hyp.custom ? 'CUSTOM' : 'USER'),
|
|
2848
|
+
source: BOUNDARY_SOURCES[source],
|
|
2849
|
+
found: !!made
|
|
2850
|
+
});
|
|
2851
|
+
if (!made)
|
|
2852
|
+
continue;
|
|
2853
|
+
var parts = Array.isArray(made) ? made : [made];
|
|
2854
|
+
var found = [];
|
|
2855
|
+
try {
|
|
2856
|
+
for (var parts_1 = (e_71 = void 0, __values(parts)), parts_1_1 = parts_1.next(); !parts_1_1.done; parts_1_1 = parts_1.next()) {
|
|
2857
|
+
var boundary = parts_1_1.value;
|
|
2858
|
+
// A part holds the strokes inside its own outline.
|
|
2859
|
+
var own = parts.length > 1 ? segsInside(segs, boundary.polygons) : segs;
|
|
2860
|
+
// A piece split off a recognized region may be small (a corner
|
|
2861
|
+
// square); the region as a whole already showed the pattern.
|
|
2862
|
+
var minimum = parts.length > 1 ? MIN_PART_SEGMENTS : opts.minSegments;
|
|
2863
|
+
if (own.length < minimum)
|
|
2864
|
+
continue;
|
|
2865
|
+
var verified = verify(lines, boundary, own, all, opts);
|
|
2866
|
+
if (!verified)
|
|
2867
|
+
continue;
|
|
2868
|
+
found.push({
|
|
2869
|
+
boundary: boundary.shapes,
|
|
2870
|
+
pattern: hatchPattern(hyp),
|
|
2871
|
+
definitionLines: lines,
|
|
2872
|
+
items: verified.matched,
|
|
2873
|
+
minSegments: minimum
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
catch (e_71_1) { e_71 = { error: e_71_1 }; }
|
|
2878
|
+
finally {
|
|
2879
|
+
try {
|
|
2880
|
+
if (parts_1_1 && !parts_1_1.done && (_j = parts_1.return)) _j.call(parts_1);
|
|
2881
|
+
}
|
|
2882
|
+
finally { if (e_71) throw e_71.error; }
|
|
2883
|
+
}
|
|
2884
|
+
if (parts.length > 1 && found.length < parts.length) {
|
|
2885
|
+
// Some part did not verify on its own (a split where the strokes
|
|
2886
|
+
// only paused): the whole may still be one hatch.
|
|
2887
|
+
var whole = {
|
|
2888
|
+
shapes: parts.flatMap(function (part) { return part.shapes; }),
|
|
2889
|
+
polygons: parts.flatMap(function (part) { return part.polygons; })
|
|
2890
|
+
};
|
|
2891
|
+
var verified = verify(lines, whole, segs, all, opts);
|
|
2892
|
+
var split = found.reduce(function (sum, hatch) { return sum + hatch.items.length; }, 0);
|
|
2893
|
+
if (verified && verified.matched.length > split) {
|
|
2894
|
+
return { value: [
|
|
2895
|
+
{
|
|
2896
|
+
boundary: whole.shapes,
|
|
2897
|
+
pattern: hatchPattern(hyp),
|
|
2898
|
+
definitionLines: lines,
|
|
2899
|
+
items: verified.matched
|
|
2900
|
+
}
|
|
2901
|
+
] };
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
if (found.length > 0)
|
|
2905
|
+
return { value: found };
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
catch (e_70_1) { e_70 = { error: e_70_1 }; }
|
|
2909
|
+
finally {
|
|
2910
|
+
try {
|
|
2911
|
+
if (_l && !_l.done && (_h = _k.return)) _h.call(_k);
|
|
2912
|
+
}
|
|
2913
|
+
finally { if (e_70) throw e_70.error; }
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2916
|
+
try {
|
|
2917
|
+
for (var tries_1 = __values(tries), tries_1_1 = tries_1.next(); !tries_1_1.done; tries_1_1 = tries_1.next()) {
|
|
2918
|
+
var entry = tries_1_1.value;
|
|
2919
|
+
var state_5 = _loop_13(entry);
|
|
2920
|
+
if (typeof state_5 === "object")
|
|
2921
|
+
return state_5.value;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
catch (e_69_1) { e_69 = { error: e_69_1 }; }
|
|
2925
|
+
finally {
|
|
2926
|
+
try {
|
|
2927
|
+
if (tries_1_1 && !tries_1_1.done && (_c = tries_1.return)) _c.call(tries_1);
|
|
2928
|
+
}
|
|
2929
|
+
finally { if (e_69) throw e_69.error; }
|
|
2930
|
+
}
|
|
2931
|
+
return [];
|
|
2932
|
+
}
|
|
2933
|
+
/** Segments whose midpoint lies inside `polygons` (even-odd). */
|
|
2934
|
+
function segsInside(segs, polygons) {
|
|
2935
|
+
var boxes = polygons.map(pointsBox);
|
|
2936
|
+
return segs.filter(function (seg) {
|
|
2937
|
+
var m = midpoint(seg);
|
|
2938
|
+
var inside = false;
|
|
2939
|
+
polygons.forEach(function (polygon, i) {
|
|
2940
|
+
if (inBox(m, boxes[i]) && pointInPolygon(m, polygon))
|
|
2941
|
+
inside = !inside;
|
|
2942
|
+
});
|
|
2943
|
+
return inside;
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
function hatchPattern(hyp) {
|
|
2947
|
+
var _a;
|
|
2948
|
+
if (hyp.custom)
|
|
2949
|
+
return { type: 'custom', name: CUSTOM_PATTERN_NAME };
|
|
2950
|
+
if (hyp.userDefined) {
|
|
2951
|
+
return {
|
|
2952
|
+
type: 'userDefined',
|
|
2953
|
+
angle: hyp.rotation,
|
|
2954
|
+
spacing: hyp.userDefined.spacing,
|
|
2955
|
+
double: hyp.userDefined.double
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
return {
|
|
2959
|
+
type: 'predefined',
|
|
2960
|
+
name: (_a = hyp.name) !== null && _a !== void 0 ? _a : '',
|
|
2961
|
+
scale: hyp.scale,
|
|
2962
|
+
angle: hyp.rotation
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
/** Pattern name recorded on measured custom hatches. */
|
|
2966
|
+
export var CUSTOM_PATTERN_NAME = 'PDF_HATCH';
|
|
2967
|
+
/** The segments of the families a hypothesis explains. */
|
|
2968
|
+
function hypothesisSegs(hyp) {
|
|
2969
|
+
var e_72, _a;
|
|
2970
|
+
var out = [];
|
|
2971
|
+
try {
|
|
2972
|
+
for (var _b = __values(new Set(hyp.families)), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
2973
|
+
var family = _c.value;
|
|
2974
|
+
if (family)
|
|
2975
|
+
out.push.apply(out, __spreadArray([], __read(family.segs), false));
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
catch (e_72_1) { e_72 = { error: e_72_1 }; }
|
|
2979
|
+
finally {
|
|
2980
|
+
try {
|
|
2981
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
2982
|
+
}
|
|
2983
|
+
finally { if (e_72) throw e_72.error; }
|
|
2984
|
+
}
|
|
2985
|
+
return out;
|
|
2986
|
+
}
|
|
2987
|
+
var BOUNDARY_SOURCES = ['given', 'shapes', 'clip', 'ends', 'faces'];
|
|
2988
|
+
function rotationRank(angle) {
|
|
2989
|
+
var deg = angle / DEG;
|
|
2990
|
+
return Math.min(deg, 360 - deg);
|
|
2991
|
+
}
|
|
2992
|
+
/**
|
|
2993
|
+
* Order of placed hypotheses: best score first; then the pattern that
|
|
2994
|
+
* explains most of the region (a cross-hatch over one of its families), the
|
|
2995
|
+
* pattern drawn closest to its own angle, and library patterns (in library
|
|
2996
|
+
* order) over user-defined lines.
|
|
2997
|
+
*/
|
|
2998
|
+
function compareScored(p, q, regionSize) {
|
|
2999
|
+
return (Math.round((q.score - p.score) * 50) ||
|
|
3000
|
+
Math.round(((q.segs.length - p.segs.length) / regionSize) * 20) ||
|
|
3001
|
+
Math.round(rotationRank(p.hyp.rotation) - rotationRank(q.hyp.rotation)) ||
|
|
3002
|
+
p.hyp.order - q.hyp.order);
|
|
3003
|
+
}
|
|
3004
|
+
// ---------------------------------------------------------------------------
|
|
3005
|
+
// Custom patterns: measured from the strokes when nothing else fits
|
|
3006
|
+
/** Most line families a measured custom pattern may have. */
|
|
3007
|
+
var MAX_CUSTOM_FAMILIES = 3;
|
|
3008
|
+
/**
|
|
3009
|
+
* A custom pattern measured from the region: per family its spacing, the
|
|
3010
|
+
* offset lines within one spacing, the dash sequence along the lines and
|
|
3011
|
+
* the stagger from one line to the next. The pattern lines come out in
|
|
3012
|
+
* drawing coordinates (rotation 0, scale 1).
|
|
3013
|
+
*/
|
|
3014
|
+
function customHypothesis(families, opts) {
|
|
3015
|
+
var e_73, _a, e_74, _b;
|
|
3016
|
+
var tol = opts.tolerance;
|
|
3017
|
+
var main = families[0];
|
|
3018
|
+
if (!main)
|
|
3019
|
+
return undefined;
|
|
3020
|
+
var picked = families
|
|
3021
|
+
.filter(function (f) {
|
|
3022
|
+
return f === main ||
|
|
3023
|
+
// Other families must be as regular and nearly as dense as the
|
|
3024
|
+
// main one; sparse ones are the line work around the hatch.
|
|
3025
|
+
(f.offsets.length >= opts.minLines &&
|
|
3026
|
+
f.residues <= 4 &&
|
|
3027
|
+
f.segs.length >= Math.max(4, main.segs.length * 0.3));
|
|
3028
|
+
})
|
|
3029
|
+
.slice(0, MAX_CUSTOM_FAMILIES);
|
|
3030
|
+
var lines = [];
|
|
3031
|
+
var owners = [];
|
|
3032
|
+
try {
|
|
3033
|
+
for (var picked_1 = __values(picked), picked_1_1 = picked_1.next(); !picked_1_1.done; picked_1_1 = picked_1.next()) {
|
|
3034
|
+
var family = picked_1_1.value;
|
|
3035
|
+
var measured = measureCustomFamily(family, tol);
|
|
3036
|
+
if (!measured) {
|
|
3037
|
+
if (family === main)
|
|
3038
|
+
return undefined;
|
|
3039
|
+
continue;
|
|
3040
|
+
}
|
|
3041
|
+
try {
|
|
3042
|
+
for (var measured_1 = (e_74 = void 0, __values(measured)), measured_1_1 = measured_1.next(); !measured_1_1.done; measured_1_1 = measured_1.next()) {
|
|
3043
|
+
var line = measured_1_1.value;
|
|
3044
|
+
lines.push(line);
|
|
3045
|
+
owners.push(family);
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
catch (e_74_1) { e_74 = { error: e_74_1 }; }
|
|
3049
|
+
finally {
|
|
3050
|
+
try {
|
|
3051
|
+
if (measured_1_1 && !measured_1_1.done && (_b = measured_1.return)) _b.call(measured_1);
|
|
3052
|
+
}
|
|
3053
|
+
finally { if (e_74) throw e_74.error; }
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
catch (e_73_1) { e_73 = { error: e_73_1 }; }
|
|
3058
|
+
finally {
|
|
3059
|
+
try {
|
|
3060
|
+
if (picked_1_1 && !picked_1_1.done && (_a = picked_1.return)) _a.call(picked_1);
|
|
3061
|
+
}
|
|
3062
|
+
finally { if (e_73) throw e_73.error; }
|
|
3063
|
+
}
|
|
3064
|
+
if (lines.length === 0)
|
|
3065
|
+
return undefined;
|
|
3066
|
+
return {
|
|
3067
|
+
order: Infinity,
|
|
3068
|
+
rotation: 0,
|
|
3069
|
+
scale: 1,
|
|
3070
|
+
lines: lines,
|
|
3071
|
+
families: owners,
|
|
3072
|
+
custom: true
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function measureCustomFamily(family, tol) {
|
|
3076
|
+
var e_75, _a, e_76, _b;
|
|
3077
|
+
var theta = family.theta;
|
|
3078
|
+
var ux = Math.cos(theta);
|
|
3079
|
+
var uy = Math.sin(theta);
|
|
3080
|
+
var period = family.period;
|
|
3081
|
+
var rows = linesOf(family.segs, theta, tol);
|
|
3082
|
+
if (rows.length < 2)
|
|
3083
|
+
return undefined;
|
|
3084
|
+
// Rows by offset within one spacing (patterns may hold several lines).
|
|
3085
|
+
var c0 = rows[0].c;
|
|
3086
|
+
var classes = [];
|
|
3087
|
+
var _loop_14 = function (row) {
|
|
3088
|
+
var r = (((row.c - c0) % period) + period) % period;
|
|
3089
|
+
var found = classes.find(function (cls) {
|
|
3090
|
+
var d = Math.abs(cls.residue - r);
|
|
3091
|
+
return Math.min(d, period - d) <= tol * 2;
|
|
3092
|
+
});
|
|
3093
|
+
if (found)
|
|
3094
|
+
found.rows.push(row);
|
|
3095
|
+
else
|
|
3096
|
+
classes.push({ residue: r, rows: [row] });
|
|
3097
|
+
};
|
|
3098
|
+
try {
|
|
3099
|
+
for (var rows_4 = __values(rows), rows_4_1 = rows_4.next(); !rows_4_1.done; rows_4_1 = rows_4.next()) {
|
|
3100
|
+
var row = rows_4_1.value;
|
|
3101
|
+
_loop_14(row);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
catch (e_75_1) { e_75 = { error: e_75_1 }; }
|
|
3105
|
+
finally {
|
|
3106
|
+
try {
|
|
3107
|
+
if (rows_4_1 && !rows_4_1.done && (_a = rows_4.return)) _a.call(rows_4);
|
|
3108
|
+
}
|
|
3109
|
+
finally { if (e_75) throw e_75.error; }
|
|
3110
|
+
}
|
|
3111
|
+
var most = Math.max.apply(Math, __spreadArray([], __read(classes.map(function (cls) { return cls.rows.length; })), false));
|
|
3112
|
+
var out = [];
|
|
3113
|
+
try {
|
|
3114
|
+
for (var classes_1 = __values(classes), classes_1_1 = classes_1.next(); !classes_1_1.done; classes_1_1 = classes_1.next()) {
|
|
3115
|
+
var cls = classes_1_1.value;
|
|
3116
|
+
if (cls.rows.length < Math.max(2, most * 0.3))
|
|
3117
|
+
continue;
|
|
3118
|
+
var line = measureCustomLine(cls.rows, period, tol);
|
|
3119
|
+
if (!line)
|
|
3120
|
+
return undefined;
|
|
3121
|
+
var s = line.s, c = line.c, shift = line.shift, dashes = line.dashes;
|
|
3122
|
+
out.push({
|
|
3123
|
+
angle: theta / DEG,
|
|
3124
|
+
originX: ux * s - uy * c,
|
|
3125
|
+
originY: uy * s + ux * c,
|
|
3126
|
+
deltaX: shift,
|
|
3127
|
+
deltaY: period,
|
|
3128
|
+
dashes: dashes,
|
|
3129
|
+
sourceLine: 0
|
|
3130
|
+
});
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
catch (e_76_1) { e_76 = { error: e_76_1 }; }
|
|
3134
|
+
finally {
|
|
3135
|
+
try {
|
|
3136
|
+
if (classes_1_1 && !classes_1_1.done && (_b = classes_1.return)) _b.call(classes_1);
|
|
3137
|
+
}
|
|
3138
|
+
finally { if (e_76) throw e_76.error; }
|
|
3139
|
+
}
|
|
3140
|
+
return out.length > 0 ? out : undefined;
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Dash sequence, phase and stagger of one set of parallel lines one
|
|
3144
|
+
* spacing apart.
|
|
3145
|
+
*/
|
|
3146
|
+
function measureCustomLine(rows, period, tol) {
|
|
3147
|
+
var e_77, _a, e_78, _b, e_79, _c, e_80, _d;
|
|
3148
|
+
var _e, _f;
|
|
3149
|
+
var intervals = rows.map(function (row) { return mergeIntervals(row, tol); });
|
|
3150
|
+
// Repeat length along the lines, from the rows with several dashes.
|
|
3151
|
+
var repeats = [];
|
|
3152
|
+
try {
|
|
3153
|
+
for (var intervals_1 = __values(intervals), intervals_1_1 = intervals_1.next(); !intervals_1_1.done; intervals_1_1 = intervals_1.next()) {
|
|
3154
|
+
var list_2 = intervals_1_1.value;
|
|
3155
|
+
if (list_2.length < 3)
|
|
3156
|
+
continue;
|
|
3157
|
+
var along_1 = fundamentalPeriod(list_2.map(function (i) { return i.s0; }), tol);
|
|
3158
|
+
if (along_1)
|
|
3159
|
+
repeats.push(along_1);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
catch (e_77_1) { e_77 = { error: e_77_1 }; }
|
|
3163
|
+
finally {
|
|
3164
|
+
try {
|
|
3165
|
+
if (intervals_1_1 && !intervals_1_1.done && (_a = intervals_1.return)) _a.call(intervals_1);
|
|
3166
|
+
}
|
|
3167
|
+
finally { if (e_77) throw e_77.error; }
|
|
3168
|
+
}
|
|
3169
|
+
if (repeats.length === 0) {
|
|
3170
|
+
// Continuous lines; rows that break up must do so at holes, not at a
|
|
3171
|
+
// regular rhythm (no repeat was found).
|
|
3172
|
+
return { s: 0, c: rows[0].c, shift: 0, dashes: [] };
|
|
3173
|
+
}
|
|
3174
|
+
repeats.sort(function (p, q) { return p - q; });
|
|
3175
|
+
var along = repeats[Math.floor(repeats.length / 2)];
|
|
3176
|
+
// One full repeat from an inner dash of the row with the most dashes
|
|
3177
|
+
// (outer dashes may be cut by the boundary).
|
|
3178
|
+
var best = -1;
|
|
3179
|
+
intervals.forEach(function (list, i) {
|
|
3180
|
+
if (best < 0 || list.length > intervals[best].length)
|
|
3181
|
+
best = i;
|
|
3182
|
+
});
|
|
3183
|
+
var list = intervals[best];
|
|
3184
|
+
if (list.length < 3)
|
|
3185
|
+
return undefined;
|
|
3186
|
+
var first = list[1];
|
|
3187
|
+
var s = first.s0;
|
|
3188
|
+
var dashes = [];
|
|
3189
|
+
var cursor = s;
|
|
3190
|
+
try {
|
|
3191
|
+
for (var _g = __values(list.slice(1)), _h = _g.next(); !_h.done; _h = _g.next()) {
|
|
3192
|
+
var interval = _h.value;
|
|
3193
|
+
if (interval.s0 >= s + along - tol)
|
|
3194
|
+
break;
|
|
3195
|
+
if (interval.s0 > cursor + tol)
|
|
3196
|
+
dashes.push(-(interval.s0 - cursor));
|
|
3197
|
+
dashes.push(interval.s1 - interval.s0);
|
|
3198
|
+
cursor = interval.s1;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
catch (e_78_1) { e_78 = { error: e_78_1 }; }
|
|
3202
|
+
finally {
|
|
3203
|
+
try {
|
|
3204
|
+
if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
|
|
3205
|
+
}
|
|
3206
|
+
finally { if (e_78) throw e_78.error; }
|
|
3207
|
+
}
|
|
3208
|
+
if (s + along - cursor > tol)
|
|
3209
|
+
dashes.push(-(s + along - cursor));
|
|
3210
|
+
if (dashes.length < 2 || dashes[0] <= 0)
|
|
3211
|
+
return undefined;
|
|
3212
|
+
var total = dashes.reduce(function (sum, d) { return sum + Math.abs(d); }, 0);
|
|
3213
|
+
if (Math.abs(total - along) > tol * 4)
|
|
3214
|
+
return undefined;
|
|
3215
|
+
// Stagger: where the same dash starts on the next line, voted over every
|
|
3216
|
+
// pair of neighbouring lines.
|
|
3217
|
+
var dashLength = first.s1 - first.s0;
|
|
3218
|
+
var bin = tol * 2;
|
|
3219
|
+
var votes = new Map();
|
|
3220
|
+
for (var i = 0; i + 1 < rows.length; i++) {
|
|
3221
|
+
var k = Math.round((rows[i + 1].c - rows[i].c) / period);
|
|
3222
|
+
if (k !== 1)
|
|
3223
|
+
continue;
|
|
3224
|
+
var here = intervals[i].filter(function (v) { return Math.abs(v.s1 - v.s0 - dashLength) <= tol * 2; });
|
|
3225
|
+
var next = intervals[i + 1].filter(function (v) { return Math.abs(v.s1 - v.s0 - dashLength) <= tol * 2; });
|
|
3226
|
+
if (here.length === 0 || next.length === 0)
|
|
3227
|
+
continue;
|
|
3228
|
+
var ref = here[Math.floor(here.length / 2)].s0;
|
|
3229
|
+
try {
|
|
3230
|
+
for (var next_1 = (e_79 = void 0, __values(next)), next_1_1 = next_1.next(); !next_1_1.done; next_1_1 = next_1.next()) {
|
|
3231
|
+
var v = next_1_1.value;
|
|
3232
|
+
var shift_1 = (((v.s0 - ref) % along) + along) % along;
|
|
3233
|
+
var key = Math.round(shift_1 / bin);
|
|
3234
|
+
var listed = votes.get(key);
|
|
3235
|
+
if (listed)
|
|
3236
|
+
listed.push(shift_1);
|
|
3237
|
+
else
|
|
3238
|
+
votes.set(key, [shift_1]);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
catch (e_79_1) { e_79 = { error: e_79_1 }; }
|
|
3242
|
+
finally {
|
|
3243
|
+
try {
|
|
3244
|
+
if (next_1_1 && !next_1_1.done && (_c = next_1.return)) _c.call(next_1);
|
|
3245
|
+
}
|
|
3246
|
+
finally { if (e_79) throw e_79.error; }
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
var shift = 0;
|
|
3250
|
+
var count = 0;
|
|
3251
|
+
try {
|
|
3252
|
+
for (var votes_1 = __values(votes), votes_1_1 = votes_1.next(); !votes_1_1.done; votes_1_1 = votes_1.next()) {
|
|
3253
|
+
var _j = __read(votes_1_1.value, 2), key = _j[0], values = _j[1];
|
|
3254
|
+
// Bins at both ends of the repeat are one value split by the wrap.
|
|
3255
|
+
var merged = __spreadArray(__spreadArray(__spreadArray([], __read(values), false), __read(((_e = votes.get(key + 1)) !== null && _e !== void 0 ? _e : [])), false), __read(((_f = votes.get(key - 1)) !== null && _f !== void 0 ? _f : [])), false);
|
|
3256
|
+
if (merged.length > count) {
|
|
3257
|
+
count = merged.length;
|
|
3258
|
+
var mean = values.reduce(function (sum, v) { return sum + v; }, 0) / values.length;
|
|
3259
|
+
shift = mean;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
catch (e_80_1) { e_80 = { error: e_80_1 }; }
|
|
3264
|
+
finally {
|
|
3265
|
+
try {
|
|
3266
|
+
if (votes_1_1 && !votes_1_1.done && (_d = votes_1.return)) _d.call(votes_1);
|
|
3267
|
+
}
|
|
3268
|
+
finally { if (e_80) throw e_80.error; }
|
|
3269
|
+
}
|
|
3270
|
+
return { s: s, c: rows[best].c, shift: shift, dashes: dashes };
|
|
3271
|
+
}
|
|
3272
|
+
/** Collinear pieces of one row joined where they touch. */
|
|
3273
|
+
function mergeIntervals(row, tol) {
|
|
3274
|
+
var e_81, _a;
|
|
3275
|
+
var out = [];
|
|
3276
|
+
try {
|
|
3277
|
+
for (var _b = __values(row.segs), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
3278
|
+
var entry = _c.value;
|
|
3279
|
+
var last = out[out.length - 1];
|
|
3280
|
+
if (last && entry.s0 <= last.s1 + tol) {
|
|
3281
|
+
last.s1 = Math.max(last.s1, entry.s1);
|
|
3282
|
+
}
|
|
3283
|
+
else {
|
|
3284
|
+
out.push({ s0: entry.s0, s1: entry.s1 });
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
catch (e_81_1) { e_81 = { error: e_81_1 }; }
|
|
3289
|
+
finally {
|
|
3290
|
+
try {
|
|
3291
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
3292
|
+
}
|
|
3293
|
+
finally { if (e_81) throw e_81.error; }
|
|
3294
|
+
}
|
|
3295
|
+
return out;
|
|
3296
|
+
}
|
|
3297
|
+
/** Most strokes an edited hatch may draw (a far too small scale). */
|
|
3298
|
+
var MAX_EDIT_STROKES = 200000;
|
|
3299
|
+
var NO_MATCH = { precision: 0, recall: 0, items: [] };
|
|
3300
|
+
/** Direction slack for short stroke pieces (radians). */
|
|
3301
|
+
var SHORT_ANGLE_GAP = (15 * Math.PI) / 180;
|
|
3302
|
+
/** Fewest line repeats a family spans to count as a pattern, not walls. */
|
|
3303
|
+
var MIN_PATTERN_REPEATS = 4;
|
|
3304
|
+
function editRegion(items, boundary, tol) {
|
|
3305
|
+
var e_82, _a;
|
|
3306
|
+
var _b, _c;
|
|
3307
|
+
var polygons = boundary
|
|
3308
|
+
.map(shapeOutline)
|
|
3309
|
+
.filter(function (outline) { return !!outline; });
|
|
3310
|
+
if (polygons.length === 0)
|
|
3311
|
+
return undefined;
|
|
3312
|
+
var best;
|
|
3313
|
+
try {
|
|
3314
|
+
for (var _d = __values(groupCandidates(items, tol)), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
3315
|
+
var group = _e.value;
|
|
3316
|
+
var region = segsInside(group.all, polygons).filter(function (seg) { return !polygons.some(function (outline) { return onOutline(midpoint(seg), outline, tol); }); });
|
|
3317
|
+
var length_1 = region.reduce(function (sum, seg) { return sum + seg.len; }, 0);
|
|
3318
|
+
if (length_1 > 0 && (!best || length_1 > best.length)) {
|
|
3319
|
+
best = { region: region, all: group.all, length: length_1 };
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
catch (e_82_1) { e_82 = { error: e_82_1 }; }
|
|
3324
|
+
finally {
|
|
3325
|
+
try {
|
|
3326
|
+
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
3327
|
+
}
|
|
3328
|
+
finally { if (e_82) throw e_82.error; }
|
|
3329
|
+
}
|
|
3330
|
+
var box = pointsBox(polygons.flat());
|
|
3331
|
+
return {
|
|
3332
|
+
shapes: __spreadArray([], __read(boundary), false),
|
|
3333
|
+
polygons: polygons,
|
|
3334
|
+
region: (_b = best === null || best === void 0 ? void 0 : best.region) !== null && _b !== void 0 ? _b : [],
|
|
3335
|
+
all: (_c = best === null || best === void 0 ? void 0 : best.all) !== null && _c !== void 0 ? _c : [],
|
|
3336
|
+
extent: Math.hypot(box.maxX - box.minX, box.maxY - box.minY)
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
/** Rough count of the strokes `lines` draw across a region of `extent`. */
|
|
3340
|
+
function strokeEstimate(lines, extent) {
|
|
3341
|
+
var e_83, _a;
|
|
3342
|
+
var count = 0;
|
|
3343
|
+
try {
|
|
3344
|
+
for (var lines_4 = __values(lines), lines_4_1 = lines_4.next(); !lines_4_1.done; lines_4_1 = lines_4.next()) {
|
|
3345
|
+
var line = lines_4_1.value;
|
|
3346
|
+
var frame = lineFrame(line);
|
|
3347
|
+
var spacing = Math.abs(frame.spacing);
|
|
3348
|
+
if (!(spacing > 0))
|
|
3349
|
+
return Infinity;
|
|
3350
|
+
var rows = extent / spacing + 1;
|
|
3351
|
+
var perRow = frame.dashes.length > 0 && frame.period > 0
|
|
3352
|
+
? (extent / frame.period + 1) * frame.dashes.length
|
|
3353
|
+
: 1;
|
|
3354
|
+
count += rows * perRow;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
catch (e_83_1) { e_83 = { error: e_83_1 }; }
|
|
3358
|
+
finally {
|
|
3359
|
+
try {
|
|
3360
|
+
if (lines_4_1 && !lines_4_1.done && (_a = lines_4.return)) _a.call(lines_4);
|
|
3361
|
+
}
|
|
3362
|
+
finally { if (e_83) throw e_83.error; }
|
|
3363
|
+
}
|
|
3364
|
+
return count;
|
|
3365
|
+
}
|
|
3366
|
+
function matchIn(edit, lines, tol) {
|
|
3367
|
+
if (lines.length === 0)
|
|
3368
|
+
return NO_MATCH;
|
|
3369
|
+
if (strokeEstimate(lines, edit.extent) > MAX_EDIT_STROKES)
|
|
3370
|
+
return NO_MATCH;
|
|
3371
|
+
var generated = generateStrokes(lines, edit.polygons, tol);
|
|
3372
|
+
return measureMatch(lines, generated, edit.region, edit.all, tol);
|
|
3373
|
+
}
|
|
3374
|
+
/** Harmonic mean of precision and recall. */
|
|
3375
|
+
function matchQuality(match) {
|
|
3376
|
+
var sum = match.precision + match.recall;
|
|
3377
|
+
return sum > 0 ? (2 * match.precision * match.recall) / sum : 0;
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Scores a hatch against the strokes inside its boundary.
|
|
3381
|
+
*
|
|
3382
|
+
* @param items - The shape items the hatch was recognized from.
|
|
3383
|
+
* @param hatch - Boundary and definition lines to score.
|
|
3384
|
+
* @param tolerance - Tolerance the hatch was recognized with.
|
|
3385
|
+
*/
|
|
3386
|
+
export function acapPdfMatchHatch(items, hatch, tolerance) {
|
|
3387
|
+
var edit = editRegion(items, hatch.boundary, tolerance);
|
|
3388
|
+
return edit ? matchIn(edit, hatch.definitionLines, tolerance) : NO_MATCH;
|
|
3389
|
+
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Fits a pattern to the strokes inside `boundary`.
|
|
3392
|
+
*
|
|
3393
|
+
* Without `request` every library pattern, user-defined lines and (unless
|
|
3394
|
+
* turned off) a measured custom pattern compete, as in the recognition.
|
|
3395
|
+
* With a pattern its size, angle and origin are measured; with size and
|
|
3396
|
+
* angle given as well only the origin is.
|
|
3397
|
+
*
|
|
3398
|
+
* @param items - Shape items (from `pdfEntitiesToHatchInput`).
|
|
3399
|
+
* @param boundary - Closed shapes; the hatch fills them even-odd.
|
|
3400
|
+
* @returns The best placement, or `undefined` when the boundary has no
|
|
3401
|
+
* closed shape or the pattern name is unknown.
|
|
3402
|
+
*/
|
|
3403
|
+
export function acapPdfFitHatch(items, boundary, request, options) {
|
|
3404
|
+
var e_84, _a;
|
|
3405
|
+
var _b;
|
|
3406
|
+
var tol = options.tolerance;
|
|
3407
|
+
var edit = editRegion(items, boundary, tol);
|
|
3408
|
+
if (!edit)
|
|
3409
|
+
return undefined;
|
|
3410
|
+
var families = edit.region.length > 0 ? measureFamilies(edit.region, tol) : [];
|
|
3411
|
+
var library = (_b = options.patterns) !== null && _b !== void 0 ? _b : AcDbPredefinedAcadIsoPat.patterns;
|
|
3412
|
+
var hypotheses = fitHypotheses(request, families, library, tol, edit);
|
|
3413
|
+
var placed = hypotheses.map(function (_a) {
|
|
3414
|
+
var hyp = _a.hyp, guessed = _a.guessed;
|
|
3415
|
+
return (__assign({ hyp: hyp, guessed: guessed, segs: hypothesisSegs(hyp) }, placeHypothesis(hyp, tol)));
|
|
3416
|
+
});
|
|
3417
|
+
placed.sort(function (p, q) { return compareScored(p, q, Math.max(edit.region.length, 1)); });
|
|
3418
|
+
var tries = placed.slice(0, MAX_VERIFIED);
|
|
3419
|
+
// A measured pattern is the last resort, as in the recognition.
|
|
3420
|
+
if (!request && options.customPatterns !== false) {
|
|
3421
|
+
var opts = resolveOptions({ tolerance: tol, customPatterns: true });
|
|
3422
|
+
var custom = customHypothesis(families, opts);
|
|
3423
|
+
if (custom) {
|
|
3424
|
+
tries.push(__assign({ hyp: custom, guessed: false, segs: hypothesisSegs(custom) }, placeHypothesis(custom, tol)));
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
var best;
|
|
3428
|
+
var bestMatch = NO_MATCH;
|
|
3429
|
+
try {
|
|
3430
|
+
for (var tries_2 = __values(tries), tries_2_1 = tries_2.next(); !tries_2_1.done; tries_2_1 = tries_2.next()) {
|
|
3431
|
+
var entry = tries_2_1.value;
|
|
3432
|
+
var match = matchIn(edit, entry.lines, tol);
|
|
3433
|
+
// Earlier entries win ties: the order prefers simpler explanations.
|
|
3434
|
+
if (!best || matchQuality(match) > matchQuality(bestMatch) + 1e-3) {
|
|
3435
|
+
best = entry;
|
|
3436
|
+
bestMatch = match;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
catch (e_84_1) { e_84 = { error: e_84_1 }; }
|
|
3441
|
+
finally {
|
|
3442
|
+
try {
|
|
3443
|
+
if (tries_2_1 && !tries_2_1.done && (_a = tries_2.return)) _a.call(tries_2);
|
|
3444
|
+
}
|
|
3445
|
+
finally { if (e_84) throw e_84.error; }
|
|
3446
|
+
}
|
|
3447
|
+
if (!best)
|
|
3448
|
+
return undefined;
|
|
3449
|
+
return {
|
|
3450
|
+
hatch: {
|
|
3451
|
+
boundary: edit.shapes,
|
|
3452
|
+
pattern: hatchPattern(best.hyp),
|
|
3453
|
+
definitionLines: best.lines,
|
|
3454
|
+
items: bestMatch.items
|
|
3455
|
+
},
|
|
3456
|
+
match: bestMatch,
|
|
3457
|
+
fitted: !best.guessed
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
/** Definition lines of a hypothesis placed on its strokes, with a score. */
|
|
3461
|
+
function placeHypothesis(hyp, tol) {
|
|
3462
|
+
var _a, _b, _c;
|
|
3463
|
+
// Measured custom lines are already in place.
|
|
3464
|
+
if (hyp.custom) {
|
|
3465
|
+
return { lines: definitionLines(hyp, { x: 0, y: 0 }), score: 1 };
|
|
3466
|
+
}
|
|
3467
|
+
var segs = hypothesisSegs(hyp);
|
|
3468
|
+
if (segs.length === 0) {
|
|
3469
|
+
return { lines: definitionLines(hyp, { x: 0, y: 0 }), score: 0 };
|
|
3470
|
+
}
|
|
3471
|
+
var one = estimateTranslation(hyp, segs, tol);
|
|
3472
|
+
if (one && one.score >= MIN_SCORE) {
|
|
3473
|
+
return { lines: definitionLines(hyp, one.t), score: one.score };
|
|
3474
|
+
}
|
|
3475
|
+
var separate = placeLinesSeparately(hyp, segs, tol);
|
|
3476
|
+
if (separate && separate.score > ((_a = one === null || one === void 0 ? void 0 : one.score) !== null && _a !== void 0 ? _a : 0))
|
|
3477
|
+
return separate;
|
|
3478
|
+
return {
|
|
3479
|
+
lines: definitionLines(hyp, (_b = one === null || one === void 0 ? void 0 : one.t) !== null && _b !== void 0 ? _b : { x: 0, y: 0 }),
|
|
3480
|
+
score: (_c = one === null || one === void 0 ? void 0 : one.score) !== null && _c !== void 0 ? _c : 0
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
/** Families with each of the first few as the anchor of a match. */
|
|
3484
|
+
function anchoredFamilies(families) {
|
|
3485
|
+
return families
|
|
3486
|
+
.slice(0, 4)
|
|
3487
|
+
.map(function (anchor) { return __spreadArray([anchor], __read(families.filter(function (f) { return f !== anchor; })), false); });
|
|
3488
|
+
}
|
|
3489
|
+
function fitHypotheses(request, families, library, tol, edit) {
|
|
3490
|
+
var e_85, _a, e_86, _b, e_87, _c;
|
|
3491
|
+
var _d, _e;
|
|
3492
|
+
var extent = edit.extent;
|
|
3493
|
+
var anchored = anchoredFamilies(families);
|
|
3494
|
+
var found = function (hyps) {
|
|
3495
|
+
return hyps.map(function (hyp) { return ({ hyp: hyp, guessed: false }); });
|
|
3496
|
+
};
|
|
3497
|
+
if (!request) {
|
|
3498
|
+
var hyps_1 = [];
|
|
3499
|
+
try {
|
|
3500
|
+
for (var _f = __values(patternSignatures(library)), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
3501
|
+
var sig_1 = _g.value;
|
|
3502
|
+
try {
|
|
3503
|
+
for (var anchored_1 = (e_86 = void 0, __values(anchored)), anchored_1_1 = anchored_1.next(); !anchored_1_1.done; anchored_1_1 = anchored_1.next()) {
|
|
3504
|
+
var list = anchored_1_1.value;
|
|
3505
|
+
hyps_1.push.apply(hyps_1, __spreadArray([], __read(matchPattern(sig_1, list, tol, extent)), false));
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
catch (e_86_1) { e_86 = { error: e_86_1 }; }
|
|
3509
|
+
finally {
|
|
3510
|
+
try {
|
|
3511
|
+
if (anchored_1_1 && !anchored_1_1.done && (_b = anchored_1.return)) _b.call(anchored_1);
|
|
3512
|
+
}
|
|
3513
|
+
finally { if (e_86) throw e_86.error; }
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
catch (e_85_1) { e_85 = { error: e_85_1 }; }
|
|
3518
|
+
finally {
|
|
3519
|
+
try {
|
|
3520
|
+
if (_g && !_g.done && (_a = _f.return)) _a.call(_f);
|
|
3521
|
+
}
|
|
3522
|
+
finally { if (e_85) throw e_85.error; }
|
|
3523
|
+
}
|
|
3524
|
+
try {
|
|
3525
|
+
for (var anchored_2 = __values(anchored), anchored_2_1 = anchored_2.next(); !anchored_2_1.done; anchored_2_1 = anchored_2.next()) {
|
|
3526
|
+
var list = anchored_2_1.value;
|
|
3527
|
+
hyps_1.push.apply(hyps_1, __spreadArray([], __read(userDefinedHypotheses(list, tol, extent)), false));
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
catch (e_87_1) { e_87 = { error: e_87_1 }; }
|
|
3531
|
+
finally {
|
|
3532
|
+
try {
|
|
3533
|
+
if (anchored_2_1 && !anchored_2_1.done && (_c = anchored_2.return)) _c.call(anchored_2);
|
|
3534
|
+
}
|
|
3535
|
+
finally { if (e_87) throw e_87.error; }
|
|
3536
|
+
}
|
|
3537
|
+
return found(hyps_1);
|
|
3538
|
+
}
|
|
3539
|
+
if (request.type === 'userDefined') {
|
|
3540
|
+
var make_1 = function (angle, spacing) {
|
|
3541
|
+
return fixedHypothesis(request.double
|
|
3542
|
+
? [userDefinedLine(0), userDefinedLine(90)]
|
|
3543
|
+
: [userDefinedLine(0)], angle, spacing, families, { order: Infinity, userDefined: { double: request.double, spacing: spacing } });
|
|
3544
|
+
};
|
|
3545
|
+
if (request.spacing !== undefined && request.angle !== undefined) {
|
|
3546
|
+
return [{ hyp: make_1(request.angle, request.spacing), guessed: false }];
|
|
3547
|
+
}
|
|
3548
|
+
var hyps_2 = anchored
|
|
3549
|
+
.flatMap(function (list) { return userDefinedHypotheses(list, tol, extent); })
|
|
3550
|
+
.filter(function (hyp) { var _a; return ((_a = hyp.userDefined) === null || _a === void 0 ? void 0 : _a.double) === request.double; });
|
|
3551
|
+
if (hyps_2.length > 0)
|
|
3552
|
+
return found(hyps_2);
|
|
3553
|
+
var main_1 = families[0];
|
|
3554
|
+
var spacing = (_d = main_1 === null || main_1 === void 0 ? void 0 : main_1.period) !== null && _d !== void 0 ? _d : Math.max(extent / 20, tol);
|
|
3555
|
+
return [{ hyp: make_1((_e = main_1 === null || main_1 === void 0 ? void 0 : main_1.theta) !== null && _e !== void 0 ? _e : 0, spacing), guessed: true }];
|
|
3556
|
+
}
|
|
3557
|
+
var order = library.findIndex(function (p) { return p.name === request.name; });
|
|
3558
|
+
if (order < 0)
|
|
3559
|
+
return [];
|
|
3560
|
+
var pattern = library[order];
|
|
3561
|
+
var make = function (angle, scale) {
|
|
3562
|
+
return fixedHypothesis(pattern.lines, angle, scale, families, {
|
|
3563
|
+
name: pattern.name,
|
|
3564
|
+
order: order
|
|
3565
|
+
});
|
|
3566
|
+
};
|
|
3567
|
+
if (request.scale !== undefined && request.angle !== undefined) {
|
|
3568
|
+
return [{ hyp: make(request.angle, request.scale), guessed: false }];
|
|
3569
|
+
}
|
|
3570
|
+
var sig = patternSignatures([pattern])[0];
|
|
3571
|
+
var hyps = sig
|
|
3572
|
+
? anchored.flatMap(function (list) {
|
|
3573
|
+
return matchPattern(__assign(__assign({}, sig), { order: order }), list, tol, extent);
|
|
3574
|
+
})
|
|
3575
|
+
: [];
|
|
3576
|
+
if (hyps.length > 0)
|
|
3577
|
+
return found(hyps);
|
|
3578
|
+
// No trace of the pattern: size its first group on the main family.
|
|
3579
|
+
var group = sig === null || sig === void 0 ? void 0 : sig.groups.find(function (g) { return !g.dotsOnly; });
|
|
3580
|
+
var main = families[0];
|
|
3581
|
+
var scale = group && main ? main.period / group.period : 1;
|
|
3582
|
+
var angle = group && main ? main.theta - group.angle : 0;
|
|
3583
|
+
return [{ hyp: make(angle, scale), guessed: true }];
|
|
3584
|
+
}
|
|
3585
|
+
/** A hypothesis at a given rotation and scale, its lines on the families. */
|
|
3586
|
+
function fixedHypothesis(lines, rotation, scale, families, extra) {
|
|
3587
|
+
return __assign({ rotation: rotation, scale: scale, lines: lines, families: lines.map(function (line) {
|
|
3588
|
+
if (isDotsOnly(line.dashes))
|
|
3589
|
+
return undefined;
|
|
3590
|
+
var direction = line.angle * DEG + rotation;
|
|
3591
|
+
return families.find(function (f) { return directionDelta(f.theta, direction) < ANGLE_GAP; });
|
|
3592
|
+
}) }, extra);
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Boundary of the area around `point` that the selected line work encloses,
|
|
3596
|
+
* with the strokes of regular patterns left out (they would cut the area
|
|
3597
|
+
* into strips). Islands inside the area belong to it.
|
|
3598
|
+
*
|
|
3599
|
+
* @param items - Shape items (from `pdfEntitiesToHatchInput`).
|
|
3600
|
+
* @param point - A point inside the area, in drawing coordinates.
|
|
3601
|
+
* @param tolerance - Snap tolerance for the line work.
|
|
3602
|
+
* @returns Closed boundary shapes, or `undefined` when no closed area
|
|
3603
|
+
* holds the point.
|
|
3604
|
+
*/
|
|
3605
|
+
export function acapPdfBoundaryAt(items, point, tolerance) {
|
|
3606
|
+
var e_88, _a, e_89, _b, e_90, _c;
|
|
3607
|
+
var opts = resolveOptions({ tolerance: tolerance });
|
|
3608
|
+
var strokes = new Set();
|
|
3609
|
+
var candidates = new Map();
|
|
3610
|
+
try {
|
|
3611
|
+
for (var _d = __values(groupCandidates(items, tolerance)), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
3612
|
+
var group = _e.value;
|
|
3613
|
+
try {
|
|
3614
|
+
for (var _f = (e_89 = void 0, __values(group.all)), _g = _f.next(); !_g.done; _g = _f.next()) {
|
|
3615
|
+
var seg = _g.value;
|
|
3616
|
+
candidates.set(seg.index, seg);
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
catch (e_89_1) { e_89 = { error: e_89_1 }; }
|
|
3620
|
+
finally {
|
|
3621
|
+
try {
|
|
3622
|
+
if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
|
|
3623
|
+
}
|
|
3624
|
+
finally { if (e_89) throw e_89.error; }
|
|
3625
|
+
}
|
|
3626
|
+
var _loop_15 = function (region) {
|
|
3627
|
+
var e_91, _k, e_92, _l, e_93, _m;
|
|
3628
|
+
// Only the regular, dense families: walls drawn like the strokes join
|
|
3629
|
+
// the region too, and a few evenly spaced walls (a room's sides) also
|
|
3630
|
+
// form a family, but one only a couple of repeats deep.
|
|
3631
|
+
var measured = measureFamilies(region, tolerance);
|
|
3632
|
+
var main = measured[0];
|
|
3633
|
+
var families = measured.filter(function (family) {
|
|
3634
|
+
return family.offsets.length >= opts.minLines &&
|
|
3635
|
+
family.offsets[family.offsets.length - 1] - family.offsets[0] >=
|
|
3636
|
+
family.period * MIN_PATTERN_REPEATS &&
|
|
3637
|
+
(family === main ||
|
|
3638
|
+
family.segs.length >= Math.max(4, main.segs.length * 0.3));
|
|
3639
|
+
});
|
|
3640
|
+
try {
|
|
3641
|
+
for (var families_1 = (e_91 = void 0, __values(families)), families_1_1 = families_1.next(); !families_1_1.done; families_1_1 = families_1.next()) {
|
|
3642
|
+
var family = families_1_1.value;
|
|
3643
|
+
try {
|
|
3644
|
+
for (var _o = (e_92 = void 0, __values(family.segs)), _p = _o.next(); !_p.done; _p = _o.next()) {
|
|
3645
|
+
var seg = _p.value;
|
|
3646
|
+
strokes.add(seg.index);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
catch (e_92_1) { e_92 = { error: e_92_1 }; }
|
|
3650
|
+
finally {
|
|
3651
|
+
try {
|
|
3652
|
+
if (_p && !_p.done && (_l = _o.return)) _l.call(_o);
|
|
3653
|
+
}
|
|
3654
|
+
finally { if (e_92) throw e_92.error; }
|
|
3655
|
+
}
|
|
3656
|
+
// Strokes clipped short at the boundary (or left out of the region)
|
|
3657
|
+
// still lie on the family's lines; between two walls they would cut
|
|
3658
|
+
// the area into strips. Short pieces have a rough direction, but the
|
|
3659
|
+
// tiny steps of an outline seldom run along the pattern.
|
|
3660
|
+
var box = segsBox(family.segs);
|
|
3661
|
+
var pad = family.period + tolerance;
|
|
3662
|
+
var longest = Math.max.apply(Math, __spreadArray([], __read(family.segs.map(function (seg) { return seg.len; })), false));
|
|
3663
|
+
try {
|
|
3664
|
+
for (var _q = (e_93 = void 0, __values(group.all)), _r = _q.next(); !_r.done; _r = _q.next()) {
|
|
3665
|
+
var seg = _r.value;
|
|
3666
|
+
if (strokes.has(seg.index))
|
|
3667
|
+
continue;
|
|
3668
|
+
if (seg.len > longest + tolerance)
|
|
3669
|
+
continue;
|
|
3670
|
+
var angleTol = seg.len >= tolerance * SHORT_FACTOR ? ANGLE_GAP : SHORT_ANGLE_GAP;
|
|
3671
|
+
if (directionDelta(seg.theta, family.theta) > angleTol)
|
|
3672
|
+
continue;
|
|
3673
|
+
var m = midpoint(seg);
|
|
3674
|
+
if (m.x < box.minX - pad ||
|
|
3675
|
+
m.x > box.maxX + pad ||
|
|
3676
|
+
m.y < box.minY - pad ||
|
|
3677
|
+
m.y > box.maxY + pad) {
|
|
3678
|
+
continue;
|
|
3679
|
+
}
|
|
3680
|
+
if (onFamilyLattice(m, family, tolerance))
|
|
3681
|
+
strokes.add(seg.index);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
catch (e_93_1) { e_93 = { error: e_93_1 }; }
|
|
3685
|
+
finally {
|
|
3686
|
+
try {
|
|
3687
|
+
if (_r && !_r.done && (_m = _q.return)) _m.call(_q);
|
|
3688
|
+
}
|
|
3689
|
+
finally { if (e_93) throw e_93.error; }
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
catch (e_91_1) { e_91 = { error: e_91_1 }; }
|
|
3694
|
+
finally {
|
|
3695
|
+
try {
|
|
3696
|
+
if (families_1_1 && !families_1_1.done && (_k = families_1.return)) _k.call(families_1);
|
|
3697
|
+
}
|
|
3698
|
+
finally { if (e_91) throw e_91.error; }
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
try {
|
|
3702
|
+
for (var _h = (e_90 = void 0, __values(findRegions(group.long, opts))), _j = _h.next(); !_j.done; _j = _h.next()) {
|
|
3703
|
+
var region = _j.value;
|
|
3704
|
+
_loop_15(region);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
catch (e_90_1) { e_90 = { error: e_90_1 }; }
|
|
3708
|
+
finally {
|
|
3709
|
+
try {
|
|
3710
|
+
if (_j && !_j.done && (_c = _h.return)) _c.call(_h);
|
|
3711
|
+
}
|
|
3712
|
+
finally { if (e_90) throw e_90.error; }
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
catch (e_88_1) { e_88 = { error: e_88_1 }; }
|
|
3717
|
+
finally {
|
|
3718
|
+
try {
|
|
3719
|
+
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
|
|
3720
|
+
}
|
|
3721
|
+
finally { if (e_88) throw e_88.error; }
|
|
3722
|
+
}
|
|
3723
|
+
var polylines = [];
|
|
3724
|
+
items.forEach(function (item, index) {
|
|
3725
|
+
if (strokes.has(index))
|
|
3726
|
+
return;
|
|
3727
|
+
var polyline = acapPdfShapePolyline(item.shape, tolerance * 0.25);
|
|
3728
|
+
if (polyline.length >= 2)
|
|
3729
|
+
polylines.push(polyline);
|
|
3730
|
+
});
|
|
3731
|
+
var arrangement = acapPdfFaceArrangement(polylines, {
|
|
3732
|
+
tolerance: tolerance,
|
|
3733
|
+
maxSegments: MAX_FACE_SEGMENTS
|
|
3734
|
+
});
|
|
3735
|
+
if (!arrangement)
|
|
3736
|
+
return undefined;
|
|
3737
|
+
// Faces split by a line drawn across a hatch (an axis) are one area where
|
|
3738
|
+
// strokes run over the line; areas whose strokes stop on it stay apart.
|
|
3739
|
+
var strokeSegs = __spreadArray([], __read(strokes), false).map(function (index) { return candidates.get(index); })
|
|
3740
|
+
.filter(function (seg) { return !!seg; });
|
|
3741
|
+
var parts = acapPdfFaceBoundaryParts(arrangement, __spreadArray([point], __read(strokeSegs.map(midpoint)), false), { minCoverage: 0, strokes: strokeSegs.map(function (seg) { return [seg.a, seg.b]; }) });
|
|
3742
|
+
var part = parts === null || parts === void 0 ? void 0 : parts.find(function (loops) { return insideLoops(point, loops); });
|
|
3743
|
+
var polygons = part !== null && part !== void 0 ? part : acapPdfFaceBoundaryIn(arrangement, [point], {
|
|
3744
|
+
minCoverage: 1
|
|
3745
|
+
});
|
|
3746
|
+
return polygons && polygons.length > 0
|
|
3747
|
+
? polygonsToBoundary(polygons).shapes
|
|
3748
|
+
: undefined;
|
|
3749
|
+
}
|
|
3750
|
+
/** Whether `p` lies inside `loops` (even-odd). */
|
|
3751
|
+
function insideLoops(p, loops) {
|
|
3752
|
+
var e_94, _a;
|
|
3753
|
+
var inside = false;
|
|
3754
|
+
try {
|
|
3755
|
+
for (var loops_1 = __values(loops), loops_1_1 = loops_1.next(); !loops_1_1.done; loops_1_1 = loops_1.next()) {
|
|
3756
|
+
var loop = loops_1_1.value;
|
|
3757
|
+
if (pointInPolygon(p, loop))
|
|
3758
|
+
inside = !inside;
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
catch (e_94_1) { e_94 = { error: e_94_1 }; }
|
|
3762
|
+
finally {
|
|
3763
|
+
try {
|
|
3764
|
+
if (loops_1_1 && !loops_1_1.done && (_a = loops_1.return)) _a.call(loops_1);
|
|
3765
|
+
}
|
|
3766
|
+
finally { if (e_94) throw e_94.error; }
|
|
3767
|
+
}
|
|
3768
|
+
return inside;
|
|
3769
|
+
}
|
|
3770
|
+
/** True when `p` lies on one of the lines of `family` or their repeats. */
|
|
3771
|
+
function onFamilyLattice(p, family, tol) {
|
|
3772
|
+
var ux = Math.cos(family.theta);
|
|
3773
|
+
var uy = Math.sin(family.theta);
|
|
3774
|
+
var c = -uy * p.x + ux * p.y;
|
|
3775
|
+
var period = family.period;
|
|
3776
|
+
return family.offsets.some(function (offset) {
|
|
3777
|
+
var r = (((c - offset) % period) + period) % period;
|
|
3778
|
+
return Math.min(r, period - r) <= tol;
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
//# sourceMappingURL=pdfHatchRecognition.js.map
|