style: render PDF in cyberpunk A4 layout
CI and Deploy / test-build-deploy (push) Successful in 4s

This commit is contained in:
Hermes Agent
2026-08-03 14:33:20 +00:00
parent 405170a90c
commit 9f8e130d41
3 changed files with 75 additions and 20 deletions
+47 -18
View File
@@ -1220,27 +1220,56 @@ function wrapPdfLines(lines, width=92){
function buildPdfBytes(lines){
const encoder=new TextEncoder();
const title=String(lines[0]||"PIZZATEIG");
const body=wrapPdfLines(lines.slice(1));
const pageSize=46;
const pages=[];
for(let i=0;i<Math.max(1,Math.ceil(body.length/pageSize));i++) pages.push(body.slice(i*pageSize,(i+1)*pageSize));
let body=wrapPdfLines(lines.slice(1),88);
const maxRows=62;
if(body.length>maxRows) body=[...body.slice(0,maxRows-1),"[... weitere Angaben gekürzt]"];
const units=body.reduce((sum,line)=>sum+(line ? 1 : .55),0)||1;
const lineHeight=Math.max(8.5,Math.min(13,610/units));
const bodySize=Math.max(6.8,Math.min(9.5,lineHeight-1.8));
let stream="q\n";
// Dunkle Grundfläche und dezentes Terminal-Raster.
stream+="0.027 0.031 0.043 rg 0 0 595 842 re f\n";
stream+="0.055 0.082 0.114 RG 0.3 w\n";
for(let x=19;x<595;x+=32) stream+=`${x} 0 m ${x} 842 l S\n`;
for(let y=10;y<842;y+=32) stream+=`0 ${y} m 595 ${y} l S\n`;
// Panel, Neon-Kanten und asymmetrische Akzentleisten.
stream+="0.063 0.082 0.122 rg 28 58 539 704 re f\n";
stream+="0 0.941 1 RG 0.9 w 28 58 539 704 re S\n";
stream+="0 0.941 1 rg 28 786 539 3 re f\n";
stream+="1 0.176 0.471 rg 28 776 128 5 re f\n";
stream+="1 0.176 0.471 rg 552 58 15 15 re f\nQ\n";
// Kopfzeile im Stil der Anwendung.
stream+=`BT\n/F2 22 Tf\n0 0.941 1 rg\n36 805 Td\n<${winAnsiHex("TEIG//TERMINAL")}> Tj\nET\n`;
stream+=`BT\n/F1 7.5 Tf\n1 0.176 0.471 rg\n37 793 Td\n<${winAnsiHex("RECIPE EXPORT // OFFLINE GENERATED // SYSTEM READY")}> Tj\nET\n`;
stream+=`BT\n/F2 14 Tf\n0.922 0.965 1 rg\n42 741 Td\n<${winAnsiHex(title)}> Tj\nET\n`;
stream+="0 0.941 1 RG 0.7 w 42 729 m 553 729 l S\n";
let y=711;
body.forEach(line=>{
if(!line){ y-=lineHeight*.55; return; }
const section=/^[A-ZÄÖÜ][A-ZÄÖÜ0-9 /()%-]{2,}$/.test(line.trim()) && line.trim().length<42;
const isLink=/^https?:\/\//.test(line.trim());
const font=section?"F2":"F1";
const size=section?Math.min(10.5,bodySize+1):isLink?Math.max(6.5,bodySize-1):bodySize;
const color=section?"1 0.176 0.471":isLink?"0 0.941 1":"0.886 0.925 0.961";
if(section) stream+=`0 0.941 1 rg 42 ${y-1.5} 5 5 re f\n`;
stream+=`BT\n/${font} ${size.toFixed(1)} Tf\n${color} rg\n${section?53:42} ${y.toFixed(1)} Td\n<${winAnsiHex(line.trimEnd())}> Tj\nET\n`;
y-=lineHeight;
});
stream+="0 0.941 1 RG 0.5 w 42 47 m 553 47 l S\n";
stream+=`BT\n/F1 7 Tf\n0 0.941 1 rg\n42 35 Td\n<${winAnsiHex("TEIG//TERMINAL // LOCAL PDF // A4 // 1 OF 1")}> Tj\nET\n`;
const objects=[];
objects[1]="<< /Type /Catalog /Pages 2 0 R >>";
objects[3]="<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>";
objects[4]="<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>";
const pageRefs=[];
pages.forEach((pageLines,index)=>{
const contentId=5+index*2, pageId=contentId+1;
pageRefs.push(`${pageId} 0 R`);
let stream=`BT\n/F2 16 Tf\n50 800 Td\n<${winAnsiHex(title)}> Tj\n/F1 10 Tf\n0 -26 Td\n14 TL\n`;
pageLines.forEach(line=>{ stream+=`<${winAnsiHex(line)}> Tj\nT*\n`; });
stream+=`ET\nBT\n/F1 8 Tf\n50 32 Td\n<${winAnsiHex(`TEIG//TERMINAL · Seite ${index+1} / ${pages.length}`)}> Tj\nET\n`;
objects[contentId]=`<< /Length ${encoder.encode(stream).length} >>\nstream\n${stream}endstream`;
objects[pageId]=`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents ${contentId} 0 R >>`;
});
objects[2]=`<< /Type /Pages /Kids [${pageRefs.join(" ")}] /Count ${pages.length} >>`;
objects[2]="<< /Type /Pages /Kids [6 0 R] /Count 1 >>";
objects[3]="<< /Type /Font /Subtype /Type1 /BaseFont /Courier /Encoding /WinAnsiEncoding >>";
objects[4]="<< /Type /Font /Subtype /Type1 /BaseFont /Courier-Bold /Encoding /WinAnsiEncoding >>";
objects[5]=`<< /Length ${encoder.encode(stream).length} >>\nstream\n${stream}endstream`;
objects[6]="<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 3 0 R /F2 4 0 R >> >> /Contents 5 0 R >>";
const chunks=[encoder.encode("%PDF-1.4\n")], offsets=[0];
let length=chunks[0].length;