MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / buildPdf

Function buildPdf

src/cli/pdf-lite.ts:74–147  ·  view source on GitHub ↗
(blocks: PdfBlock[])

Source from the content-addressed store, hash-verified

72 * with `Buffer.from(pdf, 'latin1')`. PURE + deterministic (no clock, no randomness).
73 */
74export function buildPdf(blocks: PdfBlock[]): string {
75 // 1. Lay blocks out into pages of positioned elements (text lines + bar-chart rows).
76 interface Line { kind: 'text'; x: number; y: number; size: number; font: 'F1' | 'F2' | 'F3'; text: string }
77 interface BarsRow { kind: 'bars'; x: number; y: number; values: number[]; label: string; size: number }
78 type El = Line | BarsRow;
79 const BAR_H = 36; const BAR_W = 14; const BAR_GAP = 4;
80 const pages: El[][] = [[]];
81 let y = PAGE_H - MARGIN;
82 for (const b of blocks) {
83 const size = b.size ?? 11;
84 const x = MARGIN + (b.indent ?? 0);
85 y -= b.spaceBefore ?? 0;
86 if (b.bars) {
87 if (y < MARGIN + BAR_H + size) { pages.push([]); y = PAGE_H - MARGIN; }
88 y -= BAR_H + Math.round(size * 0.6);
89 pages[pages.length - 1]!.push({ kind: 'bars', x, y, values: b.bars, label: pdfSanitize(b.text), size });
90 continue;
91 }
92 const gap = Math.round(size * 1.45);
93 const font = b.mono ? 'F3' : b.bold ? 'F2' : 'F1';
94 for (const lineText of wrap(pdfSanitize(b.text), size, !!b.mono, PAGE_W - x - MARGIN)) {
95 if (y < MARGIN + size) { pages.push([]); y = PAGE_H - MARGIN; }
96 y -= gap;
97 pages[pages.length - 1]!.push({ kind: 'text', x, y, size, font, text: lineText });
98 }
99 }
100
101 // 2. Emit objects, tracking byte offsets for the xref table.
102 // Layout: 1 Catalog · 2 Pages · 3 F1 · 4 F2 · 5 F3 · then per page: Page, Contents.
103 const objects: string[] = [];
104 const firstPageObj = 6;
105 const kids = pages.map((_, i) => `${firstPageObj + i * 2} 0 R`).join(' ');
106 objects.push(`1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n`);
107 objects.push(`2 0 obj\n<< /Type /Pages /Kids [${kids}] /Count ${pages.length} >>\nendobj\n`);
108 objects.push(`3 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n`);
109 objects.push(`4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>\nendobj\n`);
110 objects.push(`5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>\nendobj\n`);
111 pages.forEach((lines, i) => {
112 // Page-number footer on multi-page documents (a one-pager stays clean).
113 if (pages.length > 1) {
114 lines.push({ kind: 'text', x: PAGE_W / 2 - 24, y: MARGIN - 24, size: 8, font: 'F1', text: `Page ${i + 1} of ${pages.length}` });
115 }
116 const pageNum = firstPageObj + i * 2;
117 const contentNum = pageNum + 1;
118 objects.push(
119 `${pageNum} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${PAGE_W} ${PAGE_H}] ` +
120 `/Resources << /Font << /F1 3 0 R /F2 4 0 R /F3 5 0 R >> >> /Contents ${contentNum} 0 R >>\nendobj\n`,
121 );
122 const stream = lines
123 .map(l => {
124 if (l.kind === 'text') return `BT /${l.font} ${l.size} Tf 1 0 0 1 ${l.x} ${l.y} Tm (${pdfEscape(l.text)}) Tj ET`;
125 // Bar row: label text, then normalized filled rects (q/Q so the gray fill never leaks into text).
126 const max = Math.max(1, ...l.values);
127 const label = l.label ? `BT /F1 ${l.size} Tf 1 0 0 1 ${l.x} ${l.y + 2} Tm (${pdfEscape(l.label)}) Tj ET\n` : '';
128 const bx0 = l.x + (l.label ? Math.min(150, l.label.length * l.size * 0.55 + 12) : 0);
129 const rects = l.values.map((v, i) => {
130 const h = Math.max(1, Math.round((v / max) * BAR_H));
131 return `${bx0 + i * (BAR_W + BAR_GAP)} ${l.y} ${BAR_W} ${h} re f`;

Callers 4

pdf-lite.test.tsFile · 0.90
index.tsFile · 0.85

Calls 4

pdfSanitizeFunction · 0.85
wrapFunction · 0.85
pdfEscapeFunction · 0.85
pushMethod · 0.45

Tested by

no test coverage detected