@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 CHANGED
@@ -1,59 +1,73 @@
1
1
  # @embedpdf/plugin-measurement
2
2
 
3
- Document-scoped page calibration, distance authoring, and measurement readouts.
3
+ Document-scoped page calibration, measurement authoring, and measurement readouts.
4
4
  Install alongside `annotationPlugin()` and `interactionPlugin()`:
5
5
 
6
6
  ```ts
7
- import {
8
- measurementPlugin,
9
- MeasurementToken,
10
- } from '@embedpdf/plugin-measurement';
7
+ import { measurementPlugin, MeasurementToken } from '@embedpdf/plugin-measurement';
11
8
 
12
9
  const plugins = [
13
- /* interaction, annotation, ... */ measurementPlugin({
14
- defaultScale: 'metric',
15
- }),
10
+ /* interaction, annotation, ... */
11
+ measurementPlugin({ defaultScale: 'metric' }),
16
12
  ];
13
+
17
14
  const measurements = viewer.get(MeasurementToken);
18
- await measurements.calibrate(pageObjectNumber, fromPdfPoint, toPdfPoint, {
19
- value: 5,
20
- unit: 'm',
21
- });
15
+ await measurements.calibrate({ page, from, to, distance: { value: 5, unit: 'm' } });
22
16
  ```
23
17
 
24
- `calibrate` points are original PDF user space: y-up, including the page's CropBox
25
- origin. The line's length is rounded through the engine's float32 coordinate
26
- rules. `UserUnit` participates in ratio presets; a calibration from a known
27
- length already expresses the full conversion.
28
-
29
- The annotation plugin supplies the `distance` and `calibrate` tools. Distance
30
- captures the last viewport containing its **first point**. A foreign or invalid
31
- winning viewport prevents creation; a default is used only outside all viewports.
32
- Until viewport reads finish, distance creation is unavailable.
33
-
34
- `setPageScale`, `setPreset`, `setUnit`, `setPrecision`, and `calibrate` accept
35
- `{ recalculate?: boolean, allPages?: boolean }`. Recalculation defaults to true.
36
- Every annotation stores its own scale snapshot. Setting `recalculate: false`
37
- changes subsequent measurements while preserving existing values.
38
-
39
- Recalculation reports list updated, skipped, and failed annotations. A page-wide
40
- annotation read failure appears as `error`. A single-page viewport write failure
41
- rejects before annotation writes; all-pages operations return per-page
42
- `scaleError` results and finish the remaining pages. Page scales persist in the
43
- PDF when the engine provides `page.measure`; older engines retain them for the
44
- session (`pageScale().persistent === false`).
45
-
46
- `canCalibrate()` mirrors `doc.annotate.modify`; `canMeasure(pon)` combines
18
+ `calibrate` points are page space, like the `CalibrationRequest` the calibrate
19
+ tool produces; the plugin converts them to PDF user space, and the line's length
20
+ is rounded through the engine's float32 coordinate rules. `UserUnit` participates
21
+ in ratio presets; a calibration from a known length already expresses the full
22
+ conversion.
23
+
24
+ The annotation plugin supplies the `distance`, `perimeter`, `area`, and
25
+ `calibrate` tools. A measurement captures the last viewport containing its
26
+ **first point**. A foreign or invalid winning viewport prevents creation; the
27
+ default scale is used only outside all viewports. Until the page's viewport reads
28
+ finish, measurement creation is unavailable.
29
+ `createMeasurement({ kind, page, points })` creates the same annotations from
30
+ code, resolves `{ annotation }`, and rejects with `not-ready` before the page's
31
+ scale is known. A page is a ref or an index everywhere.
32
+ `startCalibration()` arms the calibrate tool, and throws `permission-denied`
33
+ without `doc.annotate.modify`; its two points arrive as
34
+ `onCalibrationRequested` (and `getCalibrationRequest()`) awaiting the real
35
+ length, and `dismissCalibration()` drops the request.
36
+
37
+ `setScale`, `setPreset`, `setUnit`, `setAreaUnit`, `setPrecision`, and
38
+ `clearScale` take a page target — one page, an array of pages, or `'all'` — and
39
+ `{ recalculate?: boolean }`. `calibrate` writes its input page unless `applyTo`
40
+ names a wider target. Recalculation defaults to true. Every annotation stores its
41
+ own scale snapshot. Setting `recalculate: false` changes subsequent measurements
42
+ while preserving existing values.
43
+
44
+ Each scale change resolves to one report per page listing updated, skipped, and
45
+ failed annotations; `listLastReports()` keeps the last set and `onScaleChanged`
46
+ fires per page. A page-wide annotation read failure appears as `error`. A
47
+ single-page viewport write failure rejects before annotation writes; multi-page
48
+ operations (several pages or `'all'`) return per-page `scaleError` results and
49
+ finish the remaining pages. Page scales persist in the PDF when the engine
50
+ provides `page.measure`; without it they last for the session
51
+ (`getPageScale(page).persistent === false`).
52
+
53
+ `canCalibrate()` mirrors `doc.annotate.modify`; `canMeasure(page)` combines
47
54
  annotation creation authority and viewport readiness. Per-record permissions and
48
- PDF flags gate recalculation and caption dragging. Distance contents are derived
49
- and cannot be edited as comment text.
55
+ PDF flags gate recalculation and caption dragging. Measurement contents are
56
+ derived and cannot be edited as comment text. `getReadout(ref)` formats a
57
+ measurement annotation; `measureDistance` and `measureArea` convert page-space
58
+ points in the page's scale without creating anything.
59
+
60
+ The settings, `defaultScale` and `presets`, change while the app runs:
61
+ `updateSettings()`, `resetSettings()` and `onSettingsChanged`, shared by every
62
+ document.
50
63
 
