OFDGo推出Windows桌面版,支持OFD、PDF阅读编辑,完全离线使用

@Ta 10-19 23:43发布,10-20 09:09修改 2290点击
OFDGo的Windows桌面版做好了,exe放在附件里,下载后直接运行,不用安装。

做这个工具,主要是想解决OFD文件不好打开、打开了又改不了的问题。为了看一份文件装个阅读器,需要修改时再找编辑器,确实挺折腾。现在阅读、编辑、保存和格式转换都可以在一个程序里完成,PDF也能打开修改,可以当PDF编辑器用。

桌面版有什么不同?

沿用网页版的界面,但文档处理走原生,不再受浏览器WASM运行环境的限制。大文件和批量转换可以利用本机多核性能,不只是把网页套进一个窗口。

编辑时可以随时保存,直接写回原文件;想保留原件就用另存。新建文档保存一次后,后续也能直接保存,不用每次重新选位置。

完全离线,文件留在本机

不需要注册登录,不需要上传文件,阅读、编辑和转换都在本机完成,断网也能用。合同、票据、个人资料这些不方便传到在线网站的文件,可以直接用桌面版处理。

目前需要手动更新

原生版暂时没有检查更新和自动更新功能,有新版本也不会弹出提醒。需要自行下载新版exe,关闭程序后替换旧版。

下载与访问

Windows桌面版:见附件

OFDGo(202610200009).exe(40.41 MB)

网页版:https://ofdgo.aite.me

开源项目:https://github.com/xiaoqidun/ofdgo

