mirror of
https://github.com/foliojs/pdfkit.git
synced 2025-12-08 20:15:54 +00:00
* Add page size utilities - Added page.contentWidth - Added page.contentHeight * Add table support - Tables support cell customization (including colors) - Tables also support rotatable text (with alignment support) - Tables have accessibility support * chore: fix code generation context - code generation now respects the current document positioning to allow use of page dependent operations * chore: remove comments from build * removed unnecessary config optimisations * Optimize table minification * Performance improvements to tables * Improve font handling in tables
201 lines
5.8 KiB
JavaScript
201 lines
5.8 KiB
JavaScript
import { deepMerge, memoize } from './utils';
|
|
import {
|
|
normalizeAlignment,
|
|
normalizedColumnStyle,
|
|
normalizedDefaultStyle,
|
|
normalizedRowStyle,
|
|
} from './style';
|
|
import { normalizeSides } from '../utils';
|
|
|
|
/**
|
|
* Normalize a table
|
|
*
|
|
* @this PDFTable
|
|
* @memberOf PDFTable
|
|
* @private
|
|
*/
|
|
export function normalizeTable() {
|
|
const doc = this.document;
|
|
const opts = this.opts;
|
|
|
|
// Normalize config
|
|
let index = doc._tableIndex++;
|
|
this._id = new String(opts.id ?? `table-${index}`);
|
|
this._position = {
|
|
x: doc.sizeToPoint(opts.position?.x, doc.x),
|
|
y: doc.sizeToPoint(opts.position?.y, doc.y),
|
|
};
|
|
this._maxWidth = doc.sizeToPoint(
|
|
opts.maxWidth,
|
|
doc.page.width - doc.page.margins.right - this._position.x,
|
|
);
|
|
|
|
const { defaultStyle, defaultColStyle, defaultRowStyle } =
|
|
normalizedDefaultStyle(opts.defaultStyle);
|
|
this._defaultStyle = defaultStyle;
|
|
|
|
let colStyle;
|
|
if (opts.columnStyles) {
|
|
if (Array.isArray(opts.columnStyles)) {
|
|
colStyle = (i) => opts.columnStyles[i];
|
|
} else if (typeof opts.columnStyles === 'function') {
|
|
// memoize all columns
|
|
colStyle = memoize((i) => opts.columnStyles(i), Infinity);
|
|
} else if (typeof opts.columnStyles === 'object') {
|
|
colStyle = () => opts.columnStyles;
|
|
}
|
|
}
|
|
if (!colStyle) colStyle = () => ({});
|
|
this._colStyle = normalizedColumnStyle.bind(this, defaultColStyle, colStyle);
|
|
|
|
let rowStyle;
|
|
if (opts.rowStyles) {
|
|
if (Array.isArray(opts.rowStyles)) {
|
|
rowStyle = (i) => opts.rowStyles[i];
|
|
} else if (typeof opts.rowStyles === 'function') {
|
|
// Memoize the row configs in a rolling buffer
|
|
rowStyle = memoize((i) => opts.rowStyles(i), 10);
|
|
} else if (typeof opts.rowStyles === 'object') {
|
|
rowStyle = () => opts.rowStyles;
|
|
}
|
|
}
|
|
if (!rowStyle) rowStyle = () => ({});
|
|
this._rowStyle = normalizedRowStyle.bind(this, defaultRowStyle, rowStyle);
|
|
}
|
|
|
|
/**
|
|
* Convert text into a string
|
|
* - null and undefined are preserved (as they will be ignored)
|
|
* - everything else is run through `String()`
|
|
*
|
|
* @param {*} text
|
|
* @returns {string}
|
|
* @private
|
|
*/
|
|
export function normalizeText(text) {
|
|
// Parse out text
|
|
if (text != null) text = `${text}`;
|
|
return text;
|
|
}
|
|
|
|
/**
|
|
* Normalize a cell config
|
|
*
|
|
* @this PDFTable
|
|
* @memberOf PDFTable
|
|
* @param {TableCellStyle} cell - The cell to mutate
|
|
* @param {number} rowIndex - The cells row
|
|
* @param {number} colIndex - The cells column
|
|
* @returns {NormalizedTableCellStyle}
|
|
* @private
|
|
*/
|
|
export function normalizeCell(cell, rowIndex, colIndex) {
|
|
const colStyle = this._colStyle(colIndex);
|
|
let rowStyle = this._rowStyle(rowIndex);
|
|
|
|
const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);
|
|
const customFont = Object.values(font).filter((v) => v != null).length > 0;
|
|
const doc = this.document;
|
|
|
|
// Initialize cell context
|
|
const rollbackFont = doc._fontSource;
|
|
const rollbackFontSize = doc._fontSize;
|
|
const rollbackFontFamily = doc._fontFamily;
|
|
if (customFont) {
|
|
if (font.src) doc.font(font.src, font.family);
|
|
if (font.size) doc.fontSize(font.size);
|
|
|
|
// Refetch rowStyle to reflect font changes
|
|
rowStyle = this._rowStyle(rowIndex);
|
|
}
|
|
|
|
cell.padding = normalizeSides(cell.padding);
|
|
cell.border = normalizeSides(cell.border);
|
|
cell.borderColor = normalizeSides(cell.borderColor);
|
|
|
|
// Cell takes highest priority, then row, then column, then defaultConfig
|
|
const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
|
|
config.rowIndex = rowIndex;
|
|
config.colIndex = colIndex;
|
|
config.font = font ?? {};
|
|
config.customFont = customFont;
|
|
|
|
// Normalize config
|
|
config.text = normalizeText(config.text);
|
|
config.rowSpan = config.rowSpan ?? 1;
|
|
config.colSpan = config.colSpan ?? 1;
|
|
config.padding = normalizeSides(config.padding, '0.25em', (x) =>
|
|
doc.sizeToPoint(x, '0.25em'),
|
|
);
|
|
config.border = normalizeSides(config.border, 1, (x) =>
|
|
doc.sizeToPoint(x, 1),
|
|
);
|
|
config.borderColor = normalizeSides(
|
|
config.borderColor,
|
|
'black',
|
|
(x) => x ?? 'black',
|
|
);
|
|
config.align = normalizeAlignment(config.align);
|
|
config.align.x = config.align.x ?? 'left';
|
|
config.align.y = config.align.y ?? 'top';
|
|
config.textStroke = doc.sizeToPoint(config.textStroke, 0);
|
|
config.textStrokeColor = config.textStrokeColor ?? 'black';
|
|
config.textColor = config.textColor ?? 'black';
|
|
config.textOptions = config.textOptions ?? {};
|
|
|
|
// Accessibility settings
|
|
config.id = new String(config.id ?? `${this._id}-${rowIndex}-${colIndex}`);
|
|
config.type = config.type?.toUpperCase() === 'TH' ? 'TH' : 'TD';
|
|
if (config.scope) {
|
|
config.scope = config.scope.toLowerCase();
|
|
if (config.scope === 'row') config.scope = 'Row';
|
|
else if (config.scope === 'both') config.scope = 'Both';
|
|
else if (config.scope === 'column') config.scope = 'Column';
|
|
}
|
|
|
|
if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;
|
|
|
|
// Rollback font
|
|
if (customFont) doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Normalize a row
|
|
*
|
|
* @this PDFTable
|
|
* @memberOf PDFTable
|
|
* @param {TableCell[]} row
|
|
* @param {number} rowIndex
|
|
* @returns {NormalizedTableCellStyle[]}
|
|
* @private
|
|
*/
|
|
export function normalizeRow(row, rowIndex) {
|
|
if (!this._cellClaim) this._cellClaim = new Set();
|
|
|
|
let colIndex = 0;
|
|
return row.map((cell) => {
|
|
// Ensure TableCell
|
|
if (cell == null || typeof cell !== 'object') cell = { text: cell };
|
|
|
|
// Find the starting column of the cell
|
|
// Skipping over the claimed cells
|
|
while (this._cellClaim.has(`${rowIndex},${colIndex}`)) {
|
|
colIndex++;
|
|
}
|
|
|
|
cell = normalizeCell.call(this, cell, rowIndex, colIndex);
|
|
|
|
// Claim any spanning cells
|
|
for (let i = 0; i < cell.rowSpan; i++) {
|
|
for (let j = 0; j < cell.colSpan; j++) {
|
|
this._cellClaim.add(`${rowIndex + i},${colIndex + j}`);
|
|
}
|
|
}
|
|
|
|
colIndex += cell.colSpan;
|
|
return cell;
|
|
});
|
|
}
|