UNPKG

@pdfme/common

Version:

TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!

337 lines 17.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const path = __importStar(require("path")); const dynamicTemplate_js_1 = require("../src/dynamicTemplate.js"); const sansData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/NotoSans-Regular.ttf`)); const serifData = (0, fs_1.readFileSync)(path.join(__dirname, `/assets/fonts/NotoSerif-Regular.ttf`)); const getSampleFont = () => ({ NotoSans: { fallback: true, data: sansData }, NotoSerif: { data: serifData }, }); describe('getDynamicTemplate', () => { const height = 10; const aPositionY = 10; const bPositionY = 30; const padding = 10; const template = { schemas: [ [ { name: 'a', content: 'a', type: 'a', position: { x: 10, y: aPositionY }, width: 10, height, }, { name: 'b', content: 'b', type: 'b', position: { x: 10, y: bPositionY }, width: 10, height, }, ], ], basePdf: { width: 100, height: 100, padding: [padding, padding, padding, padding] }, }; const input = { a: 'a', b: 'b' }; const options = { font: getSampleFont() }; const _cache = new Map(); const getDynamicTemplateArg = { template, input, options, _cache }; const createGetDynamicTemplateArg = (increaseHeights, bHeight) => ({ ...getDynamicTemplateArg, getDynamicHeights: async (value, args) => { if (args.schema.type === 'a') { return Promise.resolve(increaseHeights); } return Promise.resolve([bHeight || args.schema.height]); }, }); const verifyBasicStructure = (dynamicTemplate) => { expect(dynamicTemplate.schemas).toBeDefined(); expect(Array.isArray(dynamicTemplate.schemas)).toBe(true); expect(dynamicTemplate.basePdf).toEqual({ width: 100, height: 100, padding: [padding, padding, padding, padding], }); }; describe('Single page scenarios', () => { test('should handle no page break', async () => { const increaseHeights = [10, 10, 10, 10, 10]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(1); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); expect(dynamicTemplate.schemas[0][1].position.y).toEqual(increaseHeights.reduce((a, b) => a + b, 0) - height + bPositionY); expect(dynamicTemplate.schemas[0][1].name).toEqual('b'); }); }); describe('Multiple page scenarios', () => { test('should handle page break with a on page 1 and b on page 2', async () => { const increaseHeights = [20, 20, 20, 20]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(2); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); expect(dynamicTemplate.schemas[0][1]).toBeUndefined(); expect(dynamicTemplate.schemas[1][0].name).toEqual('b'); // b maintains its relative offset from a's end position // a ends at y=90 (page content), b was 20 units below a, so b is at y=10 in page coords + padding = 20 expect(dynamicTemplate.schemas[1][0].position.y).toEqual(padding + padding); expect(dynamicTemplate.schemas[1][1]).toBeUndefined(); }); test('should handle page break with a on page 1 and 2, b on page 2', async () => { const increaseHeights = [20, 20, 20, 20, 20]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(2); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); expect(dynamicTemplate.schemas[0][1]).toBeUndefined(); expect(dynamicTemplate.schemas[1][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[1][0].name).toEqual('a'); expect(dynamicTemplate.schemas[1][1].position.y).toEqual(increaseHeights.slice(3).reduce((a, b) => a + b, 0) - height + padding); expect(dynamicTemplate.schemas[1][1].name).toEqual('b'); }); test('should handle multiple page breaks', async () => { const increaseHeights = [50, 50, 50, 50, 50]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(5); // Verify 'a' elements across pages // Page 0: 'a' first segment (50px) expect(dynamicTemplate.schemas[0][0]).toBeDefined(); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].height).toEqual(50); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); // Page 1: 'a' second segment (50px) expect(dynamicTemplate.schemas[1][0]).toBeDefined(); expect(dynamicTemplate.schemas[1][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[1][0].height).toEqual(50); expect(dynamicTemplate.schemas[1][0].name).toEqual('a'); // Page 2: 'a' third segment (50px) expect(dynamicTemplate.schemas[2][0]).toBeDefined(); expect(dynamicTemplate.schemas[2][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[2][0].height).toEqual(50); expect(dynamicTemplate.schemas[2][0].name).toEqual('a'); // Page 3: 'a' fourth segment (50px) expect(dynamicTemplate.schemas[3][0]).toBeDefined(); expect(dynamicTemplate.schemas[3][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[3][0].height).toEqual(50); expect(dynamicTemplate.schemas[3][0].name).toEqual('a'); // Page 4: 'a' fifth segment (50px) and 'b' element (10px) expect(dynamicTemplate.schemas[4][0]).toBeDefined(); expect(dynamicTemplate.schemas[4][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[4][0].height).toEqual(50); expect(dynamicTemplate.schemas[4][0].name).toEqual('a'); expect(dynamicTemplate.schemas[4][1]).toBeDefined(); expect(dynamicTemplate.schemas[4][1].position.y).toEqual(70); expect(dynamicTemplate.schemas[4][1].height).toEqual(10); expect(dynamicTemplate.schemas[4][1].name).toEqual('b'); }); test('should handle both a and b on next page', async () => { const increaseHeights = [80, 10, 10]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(2); // Check first page expect(dynamicTemplate.schemas[0][0]).toBeDefined(); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].height).toEqual(80); expect(dynamicTemplate.schemas[0][1]).toBeUndefined(); // Check second page expect(dynamicTemplate.schemas[1][0]).toBeDefined(); expect(dynamicTemplate.schemas[1][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[1][0].height).toEqual(20); expect(dynamicTemplate.schemas[1][1]).toBeDefined(); expect(dynamicTemplate.schemas[1][1].position.y).toBeGreaterThanOrEqual(dynamicTemplate.schemas[1][0].position.y + dynamicTemplate.schemas[1][0].height); }); }); describe('Element height modifications', () => { test('should handle increased height for b', async () => { const increaseHeights = [10, 10, 10, 10, 10]; const bHeight = 30; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights, bHeight)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(2); // Check 'a' element expect(dynamicTemplate.schemas[0][0]).toBeDefined(); expect(dynamicTemplate.schemas[0][0].position.y).toEqual(aPositionY); expect(dynamicTemplate.schemas[0][0].height).toEqual(50); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); // Check 'b' element expect(dynamicTemplate.schemas[1][0]).toBeDefined(); expect(dynamicTemplate.schemas[1][0].position.y).toEqual(padding); expect(dynamicTemplate.schemas[1][0].height).toEqual(bHeight); expect(dynamicTemplate.schemas[1][0].name).toEqual('b'); }); }); describe('Edge cases', () => { test('should handle empty increase heights', async () => { const increaseHeights = []; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBe(1); // Both schemas are placed; 'a' with height 0, 'b' follows expect(dynamicTemplate.schemas[0][0]).toBeDefined(); expect(dynamicTemplate.schemas[0][0].name).toEqual('a'); expect(dynamicTemplate.schemas[0][0].height).toEqual(0); expect(dynamicTemplate.schemas[0][1]).toBeDefined(); expect(dynamicTemplate.schemas[0][1].name).toEqual('b'); }); test('should handle very large increase heights', async () => { const increaseHeights = [1000, 1000]; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)(createGetDynamicTemplateArg(increaseHeights)); verifyBasicStructure(dynamicTemplate); expect(dynamicTemplate.schemas.length).toBeGreaterThan(1); }); }); describe('Long page flow (cross-template-page)', () => { test('should process pages independently - static pages are added as-is without offset propagation', async () => { // New behavior: pages without dynamic content are added as-is, // without being affected by previous page's table expansion. // This reduces computation cost by skipping layout calculations for static pages. const templateWithTwoPages = { schemas: [ [ { name: 'table', content: 'table', type: 'table', position: { x: 10, y: 60 }, width: 80, height: 10, }, ], [ { name: 'text', content: 'text', type: 'text', position: { x: 10, y: 10 }, width: 80, height: 10, }, ], ], basePdf: { width: 100, height: 100, padding: [10, 10, 10, 10] }, }; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)({ template: templateWithTwoPages, input: { table: 'table', text: 'text' }, options: { font: getSampleFont() }, _cache: new Map(), getDynamicHeights: async (value, args) => { if (args.schema.type === 'table') { return [10, 10, 10, 10]; // 40 total height, will cause page break } return [args.schema.height]; }, }); verifyBasicStructure(dynamicTemplate); // Page 1: table starts at y=60, with 40 height, will split across pages // Page 2: table continuation // Page 3: text from template page 2 (added as-is, no offset propagation) expect(dynamicTemplate.schemas.length).toBe(3); // First page has table expect(dynamicTemplate.schemas[0].some((s) => s.name === 'table')).toBe(true); // Second page has table continuation expect(dynamicTemplate.schemas[1].some((s) => s.name === 'table')).toBe(true); // Third page has text (from template page 2, added as-is) expect(dynamicTemplate.schemas[2].some((s) => s.name === 'text')).toBe(true); // Text position should be unchanged from template (y=10) const textOnPage3 = dynamicTemplate.schemas[2].find((s) => s.name === 'text'); expect(textOnPage3).toBeDefined(); expect(textOnPage3.position.y).toBe(10); }); test('should keep static page schemas together with dynamic page when both on same template page', async () => { // When table and text are on the SAME template page, they should be processed together const templateWithOnePage = { schemas: [ [ { name: 'table', content: 'table', type: 'table', position: { x: 10, y: 10 }, width: 80, height: 10, }, { name: 'text', content: 'text', type: 'text', position: { x: 10, y: 30 }, width: 80, height: 10, }, ], ], basePdf: { width: 100, height: 100, padding: [10, 10, 10, 10] }, }; const dynamicTemplate = await (0, dynamicTemplate_js_1.getDynamicTemplate)({ template: templateWithOnePage, input: { table: 'table', text: 'text' }, options: { font: getSampleFont() }, _cache: new Map(), getDynamicHeights: async (value, args) => { if (args.schema.type === 'table') { return [10, 10, 10, 10]; // 40 total height } return [args.schema.height]; }, }); verifyBasicStructure(dynamicTemplate); // Table expands from 10 to 40, pushing text down by 30 // Both should still fit on one page (table ends at 50, text at 70) expect(dynamicTemplate.schemas.length).toBe(1); expect(dynamicTemplate.schemas[0].some((s) => s.name === 'table')).toBe(true); expect(dynamicTemplate.schemas[0].some((s) => s.name === 'text')).toBe(true); const table = dynamicTemplate.schemas[0].find((s) => s.name === 'table'); const text = dynamicTemplate.schemas[0].find((s) => s.name === 'text'); expect(table.height).toBe(40); // Text should be pushed down: original y=30 + (40-10) offset = 60 expect(text.position.y).toBe(60); }); }); }); //# sourceMappingURL=dynamicTemplate.test.js.map