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

JavaFX应用热敏打印机发票打印字符串格式化对齐问题

JavaFX热敏打印机发票列对齐问题

我在JavaFX应用中实现热敏打印机发票打印功能时,遇到了字符串格式化问题,发票内容无法按预期对齐,尤其是**Qty(数量)和Price(价格)**列。

功能逻辑:生成发票内容并打印至热敏打印机,若未找到打印机则使用系统默认打印介质。我尝试通过添加额外空格调整格式,但没能实现列的正确对齐。以下是我的实现代码及当前输出效果,求解决。


实现代码

public static String padRight(String s, int n) {
    return String.format("%-" + n + "s", s);
}

public static String padRightInt(int s, int n) {
    return String.format("%-" + n + "d", s);
}

public static void printIvoice(Node node){
    Printer printer = Printer.getDefaultPrinter();
    PrinterJob printerJob = PrinterJob.createPrinterJob(printer);
    double scale = 0.791;
    node.getTransforms().add(new Scale(scale, scale));
    boolean success = printerJob.printPage(node);
    if (success) {
        printerJob.endJob();
    }
}

public static Node getPrintableText(String[] products){
    Label text = new Label();
    text.setFont(Font.font("Monospaced Regular", FontWeight.LIGHT, 11));
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("             Company Name\n");
    stringBuilder.append("---------------------------------------------\n");
    stringBuilder.append(String.format("%-40s %5s %10s\n", "Item", "Qty", "Price"));
    stringBuilder.append(String.format( "%-40s %5s %10s\n", "----", "---", "-----"));
    for(String product: products){

        if(product.trim().length() > 25){
            String firstLine = product.trim().substring(0, 25);
            stringBuilder.append(firstLine).append(" \n");
            String lastLine = product.trim().substring(25);
            int spaces = 50 - lastLine.length();
            stringBuilder.append( String.format(padRight(lastLine, spaces), lastLine) + String.format(padRightInt(1, 10), 1) +" "+ String.format(padRightInt(1000, 10), 1000)).append("\n");
        }else{
            String temp = product.trim();
            int spaces = 50 - temp.length();
            stringBuilder.append( String.format(padRight(temp, spaces), temp) + String.format(padRightInt(1, 10), 1) +" "+ String.format(padRightInt(1000, 10), 1000)).append("\n");
        }
    }
    stringBuilder.append("_____________________________________________\n");
    stringBuilder.append("Net Amount                                 5000\n");
    stringBuilder.append("_____________________________________________\n");
    stringBuilder.append("Thank you so much!\n");
    text.setText(stringBuilder.toString());
    return text;
}

主方法

public static void main(String[] args) {
    String[] productName = {
            "Android",
            "Mac OS",
            "Windows 10 Operating System",
            "Linux Operating System",
            "Windows Server OS"
    };
    Node node = getPrintableText(productName);
    printIvoice(node);
}

当前输出效果

发票输出效果


问题分析与解决办法

核心问题

  1. 格式拼接逻辑混乱:手动计算空格的方式错误,且混用String.format和自定义padRight方法,导致列宽计算偏差。
  2. 多行列对齐失效:长产品名称换行后,第二行未与其他行的Qty、Price列保持对齐,未预留固定列宽位置。
  3. 组件渲染偏差:Label对多行等宽文本的排版不够精准,容易出现间距异常。

修改后的代码

import javafx.scene.Node;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.transform.Scale;

public class InvoicePrinter {

    // 统一列宽常量,方便维护
    private static final int ITEM_COL_WIDTH = 30;
    private static final int QTY_COL_WIDTH = 8;
    private static final int PRICE_COL_WIDTH = 12;

    public static void printInvoice(Node node) {
        Printer printer = Printer.getDefaultPrinter();
        PrinterJob printerJob = PrinterJob.createPrinterJob(printer);
        if (printerJob == null) {
            System.err.println("无法创建打印任务");
            return;
        }
        double scale = 0.791;
        node.getTransforms().add(new Scale(scale, scale));
        boolean success = printerJob.printPage(node);
        if (success) {
            printerJob.endJob();
        } else {
            System.err.println("打印失败");
        }
    }

    public static Node getPrintableText(String[] products) {
        Text text = new Text();
        text.setFont(Font.font("Monospaced", FontWeight.LIGHT, 11));
        StringBuilder stringBuilder = new StringBuilder();

        // 表头部分
        stringBuilder.append(String.format("%" + 45 + "s%n", "Company Name"));
        stringBuilder.append("---------------------------------------------%n");
        stringBuilder.append(String.format("%-" + ITEM_COL_WIDTH + "s %-" + QTY_COL_WIDTH + "s %-" + PRICE_COL_WIDTH + "s%n", "Item", "Qty", "Price"));
        stringBuilder.append(String.format("%-" + ITEM_COL_WIDTH + "s %-" + QTY_COL_WIDTH + "s %-" + PRICE_COL_WIDTH + "s%n", "----", "---", "-----"));

        // 产品列表部分
        for (String product : products) {
            String trimmedProduct = product.trim();
            if (trimmedProduct.length() > ITEM_COL_WIDTH) {
                // 长文本拆分,第一行仅显示Item内容
                stringBuilder.append(String.format("%-" + ITEM_COL_WIDTH + "s%n", trimmedProduct.substring(0, ITEM_COL_WIDTH)));
                // 第二行显示剩余内容,对齐Qty和Price列
                String remaining = trimmedProduct.substring(ITEM_COL_WIDTH);
                stringBuilder.append(String.format("%-" + ITEM_COL_WIDTH + "s %-" + QTY_COL_WIDTH + "d %-" + PRICE_COL_WIDTH + "d%n", remaining, 1, 1000));
            } else {
                // 短文本直接按列对齐
                stringBuilder.append(String.format("%-" + ITEM_COL_WIDTH + "s %-" + QTY_COL_WIDTH + "d %-" + PRICE_COL_WIDTH + "d%n", trimmedProduct, 1, 1000));
            }
        }

        // 底部合计
        stringBuilder.append("_____________________________________________%n");
        stringBuilder.append(String.format("%-" + (ITEM_COL_WIDTH + QTY_COL_WIDTH) + "s %" + PRICE_COL_WIDTH + "d%n", "Net Amount", 5000));
        stringBuilder.append("_____________________________________________%n");
        stringBuilder.append(String.format("%" + 45 + "s%n", "Thank you so much!"));

        text.setText(stringBuilder.toString());
        return text;
    }

    public static void main(String[] args) {
        String[] productName = {
                "Android",
                "Mac OS",
                "Windows 10 Operating System",
                "Linux Operating System",
                "Windows Server OS"
        };
        Node node = getPrintableText(productName);
        printInvoice(node);
    }
}

修改要点

  1. 定义统一列宽常量:将Item、Qty、Price的列宽固化,避免手动计算错误。
  2. 简化格式化逻辑:全程使用String.format语法,确保每列宽度严格一致。
  3. 优化长文本换行:长产品名称拆分后,第二行仍占用Item列宽度,保证Qty和Price列对齐。
  4. 替换组件:用Text替代Label,提升多行等宽文本的渲染精度。
  5. 增加异常处理:打印任务创建失败时给出提示,增强代码健壮性。

内容的提问来源于stack exchange,提问作者Qaisar Shabbir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:54:58