@pallasoft/ps-file-components 2.0.0 → 2.1.0

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 (89) hide show
  1. package/dist/components/ps-document-scanner-loader.d.ts +1 -1
  2. package/dist/components/ps-document-scanner-sheet.d.ts +1 -1
  3. package/dist/components/ps-document-scanner-sheet.js +87 -2
  4. package/dist/components/ps-document-scanner.d.ts +1 -1
  5. package/dist/components/ps-document-scanner.js +2 -2
  6. package/dist/components/ps-file-delete-confirm.js +1 -1
  7. package/dist/components/ps-image-viewer.js +35 -16
  8. package/dist/components/ps-pdf-viewer.js +18 -1
  9. package/dist/components/ps-scan-camera-view.js +2 -2
  10. package/dist/components/ps-scan-page-strip.d.ts +8 -0
  11. package/dist/components/ps-scan-page-strip.js +8 -0
  12. package/dist/components/ps-scan-preview.d.ts +3 -1
  13. package/dist/components/ps-scan-preview.js +5 -1
  14. package/dist/components/ps-scan-viewfinder-overlay.d.ts +4 -2
  15. package/dist/components/ps-scan-viewfinder-overlay.js +6 -6
  16. package/dist/components/ps-upload-modal.js +1 -1
  17. package/dist/constants/ps-file-viewer-config.d.ts +1 -0
  18. package/dist/constants/ps-file-viewer-config.js +1 -0
  19. package/dist/constants/ps-scan-config.d.ts +7 -3
  20. package/dist/constants/ps-scan-config.js +7 -3
  21. package/dist/constants/ps-scan-detect-profiles.d.ts +10 -0
  22. package/dist/constants/ps-scan-detect-profiles.js +8 -0
  23. package/dist/esm/components/ps-document-scanner-sheet.js +87 -2
  24. package/dist/esm/components/ps-document-scanner.js +2 -2
  25. package/dist/esm/components/ps-file-delete-confirm.js +2 -2
  26. package/dist/esm/components/ps-image-viewer.js +35 -16
  27. package/dist/esm/components/ps-pdf-viewer.js +18 -1
  28. package/dist/esm/components/ps-scan-camera-view.js +2 -2
  29. package/dist/esm/components/ps-scan-page-strip.js +4 -0
  30. package/dist/esm/components/ps-scan-preview.js +5 -1
  31. package/dist/esm/components/ps-scan-viewfinder-overlay.js +6 -6
  32. package/dist/esm/components/ps-upload-modal.js +2 -2
  33. package/dist/esm/constants/ps-file-viewer-config.js +1 -0
  34. package/dist/esm/constants/ps-scan-config.js +7 -3
  35. package/dist/esm/constants/ps-scan-detect-profiles.js +5 -0
  36. package/dist/esm/hooks/use-ps-drag-pan.js +66 -0
  37. package/dist/esm/hooks/use-ps-scan-live-detection.js +47 -22
  38. package/dist/esm/hooks/use-ps-scan-quad-tween.js +34 -0
  39. package/dist/esm/index.js +4 -0
  40. package/dist/esm/models/ps-document-scanner-page-labels.js +1 -0
  41. package/dist/esm/models/ps-drag-pan-handlers.js +1 -0
  42. package/dist/esm/models/ps-drag-pan-options.js +1 -0
  43. package/dist/esm/models/ps-scan-detect-profile.js +1 -0
  44. package/dist/esm/models/ps-scan-multi-page.js +1 -0
  45. package/dist/esm/models/ps-scan-page.js +1 -0
  46. package/dist/esm/models/ps-scan-pdf-page.js +1 -0
  47. package/dist/esm/services/ps-pan-service.js +13 -0
  48. package/dist/esm/services/ps-scan-edge-service.js +7 -4
  49. package/dist/esm/services/ps-scan-file-name-service.js +3 -0
  50. package/dist/esm/services/ps-scan-frame-service.js +10 -0
  51. package/dist/esm/services/ps-scan-pdf-service.js +69 -0
  52. package/dist/esm/services/ps-scan-quad-tween-service.js +10 -0
  53. package/dist/hooks/use-ps-drag-pan.d.ts +3 -0
  54. package/dist/hooks/use-ps-drag-pan.js +70 -0
  55. package/dist/hooks/use-ps-scan-live-detection.js +46 -21
  56. package/dist/hooks/use-ps-scan-quad-tween.d.ts +3 -0
  57. package/dist/hooks/use-ps-scan-quad-tween.js +38 -0
  58. package/dist/index.d.ts +4 -0
  59. package/dist/index.js +4 -0
  60. package/dist/models/ps-document-scanner-page-labels.d.ts +6 -0
  61. package/dist/models/ps-document-scanner-page-labels.js +2 -0
  62. package/dist/models/ps-document-scanner-props.d.ts +3 -0
  63. package/dist/models/ps-document-scanner-sheet-props.d.ts +2 -0
  64. package/dist/models/ps-drag-pan-handlers.d.ts +8 -0
  65. package/dist/models/ps-drag-pan-handlers.js +2 -0
  66. package/dist/models/ps-drag-pan-options.d.ts +9 -0
  67. package/dist/models/ps-drag-pan-options.js +2 -0
  68. package/dist/models/ps-scan-detect-profile.d.ts +4 -0
  69. package/dist/models/ps-scan-detect-profile.js +2 -0
  70. package/dist/models/ps-scan-live-detection.d.ts +3 -1
  71. package/dist/models/ps-scan-multi-page.d.ts +9 -0
  72. package/dist/models/ps-scan-multi-page.js +2 -0
  73. package/dist/models/ps-scan-page.d.ts +4 -0
  74. package/dist/models/ps-scan-page.js +2 -0
  75. package/dist/models/ps-scan-pdf-page.d.ts +5 -0
  76. package/dist/models/ps-scan-pdf-page.js +2 -0
  77. package/dist/services/ps-pan-service.d.ts +6 -0
  78. package/dist/services/ps-pan-service.js +17 -0
  79. package/dist/services/ps-scan-edge-service.d.ts +2 -1
  80. package/dist/services/ps-scan-edge-service.js +7 -4
  81. package/dist/services/ps-scan-file-name-service.d.ts +1 -0
  82. package/dist/services/ps-scan-file-name-service.js +3 -0
  83. package/dist/services/ps-scan-frame-service.d.ts +3 -0
  84. package/dist/services/ps-scan-frame-service.js +14 -0
  85. package/dist/services/ps-scan-pdf-service.d.ts +10 -0
  86. package/dist/services/ps-scan-pdf-service.js +73 -0
  87. package/dist/services/ps-scan-quad-tween-service.d.ts +4 -0
  88. package/dist/services/ps-scan-quad-tween-service.js +14 -0
  89. package/package.json +3 -5
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export class PsPanService {
2
+ static translate(offset) {
3
+ return `translate(${offset.x}px, ${offset.y}px)`;
4
+ }
5
+ static add(offset, delta) {
6
+ return { x: offset.x + delta.x, y: offset.y + delta.y };
7
+ }
8
+ static isOnScrollbar(element, clientX, clientY) {
9
+ const box = element.getBoundingClientRect();
10
+ return ((element.clientWidth > 0 && clientX - box.left > element.clientWidth) ||
11
+ (element.clientHeight > 0 && clientY - box.top > element.clientHeight));
12
+ }
13
+ }
@@ -1,4 +1,5 @@
1
1
  import { PsScanConfig } from '../constants/ps-scan-config';
