@pallasoft/ps-file-components 1.4.0 → 2.0.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 (194) 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 +64 -86
  4. package/dist/components/ps-document-scanner.d.ts +1 -1
  5. package/dist/components/ps-document-scanner.js +2 -3
  6. package/dist/components/ps-file-picker-actions.d.ts +10 -0
  7. package/dist/components/ps-file-picker-actions.js +21 -0
  8. package/dist/components/ps-file-picker.d.ts +7 -1
  9. package/dist/components/ps-file-picker.js +30 -6
  10. package/dist/components/ps-scan-button.d.ts +13 -0
  11. package/dist/components/ps-scan-button.js +9 -0
  12. package/dist/components/ps-scan-camera-view.d.ts +11 -8
  13. package/dist/components/ps-scan-camera-view.js +14 -16
  14. package/dist/components/ps-scan-crop-editor.d.ts +4 -4
  15. package/dist/components/ps-scan-crop-editor.js +24 -8
  16. package/dist/components/ps-scan-denied-steps.d.ts +6 -0
  17. package/dist/components/ps-scan-denied-steps.js +33 -0
  18. package/dist/components/ps-scan-icon-button.d.ts +4 -3
  19. package/dist/components/ps-scan-icon-button.js +3 -1
  20. package/dist/components/ps-scan-illustration.d.ts +1 -0
  21. package/dist/components/ps-scan-illustration.js +6 -0
  22. package/dist/components/ps-scan-loupe.d.ts +10 -0
  23. package/dist/components/ps-scan-loupe.js +31 -0
  24. package/dist/components/ps-scan-permission-view.d.ts +10 -0
  25. package/dist/components/ps-scan-permission-view.js +48 -0
  26. package/dist/components/ps-scan-photo-button.d.ts +7 -0
  27. package/dist/components/ps-scan-photo-button.js +20 -0
  28. package/dist/components/ps-scan-preview.d.ts +12 -0
  29. package/dist/components/ps-scan-preview.js +10 -0
  30. package/dist/components/ps-scan-shutter.d.ts +6 -0
  31. package/dist/components/ps-scan-shutter.js +6 -0
  32. package/dist/components/ps-scan-viewfinder-overlay.d.ts +8 -0
  33. package/dist/components/ps-scan-viewfinder-overlay.js +28 -0
  34. package/dist/components/ps-upload-row.js +1 -1
  35. package/dist/constants/ps-document-scanner-labels.d.ts +39 -38
  36. package/dist/constants/ps-document-scanner-labels.js +39 -38
  37. package/dist/constants/ps-file-picker-labels.d.ts +5 -3
  38. package/dist/constants/ps-file-picker-labels.js +5 -3
  39. package/dist/constants/ps-scan-camera-status.d.ts +4 -0
  40. package/dist/constants/ps-scan-camera-status.js +4 -0
  41. package/dist/constants/ps-scan-chrome-classes.d.ts +10 -0
  42. package/dist/constants/ps-scan-chrome-classes.js +13 -0
  43. package/dist/constants/ps-scan-config.d.ts +34 -24
  44. package/dist/constants/ps-scan-config.js +34 -24
  45. package/dist/constants/ps-scan-platform.d.ts +6 -0
  46. package/dist/constants/ps-scan-platform.js +8 -0
  47. package/dist/constants/ps-scan-step.d.ts +1 -1
  48. package/dist/constants/ps-scan-step.js +1 -1
  49. package/dist/esm/components/ps-document-scanner-sheet.js +67 -89
  50. package/dist/esm/components/ps-document-scanner.js +2 -3
  51. package/dist/esm/components/ps-file-picker-actions.js +17 -0
  52. package/dist/esm/components/ps-file-picker.js +31 -7
  53. package/dist/esm/components/ps-scan-button.js +5 -0
  54. package/dist/esm/components/ps-scan-camera-view.js +17 -19
  55. package/dist/esm/components/ps-scan-crop-editor.js +26 -10
  56. package/dist/esm/components/ps-scan-denied-steps.js +29 -0
  57. package/dist/esm/components/ps-scan-icon-button.js +3 -1
  58. package/dist/esm/components/ps-scan-illustration.js +2 -0
  59. package/dist/esm/components/ps-scan-loupe.js +27 -0
  60. package/dist/esm/components/ps-scan-permission-view.js +44 -0
  61. package/dist/esm/components/ps-scan-photo-button.js +16 -0
  62. package/dist/esm/components/ps-scan-preview.js +6 -0
  63. package/dist/esm/components/ps-scan-shutter.js +2 -0
  64. package/dist/esm/components/ps-scan-viewfinder-overlay.js +24 -0
  65. package/dist/esm/components/ps-upload-row.js +1 -1
  66. package/dist/esm/constants/ps-document-scanner-labels.js +39 -38
  67. package/dist/esm/constants/ps-file-picker-labels.js +5 -3
  68. package/dist/esm/constants/ps-scan-camera-status.js +4 -0
  69. package/dist/esm/constants/ps-scan-chrome-classes.js +10 -0
  70. package/dist/esm/constants/ps-scan-config.js +34 -24
  71. package/dist/esm/constants/ps-scan-platform.js +5 -0
  72. package/dist/esm/constants/ps-scan-step.js +1 -1
  73. package/dist/esm/hooks/use-ps-scan-camera.js +52 -15
  74. package/dist/esm/hooks/use-ps-scan-live-detection.js +41 -0
  75. package/dist/esm/hooks/use-ps-scan-photo.js +42 -0
  76. package/dist/esm/index.js +1 -2
  77. package/dist/esm/models/ps-scan-region.js +1 -0
  78. package/dist/esm/models/ps-scan-result.js +1 -0
  79. package/dist/esm/models/ps-scan-stability.js +1 -0
  80. package/dist/esm/services/ps-scan-camera-service.js +52 -7
  81. package/dist/esm/services/ps-scan-canny-service.js +93 -0
  82. package/dist/esm/services/ps-scan-canvas-service.js +13 -1
  83. package/dist/esm/services/ps-scan-edge-service.js +73 -137
  84. package/dist/esm/services/ps-scan-file-name-service.js +2 -11
  85. package/dist/esm/services/ps-scan-geometry-service.js +56 -0
  86. package/dist/esm/services/ps-scan-hull-service.js +0 -39
  87. package/dist/esm/services/ps-scan-image-filter-service.js +0 -21
  88. package/dist/esm/services/ps-scan-morphology-service.js +44 -0
  89. package/dist/esm/services/ps-scan-pipeline-service.js +9 -19
  90. package/dist/esm/services/ps-scan-platform-service.js +15 -0
  91. package/dist/esm/services/ps-scan-polygon-service.js +75 -0
  92. package/dist/esm/services/ps-scan-region-service.js +52 -0
  93. package/dist/esm/services/ps-scan-stability-service.js +28 -0
  94. package/dist/esm/services/ps-scan-warp-service.js +8 -2
  95. package/dist/esm/services/ps-scan-yield-service.js +5 -0
  96. package/dist/hooks/use-ps-scan-camera.d.ts +3 -1
  97. package/dist/hooks/use-ps-scan-camera.js +52 -15
  98. package/dist/hooks/use-ps-scan-live-detection.d.ts +3 -0
  99. package/dist/hooks/use-ps-scan-live-detection.js +45 -0
  100. package/dist/hooks/use-ps-scan-photo.d.ts +9 -0
  101. package/dist/hooks/use-ps-scan-photo.js +46 -0
  102. package/dist/index.d.ts +1 -2
  103. package/dist/index.js +1 -2
  104. package/dist/models/ps-document-scanner-props.d.ts +0 -3
  105. package/dist/models/ps-document-scanner-sheet-props.d.ts +0 -3
  106. package/dist/models/ps-file-picker-scan.d.ts +4 -0
  107. package/dist/models/ps-scan-gradient.d.ts +4 -0
  108. package/dist/models/ps-scan-live-detection.d.ts +4 -0
  109. package/dist/models/ps-scan-located.d.ts +5 -0
  110. package/dist/models/ps-scan-photo.d.ts +9 -0
  111. package/dist/models/ps-scan-region.d.ts +5 -0
  112. package/dist/models/ps-scan-region.js +2 -0
  113. package/dist/models/ps-scan-result.d.ts +6 -0
  114. package/dist/models/ps-scan-result.js +2 -0
  115. package/dist/models/ps-scan-stability.d.ts +5 -0
  116. package/dist/models/ps-scan-stability.js +2 -0
  117. package/dist/services/ps-scan-camera-service.d.ts +4 -0
  118. package/dist/services/ps-scan-camera-service.js +52 -7
  119. package/dist/services/ps-scan-canny-service.d.ts +7 -0
  120. package/dist/services/ps-scan-canny-service.js +97 -0
  121. package/dist/services/ps-scan-canvas-service.d.ts +1 -0
  122. package/dist/services/ps-scan-canvas-service.js +13 -1
  123. package/dist/services/ps-scan-edge-service.d.ts +5 -5
  124. package/dist/services/ps-scan-edge-service.js +73 -137
  125. package/dist/services/ps-scan-file-name-service.d.ts +1 -3
  126. package/dist/services/ps-scan-file-name-service.js +2 -11
  127. package/dist/services/ps-scan-geometry-service.d.ts +4 -0
  128. package/dist/services/ps-scan-geometry-service.js +56 -0
  129. package/dist/services/ps-scan-hull-service.d.ts +0 -2
  130. package/dist/services/ps-scan-hull-service.js +0 -39
  131. package/dist/services/ps-scan-image-filter-service.d.ts +0 -1
  132. package/dist/services/ps-scan-image-filter-service.js +0 -21
  133. package/dist/services/ps-scan-morphology-service.d.ts +7 -0
  134. package/dist/services/ps-scan-morphology-service.js +48 -0
  135. package/dist/services/ps-scan-pipeline-service.d.ts +2 -5
  136. package/dist/services/ps-scan-pipeline-service.js +9 -19
  137. package/dist/services/ps-scan-platform-service.d.ts +4 -0
  138. package/dist/services/ps-scan-platform-service.js +19 -0
  139. package/dist/services/ps-scan-polygon-service.d.ts +8 -0
  140. package/dist/services/ps-scan-polygon-service.js +79 -0
  141. package/dist/services/ps-scan-region-service.d.ts +4 -0
  142. package/dist/services/ps-scan-region-service.js +56 -0
  143. package/dist/services/ps-scan-stability-service.d.ts +7 -0
  144. package/dist/services/ps-scan-stability-service.js +32 -0
  145. package/dist/services/ps-scan-warp-service.d.ts +1 -1
  146. package/dist/services/ps-scan-warp-service.js +8 -2
  147. package/dist/services/ps-scan-yield-service.d.ts +3 -0
  148. package/dist/services/ps-scan-yield-service.js +9 -0
  149. package/package.json +3 -3
  150. package/dist/components/ps-scan-page-list.d.ts +0 -12
  151. package/dist/components/ps-scan-page-list.js +0 -23
  152. package/dist/components/ps-scan-review.d.ts +0 -23
  153. package/dist/components/ps-scan-review.js +0 -20
  154. package/dist/constants/ps-scan-filter.d.ts +0 -7
  155. package/dist/constants/ps-scan-filter.js +0 -9
  156. package/dist/constants/ps-scan-output-format.d.ts +0 -5
  157. package/dist/constants/ps-scan-output-format.js +0 -7
  158. package/dist/esm/components/ps-scan-page-list.js +0 -19
  159. package/dist/esm/components/ps-scan-review.js +0 -16
  160. package/dist/esm/constants/ps-scan-filter.js +0 -6
  161. package/dist/esm/constants/ps-scan-output-format.js +0 -4
  162. package/dist/esm/hooks/use-ps-scan-pages.js +0 -62
  163. package/dist/esm/hooks/use-ps-scan-previews.js +0 -57
  164. package/dist/esm/services/ps-scan-enhance-service.js +0 -97
  165. package/dist/esm/services/ps-scan-order-service.js +0 -13
  166. package/dist/esm/services/ps-scan-output-service.js +0 -24
  167. package/dist/esm/services/ps-scan-pdf-service.js +0 -56
  168. package/dist/hooks/use-ps-scan-pages.d.ts +0 -12
  169. package/dist/hooks/use-ps-scan-pages.js +0 -66
  170. package/dist/hooks/use-ps-scan-previews.d.ts +0 -2
  171. package/dist/hooks/use-ps-scan-previews.js +0 -61
  172. package/dist/models/ps-scan-added-page.d.ts +0 -5
  173. package/dist/models/ps-scan-levels.d.ts +0 -4
  174. package/dist/models/ps-scan-output-options.d.ts +0 -7
  175. package/dist/models/ps-scan-page.d.ts +0 -12
  176. package/dist/models/ps-scan-pdf-page.d.ts +0 -5
  177. package/dist/services/ps-scan-enhance-service.d.ts +0 -9
  178. package/dist/services/ps-scan-enhance-service.js +0 -101
  179. package/dist/services/ps-scan-order-service.d.ts +0 -4
  180. package/dist/services/ps-scan-order-service.js +0 -17
  181. package/dist/services/ps-scan-output-service.d.ts +0 -5
  182. package/dist/services/ps-scan-output-service.js +0 -28
  183. package/dist/services/ps-scan-pdf-service.d.ts +0 -5
  184. package/dist/services/ps-scan-pdf-service.js +0 -60
  185. /package/dist/esm/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  186. /package/dist/esm/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  187. /package/dist/esm/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  188. /package/dist/esm/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  189. /package/dist/esm/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
  190. /package/dist/models/{ps-scan-added-page.js → ps-file-picker-scan.js} +0 -0
  191. /package/dist/models/{ps-scan-levels.js → ps-scan-gradient.js} +0 -0
  192. /package/dist/models/{ps-scan-output-options.js → ps-scan-live-detection.js} +0 -0
  193. /package/dist/models/{ps-scan-page.js → ps-scan-located.js} +0 -0
  194. /package/dist/models/{ps-scan-pdf-page.js → ps-scan-photo.js} +0 -0
