抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

如何开发一款 Android PDF 编辑器

这半个月来,一直都在做 PDF 编辑,因为是公司项目,并不打算多说,但多少总结一下。毕竟从一开始毫无头绪,到现在成品已出,中间阻力还是不算小的。

免费的 PDF 编辑库

之前有调研过,直到目前做成,pdfium 已经被证明可以很好的实现常规的 pdf 操作,高亮、下划线、波浪线、手写、印章、文字等。

关于底层库的编译,移步:浅谈 Android pdfium.so 编译

我看到有人说无法编译成功,这里请确保网络通畅,并且请多尝试,我大概下了一整天加一晚上,完成后再更新就简单很多了。

中间层 JNI

关于 SO 库与 上层交互的部分,我是使用的:

https://github.com/benjinus/android-support-pdfium

主要原因是,首先他的代码比较新,其次JNI代码写得不错,很多方法,传参的方式等等都能用这个 demo 中得知。(如果又恰好不是那么懂 JNI 的话,这个可以说是刚刚好,模仿就完事儿了)

上层 PDF阅读

关于阅读以及编辑的基础,也就是底子,这里选的是:

https://github.com/barteksc/AndroidPdfViewer

我将其 PDF SDK具体实现逻辑改成了上面的 android-support-pdfium,也证明了这个库在抽象上做得也很好,大概花了1个小时左右,就完成了替换。

PDF 的编辑

AndroidPdfViewer 这个库代码比较清晰,逻辑没有那么复杂,做编辑则直接继承其阅读的 View 进行扩展即可。当然,这只是开始,另外还有两个难点:

  • 读懂渲染逻辑,做到编辑后实时渲染,笔记不闪烁。
  • 读懂坐标与缩放逻辑,完成 pdf 与屏幕坐标的转换。

关于坐标转换,这里是三层:

  • 具备缩放与位移的屏幕位置 => 原始的屏幕位置
  • 手机原始的屏幕位置 => PDF 原始的页面位置

不知道好不好理解,就是 缩放和位移后的屏幕坐标 => 不缩放也不位移的屏幕坐标 => PDF 页面本身的坐标。

一些坑

在PDF 中,有些很骚的操作,比如有一个 AP 模式。

简单说明:有一个获取 annot_color 的接口,如果不将 AP 模式置空,则无法获取颜色,但一旦将 AP 置空,则会导致 annot 中添加的对象 object 丢失(也就是前一秒通过 annot_get_object_count 获取到的是有对象,在经过 annot_get_color后就变成了0),如果恰好在遍历每个 annot 时都进行了 color 的获取,那么在擦除其中一个 annot 的时候,将会丢失所有 annot 的对象,也就是屏幕上将不会存在任何能看见的东西。

解决办法:对于 SDK 中定义好的28种 annot,均可通过设置 AP 来进行颜色获取。但对与需要 appendObject 的 annot,不要使用设置 AP 来进行颜色获取,而是使用 annot_get_object 再 get_object_stroke/fill_color 进行获取。

接口主要参考:fpdf_annot.hfpdf_edit.h

一些关系

PDF 每一页上面每个元素都是 pageObject。添加的批注 annot 实际上也是 pageObject,对于 TEXT,IMAGE,PATH,RECT,可以依附于 STAMP、INK 类型的 annot,也可以直接加入 page 中作为一个 pageObject。

对于每一个对象的操作,都是依赖于 page 的,对于各元素的获取,都是先获取 pageObejectCount 或者 pageAnnotCount 进行getByIndex遍历拿到的。

如果玩熟了,就会很简单,否则就是一头雾水。

其他问题

  • 一些类型比如 FREE_TEXT的正确使用方式 并不知道
  • 画笔的笔头实现(或许是多个 object 拼接而成,很是复杂)
  • 图像、音频、视频插入(实现方式未曾尝试)

主要参考实现

主要参考源码中的 TEST 用例:

https://pdfium.googlesource.com/pdfium/+/refs/heads/master/fpdfsdk/fpdf_annot_embeddertest.cpp

另外这里要特别感谢一位大佬以及他的项目,若不是他的逻辑让我看到了实现的可能,那我很可能没有勇气去做:

https://github.com/KnIfER/PolymPic

这个项目可以得到一些对于批注等操作的使用经验,比如文本传入底层需要追加”0″、文件的保存、又或者是编辑比如高亮等批注操作需要传入怎样的参数等等。另外,可以看这位大佬的博客:

https://www.jianshu.com/u/77921c0f8d4f

在这个项目中使用到的 API 应该有一百多个~ 很累。


本站由以下主机服务商提供服务支持:

13条评论

  • ANDROIDER

    博主可否分享一下如何把AndroidPdfViewer中的屏幕坐标转换成pdf中的坐标 ?

    • Mosaic-C

      抱歉,这个不通用,没法分享,大概思路就是按 view 大小和 page size 去缩放,再使用 sdk 转换一次即可。

      • ANDROIDER

        我看你也是用 AndroidPdfViewer 所以才这么问, 🙂 “大概思路就是按 view 大小和 page size 去缩放,再使用 sdk 转换一次即可” 这里可以稍微再细说一下么 ~

        • Mosaic-C

          具体到 API 就是 sdk 中那几个 map 开头的方法(辨识度很高的),传入的是未缩放的坐标,对应得到的就是 page 中的坐标,另外,你可以参考文末PolymPic项目,其中基于放缩的下划线和标记逻辑中可以看到转换相关的调用。你多试试就知道了。

  • LinJ

    总共花了多久 ? 我想知道大概的工作量

    • Mosaic-C

      自行评估,非一人所为。如果你熟悉 pdfium 那只需要评估上层,如果要自己封底层,代码修改什么的,看你手速了~

  • dayang

    嗨喽楼主 我个人也在开发一款 PDF 编辑的软件 走的也是Pdfium这条路 。 在看到这篇文章之前 已经走完了 “免费的 PDF 编辑库” “中间层 JNI” “ 上层 PDF阅读” 这三步。 开始深耕源码里面的 public api 文档,说实话 我也是一边看 一边评估时间, 感觉api 这条路 不是那么好走。 我看了你的博客 从你发了一篇介绍 PDFium 源码连接介绍的文章 到这篇文章似乎有个月的时间间隔, 我可否理解为 一个团队 做了四个月? 你们团队有几个人 可否告知下 , 因为我这边是我个人开发, 到时候测试和设计UI 和交互也要我自己来。。。。 感觉任务艰巨啊 啊啊啊啊 。 另外你们有没有想过一些比较走捷径的路子, 我有一些想法, 方便的话咱们交流下

    • Mosaic-C

      根据互联网监管要求,评论均需审核,不会直接放出来。

  • aiden

    楼主好,我用pdfium时,添加的annot都直接写入了/Page对象。按理说应该生成一个/Annot对象,然后/Page对象将引用这个/Annot对象才对。这里你有遇到过吗

    • Mosaic-C

      没有细纠过,过去太久也已经记不太清了,抱歉~

发表评论