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 CHANGED
@@ -328,19 +328,22 @@ requestPermissions() => Promise<PermissionStatus>
328
328
 
329
329
  #### DetectionOptions
330
330
 
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> |
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
- String name = null;
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
- if (r.top < nameTop && isNameCandidate(s)) {
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
- private static boolean isNameCandidate(String s) {
360
- if (HEADER.matcher(s).find()) return false;
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 >= 3;
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-card-scanner",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Recognize a pokemon card with camera open in background",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",