热敏打印机打印发票,javaFX 应用程序中的字符串格式问题

问题描述 投票:0回答:2

我在打印发票时遇到字符串格式问题。发票没有按照我的要求正确对齐。 该代码只是向热敏打印机生成发票。如果未找到打印机设备,则获取系统默认的打印介质。 我的代码如下。

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(" ").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);
}

代码输出如下

enter image description here

我想根据产品名称长度对齐输出。 我尝试通过额外的空格来格式化它,但它不起作用。我想正确对齐数量和价格列。请帮忙。预先感谢。

java string javafx formatting
2个回答
1
投票

这只是一个概念证明。

所以程序只是确认打印的布局内容,然后将其发送到默认打印机。

因此,程序也非常短小简单。足以完成概念验证。

PrintCanvasExample2.java

package com.example.demoticketprintapp;

import javafx.application.Application;
import javafx.print.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class PrintCanvasExample2 extends Application {
    static String invoice;

    public static String 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("%-24s  %5s  %10s\n", "Item", "Qty", "Price"));
        stringBuilder.append(String.format("%-24s  %5s  %10s\n", "----", "-----", "----------"));
        for (String product : products) {
            stringBuilder.append(formatTemplateString(product, 1, 12500));
        }
        stringBuilder.append("___________________________________________" + "\n");
        stringBuilder.append("Net Amount                             5000" + "\n");
        stringBuilder.append("___________________________________________" + "\n");
        stringBuilder.append("Thank you so much!" + "\n");
        //DEBUG
        System.out.println(stringBuilder.toString());
        return stringBuilder.toString();
    }

    private void print(Node node) {
        PrinterJob job = PrinterJob.createPrinterJob();
        if (job != null) {
            // Print the node
            boolean printed = job.printPage(node);
            if (printed) {
                // End the printer job
                job.endJob();
            } else {
                System.out.println("Printing failed.");
            }
        } else {
            System.out.println("Could not create a printer job.");
        }
    }

    public static String formatTemplateString(String str, long num1, long num2) {
        String formattedStr = String.format("%-24s", str);
        String formattedNum1 = String.format("%5d", num1);
        String formattedNum2 = String.format("%10d", num2);
        String rt = "";
        if (str.length() > 25) {
            if (str.length() <= 44) {
                rt = str + "\n" + " ".repeat(24) + "  " + formattedNum1 + "  " + formattedNum2;
            } else if (str.length() > 44) {
                String str1 = str.substring(0, 44);
                String str2 = str.substring(44);
                rt = str1 + "\n" + String.format("%-24s", str2) + "  " + formattedNum1 + "  " + formattedNum2;
            }
        } else {
            rt = formattedStr + "  " + formattedNum1 + "  " + formattedNum2;
        }
        return rt + "\n";
    }

    @Override
    public void start(Stage primaryStage) {
        Canvas canvas = new Canvas(400, 300);
        GraphicsContext gc = canvas.getGraphicsContext2D();

        gc.setFill(Color.WHITE);
        gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());

        // setup fonts
        //Linux
        gc.setFont(new Font("Monospaced Regular", 12));
        //WIndows
        //gc.setFont(new Font("Courier New", 12));
        gc.setFill(Color.BLACK);
        // Draw Text
        //gc.fillText(invoice2, 50, 50);
        gc.fillText(invoice, 50, 50);

        print(canvas);

        StackPane root = new StackPane();
        Scene scene = new Scene(root, 400, 300);
        primaryStage.setTitle("Print Canvas Example");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

FormatTemlate:44 个字符宽度

  • 如果项目名称长度> 24,请将其保持在一行
  • 如果项目名称长度> 44,则将其分成两行
public static String formatTemplateString(String str, long num1, long num2) {
        String formattedStr = String.format("%-24s", str);
        String formattedNum1 = String.format("%5d", num1);
        String formattedNum2 = String.format("%10d", num2);
        String rt = "";
        if (str.length() > 25) {
            if (str.length() <= 44) {
                rt = str + "\n" + " ".repeat(24) + "  " + formattedNum1 + "  " + formattedNum2;
            } else if (str.length() > 44) {
                String str1 = str.substring(0, 44);
                String str2 = str.substring(44);
                rt = str1 + "\n" + String.format("%-24s", str2) + "  " + formattedNum1 + "  " + formattedNum2;
            }
        } else {
            rt = formattedStr + "  " + formattedNum1 + "  " + formattedNum2;
        }
        return rt + "\n";
    }

调试输出

             Company Name            
-------------------------------------------
Item                        Qty       Price
----                      -----  ----------
Android                       1       12500
Mac OS                        1       12500
Windows 10 Operating System
                              1       12500
Linux Operating System        1       12500
Windows Server OS             1       12500
___________________________________________
Net Amount                             5000
___________________________________________
Thank you so much!

