API da ponte JavaScript
Leitor de QR e código de barras
A tela de leitura do Google a partir de uma promessa, AppMint.scanCode() - sem permissão de câmera, e com a URL, o Wi-Fi, o telefone ou o contato já interpretados para os QR codes comuns.
AppMint.scanCode helper#
AppMint.scanCode(options)
Exemplo
Abre a tela do leitor de QR code e código de barras do Google e entrega o que foi lido. Uma chamada - sem permissão de câmera, sem <video>, sem interface própria.
Retorna: uma Promise que sempre é resolvida: {ok: true, value, displayValue, format, type, …} ou {ok: false, code, message}. Requer: nada para ativar. O celular precisa do Google Play services.
Qualquer código:
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"
}Apenas alguns formatos, e uma opção "digitar" para etiquetas danificadas:
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);Partes prontas - só está presente a parte que corresponde 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);
}Observações: Nomes de formato: qr_code, aztec, codabar, code_39, code_93, code_128, data_matrix, ean_8, ean_13, itf, pdf417, upc_a, upc_e (nomes desconhecidos são ignorados; nenhum = todos os formatos). type é um entre url, wifi, text, phone, email, sms, geo, contact, calendar, isbn, product, driver_license, unknown. Códigos de erro: cancelled (fechado, inclusive com Voltar), unavailable (sem Play services, ou não foi possível baixar o leitor), busy (já há uma leitura aberta), failed. A primeira leitura em um celular pode levar alguns segundos enquanto o Play services baixa o leitor. Apps criados com IA usam scanCode() de @/lib/appmintNative, que também lê na web.
AppMint.textRecognition helper#
AppMint.textRecognition.recognize(image, {script}) / prepare(script, onProgress)
Exemplo
Lê texto em alfabetos além do latino: devanágari (hindi, marati, nepalês …), chinês, japonês e coreano - ou 'auto', que testa cada alfabeto e fica com a leitura de mais caracteres. A mesma opção funciona no detector padrão: new TextDetector({ script: 'devanagari' }). Sem a opção, tudo é lido como latino, exatamente como antes.
Retorna: recognize() → uma Promise de {script, text, blocks: [{text, box, corners, lines}]} (script = o usado); é rejeitada com uma DOMException - NotSupportedError para um alfabeto desconhecido ou sem Google Play services, OperationError nos demais casos. prepare() → uma Promise de {script, status: 'ready'}. Requer: Google Play services. O reconhecedor de cada alfabeto é um download do Play services: os alfabetos que seu código cita são instalados com o app, os demais no primeiro uso.
Texto em hindi a partir de uma 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);
}
});Baixe antes o modelo de um alfabeto, com progresso - caso contrário, o primeiro uso em um celular espera o 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'
});Documentos mistos - deixe o app escolher entre os alfabetos que você espera:
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 lineObservações: scripts é a lista do AppMint: latin, devanagari, chinese, japanese, korean. Árabe, cirílico, tailandês e hebraico não têm reconhecedor no dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) é o transporte interno; use o auxiliar.
__ocrAvailable ponte#
window.WebToApk.__ocrAvailable(): Boolean
TextDetector / recognizeText(): o Google Play services está presente (a casa do módulo de OCR).
Exemplo
Transporte interno por trás de TextDetector: informa se o Google Play services (de onde vem o reconhecedor) está neste celular. As páginas usam a API padrão; nunca chame isto diretamente - os métodos __ podem mudar sem aviso.
Retorna: true ou false. Quando false, TextDetector não está instalado de forma alguma. Requer: nada.
Use a API pública:
if ('TextDetector' in window) scanButton.hidden = false;Escolha entre o fluxo de leitura e um formulário digitado uma única vez, na inicialização. Quando o celular não tem Google Play services, o detector simplesmente não existe, então a página precisa oferecer outro caminho:
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);Um detector que existe ainda pode responder que o reconhecimento está indisponível no momento da chamada. Diferencie as duas falhas pelo nome do erro:
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;
}
}Observações: Implementado no shell do app (TextRecognitionBridge.kt).
__ocrRecognize ponte#
window.WebToApk.__ocrRecognize(requestId: String, base64: String)
Reconhece o texto em [base64] (PNG/JPEG); a resposta chega como `appmint:ocr {requestId, blocks|error}`.
Exemplo
Transporte interno por trás de TextDetector.detect(): reconhece o texto em um PNG/JPEG. As páginas usam a API padrão; nunca chame isto diretamente - os métodos __ podem mudar sem aviso.
Retorna: nada; a resposta chega como o evento appmint:ocr {requestId, blocks:[{text, box, corners, lines}]} ou {requestId, error} (unavailable, bad-image, failed: …). Requer: nada.
Use a API pública:
const lines = await new TextDetector().detect(document.getElementById('receipt'));
console.log(lines.map((l) => l.rawValue));Leia o texto de uma foto escolhida pelo usuário. detect() aceita um Blob/File além de um <img>, <canvas> ou 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>Contorne cada linha reconhecida sobre a imagem. boundingBox está nos pixels da própria imagem, então ajuste à escala do tamanho exibido. A imagem precisa ter terminado de carregar, ou detect() é rejeitado com 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);
});
}Extraia um valor de um recibo. Uma imagem de outra origem que contamina o canvas é rejeitada com SecurityError, então sirva a imagem a partir da origem do próprio app:
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;
}
}Observações: Implementado no shell do app (TextRecognitionBridge.kt).
__ocrRecognizeWith ponte#
window.WebToApk.__ocrRecognizeWith(requestId: String, base64: String, optionsJson: String)
[__ocrRecognize] com opções [optionsJson] `{script: 'latin'|'devanagari'|'chinese'|'japanese'|'korean'|'auto', scripts?}`; a resposta `appmint:ocr` também informa o `script` usado.
Exemplo
Documentado junto com AppMint.textRecognition - o exemplo de lá mostra este também.
Lê texto em alfabetos além do latino: devanágari (hindi, marati, nepalês …), chinês, japonês e coreano - ou 'auto', que testa cada alfabeto e fica com a leitura de mais caracteres. A mesma opção funciona no detector padrão: new TextDetector({ script: 'devanagari' }). Sem a opção, tudo é lido como latino, exatamente como antes.
Retorna: recognize() → uma Promise de {script, text, blocks: [{text, box, corners, lines}]} (script = o usado); é rejeitada com uma DOMException - NotSupportedError para um alfabeto desconhecido ou sem Google Play services, OperationError nos demais casos. prepare() → uma Promise de {script, status: 'ready'}. Requer: Google Play services. O reconhecedor de cada alfabeto é um download do Play services: os alfabetos que seu código cita são instalados com o app, os demais no primeiro uso.
Texto em hindi a partir de uma 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);
}
});Baixe antes o modelo de um alfabeto, com progresso - caso contrário, o primeiro uso em um celular espera o 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'
});Documentos mistos - deixe o app escolher entre os alfabetos que você espera:
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 lineObservações: scripts é a lista do AppMint: latin, devanagari, chinese, japanese, korean. Árabe, cirílico, tailandês e hebraico não têm reconhecedor no dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) é o transporte interno; use o auxiliar.
prepareTextRecognition ponte#
window.WebToApk.prepareTextRecognition(script: String): String
Baixa com antecedência o reconhecedor de texto para [script]. Retorna "checking" (depois `appmint:ocr-model {script, status: 'downloading'|'ready'|'failed', progress?}`), "unsupported-script" ou "unavailable" (sem Google Play services).
Exemplo
Baixa com antecedência o reconhecedor de texto no dispositivo para um alfabeto, para que o primeiro recognize() não espere. As páginas normalmente chamam AppMint.textRecognition.prepare(script), que o encapsula.
Retorna: uma string imediatamente - "checking" (o resultado segue como eventos appmint:ocr-model {script, status: 'downloading' | 'ready' | 'failed', progress?, error?}), "unsupported-script" ou "unavailable" (sem Google Play services). 'auto' prepara todos os scripts. Requer: 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);
}Prepare e depois leia pelo wrapper:
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;
}
}Alfabetos não suportados e a falta do Play services são informados no valor de retorno:
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.');
}Observações: Alfabetos: latin, devanagari, chinese, japanese, korean, auto. Um alfabeto que já está no celular responde ready imediatamente.
scanCode ponte#
window.WebToApk.scanCode(optionsJson: String, callbackId: String)
Abre o leitor de códigos do Google; [optionsJson] `{formats?, autoZoom?, manualInput?}`. Resolve a promessa AppMint.scanCode [callbackId].
Exemplo
Infraestrutura interna por trás de AppMint.scanCode() - as páginas usam esse auxiliar de Promise, que abre o leitor de QR/código de barras do Google e retorna com o resultado.
Retorna: nada diretamente; o resultado resolve a promessa AppMint.scanCode(). Requer: nada para ativar (sem permissão de câmera). O celular precisa do Google Play services.
A API pública:
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 + ')');
}Conecte-se a uma rede Wi-Fi a partir de um QR code impresso. Um código de Wi-Fi volta com type: 'wifi' e um objeto 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'
}Consulte um produto pelo código de barras da loja, deixando o usuário digitar os dígitos quando a etiqueta está danificada (manualInput). A primeira leitura em um celular pode baixar o leitor, o que falha com unavailable quando 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 + ')');
}Abra o que quer que um QR code aponte, pelo seu valor decodificado 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);
}Observações: O método bruto recebe optionsJson ({formats?, autoZoom?, manualInput?} como string JSON) e um callbackId; a resposta é entregue à tabela de respostas interna do auxiliar, então uma página que o chame diretamente não recebe resposta. Só uma leitura roda por vez - uma segunda chamada responde code: 'busy'. Veja AppMint.scanCode para todos os campos do resultado e códigos de erro.