sane-wasm 0.3.0 → 0.3.1
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/build/libsane.wasm +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/options.d.ts +127 -0
- package/dist/options.js +165 -0
- package/dist/readers.d.ts +108 -0
- package/dist/readers.js +320 -0
- package/package.json +1 -1
package/build/libsane.wasm
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
17
|
exports.libsane = exports.SANEFrame = exports.SANEConstraintType = exports.SANEUnit = exports.SANEValueType = exports.SANEStatus = void 0;
|
|
4
18
|
/**
|
|
@@ -79,3 +93,5 @@ exports.libsane = require('../lib');
|
|
|
79
93
|
* Use the 'libsane' named export.
|
|
80
94
|
*/
|
|
81
95
|
exports.default = exports.libsane;
|
|
96
|
+
__exportStar(require("./options"), exports);
|
|
97
|
+
__exportStar(require("./readers"), exports);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { LibSANE, SANEOptionDescriptor, SANEStatus } from ".";
|
|
2
|
+
export interface ScanOption<T = any> {
|
|
3
|
+
index: number;
|
|
4
|
+
descriptor: SANEOptionDescriptor;
|
|
5
|
+
value: T;
|
|
6
|
+
}
|
|
7
|
+
export interface IScanOptions extends Iterable<ScanOption> {
|
|
8
|
+
/**
|
|
9
|
+
* List of all SANE options.
|
|
10
|
+
*/
|
|
11
|
+
readonly options: ReadonlyArray<ScanOption>;
|
|
12
|
+
/**
|
|
13
|
+
* Well-known SANE option index zero (total number of options).
|
|
14
|
+
*
|
|
15
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
16
|
+
*/
|
|
17
|
+
readonly zero: ScanOption<number>;
|
|
18
|
+
/**
|
|
19
|
+
* Well-known SANE option `resolution`.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
22
|
+
*/
|
|
23
|
+
readonly resolution: ScanOption<number> | null;
|
|
24
|
+
/**
|
|
25
|
+
* Well-known SANE option `preview`.
|
|
26
|
+
*
|
|
27
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
28
|
+
*/
|
|
29
|
+
readonly preview: ScanOption<boolean> | null;
|
|
30
|
+
/**
|
|
31
|
+
* Well-known SANE option `tl-x`.
|
|
32
|
+
*
|
|
33
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
34
|
+
*/
|
|
35
|
+
readonly tlX: ScanOption<number> | null;
|
|
36
|
+
/**
|
|
37
|
+
* Well-known SANE option `tl-y`.
|
|
38
|
+
*
|
|
39
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
40
|
+
*/
|
|
41
|
+
readonly tlY: ScanOption<number> | null;
|
|
42
|
+
/**
|
|
43
|
+
* Well-known SANE option `br-x`.
|
|
44
|
+
*
|
|
45
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
46
|
+
*/
|
|
47
|
+
readonly brX: ScanOption<number> | null;
|
|
48
|
+
/**
|
|
49
|
+
* Well-known SANE option `br-y`.
|
|
50
|
+
*
|
|
51
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#well-known-options}
|
|
52
|
+
*/
|
|
53
|
+
readonly brY: ScanOption<number> | null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
export declare abstract class ScanOptionsBase implements IScanOptions {
|
|
59
|
+
protected _lib: LibSANE;
|
|
60
|
+
readonly options: ReadonlyArray<ScanOption>;
|
|
61
|
+
readonly zero: ScanOption<number>;
|
|
62
|
+
readonly resolution: ScanOption<number> | null;
|
|
63
|
+
readonly preview: ScanOption<boolean> | null;
|
|
64
|
+
readonly tlX: ScanOption<number> | null;
|
|
65
|
+
readonly tlY: ScanOption<number> | null;
|
|
66
|
+
readonly brX: ScanOption<number> | null;
|
|
67
|
+
readonly brY: ScanOption<number> | null;
|
|
68
|
+
[Symbol.iterator](): IterableIterator<ScanOption>;
|
|
69
|
+
protected static _getOpts(lib: LibSANE): Promise<ScanOption<any>[]>;
|
|
70
|
+
protected constructor(lib: LibSANE, opts: Array<ScanOption>);
|
|
71
|
+
protected _setOpts(this: {
|
|
72
|
+
-readonly [K in keyof ScanOptionsBase]: ScanOptionsBase[K];
|
|
73
|
+
}, opts: Array<ScanOption>): void;
|
|
74
|
+
protected _setValue(index: number, value: any): Promise<{
|
|
75
|
+
status: SANEStatus.UNSUPPORTED | SANEStatus.CANCELLED | SANEStatus.DEVICE_BUSY | SANEStatus.INVAL | SANEStatus.EOF | SANEStatus.JAMMED | SANEStatus.NO_DOCS | SANEStatus.COVER_OPEN | SANEStatus.IO_ERROR | SANEStatus.NO_MEM | SANEStatus.ACCESS_DENIED;
|
|
76
|
+
info: null;
|
|
77
|
+
opts: null;
|
|
78
|
+
} | {
|
|
79
|
+
status: SANEStatus.GOOD;
|
|
80
|
+
info: import(".").SANEInfo;
|
|
81
|
+
opts: ScanOption<any>[];
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Get class instance (with all scanning options).
|
|
85
|
+
*/
|
|
86
|
+
static get: (lib: LibSANE) => Promise<IScanOptions>;
|
|
87
|
+
/**
|
|
88
|
+
* Set option value. Use `null` as `value` for automatic.
|
|
89
|
+
*
|
|
90
|
+
* @see {@link https://sane-project.gitlab.io/standard/1.06/api.html#sane-control-option}
|
|
91
|
+
*/
|
|
92
|
+
abstract setValue(index: number, value: any): any;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Helper class for getting/setting scan options.
|
|
96
|
+
*
|
|
97
|
+
* Any changes (i.e. calls to {@link ScanOptionsMutable.setValue}) affect the
|
|
98
|
+
* internal state.
|
|
99
|
+
*/
|
|
100
|
+
export declare class ScanOptionsMutable extends ScanOptionsBase {
|
|
101
|
+
static get(lib: LibSANE): Promise<ScanOptionsMutable>;
|
|
102
|
+
setValue(index: number, value: any): Promise<{
|
|
103
|
+
status: SANEStatus.GOOD;
|
|
104
|
+
info: import(".").SANEInfo;
|
|
105
|
+
} | {
|
|
106
|
+
status: SANEStatus.UNSUPPORTED | SANEStatus.CANCELLED | SANEStatus.DEVICE_BUSY | SANEStatus.INVAL | SANEStatus.EOF | SANEStatus.JAMMED | SANEStatus.NO_DOCS | SANEStatus.COVER_OPEN | SANEStatus.IO_ERROR | SANEStatus.NO_MEM | SANEStatus.ACCESS_DENIED;
|
|
107
|
+
info: null;
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Helper class for getting/setting scan options.
|
|
112
|
+
*
|
|
113
|
+
* Any changes (i.e. calls to {@link ScanOptions.setValue}) return a
|
|
114
|
+
* new class instance.
|
|
115
|
+
*/
|
|
116
|
+
export declare class ScanOptions extends ScanOptionsBase {
|
|
117
|
+
static get(lib: LibSANE): Promise<ScanOptions>;
|
|
118
|
+
setValue(index: number, value: any): Promise<{
|
|
119
|
+
status: SANEStatus.GOOD;
|
|
120
|
+
info: import(".").SANEInfo;
|
|
121
|
+
updated: ScanOptions;
|
|
122
|
+
} | {
|
|
123
|
+
status: SANEStatus.UNSUPPORTED | SANEStatus.CANCELLED | SANEStatus.DEVICE_BUSY | SANEStatus.INVAL | SANEStatus.EOF | SANEStatus.JAMMED | SANEStatus.NO_DOCS | SANEStatus.COVER_OPEN | SANEStatus.IO_ERROR | SANEStatus.NO_MEM | SANEStatus.ACCESS_DENIED;
|
|
124
|
+
info: null;
|
|
125
|
+
updated: null;
|
|
126
|
+
}>;
|
|
127
|
+
}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ScanOptions = exports.ScanOptionsMutable = exports.ScanOptionsBase = void 0;
|
|
13
|
+
const _1 = require(".");
|
|
14
|
+
/**
|
|
15
|
+
* @private
|
|
16
|
+
*/
|
|
17
|
+
class ScanOptionsBase {
|
|
18
|
+
[Symbol.iterator]() {
|
|
19
|
+
return this.options[Symbol.iterator]();
|
|
20
|
+
}
|
|
21
|
+
static _getOpts(lib) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const opts = [];
|
|
24
|
+
let desc = null;
|
|
25
|
+
for (let i = 0, n = -1; desc || i === 0; i++) {
|
|
26
|
+
({ option_descriptor: desc } = lib.sane_get_option_descriptor(i));
|
|
27
|
+
if (desc) {
|
|
28
|
+
if (!desc.cap.INACTIVE && desc.cap.SOFT_DETECT && desc.type !== _1.SANEValueType.BUTTON) {
|
|
29
|
+
const { status, value } = yield lib.sane_control_option_get_value(i);
|
|
30
|
+
if (status !== _1.SANEStatus.GOOD) {
|
|
31
|
+
throw new Error(`Unexpected status ${_1.SANEStatus[status]} while getting option value.`);
|
|
32
|
+
}
|
|
33
|
+
opts.push({ index: i, descriptor: desc, value });
|
|
34
|
+
if (i === 0) {
|
|
35
|
+
n = value; // option 0 contains total number of options
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
opts.push({ index: i, descriptor: desc, value: null });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (!desc && i !== n) {
|
|
43
|
+
throw new Error('Unexpected number of options.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return opts;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
constructor(lib, opts) {
|
|
50
|
+
this.resolution = null;
|
|
51
|
+
this.preview = null;
|
|
52
|
+
this.tlX = null;
|
|
53
|
+
this.tlY = null;
|
|
54
|
+
this.brX = null;
|
|
55
|
+
this.brY = null;
|
|
56
|
+
this._lib = lib;
|
|
57
|
+
this._setOpts(opts);
|
|
58
|
+
}
|
|
59
|
+
_setOpts(opts) {
|
|
60
|
+
this.options = opts;
|
|
61
|
+
this.zero = this.options[0];
|
|
62
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
63
|
+
if (this.options[i].index !== i) {
|
|
64
|
+
throw new Error(`Option index mismatch ${this.options[i].index} != ${i}.`);
|
|
65
|
+
}
|
|
66
|
+
switch (this.options[i].descriptor.name) {
|
|
67
|
+
case 'resolution':
|
|
68
|
+
this.resolution = this.options[i];
|
|
69
|
+
break;
|
|
70
|
+
case 'preview':
|
|
71
|
+
this.preview = this.options[i];
|
|
72
|
+
break;
|
|
73
|
+
case 'tl-x':
|
|
74
|
+
this.tlX = this.options[i];
|
|
75
|
+
break;
|
|
76
|
+
case 'tl-y':
|
|
77
|
+
this.tlY = this.options[i];
|
|
78
|
+
break;
|
|
79
|
+
case 'br-x':
|
|
80
|
+
this.brX = this.options[i];
|
|
81
|
+
break;
|
|
82
|
+
case 'br-y':
|
|
83
|
+
this.brY = this.options[i];
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_setValue(index, value) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
const auto = value === null; // null means auto
|
|
91
|
+
const { status, info } = auto ? yield this._lib.sane_control_option_set_auto(index) : yield this._lib.sane_control_option_set_value(index, value);
|
|
92
|
+
if (status !== _1.SANEStatus.GOOD) {
|
|
93
|
+
return { status, info, opts: null };
|
|
94
|
+
}
|
|
95
|
+
if (info.RELOAD_OPTIONS) {
|
|
96
|
+
return { status, info, opts: yield ScanOptionsBase._getOpts(this._lib) };
|
|
97
|
+
}
|
|
98
|
+
if (info.INEXACT || typeof value === 'number' || auto) {
|
|
99
|
+
// auto triggers get value... ok
|
|
100
|
+
// number triggers get value... this is a quirk for fixed numbers
|
|
101
|
+
// because the conversion from floating to fixed point happens
|
|
102
|
+
// on the library side, we might get INEXACT == false but in
|
|
103
|
+
// reality there were some small difference
|
|
104
|
+
// XXX: this could probably be fixed on the C side
|
|
105
|
+
const { status, value: v } = yield this._lib.sane_control_option_get_value(index);
|
|
106
|
+
if (status !== _1.SANEStatus.GOOD) {
|
|
107
|
+
throw new Error(`Unexpected status ${_1.SANEStatus[status]} while getting option value.`);
|
|
108
|
+
}
|
|
109
|
+
value = v;
|
|
110
|
+
}
|
|
111
|
+
// update value
|
|
112
|
+
const opts = [...this.options];
|
|
113
|
+
opts[index] = Object.assign(Object.assign({}, opts[index]), { value });
|
|
114
|
+
return { status, info, opts };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.ScanOptionsBase = ScanOptionsBase;
|
|
119
|
+
/**
|
|
120
|
+
* Helper class for getting/setting scan options.
|
|
121
|
+
*
|
|
122
|
+
* Any changes (i.e. calls to {@link ScanOptionsMutable.setValue}) affect the
|
|
123
|
+
* internal state.
|
|
124
|
+
*/
|
|
125
|
+
class ScanOptionsMutable extends ScanOptionsBase {
|
|
126
|
+
static get(lib) {
|
|
127
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
128
|
+
return new this(lib, yield this._getOpts(lib));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
setValue(index, value) {
|
|
132
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
133
|
+
const { status, info, opts } = yield this._setValue(index, value);
|
|
134
|
+
if (status == _1.SANEStatus.GOOD) {
|
|
135
|
+
this._setOpts(opts);
|
|
136
|
+
return { status, info };
|
|
137
|
+
}
|
|
138
|
+
return { status, info };
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.ScanOptionsMutable = ScanOptionsMutable;
|
|
143
|
+
/**
|
|
144
|
+
* Helper class for getting/setting scan options.
|
|
145
|
+
*
|
|
146
|
+
* Any changes (i.e. calls to {@link ScanOptions.setValue}) return a
|
|
147
|
+
* new class instance.
|
|
148
|
+
*/
|
|
149
|
+
class ScanOptions extends ScanOptionsBase {
|
|
150
|
+
static get(lib) {
|
|
151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
152
|
+
return new this(lib, yield this._getOpts(lib));
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
setValue(index, value) {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
+
const { status, info, opts } = yield this._setValue(index, value);
|
|
158
|
+
if (status == _1.SANEStatus.GOOD) {
|
|
159
|
+
return { status, info, updated: new ScanOptions(this._lib, opts) };
|
|
160
|
+
}
|
|
161
|
+
return { status, info, updated: null };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.ScanOptions = ScanOptions;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { LibSANE, SANEParameters, SANEStatus } from ".";
|
|
2
|
+
declare abstract class EventBaseClass<T extends Record<keyof T, any[]>> {
|
|
3
|
+
private _firing;
|
|
4
|
+
private _listeners;
|
|
5
|
+
/**
|
|
6
|
+
* Add event listener.
|
|
7
|
+
*/
|
|
8
|
+
on<K extends keyof T>(type: K, listener: (...args: T[K]) => void): void;
|
|
9
|
+
/**
|
|
10
|
+
* Fire event.
|
|
11
|
+
*/
|
|
12
|
+
protected fire<K extends keyof T>(type: K, ...args: T[K]): Promise<null> | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @private Events types for {@link ScanDataReader}.
|
|
16
|
+
*/
|
|
17
|
+
export interface ScanDataReaderEventMap extends Record<string, any[]> {
|
|
18
|
+
/**
|
|
19
|
+
* Scanning stop event.
|
|
20
|
+
*/
|
|
21
|
+
start: [parameters: SANEParameters];
|
|
22
|
+
/**
|
|
23
|
+
* Scanning start event.
|
|
24
|
+
*/
|
|
25
|
+
stop: [parameters: SANEParameters, error: Error | null];
|
|
26
|
+
/**
|
|
27
|
+
* Raw image data event.
|
|
28
|
+
*/
|
|
29
|
+
data: [parameters: SANEParameters, data: Uint8Array];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Raw data reader that automatically handles SANE's read code flow.
|
|
33
|
+
*
|
|
34
|
+
* Use {@link ScanDataReader.on} to listen to events, available event types
|
|
35
|
+
* are declared on {@link ScanDataReaderEventMap}.
|
|
36
|
+
*
|
|
37
|
+
* A device should already be open with sane_open(), the reader will call
|
|
38
|
+
* sane_start() do the scanning and call sane_stop().
|
|
39
|
+
*
|
|
40
|
+
* Other SANE functions cannot be used while scanning.
|
|
41
|
+
*
|
|
42
|
+
* Scan readers are single use.
|
|
43
|
+
*
|
|
44
|
+
* {@link https://sane-project.gitlab.io/standard/1.06/api.html#code-flow}
|
|
45
|
+
*/
|
|
46
|
+
export declare class ScanDataReader<T extends ScanDataReaderEventMap = ScanDataReaderEventMap> extends EventBaseClass<T> {
|
|
47
|
+
private _lib;
|
|
48
|
+
private _used;
|
|
49
|
+
private _killed;
|
|
50
|
+
constructor(lib: LibSANE);
|
|
51
|
+
private _readPromise;
|
|
52
|
+
/**
|
|
53
|
+
* Start scanning operation.
|
|
54
|
+
*/
|
|
55
|
+
start(): {
|
|
56
|
+
status: SANEStatus.UNSUPPORTED | SANEStatus.CANCELLED | SANEStatus.DEVICE_BUSY | SANEStatus.INVAL | SANEStatus.EOF | SANEStatus.JAMMED | SANEStatus.NO_DOCS | SANEStatus.COVER_OPEN | SANEStatus.IO_ERROR | SANEStatus.NO_MEM | SANEStatus.ACCESS_DENIED;
|
|
57
|
+
parameters: null;
|
|
58
|
+
promise: Promise<void>;
|
|
59
|
+
} | {
|
|
60
|
+
status: SANEStatus.GOOD;
|
|
61
|
+
parameters: SANEParameters;
|
|
62
|
+
promise: Promise<void>;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Cancel scanning operation.
|
|
66
|
+
*/
|
|
67
|
+
cancel(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* @private Events types for {@link ScanImageReader}.
|
|
71
|
+
*/
|
|
72
|
+
export interface ScanImageReaderEventMap extends ScanDataReaderEventMap {
|
|
73
|
+
/**
|
|
74
|
+
* Image line event, one or more full lines of RGBA data.
|
|
75
|
+
*/
|
|
76
|
+
line: [parameters: SANEParameters, data: Uint8ClampedArray, line: number];
|
|
77
|
+
/**
|
|
78
|
+
* Full image event (end of scan), RGBA data.
|
|
79
|
+
*/
|
|
80
|
+
image: [parameters: SANEParameters, data: Uint8ClampedArray];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Image reader that automatically generates RGBA data while reading from
|
|
84
|
+
* SANE's API using {@link ScanDataReader}. It supports the most common scan
|
|
85
|
+
* modes and image formats. More formats can be added in the future.
|
|
86
|
+
*
|
|
87
|
+
* Use {@link ScanImageReader.on} to listen to events, available event types
|
|
88
|
+
* are declared on {@link ScanImageReaderEventMap}.
|
|
89
|
+
*
|
|
90
|
+
* A device should already be open with sane_open(), the reader will call
|
|
91
|
+
* sane_start() do the scanning and call sane_stop().
|
|
92
|
+
*
|
|
93
|
+
* Other SANE functions cannot be used while scanning.
|
|
94
|
+
*
|
|
95
|
+
* Scan readers are single use.
|
|
96
|
+
*
|
|
97
|
+
* {@link https://sane-project.gitlab.io/standard/1.06/api.html#code-flow}
|
|
98
|
+
*/
|
|
99
|
+
export declare class ScanImageReader<T extends ScanImageReaderEventMap = ScanImageReaderEventMap> extends ScanDataReader<T> {
|
|
100
|
+
private _line;
|
|
101
|
+
private _allData;
|
|
102
|
+
private _remainder;
|
|
103
|
+
constructor(lib: LibSANE);
|
|
104
|
+
private _onStart;
|
|
105
|
+
private _onData;
|
|
106
|
+
private _onStop;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
package/dist/readers.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ScanImageReader = exports.ScanDataReader = void 0;
|
|
13
|
+
const _1 = require(".");
|
|
14
|
+
class EventBaseClass {
|
|
15
|
+
constructor() {
|
|
16
|
+
this._firing = false;
|
|
17
|
+
this._listeners = {};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Add event listener.
|
|
21
|
+
*/
|
|
22
|
+
on(type, listener) {
|
|
23
|
+
var _a;
|
|
24
|
+
if (!this._listeners[type]) {
|
|
25
|
+
this._listeners[type] = [];
|
|
26
|
+
}
|
|
27
|
+
(_a = this._listeners[type]) === null || _a === void 0 ? void 0 : _a.push(listener);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fire event.
|
|
31
|
+
*/
|
|
32
|
+
fire(type, ...args) {
|
|
33
|
+
var _a;
|
|
34
|
+
// if already firing an event, queue for later
|
|
35
|
+
if (this._firing) {
|
|
36
|
+
return Promise.resolve().then(() => this.fire(type, ...args));
|
|
37
|
+
}
|
|
38
|
+
// fire now
|
|
39
|
+
this._firing = true;
|
|
40
|
+
try {
|
|
41
|
+
(_a = this._listeners[type]) === null || _a === void 0 ? void 0 : _a.forEach(fn => fn.apply(this, args));
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
this._firing = false;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Raw data reader that automatically handles SANE's read code flow.
|
|
51
|
+
*
|
|
52
|
+
* Use {@link ScanDataReader.on} to listen to events, available event types
|
|
53
|
+
* are declared on {@link ScanDataReaderEventMap}.
|
|
54
|
+
*
|
|
55
|
+
* A device should already be open with sane_open(), the reader will call
|
|
56
|
+
* sane_start() do the scanning and call sane_stop().
|
|
57
|
+
*
|
|
58
|
+
* Other SANE functions cannot be used while scanning.
|
|
59
|
+
*
|
|
60
|
+
* Scan readers are single use.
|
|
61
|
+
*
|
|
62
|
+
* {@link https://sane-project.gitlab.io/standard/1.06/api.html#code-flow}
|
|
63
|
+
*/
|
|
64
|
+
class ScanDataReader extends EventBaseClass {
|
|
65
|
+
constructor(lib) {
|
|
66
|
+
super();
|
|
67
|
+
this._used = false;
|
|
68
|
+
this._killed = false;
|
|
69
|
+
this._lib = lib;
|
|
70
|
+
}
|
|
71
|
+
_readPromise(parameters) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const read = () => __awaiter(this, void 0, void 0, function* () {
|
|
74
|
+
try {
|
|
75
|
+
if (this._killed) {
|
|
76
|
+
yield this._lib.sane_cancel(); // ignore status
|
|
77
|
+
}
|
|
78
|
+
// reads are non-blocking because of how emscripten works
|
|
79
|
+
// this causes sane_read() to immediately return with
|
|
80
|
+
// 0 bytes of data, this goes against the SANE spec,
|
|
81
|
+
// blocking I/O by default
|
|
82
|
+
// the only option right now is to poll the read using
|
|
83
|
+
// setTimeout(), this is not ideal
|
|
84
|
+
// currently even emscripten's asyncify does not help here,
|
|
85
|
+
// but in the future it might help support blocking I/O
|
|
86
|
+
// XXX: are we in the future now? can we improve this
|
|
87
|
+
// https://github.com/emscripten-core/emscripten/issues/13214
|
|
88
|
+
const { status, data } = yield this._lib.sane_read(); // non-blocking
|
|
89
|
+
if (status === _1.SANEStatus.GOOD) {
|
|
90
|
+
if (parameters && data.length) {
|
|
91
|
+
this.fire('data', parameters, data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (status === _1.SANEStatus.CANCELLED ||
|
|
95
|
+
// special case for EOF after sane_cancel, after a successful
|
|
96
|
+
// page read (EOF) and subsequent call to sane_cancel we expected a
|
|
97
|
+
// CANCELLED status but the test backend never changes from EOF,
|
|
98
|
+
// this is different on the pixma backend (it changes to CANCELLED),
|
|
99
|
+
// it's unknown what is the correct option, or what happens on other
|
|
100
|
+
// backends, handle special case as cancelled for now
|
|
101
|
+
// XXX: more research
|
|
102
|
+
// https://sane-project.gitlab.io/standard/1.06/api.html#code-flow
|
|
103
|
+
(status === _1.SANEStatus.EOF && this._killed)) {
|
|
104
|
+
if (this._killed instanceof Error) {
|
|
105
|
+
reject(this._killed);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
resolve();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
else if (status === _1.SANEStatus.EOF) {
|
|
112
|
+
// image finished
|
|
113
|
+
this._killed = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// TODO: handle document feeder / multi-page scans
|
|
117
|
+
this._killed = new Error(`Status ${_1.SANEStatus[status]} during sane_read().`);
|
|
118
|
+
}
|
|
119
|
+
setTimeout(read, data && data.length > 0 ? 10 : 200);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
const ee = e instanceof Error ? e : new Error("Unknown error while scanning.");
|
|
123
|
+
if (this._killed) {
|
|
124
|
+
// already killed, but sane_read is still going? die
|
|
125
|
+
reject(ee);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this._killed = ee;
|
|
129
|
+
setTimeout(read, 200);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
setTimeout(read, 200);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Start scanning operation.
|
|
137
|
+
*/
|
|
138
|
+
start() {
|
|
139
|
+
if (this._used) {
|
|
140
|
+
// readers are single use
|
|
141
|
+
return {
|
|
142
|
+
status: _1.SANEStatus.INVAL,
|
|
143
|
+
parameters: null,
|
|
144
|
+
promise: Promise.reject(new Error("Scan readers cannot be reused.")),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const { status } = this._lib.sane_start();
|
|
149
|
+
if (status !== _1.SANEStatus.GOOD) {
|
|
150
|
+
return {
|
|
151
|
+
status,
|
|
152
|
+
parameters: null,
|
|
153
|
+
promise: Promise.reject(new Error(`Status ${_1.SANEStatus[status]} during sane_start().`)),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
this._used = true;
|
|
157
|
+
}
|
|
158
|
+
// after successful start, sane_cancel needs to be called and we need
|
|
159
|
+
// to wait for sane_read, so subsequent fails need to use this._killed
|
|
160
|
+
// and run the read loop
|
|
161
|
+
const { status, parameters } = this._lib.sane_get_parameters();
|
|
162
|
+
if (status !== _1.SANEStatus.GOOD) {
|
|
163
|
+
this._killed = new Error(`Status ${_1.SANEStatus[status]} during sane_get_parameters().`);
|
|
164
|
+
return {
|
|
165
|
+
status, parameters,
|
|
166
|
+
promise: this._readPromise(parameters),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
this.fire('start', parameters);
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
this._killed = e instanceof Error ? e : new Error("Unknown error while starting the scan.");
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
status, parameters,
|
|
177
|
+
promise: this._readPromise(parameters).finally(() => {
|
|
178
|
+
this.fire('stop', parameters, this._killed instanceof Error ? this._killed : null);
|
|
179
|
+
}),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Cancel scanning operation.
|
|
184
|
+
*/
|
|
185
|
+
cancel() {
|
|
186
|
+
if (this._used && !this._killed) {
|
|
187
|
+
this._killed = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.ScanDataReader = ScanDataReader;
|
|
192
|
+
/**
|
|
193
|
+
* Image reader that automatically generates RGBA data while reading from
|
|
194
|
+
* SANE's API using {@link ScanDataReader}. It supports the most common scan
|
|
195
|
+
* modes and image formats. More formats can be added in the future.
|
|
196
|
+
*
|
|
197
|
+
* Use {@link ScanImageReader.on} to listen to events, available event types
|
|
198
|
+
* are declared on {@link ScanImageReaderEventMap}.
|
|
199
|
+
*
|
|
200
|
+
* A device should already be open with sane_open(), the reader will call
|
|
201
|
+
* sane_start() do the scanning and call sane_stop().
|
|
202
|
+
*
|
|
203
|
+
* Other SANE functions cannot be used while scanning.
|
|
204
|
+
*
|
|
205
|
+
* Scan readers are single use.
|
|
206
|
+
*
|
|
207
|
+
* {@link https://sane-project.gitlab.io/standard/1.06/api.html#code-flow}
|
|
208
|
+
*/
|
|
209
|
+
class ScanImageReader extends ScanDataReader {
|
|
210
|
+
constructor(lib) {
|
|
211
|
+
super(lib);
|
|
212
|
+
this._line = 0;
|
|
213
|
+
this._allData = new Uint8ClampedArray();
|
|
214
|
+
this._remainder = new Uint8ClampedArray();
|
|
215
|
+
this.on('start', this._onStart);
|
|
216
|
+
this.on('data', this._onData);
|
|
217
|
+
this.on('stop', this._onStop);
|
|
218
|
+
}
|
|
219
|
+
_onStart(parameters) {
|
|
220
|
+
if (parameters.format !== _1.SANEFrame.GRAY && parameters.format !== _1.SANEFrame.RGB) {
|
|
221
|
+
throw new Error(`Invalid format (${JSON.stringify(parameters)}).`);
|
|
222
|
+
}
|
|
223
|
+
if (parameters.depth !== 1 && parameters.depth !== 8) {
|
|
224
|
+
throw new Error(`Invalid bit depth (${JSON.stringify(parameters)}).`);
|
|
225
|
+
}
|
|
226
|
+
if (!parameters.last_frame) {
|
|
227
|
+
throw new Error(`Invalid scanner (3-pass) (${JSON.stringify(parameters)}).`);
|
|
228
|
+
}
|
|
229
|
+
if (parameters.lines < 0) {
|
|
230
|
+
throw new Error(`Invalid scanner (hand-scanner) (${JSON.stringify(parameters)}).`);
|
|
231
|
+
}
|
|
232
|
+
if (parameters.bytes_per_line <= 0 || parameters.pixels_per_line <= 0 || parameters.lines <= 0) {
|
|
233
|
+
throw new Error(`Unexpected image size (${JSON.stringify(parameters)}).`);
|
|
234
|
+
}
|
|
235
|
+
const channels = parameters.format === _1.SANEFrame.GRAY ? 1 : 3;
|
|
236
|
+
const bits = parameters.pixels_per_line * parameters.depth * channels;
|
|
237
|
+
if (parameters.bytes_per_line * 8 < bits) {
|
|
238
|
+
throw new Error(`Unexpected byte count (${JSON.stringify(parameters)}).`);
|
|
239
|
+
}
|
|
240
|
+
// what do we support then?
|
|
241
|
+
// we support GRAY and RGB single-pass, 1 and 8-bit, known height
|
|
242
|
+
// that should cover most modern scanners
|
|
243
|
+
this._allData = new Uint8ClampedArray(parameters.lines * parameters.pixels_per_line * 4);
|
|
244
|
+
}
|
|
245
|
+
_onData(parameters, data) {
|
|
246
|
+
const dataIn = new Uint8ClampedArray(this._remainder.length + data.length);
|
|
247
|
+
dataIn.set(this._remainder, 0);
|
|
248
|
+
dataIn.set(data, this._remainder.length);
|
|
249
|
+
// line count, number of full available lines
|
|
250
|
+
const lc = Math.floor(dataIn.length / parameters.bytes_per_line);
|
|
251
|
+
if (!lc) {
|
|
252
|
+
// not enough data for a full line
|
|
253
|
+
this._remainder = dataIn;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const dataOut = new Uint8ClampedArray(lc * parameters.pixels_per_line * 4); // RGBA (8-bit)
|
|
257
|
+
let ik = 0; // consumed data on dataIn
|
|
258
|
+
if (parameters.depth === 8) {
|
|
259
|
+
if (parameters.format === _1.SANEFrame.GRAY) {
|
|
260
|
+
// 8-bit / 1 channel
|
|
261
|
+
for (let l = 0, o = 0; l < lc; l++, ik += parameters.bytes_per_line) { // for lines
|
|
262
|
+
for (let p = 0, i = ik; p < parameters.pixels_per_line; p++, i++, o += 4) { // for pixels
|
|
263
|
+
dataOut[o] = dataOut[o + 1] = dataOut[o + 2] = dataIn[i];
|
|
264
|
+
dataOut[o + 3] = 0xff; // alpha
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
// 8-bit / 3 channels
|
|
270
|
+
for (let l = 0, o = 0; l < lc; l++, ik += parameters.bytes_per_line) { // for lines
|
|
271
|
+
for (let p = 0, i = ik; p < parameters.pixels_per_line; p++, i += 3, o += 4) { // for pixels
|
|
272
|
+
dataOut.set(dataIn.subarray(i, i + 3), o);
|
|
273
|
+
dataOut[o + 3] = 0xff; // alpha
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else { // parameters.depth == 1
|
|
279
|
+
if (parameters.format === _1.SANEFrame.GRAY) {
|
|
280
|
+
// 1-bit / 1 channel
|
|
281
|
+
for (let l = 0, o = 0; l < lc; l++, ik += parameters.bytes_per_line) { // for lines
|
|
282
|
+
for (let p = 0, i = ik; p < parameters.pixels_per_line; i++) { // for in bytes
|
|
283
|
+
for (let mask = 0x80; mask !== 0 && p < parameters.pixels_per_line; mask >>= 1, p++, o += 4) { // for pixels
|
|
284
|
+
dataOut[o] = dataOut[o + 1] = dataOut[o + 2] = dataIn[i] & mask ? 0x00 : 0xff;
|
|
285
|
+
dataOut[o + 3] = 0xff; // alpha
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// 1-bit / 3 channels
|
|
292
|
+
// niche format, probably very few (if any) scanners support this
|
|
293
|
+
// according to the test backend the bits are inverted on this format, if compared
|
|
294
|
+
// with 1-bit gray (1 channel), it remains to be seen if this is correct in the wild
|
|
295
|
+
for (let l = 0, o = 0; l < lc; l++, ik += parameters.bytes_per_line) { // for lines
|
|
296
|
+
for (let p = 0, i = ik; p < parameters.pixels_per_line; i += 3) { // for in bytes
|
|
297
|
+
for (let mask = 0x80; mask !== 0 && p < parameters.pixels_per_line; mask >>= 1, p++, o += 4) { // for pixels
|
|
298
|
+
// rbg is interlaced byte-by-byte not bit-by-bit
|
|
299
|
+
// so 3 bytes have the R-G-B for 8 pixels
|
|
300
|
+
dataOut[o] = dataIn[i] & mask ? 0xff : 0x00;
|
|
301
|
+
dataOut[o + 1] = dataIn[i + 1] & mask ? 0xff : 0x00;
|
|
302
|
+
dataOut[o + 2] = dataIn[i + 2] & mask ? 0xff : 0x00;
|
|
303
|
+
dataOut[o + 3] = 0xff; // alpha
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
this._remainder = dataIn.subarray(ik);
|
|
310
|
+
this.fire('line', parameters, dataOut, this._line);
|
|
311
|
+
this._allData.set(dataOut, this._line * parameters.pixels_per_line * 4);
|
|
312
|
+
this._line += lc;
|
|
313
|
+
}
|
|
314
|
+
_onStop(parameters, error) {
|
|
315
|
+
if (!error) {
|
|
316
|
+
this.fire('image', parameters, this._allData);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.ScanImageReader = ScanImageReader;
|