前端產生 PDF
tags: Web
category: Front-End
description: 前端產生 PDF
created_at: 2022/07/10 13:00:00
前言
最近有先知道未來在交大戊組的工作需要做到的功能,主要是要可以產生 PDF
讓使用者下載,所以先來實驗一下。
以前在寫 Python
的時候就有用過套件改過 PDF
, 所以一開始是想說反正一定達的到,只是看怎麼做而已,然後這次想說看能不能單純一點,能在前端做就把 Loading
丟到前端,把 。Loading
丟去給使用者
但是在前端做就會有個不確定性,不確定使用者會使用怎樣的瀏覽器,使用到的工具會不會在使用者那邊就爆炸了。(根據前陣子慘痛的經驗(?))
所以就先來看看待會使用到的工具的瀏覽器支援度吧!
然後我們要用到的套件有幾個
- html2canvas: 將
html
轉成canvas
- jspdf: 在
js
生成PDF
- jspdf-autotable:
jspdf
的一個plugin
,可以幫忙產生表格
瀏覽器支援度
html2canvas
- Firefox 3.5+
- Google Chrome
- Opera 12+
- IE9+
- Edge
- Safari 6+
jspdf
- Firefox 3+
- Chrome
- Safari 3+
- Opera.
- IE9
而jspdf-autotable
的部分,因為他是基於 jspdf
的插件,所以我先姑且相信他的支援度也不差(?),因為好像也沒特別寫他的瀏覽器支援度。
我相信上面這樣的瀏覽器支援度應該沒問題..(吧)XD
看了之後放心了很多,就可以開始用了。
先使用 cdn 稍微玩一下
引入相關套件
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.js"></script>
<script src="https://unpkg.com/[email protected]/dist/jspdf.plugin.autotable.js"></script>
做個很基本的畫面
HTML:
<button>產生快照</button>
<hr>
<table>
<thead>
<tr>
<th colspan="4">
測試表格
</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
</tr>
<!-- ... -->
<!-- 自己看要重複貼幾列 -->
</tbody>
<tfoot>
<tr>
<td colspan="4">我是表格的頁尾</td>
</tr>
</tfoot>
</table>
CSS:
table {
border-collapse: collapse;
}
thead th {
background-color: #39f;
color: #fff;
}
td,
th {
border: 1px solid #333;
padding: .25em .75em;
}
tfoot td {
text-align: right;
font-size: .5em;
}
先玩 html2canvas
現在先假設我希望直接把上面的表格貼到 PDF
中,所以我們必須先生成一張快照,再把那張照片貼去 PDF
。
所以你應該先監聽按鈕的事件,再做處理。
// 抓出對應的 DOM
const btn = document.querySelector('button')
const table = document.querySelector('table')
// 監聽點擊事件
btn.addEventListener('click', () => {
// table 是要被轉成 canvas 的元素
html2canvas(table).then(canvas => {
// 會拿到一個繪製好的 canvas,這邊直接把他丟到 body 下看看
document.body.append(canvas)
})
})
當你按下按鈕之後,應該會看到多了一個一模一樣的表格,但是他是圖片(canvas
)
再來使用 jspdf
把下面這一段丟到點擊事件中,當你按下按鈕應該就會下載檔案了。
const doc = new jspdf.jsPDF({
unit: 'px', // 設定單位是 px
format: 'a4' // 設定大小 a4
})
// 在 PDF 放入圖片,格式是 JPEG,放在 x 與 y 的第 10 單位(上方設定的px)
doc.addImage(canvas.toDataURL(), 'JPEG', 10, 10)
// 下載,檔名為 ExportFile.pdf
doc.save('ExportFile.pdf')
這樣子就可以很單純的把你在網頁上的畫面輸出到 PDF
但是有個小問題,因為他是圖片,所以沒有辦法複製,如果要複製就只能自己慢慢建了,所以像是要表格,就可以使用第三個套件。
開始使用 jspdf-autotable
使用這個就可以輕鬆的在你的 PDF
中建立表格,而且他還可以直接讀取 DOM
的文字,像是下面這樣使用。
// doc.addImage(...) // 把這個註解掉,替換成下行
doc.autoTable({
html: 'table', // 放 css selector,只是因為我這邊剛好只有一個 table
})
這時你會發現,中文亂碼的問題! (剛剛前一個方式因為是圖片,所以不會有影響)
解決方案的話則要設定支援中文的字體
所以首先你要準備一個支援中文的字體的 .ttf
檔案,然後 jspdf
套件官方有提供一個字體轉換器,可以幫助你使用。
可以把整包專案 clone
下來,然後到他的 /fontconverter
,打開以下畫面
照著上方的介面選擇你的 .ttf
檔案,之後按下 Create
會下載一個 .js
檔案,再把他一併引入進你的程式。
重要: 是 .ttf
檔案,不是 .ttc
檔案!
引入完成之後,在設定一下使用的 font
,就跟 css
很像,你光載字體沒用,你還是要稍微設定一下。
doc.autoTable({
html: 'table',
styles: {
font: "msjh-normal",
}
})
而這邊這個 msjh-normal
就是你在 UI
介面上輸入的 fontName
。
設定好之後,再產生一次就正常了。
設定樣式之前
會注意到他自動產生的表格,已經有了預設的樣式,但是不見得是我們想要的,不過在開始設定樣式之前,先來看一下其他產生表格的方式的範例,因為也不是每次畫面上都有現成的表格能用。
doc.autoTable({
head: [
[{
content: '測試表格',
colSpan: 4
}]
],
body: [
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
[1, 2, 3, 4],
],
foot: [
[{
content: '我是表格的頁尾',
colSpan: 4
}]
],
styles: {
font: "msjh-normal",
}
})
基本上就是原本的 html
這個屬性值被拆成了三個去定義(雖然你也可能不需要這麼細)
開始設定樣式
設定一些基本的樣式可以直接在物件的定義中設定,但是在某些比較特殊的情況會需要用到一些 Hook
,去特別做處理。
例如設定整體 head
、 body
、 foot
或是 Column
的顏色,可以直接在特定的 styles
當中下。
但是如果今天是要設定特定列或是特定格子,那們你就需要用到 Hook
。
doc.autoTable({
html: 'table', // 節省篇幅,使用 css selector
styles: {
font: "msjh-normal",
},
headStyles: { // 設定 head 的樣式
fillColor: '#39f', // 背景色
textColor: '#fff' // 文字顏色
},
columnStyles: { // 設定 column 的樣式
0: { // 第 0 欄的樣式
halign: 'center' // 左右置中
},
2: { // 第 2 欄的樣式
fillColor: '#af9', // 背景顏色
},
},
didParseCell(data) { // 當套件解析完資料時會進來,可以改內容或樣式
if (data.row.index === 3) { // 如果當前在 index = 3 的 row
if (data.column.index === 1) { // 如果在 index = 1 的 column
// 覆寫樣式 ...
data.cell.styles.textColor = [255, 255, 255]
data.cell.styles.fillColor = '#8733ff'
} else {
// 覆寫樣式 ...
data.cell.styles.textColor = [255, 255, 255]
data.cell.styles.fillColor = '#e15353'
}
}
},
})
這樣設定下來,會長這樣:
能做到這樣的話應該大多情況都能做了,其他更少數情況等碰到在去翻文件吧。