Sua experiência na palma da mão

import React, { useState, useEffect } from ‘react’; import { Search, MapPin, Calendar, Clock, ChevronLeft, ChevronRight, Ticket, User, QrCode, LogOut, BarChart2, Loader2 } from ‘lucide-react’; //———– SIMULAÇÃO DE DADOS INICIAIS (Seria carregado do Firestore) ———–// const initialEvents = [ { id: 1, title: ‘Summit de Inovação em IA’, city: ‘São Paulo, SP’, date: ’15 de Outubro, 2025′, time: ’09:00 – 18:00′, category: ‘Tecnologia’, image: ‘https://placehold.co/600×400/004aad/ffffff?text=Summit+IA’, description: ‘O maior evento sobre Inteligência Artificial da América Latina. Palestras com especialistas renomados, workshops práticos e networking de alto nível. Junte-se a nós para explorar o futuro da IA.’, tickets: [ { id: ‘t1_1’, type: ‘Pista’, price: 199.90, available: 500 }, { id: ‘t1_2’, type: ‘VIP’, price: 499.90, available: 100 }, { id: ‘t1_3’, type: ‘Acesso Total + Mentoria’, price: 999.90, available: 20 }, ], location: ‘Expo Center Norte, São Paulo, SP’ }, { id: 2, title: ‘Festival de Jazz & Blues’, city: ‘Rio de Janeiro, RJ’, date: ’22 de Novembro, 2025′, time: ’19:00 – 02:00′, category: ‘Música’, image: ‘https://placehold.co/600×400/010101/ffffff?text=Jazz+%26+Blues’, description: ‘Uma noite inesquecível com os melhores artistas de Jazz e Blues do cenário nacional e internacional. Ambiente sofisticado, gastronomia de primeira e música de alta qualidade.’, tickets: [ { id: ‘t2_1’, type: ‘Pista’, price: 150.00, available: 1000 }, { id: ‘t2_2’, type: ‘Mesa (4 pessoas)’, price: 800.00, available: 50 }, ], location: ‘Marina da Glória, Rio de Janeiro, RJ’ }, { id: 3, title: ‘Startup Conference 2025’, city: ‘Belo Horizonte, MG’, date: ’05 de Dezembro, 2025′, time: ’08:00 – 20:00′, category: ‘Negócios’, image: ‘https://placehold.co/600×400/333333/ffffff?text=Startup+Conf’, description: ‘Conecte-se com os maiores nomes do ecossistema de startups. Rodadas de investimento, pitches, painéis sobre growth hacking e muito mais.’, tickets: [ { id: ‘t3_1’, type: ‘Estudante’, price: 99.00, available: 300 }, { id: ‘t3_2’, type: ‘Profissional’, price: 249.00, available: 700 }, { id: ‘t3_3’, type: ‘Investidor’, price: 799.00, available: 150 }, ], location: ‘Expominas, Belo Horizonte, MG’ }, { id: 4, title: ‘Show Beneficente Hospital do Amor’, city: ‘Barretos, SP’, date: ’28 de Fevereiro, 2026′, time: ’20:00 – 23:00′, category: ‘Beneficente’, image: ‘https://placehold.co/600×400/004aad/ffffff?text=Show+Beneficente’, description: ‘Participe de um show emocionante com grandes artistas e ajude o Hospital do Amor de Barretos. Toda a renda será revertida para o tratamento de pacientes. Sua presença faz a diferença.’, tickets: [ { id: ‘t4_1’, type: ‘Pista Solidária’, price: 80.00, available: 5000 }, { id: ‘t4_2’, type: ‘Camarote Open Bar’, price: 250.00, available: 500 }, ], location: ‘Parque do Peão, Barretos, SP’ }, ]; //———– ESTRUTURA PARA CONEXÃO COM FIREBASE (BAAS) ———–// // Cole suas credenciais do Firebase aqui. const firebaseConfig = { apiKey: “SUA_API_KEY”, authDomain: “SEU_AUTH_DOMAIN”, projectId: “SEU_PROJECT_ID”, storageBucket: “SEU_STORAGE_BUCKET”, messagingSenderId: “SEU_MESSAGING_SENDER_ID”, appId: “SEU_APP_ID” }; // Em uma aplicação real, inicializaríamos o Firebase aqui. // import { initializeApp } from “firebase/app”; // import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, onAuthStateChanged, signOut } from “firebase/auth”; // import { getFirestore, collection, getDocs, doc, setDoc, query, where } from “firebase/firestore”; // const app = initializeApp(firebaseConfig); // const auth = getAuth(app); // const db = getFirestore(app); //———– COMPONENTES DA UI (Telas do Aplicativo) ———–// const AppButton = ({ children, onClick, className = ”, variant = ‘primary’, disabled = false }) => { const baseStyle = ‘w-full text-white font-bold py-3 px-4 rounded-lg shadow-md transition-transform transform hover:scale-105 focus:outline-none flex items-center justify-center’; const variantStyle = variant === ‘primary’ ? ‘bg-blue-600 hover:bg-blue-700’ : ‘bg-gray-700 hover:bg-gray-800’; const disabledStyle = ‘opacity-50 cursor-not-allowed’; return ( ); }; const LoginScreen = ({ setPage, setUser }) => { const [email, setEmail] = useState(”); const [password, setPassword] = useState(”); const [isRegistering, setIsRegistering] = useState(false); const [loading, setLoading] = useState(false); const handleAuth = async () => { setLoading(true); // SIMULAÇÃO: Em uma app real, aqui chamaria as funções do Firebase Auth // if (isRegistering) { // await createUserWithEmailAndPassword(auth, email, password); // } else { // await signInWithEmailAndPassword(auth, email, password); // } // O onAuthStateChanged listener cuidaria de atualizar o estado do usuário console.log(`${isRegistering ? ‘Registrando’ : ‘Logando’} com:`, email, password); setTimeout(() => { // Simula a resposta da API setUser({ name: ‘Ana Silva’, email: email }); setPage(‘home’); setLoading(false); }, 1500); }; return (
INVISTA {Array.from({ length: 25 }).map((_, i) => ())} techtickets
setEmail(e.target.value)} placeholder=”seuemail@exemplo.com” className=”w-full p-3 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500″ />
setPassword(e.target.value)} placeholder=”••••••••” className=”w-full p-3 bg-gray-800 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500″ />
{isRegistering ? ‘Cadastrar’ : ‘Entrar’}

