pdf-lib-simple-tables
Advanced tools
Comparing version
@@ -0,0 +0,0 @@ module.exports = { |
@@ -0,0 +0,0 @@ import { PdfTable } from '../src'; |
@@ -0,0 +0,0 @@ module.exports = { |
{ | ||
"name": "pdf-lib-simple-tables", | ||
"version": "1.0.0", | ||
"description": "TS Bibliothek zur Erstellung von Tabellen auf Basis von pdf-lib", | ||
"main": "dist/index.js", | ||
"version": "1.1.0", | ||
"description": "TS library for creating tables based on pdf-lib", | ||
"main": "build/index.cjs", | ||
"module": "build/index.js", | ||
"browser": { | ||
"./build/index.cjs": "./build/index.browser.js" | ||
}, | ||
"scripts": { | ||
"build": "tsc", | ||
"build": "vite build", | ||
"build:browser": "vite build --format iife", | ||
"test": "jest", | ||
@@ -26,3 +31,5 @@ "lint": "eslint . --ext .ts", | ||
"eslint-config-prettier": "^9.1.0", | ||
"husky": "^8.0.3", | ||
"jest": "^29.7.0", | ||
"lint-staged": "^13.2.2", | ||
"prettier": "^2.8.8", | ||
@@ -32,4 +39,3 @@ "ts-jest": "^29.2.6", | ||
"typescript": "^4.9.5", | ||
"husky": "^8.0.3", | ||
"lint-staged": "^13.2.2" | ||
"vite": "^6.2.0" | ||
}, | ||
@@ -36,0 +42,0 @@ "keywords": [ |
@@ -160,2 +160,55 @@ # PDF-lib Table Library | ||
## Architecture | ||
The library uses a modular architecture to separate concerns and improve maintainability: | ||
### Core Modules | ||
- **TableDataManager**: Manages the table data (cell content, styles, rows/columns) | ||
- **MergeCellManager**: Handles cell merging operations | ||
- **FontManager**: Manages custom fonts and font embedding | ||
- **TableStyleManager**: Applies and combines styles from different sources | ||
- **BorderRenderer**: Renders different types of borders (solid, dashed, dotted) | ||
- **TableRenderer**: Renders the complete table with all its elements | ||
- **ImageEmbedder**: Handles embedding tables as images in PDFs | ||
### Module Relationships | ||
The `PdfTable` class serves as a facade, coordinating these modules to provide a simple API for users. Each module has a specific responsibility: | ||
``` | ||
┌───────────────────┐ ┌──────────────────┐ | ||
│ PdfTable │ uses │ TableRenderer │ | ||
│ (Facade Class) │ ◄────────┤ │ | ||
└───────────────────┘ └────────┬─────────┘ | ||
│ │ | ||
│ delegates │ uses | ||
▼ ▼ | ||
┌───────────────────┐ ┌──────────────────┐ | ||
│ TableDataManager │ │ BorderRenderer │ | ||
└───────────────────┘ └──────────────────┘ | ||
│ │ | ||
│ │ uses | ||
▼ ▼ | ||
┌───────────────────┐ ┌──────────────────┐ | ||
│ MergeCellManager │ │ TableStyleManager │ | ||
└───────────────────┘ └──────────────────┘ | ||
│ | ||
│ | ||
▼ | ||
┌───────────────────┐ ┌──────────────────┐ | ||
│ FontManager │ │ ImageEmbedder │ | ||
└───────────────────┘ └──────────────────┘ | ||
``` | ||
## Node.js and Browser Support | ||
This project now fully supports both Node.js and browser environments. | ||
For browsers, use the bundle in `build/index.browser.js` – this is created using Vite. | ||
You can generate the browser build with the following npm script: | ||
```bash | ||
npm run build:browser | ||
``` | ||
For more details and options, see the API documentation below. | ||
@@ -188,2 +241,34 @@ | ||
### Core Modules (For Developers/Contributors) | ||
These modules are used internally by the `PdfTable` class: | ||
#### `TableDataManager` | ||
Manages table data and structure. | ||
#### `TableRenderer` | ||
Handles the rendering of tables to PDF. | ||
#### `BorderRenderer` | ||
Specializes in rendering different border styles. | ||
#### `TableStyleManager` | ||
Applies and combines cell styles. | ||
#### `MergeCellManager` | ||
Manages merged cells functionality. | ||
#### `FontManager` | ||
Handles font embedding and custom fonts. | ||
#### `ImageEmbedder` | ||
Embeds tables as images in PDFs. | ||
### Interfaces | ||
@@ -229,2 +314,16 @@ | ||
### Build | ||
Use Vite to create the bundle: | ||
```bash | ||
npm run build | ||
``` | ||
For the browser build (IIFE format): | ||
```bash | ||
npm run build:browser | ||
``` | ||
### Running Tests | ||
@@ -231,0 +330,0 @@ |
@@ -1,7 +0,13 @@ | ||
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'; | ||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'; | ||
import { CustomFont } from '../models/CustomFont'; | ||
import { defaultDesignConfig, DesignConfig } from '../config/DesignConfig'; | ||
import { MergedCell } from '../interfaces/MergedCell'; | ||
import { TableCellStyle } from '../interfaces/TableCellStyle'; | ||
import { TableOptions } from '../interfaces/TableOptions'; | ||
import { BorderRenderer } from '../renderers/BorderRenderer'; | ||
import { TableStyleManager } from '../managers/TableStyleManager'; | ||
import { TableRenderer } from '../renderers/TableRenderer'; | ||
import { TableDataManager } from '../managers/TableDataManager'; | ||
import { MergeCellManager } from '../managers/MergeCellManager'; | ||
import { FontManager } from '../managers/FontManager'; | ||
import { ImageEmbedder } from '../embedders/ImageEmbedder'; | ||
@@ -15,12 +21,16 @@ /** | ||
export class PdfTable { | ||
private options: TableOptions; | ||
private data: string[][] = []; | ||
private cellStyles: TableCellStyle[][] = []; // Matrix for cell styles | ||
private mergedCells: MergedCell[] = []; | ||
private customFont?: CustomFont; | ||
private designConfig: DesignConfig; // new property | ||
private designConfig: DesignConfig; | ||
// Module für verschiedene Funktionalitäten | ||
private borderRenderer: BorderRenderer; | ||
private styleManager: TableStyleManager; | ||
private tableRenderer: TableRenderer; | ||
private dataManager: TableDataManager; | ||
private mergeCellManager: MergeCellManager; | ||
private fontManager: FontManager; | ||
private imageEmbedder: ImageEmbedder; | ||
constructor(options: TableOptions) { | ||
// Set default values if not present and merge design config | ||
this.options = { | ||
const completeOptions = { | ||
rowHeight: 20, | ||
@@ -31,246 +41,88 @@ colWidth: 80, | ||
this.designConfig = { ...defaultDesignConfig, ...options.designConfig }; | ||
this.initData(); | ||
} | ||
private initData(): void { | ||
// Ändere cellStyles, um separate Objekte pro Zelle zu erzeugen | ||
this.data = Array.from({ length: this.options.rows }, () => | ||
Array(this.options.columns).fill(''), | ||
); | ||
this.cellStyles = Array.from({ length: this.options.rows }, () => | ||
Array.from({ length: this.options.columns }, () => ({})), | ||
); | ||
// Initialisierung der Module | ||
this.borderRenderer = new BorderRenderer(); | ||
this.styleManager = new TableStyleManager(this.designConfig); | ||
this.tableRenderer = new TableRenderer(this.borderRenderer, this.styleManager); | ||
this.dataManager = new TableDataManager(completeOptions); | ||
this.mergeCellManager = new MergeCellManager(); | ||
this.fontManager = new FontManager(); | ||
this.imageEmbedder = new ImageEmbedder(); | ||
} | ||
// Method to fill a cell | ||
// Validierung und Delegate an den DataManager | ||
setCell(row: number, col: number, value: string): void { | ||
if (row < this.options.rows && col < this.options.columns) { | ||
this.data[row][col] = value; | ||
} | ||
this.dataManager.setCell(row, col, value); | ||
} | ||
// Angepasste setCellStyle-Methode: nur den übergebenen Stil speichern | ||
setCellStyle(row: number, col: number, style: TableCellStyle): void { | ||
if (row < this.options.rows && col < this.options.columns) { | ||
// Direkte Zuweisung statt Merging mit Default-Stilen | ||
this.cellStyles[row][col] = style; | ||
} else { | ||
throw new Error('Invalid cell coordinates'); | ||
} | ||
this.dataManager.setCellStyle(row, col, style); | ||
} | ||
// New method to merge cells with validation | ||
// Delegate an den MergeCellManager | ||
mergeCells(startRow: number, startCol: number, endRow: number, endCol: number): void { | ||
// Validation: start coordinates must be less than or equal to end coordinates | ||
if (startRow > endRow || startCol > endCol) { | ||
throw new Error('Invalid cell coordinates for mergeCells'); | ||
} | ||
// ...further validations could be done here... | ||
this.mergedCells.push({ startRow, startCol, endRow, endCol }); | ||
this.dataManager.validateCellIndices(startRow, startCol); | ||
this.dataManager.validateCellIndices(endRow, endCol); | ||
this.mergeCellManager.mergeCells(startRow, startCol, endRow, endCol); | ||
} | ||
// Method to set a custom font | ||
// Delegate an den FontManager | ||
setCustomFont(font: CustomFont): void { | ||
if (!this.isValidBase64(font.base64)) { | ||
throw new Error('Invalid Base64 data'); | ||
} | ||
this.customFont = font; | ||
this.fontManager.setCustomFont(font); | ||
} | ||
// Method to read the content of a cell | ||
// Data access delegates | ||
getCell(row: number, col: number): string { | ||
if (row < this.options.rows && col < this.options.columns) { | ||
return this.data[row][col]; | ||
} | ||
throw new Error('Invalid cell coordinates'); | ||
return this.dataManager.getCell(row, col); | ||
} | ||
// Method to read the style of a cell | ||
getCellStyle(row: number, col: number): TableCellStyle { | ||
if (row < this.options.rows && col < this.options.columns) { | ||
return this.cellStyles[row][col]; | ||
} | ||
throw new Error('Invalid cell coordinates'); | ||
return this.dataManager.getCellStyle(row, col); | ||
} | ||
// Method to remove a cell | ||
removeCell(row: number, col: number): void { | ||
if (row < this.options.rows && col < this.options.columns) { | ||
this.data[row][col] = ''; | ||
this.cellStyles[row][col] = {}; | ||
} else { | ||
throw new Error('Invalid cell coordinates'); | ||
} | ||
this.dataManager.removeCell(row, col); | ||
} | ||
// Method to add a new row | ||
addRow(): void { | ||
this.options.rows += 1; | ||
this.data.push(Array(this.options.columns).fill('')); | ||
this.cellStyles.push(Array(this.options.columns).fill({})); | ||
this.dataManager.addRow(); | ||
} | ||
// Method to add a new column | ||
addColumn(): void { | ||
this.options.columns += 1; | ||
this.data.forEach((row) => row.push('')); | ||
this.cellStyles.forEach((row) => row.push({})); | ||
this.dataManager.addColumn(); | ||
} | ||
// Method to remove a row | ||
removeRow(row: number): void { | ||
if (row < this.options.rows) { | ||
this.data.splice(row, 1); | ||
this.cellStyles.splice(row, 1); | ||
this.options.rows -= 1; | ||
} else { | ||
throw new Error('Invalid row coordinate'); | ||
} | ||
this.dataManager.removeRow(row); | ||
} | ||
// Method to remove a column | ||
removeColumn(col: number): void { | ||
if (col < this.options.columns) { | ||
this.data.forEach((row) => row.splice(col, 1)); | ||
this.cellStyles.forEach((row) => row.splice(col, 1)); | ||
this.options.columns -= 1; | ||
} else { | ||
throw new Error('Invalid column coordinate'); | ||
} | ||
this.dataManager.removeColumn(col); | ||
} | ||
// Helper function to validate Base64 data | ||
private isValidBase64(base64: string): boolean { | ||
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; | ||
return base64Regex.test(base64); | ||
} | ||
// Helper function to convert Base64 to Uint8Array | ||
private base64ToUint8Array(base64: string): Uint8Array { | ||
if (!this.isValidBase64(base64)) { | ||
throw new Error('Invalid Base64 data'); | ||
} | ||
const binaryString = atob(base64); | ||
const len = binaryString.length; | ||
const bytes = new Uint8Array(len); | ||
for (let i = 0; i < len; i++) { | ||
bytes[i] = binaryString.charCodeAt(i); | ||
} | ||
return bytes; | ||
} | ||
// New method: normalize color values | ||
private normalizeColor(color: { r: number; g: number; b: number }): { | ||
r: number; | ||
g: number; | ||
b: number; | ||
} { | ||
return { | ||
r: color.r > 1 ? color.r / 255 : color.r, | ||
g: color.g > 1 ? color.g / 255 : color.g, | ||
b: color.b > 1 ? color.b / 255 : color.b, | ||
}; | ||
} | ||
// Create a PDF document with the table including cell styling | ||
// Rendering methods | ||
async toPDF(): Promise<PDFDocument> { | ||
const pdfDoc = await PDFDocument.create(); | ||
let pdfFont; // Will be set either by CustomFont or as a fallback | ||
if (this.customFont) { | ||
const fontData = this.base64ToUint8Array(this.customFont.base64); | ||
pdfFont = await pdfDoc.embedFont(fontData, { customName: this.customFont.name }); | ||
} else { | ||
// Fallback to a standard font | ||
pdfFont = await pdfDoc.embedFont(StandardFonts.Helvetica); | ||
} | ||
let page = pdfDoc.addPage(); | ||
const { height } = page.getSize(); | ||
const pdfFont = await this.fontManager.embedFont(pdfDoc); | ||
const opts = this.dataManager.getOptions(); | ||
const tableOptions = { | ||
rowHeight: opts.rowHeight ?? 20, | ||
colWidth: opts.colWidth ?? 80, | ||
rows: opts.rows, | ||
columns: opts.columns, | ||
}; | ||
// Start position for the table | ||
const startX = 50; | ||
let currentY = height - 50; | ||
const { rowHeight = 20, colWidth = 80 } = this.options; | ||
await this.tableRenderer.drawTable( | ||
pdfDoc, | ||
pdfFont, | ||
this.dataManager.getData(), | ||
this.dataManager.getCellStyles(), | ||
this.mergeCellManager.getMergedCells(), | ||
tableOptions, | ||
); | ||
// Iterate over each row and column | ||
for (let row = 0; row < this.options.rows; row++) { | ||
// Create a new page if there is not enough space | ||
if (currentY - rowHeight < 50) { | ||
page = pdfDoc.addPage(); | ||
currentY = page.getSize().height - 50; | ||
} | ||
let x = startX; | ||
for (let col = 0; col < this.options.columns; col++) { | ||
// Check if this cell is part of a merged cell | ||
const merged = this.mergedCells.find((mc) => mc.startRow === row && mc.startCol === col); | ||
// If merged, calculate total height and width | ||
let cellWidth = colWidth; | ||
let cellHeight = rowHeight; | ||
if (merged) { | ||
cellWidth = colWidth * (merged.endCol - merged.startCol + 1); | ||
cellHeight = rowHeight * (merged.endRow - merged.startRow + 1); | ||
} | ||
// Merge the individual cell style with the design defaults | ||
const style = { ...this.designConfig, ...this.cellStyles[row][col] }; | ||
// Draw background, text, border, etc. only for non-skipped cells | ||
if (!merged || (merged && row === merged.startRow && col === merged.startCol)) { | ||
if (style.backgroundColor) { | ||
const bg = this.normalizeColor(style.backgroundColor); | ||
page.drawRectangle({ | ||
x, | ||
y: currentY - cellHeight, | ||
width: cellWidth, | ||
height: cellHeight, | ||
color: rgb(bg.r, bg.g, bg.b), | ||
}); | ||
} | ||
const fontSize = style.fontSize || 12; | ||
const textColor = style.fontColor || { r: 0, g: 0, b: 0 }; | ||
const normTextColor = this.normalizeColor(textColor); | ||
const text = this.data[row][col]; | ||
// Calculate text width if possible | ||
let textWidth = text.length * fontSize * 0.6; | ||
if (pdfFont.widthOfTextAtSize) { | ||
textWidth = pdfFont.widthOfTextAtSize(text, fontSize); | ||
} | ||
// Determine the x value based on alignment | ||
let textX = x + 5; | ||
if (style.alignment === 'center') { | ||
textX = x + (cellWidth - textWidth) / 2; | ||
} else if (style.alignment === 'right') { | ||
textX = x + cellWidth - textWidth - 5; | ||
} | ||
// If a CustomFont is available, it will be used | ||
page.drawText(text, { | ||
x: textX, | ||
y: currentY - cellHeight + (cellHeight - fontSize) / 2, | ||
size: fontSize, | ||
color: rgb(normTextColor.r, normTextColor.g, normTextColor.b), | ||
font: pdfFont, // undefined if no CustomFont is set | ||
}); | ||
if (style.borderColor && style.borderWidth) { | ||
const normBorderColor = this.normalizeColor(style.borderColor); | ||
page.drawRectangle({ | ||
x, | ||
y: currentY - cellHeight, | ||
width: cellWidth, | ||
height: cellHeight, | ||
borderColor: rgb(normBorderColor.r, normBorderColor.g, normBorderColor.b), | ||
borderWidth: style.borderWidth, | ||
opacity: 0, | ||
}); | ||
} | ||
} | ||
x += colWidth; | ||
} | ||
currentY -= rowHeight; | ||
} | ||
return pdfDoc; | ||
} | ||
// New method: Embed table in an existing PDF document (as a real table) | ||
// PDF embedding | ||
async embedInPDF(existingDoc: PDFDocument, startX: number, startY: number): Promise<PDFDocument> { | ||
@@ -280,11 +132,11 @@ if (startX < 0 || startY < 0) { | ||
} | ||
// For simplicity, use a new page addition | ||
let page = existingDoc.addPage(); | ||
let currentY = startY; // Use the passed Y coordinate | ||
const rowHeight = this.options.rowHeight || 20; | ||
const colWidth = this.options.colWidth || 80; | ||
let currentY = startY; | ||
const options = this.dataManager.getOptions(); | ||
const rowHeight = options.rowHeight || 20; | ||
const colWidth = options.colWidth || 80; | ||
const pdfFont = await existingDoc.embedFont(StandardFonts.Helvetica); | ||
for (let row = 0; row < this.options.rows; row++) { | ||
// If there is not enough space, add a new page and restore the top margin. | ||
for (let row = 0; row < options.rows; row++) { | ||
if (currentY - rowHeight < 50) { | ||
@@ -294,6 +146,5 @@ page = existingDoc.addPage(); | ||
} | ||
let x = startX; // Use the passed X coordinate | ||
for (let col = 0; col < this.options.columns; col++) { | ||
// Draw cell contents – additional styles can be integrated here. | ||
const text = this.data[row][col]; | ||
let x = startX; | ||
for (let col = 0; col < options.columns; col++) { | ||
const text = this.dataManager.getCell(row, col); | ||
page.drawText(text, { | ||
@@ -310,6 +161,7 @@ x: x + 5, | ||
} | ||
return existingDoc; | ||
} | ||
// Angepasste embedTableAsImage-Methode: Fehlerbehandlung für ungültige Bilddaten | ||
// Delegate an den ImageEmbedder | ||
async embedTableAsImage( | ||
@@ -320,41 +172,4 @@ existingDoc: PDFDocument, | ||
): Promise<PDFDocument> { | ||
if (!(imageBytes instanceof Uint8Array) || imageBytes.length === 0) { | ||
throw new Error('Invalid image data'); | ||
} | ||
// Neue Validierung: PNG-Header prüfen | ||
const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10]; | ||
if (imageBytes.length < 8 || !PNG_SIGNATURE.every((b, i) => imageBytes[i] === b)) { | ||
throw new Error('Invalid image data'); | ||
} | ||
let pngImage; | ||
try { | ||
pngImage = await existingDoc.embedPng(imageBytes); | ||
} catch (error) { | ||
throw new Error('Invalid image data'); | ||
} | ||
const page = existingDoc.addPage(); | ||
page.drawImage(pngImage, { | ||
x: options.x, | ||
y: options.y, | ||
width: options.width, | ||
height: options.height, | ||
}); | ||
return existingDoc; | ||
return this.imageEmbedder.embedTableAsImage(existingDoc, imageBytes, options); | ||
} | ||
private getEffectiveCellStyle( | ||
row: number, | ||
col: number, | ||
userStyle: TableCellStyle, | ||
): TableCellStyle { | ||
let effectiveStyle: TableCellStyle = { ...userStyle }; | ||
if (row === 0 && this.designConfig.headingRowStyle) { | ||
effectiveStyle = { ...this.designConfig.headingRowStyle, ...effectiveStyle }; | ||
} | ||
if (col === 0 && this.designConfig.headingColumnStyle) { | ||
effectiveStyle = { ...this.designConfig.headingColumnStyle, ...effectiveStyle }; | ||
} | ||
return effectiveStyle; | ||
} | ||
} |
@@ -12,19 +12,7 @@ /** | ||
* @property {Partial<DesignConfig>} [headingColumnStyle] - Heading column style | ||
* @default | ||
* { | ||
* fontFamily: 'Helvetica, Arial, sans-serif', | ||
* fontSize: 12, | ||
* fontColor: { r: 0, g: 0, b: 0 }, | ||
* backgroundColor: { r: 255, g: 255, b: 255 }, | ||
* borderColor: { r: 200, g: 200, b: 200 }, | ||
* borderWidth: 1, | ||
* headingRowStyle: { | ||
* backgroundColor: { r: 220, g: 220, b: 220 }, | ||
* fontSize: 13, | ||
* }, | ||
* headingColumnStyle: { | ||
* backgroundColor: { r: 240, g: 240, b: 240 }, | ||
* fontSize: 13, | ||
* } | ||
* } | ||
* @property {BorderStyle} [defaultTopBorder] - Default top border style | ||
* @property {BorderStyle} [defaultRightBorder] - Default right border style | ||
* @property {BorderStyle} [defaultBottomBorder] - Default bottom border style | ||
* @property {BorderStyle} [defaultLeftBorder] - Default left border style | ||
* @property {AdditionalBorder[]} [additionalBorders] - Additional borders | ||
*/ | ||
@@ -42,4 +30,17 @@ export interface DesignConfig { | ||
headingColumnStyle?: Partial<DesignConfig>; | ||
// New default border style options | ||
defaultTopBorder?: BorderStyle; | ||
defaultRightBorder?: BorderStyle; | ||
defaultBottomBorder?: BorderStyle; | ||
defaultLeftBorder?: BorderStyle; | ||
// New option for additional borders (e.g., for invoices) | ||
additionalBorders?: AdditionalBorder[]; | ||
} | ||
// Import BorderStyle and AdditionalBorder definitions | ||
import { BorderStyle } from '../interfaces/TableCellStyle'; | ||
import { AdditionalBorder } from '../interfaces/AdditionalBorder'; | ||
/** | ||
@@ -84,2 +85,29 @@ * Default design config | ||
}, | ||
// Default border configurations | ||
defaultTopBorder: { | ||
display: true, | ||
color: { r: 200, g: 200, b: 200 }, | ||
width: 1, | ||
style: 'solid', | ||
}, | ||
defaultRightBorder: { | ||
display: true, | ||
color: { r: 200, g: 200, b: 200 }, | ||
width: 1, | ||
style: 'solid', | ||
}, | ||
defaultBottomBorder: { | ||
display: true, | ||
color: { r: 200, g: 200, b: 200 }, | ||
width: 1, | ||
style: 'solid', | ||
}, | ||
defaultLeftBorder: { | ||
display: true, | ||
color: { r: 200, g: 200, b: 200 }, | ||
width: 1, | ||
style: 'solid', | ||
}, | ||
// Default: no additional borders | ||
additionalBorders: [], | ||
}; | ||
@@ -86,0 +114,0 @@ |
/** | ||
* Border style definition for table cells | ||
* @interface BorderStyle | ||
* @property {boolean} [display] - Whether to display this border | ||
* @property {{ r: number; g: number; b: number }} [color] - Border color | ||
* @property {number} [width] - Border width | ||
* @property {'solid' | 'dashed' | 'dotted'} [style] - Border style | ||
* @property {number} [dashArray] - Custom dash array for custom border patterns (for 'dashed' style) | ||
* @property {number} [dashPhase] - Dash phase for custom border patterns | ||
*/ | ||
export interface BorderStyle { | ||
display?: boolean; | ||
color?: { r: number; g: number; b: number }; | ||
width?: number; | ||
style?: 'solid' | 'dashed' | 'dotted'; | ||
dashArray?: number[]; | ||
dashPhase?: number; | ||
} | ||
import { AdditionalBorder } from './AdditionalBorder'; | ||
/** | ||
* Table cell style | ||
@@ -7,5 +28,10 @@ * @interface TableCellStyle | ||
* @property {{ r: number; g: number; b: number }} [backgroundColor] - Background color | ||
* @property {{ r: number; g: number; b: number }} [borderColor] - Border color | ||
* @property {number} [borderWidth] - Border width | ||
* @property {{ r: number; g: number; b: number }} [borderColor] - Border color (legacy, applies to all borders) | ||
* @property {number} [borderWidth] - Border width (legacy, applies to all borders) | ||
* @property {'left' | 'center' | 'right'} [alignment] - Text alignment | ||
* @property {BorderStyle} [topBorder] - Top border style | ||
* @property {BorderStyle} [rightBorder] - Right border style | ||
* @property {BorderStyle} [bottomBorder] - Bottom border style | ||
* @property {BorderStyle} [leftBorder] - Left border style | ||
* @property {AdditionalBorder[]} [additionalBorders] - Neue Option für zusätzliche interne Rahmenlinien | ||
*/ | ||
@@ -19,2 +45,11 @@ export interface TableCellStyle { | ||
alignment?: 'left' | 'center' | 'right'; | ||
// Individual border styling | ||
topBorder?: BorderStyle; | ||
rightBorder?: BorderStyle; | ||
bottomBorder?: BorderStyle; | ||
leftBorder?: BorderStyle; | ||
// Neue Option für zusätzliche interne Rahmenlinien | ||
additionalBorders?: AdditionalBorder[]; | ||
} |
@@ -0,1 +1,3 @@ | ||
import { isValidBase64 } from '../utils/validateBase64'; | ||
/** | ||
@@ -13,7 +15,6 @@ * Custom font model | ||
constructor(public name: string, public base64: string, public extension?: string) { | ||
const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; | ||
if (!base64Regex.test(base64)) { | ||
throw new Error('Invalid Base64 data'); | ||
if (!isValidBase64(base64)) { | ||
throw new Error(`Invalid Base64 data for font "${name}"`); | ||
} | ||
} | ||
} |
import { CustomFont } from '../src/models/CustomFont'; | ||
describe('CustomFont', () => { | ||
test('sollte Instanz mit Name, Base64 und optionaler Extension erstellen', () => { | ||
test('should create an instance with name, base64, and optional extension', () => { | ||
const name = 'TestFont'; | ||
@@ -15,3 +15,3 @@ const base64 = 'dGVzdGJhc2U2NA=='; | ||
test('sollte auch ohne Extension instanziiert werden können', () => { | ||
test('should be instantiated without extension', () => { | ||
const name = 'TestFont'; | ||
@@ -26,5 +26,5 @@ const base64 = 'dGVzdGJhc2U2NA=='; | ||
test('sollte Fehler für ungültige Base64-Daten werfen', () => { | ||
test('should throw error for invalid Base64 data', () => { | ||
expect(() => new CustomFont('TestFont', 'invalid_base64')).toThrowError('Invalid Base64 data'); | ||
}); | ||
}); |
@@ -0,0 +0,0 @@ import { PdfTable } from '../src/classes/Table'; |
import { PdfTable, CustomFont } from '../src/index'; | ||
describe('Index Exports', () => { | ||
test('sollte PdfTable exportieren', () => { | ||
test('should export PdfTable', () => { | ||
expect(PdfTable).toBeDefined(); | ||
}); | ||
test('sollte CustomFont exportieren', () => { | ||
test('should export CustomFont', () => { | ||
expect(CustomFont).toBeDefined(); | ||
}); | ||
}); |
@@ -0,0 +0,0 @@ import { PdfTable } from '../src/classes/Table'; |
{ | ||
"compilerOptions": { | ||
/* Language and Environment */ | ||
"target": "es6" /* Set the JavaScript language version for emitted JavaScr | ||
"target": "es6" /* Set the JavaScript language version for emitted JavaScript. */, | ||
/* Modules */, | ||
/* Modules */ | ||
"module": "commonjs", | ||
@@ -8,0 +8,0 @@ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
1519178
2123.82%16859
973.14%347
39.92%13
8.33%1
Infinity%