@@ -1,25 +1,4 @@
1
- import { PsScanConfig } from '../constants/ps-scan-config';
2
- import { PsScanGeometryService } from './ps-scan-geometry-service';
3
1
  export class PsScanHullService {
4
- static bestQuad(hull) {
5
- const points = PsScanHullService.reduce(hull, PsScanConfig.hullCandidateLimit);
6
- if (points.length <= 4)
7
- return points;
8
- let best = points.slice(0, 4);
9
- let bestArea = 0;
10
- for (let a = 0; a < points.length - 3; a += 1)
11
- for (let b = a + 1; b < points.length - 2; b += 1)
12
- for (let c = b + 1; c < points.length - 1; c += 1)
13
- for (let d = c + 1; d < points.length; d += 1) {
14
- const candidate = [points[a], points[b], points[c], points[d]];
15
- const area = PsScanGeometryService.area(candidate);
16
- if (area > bestArea) {
17
- bestArea = area;
18
- best = candidate;
19
- }
20
- }
21
- return best;
22
- }
23
2
  static convexHull(input) {
24
3
  const points = [...input].sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
25
4
  if (points.length < 3)
@@ -38,22 +17,4 @@ export class PsScanHullService {
38
17
  };
39
18
  return [...build(points), ...build([...points].reverse())];
40
19
  }
41
- static reduce(hull, limit) {
42
- const points = [...hull];
43
- while (points.length > limit) {
44
- let weakest = 0;
45
- let weakestArea = Infinity;
46
- points.forEach((point, index) => {
47
- const previous = points[(index - 1 + points.length) % points.length];
48
- const next = points[(index + 1) % points.length];
49
- const area = PsScanGeometryService.area([previous, point, next]);
50
- if (area < weakestArea) {
51
- weakestArea = area;
52
- weakest = index;
53
- }
54
- });
55
- points.splice(weakest, 1);
56
- }
57
- return points;
58
- }
59
20
  }
