跳到正文
DEV Community· Muhaymin Bin Mehmood·· 4 小時前AI 評分52

如何完全在瀏覽器內把一個資料夾的 iPhone 相片轉成一份 PDF

Turning a Folder of iPhone Photos Into One PDF, Entirely in the Browser

AI 導讀

BatchSet 的免費 HEIC to PDF 工具可在瀏覽器內將 iPhone 相片合成一份 PDF,並支援調整頁序及選擇 Auto、A4 或 Letter。工具按檔案開首 12 bytes 判斷 HEIC,而非依賴副檔名,並將圖片轉為 JPEG;無法讀取的檔案會單獨標示,讓其餘圖片繼續處理。PDF 頁面是圖片,沒有 OCR,因此不能搜尋或複製其中的文字。

正文

"Scan these receipts and send me one PDF." Most people do it with their phone camera, so they end up with 12 HEIC photos and a deadline. Building the tool that turns those photos into one PDF in the browser is a nice small project. Here are the parts that took real thought.

1. Decide by bytes whether a file is HEIC

Chrome can't draw HEIC on a canvas, so HEIC photos need decoding first. But you can't trust the extension: a HEIC renamed to .jpg happens, and so does the reverse. So the input step sniffs the first 12 bytes (ftyp plus a HEIC brand) and only sends real HEIC files to the decoder:

async function decodeForPdf(file: File, pool: HeicDecoder): Promise<Blob | null> {
  if ((await sniffImageKind(file)) !== "heic") return null // the canvas can draw it as is
  const res = await pool.convert(file, { format: "jpg", quality: 90, metadata: "none" })
  return res.blob
}

Note metadata: "none". A PDF page doesn't carry EXIF anyway, and dropping it also drops the GPS location iPhones embed. People sending receipts to an accountant rarely mean to include their home coordinates.

(The HEIC decoding itself is a story of its own: native decode in Safari, libheif WASM elsewhere, and one broken method in libheif-js.)

2. Let people fix the page order

Phones sort photos by time, and people take them out of order. A PDF tool without reordering produces a PDF people have to redo.

Keep the order as plain data and make "move" a pure function:

function moveTo<T extends { id: string }>(list: T[], id: string, index: number): T[] {
  const from = list.findIndex((x) => x.id === id)
  if (from === -1) return list.slice()
  const to = Math.max(0, Math.min(list.length - 1, index))
  const out = list.slice()
  const [item] = out.splice(from, 1)
  out.splice(to, 0, item)
  return out
}

Drag-and-drop calls it, and so do up/down arrow buttons. The buttons matter for keyboard users and on phones, where dragging in a long list is painful.

3. Watch the page size

With pdf-lib, the easy path makes each page exactly as big as its image:

const img = await doc.embedJpg(jpegBytes)
const page = doc.addPage([img.width, img.height])
page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height })

PDF units are points, 72 per inch. A 4032×3024 iPhone photo becomes a page about 56 by 42 inches. It looks fine zoomed to fit on screen, then someone prints it.

So offer real paper sizes, turn the page to match each photo, and fit the photo inside with its aspect ratio kept:

let pw: number, ph: number
if (pageSize === "a4") {
  ;[pw, ph] = iw > ih ? [PageSizes.A4[1], PageSizes.A4[0]] : PageSizes.A4 // landscape photo, landscape page
} else {
  ;[pw, ph] = [iw, ih] // "auto": page matches the image
}
const page = doc.addPage([pw, ph])
const s = Math.min(pw / iw, ph / ih)
page.drawImage(img, { x: (pw - iw * s) / 2, y: (ph - ih * s) / 2, width: iw * s, height: ih * s })

Each page gets its own orientation, so a mix of portrait receipts and one landscape whiteboard photo all fill their pages.

4. Embed JPEG, even for PNG input

embedJpg puts the JPEG bytes into the PDF as they are, with no re-compression. embedPng has to decode the PNG and store the pixels with lossless compression. For photos that makes the PDF several times bigger. So every page is converted to JPEG first. Screenshots with sharp text can go at a higher JPEG quality.

5. One bad photo shouldn't sink the PDF

With 20 files, one is usually odd: a corrupt download, a Live Photo export the decoder doesn't like, a PDF someone dropped by mistake. Failing the whole job at file 17 is the worst outcome.

Instead, mark that one file as unreadable in the list, keep the rest, and let the user decide whether to remove it or try another copy. A clear "1 file couldn't be read" beats an error dialog and an empty result.

6. What this PDF is not

The pages are pictures. You can't search or copy the text in them, because nothing ran OCR. For receipts going to a person that's usually fine. For anything that needs search, see this earlier post on what "searchable" really requires.

Try it

This is how BatchSet's free HEIC to PDF tool works: drop iPhone photos, drag them into order, choose Auto, A4 or Letter, and download one PDF. For screenshots and other PNGs there's PNG to PDF with the same engine. Nothing leaves the browser, which is the point when the photos are receipts or IDs.

This article was drafted with an AI assistant from the code of a production tool. The snippets are taken from that code.

來源:DEV Community · dev.to