# pdf-covert-tnas **Repository Path**: chenshaogui/pdf-covert-tnas ## Basic Information - **Project Name**: pdf-covert-tnas - **Description**: 铁威马(TerraMaster)NAS 专用 PDF 格式转换工具。支持 PDF 与 Word、Excel、PPT、图片等格式的相互转换,提供 Web 管理界面与命令行两种操作方式,针对 TOS 系统优化部署流程,支持批量转换与队列管理,适用于办公文档的云端处理需求。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-04 - **Last Updated**: 2026-11-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 文档转换(csg-doc-covert) 铁威马 TOS 7 应用:在 NAS 本机完成 **PDF、Word、Markdown、HTML、纯文本与常见图片格式** 之间的相互转换。 所有转换都在设备本地执行,文件不会离开 NAS,也不需要任何云服务。 - 应用 ID:`csg-doc-covert` - 类型:Deb 单包 + WebUI 内嵌打开(iframe) - 后端:Go 静态二进制,通过 Unix Socket `/var/api/csg-doc-covert.sock` 与平台通信,**不监听任何 TCP 端口** - 前端:`webui.bz2` 解包后的静态页面,接口调用统一走平台代理 `/v2/proxy/csg-doc-covert/` - 架构:同时产出 `x86_64` 与 `aarch64` 两个 deb 包 ## 1. 支持的格式 内置引擎为纯 Go 实现,不依赖外部程序即可完成大部分转换;检测到外部工具时自动增强(见第 7 节)。 运行时真实可用的小区以 `GET /info` 返回的 `sources` / `targets` / `targetsBy` 为准。 | 源格式 | 可扩展名 | 可转换目标 | | --- | --- | --- | | PDF | `.pdf` | pdf、docx、md、html、txt、png、jpg、gif、bmp、tif、webp | | Word | `.docx` | pdf、docx、md、html、txt | | Markdown | `.md` `.markdown` | pdf、docx、md、html、txt | | HTML | `.html` `.htm` `.xhtml` | pdf、docx、md、html、txt | | 纯文本 | `.txt` `.log` `.csv` | pdf、docx、md、html、txt | | 图片 | `.png` `.jpg` `.jpeg` `.gif` `.bmp` `.tif` `.tiff` `.webp` | pdf、png、jpg、gif、bmp、tif、webp | | 旧版办公文档 | `.doc` `.odt` `.rtf` | 需要设备上安装 LibreOffice | 中文、代码块、表格、引用、图片内联在转 PDF 时都会保留;PDF 转图片支持逐页输出与 DPI 设置。 PDF 转 Markdown/HTML 时,文档里的内嵌图片会被提取到 `images/` 目录并在正文中改写为相对引用, 任务输出为「文稿 + 图片」多个文件,用「打包下载」(`GET /download?id=&zip=1`)一次取回。 PDF 的软掩码/SMask 属于透明度数据,不会被当成图片导出;PDF 中没有内嵌位图时(插图为矢量绘制), 任务消息会明确说明,此时如需保留版面请改为 PNG/JPG 按页输出。 PDF 转 docx/HTML/Markdown 时还会从绘图几何中重建表格(边框线段聚类成网格、字形按位置入格)、 恢复文字颜色(填充色 ≥ 0.3 亮度的着色文字以彩色 run 输出,近黑视为正文色), 折行的单元格文字会合并回同一单元格;提取出的内嵌图片会按 `Do` 操作符的绘制位置 放回原处(表格内的截图回到对应单元格),而不是堆在文末。 WebUI 的预览弹窗对 Markdown 一律渲染为格式化页面(与转换共用同一 goldmark 管线, 图片引用回链到 `/preview` 或 `/download?inline=1`),不再显示原始文本; 渲染页在 `CSP: sandbox` 的 iframe 中展示。 ## 2. 目录结构 ``` src/ Go 后端(module: gitee.com/chenshaogui/pdf-covert-tnas) cmd/csg-doc-covert 程序入口:serve / version / tools internal/config 环境变量与默认值 internal/format 格式定义与转换路由解析 internal/parse Markdown / HTML / docx / txt 解析 internal/raster 纯 Go 页面栅格化(变换栈、填充/描边、裁剪、图像与 Form XObject) internal/ocr tesseract TSV 词框重建为段落/列表(可选增强,默认关闭) internal/model 中间文档模型 internal/write PDF / docx / HTML / txt 生成 internal/engine 图片编解码、PDF 处理、外部工具探测与调用 internal/convert 引擎装配与路由执行 internal/task 任务队列、工作区、保留期清理 internal/server HTTP 处理器、鉴权、CORS、WebUI 静态页 internal/media 图片资产存储 third_party/rscpdf rsc.io/pdf v0.1.1 的本地 fork(见下) packaging/ TOS 打包件 config.ini 应用元数据(严格 JSON) csg-doc-covert.lang 26 种语言文案 csg-doc-covert.env 运行时配置(systemd EnvironmentFile) init.d/ systemd 单元(由平台安装) images/icons/ 应用图标 SVG webui/ 前端源码:index.html / app.js / styles.css webui.bz2 前端归档(打包进 deb 的固定文件名) DEBIAN/ control + preinst / postinst / prerm / postrm build.sh 构建双架构 deb(不依赖 python) validate.sh 上架前自检 ``` ## 3. 环境要求 | 用途 | 需要 | | --- | --- | | 编译后端 | Go 1.22+(纯 Go 交叉编译,`CGO_ENABLED=0`) | | 打包 deb | Linux(Debian/Ubuntu 或 WSL)、`dpkg-deb` ≥ 1.19、GNU tar、`bzip2` | | 调试接口 | curl 或浏览器 | WSL 内没有 Go 也可以:先用本机 Go 交叉编译出 `packaging/prebuilt//csg-doc-covert`,再用 `--skip-build` 打包。 ### 3.1 关于 `third_party/rscpdf` PDF 文本解析依赖 `rsc.io/pdf v0.1.1`。上游有两个缺陷会导致中文 PDF(`/Encoding /Identity-H` 的 CID 字体)提取出来全是乱码:`ToUnicode` 是流对象却被当成字典比较导致 CMap 永远不生效、 `bfchar` 映射与 CID 宽度表(`/W`)完全不支持。`src/third_party/rscpdf` 是带补丁的本地 fork, 通过 `src/go.mod` 的 `replace` 指令生效,补丁位置以 `PATCH(rscio-pdf)` 注释标注。 如果未来升级上游版本,需要重新套用这些补丁。 ## 4. 本地开发与调试 ```powershell cd src go build -o ..\.devtmp\cdc.exe .\cmd\csg-doc-covert ..\.devtmp\cdc.exe tools # 打印检测到的外部工具、字体与格式矩阵 ..\.devtmp\cdc.exe serve -listen 127.0.0.1:8083 -webui ..\packaging\webui -data ..\.devtmp\data ``` `-listen` 会同时关闭会话校验(仅用于本机),`-webui` 把前端目录挂到根路径,浏览器直接打开 即可使用完整界面。默认所有调用者共享一个全量视图;如果希望局域网内不同电脑 互不可见,设置环境变量 `DOCCONVERT_LOCAL_ISOLATION=on` 后服务会按客户端 IP 派生 owner 进行隔离。 - `-webui` 不是必须的:安装在设备上时前端就解包在安装目录里,`WebRoot()` 默认取 `AppDir`; 只有本地调试才需要指向 `packaging/webui`。不加该参数时 `/` 退化为 JSON 状态。 - 只托管 `html/js/css/json/txt/svg/图片/字体` 这些扩展名,`config.ini`、`*.env` 不会被读到, `../` 越界请求一律 404。 - 在 TOS 上运行时不使用这两个参数:服务只监听 Unix Socket,前端由平台托管,并强制校验平台会话。 接口手工验证: ```bash curl -s http://127.0.0.1:8083/health curl -s http://127.0.0.1:8083/info curl -s -F files=@note.md http://127.0.0.1:8083/upload # 返回任务 id curl -s -X POST -H 'Content-Type: application/json' \ -d '{"id":"<任务id>","target":"pdf"}' http://127.0.0.1:8083/convert curl -s "http://127.0.0.1:8083/task?id=<任务id>" curl -s -O -J "http://127.0.0.1:8083/download?id=<任务id>&name=note.pdf" ``` ## 5. 自检与打包 ```bash cd packaging bash validate.sh # 上架规则自检,出现 [E] 必须先修 bash build.sh # 双架构:需要本机有 go bash build.sh x86_64 # 单架构 bash build.sh all --skip-build # 使用 prebuilt//csg-doc-covert ``` `build.sh` 的行为: 1. 用 `sed` 读取 `config.ini` 中的 `id` 与 `version`(不使用 python); 2. 交叉编译 `CGO_ENABLED=0 GOOS=linux GOARCH=amd64|arm64`,并以 `-ldflags -X` 注入版本号; 3. 在前 Linux 临时目录里组装 `usr/local/csg-doc-covert/`(Windows 挂载点权限会让 `dpkg-deb` 报 `bad permissions`),生成 `webui.bz2`; 4. 按目标架构改写 `config.ini` 的 `platform` 与 `DEBIAN/control` 的 `Architecture`; 5. `dpkg-deb --build --root-owner-group` 产出 `packaging/build/output/csg-doc-covert__.deb`; 6. 校验包内元数据确实位于 `usr/local//` 之下,且没有任何文件散落在该目录之外。 若本机没有 Go,先交叉编译出两个二进制放入 `packaging/prebuilt/x86_64/`、`packaging/prebuilt/aarch64/`(文件名必须就是 `csg-doc-covert`),再执行 `bash build.sh all --skip-build`。 若打包机没有 `bzip2`,脚本会复用仓库里的 `webui.bz2`;改动 `webui/` 后需重新生成:`tar -cjf webui.bz2 -C webui .`。 ## 6. 安装、升级与卸载 - 安装:TOS「应用中心」→ 本地安装,选择对应架构的 deb;或 `sudo dpkg -i csg-doc-covert_1.0.2_x86_64.deb` - 升级:直接安装版本号更高的 deb,`postinst` 会 `systemctl restart` 换新二进制,`data/` 下的任务文件不会被动 - 卸载:应用中心卸载,或 `sudo dpkg -r csg-doc-covert`;`sudo dpkg --purge csg-doc-covert` 会连同 `data/`、`logs/` 一起清除 安装后的位置: ``` /usr/local/csg-doc-covert/bin/csg-doc-covert 服务主程序 /usr/local/csg-doc-covert/config.ini 平台读取的元数据 /usr/local/csg-doc-covert/csg-doc-covert.lang 多语言文案 /usr/local/csg-doc-covert/csg-doc-covert.env 运行时配置 /usr/local/csg-doc-covert/init.d/*.service systemd 单元 /usr/local/csg-doc-covert/index.html|app.js|… 解包后的 WebUI /usr/local/csg-doc-covert/data/tasks// 任务工作区(按会话隔离,默认保留 24 小时) /usr/local/csg-doc-covert/logs/ 日志目录(默认输出到 stdout,由 journald 收集) /var/api/csg-doc-covert.sock 服务 Socket(0660,属主 csg-doc-covert) ``` `` 是从平台会话 UUID `TMSESSNAME` 派生的 16 位十六进制指纹(SHA-256 截断), 同一个 TOS 账号的同一个登录会话对应同一个目录,本地调试关闭鉴权时写入 `dev/`; 升级前遗留的 `data/tasks//` 旧版目录会在下次启动时一并回收。 应用运行用户 `csg-doc-covert` 由 TOS 依据 `config.ini` 的 `user` 字段创建,包内脚本不会自行增删用户。 ## 7. HTTP 接口 前端调用形式为 `POST /v2/proxy/csg-doc-covert/`,本机调试时直接访问 `/`。 除 `health` 外都需要平台会话(Cookie `TMSESSNAME` + 头 `X-Csrf-Token`);`download` 允许仅凭会话 Cookie 的链接式访问。 | 接口 | 方法 | 说明 | | --- | --- | --- | | `/` | GET | WebUI 首页(本机调试用) | | `/health` | GET | 存活探针,返回应用名、版本与内置能力 | | `/info` | GET | 源格式、目标格式、路由表、限额、默认选项、检测到的外部工具 | | `/targets?source=pdf` | GET | 某一源格式可用的目标列表 | | `/plan?source=&target=` | GET | 解析转换路由,说明使用内置引擎还是外部工具 | | `/upload` | POST | `multipart/form-data`,字段名 `files`,可多文件;返回任务 id 与已存文件 | | `/files?id=` | GET | 任务工作区内已有的源文件 | | `/preview?id=&name=` | GET | 预览已上传的源文件(PDF/图片/文本/网页),支持 Range;md 加 `&render=1` 返回格式化 HTML | | `/convert` | POST | JSON:`{id, target, files?, options?}`,`202` 入队 | | `/task?id=` | GET | 任务状态、进度、输出文件列表 | | `/tasks?limit=` | GET | 最近任务列表与队列统计 | | `/cancel` | POST | JSON:`{id}`,取消排队或运行中的任务 | | `/delete` | POST \| DELETE | JSON:`{id}`,删除任务与工作区 | | `/download?id=&name=` | GET | 下载单个结果;`&zip=1` 或多结果时打包为 zip;`&inline=1` 直接预览,md 再加 `&render=1` 返回格式化 HTML | `/convert` 的 `options` 为补丁对象,未提供的项使用 `info.defaults`: | 分组 | 字段 | | --- | --- | | 版面 | `title` `paper`(A4/A3/A5/Letter) `margin` `fontSize` `footer` `landscape` | | 图片 | `quality` `dpi` `maxWidth` `maxHeight` `white` | | 文本与网页 | `width` `standalone` | | 多文件 | `mergePdf`(多个输入合并为一个 PDF) | ## 8. 运行时配置 全部通过 `csg-doc-covert.env` 修改,改完 `systemctl restart csg-doc-covert` 生效。未设置的项使用代码内默认值。 | 变量 | 默认 | 说明 | | --- | --- | --- | | `DOCCONVERT_APP_DIR` | `/usr/local/csg-doc-covert` | 安装目录 | | `DOCCONVERT_WEB_DIR` | 空,即 `` | 前端静态目录,本地调试指向 `packaging/webui` | | `DOCCONVERT_DATA_DIR` | `/data` | 任务与媒体目录 | | `DOCCONVERT_LOG_DIR` | `/logs` | 日志目录 | | `DOCCONVERT_SOCKET` | `/var/api/csg-doc-covert.sock` | 服务 Socket | | `DOCCONVERT_SOCKET_MODE` | `0660` | Socket 权限 | | `DOCCONVERT_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | | `DOCCONVERT_REQUIRE_AUTH` | `true` | 平台会话校验,安装包内不得关闭 | | `DOCCONVERT_LOCAL_ISOLATION` | `off` | 仅本地调试(`REQUIRE_AUTH=false`)时生效:设为 `on` 按客户端 IP 隔离任务,否则所有调用者共享全量视图 | | `DOCCONVERT_ALLOWED_ORIGINS` | 空 | 额外允许的跨域来源 | | `DOCCONVERT_MAX_FILE_MB` | `64` | 单个源文件上限 | | `DOCCONVERT_MAX_REQUEST_MB` | `128` | 单次请求(含 zip 打包)上限 | | `DOCCONVERT_MAX_FILES` | `20` | 单任务文件数上限 | | `DOCCONVERT_WORKERS` | CPU 数(最多 4),包内设 `2` | 并发转换数 | | `DOCCONVERT_TASK_TTL_HOURS` | `24` | 任务文件保留时长 | | `DOCCONVERT_TASK_TIMEOUT_SEC` | `600` | 单任务超时 | | `DOCCONVERT_SHUTDOWN_SEC` | `8` | 优雅退出等待 | | `DOCCONVERT_ALLOW_EXTERNAL_TOOLS` | `true` | 是否允许调用外部程序 | | `DOCCONVERT_TOOL_TIMEOUT_SEC` | `180` | 外部程序超时 | | `DOCCONVERT_FONT_DIRS` | 空 | 追加字体搜索目录(路径分隔) | | `DOCCONVERT_INPUT_ROOTS` | 空 | 允许直接读取的本地目录前缀 | | `DOCCONVERT_OUTPUT_DIR` | 空 | 额外把结果复制到共享目录,便于文件管理器取用 | | `DOCCONVERT_OCR` | 空(关闭) | 设为 `on` 时对无文本层 PDF 启用 OCR(需设备自带 tesseract/poppler,非必需) | ## 9. 外部工具与中文字体 外部工具都是可选的,服务启动时探测一次,缺失只影响对应功能: | 工具 | 提供能力 | 缺失时的表现 | | --- | --- | --- | | LibreOffice (`soffice`) | 读取 `.doc` `.odt` `.rtf` | 这三种源格式不出现在可用列表中 | | Ghostscript (`gs`) | PDF 整页栅格化为图片 | 改由内置 PDF 解析完成,复杂页面效果下降 | | poppler (`pdftoppm` `pdftotext`) | PDF 转图片 / 抽取文字 | 使用内置实现 | | ImageMagick (`convert`) | 更多图片格式与色彩处理 | 使用 Go 内置编解码 | | Pandoc | 文档结构解析增强 | 使用内置 Markdown / HTML 解析 | ### 无文本层 PDF:内置整页渲染(不依赖任何外部程序) 扫描件、以及“打印成 PDF 时字体被转成矢量轮廓”的文件在数据层面没有文字。这类 PDF 现在由**内置栅格化器**处理: 1. 复用 fork 的内容流解释器逐页解释 `/Contents`(支持数组形式的多内容流) 2. 内置实现图形模型:变换栈 `q/Q/cm`、填充/描边颜色(`rg`/`g`/`k`/`sc`)、路径构造(`m`/`l`/`c`/`v`/`y`/`re`/`h`)、 两种填充规则(`f` 非零环绕、`f*` 奇偶)、描边(`S`/`s`/`B`,含贝塞尔扁平化)、裁剪矩形(`W n`)、 图像 XObject 与 Form XObject(递归) 3. 按 150–220dpi 输出整页 PNG,作为一页一张图片写入文档:md/html 引用 `images/` 下文件,docx/pdf 直接内嵌, 页间保留分页符 因此**设备上不需要 Ghostscript / poppler / tesseract 任何一个**,转换结果与 PDF 的版面完全一致(文字以像素呈现)。 单次最多渲染 40 页,超出会截断并提示。 另有一条**可选**的 OCR 增强:设置 `DOCCONVERT_OCR=on` 且设备恰好装有 `tesseract`(含 `chi_sim`)与 `Ghostscript`/`pdftoppm` 时,才会先做文字识别、把可编辑文本写进文档;未开启或不具备工具时自动走上面的纯 Go 路线, **不会因为缺少这些程序而失败或降级**。 **中文转 PDF 需要设备上有可用的 TrueType 中文字体**。若 `tools` 子命令显示 `中文字体:false`,把字体放进设备任意可读目录并把它写进 `DOCCONVERT_FONT_DIRS`(多个目录用系统路径分隔符),也可以随包附带字体到 `usr/local/csg-doc-covert/depends/fonts/`,`ProtectSystem=strict` 只限制写入、不影响读取。 ### 应用商店合规材料 | 材料 | 位置 | 说明 | | --- | --- | --- | | 隐私政策 | `packaging/PRIVACY.md`(随包安装到 `/usr/local/csg-doc-covert/`) | 应用内可读版为 `webui/privacy.html`,WebUI 底部有入口;`config.ini` 的 `privacy` 指向线上文档 | | 可执行文件声明 | `packaging/BINARIES.md` | 说明包内唯一二进制的源码、构建命令、依赖与「无第三方预编译文件」 | | 漏洞扫描报告 | `packaging/VULNERABILITY_SCAN.md` | `govulncheck v1.9.0`(Go 1.27.3,vuln.go.dev 2026-11-05 库)扫描全部包,结果 `No vulnerabilities found` | | 用户数据权利 | 界面「预览 / 下载 / 删除」+ `POST /delete` | 任务文件 24 小时自动过期,卸载时 `remove` 与 `purge` 都会清空 `data/`、`logs/`、解包 WebUI 与 socket | | 版本一致性 | `config.ini` / `DEBIAN/control` / `.lang` 26 个语言段 / WebUI `?v=` / `internal/config.Version` | `build.sh` 用 `config.ini` 的 version 注入 ldflags、改写 control 与资源缓存标记,`validate.sh` 会检查 26 段 release_note 都提到当前版本 | | 访问控制 | `csg-doc-covert.env` 的 `DOCCONVERT_REQUIRE_AUTH=true` | 只走平台代理的 Unix Socket,不监听 TCP;`validate.sh` 禁止打包时关掉鉴权或开启 TCP | ## 10. 安全与资源约束 - systemd 单元采用 `ProtectSystem=strict` + `ProtectHome=true` + `NoNewPrivileges=true` + `PrivateDevices`,仅放开 `data/`、`logs/` 与 `/var/api`;**不启用 `PrivateTmp`**:TOS 的 `/var/api` 链到 `/tmp/api`,私有 tmp 会让 `ReadWritePaths` 的绑定源消失并报 `226/NAMESPACE` - **不配置 `Restart=`**:重启策略由 TOS 统一管理(开发规范 8.12) - `MemoryHigh=768M`、`MemoryMax=1G`、`TasksMax=256`、`LimitNOFILE=65536` - 不设 `MemoryDenyWriteExecute`,否则无法 fork LibreOffice(仅 `.doc/.odt/.rtf` 输入需要) - 空闲连接 30 秒回收,支持 keep-alive,满足 ≥100 并发的 Socket 要求 - 每个 panic 都会被转换为 500 响应,避免单个畸形文档导致整个服务显示为已停止 - 任务文件路径由服务自身生成,下载只能按记录的文件名取回,无法读取任意路径 - **任务数据按平台会话隔离**:`Server.ownerOf` 从 `TMSESSNAME` 派生 SHA-256 前 16 位作为 owner key,`task.Queue` 给每个任务写入 owner,列表/详情/预览/下载/追加上传/取消/删除均先比对 owner,不匹配一律 `404`(不返回 403,避免确认任务存在)。磁盘上工作区也位于 `/tasks///`;`DOCCONVERT_REQUIRE_AUTH=false` 时 owner 为空,视为通配以保留本地开发者全量视图;如果设置 `DOCCONVERT_LOCAL_ISOLATION=on`,则本地 TCP 模式下按 `sha256(remoteIP)[:16]` 隔离,不同机器互不可见。参见 `packaging/PRIVACY.md` 中「多用户隔离」节。 ## 11. 已知限制 - 上传与打包均在内存中完成,故限额保守(默认 64 MB / 128 MB);大文件建议分批处理 - `.doc` `.odt` `.rtf` 完全依赖 LibreOffice,未安装时相关路由不可用 - 生成的 PDF 采用文字绘制,不做字体子集内嵌;缺少 TrueType 中文字体时中文会无法正常显示 - 任务记录保存在内存中,服务重启后历史列表清空(磁盘上未处理完的工作区会被回收) ## 12. 提交前检查清单 ```powershell cd src gofmt -l . # 应为空 go vet ./... go test ./... -count=1 ``` ```bash cd packaging bash validate.sh # 0 error(s) bash build.sh all # 产出两个 deb dpkg-deb --info build/output/csg-doc-covert_1.0.2_x86_64.deb dpkg-deb --contents build/output/csg-doc-covert_1.0.2_x86_64.deb ``` - [x] `config.ini` 为严格 JSON,`id` / `icon` / `publisher` / `exec` / `version` / `help` / `category` / `platform` / `application_type` / `system_id` / `package` / `user` 齐全 - [x] `type` 与 `open_path` 未同时出现,`path` 为 `/csg-doc-covert/` - [x] 元数据全部位于 `usr/local/csg-doc-covert/`,未散落在包根目录 - [x] 语言文件覆盖 26 个标签,UTF-8 无 BOM、LF - [x] 图标为 SVG 且 1 KB 远小于 50 KB 上限,无脚本与外链 - [x] 包内脚本不创建/删除系统用户(仅幂等补齐同名组,供 systemd `Group=` 使用) - [x] 文本文件全部 LF + UTF-8 无 BOM ## 13. 许可与反馈 源码位于 ,问题请在仓库 Issue 中提交。