|
@@ -34,13 +34,16 @@ public class InvoiceProofPdfRenderer {
|
|
|
public byte[] proof(InvoicePrintSnapshot s, boolean reprint) throws Exception {
|
|
public byte[] proof(InvoicePrintSnapshot s, boolean reprint) throws Exception {
|
|
|
try (PDDocument doc = new PDDocument(); InputStream input = getClass().getResourceAsStream(FONT)) {
|
|
try (PDDocument doc = new PDDocument(); InputStream input = getClass().getResourceAsStream(FONT)) {
|
|
|
PDType0Font font = PDType0Font.load(doc, input, true);
|
|
PDType0Font font = PDType0Font.load(doc, input, true);
|
|
|
- List<String> detail = detailLines(s, font);
|
|
|
|
|
|
|
+ List<DetailLine> detail = detailLines(s, font);
|
|
|
// B2B明细与证明联同页不裁切;B2C另页,对应纸卷裁切边界。
|
|
// B2B明细与证明联同页不裁切;B2C另页,对应纸卷裁切边界。
|
|
|
- float height = s.businessBuyer() ? 94 + detail.size() * 4.5f + 5 : 90;
|
|
|
|
|
|
|
+ float height = s.businessBuyer() ? 94 + detailHeight(detail) + 5 : 90;
|
|
|
InvoicePrintSnapshot.require(height < 5000, "tooLarge");
|
|
InvoicePrintSnapshot.require(height < 5000, "tooLarge");
|
|
|
PDPage page = addPage(doc, height);
|
|
PDPage page = addPage(doc, height);
|
|
|
try (var out = new PDPageContentStream(doc, page)) {
|
|
try (var out = new PDPageContentStream(doc, page)) {
|
|
|
- text(out, page, font, s.sellerName(), 5, 8, TEXT_SIZE, false, 47);
|
|
|
|
|
|
|
+ // 平台品牌独立居中,门店名称仅在交易明细出现。
|
|
|
|
|
+ String brand = "恰恰吃";
|
|
|
|
|
+ float brandWidth = font.getStringWidth(brand) / 1000 * 16 / MM;
|
|
|
|
|
+ text(out, page, font, brand, (57 - brandWidth) / 2, 9, 16, true, 47);
|
|
|
// 字高不通过缩小字号适配;长标题仅水平压缩,补印必须与标题并列。
|
|
// 字高不通过缩小字号适配;长标题仅水平压缩,补印必须与标题并列。
|
|
|
text(out, page, font, "電子發票證明聯" + (reprint ? " 補印" : ""), 5, 17, 19, false, 47);
|
|
text(out, page, font, "電子發票證明聯" + (reprint ? " 補印" : ""), 5, 17, 19, false, 47);
|
|
|
text(out, page, font, s.period(), 5, 26, 22, true, 47);
|
|
text(out, page, font, s.period(), 5, 26, 22, true, 47);
|
|
@@ -57,7 +60,7 @@ public class InvoiceProofPdfRenderer {
|
|
|
// 不能将不同版本二维码连同静区一起缩成同尺寸,否则实际符号大小不同。
|
|
// 不能将不同版本二维码连同静区一起缩成同尺寸,否则实际符号大小不同。
|
|
|
matrix(out, page, s.qrLeft(), BarcodeFormat.QR_CODE, charset, 8.5f, 68.5f, 17.5f, 17.5f);
|
|
matrix(out, page, s.qrLeft(), BarcodeFormat.QR_CODE, charset, 8.5f, 68.5f, 17.5f, 17.5f);
|
|
|
matrix(out, page, s.qrRight(), BarcodeFormat.QR_CODE, charset, 31.5f, 68.5f, 17.5f, 17.5f);
|
|
matrix(out, page, s.qrRight(), BarcodeFormat.QR_CODE, charset, 31.5f, 68.5f, 17.5f, 17.5f);
|
|
|
- if (s.businessBuyer()) lines(out, page, font, detail, 94);
|
|
|
|
|
|
|
+ if (s.businessBuyer()) detailText(out, page, font, detail, 94);
|
|
|
}
|
|
}
|
|
|
if (!s.businessBuyer()) detailPage(doc, font, detail);
|
|
if (!s.businessBuyer()) detailPage(doc, font, detail);
|
|
|
return save(doc);
|
|
return save(doc);
|
|
@@ -95,15 +98,20 @@ public class InvoiceProofPdfRenderer {
|
|
|
return "0".equals(sections[4]) ? "Big5" : "UTF-8";
|
|
return "0".equals(sections[4]) ? "Big5" : "UTF-8";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static List<String> detailLines(InvoicePrintSnapshot s, PDType0Font font) throws Exception {
|
|
|
|
|
- List<String> result = new ArrayList<>();
|
|
|
|
|
- for (String header : List.of("交易明細(非兌獎憑證)", s.sellerName(), "發票號碼 " + s.invoiceNumber(), s.issuedAt())) {
|
|
|
|
|
- wrap(result, header, font);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private record DetailLine(String value, float size, boolean bold, float advance) { }
|
|
|
|
|
+
|
|
|
|
|
+ private static List<DetailLine> detailLines(InvoicePrintSnapshot s, PDType0Font font) throws Exception {
|
|
|
|
|
+ List<DetailLine> result = new ArrayList<>();
|
|
|
|
|
+ wrap(result, "交易明細(非兌獎憑證)", font, TEXT_SIZE, true);
|
|
|
|
|
+ wrap(result, s.sellerName(), font, 11, true);
|
|
|
|
|
+ wrap(result, "發票號碼 " + s.invoiceNumber(), font);
|
|
|
|
|
+ wrap(result, s.issuedAt(), font);
|
|
|
|
|
+ result.add(new DetailLine("", TEXT_SIZE, false, 2));
|
|
|
for (var item : s.items()) {
|
|
for (var item : s.items()) {
|
|
|
wrap(result, item.name(), font);
|
|
wrap(result, item.name(), font);
|
|
|
wrap(result, "數量 " + n(item.quantity()) + " 單價 " + n(item.unitPrice()), font);
|
|
wrap(result, "數量 " + n(item.quantity()) + " 單價 " + n(item.unitPrice()), font);
|
|
|
wrap(result, "金額 " + n(item.amount()) + " TX", font);
|
|
wrap(result, "金額 " + n(item.amount()) + " TX", font);
|
|
|
|
|
+ result.add(new DetailLine("", TEXT_SIZE, false, 1.5f));
|
|
|
}
|
|
}
|
|
|
if (s.businessBuyer()) {
|
|
if (s.businessBuyer()) {
|
|
|
wrap(result, "應稅銷售額 " + n(s.sales()), font);
|
|
wrap(result, "應稅銷售額 " + n(s.sales()), font);
|
|
@@ -111,29 +119,48 @@ public class InvoiceProofPdfRenderer {
|
|
|
wrap(result, "免稅銷售額 0", font);
|
|
wrap(result, "免稅銷售額 0", font);
|
|
|
wrap(result, "稅額 " + n(s.tax()), font);
|
|
wrap(result, "稅額 " + n(s.tax()), font);
|
|
|
}
|
|
}
|
|
|
- wrap(result, "總計 " + n(s.total()), font);
|
|
|
|
|
|
|
+ wrap(result, "總計 " + n(s.total()), font, 11, true);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static void wrap(List<String> lines, String value, PDType0Font font) throws Exception {
|
|
|
|
|
|
|
+ private static void wrap(List<DetailLine> lines, String value, PDType0Font font) throws Exception {
|
|
|
|
|
+ wrap(lines, value, font, TEXT_SIZE, false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void wrap(List<DetailLine> lines, String value, PDType0Font font, float size, boolean bold) throws Exception {
|
|
|
StringBuilder line = new StringBuilder();
|
|
StringBuilder line = new StringBuilder();
|
|
|
- // 按Unicode码点换行,长品名不截断,不用省略号丢失开票明细。
|
|
|
|
|
|
|
+ float advance = size > TEXT_SIZE ? 5.5f : 4.5f;
|
|
|
|
|
+ // 按实际字号及Unicode码点换行,加粗长店名也不截断。
|
|
|
for (int point : value.codePoints().toArray()) {
|
|
for (int point : value.codePoints().toArray()) {
|
|
|
if (Character.isISOControl(point)) point = ' ';
|
|
if (Character.isISOControl(point)) point = ' ';
|
|
|
String character = new String(Character.toChars(point));
|
|
String character = new String(Character.toChars(point));
|
|
|
- if (font.getStringWidth(line + character) / 1000 * TEXT_SIZE > 47 * MM && !line.isEmpty()) {
|
|
|
|
|
- lines.add(line.toString()); line.setLength(0);
|
|
|
|
|
|
|
+ if (font.getStringWidth(line + character) / 1000 * size > 47 * MM && !line.isEmpty()) {
|
|
|
|
|
+ lines.add(new DetailLine(line.toString(), size, bold, advance)); line.setLength(0);
|
|
|
}
|
|
}
|
|
|
line.append(character);
|
|
line.append(character);
|
|
|
}
|
|
}
|
|
|
- if (!line.isEmpty()) lines.add(line.toString());
|
|
|
|
|
|
|
+ if (!line.isEmpty()) lines.add(new DetailLine(line.toString(), size, bold, advance));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static float detailHeight(List<DetailLine> detail) {
|
|
|
|
|
+ float height = 0;
|
|
|
|
|
+ for (var line : detail) height += line.advance();
|
|
|
|
|
+ return height;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void detailText(PDPageContentStream out, PDPage page, PDType0Font font,
|
|
|
|
|
+ List<DetailLine> detail, float top) throws Exception {
|
|
|
|
|
+ for (var line : detail) {
|
|
|
|
|
+ if (!line.value().isEmpty()) text(out, page, font, line.value(), 5, top, line.size(), line.bold(), 47);
|
|
|
|
|
+ top += line.advance();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private static void detailPage(PDDocument doc, PDType0Font font, List<String> detail) throws Exception {
|
|
|
|
|
- float height = Math.max(40, 12 + detail.size() * 4.5f);
|
|
|
|
|
|
|
+ private static void detailPage(PDDocument doc, PDType0Font font, List<DetailLine> detail) throws Exception {
|
|
|
|
|
+ float height = Math.max(40, 12 + detailHeight(detail));
|
|
|
InvoicePrintSnapshot.require(height < 5000, "tooLarge");
|
|
InvoicePrintSnapshot.require(height < 5000, "tooLarge");
|
|
|
PDPage page = addPage(doc, height);
|
|
PDPage page = addPage(doc, height);
|
|
|
- try (var out = new PDPageContentStream(doc, page)) { lines(out, page, font, detail, 7); }
|
|
|
|
|
|
|
+ try (var out = new PDPageContentStream(doc, page)) { detailText(out, page, font, detail, 7); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static void lines(PDPageContentStream out, PDPage page, PDType0Font font, List<String> lines, float top) throws Exception {
|
|
private static void lines(PDPageContentStream out, PDPage page, PDType0Font font, List<String> lines, float top) throws Exception {
|