capacitor-card-scanner 0.3.2 → 0.3.3
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 -13
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardDetector.java +65 -13
- package/android/src/main/java/pt/tcgclassifier/cardscanner/CardScannerPlugin.java +22 -0
- package/dist/docs.json +36 -0
- package/dist/esm/definitions.d.ts +19 -0
- package/dist/esm/definitions.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -328,19 +328,22 @@ requestPermissions() => Promise<PermissionStatus>
|
|
|
328
328
|
|
|
329
329
|
#### DetectionOptions
|
|
330
330
|
|
|
331
|
-
| Prop
|
|
332
|
-
|
|
|
333
|
-
| **`intervalMs`**
|
|
334
|
-
| **`stableFrames`**
|
|
335
|
-
| **`includeImage`**
|
|
336
|
-
| **`cardAspectRatio`**
|
|
337
|
-
| **`aspectTolerance`**
|
|
338
|
-
| **`minCardArea`**
|
|
339
|
-
| **`maxCardArea`**
|
|
340
|
-
| **`requireCard`**
|
|
341
|
-
| **`debug`**
|
|
342
|
-
| **`showRejected`**
|
|
343
|
-
| **`repeatAfterMs`**
|
|
331
|
+
| Prop | Type | Description | Default |
|
|
332
|
+
| ---------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
333
|
+
| **`intervalMs`** | <code>number</code> | Minimum time between two OCR runs, in milliseconds. The red rectangle is updated on every frame regardless. | <code>300</code> |
|
|
334
|
+
| **`stableFrames`** | <code>number</code> | How many consecutive OCR runs must read the same card before `cardDetected` fires. | <code>2</code> |
|
|
335
|
+
| **`includeImage`** | <code>boolean</code> | Include a JPEG of the detected card in the event. | <code>false</code> |
|
|
336
|
+
| **`cardAspectRatio`** | <code>number</code> | Expected width / height of an upright card. Only upright cards are detected; a card lying sideways falls outside the tolerance. | <code>0.716 (63 x 88 mm)</code> |
|
|
337
|
+
| **`aspectTolerance`** | <code>number</code> | Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%). Increase it if tilted cards are not detected. | <code>0.15</code> |
|
|
338
|
+
| **`minCardArea`** | <code>number</code> | Smallest card size, as a fraction of the camera frame (0-1). | <code>0.08</code> |
|
|
339
|
+
| **`maxCardArea`** | <code>number</code> | Largest card size, as a fraction of the camera frame (0-1). | <code>0.9</code> |
|
|
340
|
+
| **`requireCard`** | <code>boolean</code> | Only read text when a card-shaped object is visible. When false, the whole frame is read if no card is found. | <code>true</code> |
|
|
341
|
+
| **`debug`** | <code>boolean</code> | Every object found is always outlined: red when accepted as a card, grey when rejected. With `debug`, each outline is also labelled with its area (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to tune the filters above. | <code>false</code> |
|
|
342
|
+
| **`showRejected`** | <code>boolean</code> | Outline (in grey) the objects that were not accepted as a card. The accepted card is always outlined in red. | <code>true</code> |
|
|
343
|
+
| **`repeatAfterMs`** | <code>number</code> | How long (ms) the card must be out of view before the same card can be reported again. While a card stays in view it is reported only once. | <code>1500</code> |
|
|
344
|
+
| **`nameLines`** | <code>number</code> | The card name is looked for only in the first N lines of text on the card, top to bottom. | <code>3</code> |
|
|
345
|
+
| **`reservedWords`** | <code>string[]</code> | Words/phrases that are never the card name (card types, subtypes...). A line made only of these words is skipped, so "Trainer" or "Stage 1" are ignored but "Energy Retrieval" is still a name. Replaces the defaults. | <code>['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']</code> |
|
|
346
|
+
| **`reservedPrefixes`** | <code>string[]</code> | Lines starting with any of these are never the card name. Replaces the defaults. | <code>['Evolves from', 'HP']</code> |
|
|
344
347
|
|
|
345
348
|
|
|
346
349
|
#### PluginListenerHandle
|
|
@@ -24,7 +24,10 @@ import com.google.mlkit.vision.text.TextRecognition;
|
|
|
24
24
|
import com.google.mlkit.vision.text.TextRecognizer;
|
|
25
25
|
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
|
|
26
26
|
import java.io.ByteArrayOutputStream;
|
|
27
|
+
import java.text.Normalizer;
|
|
27
28
|
import java.util.ArrayList;
|
|
29
|
+
import java.util.Arrays;
|
|
30
|
+
import java.util.Comparator;
|
|
28
31
|
import java.util.List;
|
|
29
32
|
import java.util.Locale;
|
|
30
33
|
import java.util.regex.Matcher;
|
|
@@ -66,6 +69,18 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
66
69
|
public boolean showRejected = true;
|
|
67
70
|
/** How long the card must be out of view before the same card can be reported again. */
|
|
68
71
|
public long repeatAfterMs = 1500;
|
|
72
|
+
/** The name is looked for only in the first N lines of the card (top to bottom). */
|
|
73
|
+
public int nameLines = 3;
|
|
74
|
+
/**
|
|
75
|
+
* Words/phrases that are never the card name (card types, subtypes...).
|
|
76
|
+
* A line made only of these words is skipped; "Energy Retrieval" is still a name.
|
|
77
|
+
*/
|
|
78
|
+
public List<String> reservedWords = Arrays.asList(
|
|
79
|
+
"Basic", "Stage 1", "Stage 2", "Trainer", "Item", "Supporter", "Stadium",
|
|
80
|
+
"Energy", "Special Energy", "Basic Energy", "Pokémon", "Pokémon Tool"
|
|
81
|
+
);
|
|
82
|
+
/** Lines starting with any of these are never the card name. */
|
|
83
|
+
public List<String> reservedPrefixes = Arrays.asList("Evolves from", "HP");
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
/** An object found by ML Kit, with the values the card filters were checked against. */
|
|
@@ -104,10 +119,6 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
104
119
|
|
|
105
120
|
// e.g. "025/165", "SV001/SV122", "TG05/TG30", "GG01/GG70"
|
|
106
121
|
private static final Pattern NUMBER = Pattern.compile("\\b([A-Za-z]{0,4}\\d{1,3}[a-z]?)\\s*/\\s*([A-Za-z]{0,4}\\d{1,3})\\b");
|
|
107
|
-
private static final Pattern HEADER = Pattern.compile(
|
|
108
|
-
"^(basic|stage\\s*\\d|trainer|item|supporter|stadium|energy|pok[eé]mon|evolves|hp\\b)",
|
|
109
|
-
Pattern.CASE_INSENSITIVE
|
|
110
|
-
);
|
|
111
122
|
private static final Pattern HP_SUFFIX = Pattern.compile("\\s*HP\\s*\\d+\\s*$", Pattern.CASE_INSENSITIVE);
|
|
112
123
|
private static final Pattern LETTER = Pattern.compile("\\p{L}");
|
|
113
124
|
/** Names at least this similar are treated as the same card. */
|
|
@@ -121,6 +132,9 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
121
132
|
private final TextRecognizer recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
|
|
122
133
|
private final Options options;
|
|
123
134
|
private final Listener listener;
|
|
135
|
+
/** reservedWords / reservedPrefixes, normalized; words longest first. */
|
|
136
|
+
private final List<String> reservedWords = new ArrayList<>();
|
|
137
|
+
private final List<String> reservedPrefixes = new ArrayList<>();
|
|
124
138
|
private volatile BoxListener boxListener;
|
|
125
139
|
|
|
126
140
|
// Only touched from the analyzer thread.
|
|
@@ -136,7 +150,18 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
136
150
|
public CardDetector(Options options, Listener listener) {
|
|
137
151
|
this.options = options;
|
|
138
152
|
this.options.stableFrames = Math.max(1, options.stableFrames);
|
|
153
|
+
this.options.nameLines = Math.max(1, options.nameLines);
|
|
139
154
|
this.listener = listener;
|
|
155
|
+
for (String w : options.reservedWords) {
|
|
156
|
+
String n = normalize(w);
|
|
157
|
+
if (!n.isEmpty()) reservedWords.add(n);
|
|
158
|
+
}
|
|
159
|
+
// Longest first, so "Pokémon Tool" is removed before "Pokémon".
|
|
160
|
+
reservedWords.sort(Comparator.comparingInt(String::length).reversed());
|
|
161
|
+
for (String p : options.reservedPrefixes) {
|
|
162
|
+
String n = normalize(p);
|
|
163
|
+
if (!n.isEmpty()) reservedPrefixes.add(n);
|
|
164
|
+
}
|
|
140
165
|
}
|
|
141
166
|
|
|
142
167
|
public boolean isDebug() {
|
|
@@ -257,8 +282,7 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
257
282
|
float h = region.getHeight();
|
|
258
283
|
JSArray lines = new JSArray();
|
|
259
284
|
String number = null;
|
|
260
|
-
|
|
261
|
-
int nameTop = Integer.MAX_VALUE;
|
|
285
|
+
List<Text.Line> ordered = new ArrayList<>();
|
|
262
286
|
|
|
263
287
|
for (Text.TextBlock block : text.getTextBlocks()) {
|
|
264
288
|
for (Text.Line line : block.getLines()) {
|
|
@@ -275,12 +299,10 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
275
299
|
Matcher m = NUMBER.matcher(s);
|
|
276
300
|
if (m.find()) number = m.group(1) + "/" + m.group(2);
|
|
277
301
|
}
|
|
278
|
-
|
|
279
|
-
name = HP_SUFFIX.matcher(s).replaceAll("").trim();
|
|
280
|
-
nameTop = r.top;
|
|
281
|
-
}
|
|
302
|
+
ordered.add(line);
|
|
282
303
|
}
|
|
283
304
|
}
|
|
305
|
+
String name = findName(ordered);
|
|
284
306
|
|
|
285
307
|
String key = number != null ? number : name;
|
|
286
308
|
if (key == null) {
|
|
@@ -356,11 +378,41 @@ public class CardDetector implements ImageAnalysis.Analyzer {
|
|
|
356
378
|
return box;
|
|
357
379
|
}
|
|
358
380
|
|
|
359
|
-
|
|
360
|
-
|
|
381
|
+
/** First line, among the top {@code nameLines}, that is not made of reserved words. */
|
|
382
|
+
@Nullable
|
|
383
|
+
private String findName(List<Text.Line> lines) {
|
|
384
|
+
lines.sort(Comparator.comparingInt(l -> l.getBoundingBox().top));
|
|
385
|
+
for (int i = 0; i < lines.size() && i < options.nameLines; i++) {
|
|
386
|
+
String s = HP_SUFFIX.matcher(lines.get(i).getText().trim()).replaceAll("").trim();
|
|
387
|
+
if (!isReserved(s) && countLetters(s) >= 3) return s;
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private boolean isReserved(String line) {
|
|
393
|
+
String rest = " " + normalize(line) + " ";
|
|
394
|
+
for (String w : reservedWords) {
|
|
395
|
+
rest = rest.replace(" " + w + " ", " ");
|
|
396
|
+
}
|
|
397
|
+
rest = rest.trim();
|
|
398
|
+
if (countLetters(rest) < 3) return true;
|
|
399
|
+
// Checked after removing reserved words, so "STAGE 1 Evolves from Pichu" is caught too.
|
|
400
|
+
for (String p : reservedPrefixes) {
|
|
401
|
+
if (rest.equals(p) || rest.startsWith(p + " ")) return true;
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Upper case, no accents, only letters/digits separated by single spaces. */
|
|
407
|
+
private static String normalize(String s) {
|
|
408
|
+
String n = Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("\\p{M}", "");
|
|
409
|
+
return n.toUpperCase(Locale.ROOT).replaceAll("[^\\p{L}\\p{Nd}]+", " ").trim();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private static int countLetters(String s) {
|
|
361
413
|
Matcher m = LETTER.matcher(s);
|
|
362
414
|
int letters = 0;
|
|
363
415
|
while (m.find()) letters++;
|
|
364
|
-
return letters
|
|
416
|
+
return letters;
|
|
365
417
|
}
|
|
366
418
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
package pt.tcgclassifier.cardscanner;
|
|
2
2
|
|
|
3
3
|
import android.Manifest;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
import com.getcapacitor.JSArray;
|
|
4
6
|
import com.getcapacitor.JSObject;
|
|
5
7
|
import com.getcapacitor.PermissionState;
|
|
6
8
|
import com.getcapacitor.Plugin;
|
|
@@ -9,6 +11,8 @@ import com.getcapacitor.PluginMethod;
|
|
|
9
11
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
10
12
|
import com.getcapacitor.annotation.Permission;
|
|
11
13
|
import com.getcapacitor.annotation.PermissionCallback;
|
|
14
|
+
import java.util.ArrayList;
|
|
15
|
+
import java.util.List;
|
|
12
16
|
|
|
13
17
|
@CapacitorPlugin(name = "CardScanner", permissions = { @Permission(strings = { Manifest.permission.CAMERA }, alias = CardScannerPlugin.CAMERA) })
|
|
14
18
|
public class CardScannerPlugin extends Plugin {
|
|
@@ -103,6 +107,11 @@ public class CardScannerPlugin extends Plugin {
|
|
|
103
107
|
options.debug = call.getBoolean("debug", options.debug);
|
|
104
108
|
options.showRejected = call.getBoolean("showRejected", options.showRejected);
|
|
105
109
|
options.repeatAfterMs = call.getInt("repeatAfterMs", (int) options.repeatAfterMs);
|
|
110
|
+
options.nameLines = call.getInt("nameLines", options.nameLines);
|
|
111
|
+
List<String> reservedWords = stringList(call, "reservedWords");
|
|
112
|
+
if (reservedWords != null) options.reservedWords = reservedWords;
|
|
113
|
+
List<String> reservedPrefixes = stringList(call, "reservedPrefixes");
|
|
114
|
+
if (reservedPrefixes != null) options.reservedPrefixes = reservedPrefixes;
|
|
106
115
|
|
|
107
116
|
CardDetector detector = new CardDetector(options, result -> notifyListeners("cardDetected", result));
|
|
108
117
|
getActivity().runOnUiThread(() -> {
|
|
@@ -143,6 +152,19 @@ public class CardScannerPlugin extends Plugin {
|
|
|
143
152
|
call.resolve();
|
|
144
153
|
}
|
|
145
154
|
|
|
155
|
+
/** A string array option, or null when it was not passed. */
|
|
156
|
+
@Nullable
|
|
157
|
+
private static List<String> stringList(PluginCall call, String key) {
|
|
158
|
+
JSArray array = call.getArray(key, null);
|
|
159
|
+
if (array == null) return null;
|
|
160
|
+
List<String> list = new ArrayList<>();
|
|
161
|
+
for (int i = 0; i < array.length(); i++) {
|
|
162
|
+
String s = array.optString(i, null);
|
|
163
|
+
if (s != null) list.add(s);
|
|
164
|
+
}
|
|
165
|
+
return list;
|
|
166
|
+
}
|
|
167
|
+
|
|
146
168
|
private boolean ensureRunning(PluginCall call) {
|
|
147
169
|
if (!implementation.isRunning()) {
|
|
148
170
|
call.reject("Camera is not running", "NOT_RUNNING");
|
package/dist/docs.json
CHANGED
|
@@ -432,6 +432,42 @@
|
|
|
432
432
|
"docs": "How long (ms) the card must be out of view before the same card can be\nreported again. While a card stays in view it is reported only once.",
|
|
433
433
|
"complexTypes": [],
|
|
434
434
|
"type": "number | undefined"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"name": "nameLines",
|
|
438
|
+
"tags": [
|
|
439
|
+
{
|
|
440
|
+
"text": "3",
|
|
441
|
+
"name": "default"
|
|
442
|
+
}
|
|
443
|
+
],
|
|
444
|
+
"docs": "The card name is looked for only in the first N lines of text on the\ncard, top to bottom.",
|
|
445
|
+
"complexTypes": [],
|
|
446
|
+
"type": "number | undefined"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
"name": "reservedWords",
|
|
450
|
+
"tags": [
|
|
451
|
+
{
|
|
452
|
+
"text": "['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']",
|
|
453
|
+
"name": "default"
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
"docs": "Words/phrases that are never the card name (card types, subtypes...).\nA line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\nignored but \"Energy Retrieval\" is still a name. Replaces the defaults.",
|
|
457
|
+
"complexTypes": [],
|
|
458
|
+
"type": "string[] | undefined"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"name": "reservedPrefixes",
|
|
462
|
+
"tags": [
|
|
463
|
+
{
|
|
464
|
+
"text": "['Evolves from', 'HP']",
|
|
465
|
+
"name": "default"
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"docs": "Lines starting with any of these are never the card name.\nReplaces the defaults.",
|
|
469
|
+
"complexTypes": [],
|
|
470
|
+
"type": "string[] | undefined"
|
|
435
471
|
}
|
|
436
472
|
]
|
|
437
473
|
},
|
|
@@ -96,6 +96,25 @@ export interface DetectionOptions {
|
|
|
96
96
|
* @default 1500
|
|
97
97
|
*/
|
|
98
98
|
repeatAfterMs?: number;
|
|
99
|
+
/**
|
|
100
|
+
* The card name is looked for only in the first N lines of text on the
|
|
101
|
+
* card, top to bottom.
|
|
102
|
+
* @default 3
|
|
103
|
+
*/
|
|
104
|
+
nameLines?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Words/phrases that are never the card name (card types, subtypes...).
|
|
107
|
+
* A line made only of these words is skipped, so "Trainer" or "Stage 1" are
|
|
108
|
+
* ignored but "Energy Retrieval" is still a name. Replaces the defaults.
|
|
109
|
+
* @default ['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']
|
|
110
|
+
*/
|
|
111
|
+
reservedWords?: string[];
|
|
112
|
+
/**
|
|
113
|
+
* Lines starting with any of these are never the card name.
|
|
114
|
+
* Replaces the defaults.
|
|
115
|
+
* @default ['Evolves from', 'HP']
|
|
116
|
+
*/
|
|
117
|
+
reservedPrefixes?: string[];
|
|
99
118
|
}
|
|
100
119
|
/** Normalized rectangle (0-1). */
|
|
101
120
|
export interface Box {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\nexport type CameraPosition = 'rear' | 'front';\n\nexport interface StartOptions {\n /**\n * Which camera to use.\n * @default 'rear'\n */\n position?: CameraPosition;\n}\n\nexport interface CaptureOptions {\n /**\n * JPEG quality (0-100).\n * @default 85\n */\n quality?: number;\n}\n\nexport interface CaptureResult {\n /** Base64 encoded JPEG (without the `data:` prefix). */\n base64: string;\n width: number;\n height: number;\n}\n\nexport interface TorchOptions {\n enabled: boolean;\n}\n\nexport interface ZoomOptions {\n /** Zoom ratio, clamped between the camera's min and max. */\n ratio: number;\n}\n\nexport interface PermissionStatus {\n camera: PermissionState;\n}\n\nexport interface DetectionOptions {\n /**\n * Minimum time between two OCR runs, in milliseconds.\n * The red rectangle is updated on every frame regardless.\n * @default 300\n */\n intervalMs?: number;\n /**\n * How many consecutive OCR runs must read the same card before\n * `cardDetected` fires.\n * @default 2\n */\n stableFrames?: number;\n /**\n * Include a JPEG of the detected card in the event.\n * @default false\n */\n includeImage?: boolean;\n /**\n * Expected width / height of an upright card. Only upright cards are\n * detected; a card lying sideways falls outside the tolerance.\n * @default 0.716 (63 x 88 mm)\n */\n cardAspectRatio?: number;\n /**\n * Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\n * Increase it if tilted cards are not detected.\n * @default 0.15\n */\n aspectTolerance?: number;\n /**\n * Smallest card size, as a fraction of the camera frame (0-1).\n * @default 0.08\n */\n minCardArea?: number;\n /**\n * Largest card size, as a fraction of the camera frame (0-1).\n * @default 0.9\n */\n maxCardArea?: number;\n /**\n * Only read text when a card-shaped object is visible. When false, the whole\n * frame is read if no card is found.\n * @default true\n */\n requireCard?: boolean;\n /**\n * Every object found is always outlined: red when accepted as a card, grey\n * when rejected. With `debug`, each outline is also labelled with its area\n * (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\n * tune the filters above.\n * @default false\n */\n debug?: boolean;\n /**\n * Outline (in grey) the objects that were not accepted as a card.\n * The accepted card is always outlined in red.\n * @default true\n */\n showRejected?: boolean;\n /**\n * How long (ms) the card must be out of view before the same card can be\n * reported again. While a card stays in view it is reported only once.\n * @default 1500\n */\n repeatAfterMs?: number;\n}\n\n/** Normalized rectangle (0-1). */\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface TextLine {\n text: string;\n /** Position inside the card image. */\n box: Box;\n}\n\nexport interface CardDetectedEvent {\n /** Best guess for the card name (topmost line of text on the card). */\n name?: string;\n /** Collector number, e.g. `025/165`. */\n collectorNumber?: string;\n /** All text read on the card. */\n text: string;\n lines: TextLine[];\n /** Where the card is in the camera frame. Missing if no card outline was found. */\n cardBox?: Box;\n /** Base64 JPEG of the card, when `includeImage` is true. */\n image?: string;\n}\n\nexport interface CardScannerPlugin {\n /**\n * Opens the camera **behind** the WebView. The WebView background is made\n * transparent, so the HTML page must also use transparent backgrounds\n * where the camera should be visible.\n */\n start(options?: StartOptions): Promise<void>;\n\n /** Stops the camera and removes the preview. */\n stop(): Promise<void>;\n\n /** Takes a photo from the running camera. */\n capture(options?: CaptureOptions): Promise<CaptureResult>;\n\n /** Turns the flashlight on/off. */\n setTorch(options: TorchOptions): Promise<void>;\n\n /** Sets the zoom ratio. */\n setZoom(options: ZoomOptions): Promise<void>;\n\n /** Switches between the rear and front camera. */\n flip(): Promise<void>;\n\n /**\n * Starts looking for cards: draws a red rectangle around the card and reads\n * its text, emitting `cardDetected`. Requires `start()` first. Android only.\n */\n startDetection(options?: DetectionOptions): Promise<void>;\n\n /** Stops card detection and hides the rectangle. */\n stopDetection(): Promise<void>;\n\n /** Fired once per card, when its text has been read consistently. */\n addListener(eventName: 'cardDetected', listener: (event: CardDetectedEvent) => void): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n\n checkPermissions(): Promise<PermissionStatus>;\n\n requestPermissions(): Promise<PermissionStatus>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\nexport type CameraPosition = 'rear' | 'front';\n\nexport interface StartOptions {\n /**\n * Which camera to use.\n * @default 'rear'\n */\n position?: CameraPosition;\n}\n\nexport interface CaptureOptions {\n /**\n * JPEG quality (0-100).\n * @default 85\n */\n quality?: number;\n}\n\nexport interface CaptureResult {\n /** Base64 encoded JPEG (without the `data:` prefix). */\n base64: string;\n width: number;\n height: number;\n}\n\nexport interface TorchOptions {\n enabled: boolean;\n}\n\nexport interface ZoomOptions {\n /** Zoom ratio, clamped between the camera's min and max. */\n ratio: number;\n}\n\nexport interface PermissionStatus {\n camera: PermissionState;\n}\n\nexport interface DetectionOptions {\n /**\n * Minimum time between two OCR runs, in milliseconds.\n * The red rectangle is updated on every frame regardless.\n * @default 300\n */\n intervalMs?: number;\n /**\n * How many consecutive OCR runs must read the same card before\n * `cardDetected` fires.\n * @default 2\n */\n stableFrames?: number;\n /**\n * Include a JPEG of the detected card in the event.\n * @default false\n */\n includeImage?: boolean;\n /**\n * Expected width / height of an upright card. Only upright cards are\n * detected; a card lying sideways falls outside the tolerance.\n * @default 0.716 (63 x 88 mm)\n */\n cardAspectRatio?: number;\n /**\n * Allowed relative deviation from `cardAspectRatio` (0.15 = ±15%).\n * Increase it if tilted cards are not detected.\n * @default 0.15\n */\n aspectTolerance?: number;\n /**\n * Smallest card size, as a fraction of the camera frame (0-1).\n * @default 0.08\n */\n minCardArea?: number;\n /**\n * Largest card size, as a fraction of the camera frame (0-1).\n * @default 0.9\n */\n maxCardArea?: number;\n /**\n * Only read text when a card-shaped object is visible. When false, the whole\n * frame is read if no card is found.\n * @default true\n */\n requireCard?: boolean;\n /**\n * Every object found is always outlined: red when accepted as a card, grey\n * when rejected. With `debug`, each outline is also labelled with its area\n * (`a`), width/height ratio (`r`) and the filter that rejected it. Use it to\n * tune the filters above.\n * @default false\n */\n debug?: boolean;\n /**\n * Outline (in grey) the objects that were not accepted as a card.\n * The accepted card is always outlined in red.\n * @default true\n */\n showRejected?: boolean;\n /**\n * How long (ms) the card must be out of view before the same card can be\n * reported again. While a card stays in view it is reported only once.\n * @default 1500\n */\n repeatAfterMs?: number;\n /**\n * The card name is looked for only in the first N lines of text on the\n * card, top to bottom.\n * @default 3\n */\n nameLines?: number;\n /**\n * Words/phrases that are never the card name (card types, subtypes...).\n * A line made only of these words is skipped, so \"Trainer\" or \"Stage 1\" are\n * ignored but \"Energy Retrieval\" is still a name. Replaces the defaults.\n * @default ['Basic', 'Stage 1', 'Stage 2', 'Trainer', 'Item', 'Supporter', 'Stadium', 'Energy', 'Special Energy', 'Basic Energy', 'Pokémon', 'Pokémon Tool']\n */\n reservedWords?: string[];\n /**\n * Lines starting with any of these are never the card name.\n * Replaces the defaults.\n * @default ['Evolves from', 'HP']\n */\n reservedPrefixes?: string[];\n}\n\n/** Normalized rectangle (0-1). */\nexport interface Box {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface TextLine {\n text: string;\n /** Position inside the card image. */\n box: Box;\n}\n\nexport interface CardDetectedEvent {\n /** Best guess for the card name (topmost line of text on the card). */\n name?: string;\n /** Collector number, e.g. `025/165`. */\n collectorNumber?: string;\n /** All text read on the card. */\n text: string;\n lines: TextLine[];\n /** Where the card is in the camera frame. Missing if no card outline was found. */\n cardBox?: Box;\n /** Base64 JPEG of the card, when `includeImage` is true. */\n image?: string;\n}\n\nexport interface CardScannerPlugin {\n /**\n * Opens the camera **behind** the WebView. The WebView background is made\n * transparent, so the HTML page must also use transparent backgrounds\n * where the camera should be visible.\n */\n start(options?: StartOptions): Promise<void>;\n\n /** Stops the camera and removes the preview. */\n stop(): Promise<void>;\n\n /** Takes a photo from the running camera. */\n capture(options?: CaptureOptions): Promise<CaptureResult>;\n\n /** Turns the flashlight on/off. */\n setTorch(options: TorchOptions): Promise<void>;\n\n /** Sets the zoom ratio. */\n setZoom(options: ZoomOptions): Promise<void>;\n\n /** Switches between the rear and front camera. */\n flip(): Promise<void>;\n\n /**\n * Starts looking for cards: draws a red rectangle around the card and reads\n * its text, emitting `cardDetected`. Requires `start()` first. Android only.\n */\n startDetection(options?: DetectionOptions): Promise<void>;\n\n /** Stops card detection and hides the rectangle. */\n stopDetection(): Promise<void>;\n\n /** Fired once per card, when its text has been read consistently. */\n addListener(eventName: 'cardDetected', listener: (event: CardDetectedEvent) => void): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n\n checkPermissions(): Promise<PermissionStatus>;\n\n requestPermissions(): Promise<PermissionStatus>;\n}\n"]}
|