generating PDF from HTML|front end

258 阅读11分钟

正确示范

c3的columns + html2canvas+jsPDF

参考网站: segmentfault.com/a/119000002…

columns:多栏布局,它能够表现出将内容在列之间怎么流动的以及间隙和分割线怎么使用

利用c3的columns多栏布局属性解决pdf分页截断问题,未深入调研,大概试了一下有点问题,想法是可行的,不过浪费性能。

大概思想:利用c3的columns多栏布局属性,超出自动换下一栏,可以避免截断问题,渲染出canvas image平移到对应那一栏的可视区域

<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>jsPDF</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script src="https://unpkg.com/jspdf@latest/dist/jspdf.min.js"></script>
  <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
  <script src="jquery.js"></script>
  <style>
    #title {
      width: 525px;
      overflow: hidden;
    }
  </style>
</head>

<body>
  <button onclick="getPDF()">Generate PDF</button>
  <div id="title" style="background: #ffffff;">
    <div class="title" style="background: #ffffff;">
      <div class="title-h">
        <h3><a name="t0"></a><a name="t0"></a> <span>1.</span>题目 </h3>
        <p> 1. A让C要送一份资料给B。但C不可靠,A可以把资料放在盒子里锁上才能送给B,但B也需要拿到钥匙才能打开盒子。问A有方法将资料安全送达B处吗? </p>
        <p> 2. 房间里有一盏灯。房间外有四个开关,但只有一个开关是控制灯泡。现在四个开关都处于关闭状态。问能否只进入房间一次,就能找出控制灯泡的开关。 </p>
        <p> 3.
          在一个黑房间里有100枚硬币,其中20枚正面朝上,其它的反面朝上。你看不到各枚硬币的朝向,也不允许以任何形式来分辨它,但允许你对它们进行翻转(在不知朝向的情况下)。问你是否可能将这些硬币分成两堆,这两堆硬币有相同多的正面朝上的硬币。
        </p>
        <p> 4.
          三袋水果,其中一袋里面是苹果,第二袋是桔子,第三袋既有苹果也有桔子。在袋子外面有标签标明袋子的内容。但不幸的是,你被告知三个袋子的标签都被标错了。你现在可以从袋子里拿出水果来分辨袋子。问:你至少需要拿出几个水果,才能更正这三个袋子的标签?
        </p>
        <p> 5.&nbsp;
          监狱里关了50个囚犯。监狱长有一个硬币,现在正面朝上。每分钟监狱长会叫入一个囚犯。这个囚犯可选择翻转硬币或者不翻转。如果有囚犯宣布每个囚犯都被叫入至少一次,并且情况确实如此,那么所有囚犯都会被释放;如果出现错误,那么所有囚犯都会被处死。囚犯在关入单人监狱之前可以商议策略,但之后不能有任何交流。问囚犯能达成策略吗?
        </p>
        <p> 6. 1只钟从墙上摔下来,裂成三个完整的碎片。我们发现三碎片上数字之和相同。问各碎片上的数字。 </p>
        <p> 7. 手头有98个不同的整数,取值范围为1到100。问如何快速找到两个缺失的数。 </p>
        <p> 8. 10个袋子里分别有100枚硬币。其中9个袋子里的硬币是一样重的,每个硬币都是10克。另外一个袋子里硬币也都是一样,但重量都是9克或者都是11克。问怎么只用一次带刻度的电子秤分辨出与众不同的袋子以及具体的重量。
        </p>
        <p> 9. 五个袋子里分别有100枚硬币。每个袋子里的硬币都是一样的,但不同袋子的硬币可能不一样,可能有三种重量,分别为9克、10克和11克。问至少需要使用几次带刻度的电子秤才能分辨出每个袋子的硬币重量? </p>
        <p> 10. 一个与世隔绝的岛屿上有13个黑人、15个白人和17个黄种人。任何两种不同皮肤的人遇到一起,就会都变成另外一种颜色。问是否可能所有人都变成同一种颜色? </p>
        <p> 11.
          你有100个硬币。首先你将它分成两堆,分别有x枚和y枚硬币,得到它们的乘积。再任意将其中的一堆分成两堆,得到两个小堆的硬币数量的乘积。这样一直分下去直到每个堆只有一枚硬币无法再分为止。将所有得到的乘积加起来,问最后这样的乘积和最大是多少?
        </p>
        <p> 12.
          一个6×8的棋盘,你需要逐步将其分拆成小方块。你每次可以选择其中的一块,沿着方块边界将它分成两部分,比如你开始将6×8的棋盘分成一个6×3和一个6×5的小棋盘。问你至少需要分多少次,才能将棋盘分成48块小方格。
        </p>
        <p> 13.
          一个单向的环形跑道上有100个随机分布的加油站,每个加油站里有油,合计起来恰好够汽车跑一个整圈。你有一辆汽车,刚开始时汽车油箱是空的,但一遇到加油站就可以加油。问你是否总是可以选择合适的出发点,使得汽车能够跑完一圈?
        </p>
        <p> 14.
          监狱里有7名囚犯。监狱长给这些囚犯每人都带了个帽子,共有七种颜色(分别为彩虹七色)。各个囚犯都能看到其它人的帽子颜色,但看不到自己头上的帽子。之后监狱长让囚犯猜测自己头上的帽子颜色。只要有一人猜对,所有囚犯都会被释放。如果所有人都猜错了,所有囚犯都会被处死。囚犯不能听到其它人的猜测且不能互相交流。问囚犯可以使用什么策略,最大化被释放的概率?
        </p>
        <span id="OSC_h3_2"></span>
        <h3><a name="t1"></a><a name="t1"></a> <span>2.</span>答案 </h3>
        <p> 1. A将盒子加锁寄给B,B再加一把锁送还给A,A将自己的锁取下后再送给B。 </p>
        <p> 2.
          假设开关编号为1、2、3、4。那么首先打开1和2,开1分钟;然后关掉2,打开开关3。迅速进入房间,摸一摸灯泡的热度。如果灯泡是热的,并且灯是亮的,那么答案是1;如果灯泡是热的,灯是熄的,那么答案是2;如果灯泡是冷的,并且灯亮着,那么答案是3;否则答案是4。
        </p>
        <p> 3. 先随便将硬币分为两堆,其中一堆是20枚,另一堆为80枚。然后翻转第一堆所有硬币。 </p>
        <p> 4. 根据已知条件,三个袋子的标签都被标错了,那么正确标签只有两种可能性。只需要从目前标记为混合水果的口袋拿出一个水果,根据这个水果的种类就能区分这两种可能性。 </p>
        <p> 5.
          这个题目有一个隐含的假设,即每个人都无限次甚至随机地被叫入。在这种情况下,任选一人作为观察者,当碰到硬币正面朝下时则翻转硬币,否则什么都不做。其余每个囚犯在被叫入时如果硬币正面朝上则翻转硬币,,而且这49个囚犯只在第一次碰到这种情况时进行操作,其它情况什么都不做。这样当观察者翻了49次硬币之后,他可以确定其余49个囚犯都被叫入过。
        </p>
        <p> 6. 关键是注意1和12是连续的,剩下就简单了。 </p>
        <p> 7. 计算98个数的和与1到100的和的差,即为缺失的两个数的和;计算98个数的平方和与1到100的平方和的差,即为缺失的两个数的平方和;剩下来的工作是解一个两元方程。 </p>
        <p> 8. 从第i个袋子里取出i枚硬币,放在一起秤。其重量与450的差即使与众不同的袋子的编号,重量大于0表示该袋子里的硬币重11克,否则重9克。 </p>
        <p> 9. 从第i个袋子里取出3^(i-1)枚硬币,一起秤重量。不妨设硬币的重量分别为0克、1克和2克。接下来即秤出的重量的三进制表示问题。 </p>
        <p> 10. 不能。假设有x个白人、y个黑人和z个黄种人,那么y+2z被3除的余数是一个不变量。刚开始等于1,它不可能变成0。 </p>
        <p> 11. 100×99/2 = 4950。无论怎么分,最后得到的值都是对数。即任何两个硬币,它们恰好在其中某次被分开并被统计一次。 </p>
        <p> 12. 每分一次碎片数量增加1。一开始是1块,最后是48块。故恰好需要分47次。 </p>
        <p> 13. 假设加油站的油量分别是y1, y2, ..., y100,距离下一个加油站的距离是z1, z2, ..., z100。令xi = yi -
          zi。那么题目所需要的是证明一定存在某个开始位置,使得从该位置开始的任何长度的连续xi序列之和都大于0。我们先取出和最小的连续一段xi,那么从这一段的下一个加油站开始即可。如果该加油站不满足要求的话,从该加油站开始还有和小于0的连续段,这与它前面的和最小的连续段加起来可得到和更小的一段,矛盾。
        </p>
        <p> 14. 假设帽子颜色标号为0到6。 第n名囚犯回答自己的帽子颜色为n减去他看到的其他囚犯的帽子颜色之和,然后再被7除的余数。假设7个人的帽子颜色之和被7除的余数为k,那么第k名囚犯一定答对了。 </p>
      </div>
    </div>
  </div>

  <script>
    function getPDF() {
      let pdf = new jsPDF('', 'pt', 'a4', true) // A4纸,纵向
      let _this = this
      var canvas = document.createElement("canvas");

      let canvasWidth = 525
      let canvasHeight = 800
      // canvas.width = 525; // 将画布宽&&高放大两倍
      // canvas.height = 800;
      // var context = canvas.getContext("2d");
      // context.scale(2, 2);

      var pic_count_Data = []
      for (let i = 0; i < $('.title').length; i++) {
        html2canvas($(".title")[i], {
          // useCORS: true, //看情况选用上面还是下面的,
          scale: '2',
        }).then(function (canvas) {
          var contentWidth = canvasWidth;
          var contentHeight = canvas.height;
          //a4纸的尺寸[525.28,841.89],html页面生成的canvas在pdf中图片的宽高
          window.imgWidth = canvasWidth;
          window.imgHeight = 592 / contentWidth * contentHeight;
          var pageData = canvas.toDataURL('image/jpeg', 1); //后面数值 0到1之间, 数值越小,所绘制出的图像越模糊
          pic_count_Data.push(pageData);
        })
      }

      var timer = setInterval(function () {
        clearInterval(timer);
        for (var j = 0; j < pic_count_Data.length; j++) {
          if (j > 0) {
            pdf.addPage();
          }
          pdf.addImage(pic_count_Data[j], 'JPEG', 30, 20, canvasWidth, canvasHeight);
        }
        // window.blob = pdf.output("blob");
        // window.open(URL.createObjectURL(blob))
        pdf.save('abc.pdf');
      }, 1000)
    }

    function renderOverpage($el) {
      let _this = this;
      let thisHeight = $el.height();
      let totalPage = 0;
      let div = `<div style="border-bottom:1px solid #eee;line-height:70px;margin-bottom: 20px;">页头</div>`
      let div1 = `<div style="border-top:1px solid #eee;line-height:40px;">页尾</div>`
      if (thisHeight > maxheight) {
        totalPage = Math.ceil(thisHeight / 750);
        $el.find(".title-h").attr('style', 'position: relative;height:750px;width:' + window.pWidth * totalPage + 'px;column-count:' + totalPage + '');
        let $bEle = $el.eq(0);
        if (totalPage > 1) {
          for (let w = 1; w <= totalPage; w++) {
            let len = $('.title-h').length
            $bEle.after($bEle.clone()).find('.title-h').css('margin-left', '-' + window.pWidth * (totalPage - w) + 'px')
           $el.eq(len-1).find(".title-h").before(div)
           $el.eq(len-1).find(".title-h").after(div1)
          }
        }
      }
    }
    // 渲染计算页面
    function first_init() {
      let _this = this;
      window.pWidth = 525;
      window.pHeight = 800; //一页pdf显示html页面生成的canvas高度;
      window.maxheight = pHeight - 90 - 70; //每个页面最大的高度
      renderOverpage($('.title'));
    }
    first_init()
  </script>

</body>

</html>
nodejs + puppeteer

参考网站 blog.risingstack.com/pdf-from-ht…
未实践,实践过后放代码

错误示范,只为记录

使用vue模板

使用vue模板代替之前后端使用jinjia模板生成pdf, 之前后端模板生成pdf,只要html能正常显示,就能生成pdf,所以用vue来代替jinjia,万万没想到生成PDF是空白页, 原因是生成pdf不能识别js,没有深入调查

jsPDF

github [ github.com/MrRio/jsPDF ]
doc[ raw.githack.com/MrRio/jsPDF… ]

官方例子只列举了 doc.text() 的用法,接下来就放了一个 Use of UTF-8 / TTF: 意思就是使用ttf 字体包设置一下utf-8编码,其中最关键的一点就是

setFont()的时候,要注意是小写 doc.setFont('notosanscjktc', 'normal') 不要过分相信博客,粘贴别人代码时,注意错别字

能识别中文之后,使用doc.html()分页截屏,然后引入html2canvas 完成分页截屏

html2canvas+jsPDF生成

参考网站: segmentfault.com/a/119000000… (有bug,死循环,并未解决截断问题)