@pallasoft/ps-file-components 1.3.0 → 1.5.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 (216) hide show
  1. package/dist/components/ps-document-scanner-loader.d.ts +1 -0
  2. package/dist/components/ps-document-scanner-loader.js +40 -0
  3. package/dist/components/ps-document-scanner-sheet.d.ts +2 -0
  4. package/dist/components/ps-document-scanner-sheet.js +186 -0
  5. package/dist/components/ps-document-scanner.d.ts +2 -0
  6. package/dist/components/ps-document-scanner.js +17 -0
  7. package/dist/components/ps-file-picker-actions.d.ts +10 -0
  8. package/dist/components/ps-file-picker-actions.js +21 -0
  9. package/dist/components/ps-file-picker.d.ts +7 -1
  10. package/dist/components/ps-file-picker.js +30 -6
  11. package/dist/components/ps-file-viewer-pane.d.ts +2 -0
  12. package/dist/components/ps-file-viewer-pane.js +39 -0
  13. package/dist/components/ps-scan-button.d.ts +13 -0
  14. package/dist/components/ps-scan-button.js +9 -0
  15. package/dist/components/ps-scan-camera-view.d.ts +27 -0
  16. package/dist/components/ps-scan-camera-view.js +34 -0
  17. package/dist/components/ps-scan-crop-editor.d.ts +13 -0
  18. package/dist/components/ps-scan-crop-editor.js +69 -0
  19. package/dist/components/ps-scan-denied-steps.d.ts +6 -0
  20. package/dist/components/ps-scan-denied-steps.js +33 -0
  21. package/dist/components/ps-scan-icon-button.d.ts +11 -0
  22. package/dist/components/ps-scan-icon-button.js +9 -0
  23. package/dist/components/ps-scan-illustration.d.ts +1 -0
  24. package/dist/components/ps-scan-illustration.js +6 -0
  25. package/dist/components/ps-scan-loupe.d.ts +10 -0
  26. package/dist/components/ps-scan-loupe.js +31 -0
  27. package/dist/components/ps-scan-page-list.d.ts +14 -0
  28. package/dist/components/ps-scan-page-list.js +22 -0
  29. package/dist/components/ps-scan-page-stack.d.ts +7 -0
  30. package/dist/components/ps-scan-page-stack.js +6 -0
  31. package/dist/components/ps-scan-permission-view.d.ts +11 -0
  32. package/dist/components/ps-scan-permission-view.js +48 -0
  33. package/dist/components/ps-scan-photo-button.d.ts +8 -0
  34. package/dist/components/ps-scan-photo-button.js +20 -0
  35. package/dist/components/ps-scan-review.d.ts +24 -0
  36. package/dist/components/ps-scan-review.js +37 -0
  37. package/dist/components/ps-scan-shutter.d.ts +7 -0
  38. package/dist/components/ps-scan-shutter.js +6 -0
  39. package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
  40. package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
  41. package/dist/components/ps-upload-row.js +1 -1
  42. package/dist/constants/ps-document-scanner-labels.d.ts +80 -0
  43. package/dist/constants/ps-document-scanner-labels.js +83 -0
  44. package/dist/constants/ps-file-picker-labels.d.ts +5 -3
  45. package/dist/constants/ps-file-picker-labels.js +5 -3
  46. package/dist/constants/ps-scan-camera-status.d.ts +11 -0
  47. package/dist/constants/ps-scan-camera-status.js +13 -0
  48. package/dist/constants/ps-scan-chrome-classes.d.ts +12 -0
  49. package/dist/constants/ps-scan-chrome-classes.js +15 -0
  50. package/dist/constants/ps-scan-config.d.ts +48 -0
  51. package/dist/constants/ps-scan-config.js +51 -0
  52. package/dist/constants/ps-scan-corner-order.d.ts +1 -0
  53. package/dist/constants/ps-scan-corner-order.js +4 -0
  54. package/dist/constants/ps-scan-filter.d.ts +7 -0
  55. package/dist/constants/ps-scan-filter.js +9 -0
  56. package/dist/constants/ps-scan-output-format.d.ts +5 -0
  57. package/dist/constants/ps-scan-output-format.js +7 -0
  58. package/dist/constants/ps-scan-platform.d.ts +6 -0
  59. package/dist/constants/ps-scan-platform.js +8 -0
  60. package/dist/constants/ps-scan-step.d.ts +6 -0
  61. package/dist/constants/ps-scan-step.js +8 -0
  62. package/dist/esm/components/ps-document-scanner-loader.js +4 -0
  63. package/dist/esm/components/ps-document-scanner-sheet.js +182 -0
  64. package/dist/esm/components/ps-document-scanner.js +13 -0
  65. package/dist/esm/components/ps-file-picker-actions.js +17 -0
  66. package/dist/esm/components/ps-file-picker.js +31 -7
  67. package/dist/esm/components/ps-file-viewer-pane.js +35 -0
  68. package/dist/esm/components/ps-scan-button.js +5 -0
  69. package/dist/esm/components/ps-scan-camera-view.js +30 -0
  70. package/dist/esm/components/ps-scan-crop-editor.js +65 -0
  71. package/dist/esm/components/ps-scan-denied-steps.js +29 -0
  72. package/dist/esm/components/ps-scan-icon-button.js +5 -0
  73. package/dist/esm/components/ps-scan-illustration.js +2 -0
  74. package/dist/esm/components/ps-scan-loupe.js +27 -0
  75. package/dist/esm/components/ps-scan-page-list.js +18 -0
  76. package/dist/esm/components/ps-scan-page-stack.js +2 -0
  77. package/dist/esm/components/ps-scan-permission-view.js +44 -0
  78. package/dist/esm/components/ps-scan-photo-button.js +16 -0
  79. package/dist/esm/components/ps-scan-review.js +33 -0
  80. package/dist/esm/components/ps-scan-shutter.js +2 -0
  81. package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
  82. package/dist/esm/components/ps-upload-row.js +1 -1
  83. package/dist/esm/constants/ps-document-scanner-labels.js +80 -0
  84. package/dist/esm/constants/ps-file-picker-labels.js +5 -3
  85. package/dist/esm/constants/ps-scan-camera-status.js +10 -0
  86. package/dist/esm/constants/ps-scan-chrome-classes.js +12 -0
  87. package/dist/esm/constants/ps-scan-config.js +48 -0
  88. package/dist/esm/constants/ps-scan-corner-order.js +1 -0
  89. package/dist/esm/constants/ps-scan-filter.js +6 -0
  90. package/dist/esm/constants/ps-scan-output-format.js +4 -0
  91. package/dist/esm/constants/ps-scan-platform.js +5 -0
  92. package/dist/esm/constants/ps-scan-step.js +5 -0
  93. package/dist/esm/hooks/use-ps-focus-trap.js +45 -0
  94. package/dist/esm/hooks/use-ps-scan-camera.js +99 -0
  95. package/dist/esm/hooks/use-ps-scan-live-detection.js +45 -0
  96. package/dist/esm/hooks/use-ps-scan-pages.js +62 -0
  97. package/dist/esm/hooks/use-ps-scan-previews.js +57 -0
  98. package/dist/esm/index.js +8 -0
  99. package/dist/esm/models/ps-document-scanner-props.js +1 -0
  100. package/dist/esm/models/ps-document-scanner-sheet-props.js +1 -0
  101. package/dist/esm/models/ps-file-picker-scan.js +1 -0
  102. package/dist/esm/models/ps-file-viewer-pane-props.js +1 -0
  103. package/dist/esm/models/ps-scan-added-page.js +1 -0
  104. package/dist/esm/models/ps-scan-camera-session.js +1 -0
  105. package/dist/esm/models/ps-scan-decoded-image.js +1 -0
  106. package/dist/esm/models/ps-scan-levels.js +1 -0
  107. package/dist/esm/models/ps-scan-live-detection.js +1 -0
  108. package/dist/esm/models/ps-scan-output-options.js +1 -0
  109. package/dist/esm/models/ps-scan-page.js +1 -0
  110. package/dist/esm/models/ps-scan-pdf-page.js +1 -0
  111. package/dist/esm/models/ps-scan-prepared.js +1 -0
  112. package/dist/esm/models/ps-scan-quad.js +1 -0
  113. package/dist/esm/models/ps-scan-raster.js +1 -0
  114. package/dist/esm/models/ps-scan-rendered-page.js +1 -0
  115. package/dist/esm/models/ps-scan-stability.js +1 -0
  116. package/dist/esm/services/ps-scan-camera-service.js +95 -0
  117. package/dist/esm/services/ps-scan-canvas-service.js +52 -0
  118. package/dist/esm/services/ps-scan-edge-service.js +156 -0
  119. package/dist/esm/services/ps-scan-enhance-service.js +97 -0
  120. package/dist/esm/services/ps-scan-file-name-service.js +25 -0
  121. package/dist/esm/services/ps-scan-geometry-service.js +116 -0
  122. package/dist/esm/services/ps-scan-homography-service.js +46 -0
  123. package/dist/esm/services/ps-scan-hull-service.js +59 -0
  124. package/dist/esm/services/ps-scan-image-filter-service.js +52 -0
  125. package/dist/esm/services/ps-scan-order-service.js +13 -0
  126. package/dist/esm/services/ps-scan-output-service.js +24 -0
  127. package/dist/esm/services/ps-scan-pdf-service.js +56 -0
  128. package/dist/esm/services/ps-scan-pipeline-service.js +40 -0
  129. package/dist/esm/services/ps-scan-platform-service.js +15 -0
  130. package/dist/esm/services/ps-scan-raster-service.js +67 -0
  131. package/dist/esm/services/ps-scan-stability-service.js +41 -0
  132. package/dist/esm/services/ps-scan-template-service.js +5 -0
  133. package/dist/esm/services/ps-scan-warp-service.js +35 -0
  134. package/dist/hooks/use-ps-focus-trap.d.ts +2 -0
  135. package/dist/hooks/use-ps-focus-trap.js +49 -0
  136. package/dist/hooks/use-ps-scan-camera.d.ts +11 -0
  137. package/dist/hooks/use-ps-scan-camera.js +103 -0
  138. package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
  139. package/dist/hooks/use-ps-scan-live-detection.js +49 -0
  140. package/dist/hooks/use-ps-scan-pages.d.ts +12 -0
  141. package/dist/hooks/use-ps-scan-pages.js +66 -0
  142. package/dist/hooks/use-ps-scan-previews.d.ts +2 -0
  143. package/dist/hooks/use-ps-scan-previews.js +61 -0
  144. package/dist/index.d.ts +8 -0
  145. package/dist/index.js +8 -0
  146. package/dist/models/ps-document-scanner-props.d.ts +16 -0
  147. package/dist/models/ps-document-scanner-props.js +2 -0
  148. package/dist/models/ps-document-scanner-sheet-props.d.ts +14 -0
  149. package/dist/models/ps-document-scanner-sheet-props.js +2 -0
  150. package/dist/models/ps-file-picker-scan.d.ts +4 -0
  151. package/dist/models/ps-file-picker-scan.js +2 -0
  152. package/dist/models/ps-file-viewer-pane-props.d.ts +12 -0
  153. package/dist/models/ps-file-viewer-pane-props.js +2 -0
  154. package/dist/models/ps-scan-added-page.d.ts +5 -0
  155. package/dist/models/ps-scan-added-page.js +2 -0
  156. package/dist/models/ps-scan-camera-session.d.ts +5 -0
  157. package/dist/models/ps-scan-camera-session.js +2 -0
  158. package/dist/models/ps-scan-decoded-image.d.ts +6 -0
  159. package/dist/models/ps-scan-decoded-image.js +2 -0
  160. package/dist/models/ps-scan-levels.d.ts +4 -0
  161. package/dist/models/ps-scan-levels.js +2 -0
  162. package/dist/models/ps-scan-live-detection.d.ts +5 -0
  163. package/dist/models/ps-scan-live-detection.js +2 -0
  164. package/dist/models/ps-scan-output-options.d.ts +7 -0
  165. package/dist/models/ps-scan-output-options.js +2 -0
  166. package/dist/models/ps-scan-page.d.ts +12 -0
  167. package/dist/models/ps-scan-page.js +2 -0
  168. package/dist/models/ps-scan-pdf-page.d.ts +5 -0
  169. package/dist/models/ps-scan-pdf-page.js +2 -0
  170. package/dist/models/ps-scan-prepared.d.ts +7 -0
  171. package/dist/models/ps-scan-prepared.js +2 -0
  172. package/dist/models/ps-scan-quad.d.ts +2 -0
  173. package/dist/models/ps-scan-quad.js +2 -0
  174. package/dist/models/ps-scan-raster.d.ts +5 -0
  175. package/dist/models/ps-scan-raster.js +2 -0
  176. package/dist/models/ps-scan-rendered-page.d.ts +5 -0
  177. package/dist/models/ps-scan-rendered-page.js +2 -0
  178. package/dist/models/ps-scan-stability.d.ts +8 -0
  179. package/dist/models/ps-scan-stability.js +2 -0
  180. package/dist/services/ps-scan-camera-service.d.ts +14 -0
  181. package/dist/services/ps-scan-camera-service.js +99 -0
  182. package/dist/services/ps-scan-canvas-service.d.ts +9 -0
  183. package/dist/services/ps-scan-canvas-service.js +56 -0
  184. package/dist/services/ps-scan-edge-service.d.ts +9 -0
  185. package/dist/services/ps-scan-edge-service.js +160 -0
  186. package/dist/services/ps-scan-enhance-service.d.ts +9 -0
  187. package/dist/services/ps-scan-enhance-service.js +101 -0
  188. package/dist/services/ps-scan-file-name-service.d.ts +6 -0
  189. package/dist/services/ps-scan-file-name-service.js +29 -0
  190. package/dist/services/ps-scan-geometry-service.d.ts +20 -0
  191. package/dist/services/ps-scan-geometry-service.js +120 -0
  192. package/dist/services/ps-scan-homography-service.d.ts +7 -0
  193. package/dist/services/ps-scan-homography-service.js +50 -0
  194. package/dist/services/ps-scan-hull-service.d.ts +6 -0
  195. package/dist/services/ps-scan-hull-service.js +63 -0
  196. package/dist/services/ps-scan-image-filter-service.d.ts +6 -0
  197. package/dist/services/ps-scan-image-filter-service.js +56 -0
  198. package/dist/services/ps-scan-order-service.d.ts +4 -0
  199. package/dist/services/ps-scan-order-service.js +17 -0
  200. package/dist/services/ps-scan-output-service.d.ts +5 -0
  201. package/dist/services/ps-scan-output-service.js +28 -0
  202. package/dist/services/ps-scan-pdf-service.d.ts +5 -0
  203. package/dist/services/ps-scan-pdf-service.js +60 -0
  204. package/dist/services/ps-scan-pipeline-service.d.ts +10 -0
  205. package/dist/services/ps-scan-pipeline-service.js +44 -0
  206. package/dist/services/ps-scan-platform-service.d.ts +4 -0
  207. package/dist/services/ps-scan-platform-service.js +19 -0
  208. package/dist/services/ps-scan-raster-service.d.ts +6 -0
  209. package/dist/services/ps-scan-raster-service.js +71 -0
  210. package/dist/services/ps-scan-stability-service.d.ts +9 -0
  211. package/dist/services/ps-scan-stability-service.js +45 -0
  212. package/dist/services/ps-scan-template-service.d.ts +3 -0
  213. package/dist/services/ps-scan-template-service.js +9 -0
  214. package/dist/services/ps-scan-warp-service.d.ts +6 -0
  215. package/dist/services/ps-scan-warp-service.js +39 -0
  216. package/package.json +9 -6
