JavaScript基于pdf-lib實現(xiàn)圖片轉(zhuǎn)PDF工具
這篇只講功能層 JavaScript 實現(xiàn)。這個工具是我用 Vue 組織頁面交互,再配合 pdf-lib 在瀏覽器端完成 PDF 生成。pdf-lib 是公開可用的通用開源庫(MIT 許可),可直接用于瀏覽器和 Node.js。核心流程是:
上傳圖片 -> 讀取并建模 -> 調(diào)整順序/旋轉(zhuǎn) -> 計算頁面與布局 -> 寫入 PDF -> 下載
在線工具網(wǎng)址:https://see-tool.com/image-to-pdf
工具截圖:\

0)先說下 pdf-lib 在這個工具里的角色
pdf-lib 在這里主要負責(zé)三件事:創(chuàng)建 PDF 文檔、把圖片字節(jié)嵌入文檔、按給定坐標(biāo)繪制到頁面。
它的調(diào)用模型很直接:
create/load -> addPage/embed -> drawImage -> save
對圖片轉(zhuǎn) PDF 這類場景,它有幾個很實用的點:
- API 穩(wěn)定,瀏覽器端可直接運行,不依賴后端轉(zhuǎn)換
- 頁面坐標(biāo)和尺寸控制細,適合做“邊距、縮放、居中、鋪滿”這類排版
- 同一套邏輯可擴展到“合并現(xiàn)有 PDF、寫文本、填表單”等后續(xù)功能
這個工具里最核心的一步是 embedJpg + drawImage:前者把二進制圖片數(shù)據(jù)放進 PDF,后者決定圖片在頁面中的位置和顯示尺寸。只要前面把頁面尺寸和布局參數(shù)算對,最終導(dǎo)出的版式就會穩(wěn)定可控。
1)狀態(tài)模型:圍繞“圖片隊列 + 轉(zhuǎn)換配置”組織
工具初始化后維護一個狀態(tài)對象,核心字段如下:
images:當(dāng)前待轉(zhuǎn)換圖片隊列settings:頁面尺寸、方向、布局、邊距、文件名isConverting:是否正在生成,防止重復(fù)點擊
每張圖片對象都會保存:name、size、format、preview(dataURL)、width、height、rotation。這樣后續(xù)排序、旋轉(zhuǎn)、預(yù)覽和生成 PDF 都可以直接復(fù)用同一份數(shù)據(jù)。
2)文件接入:先過濾,再讀取 DataURL 和尺寸
上傳與拖拽最后都進入同一入口,先只保留圖片類型,再做數(shù)量上限控制。每個文件會經(jīng)過兩步:
FileReader.readAsDataURL讀成 DataURL- 用
Image對象加載,拿到naturalWidth/naturalHeight
核心讀取函數(shù):
function readFileAsDataUrl(file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function (event) {
resolve(
String(event.target && event.target.result ? event.target.result : ""),
);
};
reader.onerror = function () {
reject(new Error("file_read_failed"));
};
reader.readAsDataURL(file);
});
}讀取成功后把圖片對象追加進隊列,頁面統(tǒng)計和預(yù)覽區(qū)會立即重渲染。
3)頁面尺寸計算:統(tǒng)一轉(zhuǎn)換到 PDF 點單位
PDF 里統(tǒng)一使用 point(pt),因此先做單位換算:
MM_TO_PT = 72 / 25.4PX_TO_PT = 72 / 96
固定紙張(A4/A3/Letter/Legal)先由毫米轉(zhuǎn) pt;如果用戶選“原始尺寸”,則按圖片像素轉(zhuǎn) pt。方向為 auto 時,會根據(jù)圖片寬高自動判定橫版或豎版。
function getPageSizePt(pageSize, orientation, imageWidth, imageHeight) {
var widthPt;
var heightPt;
if (pageSize === "original") {
widthPt = imageWidth * PX_TO_PT;
heightPt = imageHeight * PX_TO_PT;
} else {
var pageSizeValue = PAGE_SIZES_MM[pageSize] || PAGE_SIZES_MM.a4;
widthPt = pageSizeValue.width * MM_TO_PT;
heightPt = pageSizeValue.height * MM_TO_PT;
}
// 省略方向交換邏輯
return {
widthPt: widthPt,
heightPt: heightPt,
orientation: finalOrientation,
};
}
4)布局算法:contain / cover / center
每頁先算內(nèi)容區(qū):
contentWidth = pageWidth - margin * 2
contentHeight = pageHeight - margin * 2
然后按布局模式計算繪制矩形:
contain:完整顯示圖片,按最小縮放比適配cover:鋪滿內(nèi)容區(qū),按最大縮放比適配center:不縮放,居中繪制
當(dāng)邊距過大導(dǎo)致內(nèi)容區(qū)寬高小于等于 0 時,直接拋錯,轉(zhuǎn)換流程會給出明確提示。
5)旋轉(zhuǎn)處理:先畫到 Canvas,再統(tǒng)一轉(zhuǎn) JPEG
為了讓 pdf-lib 寫入穩(wěn)定,工具會先把當(dāng)前圖片(含旋轉(zhuǎn)角度)繪制到 canvas,再導(dǎo)出 JPEG 字節(jié)。角度支持 0/90/180/270。
function drawImageToJpeg(image, rotation) {
var angle = Number(rotation || 0);
var swapSide = angle === 90 || angle === 270;
var canvas = document.createElement("canvas");
canvas.width = swapSide ? image.naturalHeight : image.naturalWidth;
canvas.height = swapSide ? image.naturalWidth : image.naturalHeight;
var context = canvas.getContext("2d", { alpha: false });
context.fillStyle = "#ffffff";
context.fillRect(0, 0, canvas.width, canvas.height);
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate((angle * Math.PI) / 180);
context.drawImage(image, -image.naturalWidth / 2, -image.naturalHeight / 2);
return parseDataUrl(canvas.toDataURL("image/jpeg", 0.92));
}
這里先鋪白底,可以避免透明圖在 PDF 中出現(xiàn)黑底。
6)PDF 生成:逐頁 addPage + embedJpg + drawImage
轉(zhuǎn)換時按圖片隊列順序循環(huán)處理,每張圖先算頁面尺寸和布局,再寫入 PDF:
async function buildPdfBlobWithPdfLib(pages, PDFDocument) {
var pdfDocument = await PDFDocument.create();
for (var i = 0; i < pages.length; i += 1) {
var item = pages[i];
var page = pdfDocument.addPage([item.pageWidthPt, item.pageHeightPt]);
var jpg = await pdfDocument.embedJpg(item.imageBytes);
page.drawImage(jpg, {
x: item.xPt,
y: item.yPt,
width: item.drawWidthPt,
height: item.drawHeightPt,
});
}
var pdfBytes = await pdfDocument.save();
return new Blob([pdfBytes], { type: "application/pdf" });
}
最終通過對象 URL 觸發(fā)瀏覽器下載,文件名會先做非法字符清洗。
7)交互動作如何影響最終 PDF
工具里“上移/下移/拖拽排序/旋轉(zhuǎn)/刪除”都直接改 images 隊列,因此最終 PDF 頁序與圖片列表始終一致。
- 排序:交換數(shù)組元素或拖拽后
splice - 旋轉(zhuǎn):
rotation = (rotation + 90) % 360 - 刪除:按
id過濾
由于生成時是按隊列順序遍歷,交互結(jié)果會無縫反映到導(dǎo)出文件。
8)轉(zhuǎn)換過程控制:狀態(tài)鎖 + 分階段提示
點擊“轉(zhuǎn)換”后會先進入 isConverting = true:
- 禁用上傳、配置項和操作按鈕
- 顯示“正在生成第 X / N 頁”
- 完成后恢復(fù)可操作狀態(tài)
錯誤按場景給出明確反饋,比如無圖片、文件名為空、邊距過大、生成失敗。整條鏈路是純前端閉環(huán):從讀取、排版到導(dǎo)出,都在瀏覽器端完成。
以上就是JavaScript基于pdf-lib實現(xiàn)圖片轉(zhuǎn)PDF工具的詳細內(nèi)容,更多關(guān)于JavaScript pdf-lib圖片轉(zhuǎn)PDF的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序?qū)崿F(xiàn)云開發(fā)上傳文件、圖片功能
在使用小程序的過程中,在編輯個人資料時,通常會面臨上傳頭像、上傳背景圖片的情況,而這個開發(fā)過程需要怎樣實現(xiàn)呢?這篇文章主要給大家介紹了關(guān)于微信小程序?qū)崿F(xiàn)云開發(fā)上傳文件、圖片功能的相關(guān)資料,需要的朋友可以參考下2022-12-12
基于MVC4+EasyUI的Web開發(fā)框架形成之旅之界面控件的使用
一般Web界面包括的界面控件有:單行文本框、多行文本框、密碼文本框、下拉列表Combobox、日期輸入控件、數(shù)值輸入控件、單項選擇、復(fù)選框、表格控件DataGrid、樹形控件、布局控件、彈出式對話框、提示信息、列表控件等,這些界面控件的操作都有哪些不同,下面逐一介紹2015-12-12
js實現(xiàn)鼠標(biāo)感應(yīng)圖片展示的方法
這篇文章主要介紹了js實現(xiàn)鼠標(biāo)感應(yīng)圖片展示的方法,實例分析了javascript鼠標(biāo)事件及樣式的操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-02-02
小程序input數(shù)據(jù)雙向綁定實現(xiàn)方法
這篇文章主要介紹了小程序input數(shù)據(jù)雙向綁定實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

