@embedpdf/viewer-chrome 3.0.0-next.13 → 3.0.0-next.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -11,6 +11,7 @@ import { StageToken } from "@embedpdf/react/stage";
11
11
  import { SearchToken } from "@embedpdf/react/search";
12
12
  import { SelectionToken } from "@embedpdf/react/selection";
13
13
  import { FormToken } from "@embedpdf/react/form";
14
+ import { MeasurementCapability, MeasurementConfig, MeasurementToken, ScaleChangeReport } from "@embedpdf/react/measurement";
14
15
  import { RedactionToken } from "@embedpdf/react/redaction";
15
16
  import { StampToken } from "@embedpdf/react/stamp";
16
17
  import { InteractionToken } from "@embedpdf/react/interaction";
@@ -25,6 +26,10 @@ type PathSpec = string | {
25
26
  /** Stroke paint: a slot name = that accent color (currentColor when
26
27
  * the accent is absent). */
27
28
  stroke?: 'primary' | 'secondary';
29
+ strokeWidth?: number;
30
+ strokeDasharray?: string;
31
+ strokeOpacity?: number;
32
+ fillOpacity?: number;
28
33
  };
29
34
  /** A registrable icon: 24×24 stroke path data (Tabler/Lucide-style), exactly
30
35
  * the shape of the built-in set — user icons are not a lesser kind. */
@@ -190,5 +195,5 @@ interface FullViewerProps extends ViewerCustomization {
190
195
  }
191
196
  declare function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale, disabledCategories, chrome, stamps, signatures, annotations, theme, themeTarget, onViewer }: FullViewerProps): import("react/jsx-runtime").JSX.Element;
192
197
  //#endregion
