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

Flutter printing: ^5.11.1生成POS小票无法占满宽度及顶部留白问题

Flutter 使用 printing: ^5.11.1 生成POS小票的问题与解决方案

问题现象

  • 已通过 PdfPageFormat 设置自定义页面格式,但内容无法占满打印页宽度;
  • 选择「Fit to printable area」选项时,内容居中显示且页面顶部存在留白。

相关截图:

待解决疑问

  1. 如何确保小票内容占满打印页的全部宽度?
  2. 选择「Fit to printable area」时,如何调整布局让内容顶部对齐?

简化版代码

import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';
import 'package:get/get.dart';

class PrintTest {
  final PdfPageFormat pageFormat;
  final double fontSize;

  PrintTest({required this.pageFormat, required this.fontSize});
}

const double defaultFontSize = 5;

Future<void> printImgTest3() async {
  List<String> toStringList = [
    "PdfPageFormat.a3",
    "PdfPageFormat.a4",
    "PdfPageFormat.a5",
    "PdfPageFormat.a6",
    "PdfPageFormat.legal",
    "PdfPageFormat.letter",
    "PdfPageFormat.roll57",
    "PdfPageFormat.roll80",
    "PdfPageFormat.undefined",
    "28 x 2",
  ];
  Get.generalDialog<PrintTest>(
    pageBuilder: (_, __, ___) {
      TextEditingController widthController = TextEditingController();
      TextEditingController heightController = TextEditingController();

      TextEditingController marginController = TextEditingController(text: '2');
      TextEditingController fontSizeController =
          TextEditingController(text: '$defaultFontSize');
      final GlobalKey<FormState> formKey = GlobalKey<FormState>();

      return Align(
        alignment: Alignment.center,
        child: Container(
          width: 400,
          margin: const EdgeInsets.all(20),
          padding: const EdgeInsets.all(20),
          child: Material(
            color: Colors.white,
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(10),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const SizedBox(height: 15),
                  CustomTextFormFieldWithLabel(
                    labelText: 'Font size',
                    controller: fontSizeController,
                    hintText: 'Default Value: $defaultFontSize',
                    inputFormatterList: acceptDecimalValues(),
                  ),
                  const SizedBox(height: 10),
                  Form(
                    key: formKey,
                    child: Column(
                      children: [
                        CustomTextFormFieldWithLabel(
                          labelText: 'Width',
                          controller: widthController,
                          inputFormatterList: acceptDecimalValues(),
                        ),
                        CustomTextFormFieldWithLabel(
                          labelText: 'Height',
                          controller: heightController,
                          inputFormatterList: acceptDecimalValues(),
                        ),
                        CustomTextFormFieldWithLabel(
                          labelText: 'Margin',
                          controller: marginController,
                          inputFormatterList: acceptDecimalValues(),
                        ),
                        ElevatedButton(
                          onPressed: () {
                            if (formKey.currentState!.validate()) {
                              Get.back(
                                result: PrintTest(
                                  pageFormat: PdfPageFormat(
                                    double.parse(widthController.text) *
                                        PdfPageFormat.mm,
                                    double.parse(heightController.text) *
                                        PdfPageFormat.mm,
                                    marginAll:
                                        double.parse(marginController.text) *
                                            PdfPageFormat.mm,
                                  ),
                                  fontSize: double.tryParse(
                                          fontSizeController.text) ??
                                      defaultFontSize,
                                ),
                              );
                            }
                          },
                          child: const Text('Select Custom Format'),
                        )
                      ],
                    ),
                  ),
                  const SizedBox(height: 10),
                  Wrap(
                    alignment: WrapAlignment.center,
                    children: <PdfPageFormat>[
                      PdfPageFormat.a3,
                      PdfPageFormat.a4,
                      PdfPageFormat.a5,
                      PdfPageFormat.a6,
                      PdfPageFormat.legal,
                      PdfPageFormat.letter,
                      PdfPageFormat.roll57,
                      PdfPageFormat.roll80,
                      PdfPageFormat.undefined,
                      const PdfPageFormat(
                        28 * PdfPageFormat.mm,
                        double.infinity,
                        marginAll: 2 * PdfPageFormat.mm,
                      ),
                    ]
                        .mapIndexed(
                          (e, i) => Padding(
                            padding: const EdgeInsets.all(5),
                            child: InkWell(
                              onTap: () {
                                Get.back(
                                  result: PrintTest(
                                    pageFormat: e,
                                    fontSize: double.tryParse(
                                            fontSizeController.text) ??
                                        defaultFontSize,
                                  ),
                                );
                              },
                              child: Padding(
                                padding: const EdgeInsets.all(8.0),
                                child: Text(toStringList[i]),
                              ),
                            ),
                          ),
                        )
                        .toList(),
                  ),
                ],
              ),
            ),
          ),
        ),
      );
    },
    barrierDismissible: true,
    barrierLabel: 'test-print-3',
  ).then((PrintTest? value) async {
    if (value == null) {
      return;
    }

    final doc = pw.Document(); 
 
    doc.addPage(
      pw.Page(
        pageFormat: value.pageFormat,
        orientation: pw.PageOrientation.portrait,
        build: (pw.Context context) {
          return pw.Column(
            crossAxisAlignment: pw.CrossAxisAlignment.center,
            mainAxisSize: pw.MainAxisSize.min,
            children: [
              pw.FittedBox(
                alignment: pw.Alignment.center,
                fit: pw.BoxFit.scaleDown,
                child: pw.Text(
                  'Business Name',
                  style: pw.TextStyle(
                    fontWeight: pw.FontWeight.bold,
                    fontSize: value.fontSize + 1,
                  ),
                ),
              ), 
              pw.SizedBox(height: 1),
              pw.FittedBox(
                alignment: pw.Alignment.center,
                fit: pw.BoxFit.scaleDown,
                child: pw.Text(
                  'Transaction Id: 32',
                  style: pw.TextStyle(
                    fontWeight: pw.FontWeight.bold,
                    fontSize: value.fontSize,
                  ),
                ),
              ),
              pw.FittedBox(
                alignment: pw.Alignment.center,
                fit: pw.BoxFit.scaleDown,
                child: pw.Text(
                  'Dec 2, 2034 1:08 AM',
                  style: pw.TextStyle(
                    fontWeight: pw.FontWeight.bold,
                    fontSize: value.fontSize,
                  ),
                ),
              ),
              pw.Row(
                children: [
                  pw.Expanded(
                    flex: 3,
                    child: pw.SizedBox(
                      height: 10,
                      child: pw.FittedBox(
                        alignment: pw.Alignment.centerLeft,
                        fit: pw.BoxFit.scaleDown,
                        child: pw.Text(
                          'Product',
                          style: pw.TextStyle(
                            fontSize: value.fontSize,
                            fontWeight: pw.FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ),
                  pw.Expanded(
                    flex: 1,
                    child: pw.SizedBox(
                      height: 8,
                      child: pw.FittedBox(
                        alignment: pw.Alignment.center,
                        fit: pw.BoxFit.scaleDown,
                        child: pw.Text(
                          'Qnty.',
                          style: pw.TextStyle(
                            fontWeight: pw.FontWeight.bold,
                            fontSize: value.fontSize,
                          ),
                        ),
                      ),
                    ),
                  ),
                  pw.Expanded(
                    flex: 1,
                    child: pw.SizedBox(
                      height: 8,
                      child: pw.FittedBox(
                        alignment: pw.Alignment.center,
                        fit: pw.BoxFit.scaleDown,
                        child: pw.Text(
                          'Price',
                          style: pw.TextStyle(
                            fontWeight: pw.FontWeight.bold,
                            fontSize: value.fontSize,
                          ),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              ...[
                1,
                2,
                3,
                4,
                5,
              ].map(
                (e) => pw.Row(
                  children: [
                    pw.Expanded(
                      flex: 3,
                      child: pw.Text(
                        'Product 1 jshkdh jhkash kafhjah fkahka ',
                        style: pw.TextStyle(
                          fontSize: value.fontSize - 1,
                        ),
                      ),
                    ),
                    pw.Expanded(
                      flex: 1,
                      child: pw.SizedBox(
                        height: 7,
                        child: pw.FittedBox(
                          alignment: pw.Alignment.center,
                          fit: pw.BoxFit.scaleDown,
                          child: pw.Text(
                            '2',
                            style: pw.TextStyle(
                              fontSize: value.fontSize - 1,
                            ),
                          ),
                        ),
                      ),
                    ),
                    pw.Expanded(
                      flex: 1,
                      child: pw.SizedBox(
                        height: 7,
                        child: pw.FittedBox(
                          alignment: pw.Alignment.center,
                          fit: pw.BoxFit.scaleDown,
                          child: pw.Text(
                            'Rs.34534',
                            style: pw.TextStyle(
                              fontSize: value.fontSize - 1,
                            ),
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          );
        },
      ),
    );

    await Printing.layoutPdf(
      format: value.pageFormat,
      onLayout: (PdfPageFormat ppf) => doc.save(),
    );
  });
}

解决方案

1. 让小票内容占满打印页宽度

代码中大量使用pw.FittedBox并设置fit: pw.BoxFit.scaleDown,会强制文本缩小适应容器,同时根Column的crossAxisAlignment: pw.CrossAxisAlignment.center会限制子元素宽度。修改方式:

  • 移除不必要的pw.FittedBox,用pw.Text的textAlign属性控制对齐,配合Expanded占据横向空间;
  • 将Column的crossAxisAlignment改为pw.CrossAxisAlignment.stretch,让子元素撑满可用宽度;
  • 过长文本用softWrap: true和overflow: pw.TextOverflow.ellipsis处理,避免内容被缩小;
  • 检查PdfPageFormat的边距设置,避免过大边距压缩内容宽度。

核心布局修改示例:

return pw.Column(
  crossAxisAlignment: pw.CrossAxisAlignment.stretch,
  mainAxisSize: pw.MainAxisSize.min,
  children: [
    pw.Text(
      'Business Name',
      textAlign: pw.TextAlign.center,
      style: pw.TextStyle(
        fontWeight: pw.FontWeight.bold,
        fontSize: value.fontSize + 1,
      ),
    ),
    pw.SizedBox(height: 1),
    pw.Text(
      'Transaction Id: 32',
      textAlign: pw.TextAlign.center,
      style: pw.TextStyle(
        fontWeight: pw.FontWeight.bold,
        fontSize: value.fontSize,
      ),
    ),
    pw.Row(
      children: [
        pw.Expanded(
          flex: 3,
          child: pw.Text(
            'Product',
            textAlign: pw.TextAlign.left,
            style: pw.TextStyle(
              fontSize: value.fontSize,
              fontWeight: pw.FontWeight.bold,
            ),
          ),
        ),
        pw.Expanded(
          flex: 1,
          child: pw.Text(
            'Qnty.',
            textAlign: pw.TextAlign.center,
            style: pw.TextStyle(
              fontWeight: pw.FontWeight.bold,
              fontSize: value.fontSize,
            ),
          ),
        ),
        pw.Expanded(
          flex: 1,
          child: pw.Text(
            'Price',
            textAlign: pw.TextAlign.center,
            style: pw.TextStyle(
              fontWeight: pw.FontWeight.bold,
              fontSize: value.fontSize,
            ),
          ),
        ),
      ],
    ),
    // 商品列表部分移除SizedBox和FittedBox,直接用Expanded包裹Text
    ...[1,2,3,4,5].map((e) => pw.Row(
      children: [
        pw.Expanded(
          flex:3,
          child: pw.Text(
            'Product 1 jshkdh jhkash kafhjah fkahka ',
            style: pw.TextStyle(fontSize: value.fontSize-1),
            softWrap: true,
          ),
        ),
        pw.Expanded(
          flex:1,
          child: pw.Text(
            '2',
            textAlign: pw.TextAlign.center,
            style: pw.TextStyle(fontSize: value.fontSize-1),
          ),
        ),
        pw.Expanded(
          flex:1,
          child: pw.Text(
            'Rs.34534',
            textAlign: pw.TextAlign.center,
            style: pw.TextStyle(fontSize: value.fontSize-1),
          ),
        ),
      ],
    ))
  ],
);

2. 「Fit to printable area」时顶部对齐

该选项会让打印机自动缩放内容并居中,导致顶部留白。解决方式:

  • 用pw.Align包裹根布局,设置alignment: pw.Alignment.topCenter,强制内容从顶部开始;
  • 确保Column的mainAxisAlignment为pw.MainAxisAlignment.start(默认值,若有额外padding需移除);
  • 检查PdfPageFormat的marginTop设置,若不需要顶部边距可设为0;
  • 可尝试在Printing.layoutPdf中添加usePrinterSettings: false,禁用打印机的自动调整(需根据打印机型号适配)。

修改后的pw.Page示例:

pw.Page(
  pageFormat: value.pageFormat,
  orientation: pw.PageOrientation.portrait,
  build: (pw.Context context) {
    return pw.Align(
      alignment: pw.Alignment.topCenter,
      child: pw.Column(
        crossAxisAlignment: pw.CrossAxisAlignment.stretch,
        mainAxisSize: pw.MainAxisSize.min,
        children: [
          // 内容组件
        ],
      ),
    );
  },
),

内容的提问来源于stack exchange,提问作者MUHAMMAD UMAIR BIN SAJID

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:09:54