Skip to content

Commit

Permalink
Performance improvements to tables
Browse files Browse the repository at this point in the history
  • Loading branch information
hollandjake committed Feb 14, 2025
1 parent dafb604 commit 7c0a94e
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 300 deletions.
30 changes: 16 additions & 14 deletions lib/table/accessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import PDFDocument from '../document';
* @private
*/
export function accommodateTable() {
if (this.opts.structParent) {
const structParent = this.opts.structParent;
if (structParent) {
this._tableStruct = this.document.struct('Table');
this._tableStruct.dictionary.data.ID = this._id;
if (this.opts.structParent instanceof PDFStructureElement) {
this.opts.structParent.add(this._tableStruct);
} else if (this.opts.structParent instanceof PDFDocument) {
this.opts.structParent.addStructure(this._tableStruct);
if (structParent instanceof PDFStructureElement) {
structParent.add(this._tableStruct);
} else if (structParent instanceof PDFDocument) {
structParent.addStructure(this._tableStruct);
}
this._headerRowLookup = {};
this._headerColumnLookup = {};
Expand Down Expand Up @@ -62,7 +63,9 @@ export function accessibleRow(row, rowIndex, renderCell) {
* @private
*/
export function accessibleCell(cell, rowStruct, callback) {
const cellStruct = this.document.struct(cell.type, { title: cell.title });
const doc = this.document;

const cellStruct = doc.struct(cell.type, { title: cell.title });
cellStruct.dictionary.data.ID = cell.id;

rowStruct.add(cellStruct);
Expand Down Expand Up @@ -116,27 +119,26 @@ export function accessibleCell(cell, rowStruct, callback) {
);
if (Headers.size) attributes.Headers = Array.from(Headers);

const normalizeColor = doc._normalizeColor;
if (cell.backgroundColor != null) {
attributes.BackgroundColor = this.document._normalizeColor(
cell.backgroundColor,
);
attributes.BackgroundColor = normalizeColor(cell.backgroundColor);
}
const hasBorder = [border.top, border.bottom, border.left, border.right];
if (hasBorder.some((x) => x)) {
const borderColor = cell.borderColor;
attributes.BorderColor = [
hasBorder[0] ? this.document._normalizeColor(borderColor.top) : null,
hasBorder[1] ? this.document._normalizeColor(borderColor.bottom) : null,
hasBorder[2] ? this.document._normalizeColor(borderColor.left) : null,
hasBorder[3] ? this.document._normalizeColor(borderColor.right) : null,
hasBorder[0] ? normalizeColor(borderColor.top) : null,
hasBorder[1] ? normalizeColor(borderColor.bottom) : null,
hasBorder[2] ? normalizeColor(borderColor.left) : null,
hasBorder[3] ? normalizeColor(borderColor.right) : null,
];
}

// Remove any undefined attributes
Object.keys(attributes).forEach(
(key) => attributes[key] === undefined && delete attributes[key],
);
cellStruct.dictionary.data.A = this.document.ref(attributes);
cellStruct.dictionary.data.A = doc.ref(attributes);
cellStruct.add(callback);
cellStruct.end();
cellStruct.dictionary.data.A.end();
Expand Down
103 changes: 54 additions & 49 deletions lib/table/normalize.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { deepMerge, definedProps, normalizeSides } from '../utils';
import { deepMerge, memoize } from './utils';
import {
normalizeAlignment,
normalizedColumnStyle,
normalizedDefaultStyle,
normalizedRowStyle,
} from './style';
import { normalizeSides } from '../utils';

/**
* Normalize a table
Expand All @@ -13,45 +15,48 @@ import {
* @private
*/
export function normalizeTable() {
const doc = this.document;
const opts = this.opts;

// Normalize config
let index = this.document._tableIndex++;
this._id = new String(this.opts.id ?? `table-${index}`);
let index = doc._tableIndex++;
this._id = new String(opts.id ?? `table-${index}`);
this._position = {
x: this.document.sizeToPoint(this.opts.position?.x, this.document.x),
y: this.document.sizeToPoint(this.opts.position?.y, this.document.y),
x: doc.sizeToPoint(opts.position?.x, doc.x),
y: doc.sizeToPoint(opts.position?.y, doc.y),
};
this._maxWidth = this.document.sizeToPoint(
this.opts.maxWidth,
this.document.page.width -
this.document.page.margins.right -
this._position.x,
this._maxWidth = doc.sizeToPoint(
opts.maxWidth,
doc.page.width - doc.page.margins.right - this._position.x,
);

const { defaultStyle, defaultColStyle, defaultRowStyle } =
normalizedDefaultStyle(this.opts.defaultStyle);
normalizedDefaultStyle(opts.defaultStyle);
this._defaultStyle = defaultStyle;

let colStyle;
if (this.opts.columnStyles) {
if (Array.isArray(this.opts.columnStyles)) {
colStyle = (i) => this.opts.columnStyles[i];
} else if (typeof this.opts.columnStyles === 'function') {
colStyle = this.opts.columnStyles;
} else if (typeof this.opts.columnStyles === 'object') {
colStyle = () => this.opts.columnStyles;
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 (this.opts.rowStyles) {
if (Array.isArray(this.opts.rowStyles)) {
rowStyle = (i) => this.opts.rowStyles[i];
} else if (typeof this.opts.rowStyles === 'function') {
rowStyle = this.opts.rowStyles;
} else if (typeof this.opts.rowStyles === 'object') {
rowStyle = () => this.opts.rowStyles;
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 = () => ({});
Expand Down Expand Up @@ -90,12 +95,14 @@ export function normalizeCell(cell, rowIndex, colIndex) {

const font = deepMerge({}, colStyle.font, rowStyle.font, cell.font);

const doc = this.document;

// Initialize cell context
const rollbackFont = this.document._fontSource;
const rollbackFontSize = this.document._fontSize;
const rollbackFontFamily = this.document._fontFamily;
if (font.src) this.document.font(font.src, font.family);
if (font.size) this.document.fontSize(font.size);
const rollbackFont = doc._fontSource;
const rollbackFontSize = doc._fontSize;
const rollbackFontFamily = doc._fontFamily;
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);
Expand All @@ -105,49 +112,48 @@ export function normalizeCell(cell, rowIndex, colIndex) {
cell.borderColor = normalizeSides(cell.borderColor);

// Cell takes highest priority, then row, then column, then defaultConfig
const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell, {
rowIndex,
colIndex,
font,
});
const config = deepMerge(this._defaultStyle, colStyle, rowStyle, cell);
config.rowIndex = rowIndex;
config.colIndex = colIndex;
config.font = font;

// 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) =>
this.document.sizeToPoint(x, '0.25em'),
doc.sizeToPoint(x, '0.25em'),
);
config.border = normalizeSides(config.border, 1, (x) =>
this.document.sizeToPoint(x, 1),
doc.sizeToPoint(x, 1),
);
config.borderColor = normalizeSides(
config.borderColor,
'black',
(x) => x ?? 'black',
);
config.align =
config.align == null || typeof config.align === 'string'
? { x: config.align, y: config.align }
: config.align;
config.align = normalizeAlignment(config.align);
config.align.x = config.align.x ?? 'left';
config.align.y = config.align.y ?? 'top';
config.textStroke = this.document.sizeToPoint(config.textStroke, 0);
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';
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 (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 (this.opts.debug !== undefined) config.debug = this.opts.debug;
if (typeof this.opts.debug === 'boolean') config.debug = this.opts.debug;

this.document.font(rollbackFont, rollbackFontFamily, rollbackFontSize);
// Rollback font
doc.font(rollbackFont, rollbackFontFamily, rollbackFontSize);

return config;
}
Expand All @@ -169,7 +175,6 @@ export function normalizeRow(row, rowIndex) {
return row.map((cell) => {
// Ensure TableCell
if (cell == null || typeof cell !== 'object') cell = { text: cell };
cell = definedProps(cell);

// Find the starting column of the cell
// Skipping over the claimed cells
Expand Down
Loading

0 comments on commit 7c0a94e

Please sign in to comment.