Skip to content
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

Closed
konstantinkrumin opened this issue Apr 7, 2022 · 48 comments · Fixed by #490
Closed
Labels

Comments

@konstantinkrumin
Copy link

Hello,

On my web app I have 2 buttons: Print and Share.

I set up react-to-print library to work with Print 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, on Share 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?

@MatthewHerbst
Copy link
Owner

Hello. react-to-print does not actually generate a PDF. We just generate HTML which the browser (Chrome, Firefox, etc) then converts to a PDF. If you want to create a PDF on the client you would need to use a PDF generation library such as @react-pdf/renderer. Hope that helps!

@konstantinkrumin
Copy link
Author

@MatthewHerbst Thank you!

@konstantinkrumin
Copy link
Author

@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?

@MatthewHerbst
Copy link
Owner

Yes! You can pass a print prop to react-to-print which receives the generated iframe, and can return a Promise:

const handlePrint = useReactToPrint({
  ...,
  print: async (printIframe: HTMLIframeElement) => {
    // Do whatever you want here, including asynchronous work
    await generatePDFFunction(printIframe);
  },
});

@konstantinkrumin
Copy link
Author

konstantinkrumin commented Apr 8, 2022

@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 <style><style /> tags, so the library I'm using next to generate PDF file is getting messy layout (which I guess makes sense since those tags are only for print)

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();
			}
		}
	});

@MatthewHerbst
Copy link
Owner

Yeah, since you aren't actually printing the @media print queries won't work. You should be able to pass in any styles you need though to make it look nice. You can override the default styles react-to-print sets by passing the pageStyle, though by default all we really do is change the margin a bit and hide the URL

@JoelRaglant
Copy link

@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 <style><style /> tags, so the library I'm using next to generate PDF file is getting messy layout (which I guess makes sense since those tags are only for print)

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();
			}
		}
	});

Thank you for this. I tried the same. But onAfterPrint function is not getting triggered once the pdf got saved. Any idea?

@MatthewHerbst
Copy link
Owner

@JoelRaglant that's a bug! We forgot to call it when a custom print function is used. Will get a fixed up version published tonight, sorry about that

@MatthewHerbst
Copy link
Owner

@JoelRaglant should be fixed in v2.14.7, please let me know if you run into additional issues

@JoelRaglant
Copy link

Thank you @MatthewHerbst !! It works fine now.

@eseTunde
Copy link

@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 <style><style /> tags, so the library I'm using next to generate PDF file is getting messy layout (which I guess makes sense since those tags are only for print)

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();
			}
		}
	});

Thank you for this. I tried the same. But onAfterPrint function is not getting triggered once the pdf got saved. Any idea?

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?

@guilhermevieira010797

This comment was marked as resolved.

@eseTunde
Copy link

where the h*** this 'html2pdf' comes from? on typescript/nextjs

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.

@rkushkuley
Copy link

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?

@germanmedinasl
Copy link

germanmedinasl commented Sep 5, 2022

@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.

@Fiewor
Copy link

Fiewor commented Sep 6, 2022

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?

Sorry, I don't know of any at the moment.

@Fiewor
Copy link

Fiewor commented Sep 6, 2022

Try adding an await to exporter.getPdf(true); so it becomes await exporter.getPdf(true);

@germanmedinasl
Copy link

germanmedinasl commented Sep 6, 2022

@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.
Edit: I have tried just calling an element showing on screen and it displays on the PDF, but maybe as the print dialog is not shown yet, there is nothing to "put" in the PDF. Could it be the case? @eseTunde you could be of great help here.

@MatthewHerbst
Copy link
Owner

If you are passing a custom print prop/function then the print dialog will only show if you at some point call window.print within there, though I don't think that's what you actually want to do. The printIframe passed to that function should contain all of the DOM that needs to be printed. Have you verified that the printIframe has the content you expect? If it does then there is likely a problem with your usage of Html2Pdf

@germanmedinasl
Copy link

@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".

@MatthewHerbst
Copy link
Owner

it was only putting on the pdf file the elements shown on screen

Does the library you are using to display the list/elements do virtual rendering? If so, that would explain the issue: react-to-print doesn't know about virtual rendered items since they aren't in the DOM, so we aren't able to capture them. Ideally you would use onBeforeGetContent to modify the DOM with all the elements you want to print.

@nalinwijayasinghe
Copy link

nalinwijayasinghe commented Nov 11, 2022

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.

@MatthewHerbst
Copy link
Owner

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. react-to-print is not capable of generating PDFs on its own, we rely on the browser's print preview to do that for us

@15tanvir
Copy link

Hii
Any solution for downloading pdf directly? Or any library for downloading pdf directly.

@MatthewHerbst
Copy link
Owner

react-to-print does not support direct downloading of a PDF. There are many libraries that allow you to create PDFs in the client. You can use those on their own, and/or combine react-to-print with one by using a custom print function and passing the HTMLIframeElement that we supply to it to the PDF generator:

const handlePrint = useReactToPrint({
  //...,
  print: (iframe: HTMLIframeElement) => {
    // the `iframe` contains the content you want printed. Now pass it to an HTML->PDF generator
  }
})

@edgar-carrillo
Copy link

@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. Edit: I have tried just calling an element showing on screen and it displays on the PDF, but maybe as the print dialog is not shown yet, there is nothing to "put" in the PDF. Could it be the case? @eseTunde you could be of great help here.

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 html it logs the html elements along with the head, body, and everything else nested within <html>.

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 html element, I targeted the exact component I wanted downloaded as a pdf.

Making this const html = document.getElementsByTagName("html")[0];

Become this const html = document.getElementById("element-to-download-as-pdf");

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);
      }
    },
  });