{isRegistering ? ‘Já tem uma conta?’ : ‘Não tem uma conta?’}

); }; const HomeScreen = ({ setPage, setSelectedEvent, user, events }) => { return (

Bem-vinda,

{user.name}

Próximos Eventos

{events.map(event => (
{ setSelectedEvent(event); setPage(‘eventDetail’); }}> {event.title}

{event.title}

{event.city}
{event.date}
))}
); }; const EventDetailScreen = ({ event, setPage, setOrderDetails }) => { const [ticketCounts, setTicketCounts] = useState({}); const handleTicketCountChange = (ticketId, delta) => { setTicketCounts(prev => ({ …prev, [ticketId]: Math.max(0, (prev[ticketId] || 0) + delta) })); }; const totalOrder = event.tickets.reduce((total, ticket) => { const count = ticketCounts[ticket.id] || 0; return total + (count * ticket.price); }, 0); const totalItems = Object.values(ticketCounts).reduce((sum, count) => sum + count, 0); const handleCheckout = () => { const orderItems = event.tickets.filter(t => ticketCounts[t.id] > 0).map(t => ({…t, quantity: ticketCounts[t.id]})); setOrderDetails({ event, items: orderItems, total: totalOrder, }); setPage(‘checkout’); }; return (
{event.title}

{event.title}

{event.category}
{event.date} às {event.time}
{event.location}

{event.description}

Ingressos

{event.tickets.map(ticket => (

{ticket.type}

R$ {ticket.price.toFixed(2)}

{ticketCounts[ticket.id] || 0}
))}
{totalItems > 0 && (
TotalR$ {totalOrder.toFixed(2).replace(‘.’, ‘,’)}
Comprar Ingressos
)}
); }; const CheckoutScreen = ({ setPage, orderDetails, user, addTicket }) => { const [loading, setLoading] = useState(false); const [paymentSuccess, setPaymentSuccess] = useState(false); const [agreedToLGPD, setAgreedToLGPD] = useState(false); const handlePayment = async () => { setLoading(true); // LÓGICA DE PAGAMENTO: // 1. Chamar uma Cloud Function para criar uma preferência de pagamento no Mercado Pago. // 2. Redirecionar o usuário para o checkout do Mercado Pago. // 3. Após o pagamento, o Mercado Pago notificará um webhook (outra Cloud Function). // 4. Essa função de webhook confirmará o pagamento e chamará a função createTicket. // — SIMULAÇÃO DO FLUXO — console.log(“Iniciando pagamento para:”, orderDetails); await new Promise(resolve => setTimeout(resolve, 2500)); // Simula a comunicação com Mercado Pago // Supondo que o pagamento foi bem-sucedido: for (const item of orderDetails.items) { for (let i = 0; i < item.quantity; i++) { // Em um app real, essa função seria chamada pela Cloud Function segura const newTicket = { id: `ticket_${new Date().getTime()}_${i}`, eventId: orderDetails.event.id, purchaseDate: new Date().toLocaleDateString('pt-BR'), qrCodeValue: `IVT-${orderDetails.event.id}-${user.email}-${new Date().getTime()}-${i}`, ticketType: item.type, status: 'valid' // Status inicial do ingresso }; addTicket(newTicket); } } setLoading(false); setPaymentSuccess(true); await new Promise(resolve => setTimeout(resolve, 2000)); // Espera para mostrar a mensagem de sucesso setPage(‘myTickets’); }; if (loading || paymentSuccess) { return (

{paymentSuccess ? ‘Pagamento Aprovado!’ : ‘Processando Pagamento…’}

{paymentSuccess ? ‘Seu ingresso foi gerado com sucesso!’ : ‘Aguarde, estamos te redirecionando para o Mercado Pago.’}

); } return (

Pagamento

Resumo do Pedido

{orderDetails.event.title}

{orderDetails.items.map(item => (
{item.quantity}x {item.type} R$ {(item.quantity * item.price).toFixed(2)}
))}
Total R$ {orderDetails.total.toFixed(2)}
setAgreedToLGPD(!agreedToLGPD)} className=”mt-1 h-4 w-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500″ />
Pagar com Mercado Pago
) }; // — COMPONENTE DE QR CODE EMBUTIDO (SEM DEPENDÊNCIAS EXTERNAS) — // Usa a biblioteca qrcodegen (incluída no final do arquivo) const QRCode = ({ value, size = 256, fgColor = “#010101” }) => { // Nível de correção de erro: ALTO (equivale ao ‘H’) const qr = qrcodegen.QrCode.encodeText(value, qrcodegen.QrCode.Ecc.HIGH); // Gera o caminho SVG com uma borda de 4 módulos (padrão) const path = qr.toSvgString(4); const qrSize = qr.size + 8; // Tamanho total com borda const viewBox = `0 0 ${qrSize} ${qrSize}`; return ( ); }; const MyTicketsScreen = ({ setPage, setSelectedTicket, tickets, events }) => { return (

