前面的文章都是如何展示pdf,这篇关于如何生成pdf文件.
使用图片生成pdf
使用mupdf生成
对于图片,生成pdf时,如果没有考虑内存的问题就比较容易了,如果考虑到内存可能会溢出,那么只能对图片切割处理,然后再生成页面.
切割后可以尽量以大的区域去展示pdf.
定义一个方法
fun createPdfFromImages(pdfPath: String?, imagePaths: List<String>): Boolean
pdf存储的路径,与图片的路径,要支持多张图片生成pdf
假如生成的页面高宽值是8.3 * 72 * 2与11.7 * 72 * 2,就是8*11英寸的页面.好像是a2
那么生成页面时要处理图片大于这个高宽的情况.
以目前手机普遍在1080*1920的分辨率之上,所以我定义了最大的高为2160.当图片的高大于这个高度,比如微博里面有好多图片是长图,那么就要进行切割.
var mDocument: PDFDocument? = null
try {
mDocument = PDFDocument.openDocument(pdfPath) as PDFDocument
} catch (e: Exception) {
Log.d("TAG", "could not open:$pdfPath")
}
if (mDocument == null) {
mDocument = PDFDocument()
}
pdf的路径可以是新的,可以是旧的,所以先读取,如果没有读取到,可能是新的地址.如果是旧的地址,图片生成新的部分是追加到旧的后面的.
val resultPaths = processLargeImage(imagePaths)
//空白页面必须是-1,否则会崩溃,但插入-1的位置的页面会成为最后一个,所以追加的时候就全部用-1就行了.
var index = -1
for (path in resultPaths) {
val page = addPage(path, mDocument, index++)
mDocument.insertPage(-1, page)
}
mDocument.save(pdfPath, OPTS);
private const val OPTS = "compress-images;compress;incremental;linearize;pretty;compress-fonts"
大体的流程就是这样的.剩下的就是处理图片切割的方式了.
图片切割
private fun processLargeImage(imagePaths: List<String>): List<String> {
val options = BitmapFactory.Options()
//默认值为false,如果设置成true,那么在解码的时候就不会返回bitmap,即bitmap = null。
options.inJustDecodeBounds = true
val maxHeight = PAPER_HEIGHT
val result = arrayListOf<String>()
for (path in imagePaths) {
try {
BitmapFactory.decodeFile(path, options)
if (options.outHeight > maxHeight) {
//split image,maxheight=PAPER_HEIGHT
splitImages(result, path, options.outWidth, options.outHeight)
} else {
//result.add(path)
val bitmapPath = compressImageFitPage(path, options.outWidth, options.outHeight)
result.add(bitmapPath)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return result
}
这里处理了两种,一种是大于高的图片,肯定切割,另一种是不大于高,但图片可能比较大,比如正方形的1400*1400的这种,如果直接使用它的高宽,生成的pdf会比较大,所以进行了屏幕如1080宽的适配操作.生成的pdf就不那么大了.
splitImages切割就是从图片顶点开始,按高度切割,然后保存在临时目录里面.
var top = 0f
val right = 0 + width
var bottom = PAPER_HEIGHT
while (bottom < height) {
val rect = android.graphics.Rect()
rect.set(0, top.toInt(), right, bottom.toInt())
splitImage(path, rect, result)
top = bottom
bottom += PAPER_HEIGHT
}
if (top < height) {
val rect = android.graphics.Rect()
rect.set(0, top.toInt(), right, height)
splitImage(path, rect, result)
}
切割图片后,与不切割的图片一样,做一次宽度适配
val mDecoder = BitmapRegionDecoder.newInstance(path, true)
val bm: Bitmap = mDecoder.decodeRegion(rect, null)
val file =
File(
PDFUtils.getExternalCacheDir(App.instance).path
//FileUtils.getStorageDirPath() + "/amupdf"
+ File.separator + "create" + File.separator + System.currentTimeMillis() + ".jpg"
)
PDFUtils.saveBitmapToFile(bm, file, Bitmap.CompressFormat.JPEG, 100)
Log.d("TAG", "new file:height:${rect.bottom - rect.top}, path:${file.absolutePath}")
//result.add(file.absolutePath)
//splitPaths.add(file)
val bitmapPath = compressImageFitPage(file.absolutePath, bm.width, bm.height)
result.add(bitmapPath)
这是宽度处理
private fun compressImageFitPage(
path: String,
width: Int,
height: Int,
): String {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = false
options.outWidth = PDF_PAGE_WIDTH.toInt()
options.outHeight = (height * PDF_PAGE_WIDTH / width).toInt()
val bitmap = BitmapFactory.decodeFile(path, options)
val file =
File(
PDFUtils.getExternalCacheDir(App.instance).path
+ File.separator + "create" + File.separator + System.currentTimeMillis() + ".jpg"
)
PDFUtils.saveBitmapToFile(bitmap, file, Bitmap.CompressFormat.JPEG, 100)
Log.d(
"TAG",
"bitmap.width:$width, height:$height,:${options.outWidth},${options.outHeight}, path:${file.absolutePath}"
)
return file.absolutePath
}
到这,切割就完成了,图片的高宽处理也完成了.剩下的就是将这些图片添加到页面上.
添加页面addPage
private fun addPage(
path: String,
mDocument: PDFDocument,
index: Int
): PDFObject? {
val image = Image(path)
val resources = mDocument.newDictionary()
val xobj = mDocument.newDictionary()
val obj = mDocument.addImage(image)
xobj.put("I", obj)
resources.put("XObject", xobj)
val w = image.width
val h = image.height
val mediabox = Rect(0f, 0f, w.toFloat(), h.toFloat())
val contents = "q $w 0 0 $h 0 0 cm /I Do Q\n"
val page = mDocument.addPage(mediabox, 0, resources, contents)
Log.d("TAG", String.format("index:%s,page,%s", index, contents))
return page
}
mupdf的语法都是类似的,js,c也都是这个规则.创建image对象.
contents要注意的点,这里设置了高宽,如果要添加内容,就要额外的操作了.否则这段应该是固定的.
在添加页面时,如果是-1,表示追加,追加时如果是空的文档自然就放到第一页了.