You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Android的PdfDocument生成可选中文本并优化PDF体积?

解决PdfDocument生成PDF的文本不可选与体积过大问题(无第三方库,API29+)

问题根源

你遇到的两个问题本质上都是因为将XML布局渲染为Bitmap后写入PDF:

  • Bitmap是像素图,PDF中只能识别为图片,无法选中文本;
  • 位图文件体积远大于矢量文本/元素,导致PDF臃肿。

无第三方库的解决方案

核心思路:直接使用PdfDocument的Canvas绘制矢量元素(文本、线条、矢量图等),而非渲染整个View为Bitmap。以下是具体实现:


1. 直接绘制可选中的矢量文本

用StaticLayout(API29+推荐用StaticLayout.Builder)处理多行文本,直接绘制到PDF的Canvas上,生成的文本是矢量格式,支持选中,且体积极小。

示例代码(Kotlin):

// 初始化PdfDocument
val pdfDoc = PdfDocument()
// 创建A4尺寸的页面(宽595pt,高842pt,对应标准A4)
val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create()
val page = pdfDoc.startPage(pageInfo)
val canvas = page.canvas
val density = resources.displayMetrics.density

// 配置文本Paint
val textPaint = Paint().apply {
    color = Color.BLACK
    textSize = 16f * density // 16sp
    typeface = Typeface.DEFAULT
    isAntiAlias = true
}

// 处理多行文本,自动换行
val content = "这是一段可直接选中的多行文本,通过StaticLayout绘制到PDF Canvas,无需Bitmap。生成的PDF体积小,文本可复制选中,完全使用Android原生API实现。"
val staticLayout = StaticLayout.Builder.obtain(content, 0, content.length, textPaint, 550)
    .setAlignment(Layout.Alignment.ALIGN_NORMAL)
    .setLineSpacing(0f, 1.2f) // 行间距倍数1.2
    .build()

// 将Canvas平移到合适位置(距左20dp,距上50dp)
canvas.save()
canvas.translate(20 * density, 50 * density)
staticLayout.draw(canvas)
canvas.restore()

// 结束页面,写入文件并启用压缩(API29+)
pdfDoc.finishPage(page)
val outputFile = File(getExternalFilesDir(null), "optimized.pdf")
FileOutputStream(outputFile).use { os ->
    pdfDoc.writeTo(os, PdfDocument.WriteFlags.COMPRESS)
}
pdfDoc.close()

2. 还原XML布局的复杂结构(无需Bitmap)

如果需要复现XML中的布局逻辑(比如线性布局、图片+文本组合),可以解析布局参数后手动绘制元素:

  • 对于TextView:提取文本、字体、颜色等属性,用上述StaticLayout绘制;
  • 对于ImageView:如果是矢量图,直接调用VectorDrawable.draw(canvas);如果是位图,尽量使用小尺寸或压缩后的图片;
  • 对于布局容器(如LinearLayout):手动计算每个元素的位置(用Rect确定边界),依次绘制。

示例:绘制带矢量图标的文本行

// 加载矢量图标
val icon = resources.getDrawable(R.drawable.ic_baseline_info_24, theme) as VectorDrawable
icon.setBounds(0, 0, (24*density).toInt(), (24*density).toInt())

// 绘制图标
canvas.save()
canvas.translate(20*density, 100*density)
icon.draw(canvas)
canvas.restore()

// 绘制右侧文本
val iconTextPaint = Paint(textPaint)
iconTextPaint.textSize = 14f*density
canvas.drawText("这是图标右侧的文本", 50*density, 118*density, iconTextPaint)

3. API29+专属体积优化

使用PdfDocument.WriteFlags.COMPRESS标志写入PDF,Android会自动压缩PDF中的内容,进一步减小体积:

pdfDoc.writeTo(outputStream, PdfDocument.WriteFlags.COMPRESS)

关键注意事项

  • 绝对避免使用View.draw(canvas)将整个布局渲染为Bitmap,这会回到原始问题;
  • 所有绘制操作直接针对PdfDocument的Canvas,而非内存中的Bitmap;
  • 优先使用矢量资源(VectorDrawable、Icon)而非位图,进一步控制体积。

内容的提问来源于stack exchange,提问作者Augusto Carmo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 14:35:34