193
- export { type AddItemSpec, type AnnotationFontSpec, AnnotationToken, type AnnotationsCustomization, type BarChild, type BarGroup, type BarItem, type BarSchema, type BarSections, type CapabilityToken, type ChromeHelpers, type ChromeSchema, type CommandDef, CommandsToken, type CustomItem, type DocInfo, type DocumentsCapability, DocumentsToken, type Engine, type EngineFactory, FormToken, FullViewer, type FullViewerProps, I18nToken, type IconDef, type Importance, type InitialDocument, InteractionToken, type Mark, type MenuSchema, type MenuSection, MetadataToken, type PathSpec, RedactionToken, type ResolvedCommand, type ScopedViewerHandle, SearchToken, SelectionToken, ShellToken, type SignFieldInput, type SignatureMode, SignatureToken, type SignaturesCustomization, type SignerPort, StageToken, StampToken, type StampsCustomization, type ThemeConfig, type ThemeMode, type ThemePreference, type ThemeTokens, type TrustPort, type Unsubscribe, type Variant, type ViewerCustomization, type ViewerHandle, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
198
+ export { type AddItemSpec, type AnnotationFontSpec, AnnotationToken, type AnnotationsCustomization, type BarChild, type BarGroup, type BarItem, type BarSchema, type BarSections, type CapabilityToken, type ChromeHelpers, type ChromeSchema, type CommandDef, CommandsToken, type CustomItem, type DocInfo, type DocumentsCapability, DocumentsToken, type Engine, type EngineFactory, FormToken, FullViewer, type FullViewerProps, I18nToken, type IconDef, type Importance, type InitialDocument, InteractionToken, type Mark, type MeasurementCapability, type MeasurementConfig, MeasurementToken, type MenuSchema, type MenuSection, MetadataToken, type PathSpec, RedactionToken, type ResolvedCommand, type ScaleChangeReport, type ScopedViewerHandle, SearchToken, SelectionToken, ShellToken, type SignFieldInput, type SignatureMode, SignatureToken, type SignaturesCustomization, type SignerPort, StageToken, StampToken, type StampsCustomization, type ThemeConfig, type ThemeMode, type ThemePreference, type ThemeTokens, type TrustPort, type Unsubscribe, type Variant, type ViewerCustomization, type ViewerHandle, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
194
199
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { InteractionToken } from "@embedpdf/react/interaction";
7
7
  import { SelectionToken } from "@embedpdf/react/selection";
8
8
  import { AnnotationToken } from "@embedpdf/react/annotation";
9
9
  import { StampToken } from "@embedpdf/react/stamp";
10
+ import { MeasurementCapability, MeasurementConfig, MeasurementToken, ScaleChangeReport } from "@embedpdf/react/measurement";
10
11
  import { RedactionToken } from "@embedpdf/react/redaction";
11
12
  import { FormToken } from "@embedpdf/react/form";
12
13
  import { Mark, SignFieldInput, SignatureMode, SignatureMode as SignatureMode$1, SignatureToken, SignerPort, SignerPort as SignerPort$1, TrustPort, TrustPort as TrustPort$1, createTestSigner, indexedDbKeyStore, memoryKeyStore, personalSigner, remoteSigner, webCryptoSigner } from "@embedpdf/react/signature";
@@ -25,6 +26,10 @@ type PathSpec = string | {
25
26
  /** Stroke paint: a slot name = that accent color (currentColor when
26
27
  * the accent is absent). */
27
28
  stroke?: 'primary' | 'secondary';
29
+ strokeWidth?: number;
30
+ strokeDasharray?: string;
31
+ strokeOpacity?: number;
32
+ fillOpacity?: number;
28
33
  };
29
34
  /** A registrable icon: 24×24 stroke path data (Tabler/Lucide-style), exactly
30
35
  * the shape of the built-in set — user icons are not a lesser kind. */
@@ -190,5 +195,5 @@ interface FullViewerProps extends ViewerCustomization {
190
195
  }
191
196
  declare function FullViewer({ engine, initialDocuments, fallback, commands, icons, strings, locale, disabledCategories, chrome, stamps, signatures, annotations, theme, themeTarget, onViewer }: FullViewerProps): import("react/jsx-runtime").JSX.Element;
192
197
  //#endregion
193
- export { type AddItemSpec, type AnnotationFontSpec, AnnotationToken, type AnnotationsCustomization, type BarChild, type BarGroup, type BarItem, type BarSchema, type BarSections, type CapabilityToken, type ChromeHelpers, type ChromeSchema, type CommandDef, CommandsToken, type CustomItem, type DocInfo, type DocumentsCapability, DocumentsToken, type Engine, type EngineFactory, FormToken, FullViewer, type FullViewerProps, I18nToken, type IconDef, type Importance, type InitialDocument, InteractionToken, type Mark, type MenuSchema, type MenuSection, MetadataToken, type PathSpec, RedactionToken, type ResolvedCommand, type ScopedViewerHandle, SearchToken, SelectionToken, ShellToken, type SignFieldInput, type SignatureMode, SignatureToken, type SignaturesCustomization, type SignerPort, StageToken, StampToken, type StampsCustomization, type ThemeConfig, type ThemeMode, type ThemePreference, type ThemeTokens, type TrustPort, type Unsubscribe, type Variant, type ViewerCustomization, type ViewerHandle, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
198
+ export { type AddItemSpec, type AnnotationFontSpec, AnnotationToken, type AnnotationsCustomization, type BarChild, type BarGroup, type BarItem, type BarSchema, type BarSections, type CapabilityToken, type ChromeHelpers, type ChromeSchema, type CommandDef, CommandsToken, type CustomItem, type DocInfo, type DocumentsCapability, DocumentsToken, type Engine, type EngineFactory, FormToken, FullViewer, type FullViewerProps, I18nToken, type IconDef, type Importance, type InitialDocument, InteractionToken, type Mark, type MeasurementCapability, type MeasurementConfig, MeasurementToken, type MenuSchema, type MenuSection, MetadataToken, type PathSpec, RedactionToken, type ResolvedCommand, type ScaleChangeReport, type ScopedViewerHandle, SearchToken, SelectionToken, ShellToken, type SignFieldInput, type SignatureMode, SignatureToken, type SignaturesCustomization, type SignerPort, StageToken, StampToken, type StampsCustomization, type ThemeConfig, type ThemeMode, type ThemePreference, type ThemeTokens, type TrustPort, type Unsubscribe, type Variant, type ViewerCustomization, type ViewerHandle, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
194
199
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { InteractionToken, InteractionToken as InteractionToken$1, feedbackPlugi
9
9
  import { SelectionClipboard, SelectionHandles, SelectionLayer, SelectionMenu, SelectionToken, selectionPlugin } from "@embedpdf/react/selection";
10
10
  import { AnnotationLayer, AnnotationToken, AnnotationToken as AnnotationToken$1, annotationPlugin, refKey, useAnnotation, useAnnotationDefaults, useAnnotationSelected, useAnnotationSelection, useCommentThreads, useComments, useCommentsHydration, useFilePickerProvider, useSelectionFlags, useSelectionProps } from "@embedpdf/react/annotation";
11
11
  import { StampToken, StampToken as StampToken$1, indexedDbByteStore, persistStampLibraries, restoreStampLibraries, stampPlugin, useArmStampAsset, useStamp, useStampAssetPreviewUrl, useStampAssets, useStampLibraries } from "@embedpdf/react/stamp";
12
+ import { MeasurementToken, MeasurementToken as MeasurementToken$1, measurementPlugin, useMeasurement, usePageScale } from "@embedpdf/react/measurement";
12
13
  import { RedactionToken, RedactionToken as RedactionToken$1, redactionPlugin, usePendingRedactions, useRedaction } from "@embedpdf/react/redaction";
13
14
  import { actionsPlugin, useActionsUiAdapter } from "@embedpdf/react/actions";
14
15
  import { FormToken, FormToken as FormToken$1, formPlugin, formWidgetRenderer } from "@embedpdf/react/form";
@@ -81,6 +82,32 @@ const en = {
81
82
  code: "en",
82
83
  name: "English",
83
84
  translations: {
85
+ measurement: {
86
+ title: "Measurements",
87
+ distance: "Distance",
88
+ perimeter: "Perimeter",
89
+ area: "Area",
90
+ areaUnit: "Area unit",
91
+ resetLabel: "Reset label position",
92
+ calibrate: "Calibrate scale",
93
+ scale: "Scale",
94
+ custom: "Custom",
95
+ unavailable: "Unavailable",
96
+ instructions: "Drag to measure a distance, then click to place its dimension line. Click vertices for a perimeter or area and double-click to finish. Area boundaries must not cross; an unavailable preview cannot be finished. Press Escape to cancel. Drag the label itself to move it.",
97
+ sessionOnly: "This engine keeps page calibration for this session only.",
98
+ allPages: "Apply to all pages",
99
+ recalculate: "Recalculate existing measurements",
100
+ preset: "Scale preset",
101
+ choosePreset: "Choose a preset",
102
+ unit: "Unit",
103
+ precision: "Precision",
104
+ selection: "Selected measurements",
105
+ report: "{updated} updated · {skipped} skipped · {failed} failed",
106
+ knownLength: "Enter the real length represented by the line you just drew.",
107
+ length: "Known length",
108
+ apply: "Apply scale",
109
+ applying: "Applying…"
110
+ },
84
111
  commands: {
85
112
  zoom: {
86
113
  in: "Zoom In",
@@ -124,6 +151,7 @@ const en = {
124
151
  counterclockwise: "Rotate Counter-Clockwise"
125
152
  },
126
153
  mode: {
154
+ measure: "Measure",
127
155
  group: "Mode",
128
156
  view: "View",
129
157
  annotate: "Annotate",
@@ -133,6 +161,7 @@ const en = {
133
161
  redact: "Redact"
134
162
  },
135
163
  annotate: {
164
+ cancelCreation: "Cancel drawing",
136
165
  highlight: "Highlight",
137
166
  strikeout: "Strikeout",
138
167
  underline: "Underline",
@@ -432,7 +461,40 @@ function createViewerHandle(kernel) {
432
461
  }
433
462
  //#endregion
434
463
  //#region src/ui/icons.tsx
464
+ const measurementOutline = [{
465
+ d: "M5 4H19M20 5V19M19 20H5M4 19V5",
466
+ stroke: "primary"
467
+ }, {
468
+ d: "M3.1 3.1h1.8v1.8H3.1zM19.1 3.1h1.8v1.8h-1.8zM3.1 19.1h1.8v1.8H3.1zM19.1 19.1h1.8v1.8h-1.8z",
469
+ stroke: "primary"
470
+ }];
435
471
  const ICON_PATHS = {
472
+ ruler: ["M3 8h18v8H3z", "M7 8v4M11 8v2M15 8v4M19 8v2"],
473
+ distance: [{
474
+ d: "M5 12H19",
475
+ stroke: "primary",
476
+ strokeDasharray: "2 2"
477
+ }, {
478
+ d: "M3.5 10.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3M20.5 10.5a1.5 1.5 0 1 0 0 3a1.5 1.5 0 1 0 0-3",
479
+ stroke: "primary",
480
+ fill: "primary",
481
+ fillOpacity: .25
482
+ }],
483
+ perimeter: measurementOutline,
484
+ area: [
485
+ {
486
+ d: "M5 4H19V5H20V19H19V20H5V19H4V5H5Z",
487
+ fill: "primary",
488
+ fillOpacity: .08
489
+ },
490
+ {
491
+ d: "M4.5 7.5L7.5 4.5M4.5 10.5L10.5 4.5M4.5 13.5L13.5 4.5M4.5 16.5L16.5 4.5M4.5 19.5L19.5 4.5M7.5 19.5L19.5 7.5M10.5 19.5L19.5 10.5M13.5 19.5L19.5 13.5M16.5 19.5L19.5 16.5",
492
+ stroke: "primary",
493
+ strokeWidth: .6,
494
+ strokeOpacity: .5
495
+ },
496
+ ...measurementOutline
497
+ ],
436
498
  alertTriangle: ["M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"],
437
499
  alignBottom: [
438
500
  "M3 19v-14a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z",
@@ -482,6 +544,11 @@ const ICON_PATHS = {
482
544
  "M19 16h-12a2 2 0 0 0 -2 2",
483
545
  "M9 8h6"
484
546
  ],
547
+ calibrate: [
548
+ "M12 12m-7.5 0a7.5 7.5 0 1 0 15 0a7.5 7.5 0 1 0 -15 0",
549
+ "M12 2.75V7M12 17V21.25M2.75 12H7M17 12H21.25",
550
+ "M12 12m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0"
551
+ ],
485
552
  callout: [
486
553
  {
487
554
  d: "M20 6v4a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1",
@@ -904,6 +971,11 @@ const ICON_PATHS = {
904
971
  ],
905
972
  ungroup: ["M4 5a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z", "M13 14a1 1 0 0 1 1 -1h5a1 1 0 0 1 1 1v5a1 1 0 0 1 -1 1h-5a1 1 0 0 1 -1 -1z"],
906
973
  unlock: ["M7 11V7a5 5 0 0 1 9.9-1"],
974
+ updateScale: [
975
+ "M3.5 13.5L18.5 7.5L21.25 14.5L6.25 20.5L3.5 13.5Z",
976
+ "M8 12.8L9 15.2M11.5 11.4L12.2 13.2M15 10L16 12.4",
977
+ "M4 9.39849C4.10585 8.34804 4.487 7.34411 5.10497 6.48809C5.72294 5.63207 6.55584 4.95427 7.51957 4.52312C8.4833 4.09198 9.54373 3.92277 10.5938 4.03257C11.6438 4.14237 12.6463 4.5273 13.5 5.14849M11.5 6.00003L13.5 5.14849L12.7807 2.96999"
978
+ ],
907
979
  vertical: [
908
980
  "M8 7l4 -4l4 4",
909
981
  "M8 17l4 4l4 -4",
@@ -973,7 +1045,11 @@ function Icon({ name, size = 20, strokeWidth = 2, className, title, accent }) {
973
1045
  return /* @__PURE__ */ jsx("path", {
974
1046
  d: p.d,
975
1047
  fill: fill ?? "none",
976
- stroke
1048
+ stroke,
1049
+ strokeWidth: p.strokeWidth,
1050
+ strokeDasharray: p.strokeDasharray,
1051
+ strokeOpacity: p.strokeOpacity,
1052
+ fillOpacity: p.fillOpacity
977
1053
  }, i);
978
1054
  })
979
1055
  });
@@ -1007,6 +1083,335 @@ function ThemeProvider({ children, preference = "system", target }) {
1007
1083
  });
1008
1084
  }
1009
1085
  //#endregion
1086
+ //#region src/ui/measurement.tsx
1087
+ const control = "border-border bg-surface text-fg w-full rounded-md border px-2 py-1.5 text-sm disabled:opacity-50";
1088
+ const button = "border-border text-fg hover:bg-hover rounded-md border px-3 py-1.5 text-sm disabled:opacity-50";
1089
+ const useCurrentPon = () => useSelector(StageToken$1, (c) => c.pages()[c.currentPage()]?.pon ?? -1);
1090
+ function MeasurementScaleButton() {
1091
+ const t = useT();
1092
+ const scale = usePageScale(useCurrentPon());
1093
+ const surface = useSurface("measurement");
1094
+ return /* @__PURE__ */ jsxs("button", {
1095
+ className: `${button} inline-flex items-center gap-1.5 whitespace-nowrap`,
1096
+ onClick: () => surface.open({ exclusive: "right" }),
1097
+ title: t("measurement.title"),
1098
+ children: [/* @__PURE__ */ jsx(Icon, {
1099
+ name: "updateScale",
1100
+ size: 20,
1101
+ className: "shrink-0"
1102
+ }), /* @__PURE__ */ jsxs("span", { children: [
1103
+ t("measurement.scale"),
1104
+ ":",
1105
+ " ",
1106
+ scale.measure?.subtype === "RL" ? scale.measure.ratio ?? t("measurement.custom") : t("measurement.unavailable")
1107
+ ] })]
1108
+ });
1109
+ }
1110
+ function MeasurementSection() {
1111
+ const t = useT();
1112
+ const pon = useCurrentPon();
1113
+ const measurement = useMeasurement();
1114
+ const scale = usePageScale(pon);
1115
+ const anno = useCapability(AnnotationToken$1);
1116
+ const selected = useSelector(AnnotationToken$1, (c) => c.selection());
1117
+ const resettable = useSelector(AnnotationToken$1, (c) => c.getSelected().filter((annotation) => (annotation.subtype === "polygon" || annotation.subtype === "polyline") && annotation.caption?.center && c.canEdit(annotation.ref) && !annotation.flags.lockedContents), (a, b) => a.length === b.length && a.every((annotation, i) => annotation === b[i]));
1118
+ const readouts = useSelector(MeasurementToken$1, (c) => anno.getSelected().map((d) => c.readout(d.ref)).filter((r) => !("unavailable" in r)), (a, b) => JSON.stringify(a) === JSON.stringify(b));
1119
+ const reports = useSelector(MeasurementToken$1, (c) => c.lastReports());
1120
+ const [allPages, setAllPages] = useState(false);
1121
+ const [recalculate, setRecalculate] = useState(true);
1122
+ const [error, setError] = useState(null);
1123
+ const disabled = measurement.busy || !measurement.canCalibratePage || !scale.ready;
1124
+ const rectilinear = scale.measure?.subtype === "RL" ? scale.measure : null;
1125
+ const options = {
1126
+ allPages,
1127
+ recalculate
1128
+ };
1129
+ const run = async (work) => {
1130
+ setError(null);
1131
+ try {
1132
+ await work();
1133
+ } catch (e) {
1134
+ setError(e instanceof Error ? e.message : String(e));
1135
+ }
1136
+ };
1137
+ const precision = rectilinear?.distance[rectilinear.distance.length - 1]?.precision ?? 100;
1138
+ return /* @__PURE__ */ jsxs("div", {
1139
+ className: "flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 text-sm",
1140
+ children: [
1141
+ /* @__PURE__ */ jsx("p", {
1142
+ className: "text-fg-muted",
1143
+ children: t("measurement.instructions")
1144
+ }),
1145
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("span", {
1146
+ className: "text-fg-muted",
1147
+ children: [t("measurement.scale"), ": "]
1148
+ }), /* @__PURE__ */ jsx("strong", { children: rectilinear?.ratio ?? t("measurement.unavailable") })] }),
1149
+ scale.error && /* @__PURE__ */ jsx("p", {
1150
+ role: "alert",
1151
+ className: "text-red-600",
1152
+ children: scale.error.message
1153
+ }),
1154
+ !scale.persistent && scale.ready && /* @__PURE__ */ jsx("p", {
1155
+ className: "text-fg-muted",
1156
+ children: t("measurement.sessionOnly")
1157
+ }),
1158
+ /* @__PURE__ */ jsxs("label", {
1159
+ className: "flex items-center gap-2",
1160
+ children: [/* @__PURE__ */ jsx("input", {
1161
+ type: "checkbox",
1162
+ checked: allPages,
1163
+ disabled: measurement.busy,
1164
+ onChange: (e) => setAllPages(e.target.checked)
1165
+ }), t("measurement.allPages")]
1166
+ }),
1167
+ /* @__PURE__ */ jsxs("label", {
1168
+ className: "flex items-center gap-2",
1169
+ children: [/* @__PURE__ */ jsx("input", {
1170
+ type: "checkbox",
1171
+ checked: recalculate,
1172
+ disabled: measurement.busy,
1173
+ onChange: (e) => setRecalculate(e.target.checked)
1174
+ }), t("measurement.recalculate")]
1175
+ }),
1176
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.preset"), /* @__PURE__ */ jsxs("select", {
1177
+ "aria-label": t("measurement.preset"),
1178
+ className: control,
1179
+ value: "",
1180
+ disabled,
1181
+ onChange: (e) => void run(() => measurement.setPreset(pon, e.target.value, options)),
1182
+ children: [/* @__PURE__ */ jsx("option", {
1183
+ value: "",
1184
+ disabled: true,
1185
+ children: t("measurement.choosePreset")
1186
+ }), measurement.presets().map((p) => /* @__PURE__ */ jsx("option", {
1187
+ value: p.id,
1188
+ children: p.label
1189
+ }, p.id))]
1190
+ })] }),
1191
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.unit"), /* @__PURE__ */ jsxs("select", {
1192
+ "aria-label": t("measurement.unit"),
1193
+ className: control,
1194
+ value: rectilinear?.distance[0]?.unit.trim() ?? "",
1195
+ disabled: disabled || !rectilinear,
1196
+ onChange: (e) => void run(() => measurement.setUnit(pon, e.target.value, void 0, options)),
1197
+ children: [!measurement.units().includes(rectilinear?.distance[0]?.unit.trim()) && /* @__PURE__ */ jsx("option", {
1198
+ value: rectilinear?.distance[0]?.unit.trim() ?? "",
1199
+ children: t("measurement.custom")
1200
+ }), measurement.units().map((u) => /* @__PURE__ */ jsx("option", {
1201
+ value: u,
1202
+ children: u
1203
+ }, u))]
1204
+ })] }),
1205
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.areaUnit"), /* @__PURE__ */ jsxs("select", {
1206
+ "aria-label": t("measurement.areaUnit"),
1207
+ className: control,
1208
+ value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
1209
+ disabled: disabled || !rectilinear,
1210
+ onChange: (e) => void run(() => measurement.setAreaUnit(pon, e.target.value, options)),
1211
+ children: [!measurement.areaUnits().includes(rectilinear?.area[0]?.unit.trim().replace("²", "2")) && /* @__PURE__ */ jsx("option", {
1212
+ value: rectilinear?.area[0]?.unit.trim().replace("²", "2") ?? "",
1213
+ children: t("measurement.custom")
1214
+ }), measurement.areaUnits().map((unit) => /* @__PURE__ */ jsx("option", {
1215
+ value: unit,
1216
+ children: unit.replace("2", "²")
1217
+ }, unit))]
1218
+ })] }),
1219
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.precision"), /* @__PURE__ */ jsxs("select", {
1220
+ "aria-label": t("measurement.precision"),
1221
+ className: control,
1222
+ value: precision,
1223
+ disabled: disabled || !rectilinear,
1224
+ onChange: (e) => void run(() => measurement.setPrecision(pon, Number(e.target.value), options)),
1225
+ children: [![
1226
+ 1,
1227
+ 10,
1228
+ 100,
1229
+ 1e3,
1230
+ 1e4
1231
+ ].includes(precision) && /* @__PURE__ */ jsx("option", {
1232
+ value: precision,
1233
+ children: t("measurement.custom")
1234
+ }), [
1235
+ 1,
1236
+ 10,
1237
+ 100,
1238
+ 1e3,
1239
+ 1e4
1240
+ ].map((p) => /* @__PURE__ */ jsx("option", {
1241
+ value: p,
1242
+ children: 1 / p
1243
+ }, p))]
1244
+ })] }),
1245
+ /* @__PURE__ */ jsx("button", {
1246
+ className: button,
1247
+ disabled,
1248
+ onClick: () => measurement.startCalibration(),
1249
+ children: t("measurement.calibrate")
1250
+ }),
1251
+ selected.length > 0 && /* @__PURE__ */ jsxs("section", {
1252
+ className: "border-border-subtle border-t pt-3",
1253
+ children: [
1254
+ /* @__PURE__ */ jsx("h3", {
1255
+ className: "mb-1 font-medium",
1256
+ children: t("measurement.selection")
1257
+ }),
1258
+ readouts.length ? readouts.map((readout, i) => /* @__PURE__ */ jsxs("div", {
1259
+ className: "mb-2",
1260
+ children: [/* @__PURE__ */ jsxs("p", { children: [
1261
+ t(`measurement.${readout.kind}`),
1262
+ ": ",
1263
+ readout.label
1264
+ ] }), readout.perimeter && /* @__PURE__ */ jsxs("p", { children: [
1265
+ t("measurement.perimeter"),
1266
+ ": ",
1267
+ readout.perimeter
1268
+ ] })]
1269
+ }, i)) : /* @__PURE__ */ jsx("p", {
1270
+ className: "text-fg-muted",
1271
+ children: t("measurement.unavailable")
1272
+ }),
1273
+ resettable.length > 0 && /* @__PURE__ */ jsx("button", {
1274
+ className: button,
1275
+ disabled: measurement.busy,
1276
+ onClick: () => void run(async () => {
1277
+ for (const annotation of resettable) if (annotation.subtype === "polygon" || annotation.subtype === "polyline") await anno.update(annotation.ref, {
1278
+ subtype: annotation.subtype,
1279
+ caption: { center: null }
1280
+ });
1281
+ }),
1282
+ children: t("measurement.resetLabel")
1283
+ })
1284
+ ]
1285
+ }),
1286
+ reports.length > 0 && /* @__PURE__ */ jsx("p", {
1287
+ role: "status",
1288
+ children: t("measurement.report", { params: {
1289
+ updated: reports.reduce((n, r) => n + r.updated.length, 0),
1290
+ skipped: reports.reduce((n, r) => n + r.skipped.length, 0),
1291
+ failed: reports.reduce((n, r) => n + r.failed.length + (r.scaleError || r.error ? 1 : 0), 0)
1292
+ } })
1293
+ }),
1294
+ error && /* @__PURE__ */ jsx("p", {
1295
+ role: "alert",
1296
+ className: "text-red-600",
1297
+ children: error
1298
+ })
1299
+ ]
1300
+ });
1301
+ }
1302
+ function CalibrationDialog() {
1303
+ const t = useT();
1304
+ const measurement = useMeasurement();
1305
+ const request = useSelector(MeasurementToken$1, (c) => c.calibrationRequest());
1306
+ const input = useRef(null);
1307
+ const [value, setValue] = useState("");
1308
+ const [unit, setUnit] = useState("m");
1309
+ const [allPages, setAllPages] = useState(false);
1310
+ const [recalculate, setRecalculate] = useState(true);
1311
+ const [error, setError] = useState(null);
1312
+ useEffect(() => {
1313
+ setValue("");
1314
+ setError(null);
1315
+ input.current?.focus();
1316
+ }, [request]);
1317
+ if (!request) return null;
1318
+ const submit = async () => {
1319
+ setError(null);
1320
+ try {
1321
+ await measurement.calibrate(request.pon, request.from, request.to, {
1322
+ value: Number(value),
1323
+ unit
1324
+ }, {
1325
+ allPages,
1326
+ recalculate
1327
+ });
1328
+ measurement.dismissCalibration();
1329
+ } catch (e) {
1330
+ setError(e instanceof Error ? e.message : String(e));
1331
+ }
1332
+ };
1333
+ return /* @__PURE__ */ jsx("div", {
1334
+ className: "fixed inset-0 z-50 grid place-items-center bg-black/40",
1335
+ onKeyDown: (e) => {
1336
+ if (e.key === "Escape" && !measurement.busy) measurement.dismissCalibration();
1337
+ },
1338
+ children: /* @__PURE__ */ jsxs("form", {
1339
+ role: "dialog",
1340
+ "aria-modal": "true",
1341
+ "aria-labelledby": "measurement-calibration-title",
1342
+ className: "border-border bg-surface flex w-96 flex-col gap-4 rounded-lg border p-5 shadow-xl",
1343
+ onSubmit: (e) => {
1344
+ e.preventDefault();
1345
+ submit();
1346
+ },
1347
+ children: [
1348
+ /* @__PURE__ */ jsx("h2", {
1349
+ id: "measurement-calibration-title",
1350
+ className: "font-semibold",
1351
+ children: t("measurement.calibrate")
1352
+ }),
1353
+ /* @__PURE__ */ jsx("p", {
1354
+ className: "text-fg-muted text-sm",
1355
+ children: t("measurement.knownLength")
1356
+ }),
1357
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.length"), /* @__PURE__ */ jsx("input", {
1358
+ ref: input,
1359
+ "aria-label": t("measurement.length"),
1360
+ className: control,
1361
+ type: "number",
1362
+ min: "0",
1363
+ step: "any",
1364
+ value,
1365
+ onChange: (e) => setValue(e.target.value)
1366
+ })] }),
1367
+ /* @__PURE__ */ jsxs("label", { children: [t("measurement.unit"), /* @__PURE__ */ jsx("select", {
1368
+ className: control,
1369
+ "aria-label": t("measurement.unit"),
1370
+ value: unit,
1371
+ onChange: (e) => setUnit(e.target.value),
1372
+ children: measurement.units().map((u) => /* @__PURE__ */ jsx("option", { children: u }, u))
1373
+ })] }),
1374
+ /* @__PURE__ */ jsxs("label", {
1375
+ className: "flex items-center gap-2 text-sm",
1376
+ children: [/* @__PURE__ */ jsx("input", {
1377
+ type: "checkbox",
1378
+ checked: allPages,
1379
+ onChange: (e) => setAllPages(e.target.checked)
1380
+ }), t("measurement.allPages")]
1381
+ }),
1382
+ /* @__PURE__ */ jsxs("label", {
1383
+ className: "flex items-center gap-2 text-sm",
1384
+ children: [/* @__PURE__ */ jsx("input", {
1385
+ type: "checkbox",
1386
+ checked: recalculate,
1387
+ onChange: (e) => setRecalculate(e.target.checked)
1388
+ }), t("measurement.recalculate")]
1389
+ }),
1390
+ error && /* @__PURE__ */ jsx("p", {
1391
+ role: "alert",
1392
+ className: "text-sm text-red-600",
1393
+ children: error
1394
+ }),
1395
+ /* @__PURE__ */ jsxs("div", {
1396
+ className: "flex justify-end gap-2",
1397
+ children: [/* @__PURE__ */ jsx("button", {
1398
+ type: "button",
1399
+ className: button,
1400
+ disabled: measurement.busy,
1401
+ onClick: () => measurement.dismissCalibration(),
1402
+ children: t("demo.cancel")
1403
+ }), /* @__PURE__ */ jsx("button", {
1404
+ className: button,
1405
+ type: "submit",
1406
+ disabled: measurement.busy || !measurement.canCalibratePage || !(Number(value) > 0) || !Number.isFinite(Number(value)),
1407
+ children: measurement.busy ? t("measurement.applying") : t("measurement.apply")
1408
+ })]
1409
+ })
1410
+ ]
1411
+ })
1412
+ });
1413
+ }
1414
+ //#endregion
1010
1415
  //#region src/config/default-stamps.ts
