Html to PDF


更新: 2020-05-19 print css

refer https://www.html.cn/archives/4731

網頁要打印,通常需要寫另一分 css 樣式. 把 px 轉換成 cm 或者 inch

做 pdf 其實也是一樣的道理. 

 

 

更新 : 2020-02-09

升級到 3.1 后發現 asp.net core 要廢棄 NodeService 了

https://github.com/dotnet/aspnetcore/issues/12890

微軟給出的理由是, 當初設計 NodeService 的目的是為了解決 server side render 的問題

而如今他們已經有了其它方案. 卧槽...

然后他們要社區自己維護... 卧槽...說甩就甩

幸好...有個奇人早在 2 年前就已經寫了一個社區版的... 真不明白他怎么想的... 

官方的不用,自己跑去寫...難道有先知 ? 

https://github.com/JeringTech/Javascript.NodeJS

然后就很順利的 migrations 過去了. 

 

 

更新 : 2019-06-27 

后端制作好 pdf 后,下一個工作就是讓前端下載或者 print 了. 

下載可以用 <a href="123.pdf" download="abc.pdf" > 的方式

print 的話, 可以用 iframe

const a = document.createElement('a');
a.href = '123.pdf';
a.download = 'abc.pdf';
a.click();

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.src = 'abc.pdf';
iframe.onload = () => {
  iframe.focus();
  iframe.contentWindow!.print();
};

比較常遇到的問題是跨域, 

iframe 不支持 allow origin 所以我們不可以像處理 ajax 那樣去跨域

<a> 不會出現 error 但是不會下載只會開多一個 new tab

一個通用的方法是先用 ajax + allow origin 去把 pdf 拿下來. 獲取到 Blob 

然后用 createObjectUrl 來實現

const result = await this.httpClient.get('http://192.168.1.152:61547/123.pdf', {
  observe: 'response',
  responseType: 'blob'
}).toPromise();

const url = window.URL.createObjectURL(result.body);
const a = document.createElement('a');
a.href = url;
a.download = '123.pdf';
a.click();

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.src = url;
iframe.onload = () => {
  iframe.focus();
  iframe.contentWindow!.print();
};

這樣就是本地資源了. 

針對 <a> 還有一個方法就是不要使用靜態資源路徑, 你改成比如... /api/download-file 然后后端返回 file 是可以下載的哦. 

refer : 

https://stackoverflow.com/questions/28318017/html5-download-attribute-not-working-when-downloading-from-another-server-even

https://github.com/crabbly/Print.js/issues/95

https://segmentfault.com/a/1190000015597029

 

 

做企業項目,經常會需要保存或輸出 pdf 格式。

比如 invoice, purchase order 等等。

pdf 是 90 年 adobe 公司設計的, 08 年開放. 

制作 pdf 不難,但是互聯網時代,大多數文本都用 html 格式來寫. 

要弄一份一模一樣的 pdf 出來就 double work 了。

於是就有了 html to pdf 的需求。

市場上已經有好多公司做這個出來賣錢了。

比如: 

https://www.nrecosite.com/pdf_generator_net.aspx#

https://www.paperplane.app/pricing

價格都不便宜呢.

html 是 93 年問世的, 很長一段時間, 游覽器解析引擎都是閉源的. 所以要做 html to pdf 是蠻累的,要解析 html css。

直到 04 年蘋果開源了 safari 的引擎 webkit。 

就有人基於 webkit 做出了 webkit html to pdf

https://wkhtmltopdf.org/

只要安裝 webkit 然后通過 commond 調用一下就可以把 html 變成 pdf 文件了,開源的哦。

.net https://github.com/rdvojmoc/DinkToPdf 就是基於 wkhtmltopdf 的封裝

有了前車之鑒, 后來的 phantomjs 也有 html to pdf 的功能

jsreport 早年就是基於 phantomjs  實現的 to html  pdf 功能 

https://github.com/jsreport/jsreport 

https://github.com/jsreport/jsreport-dotnet (.net 版本)

一直到 2017 年, chromium 推出了 headless 版. 

chromium 是 google 基於 webkit 的 folk, 現在 chrome 用着的是 blink, chromuim 算是前身吧. 

這個 chromium headless 可厲害了, google 出品, phantomjs 作者隨后就宣布不在維護了,讓位給 chromium.

chromium 自然是可以實現 html to pdf 的了. jsreport 后來的版本也切換到 chromium 了. 

chromium 的底層接口不太友好,google 自己又推出了 Puppeteer 

這是 js 的庫, 可以用 nodejs 運行, 它是 chromium 友好的 api. 幾行代碼就可以實現各做游覽器行為. 當然就包括了 html to pdf。

.net core 和 nodejs 是好朋友, 要在 core 調用 nodejs 的 package 是很簡單的. 

所以又有了 https://github.com/kblok/puppeteer-sharp

我就是使用 asp.net core node service + Puppeteer  來實現 html to pdf 的.

 

上面說的都是 server side 的做法

client side 也有一個做法.

用的是一個叫 jsPDF 的插件, 它是做 pdf 的插件, 而不是 html to pdf 

實現手法是通過打開 browser 使用 url data:application/pdf 的方式輸出 base64 

游覽器支持這種 data 直接打開 file 的功能,不只 pdf, image 也是可以這樣打開.

