@pdfme/schemas
Version:
TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!
460 lines • 16.5 kB
JavaScript
import { mm2pt, pt2mm } from '@pdfme/common';
import { splitTextToSize, getFontKitFont, widthOfTextAtSize } from '../text/helper.js';
export class Cell {
constructor(raw, styles, section) {
Object.defineProperty(this, "raw", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "text", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "styles", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "section", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "contentHeight", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "contentWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "wrappedWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "minReadableWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "minWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "width", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "height", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "x", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "y", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
this.styles = styles;
this.section = section;
this.raw = raw;
const splitRegex = /\r\n|\r|\n/g;
this.text = raw.split(splitRegex);
}
getContentHeight() {
const lineCount = Array.isArray(this.text) ? this.text.length : 1;
const lineHeight = pt2mm(this.styles.fontSize) * this.styles.lineHeight;
const vPadding = this.padding('top') + this.padding('bottom');
const height = lineCount * lineHeight + vPadding;
return Math.max(height, this.styles.minCellHeight);
}
padding(name) {
return this.styles.cellPadding[name];
}
}
export class Column {
constructor(index) {
Object.defineProperty(this, "index", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "wrappedWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "minReadableWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "minWidth", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "width", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
this.index = index;
}
getMaxCustomCellWidth(table) {
let max = 0;
for (const row of table.allRows()) {
const cell = row.cells[this.index];
max = Math.max(max, cell.styles.cellWidth);
}
return max;
}
}
export class Row {
constructor(raw, index, section, cells) {
Object.defineProperty(this, "raw", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "index", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "section", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "cells", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "height", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
this.raw = raw;
this.index = index;
this.section = section;
this.cells = cells;
}
getMaxCellHeight(columns) {
return columns.reduce((acc, column) => Math.max(acc, this.cells[column.index]?.height || 0), 0);
}
getMinimumRowHeight(columns) {
return columns.reduce((acc, column) => {
const cell = this.cells[column.index];
if (!cell)
return 0;
const vPadding = cell.padding('top') + cell.padding('bottom');
const oneRowHeight = vPadding + cell.styles.lineHeight;
return oneRowHeight > acc ? oneRowHeight : acc;
}, 0);
}
}
export class Table {
constructor(input, content) {
Object.defineProperty(this, "settings", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "styles", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "columns", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "head", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "body", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.settings = input.settings;
this.styles = input.styles;
this.columns = content.columns;
this.head = content.head;
this.body = content.body;
}
static async create(arg) {
const { input, content, font, _cache } = arg;
const table = new Table(input, content);
await calculateWidths({ table, font, _cache });
return table;
}
getHeadHeight() {
return this.head.reduce((acc, row) => acc + row.getMaxCellHeight(this.columns), 0);
}
getBodyHeight() {
return this.body.reduce((acc, row) => acc + row.getMaxCellHeight(this.columns), 0);
}
allRows() {
return this.head.concat(this.body);
}
getWidth() {
return this.settings.tableWidth;
}
getHeight() {
return (this.settings.showHead ? this.getHeadHeight() : 0) + this.getBodyHeight();
}
}
async function calculateWidths(arg) {
const { table, font, _cache } = arg;
const getFontKitFontByFontName = (fontName) => getFontKitFont(fontName, font, _cache);
await calculate(table, getFontKitFontByFontName);
const resizableColumns = [];
let initialTableWidth = 0;
table.columns.forEach((column) => {
const customWidth = column.getMaxCustomCellWidth(table);
if (customWidth) {
// final column width
column.width = customWidth;
}
else {
// initial column width (will be resized)
column.width = column.wrappedWidth;
resizableColumns.push(column);
}
initialTableWidth += column.width;
});
// width difference that needs to be distributed
let resizeWidth = table.getWidth() - initialTableWidth;
// first resize attempt: with respect to minReadableWidth and minWidth
if (resizeWidth) {
resizeWidth = resizeColumns(resizableColumns, resizeWidth, (column) => Math.max(column.minReadableWidth, column.minWidth));
}
// second resize attempt: ignore minReadableWidth but respect minWidth
if (resizeWidth) {
resizeWidth = resizeColumns(resizableColumns, resizeWidth, (column) => column.minWidth);
}
resizeWidth = Math.abs(resizeWidth);
applyColSpans(table);
await fitContent(table, getFontKitFontByFontName);
applyRowSpans(table);
}
function applyRowSpans(table) {
const rowSpanCells = {};
let colRowSpansLeft = 1;
const all = table.allRows();
for (let rowIndex = 0; rowIndex < all.length; rowIndex++) {
const row = all[rowIndex];
for (const column of table.columns) {
const data = rowSpanCells[column.index];
if (colRowSpansLeft > 1) {
colRowSpansLeft--;
delete row.cells[column.index];
}
else if (data) {
data.cell.height += row.height;
colRowSpansLeft = 1;
delete row.cells[column.index];
data.left--;
if (data.left <= 1) {
delete rowSpanCells[column.index];
}
}
else {
const cell = row.cells[column.index];
if (!cell) {
continue;
}
cell.height = row.height;
}
}
}
}
function applyColSpans(table) {
const all = table.allRows();
for (let rowIndex = 0; rowIndex < all.length; rowIndex++) {
const row = all[rowIndex];
let colSpanCell = null;
let combinedColSpanWidth = 0;
let colSpansLeft = 0;
for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex++) {
const column = table.columns[columnIndex];
// Width and colspan
colSpansLeft -= 1;
if (colSpansLeft > 1 && table.columns[columnIndex + 1]) {
combinedColSpanWidth += column.width;
delete row.cells[column.index];
}
else if (colSpanCell) {
const cell = colSpanCell;
delete row.cells[column.index];
colSpanCell = null;
cell.width = column.width + combinedColSpanWidth;
}
else {
const cell = row.cells[column.index];
if (!cell)
continue;
colSpansLeft = 1;
combinedColSpanWidth = 0;
cell.width = column.width + combinedColSpanWidth;
}
}
}
}
async function fitContent(table, getFontKitFontByFontName) {
const rowSpanHeight = { count: 0, height: 0 };
for (const row of table.allRows()) {
for (const column of table.columns) {
const cell = row.cells[column.index];
if (!cell)
continue;
const fontKitFont = await getFontKitFontByFontName(cell.styles.fontName);
cell.text = splitTextToSize({
value: cell.raw,
characterSpacing: cell.styles.characterSpacing,
boxWidthInPt: mm2pt(cell.width),
fontSize: cell.styles.fontSize,
fontKitFont,
});
cell.contentHeight = cell.getContentHeight();
let realContentHeight = cell.contentHeight;
if (rowSpanHeight && rowSpanHeight.count > 0) {
if (rowSpanHeight.height > realContentHeight) {
realContentHeight = rowSpanHeight.height;
}
}
if (realContentHeight > row.height) {
row.height = realContentHeight;
}
}
rowSpanHeight.count--;
}
}
function resizeColumns(columns, resizeWidth, getMinWidth) {
const initialResizeWidth = resizeWidth;
const sumWrappedWidth = columns.reduce((acc, column) => acc + column.wrappedWidth, 0);
for (let i = 0; i < columns.length; i++) {
const column = columns[i];
const ratio = column.wrappedWidth / sumWrappedWidth;
const suggestedChange = initialResizeWidth * ratio;
const suggestedWidth = column.width + suggestedChange;
const minWidth = getMinWidth(column);
const newWidth = suggestedWidth < minWidth ? minWidth : suggestedWidth;
resizeWidth -= newWidth - column.width;
column.width = newWidth;
}
resizeWidth = Math.round(resizeWidth * 1e10) / 1e10;
// Run the resizer again if there's remaining width needs
// to be distributed and there're columns that can be resized
if (resizeWidth) {
const resizableColumns = columns.filter((column) => {
return resizeWidth < 0
? column.width > getMinWidth(column) // check if column can shrink
: true; // check if column can grow
});
if (resizableColumns.length) {
resizeWidth = resizeColumns(resizableColumns, resizeWidth, getMinWidth);
}
}
return resizeWidth;
}
async function calculate(table, getFontKitFontByFontName) {
for (const row of table.allRows()) {
for (const column of table.columns) {
const cell = row.cells[column.index];
if (!cell)
continue;
const hPadding = cell.padding('right') + cell.padding('left');
const fontKitFont = await getFontKitFontByFontName(cell.styles.fontName);
cell.contentWidth = getStringWidth(cell, fontKitFont) + hPadding;
const longestWordWidth = getStringWidth(Object.assign(cell, { text: cell.text.join(' ').split(/\s+/) }), fontKitFont);
cell.minReadableWidth = longestWordWidth + hPadding;
cell.minWidth = cell.styles.cellWidth;
cell.wrappedWidth = cell.styles.cellWidth;
}
}
for (const row of table.allRows()) {
for (const column of table.columns) {
const cell = row.cells[column.index];
// For now we ignore the minWidth and wrappedWidth of colspan cells when calculating colspan widths.
// Could probably be improved upon however.
if (cell) {
column.wrappedWidth = Math.max(column.wrappedWidth, cell.wrappedWidth);
column.minWidth = Math.max(column.minWidth, cell.minWidth);
column.minReadableWidth = Math.max(column.minReadableWidth, cell.minReadableWidth);
}
else {
// Respect cellWidth set in columnStyles even if there is no cells for this column
// or if the column only have colspan cells. Since the width of colspan cells
// does not affect the width of columns, setting columnStyles cellWidth enables the
// user to at least do it manually.
// Note that this is not perfect for now since for example row and table styles are
// not accounted for
const columnStyles = table.styles.columnStyles[column.index] || {};
const cellWidth = columnStyles.cellWidth || columnStyles.minCellWidth;
if (cellWidth) {
column.minWidth = cellWidth;
column.wrappedWidth = cellWidth;
}
}
}
}
}
function getStringWidth(cell, fontKitFont) {
const text = cell.text;
const textArr = Array.isArray(text) ? text : [text];
const fontSize = cell.styles.fontSize;
const characterSpacing = cell.styles.characterSpacing;
const widestLineWidth = textArr
.map((text) => widthOfTextAtSize(text, fontKitFont, fontSize, characterSpacing))
.reduce((a, b) => Math.max(a, b), 0);
return widestLineWidth;
}
//# sourceMappingURL=classes.js.map