Files
pdfme/packages/pdf-lib/__tests__/api/text/layout.spec.ts
devin-ai-integration[bot] e4a4c300cd Migrate pdf-lib into pdfme monorepo (#1059)
* Migrate pdf-lib into pdfme monorepo

- Add @pdfme/pdf-lib package to packages/ directory
- Update root package.json to include pdf-lib in workspaces
- Update all package dependencies to use workspace:* for @pdfme/pdf-lib
- Configure TypeScript build targets (cjs, esm, node) for pdf-lib
- Add ESLint configuration with relaxed rules for pdf-lib migration
- Integrate pdf-lib into monorepo build and clean scripts
- Add basic test suite for pdf-lib package
- All lint, build, and test suites pass successfully

This migration improves maintainability by consolidating all PDF operations
into a single repository and unified build/test/release process.

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Fix TypeScript module resolution for workspace dependencies

- Changed moduleResolution from 'bundler' to 'node' in common package
- This should resolve '@pdfme/pdf-lib' module resolution issues
- Reverted workspace dependency format back to '*' for npm compatibility

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Fix pdf-lib package.json exports paths

- Updated main, module, and exports paths to point to correct locations
- Changed from dist/*/index.js to dist/*/src/index.js to match build output
- Fixed TypeScript types path from dist/types/index.d.ts to dist/types/src/index.d.ts
- Resolves Vite package entry resolution errors and TypeScript module resolution issues

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Fix CodeQL security alerts in svg.ts

- Add input validation and sanitization for HTML/SVG parsing
- Prevent ReDoS attacks with regex limits and input size checks
- Sanitize font family names to prevent prototype pollution
- Add URL validation for image sources to prevent path traversal
- Limit transformation parsing to prevent infinite loops
- Maintain backward compatibility while improving security

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Implement comprehensive security fixes for CodeQL alerts in svg.ts

- Add input validation and sanitization for SVG content
- Implement safe HTML parsing with null checks and size limits
- Add controlled dynamic property access with allowlisted tag names
- Prevent style injection with filtered and limited style entries
- Add regex match limits to prevent ReDoS attacks
- Enhance font selection with input validation and type safety
- Sanitize image sources to prevent path traversal and injection
- Limit CSS style parsing to prevent potential vulnerabilities

These changes address the 2 high-severity CodeQL security alerts while
maintaining backward compatibility and functionality.

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Add additional security fixes for CodeQL alerts in svg.ts

- Implement safer property access for polygon node transformation
- Add input validation for points attribute with regex pattern matching
- Replace Object.assign with safer property assignment to prevent prototype pollution
- Add null checks and type validation for node attributes and childNodes
- Implement safer SVG node parsing with comprehensive validation
- Add array type checks for childNodes processing

These changes target the remaining 2 high-severity CodeQL security alerts
by addressing potential prototype pollution and unsafe property access.

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Implement comprehensive security hardening for CodeQL alerts in svg.ts

- Add comprehensive SVG content sanitization with allowlist-based tag filtering
- Implement strict input validation with bounds checking for all numeric inputs
- Replace unsafe dynamic property assignment with Object.defineProperty
- Add try-catch error handling for HTML parsing operations
- Restrict allowed style properties and validate string lengths
- Use setAttribute/removeAttribute instead of direct attribute manipulation
- Add type safety checks for all node operations
- Implement safer polygon-to-path conversion with validation

These changes address the 10 high-severity CodeQL security alerts by:
1. Preventing XSS through comprehensive input sanitization
2. Avoiding prototype pollution with safer property assignment
3. Adding bounds checking to prevent DoS attacks
4. Using allowlist-based validation for all user inputs
5. Implementing proper error handling to prevent crashes

Co-Authored-By: Kyohei Fukuda <kyoheif@wix.com>

* Potential fix for code scanning alert no. 32: Incomplete multi-character sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for code scanning alert no. 39: Incomplete multi-character sanitization

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Fix inefficient regular expression in svg.ts to pass CodeQL

- Changed /([^:\s]+)*\s*:\s*([^;]+)/g to /([^:\s]+)\s*:\s*([^;]+)/g
- Removed the problematic * quantifier that could cause exponential backtracking
- This fixes the "Inefficient regular expression" security alert from GitHub Advanced Security

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* remove sanitize-html

* move tests

* fix for security

* update dependabot.yml

* organize

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Kyohei Fukuda <kyouhei.fukuda0729@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2025-06-26 18:30:05 +09:00

167 lines
4.7 KiB
TypeScript

import { PDFDocument, StandardFonts, TextAlignment } from '../../../src/index';
import { layoutMultilineText } from '../../../src/api/text/layout';
const MIN_FONT_SIZE = 4;
const MAX_FONT_SIZE = 500;
describe(`layoutMultilineText`, () => {
it('should layout the text on one line when it fits near-perfectly', async () => {
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const alignment = TextAlignment.Left;
const padding = 0;
const borderWidth = 0;
const text = 'Super Mario Bros.';
for (let fontSize = MIN_FONT_SIZE; fontSize <= MAX_FONT_SIZE; fontSize++) {
const height = font.heightAtSize(fontSize) - (borderWidth + padding) * 2;
const width =
font.widthOfTextAtSize(text, fontSize) - (borderWidth + padding) * 2;
const bounds = {
x: borderWidth + padding,
y: borderWidth + padding,
width,
height,
};
const multilineTextLayout = layoutMultilineText(text, {
alignment,
bounds,
font,
});
expect(multilineTextLayout.lines.length).toStrictEqual(1);
}
});
it('should layout the text on one line when it fits comfortably', async () => {
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const alignment = TextAlignment.Left;
const padding = 0;
const borderWidth = 0;
const text = 'Super Mario Bros.';
const fontSize = 12;
const height = font.heightAtSize(fontSize) - (borderWidth + padding) * 2;
// Bounds width twice that of the text
const width =
(font.widthOfTextAtSize(text, fontSize) - (borderWidth + padding) * 2) *
2;
const bounds = {
x: borderWidth + padding,
y: borderWidth + padding,
width,
height,
};
const multilineTextLayout = layoutMultilineText(text, {
alignment,
bounds,
font,
});
expect(multilineTextLayout.lines.length).toStrictEqual(1);
});
it('should layout the text on multiple lines when it does not fit horizontally but there is space vertically', async () => {
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const alignment = TextAlignment.Left;
const padding = 0;
const borderWidth = 0;
const text = 'Super Mario Bros.';
for (let fontSize = MIN_FONT_SIZE; fontSize <= MAX_FONT_SIZE; fontSize++) {
// Height twice that of the text
const height =
(font.heightAtSize(fontSize) - (borderWidth + padding) * 2) * 2;
// Width half that of the text
const width =
(font.widthOfTextAtSize(text, fontSize) - (borderWidth + padding) * 2) /
2;
const bounds = {
x: borderWidth + padding,
y: borderWidth + padding,
width,
height,
};
const multilineTextLayout = layoutMultilineText(text, {
alignment,
bounds,
font,
});
expect(multilineTextLayout.lines.length).toStrictEqual(2);
}
});
it('should never exceed the maximum font size', async () => {
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const alignment = TextAlignment.Left;
const padding = 0;
const borderWidth = 0;
const text = 'Super Mario Bros.';
const height = Number.MAX_VALUE;
const width = Number.MAX_VALUE;
const bounds = {
x: borderWidth + padding,
y: borderWidth + padding,
width,
height,
};
const multilineTextLayout = layoutMultilineText(text, {
alignment,
bounds,
font,
});
expect(multilineTextLayout.lines.length).toStrictEqual(1);
expect(multilineTextLayout.fontSize).toStrictEqual(MAX_FONT_SIZE);
});
it('should respect empty lines', async () => {
const pdfDoc = await PDFDocument.create();
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
const alignment = TextAlignment.Left;
const padding = 0;
const borderWidth = 0;
const lines = ['Super Mario Bros.', '', 'Boop'];
const text = lines.join('\n');
for (let fontSize = MIN_FONT_SIZE; fontSize <= MAX_FONT_SIZE; fontSize++) {
const height = font.heightAtSize(fontSize) - (borderWidth + padding) * 2;
const width =
font.widthOfTextAtSize(lines[0], fontSize) -
(borderWidth + padding) * 2;
const bounds = {
x: borderWidth + padding,
y: borderWidth + padding,
width,
height,
};
const multilineTextLayout = layoutMultilineText(text, {
alignment,
bounds,
font,
});
expect(multilineTextLayout.lines.length).toStrictEqual(3);
}
});
});