Meus Ingressos

{tickets.length > 0 ? (
{tickets.map(ticket => { const event = events.find(e => e.id === ticket.eventId); return (
{ setSelectedTicket({ …ticket, event }); setPage(‘qrCode’); }}> {event.title}

{event.title}

{event.city}

{ticket.ticketType}

); })}
) : (

Você ainda não tem ingressos

Explore os eventos e comece a comprar!

)}
); }; const QRCodeScreen = ({ setPage, ticket, user }) => { const { event } = ticket; /* * LÓGICA DE VALIDAÇÃO (PORTARIA): * 1. O app da portaria abre um leitor de QR Code. * 2. Ao escanear, ele lê o valor `ticket.qrCodeValue`. * 3. O app da portaria faz uma consulta no Firestore: `getDoc(doc(db, “tickets”, ticket.qrCodeValue))`. * 4. A consulta verifica o campo `status` do ingresso: * – Se `status` for “valid”, a tela fica VERDE (APROVADO). O app então atualiza o status para “used”. * – Se `status` for “used” ou não existir, a tela fica VERMELHA (INVÁLIDO/JÁ UTILIZADO). * Essa lógica garante que cada ingresso só possa ser usado uma vez. */ return (

Seu Ingresso

{event.title}

{event.date} – {event.time}

{event.location}

Apresente este QR Code na entrada do evento

{user.name}

{ticket.ticketType}

{ticket.qrCodeValue}

); } const ProfileScreen = ({ setPage, user, setUser }) => { const handleLogout = () => { // await signOut(auth); // Função real do Firebase setUser(null); setPage(‘login’); }; return (

Meu Perfil

{user.name.charAt(0)}

{user.name}

{user.email}

); }; export default function App() { const [page, setPage] = useState(‘login’); const [user, setUser] = useState(null); // Gerencia o estado de autenticação const [events, setEvents] = useState([]); const [tickets, setTickets] = useState([]); const [selectedEvent, setSelectedEvent] = useState(null); const [selectedTicket, setSelectedTicket] = useState(null); const [orderDetails, setOrderDetails] = useState(null); // Efeito para carregar dados iniciais (simula fetch do Firestore) useEffect(() => { setEvents(initialEvents); }, []); const addTicket = (newTicket) => { setTickets(prevTickets => […prevTickets, newTicket]); }; const renderPage = () => { if (!user) { return ; } switch (page) { case ‘home’: return ; case ‘eventDetail’: return ; case ‘checkout’: return ; case ‘myTickets’: return ; case ‘qrCode’: return ; case ‘profile’: return ; default: return ; } }; return (
{renderPage()}
); } /* * QR Code Generator Library * Copyright (c) 2023 Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * TypeScript version: * https://github.com/nayuki/QR-Code-generator/blob/master/typescript/qrcodegen.ts */ var qrcodegen; (function (qrcodegen) { “use strict”; class BitBuffer { constructor() { this.bits = []; this.length = 0; } getBits(num) { const i = Math.floor(this.length / 8); const j = this.length % 8; if (j + num <= 8) return (this.bits[i] >>> (8 – j – num)) & ((1 << num) - 1); else { let result = (this.bits[i] & (0xFF >> j)) << (num - (8 - j)); for (let k = i + 1; ; k++) { num -= 8; if (num > 8) result |= this.bits[k] << num; else { result |= this.bits[k] >>> (8 – num); break; } } return result; } } getLength() { return this.length; } appendBits(val, len) { for (let i = 0; i < len; i++) { const bit = (val >>> (len – 1 – i)) & 1; const j = Math.floor(this.length / 8); if (this.length % 8 == 0) this.bits.push(0); this.bits[j] |= bit << (7 - this.length % 8); this.length++; } } appendData(data) { const n = data.getLength(); for (let i = 0; i < n; i++) this.appendBits(data.getBits(1), 1); } } qrcodegen.BitBuffer = BitBuffer; class ReedSolomon { constructor(degree) { if (degree < 1 || degree > 255) throw new RangeError(“Degree out of range”); this.degree = degree; this.coefficients = []; for (let i = 0; i < degree - 1; i++) this.coefficients.push(0); this.coefficients.push(1); let root = 1; for (let i = 0; i < degree; i++) { for (let j = 0; j < this.coefficients.length; j++) { this.coefficients[j] = ReedSolomon.multiply(this.coefficients[j], root); if (j + 1 < this.coefficients.length) this.coefficients[j] ^= this.coefficients[j + 1]; } root = ReedSolomon.multiply(root, 2); } } getRemainder(data) { let result = this.coefficients.map(_ => 0); for (const b of data) { const factor = b ^ result.shift(); result.push(0); for (let i = 0; i < this.coefficients.length; i++) result[i] ^= ReedSolomon.multiply(this.coefficients[i], factor); } return result; } static multiply(x, y) { let z = 0; for (let i = 7; i >= 0; i–) { z = (z << 1) ^ ((z >>> 7) * 0x11D); z ^= ((y >>> i) & 1) * x; } return z; } } qrcodegen.ReedSolomon = ReedSolomon; class QrSegment { static makeBytes(data) { const bb = new BitBuffer(); for (const b of data) bb.appendBits(b, 8); return new QrSegment(QrSegment.Mode.BYTE, data.length, bb); } static makeNumeric(digits) { if (!/^[0-9]*$/.test(digits)) throw new RangeError(“String contains non-numeric characters”); const bb = new BitBuffer(); let i; for (i = 0; i + 3 <= digits.length; i += 3) bb.appendBits(parseInt(digits.substring(i, i + 3)), 10); const rem = digits.length - i; if (rem > 0) bb.appendBits(parseInt(digits.substring(i)), rem * 3 + 1); return new QrSegment(QrSegment.Mode.NUMERIC, digits.length, bb); } static makeAlphanumeric(text) { if (!/^[A-Z0-9 $%*+.\/:-]*$/.test(text)) throw new RangeError(“String contains unencodable characters in alphanumeric mode”); const bb = new BitBuffer(); let i; for (i = 0; i + 2 <= text.length; i += 2) { let temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45; temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1)); bb.appendBits(temp, 11); } if (i < text.length) bb.appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6); return new QrSegment(QrSegment.Mode.ALPHANUMERIC, text.length, bb); } static makeSegments(text) { if (text == "") return []; else if (/^[0-9]*$/.test(text)) return [QrSegment.makeNumeric(text)]; else if (/^[A-Z0-9 $%*+.\/:-]*$/.test(text)) return [QrSegment.makeAlphanumeric(text)]; else return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))]; } constructor(mode, numChars, data) { this.mode = mode; this.numChars = numChars; this.data = data; if (numChars < 0) throw new RangeError("Invalid argument"); } static toUtf8ByteArray(str) { str = encodeURI(str); let result = []; for (let i = 0; i < str.length; i++) { if (str.charAt(i) != "%") result.push(str.charCodeAt(i)); else { result.push(parseInt(str.substring(i + 1, i + 3), 16)); i += 2; } } return result; } } QrSegment.Mode = { NUMERIC: { modeBits: 0x1, numCharCountBits: (ver) => { if (ver <= 9) return 10; else if (ver <= 26) return 12; else return 14; } }, ALPHANUMERIC: { modeBits: 0x2, numCharCountBits: (ver) => { if (ver <= 9) return 9; else if (ver <= 26) return 11; else return 13; } }, BYTE: { modeBits: 0x4, numCharCountBits: (ver) => { if (ver <= 9) return 8; else return 16; } }, KANJI: { modeBits: 0x8, numCharCountBits: (ver) => { if (ver <= 9) return 8; else if (ver <= 26) return 10; else return 12; } }, ECI: { modeBits: 0x7, numCharCountBits: (ver) => 0 }, }; QrSegment.ALPHANUMERIC_CHARSET = “0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:”; qrcodegen.QrSegment = QrSegment; class QrCode { static encodeText(text, ecl) { const segs = qrcodegen.QrSegment.makeSegments(text); return QrCode.encodeSegments(segs, ecl); } static encodeBinary(data, ecl) { const segs = [qrcodegen.QrSegment.makeBytes(data)]; return QrCode.encodeSegments(segs, ecl); } static encodeSegments(segs, ecl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) { if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40)) throw new RangeError("Invalid version number"); let version; let dataUsedBits; for (version = minVersion; ; version++) { const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; const usedBits = QrCode.getTotalBits(segs, version); if (usedBits <= dataCapacityBits) { dataUsedBits = usedBits; break; } if (version >= maxVersion) throw new RangeError(“Data too long”); } const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8; if (boostEcl) { let newEcl = ecl; if (dataUsedBits <= QrCode.getNumDataCodewords(version, QrCode.Ecc.MEDIUM) * 8) newEcl = QrCode.Ecc.MEDIUM; if (dataUsedBits <= QrCode.getNumDataCodewords(version, QrCode.Ecc.QUARTILE) * 8) newEcl = QrCode.Ecc.QUARTILE; if (dataUsedBits <= QrCode.getNumDataCodewords(version, QrCode.Ecc.HIGH) * 8) newEcl = QrCode.Ecc.HIGH; ecl = newEcl; } const bb = new BitBuffer(); for (const seg of segs) { bb.appendBits(seg.mode.modeBits, 4); bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)); bb.appendData(seg.data); } bb.appendBits(0, Math.min(4, dataCapacityBits - bb.getLength())); bb.appendBits(0, (8 - bb.getLength() % 8) % 8); for (let padByte = 0xEC; bb.getLength() < dataCapacityBits; padByte = 0x11) bb.appendBits(padByte, 8); let dataCodewords = []; while (bb.getLength() >= 8) dataCodewords.push(bb.getBits(8)); const allCodewords = QrCode.addEccAndInterleave(dataCodewords, version, ecl); const qr = new QrCode(version, ecl, allCodewords, mask); return qr; } constructor(version, ecl, dataCodewords, msk) { this.version = version; this.size = version * 4 + 17; this.errorCorrectionLevel = ecl; this.modules = []; this.isFunction = []; if (msk < -1 || msk > 7) throw new RangeError(“Mask value out of range”); this.mask = msk; this.drawFunctionPatterns(); const allCodewords = this.addEccAndInterleave(dataCodewords, this.version, this.errorCorrectionLevel); this.drawCodewords(allCodewords); if (msk == -1) { let minPenalty = Infinity; for (let i = 0; i < 8; i++) { this.applyMask(i); this.drawFormatBits(i); const penalty = this.getPenaltyScore(); if (penalty < minPenalty) { minPenalty = penalty; this.mask = i; } this.applyMask(i); } } this.applyMask(this.mask); this.drawFormatBits(this.mask); } getModule(x, y) { return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x]; } toSvgString(border) { let parts = []; for (let y = 0; y < this.size; y++) { for (let x = 0; x < this.size; x++) { if (this.getModule(x, y)) parts.push(`M${x + border},${y + border}h1v1h-1z`); } } return parts.join(" "); } drawFunctionPatterns() { for (let i = 0; i < this.size; i++) { this.modules.push([]); this.isFunction.push([]); for (let j = 0; j < this.size; j++) { this.modules[i].push(false); this.isFunction[i].push(false); } } this.drawFinderPattern(3, 3); this.drawFinderPattern(this.size - 4, 3); this.drawFinderPattern(3, this.size - 4); this.drawAlignmentPatterns(); this.drawTimingPatterns(); this.drawFormatBits(0); this.drawVersion(); } drawAlignmentPatterns() { if (this.version > 1) { const alignPatPos = QrCode.ALIGNMENT_PATTERN_LOCATIONS[this.version]; for (const y of alignPatPos) { for (const x of alignPatPos) { if (this.isFunction[y][x]) continue; this.drawAlignmentPattern(x, y); } } } } drawAlignmentPattern(x, y) { for (let dy = -2; dy <= 2; dy++) { for (let dx = -2; dx <= 2; dx++) { this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1); } } } drawFinderPattern(x, y) { for (let dy = -4; dy <= 4; dy++) { for (let dx = -4; dx <= 4; dx++) { const dist = Math.max(Math.abs(dx), Math.abs(dy)); const xx = x + dx; const yy = y + dy; if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size) this.setFunctionModule(xx, yy, dist != 2 && dist != 4); } } } drawTimingPatterns() { for (let i = 8; i < this.size - 8; i++) { this.setFunctionModule(6, i, i % 2 == 0); this.setFunctionModule(i, 6, i % 2 == 0); } } drawFormatBits(mask) { const data = this.errorCorrectionLevel.formatBits << 3 | mask; let rem = data; for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537); const bits = (data << 10 | rem) ^ 0x5412; for (let i = 0; i <= 14; i++) this.setFunctionModule(QrCode.FORMAT_BITS_LOCATIONS[i][0], QrCode.FORMAT_BITS_LOCATIONS[i][1], ((bits >>> i) & 1) != 0); } drawVersion() { if (this.version >= 7) { let rem = this.version; for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25); const bits = this.version << 12 | rem; for (let i = 0; i < 18; i++) { const a = this.size - 11 + i % 3; const b = Math.floor(i / 3); this.setFunctionModule(a, b, ((bits >>> i) & 1) != 0); this.setFunctionModule(b, a, ((bits >>> i) & 1) != 0); } } } drawCodewords(data) { let i = 0; for (let right = this.size – 1; right >= 1; right -= 2) { if (right == 6) right = 5; for (let vert = 0; vert < this.size; vert++) { for (let j = 0; j < 2; j++) { const x = right - j; const upward = ((right + 1) & 2) == 0; const y = upward ? this.size - 1 - vert : vert; if (!this.isFunction[y][x] && i < data.length * 8) { this.modules[y][x] = ((data[i >>> 3] >>> (7 – (i & 7))) & 1) != 0; i++; } } } } } applyMask(mask) { for (let y = 0; y < this.size; y++) { for (let x = 0; x < this.size; x++) { let invert; switch (mask) { case 0: invert = (x + y) % 2 == 0; break; case 1: invert = y % 2 == 0; break; case 2: invert = x % 3 == 0; break; case 3: invert = (x + y) % 3 == 0; break; case 4: invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0; break; case 5: invert = x * y % 2 + x * y % 3 == 0; break; case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; default: throw new Error("Unreachable"); } if (!this.isFunction[y][x] && invert) this.modules[y][x] = !this.modules[y][x]; } } } getPenaltyScore() { let result = 0; for (let y = 0; y < this.size; y++) { let runColor = false; let runX = 0; let runHistory = [0, 0, 0, 0, 0, 0, 0]; for (let x = 0; x < this.size; x++) { if (this.getModule(x, y) == runColor) { runX++; if (runX == 5) result += 3; else if (runX > 5) result++; } else { this.finderPenaltyAddHistory(runX, runHistory); if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * 40; runColor = this.getModule(x, y); runX = 1; } } result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * 40; } for (let x = 0; x < this.size; x++) { let runColor = false; let runY = 0; let runHistory = [0, 0, 0, 0, 0, 0, 0]; for (let y = 0; y < this.size; y++) { if (this.getModule(x, y) == runColor) { runY++; if (runY == 5) result += 3; else if (runY > 5) result++; } else { this.finderPenaltyAddHistory(runY, runHistory); if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * 40; runColor = this.getModule(x, y); runY = 1; } } result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * 40; } for (let y = 0; y < this.size - 1; y++) { for (let x = 0; x < this.size - 1; x++) { const color = this.getModule(x, y); if (color == this.getModule(x + 1, y) && color == this.getModule(x, y + 1) && color == this.getModule(x + 1, y + 1)) result += 3; } } let dark = 0; for (const row of this.modules) dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark); const total = this.size * this.size; const k = Math.ceil(Math.abs(dark * 20 – total * 10) / total) – 1; result += k * 10; return result; } finderPenaltyCountPatterns(runHistory) { const n = runHistory[1]; if (n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n) { if (this.finderPenaltyCheckRatio(runHistory, 1) && this.finderPenaltyCheckRatio(runHistory, -1)) return 1; } return 0; } finderPenaltyCheckRatio(runHistory, dir) { let i = 0; for (let j = 0; j < 7; j++) i += runHistory[j]; if (dir == 1) { if (runHistory[0] + runHistory[1] >= i / 2) return false; } else { if (runHistory[5] + runHistory[6] >= i / 2) return false; } return true; } finderPenaltyAddHistory(currentRunLength, runHistory) { if (runHistory[0] > 0) runHistory.unshift(currentRunLength); else { runHistory[0] = currentRunLength; runHistory.unshift(0); } runHistory.pop(); } finderPenaltyTerminateAndCount(currentRunColor, currentRunLength, runHistory) { if (currentRunColor) { this.finderPenaltyAddHistory(currentRunLength, runHistory); currentRunLength = 0; } this.finderPenaltyAddHistory(currentRunLength, runHistory); return this.finderPenaltyCountPatterns(runHistory); } addEccAndInterleave(data, version, ecl) { const rs = QrCode.REED_SOLOMON_GENERATORS[ecl.ordinal]; const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][version]; const blockEccLen = rs.degree; const rawCodewords = Math.floor(QrCode.getNumDataCodewords(version, ecl) / numBlocks); const numShortBlocks = numBlocks – QrCode.getTotalBits([], version) / 8 % numBlocks; const shortBlockLen = rawCodewords; const longBlockLen = rawCodewords + 1; const shortDataBlocks = []; const longDataBlocks = []; let k = 0; for (let i = 0; i < numBlocks; i++) { const dat = data.slice(k, k + (i < numShortBlocks ? shortBlockLen : longBlockLen)); k += dat.length; if (i < numShortBlocks) shortDataBlocks.push(dat); else longDataBlocks.push(dat); } const eccs = []; for (const block of shortDataBlocks) eccs.push(rs.getRemainder(block)); for (const block of longDataBlocks) eccs.push(rs.getRemainder(block)); let result = []; for (let i = 0; i < Math.max(shortBlockLen, longBlockLen); i++) { for (let j = 0; j < numBlocks; j++) { if (i < (j < numShortBlocks ? shortBlockLen : longBlockLen)) result.push(data[j * (j < numShortBlocks ? shortBlockLen : longBlockLen) + i]); } } for (let i = 0; i < blockEccLen; i++) { for (let j = 0; j < numBlocks; j++) { result.push(eccs[j][i]); } } return result; } static getNumDataCodewords(version, ecl) { return Math.floor(QrCode.getTotalBits([], version) / 8) - QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][version] * QrCode.REED_SOLOMON_GENERATORS[ecl.ordinal].degree; } static getTotalBits(segs, version) { let result = 0; for (const seg of segs) { const ccbits = seg.mode.numCharCountBits(version); if (seg.numChars >= (1 << ccbits)) return Infinity; result += 4 + ccbits + seg.data.getLength(); } return result; } setFunctionModule(x, y, isDark) { this.modules[y][x] = isDark; this.isFunction[y][x] = true; } } QrCode.Ecc = { LOW: { ordinal: 0, formatBits: 1 }, MEDIUM: { ordinal: 1, formatBits: 0 }, QUARTILE: { ordinal: 2, formatBits: 3 }, HIGH: { ordinal: 3, formatBits: 2 }, }; QrCode.REED_SOLOMON_GENERATORS = [ new ReedSolomon(7), new ReedSolomon(10), new ReedSolomon(13), new ReedSolomon(15) ]; QrCode.NUM_ERROR_CORRECTION_BLOCKS = [ [-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [-1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [-1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [-1, 1, 1, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], ]; QrCode.ALIGNMENT_PATTERN_LOCATIONS = [ [], [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50], [6, 30, 54], [6, 32, 58], [6, 34, 62], [6, 26, 46, 66], [6, 26, 48, 70], [6, 26, 50, 74], [6, 30, 54, 78], [6, 30, 56, 82], [6, 30, 58, 86], [6, 34, 62, 90], [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102], [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118], [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134], [6, 34, 60, 86, 112, 138], [6, 30, 58, 86, 114, 142], [6, 34, 62, 90, 118, 146], [6, 30, 54, 78, 102, 126, 150], [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162], [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170], ]; QrCode.FORMAT_BITS_LOCATIONS = [ [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 7], [8, 8], [7, 8], [5, 8], [4, 8], [3, 8], [2, 8], [1, 8], [0, 8], ]; qrcodegen.QrCode = QrCode; })(qrcodegen || (qrcodegen = {}));