@scanmate/merge 0.7.0 → 0.8.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/README.md +16 -0
- package/dist/index.esm.js +198 -3
- package/dist/src/index.d.ts +4 -0
- package/dist/src/page-marking/index.d.ts +6 -0
- package/dist/src/page-marking/mark-pages.use-case.d.ts +14 -0
- package/dist/src/page-marking/page-mark.contract.d.ts +40 -0
- package/dist/src/page-marking/page-viewport.policy.d.ts +38 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -58,6 +58,22 @@ Measured on real scans: three 120-dpi page JPEGs plus a 7-page PDF merged in 20
|
|
|
58
58
|
|
|
59
59
|
PDF writing uses `@cantoo/pdf-lib`, the maintained fork of pdf-lib, which is pure JavaScript with nothing to install on the host.
|
|
60
60
|
|
|
61
|
+
## Drawing on a PDF, not just assembling one
|
|
62
|
+
|
|
63
|
+
The other thing this package writes is marks. `markPages` draws a set of regions on a PDF as vector rectangles, each with its bleed dashed around it, and hands the PDF back:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { markPages } from '@scanmate/merge'
|
|
67
|
+
|
|
68
|
+
const { pdf, drawn, warnings } = await markPages('issued.pdf', [
|
|
69
|
+
{ page: 1, id: 'signature', x: 120, y: 577, width: 262, height: 22 },
|
|
70
|
+
], { bleedTop: 2, bleedBottom: 12 })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
It exists to check that the regions a validation will measure are where the document's fields actually are - most people reach it as `Scanmate.mark` in `@scanmate/scan`, whose README has a worked example on the W-9.
|
|
74
|
+
|
|
75
|
+
Two things it gets right that a naive version would not. **Rotation and crop box:** a region is in points from the top-left of the page *as displayed*, which is how `@scanmate/extract` reports text, while pdf-lib draws from the bottom-left of the unrotated media box. The conversion between the two is pdf.js's own page transform, ported line for line and inverted, and its spec pins it to values read off real pdf.js for every quarter turn, with and without an offset crop box. **Bleed:** it is resolved by `resolveBleed` from `@scanmate/ink`, the same function the pixel comparison uses, so the band drawn is the band measured.
|
|
76
|
+
|
|
61
77
|
## How it decides
|
|
62
78
|
|
|
63
79
|
[`documentation/algorithms.md`](./documentation/algorithms.md) has the algorithms in full: what each step measures, the decision flows, every constant with the measurement behind it, and what the package deliberately does not do.
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PDFDocument, EncryptedPDFError } from '@cantoo/pdf-lib';
|
|
2
|
-
import { isRaster, decodeImage, readImageMetadata, encodeImage } from '@scanmate/ink';
|
|
1
|
+
import { PDFDocument, EncryptedPDFError, rgb, StandardFonts, degrees } from '@cantoo/pdf-lib';
|
|
2
|
+
import { isRaster, decodeImage, readImageMetadata, encodeImage, resolveBleed, hasBleed, growBy } from '@scanmate/ink';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -353,5 +353,200 @@ function embeddableFormat(bytes) {
|
|
|
353
353
|
return null;
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
|
|
356
|
+
/**
|
|
357
|
+
* The page as a reader sees it, and the way back to the PDF's own coordinates.
|
|
358
|
+
*
|
|
359
|
+
* A mark is measured the way `@scanmate/extract` reports text: from the top-left
|
|
360
|
+
* of the page as displayed, in points, with the page's `/Rotate` and crop box
|
|
361
|
+
* already applied. pdf-lib draws in the PDF's own user space instead: from the
|
|
362
|
+
* bottom-left of the media box, before any rotation.
|
|
363
|
+
*
|
|
364
|
+
* Getting from one to the other is the whole difficulty of drawing a mark in
|
|
365
|
+
* the right place, and doing it approximately would defeat the purpose - a
|
|
366
|
+
* helper for checking positions that is wrong on rotated or cropped pages gives
|
|
367
|
+
* false confidence on exactly the pages that most need checking.
|
|
368
|
+
*
|
|
369
|
+
* So this is pdf.js's own `PageViewport` transform at scale 1, ported line for
|
|
370
|
+
* line rather than re-derived, and inverted. The text a mark was measured from
|
|
371
|
+
* and the box drawn for it then go through the same arithmetic in opposite
|
|
372
|
+
* directions.
|
|
373
|
+
*/
|
|
374
|
+
/** User space to what a reader sees - pdf.js's `PageViewport` at scale 1. */
|
|
375
|
+
function viewportTransform({
|
|
376
|
+
view,
|
|
377
|
+
rotation
|
|
378
|
+
}) {
|
|
379
|
+
const centerX = (view[2] + view[0]) / 2;
|
|
380
|
+
const centerY = (view[3] + view[1]) / 2;
|
|
381
|
+
const [a, b, c, d] = rotationOf(rotation);
|
|
382
|
+
const quarter = a === 0;
|
|
383
|
+
const offsetX = quarter ? Math.abs(centerY - view[1]) : Math.abs(centerX - view[0]);
|
|
384
|
+
const offsetY = quarter ? Math.abs(centerX - view[0]) : Math.abs(centerY - view[1]);
|
|
385
|
+
return [a, b, c, d, offsetX - a * centerX - c * centerY, offsetY - b * centerX - d * centerY];
|
|
386
|
+
}
|
|
387
|
+
/** How large the page is as a reader sees it: a quarter turn swaps width and height. */
|
|
388
|
+
function viewportSize({
|
|
389
|
+
view,
|
|
390
|
+
rotation
|
|
391
|
+
}) {
|
|
392
|
+
const [a] = rotationOf(rotation);
|
|
393
|
+
const across = view[2] - view[0];
|
|
394
|
+
const down = view[3] - view[1];
|
|
395
|
+
return a === 0 ? {
|
|
396
|
+
width: down,
|
|
397
|
+
height: across
|
|
398
|
+
} : {
|
|
399
|
+
width: across,
|
|
400
|
+
height: down
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** A point a reader sees, back in the PDF's own coordinates. */
|
|
404
|
+
function toUserSpace([a, b, c, d, e, f], x, y) {
|
|
405
|
+
const det = a * d - b * c;
|
|
406
|
+
const dx = x - e;
|
|
407
|
+
const dy = y - f;
|
|
408
|
+
// `+ 0` turns a negative zero positive: harmless to draw at, noisy to read back.
|
|
409
|
+
return {
|
|
410
|
+
x: (d * dx - c * dy) / det + 0,
|
|
411
|
+
y: (-b * dx + a * dy) / det + 0
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* pdf.js's rotation matrix for a page turned by `rotation` degrees.
|
|
416
|
+
*
|
|
417
|
+
* Only quarter turns are legal in a PDF; anything else is refused here exactly
|
|
418
|
+
* as pdf.js refuses it, rather than drawn somewhere plausible.
|
|
419
|
+
*/
|
|
420
|
+
function rotationOf(rotation) {
|
|
421
|
+
const matrix = ROTATIONS.get((rotation % 360 + 360) % 360);
|
|
422
|
+
if (matrix === undefined) throw new RangeError(`a page may only be rotated by a multiple of 90 degrees, and is rotated by ${rotation}`);
|
|
423
|
+
return matrix;
|
|
424
|
+
}
|
|
425
|
+
/** pdf.js's `rotateA` to `rotateD`, by quarter turn. */
|
|
426
|
+
const ROTATIONS = new Map([[0, [1, 0, 0, -1]], [90, [0, 1, 1, 0]], [180, [-1, 0, 0, 1]], [270, [0, -1, -1, 0]]]);
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* The colours of the audit's evidence page, so the two read the same way: the
|
|
430
|
+
* region in blue, its bleed in magenta.
|
|
431
|
+
*/
|
|
432
|
+
const REGION = rgb(0, 23 / 255, 252 / 255);
|
|
433
|
+
const BLEED = rgb(245 / 255, 0, 252 / 255);
|
|
434
|
+
const LABEL_SIZE = 7;
|
|
435
|
+
/**
|
|
436
|
+
* Draws each mark on the original, with its bleed around it, and hands the PDF
|
|
437
|
+
* back - a way to see whether the positions a validation will use are where
|
|
438
|
+
* the fields actually are, before anything is measured with them.
|
|
439
|
+
*
|
|
440
|
+
* Drawn on the document itself, as vectors: the page is not re-rendered, so it
|
|
441
|
+
* stays sharp at any zoom and the boxes sit exactly where their coordinates
|
|
442
|
+
* say. Nothing is aligned or adjusted, because there is nothing to align - this
|
|
443
|
+
* is the original, and the marks were measured against it.
|
|
444
|
+
*/
|
|
445
|
+
async function markPages(pdf, marks, options = {}) {
|
|
446
|
+
const source = await readSource(pdf, 0);
|
|
447
|
+
if (source.kind !== 'pdf') throw new TypeError('marks are drawn on a PDF, and this is not one');
|
|
448
|
+
const document = await PDFDocument.load(source.bytes);
|
|
449
|
+
const font = options.labels === false ? null : await document.embedFont(StandardFonts.Helvetica);
|
|
450
|
+
const bleed = resolveBleed(options);
|
|
451
|
+
const pages = document.getPages();
|
|
452
|
+
const warnings = [];
|
|
453
|
+
let drawn = 0;
|
|
454
|
+
for (const mark of marks) {
|
|
455
|
+
const name = mark.id ?? `mark ${drawn + warnings.length + 1}`;
|
|
456
|
+
const page = pages[mark.page - 1];
|
|
457
|
+
if (page === undefined) {
|
|
458
|
+
warnings.push(`${name} is on page ${mark.page}, and the document has ${pages.length}`);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const geometry = geometryOf(page);
|
|
462
|
+
const size = viewportSize(geometry);
|
|
463
|
+
if (mark.x < 0 || mark.y < 0 || mark.x + mark.width > size.width || mark.y + mark.height > size.height) warnings.push(`${name} reaches past the edge of page ${mark.page}, which is ${round(size.width)} x ${round(size.height)} pt`);
|
|
464
|
+
// The band first, so the region's own outline draws over it where they meet.
|
|
465
|
+
if (hasBleed(bleed)) draw(page, geometry, growBy(mark, bleed), {
|
|
466
|
+
colour: BLEED,
|
|
467
|
+
dashed: true
|
|
468
|
+
});
|
|
469
|
+
draw(page, geometry, mark, {
|
|
470
|
+
colour: REGION,
|
|
471
|
+
dashed: false
|
|
472
|
+
});
|
|
473
|
+
if (font !== null && mark.id !== undefined) label(page, geometry, font, mark);
|
|
474
|
+
drawn++;
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
pdf: await document.save(),
|
|
478
|
+
drawn,
|
|
479
|
+
warnings
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/** The page's visible box and rotation, as pdf.js would read them. */
|
|
483
|
+
function geometryOf(page) {
|
|
484
|
+
const crop = page.getCropBox();
|
|
485
|
+
return {
|
|
486
|
+
view: [crop.x, crop.y, crop.x + crop.width, crop.y + crop.height],
|
|
487
|
+
rotation: page.getRotation().angle
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* A rectangle measured as displayed, drawn in the PDF's own coordinates.
|
|
492
|
+
*
|
|
493
|
+
* A quarter turn keeps a rectangle a rectangle, so its corners are taken back
|
|
494
|
+
* to user space and their extent is the box to draw.
|
|
495
|
+
*/
|
|
496
|
+
function draw(page, geometry, rect, style) {
|
|
497
|
+
const {
|
|
498
|
+
x,
|
|
499
|
+
y,
|
|
500
|
+
width,
|
|
501
|
+
height
|
|
502
|
+
} = userBox(geometry, rect);
|
|
503
|
+
page.drawRectangle({
|
|
504
|
+
x,
|
|
505
|
+
y,
|
|
506
|
+
width,
|
|
507
|
+
height,
|
|
508
|
+
borderColor: style.colour,
|
|
509
|
+
borderWidth: style.dashed ? 0.6 : 0.9,
|
|
510
|
+
borderDashArray: style.dashed ? [2.5, 1.5] : undefined,
|
|
511
|
+
opacity: 0,
|
|
512
|
+
borderOpacity: 1
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The id inside the box's top-left corner, upright to the reader however the
|
|
517
|
+
* page is turned.
|
|
518
|
+
*
|
|
519
|
+
* Inside rather than above: on the original a field is empty, so the corner is
|
|
520
|
+
* free, while the space above it is usually the printed line before - and a
|
|
521
|
+
* label written over that would obscure the very edge being checked.
|
|
522
|
+
*/
|
|
523
|
+
function label(page, geometry, font, mark) {
|
|
524
|
+
const at = toUserSpace(viewportTransform(geometry), mark.x + 2, mark.y + LABEL_SIZE);
|
|
525
|
+
page.drawText(mark.id ?? '', {
|
|
526
|
+
x: at.x,
|
|
527
|
+
y: at.y,
|
|
528
|
+
size: LABEL_SIZE,
|
|
529
|
+
font,
|
|
530
|
+
color: REGION,
|
|
531
|
+
// Text is laid in user space; turning it with the page keeps it readable.
|
|
532
|
+
rotate: degrees(geometry.rotation)
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
function userBox(geometry, rect) {
|
|
536
|
+
const transform = viewportTransform(geometry);
|
|
537
|
+
const corners = [toUserSpace(transform, rect.x, rect.y), toUserSpace(transform, rect.x + rect.width, rect.y + rect.height)];
|
|
538
|
+
const xs = corners.map(corner => corner.x);
|
|
539
|
+
const ys = corners.map(corner => corner.y);
|
|
540
|
+
return {
|
|
541
|
+
x: Math.min(...xs),
|
|
542
|
+
y: Math.min(...ys),
|
|
543
|
+
width: Math.abs(xs[1] - xs[0]),
|
|
544
|
+
height: Math.abs(ys[1] - ys[0])
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function round(value) {
|
|
548
|
+
return Math.round(value * 10) / 10;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export { MIN_RECORDED_DPI, MergeSourceError, PAPER, isPdf, markPages, mergeDocuments, placeImage, readSource, resolveDpi, toUserSpace, viewportSize, viewportTransform };
|
|
357
552
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -23,9 +23,13 @@ export { mergeDocuments } from './document-merge/index.js';
|
|
|
23
23
|
export type { Embedding, MergedPage, MergeOptions, MergeResult } from './document-merge/index.js';
|
|
24
24
|
export { MergeSourceError } from './source-reading/index.js';
|
|
25
25
|
export type { SourceKind } from './source-reading/index.js';
|
|
26
|
+
export { markPages } from './page-marking/index.js';
|
|
27
|
+
export type { MarkOptions, MarkResult, PageMark } from './page-marking/index.js';
|
|
26
28
|
export type { PageSize } from './page-placement/index.js';
|
|
27
29
|
export { MIN_RECORDED_DPI, PAPER, placeImage, resolveDpi } from './page-placement/index.js';
|
|
28
30
|
export type { Placement } from './page-placement/index.js';
|
|
29
31
|
export { isPdf, readSource } from './source-reading/index.js';
|
|
30
32
|
export type { ResolvedSource } from './source-reading/index.js';
|
|
33
|
+
export { toUserSpace, viewportSize, viewportTransform } from './page-marking/index.js';
|
|
34
|
+
export type { Affine, PageGeometry } from './page-marking/index.js';
|
|
31
35
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Drawing expected regions on the original, to see whether they are where the fields are. */
|
|
2
|
+
export { markPages } from './mark-pages.use-case.js';
|
|
3
|
+
export type { MarkOptions, MarkResult, PageMark } from './page-mark.contract.js';
|
|
4
|
+
export { toUserSpace, viewportSize, viewportTransform } from './page-viewport.policy.js';
|
|
5
|
+
export type { Affine, PageGeometry } from './page-viewport.policy.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ScanmateBinarySource } from '@scanmate/ink';
|
|
2
|
+
import type { MarkOptions, MarkResult, PageMark } from './page-mark.contract.js';
|
|
3
|
+
/**
|
|
4
|
+
* Draws each mark on the original, with its bleed around it, and hands the PDF
|
|
5
|
+
* back - a way to see whether the positions a validation will use are where
|
|
6
|
+
* the fields actually are, before anything is measured with them.
|
|
7
|
+
*
|
|
8
|
+
* Drawn on the document itself, as vectors: the page is not re-rendered, so it
|
|
9
|
+
* stays sharp at any zoom and the boxes sit exactly where their coordinates
|
|
10
|
+
* say. Nothing is aligned or adjusted, because there is nothing to align - this
|
|
11
|
+
* is the original, and the marks were measured against it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function markPages(pdf: ScanmateBinarySource, marks: readonly PageMark[], options?: MarkOptions): Promise<MarkResult>;
|
|
14
|
+
//# sourceMappingURL=mark-pages.use-case.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Bleed, ScanmateRect } from '@scanmate/ink';
|
|
2
|
+
/**
|
|
3
|
+
* One region to draw, where the document's own fields are expected to be.
|
|
4
|
+
*
|
|
5
|
+
* In points, from the top-left of the page as displayed - the same coordinates
|
|
6
|
+
* `@scanmate/extract` reports text in, and the same shape as `@scanmate/diff`'s
|
|
7
|
+
* `ExpectedChange`. So the array about to be handed to an audit can be drawn
|
|
8
|
+
* as it is, and what is checked visually is exactly what will be measured.
|
|
9
|
+
*/
|
|
10
|
+
export interface PageMark extends ScanmateRect {
|
|
11
|
+
/** One-based page number. */
|
|
12
|
+
page: number;
|
|
13
|
+
/** Written beside the box, so a reviewer can tell which field is which. */
|
|
14
|
+
id?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* How the marks are drawn. The bleed - `bleed`, and `bleedTop`, `bleedRight`,
|
|
18
|
+
* `bleedBottom`, `bleedLeft` to override a side - is drawn as a dashed band
|
|
19
|
+
* around each mark, and is the same rule the pixel comparison measures with. A
|
|
20
|
+
* mark checked here with a given bleed is checked against the region an audit
|
|
21
|
+
* with that bleed will actually claim. Default 6 points on every side.
|
|
22
|
+
*/
|
|
23
|
+
export interface MarkOptions extends Bleed {
|
|
24
|
+
/** Write each mark's `id` beside it. Default `true`. */
|
|
25
|
+
labels?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface MarkResult {
|
|
28
|
+
/** The original, with the marks drawn on it. Nothing else about it changes. */
|
|
29
|
+
pdf: Uint8Array;
|
|
30
|
+
/** How many marks were drawn. */
|
|
31
|
+
drawn: number;
|
|
32
|
+
/**
|
|
33
|
+
* What could not be drawn, or was drawn somewhere it cannot be right: a mark
|
|
34
|
+
* on a page the document does not have, or one that reaches past the edge of
|
|
35
|
+
* its page. The second is itself a positioning error, and exactly the kind
|
|
36
|
+
* this exists to catch.
|
|
37
|
+
*/
|
|
38
|
+
warnings: string[];
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=page-mark.contract.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page as a reader sees it, and the way back to the PDF's own coordinates.
|
|
3
|
+
*
|
|
4
|
+
* A mark is measured the way `@scanmate/extract` reports text: from the top-left
|
|
5
|
+
* of the page as displayed, in points, with the page's `/Rotate` and crop box
|
|
6
|
+
* already applied. pdf-lib draws in the PDF's own user space instead: from the
|
|
7
|
+
* bottom-left of the media box, before any rotation.
|
|
8
|
+
*
|
|
9
|
+
* Getting from one to the other is the whole difficulty of drawing a mark in
|
|
10
|
+
* the right place, and doing it approximately would defeat the purpose - a
|
|
11
|
+
* helper for checking positions that is wrong on rotated or cropped pages gives
|
|
12
|
+
* false confidence on exactly the pages that most need checking.
|
|
13
|
+
*
|
|
14
|
+
* So this is pdf.js's own `PageViewport` transform at scale 1, ported line for
|
|
15
|
+
* line rather than re-derived, and inverted. The text a mark was measured from
|
|
16
|
+
* and the box drawn for it then go through the same arithmetic in opposite
|
|
17
|
+
* directions.
|
|
18
|
+
*/
|
|
19
|
+
/** A two-dimensional affine transform, `[a, b, c, d, e, f]` as PDF writes them. */
|
|
20
|
+
export type Affine = readonly [number, number, number, number, number, number];
|
|
21
|
+
/** The page's visible box, `[x0, y0, x1, y1]` in user space, and its rotation. */
|
|
22
|
+
export interface PageGeometry {
|
|
23
|
+
view: readonly [number, number, number, number];
|
|
24
|
+
rotation: number;
|
|
25
|
+
}
|
|
26
|
+
/** User space to what a reader sees - pdf.js's `PageViewport` at scale 1. */
|
|
27
|
+
export declare function viewportTransform({ view, rotation }: PageGeometry): Affine;
|
|
28
|
+
/** How large the page is as a reader sees it: a quarter turn swaps width and height. */
|
|
29
|
+
export declare function viewportSize({ view, rotation }: PageGeometry): {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
};
|
|
33
|
+
/** A point a reader sees, back in the PDF's own coordinates. */
|
|
34
|
+
export declare function toUserSpace([a, b, c, d, e, f]: Affine, x: number, y: number): {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=page-viewport.policy.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scanmate/merge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Merge PDFs, images and rasters into one PDF: PDF pages copied as they are, JPEGs embedded without recompression, every page of a TIFF.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduardo Russo",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@cantoo/pdf-lib": "^2.11.1",
|
|
44
|
-
"@scanmate/ink": "^0.
|
|
44
|
+
"@scanmate/ink": "^0.8.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|