jspdf-invoice-template-nodejs
Version:
PDF template created for invoice with optional parameters (for NodeJs). Using jsPDF library.
658 lines (582 loc) • 20.7 kB
JavaScript
import { jsPDF } from "jspdf";
const OutputType = {
// Save: "save", //save pdf as a file
DataUriString: "datauristring", //returns the data uri string
// DataUri: "datauri", //opens the data uri in current window
// DataUrlNewWindow: "dataurlnewwindow", //opens the data uri in new window
// Blob: "blob", //return blob format of the doc,
// ArrayBuffer: "arraybuffer", //return ArrayBuffer format
};
export { OutputType, jsPDF };
/**
*
* @param { {
* outputType: OutputType | string,
* onJsPDFDocCreation?: (doc: jsPDF) => void,
* returnJsPDFDocObject?: boolean,
* fileName: string,
* orientationLandscape?: boolean,
* compress?: boolean,
* logo?: {
* src?: string,
* type?: string,
* width?: number,
* height?: number,
* margin?: {
* top?: number,
* left?: number
* }
* },
* stamp?: {
* inAllPages?: boolean,
* src?: string,
* type?: string,
* width?: number,
* height?: number,
* margin?: {
* top?: number,
* left?: number
* }
* },
* business?: {
* name?: string,
* address?: string,
* phone?: string,
* email?: string,
* email_1?: string,
* website?: string,
* },
* contact?: {
* label?: string,
* name?: string,
* address?: string,
* phone?: string,
* email?: string,
* otherInfo?: string,
* },
* invoice?: {
* label?: string,
* num?: number,
* invDate?: string,
* invGenDate?: string,
* headerBorder?: boolean,
* tableBodyBorder?: boolean,
* header?:
* {
* title: string,
* style?: { width?: number }
* }[],
* table?: any,
* invDescLabel?: string,
* invDesc?: string,
* additionalRows?: {
* col1?: string,
* col2?: string,
* col3?: string,
* style?: {
* fontSize?: number
* }
* }[],
* },
* footer?: {
* text?: string,
* },
* pageEnable?: boolean,
* pageLabel?: string, } } props
*/
function jsPDFInvoiceTemplate(props) {
const param = {
outputType: props.outputType || "save",
onJsPDFDocCreation: props.onJsPDFDocCreation || null,
returnJsPDFDocObject: props.returnJsPDFDocObject || false,
fileName: props.fileName || "",
orientationLandscape: props.orientationLandscape || false,
compress: props.compress || false,
logo: {
src: props.logo?.src || "",
type: props.logo?.type || "",
width: props.logo?.width || "",
height: props.logo?.height || "",
margin: {
top: props.logo?.margin?.top || 0,
left: props.logo?.margin?.left || 0,
},
},
stamp: {
inAllPages: props.stamp?.inAllPages || false,
src: props.stamp?.src || "",
width: props.stamp?.width || "",
height: props.stamp?.height || "",
margin: {
top: props.stamp?.margin?.top || 0,
left: props.stamp?.margin?.left || 0,
},
},
business: {
name: props.business?.name || "",
address: props.business?.address || "",
phone: props.business?.phone || "",
email: props.business?.email || "",
email_1: props.business?.email_1 || "",
website: props.business?.website || "",
},
contact: {
label: props.contact?.label || "",
name: props.contact?.name || "",
address: props.contact?.address || "",
phone: props.contact?.phone || "",
email: props.contact?.email || "",
otherInfo: props.contact?.otherInfo || "",
},
invoice: {
label: props.invoice?.label || "",
num: props.invoice?.num || "",
invDate: props.invoice?.invDate || "",
invGenDate: props.invoice?.invGenDate || "",
headerBorder: props.invoice?.headerBorder || false,
tableBodyBorder: props.invoice?.tableBodyBorder || false,
header: props.invoice?.header || [],
table: props.invoice?.table || [],
invDescLabel: props.invoice?.invDescLabel || "",
invDesc: props.invoice?.invDesc || "",
additionalRows: props.invoice?.additionalRows?.map(x => {
return {
col1: x?.col1 || "",
col2: x?.col2 || "",
col3: x?.col3 || "",
style: {
fontSize: x?.style?.fontSize || 12,
}
}
})
},
footer: {
text: props.footer?.text || "",
},
pageEnable: props.pageEnable || false,
pageLabel: props.pageLabel || "Page",
};
const splitTextAndGetHeight = (text, size) => {
var lines = doc.splitTextToSize(text, size);
return {
text: lines,
height: doc.getTextDimensions(lines).h,
};
};
if (param.invoice.table && param.invoice.table.length) {
if (param.invoice.table[0].length != param.invoice.header.length)
throw Error("Length of header and table column must be equal.");
}
const options = {
orientation: param.orientationLandscape ? "landscape" : "",
compress: param.compress
};
var doc = new jsPDF(options);
props.onJsPDFDocCreation && props.onJsPDFDocCreation(doc);
var docWidth = doc.internal.pageSize.width;
var docHeight = doc.internal.pageSize.height;
var colorBlack = "#000000";
var colorGray = "#4d4e53";
//starting at 15mm
var currentHeight = 15;
//var startPointRectPanel1 = currentHeight + 6;
var pdfConfig = {
headerTextSize: 20,
labelTextSize: 12,
fieldTextSize: 10,
lineHeight: 6,
subLineHeight: 4,
};
doc.setFontSize(pdfConfig.headerTextSize);
doc.setTextColor(colorBlack);
doc.text(docWidth - 10, currentHeight, param.business.name, "right");
doc.setFontSize(pdfConfig.fieldTextSize);
if (param.logo.src) {
var imageHeader = '';
if (typeof window === "undefined") {
imageHeader = param.logo.src;
} else {
imageHeader = new Image();
imageHeader.src = param.logo.src;
}
//doc.text(htmlDoc.sessionDateText, docWidth - (doc.getTextWidth(htmlDoc.sessionDateText) + 10), currentHeight);
if (param.logo.type)
doc.addImage(
imageHeader,
param.logo.type,
10 + param.logo.margin.left,
currentHeight - 5 + param.logo.margin.top,
param.logo.width,
param.logo.height
);
else
doc.addImage(
imageHeader,
10 + param.logo.margin.left,
currentHeight - 5 + param.logo.margin.top,
param.logo.width,
param.logo.height
);
}
doc.setTextColor(colorGray);
currentHeight += pdfConfig.subLineHeight;
currentHeight += pdfConfig.subLineHeight;
doc.text(docWidth - 10, currentHeight, param.business.address, "right");
currentHeight += pdfConfig.subLineHeight;
doc.text(docWidth - 10, currentHeight, param.business.phone, "right");
doc.setFontSize(pdfConfig.fieldTextSize);
// doc.setTextColor(colorGray);
currentHeight += pdfConfig.subLineHeight;
doc.text(docWidth - 10, currentHeight, param.business.email, "right");
currentHeight += pdfConfig.subLineHeight;
doc.text(docWidth - 10, currentHeight, param.business.email_1, "right");
currentHeight += pdfConfig.subLineHeight;
doc.text(docWidth - 10, currentHeight, param.business.website, "right");
//line breaker after logo & business info
if (param.invoice.header.length) {
currentHeight += pdfConfig.subLineHeight;
doc.line(10, currentHeight, docWidth - 10, currentHeight);
}
//Contact part
doc.setTextColor(colorGray);
doc.setFontSize(pdfConfig.fieldTextSize);
currentHeight += pdfConfig.lineHeight;
if (param.contact.label) {
doc.text(10, currentHeight, param.contact.label);
currentHeight += pdfConfig.lineHeight;
}
doc.setTextColor(colorBlack);
doc.setFontSize(pdfConfig.headerTextSize - 5);
if (param.contact.name) doc.text(10, currentHeight, param.contact.name);
if (param.invoice.label && param.invoice.num) {
doc.text(
docWidth - 10,
currentHeight,
param.invoice.label + param.invoice.num,
"right"
);
}
if (param.contact.name || (param.invoice.label && param.invoice.num))
currentHeight += pdfConfig.subLineHeight;
doc.setTextColor(colorGray);
doc.setFontSize(pdfConfig.fieldTextSize - 2);
if (param.contact.address || param.invoice.invDate) {
doc.text(10, currentHeight, param.contact.address);
doc.text(docWidth - 10, currentHeight, param.invoice.invDate, "right");
currentHeight += pdfConfig.subLineHeight;
}
if (param.contact.phone || param.invoice.invGenDate) {
doc.text(10, currentHeight, param.contact.phone);
doc.text(docWidth - 10, currentHeight, param.invoice.invGenDate, "right");
currentHeight += pdfConfig.subLineHeight;
}
if (param.contact.email) {
doc.text(10, currentHeight, param.contact.email);
currentHeight += pdfConfig.subLineHeight;
}
if (param.contact.otherInfo)
doc.text(10, currentHeight, param.contact.otherInfo);
else currentHeight -= pdfConfig.subLineHeight;
//end contact part
//TABLE PART
//var tdWidth = 31.66;
//10 margin left - 10 margin right
var tdWidth = (doc.getPageWidth() - 20) / param.invoice.header.length;
//#region TD WIDTH
if (param.invoice.header.length > 2) { //add style for 2 or more columns
const customColumnNo = param.invoice.header.map(x => x?.style?.width || 0).filter(x => x > 0);
let customWidthOfAllColumns = customColumnNo.reduce((a, b) => a + b, 0);
tdWidth = (doc.getPageWidth() - 20 - customWidthOfAllColumns) / (param.invoice.header.length - customColumnNo.length);
}
//#endregion
//#region TABLE HEADER BORDER
var addTableHeaderBorder = () => {
currentHeight += 2;
const lineHeight = 7;
let startWidth = 0;
for (let i = 0; i < param.invoice.header.length; i++) {
const currentTdWidth = param.invoice.header[i]?.style?.width || tdWidth;
if (i === 0) doc.rect(10, currentHeight, currentTdWidth, lineHeight);
else {
const previousTdWidth = param.invoice.header[i - 1]?.style?.width || tdWidth;
const widthToUse = currentTdWidth == previousTdWidth ? currentTdWidth : previousTdWidth;
startWidth += widthToUse;
doc.rect(startWidth + 10, currentHeight, currentTdWidth, lineHeight);
}
}
currentHeight -= 2;
};
//#endregion
//#region TABLE BODY BORDER
var addTableBodyBorder = (lineHeight) => {
let startWidth = 0;
for (let i = 0; i < param.invoice.header.length; i++) {
const currentTdWidth = param.invoice.header[i]?.style?.width || tdWidth;
if (i === 0) doc.rect(10, currentHeight, currentTdWidth, lineHeight);
else {
const previousTdWidth = param.invoice.header[i - 1]?.style?.width || tdWidth;
const widthToUse = currentTdWidth == previousTdWidth ? currentTdWidth : previousTdWidth;
startWidth += widthToUse;
doc.rect(startWidth + 10, currentHeight, currentTdWidth, lineHeight);
}
}
};
//#endregion
//#region TABLE HEADER
var addTableHeader = () => {
if (param.invoice.headerBorder) addTableHeaderBorder();
currentHeight += pdfConfig.subLineHeight;
doc.setTextColor(colorBlack);
doc.setFontSize(pdfConfig.fieldTextSize);
//border color
doc.setDrawColor(colorGray);
currentHeight += 2;
let startWidth = 0;
param.invoice.header.forEach(function (row, index) {
if (index == 0) doc.text(row.title, 11, currentHeight);
else {
const currentTdWidth = row?.style?.width || tdWidth;
const previousTdWidth = param.invoice.header[index - 1]?.style?.width || tdWidth;
const widthToUse = currentTdWidth == previousTdWidth ? currentTdWidth : previousTdWidth;
startWidth += widthToUse;
doc.text(row.title, startWidth + 11, currentHeight);
}
});
currentHeight += pdfConfig.subLineHeight - 1;
doc.setTextColor(colorGray);
};
//#endregion
addTableHeader();
//#region TABLE BODY
var tableBodyLength = param.invoice.table.length;
param.invoice.table.forEach(function (row, index) {
doc.line(10, currentHeight, docWidth - 10, currentHeight);
//get nax height for the current row
var getRowsHeight = function () {
let rowsHeight = [];
row.forEach(function (rr, index) {
const widthToUse = param.invoice.header[index]?.style?.width || tdWidth;
let item = splitTextAndGetHeight(rr.toString(), widthToUse - 1); //minus 1, to fix the padding issue between borders
rowsHeight.push(item.height);
});
return rowsHeight;
};
var maxHeight = Math.max(...getRowsHeight());
//body borders
if (param.invoice.tableBodyBorder) addTableBodyBorder(maxHeight + 1);
let startWidth = 0;
row.forEach(function (rr, index) {
const widthToUse = param.invoice.header[index]?.style?.width || tdWidth;
let item = splitTextAndGetHeight(rr.toString(), widthToUse - 1); //minus 1, to fix the padding issue between borders
if (index == 0) doc.text(item.text, 11, currentHeight + 4);
else {
const currentTdWidth = rr?.style?.width || tdWidth;
const previousTdWidth = param.invoice.header[index - 1]?.style?.width || tdWidth;
const widthToUse = currentTdWidth == previousTdWidth ? currentTdWidth : previousTdWidth;
startWidth += widthToUse;
doc.text(item.text, 11 + startWidth, currentHeight + 4);
}
});
currentHeight += maxHeight - 4;
//td border height
currentHeight += 5;
//pre-increase currentHeight to check the height based on next row
if (index + 1 < tableBodyLength) currentHeight += maxHeight;
if (
param.orientationLandscape &&
(currentHeight > 185 ||
(currentHeight > 178 && doc.getNumberOfPages() > 1))
) {
doc.addPage();
currentHeight = 10;
if (index + 1 < tableBodyLength) addTableHeader();
}
if (
!param.orientationLandscape &&
(currentHeight > 265 ||
(currentHeight > 255 && doc.getNumberOfPages() > 1))
) {
doc.addPage();
currentHeight = 10;
if (index + 1 < tableBodyLength) addTableHeader();
//else
//currentHeight += pdfConfig.subLineHeight + 2 + pdfConfig.subLineHeight - 1; //same as in addtableHeader
}
//reset the height that was increased to check the next row
if (index + 1 < tableBodyLength && currentHeight > 30)
// check if new page
currentHeight -= maxHeight;
});
//doc.line(10, currentHeight, docWidth - 10, currentHeight); //if we want to show the last table line
//#endregion
var invDescSize = splitTextAndGetHeight(
param.invoice.invDesc,
docWidth / 2
).height;
//#region PAGE BREAKER
var checkAndAddPageLandscape = function () {
if (!param.orientationLandscape && currentHeight + invDescSize > 270) {
doc.addPage();
currentHeight = 10;
}
}
var checkAndAddPageNotLandscape = function (heightLimit = 173) {
if (param.orientationLandscape && currentHeight + invDescSize > heightLimit) {
doc.addPage();
currentHeight = 10;
}
}
var checkAndAddPage = function () {
checkAndAddPageNotLandscape();
checkAndAddPageLandscape();
}
//#endregion
//#region Stamp
var addStamp = () => {
let _addStampBase = () => {
var stampImage = '';
if (typeof window === "undefined") {
stampImage = param.stamp.src;
} else {
stampImage = new Image();
stampImage.src = param.stamp.src;
}
if (param.stamp.type)
doc.addImage(
stampImage,
param.stamp.type,
10 + param.stamp.margin.left,
docHeight - 22 + param.stamp.margin.top,
param.stamp.width,
param.stamp.height
);
else
doc.addImage(
stampImage,
10 + param.stamp.margin.left,
docHeight - 22 + param.stamp.margin.top,
param.stamp.width,
param.stamp.height
);
};
if (param.stamp.src) {
if (param.stamp.inAllPages)
_addStampBase();
else if (!param.stamp.inAllPages && doc.getCurrentPageInfo().pageNumber == doc.getNumberOfPages())
_addStampBase();
}
}
//#endregion
checkAndAddPage();
doc.setTextColor(colorBlack);
doc.setFontSize(pdfConfig.labelTextSize);
currentHeight += pdfConfig.lineHeight;
//#region additionalRows
if (param.invoice.additionalRows?.length > 0) {
//#region Line breaker before invoce total
doc.line(docWidth / 2, currentHeight, docWidth - 10, currentHeight);
currentHeight += pdfConfig.lineHeight;
//#endregion
for (let i = 0; i < param.invoice.additionalRows.length; i++) {
currentHeight += pdfConfig.lineHeight;
doc.setFontSize(param.invoice.additionalRows[i].style.fontSize);
doc.text(docWidth / 1.5, currentHeight, param.invoice.additionalRows[i].col1, "right");
doc.text(docWidth - 25, currentHeight, param.invoice.additionalRows[i].col2, "right");
doc.text(docWidth - 10, currentHeight, param.invoice.additionalRows[i].col3, "right");
checkAndAddPage();
}
}
//#endregion
checkAndAddPage();
doc.setTextColor(colorBlack);
currentHeight += pdfConfig.subLineHeight;
currentHeight += pdfConfig.subLineHeight;
// currentHeight += pdfConfig.subLineHeight;
doc.setFontSize(pdfConfig.labelTextSize);
//#region Add num of pages at the bottom
if (doc.getNumberOfPages() > 1) {
for (let i = 1; i <= doc.getNumberOfPages(); i++) {
doc.setFontSize(pdfConfig.fieldTextSize - 2);
doc.setTextColor(colorGray);
if (param.pageEnable) {
doc.text(docWidth / 2, docHeight - 10, param.footer.text, "center");
doc.setPage(i);
doc.text(
param.pageLabel + " " + i + " / " + doc.getNumberOfPages(),
docWidth - 20,
doc.internal.pageSize.height - 6
);
}
checkAndAddPageNotLandscape(183);
checkAndAddPageLandscape();
addStamp();
}
}
//#endregion
//#region INVOICE DESCRIPTION
var addInvoiceDesc = () => {
doc.setFontSize(pdfConfig.labelTextSize);
doc.setTextColor(colorBlack);
doc.text(param.invoice.invDescLabel, 10, currentHeight);
currentHeight += pdfConfig.subLineHeight;
doc.setTextColor(colorGray);
doc.setFontSize(pdfConfig.fieldTextSize - 1);
var lines = doc.splitTextToSize(param.invoice.invDesc, docWidth / 2);
//text in left half
doc.text(lines, 10, currentHeight);
currentHeight +=
doc.getTextDimensions(lines).h > 5
? doc.getTextDimensions(lines).h + 6
: pdfConfig.lineHeight;
return currentHeight;
};
addInvoiceDesc();
//#endregion
addStamp();
//#region Add num of first page at the bottom
if (doc.getNumberOfPages() === 1 && param.pageEnable) {
doc.setFontSize(pdfConfig.fieldTextSize - 2);
doc.setTextColor(colorGray);
doc.text(docWidth / 2, docHeight - 10, param.footer.text, "center");
doc.text(
param.pageLabel + "1 / 1",
docWidth - 20,
doc.internal.pageSize.height - 6
);
}
//#endregion
let returnObj = {
pagesNumber: doc.getNumberOfPages(),
};
if (param.returnJsPDFDocObject) {
returnObj = {
...returnObj,
jsPDFDocObject: doc,
};
}
if (param.outputType === "save") doc.save(param.fileName);
else if (param.outputType === "blob") {
const blobOutput = doc.output("blob");
returnObj = {
...returnObj,
blob: blobOutput,
};
} else if (param.outputType === "datauristring") {
returnObj = {
...returnObj,
dataUriString: doc.output("datauristring", {
filename: param.fileName,
}),
};
} else if (param.outputType === "arraybuffer") {
returnObj = {
...returnObj,
arrayBuffer: doc.output("arraybuffer"),
};
} else
doc.output(param.outputType, {
filename: param.fileName,
});
return returnObj;
}
export default jsPDFInvoiceTemplate;