generating pdf from Html in React using html2canvas and jspdf

倖福魔咒の 提交于 2019-12-08 05:22:57

问题


I have successfully generated the pdf from html2canvas and jspdf in React. But the problem is the image rendered in the pdf is stretched. Please let me know what the best option to do this kind of work is. Is there any possibility of rendering the HTML directly to pdf without using html2canvas? Below are my screenshots Output Screenshot and pdf's screenshot

Below is my code

import React, { Component } from 'react';
import * as jsPDF from 'jspdf';
import * as html2canvas from 'html2canvas';


class Invoice extends Component {

handlePdf = () => {
    const input = document.getElementById('page');

    html2canvas(input)
        .then((canvas) => {
            const imgData = canvas.toDataURL('image/png');
            const pdf = new jsPDF('p', 'px', 'a4');
            var width = pdf.internal.pageSize.getWidth();
            var height = pdf.internal.pageSize.getHeight();

            pdf.addImage(imgData, 'JPEG', 0, 0, width, height);
            pdf.save("test.pdf");
        });
};

render() {

    const hrStyle = {
        border: '5px solid rgb(23, 162, 184)'
    };

    const subtotal = [0, ...this.props.inputs.totals];
    const add = (a, b) => a + b;
    const sum = subtotal.reduce(add);

    const tax = sum * 0.1;
    const total = sum + tax;

    return (
        <React.Fragment>
            <div className="col-12 col-lg-6" id="page">
                <div className="container-fluid bg-info text-white">
                    <div className="row">
                        <div className="col text-left m-2">
                            <p>Your Company Name</p>
                            <h2>Invoice</h2>
                        </div>
                        <div className="col text-right">
                            <p>22 Yusen St</p><br />
                            <p>borburn</p><br />
                            <p>WSN</p>
                        </div>
                    </div>
                </div>
                <div className="container-fluid">
                    <div className="row">
                        <div className="col-4">
                            <h5>Billed To</h5>
                            <p>{this.props.inputs.company}</p>
                            <p>{this.props.inputs.address}</p>
                            <p>{this.props.inputs.zip}</p>
                        </div>
                        <div className="col-4">
                            <div>
                                <h5>Invoive number</h5>
                                <p>Za{Math.floor((Math.random() * 100) + 1)}</p>
                            </div>
                            <div>
                                <h5>Date</h5>
                                <p>{this.props.inputs.date}</p>
                            </div>
                        </div>
                        <div className="col-4">
                            <div>
                                <h5>Invoice Totals</h5>
                                <p>${total}</p>
                            </div>
                        </div>
                    </div>
                </div>
                <hr style={hrStyle} />
                <div className="Invoices">
                    <table className="table">
                        <thead>
                            <tr>
                                <th>Description</th>
                                <th>Unit Price</th>
                                <th>Quantity</th>
                                <th>Total</th>
                            </tr>
                        </thead>
                        <tbody>
                            {this.props.inputs.invoices.map((invoice, index) => {
                                return (
                                    <tr key={index}>
                                        <td>{invoice.description}</td>
                                        <td>{invoice.unit}</td>
                                        <td>{invoice.quantity}</td>
                                        <td>{invoice.quantity * invoice.unit}</td>
                                    </tr>
                                )
                            })
                            }
                        </tbody>
                    </table>
                </div>
                <hr />
                <div className="col-12 text-right" >
                    <h5 className="m-2">
                        Subtotal<span className="m-2">${sum}</span>
                    </h5 >
                    <p>Tax(10%)<span className="m-2">${tax.toFixed(2)}</span></p>
                    <h2>Total<span className="m-2">${total}</span></h2>
                </div>
            </div>
            <button onClick={this.handlePdf} className="btn btn-primary btn-lg mx-auto">Generate PDF</button>

        </React.Fragment >
    );
}
}

export default Invoice;

回答1:


You can use the excellent React-pdf library. It has good performance and has the added benefit that if you print text, it will be searchable.

If you need to render the page in the DOM as well as in PDF, I recommend to create yourself primitives and use these instead of the dom tags (div, p, etc). By using these primitives, you will be able to render the same components in the dom as well as in a pdf.

Here is an example of a View primitive:

import React from 'react';
import { View as ViewPDF } from '@react-pdf/renderer';
import { RENDERING_CONTEXT, withRenderingContext } from '../RenderingContext';

class View extends React.Component {
  render() {
    const { renderingContext, children, className, style } = this.props;
    const isPdf = renderingContext === RENDERING_CONTEXT.PDF;
    return isPdf
      ? <ViewPDF className={className} style={style}>
          {children}
        </ViewPDF>
      : <div className={className} style {style}>
          {children}
        </div>;
  }
}
export default withRenderingContext(View);

The RenderingContext is a context in which you can store the current rendering context. Then, you only need to wrap your pdf page with a provider that sets the proper context. The right component (from react-pdf or dom) will be used based on the rendering context.

In your example, you could simply replace the dom element in Invoice with these primitives. Then, you would be able to render the Invoice component in a pdf file or in the dom.




回答2:


You can still render jspdf with html2canvas with 100% width and auto height to avoid stretch. I found this solution works.

https://stackoverflow.com/a/55497749/5347308



来源:https://stackoverflow.com/questions/53117630/generating-pdf-from-html-in-react-using-html2canvas-and-jspdf

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!