eKoopmans / html2pdf.js

Client-side HTML-to-PDF rendering using pure JS.
MIT License
4.18k stars 1.39k forks source link

text is cut in the middle #83

Open labyed opened 6 years ago

labyed commented 6 years ago

Hello, i got a problem with librarie, the text is cut in the middle, is there a way to avoid that, the current behavior :

error
labyed commented 6 years ago

i want to know is ther an option if the content of the element is too big and will not be rendered in the rest of the page, then put it in the next page

lcrojanouninorte commented 6 years ago

I think there is not an option for that yet. In my case, I have a table, but some rows were cut. I have solve this by calculating the height of the rows, and if this height was greater than 500px, I insert a page break before of last row. I have done it with jquery.

labyed commented 6 years ago

@lcrojanouninorte can you post the code you used for that, thank you

lcrojanouninorte commented 6 years ago

@labyed it is not the same case, but you can try. I think you have to set a size limit and calculate the size of each element, and put a page every time this pass the limit :

I create a funtion that put page break in a table every 500px, based on the sum of rows of a table. if the sum of rows height is bigger than a limit, in this case 500, then insert a page break. After all page break are set, continue normal with the plugin.

insertBreaks(){

    //get table rows in html 
    var rows = $('#basic-table > tbody > tr')
    let current_page_height = 0; //
    let max_page_height = 500; //Is the max size  of page i want. in px
    angular.forEach(rows, function(tr, key){
        var row_height = tr.offsetHeight;
        current_page_height =current_page_height + row_height

        //If the sum of page rows heights are bigger thant my limit, then insert break
        if(current_page_height>max_page_height){
            current_page_height = 0
            $(tr).before( '<div class="html2pdf__page-break"></div>' );
        }
    }) 
}

Then first call the function in order to insert the breakes, and continue normal.

download_pdf(){
            this.insertBreaks()
            var element = document.getElementById('exportthis'); 
            element.style.display = 'block'  
            let options = {
                            margin:       [22,10, 15, 10], //top, left, buttom, right
                            filename:     'Gantt.pdf',
                            image:        { type: 'jpeg', quality: 1 },
                            html2canvas:  { dpi: 192, letterRendering: true},
                            jsPDF:        { unit: 'mm', format: 'A4', orientation: 'landscape' },
                        }
            setTimeout(function(){ 
              html2pdf().from(element).set(options).to('pdf').get('pdf').then(pdfCallback, pdfErrorCallback)
           },50);
       }
Linpter commented 6 years ago

@labyed Hi Labyed ! Did you find a solution to work around this ?

Linpter commented 6 years ago

So i managed thanks to @lcrojanouninorte basis to create the same kind of function to insert breaks between pages, for text + tables (in my case it's a report). I'm working with Angular 5 btw. There is maybe some needs to adapt it for your situation. I advice people to do some tests.

  insertBreaks(element) {
    let currentPageHeight = 0;
    // this mawPageHeight depends of the dpi of your pdf, your format, etc.
    let maxPageHeight = 650;
    // select all elements from our created html element which contains our text
    let allElem = element.getElementsByTagName('*');

    // iterate through the elements to calculate the height and then avoid height repetition from 
    // elements within other elements by setting their height to 0
    for (let i = 2; i < allElem.length; i++) {

      // get the height of each element
      let lineHeight = allElem[i].offsetHeight;

      if (allElem[i].tagName === 'TBODY') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'UL') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'TABLE') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'DIV') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'TD') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'SPAN') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'STRONG') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'U') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'EM') {
        lineHeight = 0;
      }
      else if (allElem[i].tagName === 'P' && allElem[i].parentElement.tagName === 'TD') {
        lineHeight = 0;
      }

      // calculate the total height
      currentPageHeight = currentPageHeight + lineHeight;

      if (currentPageHeight > maxPageHeight) {
        currentPageHeight = 0;
        // insert an html page break when max height page is reached
        allElem[i].insertAdjacentHTML('beforebegin', '<div class="html2pdf__page-break"></div>');
      }
    }
    return element;
  }
  pdfyOnClick() {
      // create temporary element to insert the html string and sanitize the html to make it readable
      let reportInHtmlElem = document.createElement('div');

      // we hide it from the user view
      reportInHtmlElem.style.visibility = 'hidden';

      // below method of inserting html expose to possible XSS security issues but it's the only method i've 
     // found that is working to insert my html content 
    // + my html inserted here is created by my rich text editor which is safe
      reportInHtmlElem.innerHTML += this.reportInHtml;
      document.body.appendChild(reportInHtmlElem);

      reportInHtmlElem = this.insertBreaks(reportInHtmlElem);

      let opt = {
        margin: [50, 20, 20, 20],
        enableLinks: true,
        filename: 'somefileName',
        type: 'jpeg', quality: 1
      };

      html2pdf().from(reportInHtmlElem).set(opt).toPdf().get('pdf').then(function (pdfObject) {
        // function to insert the header img
        // get the number of pages in the pdf
        let pdf_pages = pdfObject.internal.pages;
        let headerImg = 'myImg';

          // We are telling our pdfObject that we are now working on this page
          pdfObject.setPage(i);
          // then we put our img header
          pdfObject.addImage(headerImg, 0, 0, 0 , 0);
      }}).save();

      // make it visible back
      reportInHtmlElem.style.visibility = 'visible';
      // remove temporary element
      document.body.removeChild(reportInHtmlElem);
    }
  }
