-
-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to create a PDF automatically without opening a print window? #484
Comments
Hello. |
@MatthewHerbst Thank you! |
@MatthewHerbst is there actually a way to hook in the hook to get a generated HTML without opening this print window and just simply reuse the HTML generated with such library as html2canvas to create a screenshot of it and then input it to the React-pdf? |
Yes! You can pass a const handlePrint = useReactToPrint({
...,
print: async (printIframe: HTMLIframeElement) => {
// Do whatever you want here, including asynchronous work
await generatePDFFunction(printIframe);
},
}); |
@MatthewHerbst Thanks! I tried this one and I was able to extract HTML from this iFrame, however the styling went completely off. I noticed that react-to-print library is passing that additional styling that I added for printing inside const handleShare = useReactToPrint({
content: () => componentRef.current,
documentTitle: `${documentTitle}.pdf`,
copyStyles: true,
print: async (printIframe: HTMLIFrameElement) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementsByTagName('html')[0];
console.log(html);
await html2pdf().from(html).save();
}
}
}); |
Yeah, since you aren't actually printing the |
Thank you for this. I tried the same. But onAfterPrint function is not getting triggered once the pdf got saved. Any idea? |
@JoelRaglant that's a bug! We forgot to call it when a custom |
@JoelRaglant should be fixed in |
Thank you @MatthewHerbst !! It works fine now. |
Hello, I'm trying to do this same thing. Was this all you used to create pdf download to local folder on the click of a button? |
This comment was marked as resolved.
This comment was marked as resolved.
It's an NPM package. However it didn't quite give me the expected result, so I ended up achieveing that using react-to-print and another NPM package - js-html2pdf. Here's what my final code looked like: const handleDownload = useReactToPrint({
onPrintError: (error) => console.log(error),
content: () => ref.current,
removeAfterPrint: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementsByTagName("html")[0];
console.log(html);
const exporter = new Html2Pdf(html);
await exporter.getPdf(true);
}
},
}); Hope it helps. |
Html2PDF is returning it as an image inside the pdf. Are there any libraries that return it at the same quality as the preview does after you download it? |
@eseTunde Hello, I'm using yours: const handleDownload = useReactToPrint({
onPrintError: (error) => console.log(error),
content: () => componentRef.current,
removeAfterPrint: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementsByTagName("html")[0];
console.log(html);
const exporter = new Html2Pdf(html,{filename:"Nota Simple.pdf"});
exporter.getPdf(true);
}
},
}); The console log shows the html code tags, however, the pdf is generated empty. I don't know what else to do at this point. Any suggestion would be welcome. |
Sorry, I don't know of any at the moment. |
Try adding an await to |
@Fiewor Thank you for your answer. Actually that gives me the exact same result. I'm really desperate, I don't understand why it does not work. |
If you are passing a custom |
@MatthewHerbst Unable to get what I was triying, I did a workaround and conditionally renderer elements on screen to print correctly as it was only putting on the pdf file the elements shown on screen. I'm not that good of a programmer so you can put here that meme of the dog in the lab: "I have no idea what I'm doing". |
Does the library you are using to display the list/elements do virtual rendering? If so, that would explain the issue: |
Hello Can anyone help me to open the PDF on the browser instead of the print window please? I am a new one to this. |
Hello, you would need to use a PDF generation library. |
Hii |
const handlePrint = useReactToPrint({
//...,
print: (iframe: HTMLIframeElement) => {
// the `iframe` contains the content you want printed. Now pass it to an HTML->PDF generator
}
}) |
I also ran into this, the only thing was that it would only download the component I wanted as a pdf once, if that, and the rest of the time it would download blank pdfs. If you look at the code below, if you log variable const handleDownload = useReactToPrint({
onPrintError: (error) => console.log(error),
content: () => componentRef.current,
removeAfterPrint: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementsByTagName("html")[0];
console.log(html);
const exporter = new Html2Pdf(html,{filename:"Nota Simple.pdf"});
exporter.getPdf(true);
}
},
}); Instead of returning the Making this Become this Now it downloads the component I want 100% of the time. const handleDownload = useReactToPrint({
onPrintError: (error) => console.log(error),
content: () => componentRef.current,
removeAfterPrint: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementById("element-to-download-as-pdf");
console.log(html);
const exporter = new Html2Pdf(html,{filename:"Nota Simple.pdf"});
exporter.getPdf(true);
}
},
}); |
@edgar-carrillo nice, it worked for a while and then it work no more, because it says can you show me the component you print please? thank you! it's very strange because i have a class component not a functional one. this is the function where i call: import React, { useRef } from 'react';
import { useReactToPrint } from 'react-to-print';
import Button from '@mui/material/Button';
import { useStateContext } from "../../../../contexts/ContextProvider";
import ComponentToPrint from "./ComponentToPrint";
import Html2Pdf from 'js-html2pdf';
export default function CreatePDF(props) {
const { primary } = useStateContext();
let componentRef = useRef();
const handleDownload = useReactToPrint({
onPrintError: (error) => console.log(error),
content: () => componentRef.current,
removeAfterPrint: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementById("element-to-download-as-pdf");
console.log(html);
const exporter = new Html2Pdf(html,{filename:"Nota Simple.pdf"});
exporter.getPdf(true);
}
},
});
return (
<>
<Button variant='contained' style={{ backgroundColor: primary, marginLeft: '1%' }} onClick={handleDownload}>PDF</Button>
<div style={{ display: "none" }}>
<ComponentToPrint ref={(el) => (componentRef = el)} genVoucher={props.genVoucher} />
</div>
</>
); this is my component that is printed: import React from 'react';
export default class ComponentToPrint extends React.Component {
render() {
const { genVoucher } = this.props
return (
<div style={{ padding: '100px', textAlign: 'center' }} id='element-to-download-as-pdf'>
<h3>Username: {genVoucher.username}</h3>
<h3>Password: {genVoucher.password}</h3>
<h3>Validity: {genVoucher.validity / 60} minutes</h3>
<h3>Expiry time: {new Date(genVoucher.expirytime * 1000).toLocaleDateString()} {new Date(genVoucher.expirytime * 1000).toLocaleTimeString() }</h3>
<h3>Voucher group: {genVoucher.vouchergroup}</h3>
</div>
);
}
} |
just like jspdf,but it needs draw the content by yourself |
im using electronJs any one can help me ? |
const handlePrint = useReactToPrint({
content: () => printComponent.current,
documentTitle: 'doc.pdf',
copyStyles: true,
print: async (printIframe) => {
const document = printIframe.contentDocument;
if (document) {
const html = document.getElementsByTagName('html')[0];
get_PDF_in_base64(html)
}
}
});
export const get_PDF_in_base64 = async (htmldoc) => {
if (htmldoc) {
const canvas = await html2canvas(htmldoc);
const pdf = new jsPDF({
format: "a4",
unit: "mm",
});
pdf.addImage(
canvas.toDataURL('image/jpeg'),
'JPEG',
0,
0,
pdf.internal.pageSize.getWidth(),
pdf.internal.pageSize.getHeight()
);
const pdfBase64 = pdf.output('dataurlstring');
console.log(pdfBase64);
}
} Use html2canvas and jspdf ... |
But html2pdf has not types ( Typescript ) ! |
I'm getting blank page though the HTML content is available:
|
yes it is possible to create pdf without opening. a print window but the problem that i am facing is that |
@Tsandesh that sounds like a configuration issue with your site/server, but if you think it's an issue with |
Does this solution take account for screen resolution? I have a problem of where the end result ends up incredibly zoomed in if a user downloads the PDF on mobile |
Just wanted to post my findings here before I moved onto spending my time in better ways. I got pdf generation and downloading via a single button press working (sorta) in a way that DOESN'T use canvas or create an image to lower the quality. The issue is that the styling doesn't properly carry over. I'm sure someone else could pick up from here and make it work and add further improvements but I've decided it's not worth my time and I'll just try html2canvas + jsPDF (or something similar). I'm using 3 tools:
One thing I don't think people realize at first is that all this tool (react-to-print) does is make HTML from your component with some minor styling changes that appear nice in print view. It's only 1 step in the process of actually creating a pdf. React-to-print creates the HTML, react-pdf-html converts it to a format react-pdf likes better, and react-pdf actually generates the pdf. Here's my code: "use client"
import { useState, useEffect } from "react"
import {
PDFDownloadLink,
Document,
Page,
} from "@alexandernanberg/react-pdf-renderer"
import { PiFilePdf } from "react-icons/pi"
import { useReactToPrint } from "react-to-print"
import Html from "react-pdf-html"
type PrintButtonProps = {
contentRef: React.RefObject<HTMLDivElement>
}
function MyDocument({ htmlString }: { htmlString: string }) {
return (
<Document>
<Page size="A4">{<Html>{htmlString}</Html>}</Page>
</Document>
)
}
export default function PrintButton({ contentRef }: PrintButtonProps) {
const [htmlContent, setHtmlContent] = useState<string | null>(null)
const handlePrint = useReactToPrint({
contentRef: contentRef,
print: async (printIframe: HTMLIFrameElement) => {
if (printIframe.contentDocument) {
setHtmlContent(printIframe.contentDocument.body.innerHTML)
console.log("Prepared", printIframe.contentDocument.body.innerHTML)
}
},
})
useEffect(() => {
if (contentRef.current) {
handlePrint()
}
}, [contentRef, handlePrint])
return (
<div className="fixed right-12 top-3">
{htmlContent && (
<PDFDownloadLink
document={<MyDocument htmlString={htmlContent} />}
fileName="test.pdf"
>
<PiFilePdf size={26} className="opacity-25" />
</PDFDownloadLink>
)}
</div>
)
} That's why I wrap it with the tag provided by react-pdf-html—it converts it to that structure of Document, Page, View, etc. That SHOULD be it, but there's another issue. I'm using an experimental build of react-pdf, and it's actually a literal different package "@alexandernanberg/react-pdf-renderer" instead of "@react-pdf/renderer". That means the react-pdf-html library itself is pointing to the wrong dependencies and it will give you an error. So follow the error path it tells you (node-modules/react-pdf-html/dist/esm/[filename]) Yay, you're done. By now, you should have a pdf that generates on your button press (just assign the ref to the element you want to print and pass it to your print button component, as per standard react-to-print setup) and you'll have a very ugly pdf document that technically has all of your text. Horray. Note that with NextJs there are some errors in the server-side console and the build times have all increased. I'm assuming that's because it's trying to use client-only logic at first with react-pdf as mentioned in this video. I haven't bothered trying to fix it since I'm moving on because of the reasons listed below. The issue is that most (if not all) of my styling doesn't carry over. I'll also have to import a unique .ttf custom font file for every font style and another file for every weight for every custom font I use. Right now, I'm using Asian (Japanese) characters in my component which are converted into pure gibberish, maybe that will be fixed if I import a font meant for those characters (like my Noto Sans JP font). As you can see, it's pretty butchered. All the basic HTML IS there (even the lists which are apparently hard to convert [supposedly flexboxes work too]). Maybe the styling will work better without TailwindCSS, idk. If someone wants to develop another tool to make these tools interplay better, go for it. But for anyone else, just stick with something like html2canvas and jsPDF. |
Thanks for the writeup @Dillpickleschmidt, really interesting! |
@Dillpickleschmidt Thx for the extensive write-up. I tried all the method mentioned on this page and still have a lot of problem exporting tailwind components as PDF. |
@jt6677 , could you add more details about the issues you are facing? |
@alamenai |
@Dillpickleschmidt I had some success turning component into svg using satori, encode it as dataURL and use it as Svg in react-pdf/render . At this point, I might as well turn components as pngs. PDF just does not have much support for costumed svg. |
or directly to pdf . https://og-playground.vercel.app/ |
@jt6677 , how did you handle multi pages ? |
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
async function createPDF() {
const doc = new PDFDocument({
compress: false,
size: 'A4',
})
const pngUrl = await componentToPNG(TrainCardRender, ctaVariants, 595, 842)
if (pngUrl) {
const imageBuffer = await fetch(pngUrl).then((res) => res.arrayBuffer())
doc.addPage().image(imageBuffer, 0, 100, { width: 595, height: 842 })
}
const stream = doc.pipe(blobStream())
return new Promise<string>((resolve) => {
stream.on('finish', () => {
const blob = stream.toBlob('application/pdf')
const url = URL.createObjectURL(blob)
resolve(url)
})
doc.end()
})
} You just |
You can also try this microservice developed by me to convert html into pdf https://github.com/vikramsamak/html-to-pdf-microservice |
I have HTML content that is very long, sometimes over 50 pages, and occasionally even over 300 pages or more. I want to generate a PDF from this content and allow users to download it without displaying a preview in Angular and also I don't want to interrupt its workflow. Is it possible to achieve this without using a backend |
Hi @Ridafatima17 it is possible, you need to find a JavaScript package that can turn HTML elements into PDF (this would be what const handlePrint = useReactToPrint({
...,
print: async (printIframe: HTMLIframeElement) => {
// Do whatever you want here, including asynchronous work
await generateAndSavePDF(printIframe);
},
}); |
@Ridafatima17 I recommend you use pdf js libs like PDFkit to turn content into pdf. They have a lot more methods and actually print content as text instead of as image. Render 300 pages of content as image and print as pdf format will be painfully slow. |
Hello,
On my web app I have 2 buttons:
Print
andShare
.I set up
react-to-print
library to work withPrint
button, it opens up a print overlay with contents of the page and I'm able to either print it directly or save as PDF as it is supposed to be. However, onShare
button click I would like to open an overlay inside the app from which user would be able to send a PDF with contents of the page to emails or other messengers, therefore I would need to have a PDF document generated automatically, when user clicks on Share button, that would be attached to emails or messages sent out.Is there a way to set it up with react-to-print library?
The text was updated successfully, but these errors were encountered: