There are 2 popular javascript tools to generate PDF on the client side. One is jsPDF, the other is pdfmake. The pdfmake is a layout engine on top of pdfkit, which supports a nice declarative json document description format. Pdfmake has better documentation, and jsPDF only have minimal document. jsPDF doesn't trim each line like Pdfmake does. Pdfmake supports new pages, which jsPDF doesn't. However pdfmake is still under beta version currently. The jsPDF has very simple documentation so be patient to apply it on complex project.
jsPDF prints using:
var doc = new jsPDF();
doc.setFontSize(40);
doc.text(35, 25, "Paranyan loves jsPDF");
doc.addImage(imgData, 'JPEG', 15, 40, 180, 160);
Pdfmake on the other hand:
var dd = {
content: [
'First paragraph',
'Another paragraph, this time a little bit longer to make sure, this line will be divided into at least two lines'
]
}
Note datatable javascript plugin applied pdfmake to export table data to pdf. So If your application used datatable pdf export feature, you don't need to add another javascript library.
The Flyer Builder Example
The flyer builder is a user interface applying jsPDF to allow the user to insert some basic data (a title, an abstract and a price). Optionally, an image can be added, otherwise a grey-boxed Special Offer title is displayed. This example is from this link and could be a very good start for learning how to generate PDF using jsPDF.
The flyer builder is a user interface applying jsPDF to allow the user to insert some basic data (a title, an abstract and a price). Optionally, an image can be added, otherwise a grey-boxed Special Offer title is displayed. This example is from this link and could be a very good start for learning how to generate PDF using jsPDF.
There is one demo for this interface. Note the demo used iFrame to hold the interface and it only worked for Firefox.
var pdf = new jsPDF('p', 'mm', 'a4');
// pdf.addImage(base64_source, image format, X, Y, width, height)
pdf.addImage(agency_logo.src, 'PNG', logo_sizes.centered_x, _y, logo_sizes.w, logo_sizes.h);
Also those images must be Base64 encoded: the agency logo is embedded in the script in this format, while the image loaded by the user is encoded using the readAsDataURL method in the $('#flyer-image').change listener.
The title is added using the textAlign function. Note that this function is not part of the jsPDF core, but, as suggested by the author in his examples, the library can be easily expanded using its API. You can find the textAlign() function at the top of flyer builder script:
This function calculates the X coordinate of the the text string to make it centered, and then calls the native text() method:
The Special offer grey box and the price circle use two similar methods: roundedRect() and circle(). Both of them require top-left coordinates, size values (the width and height in the first case and the radius in the second one):
The style parameters refers to the fill and stroke properties of the object. Valid styles are: S [default] for stroke, F for fill, and DF (or FD) for fill and stroke.
The title is added using the textAlign function. Note that this function is not part of the jsPDF core, but, as suggested by the author in his examples, the library can be easily expanded using its API. You can find the textAlign() function at the top of flyer builder script:
pdf.textAlign(flyer_title, {align: "center"}, 0, _y);
pdf.text(text string, X, Y);
To change text properties, you can use following methods:
To set a 20pt Times Bold red string, for example, you need to type this:setFontSize()
, setFont()
, setTextColor()
, setFontType()
.pdf.setFontSize(20);
pdf.setFont("times");
pdf.setFontType("bold");
pdf.setTextColor(255, 0, 0);
pdf.text(10,10, 'This is a 20pt Times Bold red string');
pdf.roundedRect( X, Y, width, height, radius along X axis, radius along Y axis, style);
pdf.circle( X, Y, radius, style);
Fill and stroke properties must be set in advance using setFillColor and setDrawColor, which require a RGB value and setLineWidth that requires the line width value in the unit declared at inception of PDF document.
The full source code:
(function (API) {
API.textAlign = function (txt, options, x, y) {
options = options || {};
// Use the options align property to specify desired text alignment
// Param x will be ignored if desired text alignment is 'center'.
// Usage of options can easily extend the function to apply different text
// styles and sizes
// Get current font size
var fontSize = this.internal.getFontSize();
// Get page width
var pageWidth = this.internal.pageSize.width;
// Get the actual text's width
// You multiply the unit width of your string by your font size and divide
// by the internal scale factor. The division is necessary
// for the case where you use units other than 'pt' in the constructor
// of jsPDF.
var txtWidth = this.getStringUnitWidth(txt) * fontSize / this.internal.scaleFactor;
if (options.align === "center") {
// Calculate text's x coordinate
x = (pageWidth - txtWidth) / 2;
}
else if (options.align === "centerAtX") { // center on X value
x = x - (txtWidth / 2);
}
else if (options.align === "right") {
x = x - txtWidth;
}
// Draw text at x,y
this.text(txt, x, y);
};
/*
API.textWidth = function(txt) {
var fontSize = this.internal.getFontSize();
return this.getStringUnitWidth(txt)*fontSize / this.internal.scaleFactor;
};
*/
API.getLineHeight = function (txt) {
return this.internal.getLineHeight();
};
})(jsPDF.API);
(function () {
"use strict";
/*
Refs:
http://mrrio.github.io/jsPDF/
https://github.com/MrRio/jsPDF
https://mrrio.github.io/jsPDF/doc/symbols/jsPDF.html
*/
// some variables
var agency_logo = {
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAEdCAMAAADD1fMAAAAAGXRFWHRTb2Z0d2' +
'FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhQTFRFAHC6RpjOtdbr1uj0f7jd7/b7////IYLCvV4sVgAAIv' +
'1JREFUeNrsnYvao6oOQE0I5v3feFqvqIQEqv61k5z9nT17tIrAIhcCdL1LiwTGR5UXAb3RWqTzKmgS6rqOwkMKG4m7Drz'
w: 800,
h: 285
},
agency_name = 'Travel & Holidays',
agency_site_url = 'www.travelandholidays.com',
footer = agency_name + ' - ' + agency_site_url,
page_size = 'a4',
page_width = 210, // mm
page_margin = 10, // mm
content_width = page_width - (page_margin * 2), // available width for the content
_y, _x, // drawing coord
color_array, _string, lineHeight, y_correction, // some variables
vspace = 10 // standard vertical space between elements
;
// some variables
var can_display_preview = true, // if true a preview of the PDF can be displayed in the iframe,
// this value is set to false if the browser can't display the preview
preview_container = $('#pdf_preview'),
update_preview_button = $('#flyer_preview_btn'),
download_button = $('#flyer_download_btn');
// preview can be displayed?
if (navigator.msSaveBlob) { // older IE
update_preview_button.prop('disabled', true);
can_display_preview = false;
preview_container.replaceWith(
'<div class="no_iframe">' +
'<div>' +
"The preview can't be displayed" +
'</div>' +
'</div>'
);
}
// utilities
var hex2rgb = function (hex_string) {
if (/^#/.test(hex_string)) {
hex_string = hex_string.substr(1);
}
if (hex_string.length === 3) {
hex_string = hex_string.replace(/\w/, function (match) {
return String(match) + String(match);
});
}
return {
red: parseInt(hex_string.substr(0, 2), 16),
green: parseInt(hex_string.substr(2, 2), 16),
blue: parseInt(hex_string.substr(4, 2), 16)
};
}
px2mm = function (pixel) {
// px to inches
var inches = pixel / 72;
return inches * 25.4;
},
mm2px = function (millimeters) {
// mm to inches
var inches = millimeters / 25.4;
return inches * 72;
},
// function to calculate and check img sizes
imgSizes = function (img_w, img_h, img_mm_w) {
/* img_w and img_h represent the original image size, in pixel
img_mm_w is the desidered rendered image size, in millimeters */
if (img_mm_w > content_width) { // this should be never used...
img_mm_w = content_width;
}
if (mm2px(img_mm_w) > img_w) {
throw 'The `img_mm_w` parameter is too big';
}
var img_mm_h = Math.round((px2mm(img_h) * img_mm_w) / px2mm(img_w));
return {
w: img_mm_w,
h: img_mm_h,
centered_x: (page_width - img_mm_w) / 2
};
};
try {
// image reading
// More info at https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
// for more examples about file api
// take a look at https://scotch.io/tutorials/use-the-html5-file-api-to-work-with-files-locally-in-the-browser
var flyer_img = $('#flyer-image'),
img_data = null;
$('#flyer-image').change(function () {
// temporary disabling buttons while parsing image
update_preview_button.prop('disabled', true);
download_button.prop('disabled', true);
/*
getting the file
flyer_img[0] : transforms the jQuery reference to a DOM object
files[0] : refers to the file the the user has chosen
use `console.log(user_file);` to show some info about the file
*/
var user_file = flyer_img[0].files[0];
img_data = {
type: user_file.type === 'image/jpeg' ? 'JPEG' : 'PNG' // maybe you should add some controls to prevent loading of other file types
};
var reader = new FileReader();
reader.onload = function (event) {
img_data.src = event.target.result;
// we need this to get img dimensions
var user_img = new Image();
user_img.onload = function () {
img_data.w = user_img.width;
img_data.h = user_img.height;
// restoring buttons
download_button.prop('disabled', false);
if (can_display_preview) {
update_preview_button.prop('disabled', false);
}
};
user_img.src = img_data.src;
};
//when the file is read it triggers the onload event above.
reader.readAsDataURL(user_file);
});
//!pdf builder
var createPDF = function (update_preview) {
/*
update_preview:
==> true: shows pdf online
==> false: downloads the pdf
*/
_y = page_margin; // vertical starting point
// form data
var flyer_title = $('#flyer-title').val(),
flyer_title_size = $('#flyer-title-size').val(),
flyer_title_color = $('#flyer-title-color').val(),
flyer_description = $('#flyer-description').val(),
flyer_price = $('#flyer-price').val(),
flyer_price_currency = $('#flyer-price-currency').val(),
flyer_price_color = $('#flyer-price-color').val();
var pdf = new jsPDF('p', 'mm', page_size),
text_baseline,
// some colors:
light_grey = 237,
grey = 128,
black = 0,
white = 255;
// Optional - set properties of the document
pdf.setProperties({
title: flyer_title,
subject: footer,
author: 'me',
creator: 'Flyer Builder & jsPDF'
});
// !logo
var logo_sizes = imgSizes(agency_logo.w, agency_logo.h, 60);
pdf.addImage(agency_logo.src, 'PNG', logo_sizes.centered_x, _y, logo_sizes.w, logo_sizes.h);
// fonts initializing
pdf.setFont("helvetica");
pdf.setFontType("bold");
// !main title
color_array = hex2rgb(flyer_title_color);
pdf.setTextColor(color_array.red, color_array.green, color_array.blue);
pdf.setFontSize(flyer_title_size);
lineHeight = px2mm(pdf.getLineHeight(flyer_title));
_y += (logo_sizes.h + vspace + lineHeight);
pdf.textAlign(flyer_title, {
align: "center"
}, 0, _y);
_y += vspace;
// !user image
if (img_data) {
var img_sizes = imgSizes(img_data.w, img_data.h, content_width);
pdf.addImage(img_data.src, img_data.type, img_sizes.centered_x, _y, img_sizes.w, img_sizes.h);
_y += img_sizes.h;
}
else {
// if we haven't an image, a grey box with a text is added
var box_height = 80;
pdf.setFillColor(light_grey);
pdf.roundedRect(page_margin, _y, content_width, box_height, 5, 5, 'F');
pdf.setFontSize(60);
pdf.setTextColor(white);
_string = 'SPECIAL OFFER';
lineHeight = px2mm(pdf.getLineHeight(_string));
// y_correction: value to be added to y coord of the grey box to have text vertically centered
// it is empirically calculated adding 1/3 of text line height to half box height
y_correction = box_height / 2 + lineHeight / 3;
pdf.textAlign(_string, {
align: "center"
}, 0, _y + y_correction);
_y += box_height;
}
// !price
// first: creating a circle that overlaps the bottom side of the image
var circle_radius = 30;
color_array = hex2rgb(flyer_price_color);
pdf.setFillColor(color_array.red, color_array.green, color_array.blue);
// _x and _y refer to center of the circle
_x = content_width - circle_radius; // circle ends at `page_margin` millimeters from the image right side
pdf.circle(_x, _y, circle_radius, 'F'); // circle overlaps image for 1/2 of its height
pdf.setFontSize(60);
pdf.setFont("times");
pdf.setFontType("bold");
_string = flyer_price_currency + parseInt(flyer_price); // decimals are removed
lineHeight = px2mm(pdf.getLineHeight(_string));
y_correction = lineHeight / 3;
pdf.setTextColor(white);
pdf.textAlign(_string, {
align: "centerAtX"
}, _x, _y + y_correction);
// !description
if (flyer_description) {
pdf.setFontSize(20);
pdf.setFont("helvetica");
pdf.setFontType("italic");
pdf.setTextColor(grey);
var lineWidth = content_width - (circle_radius * 2) - (page_margin * 2);
_y += page_margin;
var line_height = 12; // mm
var description_lines = pdf.splitTextToSize(flyer_description, lineWidth);
//pdf.text(page_margin, _y, description_lines); // doesn't allows to change line spacing
for (var i = 0; i < description_lines.length; i++) {
pdf.text(page_margin, _y, description_lines[i]);
_y += line_height;
}
}
// !footer
_y = 287;
pdf.setFontSize(9);
pdf.setFontType("normal");
pdf.setTextColor(black);
pdf.textAlign(footer, {
align: "center"
}, 0, _y);
// ****************************
// output
if (update_preview) {
preview_container.attr('src', pdf.output('datauristring'));
}
else {
pdf.save('flyer ' + flyer_title + '.pdf');
}
}; // end createPDF
// !buttons
update_preview_button.click(function () {
createPDF(true);
});
$('#flyer_download_btn').click(function () {
createPDF(false);
});
}
catch (e) {
console.log(e);
}
})();
Source:
https://news.ycombinator.com/item?id=9630431
http://andreasgal.com/2011/06/15/pdf-js/
https://developer.tizen.org/community/tip-tech/displaying-pdf-files-pdf.js-library
https://github.com/simonbengtsson/jsPDF-AutoTable
https://micropyramid.com/blog/export-html-web-page-to-pdf-using-jspdf/
http://scn.sap.com/community/developer-center/front-end/blog/2015/06/18/generate-pdf-at-client-side-with-jspdf-plugin
http://stackoverflow.com/questions/16858954/how-to-properly-use-jspdf-library
http://andreasgal.com/2011/06/15/pdf-js/
https://developer.tizen.org/community/tip-tech/displaying-pdf-files-pdf.js-library
https://github.com/simonbengtsson/jsPDF-AutoTable
https://micropyramid.com/blog/export-html-web-page-to-pdf-using-jspdf/
http://scn.sap.com/community/developer-center/front-end/blog/2015/06/18/generate-pdf-at-client-side-with-jspdf-plugin
http://stackoverflow.com/questions/16858954/how-to-properly-use-jspdf-library
No comments:
Post a Comment