/* PDF simples e local para a cópia da solicitação. Nenhum dado é enviado para outro serviço. */ window.downloadSwagOrderPdf = async function ({ items = [], company = '', contact = '', email = '', phone = '', observations = '', total, orderId = '', status = 'Carrinho', date = new Date() }) { const photos = await Promise.all(items.map(item => new Promise(resolve => { if (!item.image) return resolve(null); const img = new Image(); img.crossOrigin = 'anonymous'; const timer = setTimeout(() => resolve(null), 7000); img.onload = () => { clearTimeout(timer); try { const canvas = document.createElement('canvas'); canvas.width = canvas.height = 112; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, 112, 112); const scale = Math.min(112 / img.naturalWidth, 112 / img.naturalHeight); ctx.drawImage(img, (112 - img.naturalWidth * scale) / 2, (112 - img.naturalHeight * scale) / 2, img.naturalWidth * scale, img.naturalHeight * scale); resolve(atob(canvas.toDataURL('image/jpeg', .75).split(',')[1])); } catch { resolve(null); } }; img.onerror = () => { clearTimeout(timer); resolve(null); }; img.src = /^https:\/\/cdndeprodutos\.azureedge\.net\//i.test(item.image) ? '/api/order-image?url=' + encodeURIComponent(item.image) : item.image; }))); const clean = value => String(value ?? '').replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim(); const money = value => Number(value || 0).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' }).replace(/\u00a0/g, ' '); const wrap = (value, max = 83) => { const words = clean(value).split(' '), lines = []; let line = ''; for (const word of words) { if ((line + ' ' + word).trim().length > max && line) { lines.push(line); line = word; } else line = (line + ' ' + word).trim(); } if (line) lines.push(line); return lines.length ? lines : ['']; }; const entries = [ { text: 'SWAGDECOR | Cópia da solicitação', bold: true, size: 17, gap: 30 }, { text: `${status}${orderId ? ' #' + orderId : ''} | ${new Date(date).toLocaleString('pt-BR')}`, size: 10, gap: 25 }, { text: `Empresa: ${clean(company) || 'Não informada'}`, bold: true }, { text: `Contato: ${clean(contact) || 'Não informado'}` }, { text: `E-mail: ${clean(email) || 'Não informado'} | Telefone: ${clean(phone) || 'Não informado'}`, gap: 24 }, { text: 'PRODUTOS', bold: true, size: 12, gap: 20 }, ]; for (const [index, item] of items.entries()) { if (photos[index]) entries.push({ image: index }); const qty = Number(item.qty ?? item.quantity ?? 0), unit = Number(item.price ?? item.unit_price ?? 0); entries.push({ text: clean(item.name), bold: true, gap: 14 }); entries.push({ text: `SKU: ${clean(item.variantSku ?? item.sku)} | Cor: ${clean(item.color || 'A definir')}` }); entries.push({ text: `${qty} un. x ${money(unit)} = ${money(qty * unit)}`, gap: 24 }); } entries.push({ text: `TOTAL DOS PRODUTOS: ${money(total ?? items.reduce((sum, item) => sum + Number(item.qty ?? item.quantity ?? 0) * Number(item.price ?? item.unit_price ?? 0), 0))}`, bold: true, size: 12, gap: 26 }); if (clean(observations)) { entries.push({ text: 'OBSERVAÇÕES', bold: true, gap: 16 }); entries.push({ text: observations, gap: 24 }); } entries.push({ text: 'O valor da personalização não está incluído neste pedido. Personalização, frete e condições finais serão confirmados na proposta comercial.', size: 9, gap: 16 }); entries.push({ text: 'Preços e estoque sujeitos à confirmação. Este documento não é uma proposta final.', size: 9 }); const esc = value => clean(value).replace(/[\\()]/g, match => '\\' + match); const latin = value => Array.from(value, c => { const n = c.charCodeAt(0); return n <= 255 ? String.fromCharCode(n) : ({ '€': '\x80', '–': '\x96', '—': '\x97', '‘': '\x91', '’': '\x92', '“': '\x93', '”': '\x94' }[c] || '?'); }).join(''); const pages = []; let commands = [], y = 800; const flush = () => { pages.push(commands.join('\n')); commands = []; y = 800; }; for (const entry of entries) { if (entry.image !== undefined) { if (y < 140) flush(); commands.push(`q 82 0 0 82 42 ${y - 82} cm /Im${entry.image} Do Q`); y -= 96; continue; } const size = entry.size || 10, max = size >= 17 ? 52 : size >= 12 ? 72 : 86; for (const line of wrap(entry.text, max)) { if (y < 65) flush(); commands.push(`BT /${entry.bold ? 'F2' : 'F1'} ${size} Tf 42 ${y} Td (${esc(line)}) Tj ET`); y -= size + 5; } y -= (entry.gap || 7) - 7; } flush(); const objects = [null, null, '<< /Producer (SwagDecor) >>', '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>', '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>']; const imageIds = photos.map(binary => binary ? objects.push(`<< /Type /XObject /Subtype /Image /Width 112 /Height 112 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${binary.length} >>\nstream\n${binary}\nendstream`) : null); const xobjects = imageIds.map((id, index) => id ? `/Im${index} ${id} 0 R` : '').filter(Boolean).join(' '); const pageIds = []; for (const pageContent of pages) { const content = latin(pageContent); const streamId = objects.push(`<< /Length ${content.length} >>\nstream\n${content}\nendstream`); const pageId = objects.push(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R /F2 5 0 R >> /XObject << ${xobjects} >> >> /Contents ${streamId} 0 R >>`); pageIds.push(pageId); } objects[0] = '<< /Type /Catalog /Pages 2 0 R >>'; objects[1] = `<< /Type /Pages /Kids [${pageIds.map(id => id + ' 0 R').join(' ')}] /Count ${pageIds.length} >>`; let pdf = '%PDF-1.4\n', offsets = [0]; for (let i = 0; i < objects.length; i++) { offsets.push(pdf.length); pdf += `${i + 1} 0 obj\n${latin(objects[i])}\nendobj\n`; } const startXref = pdf.length; pdf += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n${offsets.slice(1).map(n => String(n).padStart(10, '0') + ' 00000 n \n').join('')}trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${startXref}\n%%EOF`; const blob = new Blob([Uint8Array.from(pdf, c => c.charCodeAt(0))], { type: 'application/pdf' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `SwagDecor-${orderId ? 'pedido-' + orderId : 'carrinho'}-${new Date().toISOString().slice(0, 10)}.pdf`; document.body.append(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(link.href), 60000); };