1011
1416
  /**
1012
1417
  * The viewer's BUILT-IN stamp library: `@embedpdf/default-stamps`, the
@@ -1576,6 +1981,7 @@ function AppToolbar({ bar, className }) {
1576
1981
  run
1577
1982
  }),
1578
1983
  renderCustom: {
1984
+ "measurement-scale": () => /* @__PURE__ */ jsx(MeasurementScaleButton, {}),
1579
1985
  "zoom-controls": (variant) => variant === "inline" ? /* @__PURE__ */ jsx(ZoomControls, {}) : /* @__PURE__ */ jsx(ZoomButton, {}),
1580
1986
  "quick-stamps": () => /* @__PURE__ */ jsx(QuickStamps, {})
1581
1987
  },
@@ -2022,7 +2428,13 @@ function iconGlyph(name, accent) {
2022
2428
  const fill = spec.fill === true ? INK : spec.fill ? accent?.[spec.fill] : void 0;
2023
2429
  const stroke = spec.stroke ? accent?.[spec.stroke] ?? INK : spec.fill ? "none" : INK;
2024
2430
  halo.push(`<path d="${spec.d}" fill="none" stroke="${HALO}" stroke-width="3.5"/>`);
2025
- draw.push(`<path d="${spec.d}" fill="${fill ?? "none"}" stroke="${stroke}"/>`);
2431
+ const decoration = [
2432
+ ["stroke-width", spec.strokeWidth],
2433
+ ["stroke-dasharray", spec.strokeDasharray],
2434
+ ["stroke-opacity", spec.strokeOpacity],
2435
+ ["fill-opacity", spec.fillOpacity]
2436
+ ].filter(([, value]) => value !== void 0).map(([key, value]) => `${key}="${value}"`).join(" ");
2437
+ draw.push(`<path d="${spec.d}" fill="${fill ?? "none"}" stroke="${stroke}" ${decoration}/>`);
2026
2438
  }
