@embedpdf/plugin-measurement 3.0.0-next.14 → 3.0.0-next.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -43
- package/dist/contract-BGn30CU9.d.cts +185 -0
- package/dist/contract-BHj3w9WF.cjs +178 -0
- package/dist/contract-BHj3w9WF.cjs.map +1 -0
- package/dist/contract-CSnWNeXI.d.ts +185 -0
- package/dist/contract-DyqqYfyE.js +131 -0
- package/dist/contract-DyqqYfyE.js.map +1 -0
- package/dist/contract.cjs +4 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/index.cjs +554 -396
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +553 -399
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/contract-BnLRuw-Z.d.cts +0 -101
- package/dist/contract-C_ugpRGJ.d.ts +0 -101
- package/dist/contract-DO0emN-T.js +0 -7
- package/dist/contract-DO0emN-T.js.map +0 -1
- package/dist/contract-Dy7X9nSj.cjs +0 -11
- package/dist/contract-Dy7X9nSj.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,469 +1,623 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { definePlugin } from "@embedpdf/core";
|
|
1
|
+
import { a as defaultMeasure, c as withUnit, i as MeasurementToken, n as NO_PAGE_SCALE, o as withAreaUnit, r as NO_READOUT, s as withPrecision, t as MEASUREMENT_DEFAULTS } from "./contract-DyqqYfyE.js";
|
|
2
|
+
import { PluginError, composeApi, definePlugin, defineState, memoByKey, toPageRef, toPluginError } from "@embedpdf/core";
|
|
3
3
|
import { AnnotationToken } from "@embedpdf/plugin-annotation/contract/host";
|
|
4
4
|
import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
...
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
20,
|
|
13
|
-
50,
|
|
14
|
-
100,
|
|
15
|
-
200
|
|
16
|
-
].map((real) => ({
|
|
17
|
-
id: `metric-${real}`,
|
|
18
|
-
label: `1:${real}`,
|
|
19
|
-
paper: 1,
|
|
20
|
-
real,
|
|
21
|
-
unit: "m"
|
|
22
|
-
})),
|
|
23
|
-
{
|
|
24
|
-
id: "imperial-quarter",
|
|
25
|
-
label: "1/4 in = 1 ft",
|
|
26
|
-
paper: 1,
|
|
27
|
-
real: 48,
|
|
28
|
-
unit: "ft"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: "imperial-eighth",
|
|
32
|
-
label: "1/8 in = 1 ft",
|
|
33
|
-
paper: 1,
|
|
34
|
-
real: 96,
|
|
35
|
-
unit: "ft"
|
|
36
|
-
}
|
|
5
|
+
import { METRES, assertWritableMeasure, measureFromKnownLength, measureFromRatio, measurementPoint, measurementReadout, pageRefsEqual, serializeError, squareOf, viewportForPoint } from "@embedpdf/engine-core/runtime";
|
|
6
|
+
//#region src/read/scale.ts
|
|
7
|
+
const UNITS = Object.keys(METRES);
|
|
8
|
+
const AREA_UNITS = [
|
|
9
|
+
...UNITS.map(squareOf),
|
|
10
|
+
"ha",
|
|
11
|
+
"acre"
|
|
37
12
|
];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
function createScaleReads(ctx, { siblings }, { scaleOf }) {
|
|
14
|
+
const { annotation } = siblings;
|
|
15
|
+
const settings = ctx.settings();
|
|
16
|
+
const state = () => ctx.state.get();
|
|
17
|
+
const readouts = /* @__PURE__ */ new WeakMap();
|
|
18
|
+
const getReadout = (ref) => {
|
|
19
|
+
const raw = annotation.get(ref);
|
|
20
|
+
if (!raw) return NO_READOUT;
|
|
21
|
+
let readout = readouts.get(raw);
|
|
22
|
+
if (!readout) {
|
|
23
|
+
readout = measurementReadout(raw);
|
|
24
|
+
readouts.set(raw, readout);
|
|
25
|
+
}
|
|
26
|
+
return readout;
|
|
27
|
+
};
|
|
28
|
+
const measureDistance = (page, from, to) => measurementReadout({
|
|
29
|
+
subtype: "line",
|
|
30
|
+
intent: "line-dimension",
|
|
31
|
+
measure: scaleOf(page).measure,
|
|
32
|
+
linePoints: {
|
|
33
|
+
start: from,
|
|
34
|
+
end: to
|
|
35
|
+
}
|
|
43
36
|
});
|
|
37
|
+
const measureArea = (page, vertices) => measurementReadout({
|
|
38
|
+
subtype: "polygon",
|
|
39
|
+
intent: "polygon-dimension",
|
|
40
|
+
measure: scaleOf(page).measure,
|
|
41
|
+
vertices: [...vertices]
|
|
42
|
+
});
|
|
43
|
+
return { api: {
|
|
44
|
+
canMeasure: (page) => ctx.allows("annotations:create") && scaleOf(page).ready,
|
|
45
|
+
getPageScale: scaleOf,
|
|
46
|
+
isBusy: () => state().pending > 0,
|
|
47
|
+
listLastReports: () => state().reports,
|
|
48
|
+
listPresets: () => settings.get().presets,
|
|
49
|
+
listUnits: () => UNITS,
|
|
50
|
+
listAreaUnits: () => AREA_UNITS,
|
|
51
|
+
getReadout,
|
|
52
|
+
measureDistance,
|
|
53
|
+
measureArea,
|
|
54
|
+
getCalibrationRequest: () => state().calibration
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/services/events.ts
|
|
59
|
+
function createEvents(ctx) {
|
|
44
60
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
scaleChanged: ctx.events.source(),
|
|
62
|
+
calibrationRequested: ctx.events.source(),
|
|
63
|
+
calibrationCompleted: ctx.events.source(),
|
|
64
|
+
calibrationDismissed: ctx.events.source()
|
|
49
65
|
};
|
|
50
66
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const precision = previous?.fraction === "fraction" ? 100 : previous?.precision ?? 100;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/services/siblings.ts
|
|
69
|
+
/** The sibling plugins: annotation (host lens) and the interaction hub. */
|
|
70
|
+
function resolveSiblings(ctx) {
|
|
56
71
|
return {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
unit,
|
|
60
|
-
conversion: METRES[basis] / METRES[unit],
|
|
61
|
-
precision,
|
|
62
|
-
fraction: "decimal"
|
|
63
|
-
}],
|
|
64
|
-
area: [{
|
|
65
|
-
unit: areaUnitLabel(area),
|
|
66
|
-
conversion: METRES[basis] ** 2 / squareMetres(area),
|
|
67
|
-
precision,
|
|
68
|
-
fraction: "decimal"
|
|
69
|
-
}]
|
|
72
|
+
annotation: ctx.get(AnnotationToken),
|
|
73
|
+
interaction: ctx.get(InteractionToken)
|
|
70
74
|
};
|
|
71
75
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/services/store.ts
|
|
78
|
+
/** Changing a page's scale rewrites its measurement annotations. */
|
|
79
|
+
const SCALE_PERMISSION = "doc.annotate.modify";
|
|
80
|
+
function createStore(ctx) {
|
|
81
|
+
/** A page argument of a verb, as a ref or an index: the page, or `not-found`. */
|
|
82
|
+
const requirePage = (page) => ctx.pageOf(page);
|
|
83
|
+
const canCalibrate = () => ctx.allows(SCALE_PERMISSION);
|
|
84
|
+
/** The pages a verb changes, each resolved: a page that isn't there refuses the whole call. */
|
|
85
|
+
const targets = (pages) => {
|
|
86
|
+
if (pages === "all") return (ctx.document()?.pages ?? []).map((layout) => layout.ref);
|
|
87
|
+
return (Array.isArray(pages) ? pages : [pages]).map((page) => ctx.pageOf(page).ref);
|
|
88
|
+
};
|
|
79
89
|
return {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
requirePage,
|
|
91
|
+
canCalibrate,
|
|
92
|
+
targets
|
|
83
93
|
};
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!(basis in METRES)) throw new RangeError("This scale has an unknown base unit; calibrate it first");
|
|
89
|
-
const previous = measure.area[measure.area.length - 1];
|
|
90
|
-
const precision = previous?.fraction === "fraction" ? 100 : previous?.precision ?? 100;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/services/index.ts
|
|
97
|
+
function createServices(ctx) {
|
|
91
98
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
conversion: METRES[basis] ** 2 / squareMetres(unit),
|
|
96
|
-
precision,
|
|
97
|
-
fraction: "decimal"
|
|
98
|
-
}]
|
|
99
|
+
events: createEvents(ctx),
|
|
100
|
+
store: createStore(ctx),
|
|
101
|
+
siblings: resolveSiblings(ctx)
|
|
99
102
|
};
|
|
100
103
|
}
|
|
101
104
|
//#endregion
|
|
102
|
-
//#region src/
|
|
103
|
-
|
|
105
|
+
//#region src/model.ts
|
|
106
|
+
/**
|
|
107
|
+
* The measurement session: scale changes in flight, the pending calibration,
|
|
108
|
+
* the last change's reports, why a page's viewport read failed, and the
|
|
109
|
+
* session-only viewports of engines that cannot persist a scale. The pages'
|
|
110
|
+
* viewports themselves are engine data held by a page mirror. Every
|
|
111
|
+
* transition below is pure; the controller applies them with
|
|
112
|
+
* `ctx.state.update`. `selectPageScale` and `pageScaleOf` are the pure
|
|
113
|
+
* projections from viewports to the public {@link PageScale}.
|
|
114
|
+
*/
|
|
115
|
+
/** No reports yet: the same array every time. */
|
|
116
|
+
const NO_REPORTS = Object.freeze([]);
|
|
117
|
+
const initialMeasurementState = () => ({
|
|
118
|
+
pending: 0,
|
|
119
|
+
calibration: null,
|
|
120
|
+
reports: NO_REPORTS,
|
|
121
|
+
loadErrors: {},
|
|
122
|
+
localViewports: {}
|
|
123
|
+
});
|
|
124
|
+
const beginScaleChange = (state) => ({
|
|
125
|
+
...state,
|
|
126
|
+
pending: state.pending + 1
|
|
127
|
+
});
|
|
128
|
+
const endScaleChange = (state) => state.pending === 0 ? state : {
|
|
129
|
+
...state,
|
|
130
|
+
pending: state.pending - 1
|
|
131
|
+
};
|
|
132
|
+
const setCalibration = (state, calibration) => state.calibration === calibration ? state : {
|
|
133
|
+
...state,
|
|
134
|
+
calibration
|
|
135
|
+
};
|
|
136
|
+
const setReports = (state, reports) => state.reports === reports ? state : {
|
|
137
|
+
...state,
|
|
138
|
+
reports
|
|
139
|
+
};
|
|
140
|
+
const recordLoadError = (state, pageObjectNumber, error) => ({
|
|
141
|
+
...state,
|
|
142
|
+
loadErrors: {
|
|
143
|
+
...state.loadErrors,
|
|
144
|
+
[pageObjectNumber]: error
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
const clearLoadError = (state, pageObjectNumber) => {
|
|
148
|
+
if (!(pageObjectNumber in state.loadErrors)) return state;
|
|
149
|
+
const { [pageObjectNumber]: _cleared, ...loadErrors } = state.loadErrors;
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
loadErrors
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
const setLocalViewports = (state, pageObjectNumber, viewports) => ({
|
|
156
|
+
...state,
|
|
157
|
+
localViewports: {
|
|
158
|
+
...state.localViewports,
|
|
159
|
+
[pageObjectNumber]: viewports
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
/**
|
|
163
|
+
* The scale a page's viewports resolve to: the owned viewport, else the one
|
|
164
|
+
* containing the page's center, else the fallback.
|
|
165
|
+
*/
|
|
166
|
+
function selectPageScale(viewports, size, fallback, persistent) {
|
|
167
|
+
const selected = viewports.find((viewport) => viewport.owned) ?? viewportForPoint(viewports, {
|
|
168
|
+
x: size.width / 2,
|
|
169
|
+
y: size.height / 2
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
measure: selected ? selected.measure ?? null : fallback,
|
|
173
|
+
source: selected ? selected.owned ? "owned" : "foreign" : "default",
|
|
174
|
+
ready: true,
|
|
175
|
+
persistent
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The public page scale. A page is ready once its viewports were read; a
|
|
180
|
+
* failed re-read keeps the last confirmed viewports in effect and reports
|
|
181
|
+
* its error alongside them.
|
|
182
|
+
*/
|
|
183
|
+
function pageScaleOf(inputs) {
|
|
184
|
+
const error = inputs.status === "error" && inputs.error ? { error: inputs.error } : {};
|
|
185
|
+
if (!inputs.viewports || !inputs.size) return {
|
|
186
|
+
measure: null,
|
|
187
|
+
source: "default",
|
|
188
|
+
ready: false,
|
|
189
|
+
persistent: inputs.persistent,
|
|
190
|
+
...error
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
...selectPageScale(inputs.viewports, inputs.size, inputs.fallback, inputs.persistent),
|
|
194
|
+
...error
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/sync/viewports.ts
|
|
199
|
+
/**
|
|
200
|
+
* The pages' viewports: a page mirror re-read whenever a confirmed
|
|
201
|
+
* `pages.scaleSet` names a loaded page, from this session or another
|
|
202
|
+
* (stream gaps and version moves re-read every loaded page). Its `changed`
|
|
203
|
+
* callback tells the annotation plugin a page's viewports, and a new
|
|
204
|
+
* `defaultScale` setting tells it again. `connect` loads every page in the
|
|
205
|
+
* registry, including pages inserted later.
|
|
206
|
+
*/
|
|
207
|
+
/** The scale of a page index past the end: none, and never ready. */
|
|
208
|
+
const NO_PAGE = Object.freeze({
|
|
104
209
|
measure: null,
|
|
105
210
|
source: "default",
|
|
106
211
|
ready: false,
|
|
107
212
|
persistent: false
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
isBusy: () => ctx.getState().pending > 0,
|
|
126
|
-
lastReports: () => ctx.getState().reports,
|
|
127
|
-
prepare: effects.prepare,
|
|
128
|
-
setPageScale: (pon, measure, opts = {}) => effects.change(pons(pon, opts), () => measure, opts),
|
|
129
|
-
calibrate: (pon, from, to, real, opts = {}) => {
|
|
130
|
-
const a = measurementPoint(from);
|
|
131
|
-
const b = measurementPoint(to);
|
|
132
|
-
const scale = measureFromKnownLength(Math.hypot(b.x - a.x, b.y - a.y), real);
|
|
133
|
-
return effects.change(pons(pon, opts), () => scale, opts);
|
|
134
|
-
},
|
|
135
|
-
setUnit: (pon, unit, areaUnit, opts = {}) => effects.change(pons(pon, opts), (p) => withUnit(scaleOf(p), unit, areaUnit), {
|
|
136
|
-
...opts,
|
|
137
|
-
allPages: pon === "all" || opts.allPages
|
|
138
|
-
}),
|
|
139
|
-
setPrecision: (pon, precision, opts = {}) => effects.change(pons(pon, opts), (p) => withPrecision(scaleOf(p), precision), {
|
|
140
|
-
...opts,
|
|
141
|
-
allPages: pon === "all" || opts.allPages
|
|
142
|
-
}),
|
|
143
|
-
setPreset: (pon, id, opts = {}) => {
|
|
144
|
-
const preset = presets.find((p) => p.id === id);
|
|
145
|
-
if (!preset) return Promise.reject(/* @__PURE__ */ new RangeError("Unknown scale preset"));
|
|
146
|
-
return effects.change(pons(pon, opts), (p) => measureFromRatio(preset.paper, preset.real, preset.unit, ctx.document()?.pages.find((page) => page.pageObjectNumber === p)?.userUnit ?? 1), opts);
|
|
147
|
-
},
|
|
148
|
-
presets: () => presets,
|
|
149
|
-
units: () => UNITS,
|
|
150
|
-
areaUnits: () => [
|
|
151
|
-
...UNITS.map(squareOf),
|
|
152
|
-
"ha",
|
|
153
|
-
"acre"
|
|
154
|
-
],
|
|
155
|
-
setAreaUnit: (pon, unit, opts = {}) => effects.change(pons(pon, opts), (p) => withAreaUnit(scaleOf(p), unit), opts),
|
|
156
|
-
readout: (ref) => {
|
|
157
|
-
const dto = anno.get(ref);
|
|
158
|
-
return dto ? measurementReadout(dto) : { unavailable: "not-dimension" };
|
|
159
|
-
},
|
|
160
|
-
startCalibration: () => {
|
|
161
|
-
if (canCalibrate()) {
|
|
162
|
-
ctx.dispatch({
|
|
163
|
-
type: "CALIBRATION",
|
|
164
|
-
request: null
|
|
165
|
-
});
|
|
166
|
-
interaction.activateTool("calibrate");
|
|
213
|
+
});
|
|
214
|
+
function createViewportSync(ctx, { siblings }) {
|
|
215
|
+
const { annotation } = siblings;
|
|
216
|
+
const settings = ctx.settings();
|
|
217
|
+
/** The scale of a page without one: the `defaultScale` setting, in the page's user unit. */
|
|
218
|
+
const fallbackOf = (layout) => defaultMeasure(settings.get().defaultScale, layout?.userUnit ?? 1);
|
|
219
|
+
const viewports = ctx.pageMirror({
|
|
220
|
+
name: "viewports",
|
|
221
|
+
load: async (doc, page, signal) => {
|
|
222
|
+
ctx.assertPageRef(page);
|
|
223
|
+
const service = doc.page(page).measure;
|
|
224
|
+
if (!service) return ctx.state.get().localViewports[page.objectNumber] ?? [];
|
|
225
|
+
try {
|
|
226
|
+
return (await service.listViewports()).viewports;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (!signal.aborted) ctx.state.update(recordLoadError, page.objectNumber, serializeError(error));
|
|
229
|
+
throw error;
|
|
167
230
|
}
|
|
168
231
|
},
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
effects.calibrationListeners.add(cb);
|
|
176
|
-
return () => {
|
|
177
|
-
effects.calibrationListeners.delete(cb);
|
|
178
|
-
};
|
|
179
|
-
},
|
|
180
|
-
onScaleChanged: (cb) => {
|
|
181
|
-
effects.scaleListeners.add(cb);
|
|
182
|
-
return () => {
|
|
183
|
-
effects.scaleListeners.delete(cb);
|
|
184
|
-
};
|
|
232
|
+
affected: (event) => event.type === "pages.scaleSet" ? [event.page] : null,
|
|
233
|
+
changed: ({ page, cause, next }) => {
|
|
234
|
+
if (cause === "drop" || next === void 0) return;
|
|
235
|
+
ctx.state.update(clearLoadError, page.objectNumber);
|
|
236
|
+
const layout = ctx.getPage(page);
|
|
237
|
+
if (layout) annotation.setPageViewports(page, [...next], fallbackOf(layout));
|
|
185
238
|
}
|
|
239
|
+
});
|
|
240
|
+
/** The engine can persist a scale into the document. */
|
|
241
|
+
const isPersistent = (page) => ctx.doc.page(page).measure !== void 0;
|
|
242
|
+
/** The page's public scale, the same object until its viewports, read state or layout change. */
|
|
243
|
+
const scaleOfPage = memoByKey((pageObjectNumber) => {
|
|
244
|
+
const page = toPageRef(pageObjectNumber);
|
|
245
|
+
const layout = ctx.getPage(page);
|
|
246
|
+
return [
|
|
247
|
+
viewports.get(page),
|
|
248
|
+
viewports.getStatus(page),
|
|
249
|
+
ctx.state.get().loadErrors[pageObjectNumber],
|
|
250
|
+
layout,
|
|
251
|
+
layout ? isPersistent(page) : false,
|
|
252
|
+
settings.get().defaultScale
|
|
253
|
+
];
|
|
254
|
+
}, (_pageObjectNumber, pageViewports, status, error, layout, persistent) => pageScaleOf({
|
|
255
|
+
viewports: pageViewports,
|
|
256
|
+
status,
|
|
257
|
+
error,
|
|
258
|
+
size: layout?.size,
|
|
259
|
+
fallback: fallbackOf(layout),
|
|
260
|
+
persistent
|
|
261
|
+
}));
|
|
262
|
+
/** A page's scale, by ref or index; a page that isn't there has none, and isn't ready. */
|
|
263
|
+
const scaleOf = (page) => {
|
|
264
|
+
if (typeof page !== "number") return scaleOfPage(page.objectNumber);
|
|
265
|
+
const layout = ctx.getPage(page);
|
|
266
|
+
return layout ? scaleOfPage(layout.ref.objectNumber) : NO_PAGE;
|
|
267
|
+
};
|
|
268
|
+
const ensureLoaded = (page, options) => {
|
|
269
|
+
const layout = ctx.getPage(page);
|
|
270
|
+
if (!layout) return Promise.reject(new PluginError("not-found", "measurement", "no such page"));
|
|
271
|
+
return ctx.cancellable(options?.signal, viewports.ensureLoaded(layout.ref));
|
|
272
|
+
};
|
|
273
|
+
const connect = () => {
|
|
274
|
+
const hydrate = (layouts) => {
|
|
275
|
+
for (const layout of layouts ?? []) {
|
|
276
|
+
if (viewports.getStatus(layout.ref) !== "idle") continue;
|
|
277
|
+
viewports.ensureLoaded(layout.ref).catch(() => {});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
hydrate(ctx.document()?.pages);
|
|
281
|
+
ctx.watch(() => ctx.document()?.pages, hydrate);
|
|
282
|
+
ctx.listen(settings.api.onSettingsChanged, ({ changed }) => {
|
|
283
|
+
if (!changed.includes("defaultScale")) return;
|
|
284
|
+
for (const layout of ctx.document()?.pages ?? []) {
|
|
285
|
+
const known = viewports.get(layout.ref);
|
|
286
|
+
if (known) annotation.setPageViewports(layout.ref, [...known], fallbackOf(layout));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
ensureLoaded,
|
|
292
|
+
scaleOf,
|
|
293
|
+
/** Re-read one page; for session-only scales, which no engine event announces. */
|
|
294
|
+
refresh: (page) => viewports.refresh(page),
|
|
295
|
+
connect
|
|
186
296
|
};
|
|
187
297
|
}
|
|
188
298
|
//#endregion
|
|
189
|
-
//#region src/
|
|
190
|
-
/**
|
|
191
|
-
*
|
|
192
|
-
function
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const scaleListeners = /* @__PURE__ */ new Set();
|
|
201
|
-
const meta = (pon) => ctx.document()?.pages.find((p) => p.pageObjectNumber === pon);
|
|
202
|
-
const live = () => {
|
|
203
|
-
if (disposed || !ctx.doc) throw new EngineError(EngineErrorCode.DocNotOpen);
|
|
299
|
+
//#region src/write/calibration.ts
|
|
300
|
+
/** The calibrate flow: arm the tool, turn a captured distance draft into a
|
|
301
|
+
* request (page space), and dismiss it. */
|
|
302
|
+
function createCalibration(ctx, { events, store, siblings }) {
|
|
303
|
+
const { calibrationRequested, calibrationDismissed } = events;
|
|
304
|
+
const { canCalibrate } = store;
|
|
305
|
+
const { annotation, interaction } = siblings;
|
|
306
|
+
const startCalibration = () => {
|
|
307
|
+
ctx.assertAllowed(SCALE_PERMISSION, "measurement.startCalibration");
|
|
308
|
+
ctx.state.update(setCalibration, null);
|
|
309
|
+
interaction.activateTool("calibrate");
|
|
204
310
|
};
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
311
|
+
const dismissCalibration = () => {
|
|
312
|
+
if (!ctx.state.get().calibration) return;
|
|
313
|
+
ctx.state.update(setCalibration, null);
|
|
314
|
+
calibrationDismissed.emit({});
|
|
208
315
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
316
|
+
/** A distance draft from the calibrate tool becomes the pending request. */
|
|
317
|
+
const connect = () => {
|
|
318
|
+
ctx.listen(annotation.onDraftCaptured, (draft) => {
|
|
319
|
+
if (draft.tool !== "calibrate" || !canCalibrate()) return;
|
|
320
|
+
const from = measurementPoint(draft.from);
|
|
321
|
+
const to = measurementPoint(draft.to);
|
|
322
|
+
const userSpaceLength = Math.hypot(to.x - from.x, to.y - from.y);
|
|
323
|
+
if (!(userSpaceLength > 0)) return;
|
|
324
|
+
const request = {
|
|
325
|
+
page: draft.page,
|
|
326
|
+
from,
|
|
327
|
+
to,
|
|
328
|
+
userSpaceLength
|
|
329
|
+
};
|
|
330
|
+
interaction.activateTool("pointer");
|
|
331
|
+
ctx.state.update(setCalibration, request);
|
|
332
|
+
calibrationRequested.emit({ request });
|
|
220
333
|
});
|
|
221
334
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
335
|
+
return {
|
|
336
|
+
connect,
|
|
337
|
+
api: {
|
|
338
|
+
startCalibration,
|
|
339
|
+
dismissCalibration
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/write/create.ts
|
|
345
|
+
/** Measurement creation: a measurement annotation with the page's scale, through the annotation plugin. */
|
|
346
|
+
function createMeasuring(ctx, { siblings }, { scaleOf }) {
|
|
347
|
+
const { annotation } = siblings;
|
|
348
|
+
/**
|
|
349
|
+
* The measurement's engine draft: the tool's current defaults (style,
|
|
350
|
+
* caption and leader), the dimension intent, and the page's scale. The
|
|
351
|
+
* engine works out the label from the points and the scale.
|
|
352
|
+
*/
|
|
353
|
+
const draftOf = (input, page, tool) => {
|
|
354
|
+
const style = annotation.tools.getDefaults(tool);
|
|
355
|
+
const shared = {
|
|
356
|
+
color: style.color,
|
|
357
|
+
strokeWidth: style.strokeWidth,
|
|
358
|
+
opacity: style.opacity,
|
|
359
|
+
measure: scaleOf(page).measure,
|
|
360
|
+
captionEnabled: style.captionEnabled ?? true
|
|
361
|
+
};
|
|
362
|
+
switch (input.kind) {
|
|
363
|
+
case "distance": {
|
|
364
|
+
const [from, to] = input.points;
|
|
365
|
+
if (!from || !to || input.points.length !== 2) return new PluginError("invalid-input", "measurement", "a distance needs exactly two points");
|
|
366
|
+
return {
|
|
367
|
+
subtype: "line",
|
|
368
|
+
intent: "line-dimension",
|
|
369
|
+
linePoints: {
|
|
370
|
+
start: from,
|
|
371
|
+
end: to
|
|
372
|
+
},
|
|
373
|
+
lineEndings: style.lineEndings,
|
|
374
|
+
interiorColor: style.interiorColor ?? style.color,
|
|
375
|
+
...shared,
|
|
376
|
+
captionPosition: style.captionPosition ?? "inline",
|
|
377
|
+
...style.leader ? { leader: style.leader } : {}
|
|
378
|
+
};
|
|
264
379
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
380
|
+
case "perimeter": return {
|
|
381
|
+
subtype: "polyline",
|
|
382
|
+
intent: "polyline-dimension",
|
|
383
|
+
vertices: input.points,
|
|
384
|
+
lineEndings: style.lineEndings,
|
|
385
|
+
...shared
|
|
386
|
+
};
|
|
387
|
+
case "area": return {
|
|
388
|
+
subtype: "polygon",
|
|
389
|
+
intent: "polygon-dimension",
|
|
390
|
+
vertices: input.points,
|
|
391
|
+
interiorColor: style.interiorColor,
|
|
392
|
+
...shared
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
const createMeasurement = async (input, options) => {
|
|
397
|
+
ctx.assertAllowed("annotations:create", "measurement.createMeasurement");
|
|
398
|
+
const page = ctx.pageOf(input.page).ref;
|
|
399
|
+
if (!scaleOf(page).ready) throw new PluginError("not-ready", "measurement", "the page scale is not known yet");
|
|
400
|
+
const draft = draftOf(input, page, input.tool ?? input.kind);
|
|
401
|
+
if (draft instanceof Error) throw draft;
|
|
402
|
+
return annotation.create(page, draft, void 0, { signal: options?.signal });
|
|
268
403
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
404
|
+
return { api: { createMeasurement } };
|
|
405
|
+
}
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/write/scale.ts
|
|
408
|
+
/**
|
|
409
|
+
* Scale writes. Every change serializes per page, loads the page first, and
|
|
410
|
+
* re-measures the page's measurement annotations unless told not to. A
|
|
411
|
+
* multi-page change reports per-page failures; a single-page change rejects.
|
|
412
|
+
*/
|
|
413
|
+
function createScaleWrites(ctx, { events, store, siblings }, { ensureLoaded, refresh, scaleOf }) {
|
|
414
|
+
const { scaleChanged, calibrationCompleted } = events;
|
|
415
|
+
const { requirePage, targets } = store;
|
|
416
|
+
const { annotation } = siblings;
|
|
417
|
+
const settings = ctx.settings();
|
|
418
|
+
/** One write queue per page, so a page's scale changes land in order. */
|
|
419
|
+
const pageQueues = /* @__PURE__ */ new Map();
|
|
420
|
+
const queueOf = (page) => {
|
|
421
|
+
let queue = pageQueues.get(page.objectNumber);
|
|
422
|
+
if (!queue) {
|
|
423
|
+
queue = ctx.serialQueue(`scale:${page.objectNumber}`);
|
|
424
|
+
pageQueues.set(page.objectNumber, queue);
|
|
425
|
+
}
|
|
426
|
+
return queue;
|
|
277
427
|
};
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
428
|
+
ctx.onSettle(async () => {
|
|
429
|
+
await Promise.all([...pageQueues.values()].map((queue) => queue.idle()));
|
|
430
|
+
});
|
|
431
|
+
/** Write one page's scale (`null` = back to the default) and re-measure. */
|
|
432
|
+
const save = async (page, scale, options) => {
|
|
433
|
+
const { signal } = options;
|
|
434
|
+
ctx.assertAllowed(SCALE_PERMISSION, "changing a scale");
|
|
435
|
+
await ensureLoaded(page, { signal });
|
|
436
|
+
ctx.assertAllowed(SCALE_PERMISSION, "changing a scale");
|
|
437
|
+
const service = ctx.doc.page(page).measure;
|
|
283
438
|
if (service) {
|
|
284
|
-
await service.setScale(scale);
|
|
285
|
-
|
|
286
|
-
await refresh(pon);
|
|
439
|
+
await ctx.cancellable(signal, service.setScale(scale));
|
|
440
|
+
await ensureLoaded(page);
|
|
287
441
|
} else {
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
bbox:
|
|
442
|
+
const layout = requirePage(page);
|
|
443
|
+
const kept = (ctx.state.get().localViewports[page.objectNumber] ?? []).filter((viewport) => !viewport.owned);
|
|
444
|
+
ctx.state.update(setLocalViewports, page.objectNumber, [...kept, ...scale ? [{
|
|
445
|
+
bbox: {
|
|
446
|
+
x: 0,
|
|
447
|
+
y: 0,
|
|
448
|
+
...layout.size
|
|
449
|
+
},
|
|
292
450
|
name: "EmbedPDF",
|
|
293
451
|
owned: true,
|
|
294
452
|
measure: scale
|
|
295
|
-
}]);
|
|
453
|
+
}] : []]);
|
|
454
|
+
await refresh(page);
|
|
296
455
|
}
|
|
297
|
-
|
|
298
|
-
const report =
|
|
299
|
-
|
|
300
|
-
scale,
|
|
456
|
+
const effective = scale ?? defaultMeasure(settings.get().defaultScale, requirePage(page).userUnit);
|
|
457
|
+
const report = options.recalculate === false ? {
|
|
458
|
+
page,
|
|
459
|
+
scale: effective,
|
|
301
460
|
updated: [],
|
|
302
461
|
skipped: [],
|
|
303
462
|
failed: []
|
|
304
|
-
} : await
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
report
|
|
310
|
-
});
|
|
311
|
-
} catch {}
|
|
463
|
+
} : await ctx.cancellable(signal, annotation.remeasurePage(page, effective));
|
|
464
|
+
scaleChanged.emit({
|
|
465
|
+
page,
|
|
466
|
+
report
|
|
467
|
+
});
|
|
312
468
|
return report;
|
|
313
469
|
};
|
|
314
|
-
const change = async (
|
|
315
|
-
assertAllowed();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
470
|
+
const change = async (pages, measure, options = {}) => {
|
|
471
|
+
ctx.assertAllowed(SCALE_PERMISSION, "changing a scale");
|
|
472
|
+
const target = targets(pages);
|
|
473
|
+
if (options.signal?.aborted) throw new PluginError("operation-cancelled", "measurement", "operation cancelled", { cause: options.signal.reason });
|
|
474
|
+
const tolerant = pages === "all" || target.length > 1;
|
|
475
|
+
ctx.state.update(beginScaleChange);
|
|
320
476
|
try {
|
|
321
|
-
const reports = await Promise.all(
|
|
477
|
+
const reports = await Promise.all(target.map((page) => queueOf(page)(async () => {
|
|
322
478
|
try {
|
|
323
|
-
await
|
|
324
|
-
const scale = measure(
|
|
325
|
-
assertWritableMeasure(scale);
|
|
326
|
-
return await save(
|
|
479
|
+
await ensureLoaded(page, { signal: options.signal });
|
|
480
|
+
const scale = measure(page);
|
|
481
|
+
if (scale) assertWritableMeasure(scale);
|
|
482
|
+
return await save(page, scale, options);
|
|
327
483
|
} catch (error) {
|
|
328
|
-
|
|
484
|
+
const refused = toPluginError("measurement", error);
|
|
485
|
+
if (!tolerant || refused.code === "operation-cancelled") throw refused;
|
|
329
486
|
return {
|
|
330
|
-
|
|
487
|
+
page,
|
|
331
488
|
updated: [],
|
|
332
489
|
skipped: [],
|
|
333
490
|
failed: [],
|
|
334
491
|
scaleError: serializeError(error)
|
|
335
492
|
};
|
|
336
493
|
}
|
|
337
|
-
})));
|
|
338
|
-
|
|
339
|
-
ctx.dispatch({
|
|
340
|
-
type: "REPORTS",
|
|
341
|
-
reports
|
|
342
|
-
});
|
|
494
|
+
}, options)));
|
|
495
|
+
ctx.state.update(setReports, reports);
|
|
343
496
|
return reports;
|
|
344
497
|
} finally {
|
|
345
|
-
|
|
346
|
-
type: "PENDING",
|
|
347
|
-
delta: -1
|
|
348
|
-
});
|
|
498
|
+
ctx.state.update(endScaleChange);
|
|
349
499
|
}
|
|
350
500
|
};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
epochs.clear();
|
|
357
|
-
queues.clear();
|
|
358
|
-
calibrationListeners.clear();
|
|
359
|
-
scaleListeners.clear();
|
|
360
|
-
});
|
|
361
|
-
ctx.cleanup(anno.onDraftCaptured((draft) => {
|
|
362
|
-
if (draft.tool !== "calibrate" || !doc.security.allows("doc.annotate.modify")) return;
|
|
363
|
-
const a = measurementPoint(draft.from);
|
|
364
|
-
const b = measurementPoint(draft.to);
|
|
365
|
-
const userSpaceLength = Math.hypot(b.x - a.x, b.y - a.y);
|
|
366
|
-
if (!(userSpaceLength > 0)) return;
|
|
367
|
-
const request = {
|
|
368
|
-
pon: draft.pon,
|
|
369
|
-
from: a,
|
|
370
|
-
to: b,
|
|
371
|
-
userSpaceLength
|
|
372
|
-
};
|
|
373
|
-
interaction.activateTool("pointer");
|
|
374
|
-
ctx.dispatch({
|
|
375
|
-
type: "CALIBRATION",
|
|
376
|
-
request
|
|
377
|
-
});
|
|
378
|
-
for (const cb of calibrationListeners) try {
|
|
379
|
-
cb(request);
|
|
380
|
-
} catch {}
|
|
381
|
-
}));
|
|
382
|
-
const hydrate = () => {
|
|
383
|
-
for (const page of ctx.document()?.pages ?? []) refresh(page.pageObjectNumber).catch(() => {});
|
|
384
|
-
};
|
|
385
|
-
ctx.cleanup(doc.events.subscribe((event) => {
|
|
386
|
-
if (event.type === "page.viewportsChanged") refresh(event.pageObjectNumber).catch(() => {});
|
|
387
|
-
else if (event.type === "stream.desynced" || event.type === "document.versioned") hydrate();
|
|
388
|
-
}));
|
|
389
|
-
let pages = "";
|
|
390
|
-
const reconcile = () => {
|
|
391
|
-
const next = (ctx.document()?.pages ?? []).map((p) => p.pageObjectNumber).join(",");
|
|
392
|
-
if (next === pages) return;
|
|
393
|
-
pages = next;
|
|
394
|
-
for (const page of ctx.document()?.pages ?? []) if (!ctx.getState().pages[page.pageObjectNumber]) prepare(page.pageObjectNumber).catch(() => {});
|
|
395
|
-
};
|
|
396
|
-
ctx.cleanup(ctx.subscribe(reconcile));
|
|
397
|
-
reconcile();
|
|
501
|
+
/** The page's current rectilinear scale, for unit and precision edits. */
|
|
502
|
+
const rectilinearOf = (page) => {
|
|
503
|
+
const scale = scaleOf(page).measure;
|
|
504
|
+
if (!scale || scale.subtype !== "rectilinear") throw new PluginError("not-ready", "measurement", "calibrate this page before changing its units or precision");
|
|
505
|
+
return scale;
|
|
398
506
|
};
|
|
399
|
-
|
|
400
|
-
start
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
507
|
+
const calibrate = async (input, options = {}) => {
|
|
508
|
+
const start = measurementPoint(input.from);
|
|
509
|
+
const end = measurementPoint(input.to);
|
|
510
|
+
let scale;
|
|
511
|
+
try {
|
|
512
|
+
scale = measureFromKnownLength(Math.hypot(end.x - start.x, end.y - start.y), input.distance);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw new PluginError("invalid-input", "measurement", String(error.message ?? error));
|
|
515
|
+
}
|
|
516
|
+
const { applyTo, ...rest } = options;
|
|
517
|
+
const page = requirePage(input.page).ref;
|
|
518
|
+
const reports = await change(applyTo ?? page, () => scale, rest);
|
|
519
|
+
const request = ctx.state.get().calibration;
|
|
520
|
+
if (request && pageRefsEqual(request.page, page)) ctx.state.update(setCalibration, null);
|
|
521
|
+
calibrationCompleted.emit({ page });
|
|
522
|
+
return reports;
|
|
405
523
|
};
|
|
524
|
+
return { api: {
|
|
525
|
+
setScale: (pages, measure, options) => change(pages, () => measure, options),
|
|
526
|
+
calibrate,
|
|
527
|
+
setUnit: (pages, unit, options) => change(pages, (page) => withUnit(rectilinearOf(page), unit), options),
|
|
528
|
+
setAreaUnit: (pages, unit, options) => change(pages, (page) => withAreaUnit(rectilinearOf(page), unit), options),
|
|
529
|
+
setPrecision: (pages, precision, options) => change(pages, (page) => withPrecision(rectilinearOf(page), precision), options),
|
|
530
|
+
setPreset: (pages, presetId, options) => {
|
|
531
|
+
const preset = settings.get().presets.find((candidate) => candidate.id === presetId);
|
|
532
|
+
if (!preset) return Promise.reject(new PluginError("not-found", "measurement", `unknown scale preset '${presetId}'`));
|
|
533
|
+
return change(pages, (page) => measureFromRatio(preset.paper, preset.real, preset.unit, { userUnit: ctx.getPage(page)?.userUnit ?? 1 }), options);
|
|
534
|
+
},
|
|
535
|
+
clearScale: (pages, options) => change(pages, () => null, options)
|
|
536
|
+
} };
|
|
406
537
|
}
|
|
407
538
|
//#endregion
|
|
408
|
-
//#region src/
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
function
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
539
|
+
//#region src/controller.ts
|
|
540
|
+
/**
|
|
541
|
+
* The measurement controller: the composition root. It builds the plugin's
|
|
542
|
+
* services once, wires each area with the services it declares, and
|
|
543
|
+
* assembles the capability from the areas' API slices. No behavior lives
|
|
544
|
+
* here: every verb and read has a home in `read/`, `write/` or `sync/`.
|
|
545
|
+
*/
|
|
546
|
+
function createMeasurementController(ctx) {
|
|
547
|
+
const services = createServices(ctx);
|
|
548
|
+
const { events, store } = services;
|
|
549
|
+
const settings = ctx.settings();
|
|
550
|
+
const viewports = createViewportSync(ctx, services);
|
|
551
|
+
const reads = createScaleReads(ctx, services, viewports);
|
|
552
|
+
const scale = createScaleWrites(ctx, services, viewports);
|
|
553
|
+
const calibration = createCalibration(ctx, services);
|
|
554
|
+
const measuring = createMeasuring(ctx, services, viewports);
|
|
555
|
+
return {
|
|
556
|
+
api: composeApi("measurement", [
|
|
557
|
+
settings.api,
|
|
558
|
+
reads.api,
|
|
559
|
+
scale.api,
|
|
560
|
+
calibration.api,
|
|
561
|
+
measuring.api,
|
|
562
|
+
{
|
|
563
|
+
canCalibrate: store.canCalibrate,
|
|
564
|
+
ensureLoaded: viewports.ensureLoaded,
|
|
565
|
+
onScaleChanged: events.scaleChanged.on,
|
|
566
|
+
onCalibrationRequested: events.calibrationRequested.on,
|
|
567
|
+
onCalibrationCompleted: events.calibrationCompleted.on,
|
|
568
|
+
onCalibrationDismissed: events.calibrationDismissed.on
|
|
425
569
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
...s,
|
|
433
|
-
calibration: a.request
|
|
434
|
-
};
|
|
435
|
-
case "REPORTS": return {
|
|
436
|
-
...s,
|
|
437
|
-
reports: a.reports
|
|
438
|
-
};
|
|
439
|
-
}
|
|
570
|
+
]),
|
|
571
|
+
connect() {
|
|
572
|
+
calibration.connect();
|
|
573
|
+
viewports.connect();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
440
576
|
}
|
|
441
577
|
//#endregion
|
|
442
578
|
//#region src/measurement.plugin.ts
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
579
|
+
/**
|
|
580
|
+
* Page scale, calibration and measurement readouts, document-scoped. The
|
|
581
|
+
* annotation plugin owns the measurement annotations; this plugin owns the
|
|
582
|
+
* page's viewports (the scale), keeps the annotation plugin's measure in step,
|
|
583
|
+
* and turns the calibrate tool's drafts into scale requests. `config` is the
|
|
584
|
+
* settings the app registers, over {@link MEASUREMENT_DEFAULTS}.
|
|
585
|
+
*/
|
|
586
|
+
const measurementPlugin = (config) => definePlugin({
|
|
587
|
+
id: "measurement",
|
|
588
|
+
scope: "document",
|
|
589
|
+
token: MeasurementToken,
|
|
590
|
+
requires: [AnnotationToken, InteractionToken],
|
|
591
|
+
state: initialMeasurementState,
|
|
592
|
+
settings: {
|
|
593
|
+
defaults: MEASUREMENT_DEFAULTS,
|
|
594
|
+
registered: config,
|
|
595
|
+
whole: ["defaultScale"]
|
|
596
|
+
},
|
|
597
|
+
create: createMeasurementController
|
|
598
|
+
});
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/state.ts
|
|
601
|
+
/**
|
|
602
|
+
* The measurements page's State table as code: what `useMeasurementState()`
|
|
603
|
+
* returns, and the same fields in every other framework. A page's scale and a
|
|
604
|
+
* measurement's readout are not here: `usePageScale(page)` and
|
|
605
|
+
* `useMeasurementReadout(ref)` read them, so a component re-renders only for
|
|
606
|
+
* its own page or measurement.
|
|
607
|
+
*/
|
|
608
|
+
const measurementState = defineState(MeasurementToken, {
|
|
609
|
+
read: (measurement) => ({
|
|
610
|
+
busy: measurement.isBusy(),
|
|
611
|
+
calibrationRequest: measurement.getCalibrationRequest(),
|
|
612
|
+
lastReports: measurement.listLastReports()
|
|
613
|
+
}),
|
|
614
|
+
empty: {
|
|
615
|
+
busy: false,
|
|
616
|
+
calibrationRequest: null,
|
|
617
|
+
lastReports: NO_REPORTS
|
|
618
|
+
}
|
|
619
|
+
});
|
|
466
620
|
//#endregion
|
|
467
|
-
export { MeasurementToken, measurementPlugin };
|
|
621
|
+
export { MEASUREMENT_DEFAULTS, MeasurementToken, NO_PAGE_SCALE, NO_READOUT, measurementPlugin, measurementState };
|
|
468
622
|
|
|
469
623
|
//# sourceMappingURL=index.js.map
|