打印机输出:

enter image description here

JDK + JavaFX - Zulu - 21.0.3.fx-zulu


0
投票

您的问题可能有两个来源。

  1. 您的字符串格式不正确。这与 JavaFX 无关。

  2. 您没有使用等宽字体。这确实与JavaFX有关;即使您有正确格式化的字符串,使用可变宽度字体也会在视觉上弄乱对齐方式。

从您的问题和评论来看,两者似乎都适用于此。

字符串格式化逻辑应放置在一个或多个专用类中。这些类应该完全不知道 JavaFX。它们的实现方式还应该能够为价格、数量和商品名称动态分配空间。例如,确定一组订单中的最大价格和数量宽度,然后使用剩余宽度(总宽度中的)作为商品名称。

使用等宽字体可以很简单:

Text text = ...;
text.setFont(Font.font("Monospaced"));

示例

这是演示上述内容的概念验证。我并不声称

ReceiptFormatter
是以最好的方式实现的。该演示也不是 100% 稳健。例如:

  • 格式不能处理所有场景(例如,如果“页脚消息”太长则不会换行)。

  • 使用

    double
    代表金钱是一个坏主意。

  • 可能应该使用更多的类来实现该模型,而不仅仅是

    Order
    。也许一个
    Item
    代表单个产品及其价格,然后一个
    Order
    代表一个商品、数量和总价,然后一个
    Cart
    代表多个订单和总价。

  • 没有尝试根据打印机纸张的字体大小和宽度动态计算“线宽”。

我没有热敏打印机来测试这一点,但下面显示了将

Text
打印到 PDF 文件的结果。

版本

此演示是使用以下工具开发和测试的:

  • Windows 10

  • Java 22.0.1

  • JavaFX 22.0.1

源代码

  订单.java

public record Order(String item, int quantity, double totalPrice) {}

ReceiptFormatter.java

import java.util.HashMap;
import java.util.List;

public class ReceiptFormatter {

  private final String companyName;
  private final String separator;
  private final String message;
  private final int lineWidth;

  public ReceiptFormatter(String companyName, char separatorChar, String message, int lineWidth) {
    this.companyName = companyName;
    this.separator = Character.toString(separatorChar).repeat(lineWidth);
    this.message = message;
    this.lineWidth = lineWidth;
  }

  public String format(List<Order> orders, double netPrice) {
    var quantities = new HashMap<Order, String>(orders.size());
    var prices = new HashMap<Order, String>(orders.size());

    int itemWidth;
    int qtyWidth = 3;
    int priceWidth = 5;

    for (var order : orders) {
      var qtyStr = formatQuantity(order);
      qtyWidth = Math.max(qtyWidth, qtyStr.length());
      quantities.put(order, qtyStr);

      var priceStr = formatPrice(order);
      priceWidth = Math.max(priceWidth, priceStr.length());
      prices.put(order, priceStr);
    }

    itemWidth = lineWidth - qtyWidth - priceWidth - 2; // 2 for spaces
    if (itemWidth < 1) {
      throw new IllegalStateException("Orders cannot fit within given line width: " + lineWidth);
    }

    var builder = new StringBuilder();
    appendHeader(builder, itemWidth, qtyWidth, priceWidth);
    for (var order : orders) {
      appendItem(builder, order.item(), itemWidth);
      builder.append(" ");
      appendQuantity(builder, quantities.get(order), qtyWidth);
      builder.append(" ");
      appendPrice(builder, prices.get(order), priceWidth);
      builder.append("\n");
    }
    appendFooter(builder, netPrice);
    return builder.toString();
  }

  private void appendHeader(StringBuilder builder, int itemWidth, int qtyWidth, int priceWidth) {
    if (companyName != null && !companyName.isBlank()) {
      int nameWidth = companyName.length();
      int leftPadding = lineWidth / 2 - nameWidth / 2;
      builder.append(" ".repeat(leftPadding));
      builder.append(companyName);
      builder.append("\n");
    }
    builder.append(separator);
    builder.append("\n");

    var itemFmt = "%-" + itemWidth + "s";
    var qtyFmt = "%-" + qtyWidth + "s";
    var priceFmt = "%" + priceWidth + "s";
    var headerFmt = itemFmt + " " + qtyFmt + " " + priceFmt;

    builder.append(headerFmt.formatted("Item", "Qty", "Price"));
    builder.append("\n");
    builder.append(headerFmt.formatted("----", "---", "-----"));
    builder.append("\n");
  }