@@ -0,0 +1,99 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
3
+ import { PsScanConfig } from '../constants/ps-scan-config';
4
+ import { PsScanCameraService } from '../services/ps-scan-camera-service';
5
+ import { PsScanCanvasService } from '../services/ps-scan-canvas-service';
6
+ export const usePsScanCamera = (isActive) => {
7
+ const videoRef = useRef(null);
8
+ const sessionRef = useRef(null);
9
+ const [status, setStatus] = useState(PsScanCameraStatus.Checking);
10
+ const [request, setRequest] = useState(0);
11
+ const [canTorch, setCanTorch] = useState(false);
12
+ const [isTorchOn, setIsTorchOn] = useState(false);
13
+ useEffect(() => {
14
+ let isCancelled = false;
15
+ let permission = null;
16
+ const onChange = () => {
17
+ if (permission !== null && permission.state === 'granted')
18
+ setRequest((current) => current + 1);
19
+ };
20
+ PsScanCameraService.check().then(async (next) => {
21
+ if (isCancelled)
22
+ return;
23
+ if (next === PsScanCameraStatus.Starting)
24
+ setRequest(1);
25
+ else
26
+ setStatus(next);
27
+ permission = await PsScanCameraService.permission();
28
+ if (isCancelled || permission === null)
29
+ return;
30
+ permission.addEventListener('change', onChange);
31
+ });
32
+ return () => {
33
+ isCancelled = true;
34
+ if (permission !== null)
35
+ permission.removeEventListener('change', onChange);
36
+ };
37
+ }, []);
38
+ useEffect(() => {
39
+ if (!isActive || request === 0)
40
+ return;
41
+ let isCancelled = false;
42
+ setIsTorchOn(false);
43
+ setStatus(PsScanCameraStatus.Starting);
44
+ PsScanCameraService.open()
45
+ .then(async (session) => {
46
+ if (isCancelled) {
47
+ PsScanCameraService.stop(session.stream);
48
+ return;
49
+ }
50
+ sessionRef.current = session;
51
+ setCanTorch(session.canTorch);
52
+ if (videoRef.current !== null)
53
+ await PsScanCameraService.attach(videoRef.current, session);
54
+ if (!isCancelled)
55
+ setStatus(PsScanCameraStatus.Ready);
56
+ })
57
+ .catch((error) => {
58
+ if (!isCancelled)
59
+ setStatus(PsScanCameraService.classify(error));
60
+ });
61
+ return () => {
62
+ isCancelled = true;
63
+ PsScanCameraService.stop(sessionRef.current === null ? null : sessionRef.current.stream);
64
+ sessionRef.current = null;
65
+ if (videoRef.current !== null)
66
+ videoRef.current.srcObject = null;
67
+ };
68
+ }, [isActive, request]);
69
+ const attachVideo = useCallback((element) => {
70
+ videoRef.current = element;
71
+ if (element !== null && sessionRef.current !== null)
72
+ void PsScanCameraService.attach(element, sessionRef.current);
73
+ }, []);
74
+ const allow = useCallback(() => setRequest((current) => current + 1), []);
75
+ const toggleTorch = useCallback(async () => {
76
+ const session = sessionRef.current;
77
+ if (session === null)
78
+ return;
79
+ setIsTorchOn(await PsScanCameraService.setTorch(session.track, !isTorchOn));
80
+ }, [isTorchOn]);
81
+ const capture = useCallback(async () => {
82
+ const video = videoRef.current;
83
+ if (video === null ||
84
+ video.videoWidth === 0 ||
85
+ video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA)
86
+ return null;
87
+ return await PsScanCanvasService.fromVideo(video, PsScanConfig.capturedJpegQuality);
88
+ }, []);
89
+ return {
90
+ allow,
91
+ attachVideo,
92
+ canTorch,
93
+ capture,
94
+ isTorchOn,
95
+ status,
96
+ toggleTorch,
97
+ videoRef,
98
+ };
99
+ };
@@ -0,0 +1,45 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { PsScanConfig } from '../constants/ps-scan-config';
3
+ import { PsScanCanvasService } from '../services/ps-scan-canvas-service';
4
+ import { PsScanEdgeService } from '../services/ps-scan-edge-service';
5
+ import { PsScanStabilityService } from '../services/ps-scan-stability-service';
6
+ export const usePsScanLiveDetection = (videoRef, isActive, isAuto, onSteady) => {
7
+ const [detection, setDetection] = useState({ progress: 0, quad: null });
8
+ const isAutoRef = useRef(isAuto);
9
+ const onSteadyRef = useRef(onSteady);
10
+ isAutoRef.current = isAuto;
11
+ onSteadyRef.current = onSteady;
12
+ useEffect(() => {
13
+ if (!isActive) {
14
+ setDetection({ progress: 0, quad: null });
15
+ return;
16
+ }
17
+ let state = PsScanStabilityService.initial();
18
+ let last = performance.now();
19
+ let timer;
20
+ const tick = () => {
21
+ const video = videoRef.current;
22
+ const now = performance.now();
23
+ if (video !== null && video.videoWidth > 0 && document.visibilityState === 'visible') {
24
+ try {
25
+ const raster = PsScanCanvasService.sample(video, PsScanConfig.liveDetectMaxDimension);
26
+ state = PsScanStabilityService.next(state, PsScanEdgeService.detect(raster), now - last);
27
+ }
28
+ catch {
29
+ state = PsScanStabilityService.initial();
30
+ }
31
+ const progress = isAutoRef.current ? PsScanStabilityService.progress(state) : 0;
32
+ setDetection({ progress, quad: state.quad });
33
+ if (progress >= 1) {
34
+ state = PsScanStabilityService.disarm(state);
35
+ onSteadyRef.current();
36
+ }
37
+ }
38
+ last = now;
39
+ timer = setTimeout(tick, PsScanConfig.liveDetectIntervalMs);
40
+ };
41
+ timer = setTimeout(tick, PsScanConfig.liveDetectIntervalMs);
42
+ return () => clearTimeout(timer);
43
+ }, [isActive]);
44
+ return detection;
45
+ };
@@ -0,0 +1,62 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { PsScanFilter } from '../constants/ps-scan-filter';
3
+ import { PsScanOrderService } from '../services/ps-scan-order-service';
4
+ import { PsScanPipelineService } from '../services/ps-scan-pipeline-service';
5
+ export const usePsScanPages = () => {
6
+ const [pages, setPages] = useState([]);
7
+ const urls = useRef(new Set());
8
+ const counter = useRef(0);
9
+ useEffect(() => () => {
10
+ urls.current.forEach((url) => URL.revokeObjectURL(url));
11
+ urls.current.clear();
12
+ }, []);
13
+ const build = useCallback(async (source) => {
14
+ const prepared = await PsScanPipelineService.prepare(source);
15
+ const sourceUrl = URL.createObjectURL(source);
16
+ counter.current += 1;
17
+ urls.current.add(sourceUrl);
18
+ return {
19
+ isDetected: prepared.isDetected,
20
+ page: {
21
+ corners: prepared.corners,
22
+ filter: PsScanFilter.Document,
23
+ height: prepared.height,
24
+ id: `scan-page-${counter.current}`,
25
+ rotation: 0,
26
+ source,
27
+ sourceUrl,
28
+ width: prepared.width,
29
+ },
30
+ };
31
+ }, []);
32
+ const add = useCallback(async (sources) => {
33
+ const built = [];
34
+ for (const source of sources)
35
+ built.push(await build(source));
36
+ setPages((current) => [...current, ...built.map((item) => item.page)]);
37
+ return built;
38
+ }, [build]);
39
+ const replace = useCallback(async (id, source) => {
40
+ const built = await build(source);
41
+ setPages((current) => current.map((page) => {
42
+ if (page.id !== id)
43
+ return page;
44
+ URL.revokeObjectURL(page.sourceUrl);
45
+ urls.current.delete(page.sourceUrl);
46
+ return { ...built.page, filter: page.filter, id: page.id };
47
+ }));
48
+ return built;
49
+ }, [build]);
50
+ const update = useCallback((id, patch) => setPages((current) => current.map((page) => (page.id === id ? { ...page, ...patch } : page))), []);
51
+ const setCorners = useCallback((id, corners) => update(id, { corners }), [update]);
52
+ const remove = useCallback((id) => setPages((current) => {
53
+ const target = current.find((page) => page.id === id);
54
+ if (target !== undefined) {
55
+ URL.revokeObjectURL(target.sourceUrl);
56
+ urls.current.delete(target.sourceUrl);
57
+ }
58
+ return current.filter((page) => page.id !== id);
59
+ }), []);
60
+ const move = useCallback((from, to) => setPages((current) => PsScanOrderService.move(current, from, to)), []);
61
+ return { add, move, pages, remove, replace, setCorners, update };
62
+ };
@@ -0,0 +1,57 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { PsScanConfig } from '../constants/ps-scan-config';
3
+ import { PsScanCanvasService } from '../services/ps-scan-canvas-service';
4
+ import { PsScanPipelineService } from '../services/ps-scan-pipeline-service';
5
+ export const usePsScanPreviews = (pages) => {
6
+ const rasters = useRef(new Map());
7
+ const urls = useRef(new Map());
8
+ const [previews, setPreviews] = useState({});
9
+ const signature = pages
10
+ .map((page) => `${page.id}:${page.filter}:${page.rotation}:${page.corners.map((point) => `${point.x.toFixed(4)},${point.y.toFixed(4)}`).join(';')}`)
11
+ .join('|');
12
+ useEffect(() => {
13
+ let isCancelled = false;
14
+ const timer = setTimeout(async () => {
15
+ const stale = [];
16
+ for (const page of pages) {
17
+ if (isCancelled)
18
+ return;
19
+ let raster = rasters.current.get(page.id);
20
+ if (raster === undefined) {
21
+ raster = await PsScanPipelineService.measure(page.source);
22
+ rasters.current.set(page.id, raster);
23
+ }
24
+ const processed = PsScanPipelineService.process(page, raster, PsScanConfig.previewMaxDimension);
25
+ const blob = await PsScanCanvasService.encode(processed, PsScanConfig.defaultJpegQuality);
26
+ if (isCancelled)
27
+ return;
28
+ const previous = urls.current.get(page.id);
29
+ if (previous !== undefined)
30
+ stale.push(previous);
31
+ urls.current.set(page.id, URL.createObjectURL(blob));
32
+ }
33
+ if (isCancelled)
34
+ return;
35
+ const ids = new Set(pages.map((page) => page.id));
36
+ urls.current.forEach((url, id) => {
37
+ if (ids.has(id))
38
+ return;
39
+ stale.push(url);
40
+ urls.current.delete(id);
41
+ rasters.current.delete(id);
42
+ });
43
+ setPreviews(Object.fromEntries(urls.current));
44
+ setTimeout(() => stale.forEach((url) => URL.revokeObjectURL(url)), 1000);
45
+ }, PsScanConfig.previewDebounceMs);
46
+ return () => {
47
+ isCancelled = true;
48
+ clearTimeout(timer);
49
+ };
50
+ }, [signature]);
51
+ useEffect(() => () => {
52
+ urls.current.forEach((url) => URL.revokeObjectURL(url));
53
+ urls.current.clear();
54
+ rasters.current.clear();
55
+ }, []);
56
+ return previews;
57
+ };
package/dist/esm/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './components/ps-file-list';
5
5
  export * from './components/ps-file-picker';
