mcpField
P
pdf-annotations

pdf-annotations

github.com/rcosteira79开发16591 次安装6 周前更新
代码仓库

关于 pdf-annotations

面向 Android 开发者的 PDF 注释与页面对象编辑指南,说明如何根据平台版本选择编辑入口。内容涵盖注释添加、更新、删除、渲染及文件写入限制。

主要功能

  1. 01按 API 与扩展版本选择入口
  2. 02读取并编辑页面注释
  3. 03操作页面对象及变换矩阵
  4. 04配置注释渲染标志
  5. 05遵循文件描述符与页面生命周期限制

适用场景

  1. 01在兼容设备上添加 PDF 高亮
  2. 02更新或移除已有注释
  3. 03将页面对象用于点击选择和编辑
  4. 04排查注释未显示或写入失败

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rcosteira79/android-skills --skill pdf-annotations

安装后在 Claude Code 中输入/pdf-annotations确认已加载。

详细介绍

平台 PDF 注释与页面对象编辑

Android 从 API 36.1 起原生支持编辑 PDF 注释——在此之前,平台仅支持渲染,文档至今仍如此说明:Jetpack 查看器指南中的 PDF 界面是 PdfViewerFragment,它明确不支持编辑模式,并通过 android.intent.action.ANNOTATE intent 将注释操作交给其他应用 [kb://android/develop/ui/views/layout/pdf/implement-pdf-viewer]。在应用下面的版本门槛前,不要得出“注释工作要用第三方库”的结论。

版本门槛(先决定入口)

两个并行的渲染器提供相同的编辑界面 (android.graphics.pdf.component):

入口是否支持编辑设备要求
PdfRenderer.PageAPI 36.1+平台版本
PdfRendererPreV.PageSDK extension 18API 31+,且已更新 PDF 模块(PreV 本身需要 extension 13)

在运行时使用 SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) 进行门槛判断 [kb://android/guide/sdk-extensions/index]。通过 mainline 更新,Extension 18 可覆盖 API 31,因此 PdfRendererPreV 是覆盖大多数设备的分支。

工作流:打开 → 编辑 → 写入

// The fd must be seekable — pipes/sockets are rejected at construction.
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->
    PdfRenderer(fd).use { renderer ->                    // or PdfRendererPreV on the ext-18 branch
        renderer.openPage(0).use { page ->
            // Only SUPPORTED annotation types are listed. The pairs are
            // android.util.Pair — NO Kotlin destructuring (no component1/2);
            // read them with .first / .second.
            val existing = page.getPageAnnotations()
            val staleId: Int? = existing.firstOrNull { it.second is FreeTextAnnotation }?.first

            val highlight = HighlightAnnotation(boundsList)   // List<RectF> quads
            val id = page.addPageAnnotation(highlight)
            check(id != -1) { "annotation rejected" }         // add returns -1, it does NOT throw

            highlight.color = newColor
            page.updatePageAnnotation(id, highlight)          // by id -> Boolean success
            staleId?.let { page.removePageAnnotation(it) }    // by id, not list position
        }
        // Write BEFORE close(), to a separate writable fd; the boolean is
        // removePasswordProtection (IOException if true and the doc stays encrypted).
        ParcelFileDescriptor.open(
            dest,
            ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE,
        ).use { out ->
            renderer.write(out, false)
        }
    }
}

约定:

  • addPageAnnotation 返回注释 ID;无法添加注释时返回 -1——请检查返回值;只有空值、UNKNOWN 类型或已附加的参数才会抛出异常(IllegalArgumentException)。
  • 一个注释实例在整个文档中只能附加一次:添加已在“此页面或其他页面中添加”的实例会抛出异常。每次添加都要新建组件对象;不要在其他页面中重复使用已移除的对象。
  • update/remove 使用 ID,而非列表位置。 getPageAnnotations() 只返回 API 可建模的注释类型,因此包含不支持注释的页面会出现 ID 间隔——Pair.first ID 是稳定的句柄。
  • 一次只能打开一个页面——现有的 PdfRenderer 不变式(“任意时刻只能打开一个页面”)仍然适用;打开下一页前,请先关闭当前页面。
  • 先调用 write(),再调用 close(),目标必须是不同的、可写且可寻址的 fd;关闭后会抛出 IllegalStateException。
  • 这些 pair 是 android.util.Pair,不是 kotlin.Pair——Kotlin 解构(for ((id, a) in …))无法通过编译;请使用 .first/.second。

渲染编辑后的内容

只有当 RenderParams.renderFlags 包含相应标志时,注释类型才会被渲染:FLAG_RENDER_TEXT_ANNOTATIONS / FLAG_RENDER_HIGHLIGHT_ANNOTATIONS(以及 36.1 新增的 FLAG_RENDER_FREETEXT_ANNOTATIONS / FLAG_RENDER_STAMP_ANNOTATIONS)。添加的图章或自由文本“未显示”在预览位图中,通常是因为缺少渲染标志,而非添加失败——使用 RenderParams.Builder.setRenderFlags(...) 设置相应标志,并通过 render(bitmap, clip, transform, params) 重载重新渲染。渲染模式常量位于 RenderParams(以及旧版 PdfRenderer.Page)上——PdfRendererPreV.Page 没有这些常量,因此在 PreV 分支中请写作 RenderParams.Builder(RenderParams.RENDER_MODE_FOR_DISPLAY);通过 PdfRendererPreV.Page 限定该常量无法通过编译。

组件词汇(android.graphics.pdf.component)

  • HighlightAnnotation(List<RectF>) — 四边形列表,每个高亮片段对应一个矩形。
  • FreeTextAnnotation(RectF, String) — 边界 + 文本;可设置文本颜色和背景颜色。
  • StampAnnotation(RectF) — 容器:根据类契约,其子项(通过 addObject 添加)只能是 PdfPagePathObject、PdfPageTextObject 或 PdfPageImageObject。
  • 页面对象也可以直接附加到页面(addPageObject / getPageObjects / updatePageObject / removePageObject — 与注释使用相同的基于 id 的契约),并携带变换矩阵(PdfPageObject.setMatrix/transform)。PdfPagePathObject 的描边默认为 sRGB 中的不透明黑色。
  • Page.getTopPageObjectAtPosition(PointF, …) 会对最上层对象进行命中测试 — 可用于在更新或移除对象前,通过点击选中对象。

不在范围内(转到其他入口)

从头创建 PDF(PdfDocument 画布 API)、填写表单(API 35 的 applyEdit)、提取/搜索文本,以及嵌入查看器 UI(PdfViewerFragment — 仅用于显示 [kb://android/develop/ui/views/layout/pdf/implement-pdf-viewer])属于其他领域;只有在低于版本门槛时,或对于组件 API 未建模的注释类型,才考虑使用第三方 SDK。

评论

支持 Markdown