Visual Layout Builder
PDF Template & UI Builder
Build multi-page PDF templates visually. Add headings, text blocks, images/logos, custom tables, divider lines, X/Y positioning, and dynamic {{placeholders}}.
100% Client-Side — Visual PDF layout & form engine
Layout & Element Controls
Page 1 of 1
Pages:
Add Element to Page:
Live Multi-Page PDF Preview
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript code for encoding multi-page PDF layout schemas, generating shareable URL hashes, and rendering PDF documents offline using pdf-lib.
// Standalone Multi-Page PDF Builder & Link Hash Engine (Vanilla JS)
function encodeTemplateSchema(schema) {
const jsonStr = JSON.stringify(schema);
return btoa(encodeURIComponent(jsonStr)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}
function decodeTemplateSchema(base64Url) {
let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
while (base64.length % 4 !== 0) base64 += '=';
return JSON.parse(decodeURIComponent(atob(base64)));
}
async function renderMultiPagePdf(schema, formValues) {
const { PDFDocument, rgb, StandardFonts } = PDFLib;
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
for (const pgSchema of schema.pages) {
const page = pdfDoc.addPage([595.28, 841.89]); // A4
for (const el of pgSchema.elements) {
let text = (el.content || '').replace(/\{\{(\w+)\}\}/g, (_, key) => formValues[key] || `[\${key}]`);
if (el.type === 'heading') {
page.drawText(text, { x: el.x || 50, y: el.y || 750, size: el.fontSize || 18, font: boldFont, color: rgb(0.9, 0.15, 0) });
} else if (el.type === 'text') {
const lines = text.split('\n');
let curY = el.y || 700;
lines.forEach(line => {
page.drawText(line, { x: el.x || 50, y: curY, size: el.fontSize || 11, font, color: rgb(0.15, 0.15, 0.15) });
curY -= (el.fontSize || 11) + 4;
});
} else if (el.type === 'line') {
page.drawLine({ start: { x: el.x1 || 50, y: el.y1 || 700 }, end: { x: el.x2 || 545, y: el.y2 || 700 }, thickness: 1, color: rgb(0.8, 0.8, 0.8) });
} else if (el.type === 'table') {
let curY = el.y || 600;
const startX = el.x || 50;
const cols = el.columns || ["Col 1", "Col 2"];
const colW = el.colWidths || Array(cols.length).fill(100);
let curX = startX;
cols.forEach((colHeader, cIdx) => {
page.drawText(colHeader, { x: curX, y: curY, size: 10, font: boldFont, color: rgb(0,0,0) });
curX += colW[cIdx] || 100;
});
curY -= 14;
page.drawLine({ start: { x: startX, y: curY }, end: { x: startX + colW.reduce((a,b)=>a+b,0), y: curY }, thickness: 1, color: rgb(0.7,0.7,0.7) });
curY -= 16;
(el.rows || []).forEach(row => {
curX = startX;
row.forEach((cell, cIdx) => {
let cellText = cell.replace(/\{\{(\w+)\}\}/g, (_, key) => formValues[key] || `[\${key}]`);
page.drawText(cellText, { x: curX, y: curY, size: 10, font, color: rgb(0.2,0.2,0.2) });
curX += colW[cIdx] || 100;
});
curY -= 16;
});
}
}
}
return await pdfDoc.save();
}