dgolhar commented 5 years ago

Try with following option object:

          var opt = {
             margin: [15,15],
             filename: 'pdfFileName.pdf',
             image:        { type: 'jpeg', quality: 0.98 },
             html2canvas:  { scale: 2, letterRendering: true },
             jsPDF:        { unit: 'pt', format: 'letter', orientation: 'portrait' },
            pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
          };
andrealune commented 3 years ago

@dgolhar Thanks man, you saved my day

reynald27 commented 2 years ago

Your answer @dgolhar is correct, it has worked for me. Thank's

igagandeep commented 10 months ago

I wanted to share a solution I found for a common issue with jsPDF where text gets cut off at the end of a page when generating a PDF from HTML content. This can be particularly troublesome when dealing with dynamic or lengthy content.

After experimenting with various settings, I found a combination that works well to prevent text from splitting inappropriately across pages. This code is written in React. Here's the function I used to generate the PDF:

function downloadPdf() { let jsPdf = new jsPDF('p', 'pt', 'letter'); var htmlElement = pdfRef.current; // Reference to the HTML content

const opt = {
    callback: function (jsPdf) {
        jsPdf.save("Test.pdf");
    },
    margin: [20, 20, 20, 20], // Set appropriate margins
    autoPaging: 'text', // Crucial for handling text flow across pages
    html2canvas: {
        allowTaint: true,
        letterRendering: true,
        logging: false,
        scale: 0.4, // Adjust the scale to fit content
    }, 
};

jsPdf.html(htmlElement, opt);

}

Key Points:

The autoPaging: 'text' option in jsPDF was particularly useful to ensure the text flows correctly from one page to another.

Adjusting the scale in html2canvas options helped fit the content properly within the PDF pages.

This approach worked well for my use case with dynamic content. I hope this solution helps others who are struggling with similar issues in jsPDF. Feel free to tweak the settings to suit your specific needs!

svranju commented 9 months ago

https://github.com/eKoopmans/html2pdf.js/issues/83#issuecomment-382425018

I used the same logic as lcrojanouninorte - thanks!, however, another approach, which solved my problem.

My problem statement: I have a table of items which I need to convert to PDF. Until of the 6th item fit well on page 1, followed by a large space, however 7th item would start a little on page 1 and the rest on page 2. page break demo

Solution: credits also to my partner @aryarajan

  1. On page load, identify the row among the list of rows in the table that will not fit on the page using the logic mentioned by lcrojanouninorte.
  2. For this row, add a new class 'addBreak'
  3. In CSS, added 'break-before' to this class. (refer: https://developer.mozilla.org/en-US/docs/Web/CSS/break-before)
  4. in 'options' for html2pdf, give mode as 'css' and 'avoid' for table row 'tr'.

Disclaimers: "html2pdf__page-break" - did not use this as it wasn't agreeing with me somehow. Also, Im using plain javascript / html.

OnPageLoad - identify row which needs to go to the next page

` function insertBreaks() {

        //get table rows in html 
        var rows = document.querySelectorAll('#itemsTable > tbody > tr');
        let current_page_height = 0;
        let max_page_height = 500; //adjust max sizeof page in px 
        rows.forEach(row => {
            var row_height = row.offsetHeight;
            current_page_height = current_page_height + row_height
            //If the sum of page rows heights are bigger thant my limit, then insert break
            if (current_page_height > max_page_height) {
                current_page_height = 0;
                row.classList.add('addBreak');
            }
        });
    }`

CSS: .addBreak { break-before: auto; margin-top: 70px; }

Html2PDF options: var options = { margin: [30, 0, 50, 0], filename: filename, image: { type: 'jpeg', quality: 0.9 }, autoPaging: 'text', html2canvas: { scale: 2, logging: true, dpi: 300, letterRendering: true }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'p' }, pagebreak: { avoid: 'tr', mode: ['css'], } };

Hope this helps.

wortwart commented 2 months ago

I wanted to share a solution I found for a common issue with jsPDF

@igagandeep So you're using only jsPDF, but not html2pdf.js? Or did you find a way to pass the autoPaging option from html2pdf?

fabianoandrad commented 2 months ago

Para funcionar o "pagebreak" atualize a versão do html2pdf pra mais recente! versão abaixo de 0.9.3 não suporta.