@pallasoft/ps-file-components 1.3.0 → 1.4.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.
- package/dist/components/ps-document-scanner-loader.d.ts +1 -0
- package/dist/components/ps-document-scanner-loader.js +40 -0
- package/dist/components/ps-document-scanner-sheet.d.ts +2 -0
- package/dist/components/ps-document-scanner-sheet.js +138 -0
- package/dist/components/ps-document-scanner.d.ts +2 -0
- package/dist/components/ps-document-scanner.js +17 -0
- package/dist/components/ps-file-viewer-pane.d.ts +2 -0
- package/dist/components/ps-file-viewer-pane.js +39 -0
- package/dist/components/ps-scan-camera-view.d.ts +17 -0
- package/dist/components/ps-scan-camera-view.js +27 -0
- package/dist/components/ps-scan-crop-editor.d.ts +13 -0
- package/dist/components/ps-scan-crop-editor.js +53 -0
- package/dist/components/ps-scan-icon-button.d.ts +10 -0
- package/dist/components/ps-scan-icon-button.js +7 -0
- package/dist/components/ps-scan-page-list.d.ts +12 -0
- package/dist/components/ps-scan-page-list.js +23 -0
- package/dist/components/ps-scan-review.d.ts +23 -0
- package/dist/components/ps-scan-review.js +20 -0
- package/dist/constants/ps-document-scanner-labels.d.ts +50 -0
- package/dist/constants/ps-document-scanner-labels.js +53 -0
- package/dist/constants/ps-scan-camera-status.d.ts +7 -0
- package/dist/constants/ps-scan-camera-status.js +9 -0
- package/dist/constants/ps-scan-config.d.ts +31 -0
- package/dist/constants/ps-scan-config.js +34 -0
- package/dist/constants/ps-scan-corner-order.d.ts +1 -0
- package/dist/constants/ps-scan-corner-order.js +4 -0
- package/dist/constants/ps-scan-filter.d.ts +7 -0
- package/dist/constants/ps-scan-filter.js +9 -0
- package/dist/constants/ps-scan-output-format.d.ts +5 -0
- package/dist/constants/ps-scan-output-format.js +7 -0
- package/dist/constants/ps-scan-step.d.ts +6 -0
- package/dist/constants/ps-scan-step.js +8 -0
- package/dist/esm/components/ps-document-scanner-loader.js +4 -0
- package/dist/esm/components/ps-document-scanner-sheet.js +134 -0
- package/dist/esm/components/ps-document-scanner.js +13 -0
- package/dist/esm/components/ps-file-viewer-pane.js +35 -0
- package/dist/esm/components/ps-scan-camera-view.js +23 -0
- package/dist/esm/components/ps-scan-crop-editor.js +49 -0
- package/dist/esm/components/ps-scan-icon-button.js +3 -0
- package/dist/esm/components/ps-scan-page-list.js +19 -0
- package/dist/esm/components/ps-scan-review.js +16 -0
- package/dist/esm/constants/ps-document-scanner-labels.js +50 -0
- package/dist/esm/constants/ps-scan-camera-status.js +6 -0
- package/dist/esm/constants/ps-scan-config.js +31 -0
- package/dist/esm/constants/ps-scan-corner-order.js +1 -0
- package/dist/esm/constants/ps-scan-filter.js +6 -0
- package/dist/esm/constants/ps-scan-output-format.js +4 -0
- package/dist/esm/constants/ps-scan-step.js +5 -0
- package/dist/esm/hooks/use-ps-focus-trap.js +45 -0
- package/dist/esm/hooks/use-ps-scan-camera.js +62 -0
- package/dist/esm/hooks/use-ps-scan-pages.js +62 -0
- package/dist/esm/hooks/use-ps-scan-previews.js +57 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/models/ps-document-scanner-props.js +1 -0
- package/dist/esm/models/ps-document-scanner-sheet-props.js +1 -0
- package/dist/esm/models/ps-file-viewer-pane-props.js +1 -0
- package/dist/esm/models/ps-scan-added-page.js +1 -0
- package/dist/esm/models/ps-scan-camera-session.js +1 -0
- package/dist/esm/models/ps-scan-decoded-image.js +1 -0
- package/dist/esm/models/ps-scan-levels.js +1 -0
- package/dist/esm/models/ps-scan-output-options.js +1 -0
- package/dist/esm/models/ps-scan-page.js +1 -0
- package/dist/esm/models/ps-scan-pdf-page.js +1 -0
- package/dist/esm/models/ps-scan-prepared.js +1 -0
- package/dist/esm/models/ps-scan-quad.js +1 -0
- package/dist/esm/models/ps-scan-raster.js +1 -0
- package/dist/esm/models/ps-scan-rendered-page.js +1 -0
- package/dist/esm/services/ps-scan-camera-service.js +50 -0
- package/dist/esm/services/ps-scan-canvas-service.js +46 -0
- package/dist/esm/services/ps-scan-edge-service.js +156 -0
- package/dist/esm/services/ps-scan-enhance-service.js +97 -0
- package/dist/esm/services/ps-scan-file-name-service.js +25 -0
- package/dist/esm/services/ps-scan-geometry-service.js +96 -0
- package/dist/esm/services/ps-scan-homography-service.js +46 -0
- package/dist/esm/services/ps-scan-hull-service.js +59 -0
- package/dist/esm/services/ps-scan-image-filter-service.js +52 -0
- package/dist/esm/services/ps-scan-order-service.js +13 -0
- package/dist/esm/services/ps-scan-output-service.js +24 -0
- package/dist/esm/services/ps-scan-pdf-service.js +56 -0
- package/dist/esm/services/ps-scan-pipeline-service.js +40 -0
- package/dist/esm/services/ps-scan-raster-service.js +67 -0
- package/dist/esm/services/ps-scan-template-service.js +5 -0
- package/dist/esm/services/ps-scan-warp-service.js +35 -0
- package/dist/hooks/use-ps-focus-trap.d.ts +2 -0
- package/dist/hooks/use-ps-focus-trap.js +49 -0
- package/dist/hooks/use-ps-scan-camera.d.ts +9 -0
- package/dist/hooks/use-ps-scan-camera.js +66 -0
- package/dist/hooks/use-ps-scan-pages.d.ts +12 -0
- package/dist/hooks/use-ps-scan-pages.js +66 -0
- package/dist/hooks/use-ps-scan-previews.d.ts +2 -0
- package/dist/hooks/use-ps-scan-previews.js +61 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/models/ps-document-scanner-props.d.ts +15 -0
- package/dist/models/ps-document-scanner-props.js +2 -0
- package/dist/models/ps-document-scanner-sheet-props.d.ts +13 -0
- package/dist/models/ps-document-scanner-sheet-props.js +2 -0
- package/dist/models/ps-file-viewer-pane-props.d.ts +12 -0
- package/dist/models/ps-file-viewer-pane-props.js +2 -0
- package/dist/models/ps-scan-added-page.d.ts +5 -0
- package/dist/models/ps-scan-added-page.js +2 -0
- package/dist/models/ps-scan-camera-session.d.ts +5 -0
- package/dist/models/ps-scan-camera-session.js +2 -0
- package/dist/models/ps-scan-decoded-image.d.ts +6 -0
- package/dist/models/ps-scan-decoded-image.js +2 -0
- package/dist/models/ps-scan-levels.d.ts +4 -0
- package/dist/models/ps-scan-levels.js +2 -0
- package/dist/models/ps-scan-output-options.d.ts +7 -0
- package/dist/models/ps-scan-output-options.js +2 -0
- package/dist/models/ps-scan-page.d.ts +12 -0
- package/dist/models/ps-scan-page.js +2 -0
- package/dist/models/ps-scan-pdf-page.d.ts +5 -0
- package/dist/models/ps-scan-pdf-page.js +2 -0
- package/dist/models/ps-scan-prepared.d.ts +7 -0
- package/dist/models/ps-scan-prepared.js +2 -0
- package/dist/models/ps-scan-quad.d.ts +2 -0
- package/dist/models/ps-scan-quad.js +2 -0
- package/dist/models/ps-scan-raster.d.ts +5 -0
- package/dist/models/ps-scan-raster.js +2 -0
- package/dist/models/ps-scan-rendered-page.d.ts +5 -0
- package/dist/models/ps-scan-rendered-page.js +2 -0
- package/dist/services/ps-scan-camera-service.d.ts +10 -0
- package/dist/services/ps-scan-camera-service.js +54 -0
- package/dist/services/ps-scan-canvas-service.d.ts +8 -0
- package/dist/services/ps-scan-canvas-service.js +50 -0
- package/dist/services/ps-scan-edge-service.d.ts +9 -0
- package/dist/services/ps-scan-edge-service.js +160 -0
- package/dist/services/ps-scan-enhance-service.d.ts +9 -0
- package/dist/services/ps-scan-enhance-service.js +101 -0
- package/dist/services/ps-scan-file-name-service.d.ts +6 -0
- package/dist/services/ps-scan-file-name-service.js +29 -0
- package/dist/services/ps-scan-geometry-service.d.ts +18 -0
- package/dist/services/ps-scan-geometry-service.js +100 -0
- package/dist/services/ps-scan-homography-service.d.ts +7 -0
- package/dist/services/ps-scan-homography-service.js +50 -0
- package/dist/services/ps-scan-hull-service.d.ts +6 -0
- package/dist/services/ps-scan-hull-service.js +63 -0
- package/dist/services/ps-scan-image-filter-service.d.ts +6 -0
- package/dist/services/ps-scan-image-filter-service.js +56 -0
- package/dist/services/ps-scan-order-service.d.ts +4 -0
- package/dist/services/ps-scan-order-service.js +17 -0
- package/dist/services/ps-scan-output-service.d.ts +5 -0
- package/dist/services/ps-scan-output-service.js +28 -0
- package/dist/services/ps-scan-pdf-service.d.ts +5 -0
- package/dist/services/ps-scan-pdf-service.js +60 -0
- package/dist/services/ps-scan-pipeline-service.d.ts +10 -0
- package/dist/services/ps-scan-pipeline-service.js +44 -0
- package/dist/services/ps-scan-raster-service.d.ts +6 -0
- package/dist/services/ps-scan-raster-service.js +71 -0
- package/dist/services/ps-scan-template-service.d.ts +3 -0
- package/dist/services/ps-scan-template-service.js +9 -0
- package/dist/services/ps-scan-warp-service.d.ts +6 -0
- package/dist/services/ps-scan-warp-service.js +39 -0
- package/package.json +9 -6
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
|
+
export class PsScanGeometryService {
|
|
3
|
+
static area(points) {
|
|
4
|
+
let sum = 0;
|
|
5
|
+
points.forEach((point, index) => {
|
|
6
|
+
const next = points[(index + 1) % points.length];
|
|
7
|
+
sum += point.x * next.y - next.x * point.y;
|
|
8
|
+
});
|
|
9
|
+
return Math.abs(sum) / 2;
|
|
10
|
+
}
|
|
11
|
+
static clamp(point) {
|
|
12
|
+
return { x: Math.min(1, Math.max(0, point.x)), y: Math.min(1, Math.max(0, point.y)) };
|
|
13
|
+
}
|
|
14
|
+
static defaultQuad() {
|
|
15
|
+
const inset = 0.08;
|
|
16
|
+
return [
|
|
17
|
+
{ x: inset, y: inset },
|
|
18
|
+
{ x: 1 - inset, y: inset },
|
|
19
|
+
{ x: 1 - inset, y: 1 - inset },
|
|
20
|
+
{ x: inset, y: 1 - inset },
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
static distance(from, to) {
|
|
24
|
+
return Math.hypot(to.x - from.x, to.y - from.y);
|
|
25
|
+
}
|
|
26
|
+
static fit(container, content) {
|
|
27
|
+
if (container.width <= 0 || container.height <= 0 || content.width <= 0 || content.height <= 0)
|
|
28
|
+
return { height: 0, width: 0 };
|
|
29
|
+
const scale = Math.min(container.width / content.width, container.height / content.height);
|
|
30
|
+
return { height: content.height * scale, width: content.width * scale };
|
|
31
|
+
}
|
|
32
|
+
static fitWithin(size, maxDimension) {
|
|
33
|
+
const longest = Math.max(size.width, size.height);
|
|
34
|
+
if (longest <= maxDimension)
|
|
35
|
+
return { height: Math.round(size.height), width: Math.round(size.width) };
|
|
36
|
+
const scale = maxDimension / longest;
|
|
37
|
+
return {
|
|
38
|
+
height: Math.max(1, Math.round(size.height * scale)),
|
|
39
|
+
width: Math.max(1, Math.round(size.width * scale)),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
static isConvex(points) {
|
|
43
|
+
let sign = 0;
|
|
44
|
+
for (let index = 0; index < points.length; index += 1) {
|
|
45
|
+
const a = points[index];
|
|
46
|
+
const b = points[(index + 1) % points.length];
|
|
47
|
+
const c = points[(index + 2) % points.length];
|
|
48
|
+
const cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
|
|
49
|
+
if (Math.abs(cross) < 1e-9)
|
|
50
|
+
return false;
|
|
51
|
+
if (sign === 0)
|
|
52
|
+
sign = Math.sign(cross);
|
|
53
|
+
else if (Math.sign(cross) !== sign)
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
static isUsable(quad) {
|
|
59
|
+
return (PsScanGeometryService.isConvex(quad) &&
|
|
60
|
+
PsScanGeometryService.area(quad) > PsScanConfig.minCornerGap * PsScanConfig.minCornerGap);
|
|
61
|
+
}
|
|
62
|
+
static nudge(quad, index, dx, dy) {
|
|
63
|
+
const moved = PsScanGeometryService.clamp({ x: quad[index].x + dx, y: quad[index].y + dy });
|
|
64
|
+
const next = quad.map((point, at) => (at === index ? moved : point));
|
|
65
|
+
return PsScanGeometryService.isUsable(next) ? next : quad;
|
|
66
|
+
}
|
|
67
|
+
static order(points) {
|
|
68
|
+
const centre = {
|
|
69
|
+
x: points.reduce((sum, point) => sum + point.x, 0) / points.length,
|
|
70
|
+
y: points.reduce((sum, point) => sum + point.y, 0) / points.length,
|
|
71
|
+
};
|
|
72
|
+
const sorted = [...points].sort((a, b) => Math.atan2(a.y - centre.y, a.x - centre.x) - Math.atan2(b.y - centre.y, b.x - centre.x));
|
|
73
|
+
const sums = sorted.map((point) => point.x + point.y);
|
|
74
|
+
const start = sums.indexOf(Math.min(...sums));
|
|
75
|
+
return [0, 1, 2, 3].map((offset) => sorted[(start + offset) % 4]);
|
|
76
|
+
}
|
|
77
|
+
static outputSize(quad, raster) {
|
|
78
|
+
const pixels = PsScanGeometryService.toPixels(quad, raster);
|
|
79
|
+
const width = (PsScanGeometryService.distance(pixels[0], pixels[1]) +
|
|
80
|
+
PsScanGeometryService.distance(pixels[3], pixels[2])) /
|
|
81
|
+
2;
|
|
82
|
+
const height = (PsScanGeometryService.distance(pixels[0], pixels[3]) +
|
|
83
|
+
PsScanGeometryService.distance(pixels[1], pixels[2])) /
|
|
84
|
+
2;
|
|
85
|
+
return { height: Math.max(1, Math.round(height)), width: Math.max(1, Math.round(width)) };
|
|
86
|
+
}
|
|
87
|
+
static rotateSize(size, rotation) {
|
|
88
|
+
return rotation % 180 === 0 ? size : { height: size.width, width: size.height };
|
|
89
|
+
}
|
|
90
|
+
static toPixels(quad, size) {
|
|
91
|
+
return quad.map((point) => ({
|
|
92
|
+
x: point.x * size.width,
|
|
93
|
+
y: point.y * size.height,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class PsScanHomographyService {
|
|
2
|
+
static apply(matrix, x, y) {
|
|
3
|
+
const w = matrix[6] * x + matrix[7] * y + matrix[8];
|
|
4
|
+
return {
|
|
5
|
+
x: (matrix[0] * x + matrix[1] * y + matrix[2]) / w,
|
|
6
|
+
y: (matrix[3] * x + matrix[4] * y + matrix[5]) / w,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
static fromUnitSquare(quad) {
|
|
10
|
+
const unit = [
|
|
11
|
+
[0, 0],
|
|
12
|
+
[1, 0],
|
|
13
|
+
[1, 1],
|
|
14
|
+
[0, 1],
|
|
15
|
+
];
|
|
16
|
+
const rows = [];
|
|
17
|
+
unit.forEach(([u, v], index) => {
|
|
18
|
+
const { x, y } = quad[index];
|
|
19
|
+
rows.push([u, v, 1, 0, 0, 0, -u * x, -v * x, x]);
|
|
20
|
+
rows.push([0, 0, 0, u, v, 1, -u * y, -v * y, y]);
|
|
21
|
+
});
|
|
22
|
+
return [...PsScanHomographyService.solve(rows), 1];
|
|
23
|
+
}
|
|
24
|
+
static solve(rows) {
|
|
25
|
+
const size = rows.length;
|
|
26
|
+
const matrix = rows.map((row) => [...row]);
|
|
27
|
+
for (let column = 0; column < size; column += 1) {
|
|
28
|
+
let pivot = column;
|
|
29
|
+
for (let row = column + 1; row < size; row += 1)
|
|
30
|
+
if (Math.abs(matrix[row][column]) > Math.abs(matrix[pivot][column]))
|
|
31
|
+
pivot = row;
|
|
32
|
+
[matrix[column], matrix[pivot]] = [matrix[pivot], matrix[column]];
|
|
33
|
+
const lead = matrix[column][column];
|
|
34
|
+
if (Math.abs(lead) < 1e-12)
|
|
35
|
+
return [1, 0, 0, 0, 1, 0, 0, 0];
|
|
36
|
+
for (let row = 0; row < size; row += 1) {
|
|
37
|
+
if (row === column)
|
|
38
|
+
continue;
|
|
39
|
+
const factor = matrix[row][column] / lead;
|
|
40
|
+
for (let at = column; at <= size; at += 1)
|
|
41
|
+
matrix[row][at] -= factor * matrix[column][at];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return matrix.map((row, index) => row[size] / row[index]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
|
+
import { PsScanGeometryService } from './ps-scan-geometry-service';
|
|
3
|
+
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
|
+
static convexHull(input) {
|
|
24
|
+
const points = [...input].sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
|
|
25
|
+
if (points.length < 3)
|
|
26
|
+
return points;
|
|
27
|
+
const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
28
|
+
const build = (list) => {
|
|
29
|
+
const chain = [];
|
|
30
|
+
list.forEach((point) => {
|
|
31
|
+
while (chain.length >= 2 &&
|
|
32
|
+
cross(chain[chain.length - 2], chain[chain.length - 1], point) <= 0)
|
|
33
|
+
chain.pop();
|
|
34
|
+
chain.push(point);
|
|
35
|
+
});
|
|
36
|
+
chain.pop();
|
|
37
|
+
return chain;
|
|
38
|
+
};
|
|
39
|
+
return [...build(points), ...build([...points].reverse())];
|
|
40
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class PsScanImageFilterService {
|
|
2
|
+
static blur(gray, width, height) {
|
|
3
|
+
const kernel = [1, 4, 6, 4, 1];
|
|
4
|
+
const pass = new Float32Array(width * height);
|
|
5
|
+
const out = new Uint8ClampedArray(width * height);
|
|
6
|
+
for (let y = 0; y < height; y += 1)
|
|
7
|
+
for (let x = 0; x < width; x += 1) {
|
|
8
|
+
let sum = 0;
|
|
9
|
+
for (let k = -2; k <= 2; k += 1)
|
|
10
|
+
sum += gray[y * width + Math.min(width - 1, Math.max(0, x + k))] * kernel[k + 2];
|
|
11
|
+
pass[y * width + x] = sum / 16;
|
|
12
|
+
}
|
|
13
|
+
for (let y = 0; y < height; y += 1)
|
|
14
|
+
for (let x = 0; x < width; x += 1) {
|
|
15
|
+
let sum = 0;
|
|
16
|
+
for (let k = -2; k <= 2; k += 1)
|
|
17
|
+
sum += pass[Math.min(height - 1, Math.max(0, y + k)) * width + x] * kernel[k + 2];
|
|
18
|
+
out[y * width + x] = sum / 16;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
static grayscale(raster) {
|
|
23
|
+
const out = new Uint8ClampedArray(raster.width * raster.height);
|
|
24
|
+
for (let index = 0; index < out.length; index += 1) {
|
|
25
|
+
const at = index * 4;
|
|
26
|
+
out[index] =
|
|
27
|
+
0.299 * raster.data[at] + 0.587 * raster.data[at + 1] + 0.114 * raster.data[at + 2];
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class PsScanOrderService {
|
|
2
|
+
static move(items, from, to) {
|
|
3
|
+
if (from === to || from < 0 || to < 0 || from >= items.length || to >= items.length)
|
|
4
|
+
return items;
|
|
5
|
+
const next = [...items];
|
|
6
|
+
const [item] = next.splice(from, 1);
|
|
7
|
+
next.splice(to, 0, item);
|
|
8
|
+
return next;
|
|
9
|
+
}
|
|
10
|
+
static step(items, index, direction) {
|
|
11
|
+
return PsScanOrderService.move(items, index, index + direction);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PsScanOutputFormat } from '../constants/ps-scan-output-format';
|
|
2
|
+
import { PsScanFileNameService } from './ps-scan-file-name-service';
|
|
3
|
+
import { PsScanPdfService } from './ps-scan-pdf-service';
|
|
4
|
+
import { PsScanPipelineService } from './ps-scan-pipeline-service';
|
|
5
|
+
export class PsScanOutputService {
|
|
6
|
+
static async build(pages, options) {
|
|
7
|
+
const rendered = [];
|
|
8
|
+
for (const page of pages)
|
|
9
|
+
rendered.push(await PsScanPipelineService.render(page, options.maxDimension, options.jpegQuality));
|
|
10
|
+
const type = PsScanFileNameService.mimeType(options.format);
|
|
11
|
+
if (options.format === PsScanOutputFormat.Jpeg)
|
|
12
|
+
return rendered.map((item, index) => new File([item.blob], PsScanFileNameService.build(options.fileName, options.format, index, rendered.length), { type }));
|
|
13
|
+
const jpegs = await Promise.all(rendered.map(async (item) => ({
|
|
14
|
+
height: item.height,
|
|
15
|
+
jpeg: new Uint8Array(await item.blob.arrayBuffer()),
|
|
16
|
+
width: item.width,
|
|
17
|
+
})));
|
|
18
|
+
return [
|
|
19
|
+
new File([PsScanPdfService.build(jpegs)], PsScanFileNameService.build(options.fileName, options.format, 0, 1), {
|
|
20
|
+
type,
|
|
21
|
+
}),
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
|
+
export class PsScanPdfService {
|
|
3
|
+
static build(pages) {
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
const chunks = [];
|
|
6
|
+
const offsets = [];
|
|
7
|
+
let length = 0;
|
|
8
|
+
const push = (chunk) => {
|
|
9
|
+
chunks.push(chunk);
|
|
10
|
+
length += chunk.length;
|
|
11
|
+
};
|
|
12
|
+
const text = (value) => push(encoder.encode(value));
|
|
13
|
+
const begin = (id) => {
|
|
14
|
+
offsets[id] = length;
|
|
15
|
+
text(`${id} 0 obj\n`);
|
|
16
|
+
};
|
|
17
|
+
const kids = pages.map((_, index) => `${3 + index * 3} 0 R`).join(' ');
|
|
18
|
+
push(Uint8Array.from([
|
|
19
|
+
0x25, 0x50, 0x44, 0x46, 0x2d, 0x31, 0x2e, 0x34, 0x0a, 0x25, 0xe2, 0xe3, 0xcf, 0xd3, 0x0a,
|
|
20
|
+
]));
|
|
21
|
+
begin(1);
|
|
22
|
+
text('<< /Type /Catalog /Pages 2 0 R >>\nendobj\n');
|
|
23
|
+
begin(2);
|
|
24
|
+
text(`<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>\nendobj\n`);
|
|
25
|
+
pages.forEach((page, index) => {
|
|
26
|
+
const pageId = 3 + index * 3;
|
|
27
|
+
const widthPt = PsScanPdfService.points(page.width);
|
|
28
|
+
const heightPt = PsScanPdfService.points(page.height);
|
|
29
|
+
const content = `q ${widthPt} 0 0 ${heightPt} 0 0 cm /Im0 Do Q`;
|
|
30
|
+
begin(pageId);
|
|
31
|
+
text(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${widthPt} ${heightPt}] /Resources << /XObject << /Im0 ${pageId + 2} 0 R >> >> /Contents ${pageId + 1} 0 R >>\nendobj\n`);
|
|
32
|
+
begin(pageId + 1);
|
|
33
|
+
text(`<< /Length ${content.length} >>\nstream\n${content}\nendstream\nendobj\n`);
|
|
34
|
+
begin(pageId + 2);
|
|
35
|
+
text(`<< /Type /XObject /Subtype /Image /Width ${page.width} /Height ${page.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${page.jpeg.length} >>\nstream\n`);
|
|
36
|
+
push(page.jpeg);
|
|
37
|
+
text('\nendstream\nendobj\n');
|
|
38
|
+
});
|
|
39
|
+
const count = 3 + pages.length * 3;
|
|
40
|
+
const xrefAt = length;
|
|
41
|
+
text(`xref\n0 ${count}\n0000000000 65535 f \n`);
|
|
42
|
+
for (let id = 1; id < count; id += 1)
|
|
43
|
+
text(`${String(offsets[id]).padStart(10, '0')} 00000 n \n`);
|
|
44
|
+
text(`trailer\n<< /Size ${count} /Root 1 0 R >>\nstartxref\n${xrefAt}\n%%EOF\n`);
|
|
45
|
+
const out = new Uint8Array(length);
|
|
46
|
+
let at = 0;
|
|
47
|
+
chunks.forEach((chunk) => {
|
|
48
|
+
out.set(chunk, at);
|
|
49
|
+
at += chunk.length;
|
|
50
|
+
});
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
static points(pixels) {
|
|
54
|
+
return Math.round((pixels * PsScanConfig.pdfPointsPerInch * 100) / PsScanConfig.pdfDpi) / 100;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PsScanConfig } from '../constants/ps-scan-config';
|
|
2
|
+
import { PsScanCanvasService } from './ps-scan-canvas-service';
|
|
3
|
+
import { PsScanEdgeService } from './ps-scan-edge-service';
|
|
4
|
+
import { PsScanEnhanceService } from './ps-scan-enhance-service';
|
|
5
|
+
import { PsScanGeometryService } from './ps-scan-geometry-service';
|
|
6
|
+
import { PsScanRasterService } from './ps-scan-raster-service';
|
|
7
|
+
import { PsScanWarpService } from './ps-scan-warp-service';
|
|
8
|
+
export class PsScanPipelineService {
|
|
9
|
+
static async prepare(source) {
|
|
10
|
+
const decoded = await PsScanCanvasService.decode(source, PsScanConfig.detectMaxDimension * 2);
|
|
11
|
+
const detected = PsScanEdgeService.detect(decoded.raster);
|
|
12
|
+
return {
|
|
13
|
+
corners: detected === null ? PsScanGeometryService.defaultQuad() : detected,
|
|
14
|
+
height: decoded.naturalHeight,
|
|
15
|
+
isDetected: detected !== null,
|
|
16
|
+
width: decoded.naturalWidth,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
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,
|
|
28
|
+
}), 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
|
+
return {
|
|
35
|
+
blob: await PsScanCanvasService.encode(raster, quality),
|
|
36
|
+
height: raster.height,
|
|
37
|
+
width: raster.width,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class PsScanRasterService {
|
|
2
|
+
static create(width, height) {
|
|
3
|
+
return { data: new Uint8ClampedArray(width * height * 4), height, width };
|
|
4
|
+
}
|
|
5
|
+
static resize(source, width, height) {
|
|
6
|
+
if (width === source.width && height === source.height)
|
|
7
|
+
return source;
|
|
8
|
+
const target = PsScanRasterService.create(width, height);
|
|
9
|
+
const scaleX = source.width / width;
|
|
10
|
+
const scaleY = source.height / height;
|
|
11
|
+
for (let y = 0; y < height; y += 1) {
|
|
12
|
+
const y0 = Math.floor(y * scaleY);
|
|
13
|
+
const y1 = Math.min(source.height, Math.max(y0 + 1, Math.ceil((y + 1) * scaleY)));
|
|
14
|
+
for (let x = 0; x < width; x += 1) {
|
|
15
|
+
const x0 = Math.floor(x * scaleX);
|
|
16
|
+
const x1 = Math.min(source.width, Math.max(x0 + 1, Math.ceil((x + 1) * scaleX)));
|
|
17
|
+
let r = 0;
|
|
18
|
+
let g = 0;
|
|
19
|
+
let b = 0;
|
|
20
|
+
let a = 0;
|
|
21
|
+
let count = 0;
|
|
22
|
+
for (let sy = y0; sy < y1; sy += 1)
|
|
23
|
+
for (let sx = x0; sx < x1; sx += 1) {
|
|
24
|
+
const at = (sy * source.width + sx) * 4;
|
|
25
|
+
r += source.data[at];
|
|
26
|
+
g += source.data[at + 1];
|
|
27
|
+
b += source.data[at + 2];
|
|
28
|
+
a += source.data[at + 3];
|
|
29
|
+
count += 1;
|
|
30
|
+
}
|
|
31
|
+
const out = (y * width + x) * 4;
|
|
32
|
+
target.data[out] = r / count;
|
|
33
|
+
target.data[out + 1] = g / count;
|
|
34
|
+
target.data[out + 2] = b / count;
|
|
35
|
+
target.data[out + 3] = a / count;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
static rotate(source, degrees) {
|
|
41
|
+
const turns = ((Math.round(degrees / 90) % 4) + 4) % 4;
|
|
42
|
+
if (turns === 0)
|
|
43
|
+
return source;
|
|
44
|
+
const swapped = turns % 2 === 1;
|
|
45
|
+
const width = swapped ? source.height : source.width;
|
|
46
|
+
const height = swapped ? source.width : source.height;
|
|
47
|
+
const target = PsScanRasterService.create(width, height);
|
|
48
|
+
for (let y = 0; y < source.height; y += 1)
|
|
49
|
+
for (let x = 0; x < source.width; x += 1) {
|
|
50
|
+
let tx = x;
|
|
51
|
+
let ty = y;
|
|
52
|
+
if (turns === 1)
|
|
53
|
+
[tx, ty] = [source.height - 1 - y, x];
|
|
54
|
+
else if (turns === 2)
|
|
55
|
+
[tx, ty] = [source.width - 1 - x, source.height - 1 - y];
|
|
56
|
+
else
|
|
57
|
+
[tx, ty] = [y, source.width - 1 - x];
|
|
58
|
+
const from = (y * source.width + x) * 4;
|
|
59
|
+
const to = (ty * width + tx) * 4;
|
|
60
|
+
target.data[to] = source.data[from];
|
|
61
|
+
target.data[to + 1] = source.data[from + 1];
|
|
62
|
+
target.data[to + 2] = source.data[from + 2];
|
|
63
|
+
target.data[to + 3] = source.data[from + 3];
|
|
64
|
+
}
|
|
65
|
+
return target;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PsScanHomographyService } from './ps-scan-homography-service';
|
|
2
|
+
import { PsScanRasterService } from './ps-scan-raster-service';
|
|
3
|
+
export class PsScanWarpService {
|
|
4
|
+
static warp(source, pixelQuad, width, height) {
|
|
5
|
+
const matrix = PsScanHomographyService.fromUnitSquare(pixelQuad);
|
|
6
|
+
const target = PsScanRasterService.create(width, height);
|
|
7
|
+
const spanX = Math.max(1, width - 1);
|
|
8
|
+
const spanY = Math.max(1, height - 1);
|
|
9
|
+
for (let y = 0; y < height; y += 1)
|
|
10
|
+
for (let x = 0; x < width; x += 1) {
|
|
11
|
+
const point = PsScanHomographyService.apply(matrix, x / spanX, y / spanY);
|
|
12
|
+
PsScanWarpService.sample(source, point.x, point.y, target.data, (y * width + x) * 4);
|
|
13
|
+
}
|
|
14
|
+
return target;
|
|
15
|
+
}
|
|
16
|
+
static sample(source, x, y, out, at) {
|
|
17
|
+
const cx = Math.min(source.width - 1, Math.max(0, x));
|
|
18
|
+
const cy = Math.min(source.height - 1, Math.max(0, y));
|
|
19
|
+
const x0 = Math.floor(cx);
|
|
20
|
+
const y0 = Math.floor(cy);
|
|
21
|
+
const x1 = Math.min(source.width - 1, x0 + 1);
|
|
22
|
+
const y1 = Math.min(source.height - 1, y0 + 1);
|
|
23
|
+
const fx = cx - x0;
|
|
24
|
+
const fy = cy - y0;
|
|
25
|
+
const a = (y0 * source.width + x0) * 4;
|
|
26
|
+
const b = (y0 * source.width + x1) * 4;
|
|
27
|
+
const c = (y1 * source.width + x0) * 4;
|
|
28
|
+
const d = (y1 * source.width + x1) * 4;
|
|
29
|
+
for (let channel = 0; channel < 4; channel += 1) {
|
|
30
|
+
const top = source.data[a + channel] * (1 - fx) + source.data[b + channel] * fx;
|
|
31
|
+
const bottom = source.data[c + channel] * (1 - fx) + source.data[d + channel] * fx;
|
|
32
|
+
out[at + channel] = top * (1 - fy) + bottom * fy;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsFocusTrap = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const usePsFocusTrap = (ref, isActive, onEscape) => {
|
|
6
|
+
(0, react_1.useEffect)(() => {
|
|
7
|
+
if (!isActive || ref.current === null)
|
|
8
|
+
return;
|
|
9
|
+
const dialog = ref.current.closest('[role="dialog"]');
|
|
10
|
+
const root = dialog === null ? ref.current : dialog;
|
|
11
|
+
const focusable = () => Array.from(root.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((element) => element.offsetParent !== null || element === document.activeElement);
|
|
12
|
+
if (!root.contains(document.activeElement)) {
|
|
13
|
+
root.setAttribute('tabindex', '-1');
|
|
14
|
+
root.focus({ preventScroll: true });
|
|
15
|
+
}
|
|
16
|
+
const onKeyDown = (event) => {
|
|
17
|
+
if (event.key === 'Escape') {
|
|
18
|
+
event.stopPropagation();
|
|
19
|
+
onEscape();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (event.key !== 'Tab')
|
|
23
|
+
return;
|
|
24
|
+
const items = focusable();
|
|
25
|
+
if (items.length === 0) {
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const first = items[0];
|
|
30
|
+
const last = items[items.length - 1];
|
|
31
|
+
const current = document.activeElement;
|
|
32
|
+
if (!root.contains(current)) {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
first.focus();
|
|
35
|
+
}
|
|
36
|
+
else if (event.shiftKey && (current === first || current === root)) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
last.focus();
|
|
39
|
+
}
|
|
40
|
+
else if (!event.shiftKey && current === last) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
first.focus();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
document.addEventListener('keydown', onKeyDown, true);
|
|
46
|
+
return () => document.removeEventListener('keydown', onKeyDown, true);
|
|
47
|
+
}, [isActive, onEscape]);
|
|
48
|
+
};
|
|
49
|
+
exports.usePsFocusTrap = usePsFocusTrap;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PsScanCameraStatus } from '../constants/ps-scan-camera-status';
|
|
2
|
+
export declare const usePsScanCamera: (isActive: boolean) => {
|
|
3
|
+
canTorch: boolean;
|
|
4
|
+
capture: () => Promise<Blob | null>;
|
|
5
|
+
isTorchOn: boolean;
|
|
6
|
+
status: PsScanCameraStatus;
|
|
7
|
+
toggleTorch: () => Promise<void>;
|
|
8
|
+
videoRef: import("react").RefObject<HTMLVideoElement>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsScanCamera = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const ps_scan_camera_status_1 = require("../constants/ps-scan-camera-status");
|
|
6
|
+
const ps_scan_config_1 = require("../constants/ps-scan-config");
|
|
7
|
+
const ps_scan_camera_service_1 = require("../services/ps-scan-camera-service");
|
|
8
|
+
const ps_scan_canvas_service_1 = require("../services/ps-scan-canvas-service");
|
|
9
|
+
const usePsScanCamera = (isActive) => {
|
|
10
|
+
const videoRef = (0, react_1.useRef)(null);
|
|
11
|
+
const sessionRef = (0, react_1.useRef)(null);
|
|
12
|
+
const [status, setStatus] = (0, react_1.useState)(ps_scan_camera_status_1.PsScanCameraStatus.Starting);
|
|
13
|
+
const [canTorch, setCanTorch] = (0, react_1.useState)(false);
|
|
14
|
+
const [isTorchOn, setIsTorchOn] = (0, react_1.useState)(false);
|
|
15
|
+
(0, react_1.useEffect)(() => {
|
|
16
|
+
if (!isActive)
|
|
17
|
+
return;
|
|
18
|
+
let isCancelled = false;
|
|
19
|
+
setIsTorchOn(false);
|
|
20
|
+
if (!ps_scan_camera_service_1.PsScanCameraService.isSupported()) {
|
|
21
|
+
setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Unavailable);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
setStatus(ps_scan_camera_status_1.PsScanCameraStatus.Starting);
|
|
25
|
+
ps_scan_camera_service_1.PsScanCameraService.open()
|
|
26
|
+
.then(async (session) => {
|
|
27
|
+
if (isCancelled) {
|
|
28
|
+
ps_scan_camera_service_1.PsScanCameraService.stop(session.stream);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
sessionRef.current = session;
|
|
32
|
+
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);
|
|
39
|
+
})
|
|
40
|
+
.catch((error) => {
|
|
41
|
+
if (!isCancelled)
|
|
42
|
+
setStatus(ps_scan_camera_service_1.PsScanCameraService.classify(error));
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
isCancelled = true;
|
|
46
|
+
ps_scan_camera_service_1.PsScanCameraService.stop(sessionRef.current === null ? null : sessionRef.current.stream);
|
|
47
|
+
sessionRef.current = null;
|
|
48
|
+
if (videoRef.current !== null)
|
|
49
|
+
videoRef.current.srcObject = null;
|
|
50
|
+
};
|
|
51
|
+
}, [isActive]);
|
|
52
|
+
const toggleTorch = (0, react_1.useCallback)(async () => {
|
|
53
|
+
const session = sessionRef.current;
|
|
54
|
+
if (session === null)
|
|
55
|
+
return;
|
|
56
|
+
setIsTorchOn(await ps_scan_camera_service_1.PsScanCameraService.setTorch(session.track, !isTorchOn));
|
|
57
|
+
}, [isTorchOn]);
|
|
58
|
+
const capture = (0, react_1.useCallback)(async () => {
|
|
59
|
+
const video = videoRef.current;
|
|
60
|
+
if (video === null || video.videoWidth === 0)
|
|
61
|
+
return null;
|
|
62
|
+
return await ps_scan_canvas_service_1.PsScanCanvasService.fromVideo(video, ps_scan_config_1.PsScanConfig.capturedJpegQuality);
|
|
63
|
+
}, []);
|
|
64
|
+
return { canTorch, capture, isTorchOn, status, toggleTorch, videoRef };
|
|
65
|
+
};
|
|
66
|
+
exports.usePsScanCamera = usePsScanCamera;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PsScanAddedPage } from '../models/ps-scan-added-page';
|
|
2
|
+
import type { PsScanPage } from '../models/ps-scan-page';
|
|
3
|
+
import type { PsScanQuad } from '../models/ps-scan-quad';
|
|
4
|
+
export declare const usePsScanPages: () => {
|
|
5
|
+
add: (sources: Blob[]) => Promise<PsScanAddedPage[]>;
|
|
6
|
+
move: (from: number, to: number) => void;
|
|
7
|
+
pages: PsScanPage[];
|
|
8
|
+
remove: (id: string) => void;
|
|
9
|
+
replace: (id: string, source: Blob) => Promise<PsScanAddedPage>;
|
|
10
|
+
setCorners: (id: string, corners: PsScanQuad) => void;
|
|
11
|
+
update: (id: string, patch: Partial<Pick<PsScanPage, "corners" | "filter" | "rotation">>) => void;
|
|
12
|
+
};
|