2027
2439
  return halo.join("") + draw.join("");
2028
2440
  }
@@ -4431,12 +4843,13 @@ function RightSidebar() {
4431
4843
  const comment = useSurface("comment");
4432
4844
  const style = useSurface("annotation-style");
4433
4845
  const redaction = useSurface("redaction");
4846
+ const measurement = useSurface("measurement");
4434
4847
  const stamps = useSurface("stamps");
4435
4848
  const signatures = useSurface("signatures");
4436
- const active = search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : stamps.isOpen ? "stamps" : signatures.isOpen ? "signatures" : null;
4849
+ const active = measurement.isOpen ? "measurement" : search.isOpen ? "search" : comment.isOpen ? "comment" : style.isOpen ? "style" : redaction.isOpen ? "redaction" : stamps.isOpen ? "stamps" : signatures.isOpen ? "signatures" : null;
4437
4850
  if (!active) return null;
4438
- const titleKey = active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : active === "signatures" ? "demo.signaturesTitle" : "demo.styleTitle";
4439
- const close = active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : active === "signatures" ? signatures.close : style.close;
4851
+ const titleKey = active === "measurement" ? "measurement.title" : active === "search" ? "demo.searchTitle" : active === "comment" ? "demo.commentsTitle" : active === "redaction" ? "demo.redactionTitle" : active === "stamps" ? "demo.stampsTitle" : active === "signatures" ? "demo.signaturesTitle" : "demo.styleTitle";
4852
+ const close = active === "measurement" ? measurement.close : active === "search" ? search.close : active === "comment" ? comment.close : active === "redaction" ? redaction.close : active === "stamps" ? stamps.close : active === "signatures" ? signatures.close : style.close;
4440
4853
  return /* @__PURE__ */ jsxs("aside", {
4441
4854
  className: "border-border-subtle bg-surface flex w-72 shrink-0 flex-col border-l",
4442
4855
  children: [/* @__PURE__ */ jsxs("div", {
@@ -4453,7 +4866,7 @@ function RightSidebar() {
4453
4866
  size: 16
4454
4867
  })
4455
4868
  })]
4456
- }), active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ jsx(StampsPanel, {}) : active === "signatures" ? /* @__PURE__ */ jsx(SignaturesPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
4869
+ }), active === "measurement" ? /* @__PURE__ */ jsx(MeasurementSection, {}, documentId ?? "none") : active === "style" ? /* @__PURE__ */ jsx(AnnotationStylePanel, {}) : active === "search" ? /* @__PURE__ */ jsx(SearchPanel, {}, documentId ?? "none") : active === "redaction" ? /* @__PURE__ */ jsx(RedactionPanel, {}) : active === "stamps" ? /* @__PURE__ */ jsx(StampsPanel, {}) : active === "signatures" ? /* @__PURE__ */ jsx(SignaturesPanel, {}) : /* @__PURE__ */ jsx(CommentsPanel, {}, documentId ?? "none")]
4457
4870
  });