  private void appendFooter(StringBuilder builder, double netPrice) {
    builder.append(separator);
    builder.append("\n");

    var labelStr = "Net price";
    var netPriceStr = formatPrice(netPrice);
    int padding = lineWidth - labelStr.length() - netPriceStr.length();

    builder.append(labelStr);
    if (padding > 0) {
      builder.append(" ".repeat(padding));
    }
    builder.append(netPriceStr);
    builder.append("\n");

    builder.append(separator);
    builder.append("\n");

    if (message != null && !message.isBlank()) {
      builder.append(message);
    }
  }

  private void appendItem(StringBuilder builder, String item, int width) {
    int itemLength = item.length();
    if (itemLength <= width) {
      builder.append(item);
      int rightPadding = width - itemLength;
      if (rightPadding > 0) {
        builder.append(" ".repeat(rightPadding));
      }
    } else {
      int index = 0;
      while (index < itemLength) {
        builder.append(item, index, Math.min(index + width, itemLength));
        index += width;
        if (index < itemLength) {
          builder.append("\n");
        }
      }
      if (index > itemLength) {
        builder.append(" ".repeat(index - itemLength));
      }
    }
  }

  private void appendQuantity(StringBuilder builder, String qty, int width) {
    builder.append(qty);
    int rightPadding = width - qty.length();
    if (rightPadding > 0) {
      builder.append(" ".repeat(rightPadding));
    }
  }

  private void appendPrice(StringBuilder builder, String price, int width) {
    int leftPadding = width - price.length();
    if (leftPadding > 0) {
      builder.append(" ".repeat(leftPadding));
    }
    builder.append(price);
  }

  private String formatQuantity(Order order) {
    return Integer.toString(order.quantity());
  }

  private String formatPrice(Order order) {
    return formatPrice(order.totalPrice());
  }

  private String formatPrice(double price) {
    return "$%.2f".formatted(price);
  }
}

Main.java

package com.gitlab.tkslaw.workshopfx;

import java.util.List;
import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.Window;

public class Main extends Application {

  private final List<Order> orders = List.of(
      new Order("Android", 1, 1000),
      new Order("Mac OS", 1, 1000),
      new Order("Windows 10 Operating System", 1, 1000),
      new Order("Linux Operating System", 1, 1000),
      new Order("Windows Server OS", 1, 1000));
  private final double netPrice = 5000;

  @Override
  public void start(Stage primaryStage) {
    var receiptText = new Text();
    receiptText.setFont(Font.font("Monospaced", 13));

    var printBtn = new Button("Print");
    printBtn.setOnAction(e -> {
      e.consume();
      printBtn.setDisable(true);
      print(primaryStage, receiptText, () -> printBtn.setDisable(false));
    });

    var root = new BorderPane();
    root.setTop(new ToolBar(printBtn));
    root.setCenter(receiptText);

    primaryStage.setScene(new Scene(root, 500, 300));
    primaryStage.show();

    var formatter = new ReceiptFormatter("Company, Inc.", '-', "Thanks for shopping with us!", 38);
    var receipt = formatter.format(orders, netPrice);
    receiptText.setText(receipt);
    System.out.println(receipt);
  }

  private void print(Window owner, Node page, Runnable onDone) {
    // I'm not sure if a strong reference to the PrinterJob needs to be kept
    var job = PrinterJob.createPrinterJob();
    if (job.showPrintDialog(owner)) {
      job.printPage(page);
      if (job.endJob()) {
        job.jobStatusProperty().subscribe((status) -> {
          switch (status) {
            case ERROR -> {
              showErrorAlert(owner, "Print job failed!");
              onDone.run();
            }
            case DONE -> {
              showSuccessAlert(owner, "Print job completed!");
              onDone.run();
            }
          }
        });
      } else {
        showErrorAlert(owner, "Could not schedule print job.");
      }
    }
  }

  private void showSuccessAlert(Window owner, String message) {
    var alert = new Alert(Alert.AlertType.INFORMATION);
    alert.initOwner(owner);
    alert.setHeaderText(null);
    alert.setContentText(message);
    alert.show();
  }

  private void showErrorAlert(Window owner, String message) {
    var alert = new Alert(Alert.AlertType.ERROR);
    alert.initOwner(owner);
    alert.setHeaderText(null);
    alert.setContentText(message);
    alert.show();
  }
}

输出

控制台输出

             Company, Inc.
--------------------------------------
Item                      Qty    Price
----                      ---    -----
Android                   1   $1000.00
Mac OS                    1   $1000.00
Windows 10 Operating Syst
em                        1   $1000.00
Linux Operating System    1   $1000.00
Windows Server OS         1   $1000.00
--------------------------------------
Net price                     $5000.00
--------------------------------------
Thanks for shopping with us!

JavaFX 应用程序的屏幕截图

The screenshot of the JavaFX application

PDF打印截图

The screenshot showing result of printing to PDF.

© www.soinside.com 2019 - 2024. All rights reserved.