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

如何通过WiFi/蓝牙打印机打印PDF并适配不同打印机尺寸?

问题:PDF适配不同尺寸打印机的实现方案

我需要通过WiFi或蓝牙打印机打印PDF文件,目前已完成打印机连接,但遇到打印机尺寸小于PDF默认A4页面的问题,导致打印内容被截断、效果异常。现寻求让PDF适配所有尺寸打印机的解决方案,以下是我的Kotlin实现代码、打印效果及打印机信息:

生成PDF的核心代码

private fun savePdf(
    invoiceType: String,
    invoiceModel: SalesDatabase,
    date: String,
    personType: String,
    personName: String,
    paymentMethod: String
) {

    val am: AssetManager = context.assets
    var font: PdfFont?
    try {
        am.open("text.ttf").use { inStream -> 
            val buffer = ByteArrayOutputStream()
            var nRead: Int
            val data = ByteArray(16384)
            while (inStream.read(data, 0, data.size).also { nRead = it } != -1) {
                buffer.write(data, 0, nRead)
            }
            font = PdfFontFactory.createFont(buffer.toByteArray(), PdfEncodings.UTF8)
        }
    } catch (e: Exception) {
        Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
        return
    }

    val mFileName = SimpleDateFormat(
        "yyyyMMdd_HHmmss",
        Locale.getDefault()
    ).format(System.currentTimeMillis())
    val mFilePath =
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
            .toString() + "/" + "$invoiceType Invoice" + mFileName + ".pdf"
    val file = File(mFilePath)
    file.createNewFile()
    val outputStream: OutputStream = FileOutputStream(file)
    val pdfWriter = PdfWriter(outputStream)
    val pdfDocument = PdfDocument(pdfWriter)
    val mDoc = Document(pdfDocument)
    try {
        if (file.exists()) {

            //Setting
            mDoc.pdfDocument.defaultPageSize = PageSize.A4

            val typeInvoice = "Invoice $invoiceType"
            val text =
                Paragraph(typeInvoice).setBold().setFontColor(ColorConstants.BLACK)
                    .setFont(font).setTextAlignment(TextAlignment.CENTER).setFontSize(22f)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)

            val textWidth = PageSize.A4.width
            val table1 = Table(FloatArray(1) { textWidth })
            table1.addCell(
                Cell().add(text)
                    .setBorder(Border.NO_BORDER)
                    .setHorizontalAlignment(HorizontalAlignment.CENTER)
                    .setTextAlignment(TextAlignment.CENTER)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            )

            mDoc.add(table1)

            addLineSpace(mDoc)

            val width = PageSize.A4.width / 3
            val columns = FloatArray(3)
            columns[0] = width
            columns[1] = width
            columns[2] = width
            val infoTable = Table(columns)
            val theDate =
                Paragraph("Date : $date").setBold().setFont(font)
                    .setFontColor(ColorConstants.BLACK)
                    .setTextAlignment(TextAlignment.CENTER).setFontSize(12f)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            val theTypeName =
                Paragraph("$personType Name: $personName").setBold().setFont(font)
                    .setFontColor(ColorConstants.BLACK)
                    .setTextAlignment(TextAlignment.CENTER).setFontSize(12f)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            val thePaymentType =
                Paragraph("Payment method : $paymentMethod").setBold().setFont(font)
                    .setFontColor(ColorConstants.BLACK)
                    .setTextAlignment(TextAlignment.CENTER).setFontSize(12f)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)

            infoTable.addCell(
                Cell().add(theDate)
                    .setBorder(Border.NO_BORDER)
                    .setHorizontalAlignment(HorizontalAlignment.CENTER)
                    .setTextAlignment(TextAlignment.CENTER)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            )
            infoTable.addCell(
                Cell().add(theTypeName)
                    .setBorder(Border.NO_BORDER)
                    .setHorizontalAlignment(HorizontalAlignment.CENTER)
                    .setTextAlignment(TextAlignment.CENTER)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            )
            infoTable.addCell(
                Cell().add(thePaymentType)
                    .setBorder(Border.NO_BORDER)
                    .setHorizontalAlignment(HorizontalAlignment.CENTER)
                    .setTextAlignment(TextAlignment.CENTER)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE)
            )

            mDoc.add(infoTable)

            addLineSpace(mDoc)
            addLineSeparator(mDoc)
            addLineSpace(mDoc)

            addTable(mDoc, invoiceModel, font)
        }

        mDoc.close()

        showPleaseWaitDialog(false, "")
        Toast.makeText(context, "تم إنشاء الملف", Toast.LENGTH_LONG).show()
        Toast.makeText(context, "تم حفظ الملف في $mFilePath", Toast.LENGTH_LONG).show()

        Handler().postDelayed({
//                    val intent = Intent(context, ShowPdf::class.java)
//                    intent.putExtra("pdfUrl", mFilePath)
//                    context.startActivity(intent)

            printPDF(file)
        }, 1000)
    } catch (e: Exception) {
        Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
    }
}