4458
4871
  }
4459
4872
  function PageControls() {
@@ -5506,6 +5919,7 @@ function Shell() {
5506
5919
  }),
5507
5920
  /* @__PURE__ */ jsx(RightSidebar, {}),
5508
5921
  /* @__PURE__ */ jsx(RedactConfirmModal, {}),
5922
+ /* @__PURE__ */ jsx(CalibrationDialog, {}),
5509
5923
  /* @__PURE__ */ jsx(SignatureBridge, {}),
5510
5924
  /* @__PURE__ */ jsx(SignDialog, {}),
5511
5925
  /* @__PURE__ */ jsx(SignatureMakerModal, {})
@@ -5536,7 +5950,7 @@ function Shell() {
5536
5950
  * changing these props on a mounted viewer does not rebuild the workspace.
5537
5951
  */
5538
5952
  /** Lazy built-in packs — a strings override for one of these wraps its loader. */
5539
- const BUILTIN_LOADERS = { es: () => import("./es-sN6uqBgD.js").then((m) => m.es) };
5953
+ const BUILTIN_LOADERS = { es: () => import("./es-CLgTsFDS.js").then((m) => m.es) };
5540
5954
  const isDict = (v) => typeof v === "object" && v !== null;
5541
5955
  /**
5542
5956
  * Deep-merge `over` into `base` (immutably), expanding dotted keys —
@@ -5687,6 +6101,7 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
5687
6101
  }),
5688
6102
  linkPlugin(),
5689
6103
  redactionPlugin(),
6104
+ measurementPlugin(),
5690
6105
  searchPlugin(),
5691
6106
  demoToolsPlugin(),
5692
6107
  i18nPlugin({
@@ -5718,6 +6133,6 @@ function FullViewer({ engine, initialDocuments, fallback, commands, icons, strin
5718
6133
  });
5719
6134
  }
5720
6135
  //#endregion
5721
- export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, SignatureToken, StageToken, StampToken, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
6136
+ export { AnnotationToken, CommandsToken, DocumentsToken, FormToken, FullViewer, I18nToken, InteractionToken, MeasurementToken, MetadataToken, RedactionToken, SearchToken, SelectionToken, ShellToken, SignatureToken, StageToken, StampToken, addItem, chromeHelpers, createTestSigner, createViewerHandle, custom, defaultChrome, defaultCommands, ICON_PATHS as defaultIcons, defineChrome, group, indexedDbKeyStore, item, memoryKeyStore, personalSigner, remoteSigner, removeItems, replaceItem, themeConfigOf, validateChrome, webCryptoSigner };
5722
6137
 
5723
6138
  //# sourceMappingURL=index.js.map