最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How can I dynamically split chunk of text into pages for jsPDF? - Stack Overflow

programmeradmin2浏览0评论

I have a React app, page displaying some post containing title, text, few images etc. I'm trying to generate PDF file out of that.

The actual page is quite twisty, dynamic and plicated, so instead of trying to convert HTML to PDF I am using jsPDF package and as a content I insert state variables from Redux store. And it would have been ideal situation for that - I have tools to generate layout, I have bits of content, but... I bumped into a problem. Text of my post can vary from very little to few pages long, so in that case it should be passed to the next page. I have worked out how to get "pageHeight" and "textHeight", so when "textHeight" is bigger than "pageHeight" I can add another page by doc.addPage(), however the text stays on one page only and gets cut off by the end of the page (please see attached screenshot). This is where I am so far with my code:

const pdfGenerator = () => {
    // Default export is a4 paper, portrait, using millimeters for units
    const doc = new jsPDF();
    const pageHeight = doc.internal.pageSize.height;

    const wrappedText = doc.splitTextToSize(post.text, 200);
    doc.setFontSize(14);
    const dim = doc.getTextDimensions(wrappedText);
    const textHeight = dim.h;
    doc.text(15, 10, wrappedText);
    if (textHeight >= pageHeight) {
      doc.addPage();
    }
    // const textPages = Math.ceil(dim.h / pageHeight);
    // Don't know what to do with it....

    doc.save(`${post.title}.pdf`);
  };

One of the possible solutions would be to split whole text into array of chunks that can fit the page and then do a loop or map it through adding doc.addPage() every time. Big problem with that - it will split text in random places, like in the middle of the word.

I tried my best to find solution, lots of time wasted with no result...

Please help!

I have a React app, page displaying some post containing title, text, few images etc. I'm trying to generate PDF file out of that.

The actual page is quite twisty, dynamic and plicated, so instead of trying to convert HTML to PDF I am using jsPDF package and as a content I insert state variables from Redux store. And it would have been ideal situation for that - I have tools to generate layout, I have bits of content, but... I bumped into a problem. Text of my post can vary from very little to few pages long, so in that case it should be passed to the next page. I have worked out how to get "pageHeight" and "textHeight", so when "textHeight" is bigger than "pageHeight" I can add another page by doc.addPage(), however the text stays on one page only and gets cut off by the end of the page (please see attached screenshot). This is where I am so far with my code:

const pdfGenerator = () => {
    // Default export is a4 paper, portrait, using millimeters for units
    const doc = new jsPDF();
    const pageHeight = doc.internal.pageSize.height;

    const wrappedText = doc.splitTextToSize(post.text, 200);
    doc.setFontSize(14);
    const dim = doc.getTextDimensions(wrappedText);
    const textHeight = dim.h;
    doc.text(15, 10, wrappedText);
    if (textHeight >= pageHeight) {
      doc.addPage();
    }
    // const textPages = Math.ceil(dim.h / pageHeight);
    // Don't know what to do with it....

    doc.save(`${post.title}.pdf`);
  };

One of the possible solutions would be to split whole text into array of chunks that can fit the page and then do a loop or map it through adding doc.addPage() every time. Big problem with that - it will split text in random places, like in the middle of the word.

I tried my best to find solution, lots of time wasted with no result...

Please help!

Share Improve this question asked May 5, 2021 at 10:17 wawaywaway 3303 silver badges10 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

The splitTextToSize function will return an array with the text splitted into lines, we just need to work with it controlling the interactions. The code below should resolve your problem.

const pdfGenerator = () => {
    // Default export is a4 paper, portrait, using millimeters for units
    const doc = new jsPDF();
    const pageHeight = doc.internal.pageSize.height;

    const wrappedText = doc.splitTextToSize(post.text, 180);
    doc.setFontSize(14);
    let iterations = 1; // we need control the iterations of line
    const margin = 15; //top and botton margin in mm
    const defaultYJump = 5; // default space btw lines
    
    wrappedText.forEach((line) => {
      let posY = margin + defaultYJump * iterations++;
      if (posY > pageHeight - margin) {
        doc.addPage();
        iterations = 1;
        posY = margin + defaultYJump * iterations++;
      }
      doc.text(15, posY, line);
    });

    doc.save(`${post.title}.pdf`);
  };

This is a refinement on @Thiago Moura's answer. The only magic numbers, i.e. pletely subjective depending on your preference, are the margins. Everything else is derived. Also no need to keep a count of the number of lines. I'm including the method that makes the header and footer, even though it wasn't asked for, to provide context for how the margins are calculated. You may omit the header/footer logic and decrease or adjust the yMargin value without impacting the overall logic.

if (format === "PDF") {
        const doc = new jsPDF("portrait", "px", "a4");
        doc.setFontSize(12); 
        const pageWidth = doc.internal.pageSize.getWidth();
        const pageHeight = doc.internal.pageSize.getHeight();
        const xMargin = 25; // left and right margin
        const yMargin = 75; // top and botton margin including the header/footer. In px since that's what hte jsPDF is set to use
        const lineHeight = doc.getLineHeight(); // default space between lines
        const needNewPageAfterThisYPos = pageHeight - yMargin
       
        // the `description` variable here is the pdf content
        const wrappedText = doc.splitTextToSize(description, pageWidth - (2 * xMargin)); // account for both the left and right marigns
        let yPos;
        wrappedText.forEach((line) => {
          if (yPos > needNewPageAfterThisYPos) {
            doc.addPage();
            yPos = undefined;
          } 
          if (yPos === undefined) {
            yPos = yMargin;
          } else {
            yPos = yPos + lineHeight;
          }
          doc.text(line, xMargin, yPos);
        }); 
        this.#addPDFHeaderAndFooter(doc, pageWidth, pageHeight, (yMargin / 2), data.label);
        doc.save(file);  // file is the file path to save to

      } else {
           ...
       }

and the header/footer logic

  /**
   * Private method to add the header and footer to each page of a generated pdf.
   */
  #addPDFHeaderAndFooter = (doc, pageWidth, pageHeight, margin, message) => {
    const pageCount = doc.internal.getNumberOfPages();
    doc.setFont('Times');
    doc.setFontSize(10);  
    for (var i = 1; i <= pageCount; i++) {
      doc.setPage(i);
      // add the header to the page
      doc.text(message, pageWidth / 2, margin, {
        align: 'center'
      });
    
      // add the footer to the page
      doc.text(message, pageWidth / 2, pageHeight - margin, {
        align: 'center'
      })
    }
  }
发布评论

评论列表(0)

  1. 暂无评论