@ErtyDess
Copy link

ErtyDess commented May 5, 2023

@edgar-carrillo nice, it worked for a while and then it work no more, because it says
"To print a functional component ensure it is wrapped with React.forwardRef, and ensure the forwarded ref is used. See the README for an example: https://github.com/gregnb/react-to-print#examples"

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>
          );
        }
      }

@johnwojtek
Copy link

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?

just like jspdf,but it needs draw the content by yourself

@walidromeo
Copy link

im using electronJs any one can help me ?

@vikramsamak
Copy link

vikramsamak commented Dec 5, 2023

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 ...

@alamenai
Copy link

alamenai commented Mar 7, 2024

where the h*** this 'html2pdf' comes from? on typescript/nextjs

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.

But html2pdf has not types ( Typescript ) !

@alamenai
Copy link

alamenai commented Mar 7, 2024

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 ...

I'm getting blank page though the HTML content is available:


  const download = useReactToPrint({
    content: () => componentRef.current,
    documentTitle: "doc.pdf",
    copyStyles: true,
    print: async (printIframe) => {
      const document = printIframe.contentDocument
      if (document) {
        const html = document.getElementById(elementID as string)
        getPDFInBase64(html as HTMLElement)
      }
    },
  })

  const getPDFInBase64 = async (htmldoc: HTMLElement) => {
    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()
      )

      pdf.html(htmldoc).then(() => {
        pdf.save()
      })

      // const pdfBase64 = pdf.output("dataurlstring")
      // pdf.save()
    }

image

image

@Tsandesh
Copy link

yes it is possible to create pdf without opening. a print window but the problem that i am facing is that
when the public images are getting cors error when trying to convert the image into blob

@MatthewHerbst
Copy link
Owner

@Tsandesh that sounds like a configuration issue with your site/server, but if you think it's an issue with react-to-print specifically please make a new issue for it, I haven't heard of that particular problem before but we have added nonce support in the past for other security related issues, but there's not much we can do to my knowledge for CORS specific issues since react-to-print runs on the same domain as your website.

@AriLaurin
Copy link

@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. Edit: I have tried just calling an element showing on screen and it displays on the PDF, but maybe as the print dialog is not shown yet, there is nothing to "put" in the PDF. Could it be the case? @eseTunde you could be of great help here.

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 html it logs the html elements along with the head, body, and everything else nested within <html>.

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 html element, I targeted the exact component I wanted downloaded as a pdf.

Making this const html = document.getElementsByTagName("html")[0];

Become this const html = document.getElementById("element-to-download-as-pdf");

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);
      }
    },
  });

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

@Dillpickleschmidt
Copy link

Dillpickleschmidt commented Jul 16, 2024

Just wanted to post my findings here before I moved onto spending my time in better ways.
This is my attempt at generating a pdf WITHOUT using tools like html2canvas which convert everything to an image before generating a pdf, meaning I'll be able to infinitely zoom without loss of detail. I believe it's a step in the right direction if other developers want to use this as a starting point and make it better, but for most people, I'd not recommend doing this (at least in the way I've figured out).

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).
This setup is using NextJs 15 + TailwindCSS + ShadCn with react-to-print v3 beta. This is done in a component marked "use client" so there's no advanced server-side code going on.

I'm using 3 tools:

  1. react-to-print (for html styling for pdfs)
  2. react-pdf but specifically this build of it (currently needed for React 19 support). (for actually generating pdfs)
  3. react-pdf-html (for converting html code from react-to-print to a format that react-pdf understands)

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's this line:
<Page size="A4">{<Html>{htmlString}</Html>}</Page>

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])
You have to change the imports from "@react-pdf/renderer" to "@alexandernanberg/react-pdf-renderer" and save.
I think there's 3 or 4 files I changed until the error messages went away.

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've got all these warnings too:
image

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).

Here's the before and after:
image

image

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.
Or, if someone has found a better solution, please let me know.

@MatthewHerbst
Copy link
Owner

Thanks for the writeup @Dillpickleschmidt, really interesting!

@jt6677
Copy link

jt6677 commented Jul 18, 2024

@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.

@alamenai
Copy link

@jt6677 , could you add more details about the issues you are facing?

@jt6677
Copy link

jt6677 commented Jul 18, 2024

@alamenai
-generate as pdf
Snipaste_2024-07-18_20-46-53
vs
-print as pdf
Snipaste_2024-07-18_20-49-14

@jt6677
Copy link

jt6677 commented Aug 19, 2024

@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.

@jt6677
Copy link

jt6677 commented Aug 19, 2024

or directly to pdf . https://og-playground.vercel.app/

@alamenai
Copy link

@jt6677 , how did you handle multi pages ?

@jt6677
Copy link

jt6677 commented Aug 20, 2024

@alamenai

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 doc.addPage(). I tried all the lib combo, satori +pdfkit gave me the best result with tailwind support.

Snipaste_2024-08-20_20-09-40

@vikramsamak
Copy link

vikramsamak commented Aug 20, 2024

You can also try this microservice developed by me to convert html into pdf

https://github.com/vikramsamak/html-to-pdf-microservice

https://html-to-pdf-microservice.onrender.com/

@Ridafatima17
Copy link

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

@MatthewHerbst
Copy link
Owner

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 generateAndSavePDF in the example below does). Then you can use the print function to override the default behavior and get access to the print iframe element:

const handlePrint = useReactToPrint({
  ...,
  print: async (printIframe: HTMLIframeElement) => {
    // Do whatever you want here, including asynchronous work
    await generateAndSavePDF(printIframe);
  },
});

@jt6677
Copy link

jt6677 commented Oct 29, 2024

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.