@embedpdf/plugin-annotation 3.0.0-next.10 → 3.0.0-next.12
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/authoring-BhtyfsMx.d.cts +37 -0
- package/dist/authoring-R4ezdnXd.d.ts +37 -0
- package/dist/authoring.cjs +16 -0
- package/dist/authoring.d.cts +2 -0
- package/dist/authoring.d.ts +2 -0
- package/dist/authoring.js +3 -0
- package/dist/{capability-BRf0HrOi.js → capability-Bh_1PL5v.js} +266 -280
- package/dist/capability-Bh_1PL5v.js.map +1 -0
- package/dist/{capability-Cl9x8oLS.cjs → capability-R7dgogJs.cjs} +332 -364
- package/dist/capability-R7dgogJs.cjs.map +1 -0
- package/dist/contract/host.cjs +3 -0
- package/dist/contract/host.d.cts +2 -0
- package/dist/contract/host.d.ts +2 -0
- package/dist/contract/host.js +2 -0
- package/dist/contract.cjs +10 -0
- package/dist/contract.cjs.map +1 -0
- package/dist/contract.d.cts +10 -0
- package/dist/contract.d.ts +10 -0
- package/dist/contract.js +8 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.cjs +25 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -47
- package/dist/index.d.ts +5 -47
- package/dist/index.js +15 -15
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +4 -2
- package/dist/internal.d.cts +2 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +3 -1
- package/dist/seam-6pJeKOOJ.cjs +296 -0
- package/dist/seam-6pJeKOOJ.cjs.map +1 -0
- package/dist/seam-C4L_7rDt.js +225 -0
- package/dist/seam-C4L_7rDt.js.map +1 -0
- package/dist/{types-Due7UgzE.d.cts → types-BAPdbfPT.d.cts} +89 -18
- package/dist/{types-DITX_3CG.d.ts → types-BgetZ4BK.d.ts} +88 -17
- package/dist/types-BvB1vUKb.js +20 -0
- package/dist/types-BvB1vUKb.js.map +1 -0
- package/dist/types-Dlhdw2k_.cjs +31 -0
- package/dist/types-Dlhdw2k_.cjs.map +1 -0
- package/package.json +21 -7
- package/dist/capability-BRf0HrOi.js.map +0 -1
- package/dist/capability-Cl9x8oLS.cjs.map +0 -1
|
@@ -1,238 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as contentToPdfRect$1, c as insetPdfRectByRD, d as refKey, f as rotFromDTO, g as writableTarget, i as contentToPdfPoint, l as pdfToContentPoint$1, m as toPdfRotation, n as boxGeomFromDTO, o as cssToColor, p as styleFromDTO, r as colorToCss, s as fromPdfRotation, t as boxGeomFields, u as pdfToContentRect$1 } from "./seam-C4L_7rDt.js";
|
|
2
|
+
import { n as previewBucket } from "./types-BvB1vUKb.js";
|
|
3
|
+
import { CONTINUOUS_RENDER_POLICY, snapAppearanceScale } from "@embedpdf/core";
|
|
4
|
+
import { ActionsToken, createHoverPump } from "@embedpdf/plugin-actions/contract";
|
|
5
|
+
import { InteractionToken } from "@embedpdf/plugin-interaction/contract";
|
|
3
6
|
import { buildCommentThreads, encodeStableIdKey, resolveBinarySource, sniffBinaryMetadata } from "@embedpdf/engine-core/runtime";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
7
|
+
import { scriptColorToRgb } from "@embedpdf/core-acrojs";
|
|
8
|
+
import { SelectionToken } from "@embedpdf/plugin-selection/contract";
|
|
9
|
+
import { FLAG_KEYS, annotContentsEditable, annotDeletable, annotTransformable, calloutLinePoints, canMove, chrome, clickCreateGeom, cloudyBorderExtent, contentToPdfRect, creationDraftAnchor, cursorAt, defaultsFor, expandGroups, fitStampBox, geomPdfBounds, geomRotation, geomVisualBounds, groupKeyOf, hitTest, initialTextStyle, isSubstrateOnly, linkChildrenOf, linkOf, pageItems, pdfToContentPoint, pdfToContentRect, propsFor, readProp, resolveClickPlacement, rotatedAabb, selectionAnchor, selectionQuad, sharedProps, styleFromProps, textBoxes, unionRect, update, uprightRotation, viewable } from "@embedpdf/core-annotation";
|
|
6
10
|
import { normalizeQuad, textQuadBounds } from "@embedpdf/core-geometry";
|
|
7
|
-
//#region src/types.ts
|
|
8
|
-
/**
|
|
9
|
-
* The annotation capability token. Typed to the full {@link AnnotationHostCapability}
|
|
10
|
-
* here (the package internals + the `/internal` entry use this view). The package
|
|
11
|
-
* root re-exports the SAME token narrowed to {@link AnnotationCapability}.
|
|
12
|
-
*/
|
|
13
|
-
const AnnotationToken = createCapabilityToken("annotation", { hint: `add annotationPlugin() from '@embedpdf/plugin-annotation' to your plugins list` });
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region src/repository/seam.ts
|
|
16
|
-
function refKey(ref) {
|
|
17
|
-
return ref.kind === "objectNumber" ? `obj:${ref.annotObjectNumber}` : ref.kind === "nm" ? `nm:${ref.nm}` : `idx:${ref.pageObjectNumber}:${ref.index}`;
|
|
18
|
-
}
|
|
19
|
-
const h2 = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
20
|
-
const colorToCss = (c) => `#${h2(c.r)}${h2(c.g)}${h2(c.b)}`;
|
|
21
|
-
function cssToColor(css) {
|
|
22
|
-
const s = css.trim();
|
|
23
|
-
const m6 = /^#?([0-9a-f]{6})$/i.exec(s);
|
|
24
|
-
if (m6) {
|
|
25
|
-
const n = parseInt(m6[1], 16);
|
|
26
|
-
return {
|
|
27
|
-
r: n >> 16 & 255,
|
|
28
|
-
g: n >> 8 & 255,
|
|
29
|
-
b: n & 255
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
const m3 = /^#?([0-9a-f]{3})$/i.exec(s);
|
|
33
|
-
if (m3) {
|
|
34
|
-
const [a, b, c] = m3[1];
|
|
35
|
-
return {
|
|
36
|
-
r: parseInt(a + a, 16),
|
|
37
|
-
g: parseInt(b + b, 16),
|
|
38
|
-
b: parseInt(c + c, 16)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
r: 0,
|
|
43
|
-
g: 0,
|
|
44
|
-
b: 0
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const toPdfRotation = (rotCW) => normalizeDeg(-rotCW);
|
|
48
|
-
const fromPdfRotation = (rotPdf) => normalizeDeg(-rotPdf);
|
|
49
|
-
/** Advisory `rot` for a vertex geom, from a DTO's (PDF-convention) `rotation`.
|
|
50
|
-
* Absent → no `rot` key (kept off the geom so unrotated shapes stay clean). */
|
|
51
|
-
const rotFromDTO = (rotation) => rotation ? { rot: fromPdfRotation(rotation) } : {};
|
|
52
|
-
/**
|
|
53
|
-
* Geometry/rotation fields for a BOX kind (square/circle/plain free-text). The
|
|
54
|
-
* model's `rect` is the UNROTATED logical box and `rot` the applied tilt, so:
|
|
55
|
-
* - rot == 0 → `/Rect` IS the box; the transform pair is stated as `null`
|
|
56
|
-
* (TOTAL projection — the engine's tri-state writes preserve omissions, so
|
|
57
|
-
* an omitted field would keep a stale rotation instead of flattening it).
|
|
58
|
-
* - rot != 0 → `/Rect` is the rotated visual AABB (PDFium clips the baked /AP
|
|
59
|
-
* to it), `unrotatedRect` the logical box, `rotation` the PDF angle.
|
|
60
|
-
*/
|
|
61
|
-
function boxGeomFields(rect, rot, crop) {
|
|
62
|
-
if (!rot) return {
|
|
63
|
-
rect: contentToPdfRect(rect, crop),
|
|
64
|
-
rotation: null,
|
|
65
|
-
unrotatedRect: null
|
|
66
|
-
};
|
|
67
|
-
return {
|
|
68
|
-
rect: contentToPdfRect(rotatedAabb(rect, rot), crop),
|
|
69
|
-
unrotatedRect: contentToPdfRect(rect, crop),
|
|
70
|
-
rotation: toPdfRotation(rot)
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
/** A box geom (square/circle/stamp) from its DTO: when rotated, the LOCAL box
|
|
74
|
-
* is the stored `unrotatedRect` (the AABB `/Rect` is the rendered envelope)
|
|
75
|
-
* and `rot` the converted tilt; unrotated, `/Rect` IS the box. */
|
|
76
|
-
function boxGeomFromDTO(dto, rotation, unrotatedRect, crop, ellipse) {
|
|
77
|
-
const rot = rotation ? fromPdfRotation(rotation) : 0;
|
|
78
|
-
return {
|
|
79
|
-
t: "rect",
|
|
80
|
-
rect: pdfToContentRect(rot && unrotatedRect ? unrotatedRect : dto.rect, crop),
|
|
81
|
-
ellipse,
|
|
82
|
-
...rot ? { rot } : {}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/** Inset a PdfRect by a `/RD` (PDF user space, y-up: all four are non-negative
|
|
86
|
-
* insets from the matching `/Rect` edge). Used to recover the callout text box. */
|
|
87
|
-
const insetPdfRectByRD = (r, rd) => rd ? {
|
|
88
|
-
left: r.left + rd.left,
|
|
89
|
-
bottom: r.bottom + rd.bottom,
|
|
90
|
-
right: r.right - rd.right,
|
|
91
|
-
top: r.top - rd.top
|
|
92
|
-
} : r;
|
|
93
|
-
/** Engine border fields (`/BS /S`, `/BS /D`, `/BE /I`) → the `Border` union. A
|
|
94
|
-
* cloudy effect wins over the underlying border style (which stays solid). */
|
|
95
|
-
function borderFromDTO(d) {
|
|
96
|
-
if ((d.cloudyIntensity ?? 0) > 0) return {
|
|
97
|
-
kind: "cloudy",
|
|
98
|
-
intensity: d.cloudyIntensity
|
|
99
|
-
};
|
|
100
|
-
if (d.borderStyle === "dashed") return {
|
|
101
|
-
kind: "dashed",
|
|
102
|
-
dash: d.dashArray?.length ? d.dashArray : [3, 3]
|
|
103
|
-
};
|
|
104
|
-
return { kind: "solid" };
|
|
105
|
-
}
|
|
106
|
-
/** The writable projection of a `link` value: `goto`/`uri` pass through,
|
|
107
|
-
* read-only arms (`javascript`, `named`, `goto-remote`, `launch`,
|
|
108
|
-
* `unsupported`) yield `null` — they can be carried, never (re)written. */
|
|
109
|
-
function writableTarget(t) {
|
|
110
|
-
return t && (t.kind === "goto" || t.kind === "uri") ? t : null;
|
|
111
|
-
}
|
|
112
|
-
const TEXT_MARKUP = /* @__PURE__ */ new Set([
|
|
113
|
-
"highlight",
|
|
114
|
-
"underline",
|
|
115
|
-
"squiggly",
|
|
116
|
-
"strikeout"
|
|
117
|
-
]);
|
|
118
|
-
const STROKE_KINDS = /* @__PURE__ */ new Set([
|
|
119
|
-
"square",
|
|
120
|
-
"circle",
|
|
121
|
-
"line",
|
|
122
|
-
"polygon",
|
|
123
|
-
"polyline",
|
|
124
|
-
"ink"
|
|
125
|
-
]);
|
|
126
|
-
/** Engine DTO → content-space `Style` (CSS colours, `Border` union). Exported so
|
|
127
|
-
* selection-aware UIs can read display values straight off a {@link AnnotationDTO}
|
|
128
|
-
* without re-deriving the colour/border mapping. */
|
|
129
|
-
function styleFromDTO(dto) {
|
|
130
|
-
if (dto.subtype === "widget") return {
|
|
131
|
-
color: dto.color ? colorToCss(dto.color) : "#1a1a1a",
|
|
132
|
-
interiorColor: dto.interiorColor ? colorToCss(dto.interiorColor) : null,
|
|
133
|
-
strokeWidth: dto.strokeWidth,
|
|
134
|
-
opacity: 1,
|
|
135
|
-
blendMode: dto.blendMode,
|
|
136
|
-
border: dto.borderStyle === "dashed" ? {
|
|
137
|
-
kind: "dashed",
|
|
138
|
-
dash: [3, 3]
|
|
139
|
-
} : { kind: "solid" }
|
|
140
|
-
};
|
|
141
|
-
if (STROKE_KINDS.has(dto.subtype)) {
|
|
142
|
-
const d = dto;
|
|
143
|
-
return {
|
|
144
|
-
color: colorToCss(d.color),
|
|
145
|
-
interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
|
|
146
|
-
strokeWidth: d.strokeWidth,
|
|
147
|
-
opacity: d.opacity,
|
|
148
|
-
blendMode: dto.blendMode,
|
|
149
|
-
border: borderFromDTO(d)
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
if (TEXT_MARKUP.has(dto.subtype)) {
|
|
153
|
-
const d = dto;
|
|
154
|
-
return {
|
|
155
|
-
color: colorToCss(d.color),
|
|
156
|
-
interiorColor: null,
|
|
157
|
-
strokeWidth: 0,
|
|
158
|
-
opacity: d.opacity,
|
|
159
|
-
blendMode: dto.blendMode,
|
|
160
|
-
border: { kind: "solid" }
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
if (dto.subtype === "caret") {
|
|
164
|
-
const d = dto;
|
|
165
|
-
return {
|
|
166
|
-
color: colorToCss(d.color),
|
|
167
|
-
interiorColor: null,
|
|
168
|
-
strokeWidth: 1,
|
|
169
|
-
opacity: d.opacity,
|
|
170
|
-
blendMode: dto.blendMode,
|
|
171
|
-
border: { kind: "solid" }
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
if (dto.subtype === "free-text") {
|
|
175
|
-
const d = dto;
|
|
176
|
-
return {
|
|
177
|
-
color: colorToCss(d.color),
|
|
178
|
-
interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
|
|
179
|
-
strokeWidth: d.strokeWidth,
|
|
180
|
-
opacity: d.opacity,
|
|
181
|
-
blendMode: dto.blendMode,
|
|
182
|
-
border: borderFromDTO(d)
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
if (dto.subtype === "redact") {
|
|
186
|
-
const d = dto;
|
|
187
|
-
return {
|
|
188
|
-
color: colorToCss(d.color),
|
|
189
|
-
interiorColor: d.interiorColor ? colorToCss(d.interiorColor) : null,
|
|
190
|
-
strokeWidth: 1.5,
|
|
191
|
-
opacity: d.opacity,
|
|
192
|
-
blendMode: dto.blendMode,
|
|
193
|
-
border: { kind: "solid" }
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (dto.subtype === "text" || dto.subtype === "file-attachment") {
|
|
197
|
-
const d = dto;
|
|
198
|
-
return {
|
|
199
|
-
color: colorToCss(d.color),
|
|
200
|
-
interiorColor: null,
|
|
201
|
-
strokeWidth: 1,
|
|
202
|
-
opacity: d.opacity,
|
|
203
|
-
blendMode: dto.blendMode,
|
|
204
|
-
border: { kind: "solid" }
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
color: "#444444",
|
|
209
|
-
interiorColor: null,
|
|
210
|
-
strokeWidth: 1,
|
|
211
|
-
opacity: 1,
|
|
212
|
-
blendMode: dto.blendMode,
|
|
213
|
-
border: { kind: "solid" }
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* The flat props vocabulary (CSS colours, house keys) → the engine's widget
|
|
218
|
-
* appearance for `doc.forms` authoring — the SAME mapping the widget patch
|
|
219
|
-
* lowering uses, exported as a boundary utility so the form plugin can style
|
|
220
|
-
* `placeField` from a tool's `currentDefaults` without growing a second CSS
|
|
221
|
-
* parser. Absent keys stay absent (the engine writes nothing for them).
|
|
222
|
-
*/
|
|
223
|
-
function widgetAppearanceFromProps(props) {
|
|
224
|
-
return {
|
|
225
|
-
...props.color !== void 0 ? { color: cssToColor(props.color) } : {},
|
|
226
|
-
...props.interiorColor !== void 0 ? { interiorColor: props.interiorColor ? cssToColor(props.interiorColor) : null } : {},
|
|
227
|
-
...props.strokeWidth !== void 0 ? { strokeWidth: props.strokeWidth } : {},
|
|
228
|
-
...props.border !== void 0 ? { borderStyle: props.border.kind === "dashed" ? "dashed" : "solid" } : {},
|
|
229
|
-
...props.fontFamily !== void 0 ? { fontFamily: props.fontFamily } : {},
|
|
230
|
-
...props.fontSize !== void 0 ? { fontSize: props.fontSize } : {},
|
|
231
|
-
...props.fontColor !== void 0 ? { fontColor: cssToColor(props.fontColor) } : {},
|
|
232
|
-
...props.textAlign !== void 0 ? { textAlign: props.textAlign } : {}
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
//#endregion
|
|
236
11
|
//#region src/repository/props.ts
|
|
237
12
|
/**
|
|
238
13
|
* GENERIC per-key prop lowerings — the 1:1 mappings from the flat props
|
|
@@ -909,6 +684,46 @@ function toCreateDraft(a, crop) {
|
|
|
909
684
|
};
|
|
910
685
|
}
|
|
911
686
|
//#endregion
|
|
687
|
+
//#region src/hover-feed.ts
|
|
688
|
+
/**
|
|
689
|
+
* The annotation plane's E/X trigger feed — a thin adapter from the ONE
|
|
690
|
+
* pointer-driven hover seam (the `hoverAt` diff) onto the shared hover pump.
|
|
691
|
+
*
|
|
692
|
+
* Anti-cascade is structural and lives in the CALLER's seam placement:
|
|
693
|
+
* reducer-side hover clears (session-hide, remove, reload) never pass
|
|
694
|
+
* through `hoverAt`, so an effect-induced hover loss can never masquerade as
|
|
695
|
+
* a cursor exit. This module only decides WHO is hoverable here:
|
|
696
|
+
*
|
|
697
|
+
* - Drafts (no engine ref) can't carry /AA — skipped.
|
|
698
|
+
* - Widgets and links belong to their own event planes (fill controls /
|
|
699
|
+
* LinkLayer anchors own their pixels). When they ARE hit-testable on this
|
|
700
|
+
* plane, an authoring tool owns them — firing hover actions while editing
|
|
701
|
+
* would be wrong — so they are skipped unconditionally.
|
|
702
|
+
* - Tree-less annotations are FREE: no target, no dispatch, and per-event
|
|
703
|
+
* flags keep a lone /E or /X from dispatching its inert twin.
|
|
704
|
+
*/
|
|
705
|
+
function createAnnotationHoverFeed(actions, annotOf) {
|
|
706
|
+
const pump = createHoverPump(actions.dispatch);
|
|
707
|
+
const targetOf = (id) => {
|
|
708
|
+
if (!id) return null;
|
|
709
|
+
const annot = annotOf(id);
|
|
710
|
+
if (!annot?.ref) return null;
|
|
711
|
+
if (annot.subtype.startsWith("widget") || annot.subtype === "link") return null;
|
|
712
|
+
const enter = Boolean(annot.data?.actions?.cursorEnter?.root);
|
|
713
|
+
const exit = Boolean(annot.data?.actions?.cursorExit?.root);
|
|
714
|
+
if (!enter && !exit) return null;
|
|
715
|
+
return {
|
|
716
|
+
ref: annot.ref,
|
|
717
|
+
pon: annot.pon,
|
|
718
|
+
events: {
|
|
719
|
+
enter,
|
|
720
|
+
exit
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
return { hover: (id) => pump.hover(targetOf(id)) };
|
|
725
|
+
}
|
|
726
|
+
//#endregion
|
|
912
727
|
//#region src/text-item.ts
|
|
913
728
|
/**
|
|
914
729
|
* The free-text presentation projection: the core's geometry-only `textBoxes`
|
|
@@ -960,6 +775,15 @@ function buildTextItems(m, pon, view) {
|
|
|
960
775
|
//#endregion
|
|
961
776
|
//#region src/tools.ts
|
|
962
777
|
/**
|
|
778
|
+
* The tool `armStamp` activates — and the only tool an armed payload survives
|
|
779
|
+
* onto (plus the legacy `annotation-stamp` tag, honoured for embedder configs
|
|
780
|
+
* written before the tags were unified). Placement consults the armed payload
|
|
781
|
+
* BEFORE the active tool's own `source` spec, so a payload must not outlive
|
|
782
|
+
* its tool onto a sibling stamp preset: an `image` preset's click has to open
|
|
783
|
+
* its own picker, not silently place whatever the stamp panel last armed.
|
|
784
|
+
*/
|
|
785
|
+
const ARMED_STAMP_TOOL_ID = "stamp";
|
|
786
|
+
/**
|
|
963
787
|
* The creation properties each known authoring kind actually consumes. This is
|
|
964
788
|
* deliberately separate from the flat internal patch vocabulary: it gives tool
|
|
965
789
|
* configuration precise compile-time and runtime validation while mixed-selection
|
|
@@ -1293,7 +1117,7 @@ const DEFAULT_TOOLS = [
|
|
|
1293
1117
|
}
|
|
1294
1118
|
},
|
|
1295
1119
|
{
|
|
1296
|
-
id:
|
|
1120
|
+
id: ARMED_STAMP_TOOL_ID,
|
|
1297
1121
|
subtype: "stamp",
|
|
1298
1122
|
cursor: "copy",
|
|
1299
1123
|
enables: ["annotation-place", "annotation-edit"],
|
|
@@ -1522,6 +1346,8 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1522
1346
|
* slot. See {@link FilePickerProvider}. */
|
|
1523
1347
|
let filePickerProvider = null;
|
|
1524
1348
|
const model = () => ctx.getState().model;
|
|
1349
|
+
const actionsForHover = ctx.tryGet(ActionsToken);
|
|
1350
|
+
const hoverFeed = actionsForHover ? createAnnotationHoverFeed(actionsForHover, (id) => model().byId[id] ?? null) : null;
|
|
1525
1351
|
const chromeSettings = () => ctx.getState().chrome;
|
|
1526
1352
|
/**
|
|
1527
1353
|
* Ids on a page whose Behavior is currently ENGAGED (form widgets under a
|
|
@@ -1674,11 +1500,21 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1674
1500
|
if (!viewable(a.flags, false)) continue;
|
|
1675
1501
|
const target = a.link ?? (a.data?.subtype === "link" ? a.data.target ?? null : null);
|
|
1676
1502
|
if (target == null || a.geom.t !== "rect") continue;
|
|
1503
|
+
const activate = a.data?.actions?.activate;
|
|
1504
|
+
const ref = a.ref ?? a.data?.ref ?? void 0;
|
|
1505
|
+
const hoverEnter = Boolean(a.data?.actions?.cursorEnter?.root);
|
|
1506
|
+
const hoverExit = Boolean(a.data?.actions?.cursorExit?.root);
|
|
1677
1507
|
v.push({
|
|
1678
1508
|
id,
|
|
1679
1509
|
rect: a.geom.rect,
|
|
1680
1510
|
target,
|
|
1681
|
-
attached: a.group !== void 0
|
|
1511
|
+
attached: a.group !== void 0,
|
|
1512
|
+
...activate ? { activate } : {},
|
|
1513
|
+
...ref ? { ref } : {},
|
|
1514
|
+
...hoverEnter || hoverExit ? { hoverEvents: {
|
|
1515
|
+
enter: hoverEnter,
|
|
1516
|
+
exit: hoverExit
|
|
1517
|
+
} } : {}
|
|
1682
1518
|
});
|
|
1683
1519
|
}
|
|
1684
1520
|
linkItemsCache.set(pon, {
|
|
@@ -1739,6 +1575,14 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1739
1575
|
* rendered, never synced, and dies with the tool.
|
|
1740
1576
|
*/
|
|
1741
1577
|
let armedStamp = null;
|
|
1578
|
+
/** Fixed bytes as a provider: the same image at every size. */
|
|
1579
|
+
const fixedPreview = (bytes, mimeType) => {
|
|
1580
|
+
const preview = {
|
|
1581
|
+
bytes,
|
|
1582
|
+
...mimeType ? { mimeType } : {}
|
|
1583
|
+
};
|
|
1584
|
+
return async () => preview;
|
|
1585
|
+
};
|
|
1742
1586
|
/**
|
|
1743
1587
|
* The desired stamp size (PDF points) from sniffed bytes: the image's
|
|
1744
1588
|
* INTRINSIC pixel dimensions taken 1:1 as points (the v2 rule — keep the
|
|
@@ -1767,23 +1611,21 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1767
1611
|
const meta = sniffBinaryMetadata(resolved.bytes);
|
|
1768
1612
|
if (!meta) throw new Error("[annotation] stamp source must be PNG, JPEG, or single-page PDF bytes");
|
|
1769
1613
|
let preview = null;
|
|
1770
|
-
if (input.preview)
|
|
1614
|
+
if (typeof input.preview === "function") preview = input.preview;
|
|
1615
|
+
else if (input.preview) {
|
|
1771
1616
|
const p = await resolveBinarySource(input.preview);
|
|
1772
|
-
preview =
|
|
1773
|
-
|
|
1774
|
-
mimeType: p.mimeType
|
|
1775
|
-
};
|
|
1776
|
-
} else if (meta.mimeType !== "application/pdf") preview = {
|
|
1777
|
-
bytes: new Uint8Array(resolved.bytes),
|
|
1778
|
-
mimeType: meta.mimeType
|
|
1779
|
-
};
|
|
1617
|
+
preview = fixedPreview(new Uint8Array(p.bytes), p.mimeType);
|
|
1618
|
+
} else if (meta.mimeType !== "application/pdf") preview = fixedPreview(new Uint8Array(resolved.bytes), meta.mimeType);
|
|
1780
1619
|
armedStamp = {
|
|
1781
1620
|
source: input.source,
|
|
1782
1621
|
...desiredStampSize(meta, input.targetWidth, input.intrinsicSize),
|
|
1783
|
-
preview
|
|
1622
|
+
preview,
|
|
1623
|
+
previewCache: /* @__PURE__ */ new Map(),
|
|
1624
|
+
...input.name !== void 0 ? { name: input.name } : {},
|
|
1625
|
+
...input.subject !== void 0 ? { subject: input.subject } : {}
|
|
1784
1626
|
};
|
|
1785
1627
|
ctx.dispatch({ type: "STAMP_ARM_CHANGED" });
|
|
1786
|
-
ctx.tryGet(InteractionToken)?.activateTool(
|
|
1628
|
+
ctx.tryGet(InteractionToken)?.activateTool(ARMED_STAMP_TOOL_ID);
|
|
1787
1629
|
};
|
|
1788
1630
|
const disarmStamp = () => {
|
|
1789
1631
|
if (!armedStamp) return;
|
|
@@ -1910,28 +1752,49 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1910
1752
|
* emits the repository's box rotation fields — the engine bakes the tilted
|
|
1911
1753
|
* /AP exactly as an interactively rotated stamp round-trips, and the fit uses
|
|
1912
1754
|
* the ROTATED footprint. Returns false when the page/document isn't ready. */
|
|
1913
|
-
const createStampAt = (pon, point, source, desired, rotCW = 0) => {
|
|
1755
|
+
const createStampAt = (pon, point, source, desired, rotCW = 0, identity = {}) => {
|
|
1914
1756
|
const doc = ctx.doc;
|
|
1915
1757
|
const crop = cropOf(pon);
|
|
1916
|
-
if (!doc || !crop) return
|
|
1758
|
+
if (!doc || !crop) return null;
|
|
1917
1759
|
const box = fitStampBox(point, desired, {
|
|
1918
1760
|
width: crop.right - crop.left,
|
|
1919
1761
|
height: crop.top - crop.bottom
|
|
1920
1762
|
}, rotCW);
|
|
1921
|
-
doc.page(pon).annotations.create({
|
|
1763
|
+
return doc.page(pon).annotations.create({
|
|
1922
1764
|
subtype: "stamp",
|
|
1923
1765
|
...boxGeomFields(box, rotCW, crop),
|
|
1924
1766
|
source,
|
|
1925
|
-
fit: "contain"
|
|
1767
|
+
fit: "contain",
|
|
1768
|
+
...identity.name !== void 0 ? { name: identity.name } : {},
|
|
1769
|
+
...identity.subject !== void 0 ? { subject: identity.subject } : {}
|
|
1926
1770
|
}).then((res) => {
|
|
1927
1771
|
syncDTO(res.created, "baked");
|
|
1928
1772
|
apply({
|
|
1929
1773
|
t: "select",
|
|
1930
1774
|
ids: [refKey(res.created.ref)]
|
|
1931
1775
|
});
|
|
1932
|
-
|
|
1776
|
+
return res.created.ref;
|
|
1777
|
+
});
|
|
1778
|
+
};
|
|
1779
|
+
/** The click path's fire-and-forget wrapper: a rejected placement is logged,
|
|
1780
|
+
* never thrown into the gesture. */
|
|
1781
|
+
const createStampAtSync = (pon, point, source, desired, rotCW = 0, identity = {}) => {
|
|
1782
|
+
const placed = createStampAt(pon, point, source, desired, rotCW, identity);
|
|
1783
|
+
if (!placed) return false;
|
|
1784
|
+
placed.catch((err) => console.error("[annotation] stamp placement failed:", err));
|
|
1933
1785
|
return true;
|
|
1934
1786
|
};
|
|
1787
|
+
/** Programmatic placement — the same law as a click, awaited. */
|
|
1788
|
+
const placeStamp = async (input, placement) => {
|
|
1789
|
+
const meta = sniffBinaryMetadata((await resolveBinarySource(input.source)).bytes);
|
|
1790
|
+
if (!meta) throw new Error("[annotation] stamp source must be PNG, JPEG, or single-page PDF bytes");
|
|
1791
|
+
const placed = createStampAt(placement.pageObjectNumber, placement.at, input.source, desiredStampSize(meta, placement.targetWidth ?? input.targetWidth, input.intrinsicSize), placement.rotation ?? 0, {
|
|
1792
|
+
...input.name !== void 0 ? { name: input.name } : {},
|
|
1793
|
+
...input.subject !== void 0 ? { subject: input.subject } : {}
|
|
1794
|
+
});
|
|
1795
|
+
if (!placed) throw new Error(`[annotation] cannot place a stamp on page ${placement.pageObjectNumber}: document or page not ready`);
|
|
1796
|
+
return placed;
|
|
1797
|
+
};
|
|
1935
1798
|
/** The active tool's upright counter-rotation for a click at `displayRotation`
|
|
1936
1799
|
* (0 when the tool doesn't ask for upright, or the display isn't rotated). */
|
|
1937
1800
|
const uprightRotFor = (displayRotation) => {
|
|
@@ -1942,10 +1805,13 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1942
1805
|
const placeArmedStamp = (pon, point, displayRotation) => {
|
|
1943
1806
|
const armed = armedStamp;
|
|
1944
1807
|
if (!armed) return false;
|
|
1945
|
-
return
|
|
1808
|
+
return createStampAtSync(pon, point, armed.source, {
|
|
1946
1809
|
width: armed.width,
|
|
1947
1810
|
height: armed.height
|
|
1948
|
-
}, uprightRotFor(displayRotation)
|
|
1811
|
+
}, uprightRotFor(displayRotation), {
|
|
1812
|
+
...armed.name !== void 0 ? { name: armed.name } : {},
|
|
1813
|
+
...armed.subject !== void 0 ? { subject: armed.subject } : {}
|
|
1814
|
+
});
|
|
1949
1815
|
};
|
|
1950
1816
|
/** Sniff a stamp source (rejecting non-image bytes) and place it at its
|
|
1951
1817
|
* intrinsic size (fit to the page in {@link createStampAt}); `targetWidth`
|
|
@@ -1956,7 +1822,7 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
1956
1822
|
console.error("[annotation] stamp source must be PNG, JPEG, or single-page PDF bytes");
|
|
1957
1823
|
return;
|
|
1958
1824
|
}
|
|
1959
|
-
|
|
1825
|
+
createStampAtSync(pon, point, source, desiredStampSize(meta, targetWidth), rotCW);
|
|
1960
1826
|
};
|
|
1961
1827
|
/**
|
|
1962
1828
|
* Click-to-place: resolve the ACTIVE tool's source spec. Fixed `bytes` place
|
|
@@ -2637,8 +2503,62 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
2637
2503
|
});
|
|
2638
2504
|
});
|
|
2639
2505
|
} else if (fx.fx === "syncLink") scheduleLinkSync(fx.id, { target: fx.target });
|
|
2640
|
-
else
|
|
2506
|
+
else {
|
|
2507
|
+
refKey(fx.ref);
|
|
2508
|
+
doc.page(fx.ref.pageObjectNumber).annotations.delete(fx.ref).then(() => {}, () => {});
|
|
2509
|
+
}
|
|
2641
2510
|
}
|
|
2511
|
+
const reloadPageNow = async (pon) => {
|
|
2512
|
+
const doc = ctx.doc;
|
|
2513
|
+
const crop = cropOf(pon);
|
|
2514
|
+
if (!doc || !crop) return;
|
|
2515
|
+
try {
|
|
2516
|
+
const snap = await doc.page(pon).annotations.list();
|
|
2517
|
+
const m = model();
|
|
2518
|
+
const stale = m.order.filter((id) => m.byId[id]?.pon === pon);
|
|
2519
|
+
if (stale.length) apply({
|
|
2520
|
+
t: "remove",
|
|
2521
|
+
ids: stale
|
|
2522
|
+
});
|
|
2523
|
+
apply({
|
|
2524
|
+
t: "loaded",
|
|
2525
|
+
annots: snap.annotations.map((d) => ingestDTO(d, crop))
|
|
2526
|
+
});
|
|
2527
|
+
} catch {}
|
|
2528
|
+
};
|
|
2529
|
+
/** Script patch → the engine's per-kind patch vocabulary. Colors cross the
|
|
2530
|
+
* Acrobat-array → engine {r,g,b}/255 boundary here; everything else maps
|
|
2531
|
+
* one-to-one (the VM's validity matrix already scoped keys per kind). */
|
|
2532
|
+
const engineScriptPatch = (subtype, patch) => {
|
|
2533
|
+
const out = { subtype };
|
|
2534
|
+
const toEngineColor = (color) => {
|
|
2535
|
+
const rgb = scriptColorToRgb(color);
|
|
2536
|
+
return rgb ? {
|
|
2537
|
+
r: Math.round(rgb.r * 255),
|
|
2538
|
+
g: Math.round(rgb.g * 255),
|
|
2539
|
+
b: Math.round(rgb.b * 255)
|
|
2540
|
+
} : null;
|
|
2541
|
+
};
|
|
2542
|
+
if (patch.strokeColor) {
|
|
2543
|
+
const color = toEngineColor(patch.strokeColor);
|
|
2544
|
+
if (!color) return /* @__PURE__ */ new Error("transparent stroke colours are not supported");
|
|
2545
|
+
out.color = color;
|
|
2546
|
+
}
|
|
2547
|
+
if (patch.fillColor) out.interiorColor = toEngineColor(patch.fillColor);
|
|
2548
|
+
if (patch.opacity !== void 0) out.opacity = patch.opacity;
|
|
2549
|
+
if (patch.width !== void 0) out.strokeWidth = patch.width;
|
|
2550
|
+
if (patch.borderStyle) out.borderStyle = patch.borderStyle === "D" ? "dashed" : "solid";
|
|
2551
|
+
if (patch.dash) out.dashArray = patch.dash;
|
|
2552
|
+
if (patch.rect) out.rect = {
|
|
2553
|
+
left: patch.rect[0],
|
|
2554
|
+
bottom: patch.rect[1],
|
|
2555
|
+
right: patch.rect[2],
|
|
2556
|
+
top: patch.rect[3]
|
|
2557
|
+
};
|
|
2558
|
+
if (patch.contents !== void 0) out.contents = patch.contents;
|
|
2559
|
+
if (patch.flags) out.flags = patch.flags;
|
|
2560
|
+
return out;
|
|
2561
|
+
};
|
|
2642
2562
|
return {
|
|
2643
2563
|
create: async (pon, draft) => {
|
|
2644
2564
|
const doc = ctx.doc;
|
|
@@ -2652,6 +2572,7 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
2652
2572
|
return res.created.ref;
|
|
2653
2573
|
},
|
|
2654
2574
|
armStamp,
|
|
2575
|
+
placeStamp,
|
|
2655
2576
|
disarmStamp,
|
|
2656
2577
|
placeArmedStamp,
|
|
2657
2578
|
requestStampAt,
|
|
@@ -2665,7 +2586,20 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
2665
2586
|
const g = ctx.getState().toolGhost;
|
|
2666
2587
|
return g && g.pon === pon ? g : null;
|
|
2667
2588
|
},
|
|
2668
|
-
armedStampPreview: () =>
|
|
2589
|
+
armedStampPreview: (devicePixelWidth) => {
|
|
2590
|
+
const armed = armedStamp;
|
|
2591
|
+
if (!armed?.preview) return Promise.resolve(null);
|
|
2592
|
+
const bucket = previewBucket(devicePixelWidth ?? 0);
|
|
2593
|
+
let pending = armed.previewCache.get(bucket);
|
|
2594
|
+
if (!pending) {
|
|
2595
|
+
pending = armed.preview(bucket).catch((error) => {
|
|
2596
|
+
console.error("[annotation] stamp ghost preview failed:", error);
|
|
2597
|
+
return null;
|
|
2598
|
+
});
|
|
2599
|
+
armed.previewCache.set(bucket, pending);
|
|
2600
|
+
}
|
|
2601
|
+
return pending;
|
|
2602
|
+
},
|
|
2669
2603
|
stampArmEpoch: () => ctx.getState().stampArmEpoch,
|
|
2670
2604
|
setFilePickerProvider: (provider) => {
|
|
2671
2605
|
filePickerProvider = provider;
|
|
@@ -2779,10 +2713,13 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
2779
2713
|
const h = hitTest(m, at.pon, at.point, chromeGeomAt(at.scale), m.hitMargin, pageBoxOf(at.pon), inertIdsAt(at.pon), viewEnv(at.zoom, at.rotation));
|
|
2780
2714
|
if (h.t === "annot") id = h.id;
|
|
2781
2715
|
}
|
|
2782
|
-
if (m.hovered !== id)
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2716
|
+
if (m.hovered !== id) {
|
|
2717
|
+
hoverFeed?.hover(id);
|
|
2718
|
+
apply({
|
|
2719
|
+
t: "hover",
|
|
2720
|
+
id
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2786
2723
|
},
|
|
2787
2724
|
behaviorFor: (a) => behaviors.find((b) => b.matches(a) && b.engaged()) ?? null,
|
|
2788
2725
|
linkItemsOn: (pon) => memoLinkItems(pon),
|
|
@@ -3002,24 +2939,7 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
3002
2939
|
return subs.length > 0 && subs.every((a) => a.ref != null && allowsMutation("update", a.ref));
|
|
3003
2940
|
},
|
|
3004
2941
|
ensurePage: () => {},
|
|
3005
|
-
reloadPage:
|
|
3006
|
-
const doc = ctx.doc;
|
|
3007
|
-
const crop = cropOf(pon);
|
|
3008
|
-
if (!doc || !crop) return;
|
|
3009
|
-
try {
|
|
3010
|
-
const snap = await doc.page(pon).annotations.list();
|
|
3011
|
-
const m = model();
|
|
3012
|
-
const stale = m.order.filter((id) => m.byId[id]?.pon === pon);
|
|
3013
|
-
if (stale.length) apply({
|
|
3014
|
-
t: "remove",
|
|
3015
|
-
ids: stale
|
|
3016
|
-
});
|
|
3017
|
-
apply({
|
|
3018
|
-
t: "loaded",
|
|
3019
|
-
annots: snap.annotations.map((d) => ingestDTO(d, crop))
|
|
3020
|
-
});
|
|
3021
|
-
} catch {}
|
|
3022
|
-
},
|
|
2942
|
+
reloadPage: (pon) => reloadPageNow(pon),
|
|
3023
2943
|
hydration: () => ctx.getState().hydration,
|
|
3024
2944
|
ensureHydrated: () => {
|
|
3025
2945
|
if (hydrationStarted) return;
|
|
@@ -3085,6 +3005,72 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
3085
3005
|
textTimers.clear();
|
|
3086
3006
|
apply({ t: "endTextEdit" });
|
|
3087
3007
|
},
|
|
3008
|
+
commitScriptEffects: async (entries) => {
|
|
3009
|
+
const doc = ctx.doc;
|
|
3010
|
+
if (!doc) return { results: entries.map((entry) => ({
|
|
3011
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3012
|
+
status: "failed",
|
|
3013
|
+
error: "no document"
|
|
3014
|
+
})) };
|
|
3015
|
+
const results = [];
|
|
3016
|
+
const touchedPons = /* @__PURE__ */ new Set();
|
|
3017
|
+
let failed = false;
|
|
3018
|
+
for (const entry of entries) {
|
|
3019
|
+
if (failed) {
|
|
3020
|
+
results.push({
|
|
3021
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3022
|
+
status: "skipped"
|
|
3023
|
+
});
|
|
3024
|
+
continue;
|
|
3025
|
+
}
|
|
3026
|
+
const loaded = model().byId[`obj:${entry.annotObjectNumber}`];
|
|
3027
|
+
const pon = loaded?.pon ?? entry.pageObjectNumber;
|
|
3028
|
+
let ref = loaded?.ref ?? null;
|
|
3029
|
+
let subtype = loaded?.subtype;
|
|
3030
|
+
if ((!ref || !subtype) && pon !== void 0) try {
|
|
3031
|
+
const { annotations } = await doc.page(pon).annotations.list();
|
|
3032
|
+
const dto = annotations.find((candidate) => candidate.ref.kind === "objectNumber" && candidate.ref.annotObjectNumber === entry.annotObjectNumber);
|
|
3033
|
+
if (dto) {
|
|
3034
|
+
ref = dto.ref;
|
|
3035
|
+
subtype = dto.subtype;
|
|
3036
|
+
}
|
|
3037
|
+
} catch {}
|
|
3038
|
+
if (pon === void 0 || !ref || !subtype) {
|
|
3039
|
+
results.push({
|
|
3040
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3041
|
+
status: "failed",
|
|
3042
|
+
error: "annotation not resolved (page not loaded)"
|
|
3043
|
+
});
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
const patch = engineScriptPatch(subtype, entry.patch);
|
|
3047
|
+
if (patch instanceof Error) {
|
|
3048
|
+
results.push({
|
|
3049
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3050
|
+
status: "failed",
|
|
3051
|
+
error: patch.message
|
|
3052
|
+
});
|
|
3053
|
+
continue;
|
|
3054
|
+
}
|
|
3055
|
+
try {
|
|
3056
|
+
await doc.page(pon).annotations.update(ref, patch);
|
|
3057
|
+
touchedPons.add(pon);
|
|
3058
|
+
results.push({
|
|
3059
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3060
|
+
status: "applied"
|
|
3061
|
+
});
|
|
3062
|
+
} catch (error) {
|
|
3063
|
+
failed = true;
|
|
3064
|
+
results.push({
|
|
3065
|
+
annotObjectNumber: entry.annotObjectNumber,
|
|
3066
|
+
status: "failed",
|
|
3067
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
for (const touched of touchedPons) await reloadPageNow(touched);
|
|
3072
|
+
return { results };
|
|
3073
|
+
},
|
|
3088
3074
|
registerBehavior: (b) => {
|
|
3089
3075
|
behaviors.push(b);
|
|
3090
3076
|
return () => {
|
|
@@ -3095,6 +3081,6 @@ function createAnnotationCapability(ctx, config = {}) {
|
|
|
3095
3081
|
};
|
|
3096
3082
|
}
|
|
3097
3083
|
//#endregion
|
|
3098
|
-
export {
|
|
3084
|
+
export { isTouchDirect as a, toPatch as c, buildToolRegistry as i, ARMED_STAMP_TOOL_ID as n, fromDTO as o, DEFAULT_TOOLS as r, toCreateDraft as s, createAnnotationCapability as t };
|
|
3099
3085
|
|
|
3100
|
-
//# sourceMappingURL=capability-
|
|
3086
|
+
//# sourceMappingURL=capability-Bh_1PL5v.js.map
|