如果要 html to pdf 可以配上一個 html to canvas 的插件 

把 html  轉成 canvas 然后變成圖片在輸入進 pdf  ( 當然這樣文字就變成不可以 search 了 )

目前這個方案問題還是比較多的, 比如 htmltocanvas 依然處於不穩定版本,而且維護也很弱. 

 

 

記入一下 Puppeteer  的一些東西

module.exports = function (callback, bodyTemplate, format, headerTemplate, footerTemplate) {
    const puppeteer = require('puppeteer');
    (async () => {
        const browser = await puppeteer.launch();
        const page = await browser.newPage();

        //await page.goto('https://www.stooges.com.my');
        //await page.screenshot({ path: 'example.png' });
        //await page.goto('https://www.stooges.com.my', { waitUntil: 'networkidle2' });

        await page.setContent(htmlTemplate, { waitUntil: 'networkidle2' });
        await page.pdf({
            //printBackground: true,
            path: 'demo.pdf',
            format: format, //format or follow width 792 & height 1121, got footer gap... bug 
            landscape: (format === 'A5') ? true : false,
            margin: { top: 125, right: 16, bottom: 60, left: 32 },// 左右會縮小比例, 上下不會; 如果寬沒有hit 到paper的寬就不會縮小; top bottom min 21 for display element
            displayHeaderFooter: true,
            headerTemplate: headerTemplate, //header no backgroung color
            footerTemplate: footerTemplate
        });
        await browser.close();
        //console.log(process.version); // check node version 
        callback(null);
    })();
}

流程大概就是開一個 browser, 開一個 page, 然后訪問你要的網址或者直接寫入 html 

然后調用 pdf 方法. 

當你設定 format A4 時, 你的 width height 就自動設定了, 因該是 792x1121px

header 的開始時 margin 21, 也就是說如果你的 header 內容時 50, 那么你得要 set margin 71 才可以哦

還有一個比較嚴重的是 header 和 footer 的字體和 width 都會比 body 大

比如一個 div 在 header 和 body 都  set 500px, 但是出來的效果就是 header 比較大 

大多少呢? 大概是 4/3 . github 還有 issues 討論這個鬼呢. 

更新 : 2020-05-19 上次忘了記入 github follow 了 

refer : 

https://github.com/puppeteer/puppeteer/issues/4132 (header default margin)

https://github.com/puppeteer/puppeteer/issues/3672 (header default margin)

https://github.com/puppeteer/puppeteer/issues/1853 (header 比較大)

 

 

最后說一說 node_modules 的事兒.

我也是做前端的, 開發前端多少會接觸 node_modules 

這里說說,前端和后端的小區別. 

1. 后端不需要打包

前端依賴的庫也是存放在 node_modules 里的, 然后通過 webpack 去打包出來

做后端就不需要去打包了,打包的目的是為了減少下載, 在服務端 require 另一個模塊是很快的. 

2. node_modules 需要在 server side 

前端打包以后,我們的服務器是不需要存在 node_modules 這個文檔的,但是如果是 nodejs 就需要 

我們對 node_modules 的印象是...很大很大,很多文件. 

但其實很多是 dev 才需要的, 所以在 server side npm install 的時候記得寫上 --production

只使用 puppeteer 的話,大概 3x 個 files 而已.

3. puppeteer 自帶的 chromium 

安裝 puppeteer 時它會去安裝一個 chromium, 放在 node module 里. 

如果有多個項目,你可以做一個 share 的 chromium 

調用的時候放一個 path 就可以了, 我們可以通過 Environment Variables 來設置這些 (比如要不要 skip download chromium 和 chromium 的路徑等等...)

const browser = await puppeteer.launch({
    executablePath: 'C:/keatkeat/my projects/asp.net core/2.2/html-to-pdf/Project/.local-chromium/win64-669486/chrome-win/chrome.exe'
});

 

 

 

參考資源 : 

https://wkhtmltopdf.org/

https://www.nrecosite.com/pdf_generator_net.aspx#

https://github.com/wkhtmltopdf/wkhtmltopdf

https://github.com/rdvojmoc/DinkToPdf

https://github.com/jsreport/jsreport

https://github.com/jsreport/jsreport-dotnet

https://github.com/kblok/puppeteer-sharp

https://www.paperplane.app/blog/modern-html-to-pdf-conversion-2019?utm_source=medium&utm_campaign=redirect

https://www.paperplane.app/pricing

https://juejin.im/entry/5ac1e7c05188257ddb0fc853

https://blog.risingstack.com/pdf-from-html-node-js-puppeteer/

https://zhaoqize.github.io/puppeteer-api-zh_CN/#/

https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions

http://www.rotisedapsales.com/snr/cloud2/website/jsPDF-master/docs/jsPDF.html

https://github.com/niklasvh/html2canvas

https://dev.to/damcosset/generate-a-pdf-from-html-and-back-on-the-front-5ff5

https://developers.google.com/web/updates/2017/04/headless-chrome

https://zhuanlan.zhihu.com/p/33015883

https://www.jianshu.com/p/db1b230e3415

https://stackoverflow.com/questions/564650/convert-html-to-pdf-in-net

https://www.quora.com/What-is-the-best-HTML-to-PDF-converter-tool

https://www.reddit.com/r/dotnet/comments/7i6ljt/what_is_currently_the_best_way_to_convert_html_to/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM