Skip to content

Instantly share code, notes, and snippets.

@killan
Last active June 29, 2023 10:29
Show Gist options
  • Save killan/a4f6cac70063aacd58df7ba43fefaeac to your computer and use it in GitHub Desktop.
Save killan/a4f6cac70063aacd58df7ba43fefaeac to your computer and use it in GitHub Desktop.
jsPDF Goo's base
import { jsPDF } from "jspdf";
export interface Margins {
left: number
right: number
top: number
bottom: number
}
export interface TableColumns {
title: string
field: string
width: number // Percent on 1
align: "left" | "center" | "right" | "justify"
}
export interface RGBColor {
r: number,
g: number,
b: number,
a?: number
}
export const DIM_A4_POINTS = { width: 595, height: 842 }
export const DIM_MM_POINTS = 2.83465
export const DIM_PX_POINTS = 0.75 // 1.333
export type HeaderPrototype = (pdf: jsPDF) => void
export class KPdf {
doc: jsPDF
margins!: Margins // Defined in constructor
cellPaddings!: Margins
x: number
y: number
pageWidth!: number // Updated by constructor call
pageHeight!: number // Updated by constructor call
headerFct?: HeaderPrototype
footerFct?: HeaderPrototype
tableHeaderInitFct?: HeaderPrototype
tableContentInitFct?: HeaderPrototype
fontSize: number = 10
lineHeight: number = 12
constructor() {
this.doc = new jsPDF("p", "pt", "a4")
this.setMargins(20 * DIM_MM_POINTS)
this.setCellPaddings(0.5 * DIM_MM_POINTS)
this.x = this.margins.left
this.y = this.margins.top
}
// Setters
setMargins(margins: number): KPdf;
setMargins(left: number, right?: number, top?: number, bottom?: number): KPdf {
this.margins = right ? { left, right, top, bottom } as Margins
: { left: left, right: left, top: left, bottom: left } as Margins
this.updatePageDims()
return this
}
setCellPaddings(paddings: number): KPdf;
setCellPaddings(left: number, right?: number, top?: number, bottom?: number): KPdf {
this.cellPaddings = right ? { left, right, top, bottom } as Margins
: { left: left, right: left, top: left, bottom: left } as Margins
return this
}
setHeaderFct(fct: HeaderPrototype): KPdf {
this.headerFct = fct
return this
}
setFooterFct(fct: HeaderPrototype): KPdf {
this.footerFct = fct
return this
}
setTableHeaderInitFct(fct: HeaderPrototype): KPdf {
this.tableHeaderInitFct = fct
return this
}
setTableContentInitFct(fct: HeaderPrototype): KPdf {
this.tableContentInitFct = fct
return this
}
setX(x: number): KPdf {
this.x = x
return this
}
addX(value: number): KPdf {
this.x += value
return this
}
setY(y: number): KPdf {
this.y = y
return this
}
addY(value: number): KPdf {
this.y += value
return this
}
setXY(x: number, y: number): KPdf {
this.setX(x)
this.setY(y)
return this
}
setFontSize(fontSize: number): KPdf {
this.fontSize = fontSize
this.doc.setFontSize(fontSize)
return this
}
setLineHeight(lineHeight: number): KPdf {
this.lineHeight = lineHeight
return this
}
// Getters
getMargins(): Margins {
return this.margins
}
getCellPaddings(): Margins {
return this.cellPaddings
}
getX(): number {
return this.x
}
getY(): number {
return this.y
}
getXY(): { x: number, y: number } {
return { x: this.x, y: this.y }
}
getFontSize(): number {
return this.fontSize
}
getLineHeight(): number {
return this.lineHeight
}
updatePageDims(): void {
// A4 forced format on portait orientation
this.pageWidth = DIM_A4_POINTS.width - this.margins.left - this.margins.right
this.pageHeight = DIM_A4_POINTS.height - this.margins.top - this.margins.bottom
}
addHeader(): KPdf {
if (this.headerFct) {
this.headerFct(this.doc)
}
return this
}
addFooter(): KPdf {
if (this.footerFct) {
this.footerFct(this.doc)
}
return this
}
newPage(): KPdf {
this.doc.addPage()
this.addHeader().addFooter()
this.setXY(
this.margins.left,
this.margins.top
)
return this
}
// Table part
addTableHead(columns: TableColumns[]): KPdf {
if (this.tableHeaderInitFct) {
this.tableHeaderInitFct(this.doc)
}
// X, Y, fontSize and LineHeight already set by user
this.y += this.cellPaddings.top
columns.forEach((c) => {
const cellW = this.pageWidth * c.width
this.doc.text(
c.title,
this.x + (c.align === "right" ? cellW - this.cellPaddings.right : this.cellPaddings.left),
this.y,
{
baseline: "top",
align: c.align
}
)
this.x += cellW
})
this.y += this.lineHeight + this.cellPaddings.bottom
return this
}
addTableContent(columns: TableColumns[], data: any[], zebraColor: RGBColor = { r: 240, g: 240, b: 240 }): KPdf {
if (this.tableContentInitFct) {
this.tableContentInitFct(this.doc)
}
let zebra = true
data.forEach((d) => {
let maxCellHeight = 0
// Find sizes
columns.forEach((c) => {
const cellW = this.pageWidth * c.width
// Cell size prep
const content: string[] = this.doc.splitTextToSize(d[c.field], cellW - this.cellPaddings.left - this.cellPaddings.right)
maxCellHeight = Math.max(maxCellHeight, content.length * this.lineHeight)
})
if (zebra) {
this.doc
.setFillColor(zebraColor.r, zebraColor.g, zebraColor.b, zebraColor.a)
.rect(
this.x,
this.y,
this.pageWidth,
maxCellHeight + this.cellPaddings.top + this.cellPaddings.bottom,
"F"
)
}
zebra = !zebra
this.y += this.cellPaddings.top
// Write content
columns.forEach((c) => {
const cellW = this.pageWidth * c.width
const content: string[] = this.doc.splitTextToSize(d[c.field], cellW - this.cellPaddings.left - this.cellPaddings.right)
// Write cell content
content.forEach((line, i) => {
this.doc.text(
line,
this.x + (c.align === "right" ? cellW - this.cellPaddings.right : this.cellPaddings.left),
this.y + (i * this.lineHeight),
{
baseline: "top",
align: c.align
}
)
})
this.x += cellW
})
this.x = this.margins.left
this.y += maxCellHeight + this.cellPaddings.bottom
// Page break
if (this.y >= this.margins.top + this.pageHeight) {
this.newPage()
this.addTableHead(columns)
if (this.tableContentInitFct) {
this.tableContentInitFct(this.doc)
}
}
})
return this
}
save(fileName: string): KPdf {
this.doc.save(fileName)
return this
}
}
const kpdf = new KPdf()
const daabooRed: RGBColor = { r: 148, g: 14, b: 14 }
kpdf
.setMargins(
20 * DIM_MM_POINTS,
20 * DIM_MM_POINTS,
30 * DIM_MM_POINTS,
30 * DIM_MM_POINTS
)
.setCellPaddings(
2 * DIM_MM_POINTS
)
.setHeaderFct((jsPDF) => {
jsPDF
.setTextColor("#333333")
.setFontSize(20)
.text(
"blah",
kpdf.getMargins().left + kpdf.pageWidth,
30,
{
align: "right",
baseline: "top"
})
.setFontSize(16)
.text(
"blah-blah",
kpdf.getMargins().left + kpdf.pageWidth,
54,
{
align: "right",
baseline: "top"
})
/*.rect(
kpdf.getMargins().left,
kpdf.getMargins().top,
kpdf.pageWidth,
kpdf.pageHeight
)*/
})
.setFooterFct((jsPDF) => {
jsPDF
.setTextColor("#616161")
.setFontSize(10)
.text([
"Killan"
], kpdf.getMargins().left,
kpdf.getMargins().top + kpdf.pageHeight + 10,
{
baseline: "top"
})
.text([
"daaboo.net"
], kpdf.getMargins().left + kpdf.pageWidth,
kpdf.getMargins().top + kpdf.pageHeight + 10,
{
align: "right",
baseline: "top"
})
.setDrawColor(daabooRed.r, daabooRed.g, daabooRed.b)
.line(
kpdf.getMargins().left,
kpdf.getMargins().top + kpdf.pageHeight,
kpdf.getMargins().left + kpdf.pageWidth,
kpdf.getMargins().top + kpdf.pageHeight
)
})
// Manage first page
kpdf.addHeader().addFooter()
// Add address
const adrLeft = kpdf.getMargins().left + (kpdf.pageWidth * 0.5)
const adr = [
"indented text like address"
]
kpdf.doc
.setTextColor("#333333")
.setFontSize(12)
.text(
adr,
adrLeft,
kpdf.getMargins().top + (10 * DIM_MM_POINTS),
{
baseline: "top"
}
)
// Prep table
const columns: TableColumns[] = [
{
title: "col1",
width: 0.51,
align: "left",
field: "field1"
},
{
title: "col2",
width: 0.10,
align: "right",
field: "field2"
},
{
title: "col3",
width: 0.15,
align: "right",
field: "field3"
},
{
title: "col4",
width: 0.12,
align: "right",
field: "field4"
},
{
title: "col5",
width: 0.12,
align: "right",
field: "field5"
}
]
// Position start (first page)
kpdf.setXY(kpdf.getMargins().left, kpdf.getMargins().top + (30 * DIM_MM_POINTS) + (adr.length * 14))
// Config table init fct
// Helps page break display
kpdf
.setTableHeaderInitFct((jsPDF) => {
kpdf.setFontSize(12)
.setLineHeight(12)
.doc.setTextColor(daabooRed.r, daabooRed.g, daabooRed.b)
})
.setTableContentInitFct((jsPDF) => {
kpdf.setX(kpdf.getMargins().left)
.setLineHeight(14)
.doc.setTextColor("#333333")
})
kpdf
.addTableHead(columns)
.addTableContent(
columns,
[]
)
kpdf.save("mydoc.pdf")
@killan
Copy link
Author

killan commented Jun 29, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment