使用iText实现将CSDN博客保存为PDF文档

Csdn作为开发者的专业社区,是最流行的技术学习总结和分享交流的平台。但有时候我们也需要将学习总结的技术做线下分享,因此我们可能希望将我们Csdn的博客,保存成线下文档。本文将介绍如何基于iText实现将Csdn的博客保存为PDF文档保存。

iText简介

iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。

获取博客内容

本文介绍基于Android平台的转换方法,其他运行Java的平台,包括Kotlin Multiplatform、JavaWeb等都可以使用该方法。

使用Okhttp3框架访问博客链接,并获取博客内容并将结果转换成InputStream。

    val serverResponse =
        OkHttpReqKit.requestServer("$ASSISTANT_SERVER$ASSISTANT_CONVERT_PATH?url=$url")   // 借助服务器将内容将博客内容格式化

    val result = serverResponse.body?.string()?.let {
        JSON.parse(it)
    }
    val serverUri =
        (result as? JSONObject)?.takeIf { it.getString("state") == "ok" }?.getString("path")
            ?: return null
    val response = OkHttpReqKit.requestServer("$ASSISTANT_SERVER$serverUri")
    val inputStream = response.toString().byteInputStream(charset = Charset.forName("utf-8") // 将博客内容转成inputStream备用

生成PDF文档

将iText功能封装成一个工具类HtmlToPdfKit。

object HtmlToPdfKit {
    fun convertToPdf(
        inputStream: InputStream?,
        waterMark: String?,
        outputStream: OutputStream?
    ) {
        inputStream ?: return
        val defaultFontProvider = DefaultFontProvider(false, false, true, "仿宋")

        val properties = ConverterProperties().also {
            it.setFontProvider(defaultFontProvider)
        }
        val pdfWriter = PdfWriter(outputStream)
        val pdfDocument = PdfDocument(pdfWriter).also {
            // 设置为A4大小
            it.defaultPageSize = PageSize.A4
            waterMark?.let { wm ->
                it.addEventHandler(PdfDocumentEvent.INSERT_PAGE, WaterMarkEventHandler(wm)) // 添加水印
            }
            it.addEventHandler(PdfDocumentEvent.END_PAGE, PageEventHandler())  // 添加页脚
        }
        readInputStream(inputStream)?.use {
            HtmlConverter.convertToPdf(it, pdfDocument, properties)
            pdfWriter.close()
            pdfDocument.close()
        } ?: LogTool.e("转换失败")
    }

    private fun readInputStream(inputStream: InputStream): InputStream? = try {
        var content: String?
        ByteArrayOutputStream().use {
            var len: Int
            val buffer = ByteArray(1024)
            while ((inputStream.read(buffer).also { len = it }) != -1) {
                it.write(buffer, 0, len)
            }
            content = it.toString()
        }
        content?.let {
            val regexSpecial = "&[a-zA-Z]{1,10};"
            val replaceAll: String =
                Pattern.compile(regexSpecial, Pattern.CASE_INSENSITIVE).matcher(it).replaceAll("")
                    .also { res ->
                        LogTool.i("replaced str = $res")
                    }
            getStringStream(replaceAll)
        }
    } catch (e: Exception) {
        LogTool.e("错误信息:${e.message}")
        null
    }

    private fun getStringStream(string: String?): InputStream? = try {
        string?.takeIf { it.trim().isNotEmpty() }?.toByteArray()?.let {
            ByteArrayInputStream(it)
        }
    } catch (ioe: IOException) {
        LogTool.e("错误信息:${ioe.message}")
        null
    }
}

iTextPdfDocument中,可以通过添加EventHandler来处理页眉、页脚、水印等。

class PageEventHandler : IEventHandler {
    override fun handleEvent(event: Event) {
        val documentEvent = event as PdfDocumentEvent
        val document = documentEvent.document
        val page = documentEvent.page
        val pageSize: Rectangle = page.pageSize

        val pdfFont: PdfFont? = try {
            PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false)
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }

        val pdfCanvas = PdfCanvas(page.lastContentStream, page.resources, document)
        val canvas = Canvas(pdfCanvas, pageSize)
        val x: Float = (pageSize.left + pageSize.right) / 2
        val y: Float = pageSize.bottom + 15
        val paragraph: Paragraph =
            Paragraph("第" + document.getPageNumber(page) + "页/共" + document.numberOfPages + "页")
                .setFontSize(10f)
                .setFont(pdfFont)
        canvas.showTextAligned(paragraph, x, y, TextAlignment.CENTER)
        canvas.close()
    }
}
class WaterMarkEventHandler @JvmOverloads constructor(
    // 水印内容
    private val waterMarkContent: String,
    
    //一页中有几列水印    
    private val waterMarkX: Int = 5,

    // 一页中每列有多少水印
    private val waterMarkY: Int = 5
) :
    IEventHandler {
    override fun handleEvent(event: Event) {
        val documentEvent = event as PdfDocumentEvent
        val document = documentEvent.document
        val page = documentEvent.page
        val pageSize: Rectangle = page.pageSize

        val pdfFont: PdfFont? = try {
            PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false)
        } catch (e: IOException) {
            e.printStackTrace()
            null
        }

        val pdfCanvas = PdfCanvas(page.newContentStreamAfter(), page.resources, document)
        val canvas: Canvas = Canvas(pdfCanvas, pageSize).also {
            it.setFontColor(WebColors.getRGBColor("lightgray"))
            it.setFontSize(16f)
            it.setFont(pdfFont)
        }

        val waterMark: Paragraph = Paragraph(waterMarkContent).setOpacity(0.5f)
        for (i in 0 until waterMarkX) {
            for (j in 0 until waterMarkY) {
                canvas.showTextAligned(
                    waterMark,
                    (150 + i * 300f),
                    (160 + j * 150f),
                    document.numberOfPages,
                    TextAlignment.CENTER,
                    VerticalAlignment.BOTTOM,
                    120f
                )
            }
        }
        canvas.close()
    }
}

再将Html内容的inputStream传入工具类,即可将内容转换成PDF保存。

直接使用工具

但如果你不想自己实现该功能,也可以关注公众号”梦想周游世界的猿同学“,或扫码关注。

关注公众号后,如需要将这篇博客Gerrit使用和配置转成PDF保存,只需给公众号发送如下消息:

发送消息后稍作等待,按提示发送”我的文档“,即可获取pdf文档下载链接。

接下来,我们只需点击文档地址链接,即可下载pdf文档。

浏览器提示已阻止不安全的下载时,选择保留。 下载完成后,点击文档打开。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值