Commit 99c0b4c37ca38de3ee9ccffe335ba9c361c9e74b

Authored by 房远帅
1 parent 4fd35c9b

合同:打印模板调整

@@ -1194,7 +1194,7 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -1194,7 +1194,7 @@ public class ContractDistributorStandardController extends DefaultBaseController
1194 addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg()); 1194 addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg());
1195 addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg()); 1195 addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg());
1196 1196
1197 - String latex = LatexFormulaExcelExporterUtil.convertToContractSpecLatexSingleLineFixed3(formulaComponentList); 1197 + String latex = LatexFormulaExcelExporterUtil.convertToContractSpecLatexSingleLineFixed3Compact(formulaComponentList);
1198 if (StringUtils.isNotBlank(latex)) { 1198 if (StringUtils.isNotBlank(latex)) {
1199 specImageCells.add(new SpecImageCell(startRow, 5, latex)); 1199 specImageCells.add(new SpecImageCell(startRow, 5, latex));
1200 } 1200 }
@@ -1238,7 +1238,7 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -1238,7 +1238,7 @@ public class ContractDistributorStandardController extends DefaultBaseController
1238 addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg()); 1238 addFormulaComponent(formulaComponentList, line.getWidth(), line.getWidthTolPos(), line.getWidthTolNeg());
1239 addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg()); 1239 addFormulaComponent(formulaComponentList, line.getLength(), line.getLengthTolPos(), line.getLengthTolNeg());
1240 1240
1241 - String latex = LatexFormulaExcelExporterUtil.convertToContractSpecLatexSingleLineFixed3(formulaComponentList); 1241 + String latex = LatexFormulaExcelExporterUtil.convertToContractSpecLatexSingleLineFixed3Compact(formulaComponentList);
1242 if (StringUtils.isNotBlank(latex)) { 1242 if (StringUtils.isNotBlank(latex)) {
1243 specImageCells.add(new SpecImageCell(startRow, 9, latex)); 1243 specImageCells.add(new SpecImageCell(startRow, 9, latex));
1244 } 1244 }
@@ -1253,13 +1253,14 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -1253,13 +1253,14 @@ public class ContractDistributorStandardController extends DefaultBaseController
1253 } 1253 }
1254 } 1254 }
1255 1255
1256 - double specMinScale = 1.0;  
1257 for (SpecImageCell specCell : specImageCells) { 1256 for (SpecImageCell specCell : specImageCells) {
1258 - double scale = LatexFormulaExcelExporterUtil.calculateFitScale(workbook, sheet, specCell.latex, specCell.rowIndex, specCell.colIndex);  
1259 - specMinScale = Math.min(specMinScale, scale);  
1260 - }  
1261 - for (SpecImageCell specCell : specImageCells) {  
1262 - LatexFormulaExcelExporterUtil.insertLatexImageToCellWithMaxScale(workbook, sheet, specCell.latex, specCell.rowIndex, specCell.colIndex, specMinScale); 1257 + LatexFormulaExcelExporterUtil.insertContractLatexImageToCellFillTemplate(
  1258 + workbook,
  1259 + sheet,
  1260 + specCell.latex,
  1261 + specCell.rowIndex,
  1262 + specCell.colIndex
  1263 + );
1263 } 1264 }
1264 1265
1265 // --- 填充全局变量 --- 1266 // --- 填充全局变量 ---
@@ -109,7 +109,43 @@ public class LatexFormulaExcelExporterUtil { @@ -109,7 +109,43 @@ public class LatexFormulaExcelExporterUtil {
109 return "\\mathbf{\\mathrm{" + actual + "}}"; 109 return "\\mathbf{\\mathrm{" + actual + "}}";
110 } 110 }
111 111
  112 + public static String convertToContractSpecLatexSingleLineFixed3Compact(List<FormulaComponent> componentList) {
  113 + if (CollectionUtils.isEmpty(componentList)) {
  114 + return "";
  115 + }
  116 +
  117 + int nonNullCount = 0;
  118 + for (FormulaComponent comp : componentList) {
  119 + if (comp != null && comp.getBase() != null) {
  120 + nonNullCount++;
  121 + }
  122 + }
  123 + if (nonNullCount <= 0) {
  124 + return "";
  125 + }
  126 +
  127 + String actual = buildLatexLine(componentList, 0, componentList.size(), "\\!\\times\\!");
  128 + if (actual.isEmpty()) {
  129 + return "";
  130 + }
  131 +
  132 + int missing = Math.max(0, 3 - nonNullCount);
  133 + if (missing > 0) {
  134 + StringBuilder phantom = new StringBuilder();
  135 + for (int i = 0; i < missing; i++) {
  136 + phantom.append("\\!\\times\\!000^{+00}_{+00}");
  137 + }
  138 + actual = actual + "\\phantom{" + phantom + "}";
  139 + }
  140 +
  141 + return "\\mathbf{\\mathrm{" + actual + "}}";
  142 + }
  143 +
