本工具借助AI重构自信创PDF工具箱(XCPDF)V7.1,所有功能均以可读、无混淆的JavaScript重新实现,js只存放未改动的原版第三方库(PDF.js、JSZip),所有定制与功能脚本放在js/modules并以 myPDF_ 前缀命名。
所有操作均在浏览器本地完成,文件不会上传到任何服务器。
原版作者:相声,感谢其开源精神。原作链接
为符合版规,去除原作中外网链接、二维码等相关表述。
原版(7.1)存在的问题:
1、字体不嵌入的 PDF 打开显示不正常,可对比网盘中“未嵌入字体.pdf”
有些 PDF 为了减小体积,不嵌入字体,只写一个字体名加一个预定义 CMap 编码名。原版没有内置所需的对照字典,结果是文本提取/复制出来全是乱码,页面渲染整页空白或方块乱码。重构版内置了全套 CMap,这类文件可正常显示与提取。(原版加盖图章、骑缝章等导入“未嵌入字体.pdf”实测只显示部分内容)。注:Word 等导出的 PDF 自带字体,不受此问题影响,两版表现一致。
2、无内容安全策略(CSP),注入脚本可直接执行,可对比网盘中“打开百度.pdf“
原版对恶意 PDF 的字体注入没有任何拦截,打开构造好的文件时,其中的代码会被执行(原版加盖图章、骑缝章等24个模块导入“打开百度.pdf”,联网时会自动打开百度网页,断网时会显示网页打不开,但跳转动作本身已证明代码被执行了,另有5个模块点击按钮触发跳转)。
重构版采用双重防护:关闭代码动态编译 + 页面级 CSP 门禁,同样的文件打开后无任何反应。
3、加盖图章、骑缝章不符合实际需求
原版:加盖图章、骑缝章导出后,图章其实是一张贴上去的“贴纸”,用 Acrobat 等专业软件可以移动、删除。
重构版:在加盖图章、骑缝章中新增“导出不可编辑PDF”,把盖章后的页面栅格化为图片,再重新拼成 PDF,每页都是一张图片,分辨率自适应,扫描页保持原页面像素,矢量页按 300dpi 输出。

