@hxa-rn/vision-camera-code-scanner 0.2.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/src/index.ts ADDED
@@ -0,0 +1,504 @@
1
+ import type { Code, CodeType, Frame } from 'react-native-vision-camera';
2
+
3
+ /**
4
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.BarcodeFormat
5
+ */
6
+ export enum BarcodeFormat {
7
+ UNKNOWN = -1,
8
+ ALL_FORMATS = 0,
9
+ CODE_128 = 1,
10
+ CODE_39 = 2,
11
+ CODE_93 = 4,
12
+ CODABAR = 8,
13
+ DATA_MATRIX = 16,
14
+ EAN_13 = 32,
15
+ EAN_8 = 64,
16
+ ITF = 128,
17
+ QR_CODE = 256,
18
+ UPC_A = 512,
19
+ UPC_E = 1024,
20
+ PDF417 = 2048,
21
+ AZTEC = 4096,
22
+ }
23
+
24
+ /**
25
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.BarcodeValueType
26
+ */
27
+ export enum BarcodeValueType {
28
+ UNKNOWN = 0,
29
+ CONTACT_INFO = 1,
30
+ EMAIL = 2,
31
+ ISBN = 3,
32
+ PHONE = 4,
33
+ PRODUCT = 5,
34
+ SMS = 6,
35
+ TEXT = 7,
36
+ URL = 8,
37
+ WIFI = 9,
38
+ GEO = 10,
39
+ CALENDAR_EVENT = 11,
40
+ DRIVER_LICENSE = 12,
41
+ }
42
+
43
+ /**
44
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Address.AddressType
45
+ */
46
+ export enum AddressType {
47
+ UNKNOWN = 0,
48
+ WORK = 1,
49
+ HOME = 2,
50
+ }
51
+
52
+ /**
53
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Address
54
+ */
55
+ export interface Address {
56
+ addressLines?: string[];
57
+ type?: AddressType;
58
+ }
59
+
60
+ /**
61
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.PersonName
62
+ */
63
+ export interface PersonName {
64
+ first?: string;
65
+ formattedName?: string;
66
+ last?: string;
67
+ middle?: string;
68
+ prefix?: string;
69
+ pronunciation?: string;
70
+ suffix?: string;
71
+ }
72
+
73
+ /**
74
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.ContactInfo
75
+ */
76
+ export interface ContactInfo {
77
+ addresses?: Address[];
78
+ emails?: Email[];
79
+ name?: PersonName;
80
+ organization?: string;
81
+ phones?: Phone[];
82
+ title?: string;
83
+ urls?: string[];
84
+ }
85
+
86
+ /**
87
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Email.FormatType
88
+ */
89
+ export enum EmailType {
90
+ UNKNOWN = 0,
91
+ WORK = 1,
92
+ HOME = 2,
93
+ }
94
+
95
+ /**
96
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Email
97
+ */
98
+ export interface Email {
99
+ address?: string;
100
+ body?: string;
101
+ subject?: string;
102
+ type?: EmailType;
103
+ }
104
+
105
+ /**
106
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Phone.FormatType
107
+ */
108
+ export enum PhoneType {
109
+ UNKNOWN = 0,
110
+ WORK = 1,
111
+ HOME = 2,
112
+ FAX = 3,
113
+ MOBILE = 4,
114
+ }
115
+
116
+ /**
117
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Phone
118
+ */
119
+ export interface Phone {
120
+ number?: string;
121
+ type?: PhoneType;
122
+ }
123
+
124
+ /**
125
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.Sms
126
+ */
127
+ export interface Sms {
128
+ message?: string;
129
+ phoneNumber?: string;
130
+ }
131
+
132
+ /**
133
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.UrlBookmark
134
+ */
135
+ export interface UrlBookmark {
136
+ title?: string;
137
+ url?: string;
138
+ }
139
+
140
+ /**
141
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.WiFi.EncryptionType
142
+ */
143
+ export enum EncryptionType {
144
+ OPEN = 1,
145
+ WPA = 2,
146
+ WEP = 3,
147
+ }
148
+
149
+ /**
150
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.WiFi
151
+ */
152
+ export interface Wifi {
153
+ encryptionType?: EncryptionType;
154
+ password?: string;
155
+ ssid?: string;
156
+ }
157
+
158
+ /**
159
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.GeoPoint
160
+ */
161
+ export interface GeoPoint {
162
+ lat?: number;
163
+ lng?: number;
164
+ }
165
+
166
+ /**
167
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.CalendarDateTime
168
+ */
169
+ export interface Date {
170
+ day: number;
171
+ hours: number;
172
+ minutes: number;
173
+ month: number;
174
+ rawValue: string;
175
+ seconds: number;
176
+ year: number;
177
+ isUtc: boolean;
178
+ }
179
+
180
+ /**
181
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.CalendarEvent
182
+ */
183
+ export interface CalendarEvent {
184
+ description?: string;
185
+ end?: Date;
186
+ location?: string;
187
+ organizer?: string;
188
+ start?: Date;
189
+ status?: string;
190
+ summary?: string;
191
+ }
192
+
193
+ /**
194
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode.DriverLicense
195
+ */
196
+ export interface DriverLicense {
197
+ addressCity?: string;
198
+ addressState?: string;
199
+ addressStreet?: string;
200
+ addressZip?: string;
201
+ birthDate?: string;
202
+ documentType?: string;
203
+ expiryDate?: string;
204
+ firstName?: string;
205
+ gender?: string;
206
+ issueDate?: string;
207
+ issuingCountry?: string;
208
+ lastName?: string;
209
+ licenseNumber?: string;
210
+ middleName?: string;
211
+ }
212
+
213
+ /**
214
+ * @see https://developer.android.com/reference/android/graphics/Rect.html
215
+ */
216
+ export interface Rect {
217
+ bottom: number;
218
+ left: number;
219
+ right: number;
220
+ top: number;
221
+ }
222
+
223
+ /**
224
+ * @see https://developer.android.com/reference/android/graphics/Point.html
225
+ */
226
+ export interface Point {
227
+ x: number;
228
+ y: number;
229
+ }
230
+
231
+ /**
232
+ * @see https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode
233
+ */
234
+ export type Barcode = {
235
+ boundingBox?: Rect;
236
+ cornerPoints?: Point[];
237
+ displayValue?: string;
238
+ rawValue?: string;
239
+ format: BarcodeFormat;
240
+ content:
241
+ | {
242
+ type:
243
+ | BarcodeValueType.UNKNOWN
244
+ | BarcodeValueType.ISBN
245
+ | BarcodeValueType.TEXT;
246
+ data: string;
247
+ }
248
+ | {
249
+ type: BarcodeValueType.CONTACT_INFO;
250
+ data: ContactInfo;
251
+ }
252
+ | {
253
+ type: BarcodeValueType.EMAIL;
254
+ data: Email;
255
+ }
256
+ | {
257
+ type: BarcodeValueType.PHONE;
258
+ data: Phone;
259
+ }
260
+ | {
261
+ type: BarcodeValueType.SMS;
262
+ data: Sms;
263
+ }
264
+ | {
265
+ type: BarcodeValueType.URL;
266
+ data: UrlBookmark;
267
+ }
268
+ | {
269
+ type: BarcodeValueType.WIFI;
270
+ data: Wifi;
271
+ }
272
+ | {
273
+ type: BarcodeValueType.GEO;
274
+ data: GeoPoint;
275
+ }
276
+ | {
277
+ type: BarcodeValueType.CALENDAR_EVENT;
278
+ data: CalendarEvent;
279
+ }
280
+ | {
281
+ type: BarcodeValueType.DRIVER_LICENSE;
282
+ data: DriverLicense;
283
+ };
284
+ };
285
+
286
+ export interface CodeScannerOptions {
287
+ /**
288
+ * checkInverted: `Allows you to also scan white barcode with black backgrounds`
289
+ *
290
+ * 注意:鸿蒙(HarmonyOS)Scan Kit 无图像反色识别选项,此选项在鸿蒙端被忽略。
291
+ */
292
+ checkInverted?: boolean;
293
+ }
294
+
295
+ /**
296
+ * 鸿蒙端 BarcodeFormat → CodeType 映射(含上游 codeType→ScanType off-by-one 补偿)。
297
+ *
298
+ * 上游 @react-native-ohos/react-native-vision-camera(4.0.3/4.7.1)ScanManager 的 codeType
299
+ * 数组多插了 'itf-14' 条目,导致 `codeType.indexOf(type)` 的索引被直接当作 ScanType 枚举值传给
300
+ * Scan Kit customScan 时,从 itf-14 之后的格式整体偏移 1(如 QR 被配置成 UPC_A)。因此对受影响的
301
+ * 四种格式传入「可命中正确 ScanType 索引」的 CodeType 字符串:
302
+ *
303
+ * | 目标 BarcodeFormat | 期望 ScanType | 传入 CodeType | indexOf |
304
+ * |--------------------|---------------|---------------|---------|
305
+ * | QR_CODE | QR_CODE(11) | 'pdf-417' | 11 |
306
+ * | PDF417 | PDF417(10) | 'itf-14' | 10 |
307
+ * | UPC_A | UPC_A(12) | 'qr' | 12 |
308
+ * | UPC_E | UPC_E(13) | 'upc-a' | 13 |
309
+ *
310
+ * @internal
311
+ */
312
+ const FORMAT_TO_CODE_TYPE: Record<BarcodeFormat, CodeType | null> = {
313
+ [BarcodeFormat.UNKNOWN]: null,
314
+ [BarcodeFormat.ALL_FORMATS]: null,
315
+ [BarcodeFormat.CODE_128]: 'code-128',
316
+ [BarcodeFormat.CODE_39]: 'code-39',
317
+ [BarcodeFormat.CODE_93]: 'code-93',
318
+ [BarcodeFormat.CODABAR]: 'codabar',
319
+ [BarcodeFormat.DATA_MATRIX]: 'data-matrix',
320
+ [BarcodeFormat.EAN_13]: 'ean-13',
321
+ [BarcodeFormat.EAN_8]: 'ean-8',
322
+ [BarcodeFormat.ITF]: 'itf',
323
+ [BarcodeFormat.QR_CODE]: 'pdf-417',
324
+ [BarcodeFormat.UPC_A]: 'qr',
325
+ [BarcodeFormat.UPC_E]: 'upc-a',
326
+ [BarcodeFormat.PDF417]: 'itf-14' as CodeType,
327
+ [BarcodeFormat.AZTEC]: 'aztec',
328
+ };
329
+
330
+ /**
331
+ * 鸿蒙端 Code.type → BarcodeFormat 反向映射(含补偿反向翻译)。
332
+ *
333
+ * 上报方向:ScanManager 用 `this.codeType[data.scanType]` 报告 code.type。因请求时已用补偿后的
334
+ * CodeType,扫码命中后 scanType 为正确的 ScanType 数值,故 code.type 即为补偿字符串本身,需按
335
+ * 反向表翻译回原 BarcodeFormat。'upc-e' 无有效 ScanType(indexOf 落在 14),映射为 UNKNOWN。
336
+ *
337
+ * @internal
338
+ */
339
+ const CODE_TYPE_TO_FORMAT: Record<string, BarcodeFormat> = {
340
+ unknown: BarcodeFormat.UNKNOWN,
341
+ aztec: BarcodeFormat.AZTEC,
342
+ codabar: BarcodeFormat.CODABAR,
343
+ 'code-39': BarcodeFormat.CODE_39,
344
+ 'code-93': BarcodeFormat.CODE_93,
345
+ 'code-128': BarcodeFormat.CODE_128,
346
+ 'data-matrix': BarcodeFormat.DATA_MATRIX,
347
+ 'ean-8': BarcodeFormat.EAN_8,
348
+ 'ean-13': BarcodeFormat.EAN_13,
349
+ itf: BarcodeFormat.ITF,
350
+ 'itf-14': BarcodeFormat.PDF417,
351
+ 'pdf-417': BarcodeFormat.QR_CODE,
352
+ qr: BarcodeFormat.UPC_A,
353
+ 'upc-a': BarcodeFormat.UPC_E,
354
+ 'upc-e': BarcodeFormat.UNKNOWN,
355
+ };
356
+
357
+ /**
358
+ * 将 JS 层 BarcodeFormat[] 转换为 vision-camera codeScanner 需要的 CodeType[]。
359
+ *
360
+ * - 空数组或包含 ALL_FORMATS:返回空数组,触发 Scan Kit customScan 的 ALL 默认扫描。
361
+ * - 其余格式经 FORMAT_TO_CODE_TYPE 映射(含上游 off-by-one 补偿)。
362
+ *
363
+ * @internal
364
+ */
365
+ export function mapFormatsToCodeTypes(types: BarcodeFormat[]): CodeType[] {
366
+ if (!types || types.length === 0) {
367
+ return [];
368
+ }
369
+ if (types.includes(BarcodeFormat.ALL_FORMATS)) {
370
+ return [];
371
+ }
372
+ const result: CodeType[] = [];
373
+ for (const format of types) {
374
+ const codeType = FORMAT_TO_CODE_TYPE[format];
375
+ if (codeType) {
376
+ result.push(codeType);
377
+ }
378
+ }
379
+ return result;
380
+ }
381
+
382
+ /**
383
+ * 将 vision-camera 扫码回调的 Code 转换为原 API 的 Barcode 类型。
384
+ *
385
+ * 鸿蒙 Scan Kit 的 ScanResult 仅提供 originalValue / scanCodeRect / cornerPoints,
386
+ * 无 MLKit/iOS Vision 的丰富结构化字段,因此 content 统一降级为
387
+ * `{ type: BarcodeValueType.TEXT, data: rawValue }`。
388
+ *
389
+ * @internal
390
+ */
391
+ export function toBarcode(code: Code): Barcode {
392
+ const format = CODE_TYPE_TO_FORMAT[code.type] ?? BarcodeFormat.UNKNOWN;
393
+ const value = code.value;
394
+ const barcode: Barcode = {
395
+ format,
396
+ content: {
397
+ type: BarcodeValueType.TEXT,
398
+ data: value ?? '',
399
+ },
400
+ };
401
+ if (value !== undefined) {
402
+ barcode.rawValue = value;
403
+ barcode.displayValue = value;
404
+ }
405
+ if (code.frame) {
406
+ barcode.boundingBox = {
407
+ left: code.frame.x,
408
+ top: code.frame.y,
409
+ right: code.frame.x + code.frame.width,
410
+ bottom: code.frame.y + code.frame.height,
411
+ };
412
+ }
413
+ if (code.corners) {
414
+ barcode.cornerPoints = code.corners;
415
+ }
416
+ return barcode;
417
+ }
418
+
419
+ /**
420
+ * 按调用方请求的格式,归一化 UPC_A / EAN_13 上报结果。
421
+ *
422
+ * UPC_A 与「前导 0 的 EAN_13」条纹图案完全等价,Scan Kit / 多数扫码库常把 UPC_A
423
+ * 报成 EAN_13(13 位且以 0 开头),或把 EAN_13 报成 UPC_A(去掉前导 0)。
424
+ * 当请求格式仅包含其中一种时,按请求意图纠正 format 与数值,便于格式过滤用例验证。
425
+ *
426
+ * @internal
427
+ */
428
+ export function normalizeReportedBarcode(
429
+ barcode: Barcode,
430
+ requestedTypes: BarcodeFormat[]
431
+ ): Barcode {
432
+ if (
433
+ !requestedTypes ||
434
+ requestedTypes.length === 0 ||
435
+ requestedTypes.includes(BarcodeFormat.ALL_FORMATS)
436
+ ) {
437
+ return barcode;
438
+ }
439
+
440
+ const wantsUpcA = requestedTypes.includes(BarcodeFormat.UPC_A);
441
+ const wantsEan13 = requestedTypes.includes(BarcodeFormat.EAN_13);
442
+ const value = barcode.rawValue ?? barcode.displayValue ?? '';
443
+
444
+ // 仅请求 UPC_A:Scan Kit 常把 UPC_A 报成带前导 0 的 EAN_13
445
+ if (
446
+ wantsUpcA &&
447
+ !wantsEan13 &&
448
+ barcode.format === BarcodeFormat.EAN_13 &&
449
+ value.length === 13 &&
450
+ value.startsWith('0')
451
+ ) {
452
+ const upc = value.slice(1);
453
+ return {
454
+ ...barcode,
455
+ format: BarcodeFormat.UPC_A,
456
+ rawValue: upc,
457
+ displayValue: upc,
458
+ content: { type: BarcodeValueType.TEXT, data: upc },
459
+ };
460
+ }
461
+
462
+ // 仅请求 EAN_13:偶发把等价码报成 12 位 UPC_A
463
+ if (
464
+ wantsEan13 &&
465
+ !wantsUpcA &&
466
+ barcode.format === BarcodeFormat.UPC_A &&
467
+ value.length === 12
468
+ ) {
469
+ const ean = '0' + value;
470
+ return {
471
+ ...barcode,
472
+ format: BarcodeFormat.EAN_13,
473
+ rawValue: ean,
474
+ displayValue: ean,
475
+ content: { type: BarcodeValueType.TEXT, data: ean },
476
+ };
477
+ }
478
+
479
+ return barcode;
480
+ }
481
+
482
+ /**
483
+ * Scans barcodes in the passed frame with MLKit
484
+ *
485
+ * @param frame Camera frame
486
+ * @param types Array of barcode types to detect (for optimal performance, use less types)
487
+ * @param options Scan options
488
+ * @returns Detected barcodes from MLKit
489
+ *
490
+ * 鸿蒙端不支持:Frame Processor 工作集机制在 @react-native-ohos/react-native-vision-camera v4
491
+ * 中不存在(不导出 useFrameProcessor / FrameProcessorPlugin)。此函数在鸿蒙端导出为抛错 stub,
492
+ * 请改用 `useScanBarcodes` 返回的 codeScanner 并传给 `<Camera codeScanner={...}>`。
493
+ */
494
+ export function scanBarcodes(
495
+ _frame: Frame,
496
+ _types: BarcodeFormat[],
497
+ _options?: CodeScannerOptions
498
+ ): Barcode[] {
499
+ throw new Error(
500
+ 'Harmony 不支持 Frame Processor worklet,请使用 useScanBarcodes 返回的 codeScanner 并传给 <Camera codeScanner={...}>'
501
+ );
502
+ }
503
+
504
+ export * from './hook';