2
+ import { PsScanDetectProfiles } from '../constants/ps-scan-detect-profiles';
2
3
  import { PsScanCannyService } from './ps-scan-canny-service';
3
4
  import { PsScanGeometryService } from './ps-scan-geometry-service';
4
5
  import { PsScanHullService } from './ps-scan-hull-service';
@@ -9,13 +10,14 @@ import { PsScanRasterService } from './ps-scan-raster-service';
9
10
  import { PsScanRegionService } from './ps-scan-region-service';
10
11
  import { PsScanYieldService } from './ps-scan-yield-service';
11
12
  export class PsScanEdgeService {
12
- static async detect(source, maxDimension) {
13
+ static async detect(source, maxDimension, profile = PsScanDetectProfiles.full) {
13
14
  const scale = Math.min(1, maxDimension / Math.max(source.width, source.height));
14
15
  const width = Math.max(16, Math.round(source.width * scale));
15
16
  const height = Math.max(16, Math.round(source.height * scale));
16
17
  const raster = PsScanRasterService.resize(source, width, height);
17
18
  const gray = PsScanImageFilterService.blur(PsScanImageFilterService.grayscale(raster), width, height);
18
- await PsScanYieldService.next();
19
+ if (profile.isYielding)
20
+ await PsScanYieldService.next();
19
21
  const edges = PsScanCannyService.edges(gray, width, height);
20
22
  for (let index = 0; index < width; index += 1) {
21
23
  edges[index] = 1;
@@ -27,8 +29,9 @@ export class PsScanEdgeService {
27
29
  }
28
30
  let best = null;
29
31
  let bestArea = 0;
30
- for (const radius of PsScanConfig.closeRadii) {
31
- await PsScanYieldService.next();
32
+ for (const radius of profile.closeRadii) {
33
+ if (profile.isYielding)
34
+ await PsScanYieldService.next();
32
35
  const closed = PsScanMorphologyService.close(edges, width, height, radius);
33
36
  const regions = PsScanRegionService.enclosed(closed, width, height, width * height * PsScanConfig.detectMinAreaRatio * 0.5);
34
37
  for (const region of regions) {
@@ -3,6 +3,9 @@ export class PsScanFileNameService {
3
3
  static build(base) {
4
4
  return `${PsScanFileNameService.sanitize(base)}.jpg`;
5
5
  }
6
+ static buildPdf(base) {
7
+ return `${PsScanFileNameService.sanitize(base)}.pdf`;
8
+ }
6
9
  static sanitize(base) {
7
10
  const cleaned = Array.from(base)
8
11
  .filter((char) => char.charCodeAt(0) >= 32)
@@ -0,0 +1,10 @@
1
+ export class PsScanFrameService {
2
+ static schedule(video, callback) {
3
+ if (video !== null && typeof video.requestVideoFrameCallback === 'function') {
4
+ const videoHandle = video.requestVideoFrameCallback(() => callback());
5
+ return () => video.cancelVideoFrameCallback(videoHandle);
6
+ }
7
+ const frameHandle = requestAnimationFrame(() => callback());
8
+ return () => cancelAnimationFrame(frameHandle);
9
+ }
10
+ }
@@ -0,0 +1,69 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
2
+ export class PsScanPdfService {
3
+ static async build(pages) {
4
+ const loaded = [];
5
+ for (const page of pages)
6
+ loaded.push({
7
+ bytes: new Uint8Array(await page.blob.arrayBuffer()),
8
+ height: page.height,
9
+ width: page.width,
10
+ });
11
+ return PsScanPdfService.fromJpegs(loaded);
12
+ }
13
+ static fromJpegs(pages) {
14
+ const chunks = [];
15
+ const offsets = [];
16
+ let length = 0;
17
+ const push = (chunk) => {
18
+ chunks.push(chunk);
19
+ length += chunk.length;
20
+ };
21
+ const begin = (objectNumber) => {
22
+ offsets[objectNumber] = length;
23
+ push(PsScanPdfService.text(`${objectNumber} 0 obj\n`));
24
+ };
25
+ push(PsScanPdfService.text('%PDF-1.4\n'));
26
+ push(new Uint8Array([0x25, 0xe2, 0xe3, 0xcf, 0xd3, 0x0a]));
27
+ begin(1);
28
+ push(PsScanPdfService.text('<< /Type /Catalog /Pages 2 0 R >>\nendobj\n'));
29
+ const kids = pages.map((_, index) => `${PsScanPdfService.pageObject(index)} 0 R`).join(' ');
30
+ begin(2);
31
+ push(PsScanPdfService.text(`<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>\nendobj\n`));
32
+ pages.forEach((page, index) => {
33
+ const size = PsScanPdfService.pageSize(page);
34
+ const pageNumber = PsScanPdfService.pageObject(index);
35
+ const content = `q ${size.width} 0 0 ${size.height} 0 0 cm /Im0 Do Q`;
36
+ begin(pageNumber);
37
+ push(PsScanPdfService.text(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${size.width} ${size.height}] /Resources << /XObject << /Im0 ${pageNumber + 1} 0 R >> >> /Contents ${pageNumber + 2} 0 R >>\nendobj\n`));
38
+ begin(pageNumber + 1);
39
+ push(PsScanPdfService.text(`<< /Type /XObject /Subtype /Image /Width ${page.width} /Height ${page.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${page.bytes.length} >>\nstream\n`));
40
+ push(page.bytes);
41
+ push(PsScanPdfService.text('\nendstream\nendobj\n'));
42
+ begin(pageNumber + 2);
43
+ push(PsScanPdfService.text(`<< /Length ${content.length} >>\nstream\n${content}\nendstream\nendobj\n`));
44
+ });
45
+ const count = 3 + pages.length * 3;
46
+ const entries = Array.from({ length: count - 1 }, (_, index) => `${String(offsets[index + 1]).padStart(10, '0')} 00000 n \n`).join('');
47
+ push(PsScanPdfService.text(`xref\n0 ${count}\n0000000000 65535 f \n${entries}trailer\n<< /Size ${count} /Root 1 0 R >>\nstartxref\n${length}\n%%EOF\n`));
48
+ const merged = new Uint8Array(length);
49
+ let cursor = 0;
50
+ for (const chunk of chunks) {
51
+ merged.set(chunk, cursor);
52
+ cursor += chunk.length;
53
+ }
54
+ return new Blob([merged], { type: 'application/pdf' });
55
+ }
56
+ static pageSize(page) {
57
+ const scale = PsScanConfig.pdfLongEdgePoints / Math.max(page.width, page.height);
58
+ return {
59
+ height: Math.round(page.height * scale * 100) / 100,
60
+ width: Math.round(page.width * scale * 100) / 100,
61
+ };
62
+ }
63
+ static pageObject(index) {
64
+ return 3 + index * 3;
65
+ }
66
+ static text(value) {
67
+ return new TextEncoder().encode(value);
68
+ }
69
+ }
@@ -0,0 +1,10 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
2
+ export class PsScanQuadTweenService {
3
+ static step(current, target, elapsedMs) {
4
+ const weight = 1 - Math.exp(-Math.max(0, elapsedMs) / PsScanConfig.liveTweenMs);
5
+ return current.map((point, index) => ({
6
+ x: point.x + (target[index].x - point.x) * weight,
7
+ y: point.y + (target[index].y - point.y) * weight,
8
+ }));
9
+ }
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { PsDragPanHandlers } from '../models/ps-drag-pan-handlers';
2
+ import type { PsDragPanOptions } from '../models/ps-drag-pan-options';
3
+ export declare const usePsDragPan: ({ activationDistance, isEnabled, isMouseOnly, onEnd, onMove, onStart, }: PsDragPanOptions) => PsDragPanHandlers;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsDragPan = void 0;
4
+ const react_1 = require("react");
5
+ const ps_pan_service_1 = require("../services/ps-pan-service");
6
+ const usePsDragPan = ({ activationDistance, isEnabled, isMouseOnly, onEnd, onMove, onStart, }) => {
7
+ const origin = (0, react_1.useRef)(null);
8
+ const pointerId = (0, react_1.useRef)(-1);
9
+ const isActive = (0, react_1.useRef)(false);
10
+ const last = (0, react_1.useRef)({ x: 0, y: 0 });
11
+ const activate = (element) => {
12
+ isActive.current = true;
13
+ last.current = { x: 0, y: 0 };
14
+ if (typeof element.setPointerCapture === 'function')
15
+ element.setPointerCapture(pointerId.current);
16
+ element.style.userSelect = 'none';
17
+ window.getSelection()?.removeAllRanges();
18
+ onStart(element);
19
+ };
20
+ const finish = (element) => {
21
+ if (origin.current === null)
22
+ return;
23
+ const wasActive = isActive.current;
24
+ origin.current = null;
25
+ isActive.current = false;
26
+ element.style.userSelect = '';
27
+ if (wasActive)
28
+ onEnd(last.current);
29
+ };
30
+ const onPointerDown = (event) => {
31
+ if (!isEnabled || origin.current !== null)
32
+ return;
33
+ if (isMouseOnly && event.pointerType === 'touch')
34
+ return;
35
+ if (event.pointerType === 'mouse' && event.button !== 0)
36
+ return;
37
+ if (ps_pan_service_1.PsPanService.isOnScrollbar(event.currentTarget, event.clientX, event.clientY))
38
+ return;
39
+ origin.current = { x: event.clientX, y: event.clientY };
40
+ pointerId.current = event.pointerId;
41
+ isActive.current = false;
42
+ if (activationDistance <= 0)
43
+ activate(event.currentTarget);
44
+ };
45
+ const onPointerMove = (event) => {
46
+ if (origin.current === null || event.pointerId !== pointerId.current)
47
+ return;
48
+ const delta = { x: event.clientX - origin.current.x, y: event.clientY - origin.current.y };
49
+ if (!isActive.current) {
50
+ if (Math.hypot(delta.x, delta.y) < activationDistance)
51
+ return;
52
+ activate(event.currentTarget);
53
+ }
54
+ last.current = delta;
55
+ onMove(delta);
56
+ };
57
+ const onPointerEnd = (event) => {
58
+ if (event.pointerId !== pointerId.current)
59
+ return;
60
+ finish(event.currentTarget);
61
+ };
62
+ return {
63
+ onLostPointerCapture: onPointerEnd,
64
+ onPointerCancel: onPointerEnd,
65
+ onPointerDown,
66
+ onPointerMove,
67
+ onPointerUp: onPointerEnd,
68
+ };
69
+ };
70
+ exports.usePsDragPan = usePsDragPan;
@@ -3,43 +3,68 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.usePsScanLiveDetection = void 0;
4
4
  const react_1 = require("react");
5
5
  const ps_scan_config_1 = require("../constants/ps-scan-config");
6
+ const ps_scan_detect_profiles_1 = require("../constants/ps-scan-detect-profiles");
6
7
  const ps_scan_canvas_service_1 = require("../services/ps-scan-canvas-service");
7
8
  const ps_scan_edge_service_1 = require("../services/ps-scan-edge-service");
9
+ const ps_scan_frame_service_1 = require("../services/ps-scan-frame-service");
8
10
  const ps_scan_stability_service_1 = require("../services/ps-scan-stability-service");
9
11
  const usePsScanLiveDetection = (videoRef, isActive) => {
10
- const [detection, setDetection] = (0, react_1.useState)({ quad: null });
12
+ const [hasQuad, setHasQuad] = (0, react_1.useState)(false);
13
+ const quadRef = (0, react_1.useRef)(null);
11
14
  (0, react_1.useEffect)(() => {
15
+ quadRef.current = null;
12
16
  if (!isActive) {
13
- setDetection({ quad: null });
17
+ setHasQuad(false);
14
18
  return;
15
19
  }
16
20
  let isCancelled = false;
21
+ let isRunning = false;
22
+ let lastStart = 0;
17
23
  let state = ps_scan_stability_service_1.PsScanStabilityService.initial();
18
- let timer;
19
- const tick = async () => {
24
+ let cancelNext = () => undefined;
25
+ const schedule = () => {
26
+ if (!isCancelled)
27
+ cancelNext = ps_scan_frame_service_1.PsScanFrameService.schedule(videoRef.current, run);
28
+ };
29
+ const run = async () => {
20
30
  const video = videoRef.current;
21
- if (video !== null && video.videoWidth > 0 && document.visibilityState === 'visible') {
22
- try {
23
- const raster = ps_scan_canvas_service_1.PsScanCanvasService.sample(video, ps_scan_config_1.PsScanConfig.liveDetectMaxDimension);
24
- const quad = await ps_scan_edge_service_1.PsScanEdgeService.detect(raster, ps_scan_config_1.PsScanConfig.liveDetectMaxDimension);
25
- if (isCancelled)
26
- return;
27
- state = ps_scan_stability_service_1.PsScanStabilityService.next(state, quad);
28
- }
29
- catch {
30
- state = ps_scan_stability_service_1.PsScanStabilityService.initial();
31
- }
32
- setDetection((current) => (current.quad === state.quad ? current : { quad: state.quad }));
31
+ const now = performance.now();
32
+ if (isRunning ||
33
+ video === null ||
34
+ video.videoWidth === 0 ||
35
+ document.visibilityState !== 'visible' ||
36
+ now - lastStart < ps_scan_config_1.PsScanConfig.liveDetectMinIntervalMs) {
37
+ schedule();
38
+ return;
33
39
  }
34
- if (!isCancelled)
35
- timer = setTimeout(tick, ps_scan_config_1.PsScanConfig.liveDetectIntervalMs);
40
+ isRunning = true;
41
+ lastStart = now;
42
+ try {
43
+ const raster = ps_scan_canvas_service_1.PsScanCanvasService.sample(video, ps_scan_config_1.PsScanConfig.liveDetectMaxDimension);
44
+ const quad = await ps_scan_edge_service_1.PsScanEdgeService.detect(raster, ps_scan_config_1.PsScanConfig.liveDetectMaxDimension, ps_scan_detect_profiles_1.PsScanDetectProfiles.live);
45
+ if (isCancelled)
46
+ return;
47
+ state = ps_scan_stability_service_1.PsScanStabilityService.next(state, quad);
48
+ }
49
+ catch {
50
+ state = ps_scan_stability_service_1.PsScanStabilityService.initial();
51
+ }
52
+ finally {
53
+ isRunning = false;
54
+ }
55
+ if (isCancelled)
56
+ return;
57
+ quadRef.current = state.quad;
58
+ setHasQuad(state.quad !== null);
59
+ schedule();
36
60
  };
37
- timer = setTimeout(tick, ps_scan_config_1.PsScanConfig.liveDetectIntervalMs);
61
+ schedule();
38
62
  return () => {
39
63
  isCancelled = true;
40
- clearTimeout(timer);
64
+ cancelNext();
65
+ quadRef.current = null;
41
66
  };
42
67
  }, [isActive]);
43
- return detection;
68
+ return { hasQuad, quadRef };
44
69
  };
45
70
  exports.usePsScanLiveDetection = usePsScanLiveDetection;
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from 'react';
2
+ import type { PsScanQuad } from '../models/ps-scan-quad';
3
+ export declare const usePsScanQuadTween: (quadRef: RefObject<PsScanQuad | null>, format: (quad: PsScanQuad) => string) => RefObject<SVGPolygonElement>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsScanQuadTween = void 0;
4
+ const react_1 = require("react");
5
+ const ps_scan_quad_tween_service_1 = require("../services/ps-scan-quad-tween-service");
6
+ const usePsScanQuadTween = (quadRef, format) => {
7
+ const polygon = (0, react_1.useRef)(null);
8
+ const formatRef = (0, react_1.useRef)(format);
9
+ formatRef.current = format;
10
+ (0, react_1.useEffect)(() => {
11
+ let displayed = null;
12
+ let lastTime = performance.now();
13
+ let lastPoints = '';
14
+ let handle = 0;
15
+ const frame = (time) => {
16
+ const target = quadRef.current;
17
+ const elapsed = time - lastTime;
18
+ lastTime = time;
19
+ if (target === null)
20
+ displayed = null;
21
+ else
22
+ displayed =
23
+ displayed === null ? target : ps_scan_quad_tween_service_1.PsScanQuadTweenService.step(displayed, target, elapsed);
24
+ if (displayed !== null && polygon.current !== null) {
25
+ const points = formatRef.current(displayed);
26
+ if (points !== lastPoints) {
27
+ lastPoints = points;
28
+ polygon.current.setAttribute('points', points);
29
+ }
30
+ }
31
+ handle = requestAnimationFrame(frame);
32
+ };
33
+ handle = requestAnimationFrame(frame);
34
+ return () => cancelAnimationFrame(handle);
35
+ }, [quadRef]);
36
+ return polygon;
37
+ };
38
+ exports.usePsScanQuadTween = usePsScanQuadTween;
package/dist/index.d.ts CHANGED
@@ -33,6 +33,8 @@ export * from './constants/ps-upload-messages';
33
33
  export * from './constants/ps-upload-queue-config';
34
34
  export * from './constants/ps-upload-status-tones';
35
35
  export * from './constants/ps-upload-status';
36
+ export * from './models/ps-drag-pan-handlers';
37
+ export * from './models/ps-drag-pan-options';
36
38
  export * from './models/ps-file-icon-button-tone';
37
39
  export * from './models/ps-file-rejection';
38
40
  export * from './models/ps-file-selection-rules';
@@ -66,10 +68,12 @@ export * from './services/ps-text-search-service';
66
68
  export * from './services/ps-upload-queue-service';
67
69
  export * from './services/ps-xhr-upload-service';
68
70
  export * from './hooks/use-ps-active-match-scroll';
71
+ export * from './hooks/use-ps-drag-pan';
69
72
  export * from './hooks/use-ps-file-text';
70
73
  export * from './hooks/use-ps-in-view';
71
74
  export * from './hooks/use-ps-upload-queue';
72
75
  export * from './components/ps-document-scanner';
73
76
  export * from './constants/ps-document-scanner-labels';
77
+ export * from './models/ps-document-scanner-page-labels';
74
78
  export * from './models/ps-document-scanner-props';
75
79
  export * from './models/ps-file-picker-scan';
package/dist/index.js CHANGED
@@ -49,6 +49,8 @@ __exportStar(require("./constants/ps-upload-messages"), exports);
49
49
  __exportStar(require("./constants/ps-upload-queue-config"), exports);
50
50
  __exportStar(require("./constants/ps-upload-status-tones"), exports);
51
51
  __exportStar(require("./constants/ps-upload-status"), exports);
52
+ __exportStar(require("./models/ps-drag-pan-handlers"), exports);
53
+ __exportStar(require("./models/ps-drag-pan-options"), exports);
52
54
  __exportStar(require("./models/ps-file-icon-button-tone"), exports);
53
55
  __exportStar(require("./models/ps-file-rejection"), exports);
54
56
  __exportStar(require("./models/ps-file-selection-rules"), exports);
@@ -82,10 +84,12 @@ __exportStar(require("./services/ps-text-search-service"), exports);
82
84
  __exportStar(require("./services/ps-upload-queue-service"), exports);
83
85
  __exportStar(require("./services/ps-xhr-upload-service"), exports);
84
86
  __exportStar(require("./hooks/use-ps-active-match-scroll"), exports);
87
+ __exportStar(require("./hooks/use-ps-drag-pan"), exports);
85
88
  __exportStar(require("./hooks/use-ps-file-text"), exports);
86
89
  __exportStar(require("./hooks/use-ps-in-view"), exports);
87
90
  __exportStar(require("./hooks/use-ps-upload-queue"), exports);
88
91
  __exportStar(require("./components/ps-document-scanner"), exports);
89
92
  __exportStar(require("./constants/ps-document-scanner-labels"), exports);
93
+ __exportStar(require("./models/ps-document-scanner-page-labels"), exports);
90
94
  __exportStar(require("./models/ps-document-scanner-props"), exports);
91
95
  __exportStar(require("./models/ps-file-picker-scan"), exports);
@@ -0,0 +1,6 @@
1
+ export interface PsDocumentScannerPageLabels {
2
+ addPage: string;
3
+ pageCount: string;
4
+ pageLabel: string;
5
+ removePage: string;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +1,15 @@
1
1
  import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
2
+ import type { PsDocumentScannerPageLabels } from './ps-document-scanner-page-labels';
2
3
  import type { PsLabelSet } from './ps-label-set';
3
4
  export interface PsDocumentScannerProps {
4
5
  className?: string;
5
6
  fileName?: string;
7
+ isMultiPage?: boolean;
6
8
  isOpen: boolean;
7
9
  jpegQuality?: number;
8
10
  labels?: Partial<PsLabelSet<typeof PsDocumentScannerLabels>>;
9
11
  maxDimension?: number;
12
+ pageLabels?: PsDocumentScannerPageLabels;
10
13
  onClose: () => void;
11
14
  onComplete: (files: File[]) => void;
12
15
  }
@@ -1,10 +1,12 @@
1
1
  import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
2
+ import type { PsDocumentScannerPageLabels } from './ps-document-scanner-page-labels';
2
3
  import type { PsLabelSet } from './ps-label-set';
3
4
  export interface PsDocumentScannerSheetProps {
4
5
  fileName: string;
5
6
  jpegQuality: number;
6
7
  labels: PsLabelSet<typeof PsDocumentScannerLabels>;
7
8
  maxDimension: number;
9
+ pageLabels: PsDocumentScannerPageLabels | null;
8
10
  onClose: () => void;
9
11
  onComplete: (files: File[]) => void;
10
12
  }
@@ -0,0 +1,8 @@
1
+ import type { PointerEvent } from 'react';
2
+ export interface PsDragPanHandlers {
3
+ onLostPointerCapture: (event: PointerEvent<HTMLElement>) => void;
4
+ onPointerCancel: (event: PointerEvent<HTMLElement>) => void;
5
+ onPointerDown: (event: PointerEvent<HTMLElement>) => void;
6
+ onPointerMove: (event: PointerEvent<HTMLElement>) => void;
7
+ onPointerUp: (event: PointerEvent<HTMLElement>) => void;
8
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { PsPoint } from './ps-point';
2
+ export interface PsDragPanOptions {
3
+ activationDistance: number;
4
+ isEnabled: boolean;
5
+ isMouseOnly: boolean;
6
+ onEnd: (delta: PsPoint) => void;
7
+ onMove: (delta: PsPoint) => void;
8
+ onStart: (element: HTMLElement) => void;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface PsScanDetectProfile {
2
+ closeRadii: readonly number[];
3
+ isYielding: boolean;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,6 @@
1
+ import type { RefObject } from 'react';
1
2
  import type { PsScanQuad } from './ps-scan-quad';
2
3
  export interface PsScanLiveDetection {
3
- quad: PsScanQuad | null;
4
+ hasQuad: boolean;
5
+ quadRef: RefObject<PsScanQuad | null>;
4
6
  }
@@ -0,0 +1,9 @@
1
+ import type { PsDocumentScannerPageLabels } from './ps-document-scanner-page-labels';
2
+ import type { PsScanPage } from './ps-scan-page';
3
+ export interface PsScanMultiPage {
4
+ isBusy: boolean;
5
+ labels: PsDocumentScannerPageLabels;
6
+ onAddPage: () => void;
7
+ onRemovePage: (id: number) => void;
8
+ pages: PsScanPage[];
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ import type { PsScanResult } from './ps-scan-result';
2
+ export interface PsScanPage extends PsScanResult {
3
+ id: number;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,5 @@
1
+ export interface PsScanPdfPage {
2
+ bytes: Uint8Array;
3
+ height: number;
4
+ width: number;
5
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import type { PsPoint } from '../models/ps-point';
2
+ export declare class PsPanService {
3
+ static translate(offset: PsPoint): string;
4
+ static add(offset: PsPoint, delta: PsPoint): PsPoint;
5
+ static isOnScrollbar(element: HTMLElement, clientX: number, clientY: number): boolean;
6
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsPanService = void 0;
4
+ class PsPanService {
5
+ static translate(offset) {
6
+ return `translate(${offset.x}px, ${offset.y}px)`;
7
+ }
8
+ static add(offset, delta) {
9
+ return { x: offset.x + delta.x, y: offset.y + delta.y };
10
+ }
11
+ static isOnScrollbar(element, clientX, clientY) {
12
+ const box = element.getBoundingClientRect();
13
+ return ((element.clientWidth > 0 && clientX - box.left > element.clientWidth) ||
14
+ (element.clientHeight > 0 && clientY - box.top > element.clientHeight));
15
+ }
16
+ }
17
+ exports.PsPanService = PsPanService;
@@ -1,8 +1,9 @@
1
+ import type { PsScanDetectProfile } from '../models/ps-scan-detect-profile';
1
2
  import type { PsScanLocated } from '../models/ps-scan-located';
2
3
  import type { PsScanQuad } from '../models/ps-scan-quad';
3
4
  import type { PsScanRaster } from '../models/ps-scan-raster';
4
5
  export declare class PsScanEdgeService {
5
- static detect(source: PsScanRaster, maxDimension: number): Promise<PsScanQuad | null>;
6
+ static detect(source: PsScanRaster, maxDimension: number, profile?: PsScanDetectProfile): Promise<PsScanQuad | null>;
6
7
  static locate(source: PsScanRaster, maxDimension: number): Promise<PsScanLocated>;
7
8
  private static candidate;
8
9
  private static normalise;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PsScanEdgeService = void 0;
4
4
  const ps_scan_config_1 = require("../constants/ps-scan-config");
5
+ const ps_scan_detect_profiles_1 = require("../constants/ps-scan-detect-profiles");
5
6
  const ps_scan_canny_service_1 = require("./ps-scan-canny-service");
6
7
  const ps_scan_geometry_service_1 = require("./ps-scan-geometry-service");
7
8
  const ps_scan_hull_service_1 = require("./ps-scan-hull-service");
@@ -12,13 +13,14 @@ const ps_scan_raster_service_1 = require("./ps-scan-raster-service");
12
13
  const ps_scan_region_service_1 = require("./ps-scan-region-service");
13
14
  const ps_scan_yield_service_1 = require("./ps-scan-yield-service");
14
15
  class PsScanEdgeService {
15
- static async detect(source, maxDimension) {
16
+ static async detect(source, maxDimension, profile = ps_scan_detect_profiles_1.PsScanDetectProfiles.full) {
16
17
  const scale = Math.min(1, maxDimension / Math.max(source.width, source.height));
17
18
  const width = Math.max(16, Math.round(source.width * scale));
18
19
  const height = Math.max(16, Math.round(source.height * scale));
19
20
  const raster = ps_scan_raster_service_1.PsScanRasterService.resize(source, width, height);
20
21
  const gray = ps_scan_image_filter_service_1.PsScanImageFilterService.blur(ps_scan_image_filter_service_1.PsScanImageFilterService.grayscale(raster), width, height);
21
- await ps_scan_yield_service_1.PsScanYieldService.next();
22
+ if (profile.isYielding)
23
+ await ps_scan_yield_service_1.PsScanYieldService.next();
22
24
  const edges = ps_scan_canny_service_1.PsScanCannyService.edges(gray, width, height);
23
25
  for (let index = 0; index < width; index += 1) {
24
26
  edges[index] = 1;
@@ -30,8 +32,9 @@ class PsScanEdgeService {
30
32
  }
31
33
  let best = null;
32
34
  let bestArea = 0;
33
- for (const radius of ps_scan_config_1.PsScanConfig.closeRadii) {
34
- await ps_scan_yield_service_1.PsScanYieldService.next();
35
+ for (const radius of profile.closeRadii) {
36
+ if (profile.isYielding)
37
+ await ps_scan_yield_service_1.PsScanYieldService.next();
35
38
  const closed = ps_scan_morphology_service_1.PsScanMorphologyService.close(edges, width, height, radius);
36
39
  const regions = ps_scan_region_service_1.PsScanRegionService.enclosed(closed, width, height, width * height * ps_scan_config_1.PsScanConfig.detectMinAreaRatio * 0.5);
37
40
  for (const region of regions) {