/* Site DTF — PDF measurement and rasterisation, and the DPI of images inside one. Extracted verbatim from the single inline script in dtf-site.html. Loaded as classic scripts in the order listed there: they share one global scope and run top to bottom, exactly as the original did. */ // ── PDF também é conferido: rasteriza a página e mede o DPI das imagens de dentro. // Arte vetorial não tem resolução — nesses casos a nota é máxima, e isso é correto. // Served from this origin, not a CDN: the Site keeps working when a third party // does not, and the CSP can name only 'self' for scripts and workers. Provenance // and hashes are recorded in local/static/vendor/README.md. const PDFJS_URL='/vendor/pdf.min.js'; const PDFJS_WORKER=new URL('/vendor/pdf.worker.min.js', location.origin).href; let pdfLibP=null, temWorker=null; // Alguns navegadores e iframes bloqueiam Worker de blob. Descobrimos antes de // tentar, porque sem worker o PDF é lido na thread principal e precisa de outra // estratégia: grade mais grossa e mais tempo. function testaWorker(url){ try{ const w=new Worker(url); w.terminate(); return true; }catch(e){ return false; } } function carregarPdfJs(){ if(typeof window==='undefined') return Promise.resolve(null); if(window.pdfjsLib) return Promise.resolve(window.pdfjsLib); if(pdfLibP) return pdfLibP; pdfLibP=new Promise(res=>{ const sc=document.createElement('script'); sc.src=PDFJS_URL; sc.onload=()=>{ try{ const codigo='importScripts("'+PDFJS_WORKER+'");'; const url=URL.createObjectURL(new Blob([codigo],{type:'application/javascript'})); temWorker=testaWorker(url); window.pdfjsLib.GlobalWorkerOptions.workerSrc = temWorker? url : PDFJS_WORKER; }catch(e){ temWorker=false; try{ window.pdfjsLib.GlobalWorkerOptions.workerSrc=PDFJS_WORKER; }catch(e2){} } res(window.pdfjsLib||null); }; sc.onerror=()=>res(null); document.head.appendChild(sc); }); return pdfLibP; } // DPI real de cada imagem embutida: acompanha a matriz de transformação e compara // o tamanho em que a imagem foi colocada com quantos pixels ela tem. function dpiDasImagens(page, ops){ const mul=(a,b)=>[a[0]*b[0]+a[2]*b[1], a[1]*b[0]+a[3]*b[1], a[0]*b[2]+a[2]*b[3], a[1]*b[2]+a[3]*b[3], a[0]*b[4]+a[2]*b[5]+a[4], a[1]*b[4]+a[3]*b[5]+a[5]]; const O=window.pdfjsLib.OPS; let ctm=[1,0,0,1,0,0]; const pilha=[]; const dpis=[]; for(let i=0;it+x.area,0) || 1; const abaixoPiso=lista.filter(x=>x.dpix.dpit+x.dpi*x.area,0)/total; return { pior: Math.round(Math.min(...lista.map(x=>x.dpi))), ponderado: Math.round(pond), fracBaixa: abaixoPiso.reduce((t,x)=>t+x.area,0)/total, fracAviso: abaixoAviso.reduce((t,x)=>t+x.area,0)/total, quantasBaixas: abaixoPiso.length, quantasAviso: abaixoAviso.length, total: lista.length }; } // Rasteriza a primeira página a 1 mm por pixel e devolve a tela para a análise function rasterizarPdf(file, larguraCm, alturaCm){ let motivo=null; const trabalho=carregarPdfJs().then(lib=>{ if(!lib){ motivo='não foi possível carregar o leitor de PDF'; return null; } // sem worker tudo roda na thread principal · grade mais grossa para caber const folga = temWorker===false ? 2.2 : 1; return file.arrayBuffer().then(buf=>lib.getDocument({ data:buf, disableFontFace:true, isEvalSupported:false, useSystemFonts:false, verbosity:0 // sem worker é fallback, não erro }).promise).then(doc=> doc.getPage(1).then(page=>{ const vp1=page.getViewport({scale:1}); const passo=passoDe(larguraCm, alturaCm||larguraCm)*folga; const alvo=Math.max(8, Math.round(larguraCm/passo)); const esc=alvo/vp1.width; const vp=page.getViewport({scale:esc}); const cv=document.createElement('canvas'); cv.width=Math.max(8,Math.round(vp.width)); cv.height=Math.max(8,Math.round(vp.height)); const ctx=cv.getContext('2d',{willReadFrequently:true}); return page.render({canvasContext:ctx, viewport:vp, background:'rgba(0,0,0,0)'}) .promise.then(()=>page.getOperatorList()).then(ops=>{ let dpis=[]; try{ dpis=dpiDasImagens(page,ops); }catch(e){} const r=resumoDpi(dpis); return {tela:cv, dpi: r? r.ponderado : null, res:r}; }); }) ); }).catch(e=>{ motivo=(e&&e.message)? e.message : 'erro ao abrir o PDF'; return null; }); // sem worker o processo é lento: damos mais tempo antes de desistir const espera = temWorker===false ? 90000 : 30000; const tarde=new Promise(r=>setTimeout(()=>{ motivo='demorou demais neste navegador'; r('tempo'); }, espera)); return Promise.race([trabalho, tarde]).then(r=>{ const saida = r==='tempo'? null : r; if(!saida) return {erro: motivo||'não deu para conferir', semWorker: temWorker===false}; return saida; }); } function medirFolha(file){ return new Promise(res=>{ const nome=(file.name||'').toLowerCase(); if(RENDERIZA.test(nome)){ carregarImagem(file).then(img=>{ if(!img || !img.width) return res(null); const L=larguraFilme(); // Uma imagem só é folha montada se tiver pixel para ocupar a largura do // filme. Sem esta conferência, qualquer arte pequena vira "folha" e leva // o preço por metro sem nunca ter sido montada. res({larg:L, alt:+(img.height/img.width*L).toFixed(1), fonte:'proporção da imagem', dpiFolha:Math.round(img.width/(L/2.54)), px:img.width}); }); return; } if(/\.pdf$/.test(nome)){ const pedaco=(inicio,fim)=>new Promise(r=>{ const fr=new FileReader(); fr.onload=()=>r(new TextDecoder('latin1').decode(new Uint8Array(fr.result))); fr.onerror=()=>r(''); fr.readAsArrayBuffer(file.slice(inicio,fim)); }); const M=4*1048576; Promise.all([pedaco(0,M), pedaco(Math.max(0,file.size-M), file.size)]).then(([a,b])=>{ const txt=a+b; const re=/\/MediaBox\s*\[\s*(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)/; const m=txt.match(re); if(!m) return res(null); // UserUnit escala a página inteira · sem ele, 1 unidade = 1/72 pol const uu=txt.match(/\/UserUnit\s+([\d.]+)/); const esc=uu? Math.max(1,Math.min(75000,+uu[1])) : 1; const ptW=Math.abs(+m[3]-+m[1]), ptH=Math.abs(+m[4]-+m[2]); if(!(ptW>0&&ptH>0)) return res(null); const w=ptW*PT_CM*esc, h=ptH*PT_CM*esc; // acima de 14.400 unidades sem UserUnit o arquivo está fora da especificação const foraDoPadrao = !uu && (ptW>PDF_MAX_PT || ptH>PDF_MAX_PT); res({larg:+w.toFixed(1), alt:+h.toFixed(1), fonte:'página do PDF'+(uu? ' · UserUnit '+esc : ''), foraDoPadrao}); }); return; } res(null); }); } function pintaFolha(){ const L=larguraFilme(); pintaTipoEnvio(); // trava o seletor enquanto houver folha if(!folhas.length){ $('lista').innerHTML=''; agendaAvaliacao(); previaAoVivo(); return; } $('lista').innerHTML = folhas.map((x,i)=>{ const lido=!!x.med, larga=lido && x.med.larg>L+0.5, suspeito=lido && x.med.foraDoPadrao; const sub=(x.m||0)*(x.rep||1); let medida=''; if(lido){ const a=x.an; const conferido = a ? '
Conferido de verdade'+ ''+a.artes+' peça'+(a.artes===1?'':'s')+' · '+a.aproveitamento+ '% da folha virou arte · '+ (a.residuos? a.residuos+' resíduo'+(a.residuos>1?'s':'') : 'sem resíduo')+' · '+ (a.fundoChapado? 'fundo chapado' : 'fundo transparente')+''+ 'resolução '+a.dpi+' DPI · '+ (a.fonteDpi==='imagens'? 'medida imagem por imagem dentro do PDF' : a.fonteDpi==='vetor'? 'arte vetorial, sem imagem embutida' : 'lida do arquivo')+ ' · grade de '+n1(a.grade)+' mm
' : ''; medida='
'+ ''+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm'+ ''+x.med.fonte+' · '+fmtM(x.m)+' m de folha'+ conferido+ (suspeito? 'Este PDF passa do tamanho que o formato comporta. '+ 'A especificação para em 508 cm e o seu tem '+n1(x.med.alt)+' cm. '+ 'Ele abre certo só em quem o gerou — aqui vai precisar de tratamento. '+ 'Manda em PNG ou TIFF, ou divide em partes de até 5 m, que sai na hora.':'')+ (larga? 'Esta folha não roda neste filme. Ela tem '+n1(x.med.larg)+ ' cm de largura e o filme aqui tem '+n1(L)+' cm. Não dá para reduzir: '+ 'encolher a folha encolheria cada arte junto.'+ (x.med.larg<=57.5 ? ' Ela cabe no DTF têxtil de 57 cm — troque de produto ali em cima.' : ' Remonte a folha em '+n1(L)+' cm e suba de novo.')+'':'')+ '
'; }else{ const teto=TABELA[modo], piso=pisoEscada(); const dif=(x.m>0)? (teto-piso)*cobrar(x.m*(x.rep||1)) : 0; medida='
'+ ''+escapeHTML(extDe(x.f.name))+' não abre no navegador'+ 'Sem conseguir conferir o arquivo, não temos como dar nota — e sem nota '+ 'o metro sai pela tabela, '+rs(teto)+'. Alguém vai abrir esse arquivo na mão '+ 'antes de imprimir.'+ '
Exportando a mesma folha em PNG ou PDF, a conferência é '+ 'automática e o metro pode cair até '+rs(piso)+''+ (dif>0.5? ' — '+rs(dif)+' a menos só nesta folha' : '')+'.
'+ 'Informe o comprimento para seguir:'+ ''+ '
'; } const barra = x.pct!=null ? '
'+ ''+(x.pct<50?'lendo o arquivo…': x.pct<70?'medindo a folha…':'conferindo a arte…')+'' : x.semAnalise ? '
Não conseguimos conferir esta folha. '+x.semAnalise+ '. Seguimos com o preço de tabela — se quiser a nota e o desconto, exporte a mesma '+ 'folha em PNG, que a conferência é bem mais leve.
' : ''; return '
'+ (folhas.length>1? (i+1)+'. ':'')+escapeHTML(x.f.name)+''+ ''+fmt(x.f.size)+''+ '
'+ barra+medida+ (x.m>0 ? '
'+ '
'+ '
'+ '
'+fmtM(sub)+' m'+ (x.rep>1? ' · '+x.rep+' × '+fmtM(x.m)+' m':'')+'
'+ '
' : '')+ '
'; }).join(''); // total do pedido, quando há mais de uma folha const tot=folhaTotalM(), partes=Math.ceil(tot/20); if(folhas.length>1 && tot>0){ $('lista').innerHTML += '
'+ ''+folhas.length+' folhas neste pedido'+ ''+fmtM(tot)+' m no total'+ (partes>1? 'vai em '+partes+' partes de até 20 m':'')+'
'; }else if(partes>1 && tot>0){ $('lista').innerHTML += '
folha longa'+ ''+fmtM(tot)+' mvai em '+partes+' partes de até 20 m
'; } $('lista').querySelectorAll('[data-rmf]').forEach(b=>b.addEventListener('click',()=>{ folhas.splice(+b.dataset.rmf,1); pintaFolha(); })); $('lista').querySelectorAll('[data-repf]').forEach(el=>el.addEventListener('change',e=>{ folhas[+el.dataset.repf].rep=Math.max(1,Math.min(200,+e.target.value||1)); pintaFolha(); })); $('lista').querySelectorAll('[data-comp]').forEach(el=>el.addEventListener('change',e=>{ folhas[+el.dataset.comp].m=Math.max(0,Math.min(60,+e.target.value||0)); pintaFolha(); })); agendaAvaliacao(); previaAoVivo(); } // sugestão de largura const cabemNaLinha=w=>Math.max(1,Math.floor((larguraFilme()+GAP_CM)/(w+GAP_CM))); const larguraPara=n=>(larguraFilme()-(n-1)*GAP_CM)/n; // Só sugere se a folha realmente encolher. Caber mais um por fileira não adianta // nada se a quantidade pedida continuar ocupando o mesmo número de fileiras. function sugestao(a){ const w=a.cm, q=Math.max(1,a.q||1), prop=propDe(a); const n=cabemNaLinha(w), alvo=Math.floor(larguraPara(n+1)*10)/10; if(!(alvo>0) || alvo>=w) return null; if((w-alvo)/w>0.12) return null; // não corta mais que 12% da peça const fa=Math.ceil(q/n), fd=Math.ceil(q/(n+1)); if(fd>=fa) return null; // mesmo número de fileiras: não muda nada const economiaCm=fa*w*prop - fd*alvo*prop; if(economiaCm<0.5) return null; // menos de meio centímetro não é notícia return {alvo, de:n, para:n+1, fileiras:fa-fd, cm:economiaCm}; } // A cota em escala: o cliente ve a peca dentro da largura do filme, quantas cabem // lado a lado e quanto sobra. Numero em texto ninguem le; retangulo todo mundo entende. function vizPeca(a){ const BASE=300, L=larguraFilme(); const e0=BASE/L, alt0=a.cm*propDe(a)*e0; const k = alt0>132 ? 132/alt0 : 1; // tira nunca fica alta demais const e = e0*k; const n=cabemNaLinha(a.cm), pw=a.cm*e, ph=a.cm*propDe(a)*e, gp=GAP_CM*e; const sobra=L-(n*a.cm+(n-1)*GAP_CM); const ext=escapeHTML(a.f.name.split('.').pop().toUpperCase()); let pecas=''; for(let i=0;i'+(a.src?'':''+ext+'')+''; const Wt=(L*e+6).toFixed(1); // +6 = borda e respiro da tira return '
'+ '
'+ '
'+n1(a.cm*propDe(a))+' cm
'+ '
'+ '
'+n1(L)+' cm de filme
'+ '
'+pecas+ (sobra>0.25?'
sobra '+n1(sobra)+' cm
':'')+ '
'+ '
'+ '
'+ '
'+n1(a.cm)+' cm de largura · '+n+ (n>1?' peças':' peça')+' por fileira'+ (sobra>0.25?' · sobram '+n1(sobra)+' cm':' · largura toda aproveitada')+'
'+ '
'; } const n1=v=>v.toFixed(1).replace('.',','); function pintaArtes(){ $('lista').innerHTML=artes.map((a,i)=>{ let barra='', dica='', calc='informe a largura para ver a qualidade'; if(a.cm>0){ const dpi=Math.round(a.px/(a.cm/2.54)), n=Math.max(6,Math.min(100,Math.round(dpi/300*100))); barra='
'; calc=''+n+'% · '+dpi+' DPI'+ a.q+' × '+n1(a.cm)+' × '+n1(a.cm*propDe(a))+' cm'; const sug=sugestao(a); dica = sug ? '
Reduzindo para '+n1(sug.alvo)+' cm você economiza '+ n1(sug.cm)+' cm de folhaPassam de '+sug.de+' para '+sug.para+ ' por fileira, e as suas '+a.q+' peças ocupam '+sug.fileiras+ ' fileira'+(sug.fileiras>1?'s':'')+' a menos.
' : ''; dica = vizPeca(a) + dica; } return '
'+escapeHTML(a.f.name)+''+ ''+a.px+' px'+ '
'+ '
'+ '
'+ '
'+ (TAMANHOS[larguraFilme()]||[]).map(t=>'').join('')+ ''+ ''+ ''+ '
'+barra+'
'+calc+'
'+dica+'
'; }).join(''); $('lista').querySelectorAll('[data-rm]').forEach(b=>b.addEventListener('click',()=>{artes.splice(+b.dataset.rm,1);pintaArtes();})); $('lista').querySelectorAll('[data-cm]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.cm].cm=+i.value||0;pintaArtes();})); $('lista').querySelectorAll('[data-q]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.q].q=Math.max(1,Math.floor(+i.value||1));pintaArtes();})); $('lista').querySelectorAll('[data-cm], [data-q]').forEach(i=>i.addEventListener('input',()=>{ const largura=i.hasAttribute('data-cm'); const a=artes[+(largura?i.dataset.cm:i.dataset.q)]; a[largura?'cm':'q']=largura?Math.max(0,+i.value||0):Math.max(1,Math.floor(+i.value||1)); previaAoVivo(); })); $('lista').querySelectorAll('[data-usar]').forEach(b=>b.addEventListener('click',()=>{ artes[+b.dataset.usar].cm=+b.dataset.cm; pintaArtes(); })); $('lista').querySelectorAll('[data-tam]').forEach(b=>b.addEventListener('click',()=>{ artes[+b.dataset.tam].cm=+b.dataset.t; pintaArtes(); })); $('lista').querySelectorAll('[data-giro]').forEach(b=>b.addEventListener('click',()=>{ const a=artes[+b.dataset.giro]; a.giro=a.giro?0:90; pintaArtes(); })); $('lista').querySelectorAll('[data-esp]').forEach(b=>b.addEventListener('click',()=>{ const a=artes[+b.dataset.esp]; a.esp=!a.esp; pintaArtes(); })); resumoArtes(); previaAoVivo(); } function resumoArtes(){ const r=$('rA'), prontas=artes.filter(a=>a.cm>0); if(!prontas.length){ r.style.display='none'; return; } const m=montagemCm/100; // a mesma folha que aparece ao lado const notas=prontas.map(a=>Math.max(6,Math.min(100,Math.round((a.px/(a.cm/2.54))/300*100)))); const media=Math.round(notas.reduce((s,n)=>s+n,0)/notas.length), pior=Math.min(...notas); const falta=artes.length-prontas.length; r.style.display='block'; r.className='resumoA on'; r.innerHTML='
artes com tamanho'+prontas.length+' de '+artes.length+'
'+ '
qualidade média'+media+'%
'+ (piora pior arte'+pior+'%':'')+ (m>0?'
folha montada'+fmtM(m)+' m
'+ (cobrar(m)>m+0.001?'
cobrança'+fmtM(cobrar(m))+ ' m'+(cobrar(m)===MINIMO_M?' · mínimo':'')+'
':''):'')+ (falta?'
faltam informar'+falta+'
':''); }