開發歷程:在瀏覽器裡打造一整套 PDF 工具

這是 LocalPapa Notes 開發紀錄系列的第三篇。前兩篇分別寫了最早的 PDF 轉 MarkdownDORI 計算器。這次我們把鏡頭拉到開發順序中的一次重要擴充——一口氣補齊的一整套 PDF 工具

為什麼 PDF 是 LocalPapa 的核心戰場

PDF 是「需要處理、又最不想外流」的檔案類型代表:合約、報價單、病歷、還沒公開的報告。偏偏網路上多數 PDF 工具都要你先把檔案上傳到他們的伺服器。

對 LocalPapa 來說,這正是最該被「瀏覽器化」的領域。除了最早的合併拆分,這次擴充一次補上四個進階功能:旋轉 PDFPDF 轉圖片解鎖 PDF,以及強化版的圖片轉 PDF(支援邊距)

全部,零上傳。

三個函式庫,各司其職

整套 PDF 工具靠三個成熟的瀏覽器端函式庫撐起來,而且都用 CDN 懶載入——只有當你真的打開某個工具,才去抓它需要的函式庫,不拖累首頁:

  • pdf-lib:負責「寫」PDF——建立、修改、輸出(旋轉、加頁、合併都靠它)。
  • pdf.js:負責「讀」PDF——解析與渲染(轉圖片、轉 Markdown 都靠它)。
  • JSZip:負責把多個產出檔打包成一個 .zip 下載。

四個功能,四個小工程

每個功能背後,都有一個值得記下來的小決定:

1. 旋轉 PDF — 角度的讀與寫 讀出每一頁原本的傾斜角度,在畫面上用 Canvas/CSS Transform 視覺化讓你預覽,調整後再透過 page.setRotation 把新角度寫回,導出方位正確的檔案。關鍵是「所見即所得」——你轉的就是最後存的。

2. PDF 轉圖片 — Canvas 當顯影劑pdf.js 以較高品質讀取每一頁的 viewport,序列化畫到 HTML5 Canvas 上,再轉成 .jpeg blob。多頁怎麼辦?用 JSZip 把每頁打包成一個壓縮檔一次下載,不必逐張右鍵存檔。

3. 解鎖 PDF — 用「錯誤」當入口 這個最巧妙。流程是:嘗試處理檔案時主動捕獲「已加密(Encrypted)」的錯誤,藉此判斷需要密碼,於是開放輸入;密碼驗證通過後,直接把內容回存成一份無防護的 PDF。把例外處理變成功能的觸發點。

它解的是「你有密碼、但每次開檔都要重打」的麻煩,不是破解——沒有密碼一樣打不開。

4. 圖片轉 PDF — 邊距是算出來的 強化點在「邊距支援」。在呼叫 pdfDoc.addPage 時,把頁面尺寸動態算成 image.width + margin * 2(高度同理),讓圖片四周留白可調,產出排版更有彈性的 PDF。

學到的事

  • 挑對函式庫,等於解決一半問題。 pdf-libpdf.jsJSZip 各自把「寫/讀/打包」這些硬骨頭啃掉了,我們的工作是把它們組裝成順手的流程。
  • 懶載入是工具聚落的生存法則。 工具越多,越不能讓它們在首頁一起載入。用到才抓,首頁才能一直保持秒開。
  • 例外也可以是功能。 解鎖 PDF 把「加密錯誤」轉成「請輸入密碼」的入口——有時候,攔住錯誤的地方就是功能誕生的地方。
  • 隱私在 PDF 這類檔案上,不是加分項,是必需品。 合約與病歷本來就不該為了轉個檔而上傳到陌生伺服器。

一以貫之的問題

跟系列前兩篇一樣,這套 PDF 工具回答的還是同一個問題:這些原本「好像一定要上傳到伺服器」才能做的進階操作,能不能完全在瀏覽器裡完成?

旋轉、轉圖、解鎖、加邊距——答案全部是:可以。

