BentoPDF - 隐私优先的浏览器端免费 PDF 工具箱

2 阅读6分钟

项目标题与描述

BentoPDF 是一个强大、以隐私为先、客户端运行的 PDF 工具套件,支持自托管。它允许您直接在浏览器中操作、编辑、合并和处理 PDF 文件,无需服务器端处理,确保您的文件始终保持安全和私密。

项目的核心目标是提供一个完全免费、无需注册、无限使用且尊重用户隐私的 PDF 处理解决方案。所有工具都 100% 在浏览器中运行,文件永不离开您的设备,结合开源(AGPL-3.0)和商业双重许可模式,适合个人和企业使用。

功能特性

基于对项目代码的分析,BentoPDF 提供了极其丰富的功能,主要特性包括:

  • 100+ 种 PDF 工具:涵盖 PDF 处理的全方位需求,工具被组织为多个清晰的类别。
  • 隐私优先,完全本地处理:所有处理都在您的浏览器中完成,文件不会上传到任何服务器,确保了数据的绝对私密性。
  • 100% 免费,无需注册:所有工具均可免费无限次使用,无需创建账户或登录。
  • 支持离线使用:应用可作为渐进式 Web 应用 (PWA) 安装,并支持服务端缓存,提供离线功能。
  • 多语言国际化 (i18n):支持包括英语、中文、德语、法语、西班牙语、越南语等在内的十余种语言,并提供完整的翻译和语言切换机制。
  • 简洁模式 (Simple Mode):为组织内部部署提供选项,可隐藏所有品牌和营销内容,只展示核心 PDF 工具,提供更清爽的界面。
  • 键盘快捷键支持:为常用操作提供了全面的键盘快捷键,提升操作效率。
  • 完善的工具集
    • PDF 转换器:支持在 PDF 与 Word、Excel、JPG、PNG、文本、Markdown 等 40 多种格式之间相互转换。
    • PDF 编辑器:提供压缩、旋转、裁剪、添加水印、页眉页脚、页码、背景色等 25 种以上编辑功能。
    • PDF 合并与拆分:轻松合并多个 PDF 文件,或将单个 PDF 拆分为多个文件,支持页面提取、删除、重排等。
    • PDF 安全工具:提供加密、解密、数字签名、权限管理、移除元数据、线性化等安全功能。
  • 灵活部署:支持通过 Docker 容器、Docker Compose、静态网站 (Netlify, Vercel, GitHub Pages) 等多种方式轻松自托管。

安装指南

BentoPDF 提供了多种安装和部署方式,以适应不同的使用场景。

系统要求

  • 现代浏览器(支持 WebAssembly)
  • Node.js(用于从源码构建)
  • Docker(用于容器化部署)

部署方式

1. 使用 Docker(最简单快捷)

从 Docker Hub 拉取预构建的镜像并运行:

# 标准模式(包含完整品牌和营销内容)
docker run -p 3000:8080 bentopdf/bentopdf:latest

# 简洁模式(仅显示工具界面,适合内网部署)
docker run -p 3001:8080 bentopdf/bentopdf-simple:latest

访问 http://localhost:3000http://localhost:3001 即可使用。

2. 使用 Docker Compose

创建 docker-compose.yml 文件:

version: '3.8'
services:
  bentopdf:
    image: bentopdf/bentopdf:latest # 或 bentopdf/bentopdf-simple:latest
    container_name: bentopdf
    restart: unless-stopped
    ports:
      - '8080:8080'
    security_opt:
      - no-new-privileges:true

然后运行 docker-compose up -d

3. 作为静态网站部署

BentoPDF 可以部署在任何静态网站托管服务上:

  • Netlify:手动上传发布包或连接 GitHub 仓库自动部署。
  • Vercel:导入 Git 仓库并选择 Vite 框架预设。
  • GitHub Pages:通过 GitHub Actions 工作流自动构建和部署。 项目每个版本都提供预构建的 dist-{version}.zip 文件,下载解压后即可上传至静态主机。

4. 从源码构建和运行(用于开发)

# 克隆仓库
git clone https://github.com/alam00000/bentopdf.git
cd bentopdf

# 安装依赖
npm install

# 开发模式运行(标准模式)
npm run dev

# 开发模式运行(简洁模式)
npm run serve:simple

# 构建生产版本
npm run build

使用说明

基础使用

  1. 访问您部署的 BentoPDF 实例。
  2. 在主页面,您可以看到按类别组织的所有工具。您可以直接点击工具卡片,或使用顶部的搜索框快速查找工具。
  3. 选择工具后,按照界面提示上传文件、配置选项,然后开始处理。
  4. 处理完成后,结果文件将直接在浏览器中下载。

语言切换

BentoPDF 支持多语言。页脚区域内置了语言切换器,点击后可以从下拉列表中选择您偏好的语言。语言设置会保存在本地。

键盘快捷键

BentoPDF 提供了便捷的键盘快捷键以提升效率:

  • Escape:返回工具网格视图。
  • Ctrl/Cmd + K:聚焦工具搜索框。
  • Ctrl/Cmd + /:显示/隐藏快捷键帮助面板。
  • 在特定工具界面(如编辑器)中,还支持方向键翻页等更多快捷键。

API 概览

