如何用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
相关产品推荐
相关产品推荐