下一篇開發紀錄,我們會走到讓這一切被搜尋引擎看見的那一步:SPA 改多頁、SEO 架構升級。想追蹤系列?把 LocalPapa Notes 加入書籤吧。

Dev Log: Building a Whole PDF Toolkit in the Browser

This is the third post in the LocalPapa Notes dev-log series. The first two covered the earliest tool, PDF to Markdown, and the DORI Calculator. This time we zoom in on a major expansion in our development timeline — a whole set of PDF tools added in one go.

Why PDF is LocalPapa's core battleground

PDF is the poster child for "files you need to process but least want to leak": contracts, quotes, medical records, unpublished reports. And yet most online PDF tools ask you to upload the file first to their servers.

For LocalPapa, this is exactly the domain that most deserves to be "browser-ified." Beyond the earliest merge and split, this expansion added four advanced features at once: Rotate PDF, PDF to Image, Unlock PDF, and an enhanced Image to PDF with margins.

All of it, zero upload.

Three libraries, clear division of labour

The whole PDF suite stands on three mature client-side libraries — all lazy-loaded via CDN, so they're only fetched when you actually open a tool, never weighing down the homepage:

  • pdf-lib: the "writer" — create, modify, output PDFs (rotation, adding pages, merging).
  • pdf.js: the "reader" — parse and render PDFs (to image, to Markdown).
  • JSZip: bundles multiple outputs into a single .zip download.

Four features, four little engineering decisions

Behind each feature is a small decision worth recording:

1. Rotate PDF — reading and writing angles Read each page's existing rotation, visualise it on a Canvas / CSS Transform so you can preview, then write the new angle back via page.setRotation to export a correctly-oriented file. The key is WYSIWYG — what you rotate is what gets saved.

2. PDF to Image — Canvas as the developer fluid Use pdf.js to read each page's viewport at higher quality, render it onto an HTML5 Canvas, then convert to a .jpeg blob. Multiple pages? JSZip packs them into one archive for a single download — no right-click-save on each page.

3. Unlock PDF — using an "error" as the entry point This one is the cleverest. The flow: while processing the file, we deliberately catch the "Encrypted" error, use that to know a password is required, and open an input for it; once the password verifies, we re-save the content as an unprotected PDF. Exception handling becomes the feature's trigger.

It solves the "you have the password but must retype it every time you open the file" annoyance — not cracking. Without the password, it still won't open.

4. Image to PDF — margins are computed The enhancement here is margin support. When calling pdfDoc.addPage, the page size is computed dynamically as image.width + margin * 2 (and likewise for height), giving adjustable whitespace around the image and a more flexible layout.

What we learned

  • Picking the right libraries solves half the problem. pdf-lib / pdf.js / JSZip each chew through the hard bones of write / read / bundle; our job is assembling them into a smooth flow.
  • Lazy-loading is a survival rule for a tool village. The more tools you have, the less you can afford to load them all on the homepage. Fetch on demand, and the homepage stays instant.
  • An exception can be a feature. Unlock PDF turns "encrypted error" into a "please enter the password" entry point — sometimes the place where you catch the error is where the feature is born.
  • For files like these, privacy isn't a bonus — it's a requirement. Contracts and medical records shouldn't have to be uploaded to a stranger's server just to convert a file.

The same question, again

Like the first two posts, this PDF suite answers the same question: can these advanced operations — the ones that "surely must" require a server upload — be done entirely in the browser?

Rotate, to-image, unlock, add margins — the answer is, every time: yes.

In the next dev log, we reach the step that makes all of this visible to search engines: the SPA-to-multipage SEO overhaul. Want to follow the series? Bookmark LocalPapa Notes.

探索 39+ 個隱私優先的瀏覽器工具
全程本地運算、檔案不上傳。
前往 LocalPapa →
Explore 39+ privacy-first browser tools
Everything runs locally — your files never leave your device.
Visit LocalPapa →

想看英文版?點右上角 EN 切換語言。

Prefer Chinese? Tap at the top-right to switch.