@@ -28,25 +28,4 @@ export class PsScanImageFilterService {
28
28
  }
29
29
  return out;
30
30
  }
31
- static sobel(gray, width, height) {
32
- const out = new Uint16Array(width * height);
33
- for (let y = 1; y < height - 1; y += 1)
34
- for (let x = 1; x < width - 1; x += 1) {
35
- const i = y * width + x;
36
- const gx = -gray[i - width - 1] +
37
- gray[i - width + 1] -
38
- 2 * gray[i - 1] +
39
- 2 * gray[i + 1] -
40
- gray[i + width - 1] +
41
- gray[i + width + 1];
42
- const gy = -gray[i - width - 1] -
43
- 2 * gray[i - width] -
44
- gray[i - width + 1] +
45
- gray[i + width - 1] +
46
- 2 * gray[i + width] +
47
- gray[i + width + 1];
48
- out[i] = Math.hypot(gx, gy) / 4;
49
- }
50
- return out;
51
- }
52
31
  }
@@ -0,0 +1,44 @@
1
+ export class PsScanMorphologyService {
2
+ static close(mask, width, height, radius) {
3
+ return PsScanMorphologyService.erode(PsScanMorphologyService.dilate(mask, width, height, radius), width, height, radius);
4
+ }
5
+ static dilate(mask, width, height, radius) {
6
+ return PsScanMorphologyService.vertical(PsScanMorphologyService.horizontal(mask, width, height, radius, 1), width, height, radius, 1);
7
+ }
8
+ static erode(mask, width, height, radius) {
9
+ return PsScanMorphologyService.vertical(PsScanMorphologyService.horizontal(mask, width, height, radius, 0), width, height, radius, 0);
10
+ }
11
+ static horizontal(mask, width, height, radius, target) {
12
+ const out = new Uint8Array(mask.length);
13
+ for (let y = 0; y < height; y += 1)
14
+ for (let x = 0; x < width; x += 1) {
15
+ const from = Math.max(0, x - radius);
16
+ const to = Math.min(width - 1, x + radius);
17
+ let hit = target === 1 ? 0 : 1;
18
+ for (let at = from; at <= to; at += 1)
19
+ if (mask[y * width + at] === target) {
20
+ hit = target;
21
+ break;
22
+ }
23
+ out[y * width + x] = hit;
24
+ }
25
+ return out;
26
+ }
27
+ static vertical(mask, width, height, radius, target) {
28
+ const out = new Uint8Array(mask.length);
29
+ for (let y = 0; y < height; y += 1) {
30
+ const from = Math.max(0, y - radius);
31
+ const to = Math.min(height - 1, y + radius);
32
+ for (let x = 0; x < width; x += 1) {
33
+ let hit = target === 1 ? 0 : 1;
34
+ for (let at = from; at <= to; at += 1)
35
+ if (mask[at * width + x] === target) {
36
+ hit = target;
37
+ break;
38
+ }
39
+ out[y * width + x] = hit;
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ }
@@ -1,36 +1,26 @@
1
1
  import { PsScanConfig } from '../constants/ps-scan-config';
2
2
  import { PsScanCanvasService } from './ps-scan-canvas-service';
3
3
  import { PsScanEdgeService } from './ps-scan-edge-service';
4
- import { PsScanEnhanceService } from './ps-scan-enhance-service';
5
4
  import { PsScanGeometryService } from './ps-scan-geometry-service';
6
- import { PsScanRasterService } from './ps-scan-raster-service';
7
5
  import { PsScanWarpService } from './ps-scan-warp-service';
8
6
  export class PsScanPipelineService {
9
7
  static async prepare(source) {
10
8
  const decoded = await PsScanCanvasService.decode(source, PsScanConfig.detectMaxDimension * 2);
11
- const detected = PsScanEdgeService.detect(decoded.raster);
9
+ const located = await PsScanEdgeService.locate(decoded.raster, PsScanConfig.detectMaxDimension);
12
10
  return {
13
- corners: detected === null ? PsScanGeometryService.defaultQuad() : detected,
11
+ corners: located.quad,
14
12
  height: decoded.naturalHeight,
15
- isDetected: detected !== null,
13
+ isDetected: located.isDetected,
16
14
  width: decoded.naturalWidth,
17
15
  };
18
16
  }
19
- static async measure(source) {
20
- const decoded = await PsScanCanvasService.decode(source, PsScanConfig.previewMaxDimension);
21
- return decoded.raster;
22
- }
23
- static process(page, raster, maxDimension) {
24
- const size = PsScanGeometryService.fitWithin(PsScanGeometryService.outputSize(page.corners, raster), maxDimension);
25
- const warped = PsScanWarpService.warp(raster, PsScanGeometryService.toPixels(page.corners, {
26
- height: raster.height - 1,
27
- width: raster.width - 1,
17
+ static async render(source, corners, maxDimension, quality) {
18
+ const decoded = await PsScanCanvasService.decode(source, Number.MAX_SAFE_INTEGER);
19
+ const size = PsScanGeometryService.fitWithin(PsScanGeometryService.outputSize(corners, decoded.raster), maxDimension);
20
+ const raster = await PsScanWarpService.warp(decoded.raster, PsScanGeometryService.toPixels(corners, {
21
+ height: decoded.raster.height - 1,
22
+ width: decoded.raster.width - 1,
28
23
  }), size.width, size.height);
29
- return PsScanRasterService.rotate(PsScanEnhanceService.apply(warped, page.filter), page.rotation);
30
- }
31
- static async render(page, maxDimension, quality) {
32
- const decoded = await PsScanCanvasService.decode(page.source, PsScanConfig.decodeMaxDimension);
33
- const raster = PsScanPipelineService.process(page, decoded.raster, maxDimension);
34
24
  return {
35
25
  blob: await PsScanCanvasService.encode(raster, quality),
36
26
  height: raster.height,
@@ -0,0 +1,15 @@
1
+ import { PsScanPlatform } from '../constants/ps-scan-platform';
2
+ export class PsScanPlatformService {
3
+ static detect() {
4
+ if (typeof navigator === 'undefined')
5
+ return PsScanPlatform.Desktop;
6
+ const agent = navigator.userAgent;
7
+ if (/iPhone|iPad|iPod/i.test(agent))
8
+ return PsScanPlatform.Ios;
9
+ if (/Macintosh/i.test(agent) && navigator.maxTouchPoints > 1)
10
+ return PsScanPlatform.Ios;
11
+ if (/Android/i.test(agent))
12
+ return PsScanPlatform.Android;
13
+ return PsScanPlatform.Desktop;
14
+ }
15
+ }
@@ -0,0 +1,75 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
2
+ import { PsScanGeometryService } from './ps-scan-geometry-service';
3
+ export class PsScanPolygonService {
4
+ static perimeter(points) {
5
+ return points.reduce((sum, point, index) => sum + PsScanGeometryService.distance(point, points[(index + 1) % points.length]), 0);
6
+ }
7
+ static simplify(points, epsilon) {
8
+ if (points.length < 4)
9
+ return points;
10
+ let first = 0;
11
+ let second = 0;
12
+ let best = -1;
13
+ points.forEach((point, index) => {
14
+ const length = PsScanGeometryService.distance(points[0], point);
15
+ if (length > best) {
16
+ best = length;
17
+ second = index;
18
+ }
19
+ });
20
+ best = -1;
21
+ points.forEach((point, index) => {
22
+ const length = PsScanGeometryService.distance(points[second], point);
23
+ if (length > best) {
24
+ best = length;
25
+ first = index;
26
+ }
27
+ });
28
+ const low = Math.min(first, second);
29
+ const high = Math.max(first, second);
30
+ const forward = points.slice(low, high + 1);
31
+ const backward = [...points.slice(high), ...points.slice(0, low + 1)];
32
+ const left = PsScanPolygonService.chain(forward, epsilon);
33
+ const right = PsScanPolygonService.chain(backward, epsilon);
34
+ return [...left.slice(0, -1), ...right.slice(0, -1)];
35
+ }
36
+ static toQuad(hull) {
37
+ const perimeter = PsScanPolygonService.perimeter(hull);
38
+ for (const ratio of PsScanConfig.polygonEpsilons) {
39
+ const simple = PsScanPolygonService.simplify(hull, ratio * perimeter);
40
+ if (simple.length === 4)
41
+ return simple;
42
+ if (simple.length < 4)
43
+ return null;
44
+ }
45
+ return null;
46
+ }
47
+ static chain(points, epsilon) {
48
+ if (points.length < 3)
49
+ return points;
50
+ const start = points[0];
51
+ const end = points[points.length - 1];
52
+ let far = 0;
53
+ let farDistance = -1;
54
+ for (let index = 1; index < points.length - 1; index += 1) {
55
+ const length = PsScanPolygonService.lineDistance(points[index], start, end);
56
+ if (length > farDistance) {
57
+ farDistance = length;
58
+ far = index;
59
+ }
60
+ }
61
+ if (farDistance <= epsilon)
62
+ return [start, end];
63
+ const left = PsScanPolygonService.chain(points.slice(0, far + 1), epsilon);
64
+ const right = PsScanPolygonService.chain(points.slice(far), epsilon);
65
+ return [...left.slice(0, -1), ...right];
66
+ }
67
+ static lineDistance(point, from, to) {
68
+ const dx = to.x - from.x;
69
+ const dy = to.y - from.y;
70
+ const length = Math.hypot(dx, dy);
71
+ if (length === 0)
72
+ return PsScanGeometryService.distance(point, from);
73
+ return Math.abs(dy * (point.x - from.x) - dx * (point.y - from.y)) / length;
74
+ }
75
+ }
@@ -0,0 +1,52 @@
1
+ export class PsScanRegionService {
2
+ static enclosed(mask, width, height, minArea) {
3
+ const seen = new Uint8Array(mask.length);
4
+ const regions = [];
5
+ for (let start = 0; start < mask.length; start += 1) {
6
+ if (mask[start] === 1 || seen[start] === 1)
7
+ continue;
8
+ const boundary = [];
9
+ const stack = [start];
10
+ let area = 0;
11
+ seen[start] = 1;
12
+ while (stack.length > 0) {
13
+ const index = stack.pop();
14
+ if (index === undefined)
15
+ break;
16
+ const x = index % width;
17
+ const y = (index - x) / width;
18
+ let isBoundary = false;
19
+ area += 1;
20
+ const visit = (next) => {
21
+ if (mask[next] === 1)
22
+ isBoundary = true;
23
+ else if (seen[next] === 0) {
24
+ seen[next] = 1;
25
+ stack.push(next);
26
+ }
27
+ };
28
+ if (x > 0)
29
+ visit(index - 1);
30
+ else
31
+ isBoundary = true;
32
+ if (x < width - 1)
33
+ visit(index + 1);
34
+ else
35
+ isBoundary = true;
36
+ if (y > 0)
37
+ visit(index - width);
38
+ else
39
+ isBoundary = true;
40
+ if (y < height - 1)
41
+ visit(index + width);
42
+ else
43
+ isBoundary = true;
44
+ if (isBoundary)
45
+ boundary.push({ x, y });
46
+ }
47
+ if (area >= minArea)
48
+ regions.push({ area, boundary });
49
+ }
50
+ return regions;
51
+ }
52
+ }
@@ -0,0 +1,28 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
2
+ import { PsScanGeometryService } from './ps-scan-geometry-service';
3
+ export class PsScanStabilityService {
4
+ static initial() {
5
+ return { misses: 0, quad: null };
6
+ }
7
+ static movement(from, to) {
8
+ return Math.max(...from.map((point, index) => PsScanGeometryService.distance(point, to[index])));
9
+ }
10
+ static next(state, detected) {
11
+ if (detected === null) {
12
+ const misses = state.misses + 1;
13
+ return misses > PsScanConfig.liveMissLimit
14
+ ? { misses, quad: null }
15
+ : { misses, quad: state.quad };
16
+ }
17
+ if (state.quad === null)
18
+ return { misses: 0, quad: detected };
19
+ if (PsScanStabilityService.movement(state.quad, detected) < PsScanConfig.liveDeadzone)
20
+ return { misses: 0, quad: state.quad };
21
+ const weight = PsScanConfig.liveSmoothing;
22
+ const quad = state.quad.map((point, index) => ({
23
+ x: point.x + (detected[index].x - point.x) * weight,
24
+ y: point.y + (detected[index].y - point.y) * weight,
25
+ }));
26
+ return { misses: 0, quad };
27
+ }
28
+ }
@@ -1,16 +1,22 @@
1
+ import { PsScanConfig } from '../constants/ps-scan-config';
1
2
  import { PsScanHomographyService } from './ps-scan-homography-service';
2
3
  import { PsScanRasterService } from './ps-scan-raster-service';
4
+ import { PsScanYieldService } from './ps-scan-yield-service';
3
5
  export class PsScanWarpService {
4
- static warp(source, pixelQuad, width, height) {
6
+ static async warp(source, pixelQuad, width, height) {
5
7
  const matrix = PsScanHomographyService.fromUnitSquare(pixelQuad);
6
8
  const target = PsScanRasterService.create(width, height);
7
9
  const spanX = Math.max(1, width - 1);
8
10
  const spanY = Math.max(1, height - 1);
9
- for (let y = 0; y < height; y += 1)
11
+ const rowsPerChunk = Math.max(1, Math.floor(PsScanConfig.warpChunkPixels / width));
12
+ for (let y = 0; y < height; y += 1) {
10
13
  for (let x = 0; x < width; x += 1) {
11
14
  const point = PsScanHomographyService.apply(matrix, x / spanX, y / spanY);
12
15
  PsScanWarpService.sample(source, point.x, point.y, target.data, (y * width + x) * 4);
13
16
  }
17
+ if ((y + 1) % rowsPerChunk === 0)
18
+ await PsScanYieldService.next();
19
+ }
14
20
  return target;
15
21
  }
16
22
  static sample(source, x, y, out, at) {
@@ -0,0 +1,5 @@
1
+ export class PsScanYieldService {
2
+ static async next() {
3
+ await new Promise((resolve) => setTimeout(resolve, 0));
4
+ }
5
+ }
@@ -1,9 +1,11 @@
1
1
  import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
2
2
  export declare const usePsScanCamera: (isActive: boolean) => {
3
+ allow: () => void;
4
+ attachVideo: (element: HTMLVideoElement | null) => void;
3
5
  canTorch: boolean;
4
6
  capture: () => Promise<Blob | null>;
5
7
  isTorchOn: boolean;
6
8
  status: PsScanCameraStatus;
7
9
  toggleTorch: () => Promise<void>;
8
- videoRef: import("react").RefObject<HTMLVideoElement>;
10
+ videoRef: import("react").MutableRefObject<HTMLVideoElement | null>;
9
11
  };
@@ -9,18 +9,40 @@ const ps_scan_canvas_service_1 = require("../services/ps-scan-canvas-service");
9
9
  const usePsScanCamera = (isActive) => {
10
10
  const videoRef = (0, react_1.useRef)(null);
11
11
  const sessionRef = (0, react_1.useRef)(null);
12
- const [status, setStatus] = (0, react_1.useState)(ps_scan_camera_status_1.PsScanCameraStatus.Starting);
12
+ const [status, setStatus] = (0, react_1.useState)(ps_scan_camera_status_1.PsScanCameraStatus.Checking);
13
+ const [request, setRequest] = (0, react_1.useState)(0);
13
14
  const [canTorch, setCanTorch] = (0, react_1.useState)(false);
14
15
  const [isTorchOn, setIsTorchOn] = (0, react_1.useState)(false);
15
16
  (0, react_1.useEffect)(() => {
16
- if (!isActive)
17
+ let isCancelled = false;
18
+ let permission = null;
19
+ const onChange = () => {
20
+ if (permission !== null && permission.state === 'granted')
21
+ setRequest((current) => current + 1);
22
+ };
23
+ ps_scan_camera_service_1.PsScanCameraService.check().then(async (next) => {
24
+ if (isCancelled)
25
+ return;
26
+ if (next === ps_scan_camera_status_1.PsScanCameraStatus.Starting)
27
+ setRequest(1);
28
+ else
29
+ setStatus(next);
30
+ permission = await ps_scan_camera_service_1.PsScanCameraService.permission();
31
+ if (isCancelled || permission === null)
32
+ return;
33
+ permission.addEventListener('change', onChange);
34
+ });
35
+ return () => {
36
+ isCancelled = true;
37
+ if (permission !== null)
38
+ permission.removeEventListener('change', onChange);
39
+ };
40
+ }, []);
41
+ (0, react_1.useEffect)(() => {
42
+ if (!isActive || request === 0)
17
43
  return;
18
44
  let isCancelled = false;
19
45
  setIsTorchOn(false);
20
- if (!ps_scan_camera_service_1.PsScanCameraService.isSupported()) {
21
- setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Unavailable);
22
- return;
23
- }
24
46
  setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Starting);
25
47
  ps_scan_camera_service_1.PsScanCameraService.open()
26
48
  .then(async (session) => {
@@ -30,12 +52,10 @@ const usePsScanCamera = (isActive) => {
30
52
  }
31
53
  sessionRef.current = session;
32
54
  setCanTorch(session.canTorch);
33
- const video = videoRef.current;
34
- if (video !== null) {
35
- video.srcObject = session.stream;
36
- await video.play().catch(() => undefined);
37
- }
38
- setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Ready);
55
+ if (videoRef.current !== null)
56
+ await ps_scan_camera_service_1.PsScanCameraService.attach(videoRef.current, session);
57
+ if (!isCancelled)
58
+ setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Ready);
39
59
  })
40
60
  .catch((error) => {
41
61
  if (!isCancelled)
@@ -48,7 +68,13 @@ const usePsScanCamera = (isActive) => {
48
68
  if (videoRef.current !== null)
49
69
  videoRef.current.srcObject = null;
50
70
  };
51
- }, [isActive]);
71
+ }, [isActive, request]);
72
+ const attachVideo = (0, react_1.useCallback)((element) => {
73
+ videoRef.current = element;
74
+ if (element !== null && sessionRef.current !== null)
75
+ void ps_scan_camera_service_1.PsScanCameraService.attach(element, sessionRef.current);
76
+ }, []);
77
+ const allow = (0, react_1.useCallback)(() => setRequest((current) => current + 1), []);
52
78
  const toggleTorch = (0, react_1.useCallback)(async () => {
53
79
  const session = sessionRef.current;
54
80
  if (session === null)
@@ -57,10 +83,21 @@ const usePsScanCamera = (isActive) => {
57
83
  }, [isTorchOn]);
58
84
  const capture = (0, react_1.useCallback)(async () => {
59
85
  const video = videoRef.current;
60
- if (video === null || video.videoWidth === 0)
86
+ if (video === null ||
87
+ video.videoWidth === 0 ||
88
+ video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA)
61
89
  return null;
62
90
  return await ps_scan_canvas_service_1.PsScanCanvasService.fromVideo(video, ps_scan_config_1.PsScanConfig.capturedJpegQuality);
63
91
  }, []);
64
- return { canTorch, capture, isTorchOn, status, toggleTorch, videoRef };
92
+ return {
93
+ allow,
94
+ attachVideo,
95
+ canTorch,
96
+ capture,
97
+ isTorchOn,
98
+ status,
99
+ toggleTorch,
100
+ videoRef,
101
+ };
65
102
  };
66
103
  exports.usePsScanCamera = usePsScanCamera;
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from 'react';
2
+ import type { PsScanLiveDetection } from '../models/ps-scan-live-detection';
3
+ export declare const usePsScanLiveDetection: (videoRef: RefObject<HTMLVideoElement | null>, isActive: boolean) => PsScanLiveDetection;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsScanLiveDetection = void 0;
4
+ const react_1 = require("react");
5
+ const ps_scan_config_1 = require("../constants/ps-scan-config");
6
+ const ps_scan_canvas_service_1 = require("../services/ps-scan-canvas-service");
7
+ const ps_scan_edge_service_1 = require("../services/ps-scan-edge-service");
8
+ const ps_scan_stability_service_1 = require("../services/ps-scan-stability-service");
9
+ const usePsScanLiveDetection = (videoRef, isActive) => {
10
+ const [detection, setDetection] = (0, react_1.useState)({ quad: null });
11
+ (0, react_1.useEffect)(() => {
12
+ if (!isActive) {
13
+ setDetection({ quad: null });
14
+ return;
15
+ }
16
+ let isCancelled = false;
17
+ let state = ps_scan_stability_service_1.PsScanStabilityService.initial();
18
+ let timer;
19
+ const tick = async () => {
20
+ 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 }));
33
+ }
34
+ if (!isCancelled)
35
+ timer = setTimeout(tick, ps_scan_config_1.PsScanConfig.liveDetectIntervalMs);
36
+ };
37
+ timer = setTimeout(tick, ps_scan_config_1.PsScanConfig.liveDetectIntervalMs);
38
+ return () => {
39
+ isCancelled = true;
40
+ clearTimeout(timer);
41
+ };
42
+ }, [isActive]);
43
+ return detection;
44
+ };
45
+ exports.usePsScanLiveDetection = usePsScanLiveDetection;
@@ -0,0 +1,9 @@
1
+ import type { PsScanPhoto } from '../models/ps-scan-photo';
2
+ import type { PsScanQuad } from '../models/ps-scan-quad';
3
+ export declare const usePsScanPhoto: () => {
4
+ clear: () => void;
5
+ detectAgain: () => Promise<boolean>;
6
+ load: (source: Blob) => Promise<PsScanPhoto>;
7
+ photo: PsScanPhoto | null;
8
+ setCorners: (corners: PsScanQuad) => void;
9
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsScanPhoto = void 0;
4
+ const react_1 = require("react");
5
+ const ps_scan_pipeline_service_1 = require("../services/ps-scan-pipeline-service");
6
+ const usePsScanPhoto = () => {
7
+ const [photo, setPhoto] = (0, react_1.useState)(null);
8
+ const url = (0, react_1.useRef)('');
9
+ const release = (0, react_1.useCallback)(() => {
10
+ if (url.current !== '')
11
+ URL.revokeObjectURL(url.current);
12
+ url.current = '';
13
+ }, []);
14
+ (0, react_1.useEffect)(() => release, [release]);
15
+ const load = (0, react_1.useCallback)(async (source) => {
16
+ const prepared = await ps_scan_pipeline_service_1.PsScanPipelineService.prepare(source);
17
+ const next = {
18
+ corners: prepared.corners,
19
+ height: prepared.height,
20
+ isDetected: prepared.isDetected,
21
+ source,
22
+ sourceUrl: URL.createObjectURL(source),
23
+ width: prepared.width,
24
+ };
25
+ release();
26
+ url.current = next.sourceUrl;
27
+ setPhoto(next);
28
+ return next;
29
+ }, [release]);
30
+ const setCorners = (0, react_1.useCallback)((corners) => setPhoto((current) => (current === null ? current : { ...current, corners })), []);
31
+ const detectAgain = (0, react_1.useCallback)(async () => {
32
+ if (photo === null)
33
+ return false;
34
+ const prepared = await ps_scan_pipeline_service_1.PsScanPipelineService.prepare(photo.source);
35
+ setPhoto((current) => current === null
36
+ ? current
37
+ : { ...current, corners: prepared.corners, isDetected: prepared.isDetected });
38
+ return prepared.isDetected;
39
+ }, [photo]);
40
+ const clear = (0, react_1.useCallback)(() => {
41
+ release();
42
+ setPhoto(null);
43
+ }, [release]);
44
+ return { clear, detectAgain, load, photo, setCorners };
45
+ };
46
+ exports.usePsScanPhoto = usePsScanPhoto;
package/dist/index.d.ts CHANGED
@@ -71,6 +71,5 @@ export * from './hooks/use-ps-in-view';
71
71
  export * from './hooks/use-ps-upload-queue';
72
72
  export * from './components/ps-document-scanner';
73
73
  export * from './constants/ps-document-scanner-labels';
74
- export * from './constants/ps-scan-filter';
75
- export * from './constants/ps-scan-output-format';
76
74
  export * from './models/ps-document-scanner-props';
75
+ export * from './models/ps-file-picker-scan';
package/dist/index.js CHANGED
@@ -87,6 +87,5 @@ __exportStar(require("./hooks/use-ps-in-view"), exports);
87
87
  __exportStar(require("./hooks/use-ps-upload-queue"), exports);
88
88
  __exportStar(require("./components/ps-document-scanner"), exports);
89
89
  __exportStar(require("./constants/ps-document-scanner-labels"), exports);
90
- __exportStar(require("./constants/ps-scan-filter"), exports);
91
- __exportStar(require("./constants/ps-scan-output-format"), exports);
92
90
  __exportStar(require("./models/ps-document-scanner-props"), exports);
91
+ __exportStar(require("./models/ps-file-picker-scan"), exports);
@@ -1,5 +1,4 @@
1
1
  import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
2
- import type { PsScanOutputFormat } from '../constants/ps-scan-output-format';
3
2
  import type { PsLabelSet } from './ps-label-set';
4
3
  export interface PsDocumentScannerProps {
5
4
  className?: string;
@@ -8,8 +7,6 @@ export interface PsDocumentScannerProps {
8
7
  jpegQuality?: number;
9
8
  labels?: Partial<PsLabelSet<typeof PsDocumentScannerLabels>>;
10
9
  maxDimension?: number;
11
- maxPages?: number;
12
10
  onClose: () => void;
13
11
  onComplete: (files: File[]) => void;
14
- outputFormat?: PsScanOutputFormat;
15
12
  }
@@ -1,13 +1,10 @@
1
1
  import type { PsDocumentScannerLabels } from '../constants/ps-document-scanner-labels';
2
- import type { PsScanOutputFormat } from '../constants/ps-scan-output-format';
3
2
  import type { PsLabelSet } from './ps-label-set';
4
3
  export interface PsDocumentScannerSheetProps {
5
4
  fileName: string;
6
5
  jpegQuality: number;
7
6
  labels: PsLabelSet<typeof PsDocumentScannerLabels>;
8
7
  maxDimension: number;
9
- maxPages: number;
10
8
  onClose: () => void;
11
9
  onComplete: (files: File[]) => void;
12
- outputFormat: PsScanOutputFormat;
13
10
  }
@@ -0,0 +1,4 @@
1
+ import type { PsDocumentScannerProps } from './ps-document-scanner-props';
2
+ export interface PsFilePickerScan extends Omit<PsDocumentScannerProps, 'fileName' | 'isOpen' | 'onClose' | 'onComplete'> {
3
+ getFileName?: () => string;
4
+ }
@@ -0,0 +1,4 @@
1
+ export interface PsScanGradient {
2
+ direction: Uint8Array;
3
+ magnitude: Float32Array;
4
+ }