前端產生 PDF

tags: Web
category: Front-End
description: 前端產生 PDF
created_at: 2022/07/10 13:00:00

cover image


前言

最近有先知道未來在交大戊組的工作需要做到的功能,主要是要可以產生 PDF 讓使用者下載,所以先來實驗一下。

以前在寫 Python 的時候就有用過套件改過 PDF , 所以一開始是想說反正一定達的到,只是看怎麼做而已,然後這次想說看能不能單純一點,能在前端做就把 Loading 丟到前端,Loading 丟去給使用者

但是在前端做就會有個不確定性,不確定使用者會使用怎樣的瀏覽器,使用到的工具會不會在使用者那邊就爆炸了。(根據前陣子慘痛的經驗(?))

所以就先來看看待會使用到的工具的瀏覽器支援度吧!

然後我們要用到的套件有幾個


瀏覽器支援度


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>

做個很基本的畫面

cover image

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) cover image


再來使用 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
})

這時你會發現,中文亂碼的問題! (剛剛前一個方式因為是圖片,所以不會有影響) cover image

解決方案的話則要設定支援中文的字體

所以首先你要準備一個支援中文的字體的 .ttf 檔案,然後 jspdf 套件官方有提供一個字體轉換器,可以幫助你使用。

可以把整包專案 clone 下來,然後到他的 /fontconverter ,打開以下畫面

cover image

照著上方的介面選擇你的 .ttf 檔案,之後按下 Create 會下載一個 .js 檔案,再把他一併引入進你的程式。

重要:.ttf 檔案,不是 .ttc 檔案!

引入完成之後,在設定一下使用的 font ,就跟 css 很像,你光載字體沒用,你還是要稍微設定一下。

doc.autoTable({
    html: 'table',
    styles: {
        font: "msjh-normal",
    }
})

而這邊這個 msjh-normal 就是你在 UI 介面上輸入的 fontName

設定好之後,再產生一次就正常了。

cover image


設定樣式之前


會注意到他自動產生的表格,已經有了預設的樣式,但是不見得是我們想要的,不過在開始設定樣式之前,先來看一下其他產生表格的方式的範例,因為也不是每次畫面上都有現成的表格能用。

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,去特別做處理。

例如設定整體 headbodyfoot 或是 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'
            }
        }
    },
})

這樣設定下來,會長這樣:

cover image

能做到這樣的話應該大多情況都能做了,其他更少數情況等碰到在去翻文件吧。




最後更新時間: 2022年07月10日.