android pdf框架-5,生成pdf

前面的文章都是如何展示pdf,这篇关于如何生成pdf文件.

使用图片生成pdf

原来我以为是高宽的问题,所以作了裁剪,后来发现是png的问题,一张png大小1000*1000左右,500k左右,生成的pdf达到5m,png转为jpg后会缩小到400k的图片,但生成的pdf会大大减小到600k左右.

在不使用分割法去加载页面,使用长图片生成的pdf如果不切割,那么读取的时候,解码可能内存溢出.这是使用recyclerview时发生的问题.所以我在代码里面作了切割.超过6000的高就切割分断,当然这样的pdf在其它的app看体验不太好.我把分割线去了,看着就像连续的图片.

使用vudroid查看器,因为它是根据页面分割法去加载,所以没有内存溢出的状况.但是现在自动切边还没有实现.使用recyclerview加载一整张图片,很容易实现自动切边.

使用mupdf生成

对于图片,生成pdf时,如果没有考虑内存的问题就比较容易了,如果考虑到内存可能会溢出,那么只能对图片切割处理,然后再生成页面.

切割后可以尽量以大的区域去展示pdf.

定义一个方法

fun createPdfFromImages(pdfPath: String?, imagePaths: List<String>): Boolean 

pdf存储的路径,与图片的路径,要支持多张图片生成pdf

假如生成的页面高宽值是8.3 * 72 * 2与11.7 * 72 * 2,就是8*11英寸的页面.好像是a4

那么生成页面时要处理图片大于这个高宽的情况.

以目前手机普遍在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,表示追加,追加时如果是空的文档自然就放到第一页了.

系统sdk也提供了相应的方法,和文本生成类似.原理就是,通过canvas,把view画出来.但这个方法有一个问题,对于大量的图片,会内存溢出,目前没有解决方案.

private fun createImagePage(
        context: Context?,
        parent: ViewGroup?,
        pdfDocument: PdfDocument,
        pageWidth: Int,
        pageNo: Int,
        path: String
    ) {
        val contentView =
            LayoutInflater.from(context)
                .inflate(R.layout.pdf_image_content, parent, false) as ImageView
        val bitmap = BitmapFactory.decodeFile(path)
        contentView.setImageBitmap(bitmap)
        val pageHeight = bitmap.height * pageWidth / bitmap.width
        val pageInfo: PdfDocument.PageInfo = PdfDocument.PageInfo
            .Builder(pageWidth, pageHeight, pageNo)
            .create()
        val page: PdfDocument.Page = pdfDocument.startPage(pageInfo)
        val canvas: Canvas = page.getCanvas()
        canvas.setLayerType(View.LAYER_TYPE_HARDWARE, null)
        val measureWidth = View.MeasureSpec.makeMeasureSpec(pageWidth, View.MeasureSpec.EXACTLY)
        val measuredHeight = View.MeasureSpec.makeMeasureSpec(pageHeight, View.MeasureSpec.EXACTLY)
        contentView.measure(measureWidth, measuredHeight)
        contentView.layout(0, 0, pageWidth, pageHeight)
        contentView.draw(canvas)
        Log.d(
            "TAG",
            String.format(
                "createImagePage:%s-%s",
                pageWidth,
                pageHeight,
            )
        )
        BitmapPool.getInstance().release(bitmap)

        // finish the page
        pdfDocument.finishPage(page)
    }

布局就是一个imageview.

在保存时,添加一些参数,document.writeTo(outputStream, PdfDocument.COMPRESSION_MODE_MEDIUM)能压缩一些.

相比mupdf的生成方式比,这个体积要小一些.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值