app/core/services/pdf-download-service/pdf-document.ts
jsPDF wrapper for PDF generation.
Properties |
|
Methods |
|
constructor(margins: literal type)
|
||||||
Parameters :
|
addPerPageFunc |
Type : function
|
cursorX |
Type : number
|
cursorY |
Type : number
|
Private doc |
Type : any
|
Public margins |
Type : literal type
|
pageHeight |
Type : number
|
pageWidth |
Type : number
|
tableUniqueId |
Type : number
|
Public addBox |
addBox(left: number, top: number, width: number, height: number)
|
Adds a box.
Returns :
void
|
Public addImage |
addImage(image: any, width: number, height: number)
|
Adds an image from an element scaled to fit inside the given box and advances the cursor.
Returns :
void
|
Public addImageAt | ||||||||||||||||||
addImageAt(image: any, left: number, top: number, width: number, height: number)
|
||||||||||||||||||
Adds an image from an element scaled to fit inside the given box at an absolute position (left, top).
Parameters :
Returns :
void
|
Public addLine | |||||||||||||||
addLine(fromLeft: number, fromTop: number, toLeft: number, toTop: number)
|
|||||||||||||||
Adds a line between two points.
Parameters :
Returns :
void
|
Public addLink | ||||||||||||||||||
addLink(left: number, top: number, width: number, height: number, url: string)
|
||||||||||||||||||
Adds a link to the document.
Parameters :
Returns :
void
|
Public addPage |
addPage()
|
Adds a new page to the document.
Returns :
void
|
Public addPerPage | ||||||
addPerPage(func: (page: number) => void)
|
||||||
Adds content on every page using a function which receives the page number
Parameters :
Returns :
void
|
Public addTable |
addTable(element: any, caption: string, width: number)
|
Adds a table to the PDF from an element.
Returns :
void
|
Public addText | ||||||||||||||||||||
addText(text: string | string[], size: number, color: any, align: string)
|
||||||||||||||||||||
Adds a text and advances the cursor.
Parameters :
Returns :
void
|
Public addTextAt | ||||||||||||||||||||||||||||
addTextAt(text: string | string[], left: number, top: number, size: number, color: any, align: string)
|
||||||||||||||||||||||||||||
Adds a text at an absolute position (left, top). Returns the wrapped text.
Parameters :
Returns :
string[]
|
Public getCurrentPage |
getCurrentPage()
|
Returns the current page of the cursor.
Returns :
number
|
Public getTotalPages |
getTotalPages()
|
Returns the total amount of pages.
Returns :
number
|
Public pageBreak | ||||||
pageBreak(size: number)
|
||||||
Checks a number of pixels ahead if we need to add a page break.
Parameters :
Returns :
boolean
|
Public parseHtml |
parseHtml(html: string, tags: string[], tagIndex: number)
|
Parses a HTML string containing tags and returns a list of tag/text pairs.
Returns :
literal type[]
|
Public save | ||||||
save(filename: string)
|
||||||
Saves the document into a downloaded file.
Parameters :
Returns :
void
|
Public setCurrentPage | ||||||
setCurrentPage(page: number)
|
||||||
Sets the current page of the cursor.
Parameters :
Returns :
void
|
Public wordWrap |
wordWrap(text: string, width: number)
|
Returns a word wrapped text.
Returns :
string[]
|
import { pdfFonts } from './pdf-fonts';
import jsPDF from 'jspdf';
import canvg from 'canvg';
import 'jspdf-autotable';
import 'canvg';
/**
* jsPDF wrapper for PDF generation.
* */
export class PdfDocument {
private doc: any;
cursorX: number;
cursorY: number;
pageWidth: number;
pageHeight: number;
tableUniqueId: number;
addPerPageFunc: (number) => void;
constructor(
public margins: {
top: number,
bottom: number,
left: number
}
) {
// Init document
this.doc = new jsPDF('p', 'pt', 'a4');
this.pageWidth = this.doc.internal.pageSize.getWidth();
this.pageHeight = this.doc.internal.pageSize.getHeight();
this.cursorX = this.cursorY = 0;
this.tableUniqueId = 0;
// Normal font
this.doc.addFileToVFS('skfchevinot-webfont-normal.ttf', pdfFonts.normal);
this.doc.addFont('skfchevinot-webfont-normal.ttf', 'skfchevinot-webfont', 'normal', 'WinAnsiEncoding');
this.doc.setFont('skfchevinot-webfont');
this.doc.setFontStyle('normal');
}
/**
* Parses a HTML string containing tags and returns a list of tag/text pairs.
*/
public parseHtml(html: string, tags: string[], tagIndex: number = 0): { tag: string, text: string }[] {
// Split using the start tag
let tag = tags[tagIndex];
let startSplit = html.split('<' + tag + '>');
let out = [];
startSplit.forEach((text, index) => {
// Before start tag, store as regular text
if (index == 0 && text != '') {
out.push({ tag: '', text: text });
return;
}
// Split remaining by end tag
let endSplit = text.split('</' + tag + '>');
endSplit.forEach((text, index) => {
// Inside tag
if (index == 0 && text != '') {
out.push({ tag: tag, text: text });
// Outside tag, split recursively using the next tag in the list
} else if (tagIndex < tags.length - 1) {
out.push.apply(this.parseHtml(text, tags, tagIndex + 1));
}
});
});
return out;
}
/**
* Adds a table to the PDF from an element.
*/
public addTable(element: any, caption: string, width: number): void {
// Caption
this.addText(caption, 12);
// Table
const marginBottom = 30;
const fontSizes = {
'': 10,
'sup': 8,
'sub': 8
};
const textOffsets = {
'': 0,
'sup': -5,
'sub': 5
}
element.setAttribute('id', 'pdf-table-id-' + this.tableUniqueId++)
this.doc.autoTable({
html: '#' + element.getAttribute('id'),
startY: this.cursorY,
styles: {
font: 'skfchevinot-webfont',
textColor: "#333a3e"
},
tableWidth: width,
margin: {
left: this.cursorX,
top: this.margins.top,
bottom: this.margins.bottom
},
didParseCell: (hookData) => {
hookData.cell.styles.halign = (parseInt(hookData.column.index) == hookData.table.columns.length - 1) ? "right": "left";
hookData.cell.tags = [];
if (hookData.cell.raw)
{
// Look for and parse <sub> and <sup>, we will render them ourselves
let span = hookData.cell.raw.querySelector('span');
if (span && (span.innerHTML.includes('<sub>') || span.innerHTML.includes('<sup>'))) {
hookData.cell.tags = this.parseHtml(span.innerHTML, [ 'sub', 'sup' ]);
hookData.cell.text = '';
}
}
},
didDrawCell: (hookData) => {
// Render sub/superscript manually
if (hookData.cell.text == '' && hookData.cell.tags.length) {
// Apply offset and font size to each tag
hookData.cell.tags.forEach(tagText => {
this.addTextAt(tagText.text, hookData.cell.textPos.x, hookData.cell.textPos.y + textOffsets[tagText.tag], fontSizes[tagText.tag], hookData.cell.styles.textColor);
hookData.cell.textPos.x += this.doc.getStringUnitWidth(tagText.text) * fontSizes[tagText.tag];
});
}
}
});
this.cursorY = this.doc.autoTable.previous.finalY + marginBottom;
}
/**
* Checks a number of pixels ahead if we need to add a page break.
*/
public pageBreak(size: number): boolean {
if (this.cursorY + size > this.pageHeight - this.margins.bottom) {
// We're on the last page, add a new one
if (this.getCurrentPage() == this.getTotalPages()) {
this.addPage();
}
// Advance to next page and move cursor to top
this.setCurrentPage(this.getCurrentPage() + 1);
this.cursorY = this.margins.top;
return true;
}
return false;
}
/**
* Returns a word wrapped text.
* */
public wordWrap(text: string, width: number): string[] {
return this.doc.splitTextToSize(text, width);
}
/**
* Adds a text and advances the cursor.
* */
public addText(text: string | string[], size: number, color: any = '#282828', align: string = 'left'): void {
const spaceSize = 3;
const marginBottom = 10;
this.pageBreak(size);
let textWrapped = this.addTextAt(text, this.cursorX, this.cursorY, size, color, align);
this.cursorY += textWrapped.length * (size + spaceSize) + marginBottom;
}
/**
* Adds a text at an absolute position (left, top). Returns the wrapped text.
* */
public addTextAt(text: string | string[], left: number, top: number, size: number, color: any = '#282828', align = 'left'): string[] {
this.doc.setFontSize(size);
this.doc.setTextColor(color);
// Wrap depending on alignment
let textWrapped: string[];
if (typeof text === "string")
{
if (align === 'left') {
textWrapped = this.wordWrap(text, this.pageWidth - this.margins.left - left);
} else if (align === 'right') {
textWrapped = this.wordWrap(text, left - this.margins.left);
} else {
textWrapped = [ text ];
}
} else {
textWrapped = text;
}
this.doc.text(textWrapped, left, top, { align: align, baseline: 'top' });
return textWrapped;
}
/**
* Adds an image from an element scaled to fit inside the given box and advances the cursor.
*/
public addImage(image: any, width: number, height: number): void {
this.pageBreak(height);
this.addImageAt(image, this.cursorX, this.cursorY, width, height);
this.cursorY += height;
}
/**
* Adds a box.
*/
public addBox(left: number, top: number, width: number, height: number): void {
this.doc.rect(left, top, width, height);
}
/**
* Adds a line between two points.
*/
public addLine(fromLeft: number, fromTop: number, toLeft: number, toTop: number): void {
this.doc.line(fromLeft, fromTop, toLeft, toTop);
}
/** Adds a link to the document. */
public addLink(left: number, top: number, width: number, height: number, url: string): void {
this.doc.link(left, top, width, height, { url: url });
}
/**
* Adds an image from an element scaled to fit inside the given box at an absolute position (left, top).
*/
public addImageAt(image: any, left: number, top: number, width: number, height: number): void {
let data = image;
// Load SVG using canvg
if (image.src.endsWith('.svg')) {
let tmpCanvas = document.createElement('canvas');
canvg(tmpCanvas, image.src);
// To get rid of the gray border
let realCanvas = document.createElement('canvas');
realCanvas.width = tmpCanvas.width;
realCanvas.height = tmpCanvas.height;
var context = realCanvas.getContext('2d');
context.fillStyle = '#FFFFFF';
context.fillRect(0, 0, realCanvas.width, realCanvas.height);
context.drawImage(tmpCanvas, 0, 0);
data = realCanvas.toDataURL('image/png');
image = realCanvas;
}
// Center the image in the box by the largest dimension (width or height)
let ratio = image.width / image.height;
let imgWidth, imgHeight;
// Landscape, center vertically
if (image.width > image.height) {
imgWidth = Math.min(image.width, width);
imgHeight = imgWidth / ratio;
top += height / 2 - imgHeight / 2;
}
// Portrait, center horizontally
else {
imgHeight = Math.min(image.height, height);
imgWidth = imgHeight * ratio;
left += width / 2 - imgWidth / 2;
}
this.doc.addImage(data, 'PNG', left, top, imgWidth, imgHeight);
}
/**
* Adds content on every page using a function which receives the page number
*/
public addPerPage(func: (page: number) => void): void {
this.addPerPageFunc = func;
}
/**
* Returns the current page of the cursor.
*/
public getCurrentPage(): number {
return this.doc.internal.getCurrentPageInfo().pageNumber;
}
/** Sets the current page of the cursor. */
public setCurrentPage(page: number): void {
this.doc.setPage(page);
}
/**
* Adds a new page to the document.
* */
public addPage(): void {
this.doc.addPage();
}
/**
* Returns the total amount of pages.
*/
public getTotalPages(): number {
return this.doc.internal.getNumberOfPages();
}
/**
* Saves the document into a downloaded file.
*/
public save(filename: string): void {
this.doc.fromHTML(
document.createElement('div'),
this.margins.left,
this.margins.top,
{},
function (dispose) {
let totalPages = this.getTotalPages();
for (let i = 1; i <= totalPages; i++) {
this.doc.setPage(i);
this.addPerPageFunc(i);
}
}.bind(this),
this.margins
);
this.doc.save(filename);
}
}