mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
feat: add new field overflow methods (#2715)
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// scripts/generate-overflow-test-pdf.mjs
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// --- Helper functions to generate HTML for each page ---
|
||||
|
||||
function box(left, top, width, height) {
|
||||
return `left:${left}%;top:${top}%;width:${width}%;height:${height}%;`;
|
||||
}
|
||||
|
||||
function labelStyle(left, top) {
|
||||
return `left:${left}%;top:${top}%;`;
|
||||
}
|
||||
|
||||
function makeBox(left, top, width, height, label, { labelBelow = false } = {}) {
|
||||
const labelTop = labelBelow ? top + height + 0.5 : top - 1.5;
|
||||
return `
|
||||
<div class="label" style="${labelStyle(left, labelTop)}">${label}</div>
|
||||
<div class="box" style="${box(left, top, width, height)}"></div>`;
|
||||
}
|
||||
|
||||
// --- Pages 1-2: Date / Email (3×3 multi-line grid with text align variants) ---
|
||||
|
||||
function makeFieldOverflowPageWithGrid(title) {
|
||||
const startX = 10;
|
||||
const boxWidth = 35;
|
||||
|
||||
// Section A: Single-line height
|
||||
const slHeight = 1.75;
|
||||
const slStartY = 15;
|
||||
const slRowSpacing = 8;
|
||||
const sectionARows = [
|
||||
{ y: slStartY, label: 'M_AUTO TA_LEFT VA_MIDDLE' },
|
||||
{ y: slStartY + slRowSpacing, label: 'M_AUTO TA_LEFT VA_MIDDLE' },
|
||||
{ y: slStartY + 2 * slRowSpacing, label: 'M_AUTO TA_LEFT VA_MIDDLE' },
|
||||
];
|
||||
|
||||
// Section B: Multi-line height — 3×3 grid
|
||||
// Rows: TA_LEFT, TA_CENTER, TA_RIGHT
|
||||
// Columns: short, medium, long text
|
||||
const mlHeight = 12;
|
||||
const mlBoxWidth = 30;
|
||||
const mlColumnX = [2.5, 35, 67.5];
|
||||
const mlRowY = [45, 63, 83];
|
||||
const mlTextAligns = ['LEFT', 'CENTER', 'RIGHT'];
|
||||
|
||||
let content = `
|
||||
<div class="title" style="top:3%;left:10%;">${title}</div>`;
|
||||
|
||||
for (const r of sectionARows) {
|
||||
content += makeBox(startX, r.y, boxWidth, slHeight, r.label);
|
||||
}
|
||||
|
||||
for (let ri = 0; ri < 3; ri++) {
|
||||
for (let ci = 0; ci < 3; ci++) {
|
||||
const label = `M_AUTO TA_${mlTextAligns[ri]} VA_MIDDLE`;
|
||||
content += makeBox(mlColumnX[ci], mlRowY[ri], mlBoxWidth, mlHeight, label);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// --- Page 3: Signature (stacked multi-line, no text align control) ---
|
||||
|
||||
function makeSignatureOverflowPage(title) {
|
||||
const startX = 10;
|
||||
const boxWidth = 35;
|
||||
|
||||
// Section A: Single-line height
|
||||
const slHeight = 1.75;
|
||||
const slStartY = 15;
|
||||
const slRowSpacing = 8;
|
||||
const sectionARows = [
|
||||
{ y: slStartY, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
{ y: slStartY + slRowSpacing, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
{ y: slStartY + 2 * slRowSpacing, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
];
|
||||
|
||||
// Section B: Multi-line height — stacked single column
|
||||
const mlHeight = 12;
|
||||
const mlStartY = 45;
|
||||
const mlRowSpacing = 18;
|
||||
const sectionBRows = [
|
||||
{ y: mlStartY, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
{ y: mlStartY + mlRowSpacing, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
{ y: mlStartY + 2 * mlRowSpacing, label: 'M_AUTO TA_CENTER VA_MIDDLE' },
|
||||
];
|
||||
|
||||
let content = `
|
||||
<div class="title" style="top:3%;left:10%;">${title}</div>`;
|
||||
|
||||
for (const r of sectionARows) {
|
||||
content += makeBox(startX, r.y, boxWidth, slHeight, r.label);
|
||||
}
|
||||
|
||||
for (const r of sectionBRows) {
|
||||
content += makeBox(startX, r.y, boxWidth, mlHeight, r.label);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// --- Pages 4-5: Text Field Auto Mode ---
|
||||
|
||||
function makeTextAutoPage(title, boxHeight, isSingleLine) {
|
||||
const boxWidth = 28;
|
||||
|
||||
const columns = [
|
||||
{ col: 0, x: 5, textAlign: 'left' },
|
||||
{ col: 1, x: 35.5, textAlign: 'center' },
|
||||
{ col: 2, x: 66, textAlign: 'right' },
|
||||
];
|
||||
|
||||
const verticalAligns = ['top', 'middle', 'bottom'];
|
||||
|
||||
let content = `
|
||||
<div class="title" style="top:3%;left:5%;">${title}</div>`;
|
||||
|
||||
if (isSingleLine) {
|
||||
// Single-line: stagger all 9 items evenly down the page.
|
||||
// Each item gets its own Y position to avoid horizontal overflow collision.
|
||||
const itemCount = 9; // 3 rows × 3 columns
|
||||
const startY = 10;
|
||||
const endY = 92;
|
||||
const spacing = (endY - startY) / (itemCount - 1);
|
||||
|
||||
let itemIndex = 0;
|
||||
for (let ri = 0; ri < 3; ri++) {
|
||||
for (let ci = 0; ci < columns.length; ci++) {
|
||||
const col = columns[ci];
|
||||
const y = startY + itemIndex * spacing;
|
||||
const label = `M_AUTO TA_${col.textAlign.toUpperCase()} VA_${verticalAligns[ri].toUpperCase()}`;
|
||||
content += makeBox(col.x, y, boxWidth, boxHeight, label);
|
||||
itemIndex++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multi-line: 3 rows evenly spaced, bottom row near page bottom.
|
||||
// Box is 12% tall. Top of last box at 80% so bottom edge is at 92%.
|
||||
const startY = 10;
|
||||
const lastRowY = 80; // 80 + 12 = 92% (page bottom with padding)
|
||||
const midY = (startY + lastRowY) / 2; // 45%
|
||||
const rowYPositions = [startY, midY, lastRowY];
|
||||
|
||||
for (let ri = 0; ri < 3; ri++) {
|
||||
const labelBelow = verticalAligns[ri] === 'bottom';
|
||||
|
||||
for (const col of columns) {
|
||||
const label = `M_AUTO TA_${col.textAlign.toUpperCase()} VA_${verticalAligns[ri].toUpperCase()}`;
|
||||
content += makeBox(col.x, rowYPositions[ri], boxWidth, boxHeight, label, { labelBelow });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// --- Page 6: Explicit Modes ---
|
||||
|
||||
function makePage6() {
|
||||
const boxWidth = 25;
|
||||
|
||||
let content = `
|
||||
<div class="title" style="top:3%;left:5%;">Text Field Explicit Modes</div>
|
||||
`;
|
||||
|
||||
// Section A: Horizontal mode — centered on page, staggered vertically
|
||||
const centeredX = (100 - boxWidth) / 2; // 37.5%
|
||||
const horizontalRows = [
|
||||
{ x: centeredX, y: 15, label: 'M_HORIZONTAL TA_LEFT VA_TOP' },
|
||||
{ x: centeredX, y: 21, label: 'M_HORIZONTAL TA_CENTER VA_TOP' },
|
||||
{ x: centeredX, y: 27, label: 'M_HORIZONTAL TA_RIGHT VA_TOP' },
|
||||
];
|
||||
|
||||
for (const r of horizontalRows) {
|
||||
content += makeBox(r.x, r.y, boxWidth, 1.75, r.label);
|
||||
}
|
||||
|
||||
// Section B: Vertical mode — place side by side so vertical overflow has room below
|
||||
content += '';
|
||||
|
||||
const verticalCols = [
|
||||
{ x: 5, y: 43, label: 'M_VERTICAL TA_LEFT VA_TOP', labelBelow: false },
|
||||
{ x: 37.5, y: 43, label: 'M_VERTICAL TA_LEFT VA_MIDDLE', labelBelow: false },
|
||||
{ x: 70, y: 43, label: 'M_VERTICAL TA_LEFT VA_BOTTOM', labelBelow: true },
|
||||
];
|
||||
|
||||
for (const c of verticalCols) {
|
||||
content += makeBox(c.x, c.y, boxWidth, 12, c.label, { labelBelow: c.labelBelow });
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// --- Page 7: Crop Mode ---
|
||||
|
||||
function makePage7() {
|
||||
return `
|
||||
<div class="title" style="top:3%;left:10%;">Crop Mode (no overflow)</div>
|
||||
${makeBox(10, 15, 25, 1.75, 'M_CROP TA_LEFT VA_TOP')}
|
||||
${makeBox(10, 30, 25, 12, 'M_CROP TA_LEFT VA_TOP')}`;
|
||||
}
|
||||
|
||||
// --- Assemble all pages ---
|
||||
|
||||
function buildPage(contentFn) {
|
||||
return `<div class="page">${typeof contentFn === 'string' ? contentFn : contentFn}</div>`;
|
||||
}
|
||||
|
||||
const html = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
@page { size: A4; margin: 0; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: sans-serif; font-size: 10px; }
|
||||
.page { width: 210mm; height: 297mm; position: relative; page-break-after: always; }
|
||||
.page:last-child { page-break-after: auto; }
|
||||
.title { position: absolute; font-size: 18px; font-weight: bold; }
|
||||
.section-label { position: absolute; font-size: 12px; font-weight: bold; color: #666; }
|
||||
.box { position: absolute; border: 1px solid black; box-sizing: border-box; }
|
||||
.label { position: absolute; font-size: 9px; color: red; font-weight: bold; z-index: 9999; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${buildPage(makeFieldOverflowPageWithGrid('Date Field Overflow Tests'))}
|
||||
${buildPage(makeFieldOverflowPageWithGrid('Email Field Overflow Tests'))}
|
||||
${buildPage(makeSignatureOverflowPage('Signature Field Overflow Tests'))}
|
||||
${buildPage(makeTextAutoPage('Text Field Auto - Single-line Height', 1.75, true))}
|
||||
${buildPage(makeTextAutoPage('Text Field Auto - Multi-line Height', 12, false))}
|
||||
${buildPage(makeTextAutoPage('Text Field Auto - Multi-line Height Overflow', 12, false))}
|
||||
${buildPage(makePage6())}
|
||||
${buildPage(makePage7())}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage();
|
||||
|
||||
await page.setContent(html, { waitUntil: 'networkidle' });
|
||||
|
||||
const outputPath = path.join(__dirname, '../assets/field-overflow.pdf');
|
||||
|
||||
await page.pdf({
|
||||
path: outputPath,
|
||||
format: 'A4',
|
||||
printBackground: true,
|
||||
margin: { top: '0', right: '0', bottom: '0', left: '0' },
|
||||
});
|
||||
|
||||
await browser.close();
|
||||
console.log(`PDF generated: assets/field-overflow.pdf`);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
Reference in New Issue
Block a user