usa-dl-ocr 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sepehr Mohseni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # usa-dl-ocr
2
+
3
+ [![npm version](https://img.shields.io/npm/v/usa-dl-ocr.svg)](https://www.npmjs.com/package/usa-dl-ocr)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
+
7
+ Framework-agnostic OCR extraction for USA driver licenses. Feed it an image, get structured data back.
8
+
9
+ ## Why This Exists
10
+
11
+ Parsing driver licenses shouldn't require vendor lock-in or framework coupling. This library provides a single, clean interface that works identically whether you're building a React app, an Express server, a CLI tool, or anything else. No adapters, no plugins, no boilerplate.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install usa-dl-ocr
17
+ ```
18
+
19
+ For CLI usage:
20
+ ```bash
21
+ npm install usa-dl-ocr commander
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { extract } from 'usa-dl-ocr';
28
+
29
+ const result = await extract(imageBlob);
30
+
31
+ console.log(result.data.licenseNumber); // "D1234567"
32
+ console.log(result.data.dateOfBirth); // "1990-01-15"
33
+ console.log(result.data.fullName); // "JOHN DOE"
34
+ ```
35
+
36
+ That's it. No setup, no configuration, no framework integration code.
37
+
38
+ ## API
39
+
40
+ ### `extract(image, options?)`
41
+
42
+ One-shot extraction. Creates a worker, processes the image, terminates the worker.
43
+
44
+ ```typescript
45
+ import { extract } from 'usa-dl-ocr';
46
+
47
+ // From file path (Node.js)
48
+ const result = await extract('/path/to/license.jpg');
49
+
50
+ // From URL
51
+ const result = await extract('https://example.com/license.jpg');
52
+
53
+ // From Blob/File (browser)
54
+ const result = await extract(fileInput.files[0]);
55
+
56
+ // From Buffer (Node.js)
57
+ const result = await extract(fs.readFileSync('license.jpg'));
58
+
59
+ // From ArrayBuffer or Uint8Array
60
+ const result = await extract(arrayBuffer);
61
+ ```
62
+
63
+ ### `DriverLicenseOCR` Class
64
+
65
+ Same functionality, class-based interface if you prefer it.
66
+
67
+ ```typescript
68
+ import { DriverLicenseOCR } from 'usa-dl-ocr';
69
+
70
+ const ocr = new DriverLicenseOCR({ language: 'eng', timeout: 30000 });
71
+ const result = await ocr.extract(image);
72
+ ```
73
+
74
+ ### `createOCR(options?)`
75
+
76
+ Persistent worker for batch processing. Significantly faster when processing multiple images.
77
+
78
+ ```typescript
79
+ import { createOCR } from 'usa-dl-ocr';
80
+
81
+ const ocr = await createOCR();
82
+
83
+ for (const image of images) {
84
+ const result = await ocr.extract(image);
85
+ // Process result
86
+ }
87
+
88
+ await ocr.terminate(); // Clean up when done
89
+ ```
90
+
91
+ ### `validate(data)`
92
+
93
+ Check extraction completeness.
94
+
95
+ ```typescript
96
+ import { extract, validate } from 'usa-dl-ocr';
97
+
98
+ const { data } = await extract(image);
99
+ const validation = validate(data);
100
+
101
+ if (!validation.isValid) {
102
+ console.log('Missing fields:', validation.issues);
103
+ console.log('Completeness score:', validation.score);
104
+ }
105
+ ```
106
+
107
+ ## Options
108
+
109
+ ```typescript
110
+ interface ExtractOptions {
111
+ language?: string; // Tesseract language code (default: 'eng')
112
+ timeout?: number; // Timeout in ms (default: 60000)
113
+ psm?: number; // Page segmentation mode (default: 3)
114
+ oem?: number; // OCR engine mode (default: 1)
115
+ workerPath?: string; // Custom Tesseract worker path
116
+ corePath?: string; // Custom Tesseract core path
117
+ langPath?: string; // Custom language data path
118
+ onProgress?: (info: ProgressInfo) => void;
119
+ }
120
+ ```
121
+
122
+ ## Extracted Fields
123
+
124
+ ```typescript
125
+ interface DriverLicenseData {
126
+ fullName: string | null;
127
+ firstName: string | null;
128
+ middleName: string | null;
129
+ lastName: string | null;
130
+ licenseNumber: string | null;
131
+ dateOfBirth: string | null; // ISO format: YYYY-MM-DD
132
+ expirationDate: string | null; // ISO format: YYYY-MM-DD
133
+ issueDate: string | null; // ISO format: YYYY-MM-DD
134
+ address: AddressInfo | null;
135
+ sex: 'M' | 'F' | null;
136
+ height: string | null; // e.g., "5'10\""
137
+ weight: string | null; // e.g., "180 lbs"
138
+ eyeColor: string | null;
139
+ hairColor: string | null;
140
+ issuingState: string | null; // 2-letter code
141
+ licenseClass: string | null;
142
+ restrictions: string[];
143
+ endorsements: string[];
144
+ documentDiscriminator: string | null;
145
+ rawText: string;
146
+ confidence: number; // 0-100
147
+ }
148
+ ```
149
+
150
+ ## CLI
151
+
152
+ ```bash
153
+ # Basic usage
154
+ usa-dl-ocr license.jpg
155
+
156
+ # Output formats
157
+ usa-dl-ocr license.jpg --format json
158
+ usa-dl-ocr license.jpg --format text
159
+ usa-dl-ocr license.jpg --format table
160
+
161
+ # Save to file
162
+ usa-dl-ocr license.jpg -o result.json
163
+
164
+ # Verbose with progress
165
+ usa-dl-ocr license.jpg -v
166
+
167
+ # Include raw OCR text
168
+ usa-dl-ocr license.jpg -r
169
+
170
+ # Custom timeout
171
+ usa-dl-ocr license.jpg -t 30000
172
+ ```
173
+
174
+ ## Platform Support
175
+
176
+ Works anywhere JavaScript runs:
177
+
178
+ - **Node.js** ≥18.0.0
179
+ - **Browsers**: Chrome, Firefox, Safari, Edge (modern versions)
180
+ - **Electron**
181
+ - **Deno** (with Node compatibility)
182
+ - **Bun**
183
+
184
+ ## Image Requirements
185
+
186
+ For best results:
187
+
188
+ - Resolution: 300+ DPI
189
+ - Format: JPEG, PNG, WebP, BMP, TIFF, GIF
190
+ - Size: Under 10MB
191
+ - Quality: Clear, in-focus, evenly lit
192
+ - Orientation: Horizontal, minimal skew
193
+
194
+ ## Security
195
+
196
+ - Input validation with size limits (10MB max)
197
+ - URL validation for remote images
198
+ - Text sanitization (control character removal)
199
+ - No eval, no dynamic code execution
200
+ - No network requests except to provided URLs
201
+ - Timeout protection against hung operations
202
+
203
+ ## Performance Notes
204
+
205
+ - First extraction takes longer due to Tesseract initialization (~2-5s)
206
+ - Subsequent extractions with `createOCR()` are faster (~0.5-2s)
207
+ - Higher resolution images take longer but produce better results
208
+ - Consider resizing very large images client-side before processing
209
+
210
+ ## Limitations
211
+
212
+ - OCR accuracy depends heavily on image quality
213
+ - Damaged, faded, or obscured licenses may not parse correctly
214
+ - Non-standard license formats may have lower extraction rates
215
+ - Real-time processing not suitable for high-volume applications
216
+
217
+ ## Error Handling
218
+
219
+ ```typescript
220
+ import { extract } from 'usa-dl-ocr';
221
+
222
+ try {
223
+ const result = await extract(image);
224
+
225
+ if (!result.isDriverLicense) {
226
+ // Image may not be a driver license
227
+ }
228
+
229
+ if (result.warnings.length > 0) {
230
+ // Some fields couldn't be extracted
231
+ }
232
+ } catch (error) {
233
+ if (error.message.includes('timeout')) {
234
+ // OCR took too long
235
+ } else if (error.message.includes('exceeds maximum size')) {
236
+ // Image too large
237
+ } else if (error.message.includes('Unsupported')) {
238
+ // Invalid input type
239
+ }
240
+ }
241
+ ```
242
+
243
+ ## Contributing
244
+
245
+ Issues and PRs welcome at [github.com/sepehr-mohseni/usa-dl-ocr](https://github.com/sepehr-mohseni/usa-dl-ocr).
246
+
247
+ ## License
248
+
249
+ MIT © [Sepehr Mohseni](https://github.com/sepehr-mohseni)
250
+
251
+ ---
252
+
253
+ **Note**: This library performs local OCR processing. No data is sent to external servers. All processing happens in-browser or in your Node.js process.
package/dist/cli.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';var fs=require('fs'),path=require('path'),_=require('tesseract.js');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ___default=/*#__PURE__*/_interopDefault(_);var E=Object.freeze({AL:"Alabama",AK:"Alaska",AZ:"Arizona",AR:"Arkansas",CA:"California",CO:"Colorado",CT:"Connecticut",DE:"Delaware",FL:"Florida",GA:"Georgia",HI:"Hawaii",ID:"Idaho",IL:"Illinois",IN:"Indiana",IA:"Iowa",KS:"Kansas",KY:"Kentucky",LA:"Louisiana",ME:"Maine",MD:"Maryland",MA:"Massachusetts",MI:"Michigan",MN:"Minnesota",MS:"Mississippi",MO:"Missouri",MT:"Montana",NE:"Nebraska",NV:"Nevada",NH:"New Hampshire",NJ:"New Jersey",NM:"New Mexico",NY:"New York",NC:"North Carolina",ND:"North Dakota",OH:"Ohio",OK:"Oklahoma",OR:"Oregon",PA:"Pennsylvania",RI:"Rhode Island",SC:"South Carolina",SD:"South Dakota",TN:"Tennessee",TX:"Texas",UT:"Utah",VT:"Vermont",VA:"Virginia",WA:"Washington",WV:"West Virginia",WI:"Wisconsin",WY:"Wyoming",DC:"District of Columbia"}),A=Object.freeze(Object.keys(E)),D=Object.freeze({BLK:"Black",BLU:"Blue",BRO:"Brown",GRY:"Gray",GRN:"Green",HAZ:"Hazel",MAR:"Maroon",PNK:"Pink",DIC:"Dichromatic",UNK:"Unknown"}),R=Object.freeze({BAL:"Bald",BLK:"Black",BLN:"Blonde",BRO:"Brown",GRY:"Gray",RED:"Red",SDY:"Sandy",WHI:"White",UNK:"Unknown"}),c=Object.freeze({NAME:["NAME","FN","FULL NAME","NM"],FIRST_NAME:["FIRST","FIRST NAME","FN","GIVEN NAME","1"],MIDDLE_NAME:["MIDDLE","MIDDLE NAME","MN","2"],LAST_NAME:["LAST","LAST NAME","LN","FAMILY NAME","SURNAME","3"],LICENSE_NUMBER:["DL","LIC","LICENSE","DL NO","LIC NO","LICENSE NO","DRIVER LICENSE","DLN","4D"],CLASS:["CLASS","CLS","9"],DOB:["DOB","DATE OF BIRTH","BIRTH DATE","BD","BORN","3"],EXPIRATION:["EXP","EXPIRES","EXPIRATION","EXP DATE","4B"],ISSUE_DATE:["ISS","ISSUED","ISSUE DATE","4A"],SEX:["SEX","GENDER","S","11"],HEIGHT:["HT","HEIGHT","15"],WEIGHT:["WT","WEIGHT","16"],EYES:["EYES","EYE","EYE COLOR","14"],HAIR:["HAIR","HAIR COLOR","13"],ADDRESS:["ADDRESS","ADDR","ADD","8"],RESTRICTIONS:["RESTR","RESTRICTIONS","REST","12"],ENDORSEMENTS:["END","ENDORSEMENTS","ENDORSE","10"],DD:["DD","DAQ","DOCUMENT DISCRIMINATOR","AUDIT"]}),p=10*1024*1024,O=6e4;function b(s){return typeof s!="string"?"":s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,"")}function T(s){let t=b(s),e=t.toUpperCase(),r=t.split(`
3
+ `).map(n=>n.trim()).filter(Boolean);return {fullName:C(r,e),firstName:d(r,c.FIRST_NAME),middleName:d(r,c.MIDDLE_NAME),lastName:d(r,c.LAST_NAME),licenseNumber:M(e),dateOfBirth:I(e,c.DOB),expirationDate:I(e,c.EXPIRATION),issueDate:I(e,c.ISSUE_DATE),address:B(r,e),sex:$(e),height:P(e),weight:U(e),eyeColor:v(e),hairColor:k(e),issuingState:H(e),licenseClass:d(r,c.CLASS),restrictions:x(e,c.RESTRICTIONS),endorsements:x(e,c.ENDORSEMENTS),documentDiscriminator:d(r,c.DD),rawText:t,confidence:0}}function C(s,t){for(let e of c.NAME){let r=new RegExp(`${m(e)}[:\\s]+([A-Z][A-Z'\\-]+),\\s*([A-Z][A-Z'\\s.-]+)`,"i"),n=t.match(r);if(n?.[1]&&n[2])return S(`${n[2]} ${n[1]}`)}for(let e of c.NAME){let r=new RegExp(`${m(e)}[:\\s]+([A-Z][A-Z\\s,.-]+)`,"i"),n=t.match(r);if(n?.[1])return S(n[1])}for(let e of s){let r=e.match(/^([A-Z][A-Z'-]+),\s*([A-Z][A-Z'\s.-]+)$/i);if(r?.[1]&&r[2])return S(`${r[2]} ${r[1]}`)}return null}function d(s,t){for(let e of t){let r=new RegExp(`(?:^|\\s)${m(e)}[:\\s]+([^\\n]+)`,"i");for(let n of s){let i=n.match(r);if(i?.[1])return i[1].trim()}}return null}function M(s){for(let e of c.LICENSE_NUMBER){let r=new RegExp(`${m(e)}[:\\s#]*([A-Z0-9-]{6,15})`,"i"),n=s.match(r);if(n?.[1]){let i=n[1].replace(/[^A-Z0-9]/gi,"");if(i.length>=6&&i.length<=15)return i}}return s.match(/\b([A-Z]\d{7,12}|\d{7,12}|[A-Z]{2}\d{6,10})\b/)?.[1]??null}function I(s,t){for(let e of t){let r=new RegExp(`${m(e)}[:\\s]*([\\d/\\-]+)`),n=s.match(r);if(n?.[1]){let i=y(n[1]);if(i)return i}}return null}function y(s){let t=s.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/);if(t?.[1]&&t[2]&&t[3]){let e=t[1].padStart(2,"0"),r=t[2].padStart(2,"0");if(N(t[3],e,r))return `${t[3]}-${e}-${r}`}return t=s.match(/(\d{4})[\/\-](\d{2})[\/\-](\d{2})/),t?.[1]&&t[2]&&t[3]&&N(t[1],t[2],t[3])?`${t[1]}-${t[2]}-${t[3]}`:(t=s.match(/^(\d{2})(\d{2})(\d{4})$/),t?.[1]&&t[2]&&t[3]&&N(t[3],t[1],t[2])?`${t[3]}-${t[1]}-${t[2]}`:null)}function N(s,t,e){let r=parseInt(s,10),n=parseInt(t,10),i=parseInt(e,10);if(r<1900||r>2100||n<1||n>12||i<1||i>31)return false;let a=new Date(r,n-1,i);return a.getFullYear()===r&&a.getMonth()===n-1&&a.getDate()===i}function B(s,t){let e=null,r=null,n=null,i=null,a=t.match(/\b(\d{5}-\d{4})\b/)||t.match(/\b(\d{5})\b/);a?.[1]&&(i=a[1]);let o=t.match(/\b(\d+\s+[A-Z0-9\s]+(?:ST|STREET|AVE|AVENUE|BLVD|BOULEVARD|RD|ROAD|DR|DRIVE|LN|LANE|CT|COURT|WAY|PL|PLACE|CIR|CIRCLE))\b/);o?.[1]&&(e=o[1].trim());let f=t.match(/\n([A-Z][A-Z\s]+)[,\s]+([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(f?.[1]&&f[2]&&f[3])r=f[1].trim(),n=f[2],i=f[3];else {let l=t.match(/([A-Z][A-Z\s]{2,20}),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(l?.[1]&&l[2]&&l[3]){let h=l[1].trim();!h.match(/^\d+\s+/)&&(!e||!h.includes(e))&&(r=h,n=l[2],i=l[3]);}}if(!e&&!r&&!n&&!i)return null;let u=[e,r,n,i].filter(Boolean);return {street:e,city:r,state:n,zipCode:i,full:u.length>0?u.join(", "):null}}function $(s){for(let t of c.SEX){let e=new RegExp(`${m(t)}[:\\s]*(M|F|MALE|FEMALE)\\b`,"i"),r=s.match(e);if(r?.[1]){let n=r[1].toUpperCase();if(n==="M"||n==="MALE")return "M";if(n==="F"||n==="FEMALE")return "F"}}return null}function P(s){let t=[/HT[:\s]*(\d)'(\d{1,2})"/,/HT[:\s]*(\d)-(\d{1,2})/,/HT[:\s]*(\d)(\d{2})\b/,/HEIGHT[:\s]*(\d)'(\d{1,2})"/];for(let e of t){let r=s.match(e);if(r?.[1]&&r[2]){let n=parseInt(r[1],10),i=parseInt(r[2],10);if(n>=3&&n<=8&&i>=0&&i<=11)return `${n}'${i}"`}}return null}function U(s){let t=[/WT[:\s]*(\d{2,3})\s*(?:LBS?)?/i,/WEIGHT[:\s]*(\d{2,3})/i];for(let e of t){let r=s.match(e);if(r?.[1]){let n=parseInt(r[1],10);if(n>=50&&n<=500)return `${n} lbs`}}return null}function v(s){for(let t of c.EYES){let e=new RegExp(`${m(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(e);if(r?.[1]){let n=r[1].toUpperCase();if(n==="COLOR"||n==="COLO")continue;return D[n]??n}}return null}function k(s){for(let t of c.HAIR){let e=new RegExp(`${m(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(e);if(r?.[1]){let n=r[1].toUpperCase();if(n==="COLOR"||n==="COLO")continue;return R[n]??n}}return null}function H(s){for(let[r,n]of Object.entries(E))if(s.includes(n.toUpperCase()))return r;let t=/\b([A-Z]{2})\s+DRIVER(?:'?S)?\s+LICENSE/,e=s.match(t);return e?.[1]&&A.includes(e[1])?e[1]:null}function x(s,t){let e=[];for(let r of t){let n=new RegExp(`\\b${m(r)}[:\\s]+([A-Z0-9,\\s]+?)(?:\\n|$)`,"i"),i=s.match(n);if(i?.[1]){let a=i[1].trim();if(a.toUpperCase()==="NONE")return [];let o=a.split(/[,\s]+/).filter(f=>{let u=f.toUpperCase();return u.length>0&&u.length<=3&&!u.match(/^(RESTR|ICTIONS|ENDOR|SEMENTS|NONE)$/i)});e.push(...o);}}return [...new Set(e)]}function S(s){return s.replace(/[^A-Z\s,.-]/gi,"").replace(/\s+/g," ").trim()}function m(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function L(s){let t=s.toUpperCase(),e=0,r=[/DRIVER(?:'?S)?\s*LICENSE/,/OPERATOR(?:'?S)?\s*LICENSE/,/IDENTIFICATION\s*CARD/,/\bDL\b/,/\bDOB\b/,/\bEXP(?:IRES?)?\b/,/CLASS\s*[A-Z]/];for(let n of r)n.test(t)&&e++;for(let n of Object.values(E))if(t.includes(n.toUpperCase())){e++;break}return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}/.test(s)&&e++,e>=2}var g=class{constructor(t={}){this.options={language:"eng",psm:3,oem:1,timeout:O,...t};}async extract(t){let e=Date.now(),r=[],n=await this.prepareImage(t),i={};this.options.workerPath&&(i.workerPath=this.options.workerPath),this.options.corePath&&(i.corePath=this.options.corePath),this.options.langPath&&(i.langPath=this.options.langPath),this.options.onProgress&&(i.logger=o=>{this.options.onProgress?.({status:o.status,progress:o.progress});});let a=await ___default.default.createWorker(this.options.language??"eng",this.options.oem??1,i);try{await a.setParameters({tessedit_pageseg_mode:String(this.options.psm??3)});let o=a.recognize(n),f=new Promise((J,w)=>{setTimeout(()=>w(new Error("OCR timeout exceeded")),this.options.timeout);}),{data:u}=await Promise.race([o,f]),l=T(u.text);l.confidence=Math.round(u.confidence);let h=L(u.text);return h||r.push("Image may not be a driver license"),l.licenseNumber||r.push("License number not detected"),!l.fullName&&!l.firstName&&!l.lastName&&r.push("Name not detected"),l.dateOfBirth||r.push("Date of birth not detected"),{data:l,processingTimeMs:Date.now()-e,isDriverLicense:h,warnings:r}}finally{await a.terminate();}}async prepareImage(t){if(typeof t=="string"){if(t.length>2048)throw new Error("URL/path too long");if(t.startsWith("http://")||t.startsWith("https://"))try{new URL(t);}catch{throw new Error("Invalid URL")}return t}if(typeof Blob<"u"&&t instanceof Blob){if(t.size>p)throw new Error(`Image exceeds maximum size of ${p/1024/1024}MB`);return t}if(typeof Buffer<"u"&&Buffer.isBuffer(t)){if(t.length>p)throw new Error(`Image exceeds maximum size of ${p/1024/1024}MB`);return t}if(t instanceof ArrayBuffer){if(t.byteLength>p)throw new Error(`Image exceeds maximum size of ${p/1024/1024}MB`);return typeof Buffer<"u"?Buffer.from(t):t}if(t instanceof Uint8Array){if(t.byteLength>p)throw new Error(`Image exceeds maximum size of ${p/1024/1024}MB`);if(typeof Buffer<"u")return Buffer.from(t.buffer,t.byteOffset,t.byteLength);let e=t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength);return new Blob([e])}throw new Error("Unsupported image input type")}};var j="1.0.0",Y=new Set([".jpg",".jpeg",".png",".bmp",".gif",".webp",".tiff",".tif"]);async function G(){let s;try{s=(await import('commander')).Command;}catch{console.error("Error: commander package required for CLI"),console.error("Install with: npm install commander"),process.exit(1);}let t=new s;t.name("usa-dl-ocr").description("Extract data from USA driver license images").version(j).argument("<image>","Image file path").option("-o, --output <file>","Output file").option("-f, --format <fmt>","Output format: json|text|table","json").option("-v, --verbose","Verbose output",false).option("-r, --raw","Include raw OCR text",false).option("-l, --language <lang>","OCR language","eng").option("-t, --timeout <ms>","Timeout in ms","60000").action(async(e,r)=>{try{await X(e,{...r,timeout:parseInt(String(r.timeout),10)});}catch(n){console.error(`Error: ${n instanceof Error?n.message:"Unknown error"}`),process.exit(1);}}),t.parse();}async function X(s,t){let e=path.resolve(s);if(!fs.existsSync(e))throw new Error(`File not found: ${e}`);let r=path.extname(e).toLowerCase();if(!Y.has(r))throw new Error(`Unsupported format: ${r}`);let n=t.verbose?u=>{process.stderr.write(`\r${u.status}: ${Math.round(u.progress*100)}%`);}:void 0;t.verbose&&console.error(`Processing: ${e}`);let i=fs.readFileSync(e),o=await new g({language:t.language,timeout:t.timeout,onProgress:n}).extract(i);t.verbose&&console.error("");let f=K(o,t);t.output?(fs.writeFileSync(t.output,f),t.verbose&&console.error(`Written to: ${t.output}`)):console.log(f),t.verbose&&o.warnings.length>0&&(console.error(`
4
+ Warnings:`),o.warnings.forEach(u=>console.error(` - ${u}`))),o.isDriverLicense||process.exit(2);}function K(s,t){let{data:e,processingTimeMs:r,isDriverLicense:n,warnings:i}=s;if(t.format==="json"){let o={...e,processingTimeMs:r,isDriverLicense:n};return t.raw||delete o.rawText,t.verbose&&(o.warnings=i),JSON.stringify(o,null,2)}if(t.format==="text"){let o=["=== Driver License Data ===",""];return e.fullName?o.push(`Name: ${e.fullName}`):(e.firstName&&o.push(`First: ${e.firstName}`),e.middleName&&o.push(`Middle: ${e.middleName}`),e.lastName&&o.push(`Last: ${e.lastName}`)),e.licenseNumber&&o.push(`License #: ${e.licenseNumber}`),e.issuingState&&o.push(`State: ${e.issuingState}`),e.licenseClass&&o.push(`Class: ${e.licenseClass}`),o.push(""),e.dateOfBirth&&o.push(`DOB: ${e.dateOfBirth}`),e.issueDate&&o.push(`Issued: ${e.issueDate}`),e.expirationDate&&o.push(`Expires: ${e.expirationDate}`),e.address?.full&&(o.push(""),o.push(`Address: ${e.address.full}`)),o.push(""),e.sex&&o.push(`Sex: ${e.sex}`),e.height&&o.push(`Height: ${e.height}`),e.weight&&o.push(`Weight: ${e.weight}`),e.eyeColor&&o.push(`Eyes: ${e.eyeColor}`),e.hairColor&&o.push(`Hair: ${e.hairColor}`),o.push(""),o.push(`Confidence: ${e.confidence}%`),o.push(`Time: ${r}ms`),t.raw&&e.rawText&&o.push("","--- Raw Text ---",e.rawText),o.join(`
5
+ `)}let a=[["Field","Value"],["\u2500".repeat(18),"\u2500".repeat(38)]];return e.fullName&&a.push(["Name",e.fullName]),e.licenseNumber&&a.push(["License #",e.licenseNumber]),e.issuingState&&a.push(["State",e.issuingState]),e.dateOfBirth&&a.push(["DOB",e.dateOfBirth]),e.expirationDate&&a.push(["Expires",e.expirationDate]),e.address?.full&&a.push(["Address",e.address.full]),e.sex&&a.push(["Sex",e.sex]),e.height&&a.push(["Height",e.height]),a.push(["\u2500".repeat(18),"\u2500".repeat(38)]),a.push(["Confidence",`${e.confidence}%`]),a.push(["Time",`${r}ms`]),a.map(([o,f])=>`${o.padEnd(18)} ${f}`).join(`
6
+ `)}G().catch(console.error);
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Type definitions for usa-dl-ocr
3
+ * @packageDocumentation
4
+ */
5
+ /** Extracted address components */
6
+ interface AddressInfo {
7
+ street: string | null;
8
+ city: string | null;
9
+ state: string | null;
10
+ zipCode: string | null;
11
+ full: string | null;
12
+ }
13
+ /** Complete driver license data structure */
14
+ interface DriverLicenseData {
15
+ /** Full name as printed on license */
16
+ fullName: string | null;
17
+ /** First/given name */
18
+ firstName: string | null;
19
+ /** Middle name or initial */
20
+ middleName: string | null;
21
+ /** Last/family name */
22
+ lastName: string | null;
23
+ /** License number */
24
+ licenseNumber: string | null;
25
+ /** Date of birth in YYYY-MM-DD format */
26
+ dateOfBirth: string | null;
27
+ /** Expiration date in YYYY-MM-DD format */
28
+ expirationDate: string | null;
29
+ /** Issue date in YYYY-MM-DD format */
30
+ issueDate: string | null;
31
+ /** Parsed address */
32
+ address: AddressInfo | null;
33
+ /** Sex/gender (M or F) */
34
+ sex: 'M' | 'F' | null;
35
+ /** Height (e.g., "5'10\"") */
36
+ height: string | null;
37
+ /** Weight in pounds */
38
+ weight: string | null;
39
+ /** Eye color */
40
+ eyeColor: string | null;
41
+ /** Hair color */
42
+ hairColor: string | null;
43
+ /** Issuing state (2-letter abbreviation) */
44
+ issuingState: string | null;
45
+ /** License class */
46
+ licenseClass: string | null;
47
+ /** Restrictions codes */
48
+ restrictions: string[];
49
+ /** Endorsement codes */
50
+ endorsements: string[];
51
+ /** Document discriminator */
52
+ documentDiscriminator: string | null;
53
+ /** Raw OCR text */
54
+ rawText: string;
55
+ /** OCR confidence score 0-100 */
56
+ confidence: number;
57
+ }
58
+ /** OCR processing options */
59
+ interface ExtractOptions {
60
+ /** Tesseract language code (default: 'eng') */
61
+ language?: string;
62
+ /** Custom worker path */
63
+ workerPath?: string;
64
+ /** Custom core WASM path */
65
+ corePath?: string;
66
+ /** Custom language data path */
67
+ langPath?: string;
68
+ /** Progress callback */
69
+ onProgress?: (progress: ProgressInfo) => void;
70
+ /** Page segmentation mode (default: 3) */
71
+ psm?: number;
72
+ /** OCR engine mode (default: 1) */
73
+ oem?: number;
74
+ /** Timeout in milliseconds (default: 60000) */
75
+ timeout?: number;
76
+ }
77
+ /** Progress callback data */
78
+ interface ProgressInfo {
79
+ status: string;
80
+ progress: number;
81
+ }
82
+ /** Extraction result */
83
+ interface ExtractionResult {
84
+ /** Extracted license data */
85
+ data: DriverLicenseData;
86
+ /** Processing time in ms */
87
+ processingTimeMs: number;
88
+ /** Whether image appears to be a driver license */
89
+ isDriverLicense: boolean;
90
+ /** Any warnings during processing */
91
+ warnings: string[];
92
+ }
93
+ /** Validation result */
94
+ interface ValidationResult {
95
+ /** Whether data meets minimum requirements */
96
+ isValid: boolean;
97
+ /** Completeness score 0-100 */
98
+ score: number;
99
+ /** List of missing or invalid fields */
100
+ issues: string[];
101
+ }
102
+ /** Supported image input types */
103
+ type ImageInput = Blob | File | Buffer | ArrayBuffer | Uint8Array | string;
104
+
105
+ /**
106
+ * Sanitize input text to prevent injection attacks
107
+ */
108
+ declare function sanitizeText(text: string): string;
109
+ /**
110
+ * Parse OCR text into structured driver license data
111
+ */
112
+ declare function parseOCRText(rawText: string): DriverLicenseData;
113
+ /**
114
+ * Detect if text appears to be from a driver license
115
+ */
116
+ declare function detectDriverLicense(text: string): boolean;
117
+
118
+ /**
119
+ * Main OCR extraction class - framework agnostic
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const ocr = new DriverLicenseOCR();
124
+ * const result = await ocr.extract(imageBlob);
125
+ * console.log(result.data.licenseNumber);
126
+ * ```
127
+ */
128
+ declare class DriverLicenseOCR {
129
+ private options;
130
+ constructor(options?: ExtractOptions);
131
+ /**
132
+ * Extract driver license data from an image
133
+ */
134
+ extract(image: ImageInput): Promise<ExtractionResult>;
135
+ /**
136
+ * Validate and prepare image input
137
+ */
138
+ private prepareImage;
139
+ }
140
+ /**
141
+ * Stateless extraction function
142
+ */
143
+ declare function extract(image: ImageInput, options?: ExtractOptions): Promise<ExtractionResult>;
144
+ /**
145
+ * Create reusable OCR instance with persistent worker for batch processing
146
+ */
147
+ declare function createOCR(options?: ExtractOptions): Promise<{
148
+ extract(image: ImageInput): Promise<ExtractionResult>;
149
+ terminate(): Promise<void>;
150
+ }>;
151
+ /**
152
+ * Validate extracted license data
153
+ */
154
+ declare function validate(data: DriverLicenseData): ValidationResult;
155
+
156
+ /** US state codes mapped to full names */
157
+ declare const US_STATES: Readonly<Record<string, string>>;
158
+ declare const STATE_CODES: readonly string[];
159
+ /** Standard eye color abbreviations */
160
+ declare const EYE_COLORS: Readonly<Record<string, string>>;
161
+ /** Standard hair color abbreviations */
162
+ declare const HAIR_COLORS: Readonly<Record<string, string>>;
163
+
164
+ export { type AddressInfo, type DriverLicenseData, DriverLicenseOCR, EYE_COLORS, type ExtractOptions, type ExtractionResult, HAIR_COLORS, type ImageInput, type ProgressInfo, STATE_CODES, US_STATES, type ValidationResult, createOCR, detectDriverLicense, extract, parseOCRText, sanitizeText, validate };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Type definitions for usa-dl-ocr
3
+ * @packageDocumentation
4
+ */
5
+ /** Extracted address components */
6
+ interface AddressInfo {
7
+ street: string | null;
8
+ city: string | null;
9
+ state: string | null;
10
+ zipCode: string | null;
11
+ full: string | null;
12
+ }
13
+ /** Complete driver license data structure */
14
+ interface DriverLicenseData {
15
+ /** Full name as printed on license */
16
+ fullName: string | null;
17
+ /** First/given name */
18
+ firstName: string | null;
19
+ /** Middle name or initial */
20
+ middleName: string | null;
21
+ /** Last/family name */
22
+ lastName: string | null;
23
+ /** License number */
24
+ licenseNumber: string | null;
25
+ /** Date of birth in YYYY-MM-DD format */
26
+ dateOfBirth: string | null;
27
+ /** Expiration date in YYYY-MM-DD format */
28
+ expirationDate: string | null;
29
+ /** Issue date in YYYY-MM-DD format */
30
+ issueDate: string | null;
31
+ /** Parsed address */
32
+ address: AddressInfo | null;
33
+ /** Sex/gender (M or F) */
34
+ sex: 'M' | 'F' | null;
35
+ /** Height (e.g., "5'10\"") */
36
+ height: string | null;
37
+ /** Weight in pounds */
38
+ weight: string | null;
39
+ /** Eye color */
40
+ eyeColor: string | null;
41
+ /** Hair color */
42
+ hairColor: string | null;
43
+ /** Issuing state (2-letter abbreviation) */
44
+ issuingState: string | null;
45
+ /** License class */
46
+ licenseClass: string | null;
47
+ /** Restrictions codes */
48
+ restrictions: string[];
49
+ /** Endorsement codes */
50
+ endorsements: string[];
51
+ /** Document discriminator */
52
+ documentDiscriminator: string | null;
53
+ /** Raw OCR text */
54
+ rawText: string;
55
+ /** OCR confidence score 0-100 */
56
+ confidence: number;
57
+ }
58
+ /** OCR processing options */
59
+ interface ExtractOptions {
60
+ /** Tesseract language code (default: 'eng') */
61
+ language?: string;
62
+ /** Custom worker path */
63
+ workerPath?: string;
64
+ /** Custom core WASM path */
65
+ corePath?: string;
66
+ /** Custom language data path */
67
+ langPath?: string;
68
+ /** Progress callback */
69
+ onProgress?: (progress: ProgressInfo) => void;
70
+ /** Page segmentation mode (default: 3) */
71
+ psm?: number;
72
+ /** OCR engine mode (default: 1) */
73
+ oem?: number;
74
+ /** Timeout in milliseconds (default: 60000) */
75
+ timeout?: number;
76
+ }
77
+ /** Progress callback data */
78
+ interface ProgressInfo {
79
+ status: string;
80
+ progress: number;
81
+ }
82
+ /** Extraction result */
83
+ interface ExtractionResult {
84
+ /** Extracted license data */
85
+ data: DriverLicenseData;
86
+ /** Processing time in ms */
87
+ processingTimeMs: number;
88
+ /** Whether image appears to be a driver license */
89
+ isDriverLicense: boolean;
90
+ /** Any warnings during processing */
91
+ warnings: string[];
92
+ }
93
+ /** Validation result */
94
+ interface ValidationResult {
95
+ /** Whether data meets minimum requirements */
96
+ isValid: boolean;
97
+ /** Completeness score 0-100 */
98
+ score: number;
99
+ /** List of missing or invalid fields */
100
+ issues: string[];
101
+ }
102
+ /** Supported image input types */
103
+ type ImageInput = Blob | File | Buffer | ArrayBuffer | Uint8Array | string;
104
+
105
+ /**
106
+ * Sanitize input text to prevent injection attacks
107
+ */
108
+ declare function sanitizeText(text: string): string;
109
+ /**
110
+ * Parse OCR text into structured driver license data
111
+ */
112
+ declare function parseOCRText(rawText: string): DriverLicenseData;
113
+ /**
114
+ * Detect if text appears to be from a driver license
115
+ */
116
+ declare function detectDriverLicense(text: string): boolean;
117
+
118
+ /**
119
+ * Main OCR extraction class - framework agnostic
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const ocr = new DriverLicenseOCR();
124
+ * const result = await ocr.extract(imageBlob);
125
+ * console.log(result.data.licenseNumber);
126
+ * ```
127
+ */
128
+ declare class DriverLicenseOCR {
129
+ private options;
130
+ constructor(options?: ExtractOptions);
131
+ /**
132
+ * Extract driver license data from an image
133
+ */
134
+ extract(image: ImageInput): Promise<ExtractionResult>;
135
+ /**
136
+ * Validate and prepare image input
137
+ */
138
+ private prepareImage;
139
+ }
140
+ /**
141
+ * Stateless extraction function
142
+ */
143
+ declare function extract(image: ImageInput, options?: ExtractOptions): Promise<ExtractionResult>;
144
+ /**
145
+ * Create reusable OCR instance with persistent worker for batch processing
146
+ */
147
+ declare function createOCR(options?: ExtractOptions): Promise<{
148
+ extract(image: ImageInput): Promise<ExtractionResult>;
149
+ terminate(): Promise<void>;
150
+ }>;
151
+ /**
152
+ * Validate extracted license data
153
+ */
154
+ declare function validate(data: DriverLicenseData): ValidationResult;
155
+
156
+ /** US state codes mapped to full names */
157
+ declare const US_STATES: Readonly<Record<string, string>>;
158
+ declare const STATE_CODES: readonly string[];
159
+ /** Standard eye color abbreviations */
160
+ declare const EYE_COLORS: Readonly<Record<string, string>>;
161
+ /** Standard hair color abbreviations */
162
+ declare const HAIR_COLORS: Readonly<Record<string, string>>;
163
+
164
+ export { type AddressInfo, type DriverLicenseData, DriverLicenseOCR, EYE_COLORS, type ExtractOptions, type ExtractionResult, HAIR_COLORS, type ImageInput, type ProgressInfo, STATE_CODES, US_STATES, type ValidationResult, createOCR, detectDriverLicense, extract, parseOCRText, sanitizeText, validate };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var M=require('tesseract.js');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var M__default=/*#__PURE__*/_interopDefault(M);var h=Object.freeze({AL:"Alabama",AK:"Alaska",AZ:"Arizona",AR:"Arkansas",CA:"California",CO:"Colorado",CT:"Connecticut",DE:"Delaware",FL:"Florida",GA:"Georgia",HI:"Hawaii",ID:"Idaho",IL:"Illinois",IN:"Indiana",IA:"Iowa",KS:"Kansas",KY:"Kentucky",LA:"Louisiana",ME:"Maine",MD:"Maryland",MA:"Massachusetts",MI:"Michigan",MN:"Minnesota",MS:"Mississippi",MO:"Missouri",MT:"Montana",NE:"Nebraska",NV:"Nevada",NH:"New Hampshire",NJ:"New Jersey",NM:"New Mexico",NY:"New York",NC:"North Carolina",ND:"North Dakota",OH:"Ohio",OK:"Oklahoma",OR:"Oregon",PA:"Pennsylvania",RI:"Rhode Island",SC:"South Carolina",SD:"South Dakota",TN:"Tennessee",TX:"Texas",UT:"Utah",VT:"Vermont",VA:"Virginia",WA:"Washington",WV:"West Virginia",WI:"Wisconsin",WY:"Wyoming",DC:"District of Columbia"}),S=Object.freeze(Object.keys(h)),R=Object.freeze({BLK:"Black",BLU:"Blue",BRO:"Brown",GRY:"Gray",GRN:"Green",HAZ:"Hazel",MAR:"Maroon",PNK:"Pink",DIC:"Dichromatic",UNK:"Unknown"}),N=Object.freeze({BAL:"Bald",BLK:"Black",BLN:"Blonde",BRO:"Brown",GRY:"Gray",RED:"Red",SDY:"Sandy",WHI:"White",UNK:"Unknown"}),c=Object.freeze({NAME:["NAME","FN","FULL NAME","NM"],FIRST_NAME:["FIRST","FIRST NAME","FN","GIVEN NAME","1"],MIDDLE_NAME:["MIDDLE","MIDDLE NAME","MN","2"],LAST_NAME:["LAST","LAST NAME","LN","FAMILY NAME","SURNAME","3"],LICENSE_NUMBER:["DL","LIC","LICENSE","DL NO","LIC NO","LICENSE NO","DRIVER LICENSE","DLN","4D"],CLASS:["CLASS","CLS","9"],DOB:["DOB","DATE OF BIRTH","BIRTH DATE","BD","BORN","3"],EXPIRATION:["EXP","EXPIRES","EXPIRATION","EXP DATE","4B"],ISSUE_DATE:["ISS","ISSUED","ISSUE DATE","4A"],SEX:["SEX","GENDER","S","11"],HEIGHT:["HT","HEIGHT","15"],WEIGHT:["WT","WEIGHT","16"],EYES:["EYES","EYE","EYE COLOR","14"],HAIR:["HAIR","HAIR COLOR","13"],ADDRESS:["ADDRESS","ADDR","ADD","8"],RESTRICTIONS:["RESTR","RESTRICTIONS","REST","12"],ENDORSEMENTS:["END","ENDORSEMENTS","ENDORSE","10"],DD:["DD","DAQ","DOCUMENT DISCRIMINATOR","AUDIT"]}),E=10*1024*1024,D=6e4;function C(s){return typeof s!="string"?"":s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,"")}function m(s){let t=C(s),n=t.toUpperCase(),r=t.split(`
2
+ `).map(e=>e.trim()).filter(Boolean);return {fullName:w(r,n),firstName:g(r,c.FIRST_NAME),middleName:g(r,c.MIDDLE_NAME),lastName:g(r,c.LAST_NAME),licenseNumber:y(n),dateOfBirth:T(n,c.DOB),expirationDate:T(n,c.EXPIRATION),issueDate:T(n,c.ISSUE_DATE),address:P(r,n),sex:U(n),height:_(n),weight:k(n),eyeColor:$(n),hairColor:H(n),issuingState:Z(n),licenseClass:g(r,c.CLASS),restrictions:x(n,c.RESTRICTIONS),endorsements:x(n,c.ENDORSEMENTS),documentDiscriminator:g(r,c.DD),rawText:t,confidence:0}}function w(s,t){for(let n of c.NAME){let r=new RegExp(`${p(n)}[:\\s]+([A-Z][A-Z'\\-]+),\\s*([A-Z][A-Z'\\s.-]+)`,"i"),e=t.match(r);if(e?.[1]&&e[2])return L(`${e[2]} ${e[1]}`)}for(let n of c.NAME){let r=new RegExp(`${p(n)}[:\\s]+([A-Z][A-Z\\s,.-]+)`,"i"),e=t.match(r);if(e?.[1])return L(e[1])}for(let n of s){let r=n.match(/^([A-Z][A-Z'-]+),\s*([A-Z][A-Z'\s.-]+)$/i);if(r?.[1]&&r[2])return L(`${r[2]} ${r[1]}`)}return null}function g(s,t){for(let n of t){let r=new RegExp(`(?:^|\\s)${p(n)}[:\\s]+([^\\n]+)`,"i");for(let e of s){let o=e.match(r);if(o?.[1])return o[1].trim()}}return null}function y(s){for(let n of c.LICENSE_NUMBER){let r=new RegExp(`${p(n)}[:\\s#]*([A-Z0-9-]{6,15})`,"i"),e=s.match(r);if(e?.[1]){let o=e[1].replace(/[^A-Z0-9]/gi,"");if(o.length>=6&&o.length<=15)return o}}return s.match(/\b([A-Z]\d{7,12}|\d{7,12}|[A-Z]{2}\d{6,10})\b/)?.[1]??null}function T(s,t){for(let n of t){let r=new RegExp(`${p(n)}[:\\s]*([\\d/\\-]+)`),e=s.match(r);if(e?.[1]){let o=B(e[1]);if(o)return o}}return null}function B(s){let t=s.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/);if(t?.[1]&&t[2]&&t[3]){let n=t[1].padStart(2,"0"),r=t[2].padStart(2,"0");if(O(t[3],n,r))return `${t[3]}-${n}-${r}`}return t=s.match(/(\d{4})[\/\-](\d{2})[\/\-](\d{2})/),t?.[1]&&t[2]&&t[3]&&O(t[1],t[2],t[3])?`${t[1]}-${t[2]}-${t[3]}`:(t=s.match(/^(\d{2})(\d{2})(\d{4})$/),t?.[1]&&t[2]&&t[3]&&O(t[3],t[1],t[2])?`${t[3]}-${t[1]}-${t[2]}`:null)}function O(s,t,n){let r=parseInt(s,10),e=parseInt(t,10),o=parseInt(n,10);if(r<1900||r>2100||e<1||e>12||o<1||o>31)return false;let i=new Date(r,e-1,o);return i.getFullYear()===r&&i.getMonth()===e-1&&i.getDate()===o}function P(s,t){let n=null,r=null,e=null,o=null,i=t.match(/\b(\d{5}-\d{4})\b/)||t.match(/\b(\d{5})\b/);i?.[1]&&(o=i[1]);let f=t.match(/\b(\d+\s+[A-Z0-9\s]+(?:ST|STREET|AVE|AVENUE|BLVD|BOULEVARD|RD|ROAD|DR|DRIVE|LN|LANE|CT|COURT|WAY|PL|PLACE|CIR|CIRCLE))\b/);f?.[1]&&(n=f[1].trim());let l=t.match(/\n([A-Z][A-Z\s]+)[,\s]+([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(l?.[1]&&l[2]&&l[3])r=l[1].trim(),e=l[2],o=l[3];else {let a=t.match(/([A-Z][A-Z\s]{2,20}),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(a?.[1]&&a[2]&&a[3]){let d=a[1].trim();!d.match(/^\d+\s+/)&&(!n||!d.includes(n))&&(r=d,e=a[2],o=a[3]);}}if(!n&&!r&&!e&&!o)return null;let u=[n,r,e,o].filter(Boolean);return {street:n,city:r,state:e,zipCode:o,full:u.length>0?u.join(", "):null}}function U(s){for(let t of c.SEX){let n=new RegExp(`${p(t)}[:\\s]*(M|F|MALE|FEMALE)\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="M"||e==="MALE")return "M";if(e==="F"||e==="FEMALE")return "F"}}return null}function _(s){let t=[/HT[:\s]*(\d)'(\d{1,2})"/,/HT[:\s]*(\d)-(\d{1,2})/,/HT[:\s]*(\d)(\d{2})\b/,/HEIGHT[:\s]*(\d)'(\d{1,2})"/];for(let n of t){let r=s.match(n);if(r?.[1]&&r[2]){let e=parseInt(r[1],10),o=parseInt(r[2],10);if(e>=3&&e<=8&&o>=0&&o<=11)return `${e}'${o}"`}}return null}function k(s){let t=[/WT[:\s]*(\d{2,3})\s*(?:LBS?)?/i,/WEIGHT[:\s]*(\d{2,3})/i];for(let n of t){let r=s.match(n);if(r?.[1]){let e=parseInt(r[1],10);if(e>=50&&e<=500)return `${e} lbs`}}return null}function $(s){for(let t of c.EYES){let n=new RegExp(`${p(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="COLOR"||e==="COLO")continue;return R[e]??e}}return null}function H(s){for(let t of c.HAIR){let n=new RegExp(`${p(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="COLOR"||e==="COLO")continue;return N[e]??e}}return null}function Z(s){for(let[r,e]of Object.entries(h))if(s.includes(e.toUpperCase()))return r;let t=/\b([A-Z]{2})\s+DRIVER(?:'?S)?\s+LICENSE/,n=s.match(t);return n?.[1]&&S.includes(n[1])?n[1]:null}function x(s,t){let n=[];for(let r of t){let e=new RegExp(`\\b${p(r)}[:\\s]+([A-Z0-9,\\s]+?)(?:\\n|$)`,"i"),o=s.match(e);if(o?.[1]){let i=o[1].trim();if(i.toUpperCase()==="NONE")return [];let f=i.split(/[,\s]+/).filter(l=>{let u=l.toUpperCase();return u.length>0&&u.length<=3&&!u.match(/^(RESTR|ICTIONS|ENDOR|SEMENTS|NONE)$/i)});n.push(...f);}}return [...new Set(n)]}function L(s){return s.replace(/[^A-Z\s,.-]/gi,"").replace(/\s+/g," ").trim()}function p(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function A(s){let t=s.toUpperCase(),n=0,r=[/DRIVER(?:'?S)?\s*LICENSE/,/OPERATOR(?:'?S)?\s*LICENSE/,/IDENTIFICATION\s*CARD/,/\bDL\b/,/\bDOB\b/,/\bEXP(?:IRES?)?\b/,/CLASS\s*[A-Z]/];for(let e of r)e.test(t)&&n++;for(let e of Object.values(h))if(t.includes(e.toUpperCase())){n++;break}return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}/.test(s)&&n++,n>=2}var I=class{constructor(t={}){this.options={language:"eng",psm:3,oem:1,timeout:D,...t};}async extract(t){let n=Date.now(),r=[],e=await this.prepareImage(t),o={};this.options.workerPath&&(o.workerPath=this.options.workerPath),this.options.corePath&&(o.corePath=this.options.corePath),this.options.langPath&&(o.langPath=this.options.langPath),this.options.onProgress&&(o.logger=f=>{this.options.onProgress?.({status:f.status,progress:f.progress});});let i=await M__default.default.createWorker(this.options.language??"eng",this.options.oem??1,o);try{await i.setParameters({tessedit_pageseg_mode:String(this.options.psm??3)});let f=i.recognize(e),l=new Promise((z,b)=>{setTimeout(()=>b(new Error("OCR timeout exceeded")),this.options.timeout);}),{data:u}=await Promise.race([f,l]),a=m(u.text);a.confidence=Math.round(u.confidence);let d=A(u.text);return d||r.push("Image may not be a driver license"),a.licenseNumber||r.push("License number not detected"),!a.fullName&&!a.firstName&&!a.lastName&&r.push("Name not detected"),a.dateOfBirth||r.push("Date of birth not detected"),{data:a,processingTimeMs:Date.now()-n,isDriverLicense:d,warnings:r}}finally{await i.terminate();}}async prepareImage(t){if(typeof t=="string"){if(t.length>2048)throw new Error("URL/path too long");if(t.startsWith("http://")||t.startsWith("https://"))try{new URL(t);}catch{throw new Error("Invalid URL")}return t}if(typeof Blob<"u"&&t instanceof Blob){if(t.size>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return t}if(typeof Buffer<"u"&&Buffer.isBuffer(t)){if(t.length>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return t}if(t instanceof ArrayBuffer){if(t.byteLength>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return typeof Buffer<"u"?Buffer.from(t):t}if(t instanceof Uint8Array){if(t.byteLength>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);if(typeof Buffer<"u")return Buffer.from(t.buffer,t.byteOffset,t.byteLength);let n=t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength);return new Blob([n])}throw new Error("Unsupported image input type")}};async function F(s,t){return new I(t).extract(s)}async function v(s={}){let t={language:"eng",psm:3,oem:1,timeout:D,...s},n={};t.workerPath&&(n.workerPath=t.workerPath),t.corePath&&(n.corePath=t.corePath),t.langPath&&(n.langPath=t.langPath),t.onProgress&&(n.logger=e=>{t.onProgress?.({status:e.status,progress:e.progress});});let r=await M__default.default.createWorker(t.language??"eng",t.oem??1,n);return await r.setParameters({tessedit_pageseg_mode:String(t.psm??3)}),{async extract(e){let o=Date.now(),i=[],f;if(typeof e=="string")f=e;else if(typeof Blob<"u"&&e instanceof Blob)f=e;else if(typeof Buffer<"u"&&Buffer.isBuffer(e))f=e;else if(e instanceof ArrayBuffer)f=typeof Buffer<"u"?Buffer.from(e):e;else if(e instanceof Uint8Array)f=typeof Buffer<"u"?Buffer.from(e.buffer,e.byteOffset,e.byteLength):new Blob([e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)]);else throw new Error("Unsupported image input type");let{data:l}=await r.recognize(f),u=m(l.text);u.confidence=Math.round(l.confidence);let a=A(l.text);return a||i.push("Image may not be a driver license"),u.licenseNumber||i.push("License number not detected"),{data:u,processingTimeMs:Date.now()-o,isDriverLicense:a,warnings:i}},async terminate(){await r.terminate();}}}function W(s){let t=[],n=0,r=11;s.licenseNumber?n+=2:t.push("licenseNumber"),s.dateOfBirth?n+=2:t.push("dateOfBirth"),s.expirationDate?n+=2:t.push("expirationDate"),s.fullName||s.firstName&&s.lastName?n+=1:t.push("name"),s.address?n+=1:t.push("address"),s.issuingState?n+=1:t.push("issuingState"),s.sex?n+=1:t.push("sex"),s.licenseClass?n+=1:t.push("licenseClass");let e=Math.round(n/r*100);return {isValid:e>=40&&s.licenseNumber!==null,score:e,issues:t}}
3
+ exports.DriverLicenseOCR=I;exports.EYE_COLORS=R;exports.HAIR_COLORS=N;exports.STATE_CODES=S;exports.US_STATES=h;exports.createOCR=v;exports.detectDriverLicense=A;exports.extract=F;exports.parseOCRText=m;exports.sanitizeText=C;exports.validate=W;
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import M from'tesseract.js';var h=Object.freeze({AL:"Alabama",AK:"Alaska",AZ:"Arizona",AR:"Arkansas",CA:"California",CO:"Colorado",CT:"Connecticut",DE:"Delaware",FL:"Florida",GA:"Georgia",HI:"Hawaii",ID:"Idaho",IL:"Illinois",IN:"Indiana",IA:"Iowa",KS:"Kansas",KY:"Kentucky",LA:"Louisiana",ME:"Maine",MD:"Maryland",MA:"Massachusetts",MI:"Michigan",MN:"Minnesota",MS:"Mississippi",MO:"Missouri",MT:"Montana",NE:"Nebraska",NV:"Nevada",NH:"New Hampshire",NJ:"New Jersey",NM:"New Mexico",NY:"New York",NC:"North Carolina",ND:"North Dakota",OH:"Ohio",OK:"Oklahoma",OR:"Oregon",PA:"Pennsylvania",RI:"Rhode Island",SC:"South Carolina",SD:"South Dakota",TN:"Tennessee",TX:"Texas",UT:"Utah",VT:"Vermont",VA:"Virginia",WA:"Washington",WV:"West Virginia",WI:"Wisconsin",WY:"Wyoming",DC:"District of Columbia"}),S=Object.freeze(Object.keys(h)),R=Object.freeze({BLK:"Black",BLU:"Blue",BRO:"Brown",GRY:"Gray",GRN:"Green",HAZ:"Hazel",MAR:"Maroon",PNK:"Pink",DIC:"Dichromatic",UNK:"Unknown"}),N=Object.freeze({BAL:"Bald",BLK:"Black",BLN:"Blonde",BRO:"Brown",GRY:"Gray",RED:"Red",SDY:"Sandy",WHI:"White",UNK:"Unknown"}),c=Object.freeze({NAME:["NAME","FN","FULL NAME","NM"],FIRST_NAME:["FIRST","FIRST NAME","FN","GIVEN NAME","1"],MIDDLE_NAME:["MIDDLE","MIDDLE NAME","MN","2"],LAST_NAME:["LAST","LAST NAME","LN","FAMILY NAME","SURNAME","3"],LICENSE_NUMBER:["DL","LIC","LICENSE","DL NO","LIC NO","LICENSE NO","DRIVER LICENSE","DLN","4D"],CLASS:["CLASS","CLS","9"],DOB:["DOB","DATE OF BIRTH","BIRTH DATE","BD","BORN","3"],EXPIRATION:["EXP","EXPIRES","EXPIRATION","EXP DATE","4B"],ISSUE_DATE:["ISS","ISSUED","ISSUE DATE","4A"],SEX:["SEX","GENDER","S","11"],HEIGHT:["HT","HEIGHT","15"],WEIGHT:["WT","WEIGHT","16"],EYES:["EYES","EYE","EYE COLOR","14"],HAIR:["HAIR","HAIR COLOR","13"],ADDRESS:["ADDRESS","ADDR","ADD","8"],RESTRICTIONS:["RESTR","RESTRICTIONS","REST","12"],ENDORSEMENTS:["END","ENDORSEMENTS","ENDORSE","10"],DD:["DD","DAQ","DOCUMENT DISCRIMINATOR","AUDIT"]}),E=10*1024*1024,D=6e4;function C(s){return typeof s!="string"?"":s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,"")}function m(s){let t=C(s),n=t.toUpperCase(),r=t.split(`
2
+ `).map(e=>e.trim()).filter(Boolean);return {fullName:w(r,n),firstName:g(r,c.FIRST_NAME),middleName:g(r,c.MIDDLE_NAME),lastName:g(r,c.LAST_NAME),licenseNumber:y(n),dateOfBirth:T(n,c.DOB),expirationDate:T(n,c.EXPIRATION),issueDate:T(n,c.ISSUE_DATE),address:P(r,n),sex:U(n),height:_(n),weight:k(n),eyeColor:$(n),hairColor:H(n),issuingState:Z(n),licenseClass:g(r,c.CLASS),restrictions:x(n,c.RESTRICTIONS),endorsements:x(n,c.ENDORSEMENTS),documentDiscriminator:g(r,c.DD),rawText:t,confidence:0}}function w(s,t){for(let n of c.NAME){let r=new RegExp(`${p(n)}[:\\s]+([A-Z][A-Z'\\-]+),\\s*([A-Z][A-Z'\\s.-]+)`,"i"),e=t.match(r);if(e?.[1]&&e[2])return L(`${e[2]} ${e[1]}`)}for(let n of c.NAME){let r=new RegExp(`${p(n)}[:\\s]+([A-Z][A-Z\\s,.-]+)`,"i"),e=t.match(r);if(e?.[1])return L(e[1])}for(let n of s){let r=n.match(/^([A-Z][A-Z'-]+),\s*([A-Z][A-Z'\s.-]+)$/i);if(r?.[1]&&r[2])return L(`${r[2]} ${r[1]}`)}return null}function g(s,t){for(let n of t){let r=new RegExp(`(?:^|\\s)${p(n)}[:\\s]+([^\\n]+)`,"i");for(let e of s){let o=e.match(r);if(o?.[1])return o[1].trim()}}return null}function y(s){for(let n of c.LICENSE_NUMBER){let r=new RegExp(`${p(n)}[:\\s#]*([A-Z0-9-]{6,15})`,"i"),e=s.match(r);if(e?.[1]){let o=e[1].replace(/[^A-Z0-9]/gi,"");if(o.length>=6&&o.length<=15)return o}}return s.match(/\b([A-Z]\d{7,12}|\d{7,12}|[A-Z]{2}\d{6,10})\b/)?.[1]??null}function T(s,t){for(let n of t){let r=new RegExp(`${p(n)}[:\\s]*([\\d/\\-]+)`),e=s.match(r);if(e?.[1]){let o=B(e[1]);if(o)return o}}return null}function B(s){let t=s.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/);if(t?.[1]&&t[2]&&t[3]){let n=t[1].padStart(2,"0"),r=t[2].padStart(2,"0");if(O(t[3],n,r))return `${t[3]}-${n}-${r}`}return t=s.match(/(\d{4})[\/\-](\d{2})[\/\-](\d{2})/),t?.[1]&&t[2]&&t[3]&&O(t[1],t[2],t[3])?`${t[1]}-${t[2]}-${t[3]}`:(t=s.match(/^(\d{2})(\d{2})(\d{4})$/),t?.[1]&&t[2]&&t[3]&&O(t[3],t[1],t[2])?`${t[3]}-${t[1]}-${t[2]}`:null)}function O(s,t,n){let r=parseInt(s,10),e=parseInt(t,10),o=parseInt(n,10);if(r<1900||r>2100||e<1||e>12||o<1||o>31)return false;let i=new Date(r,e-1,o);return i.getFullYear()===r&&i.getMonth()===e-1&&i.getDate()===o}function P(s,t){let n=null,r=null,e=null,o=null,i=t.match(/\b(\d{5}-\d{4})\b/)||t.match(/\b(\d{5})\b/);i?.[1]&&(o=i[1]);let f=t.match(/\b(\d+\s+[A-Z0-9\s]+(?:ST|STREET|AVE|AVENUE|BLVD|BOULEVARD|RD|ROAD|DR|DRIVE|LN|LANE|CT|COURT|WAY|PL|PLACE|CIR|CIRCLE))\b/);f?.[1]&&(n=f[1].trim());let l=t.match(/\n([A-Z][A-Z\s]+)[,\s]+([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(l?.[1]&&l[2]&&l[3])r=l[1].trim(),e=l[2],o=l[3];else {let a=t.match(/([A-Z][A-Z\s]{2,20}),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)/);if(a?.[1]&&a[2]&&a[3]){let d=a[1].trim();!d.match(/^\d+\s+/)&&(!n||!d.includes(n))&&(r=d,e=a[2],o=a[3]);}}if(!n&&!r&&!e&&!o)return null;let u=[n,r,e,o].filter(Boolean);return {street:n,city:r,state:e,zipCode:o,full:u.length>0?u.join(", "):null}}function U(s){for(let t of c.SEX){let n=new RegExp(`${p(t)}[:\\s]*(M|F|MALE|FEMALE)\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="M"||e==="MALE")return "M";if(e==="F"||e==="FEMALE")return "F"}}return null}function _(s){let t=[/HT[:\s]*(\d)'(\d{1,2})"/,/HT[:\s]*(\d)-(\d{1,2})/,/HT[:\s]*(\d)(\d{2})\b/,/HEIGHT[:\s]*(\d)'(\d{1,2})"/];for(let n of t){let r=s.match(n);if(r?.[1]&&r[2]){let e=parseInt(r[1],10),o=parseInt(r[2],10);if(e>=3&&e<=8&&o>=0&&o<=11)return `${e}'${o}"`}}return null}function k(s){let t=[/WT[:\s]*(\d{2,3})\s*(?:LBS?)?/i,/WEIGHT[:\s]*(\d{2,3})/i];for(let n of t){let r=s.match(n);if(r?.[1]){let e=parseInt(r[1],10);if(e>=50&&e<=500)return `${e} lbs`}}return null}function $(s){for(let t of c.EYES){let n=new RegExp(`${p(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="COLOR"||e==="COLO")continue;return R[e]??e}}return null}function H(s){for(let t of c.HAIR){let n=new RegExp(`${p(t)}[:\\s]+([A-Z]{2,4})\\b`,"i"),r=s.match(n);if(r?.[1]){let e=r[1].toUpperCase();if(e==="COLOR"||e==="COLO")continue;return N[e]??e}}return null}function Z(s){for(let[r,e]of Object.entries(h))if(s.includes(e.toUpperCase()))return r;let t=/\b([A-Z]{2})\s+DRIVER(?:'?S)?\s+LICENSE/,n=s.match(t);return n?.[1]&&S.includes(n[1])?n[1]:null}function x(s,t){let n=[];for(let r of t){let e=new RegExp(`\\b${p(r)}[:\\s]+([A-Z0-9,\\s]+?)(?:\\n|$)`,"i"),o=s.match(e);if(o?.[1]){let i=o[1].trim();if(i.toUpperCase()==="NONE")return [];let f=i.split(/[,\s]+/).filter(l=>{let u=l.toUpperCase();return u.length>0&&u.length<=3&&!u.match(/^(RESTR|ICTIONS|ENDOR|SEMENTS|NONE)$/i)});n.push(...f);}}return [...new Set(n)]}function L(s){return s.replace(/[^A-Z\s,.-]/gi,"").replace(/\s+/g," ").trim()}function p(s){return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function A(s){let t=s.toUpperCase(),n=0,r=[/DRIVER(?:'?S)?\s*LICENSE/,/OPERATOR(?:'?S)?\s*LICENSE/,/IDENTIFICATION\s*CARD/,/\bDL\b/,/\bDOB\b/,/\bEXP(?:IRES?)?\b/,/CLASS\s*[A-Z]/];for(let e of r)e.test(t)&&n++;for(let e of Object.values(h))if(t.includes(e.toUpperCase())){n++;break}return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{4}/.test(s)&&n++,n>=2}var I=class{constructor(t={}){this.options={language:"eng",psm:3,oem:1,timeout:D,...t};}async extract(t){let n=Date.now(),r=[],e=await this.prepareImage(t),o={};this.options.workerPath&&(o.workerPath=this.options.workerPath),this.options.corePath&&(o.corePath=this.options.corePath),this.options.langPath&&(o.langPath=this.options.langPath),this.options.onProgress&&(o.logger=f=>{this.options.onProgress?.({status:f.status,progress:f.progress});});let i=await M.createWorker(this.options.language??"eng",this.options.oem??1,o);try{await i.setParameters({tessedit_pageseg_mode:String(this.options.psm??3)});let f=i.recognize(e),l=new Promise((z,b)=>{setTimeout(()=>b(new Error("OCR timeout exceeded")),this.options.timeout);}),{data:u}=await Promise.race([f,l]),a=m(u.text);a.confidence=Math.round(u.confidence);let d=A(u.text);return d||r.push("Image may not be a driver license"),a.licenseNumber||r.push("License number not detected"),!a.fullName&&!a.firstName&&!a.lastName&&r.push("Name not detected"),a.dateOfBirth||r.push("Date of birth not detected"),{data:a,processingTimeMs:Date.now()-n,isDriverLicense:d,warnings:r}}finally{await i.terminate();}}async prepareImage(t){if(typeof t=="string"){if(t.length>2048)throw new Error("URL/path too long");if(t.startsWith("http://")||t.startsWith("https://"))try{new URL(t);}catch{throw new Error("Invalid URL")}return t}if(typeof Blob<"u"&&t instanceof Blob){if(t.size>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return t}if(typeof Buffer<"u"&&Buffer.isBuffer(t)){if(t.length>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return t}if(t instanceof ArrayBuffer){if(t.byteLength>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);return typeof Buffer<"u"?Buffer.from(t):t}if(t instanceof Uint8Array){if(t.byteLength>E)throw new Error(`Image exceeds maximum size of ${E/1024/1024}MB`);if(typeof Buffer<"u")return Buffer.from(t.buffer,t.byteOffset,t.byteLength);let n=t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength);return new Blob([n])}throw new Error("Unsupported image input type")}};async function F(s,t){return new I(t).extract(s)}async function v(s={}){let t={language:"eng",psm:3,oem:1,timeout:D,...s},n={};t.workerPath&&(n.workerPath=t.workerPath),t.corePath&&(n.corePath=t.corePath),t.langPath&&(n.langPath=t.langPath),t.onProgress&&(n.logger=e=>{t.onProgress?.({status:e.status,progress:e.progress});});let r=await M.createWorker(t.language??"eng",t.oem??1,n);return await r.setParameters({tessedit_pageseg_mode:String(t.psm??3)}),{async extract(e){let o=Date.now(),i=[],f;if(typeof e=="string")f=e;else if(typeof Blob<"u"&&e instanceof Blob)f=e;else if(typeof Buffer<"u"&&Buffer.isBuffer(e))f=e;else if(e instanceof ArrayBuffer)f=typeof Buffer<"u"?Buffer.from(e):e;else if(e instanceof Uint8Array)f=typeof Buffer<"u"?Buffer.from(e.buffer,e.byteOffset,e.byteLength):new Blob([e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)]);else throw new Error("Unsupported image input type");let{data:l}=await r.recognize(f),u=m(l.text);u.confidence=Math.round(l.confidence);let a=A(l.text);return a||i.push("Image may not be a driver license"),u.licenseNumber||i.push("License number not detected"),{data:u,processingTimeMs:Date.now()-o,isDriverLicense:a,warnings:i}},async terminate(){await r.terminate();}}}function W(s){let t=[],n=0,r=11;s.licenseNumber?n+=2:t.push("licenseNumber"),s.dateOfBirth?n+=2:t.push("dateOfBirth"),s.expirationDate?n+=2:t.push("expirationDate"),s.fullName||s.firstName&&s.lastName?n+=1:t.push("name"),s.address?n+=1:t.push("address"),s.issuingState?n+=1:t.push("issuingState"),s.sex?n+=1:t.push("sex"),s.licenseClass?n+=1:t.push("licenseClass");let e=Math.round(n/r*100);return {isValid:e>=40&&s.licenseNumber!==null,score:e,issues:t}}
3
+ export{I as DriverLicenseOCR,R as EYE_COLORS,N as HAIR_COLORS,S as STATE_CODES,h as US_STATES,v as createOCR,A as detectDriverLicense,F as extract,m as parseOCRText,C as sanitizeText,W as validate};
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "usa-dl-ocr",
3
+ "version": "1.0.0",
4
+ "description": "Framework-agnostic USA driver license OCR extraction using Tesseract.js",
5
+ "author": "Sepehr Mohseni <https://github.com/sepehr-mohseni>",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/sepehr-mohseni/usa-dl-ocr.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/sepehr-mohseni/usa-dl-ocr/issues"
13
+ },
14
+ "homepage": "https://github.com/sepehr-mohseni/usa-dl-ocr#readme",
15
+ "main": "dist/index.js",
16
+ "module": "dist/index.mjs",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "bin": {
26
+ "usa-dl-ocr": "./dist/cli.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "LICENSE"
31
+ ],
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "build": "tsup && node scripts/postbuild.js",
35
+ "dev": "tsup --watch",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:coverage": "vitest run --coverage",
39
+ "lint": "eslint src --ext .ts",
40
+ "typecheck": "tsc --noEmit",
41
+ "prepublishOnly": "npm run build",
42
+ "size": "size-limit",
43
+ "audit:fix": "npm audit fix",
44
+ "clean": "rm -rf dist coverage"
45
+ },
46
+ "keywords": [
47
+ "ocr",
48
+ "driver-license",
49
+ "usa",
50
+ "tesseract",
51
+ "tesseract.js",
52
+ "document-recognition",
53
+ "id-extraction",
54
+ "license-parser"
55
+ ],
56
+ "dependencies": {
57
+ "tesseract.js": "^5.1.1"
58
+ },
59
+ "devDependencies": {
60
+ "@size-limit/preset-small-lib": "^11.2.0",
61
+ "@types/node": "^22.10.5",
62
+ "@vitest/coverage-v8": "^3.0.0",
63
+ "commander": "^13.0.0",
64
+ "size-limit": "^11.2.0",
65
+ "tsup": "^8.3.5",
66
+ "tsx": "^4.19.2",
67
+ "typescript": "^5.7.2",
68
+ "vitest": "^3.0.0"
69
+ },
70
+ "peerDependencies": {
71
+ "commander": ">=12.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "commander": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "engines": {
79
+ "node": ">=18.0.0"
80
+ },
81
+ "size-limit": [
82
+ {
83
+ "path": "dist/index.mjs",
84
+ "limit": "10 KB"
85
+ }
86
+ ]
87
+ }