I'm trying to generate a PDF from a React component. In my scenario it is a MaterialUI table but it could be anything.
The problem I'm facing is not with generating the PDF but the produced PDF itself which is too heavy and unusable.
I have a function like this:
createPdf = async (html: HTMLElement, pdfName: string = "report.pdf") => {
const doc = new jsPDF("p", "pt", "a4", true);
await doc.html(html, {
margin: 10,
html2canvas: {
scale: 0.65
},
});
doc.save(pdfName);
return doc.output("blob");
};
I insert an HTMLElement which is the DOM content for my React component and it generates the PDF correctly. However this pdf is very big and extremely slow to load. Mind that I have an i9 with 32GB of RAM and it easily takes 15secs to render one page of the pdf...
Initially it was generating a 150MB pdf but then I set compressed to true and it's now around 600KB. That changes the size but it didn't improve the performance somehow. I've tried multiple computers and browsers and I've tinkered with the options for html2canvas and nothing seems to fix this.
Does anyone have any insight on this?
Thanks in advance.
Related
I'm trying to convert HTML & CSS into a pdf page using Django-weasyprint but I'm kind of stuck with their tutorial because I want the PDF to render the current page when the user clicks on the download button and the pdf downloads automatically with read-only privileges. This whole process kind of feels painful to do.
Currently, weasyprint just converts a URL in Django to pdf, but I don't know how to set the button to look at the weasyprint view.
Maybe I am looking past it and over complicating it, any assistance would be appreciated.
Weasyprints example code:
from django.conf import settings
from django.views.generic import DetailView
from django_weasyprint import WeasyTemplateResponseMixin
from django_weasyprint.views import CONTENT_TYPE_PNG
class MyModelView(DetailView):
# vanilla Django DetailView
model = MyModel
template_name = 'mymodel.html'
class MyModelPrintView(WeasyTemplateResponseMixin, MyModelView):
# output of MyModelView rendered as PDF with hardcoded CSS
pdf_stylesheets = [
settings.STATIC_ROOT + 'css/app.css',
]
# show pdf in-line (default: True, show download dialog)
pdf_attachment = False
# suggested filename (is required for attachment!)
pdf_filename = 'foo.pdf'
class MyModelImageView(WeasyTemplateResponseMixin, MyModelView):
# generate a PNG image instead
content_type = CONTENT_TYPE_PNG
# dynamically generate filename
def get_pdf_filename(self):
return 'bar-{at}.pdf'.format(
at=timezone.now().strftime('%Y%m%d-%H%M'),
)
I made a virtual env on my pc and it's setup exactly like the example. Currently using Boostrap 4.
*Edit if there is a better way of doing it, you are more than welcome to share it :)
Also I want to target just the body tags so that it converts only that section to pdf and not the ENTIRE page.
The solution I used before this is: https://codepen.io/AshikNesin/pen/KzgeYX but this doesn't work very well.
*EDIT 2.0
I've moved on to js and I'm stuck with this script where it doesn't want to create the pdf form on click function also is there a way to set the js function to ONLY download the selected Id in the div and not on certain scale? (afraid that it's going to use the resolution instead of the actual content that needs to be rendered)
https://jsfiddle.net/u4ko9pzs/18/
Any suggestions would be greatly appreciated.
I was tried django-wkhtmltopdf and weasyprint. For shure weasyprint allow more in subject of css but still it isn't one to one to browser version. I wasn't happy with results.
It is ok for tables, and all word/excel like data. But in my case i have to made px to px 'certificate' like pdf with svg shaped background Then some lack of features starts to be not convenient problem.
So if you need use the same css with minimal modification to get effect like in browswer then puppeter solving all compabitlity problems. But this force to use Node to run puppeteer. (in my case i already use it to compile assets)
Cli for puppetere:
npm i- puppeteer-pdf
then install wrapper
pip install django-puppeteer-pdf
add node puppeteer cli start command in settings:
PUPPETEER_PDF_CMD = 'npx puppeteer-pdf'
and create view
from puppeteer_pdf import render_pdf_from_template
from django.http import HttpResponse,
import os
# Rendering code
context = {
'data': 'test data'
}
output_temp_file = os.path.join(settings.BASE_DIR, 'static', 'temp.pdf')
pdf = render_pdf_from_template(
input_template='path to template in templates dir'
header_template='',
footer_template='',
context=context,
cmd_options={
'format': 'A4',
'scale': '1',
'marginTop': '0',
'marginLeft': '0',
'marginRight': '0',
'marginBottom': '0',
'printBackground': True,
'preferCSSPageSize': True,
'output': output_temp_file,
'pageRanges': 1
}
)
#you can remove temp file now or collecte them and run some crone task.(performance)
if os.path.exists(output_temp_file):
os.remove(output_temp_file)
else:
# raise error or something
filename = f'filename={'your donwload file name'}.pdf'
response = HttpResponse(pdf, content_type='application/pdf;')
response['Content-Disposition'] = filename
To keep original color you have to add
html{
-webkit-print-color-adjust: exact;
}
controlling page size and margin when you choice 'preferCSSPageSize'.
#page {
size: A4;
margin: 0 0 0 0;
}
Use whkhtmltopdf it's very easy to use in Django. It's provide pure html generated PDF conversion
pip install django-wkhtmltopdf
https://django-wkhtmltopdf.readthedocs.io/en/latest/
_____ Project Overview _____
I am building an Electron application, using React and Typescript, in which I read a plain HTML string to memory to then adjust some elements in it and finally convert it to a pdf.
_____ Problem Description _____
Because the html is never really rendered (it is just in memory), I am uncertain on how to convert the html (with style) to a pdf without rendering the html first.
_____ What I've tried _____
I have tried using a combination of html2canvas and jspdf.
jspdf works fine to convert the HTML, without styling, to pdf, but
I need the styling. So I tried getting a "screenshot" of my page with
html2canvas first but that fails because the page is never
rendered.
I tried using electron-pdf, but I could not get this to work in my application. It works fine as a command line tool though, so an option would be to invoke the command line tool. I think that that should work, but it feels very hacky (so I'd prefer not going that route).
_____ Code _____
const createPDF = async (invoiceData: IInvoiceFields) => {
const invoiceTemplate = fs
.readFileSync(
resolve(
remote.app.getAppPath(),
"src",
"invoice_templates",
"invoice_EN.html"
)
)
.toString();
const invoiceHTMLString = fillInInvoice(invoiceTemplate, invoiceData);
const domParser = new DOMParser();
const invoiceHTMLDocument = domParser.parseFromString(
invoiceHTMLString,
"text/html"
);
// TODO: Create actual pdf...
};
According to Generating PDF with Electron.js, it seems possible to use the webContents method printToPDF in a new BrowserWindow without actually showing the window:
window_to_PDF = new BrowserWindow({show : false});//to just open the browser in background
window_to_PDF.loadURL('anylink.html'); //give the file link you want to display
function pdfSettings() {
var paperSizeArray = ["A4", "A5"];
var option = {
landscape: false,
marginsType: 0,
printBackground: false,
printSelectionOnly: false,
pageSize: paperSizeArray[settingCache.getPrintPaperSize()-1],
};
return option;
}
window_to_PDF.webContents.on("did-finish-load", function() {
window_to_PDF.webContents.printToPDF(pdfSettings(), function(err, data) {
if (err) {
//do whatever you want
return;
}
try{
fs.writeFileSync('./generated_pdf.pdf', data);
}catch(err){
//unable to save pdf..
}
});
});
So, in your case, you may have to write your generated HTML to a temporary file anylink.html then use loadURL or preferably loadFile to load it in the "off-screen" window.
The title may be confusing, so let me expand a little more:
My goal is to have a front end framework/(library), like React, Vue, or Angular, that has a normal user interface stuff, such as the user inputting data or an uploading an image to a server.
I then want the web app to basically make an HTML email. So, I'm thinking the best way is to create a text file of HTML, but it will be of the format .eml instead of .txt so it's easy to open in mail clients and send the email.
My question:
- How can I create a string of dynamic HTML that is then saved as a file for the user to download. dynamic meaning sometimes it may be just 1 or 2 items, sometimes it may be 15, but the point is that the variable will change and a loop will be run for as many objects as there are so that the appropriate amount of HTML will be created.
I'm asking because we all know how to display a view in React/others, but how can we get a programmatic pseudo-view in the logic code. That is, how do we get a string representation of the views output of the resulting html, if that makes sense. And then create an .eml file holding that html so the user can download.
Is this even possible in the operations of today's popular frameworks?
====
EDIT
Just an idea I had from research, for generating the file it seems a Blob might be best.
var file = new Blob([html_string], {type: 'text/plain'})
Some, for React, some code would be like the following (thanks to Chris's answer from this SO question.)
class MyApp extends React.Component {
_downloadTxtFile = () => {
var element = document.createElement("a");
var file = new Blob([document.getElementById('myInput').value], {type: 'text/plain'});
element.href = URL.createObjectURL(file);
element.download = "myFile.txt";
element.click();
}
render() {
return (
<div>
<input id="myInput" />
<button onClick={this._downloadTxtFile}>Download txt</button>
</div>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
Which leaves the question of how to create the string of HTML. Maybe ES6 template literals with embedded expressions? But, that wouldn't be JSX exactly, so I'm not sure how to throw a for loop in there. I'll continue researching or if someone knows how to throw all this together.
I'm trying to write a little electron app which nests multiple pdf files or pages to one bigger page (for saving paper when plotting a lot of CAD drawings).
Basically the unix command pdfnup from pdfjam is what I want - but due to different OS (Mac and Windows) I need a cross plattform solution.
Has anyone done something smiliar with node/javascript so far? After a lot of reasearch I haven't found a reasonable solution or library.
Thanks to Zxifer's answer I stumbled across the HummusRecipe library which provides an high-level API to the HummusJS project. The overlay method is what I was looking for.
I ended up with this code:
// Maximum plottable height (915 mm) - Conversion to point
const maxPageHeight = (915 / 0.3528);
// Read files and determine width/length of plot
const fileOne = new HummusRecipe('lp.pdf', 'output1.pdf');
const fileTwo = new HummusRecipe('ls.pdf', 'output2.pdf');
let width = Math.max(fileOne.pageInfo(1).width, fileTwo.pageInfo(1).width) + 30;
// Create new pdf file
const pdfDoc = new HummusRecipe('new', 'output.pdf', {
version: 1.6,
author: 'IBB Wörn Ingenieure GmbH',
title: 'Print PDF',
subject: 'Imposition of various PDF files for optimized printing.'
});
// Get height of first pdf to generate offsett
let heightOne = fileOne.pageInfo(1).height;
// Overlay PDFs to new pdf with offset and ~ 5mm margin
pdfDoc
.createPage(width, maxPageHeight)
.overlay('lp.pdf', 15, 15)
.overlay('ls.pdf', 15, heightOne + 15)
.endPage()
.endPDF();
I am going to implement a dynamic legend using JavaScript in Adobe Acrobat.
The document contains a lot of layers. Every layer has an own legend. The origin idea is to implement the legend so, that it contains the images in a dialog box for the visible layers.
I can only hide/show the layers by setting state to false or true (this.getOCGs()[i].state = false;) on document-level.
Question 1: Can I extract data from layer somehow for legend establishing? I think no, as we only have these function on layers: getIntent(), setIntent() and setAction(). Right? Therefore I decided to arrange it so, that all needed icons for every layer are saved in a folder with corresponding names. JavaScript should import the icons and I build the a dialog window with icons of visible Layers and place a text(description for this icon).
I tried all possibilities of image import described here: http://pubhelper.blogspot.com.au/2012/07/astuces-toolbar-icons-et-javascript.html. I got only one way (Convert the icons as hexadecimal strings). This way isn't good, as it is too much work to create with an other tool a hexadecimal string from a images and place it into a javascript code.
Unfortunately, I cannot import image using other methods:(. Since the security settings in Adobe are changed after version 7 or so, it is not possible to use functions like app.newDoc, app.openDoc, even app.getPath On document-level. I decided to implement the import on a folder level using trusted functions like this:
Variant 1:
var importImg = app.trustedFunction(function() {
app.beginPriv();
var myDoc = app.newDoc({
nWidth: 20,
nHeight: 20
});
var img = myDoc.importIcon("icon", "/icon.png", 0);
app.endPriv();
return img; });
var oIcon = importImg();
The settings in Preferences->JavaScript-> JavaScript Security are disabled (Enable menu item JS execution privileges, enable global object security policy)
NotAllowedError: Security settings prevent access to this property or method.
App.newDoc:109:Folder-Level:User:acrobat.js
Variant 2:
var importImg = app.trustedFunction(function() {
var appPath = var phPath = app.getPath({
cCategory: "user",
cFolder: "javascript"
});
try {
app.beginPriv();
var doc = app.openDoc({
cPath: phPath + "/icon.png",
bHidden: true
});
app.endPriv();
} catch (e) {
console.println("Could not open icon file: " + e);
return;
}
var oIcon = util.iconStreamFromIcon(doc.getIcon("icon"));
return oIcon;});
var oIcon = importImg();
Could not open icon file: NotAllowedError: Security settings prevent access to this property or method.
At least it allows the execution of all these functions like app.newDoc, but in the second variant it says, wrong range of content or so. Maybe is here the pdf from an image created false? I just took the image and printed it into a pdf.
I tried all these possibilities with .jpg, .png, .pdf. with different sizes(big images and 20x20), It doesn't work.
Could somebody help me, as I spent a lot of time with trying different possibilities. It would be actually better to implement the main goal described above on document level, is it possible?
Thank you and kind regards,
Alex
Do you have the Console fully activated in Acrobat? If not, do so and look for error messages you get.
The first variant does not work, because myDoc is not defined (unless you have done that before you call the importImg function).
If you want to import the image into the newly created file, you will have to make a reference to the Document Object you create with newDoc(). Actually, that would make the link to myDoc, as in
var myDoc = app.newDoc(1,1)
(are you sure you want to create a document measuring 1x1 pt?)
The next issue with the first variant is a bug in Acrobat, which discards "floating" Icon Objects when saving the document; you'd have to attach the Icon Object to a field to keep it; this field can be hidden, or even on a hidden Template page in the document.