Selectable PDF Creation is not working with jsPDF

42 Views Asked by At

Attempting to generate a PDF from a React component's HTML page containing text, tables, and graphs. Successfully created a PDF using jsPDF and html2canvas, but facing an issue where text is rendered as a single image, hindering the selection of individual text elements. A sample of the generated PDF is attached for reference.

The requirement is to enable the selection of both text content and tabular data upon downloading the PDF. Assistance is sought in identifying the necessary code changes to meet this requirement. Other packages like react-pdf have been experimented with, but the desired outcome has not been achieved.

Code

import React, { useRef, useEffect } from 'react';
import { Row, Col, Table } from 'react-bootstrap';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import { Container, Button } from 'react-bootstrap';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';


const EcommerceInIndiaPage = ({ generatePDF }) => {
   const componentRef = useRef();

   const ecommercePieChartOptions = {
       chart: {
           type: 'pie',
       },
       title: {
           text: 'E-commerce Market Share in India',
       },
       series: [
           {
               name: 'Categories',
               colorByPoint: true,
               data: [
                   {
                       name: 'Electronics',
                       y: 30,
                   },
                   {
                       name: 'Fashion',
                       y: 25,
                   },
                   {
                       name: 'Grocery',
                       y: 20,
                   },
                   {
                       name: 'Home & Furniture',
                       y: 15,
                   },
                   {
                       name: 'Others',
                       y: 10,
                   },
               ],
           },
       ],
   };



   const downloadPDF = async () => {
       const element = componentRef.current; // Reference to the component
       const canvas = await html2canvas(element);
       const data = canvas.toDataURL('image/png');
       let pdf = new jsPDF('p', 'mm', 'a4');
       let pageWidth = pdf.internal.pageSize.getWidth();
       let pageHeight = pdf.internal.pageSize.getHeight();

       // Calculate the number of pages.
       let imgHeight = canvas.height * pageWidth / canvas.width;
       let heightLeft = imgHeight;

       let position = 0;

       // Add content to the first page.
       pdf.addImage(data, 'PNG', 0, position, pageWidth, imgHeight);
       heightLeft -= pageHeight;

       while (heightLeft >= 0) {
           position = heightLeft - imgHeight;
           pdf.addPage();
           pdf.addImage(data, 'PNG', 0, position, pageWidth, imgHeight);
           heightLeft -= pageHeight;
       }

       // Additional Pages with Dummy Data
       // Adding a new page for conclusion
       pdf.addPage();
       pdf.setFontSize(10);
       pdf.text("Conclusion", 10, 10);
       pdf.text("This is a conclusion section with some insights and final thoughts about the E-commerce market trends in India.", 10, 20);

       // Adding a content page with dummy data
       pdf.addPage();
       pdf.setFontSize(10);
       pdf.text("Content Page", 10, 10);
       pdf.text("Here you can describe the document's structure or provide any additional information.", 10, 20);

       // Save the PDF
       pdf.save('ecommerce-in-india-report.pdf');
   };


   useEffect(() => {
       // Call the generatePDF function with the HTML content when the component mounts
       if (generatePDF && componentRef.current) {
         const htmlContent = componentRef.current.innerHTML;
         generatePDF(htmlContent);
       }
     }, [generatePDF]);

   return (
       <Container className="mt-4" ref={componentRef}>
           <div>
               <Row>
                   <Col>
                       <h1>E-commerce in India</h1>
                       <p>
                           E-commerce has experienced tremendous growth in India over the past decade, transforming the way
                           people shop and businesses operate. The diverse and dynamic nature of the Indian market has led to
                           the rise of various e-commerce segments, catering to a wide range of consumer needs.
                       </p>

                       <h2>Market Trends</h2>
                       <p>
                           The Indian e-commerce market is characterized by the dominance of categories such as Electronics,
                           Fashion, Grocery, Home & Furniture, and more. According to recent studies, the electronics segment
                           holds a significant market share, followed closely by the fashion and grocery sectors.
                       </p>

                       <h2>Consumer Behavior</h2>
                       <p>
                           With the advent of affordable smartphones and widespread internet connectivity, more and more
                           consumers in India are embracing online shopping. The convenience of browsing through a vast
                           assortment of products, coupled with attractive discounts and doorstep delivery, has contributed to
                           the popularity of e-commerce.
                       </p>

                       {/* React Bootstrap Table */}
                       <h2>E-commerce Market Overview</h2>
                       <Table striped bordered hover>
                           <thead>
                               <tr>
                                   <th>#</th>
                                   <th>Category</th>
                                   <th>Market Share</th>
                               </tr>
                           </thead>
                           <tbody>
                               {[
                                   { category: 'Electronics', marketShare: '30%' },
                                   { category: 'Fashion', marketShare: '25%' },
                                   { category: 'Grocery', marketShare: '20%' },
                                   { category: 'Home & Furniture', marketShare: '15%' },
                                   { category: 'Others', marketShare: '10%' },
                               ].map((item, index) => (
                                   <tr key={index}>
                                       <td>{index + 1}</td>
                                       <td>{item.category}</td>
                                       <td>{item.marketShare}</td>
                                   </tr>
                               ))}
                           </tbody>
                       </Table>

                       <h3>Challenges and Opportunities</h3>
                       <p>
                           While the e-commerce sector in India continues to thrive, it faces challenges such as logistical
                           complexities and regulatory frameworks. However, the market presents immense opportunities for
                           innovation, technological advancements, and the integration of e-commerce into various aspects of
                           daily life.
                       </p>
                   </Col>

                   <Col>
                       {/* Highcharts Pie Chart */}
                       <HighchartsReact highcharts={Highcharts} options={ecommercePieChartOptions} />
                   </Col>
               </Row>
               <Button onClick={downloadPDF}>Download as PDF</Button>
           </div>
       </Container>
   );
};

