kviewer 0.0.3 → 0.0.5

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.
Files changed (64) hide show
  1. package/README.md +3 -0
  2. package/dist/module.json +2 -2
  3. package/dist/runtime/annotation/engine/painter.d.ts +7 -0
  4. package/dist/runtime/annotation/engine/painter.js +14 -0
  5. package/dist/runtime/annotation/engine/store.d.ts +5 -0
  6. package/dist/runtime/annotation/engine/store.js +17 -0
  7. package/dist/runtime/annotation/engine/tools/free-text.js +3 -1
  8. package/dist/runtime/annotation/engine/types.d.ts +20 -0
  9. package/dist/runtime/annotation/pdf-export/export.d.ts +4 -1
  10. package/dist/runtime/annotation/pdf-export/export.js +101 -4
  11. package/dist/runtime/annotation/pdf-export/parse_freetext.js +106 -18
  12. package/dist/runtime/annotation/pdf-export/parse_highlight.js +22 -6
  13. package/dist/runtime/annotation/pdf-export/parse_ink.js +32 -3
  14. package/dist/runtime/annotation/pdf-export/parse_line.js +133 -13
  15. package/dist/runtime/annotation/pdf-import/decode.d.ts +21 -0
  16. package/dist/runtime/annotation/pdf-import/decode.js +134 -0
  17. package/dist/runtime/annotation/pdf-import/decode_circle.d.ts +3 -0
  18. package/dist/runtime/annotation/pdf-import/decode_circle.js +36 -0
  19. package/dist/runtime/annotation/pdf-import/decode_freetext.d.ts +3 -0
  20. package/dist/runtime/annotation/pdf-import/decode_freetext.js +87 -0
  21. package/dist/runtime/annotation/pdf-import/decode_highlight.d.ts +3 -0
  22. package/dist/runtime/annotation/pdf-import/decode_highlight.js +96 -0
  23. package/dist/runtime/annotation/pdf-import/decode_ink.d.ts +3 -0
  24. package/dist/runtime/annotation/pdf-import/decode_ink.js +48 -0
  25. package/dist/runtime/annotation/pdf-import/decode_line.d.ts +3 -0
  26. package/dist/runtime/annotation/pdf-import/decode_line.js +48 -0
  27. package/dist/runtime/annotation/pdf-import/decode_square.d.ts +3 -0
  28. package/dist/runtime/annotation/pdf-import/decode_square.js +39 -0
  29. package/dist/runtime/annotation/pdf-import/decode_stamp.d.ts +3 -0
  30. package/dist/runtime/annotation/pdf-import/decode_stamp.js +38 -0
  31. package/dist/runtime/annotation/pdf-import/decode_text.d.ts +3 -0
  32. package/dist/runtime/annotation/pdf-import/decode_text.js +33 -0
  33. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +23 -0
  34. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +168 -0
  35. package/dist/runtime/annotation/pdf-import/types.d.ts +57 -0
  36. package/dist/runtime/annotation/pdf-import/types.js +25 -0
  37. package/dist/runtime/annotation/pdf-import/utils.d.ts +48 -0
  38. package/dist/runtime/annotation/pdf-import/utils.js +250 -0
  39. package/dist/runtime/assets/kviewer.css +1 -1
  40. package/dist/runtime/components/AnnotationToolbar.vue +1 -0
  41. package/dist/runtime/components/FloatingPageIndicator.vue +4 -1
  42. package/dist/runtime/components/PdfPage.vue +27 -1
  43. package/dist/runtime/components/Viewer.d.vue.ts +12 -1
  44. package/dist/runtime/components/Viewer.vue +112 -34
  45. package/dist/runtime/components/Viewer.vue.d.ts +12 -1
  46. package/dist/runtime/components/ViewerBar.vue +10 -5
  47. package/dist/runtime/components/ViewerTabs.d.vue.ts +16 -1
  48. package/dist/runtime/components/ViewerTabs.vue +42 -12
  49. package/dist/runtime/components/ViewerTabs.vue.d.ts +16 -1
  50. package/dist/runtime/components/form-fields/FormCheckbox.vue +37 -1
  51. package/dist/runtime/components/tools/ActionTools.vue +3 -0
  52. package/dist/runtime/components/tools/PageInfo.vue +1 -1
  53. package/dist/runtime/components/tools/SearchTool.vue +3 -1
  54. package/dist/runtime/components/tools/ZoomControls.vue +3 -0
  55. package/dist/runtime/composables/shape-detection-utils.d.ts +38 -0
  56. package/dist/runtime/composables/shape-detection-utils.js +50 -0
  57. package/dist/runtime/composables/useFormFields.d.ts +3 -1
  58. package/dist/runtime/composables/useFormFields.js +47 -0
  59. package/dist/runtime/composables/useShapeDetection.d.ts +24 -0
  60. package/dist/runtime/composables/useShapeDetection.js +235 -0
  61. package/dist/runtime/composables/useViewerState.d.ts +2 -0
  62. package/dist/runtime/composables/useViewerState.js +5 -1
  63. package/dist/runtime/plugin.d.ts +1 -1
  64. package/package.json +28 -5
