背景是我最近在做一些文档,需要对PDF进行一些加密
、解密
和拆分
等操作,于是我打开了某PDF编辑工具,发现每一个功能都需要付费,给我整的非常闹心。
于是我就准备自己写一个关于PDF的编辑能力。
本文档介绍了一个基于SolidJS、pdfjs-dist和qpdf.wasm实现的Web版PDF预览编辑工具。 该工具允许用户在浏览器中预览PDF文档,并执行基本的编辑操作,如加密、解密和拆分操作。

体验网站:aicut.online:8088/
使用qpdf.wasm实现客户端PDF处理能力,你可以放心使用,所有操作均在你的浏览器本地完成,没有任何隐私安全风险。
- 零服务器依赖:所有PDF处理在浏览器完成
- 接近原生性能:WASM执行效率比JavaScript高5-10倍
- 内存优化:采用分块处理策略避免大文件内存溢出
javascript
const qpdf = await createModule({
locateFile: () => "/qpdf.wasm",
});
技术栈
- SolidJS:高性能响应式前端框架
- pdfjs-dist:Mozilla开源的PDF渲染库
- qpdf.wasm:基于WebAssembly的PDF处理工具
- TypeScript:静态类型检查
- Vite:现代前端构建工具
系统架构
diff
+-----------------------+
| UI组件层 |
| (SolidJS Components) |
+-----------------------+
↓
+-----------------------+
| PDF操作服务层 |
| (pdfjs-dist + qpdf) |
+-----------------------+
↓
+-----------------------+
| 文件/数据处理层 |
| (WASM + ArrayBuffer) |
+-----------------------+
核心功能实现
1. PDF预览功能
使用pdfjs-dist库实现PDF文档的渲染:
typescript
createEffect(() => {
if (!props.pdfUrl) return;
setLoading(true);
pdfjsLib
.getDocument(props.pdfUrl)
.promise.then((doc: any) => {
setPdfDoc(doc);
setPageCount(doc.numPages);
setCurrentPage(1);
// 生成缩略图
const thumbs: string[] = [];
const genThumbs = async () => {
for (let i = 1; i <= doc.numPages; i++) {
const page = await doc.getPage(i);
const viewport = page.getViewport({ scale: 0.18 });
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.width = viewport.width;
canvas.height = viewport.height;
await page.render({ canvasContext: context, viewport }).promise;
thumbs.push(canvas.toDataURL());
}
setThumbnails(thumbs);
};
genThumbs();
setLoading(false);
})
.catch(() => {
toast.error("PDF加载失败");
setLoading(false);
});
});
2. PDF加密
使用qpdf.wasm实现PDF加密的操作:
typescript
const handleEncryptPDF = async () => {
if (!qpdf() || !props.pdfUrl || password() !== confirmPassword()) {
if (password() !== confirmPassword()) {
toast.error("两次密码不匹配");
}
return;
}
setLoading(true);
try {
const response = await fetch(props.pdfUrl);
const arrayBuffer = await response.arrayBuffer();
const inputData = new Uint8Array(arrayBuffer);
qpdf().FS.writeFile("input.pdf", inputData);
qpdf().callMain([
"--encrypt",
password(),
password(),
"256",
"--",
"input.pdf", // 输入文件在前
"output.pdf", // 输出文件,
]);
const output = qpdf().FS.readFile("/output.pdf");
const blob = new Blob([output], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "encrypt.pdf";
a.click();
URL.revokeObjectURL(url);
setShowEncryptModal(false);
} catch (err) {
console.error("Encryption failed:", err);
toast.error("PDF加密失败");
} finally {
setLoading(false);
}
};
3. PDF解密
使用qpdf.wasm实现PDF解密的操作:
tsx
const handleDecryptPDF = async () => {
if (!qpdf() || !props.pdfUrl || !decryptPassword()) return;
setLoading(true);
try {
const response = await fetch(props.pdfUrl);
const arrayBuffer = await response.arrayBuffer();
const inputData = new Uint8Array(arrayBuffer);
qpdf().FS.writeFile("input.pdf", inputData);
qpdf().callMain([
`--password=${decryptPassword()}`,
"--decrypt", // 第一个分隔符 (权限前)
"/input.pdf", // 输入文件
"/output.pdf", // 输出文件
]);
const decryptedData = qpdf().FS.readFile("/output.pdf");
const blob = new Blob([decryptedData], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "decrypted.pdf";
a.click();
URL.revokeObjectURL(url);
setShowDecryptModal(false);
setDecryptPassword("");
} catch (err) {
console.error("Decryption failed:", err);
toast.error("PDF解密失败");
} finally {
setLoading(false);
}
};
4. PDF拆分
使用pdfjs-dist库实现PDF文档的拆分:
typescript
const handleSplitPDF = async (pages: string) => {
if (!qpdf() || !props.pdfUrl || !pages) return;
setLoading(true);
try {
const response = await fetch(props.pdfUrl);
const arrayBuffer = await response.arrayBuffer();
const inputData = new Uint8Array(arrayBuffer);
qpdf().FS.writeFile("input.pdf", inputData);
const args = ["input.pdf", "--pages", ".", pages, "--", "output.pdf"];
qpdf().callMain(args);
const splitData = qpdf().FS.readFile("/output.pdf");
const blob = new Blob([splitData], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "split.pdf";
a.click();
URL.revokeObjectURL(url);
} catch (err) {
console.error("PDF拆分失败:", err);
toast.error("PDF拆分失败");
} finally {
setLoading(false);
}
};
后面我还会持续迭代PDF相关的操作。
总结
本PDF预览编辑工具充分利用了现代Web技术栈的优势:
- SolidJS提供了高效的响应式UI
- pdfjs-dist实现了高质量的PDF渲染
- qpdf.wasm提供了强大的PDF处理能力
- WebAssembly带来了接近原生的性能
这种技术组合使在浏览器中实现复杂的PDF编辑功能成为可能,同时保持了Web应用的便捷性和可访问性。