BentoPDF 是一个前端应用,没有传统后端 API。其核心功能通过集成多个 WebAssembly 库(如 pdf.js, pymupdf-wasm, gs-wasm)在浏览器中实现。对于数字签名等需要网络请求的功能,项目提供了一个可选的 Cloudflare Worker 代理 (cors-proxy-worker.js) 来处理跨域请求。

项目的构建和配置基于 Vite,并使用了多个插件:

  • @tailwindcss/vite: 用于 Tailwind CSS。
  • vite-plugin-handlebars: 用于 HTML 模板和局部组件(如导航栏、页脚)。
  • vite-plugin-static-copy: 用于复制静态资源。
  • 自定义的 languageRouterPlugin: 用于开发环境下的多语言路由重写。

核心代码

以下是项目中一些核心模块的代码示例,展示了其关键实现。

1. 应用状态管理 (src/js/state.js)

此模块集中管理了应用的核心状态,确保在不同工具间切换时状态清晰。

// src/js/state.js
export const state = {
  activeTool: null,     // 当前激活的工具ID
  files: [],            // 用户上传的文件列表
  pdfDoc: null,         // 当前加载的PDF.js文档对象
  pdfPages: [],         // 文档的页面代理对象数组
  currentPdfUrl: null,  // 当前PDF文件的Blob URL
};

// 重置状态,通常在切换工具或完成操作时调用
export function resetState() {
  state.activeTool = null;
  state.files = [];
  state.pdfDoc = null;
  state.pdfPages = [];
  state.currentPdfUrl = null;
  document.getElementById('tool-content').innerHTML = '';
}

2. 国际化 (i18n) 语言检测 (src/js/i18n/i18n.js)

此函数根据URL路径或本地存储来确定当前应使用的语言。

// src/js/i18n/i18n.js (节选)
export const getLanguageFromUrl = () => {
  const basePath = import.meta.env.BASE_URL.replace(/\/$/, '');
  let path = window.location.pathname;

  // 处理基础路径(如部署在子目录时)
  if (basePath && basePath !== '/' && path.startsWith(basePath)) {
    path = path.slice(basePath.length) || '/';
  }

  // 从URL路径匹配语言代码
  const langMatch = path.match(
    /^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)(?:\/|$)/
  );
  if (langMatch && supportedLanguages.includes(langMatch[1])) {
    return langMatch[1];
  }

  // 回退到本地存储的语言设置
  const storedLang = localStorage.getItem('i18nextLng');
  if (storedLang && supportedLanguages.includes(storedLang)) {
    return storedLang;
  }

  // 默认返回英语
  return 'en';
};

3. Vite 配置中的语言路由中间件 (vite.config.ts)

在开发服务器中,此中间件负责将类似 /de/tools 的请求重写为 /tools.html,并注入语言上下文,实现无刷新语言切换。

// vite.config.ts (节选)
function createLanguageMiddleware(isDev: boolean) {
  return (req, res, next) => {
    const basePath = getBasePath();
    let pathname = req.url.split('?')[0];

    // 剥离基础路径
    if (basePath && basePath !== '/' && pathname.startsWith(basePath)) {
      pathname = pathname.slice(basePath.length) || '/';
    }

    const langMatch = pathname.match(LANG_REGEX);
    if (langMatch) {
      const [, lang, rest] = langMatch;
      // 重写请求到对应的 .html 文件
      req.url = `${basePath}/${rest || 'index'}.html`;
      // 将语言信息附加到请求对象,供后续插件使用
      (req as any).language = lang;
    } else if (PAGES.has(pathname.slice(1).replace(/\.html$/, ''))) {
      // 处理无语言前缀的页面请求,附加默认语言
      (req as any).language = 'en';
    }
    next();
  };
}

4. 服务端脚本 - 版本发布 (scripts/release.js)

此脚本自动化了项目的版本发布流程,包括更新版本号、创建 Git 标签、构建和打包。

#!/usr/bin/env node
// scripts/release.js (节选)
function main() {
  const type = process.argv[2] || 'patch'; // 接受 major, minor, patch 参数

  console.log(`🚀 Releasing ${type} version...`);

  // 1. 更新 package.json 中的版本号
  const newVersion = updateVersion(type);

  // 2. 更新 HTML 文件中的版本号显示
  execSync('npm run update-version', { stdio: 'inherit' });

  // 3. 提交版本变更
  execSync('git add package.json *.html src/pages/*.html', { stdio: 'inherit' });
  execSync(`git commit -m "Release v${newVersion}"`, { stdio: 'inherit' });

  // 4. 创建 Git 标签
  const tagName = createGitTag(newVersion);

  // 5. 构建并打包发行文件
  execSync('npm run package', { stdio: 'inherit' }); // 这会生成 dist-{version}.zip

  // 6. 推送代码和标签到远程仓库
  execSync('git push origin main', { stdio: 'inherit' });
  execSync(`git push origin ${tagName}`, { stdio: 'inherit' });

  console.log(`🎉 Release v${newVersion} complete!`);
  console.log(`📦 Docker image: bentopdf/bentopdf:${newVersion}`);
  console.log(`📦 Distribution: dist-${newVersion}.zip`);
}

gqq1IFjQTrOralpsd4SvLFV2TeMkbWkR3xz9WAatxU0=