Skip to content

Instantly share code, notes, and snippets.

@guz-anton
Created June 24, 2017 19:51
Show Gist options
  • Save guz-anton/051af301ca303c5cdac0533c5f8a0e34 to your computer and use it in GitHub Desktop.
Save guz-anton/051af301ca303c5cdac0533c5f8a0e34 to your computer and use it in GitHub Desktop.
The plugin to jsPDF to print in pdf DOM element.
/**
* AddMarkupByCanvas v1.0.0
*
* The plugin print to jsPDF library that uses html2canvas to print DOM node to pdf.
*/
// import html2canvas from 'html2canvas'; // from v0.5.0
declare const html2canvas: any;
const fitPageSize = (pdfObj, options) => {
const I = pdfObj.internal;
const K = I.scaleFactor;
const W = I.pageSize.width;
const H = I.pageSize.height;
options.w = options.dim.w || Math.min(W, pdfObj.width / K) - 2 * options.x;
options.h = options.dim.h || Math.min(H, pdfObj.height / K) - 2 * options.y;
return options;
};
const toImageTag = (canvas): Promise<Node> => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
resolve(img);
};
img.src = canvas.toDataURL('image/png');
});
};
const cutToPages = (screenshot, pdf, options) => {
let currentShift = 0;
let sFactor = 1;
const onePageCanvas = document.createElement('canvas');
const opcContext = onePageCanvas.getContext('2d');
const I = pdf.internal;
const K = I.scaleFactor;
const W = I.pageSize.width;
const H = I.pageSize.height;
const pageDimensions = {
w: 0,
h: 0,
topShift: 0
};
pageDimensions.w = Math.min(options.w, W * K);
pageDimensions.h = Math.min(options.h, H * K);
if (screenshot.width > pageDimensions.w) {
sFactor = pageDimensions.w / screenshot.width;
}
pageDimensions.topShift = Math.min(screenshot.height, pageDimensions.h / sFactor);
onePageCanvas.width = pageDimensions.w;
onePageCanvas.height = pageDimensions.h;
opcContext.fillStyle = 'white';
while (currentShift < screenshot.height) {
let n = 0;
let args;
opcContext.fillRect(0, 0, onePageCanvas.width, onePageCanvas.height);
// Current cycle need to fix cut text upper baseline. We do assumption
// that text is on white background and no more then 15px size.
while (options.vary && isLineEmpty(screenshot, currentShift + pageDimensions.h - n)) {
n++;
if (n > 15) { // magic number. Let's delta for shift no more then 15 px.
n = 0;
break;
}
}
// With drawImage method
// we scale rectangle [screenshot.width] x [pageDimensions.topShift - n - currentShift]
// to rectangle [onePageCanvas.width] x [onePageCanvas.height - Math.floor(n / sFactor)]
opcContext.drawImage(screenshot,
0, currentShift, screenshot.width, pageDimensions.topShift - n,
0, 0, onePageCanvas.width, onePageCanvas.height - Math.ceil(n / sFactor));
// imageData, format, x, y, w, h, alias, compression, rotation
args = [
onePageCanvas,
options.format,
options.x,
options.y,
onePageCanvas.width / K,
onePageCanvas.height / K,
null,
'SLOW'
];
if (currentShift) {
pdf.addPage();
}
pdf.addImage.apply(pdf, args);
currentShift += pageDimensions.topShift - n;
}
return pdf;
};
const isLineEmpty = (canvas, linePosition): boolean => {
const data = canvas
.getContext('2d')
.getImageData(0, linePosition - 1, canvas.width, 1)
.data;
return 5 > data.reduce((a, b, c) => c % 4 < 3 && (255 - b) ? a++ : a, 0);
// data.reduce(..) is a summa of all non-white pixels in the line.
// 5 - is a magic number. We decided that 5px is ok to be cut.
};
/**
* Main function that do manipulation over jsPDF.
*
* @param {jsPDF} pdfInstance - instance of jsPDF function.
* @param {Node} element - one DOM Node to print.
* @param {object} options
* @returns {Promise<TResult2|TResult1>}
*/
export function addMarkupByCanvas(pdfInstance, element, options): Promise<any> {
options = {
addMarkupByCanvas: Object.assign({
x: 0,
y: 0,
format: 'JPEG',
dim: { // dimensions of area where screenshot will be fit.
w: 0,
h: 0
},
vary: true,
selectorToOmit: null,
callback: new Function(),
}, options.addMarkupByCanvas),
html2canvas: Object.assign({
javascriptEnabled: false
}, options.html2canvas)
};
options.addMarkupByCanvas = fitPageSize(pdfInstance, options.addMarkupByCanvas);
pdfInstance.addPage = pdfInstance.addPage || new Function();
pdfInstance.addImage = pdfInstance.addImage || new Function();
return new Promise(
(resolve, reject) => {
options.html2canvas.onrendered = resolve;
html2canvas(element, options.html2canvas);
// return html2canvas(element, options.html2canvas);
})
.then((screenshotOfMarkup) => {
return cutToPages(screenshotOfMarkup, pdfInstance, options.addMarkupByCanvas);
});
}
@guz-anton
Copy link
Author

The jsPDF allows to generate pdf document from markup amd images.
But it has limited options working with markup. Another library html2canvas.js can properly conver markup to image.

The plugin provide bridge between jsPDF and Angular application. It uses page as source, and returns jsPDF instance fullfilled with page splited on pages. So the output can be easily saved as pdf file or modified by app later.

The task: connect jsPDF and solve font rendering issues, and proper page size support.

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