@@ -0,0 +1,235 @@
1
+ import { provide, inject } from "vue";
2
+ import {
3
+ pdfRectToViewport,
4
+ isCheckboxLikePdf,
5
+ isCheckboxLikeWidget,
6
+ makeShape,
7
+ SYMBOL_CHARS
8
+ } from "./shape-detection-utils.js";
9
+ const SHAPE_DETECTION_KEY = Symbol("kviewer-shape-detection");
10
+ let opsPromise = null;
11
+ function getOPS() {
12
+ if (!opsPromise) {
13
+ opsPromise = import("pdfjs-dist/legacy/build/pdf.mjs").then(
14
+ (pdfjs) => pdfjs.OPS
15
+ );
16
+ }
17
+ return opsPromise;
18
+ }
19
+ async function detectAnnotationShapes(pageNumber, pdfPage, viewport) {
20
+ const shapes = [];
21
+ const annotations = await pdfPage.getAnnotations({ intent: "display" });
22
+ for (const annot of annotations) {
23
+ if (!annot.rect) continue;
24
+ const isWidget = annot.fieldType === "Btn" || annot.subtype === "Widget" || annot.subtype === "Square" || annot.subtype === "Circle";
25
+ if (!isWidget) continue;
26
+ const [x1, y1, x2, y2] = annot.rect;
27
+ const { left, top, w, h } = pdfRectToViewport(viewport, x1, y1, x2, y2);
28
+ if (!isCheckboxLikeWidget(w, h)) continue;
29
+ shapes.push(
30
+ makeShape(
31
+ annot.id || `annot_p${pageNumber}_${shapes.length}`,
32
+ left,
33
+ top,
34
+ w,
35
+ h
36
+ )
37
+ );
38
+ }
39
+ return shapes;
40
+ }
41
+ async function detectGraphicalShapes(pageNumber, pdfPage, viewport) {
42
+ const shapes = [];
43
+ try {
44
+ const opList = await pdfPage.getOperatorList();
45
+ const OPS = await getOPS();
46
+ let pathStarted = false;
47
+ let currentPath = [];
48
+ let pathBBox = null;
49
+ const addShape = (x, y, w, h, type) => {
50
+ const aw = Math.abs(w);
51
+ const ah = Math.abs(h);
52
+ if (!isCheckboxLikePdf(aw, ah)) return;
53
+ const { left, top, w: vw, h: vh } = pdfRectToViewport(viewport, x, y, x + w, y + h);
54
+ shapes.push(makeShape(`gfx_p${pageNumber}_${shapes.length}`, left, top, vw, vh, type));
55
+ };
56
+ const checkPathBBox = (type) => {
57
+ if (!pathBBox) return;
58
+ const w = pathBBox.maxX - pathBBox.minX;
59
+ const h = pathBBox.maxY - pathBBox.minY;
60
+ if (!isCheckboxLikePdf(w, h)) return;
61
+ const { left, top, w: vw, h: vh } = pdfRectToViewport(
62
+ viewport,
63
+ pathBBox.minX,
64
+ pathBBox.minY,
65
+ pathBBox.maxX,
66
+ pathBBox.maxY
67
+ );
68
+ shapes.push(makeShape(`gfx_p${pageNumber}_${shapes.length}`, left, top, vw, vh, type));
69
+ };
70
+ const expandBBox = (x, y) => {
71
+ if (!pathBBox) {
72
+ pathBBox = { minX: x, maxX: x, minY: y, maxY: y };
73
+ } else {
74
+ pathBBox.minX = Math.min(pathBBox.minX, x);
75
+ pathBBox.maxX = Math.max(pathBBox.maxX, x);
76
+ pathBBox.minY = Math.min(pathBBox.minY, y);
77
+ pathBBox.maxY = Math.max(pathBBox.maxY, y);
78
+ }
79
+ };
80
+ for (let i = 0; i < opList.fnArray.length; i++) {
81
+ const fn = opList.fnArray[i];
82
+ const args = opList.argsArray[i];
83
+ if (fn === OPS.rectangle) {
84
+ const [x, y, w, h] = args;
85
+ addShape(x, y, w, h, "rectangle");
86
+ } else if (fn === OPS.constructPath) {
87
+ if (args[2] && args[2].length === 4) {
88
+ const bbox = args[2];
89
+ const bw = bbox[2] - bbox[0];
90
+ const bh = bbox[3] - bbox[1];
91
+ addShape(bbox[0], bbox[1], bw, bh, "rectangle");
92
+ } else if (Array.isArray(args[0])) {
93
+ const ops = args[0];
94
+ const coords = args[1];
95
+ if (!ops || !coords) continue;
96
+ currentPath = [];
97
+ pathBBox = null;
98
+ let ci = 0;
99
+ for (let oi = 0; oi < ops.length; oi++) {
100
+ const op = ops[oi];
101
+ if (op === OPS.moveTo) {
102
+ const x = coords[ci++];
103
+ const y = coords[ci++];
104
+ currentPath.push([x, y]);
105
+ expandBBox(x, y);
106
+ } else if (op === OPS.lineTo) {
107
+ const x = coords[ci++];
108
+ const y = coords[ci++];
109
+ currentPath.push([x, y]);
110
+ expandBBox(x, y);
111
+ } else if (op === OPS.rectangle) {
112
+ const x = coords[ci++];
113
+ const y = coords[ci++];
114
+ const w = coords[ci++];
115
+ const h = coords[ci++];
116
+ addShape(x, y, w, h, "rectangle");
117
+ } else if (op === OPS.curveTo) {
118
+ ci += 6;
119
+ } else if (op === OPS.curveTo2) {
120
+ ci += 4;
121
+ } else if (op === OPS.curveTo3) {
122
+ ci += 4;
123
+ }
124
+ }
125
+ if (pathBBox && currentPath.length >= 4) {
126
+ checkPathBBox(currentPath.length > 8 ? "circle" : "rectangle");
127
+ }
128
+ pathStarted = true;
129
+ }
130
+ } else if (fn === OPS.moveTo) {
131
+ pathStarted = true;
132
+ currentPath = [[args[0], args[1]]];
133
+ pathBBox = null;
134
+ expandBBox(args[0], args[1]);
135
+ } else if (fn === OPS.lineTo && pathStarted) {
136
+ currentPath.push([args[0], args[1]]);
137
+ expandBBox(args[0], args[1]);
138
+ } else if (pathStarted && (fn === OPS.stroke || fn === OPS.fill || fn === OPS.eoFill || fn === OPS.fillStroke)) {
139
+ if ((currentPath.length === 5 || currentPath.length > 8) && pathBBox) {
140
+ checkPathBBox(currentPath.length > 8 ? "circle" : "rectangle");
141
+ }
142
+ pathStarted = false;
143
+ currentPath = [];
144
+ pathBBox = null;
145
+ }
146
+ }
147
+ } catch (err) {
148
+ console.error("[ShapeDetection] Graphical detection error:", err);
149
+ }
150
+ return shapes;
151
+ }
152
+ async function detectIconFontShapes(pageNumber, pdfPage, viewport) {
153
+ const shapes = [];
154
+ try {
155
+ const textContent = await pdfPage.getTextContent();
156
+ for (const item of textContent.items) {
157
+ if (!("str" in item) || !item.str || !("transform" in item) || !item.transform) continue;
158
+ const char = item.str;
159
+ const code = char.charCodeAt(0);
160
+ const isSymbol = SYMBOL_CHARS.has(char);
161
+ const isBox = code >= 9472 && code <= 9599;
162
+ const isGeo = code >= 9632 && code <= 9727;
163
+ const isPUA = code >= 57344 && code <= 63743;
164
+ if (!isSymbol && !isBox && !isGeo && !isPUA) continue;
165
+ const t = item.transform;
166
+ const w = item.width || Math.abs(t[0]);
167
+ const h = item.height || Math.abs(t[3]);
168
+ const x = t[4];
169
+ const y = t[5];
170
+ const minSize = isPUA ? 12 : 5;
171
+ if (w < minSize || w > 40 || h < minSize || h > 40) continue;
172
+ if (isPUA) {
173
+ const ratio = w / h;
174
+ if (ratio < 0.8 || ratio > 1.25) continue;
175
+ }
176
+ const { left, top, w: vw, h: vh } = pdfRectToViewport(viewport, x, y, x + w, y + h);
177
+ const type = isPUA ? "circle" : void 0;
178
+ shapes.push(makeShape(`icon_p${pageNumber}_${shapes.length}`, left, top, vw, vh, type));
179
+ }
180
+ } catch (err) {
181
+ console.error("[ShapeDetection] Icon-font detection error:", err);
182
+ }
183
+ return shapes;
184
+ }
185
+ export function createShapeDetection() {
186
+ const shapeCache = /* @__PURE__ */ new Map();
187
+ async function preprocessShapesForPage(pageNumber, pdfPage, viewport) {
188
+ try {
189
+ const [annotShapes, gfxShapes, iconShapes] = await Promise.all([
190
+ detectAnnotationShapes(pageNumber, pdfPage, viewport),
191
+ detectGraphicalShapes(pageNumber, pdfPage, viewport),
192
+ detectIconFontShapes(pageNumber, pdfPage, viewport)
193
+ ]);
194
+ const shapes = [...annotShapes, ...gfxShapes, ...iconShapes];
195
+ shapeCache.set(pageNumber, shapes);
196
+ if (import.meta.dev) {
197
+ console.debug(
198
+ `[ShapeDetection] Page ${pageNumber}: ${shapes.length} shapes (${annotShapes.length} annot, ${gfxShapes.length} gfx, ${iconShapes.length} icon)`
199
+ );
200
+ }
201
+ return shapes;
202
+ } catch (err) {
203
+ console.error("[ShapeDetection] preprocessShapesForPage error:", err);
204
+ shapeCache.set(pageNumber, []);
205
+ return [];
206
+ }
207
+ }
208
+ function findShapeAtPoint(pageNumber, x, y) {
209
+ const shapes = shapeCache.get(pageNumber) || [];
210
+ return shapes.find(
211
+ (s) => x >= s.rect.x && x <= s.rect.x + s.rect.w && y >= s.rect.y && y <= s.rect.y + s.rect.h
212
+ ) || null;
213
+ }
214
+ function clearShapeCache(pageNumber) {
215
+ shapeCache.delete(pageNumber);
216
+ }
217
+ function clearAllShapeCache() {
218
+ shapeCache.clear();
219
+ }
220
+ const detection = {
221
+ preprocessShapesForPage,
222
+ findShapeAtPoint,
223
+ clearShapeCache,
224
+ clearAllShapeCache
225
+ };
226
+ provide(SHAPE_DETECTION_KEY, detection);
227
+ return detection;
228
+ }
229
+ export function useShapeDetection() {
230
+ const detection = inject(SHAPE_DETECTION_KEY);
231
+ if (!detection) {
232
+ throw new Error("useShapeDetection() must be used inside a <KViewer> component");
233
+ }
234
+ return detection;
235
+ }
@@ -26,6 +26,7 @@ export interface ViewerState {
26
26
  currentPage: Ref<number>;
27
27
  totalPages: Ref<number>;
28
28
  isLoading: Ref<boolean>;
29
+ error: Ref<string | null>;
29
30
  activeTool: Ref<ActiveTool>;
30
31
  doc: ShallowRef<PDFDocumentProxy | null>;
31
32
  annotations: ShallowRef<Map<string, IAnnotationStore>>;
@@ -54,6 +55,7 @@ export interface ViewerState {
54
55
  scrollToPage: (pageNumber: number) => void;
55
56
  history: AnnotationHistory;
56
57
  readonly: Ref<boolean>;
58
+ shapeDetection: Ref<boolean>;
57
59
  }
58
60
  export interface ViewerStateProvider {
59
61
  state: ViewerState;
@@ -20,6 +20,7 @@ export function provideViewerState() {
20
20
  const currentPage = ref(1);
21
21
  const totalPages = ref(0);
22
22
  const isLoading = ref(false);
23
+ const error = ref(null);
23
24
  const activeTool = ref("hand");
24
25
  const doc = shallowRef(null);
25
26
  const annotations = shallowRef(/* @__PURE__ */ new Map());
@@ -63,6 +64,7 @@ export function provideViewerState() {
63
64
  }
64
65
  }
65
66
  const readonly = ref(false);
67
+ const shapeDetectionEnabled = ref(false);
66
68
  const stylusMode = ref(false);
67
69
  function setStylusMode(enabled) {
68
70
  stylusMode.value = enabled;
@@ -145,6 +147,7 @@ export function provideViewerState() {
145
147
  currentPage,
146
148
  totalPages,
147
149
  isLoading,
150
+ error,
148
151
  activeTool,
149
152
  doc,
150
153
  annotations,
@@ -172,7 +175,8 @@ export function provideViewerState() {
172
175
  setStylusMode,
173
176
  scrollToPage,
174
177
  history,
175
- readonly
178
+ readonly,
179
+ shapeDetection: shapeDetectionEnabled
176
180
  };
177
181
  provide(VIEWER_STATE_KEY, state);
178
182
  return {
@@ -1,2 +1,2 @@
1
- declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: any;
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kviewer",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Kabema PDF Editor",
5
5
  "repository": "kabema/kviewer",
6
6
  "license": "MIT",
@@ -23,7 +23,8 @@
23
23
  "dist"
24
24
  ],
25
25
  "workspaces": [
26
- "playground"
26
+ "playground",
27
+ "docs"
27
28
  ],
28
29
  "scripts": {
29
30
  "prepack": "nuxt-module-build build",
@@ -32,9 +33,11 @@
32
33
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
33
34
  "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
34
35
  "lint": "eslint .",
35
- "test": "vitest run",
36
+ "test": "vitest run --exclude '**/e2e/**'",
36
37
  "test:watch": "vitest watch",
37
- "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
38
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
39
+ "test:e2e": "playwright test",
40
+ "test:e2e:ui": "playwright test --ui"
38
41
  },
39
42
  "dependencies": {
40
43
  "@nuxt/kit": "^4.4.2",
@@ -52,13 +55,33 @@
52
55
  "@nuxt/schema": "^4.4.2",
53
56
  "@nuxt/test-utils": "^4.0.0",
54
57
  "@nuxt/ui": "4.6.0",
58
+ "@playwright/test": "^1.59.1",
55
59
  "@types/node": "latest",
60
+ "canvas": "^3.2.3",
56
61
  "changelogen": "^0.6.2",
57
62
  "eslint": "^10.1.0",
58
63
  "nuxt": "^4.4.2",
64
+ "path2d": "^0.3.1",
65
+ "pixelmatch": "^7.1.0",
66
+ "pngjs": "^7.0.0",
59
67
  "typescript": "~6.0.2",
60
68
  "vitest": "^4.1.1",
61
69
  "vue-tsc": "^3.2.6"
62
70
  },
63
- "packageManager": "pnpm@10.33.0"
71
+ "packageManager": "pnpm@10.33.0",
72
+ "pnpm": {
73
+ "onlyBuiltDependencies": [
74
+ "canvas",
75
+ "@parcel/watcher",
76
+ "better-sqlite3",
77
+ "esbuild",
78
+ "sharp",
79
+ "vue-demi",
80
+ "unrs-resolver"
81
+ ],
82
+ "overrides": {
83
+ "vue": "^3.5.32",
84
+ "vue-router": "^5.0.4"
85
+ }
86
+ }
64
87
  }