我在打印发票时遇到字符串格式问题。发票没有按照我的要求正确对齐。 该代码只是向热敏打印机生成发票。如果未找到打印机设备,则获取系统默认的打印介质。 我的代码如下。
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);
}
代码输出如下
我想根据产品名称长度对齐输出。 我尝试通过额外的空格来格式化它,但它不起作用。我想正确对齐数量和价格列。请帮忙。预先感谢。
这只是一个概念证明。
所以程序只是确认打印的布局内容,然后将其发送到默认打印机。
因此,程序也非常短小简单。足以完成概念验证。
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 个字符宽度
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!
打印机输出:
JDK + JavaFX - Zulu - 21.0.3.fx-zulu
您的问题可能有两个来源。
您的字符串格式不正确。这与 JavaFX 无关。
您没有使用等宽字体。这确实与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 应用程序的屏幕截图
PDF打印截图