打印PDF的代码

private fun printPDF(file: File) {
    if (OptionDatabase.isWifiMode(context)) {
        val printManager: PrintManager? =
            getSystemService(context, PrintManager::class.java)
        try {
            val printDocumentAdapter: PrintDocumentAdapter =
                PdfDocumentAdapter(context, file.path)
            printManager!!.print(
                "Document",
                printDocumentAdapter,
                PrintAttributes.Builder().build()
            )
        } catch (ex: java.lang.Exception) {
            Toast.makeText(context, "Can't read pdf file", Toast.LENGTH_SHORT).show()
        }
        return
    }
    if (OptionDatabase.isBluetoothMode(context)) {
        Printooth.printer().printingCallback = this
        Printooth.printer().print(pdfToBitmap(file))
        return
    }
}

PDF转Bitmap的代码

private fun pdfToBitmap(pdfFile: File): ArrayList<Printable> {
    val bitmaps = ArrayList<Printable>()
    try {
        val renderer = PdfRenderer(
            ParcelFileDescriptor.open(
                pdfFile,
                ParcelFileDescriptor.MODE_READ_ONLY
            )
        )
        var bitmap: Bitmap
        val pageCount = renderer.pageCount
        for (i in 0 until pageCount) {
            val page = renderer.openPage(i)
            val width: Int =
                resources.displayMetrics.densityDpi / 72 * page.width
            val height: Int =
                resources.displayMetrics.densityDpi / 72 * page.height
            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
            page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
            bitmaps.add(ImagePrintable.Builder(bitmap).build())

            // close the page
            page.close()
        }

        // close the renderer
        renderer.close()
    } catch (ex: java.lang.Exception) {
        context.showToast(ex.message.toString(), 0)
    }
    return bitmaps
}

添加表格的代码

private fun addTable(document: Document, invoiceModel: PurchaseDatabase, font: PdfFont?) {

    val columns = FloatArray(5)
    columns[0] = 30f
    columns[1] = (PageSize.A4.width - 30f) / 4
    columns[2] = (PageSize.A4.width - 30f) / 4
    columns[3] = (PageSize.A4.width - 30f) / 4
    columns[4] = (PageSize.A4.width - 30f) / 4

    val table = Table(columns).setMarginBottom(20f)

    addCell(table, "", font!!, true)
    addCell(table, "Product Name", font, true)
    addCell(table, "Price", font, true)
    addCell(table, "Quantity", font, true)
    addCell(table, "Total", font, true)

    val myType = object : TypeToken<ArrayList<ProductBuyModel>>() {}.type
    val products =
        Gson().fromJson<ArrayList<ProductBuyModel>>(invoiceModel.products, myType)

    for (s in products.indices) {
        addCell(table, (s + 1).toString(), font, false)
        addCell(table, products[s].productName, font, false)
        val price =
            (products[s].productPrixTotal - products[s].productTax) / products[s].productQuantity
        addCell(table, price.toString(), font, false)
        addCell(table, products[s].productQuantity.toString(), font, false)
        addCell(table, products[s].productPrixTotal.toString(), font, false)
    }

    addCell(table, "", font, false)
    addCell(table, "", font, false)
    addCell(table, "Payment : ${invoiceModel.paymentPrice}", font, true)
    addCell(table, "Rest : ${invoiceModel.restPrice}", font, true)
    addCell(table, "Total : ${invoiceModel.totalPrice}", font, true)

    table.setBackgroundColor(ColorConstants.WHITE)
    table.isKeepTogether = true
    document.add(table)
}

打印效果与打印机信息

  • 打印效果:PDF内容因打印机尺寸小于A4,右侧内容被截断,无法完整显示。
  • 打印机信息:打印机为热敏小票打印机,支持的纸张宽度为80mm(约3.15英寸),远小于A4的210mm宽度。

内容的提问来源于stack exchange,提问作者Bakir Lakaf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 13:18:05