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 (
{disabled && }
{children}
);
};
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
{isRegistering ? ‘Cadastrar’ : ‘Entrar’}
{isRegistering ? ‘Já tem uma conta?’ : ‘Não tem uma conta?’} setIsRegistering(!isRegistering)} className=”font-bold text-blue-400 hover:underline”>{isRegistering ? ‘Faça login’ : ‘Cadastre-se’}
);
};
const HomeScreen = ({ setPage, setSelectedEvent, user, events }) => {
return (
Próximos Eventos
{events.map(event => (
{ setSelectedEvent(event); setPage(‘eventDetail’); }}>
{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 (
setPage(‘home’)} className=”absolute top-4 left-4 bg-black bg-opacity-50 p-2 rounded-full text-white”>
{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)}
handleTicketCountChange(ticket.id, -1)} className=”px-2 text-xl”>-
{ticketCounts[ticket.id] || 0}
handleTicketCountChange(ticket.id, 1)} className=”px-2 text-xl”>+
))}
{totalItems > 0 && (
Total R$ {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 (
setPage(‘eventDetail’)} className=”absolute left-0 p-1″> 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″ />
Eu concordo com os Termos de Serviço e com a Política de Privacidade, aderindo à Lei Geral de Proteção de Dados (LGPD).
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 (
setPage(‘home’)} className=”absolute left-0 p-1″> 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.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 (
setPage(‘myTickets’)} className=”absolute left-0 p-1″> 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 (
setPage(‘home’)} className=”absolute left-0 p-1″> Meu Perfil
{user.name.charAt(0)}
{user.name}
{user.email}
Editar Perfil
alert(‘Função para Criar Evento e Ver Relatórios’)}>Área do Organizador
Sair
);
};
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 (
);
}
/*
* 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 = {}));