6
6
  export * from './components/ps-file-viewer-button';
7
7
  export * from './components/ps-file-viewer-content';
8
+ export * from './components/ps-file-viewer-pane';
8
9
  export * from './components/ps-file-viewer';
9
10
  export * from './components/ps-highlighted-text';
10
11
  export * from './components/ps-image-viewer';
@@ -39,6 +40,7 @@ export * from './models/ps-file-selection';
39
40
  export * from './models/ps-file-text-state';
40
41
  export * from './models/ps-file-viewer-button-tone';
41
42
  export * from './models/ps-file-viewer-content-props';
43
+ export * from './models/ps-file-viewer-pane-props';
42
44
  export * from './models/ps-label-set';
43
45
  export * from './models/ps-page-size';
44
46
  export * from './models/ps-pdf-match';
@@ -67,3 +69,9 @@ export * from './hooks/use-ps-active-match-scroll';
67
69
  export * from './hooks/use-ps-file-text';
68
70
  export * from './hooks/use-ps-in-view';
69
71
  export * from './hooks/use-ps-upload-queue';
72
+ export * from './components/ps-document-scanner';
73
+ export * from './constants/ps-document-scanner-labels';
74
+ export * from './constants/ps-scan-filter';
75
+ export * from './constants/ps-scan-output-format';
76
+ export * from './models/ps-document-scanner-props';
77
+ export * from './models/ps-file-picker-scan';
@@ -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 @@
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 @@
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,95 @@
1
+ import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
2
+ import { PsScanConfig } from '../constants/ps-scan-config';
3
+ export class PsScanCameraService {
4
+ static async attach(video, session) {
5
+ if (video.srcObject !== session.stream)
6
+ video.srcObject = session.stream;
7
+ await video.play().catch(() => undefined);
8
+ }
9
+ static async check() {
10
+ if (globalThis.isSecureContext === false)
11
+ return PsScanCameraStatus.Insecure;
12
+ if (!PsScanCameraService.isSupported())
13
+ return PsScanCameraStatus.Unavailable;
14
+ if (!(await PsScanCameraService.hasCamera()))
15
+ return PsScanCameraStatus.Unavailable;
16
+ const permission = await PsScanCameraService.permission();
17
+ if (permission === null)
18
+ return PsScanCameraStatus.Prompt;
19
+ if (permission.state === 'granted')
20
+ return PsScanCameraStatus.Starting;
21
+ return permission.state === 'denied' ? PsScanCameraStatus.Denied : PsScanCameraStatus.Prompt;
22
+ }
23
+ static classify(error) {
24
+ const name = typeof error === 'object' && error !== null && 'name' in error ? String(error.name) : '';
25
+ if (name === 'NotAllowedError' || name === 'PermissionDeniedError')
26
+ return PsScanCameraStatus.Denied;
27
+ if (name === 'SecurityError')
28
+ return PsScanCameraStatus.Insecure;
29
+ if (name === 'NotReadableError' || name === 'TrackStartError' || name === 'AbortError')
30
+ return PsScanCameraStatus.Busy;
31
+ return PsScanCameraStatus.Unavailable;
32
+ }
33
+ static async hasCamera() {
34
+ if (typeof navigator.mediaDevices.enumerateDevices !== 'function')
35
+ return true;
36
+ try {
37
+ const devices = await navigator.mediaDevices.enumerateDevices();
38
+ return devices.length === 0 || devices.some((device) => device.kind === 'videoinput');
39
+ }
40
+ catch {
41
+ return true;
42
+ }
43
+ }
44
+ static isSupported() {
45
+ return (typeof navigator !== 'undefined' &&
46
+ typeof navigator.mediaDevices !== 'undefined' &&
47
+ typeof navigator.mediaDevices.getUserMedia === 'function');
48
+ }
49
+ static async open() {
50
+ const stream = await navigator.mediaDevices.getUserMedia({
51
+ audio: false,
52
+ video: {
53
+ facingMode: { ideal: 'environment' },
54
+ height: { ideal: 1440 },
55
+ width: { ideal: 1920 },
56
+ },
57
+ });
58
+ const track = stream.getVideoTracks()[0];
59
+ return { canTorch: PsScanCameraService.supportsTorch(track), stream, track };
60
+ }
61
+ static async permission() {
62
+ if (typeof navigator.permissions === 'undefined' ||
63
+ typeof navigator.permissions.query !== 'function')
64
+ return null;
65
+ try {
66
+ return await navigator.permissions.query({
67
+ name: PsScanConfig.cameraPermission,
68
+ });
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ static async setTorch(track, isOn) {
75
+ try {
76
+ await track.applyConstraints({
77
+ advanced: [{ [PsScanConfig.torchConstraint]: isOn }],
78
+ });
79
+ return isOn;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ static stop(stream) {
86
+ if (stream === null)
87
+ return;
88
+ stream.getTracks().forEach((track) => track.stop());
89
+ }
90
+ static supportsTorch(track) {
91
+ if (track === undefined || typeof track.getCapabilities !== 'function')
92
+ return false;
93
+ return PsScanConfig.torchConstraint in track.getCapabilities();
94
+ }
95
+ }
@@ -0,0 +1,52 @@
1
+ import { PsScanGeometryService } from './ps-scan-geometry-service';
2
+ export class PsScanCanvasService {
3
+ static async decode(blob, maxDimension) {
4
+ const bitmap = await createImageBitmap(blob, { imageOrientation: 'from-image' });
5
+ const size = PsScanGeometryService.fitWithin({ height: bitmap.height, width: bitmap.width }, maxDimension);
6
+ const context = PsScanCanvasService.context(size.width, size.height);
7
+ context.drawImage(bitmap, 0, 0, size.width, size.height);
8
+ const naturalHeight = bitmap.height;
9
+ const naturalWidth = bitmap.width;
10
+ bitmap.close();
11
+ return {
12
+ naturalHeight,
13
+ naturalWidth,
14
+ raster: context.getImageData(0, 0, size.width, size.height),
15
+ };
16
+ }
17
+ static async encode(raster, quality) {
18
+ const context = PsScanCanvasService.context(raster.width, raster.height);
19
+ context.putImageData(new ImageData(raster.data, raster.width, raster.height), 0, 0);
20
+ const canvas = context.canvas;
21
+ if (canvas instanceof HTMLCanvasElement)
22
+ return await new Promise((resolve, reject) => canvas.toBlob((blob) => (blob === null ? reject(new Error('encode')) : resolve(blob)), 'image/jpeg', quality));
23
+ return await canvas.convertToBlob({ quality, type: 'image/jpeg' });
24
+ }
25
+ static async fromVideo(video, quality) {
26
+ const context = PsScanCanvasService.context(video.videoWidth, video.videoHeight);
27
+ context.drawImage(video, 0, 0);
28
+ return await PsScanCanvasService.encode(context.getImageData(0, 0, video.videoWidth, video.videoHeight), quality);
29
+ }
30
+ static sample(video, maxDimension) {
31
+ const size = PsScanGeometryService.fitWithin({ height: video.videoHeight, width: video.videoWidth }, maxDimension);
32
+ const context = PsScanCanvasService.context(size.width, size.height);
33
+ context.drawImage(video, 0, 0, size.width, size.height);
34
+ return context.getImageData(0, 0, size.width, size.height);
35
+ }
36
+ static context(width, height) {
37
+ if (typeof OffscreenCanvas !== 'undefined') {
38
+ const context = new OffscreenCanvas(width, height).getContext('2d', {
39
+ willReadFrequently: true,
40
+ });
41
+ if (context !== null)
42
+ return context;
43
+ }
44
+ const canvas = document.createElement('canvas');
45
+ canvas.width = width;
46
+ canvas.height = height;
47
+ const context = canvas.getContext('2d', { willReadFrequently: true });
48
+ if (context === null)
49
+ throw new Error('canvas');
50
+ return context;
51
+ }
52
+ }
@@ -0,0 +1,156 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
2
+ import { PsScanGeometryService } from './ps-scan-geometry-service';
3
+ import { PsScanHullService } from './ps-scan-hull-service';
4
+ import { PsScanImageFilterService } from './ps-scan-image-filter-service';
5
+ import { PsScanRasterService } from './ps-scan-raster-service';
6
+ export class PsScanEdgeService {
7
+ static detect(source) {
8
+ const scale = Math.min(1, PsScanConfig.detectMaxDimension / Math.max(source.width, source.height));
9
+ const width = Math.max(8, Math.round(source.width * scale));
10
+ const height = Math.max(8, Math.round(source.height * scale));
11
+ const raster = PsScanRasterService.resize(source, width, height);
12
+ const gray = PsScanImageFilterService.blur(PsScanImageFilterService.grayscale(raster), width, height);
13
+ const edges = PsScanImageFilterService.sobel(gray, width, height);
14
+ let mask = PsScanEdgeService.threshold(edges);
15
+ for (let step = 0; step < PsScanConfig.detectDilations; step += 1)
16
+ mask = PsScanEdgeService.dilate(mask, width, height);
17
+ const foreground = PsScanEdgeService.foreground(mask, width, height);
18
+ const component = PsScanEdgeService.largestComponent(foreground, width, height);
19
+ if (component.length < 8)
20
+ return null;
21
+ const hull = PsScanHullService.convexHull(component);
22
+ if (hull.length < 4)
23
+ return null;
24
+ const quad = PsScanGeometryService.order(PsScanHullService.bestQuad(hull));
25
+ const imageArea = width * height;
26
+ const quadArea = PsScanGeometryService.area(quad);
27
+ const hullArea = PsScanGeometryService.area(hull);
28
+ if (quadArea / imageArea < PsScanConfig.detectMinAreaRatio)
29
+ return null;
30
+ if (quadArea / imageArea > PsScanConfig.detectMaxAreaRatio)
31
+ return null;
32
+ if (hullArea > 0 && quadArea / hullArea < PsScanConfig.detectMinHullFill)
33
+ return null;
34
+ if (!PsScanGeometryService.isConvex(quad))
35
+ return null;
36
+ return PsScanGeometryService.inset(quad, PsScanConfig.detectInset).map((point) => ({
37
+ x: Math.min(1, Math.max(0, point.x / (width - 1))),
38
+ y: Math.min(1, Math.max(0, point.y / (height - 1))),
39
+ }));
40
+ }
41
+ static dilate(mask, width, height) {
42
+ const out = new Uint8Array(mask.length);
43
+ for (let y = 0; y < height; y += 1)
44
+ for (let x = 0; x < width; x += 1) {
45
+ if (mask[y * width + x] === 0)
46
+ continue;
47
+ for (let dy = -1; dy <= 1; dy += 1)
48
+ for (let dx = -1; dx <= 1; dx += 1) {
49
+ const nx = x + dx;
50
+ const ny = y + dy;
51
+ if (nx >= 0 && ny >= 0 && nx < width && ny < height)
52
+ out[ny * width + nx] = 1;
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ static foreground(mask, width, height) {
58
+ const background = new Uint8Array(mask.length);
59
+ const stack = [];
60
+ const seed = (index) => {
61
+ if (mask[index] === 0 && background[index] === 0) {
62
+ background[index] = 1;
63
+ stack.push(index);
64
+ }
65
+ };
66
+ for (let x = 0; x < width; x += 1) {
67
+ seed(x);
68
+ seed((height - 1) * width + x);
69
+ }
70
+ for (let y = 0; y < height; y += 1) {
71
+ seed(y * width);
72
+ seed(y * width + width - 1);
73
+ }
74
+ while (stack.length > 0) {
75
+ const index = stack.pop();
76
+ if (index === undefined)
77
+ break;
78
+ const x = index % width;
79
+ const y = (index - x) / width;
80
+ if (x > 0)
81
+ seed(index - 1);
82
+ if (x < width - 1)
83
+ seed(index + 1);
84
+ if (y > 0)
85
+ seed(index - width);
86
+ if (y < height - 1)
87
+ seed(index + width);
88
+ }
89
+ return background.map((value) => 1 - value);
90
+ }
91
+ static largestComponent(mask, width, height) {
92
+ const seen = new Uint8Array(mask.length);
93
+ let best = [];
94
+ let bestSize = 0;
95
+ for (let start = 0; start < mask.length; start += 1) {
96
+ if (mask[start] === 0 || seen[start] === 1)
97
+ continue;
98
+ const members = [];
99
+ const stack = [start];
100
+ let size = 0;
101
+ seen[start] = 1;
102
+ while (stack.length > 0) {
103
+ const index = stack.pop();
104
+ if (index === undefined)
105
+ break;
106
+ const x = index % width;
107
+ const y = (index - x) / width;
108
+ let isBoundary = false;
109
+ size += 1;
110
+ for (let dy = -1; dy <= 1; dy += 1)
111
+ for (let dx = -1; dx <= 1; dx += 1) {
112
+ if (dx === 0 && dy === 0)
113
+ continue;
114
+ const nx = x + dx;
115
+ const ny = y + dy;
116
+ if (nx < 0 || ny < 0 || nx >= width || ny >= height) {
117
+ isBoundary = true;
118
+ continue;
119
+ }
120
+ const next = ny * width + nx;
121
+ if (mask[next] === 0)
122
+ isBoundary = true;
123
+ else if (seen[next] === 0) {
124
+ seen[next] = 1;
125
+ stack.push(next);
126
+ }
127
+ }
128
+ if (isBoundary)
129
+ members.push({ x, y });
130
+ }
131
+ if (size > bestSize) {
132
+ bestSize = size;
133
+ best = members;
134
+ }
135
+ }
136
+ return best;
137
+ }
138
+ static threshold(edges) {
139
+ const bins = new Uint32Array(512);
140
+ edges.forEach((value) => {
141
+ bins[Math.min(511, value)] += 1;
142
+ });
143
+ const target = edges.length * PsScanConfig.detectPercentile;
144
+ let cumulative = 0;
145
+ let high = 0;
146
+ for (let bin = 0; bin < bins.length; bin += 1) {
147
+ cumulative += bins[bin];
148
+ if (cumulative >= target) {
149
+ high = bin;
150
+ break;
151
+ }
152
+ }
153
+ const limit = Math.max(PsScanConfig.detectMinEdgeStrength, high * PsScanConfig.detectRelativeStrength);
154
+ return Uint8Array.from(edges, (value) => (value >= limit ? 1 : 0));
155
+ }
156
+ }