2026-10-19_23-52-11.png(343.19 KB)
回复列表(12|隐藏机器人聊天)
  • @Ta / 10-20 10:05 / 样 / 源

    有的印章会渲染成白色背景,盖住底部的文字,另外ofd需要一个单独的打印功能,不然还要另存为pdf再打印

  • @Ta / 10-20 10:27 / 样 / 源
    @旧人,印章目前没发现问题,大量样本测试均无问题,可能是文件本身有问题或生成的程序写入的文件不符合OFD标准。
  • @Ta / 10-20 11:58 / 样 / 源

    @爱特,我让ai帮我修复了 你看看

    # 签章白底盖住落款和日期
    
    现象:用 OFDGo 打开带电子签章的 OFD 时,章不是只压住红印底下的字,而是一整块矩形把落款和时间都挡住。红印压在日期上是公文盖章的正常效果;整块挡住,是印章里的白底被当成不透明内容画到了正文上面。
    
    本文说明原因、修复方式和相关代码,供上游合入时对照。
    
    ## 原因
    
    电子签章(GB/T 38540 SES)的外观通常是一份小 OFD,或一张 PNG/GIF/JPEG。外层用 `StampAnnot` 的 `Boundary` 把它放到页面上,这个框正好压在单位落款和日期上。很多数科等阅读器生成的印章里,除了红印,还有一块铺满整个框的白底:白路径,或者没有透明通道的白底图片。`Clip` 经常不写。
    
    OFDGo 先画完正文,再在 `WalkPage` 末尾调用 `DrawStamp`,印章永远叠在文字上面。这一层叠放是对的。问题出在白底没有被当成透明。
    
    已有的 `Renderer.TransparentBackground` 只关掉印章页自己的页面底色。`renderCanvasPageToContext` 的 `drawBackground=false`,以及编译路径里 `TransparentBackground=true`,都只是不再额外铺一层页面白。印章内容里的白矩形、白描边,以及 RGB 接近白色且 alpha 为不透明的像素,仍然按普通源覆盖画进 `Boundary`。
    
    两条绘制路径都会出现这个问题:
    
    1. 屏幕 SVG。`RenderPreview` 在非光栅模式下走 Canvas,入口是 `Renderer.renderStamp`。
    2. PNG、打印和编译输出。`RenderToPNG` / `CompilePage` 走 `semanticCompiler.drawStampContent`。
    
    `s.Type == "ofd"` 且 `Clip == nil` 时,Canvas 路径把整页印章缩放到 `Stamp.Box`。图片章则把位图拉满这个框。没有 `Clip` 时,白底的范围就是整个签章框。
    
    ## 修复
    
    只在绘制印章外观时去掉不透明近白底,不改 OFD 文件,也不改正文里的白色对象。
    
    判定:8 位通道 R、G、B 都大于等于 250,且 alpha 不透明(16 位 alpha 大于等于 `0x8000`)。`color.Color.RGBA()` 的 16 位下限是 `250 * 257 = 64250`。渐变和图案不按这个规则丢弃。
    
    做法:
    
    - 给 `Renderer` 增加内部标志 `stampPlate`。只有印章子渲染器打开它,`childRenderer` 会把标志传给印章内部的嵌套内容。正文渲染器保持关闭。
    - OFD 印章:子渲染器设置 `stampPlate = true`,并继续设置 `TransparentBackground = true`。
    - 矢量纯色:Canvas 跳过近白填充和近白描边。编译器在 `command` 里直接不生成这条指令。
    - 图像:`imageWithoutStampPlate` 把近白不透明像素的 alpha 改成 0。没有这类像素时返回原图,避免无谓重编码。直接放置的 PNG/GIF/JPEG 章,以及 OFD 印章内部的图片对象,都走这里。
    
    红印像素保留。红印压到的字仍然会被盖住。
    
    近白阈值留了缝:低于 250 的灰底、以及渐变或图案形式的底,不会被去掉。印章字符如果本来就是用白色像素画在红印上,这些像素会变成镂空,底下的纸面或文字能透出来,这和实体印章的阴文效果一致。
    
    ## 相关代码
    
    ### 近白判断和图像抠底
    
    `ofdgo_stamp_plate.go`
    
    ```go
    const stampPlateMin = 250 * 257
    
    func stampPlateColor(c color.Color) bool {
    	if c == nil {
    		return false
    	}
    	r, g, b, a := c.RGBA()
    	return a >= 0x8000 && r >= stampPlateMin && g >= stampPlateMin && b >= stampPlateMin
    }
    
    func imageWithoutStampPlate(ctx context.Context, img image.Image) (image.Image, error) {
    	// 解码后扫描 NRGBA。R、G、B 均 >= 250 且 alpha > 0 的像素改为 alpha 0。
    	// 没有任何改动时返回传入的原图。
    }
    

    标志只作用于印章子渲染器

    ofdgo_renderer.go

    TransparentBackground bool
    stampPlate            bool
    

    childRenderer 复制该标志:

    renderer.TransparentBackground = r.TransparentBackground
    renderer.stampPlate = r.stampPlate
    

    Canvas:OFD 印章打开抠底后再画

    ofdgo_renderer_stamp_canvas.go 的 renderStamp。无 Clip 时仍按矢量缩放进 Stamp.Box,但子渲染器会丢掉白底:

    renderer := r.childRenderer(reader)
    renderer.stampPlate = true
    renderer.TransparentBackground = true
    // ...
    ctx.Scale(s.Box.W/sealBox.W, s.Box.H/sealBox.H)
    err = renderer.renderCanvasPageToContext(ctx, content, false)
    

    有 Clip 时仍走 renderOFDStampImage。那里同样打开 stampPlate,再光栅化。图片章和光栅化结果进入 renderStampImage,调用 imageWithoutStampPlate。

    Canvas:跳过近白填充和描边

    ofdgo_renderer_path_canvas.go 的 renderPath:

    if shouldFill && r.stampPlate && style.fillPattern == nil && stampPlatePaint(style.fillPaint) {
    	shouldFill = false
    }
    // ...
    if shouldStroke && r.stampPlate && style.strokePattern == nil && stampPlatePaint(style.strokePaint) {
    	shouldStroke = false
    }
    

    stampPlatePaint 只接受 color.Color。渐变对象断言失败,不会被跳过。

    Canvas:OFD 印章内部的图片

    ofdgo_renderer_image_canvas.go 的 renderImage,在效果处理之后:

    if r.stampPlate {
    	img, err = imageWithoutStampPlate(r.pageContext, img)
    }
    

    编译路径:OFD 印章同样打开标志

    ofdgo_render_compile_image.go 的 drawStampContent:

    renderer := c.renderer.childRenderer(reader)
    renderer.stampPlate = true
    renderer.TransparentBackground = true
    page, err := renderer.CompilePage(content)
    

    直接的图片章在 stampImage 里调用 imageWithoutStampPlate。印章内部图片在 addImage 里、仅当 stampPlate 为真时抠底。

    编译路径:不生成近白纯色指令

    ofdgo_render_compile.go 的 command:

    if c.renderer.stampPlate && paint.Gradient == nil && paint.Mesh == nil && stampPlateColor(paint.Color) {
    	return nil
    }
    

    填充和普通描边都会进入 command,所以同色的白填充和白描边都不会进入页面指令。

    测试

    ofdgo_stamp_plate_test.go 的 TestStampPlateDoesNotCoverPage。

    页面是 20mm 黑色矩形。印章框是上方 20mm×10mm,左半是不透明白,右半是红。分别用 PNG 章和内嵌白路径加红路径的 OFD 章,走 RenderToPNG 和 Canvas 光栅化。红印必须还在;红印左侧、同一行上的像素必须是页面黑色,不能是白或浅灰。

    go test -count=1 -run TestStampPlateDoesNotCoverPage .
    
    
    
  • @Ta / 10-20 12:20 / 样 / 源
    @旧人,这是错误做法,这应该是素材自身保证,而不是软件来擦屁股,不做非标准兼容。
  • @Ta / 10-20 12:22 / 样 / 源
    @旧人,白底和透明是两种概念,程序不应该做特殊处理,且这么处理也不符合OFD标准。
  • @Ta / 10-20 12:24 / 样 / 源

    @爱特,

    不清楚是否素材问题,数科打开是没有这个问题的

  • @Ta / 10-20 12:33 / 样 / 源
    @旧人,数科有很多非标准做法,比如签章会自动半透明等等。
  • @Ta / 10-20 12:57 / 样 / 源

    太强了,这个ui看起来

  • @Ta / 10-20 14:10 / 样 / 源

    @爱特,

    这也是没办法的事情,我们只能规范自己,规范不了别人,所以很多时候只能做兼容处理

  • @Ta / 10-20 14:20 / 样 / 源
    @旧人,没必要,我对比过,除了数科及用数科引擎的,第三方库、各种在线阅读器,都不做这个特殊处理,本身不符合标准。
  • @Ta / 10-21 19:06 / 样 / 源
    ofdgo_windows_amd64(202610211859).exe(40.7 MB)
    OFDGo(202610211859-67f4cf9)更新说明:
    1.修复前期可打开的151份PDF样本因严格校验导致的打开阻断。
    2.大幅提升PDF兼容,转换4343份PDF样本,成功4200份,通过率高达96.71%。
  • @Ta / 10-23 23:35 / 样 / 源
    后续最新版本直接在https://ofdgo.aite.me/releases/下载
添加新回复
回复需要登录。