在 Node.js 中把 Office 文档转成 PDF 或 PNG
34.3/100
综合分
6
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/9/30 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add deepseek-ai/dsh-libreoffice-kit该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · other
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 31 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@deepseek-ai/libreoffice-kit-workspace(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22.19.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 06:43:44
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件
查看扫描证据(共 136 处,展示前 105 条)
- 读取本地文件benchmarks/convert.mjs:22
const job = JSON.parse(await readFile(values['child-job'], 'utf8')); - 读取本地文件benchmarks/convert.mjs:36
const pdf = await readFile(outputPath); - 写入本地文件benchmarks/convert.mjs:43
await writeFile(join(job.output, 'result.json'), `${JSON.stringify({ status: 'complete', - 写入本地文件benchmarks/convert.mjs:53
await mkdir(output, { recursive: true }); - 读取本地文件benchmarks/convert.mjs:54
const all = JSON.parse(await readFile(manifestPath, 'utf8')); - 写入本地文件benchmarks/convert.mjs:68
await writeFile(join(output, 'environment.json'), `${JSON.stringify({ timestamp: new Date().toISOString(), - 读取本地文件benchmarks/convert.mjs:79
assert.equal(hash(await readFile(input)), fixture.sha256, 'Input fixture changed.'); - 写入本地文件benchmarks/convert.mjs:86
await mkdir(directory); - 写入本地文件benchmarks/convert.mjs:90
await writeFile(jobPath, JSON.stringify(job)); - 读取本地文件benchmarks/convert.mjs:95
try { Object.assign(outcome, JSON.parse(await readFile(join(directory, 'result.json'), 'utf8'))); } - 写入本地文件benchmarks/convert.mjs:98
await appendFile(join(output, 'samples.jsonl'), `${JSON.stringify(outcome)}\n`); - 删除本地文件递归benchmarks/font-cache-formats.mjs:43
} finally { await rm(cache, { recursive: true, force: true }); } - 读取本地文件benchmarks/font-cache-regression.mjs:28
if (message.ok && message.fonts) selected.push(hash({ files: message.fonts.map(path => hash(readFileSync(path))), substitutions: message.substitutions - 执行外部命令来自配置/变量benchmarks/font-cache-regression.mjs:67
const compared = spawnSync(values.python, [fileURLToPath(new URL('./font-cache-compare.py', import.meta.url)), output], { stdio: 'inherit' }); - 写入本地文件benchmarks/font-cache-stage.mjs:9
await mkdir(resolve(values.output), { mode: 0o700 }); - 写入本地文件benchmarks/font-cache-stage.mjs:11
const directory = resolve(values.output, variant); await mkdir(directory); - 读取本地文件benchmarks/font-cache-stage.mjs:20
const manifest = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8')); - 写入本地文件benchmarks/font-cache-stage.mjs:22
await mkdir(join(dependencies, '@deepseek-ai'), { recursive: true }); - 读取本地文件benchmarks/font-cache-stage.mjs:24
await symlink(await realpath(join(resolve(values.dependencies), name)), join(dependencies, name), process.platform === 'win32' ? 'junction' : 'dir'); - 读取本地文件benchmarks/font-cache-stage.mjs:30
await cp(await realpath(source), engine, { recursive: true }); - 读取本地文件benchmarks/font-cache-stage.mjs:31
const original = JSON.parse(await readFile(join(engine, 'package.json'), 'utf8')); - 执行外部命令来自配置/变量benchmarks/process.mjs:16
const child = spawn(command, args, { detached: process.platform !== 'win32', stdio: ['ignore', 'pipe', 'pipe'], env }); - 写入本地文件benchmarks/process.mjs:18
pipeline(child.stdout, createWriteStream(join(directory, 'stdout.log'), { flags: 'wx' })), - 写入本地文件benchmarks/process.mjs:19
pipeline(child.stderr, createWriteStream(join(directory, 'stderr.log'), { flags: 'wx' })), - 读取本地文件benchmarks/report.mjs:55
const environment = JSON.parse(await readFile(join(results, 'environment.json'), 'utf8')); - 读取本地文件benchmarks/report.mjs:56
const all = JSON.parse(await readFile(manifest, 'utf8')); - 读取本地文件benchmarks/report.mjs:70
const bytes = await readFile(join(dirname(manifest), 'inputs', fixture.file)); - 读取本地文件benchmarks/report.mjs:75
const source = await readFile(join(results, 'samples.jsonl'), 'utf8'); - 读取本地文件benchmarks/report.mjs:136
return { schemaVersion: 1, environment, manifestSha256: hash(await readFile(manifest)), - 写入本地文件benchmarks/report.mjs:187
await mkdir(output, { recursive: true }); - 删除本地文件benchmarks/report.mjs:196
} catch (error) { await Promise.all(created.map(path => rm(path))); throw error; } - 读取本地文件engine/build-identity.mjs:46
recipe: createHash('sha256').update(readFileSync(new URL(import.meta.url))).digest('hex'), - 读取本地文件engine/core-source.mjs:12
if (existsSync(join(repo, '.git'))) { - 执行外部命令固定命令engine/core-source.mjs:13
const git = args => execFileSync('git', args, { cwd: repo, encoding: 'utf8' }).trim(); - 读取本地文件engine/core-source.mjs:22
source = JSON.parse(readFileSync(join(repo, 'core-source.json'), 'utf8')); - 执行外部命令来自配置/变量engine/native/build-helper.mjs:21
const result = spawnSync(command, args, { cwd, env, stdio: 'inherit' }); - 读取本地文件engine/native/core-patches.mjs:9
return readdirSync(join(repo, directory)).filter(file => file.endsWith('.patch')).sort().map(file => `${directory}/${file}`); - 执行外部命令来自配置/变量engine/native/glibc-minimum.mjs:34
const result = spawnSync(readelf, ['--version-info', '--wide', path], { - 执行外部命令来自配置/变量engine/wasm-source/build.mjs:63
const result = spawnSync(command, args, { cwd: repository, env, stdio: 'inherit', ...options }); - 读取本地文件engine/wasm-source/build.mjs:80
if (!lstatSync(filename).isFile()) throw new Error(`Expected a regular build file: ${filename}`); - 读取本地文件engine/wasm-source/build.mjs:81
return sha256(readFileSync(filename)); - 读取本地文件engine/wasm-source/build.mjs:85
const files = readdirSync(path.join(owner, pinned.patches)).filter((name) => name.endsWith('.patch')).sort(); - 读取本地文件engine/wasm-source/build.mjs:91
if (!existsSync(path.join(directory, '.git'))) { - 执行外部命令固定命令engine/wasm-source/build.mjs:107
const result = spawnSync('git', args, { cwd: source, env, stdio: 'ignore' }); - 写入本地文件engine/wasm-source/build.mjs:120
copyFileSync(path.join(owner, '../document-operations.hxx'), operationsTarget); - 读取本地文件engine/wasm-source/build.mjs:121
const shim = readFileSync(path.join(owner, pinned.shim)); - 读取本地文件engine/wasm-source/build.mjs:122
if (!existsSync(shimTarget) || !readFileSync(shimTarget).equals(shim)) { - 写入本地文件engine/wasm-source/build.mjs:123
writeFileSync(shimTarget, shim); - 写入本地文件engine/wasm-source/build.mjs:149
mkdirSync(build, { recursive: true }); - 写入本地文件engine/wasm-source/build.mjs:150
mkdirSync(tarballs, { recursive: true }); - 写入本地文件engine/wasm-source/build.mjs:155
writeFileSync(path.join(build, 'autogen.input'), `${configuration}--with-external-tar=${tarballs}\n--with-parallelism=${jobs}\n`); - 写入本地文件engine/wasm-source/build.mjs:157
writeFileSync(identityFile, `${JSON.stringify(identity, null, 2)}\n`); - 删除本地文件递归engine/wasm-source/build.mjs:265
if (!published) rmSync(staging, { recursive: true, force: true }); - 读取本地文件engine/wasm-source/source.mjs:9
const recipe = JSON.parse(readFileSync(join(repo, 'engine/wasm-source/source.json'), 'utf8')); - 执行外部命令来自配置/变量engine/wasm-source/stage.mjs:23
const result = spawnSync(command, args, { cwd: root, env: environment, stdio: 'inherit', ...options }); - 读取本地文件engine/wasm-source/stage.mjs:29
const autogen = readFileSync(join(paths.build, 'autogen.input'), 'utf8'); - 读取本地文件engine/wasm-source/stage.mjs:37
for (const file of readdirSync(published, { withFileTypes: true })) { - 写入本地文件engine/wasm-source/stage.mjs:38
if (file.isFile() && file.name !== 'prebuilds.json') copyFileSync(join(published, file.name), join(destination, file.name)); - 写入本地文件engine/wasm-source/stage.mjs:40
for (const name of ['assets', 'sources', 'licenses']) mkdirSync(join(destination, name), { recursive: true }); - 写入本地文件engine/wasm-source/stage.mjs:41
for (const name of readdirSync(paths.bundle)) copyFileSync(join(paths.bundle, name), join(destination, 'assets', name)); - 读取本地文件engine/wasm-source/stage.mjs:41
for (const name of readdirSync(paths.bundle)) copyFileSync(join(paths.bundle, name), join(destination, 'assets', name)); - 写入本地文件engine/wasm-source/stage.mjs:42
for (const name of ['LICENSE', 'NOTICE']) copyFileSync(join(paths.build, 'instdir', name), join(destination, 'licenses', name)); - 写入本地文件engine/wasm-source/stage.mjs:43
copyFileSync(join(root, 'NOTICE'), join(destination, 'licenses/DeepSeek-Harness-MIT.txt')); - 写入本地文件engine/wasm-source/stage.mjs:51
mkdirSync(dirname(target), { recursive: true }); - 读取本地文件engine/wasm-source/stage.mjs:60
for (const entry of readdirSync(join(destination, prefix), { withFileTypes: true })) { - 读取本地文件engine/wasm-source/stage.mjs:69
const prior = JSON.parse(readFileSync(join(published, 'prebuilds.json'), 'utf8')); - 删除本地文件递归engine/wasm-source/stage.mjs:89
rmSync(backup, { recursive: true }); - 删除本地文件递归engine/wasm-source/stage.mjs:91
} finally { rmSync(destination, { recursive: true, force: true }); } - 读取本地文件packages/entry/src/engine.ts:102
return (require.resolve.paths(name) ?? []).some(directory => lstatSync(join(directory, name), { throwIfNoEntry: false }) !== undefined) - 读取本地文件packages/entry/src/engine.ts:176
const status = await stat(path) - 读取本地文件packages/entry/src/engine.ts:185
readFile(packageFile, 'utf8').then(text => JSON.parse(text) as EngineManifest), - 读取本地文件packages/entry/src/engine.ts:186
readFile(resolve(root, 'prebuilds.json'), 'utf8').then(text => JSON.parse(text) as EnginePrebuildManifest), - 写入本地文件packages/entry/src/font-metadata-cache.ts:86
mkdirSync(options.fontMetadataCacheDirectory, { recursive: true, mode: 0o700 }) - 写入本地文件packages/entry/src/font-metadata-cache.ts:89
try { writeFileSync(fd, bytes) } finally { closeSync(fd) } - 写入本地文件packages/entry/src/font-metadata-cache.ts:90
renameSync(temporaryPath, join(options.fontMetadataCacheDirectory, 'font-metadata.json')) - 删除本地文件packages/entry/src/font-metadata-cache.ts:96
try { unlinkSync(temporaryPath) } catch { } - 删除本地文件packages/entry/src/font-snapshot-state.ts:36
try { await rm(temporaryPath, { force: true }) } catch { - 读取本地文件packages/entry/src/font-snapshot-validation.ts:11
const current = await lstat(record.path, { bigint: true }) - 读取本地文件packages/entry/src/fonts.ts:149
entries = readdirSync(assets) - 读取本地文件packages/entry/src/fonts.ts:168
status = statSync(canonical) - 读取本地文件packages/entry/src/fonts.ts:182
names = readdirSync(canonical) - 写入本地文件packages/entry/src/image-renderer.ts:103
writeFileSync(path, png, { flag: 'wx', mode: 0o600 }) - 写入本地文件packages/entry/src/index.ts:320
await mkdir(profile, { mode: 0o700 }) - 删除本地文件packages/entry/src/index.ts:341
...(output && !succeeded ? [unlink(request.outputPath)] : []), - 删除本地文件递归packages/entry/src/index.ts:342
...(scratch ? [rm(scratch, { recursive: true, force: true })] : []), - 写入本地文件packages/entry/src/index.ts:364
await mkdir(request.outputDir, { mode: 0o700 }) - 写入本地文件packages/entry/src/index.ts:369
await writeFile(inputPath, source, { flag: 'wx', mode: 0o600 }) - 写入本地文件packages/entry/src/index.ts:384
await writeFile(join(request.outputDir, 'manifest.json'), `${JSON.stringify(images, null, 2)}\n`, { flag: 'wx', mode: 0o600 }) - 删除本地文件递归packages/entry/src/index.ts:392
...(scratch ? [rm(scratch, { recursive: true, force: true })] : []), - 删除本地文件递归packages/entry/src/index.ts:393
...(owned && !succeeded ? [rm(request.outputDir, { recursive: true, force: true })] : []), - 写入本地文件packages/entry/src/native-image-renderer.ts:93
await mkdir(profile, { mode: 0o700 }) - 执行外部命令来自配置/变量packages/entry/src/native-image-renderer.ts:97
const child = spawn(engine.executable, ['--operation', 'render-images', '--program-directory', engine.programDirectory, - 读取本地文件packages/entry/src/native-image-renderer.ts:179
const raw = await readFile(painted.path) - 写入本地文件packages/entry/src/native-image-renderer.ts:186
await writeFile(path, png, { flag: 'wx', mode: 0o600 }) - 写入本地文件packages/entry/src/native.ts:49
await mkdir(user, { mode: 0o700 }) - 写入本地文件packages/entry/src/native.ts:50
await writeFile(join(user, 'registrymodifications.xcu'), xml, { flag: 'wx', mode: 0o600 }) - 写入本地文件packages/entry/src/native.ts:86
await copyFile(engine.executable, executable, constants.COPYFILE_EXCL) - 删除本地文件packages/entry/src/native.ts:88
} finally { await unlink(root) } - 执行外部命令来自配置/变量packages/entry/src/native.ts:113
const child = spawn(engine.executable, ['--program-directory', engine.programDirectory, '--input-path', input, - 读取本地文件packages/entry/src/office-fonts.ts:22
try { return statSync(path).isDirectory() } catch (error) { if (absent(error)) return false; throw error } - 读取本地文件packages/entry/src/office-fonts.ts:29
try { entries = readdirSync(current, { withFileTypes: true }) } catch (error) { - 读取本地文件packages/entry/src/wasm.ts:161
const raw = readFileSync(engine.data) - 读取本地文件packages/entry/src/worker.ts:47
const bytes = readFileSync(inputPath) - 写入本地文件packages/entry/src/worker.ts:69
mkdirSync(directory, { mode: 0o700 }) - 写入本地文件packages/entry/src/worker.ts:72
writeFileSync(path, data, { flag: 'wx', mode: 0o600 })
扫描于 2026/10/31(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- Office文档转PDF
- Office文档转PNG
DeepSeek Harness 内部使用的组件
README
由 DeepSeek 最新模型翻译生成description: "使用预编译 LibreOffice 引擎,在 Node.js 中实现字体友好的 Office → PDF 转换。"
kind: "package-library"
@deepseek-ai/libreoffice-kit
当前目标
在 Node.js 中实现字体友好的 Office 转换与渲染。 0.1.0 在稳定的 0.0.1 系列上增加格式转换、工作簿重算、CLI、直接 PNG 渲染和共享 converter factory 字体缓存,同时保留原生引擎包。
当前优先保证文档排版和文字可读,让调用方能明确控制可用字体与替换策略,并让应用可以打包引擎、离线运行。精简也服务于这个目标:保留文档导入、排版、绘图和 PDF 导出所需的能力,移除与转换无关的桌面功能和资源。
API 支持 Office 导出与重算,以及 Office/PDF 输入的直接 PNG 渲染,可用于 Node.js 服务、桌面应用和文档处理任务。应用自行管理授权、存储和预览界面。
二进制 .doc、.xls、.ppt 支持 Office 97–2003 等 OLE 复合文档,不接受改成这些后缀的 RTF/HTML 文件或 .wps。missingFonts 仅报告 OOXML 中识别到的字体声明;二进制格式返回空列表,字体匹配由 LibreOffice 完成。
展开完整文档(README 剩余 1 节)
description: "使用预编译 LibreOffice 引擎,在 Node.js 中实现字体友好的 Office → PDF 转换。"
kind: "package-library"
@deepseek-ai/libreoffice-kit
当前目标
在 Node.js 中实现字体友好的 Office 转换与渲染。 0.1.0 在稳定的 0.0.1 系列上增加格式转换、工作簿重算、CLI、直接 PNG 渲染和共享 converter factory 字体缓存,同时保留原生引擎包。
当前优先保证文档排版和文字可读,让调用方能明确控制可用字体与替换策略,并让应用可以打包引擎、离线运行。精简也服务于这个目标:保留文档导入、排版、绘图和 PDF 导出所需的能力,移除与转换无关的桌面功能和资源。
API 支持 Office 导出与重算,以及 Office/PDF 输入的直接 PNG 渲染,可用于 Node.js 服务、桌面应用和文档处理任务。应用自行管理授权、存储和预览界面。
二进制 .doc、.xls、.ppt 支持 Office 97–2003 等 OLE 复合文档,不接受改成这些后缀的 RTF/HTML 文件或 .wps。missingFonts 仅报告 OOXML 中识别到的字体声明;二进制格式返回空列表,字体匹配由 LibreOffice 完成。
快速开始
需要 Node.js 22.19.0 或更新版本。安装 0.1.3 及其可选引擎依赖:
npm install @deepseek-ai/libreoffice-kit@0.1.3
此包安装 dsoffice 命令。使用本地安装的命令:
npm exec -- dsoffice render --input report.docx --output-dir report-pages --pages 1
npm exec -- dsoffice convert --input report.docx --output report.pdf
npm exec -- dsoffice recalculate --input budget.xlsx --output recalculated.xlsx
import { createConverter } from '@deepseek-ai/libreoffice-kit';
const converter = await createConverter({
timeoutMs: 120_000,
// 不设置时,从常规系统和用户目录发现字体。
// fontDirectories: ['/absolute/path/to/fonts'],
});
try {
const result = await converter.render({
inputPath: '/private/work/document.docx',
outputPath: '/private/work/document.pdf',
});
console.log(result.backend, result.missingFonts);
} finally {
await converter.dispose();
}
路径必须是绝对路径,目录由调用方私有管理,输出文件必须尚不存在。PDF 写入 outputPath,render 返回所选引擎和缺失字体名称。每次转换使用独立的原生进程或 Node Worker 及私有配置;同一转换器串行执行,取消和释放会等待引擎退出与清理完成。取消、错误和资源限制见 Node API。
0.1.0 的“字体友好”具体指什么
- 使用环境中可用的字体。 默认发现常规系统和用户字体目录,也可通过 fontDirectories 指定扫描目录;自定义目录会替换默认列表。fontkit 读取字体元数据和字形覆盖,将选中的原始字体文件交给引擎。
- 优先保留文档指定的字体。 已安装的同名字体族优先,包括书法和装饰字体。WASM 字体请求还携带字重、斜体信息,以便选择已安装的对应字面;缺字时可以继续从字体目录中选择补充字体。
- 允许配置替换顺序。 默认 fontFallbacks 覆盖常见西文和简体中文字体,例如 Calibri 缺失时选择 Carlito、Cambria 缺失时选择 Caladea。调用方传入的分组会替换默认值。这些规则只选择可用字体,不会安装字体;具体见默认分组。
- 提供缺失字体诊断和资源预算。 missingFonts 报告可读文档 XML 中声明但不可用的字体族;maxFontFiles、maxFontFileBytes 和 maxLoadedFontBytes 限制索引及显式导入。每次操作重新发现并验证字体,未变化文件的元数据通过进程内共享和有界用户级磁盘缓存复用;见缓存选项与失效规则。
引擎不捆绑或下载字体集合。部署方根据文档需求和再分发权限提供字体;最小化容器需要安装字体或指定字体目录。WASM 仅使用导入的字体,没有可用字体时以 unavailable 拒绝转换。macOS 和 Windows 原生引擎还可使用操作系统管理的字体。
这些能力让字体选择更可控,但不保证与 Microsoft Office 或另一引擎的输出完全一致。原生 LibreOffice 会先解析已安装的原始字体和度量兼容字体,再参考配置的替换项,并自行选择字面。字体度量差异仍可能改变换行和分页;missingFonts 也不是完整的缺字报告。比较引擎效果时,需要使用相同的文档字节、字体和导出选项。
引擎与分发
Node 包清单为 0.1.0 声明了以下引擎:
| 引擎 | 用途 |
| @deepseek-ai/libreoffice-kit-darwin-arm64 | Apple Silicon macOS 的原生 helper。 |
| @deepseek-ai/libreoffice-kit-darwin-x64 | Intel macOS 的原生 helper。 |
| @deepseek-ai/libreoffice-kit-win32-arm64 | Windows ARM64 原生 helper;需要 ARM64 Node.js 和 Microsoft Visual C++ v14 ARM64 Redistributable。 |
| @deepseek-ai/libreoffice-kit-win32-x64 | Windows x64 原生 helper;需要 Microsoft Visual C++ v14 x64 Redistributable。 |
| @deepseek-ai/libreoffice-kit-wasm | 供 Linux 使用的共享 Node WASM 引擎。 |
其他原生目录保留为开发构建配方,不代表额外的已发布目标。共享 WASM 包的 npm OS 限制为 Linux,不限制 CPU 或 libc;这项声明本身不代表每个 Linux 宿主都已验证。原生与 WASM 引擎的排版和 PDF 序列化均由 CPU 完成。
npm 在 macOS/Windows ARM64 或 x64 上仅安装匹配的原生包,在 Linux 上安装 WASM。macOS 和 Windows 必须具有对应原生包,绝不回退到 WASM。Linux 使用 WASM,除非显式安装的原生开发包支持其 glibc 版本。必需引擎缺失或已安装引擎损坏时,createConverter 以 unavailable 拒绝;转换失败不会切换引擎。
Node API 与引擎共享 kit 版本。安装使用预先构建的包;安装钩子和转换过程均不会编译 LibreOffice、额外下载引擎,或查找用户的 LibreOffice 安装。npm 分发标准 .tgz 包;GitHub Release 引擎下载使用经校验的 XZ 传输包,供应用构建时准备并打包。两种分发路径及安装后转换验证见打包指南和发布流程。
做了哪些精简,为什么
0.0.1 分别在构建组件、安装资源和传输大小三个层面做精简:
| 层面 | 代码中的改动 | 原因 |
| 原生构建 | 禁用 Java/Python、脚本与扩展、Base 数据库连接、PDF 导入、帮助与词典、图库/模板/图标主题、远程控制、在线更新,以及未使用的 curl/WebDAV/CMIS/LDAP 集成。 | 本地 OOXML → PDF 需要文档导入和 PDF 导出;桌面自动化、数据库访问、PDF 输入和在线服务会引入转换路径之外的依赖。 |
| 原生安装资源 | 校验后移除重复的 macOS urelibs 别名,删除 SDK 工具、启动器、Quick Look/Spotlight 资源、已禁用组件残留库、Basic/Python 脚本、Notebookbar、菜单和工具栏;清理非必要符号并保留动态导出,恢复和验证 macOS 签名。 | 避免应用内的转换引擎携带重复库、桌面资源和开发工具。 |
| WASM 构建及资源 | 为无界面的 Node Worker 构建 Writer、Calc、Impress,禁用 Java/Python、内置字体、OpenCL/OpenGL 和 Skia;从 soffice.data 移除图标归档、Notebookbar、菜单/工具栏、Android 示例文档、启动图片和 shell 资源。 | 保留文档引擎和 CPU 渲染路径,减少加载进 WASM 文件系统的资源。资源裁剪保留其余文件字节和元数据,只重算偏移;该步骤不改变加载器或已编译模块。 |
| 字体载荷 | 不捆绑字体集合,运行时加载部署环境提供的原始字体文件。 | 避免固定字体包的体积,让应用按文档需求选择字体,包括中日韩字形覆盖和 Office 兼容替代字体。 |
| 发布传输 | GitHub Release 引擎下载使用 XZ,分别校验压缩封装和精确安装 tar 的大小及哈希。 | 在不改变安装内容的情况下减少传输字节;这与资源裁剪、npm 的 .tgz 格式是不同层面的处理。 |
具体规则由原生构建配置、原生资源裁剪、WASM 构建配置和 WASM 资源裁剪维护。
原生资源裁剪移除 XSLT 资源及其过滤器注册、CREDITS.fodt,并且只在 licenses/ 保留逐字节相同的第三方声明时移除安装目录中的 LICENSE.html 副本。移除的 XSLT 格式包括 Word 2003 XML、SpreadsheetML、UOF、DocBook 和 XHTML;二进制 Office 与 OOXML 转换过滤器仍然保留。
Windows 资源裁剪移除未使用的 OpenSSL、MSI 安装器、Shell 扩展、包括 regactivex.dll 的 ActiveX/SharePoint 集成、.NET CLI 绑定、桌面启动器、program/wizards/ 和 program/program/wizards/ 下的 Python 向导及品牌图片。转换 helper、扫描仪/GPG helper、已注册 UNO 组件和运行时 .ini 文件仍然保留,详见打包说明。
Writer、Calc、Impress、二进制 Office 与 OOXML 过滤器、PDF 导出、用于内嵌 PDF/EMF 图形的 PDFium、共享排版与绘图库、图表、ICU 和语言资源仍然保留。原生构建的 en-US 选择的是界面资源,并不限制文档只能包含英文。必要的运行时配置和部分 UI 资源也会保留,因为文档服务仍依赖它们。每个引擎包还保留匹配的源码配方、补丁、哈希和许可声明。这些依赖也解释了为什么精简后的文档引擎仍有一定体积。
已记录的体积与保真度验证
仓库已有的本地候选包实测,将此前 0.1.2 的 gzip 包与独立版本 0.0.1 的 XZ 包比较。下载降幅同时包含载荷变化和压缩格式变化,不能全部归因于代码删除;这些是历史候选包数据,不是当前 npm 下载大小。MB 按 1,000,000 字节计算;解包大小为普通文件字节之和,不含文件系统分配开销和依赖包。
| 引擎 | 此前下载 | 0.0.1 下载 | 减少 | 此前解包 | 0.0.1 解包 |
| macOS ARM64 | 98.85 MB | 60.49 MB | 38.81% | 301.04 MB | 269.43 MB |
| Node WASM | 56.47 MB | 35.85 MB | 36.51% | 210.24 MB | 190.64 MB |
已记录的本地 macOS ARM64 验证,以原生和 WASM 两种选择离线安装同一候选包。每个引擎使用六份合成 DOCX/XLSX/PPTX 文档,覆盖中英文、表格、公式和图片;与此前包相比,提取文字、页数和 96 DPI 渲染像素均一致。这些证据只覆盖上述样例与宿主,不代表所有文档保真或其他平台已通过验收。运行时测试还覆盖外部链接抑制、字体替换、限制和取消;发布验证要求提供安装后运行引擎的证据。
构建指南
平台依赖、构建路径、并行度及包验证要求见通用构建指南。
使用当前检出版本固定的源码和工具链,在相应平台上逐个构建、验证目标。
开发
原生源码和 Node WASM 配方使用同一个由 engine/core submodule 固定的 LibreOffice 修订版。.gitmodules 管理上游 URL,gitlink 管理 commit。检出脚本保持 submodule 干净,并在被忽略的 .build/ 下创建可丢弃的独立源码树用于应用补丁。本仓库维护组件选择和完整打包配方;匹配的源码与许可材料随引擎一起分发。
无需构建 LibreOffice 即可运行仓库检查:
pnpm verify:metadata
pnpm test
pnpm test:packaging
真实引擎测试还需要执行 pnpm run build:adapter、准备引擎载荷,并将 LIBREOFFICE_RUNTIME_ENTRY 设为 packages/entry/lib/index.js 的绝对路径。各平台顺序构建,按 CPU 和内存显式设置并行度;pnpm gha:matrix 展示 CI 矩阵。构建产物保持忽略。
基准测试在独立的原生与 WASM 安装中测量公开磁盘 API,并区分首次字体索引和转换器复用。比较时使用相同的源码修订版、文档、字体目录和 PDF 选项;前端传输和绘制单独测量。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
deepseek-ai/dsh-libreoffice-kit
在 Node.js 中把 Office 文档转成 PDF 或 PNG
相关插件 · 其他
模型破甲清洗YuJunZhiXue/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 14,049已汉化DeepSeek 客户端
73
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,597已汉化DSH
71.3
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 718已汉化DSH
71.2
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,261已汉化DSH
70.7
破甲评测WoYun520/dsh-purge
在本机分离宿主拦截与模型拒绝,做受控红队评测
★ 199已汉化DSH
69.7