html代码
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
- <!-- ============================================================================
- 内容安全策略(CSP):安全加固,请勿删除
- ----------------------------------------------------------------------------
- 这里刻意不包含 'unsafe-eval',因此浏览器会拒绝一切 eval / new Function 动态执行。
- pdf.js 2.x 存在 CVE-2024-4367(恶意 PDF 借字体 FontMatrix 注入 JS,注入点就是
- new Function 编译字形指令);本应用已在 js/modules/myPDF_pdf.worker.js 里关闭 isEvalSupported,
- CSP 再兜底一层:即使将来有代码重新打开该开关,注入的代码也无法被编译执行。
- 其余规则用于放行离线运行所需的本地脚本、内联样式、data:/blob: 图片与 Worker。
- ========================================================================== -->
- <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: blob: file:; script-src 'self' 'unsafe-inline' data: blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data: blob:; worker-src blob: data:; child-src blob:; connect-src 'self' data: blob: file:; object-src 'none'; base-uri 'none'">
- <title>离线PDF工具箱</title>
- <style>
- /* ============================================================================
- 基础样式(与原 XCPDF V7.1 界面保持一致的视觉规范)
- ========================================================================== */
- :root { --bg: #f1f5f9; --card-bg: #ffffff; --primary: #2563eb; --primary-hover: #1d4ed8; --primary-light: #eff6ff; --text: #0f172a; --text-secondary: #475569; --border: #e2e8f0; --danger: #dc2626; --success: #16a34a; --shadow-sm: 0 1px 3px rgba(0,0,0,0.06); --shadow: 0 10px 25px -5px rgba(0,0,0,0.10), 0 8px 10px -6px rgba(0,0,0,0.05); --radius-sm: 8px; --radius: 12px; --radius-lg: 16px; --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1); --font: system-ui, -apple-system, 'Segoe UI', Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif; }
- * { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:var(--font); background:var(--bg); color:var(--text); min-height:100vh; line-height:1.5; }
- [url=home.php?mod=space&uid=945662]@media[/url] (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; scroll-behavior: auto !important; } }
- .header { background:var(--card-bg); border-bottom:1px solid var(--border); padding:0 24px; position:sticky; top:0; z-index:100; box-shadow:var(--shadow-sm); }
- .header-inner { max-width: 1350px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; min-height: 60px; width: 100%; }
- .logo { font-weight:700; font-size:1.3rem; color:var(--text); white-space:nowrap; flex-shrink:0; cursor:pointer; display:flex; align-items:center; gap:8px; }
- .logo img { width:30px; height:30px; }
- .tab-nav { display:flex; gap:4px; background:#f1f5f9; border-radius:28px; padding:4px; flex-wrap:nowrap; flex-shrink:0; }
- .tab-btn { padding:8px 16px; border-radius:24px; border:none; cursor:pointer; font-size:0.85rem; font-weight:500; background:transparent; color:var(--text-secondary); transition:var(--transition); white-space:nowrap; font-family:var(--font); }
- .tab-btn.active { background:#fff; color:var(--primary); box-shadow:0 1px 3px rgba(0,0,0,0.06); font-weight:600; }
- .tab-btn:hover:not(.active) { color:var(--text); background:rgba(255,255,255,0.6); }
- .main-container { max-width:1400px; margin:0 auto; padding:24px; }
- .tab-panel { display:none; }
- .tab-panel.active { display:block; }
- .card { background:var(--card-bg); border-radius:var(--radius-lg); box-shadow:var(--shadow); padding:28px; margin-bottom:24px; }
- .card-title { font-size:1.15rem; font-weight:700; margin-bottom:20px; display:flex; align-items:center; gap:10px; color:var(--text); }
- .icon-dot { width:6px; height:22px; background:var(--primary); border-radius:3px; display:inline-block; flex-shrink:0; }
- .card-grid { display:grid; grid-template-columns:repeat(10, minmax(0,1fr)); gap:10px; margin-bottom:28px; }
- @media (max-width:1100px) { .card-grid { grid-template-columns:repeat(5, minmax(0,1fr)); } }
- @media (max-width:640px) { .card-grid { grid-template-columns:repeat(2, minmax(0,1fr)); } }
- .tool-card { background:var(--card-bg); border:1px solid var(--border); border-radius:var(--radius); padding:12px 6px; text-align:center; cursor:pointer; transition:var(--transition); user-select:none; }
- .tool-card:hover { transform:translateY(-3px); box-shadow:var(--shadow); border-color:var(--primary); }
- .tool-icon { font-size:1.45rem; margin-bottom:6px; display:flex; justify-content:center; }
- .tool-name { font-weight:600; font-size:0.82rem; margin-bottom:3px; }
- .tool-desc { font-size:0.7rem; color:var(--text-secondary); line-height:1.35; }
- .btn { padding:10px 20px; border-radius:var(--radius-sm); border:1px solid var(--border); background:var(--card-bg); color:var(--text); font-size:0.9rem; cursor:pointer; transition:var(--transition); font-family:var(--font); }
- .btn:hover { border-color:var(--primary); color:var(--primary); }
- .btn-primary { background:var(--primary); color:#fff; border-color:var(--primary); font-weight:600; }
- .btn-primary:hover { background:var(--primary-hover); color:#fff; }
- .btn-danger { background:var(--danger); color:#fff; border-color:var(--danger); font-weight:600; }
- .btn-danger:hover { background:#b91c1c; color:#fff; }
- .btn-sm { padding:6px 12px; font-size:0.8rem; }
- .btn:disabled { opacity:0.5; cursor:not-allowed; }
- .upload-area { border:2px dashed var(--border); border-radius:var(--radius); padding:36px 20px; text-align:center; cursor:pointer; transition:var(--transition); margin-bottom:14px; background:var(--primary-light); }
- .upload-area:hover, .upload-area.drag-over { border-color:var(--primary); background:var(--primary-light); transform:scale(1.01); }
- .primary-text { font-size:1rem; font-weight:600; color:var(--primary); margin-bottom:8px; display:flex; align-items:center; justify-content:center; gap:6px; flex-wrap:wrap; }
- .secondary-text { font-size:0.8rem; color:var(--text-secondary); }
- .file-list { max-height:300px; overflow-y:auto; margin-bottom:14px; }
- .file-item { display:flex; align-items:center; gap:10px; padding:10px 14px; background:var(--card-bg); border:1px solid var(--border); border-radius:var(--radius-sm); margin-bottom:8px; cursor:grab; transition:var(--transition); }
- .file-item:hover { border-color:var(--primary); }
- .file-item.dragging { opacity:0.4; border-style:dashed; }
- .file-item.drag-over-top { box-shadow:0 -2px 0 var(--primary); }
- .file-item.drag-over-bottom { box-shadow:0 2px 0 var(--primary); }
- .file-name { flex:1; font-size:0.85rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
- .file-size { font-size:0.75rem; color:var(--text-secondary); flex-shrink:0; }
- .empty-message { text-align:center; padding:24px; color:var(--text-secondary); font-size:0.85rem; }
- .progress { width:100%; height:8px; background:var(--border); border-radius:4px; overflow:hidden; margin:10px 0; }
- .progress-bar { height:100%; background:var(--primary); border-radius:4px; transition:width 0.3s; width:0%; }
- /* 带"轨道 + 填充 + 百分比文字"的进度条(扇形骑缝章等模块使用) */
- .progress-wrap { display:flex; align-items:center; gap:10px; width:100%; }
- .progress-wrap .progress-bar { flex:1; height:8px; width:auto; background:var(--border); border-radius:4px; overflow:hidden; transition:none; }
- .progress-fill { display:block; height:100%; width:0%; background:var(--primary); border-radius:4px; transition:width 0.25s; }
- .progress-text { font-size:0.85rem; color:var(--text-secondary); min-width:40px; text-align:right; flex-shrink:0; font-variant-numeric:tabular-nums; }
- .status-message { margin-top:12px; padding:10px 14px; border-radius:var(--radius-sm); background:var(--primary-light); color:var(--text); font-size:0.85rem; line-height:1.6; word-break:break-all; }
- .usage-note { background:var(--primary-light); border-radius:var(--radius-sm); padding:14px 16px; font-size:0.85rem; color:var(--text-secondary); }
- .split-layout { display:grid; grid-template-columns:1fr 1.2fr; gap:24px; }
- @media (max-width:900px) { .split-layout { grid-template-columns:1fr; } }
- .form-row { display:flex; align-items:center; gap:10px; margin-bottom:14px; flex-wrap:wrap; }
- .form-label { font-size:0.85rem; font-weight:600; color:var(--text); min-width:90px; flex-shrink:0; }
- input[type=text], input[type=number], input[type=password], select, textarea { padding:9px 12px; border:1px solid var(--border); border-radius:var(--radius-sm); font-size:0.85rem; font-family:var(--font); background:var(--card-bg); color:var(--text); transition:var(--transition); outline:none; }
- input:focus, select:focus, textarea:focus { border-color:var(--primary); }
- input[type=color] { width:42px; height:36px; border:1px solid var(--border); border-radius:6px; padding:2px; cursor:pointer; background:var(--card-bg); }
- input[type=range] { accent-color:var(--primary); cursor:pointer; }
- input[type=checkbox] { width:16px; height:16px; accent-color:var(--primary); cursor:pointer; flex-shrink:0; }
- label { cursor:pointer; }
- .preview-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:12px; }
- .preview-item { border:1px solid var(--border); border-radius:var(--radius-sm); padding:8px; text-align:center; background:var(--card-bg); transition:var(--transition); position:relative; }
- .preview-item:hover { box-shadow:var(--shadow-sm); border-color:var(--primary); }
- .preview-item canvas, .preview-item img { max-width:100%; border-radius:4px; cursor:pointer; }
- .page-thumb { width:100%; display:block; background:#e2e8f0; }
- .bookmark-tree-item { margin-bottom:2px; }
- .bookmark-tree-label { display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:var(--radius-sm); transition:background 0.15s; }
- .bookmark-tree-label:hover { background:var(--primary-light); }
- .bookmark-tree-title { flex:1; font-size:0.85rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
- .bookmark-tree-page { font-size:0.75rem; color:var(--text-secondary); flex-shrink:0; }
- .bookmark-tree-children { margin-left:18px; }
- .loading-spinner { width:26px; height:26px; border:3px solid #e2e8f0; border-top-color:var(--primary); border-radius:50%; animation:spin 0.8s linear infinite; flex-shrink:0; }
- @keyframes spin { to { transform:rotate(360deg); } }
- .loading-overlay { position:fixed; inset:0; background:rgba(15,23,42,0.45); z-index:9998; display:flex; align-items:center; justify-content:center; }
- .shake-once { animation:shake 0.3s; }
- @keyframes shake { 0%,100%{transform:translateX(0);} 25%{transform:translateX(-6px);} 75%{transform:translateX(6px);} }
- .emoji-icon { width:1.35em; height:1.35em; vertical-align:-0.25em; }
- .back-home-btn { display:inline-flex; align-items:center; gap:6px; margin-bottom:16px; }
- .global-preview-overlay { position:fixed; inset:0; background:rgba(15,23,42,0.9); z-index:10000; align-items:center; justify-content:center; flex-direction:column; padding:40px; }
- @media (max-width:700px) { .header-inner { flex-direction:column; padding:10px 0; gap:8px; } .main-container { padding:14px; } .card { padding:18px; } }
- </style>
- </head>
- <body>
-
- <!-- 顶部导航 -->
- <header class="header">
- <div class="header-inner">
- <div class="logo">
- <span>离线PDF工具箱</span>
- </div>
- <div style="font-size:0.8rem; color:var(--text-secondary); margin-top:6px; line-height:1.4;">
- 重构自<strong>信创PDF工具箱(XCPDF)V7.1</strong>,所有功能均以<strong>可读、无混淆</strong>的JavaScript重新实现,所有操作均在浏览器本地完成
- </div>
- <nav class="tab-nav">
- <button class="tab-btn active" data-tab="tab-home">🏠 所有工具</button>
- <button class="tab-btn" data-tab="tab-about">☕ 关于</button>
- </nav>
- </div>
- </header>
-
- <div class="main-container">
- <!-- 首页:由 js/modules/myPDF_app.js 依据各模块注册信息自动生成 -->
- <div class="tab-panel active" id="tab-home"></div>
-
- <!-- 关于 -->
- <div class="tab-panel" id="tab-about">
- <div class="card" style="max-width:900px; margin:0 auto;">
- <div class="card-title"><span class="icon-dot"></span>关于</div>
- <div style="line-height:2; color:var(--text-secondary); font-size:1rem;">
- <p>本工具重构自<strong>信创PDF工具箱(XCPDF)V7.1</strong>,所有功能均以<strong>可读、无混淆</strong>的JavaScript重新实现,<strong>js</strong>只存放未改动的原版第三方库(pdf.js、JSZip),所有定制与功能脚本放在<strong>js/modules</strong>并以 <code>myPDF_</code> 前缀命名。</p>
- <p>所有操作均在浏览器本地完成,文件不会上传到任何服务器。</p>
- <p><strong>原版作者:相声</strong>,感谢其开源精神。</p>
- </div>
- </div>
- </div>
-
- <!-- 40 个功能模块面板:由 js/modules/myPDF_app.js 创建 -->
- <div id="module-panels"></div>
- </div>
-
- <!-- 全局加载遮罩 -->
- <div class="loading-overlay" id="loading-overlay" style="display:none;">
- <div style="display:flex;align-items:center;background:#fff;padding:20px 30px;border-radius:24px;box-shadow:0 12px 32px rgba(0,0,0,0.12);">
- <div class="loading-spinner"></div>
- <span id="loading-text" style="margin-left:12px;font-weight:500;">处理中...</span>
- </div>
- </div>
-
- <!-- 全局图片预览层 -->
- <div id="global-preview-overlay" class="global-preview-overlay" style="display:none;">
- <div style="position:absolute; top:16px; left:50%; transform:translateX(-50%); display:flex; gap:8px; align-items:center; max-width:90%;">
- <span id="global-preview-caption" style="color:#fff; font-size:0.9rem; margin-right:6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;"></span>
- <div id="global-preview-toolbar" style="display:flex; gap:6px;"></div>
- </div>
- <button id="global-preview-close" class="btn btn-sm" style="position:absolute; top:16px; right:16px;">✕ 关闭</button>
- <button id="global-preview-prev" style="position:absolute; left:20px; top:50%; transform:translateY(-50%); font-size:2rem; line-height:1; color:#fff; background:rgba(255,255,255,0.15); border:none; border-radius:50%; width:52px; height:52px; cursor:pointer;">‹</button>
- <img id="global-preview-img" style="max-width:88%; max-height:82%; object-fit:contain; box-shadow:0 0 40px rgba(0,0,0,0.6); border-radius:6px; transition:opacity .15s;">
- <button id="global-preview-next" style="position:absolute; right:20px; top:50%; transform:translateY(-50%); font-size:2rem; line-height:1; color:#fff; background:rgba(255,255,255,0.15); border:none; border-radius:50%; width:52px; height:52px; cursor:pointer;">›</button>
- <div id="global-preview-index" style="position:absolute; bottom:20px; color:#fff; font-size:0.95rem; background:rgba(0,0,0,0.4); padding:4px 12px; border-radius:12px;"></div>
- </div>
-
- <!-- ===== 第三方库:纯官方、未改动的原版依赖(原样保留在 js/ 根目录) ===== -->
- <script src="js/pdf.min.js"></script> <!-- pdf.js 2.16.105 官方构建:PDF 解析与渲染 -->
- <script src="js/jszip.min.js"></script> <!-- JSZip 3.10.2 官方构建:批量导出打包 zip -->
-
- <!-- ===== 定制层:改写/定制的公用脚本(统一放在 js/modules/,文件名带 myPDF_ 前缀) ===== -->
- <script src="js/modules/myPDF_pdf.cmaps.js"></script> <!-- CJK 字符映射表(必须早于 myPDF_pdf.worker.js 加载,以便注入自定义 CMap 读取工厂) -->
- <script src="js/modules/myPDF_pdf.worker.js"></script> <!-- pdf.js Worker(base64 内嵌 + CMap 中文支持配置) -->
- <script src="js/modules/myPDF_pdf-lib.min.js"></script> <!-- pdf-lib 1.17.x 定制加密分支:PDF 写入 / 修改 / 加密解密 -->
- <script src="js/modules/myPDF_icons.js"></script> <!-- SVG 图标雪碧图 -->
- <script src="js/modules/myPDF_common.js"></script> <!-- 公共工具库 + 模块注册表 -->
-
- <!-- ===== 功能模块(每个文件一项功能,注册到 MyPDF) ===== -->
- <script src="js/modules/myPDF_bates.js"></script>
- <script src="js/modules/myPDF_blank.js"></script>
- <script src="js/modules/myPDF_blueprint.js"></script>
- <script src="js/modules/myPDF_booklet-split.js"></script>
- <script src="js/modules/myPDF_booklet.js"></script>
- <script src="js/modules/myPDF_ca-sign.js"></script>
- <script src="js/modules/myPDF_compress.js"></script>
- <script src="js/modules/myPDF_convert.js"></script>
- <script src="js/modules/myPDF_crop-margin.js"></script>
- <script src="js/modules/myPDF_delete.js"></script>
- <script src="js/modules/myPDF_encrypt.js"></script>
- <script src="js/modules/myPDF_extract-images.js"></script>
- <script src="js/modules/myPDF_filter.js"></script>
- <script src="js/modules/myPDF_flatten.js"></script>
- <script src="js/modules/myPDF_img2pdf.js"></script>
- <script src="js/modules/myPDF_impose.js"></script>
- <script src="js/modules/myPDF_manage.js"></script>
- <script src="js/modules/myPDF_mark.js"></script>
- <script src="js/modules/myPDF_merge-alt.js"></script>
- <script src="js/modules/myPDF_merge.js"></script>
- <script src="js/modules/myPDF_meta.js"></script>
- <script src="js/modules/myPDF_pad-even.js"></script>
- <script src="js/modules/myPDF_pagenum.js"></script>
- <script src="js/modules/myPDF_perf-stamp.js"></script>
- <script src="js/modules/myPDF_remove-blank.js"></script>
- <script src="js/modules/myPDF_remove-bookmarks.js"></script>
- <script src="js/modules/myPDF_reverse.js"></script>
- <script src="js/modules/myPDF_rotate-single.js"></script>
- <script src="js/modules/myPDF_rotate.js"></script>
- <script src="js/modules/myPDF_scan-effect.js"></script>
- <script src="js/modules/myPDF_skew.js"></script>
- <script src="js/modules/myPDF_split-book.js"></script>
- <script src="js/modules/myPDF_split-bookmark-explode.js"></script>
- <script src="js/modules/myPDF_split-bookmark.js"></script>
- <script src="js/modules/myPDF_split.js"></script>
- <script src="js/modules/myPDF_stamp.js"></script>
- <script src="js/modules/myPDF_unify.js"></script>
- <script src="js/modules/myPDF_unlock.js"></script>
- <script src="js/modules/myPDF_watermark-image.js"></script>
- <script src="js/modules/myPDF_watermark-text.js"></script>
-
- <!-- ===== 应用框架(最后加载) ===== -->
- <script src="js/modules/myPDF_app.js"></script>
- </body>
- </html>
复制代码
下载地址
https://github.com/wallechfox/offline-pdf-tools
https://wwazg.lanzoub.com/b00q1tvw9a
密码:4mse