Skip to content

Commit

Permalink
Rewrite width options and distribution algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
mmghv committed May 2, 2024
1 parent 7dd9892 commit 5ab4b57
Show file tree
Hide file tree
Showing 10 changed files with 585 additions and 384 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Checkout more examples in [examples.js](examples) which is also the source code
- Major: Only the `autoTable(doc, {...})` usage style is now supported and `doc.autoTable({...})` has been removed (see usage section above. Read more: #997)
- Setting default options has been removed and can be replaced by keeping track of this state yourself
- Removed old deprecations
- The value `'wrap'` of `cellWidth` and `tableWidth` has been renamed to `'max-content'`

## Options

Expand Down Expand Up @@ -121,8 +122,9 @@ autoTable(doc, {
- `overflow: 'linebreak'|'ellipsize'|'visible'|'hidden' = 'linebreak'`
- `fillColor: Color? = null`
- `textColor: Color? = 20`
- `cellWidth: 'auto'|'wrap'|number = 'auto'`
- `minCellWidth: number? = 10`
- `cellWidth: 'auto'|'min-content'|'max-content'|number = 'auto'`
- `minCellWidth: 'auto'|'min-content'|'max-content'|number = 'auto'` (`'auto'` = 10pt for columns with `'auto'` cellWidth, 0 otherwise)
- `maxCellWidth: 'auto'|'min-content'|'max-content'|number = 'auto'`
- `minCellHeight: number = 0`
- `halign: 'left'|'center'|'right' = 'left'`
- `valign: 'top'|'middle'|'bottom' = 'top'`
Expand Down Expand Up @@ -187,9 +189,9 @@ autoTable(doc, {
- `margin: Margin = 40`
- `pageBreak: 'auto'|'avoid'|'always'` If set to `avoid` the plugin will only split a table onto multiple pages if table height is larger than page height.
- `rowPageBreak: 'auto'|'avoid' = 'auto'` If set to `avoid` the plugin will only split a row onto multiple pages if row height is larger than page height.
- `tableWidth: 'auto'|'wrap'|number = 'auto'`
- `showHead: 'everyPage'|'firstPage'|'never' = 'everyPage''`
- `showFoot: 'everyPage'|'lastPage'|'never' = 'everyPage''`
- `tableWidth: 'auto'|'min-content'|'max-content'|'fit-content'|number = 'auto'` Option `'fit-content'` is similar to `'max-content'` but limited to the page width.
- `showHead: 'everyPage'|'firstPage'|'never' = 'everyPage'`
- `showFoot: 'everyPage'|'lastPage'|'never' = 'everyPage'`
- `tableLineWidth: number = 0`
- `tableLineColor: Color = 200` The table line/border color
- `horizontalPageBreak: boolean = false` To split/break the table into multiple pages if the given table width exceeds the page width
Expand Down
4 changes: 2 additions & 2 deletions examples/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ examples.minimal = function () {
const doc = new jsPDF()
autoTable(doc, {
html: '.table',
tableWidth: 'wrap',
tableWidth: 'fit-content',
styles: { cellPadding: 0.5, fontSize: 8 },
})
return doc
Expand All @@ -87,7 +87,7 @@ examples.long = function () {
body: body,
startY: 25,
// Default for all columns
styles: { overflow: 'ellipsize', cellWidth: 'wrap' },
styles: { overflow: 'ellipsize', cellWidth: 'max-content' },
// Override the default above for the text column
columnStyles: { text: { cellWidth: 'auto' } },
})
Expand Down
6 changes: 3 additions & 3 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function addTableBorder(
doc.rect(
startPos.x,
startPos.y,
table.getWidth(doc.pageSize().width),
cursor.x - startPos.x,
cursor.y - startPos.y,
fillStyle,
)
Expand Down Expand Up @@ -119,7 +119,7 @@ export function parseSpacing(
return { top: value, right: value, bottom: value, left: value }
}

export function getPageAvailableWidth(doc: DocHandler, table: Table) {
const margins = parseSpacing(table.settings.margin, 0)
export function getPageNetWidth(doc: DocHandler, table: Table) {
const margins = table.settings.margin
return doc.pageSize().width - (margins.left + margins.right)
}
12 changes: 7 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type OverflowType =
| 'visible'
| 'hidden'
| ((text: string | string[], width: number) => string | string[])
export type CellWidthType = 'auto' | 'wrap' | number
export type CellWidthType = 'auto' | 'min-content' | 'max-content' | number

export interface Styles {
font: FontType
Expand All @@ -34,14 +34,15 @@ export interface Styles {
lineColor: Color
lineWidth: number | Partial<LineWidths>
cellWidth: CellWidthType
minCellWidth: CellWidthType
maxCellWidth: CellWidthType
minCellHeight: number
minCellWidth: number
}

export type ThemeType = 'striped' | 'grid' | 'plain' | null
export type PageBreakType = 'auto' | 'avoid' | 'always'
export type RowPageBreakType = 'auto' | 'avoid'
export type TableWidthType = 'auto' | 'wrap' | number
export type TableWidthType = CellWidthType | 'fit-content'
export type ShowHeadType = 'everyPage' | 'firstPage' | 'never' | boolean
export type ShowFootType = 'everyPage' | 'lastPage' | 'never' | boolean
export type HorizontalPageBreakBehaviourType = 'immediately' | 'afterAllRows'
Expand Down Expand Up @@ -149,9 +150,10 @@ export function defaultStyles(scaleFactor: number): Styles {
cellPadding: 5 / scaleFactor, // number or {top,left,right,left,vertical,horizontal}
lineColor: 200,
lineWidth: 0,
cellWidth: 'auto', // 'auto'|'wrap'|number
cellWidth: 'auto', // 'auto' | 'min-content' | 'max-content' | number
minCellWidth: 'auto', // 'auto' | 'min-content' | 'max-content' | number
maxCellWidth: 'auto', // 'auto' | 'min-content' | 'max-content' | number
minCellHeight: 0,
minCellWidth: 0,
}
}

Expand Down
63 changes: 29 additions & 34 deletions src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HtmlRowInput,
RowInput,
Styles,
TableWidthType,
} from './config'
import { DocHandler } from './documentHandler'
import { CellHookData, HookData } from './HookData'
Expand All @@ -16,8 +17,8 @@ export type PageHook = (data: HookData) => void | boolean
export type CellHook = (data: CellHookData) => void | boolean

export interface HookProps {
// Make sure to use undefined union and not optional "?"
// so that all the keys are always present
// Note: Make sure to use undefined union here rather than the optional operator ("?")
// to ensure that all the keys are always present
didParseCell: CellHook | undefined
willDrawCell: CellHook | undefined
didDrawCell: CellHook | undefined
Expand All @@ -33,7 +34,7 @@ export interface Settings {
margin: MarginPadding
pageBreak: 'auto' | 'avoid' | 'always'
rowPageBreak: 'auto' | 'avoid'
tableWidth: 'auto' | 'wrap' | number
tableWidth: TableWidthType
showHead: 'everyPage' | 'firstPage' | 'never'
showFoot: 'everyPage' | 'lastPage' | 'never'
tableLineWidth: number
Expand Down Expand Up @@ -70,9 +71,10 @@ export class Table {
readonly body: Row[]
readonly foot: Row[]

pageNumber = 1
finalY?: number
startPageNumber?: number
pageNumber = 1
finalY = 0
width = 0

constructor(input: TableInput, content: ContentSettings) {
this.id = input.id
Expand Down Expand Up @@ -132,21 +134,6 @@ export class Table {
this.hooks.willDrawPage(new HookData(doc, this, cursor))
}
}

getWidth(pageWidth: number) {
if (typeof this.settings.tableWidth === 'number') {
return this.settings.tableWidth
} else if (this.settings.tableWidth === 'wrap') {
const wrappedWidth = this.columns.reduce(
(total, col) => total + col.wrappedWidth,
0,
)
return wrappedWidth
} else {
const margin = this.settings.margin
return pageWidth - margin.left - margin.right
}
}
}

export class Row {
Expand Down Expand Up @@ -221,9 +208,7 @@ export class Cell {

contentHeight = 0
contentWidth = 0
wrappedWidth = 0
minReadableWidth = 0
minWidth = 0
minContentWidth = 0

width = 0
height = 0
Expand Down Expand Up @@ -303,11 +288,24 @@ export class Column {
dataKey: string | number
index: number

wrappedWidth = 0
minReadableWidth = 0
/** The calculated full-width required to fit the content without introducing new line-breaks */
maxContentWidth = 0

/** The minimum calculated width required to fit the content in a readable manner (with no word-breaking) */
minContentWidth = 0

/** The minimum width allowed for the column based on all cells and the constrains */
minWidth = 0

/** The maximum width allowed for the column based on all cells and the constrains */
maxWidth = Infinity

/** The final used width */
width = 0

/** If the column or any of its cells has a fixed width */
isFixed = false

constructor(
dataKey: string | number,
raw: ColumnInput | null,
Expand All @@ -318,14 +316,11 @@ export class Column {
this.index = index
}

getMaxCustomCellWidth(table: Table) {
let max = 0
for (const row of table.allRows()) {
const cell: Cell = row.cells[this.index]
if (cell && typeof cell.styles.cellWidth === 'number') {
max = Math.max(max, cell.styles.cellWidth)
}
}
return max
getStyles(tableStyles: StylesProps) {
return (
tableStyles.columnStyles[this.dataKey] ||
tableStyles.columnStyles[this.index] ||
{}
)
}
}
42 changes: 26 additions & 16 deletions src/tableCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ function cellStyles(
scaleFactor: number,
cellInputStyles: Partial<Styles>,
) {
const defaultStyle = defaultStyles(scaleFactor)
const theme = getTheme(themeName)
let sectionStyles
if (sectionName === 'head') {
Expand All @@ -176,29 +177,38 @@ function cellStyles(
} else if (sectionName === 'foot') {
sectionStyles = styles.footStyles
}
const otherStyles = Object.assign(
{},
theme.table,
theme[sectionName],
styles.styles,
sectionStyles,
)
const columnStyles =
styles.columnStyles[column.dataKey] ||
styles.columnStyles[column.index] ||
{}
const colStyles = sectionName === 'body' ? columnStyles : {}
const rowStyles =
sectionName === 'body' && rowIndex % 2 === 0
? Object.assign({}, theme.alternateRow, styles.alternateRowStyles)
: {}
const defaultStyle = defaultStyles(scaleFactor)
const themeStyles = Object.assign(

const columnStyles = column.getStyles(styles)
let colStyles: typeof columnStyles
if (sectionName === 'body') {
colStyles = columnStyles
} else {
// For head & foot cells, only inherit width styles from columnStyles (if any)
// If we didn't do that, head & foot cells would inherit width from styles but not columnStyles
// which would prevent us from resetting width styles for columns in certain situations
// (check testcase : testWidthCalculator > 'reset column width')
colStyles = {}
if ('cellWidth' in columnStyles)
colStyles.cellWidth = columnStyles.cellWidth
if ('minCellWidth' in columnStyles)
colStyles.minCellWidth = columnStyles.minCellWidth
if ('maxCellWidth' in columnStyles)
colStyles.maxCellWidth = columnStyles.maxCellWidth
}

return Object.assign(
{},
defaultStyle,
otherStyles,
theme.table,
theme[sectionName],
styles.styles,
sectionStyles,
rowStyles,
colStyles,
cellInputStyles,
)
return Object.assign(themeStyles, cellInputStyles)
}
14 changes: 7 additions & 7 deletions src/tablePrinter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getPageAvailableWidth } from './common'
import { getPageNetWidth } from './common'
import { DocHandler } from './documentHandler'
import { Column, Table } from './models'

Expand All @@ -14,8 +14,8 @@ function getColumnsCanFitInPage(
table: Table,
config: { start?: number } = {},
): ColumnFitInPageResult {
// Get page width
let remainingWidth = getPageAvailableWidth(doc, table)
// Get page width (allow for small tolerance to prevent unwanted page breaks)
let remainingWidth = getPageNetWidth(doc, table) + 1e-10

// Get column data key to repeat
const repeatColumnsMap = new Map<number, boolean>()
Expand Down Expand Up @@ -45,22 +45,22 @@ function getColumnsCanFitInPage(
repeatColumnsMap.set(col.index, true)
colIndexes.push(col.index)
columns.push(table.columns[col.index])
remainingWidth -= col.wrappedWidth
remainingWidth -= col.width
}
})

let first = true
let i = config?.start ?? 0 // make sure couter is initiated outside the loop
let i = config?.start ?? 0
while (i < table.columns.length) {
// Prevent duplicates
if (repeatColumnsMap.has(i)) {
i++
continue
}

const colWidth = table.columns[i].wrappedWidth
const colWidth = table.columns[i].width

// Take at least one column even if it doesn't fit
// Take at least one column even if it doesn't fit to prevent infinite loops
if (first || remainingWidth >= colWidth) {
first = false
colIndexes.push(i)
Expand Down
Loading

0 comments on commit 5ab4b57

Please sign in to comment.