整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一個支持將html轉為PDF、圖片,且支持PDF加水印的項目

          信首發于微信公眾號「GitHub精選」,歡迎大家關注。

          大家好,我是章魚貓。

          今天給大家推薦的這個開源項目是來自于讀者的投稿,我超級喜歡這個開源項目。尤其是做小程序開發的時候,經常遇到將內容生成圖片分享到朋友圈。這個開源項目就能夠解決你的問題,可以將 html 轉為圖片,還可以轉為 PDF ,還支持加水印。

          這個開源項目就是:Doctron它是基于 Docker、無狀態、簡單、快速、高質量的文檔轉換服務。目前支持將 html 轉為 pdf、圖片 (使用 chrome (Chromium) 瀏覽器內核,保證轉換質量)。支持 PDF 添加水印。

          作者認為目前開源界沒有較好的服務器端 HTML 轉 PDF、圖片的工具,像 wkhtmltopdf、dompdf、mpdf 等這些比較出名的轉換工具,對一些簡單 CSS 樣式的 HTML 轉換能做到不失真,對一些有復雜 CSS 樣式的 HTML 不能做到所見即所得。Doctron 使用 chrome 內核恰巧彌補了這些缺點。

          開源項目作者還提供了體驗網站:

          您可以打開下面的鏈接在線體驗轉換質量,由于服務器配置較低,以及網絡原因,轉換可能會慢一點,實際部署到服務器速度會不一樣。

          項目體驗地址:http://doctron.lampnick.com/

          開源項目特性如下:

          • 使用 chrome 內核保證高質量將 HTML 轉為 pdf / 圖片。
          • 簡易部署 (提供 docker 鏡像,Dockerfile 以及 k8s yaml 配置文件)。
          • 支持豐富的轉換參數。
          • 轉為 pdf 和圖片支持自定義大小。
          • 無狀態服務支持。

          安裝和使用步驟如下:

          開源項目地址:https://github.com/lampnick/doctron

          開源項目作者:lampnick

          公眾號:「GitHub 精選」,值得你關注,每天都分享開源項目,挖掘開源的價值。

          文概述:如何使用 nodejs 在服務端將 html 批量轉成 pdf 并客戶端下載。

          目標一:使用 node 在服務端實現 html 批量轉成 pdf


          分為兩步:

          1. 找段 html 把它轉成 pdf。
          2. 循環執行第一步就達到了批量操作目的。
          • 準備html 片段:<div>111</div>
          • 將html 片段轉 pdf 。

          自己轉(存疑)?或者找個中間件。自己轉的話。。。算了,找插件。

          網上搜到了 html-pdf,看了下周下載量接近 7W,版本迭代 21 個,最近更新時間 latest,不錯,整體滿足心里預期。

          api使用如圖:

          提供了三種接口:文件、stream 流、buffer。不同返回值類型,使用 toFile()需要 2 個參數:文件地址(末尾要有文件名)、回調函數。

          toFile()結果會在指定文件夾生成 pdf 文件;

          toStream()、toBuffer()可以在回調里拿到pdf文件數據流

          目標二:將 PDF 傳到客戶端


          怎么傳輸?

          經過分析歸納,可以濃縮為 3 個點:

          1. 傳輸者:誰是傳輸者?它肯定是個主動行為,就像點擊事件你必須先有一個點擊動作一樣。由此聯想:接口,回調,node,req,res,send,end,前端 success 接收 data,res 浮現而出。
          2. 傳輸方法:原生 res.end(),express 框架 res.send(),res.sendFile(),res.download(),fs 模塊 res.createWriteStream(),管道 pipe()。
          3. 傳輸內容:PDF 文件,stream 流,buffer,二進制流,下載鏈接,base64 碼。

          以下分析可以略過直接到結果:

          分析:測試用的是 express 框架,那么可以排除原生 res.end()(存疑),為啥排除它?有 send 我干嘛還用 end;將 pdf 傳向客戶端又不是下載或寫入文件,排除 createWriteStream()方法;PDF 文件可以自己跑過去嗎?肯定不行!如果啥都不動能不能成功下載文件?也可以。鏈接下載,好像挺萬能。base64 能用嗎?。。。好像是圖片在用!不知道對文件好使不好使(存疑)。

          篩選后:

          傳輸方法:res.send()、管道 pipe();

          傳輸內容:stream 流、buffer、下載鏈接;

          使用下載鏈接:

          經過分析了,下載鏈接肯定能實現這個功能!為什么呢?只要把 pdf 的文件路徑用變量存下來,然后返回給前端,拿著絕對路徑地址模擬點擊下載應該就能實現!但這種方法并沒有在真正意義上跟要傳輸的內容打交道,所以還值得繼續探索。


          最終決定使用,管道pipe()發送stream流輸出PDF文件流到客戶端

          目標三:客戶端可以成功接收并自動下載


          網上搜索有:Windows.open()方法,iframe 等。感覺都有點偏。

          本文使用a標簽實現,其他方法另行嘗試。

          ajax + a標簽

          $.ajax({
              url:'url',
              responseType:'blob',
              success (res) {
                  console.log(res.toString('utf-8'));
                  // 創建 blob 對象,解析流數據
              // , {
                   // 如何后端沒返回下載文件類型,則需要手動設置:type: 'application/pdf;chartset=UTF-8' 表示下載文檔為 pdf,如果是 word 則設置為 msword,excel 為 excel
              //         type: 'application/pdf;chartset=UTF-8'
              //     }
                  const blob = new Blob([res]);
                  const a = document.createElement('a')
              //     // 兼容 webkix 瀏覽器,處理 webkit 瀏覽器中 href 自動添加 blob 前綴,默認在瀏覽器打開而不是下載
                  const URL = window.URL || window.webkitURL
              //     // 根據解析后的 blob 對象創建 URL 對象
                  const herf = URL.createObjectURL(blob)
                  // 下載鏈接
                  // a.href = 'url'
                  a.href = herf
                  // // 下載文件名,如果后端沒有返回,可以自己寫 a.download = '文件.pdf'
                  a.download = '文件.pdf'
                  // document.body.appendChild(a)
                  a.click()
                  // document.body.removeChild(a)
                  // 在內存中移除 URL 對象
                  window.URL.revokeObjectURL(herf)
              }
          })

          傳統jQuery接收數據流后,需要使用new Blob([res])再處理才能繼續使用,

          而,fetch也需要,不過簡便多了

          fetch+ a標簽

          //1.請求
          let res = await fetch([地址]);
          //2.解析
          let data = res.blob()
          //3.創建a標簽
          let eleA = document.createElement('a')
          //4.創建鼠標事件對象
          let e = document.createEvent("MouseEvents")
          //5.初始化事件對象
          e.initEvent("click", false, false)
          ?
          ?
          document.body.appendChild(eleA)
          eleA.download = 'index.zip'
          eleA.href = data
          //給指定的元素,執行事件 click 事件
          eleA.dispatchEvent(e)
          document.body.removeChild(eleA)

          報錯:

          eleA.href直接使用 blob 數據作為下載鏈接是不行的,必須使用 window.URL 對象,果然無知者無畏,更改后的代碼如下:

          //1.請求
          let res=await fetch([地址]);
          //2.解析
          let data=await res.blob();
          const a = document.createElement('a')
          const URL = window.URL || window.webkitURL
          const herf = URL.createObjectURL(data)
          a.href = herf
          a.download = '文件.zip'
          a.click()
          window.URL.revokeObjectURL(herf)

          使用 oFile() 測試

          server.get('/', async (req, res) => {
              toPdf(res);
          });
          ?
          ?
          function toPdf (res) {
              let archive = archiver('zip', {
              zlib: { level: 9 } // 設置壓縮級別
              });
              archive.on('error', function(err){
                  throw err;
              });
              archive.pipe(res);
              for (let i = 0; i < 100; i++) {
                  let html = `<div style="width: 100px;height:100px;background:#fff;color:red;font-size:16px;font-weight:bold">這是第 <span>${i}</span> 個 pdf</div>`;
                  pdf.create(html, options).toFile(`./static/${i}.pdf`, () => {
                       archive.glob('static/*')
                       archive.finalize()
                  })
              }
          }

          看命令窗口偶爾還會報 Queue Closed 錯誤;

          觀察發現程序一邊轉 pdf,一邊下載,而且是按照順序轉換下載有 0.1.2.3...,最后壓縮返回,這個過程循環很少時發現沒問題,但次數增加很多后如20,100次,當for循環到最后一次時,直接執行archiver.finalize(),完。。。結束了,所以造成Queue Closed原因是沒限制archiver.finalize()執行時機???

          找證據:

          官方文檔顯示:

          // finalize the archive (ie we are done appending files but streams have to finish yet)

          // 完成存檔

          // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand

          // 調用此方法后,可能會立即觸發“ close”,“ end”或“ finish”,因此請事先注冊

          archive.finalize();

          果然,,

          添加結束條件:

          pdf.create(html, options).toFile(`./static/${i}.pdf`, () => {
               if (i === 99) {
                 archive.glob('static/*')
                 archive.finalize()
               }
          })

          執行成功,Queue Closed沒出現,并且在瀏覽器自動下載了一個壓縮包,打開后

          發現只有 91 個文件,缺少了最后幾個文件

          for 循環明明執行了 100 次。百思不得解,開始我以為跟網絡有關系,畢竟有傳言只要(網絡)夠快什么(隊列關閉)錯誤都追不上你。后來我發現用筆記本執行程序這個錯誤會頻繁出現,

          查看了 html-pdf 源碼猜測會不會是同步在阻塞,導致循環結束后 PDF 生成文件還未完成導致 Queue Closed,怎么解決呢,閉包!具體原理未知(存疑),

          經過修改

          ((html, i) => {
            pdf.create(html).toFile(`./static/${i}.pdf`, () => {
                  if (i === 49) {
                      archive.glob('static/*')
                      archive.finalize();
                  }
              })
          })(html, i)

          成功!!!打開壓縮文件查看文件總數,

          一切正常,Good!

          使用 toStream()測試

          (function(html, i) {
              pdf.create(html).toStream((err, stream) => {
                  archive.append(stream, { name: `${i}.pdf`});
                  if (i === 99) {
                      archive.finalize();
                  }
              });
          })(html, i)

          我去,第一次跑果然有問題,反復執行多次總是缺少前幾個文件

          第一次跑,少了第一個文件 1.pdf:

          第二次,循環執行 50 次,結果只有 45 個成功,前 5 個失敗:

          命令行打印也證明了這一點。。。每次失敗個數竟然還不同,但還好有個規律它們都是前幾個

          。。。

          繼續執行,竟然還有其他報錯類型。。。無語。。

          大致意思是:輸入資源必須是 Stream 流或者 Buffer,可能是在使用 archiver.append 時塞入了 undefined 之類的,給它上個保險,如果值存在才執行

          stream && archive.append(stream, { name: `${i}.pdf`});

          綜合問題共有 2 個:1.并不是所有 html 片段都進行了轉換 PDF 的操作,可能會隨機出現遺漏,比如 45.46.突然就到 48;2.即使所有 html 都進行了轉換操作,還總是缺少前面幾個文件。

          問題一解決:

          html-pdf 源碼如下圖

          研究發現 html-pdf 的 toStream 應該是一個異步方法,查看源碼后 stream.on('end')也證明了這一點。由于不是順序生成 stream 流,那么最后一個流生成并不代表所有都完成,所以當用 i===99 判斷結束就有問題,可能會跳過某一個不執行轉換PDF需要len=50來避免,然而添加過后,每個文件都進行了PDF轉換,但是stream顯示為undefined,進而PDF文件也總是少前面幾個,導致 i=99 出現時還有好多 toStream 沒有完成,SO 第一個問題就出現了;(注:不過異步操作也因此避免同步帶來方法一的問題:隊列關閉錯誤。)

          原因找到,解決辦法就是添加變量手動強行控制進程:

          如圖,len 初始 100,減為 0 時代表所有都已經轉換,可以結束

          let len = 100;
          然后,開始計數,不到最后一個完成 stream 流轉換不結束
          pdf.create(html).toStream((err, stream) => {
              len--;
              stream && archive.append(stream, { name: `${i}.pdf`});
              if (len === 0) {
                  archive.finalize();
              }
          });

          命令行打印下錯誤信息看看怎么回事控制臺

          問題二解決:

          先打印下錯誤信息 console.log('err:', err)


          暴露了,PDF generation timeout. 一個 timeout 已經夠了,能夠說明很多問題,html-pdf 提供了轉換超時限制,時間超出 timeout 自然無法成功轉換成 stream 流輸出,解決辦法更簡單:

          官方文檔,給出了一個配置項 options:{}對象,其中就有 timeout 設置,我們可以視情況放大此參數,

          // 一分鐘內轉 pdf 不成功,則視為失敗
          let options = {
              timeout: 60000
          }
          pdf.create(html, options).toStream((err, stream) => {
              。。。。。。
          });

          繼續測試,終于完成。。。

          結語:

          1.本文走了許多彎路,踩了多多個坑;

          2.文中標記存疑的地方依然有很多,都是等待去學習理解的地方;

          3.對于名詞、問題的解釋描述不夠精準透徹,需要深度挖掘對知識點的認知;

          4.對問題的解決方式不夠標準、熟練,這才是造成多走彎路的原因;

          5.雖然解決問題才是我們的最終目的,但是仍需追求解決方式的多樣化,找到問題的根源 格物致知 才能給自己醍醐灌頂之感;

          家好,很高興又見面了,我是"高級前端?進階?",由我帶著大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!

          1.什么是 react-print-pdf

          react-print-pdf 使用適用于 PDF 和打印文檔的 React UI 套件構建和生成 PDF。 簡單、可重復使用的組件和模板可創建出色的發票、文檔、小冊子。 同時,允許開發者使用最喜歡的前端框架 React 構建。

          react-print-pdf 使用一系列高質量、無樣式的組件,用于使用 React 和 TypeScript 創建漂亮的 PDF,可以完全替代 docx、latex 等令人痛苦和過時庫。 借助 react-print-pdf,采用一種由開發人員設計并為開發人員設計的 PDF 創建新方法。

          與其他解決方案不同,react-print-pdf 使開發者可以完全控制文檔,允許設計具有腳注、標題、頁邊距等功能的復雜布局。 此外,react-print-pdf 還使開發者能夠跟蹤和分析文檔的特定部分,并使用數據庫中的數據構建和更新圖表。 同時,react-print-pdf 團隊和社區將繼續開發出色的功能來簡化 PDF 生成過程。

          react-print-pdf 的典型特征包括:

          • 易于使用:使用 react-print-pdf 在不到 5 分鐘內構建第一個 PDF
          • 完全開源:完全開源且免費使用
          • 組件和模板:使用 Onedoc 團隊和社區創建的組件和模板列表來啟動下一個文檔
          • 100% 布局控制:與其他解決方案不同,開發者可以完全控制 100% 的布局,包括:邊距、頁眉、頁腳等
          • 將動態數據集成到 PDF:簡化數據庫中的數據并將其無縫集成到 PDF 中

          目前 react-print-pdf 在 Github 通過 Apache-2.0 協議開源,是一個值得關注的前端開源項目。

          2.如何使用 react-print-pdf

          2.1 react-print-pdf 基礎用法

          首先需要安裝相應的依賴:

          npm install @onedoc/react-print
          // npm
          yarn add @onedoc/react-print
          // yarn
          pnpm add @onedoc/react-print
          // pnpm

          接著從預構建組件列表中將需要的組件導入 PDF 模板:

          import {PageTop, PageBottom, PageBreak} from "@onedoc/react-print";

          接著集成組件:

          export const document = ({props}) => {
            return (
              <div>
                <PageTop>
                  <span>Hello #1</span>
                </PageTop>
                <div>Hello #2</div>
                <PageBottom>
                  <div className="text-gray-400 text-sm">Hello #3</div>
                </PageBottom>
                <PageBreak />
                <span>Hello #4, but on a new page ! </span>
              </div>
            );
          };

          2.2 react-print-pdf 支持 CSS

          react-print-pdf 允許將 CSS 添加到文檔,同時安全地解析和轉義。

          但是值得注意的是,雖然可以使用 <style> 標簽添加常規 CSS,但建議使用 CSS 組件來確保正確轉義 CSS,尤其是在使用 URL 或其他潛在不安全內容時。

          import {CSS} from "@onedoc/react-print";
          
          <CSS>{`@page { size: a4 landscape; }`}</CSS>;

          下面是 CSS 的內容:

          @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");
          
          html,
          body {
            font-size: 28px;
            font-family: "Inter", sans-serif;
          }
          
          @page {
            size: A4;
          }

          2.3 react-print-pdf 支持 LaTeX

          react-print-pd 還支持直接在 React 組件中渲染 LaTeX 公式,只需要導入相應的組件即可:

          import {Latex} from "@onedoc/react-print";
          
          <Latex>{String.raw`\frac{1}{2}`}</Latex>;

          2.4 react-print-pdf 支持 Markdown

          在模板中渲染 Markdown 只需要提供一個簡單的 markdown-to-jsx 包裝器。

          Markdown 允許開發者輕松地將內容與布局分開,從而更輕松地維護和更新模板。 開發者可以從 CMS 或其他來源提取內容,并使用 Markdown 對其進行格式化。

          開發者還可以使用自定義組件和變量使 Markdown 更加動態。 例如,可以用自己的組件替換 Markdown 組件,或者使用變量插入動態內容。

          import {Markdown} from "@onedoc/react-print";
          
          <Markdown>{`# Hello, world!
          
          > This is a blockquote
          
          ---
          
          This is a paragraph with a [link](https://google.com)`}</Markdown>;

          值得一提的是,使用 react-print-print 設計的 PDF 可以由首選的文檔管理提供商生成、托管(等等),比如:

          • Onedoc:HTML 到 PDF、云托管、分析等
          • Prince XML:簡單的 HTML 轉 PDF 工具

          3.本文總結

          本文主要和大家介紹 react-print-pdf ,其使用一系列高質量、無樣式的組件,用于使用 React 和 TypeScript 創建漂亮的 PDF,可以完全替代 docx、latex 等令人痛苦和過時庫。因為篇幅問題,關于 react-print-pdf 只是做了一個簡短的介紹,但是文末的參考資料提供了大量優秀文檔以供學習,如果有興趣可以自行閱讀。如果大家有什么疑問歡迎在評論區留言。

          參考資料

          https://github.com/OnedocLabs/react-print-pdf

          https://react.onedoclabs.com/introduction

          https://www.dhiwise.com/post/how-to-simplify-react-pdf-handling-with-react-pdf-renderer


          主站蜘蛛池模板: 久久精品无码一区二区WWW| 日韩精品一区二区三区中文字幕| 理论亚洲区美一区二区三区| 国产一区内射最近更新| 精品视频一区二区三区免费| 成人精品一区久久久久| 久久国产免费一区| 香蕉在线精品一区二区| 国产精品视频一区| 国产激情无码一区二区app| 精品一区精品二区| 亚洲AV无码一区二区乱子仑| 国产精品无码AV一区二区三区| 国产精品亚洲综合一区在线观看| 久久青草精品一区二区三区| 中文字幕在线观看一区| 国产SUV精品一区二区88L| 无码播放一区二区三区| 久久精品一区二区三区资源网| 国产一区二区影院| 无码人妻精一区二区三区 | 国产乱码一区二区三区四| 国产在线不卡一区二区三区| 视频在线一区二区| 国模大胆一区二区三区| 亚洲熟女www一区二区三区| 日本内射精品一区二区视频| 国产一区二区三区乱码| 大屁股熟女一区二区三区| 亚洲欧洲一区二区三区| 精品视频在线观看一区二区| 无码日韩AV一区二区三区| 天天视频一区二区三区| 亚洲AV无码一区二区三区鸳鸯影院| 亚洲AV无码一区二区三区在线| 日韩精品区一区二区三VR| 男女久久久国产一区二区三区| 国产成人无码一区二区在线播放| 狠狠色婷婷久久一区二区| 中文字幕一区二区人妻性色 | 国模私拍福利一区二区|