API del bridge JavaScript
Scanner QR e codici a barre
La schermata dello scanner di Google con una sola promise, AppMint.scanCode(): nessuna autorizzazione per la fotocamera, e URL, Wi-Fi, telefono o contatto già interpretati per i codici QR più comuni.
AppMint.scanCode helper#
AppMint.scanCode(options)
Esempio
Apre la schermata dello scanner di codici QR e codici a barre di Google e ti restituisce ciò che è stato scansionato. Una sola chiamata: nessuna autorizzazione per la fotocamera, nessun <video>, nessuna interfaccia tua.
Restituisce: una Promise che si risolve sempre: {ok: true, value, displayValue, format, type, …} oppure {ok: false, code, message}. Richiede: nulla da attivare. Il telefono ha bisogno di Google Play services.
Qualsiasi codice:
async function scan() {
if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
var r = await AppMint.scanCode();
if (!r.ok) {
if (r.code === 'cancelled') return; // the user closed the scanner
if (r.code === 'unavailable') alert('The scanner is not available on this phone');
else if (r.code === 'busy') alert('A scan is already open');
else alert('Scan failed: ' + r.message); // 'failed'
return;
}
console.log(r.value, r.format, r.type); // "TICKET-4821", "qr_code", "text"
}Solo alcuni formati, e un'opzione "digitalo" per le etichette rovinate:
var r = await AppMint.scanCode({
formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'], // shop barcodes
manualInput: true, // lets the user type the code
autoZoom: true // default true
});
if (r.ok) lookUpProduct(r.value);Parti già pronte: è presente solo la parte che corrisponde a r.type:
var r = await AppMint.scanCode({ formats: ['qr_code'] });
if (r.ok) {
if (r.type === 'url') openLink(r.url.url); // url: { url, title }
if (r.type === 'wifi') showWifi(r.wifi.ssid, r.wifi.password, r.wifi.encryption); // 'wpa'|'wep'|'open'
if (r.type === 'phone') callNumber(r.phone); // a string
if (r.type === 'email') writeMail(r.email.address, r.email.subject, r.email.body);
if (r.type === 'sms') writeSms(r.sms.phoneNumber, r.sms.message);
if (r.type === 'geo') showMap(r.geo.lat, r.geo.lng);
if (r.type === 'contact') addContact(r.contact.name, r.contact.organization, r.contact.phones, r.contact.emails);
}Note: nomi dei formati: qr_code, aztec, codabar, code_39, code_93, code_128, data_matrix, ean_8, ean_13, itf, pdf417, upc_a, upc_e (i nomi sconosciuti vengono ignorati; nessuno = tutti i formati). type è uno tra url, wifi, text, phone, email, sms, geo, contact, calendar, isbn, product, driver_license, unknown. Codici di errore: cancelled (chiuso, anche con Indietro), unavailable (niente Play services, o lo scanner non si è potuto scaricare), busy (una scansione è già aperta), failed. La prima scansione su un telefono può richiedere qualche secondo mentre Play services scarica lo scanner. Le app create con l'AI usano scanCode() da @/lib/appmintNative, che esegue la scansione anche sul web.
AppMint.textRecognition helper#
AppMint.textRecognition.recognize(image, {script}) / prepare(script, onProgress)
Esempio
Legge testo in alfabeti oltre il latino: devanagari (hindi, marathi, nepalese …), cinese, giapponese e coreano, oppure 'auto', che prova ogni alfabeto e tiene la lettura con più caratteri. La stessa opzione funziona sul rilevatore standard: new TextDetector({ script: 'devanagari' }). Senza l'opzione tutto viene letto come latino, esattamente come prima.
Restituisce: recognize() → una Promise di {script, text, blocks: [{text, box, corners, lines}]} (script = quello usato); viene rifiutata con una DOMException: NotSupportedError per un alfabeto sconosciuto o senza Google Play services, OperationError negli altri casi. prepare() → una Promise di {script, status: 'ready'}. Richiede: Google Play services. Il riconoscimento di ogni alfabeto è un download di Play services: gli alfabeti che il tuo codice nomina vengono installati con l'app, gli altri al primo uso.
Testo hindi da una foto:
document.getElementById('photo').addEventListener('change', async function (e) {
var file = e.target.files[0];
if (!file || !(window.AppMint && AppMint.textRecognition)) return;
try {
var r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
document.getElementById('out').textContent = r.text;
} catch (err) {
alert('Could not read the photo: ' + err.name);
}
});Scarica prima il modello di un alfabeto, con l'avanzamento: altrimenti il primo uso su un telefono resta in attesa del download:
async function getReady() {
var bar = document.getElementById('model-progress');
await AppMint.textRecognition.prepare('japanese', function (s) {
if (s.progress != null) bar.value = s.progress; // 0..1 while 'downloading'
});
bar.hidden = true;
}
// The same states arrive as events, also when a recognize() call starts a download:
window.addEventListener('appmint:ocr-model', function (e) {
console.log(e.detail.script, e.detail.status, e.detail.progress); // 'downloading' | 'ready' | 'failed'
});Documenti misti: lascia che l'app scelga tra gli alfabeti che ti aspetti:
var r = await AppMint.textRecognition.recognize(img, { script: 'auto', scripts: ['latin', 'chinese', 'korean'] });
console.log('read as ' + r.script + ': ' + r.text);
var lines = await new TextDetector({ script: 'korean' }).detect(img); // standard shape, one entry per lineNote: scripts è l'elenco di AppMint: latin, devanagari, chinese, japanese, korean. Arabo, cirillico, thai ed ebraico non hanno un riconoscimento sul dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) è il meccanismo interno; usa l'helper.
__ocrAvailable bridge#
window.WebToApk.__ocrAvailable(): Boolean
TextDetector / recognizeText(): qui c'è Google Play services (la casa del modulo OCR).
Esempio
Meccanismo interno dietro TextDetector: indica se Google Play services (da cui arriva il riconoscimento) è presente su questo telefono. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.
Restituisce: true oppure false. Quando è false, TextDetector non è installato affatto. Richiede: nulla.
Usa l'API pubblica:
if ('TextDetector' in window) scanButton.hidden = false;Scegli una sola volta, all'avvio, tra il flusso di scansione e un modulo da compilare a mano. Quando il telefono non ha Google Play services il rilevatore semplicemente non c'è, quindi la pagina deve offrire un'altra strada:
function setUpReceiptEntry() {
var canScan = typeof window.TextDetector === 'function';
document.getElementById('scan-receipt').hidden = !canScan;
document.getElementById('type-receipt').hidden = canScan;
if (!canScan) document.getElementById('entry-hint').textContent =
'Text scanning is not available on this phone. Type the total below.';
}
document.addEventListener('DOMContentLoaded', setUpReceiptEntry);Un rilevatore che esiste può comunque rispondere che il riconoscimento non è disponibile nel momento della chiamata. Distingui i due errori dal nome dell'errore:
async function readLabel(img) {
if (typeof window.TextDetector !== 'function') return null;
try {
var lines = await new TextDetector().detect(img);
return lines.map(function (l) { return l.rawValue; }).join('\n');
} catch (e) {
if (e.name === 'NotSupportedError') showMessage('Text recognition is not available right now.');
else showMessage('Could not read the label: ' + e.message);
return null;
}
}Note: implementato nella shell dell'app (TextRecognitionBridge.kt).
__ocrRecognize bridge#
window.WebToApk.__ocrRecognize(requestId: String, base64: String)
Riconosce il testo in [base64] (PNG/JPEG); la risposta arriva come `appmint:ocr {requestId, blocks|error}`.
Esempio
Meccanismo interno dietro TextDetector.detect(): riconosce il testo in un PNG/JPEG. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.
Restituisce: niente; la risposta arriva come evento appmint:ocr {requestId, blocks:[{text, box, corners, lines}]} oppure {requestId, error} (unavailable, bad-image, failed: …). Richiede: nulla.
Usa l'API pubblica:
const lines = await new TextDetector().detect(document.getElementById('receipt'));
console.log(lines.map((l) => l.rawValue));Leggi il testo di una foto scelta dall'utente. detect() accetta un Blob/File oltre a un <img>, <canvas> o ImageData:
<input type="file" id="doc" accept="image/*" capture="environment">
<pre id="out"></pre>
<script>
document.getElementById('doc').addEventListener('change', async function (e) {
var file = e.target.files[0];
if (!file || typeof window.TextDetector !== 'function') return;
try {
var lines = await new TextDetector().detect(file);
document.getElementById('out').textContent = lines.map(function (l) { return l.rawValue; }).join('\n');
} catch (err) {
document.getElementById('out').textContent = 'Could not read the photo (' + err.name + ')';
}
});
</script>Evidenzia ogni riga riconosciuta sopra l'immagine. boundingBox è nei pixel dell'immagine stessa, quindi scalalo alla dimensione visualizzata. L'immagine deve aver finito di caricarsi, altrimenti detect() viene rifiutato con InvalidStateError:
async function highlightLines(img, overlay) {
if (!img.complete) await new Promise(function (r) { img.onload = r; });
var lines = await new TextDetector().detect(img);
var sx = img.clientWidth / img.naturalWidth, sy = img.clientHeight / img.naturalHeight;
overlay.innerHTML = '';
lines.forEach(function (l) {
var b = l.boundingBox, box = document.createElement('div');
box.className = 'ocr-box';
box.title = l.rawValue;
box.style.cssText = 'position:absolute;left:' + b.x * sx + 'px;top:' + b.y * sy +
'px;width:' + b.width * sx + 'px;height:' + b.height * sy + 'px';
overlay.appendChild(box);
});
}Estrai un importo da uno scontrino. Un'immagine di un'altra origine che contamina il canvas viene rifiutata con SecurityError, quindi servi l'immagine dall'origine dell'app stessa:
async function findTotal(img) {
try {
var lines = await new TextDetector().detect(img);
var hit = lines.find(function (l) { return /total/i.test(l.rawValue); });
var m = hit && hit.rawValue.match(/(\d+[.,]\d{2})/);
return m ? parseFloat(m[1].replace(',', '.')) : null;
} catch (e) {
if (e.name === 'SecurityError') console.warn('Image is from another origin; load it from the app instead.');
else console.warn('OCR failed:', e.name, e.message);
return null;
}
}Note: implementato nella shell dell'app (TextRecognitionBridge.kt).
__ocrRecognizeWith bridge#
window.WebToApk.__ocrRecognizeWith(requestId: String, base64: String, optionsJson: String)
[__ocrRecognize] con le opzioni [optionsJson] `{script: 'latin'|'devanagari'|'chinese'|'japanese'|'korean'|'auto', scripts?}`; anche la risposta `appmint:ocr` indica lo `script` usato.
Esempio
Documentato insieme a AppMint.textRecognition - l'esempio lì mostra anche questo.
Legge testo in alfabeti oltre il latino: devanagari (hindi, marathi, nepalese …), cinese, giapponese e coreano, oppure 'auto', che prova ogni alfabeto e tiene la lettura con più caratteri. La stessa opzione funziona sul rilevatore standard: new TextDetector({ script: 'devanagari' }). Senza l'opzione tutto viene letto come latino, esattamente come prima.
Restituisce: recognize() → una Promise di {script, text, blocks: [{text, box, corners, lines}]} (script = quello usato); viene rifiutata con una DOMException: NotSupportedError per un alfabeto sconosciuto o senza Google Play services, OperationError negli altri casi. prepare() → una Promise di {script, status: 'ready'}. Richiede: Google Play services. Il riconoscimento di ogni alfabeto è un download di Play services: gli alfabeti che il tuo codice nomina vengono installati con l'app, gli altri al primo uso.
Testo hindi da una foto:
document.getElementById('photo').addEventListener('change', async function (e) {
var file = e.target.files[0];
if (!file || !(window.AppMint && AppMint.textRecognition)) return;
try {
var r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
document.getElementById('out').textContent = r.text;
} catch (err) {
alert('Could not read the photo: ' + err.name);
}
});Scarica prima il modello di un alfabeto, con l'avanzamento: altrimenti il primo uso su un telefono resta in attesa del download:
async function getReady() {
var bar = document.getElementById('model-progress');
await AppMint.textRecognition.prepare('japanese', function (s) {
if (s.progress != null) bar.value = s.progress; // 0..1 while 'downloading'
});
bar.hidden = true;
}
// The same states arrive as events, also when a recognize() call starts a download:
window.addEventListener('appmint:ocr-model', function (e) {
console.log(e.detail.script, e.detail.status, e.detail.progress); // 'downloading' | 'ready' | 'failed'
});Documenti misti: lascia che l'app scelga tra gli alfabeti che ti aspetti:
var r = await AppMint.textRecognition.recognize(img, { script: 'auto', scripts: ['latin', 'chinese', 'korean'] });
console.log('read as ' + r.script + ': ' + r.text);
var lines = await new TextDetector({ script: 'korean' }).detect(img); // standard shape, one entry per lineNote: scripts è l'elenco di AppMint: latin, devanagari, chinese, japanese, korean. Arabo, cirillico, thai ed ebraico non hanno un riconoscimento sul dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) è il meccanismo interno; usa l'helper.
prepareTextRecognition bridge#
window.WebToApk.prepareTextRecognition(script: String): String
Scarica in anticipo il riconoscimento del testo per [script]. Restituisce "checking" (poi `appmint:ocr-model {script, status: 'downloading'|'ready'|'failed', progress?}`), "unsupported-script" oppure "unavailable" (senza Google Play services).
Esempio
Scarica in anticipo il riconoscimento del testo sul dispositivo per un alfabeto, così il primo recognize() non deve aspettare. Le pagine di solito chiamano AppMint.textRecognition.prepare(script), che lo incapsula.
Restituisce: una stringa subito: "checking" (l'esito segue come eventi appmint:ocr-model {script, status: 'downloading' | 'ready' | 'failed', progress?, error?}), "unsupported-script" o "unavailable" (senza Google Play services). 'auto' prepara tutti gli script. Richiede: Google Play services.
if (window.WebToApk && typeof window.WebToApk.prepareTextRecognition === 'function') {
window.addEventListener('appmint:ocr-model', function (e) {
if (e.detail.script !== 'chinese') return;
if (e.detail.status === 'downloading') console.log('downloading', Math.round((e.detail.progress || 0) * 100) + '%');
if (e.detail.status === 'ready') document.getElementById('scan').disabled = false;
if (e.detail.status === 'failed') console.warn(e.detail.error);
});
var state = window.WebToApk.prepareTextRecognition('chinese');
if (state !== 'checking') console.warn('Chinese text recognition: ' + state);
}Prepara, poi scansiona tramite l'helper:
async function readSign(canvas) {
if (!(window.AppMint && AppMint.textRecognition)) return null;
try {
await AppMint.textRecognition.prepare('japanese');
return await AppMint.textRecognition.recognize(canvas, { script: 'japanese' });
} catch (err) {
console.warn('Text recognition:', err.name, err.message); // e.g. NotSupportedError without Play services
return null;
}
}Alfabeti non supportati e Play services mancanti vengono segnalati nel valore restituito:
const W = window.WebToApk;
if (W && typeof W.prepareTextRecognition === 'function') {
const state = W.prepareTextRecognition('arabic');
if (state === 'unsupported-script') showMessage('Arabic text cannot be read on the phone.');
else if (state === 'unavailable') showMessage('Text recognition needs Google Play services.');
}Note: alfabeti: latin, devanagari, chinese, japanese, korean, auto. Un alfabeto già presente sul telefono risponde subito ready.
scanCode bridge#
window.WebToApk.scanCode(optionsJson: String, callbackId: String)
Apre lo scanner di codici di Google; [optionsJson] `{formats?, autoZoom?, manualInput?}`. Risolve la promise AppMint.scanCode [callbackId].
Esempio
Meccanismo interno dietro AppMint.scanCode(): le pagine usano quell'helper basato su Promise, che apre lo scanner QR/codici a barre di Google e restituisce il risultato.
Restituisce: niente direttamente; il risultato risolve la promise AppMint.scanCode(). Richiede: nulla da attivare (nessuna autorizzazione per la fotocamera). Il telefono ha bisogno di Google Play services.
L'API pubblica:
async function scanTicket() {
if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
var r = await AppMint.scanCode({ formats: ['qr_code'] });
if (r.ok) checkTicket(r.value); // r.format, r.type, r.displayValue…
else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}Collegati a una rete Wi-Fi da un codice QR stampato. Un codice Wi-Fi torna con type: 'wifi' e un oggetto wifi:
async function scanWifi() {
if (!(window.AppMint && AppMint.scanCode)) return;
var r = await AppMint.scanCode({ formats: ['qr_code'] });
if (!r.ok) { if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')'); return; }
if (r.type !== 'wifi') { alert('That is not a Wi-Fi code.'); return; }
showWifiCard(r.wifi.ssid, r.wifi.password, r.wifi.encryption); // encryption: 'wpa' | 'wep' | 'open'
}Cerca un prodotto dal codice a barre del negozio, lasciando che l'utente digiti le cifre quando l'etichetta è rovinata (manualInput). La prima scansione su un telefono può scaricare lo scanner, e fallisce con unavailable quando sei offline:
async function scanProduct() {
if (!(window.AppMint && AppMint.scanCode)) { showManualEntry(); return; }
var r = await AppMint.scanCode({ formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'], manualInput: true });
if (r.ok) return lookUpProduct(r.value, r.format); // e.g. '4006381333931', 'ean_13'
if (r.code === 'busy') return; // a scan is already open
if (r.code === 'unavailable') alert('The scanner is not available: ' + r.message);
else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}Apri ciò a cui punta un codice QR, dal suo contenuto decodificato type:
async function scanAndOpen() {
var r = await AppMint.scanCode({ formats: ['qr_code'], autoZoom: true });
if (!r.ok) return;
if (r.type === 'url') location.href = r.url.url;
else if (r.type === 'phone') location.href = 'tel:' + r.phone;
else if (r.type === 'email') location.href = 'mailto:' + r.email.address;
else if (r.type === 'geo') showOnMap(r.geo.lat, r.geo.lng);
else if (r.type === 'contact') saveContact(r.contact.name, r.contact.phones, r.contact.emails);
else showText(r.displayValue);
}Note: il metodo grezzo accetta optionsJson ({formats?, autoZoom?, manualInput?} come stringa JSON) e un callbackId; la risposta viene consegnata alla tabella di risposte interna dell'helper, quindi una pagina che lo chiama direttamente non riceve risposta. Una sola scansione alla volta: una seconda chiamata risponde code: 'busy'. Vedi AppMint.scanCode per ogni campo del risultato e codice di errore.