51
- React consumers import `measurementPlugin`, `useMeasurement`, `usePageScale`, and
52
- `useMeasurementReadout` from `@embedpdf/react/measurement`. The full viewer includes
53
- a Measure toolbar, scale sidebar, and known-length calibration dialog.
64
+ React consumers import `measurementPlugin`, `useMeasurement` (the capability),
65
+ `useMeasurementState` (`busy`, `calibrationRequest`, `lastReports`),
66
+ `useMeasurementSettings`, `usePageScale`, `useMeasurementReadout` and
67
+ `useMeasurementEvent` from `@embedpdf/react/measurement`. The full viewer includes a Measure toolbar
68
+ (distance, perimeter, area, calibrate), a scale sidebar, and a known-length
69
+ calibration dialog.
54
70
 
55
71
  Distance captions use standard PDF line-axis offsets. Their live layout uses an
56
72
  estimated Helvetica advance; committed annotations render the native PDF
57
- appearance. Perimeter and area authoring tools are not included in this package's
58
- initial viewer increment; readouts and page recalculation support their existing
59
- engine annotations.
73
+ appearance.
@@ -0,0 +1,185 @@
1
+ import { EventHook, OperationOptions, SettingsApi } from "@embedpdf/core";
2
+ import { Point } from "@embedpdf/core-geometry";
3
+ import { Annotation, AnnotationRef, AnnotationRef as AnnotationRef$1, AreaUnit, AreaUnit as AreaUnit$1, LengthUnit, LengthUnit as LengthUnit$1, MeasurementReadout, MeasurementReadout as MeasurementReadout$1, MeasurementUnavailable, MeasurementUnavailable as MeasurementUnavailable$1, PageRef, PdfMeasure, PdfMeasure as PdfMeasure$1, PdfMeasurement, SerializedEngineError } from "@embedpdf/engine-core/runtime";
4
+ import { RecalibrationReport, RecalibrationReport as RecalibrationReport$1 } from "@embedpdf/plugin-annotation/contract/host";
5
+ //#region src/token.d.ts
6
+ declare const MeasurementToken: import("@embedpdf/core").CapabilityToken<MeasurementCapability>;
7
+ //#endregion
8
+ //#region src/contract.d.ts
9
+ /** One page, several pages, or every page of the document: each a ref or an index. */
10
+ type PageTarget = PageRef | number | readonly (PageRef | number)[] | 'all';
11
+ interface ScalePreset {
12
+ id: string;
13
+ label: string;
14
+ paper: number;
15
+ real: number;
16
+ unit: LengthUnit;
17
+ }
18
+ /**
19
+ * The measurement plugin's settings. `measurementPlugin(config)` registers
20
+ * them over {@link MEASUREMENT_DEFAULTS}, and `updateSettings()` changes them
21
+ * for every document while the app runs.
22
+ */
23
+ interface MeasurementSettings {
24
+ /**
25
+ * The scale of a page that has none: 1:1 in meters (`'metric'`) or in
26
+ * feet (`'imperial'`), or a scale of your own.
27
+ */
28
+ readonly defaultScale: 'metric' | 'imperial' | PdfMeasure;
29
+ /** The scales `listPresets()` offers. */
30
+ readonly presets: readonly ScalePreset[];
31
+ }
32
+ /** What the measurement settings are when the app registers none. */
33
+ declare const MEASUREMENT_DEFAULTS: MeasurementSettings;
34
+ /** What `measurementPlugin(config)` takes: either setting, each a whole value. */
35
+ type MeasurementConfig = Partial<MeasurementSettings>;
36
+ interface ScaleChangeOptions extends OperationOptions {
37
+ /** Re-measure the page's measurement annotations with the new scale. Default true. */
38
+ recalculate?: boolean;
39
+ }
40
+ interface PageScale {
41
+ measure: PdfMeasurement | null;
42
+ source: 'owned' | 'foreign' | 'default';
43
+ /** The page's viewports are known. */
44
+ ready: boolean;
45
+ /** The engine can persist a scale into the document. */
46
+ persistent: boolean;
47
+ /** Why the last read of the page's viewports failed; the last known viewports stay in effect. */
48
+ error?: SerializedEngineError;
49
+ }
50
+ /**
51
+ * A page's scale where there is none to read: before a document opens, or for
52
+ * a page that isn't there. Not ready, with no measure; the same object every
53
+ * time, so a framework read compares it by identity.
54
+ */
55
+ declare const NO_PAGE_SCALE: PageScale;
56
+ /**
57
+ * The readout where there is no measurement to read: before a document
58
+ * opens, or for an annotation that isn't there. `getReadout` answers it too.
59
+ */
60
+ declare const NO_READOUT: MeasurementUnavailable;
61
+ /** Two points captured by the calibrate tool, awaiting the real length. Page space. */
62
+ interface CalibrationRequest {
63
+ page: PageRef;
64
+ from: Point;
65
+ to: Point;
66
+ /** The captured length in PDF user space (the scale is derived from it). */
67
+ userSpaceLength: number;
68
+ }
69
+ interface CalibrateInput {
70
+ /** The page, as a ref or an index. */
71
+ page: PageRef | number;
72
+ /** The two ends of the known length, in page space. */
73
+ from: Point;
74
+ to: Point;
75
+ /** The real-world length between the two points. */
76
+ distance: {
77
+ value: number;
78
+ unit: LengthUnit;
79
+ };
80
+ }
81
+ /** A failed viewport write is reported per page for a multi-page change.
82
+ * Single-page failures reject before any annotation write. */
83
+ type ScaleChangeReport = (RecalibrationReport & {
84
+ scaleError?: never;
85
+ }) | {
86
+ page: PageRef;
87
+ scale?: never;
88
+ error?: never;
89
+ updated: [];
90
+ skipped: [];
91
+ failed: [];
92
+ scaleError: SerializedEngineError;
93
+ };
94
+ type MeasurementKind = 'distance' | 'perimeter' | 'area';
95
+ interface CreateMeasurementInput {
96
+ kind: MeasurementKind;
97
+ /** The page, as a ref or an index. */
98
+ page: PageRef | number;
99
+ /** Page-space points: two for a distance, the vertices otherwise. */
100
+ points: readonly Point[];
101
+ /** The authoring tool whose defaults apply (defaults to the kind's own tool). */
102
+ tool?: string;
103
+ }
104
+ interface MeasurementScaleChangedEvent {
105
+ readonly page: PageRef;
106
+ readonly report: ScaleChangeReport;
107
+ }
108
+ interface CalibrationRequestedEvent {
109
+ readonly request: CalibrationRequest;
110
+ }
111
+ interface CalibrationCompletedEvent {
112
+ readonly page: PageRef;
113
+ }
114
+ type CalibrationDismissedEvent = Record<string, never>;
115
+ /**
116
+ * Page scales, calibration and measurement readouts for one document. A page
117
+ * argument is a ref or an index: a read given a page that isn't there
118
+ * answers empty, a verb refuses it with `not-found`. Every async verb takes
119
+ * a `signal`. The settings belong to the plugin, not to a document: a change
120
+ * reaches every open document.
121
+ */
122
+ interface MeasurementCapability extends SettingsApi<MeasurementSettings> {
123
+ /** May write a page scale and calibrate (`doc.annotate.modify`). */
124
+ canCalibrate(): boolean;
125
+ /** May measure on the page: `annotations:create`, and the page's scale has loaded. */
126
+ canMeasure(page: PageRef | number): boolean;
127
+ /** The resolved scale, its source and readiness. Reference-stable while unchanged. */
128
+ getPageScale(page: PageRef | number): PageScale;
129
+ /** Load the page's viewports. Resolves at once when already known. Rejects `not-found`. */
130
+ ensureLoaded(page: PageRef | number, options?: OperationOptions): Promise<void>;
131
+ /** A scale change is in flight. */
132
+ isBusy(): boolean;
133
+ /** The reports of the last scale change, one per page. */
134
+ listLastReports(): readonly ScaleChangeReport[];
135
+ listPresets(): readonly ScalePreset[];
136
+ listUnits(): readonly LengthUnit[];
137
+ listAreaUnits(): readonly AreaUnit[];
138
+ /**
139
+ * The formatted measurement of a measurement annotation, or why there is
140
+ * none ({@link NO_READOUT} for an annotation that isn't there). The same
141
+ * object until the annotation changes.
142
+ */
143
+ getReadout(ref: AnnotationRef): MeasurementReadout | MeasurementUnavailable;
144
+ /** A distance between two page-space points in the page's scale: a pure conversion, no annotation. */
145
+ measureDistance(page: PageRef | number, from: Point, to: Point): MeasurementReadout | MeasurementUnavailable;
146
+ /** The area of a page-space polygon in the page's scale: a pure conversion, no annotation. */
147
+ measureArea(page: PageRef | number, vertices: readonly Point[]): MeasurementReadout | MeasurementUnavailable;
148
+ /** Two points captured, awaiting a length. */
149
+ getCalibrationRequest(): CalibrationRequest | null;
150
+ setScale(pages: PageTarget, measure: PdfMeasure, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
151
+ /** Derive a scale from a known length between two page points. `applyTo` widens the write (default: the input page). Fires `onCalibrationCompleted`. */
152
+ calibrate(input: CalibrateInput, options?: ScaleChangeOptions & {
153
+ applyTo?: PageTarget;
154
+ }): Promise<readonly ScaleChangeReport[]>;
155
+ setUnit(pages: PageTarget, unit: LengthUnit, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
156
+ setAreaUnit(pages: PageTarget, unit: AreaUnit, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
157
+ setPrecision(pages: PageTarget, precision: number, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
158
+ /** A scale from the `presets` setting. Rejects `not-found` for an unknown preset. */
159
+ setPreset(pages: PageTarget, presetId: string, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
160
+ /** Back to the default scale (the owned viewport is removed). */
161
+ clearScale(pages: PageTarget, options?: ScaleChangeOptions): Promise<readonly ScaleChangeReport[]>;
162
+ /**
163
+ * Create a measurement annotation with the page's scale and the tool's
164
+ * style. Resolves `{ annotation }`. Rejects `permission-denied` without
165
+ * `annotations:create`, `not-ready` before the scale is known,
166
+ * `not-found` for a page that isn't in the document.
167
+ */
168
+ createMeasurement(input: CreateMeasurementInput, options?: OperationOptions): Promise<{
169
+ annotation: Annotation;
170
+ }>;
171
+ /**
172
+ * Arm the calibrate tool (two points, then `onCalibrationRequested`).
173
+ * Throws `permission-denied` without `doc.annotate.modify` (see `canCalibrate`).
174
+ */
175
+ startCalibration(): void;
176
+ /** Drop the pending request. Fires `onCalibrationDismissed` when there was one. */
177
+ dismissCalibration(): void;
178
+ readonly onScaleChanged: EventHook<MeasurementScaleChangedEvent>;
179
+ readonly onCalibrationRequested: EventHook<CalibrationRequestedEvent>;
180
+ readonly onCalibrationCompleted: EventHook<CalibrationCompletedEvent>;
181
+ readonly onCalibrationDismissed: EventHook<CalibrationDismissedEvent>;
182
+ }
183
+ //#endregion
184
+ export { RecalibrationReport$1 as C, MeasurementToken as D, ScalePreset as E, PdfMeasure$1 as S, ScaleChangeReport as T, MeasurementUnavailable$1 as _, CalibrationDismissedEvent as a, PageScale as b, CreateMeasurementInput as c, MeasurementCapability as d, MeasurementConfig as f, MeasurementSettings as g, MeasurementScaleChangedEvent as h, CalibrationCompletedEvent as i, LengthUnit$1 as l, MeasurementReadout$1 as m, AreaUnit$1 as n, CalibrationRequest as o, MeasurementKind as p, CalibrateInput as r, CalibrationRequestedEvent as s, AnnotationRef$1 as t, MEASUREMENT_DEFAULTS as u, NO_PAGE_SCALE as v, ScaleChangeOptions as w, PageTarget as x, NO_READOUT as y };
185
+ //# sourceMappingURL=contract-BGn30CU9.d.cts.map
@@ -0,0 +1,178 @@
1
+ let _embedpdf_core = require("@embedpdf/core");
2
+ let _embedpdf_engine_core_runtime = require("@embedpdf/engine-core/runtime");
3
+ //#region src/scale.ts
4
+ /** Scale arithmetic: the default presets and scale, and unit and precision edits of a scale. */
5
+ /** The scales offered when the app registers none: metric 1:1 to 1:200, and two imperial ones. */
6
+ const DEFAULT_PRESETS = [
7
+ ...[
8
+ 1,
9
+ 10,
10
+ 20,
11
+ 50,
12
+ 100,
13
+ 200
14
+ ].map((real) => ({
15
+ id: `metric-${real}`,
16
+ label: `1:${real}`,
17
+ paper: 1,
18
+ real,
19
+ unit: "m"
20
+ })),
21
+ {
22
+ id: "imperial-quarter",
23
+ label: "1/4 in = 1 ft",
24
+ paper: 1,
25
+ real: 48,
26
+ unit: "ft"
27
+ },
28
+ {
29
+ id: "imperial-eighth",
30
+ label: "1/8 in = 1 ft",
31
+ paper: 1,
32
+ real: 96,
33
+ unit: "ft"
34
+ }
35
+ ];
36
+ /** The scale of a page that has none, from the `defaultScale` setting. */
37
+ const defaultMeasure = (defaultScale, userUnit = 1) => typeof defaultScale === "object" ? defaultScale : (0, _embedpdf_engine_core_runtime.measureFromRatio)(1, 1, defaultScale === "imperial" ? "ft" : "m", { userUnit });
38
+ function withUnit(measure, unit, area = (0, _embedpdf_engine_core_runtime.squareOf)(unit)) {
39
+ const basis = measure.x[0]?.unit.trim();
40
+ if (!(basis in _embedpdf_engine_core_runtime.METRES)) throw new RangeError("This scale has an unknown base unit; calibrate it first");
41
+ const previous = measure.distance[measure.distance.length - 1];
42
+ const precision = previous?.fraction === "fraction" ? 100 : previous?.precision ?? 100;
43
+ return {
44
+ ...measure,
45
+ distance: [{
46
+ unit,
47
+ conversion: _embedpdf_engine_core_runtime.METRES[basis] / _embedpdf_engine_core_runtime.METRES[unit],
48
+ precision,
49
+ fraction: "decimal"
50
+ }],
51
+ area: [{
52
+ unit: (0, _embedpdf_engine_core_runtime.areaUnitLabel)(area),
53
+ conversion: _embedpdf_engine_core_runtime.METRES[basis] ** 2 / (0, _embedpdf_engine_core_runtime.squareMetres)(area),
54
+ precision,
55
+ fraction: "decimal"
56
+ }]
57
+ };
58
+ }
59
+ function withPrecision(measure, precision) {
60
+ if (!Number.isInteger(Math.log10(precision)) || precision < 1 || precision > 1e9) throw new RangeError("Invalid decimal precision");
61
+ const update = (formats) => formats.map((format, index) => index === formats.length - 1 ? {
62
+ ...format,
63
+ precision,
64
+ fraction: "decimal"
65
+ } : format);
66
+ return {
67
+ ...measure,
68
+ distance: update(measure.distance),
69
+ area: update(measure.area)
70
+ };
71
+ }
72
+ /** Change the area display unit while preserving imported distance formatting. */
73
+ function withAreaUnit(measure, unit) {
74
+ const basis = measure.x[0]?.unit.trim();
75
+ if (!(basis in _embedpdf_engine_core_runtime.METRES)) throw new RangeError("This scale has an unknown base unit; calibrate it first");
76
+ const previous = measure.area[measure.area.length - 1];
77
+ const precision = previous?.fraction === "fraction" ? 100 : previous?.precision ?? 100;
78
+ return {
79
+ ...measure,
80
+ area: [{
81
+ unit: (0, _embedpdf_engine_core_runtime.areaUnitLabel)(unit),
82
+ conversion: _embedpdf_engine_core_runtime.METRES[basis] ** 2 / (0, _embedpdf_engine_core_runtime.squareMetres)(unit),
83
+ precision,
84
+ fraction: "decimal"
85
+ }]
86
+ };
87
+ }
88
+ //#endregion
89
+ //#region src/token.ts
90
+ /** The package token, created once here and re-exported by `contract.ts`. */
91
+ const MeasurementToken = (0, _embedpdf_core.createCapabilityToken)("measurement", {
92
+ hint: `add measurementPlugin() from '@embedpdf/plugin-measurement' to your plugins list`,
93
+ promises: {
94
+ ensureLoaded: true,
95
+ setScale: true,
96
+ calibrate: true,
97
+ setUnit: true,
98
+ setAreaUnit: true,
99
+ setPrecision: true,
100
+ setPreset: true,
101
+ clearScale: true,
102
+ createMeasurement: true
103
+ }
104
+ });
105
+ //#endregion
106
+ //#region src/contract.ts
107
+ /** What the measurement settings are when the app registers none. */
108
+ const MEASUREMENT_DEFAULTS = {
109
+ defaultScale: "metric",
110
+ presets: DEFAULT_PRESETS
111
+ };
112
+ /**
113
+ * A page's scale where there is none to read: before a document opens, or for
114
+ * a page that isn't there. Not ready, with no measure; the same object every
115
+ * time, so a framework read compares it by identity.
116
+ */
117
+ const NO_PAGE_SCALE = Object.freeze({
118
+ measure: null,
119
+ source: "default",
120
+ ready: false,
121
+ persistent: false
122
+ });
123
+ /**
124
+ * The readout where there is no measurement to read: before a document
125
+ * opens, or for an annotation that isn't there. `getReadout` answers it too.
126
+ */
127
+ const NO_READOUT = Object.freeze({ unavailable: "not-dimension" });
128
+ //#endregion
129
+ Object.defineProperty(exports, "MEASUREMENT_DEFAULTS", {
130
+ enumerable: true,
131
+ get: function() {
132
+ return MEASUREMENT_DEFAULTS;
133
+ }
134
+ });
135
+ Object.defineProperty(exports, "MeasurementToken", {
136
+ enumerable: true,
137
+ get: function() {
138
+ return MeasurementToken;
139
+ }
140
+ });
141
+ Object.defineProperty(exports, "NO_PAGE_SCALE", {
142
+ enumerable: true,
143
+ get: function() {
144
+ return NO_PAGE_SCALE;
145
+ }
146
+ });
147
+ Object.defineProperty(exports, "NO_READOUT", {
148
+ enumerable: true,
149
+ get: function() {
150
+ return NO_READOUT;
151
+ }
152
+ });
153
+ Object.defineProperty(exports, "defaultMeasure", {
154
+ enumerable: true,
155
+ get: function() {
156
+ return defaultMeasure;
157
+ }
158
+ });
159
+ Object.defineProperty(exports, "withAreaUnit", {
160
+ enumerable: true,
161
+ get: function() {
162
+ return withAreaUnit;
163
+ }
164
+ });
165
+ Object.defineProperty(exports, "withPrecision", {
166
+ enumerable: true,
167
+ get: function() {
168
+ return withPrecision;
169
+ }
170
+ });
171
+ Object.defineProperty(exports, "withUnit", {
172
+ enumerable: true,
173
+ get: function() {
174
+ return withUnit;
175
+ }
176
+ });
177
+
178
+ //# sourceMappingURL=contract-BHj3w9WF.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-BHj3w9WF.cjs","names":["METRES"],"sources":["../src/scale.ts","../src/token.ts","../src/contract.ts"],"sourcesContent":["/** Scale arithmetic: the default presets and scale, and unit and precision edits of a scale. */\nimport {\n areaUnitLabel,\n measureFromRatio,\n METRES,\n squareMetres,\n squareOf,\n} from '@embedpdf/engine-core/runtime';\nimport type { AreaUnit, LengthUnit, PdfMeasure } from '@embedpdf/engine-core/runtime';\n\nimport type { MeasurementSettings, ScalePreset } from './contract';\n\n/** The scales offered when the app registers none: metric 1:1 to 1:200, and two imperial ones. */\nexport const DEFAULT_PRESETS: readonly ScalePreset[] = [\n ...[1, 10, 20, 50, 100, 200].map((real) => ({\n id: `metric-${real}`,\n label: `1:${real}`,\n paper: 1,\n real,\n unit: 'm' as const,\n })),\n { id: 'imperial-quarter', label: '1/4 in = 1 ft', paper: 1, real: 48, unit: 'ft' },\n { id: 'imperial-eighth', label: '1/8 in = 1 ft', paper: 1, real: 96, unit: 'ft' },\n];\n\n/** The scale of a page that has none, from the `defaultScale` setting. */\nexport const defaultMeasure = (\n defaultScale: MeasurementSettings['defaultScale'],\n userUnit = 1,\n): PdfMeasure =>\n typeof defaultScale === 'object'\n ? defaultScale\n : measureFromRatio(1, 1, defaultScale === 'imperial' ? 'ft' : 'm', { userUnit });\n\nexport function withUnit(\n measure: PdfMeasure,\n unit: LengthUnit,\n area: AreaUnit = squareOf(unit),\n): PdfMeasure {\n const basis = measure.x[0]?.unit.trim() as LengthUnit;\n if (!(basis in METRES)) {\n throw new RangeError('This scale has an unknown base unit; calibrate it first');\n }\n const previous = measure.distance[measure.distance.length - 1];\n const precision = previous?.fraction === 'fraction' ? 100 : (previous?.precision ?? 100);\n return {\n ...measure,\n distance: [{ unit, conversion: METRES[basis] / METRES[unit], precision, fraction: 'decimal' }],\n area: [\n {\n unit: areaUnitLabel(area),\n conversion: METRES[basis] ** 2 / squareMetres(area),\n precision,\n fraction: 'decimal',\n },\n ],\n };\n}\n\nexport function withPrecision(measure: PdfMeasure, precision: number): PdfMeasure {\n if (!Number.isInteger(Math.log10(precision)) || precision < 1 || precision > 1e9) {\n throw new RangeError('Invalid decimal precision');\n }\n const update = (formats: PdfMeasure['distance']) =>\n formats.map((format, index) =>\n index === formats.length - 1\n ? { ...format, precision, fraction: 'decimal' as const }\n : format,\n );\n return { ...measure, distance: update(measure.distance), area: update(measure.area) };\n}\n\n/** Change the area display unit while preserving imported distance formatting. */\nexport function withAreaUnit(measure: PdfMeasure, unit: AreaUnit): PdfMeasure {\n const basis = measure.x[0]?.unit.trim() as LengthUnit;\n if (!(basis in METRES)) {\n throw new RangeError('This scale has an unknown base unit; calibrate it first');\n }\n const previous = measure.area[measure.area.length - 1];\n const precision = previous?.fraction === 'fraction' ? 100 : (previous?.precision ?? 100);\n return {\n ...measure,\n area: [\n {\n unit: areaUnitLabel(unit),\n conversion: METRES[basis] ** 2 / squareMetres(unit),\n precision,\n fraction: 'decimal',\n },\n ],\n };\n}\n","/** The package token, created once here and re-exported by `contract.ts`. */\nimport { createCapabilityToken } from '@embedpdf/core';\n\nimport type { MeasurementCapability } from './contract';\n\nexport const MeasurementToken = createCapabilityToken<MeasurementCapability>('measurement', {\n hint: `add measurementPlugin() from '@embedpdf/plugin-measurement' to your plugins list`,\n // Without a document, an adapter's stand-in rejects these with `not-ready`, as the\n // capability would; the type asks for every member that returns a promise.\n promises: {\n ensureLoaded: true,\n setScale: true,\n calibrate: true,\n setUnit: true,\n setAreaUnit: true,\n setPrecision: true,\n setPreset: true,\n clearScale: true,\n createMeasurement: true,\n },\n});\n","/**\n * @embedpdf/plugin-measurement/contract: the public measurement vocabulary:\n * page scale, calibration, scale-aware readouts, and measurement creation.\n * Creating a measurement annotation is `annotation.create` with a\n * measurement tool; this plugin adds the page-scale sugar around it.\n */\nimport type { EventHook, OperationOptions, SettingsApi } from '@embedpdf/core';\nimport type { Point } from '@embedpdf/core-geometry';\nimport type {\n Annotation,\n AnnotationRef,\n AreaUnit,\n LengthUnit,\n MeasurementReadout,\n MeasurementUnavailable,\n PageRef,\n PdfMeasure,\n PdfMeasurement,\n SerializedEngineError,\n} from '@embedpdf/engine-core/runtime';\nimport type { RecalibrationReport } from '@embedpdf/plugin-annotation/contract/host';\n\nimport { DEFAULT_PRESETS } from './scale';\n\nexport { MeasurementToken } from './token';\nexport type { RecalibrationReport } from '@embedpdf/plugin-annotation/contract/host';\nexport type {\n AnnotationRef,\n AreaUnit,\n LengthUnit,\n MeasurementReadout,\n MeasurementUnavailable,\n PdfMeasure,\n} from '@embedpdf/engine-core/runtime';\n\n/** One page, several pages, or every page of the document: each a ref or an index. */\nexport type PageTarget = PageRef | number | readonly (PageRef | number)[] | 'all';\n\nexport interface ScalePreset {\n id: string;\n label: string;\n paper: number;\n real: number;\n unit: LengthUnit;\n}\n\n/**\n * The measurement plugin's settings. `measurementPlugin(config)` registers\n * them over {@link MEASUREMENT_DEFAULTS}, and `updateSettings()` changes them\n * for every document while the app runs.\n */\nexport interface MeasurementSettings {\n /**\n * The scale of a page that has none: 1:1 in meters (`'metric'`) or in\n * feet (`'imperial'`), or a scale of your own.\n */\n readonly defaultScale: 'metric' | 'imperial' | PdfMeasure;\n /** The scales `listPresets()` offers. */\n readonly presets: readonly ScalePreset[];\n}\n\n/** What the measurement settings are when the app registers none. */\nexport const MEASUREMENT_DEFAULTS: MeasurementSettings = {\n defaultScale: 'metric',\n presets: DEFAULT_PRESETS,\n};\n\n/** What `measurementPlugin(config)` takes: either setting, each a whole value. */\nexport type MeasurementConfig = Partial<MeasurementSettings>;\n\nexport interface ScaleChangeOptions extends OperationOptions {\n /** Re-measure the page's measurement annotations with the new scale. Default true. */\n recalculate?: boolean;\n}\n\nexport interface PageScale {\n measure: PdfMeasurement | null;\n source: 'owned' | 'foreign' | 'default';\n /** The page's viewports are known. */\n ready: boolean;\n /** The engine can persist a scale into the document. */\n persistent: boolean;\n /** Why the last read of the page's viewports failed; the last known viewports stay in effect. */\n error?: SerializedEngineError;\n}\n\n/**\n * A page's scale where there is none to read: before a document opens, or for\n * a page that isn't there. Not ready, with no measure; the same object every\n * time, so a framework read compares it by identity.\n */\nexport const NO_PAGE_SCALE: PageScale = Object.freeze({\n measure: null,\n source: 'default',\n ready: false,\n persistent: false,\n});\n\n/**\n * The readout where there is no measurement to read: before a document\n * opens, or for an annotation that isn't there. `getReadout` answers it too.\n */\nexport const NO_READOUT: MeasurementUnavailable = Object.freeze({ unavailable: 'not-dimension' });\n\n/** Two points captured by the calibrate tool, awaiting the real length. Page space. */\nexport interface CalibrationRequest {\n page: PageRef;\n from: Point;\n to: Point;\n /** The captured length in PDF user space (the scale is derived from it). */\n userSpaceLength: number;\n}\n\nexport interface CalibrateInput {\n /** The page, as a ref or an index. */\n page: PageRef | number;\n /** The two ends of the known length, in page space. */\n from: Point;\n to: Point;\n /** The real-world length between the two points. */\n distance: { value: number; unit: LengthUnit };\n}\n\n/** A failed viewport write is reported per page for a multi-page change.\n * Single-page failures reject before any annotation write. */\nexport type ScaleChangeReport =\n | (RecalibrationReport & { scaleError?: never })\n | {\n page: PageRef;\n scale?: never;\n error?: never;\n updated: [];\n skipped: [];\n failed: [];\n scaleError: SerializedEngineError;\n };\n\nexport type MeasurementKind = 'distance' | 'perimeter' | 'area';\n\nexport interface CreateMeasurementInput {\n kind: MeasurementKind;\n /** The page, as a ref or an index. */\n page: PageRef | number;\n /** Page-space points: two for a distance, the vertices otherwise. */\n points: readonly Point[];\n /** The authoring tool whose defaults apply (defaults to the kind's own tool). */\n tool?: string;\n}\n\n// ── events ──\nexport interface MeasurementScaleChangedEvent {\n readonly page: PageRef;\n readonly report: ScaleChangeReport;\n}\nexport interface CalibrationRequestedEvent {\n readonly request: CalibrationRequest;\n}\nexport interface CalibrationCompletedEvent {\n readonly page: PageRef;\n}\nexport type CalibrationDismissedEvent = Record<string, never>;\n\n/**\n * Page scales, calibration and measurement readouts for one document. A page\n * argument is a ref or an index: a read given a page that isn't there\n * answers empty, a verb refuses it with `not-found`. Every async verb takes\n * a `signal`. The settings belong to the plugin, not to a document: a change\n * reaches every open document.\n */\nexport interface MeasurementCapability extends SettingsApi<MeasurementSettings> {\n // ── twins ──\n /** May write a page scale and calibrate (`doc.annotate.modify`). */\n canCalibrate(): boolean;\n /** May measure on the page: `annotations:create`, and the page's scale has loaded. */\n canMeasure(page: PageRef | number): boolean;\n\n // ── reading ──\n /** The resolved scale, its source and readiness. Reference-stable while unchanged. */\n getPageScale(page: PageRef | number): PageScale;\n /** Load the page's viewports. Resolves at once when already known. Rejects `not-found`. */\n ensureLoaded(page: PageRef | number, options?: OperationOptions): Promise<void>;\n /** A scale change is in flight. */\n isBusy(): boolean;\n /** The reports of the last scale change, one per page. */\n listLastReports(): readonly ScaleChangeReport[];\n listPresets(): readonly ScalePreset[];\n listUnits(): readonly LengthUnit[];\n listAreaUnits(): readonly AreaUnit[];\n /**\n * The formatted measurement of a measurement annotation, or why there is\n * none ({@link NO_READOUT} for an annotation that isn't there). The same\n * object until the annotation changes.\n */\n getReadout(ref: AnnotationRef): MeasurementReadout | MeasurementUnavailable;\n /** A distance between two page-space points in the page's scale: a pure conversion, no annotation. */\n measureDistance(\n page: PageRef | number,\n from: Point,\n to: Point,\n ): MeasurementReadout | MeasurementUnavailable;\n /** The area of a page-space polygon in the page's scale: a pure conversion, no annotation. */\n measureArea(\n page: PageRef | number,\n vertices: readonly Point[],\n ): MeasurementReadout | MeasurementUnavailable;\n /** Two points captured, awaiting a length. */\n getCalibrationRequest(): CalibrationRequest | null;\n\n // ── the scale ──\n // Every scale change rejects `permission-denied` without\n // `doc.annotate.modify` (see `canCalibrate`), and `not-found` for a page\n // that isn't in the document.\n setScale(\n pages: PageTarget,\n measure: PdfMeasure,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n /** Derive a scale from a known length between two page points. `applyTo` widens the write (default: the input page). Fires `onCalibrationCompleted`. */\n calibrate(\n input: CalibrateInput,\n options?: ScaleChangeOptions & { applyTo?: PageTarget },\n ): Promise<readonly ScaleChangeReport[]>;\n setUnit(\n pages: PageTarget,\n unit: LengthUnit,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n setAreaUnit(\n pages: PageTarget,\n unit: AreaUnit,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n setPrecision(\n pages: PageTarget,\n precision: number,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n /** A scale from the `presets` setting. Rejects `not-found` for an unknown preset. */\n setPreset(\n pages: PageTarget,\n presetId: string,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n /** Back to the default scale (the owned viewport is removed). */\n clearScale(\n pages: PageTarget,\n options?: ScaleChangeOptions,\n ): Promise<readonly ScaleChangeReport[]>;\n\n // ── measuring ──\n /**\n * Create a measurement annotation with the page's scale and the tool's\n * style. Resolves `{ annotation }`. Rejects `permission-denied` without\n * `annotations:create`, `not-ready` before the scale is known,\n * `not-found` for a page that isn't in the document.\n */\n createMeasurement(\n input: CreateMeasurementInput,\n options?: OperationOptions,\n ): Promise<{ annotation: Annotation }>;\n\n // ── calibration flow ──\n /**\n * Arm the calibrate tool (two points, then `onCalibrationRequested`).\n * Throws `permission-denied` without `doc.annotate.modify` (see `canCalibrate`).\n */\n startCalibration(): void;\n /** Drop the pending request. Fires `onCalibrationDismissed` when there was one. */\n dismissCalibration(): void;\n\n // ── events ──\n readonly onScaleChanged: EventHook<MeasurementScaleChangedEvent>;\n readonly onCalibrationRequested: EventHook<CalibrationRequestedEvent>;\n readonly onCalibrationCompleted: EventHook<CalibrationCompletedEvent>;\n readonly onCalibrationDismissed: EventHook<CalibrationDismissedEvent>;\n}\n"],"mappings":";;;;;AAaA,MAAa,kBAA0C;CACrD,GAAG;EAAC;EAAG;EAAI;EAAI;EAAI;EAAK;CAAG,CAAC,CAAC,KAAK,UAAU;EAC1C,IAAI,UAAU;EACd,OAAO,KAAK;EACZ,OAAO;EACP;EACA,MAAM;CACR,EAAE;CACF;EAAE,IAAI;EAAoB,OAAO;EAAiB,OAAO;EAAG,MAAM;EAAI,MAAM;CAAK;CACjF;EAAE,IAAI;EAAmB,OAAO;EAAiB,OAAO;EAAG,MAAM;EAAI,MAAM;CAAK;AAClF;;AAGA,MAAa,kBACX,cACA,WAAW,MAEX,OAAO,iBAAiB,WACpB,gBAAA,GAAA,8BAAA,iBAAA,CACiB,GAAG,GAAG,iBAAiB,aAAa,OAAO,KAAK,EAAE,SAAS,CAAC;AAEnF,SAAgB,SACd,SACA,MACA,QAAA,GAAA,8BAAA,SAAA,CAA0B,IAAI,GAClB;CACZ,MAAM,QAAQ,QAAQ,EAAE,EAAE,EAAE,KAAK,KAAK;CACtC,IAAI,EAAE,SAASA,8BAAAA,SACb,MAAM,IAAI,WAAW,yDAAyD;CAEhF,MAAM,WAAW,QAAQ,SAAS,QAAQ,SAAS,SAAS;CAC5D,MAAM,YAAY,UAAU,aAAa,aAAa,MAAO,UAAU,aAAa;CACpF,OAAO;EACL,GAAG;EACH,UAAU,CAAC;GAAE;GAAM,YAAYA,8BAAAA,OAAO,SAASA,8BAAAA,OAAO;GAAO;GAAW,UAAU;EAAU,CAAC;EAC7F,MAAM,CACJ;GACE,OAAA,GAAA,8BAAA,cAAA,CAAoB,IAAI;GACxB,YAAYA,8BAAAA,OAAO,UAAU,KAAA,GAAA,8BAAA,aAAA,CAAiB,IAAI;GAClD;GACA,UAAU;EACZ,CACF;CACF;AACF;AAEA,SAAgB,cAAc,SAAqB,WAA+B;CAChF,IAAI,CAAC,OAAO,UAAU,KAAK,MAAM,SAAS,CAAC,KAAK,YAAY,KAAK,YAAY,KAC3E,MAAM,IAAI,WAAW,2BAA2B;CAElD,MAAM,UAAU,YACd,QAAQ,KAAK,QAAQ,UACnB,UAAU,QAAQ,SAAS,IACvB;EAAE,GAAG;EAAQ;EAAW,UAAU;CAAmB,IACrD,MACN;CACF,OAAO;EAAE,GAAG;EAAS,UAAU,OAAO,QAAQ,QAAQ;EAAG,MAAM,OAAO,QAAQ,IAAI;CAAE;AACtF;;AAGA,SAAgB,aAAa,SAAqB,MAA4B;CAC5E,MAAM,QAAQ,QAAQ,EAAE,EAAE,EAAE,KAAK,KAAK;CACtC,IAAI,EAAE,SAASA,8BAAAA,SACb,MAAM,IAAI,WAAW,yDAAyD;CAEhF,MAAM,WAAW,QAAQ,KAAK,QAAQ,KAAK,SAAS;CACpD,MAAM,YAAY,UAAU,aAAa,aAAa,MAAO,UAAU,aAAa;CACpF,OAAO;EACL,GAAG;EACH,MAAM,CACJ;GACE,OAAA,GAAA,8BAAA,cAAA,CAAoB,IAAI;GACxB,YAAYA,8BAAAA,OAAO,UAAU,KAAA,GAAA,8BAAA,aAAA,CAAiB,IAAI;GAClD;GACA,UAAU;EACZ,CACF;CACF;AACF;;;;ACtFA,MAAa,oBAAA,GAAA,eAAA,sBAAA,CAAgE,eAAe;CAC1F,MAAM;CAGN,UAAU;EACR,cAAc;EACd,UAAU;EACV,WAAW;EACX,SAAS;EACT,aAAa;EACb,cAAc;EACd,WAAW;EACX,YAAY;EACZ,mBAAmB;CACrB;AACF,CAAC;;;;AC0CD,MAAa,uBAA4C;CACvD,cAAc;CACd,SAAS;AACX;;;;;;AA0BA,MAAa,gBAA2B,OAAO,OAAO;CACpD,SAAS;CACT,QAAQ;CACR,OAAO;CACP,YAAY;AACd,CAAC;;;;;AAMD,MAAa,aAAqC,OAAO,OAAO,EAAE,aAAa,gBAAgB,CAAC"}