😄我再也不用付费使用PDF工具了,我在Web上实现了一个PDF预览/编辑工具

背景是我最近在做一些文档,需要对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应用的便捷性和可访问性。

相关推荐
一 乐4 分钟前
民宿|基于java的民宿推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·论文·源码
sunny-ll10 分钟前
【C++】详解vector二维数组的全部操作(超细图例解析!!!)
c语言·开发语言·c++·算法·面试
testleaf33 分钟前
前端面经整理【1】
前端·面试
好了来看下一题34 分钟前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子35 分钟前
前端八股文-react篇
前端·react.js·前端框架
小前端大牛马41 分钟前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy41 分钟前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
秋田君2 小时前
深入理解JavaScript设计模式之策略模式
javascript·设计模式·策略模式
潘小磊2 小时前
高频面试之11Flink
面试·flink
萌萌哒草头将军3 小时前
🚀🚀🚀VSCode 发布 1.101 版本,Copilot 更全能!
前端·vue.js·react.js