export default EcommerceInIndiaPage;
1

There are 1 best solutions below

0
Mr. Terminix On

I was able to create pdf with selectable text from the given HTML:

shot of pdf with selectable text

I changed the function downloadPdf:

const downloadPDF = async () => {
    const element = componentRef.current; // Reference to the component
    const canvas = await html2canvas(element);
    const data = canvas.toDataURL("image/png");
    let pdf = new jsPDF("p", "mm", "a4");
    let pageWidth = pdf.internal.pageSize.getWidth();
    let pageHeight = pdf.internal.pageSize.getHeight();

    // Calculate the number of pages.
    let imgHeight = (canvas.height * pageWidth) / canvas.width;
    let heightLeft = imgHeight;

    let position = 0;

    // Add content to the first page.
    //    pdf.addImage(data, 'PNG', 0, position, pageWidth, imgHeight);
    //    heightLeft -= pageHeight;

    //    while (heightLeft >= 0) {
    //        position = heightLeft - imgHeight;
    //        pdf.addPage();
    //        pdf.addImage(data, 'PNG', 0, position, pageWidth, imgHeight);
    //        heightLeft -= pageHeight;
    //    }


    // New code
    await pdf.html(element, {
        margin: [20, 20, 20, 20],
        autoPaging: "text",
        width: 170,
        windowWidth: element.offsetWidth,
    });
    const chartCanvas = await html2canvas(chartRef.current);
    pdf.addImage(
        chartCanvas,
        "PNG",
        110,
        20,
        chartCanvas.width / 7,
        chartCanvas.height / 7
    );

    // Additional Pages with Dummy Data
    // Adding a new page for conclusion
    pdf.addPage();
    pdf.setFontSize(10);
    pdf.text("Conclusion", 10, 10);
    pdf.text(
        "This is a conclusion section with some insights and final thoughts about the E-commerce market trends in India.",
        10,
        20
    );

    // Adding a content page with dummy data
    pdf.addPage();
    pdf.setFontSize(10);
    pdf.text("Content Page", 10, 10);
    pdf.text(
        "Here you can describe the document's structure or provide any additional information.",
        10,
        20
    );

    // Save the PDF
    pdf.save("ecommerce-in-india-report.pdf");
};

I used the method .html, which adds selectable text, not text from image. Unfortunately jsPDF have some problem with adding svg files, so I had to tweak the code to convert the svg to png and then to add it. There can be better and more convenient way of doing that. I want to mention that I divide the chartCanvas width and height by 7, because this is the ratio between the chartCanvas width and half of the width of the pdf page. This can be replaced by formula. Also, I added one more reference called chartRef and added it to the Col element, which holds the chart. I want to mention that I added the code inside repo with a lot of CSS, for that maybe the text is not looking very good. If there are some specific fonts, they have to be added to jsPDF too.