112 private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive) { 144 private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive) {
  145 + return buildLatexLine(componentList, startIndex, endIndexExclusive, " \\times ");
  146 + }
  147 +
  148 + private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive, String separator) {
113 StringBuilder latex = new StringBuilder(); 149 StringBuilder latex = new StringBuilder();
114 boolean first = true; 150 boolean first = true;
115 int end = Math.min(componentList.size(), endIndexExclusive); 151 int end = Math.min(componentList.size(), endIndexExclusive);
@@ -119,7 +155,7 @@ public class LatexFormulaExcelExporterUtil { @@ -119,7 +155,7 @@ public class LatexFormulaExcelExporterUtil {
119 continue; 155 continue;
120 } 156 }
121 if (!first) { 157 if (!first) {
122 - latex.append(" \\times "); 158 + latex.append(separator);
123 } 159 }
124 first = false; 160 first = false;
125 161
@@ -253,6 +289,29 @@ public class LatexFormulaExcelExporterUtil { @@ -253,6 +289,29 @@ public class LatexFormulaExcelExporterUtil {
253 insertLatexImageToRegion(workbook, sheet, latex, rowIndex, colIndex, rowRange, colRange, 16, null); 289 insertLatexImageToRegion(workbook, sheet, latex, rowIndex, colIndex, rowRange, colRange, 16, null);
254 } 290 }
255 291
  292 + /**
  293 + * 合同规格专用:按照模板单元格区域高度优先铺满,并根据区域大小动态选择更合适的渲染字号。
  294 + */
  295 + public static void insertContractLatexImageToCellFillTemplate(Workbook workbook, Sheet sheet, String latex,
  296 + int rowIndex, int colIndex) throws IOException {
  297 + int baseFontSize = Math.max(16, resolveCellFontSize(workbook, sheet, rowIndex, colIndex));
  298 + CellRangeAddress mergedRegion = findMergedRegion(sheet, rowIndex, colIndex);
  299 + if (mergedRegion != null) {
  300 + insertContractLatexImageToRegionFillTemplate(
  301 + workbook,
  302 + sheet,
  303 + latex,
  304 + mergedRegion.getFirstRow(),
  305 + mergedRegion.getFirstColumn(),
  306 + mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1,
  307 + mergedRegion.getLastColumn() - mergedRegion.getFirstColumn() + 1,
  308 + baseFontSize
  309 + );
  310 + return;
  311 + }
  312 + insertContractLatexImageToRegionFillTemplate(workbook, sheet, latex, rowIndex, colIndex, 1, 1, baseFontSize);
  313 + }
  314 +
256 private static final int EMU_PER_PX = 9525; 315 private static final int EMU_PER_PX = 9525;
257 316
258 public static void insertLatexImageToCellWithMaxScale(Workbook workbook, Sheet sheet, String latex, int rowIndex, int colIndex, 317 public static void insertLatexImageToCellWithMaxScale(Workbook workbook, Sheet sheet, String latex, int rowIndex, int colIndex,
@@ -417,6 +476,96 @@ public class LatexFormulaExcelExporterUtil { @@ -417,6 +476,96 @@ public class LatexFormulaExcelExporterUtil {
417 drawing.createPicture(anchor, pictureIdx); 476 drawing.createPicture(anchor, pictureIdx);
418 } 477 }
419 478
  479 + private static void insertContractLatexImageToRegionFillTemplate(Workbook workbook, Sheet sheet, String latex,
  480 + int rowIndex, int colIndex, int rowRange, int colRange,
  481 + int baseFontSize) throws IOException {
  482 + int regionWidthPx = 0;
  483 + for (int c = colIndex; c < colIndex + colRange; c++) {
  484 + regionWidthPx += getColumnWidthPx(sheet, c);
  485 + }
  486 + int regionHeightPx = 0;
  487 + for (int r = rowIndex; r < rowIndex + rowRange; r++) {
  488 + regionHeightPx += getRowHeightPx(sheet, r);
  489 + }
  490 +
  491 + int paddingXPx = 4;
  492 + int paddingYPx = 2;
  493 + int availableWidthPx = Math.max(1, regionWidthPx - paddingXPx * 2);
  494 + int availableHeightPx = Math.max(1, regionHeightPx - paddingYPx * 2);
  495 + RenderedLatexImage renderedImage = renderBestFitLatexImageByHeight(
  496 + latex,
  497 + baseFontSize,
  498 + availableHeightPx
  499 + );
  500 + if (renderedImage.image == null) {
  501 + return;
  502 + }
  503 +
  504 + byte[] imageBytes = renderedImage.imageBytes;
  505 + BufferedImage image = renderedImage.image;
  506 + int scaledHeightPx = availableHeightPx;
  507 + int widthByHeightPx = Math.max(1, (int) Math.round(
  508 + (double) image.getWidth() * scaledHeightPx / Math.max(1, image.getHeight())
  509 + ));
  510 + int scaledWidthPx = Math.min(availableWidthPx, widthByHeightPx);
  511 +
  512 + int offsetXPx = Math.max(paddingXPx, (regionWidthPx - scaledWidthPx) / 2);
  513 + int offsetYPx = Math.max(paddingYPx, (regionHeightPx - scaledHeightPx) / 2);
  514 +
  515 + int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
  516 + Drawing<?> drawing = sheet.createDrawingPatriarch();
  517 + ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
  518 + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_DONT_RESIZE);
  519 +
  520 + AnchorCoord x1 = resolveAnchorX(sheet, colIndex, colRange, offsetXPx);
  521 + AnchorCoord x2 = resolveAnchorX(sheet, colIndex, colRange, offsetXPx + scaledWidthPx);
  522 + AnchorCoord y1 = resolveAnchorY(sheet, rowIndex, rowRange, offsetYPx);
  523 + AnchorCoord y2 = resolveAnchorY(sheet, rowIndex, rowRange, offsetYPx + scaledHeightPx);
  524 +
  525 + anchor.setCol1(x1.index);
  526 + anchor.setCol2(x2.index);
  527 + anchor.setRow1(y1.index);
  528 + anchor.setRow2(y2.index);
  529 +
  530 + if (anchor instanceof org.apache.poi.hssf.usermodel.HSSFClientAnchor) {
  531 + int dx1 = pxToHssfDx(sheet, x1.index, x1.offsetPx);
  532 + int dx2 = pxToHssfDx(sheet, x2.index, x2.offsetPx);
  533 + int dy1 = pxToHssfDy(sheet, y1.index, y1.offsetPx);
  534 + int dy2 = pxToHssfDy(sheet, y2.index, y2.offsetPx);
  535 +
  536 + if (x2.index == x1.index && dx2 <= dx1) {
  537 + dx2 = Math.min(1023, dx1 + 1);
  538 + }
  539 + if (y2.index == y1.index && dy2 <= dy1) {
  540 + dy2 = Math.min(255, dy1 + 1);
  541 + }
  542 +
  543 + anchor.setDx1(dx1);
  544 + anchor.setDx2(dx2);
  545 + anchor.setDy1(dy1);
  546 + anchor.setDy2(dy2);
  547 + } else {
  548 + int dx1 = x1.offsetPx * EMU_PER_PX;
  549 + int dx2 = x2.offsetPx * EMU_PER_PX;
  550 + int dy1 = y1.offsetPx * EMU_PER_PX;
  551 + int dy2 = y2.offsetPx * EMU_PER_PX;
  552 +
  553 + if (x2.index == x1.index && dx2 <= dx1) {
  554 + dx2 = dx1 + 1;
  555 + }
  556 + if (y2.index == y1.index && dy2 <= dy1) {
  557 + dy2 = dy1 + 1;
  558 + }
  559 +
  560 + anchor.setDx1(dx1);
  561 + anchor.setDx2(dx2);
  562 + anchor.setDy1(dy1);
  563 + anchor.setDy2(dy2);
  564 + }
  565 +
  566 + drawing.createPicture(anchor, pictureIdx);
  567 + }
  568 +
420 private static int getColumnWidthPx(Sheet sheet, int colIndex) { 569 private static int getColumnWidthPx(Sheet sheet, int colIndex) {
421 int widthUnits = sheet.getColumnWidth(colIndex); 570 int widthUnits = sheet.getColumnWidth(colIndex);
422 return Math.max(1, (int) Math.round(widthUnits / 256.0 * 7.0 + 5)); 571 return Math.max(1, (int) Math.round(widthUnits / 256.0 * 7.0 + 5));
@@ -428,6 +577,148 @@ public class LatexFormulaExcelExporterUtil { @@ -428,6 +577,148 @@ public class LatexFormulaExcelExporterUtil {
428 return Math.max(1, (int) Math.round(points * 96.0 / 72.0)); 577 return Math.max(1, (int) Math.round(points * 96.0 / 72.0));
429 } 578 }
430 579
  580 + private static RenderedLatexImage renderBestFitLatexImageByHeight(String latex, int preferredFontSize,
  581 + int targetHeightPx) throws IOException {
  582 + int minFontSize = 8;
  583 + int maxFontSize = Math.max(minFontSize, preferredFontSize + 40);
  584 + maxFontSize = Math.min(96, maxFontSize);
  585 + int left = minFontSize;
  586 + int right = maxFontSize;
  587 + RenderedLatexImage best = renderLatexImage(latex, minFontSize);
  588 +
  589 + while (left <= right) {
  590 + int mid = (left + right) >>> 1;
  591 + RenderedLatexImage candidate = renderLatexImage(latex, mid);
  592 + if (candidate.image != null && candidate.image.getHeight() <= targetHeightPx) {
  593 + best = candidate;
  594 + left = mid + 1;
  595 + } else {
  596 + right = mid - 1;
  597 + }
  598 + }
  599 + return best;
  600 + }
  601 +
  602 + private static RenderedLatexImage renderLatexImage(String latex, int fontSize) throws IOException {
  603 + byte[] imageBytes = latexToContractImageBytes(latex, fontSize);
  604 + BufferedImage image;
  605 + try (ByteArrayInputStream in = new ByteArrayInputStream(imageBytes)) {
  606 + image = ImageIO.read(in);
  607 + }
  608 + return new RenderedLatexImage(imageBytes, image);
  609 + }
  610 +
  611 + private static byte[] latexToContractImageBytes(String latex, int fontSize) throws IOException {
  612 + try {
  613 + double scale = 5.0;
  614 + TeXFormula formula = new TeXFormula(latex);
  615 + TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, fontSize);
  616 +
  617 + int baseWidth = icon.getIconWidth();
  618 + int baseHeight = icon.getIconHeight();
  619 + if (baseWidth <= 0 || baseHeight <= 0) {
  620 + return new byte[0];
  621 + }
  622 +
  623 + int maxWidth = 4200;
  624 + int maxHeight = 3200;
  625 + double widthScale = (double) maxWidth / baseWidth;
  626 + double heightScale = (double) maxHeight / baseHeight;
  627 + scale = Math.min(scale, Math.min(widthScale, heightScale));
  628 + scale = Math.max(1.0, scale);
  629 +
  630 + int scaledWidth = Math.max(1, (int) Math.ceil(baseWidth * scale));
  631 + int scaledHeight = Math.max(1, (int) Math.ceil(baseHeight * scale));
  632 + BufferedImage image = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
  633 + Graphics2D g2 = image.createGraphics();
  634 + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  635 + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  636 + g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  637 + g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  638 + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
  639 + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  640 +
  641 + java.awt.Font renderFont = new java.awt.Font("宋体", java.awt.Font.BOLD, fontSize);
  642 + g2.setFont(renderFont);
  643 + g2.setColor(Color.WHITE);
  644 + g2.fillRect(0, 0, scaledWidth, scaledHeight);
  645 + g2.scale(scale, scale);
  646 +
  647 + JLabel label = new JLabel();
  648 + label.setFont(renderFont);
  649 + label.setForeground(Color.BLACK);
  650 + icon.paintIcon(label, g2, 0, 0);
  651 + g2.dispose();
  652 +
  653 + image = trimWhitespace(image, 248, 0);
  654 + ByteArrayOutputStream baos = new ByteArrayOutputStream();
  655 + ImageIO.write(image, "png", baos);
  656 + return baos.toByteArray();
  657 + } catch (Exception e) {
  658 + throw new IOException("合同规格图片渲染失败: " + e.getMessage(), e);
  659 + }
  660 + }
  661 +
  662 + private static BufferedImage trimWhitespace(BufferedImage source, int whiteThreshold, int marginPx) {
  663 + if (source == null || source.getWidth() <= 0 || source.getHeight() <= 0) {
  664 + return source;
  665 + }
  666 +
  667 + int minX = source.getWidth();
  668 + int minY = source.getHeight();
  669 + int maxX = -1;
  670 + int maxY = -1;
  671 +
  672 + for (int y = 0; y < source.getHeight(); y++) {
  673 + for (int x = 0; x < source.getWidth(); x++) {
  674 + if (!isNearWhite(source.getRGB(x, y), whiteThreshold)) {
  675 + minX = Math.min(minX, x);
  676 + minY = Math.min(minY, y);
  677 + maxX = Math.max(maxX, x);
  678 + maxY = Math.max(maxY, y);
  679 + }
  680 + }
  681 + }
  682 +
  683 + if (maxX < minX || maxY < minY) {
  684 + return source;
  685 + }
  686 +
  687 + int cropMinX = Math.max(0, minX - marginPx);
  688 + int cropMinY = Math.max(0, minY - marginPx);
  689 + int cropMaxX = Math.min(source.getWidth() - 1, maxX + marginPx);
  690 + int cropMaxY = Math.min(source.getHeight() - 1, maxY + marginPx);
  691 + int cropWidth = cropMaxX - cropMinX + 1;
  692 + int cropHeight = cropMaxY - cropMinY + 1;
  693 +
  694 + if (cropWidth >= source.getWidth() && cropHeight >= source.getHeight()) {
  695 + return source;
  696 + }
  697 +
  698 + BufferedImage trimmed = new BufferedImage(cropWidth, cropHeight, BufferedImage.TYPE_INT_RGB);
  699 + Graphics2D g2 = trimmed.createGraphics();
  700 + g2.drawImage(source.getSubimage(cropMinX, cropMinY, cropWidth, cropHeight), 0, 0, null);
  701 + g2.dispose();
  702 + return trimmed;
  703 + }
  704 +
  705 + private static boolean isNearWhite(int rgb, int whiteThreshold) {
  706 + int red = (rgb >> 16) & 0xFF;
  707 + int green = (rgb >> 8) & 0xFF;
  708 + int blue = rgb & 0xFF;
  709 + return red >= whiteThreshold && green >= whiteThreshold && blue >= whiteThreshold;
  710 + }
  711 +
  712 + private static class RenderedLatexImage {
  713 + final byte[] imageBytes;
  714 + final BufferedImage image;
  715 +
  716 + private RenderedLatexImage(byte[] imageBytes, BufferedImage image) {
  717 + this.imageBytes = imageBytes;
  718 + this.image = image;
  719 + }
  720 + }
  721 +
431 private static class AnchorCoord { 722 private static class AnchorCoord {
432 final int index; 723 final int index;
433 final int offsetPx; 724 final int offsetPx;