Commit 368334159750cc814662214d66d8e118d60d9e83

Authored by 房远帅
2 parents cc2de118 9315b68a

Merge branch 'master_after0506' into master_after0506_report02

@@ -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,42 @@ public class ContractDistributorStandardController extends DefaultBaseController @@ -1253,13 +1253,42 @@ public class ContractDistributorStandardController extends DefaultBaseController
1253 } 1253 }
1254 } 1254 }
1255 1255
1256 - double specMinScale = 1.0; 1256 + int contractSpecFontSize = Integer.MAX_VALUE;
1257 for (SpecImageCell specCell : specImageCells) { 1257 for (SpecImageCell specCell : specImageCells) {
1258 - double scale = LatexFormulaExcelExporterUtil.calculateFitScale(workbook, sheet, specCell.latex, specCell.rowIndex, specCell.colIndex);  
1259 - specMinScale = Math.min(specMinScale, scale); 1258 + int currentFontSize = LatexFormulaExcelExporterUtil.calculateContractReferenceFontSize(
  1259 + workbook,
  1260 + sheet,
  1261 + specCell.rowIndex,
  1262 + specCell.colIndex
  1263 + );
  1264 + contractSpecFontSize = Math.min(contractSpecFontSize, currentFontSize);
1260 } 1265 }
  1266 + if (contractSpecFontSize == Integer.MAX_VALUE) {
  1267 + contractSpecFontSize = 16;
  1268 + }
  1269 +
  1270 + double contractSpecScale = 1.0;
  1271 + for (SpecImageCell specCell : specImageCells) {
  1272 + double currentScale = LatexFormulaExcelExporterUtil.calculateContractReferenceFitScale(
  1273 + workbook,
  1274 + sheet,
  1275 + specCell.rowIndex,
  1276 + specCell.colIndex,
  1277 + contractSpecFontSize
  1278 + );
  1279 + contractSpecScale = Math.min(contractSpecScale, currentScale);
  1280 + }
  1281 +
1261 for (SpecImageCell specCell : specImageCells) { 1282 for (SpecImageCell specCell : specImageCells) {
1262 - LatexFormulaExcelExporterUtil.insertLatexImageToCellWithMaxScale(workbook, sheet, specCell.latex, specCell.rowIndex, specCell.colIndex, specMinScale); 1283 + LatexFormulaExcelExporterUtil.insertContractLatexImageToCellFillTemplate(
  1284 + workbook,
  1285 + sheet,
  1286 + specCell.latex,
  1287 + specCell.rowIndex,
  1288 + specCell.colIndex,
  1289 + contractSpecFontSize,
  1290 + contractSpecScale
  1291 + );
1263 } 1292 }
1264 1293
1265 // --- 填充全局变量 --- 1294 // --- 填充全局变量 ---
@@ -444,7 +444,7 @@ public class PurchaseOrderInfoController extends DefaultBaseController { @@ -444,7 +444,7 @@ public class PurchaseOrderInfoController extends DefaultBaseController {
444 dataMap.put("supplyUnit", supplyUnitDicItem == null ? "" : supplyUnitDicItem.getName()); 444 dataMap.put("supplyUnit", supplyUnitDicItem == null ? "" : supplyUnitDicItem.getName());
445 dataMap.put("orderNo", data.getOrderNo()); 445 dataMap.put("orderNo", data.getOrderNo());
446 dataMap.put("orderingUnitName", data.getOrderingUnitName()); 446 dataMap.put("orderingUnitName", data.getOrderingUnitName());
447 - dataMap.put("workshopName", data.getWorkshopName()); 447 + dataMap.put("workshopName", data.getWorkshopName() == null ? "" : data.getWorkshopName());
448 dataMap.put("customerTier", data.getCustomerTier()); 448 dataMap.put("customerTier", data.getCustomerTier());
449 dataMap.put("orderDate", data.getOrderDate() == null ? "" : data.getOrderDate().format(dateFormatter)); 449 dataMap.put("orderDate", data.getOrderDate() == null ? "" : data.getOrderDate().format(dateFormatter));
450 450
@@ -243,6 +243,7 @@ public class BusinessDataExportHandler implements ExportHandler { @@ -243,6 +243,7 @@ public class BusinessDataExportHandler implements ExportHandler {
243 SysDataDicItem supplyUnitDicItem = sysDataDicItemService.findByCode("SUPPLIER", orderInfo.getSupplyUnit()); 243 SysDataDicItem supplyUnitDicItem = sysDataDicItemService.findByCode("SUPPLIER", orderInfo.getSupplyUnit());
244 dataMap.put("supplyUnit", supplyUnitDicItem == null ? "" : supplyUnitDicItem.getName()); 244 dataMap.put("supplyUnit", supplyUnitDicItem == null ? "" : supplyUnitDicItem.getName());
245 dataMap.put("orderNo", orderInfo.getOrderNo()); 245 dataMap.put("orderNo", orderInfo.getOrderNo());
  246 + dataMap.put("workshopName", orderInfo.getWorkshopName() == null ? "" : orderInfo.getWorkshopName());
246 dataMap.put("orderingUnitName", orderInfo.getOrderingUnitName()); 247 dataMap.put("orderingUnitName", orderInfo.getOrderingUnitName());
247 dataMap.put("customerTier", orderInfo.getCustomerTier()); 248 dataMap.put("customerTier", orderInfo.getCustomerTier());
248 dataMap.put("orderDate", orderInfo.getOrderDate() == null ? "" : orderInfo.getOrderDate().format(dateFormatter)); 249 dataMap.put("orderDate", orderInfo.getOrderDate() == null ? "" : orderInfo.getOrderDate().format(dateFormatter));
@@ -288,7 +289,8 @@ public class BusinessDataExportHandler implements ExportHandler { @@ -288,7 +289,8 @@ public class BusinessDataExportHandler implements ExportHandler {
288 } 289 }
289 File pdfFile = ExcelUtil.convertExcelToPdf(tempExcel, "/usr/bin/libreoffice --headless --convert-to pdf --outdir %s %s"); 290 File pdfFile = ExcelUtil.convertExcelToPdf(tempExcel, "/usr/bin/libreoffice --headless --convert-to pdf --outdir %s %s");
290 try (InputStream pdfIn = new FileInputStream(pdfFile)) { 291 try (InputStream pdfIn = new FileInputStream(pdfFile)) {
291 - ZipEntry entry = new ZipEntry(orderInfo.getOrderNo() + "-订货单打印-" + createTime + ".pdf"); 292 + ZipEntry entry = new ZipEntry(orderInfo.getOrderNo() + "-"
  293 + + orderInfo.getOrderingUnitName() + "-" + orderInfo.getWorkshopName() + "-订货单打印" + ".pdf");
292 zos.putNextEntry(entry); 294 zos.putNextEntry(entry);
293 byte[] buffer = new byte[8192]; 295 byte[] buffer = new byte[8192];
294 int len; 296 int len;
@@ -307,7 +309,8 @@ public class BusinessDataExportHandler implements ExportHandler { @@ -307,7 +309,8 @@ public class BusinessDataExportHandler implements ExportHandler {
307 } else { 309 } else {
308 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 310 ByteArrayOutputStream baos = new ByteArrayOutputStream();
309 workbook.write(baos); 311 workbook.write(baos);
310 - ZipEntry entry = new ZipEntry(orderInfo.getOrderNo() + "-订货单打印-" + createTime + ".xlsx"); 312 + ZipEntry entry = new ZipEntry(orderInfo.getOrderNo() + "-"
  313 + + orderInfo.getOrderingUnitName() + "-" + orderInfo.getWorkshopName() + "-订货单打印" + ".xlsx");
311 zos.putNextEntry(entry); 314 zos.putNextEntry(entry);
312 zos.write(baos.toByteArray()); 315 zos.write(baos.toByteArray());
313 zos.closeEntry(); 316 zos.closeEntry();
@@ -24,6 +24,9 @@ import java.util.List; @@ -24,6 +24,9 @@ import java.util.List;
24 @Slf4j 24 @Slf4j
25 public class LatexFormulaExcelExporterUtil { 25 public class LatexFormulaExcelExporterUtil {
26 26
  27 + private static final String CONTRACT_SPEC_REFERENCE_LATEX =
  28 + "\\mathbf{\\mathrm{22^{+0.2}_{+0.1}\\!\\times\\!22^{+0.2}_{+0.1}\\!\\times\\!22^{+0.2}_{+0.1}}}";
  29 +
27 public static String convertToLatex(List<FormulaComponent> componentList) { 30 public static String convertToLatex(List<FormulaComponent> componentList) {
28 if (CollectionUtils.isEmpty(componentList)) { 31 if (CollectionUtils.isEmpty(componentList)) {
29 return ""; 32 return "";
@@ -97,11 +100,45 @@ public class LatexFormulaExcelExporterUtil { @@ -97,11 +100,45 @@ public class LatexFormulaExcelExporterUtil {
97 return ""; 100 return "";
98 } 101 }
99 102
  103 + String referenceComponentLatex = resolveReferenceComponentLatex(componentList);
100 int missing = Math.max(0, 3 - nonNullCount); 104 int missing = Math.max(0, 3 - nonNullCount);
101 if (missing > 0) { 105 if (missing > 0) {
102 StringBuilder phantom = new StringBuilder(); 106 StringBuilder phantom = new StringBuilder();
103 for (int i = 0; i < missing; i++) { 107 for (int i = 0; i < missing; i++) {
104 - phantom.append(" \\times 000^{+00}_{+00}"); 108 + phantom.append(" \\times ").append(referenceComponentLatex);
  109 + }
  110 + actual = actual + "\\phantom{" + phantom + "}";
  111 + }
  112 +
  113 + return "\\mathbf{\\mathrm{" + actual + "}}";
  114 + }
  115 +
  116 + public static String convertToContractSpecLatexSingleLineFixed3Compact(List<FormulaComponent> componentList) {
  117 + if (CollectionUtils.isEmpty(componentList)) {
  118 + return "";
  119 + }
  120 +
  121 + int nonNullCount = 0;
  122 + for (FormulaComponent comp : componentList) {
  123 + if (comp != null && comp.getBase() != null) {
  124 + nonNullCount++;
  125 + }
  126 + }
  127 + if (nonNullCount <= 0) {
  128 + return "";
  129 + }
  130 +
  131 + String actual = buildLatexLine(componentList, 0, componentList.size(), "\\!\\times\\!");
  132 + if (actual.isEmpty()) {
  133 + return "";
  134 + }
  135 +
  136 + String referenceComponentLatex = resolveReferenceComponentLatex(componentList);
  137 + int missing = Math.max(0, 3 - nonNullCount);
  138 + if (missing > 0) {
  139 + StringBuilder phantom = new StringBuilder();
  140 + for (int i = 0; i < missing; i++) {
  141 + phantom.append("\\!\\times\\!").append(referenceComponentLatex);
105 } 142 }
106 actual = actual + "\\phantom{" + phantom + "}"; 143 actual = actual + "\\phantom{" + phantom + "}";
107 } 144 }
@@ -110,6 +147,10 @@ public class LatexFormulaExcelExporterUtil { @@ -110,6 +147,10 @@ public class LatexFormulaExcelExporterUtil {
110 } 147 }
111 148
112 private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive) { 149 private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive) {
  150 + return buildLatexLine(componentList, startIndex, endIndexExclusive, " \\times ");
  151 + }
  152 +
  153 + private static String buildLatexLine(List<FormulaComponent> componentList, int startIndex, int endIndexExclusive, String separator) {
113 StringBuilder latex = new StringBuilder(); 154 StringBuilder latex = new StringBuilder();
114 boolean first = true; 155 boolean first = true;
115 int end = Math.min(componentList.size(), endIndexExclusive); 156 int end = Math.min(componentList.size(), endIndexExclusive);
@@ -119,24 +160,48 @@ public class LatexFormulaExcelExporterUtil { @@ -119,24 +160,48 @@ public class LatexFormulaExcelExporterUtil {
119 continue; 160 continue;
120 } 161 }
121 if (!first) { 162 if (!first) {
122 - latex.append(" \\times "); 163 + latex.append(separator);
123 } 164 }
124 first = false; 165 first = false;
  166 + latex.append(buildLatexComponent(comp));
  167 + }
  168 + return latex.toString();
  169 + }
125 170
126 - latex.append(formatScientificNotation(comp.getBase()));  
127 - if (comp.getSup() != null && comp.getSup().compareTo(BigDecimal.ZERO) >= 0) {  
128 - latex.append("^{+").append(formatScientificNotation(comp.getSup())).append("}"); 171 + private static String resolveReferenceComponentLatex(List<FormulaComponent> componentList) {
  172 + String reference = "000^{+00}_{+00}";
  173 + int maxLength = reference.length();
  174 + for (FormulaComponent comp : componentList) {
  175 + if (comp == null || comp.getBase() == null) {
  176 + continue;
129 } 177 }
130 - if (comp.getSup() != null && comp.getSup().compareTo(BigDecimal.ZERO) < 0) {  
131 - latex.append("^{").append(formatScientificNotation(comp.getSup())).append("}"); 178 + String candidate = buildLatexComponent(comp);
  179 + if (candidate.length() > maxLength) {
  180 + reference = candidate;
  181 + maxLength = candidate.length();
132 } 182 }
  183 + }
  184 + return reference;
  185 + }
133 186
134 - if (comp.getSub() != null && comp.getSub().compareTo(BigDecimal.ZERO) >= 0) {  
135 - latex.append("_{+").append(formatScientificNotation(comp.getSub())).append("}");  
136 - }  
137 - if (comp.getSub() != null && comp.getSub().compareTo(BigDecimal.ZERO) < 0) {  
138 - latex.append("_{").append(formatScientificNotation(comp.getSub())).append("}");  
139 - } 187 + private static String buildLatexComponent(FormulaComponent comp) {
  188 + if (comp == null || comp.getBase() == null) {
  189 + return "";
  190 + }
  191 + StringBuilder latex = new StringBuilder();
  192 + latex.append(formatScientificNotation(comp.getBase()));
  193 + if (comp.getSup() != null && comp.getSup().compareTo(BigDecimal.ZERO) >= 0) {
  194 + latex.append("^{+").append(formatScientificNotation(comp.getSup())).append("}");
  195 + }
  196 + if (comp.getSup() != null && comp.getSup().compareTo(BigDecimal.ZERO) < 0) {
  197 + latex.append("^{").append(formatScientificNotation(comp.getSup())).append("}");
  198 + }
  199 +
  200 + if (comp.getSub() != null && comp.getSub().compareTo(BigDecimal.ZERO) >= 0) {
  201 + latex.append("_{+").append(formatScientificNotation(comp.getSub())).append("}");
  202 + }
  203 + if (comp.getSub() != null && comp.getSub().compareTo(BigDecimal.ZERO) < 0) {
  204 + latex.append("_{").append(formatScientificNotation(comp.getSub())).append("}");
140 } 205 }
141 return latex.toString(); 206 return latex.toString();
142 } 207 }
@@ -253,6 +318,86 @@ public class LatexFormulaExcelExporterUtil { @@ -253,6 +318,86 @@ public class LatexFormulaExcelExporterUtil {
253 insertLatexImageToRegion(workbook, sheet, latex, rowIndex, colIndex, rowRange, colRange, 16, null); 318 insertLatexImageToRegion(workbook, sheet, latex, rowIndex, colIndex, rowRange, colRange, 16, null);
254 } 319 }
255 320
  321 + /**
  322 + * 合同规格专用:按照模板单元格区域高度优先铺满,并根据区域大小动态选择更合适的渲染字号。
  323 + */
  324 + public static void insertContractLatexImageToCellFillTemplate(Workbook workbook, Sheet sheet, String latex,
  325 + int rowIndex, int colIndex) throws IOException {
  326 + int baseFontSize = Math.max(16, resolveCellFontSize(workbook, sheet, rowIndex, colIndex));
  327 + insertContractLatexImageToCellFillTemplate(workbook, sheet, latex, rowIndex, colIndex, baseFontSize);
  328 + }
  329 +
  330 + public static void insertContractLatexImageToCellFillTemplate(Workbook workbook, Sheet sheet, String latex,
  331 + int rowIndex, int colIndex, int baseFontSize) throws IOException {
  332 + insertContractLatexImageToCellFillTemplate(workbook, sheet, latex, rowIndex, colIndex, baseFontSize, null);
  333 + }
  334 +
  335 + public static void insertContractLatexImageToCellFillTemplate(Workbook workbook, Sheet sheet, String latex,
  336 + int rowIndex, int colIndex, int baseFontSize,
  337 + Double maxScale) throws IOException {
  338 + CellRangeAddress mergedRegion = findMergedRegion(sheet, rowIndex, colIndex);
  339 + if (mergedRegion != null) {
  340 + insertContractLatexImageToRegionFillTemplate(
  341 + workbook,
  342 + sheet,
  343 + latex,
  344 + mergedRegion.getFirstRow(),
  345 + mergedRegion.getFirstColumn(),
  346 + mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1,
  347 + mergedRegion.getLastColumn() - mergedRegion.getFirstColumn() + 1,
  348 + baseFontSize,
  349 + maxScale
  350 + );
  351 + return;
  352 + }
  353 + insertContractLatexImageToRegionFillTemplate(workbook, sheet, latex, rowIndex, colIndex, 1, 1, baseFontSize, maxScale);
  354 + }
  355 +
  356 + public static int calculateContractBestFontSize(Workbook workbook, Sheet sheet, String latex,
  357 + int rowIndex, int colIndex) throws IOException {
  358 + int baseFontSize = Math.max(16, resolveCellFontSize(workbook, sheet, rowIndex, colIndex));
  359 + CellRangeAddress mergedRegion = findMergedRegion(sheet, rowIndex, colIndex);
  360 + if (mergedRegion != null) {
  361 + return calculateContractBestFontSizeForRegion(
  362 + sheet,
  363 + latex,
  364 + mergedRegion.getFirstRow(),
  365 + mergedRegion.getFirstColumn(),
  366 + mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1,
  367 + mergedRegion.getLastColumn() - mergedRegion.getFirstColumn() + 1,
  368 + baseFontSize
  369 + );
  370 + }
  371 + return calculateContractBestFontSizeForRegion(sheet, latex, rowIndex, colIndex, 1, 1, baseFontSize);
  372 + }
  373 +
  374 + public static int calculateContractReferenceFontSize(Workbook workbook, Sheet sheet,
  375 + int rowIndex, int colIndex) throws IOException {
  376 + return calculateContractBestFontSize(workbook, sheet, CONTRACT_SPEC_REFERENCE_LATEX, rowIndex, colIndex);
  377 + }
  378 +
  379 + public static double calculateContractFitScale(Workbook workbook, Sheet sheet, String latex,
  380 + int rowIndex, int colIndex, int fontSize) throws IOException {
  381 + CellRangeAddress mergedRegion = findMergedRegion(sheet, rowIndex, colIndex);
  382 + if (mergedRegion != null) {
  383 + return calculateContractFitScaleForRegion(
  384 + sheet,
  385 + latex,
  386 + mergedRegion.getFirstRow(),
  387 + mergedRegion.getFirstColumn(),
  388 + mergedRegion.getLastRow() - mergedRegion.getFirstRow() + 1,
  389 + mergedRegion.getLastColumn() - mergedRegion.getFirstColumn() + 1,
  390 + fontSize
  391 + );
  392 + }
  393 + return calculateContractFitScaleForRegion(sheet, latex, rowIndex, colIndex, 1, 1, fontSize);
  394 + }
  395 +
  396 + public static double calculateContractReferenceFitScale(Workbook workbook, Sheet sheet,
  397 + int rowIndex, int colIndex, int fontSize) throws IOException {
  398 + return calculateContractFitScale(workbook, sheet, CONTRACT_SPEC_REFERENCE_LATEX, rowIndex, colIndex, fontSize);
  399 + }
  400 +
256 private static final int EMU_PER_PX = 9525; 401 private static final int EMU_PER_PX = 9525;
257 402
258 public static void insertLatexImageToCellWithMaxScale(Workbook workbook, Sheet sheet, String latex, int rowIndex, int colIndex, 403 public static void insertLatexImageToCellWithMaxScale(Workbook workbook, Sheet sheet, String latex, int rowIndex, int colIndex,
@@ -417,6 +562,148 @@ public class LatexFormulaExcelExporterUtil { @@ -417,6 +562,148 @@ public class LatexFormulaExcelExporterUtil {
417 drawing.createPicture(anchor, pictureIdx); 562 drawing.createPicture(anchor, pictureIdx);
418 } 563 }
419 564
  565 + private static void insertContractLatexImageToRegionFillTemplate(Workbook workbook, Sheet sheet, String latex,
  566 + int rowIndex, int colIndex, int rowRange, int colRange,
  567 + int baseFontSize, Double maxScale) throws IOException {
  568 + int regionWidthPx = 0;
  569 + for (int c = colIndex; c < colIndex + colRange; c++) {
  570 + regionWidthPx += getColumnWidthPx(sheet, c);
  571 + }
  572 + int regionHeightPx = 0;
  573 + for (int r = rowIndex; r < rowIndex + rowRange; r++) {
  574 + regionHeightPx += getRowHeightPx(sheet, r);
  575 + }
  576 +
  577 + int paddingXPx = 6;
  578 + int paddingYPx = 4;
  579 + int availableWidthPx = Math.max(1, regionWidthPx - paddingXPx * 2);
  580 + int availableHeightPx = Math.max(1, regionHeightPx - paddingYPx * 2);
  581 + RenderedLatexImage renderedImage = renderLatexImage(latex, baseFontSize);
  582 + if (renderedImage.image == null) {
  583 + return;
  584 + }
  585 +
  586 + byte[] imageBytes = renderedImage.imageBytes;
  587 + BufferedImage image = renderedImage.image;
  588 + double scale = Math.min(
  589 + 1.0,
  590 + Math.min(
  591 + (double) availableWidthPx / Math.max(1, image.getWidth()),
  592 + (double) availableHeightPx / Math.max(1, image.getHeight())
  593 + )
  594 + );
  595 + if (maxScale != null) {
  596 + scale = Math.min(scale, maxScale);
  597 + }
  598 + int scaledWidthPx = Math.max(1, (int) Math.round(image.getWidth() * scale));
  599 + int scaledHeightPx = Math.max(1, (int) Math.round(image.getHeight() * scale));
  600 +
  601 + int offsetXPx = Math.max(paddingXPx, (regionWidthPx - scaledWidthPx) / 2);
  602 + int offsetYPx = Math.max(paddingYPx, (regionHeightPx - scaledHeightPx) / 2);
  603 +
  604 + int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
  605 + Drawing<?> drawing = sheet.createDrawingPatriarch();
  606 + ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
  607 + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_DONT_RESIZE);
  608 +
  609 + AnchorCoord x1 = resolveAnchorX(sheet, colIndex, colRange, offsetXPx);
  610 + AnchorCoord x2 = resolveAnchorX(sheet, colIndex, colRange, offsetXPx + scaledWidthPx);
  611 + AnchorCoord y1 = resolveAnchorY(sheet, rowIndex, rowRange, offsetYPx);
  612 + AnchorCoord y2 = resolveAnchorY(sheet, rowIndex, rowRange, offsetYPx + scaledHeightPx);
  613 +
  614 + anchor.setCol1(x1.index);
  615 + anchor.setCol2(x2.index);
  616 + anchor.setRow1(y1.index);
  617 + anchor.setRow2(y2.index);
  618 +
  619 + if (anchor instanceof org.apache.poi.hssf.usermodel.HSSFClientAnchor) {
  620 + int dx1 = pxToHssfDx(sheet, x1.index, x1.offsetPx);
  621 + int dx2 = pxToHssfDx(sheet, x2.index, x2.offsetPx);
  622 + int dy1 = pxToHssfDy(sheet, y1.index, y1.offsetPx);
  623 + int dy2 = pxToHssfDy(sheet, y2.index, y2.offsetPx);
  624 +
  625 + if (x2.index == x1.index && dx2 <= dx1) {
  626 + dx2 = Math.min(1023, dx1 + 1);
  627 + }
  628 + if (y2.index == y1.index && dy2 <= dy1) {
  629 + dy2 = Math.min(255, dy1 + 1);
  630 + }
  631 +
  632 + anchor.setDx1(dx1);
  633 + anchor.setDx2(dx2);
  634 + anchor.setDy1(dy1);
  635 + anchor.setDy2(dy2);
  636 + } else {
  637 + int dx1 = x1.offsetPx * EMU_PER_PX;
  638 + int dx2 = x2.offsetPx * EMU_PER_PX;
  639 + int dy1 = y1.offsetPx * EMU_PER_PX;
  640 + int dy2 = y2.offsetPx * EMU_PER_PX;
  641 +
  642 + if (x2.index == x1.index && dx2 <= dx1) {
  643 + dx2 = dx1 + 1;
  644 + }
  645 + if (y2.index == y1.index && dy2 <= dy1) {
  646 + dy2 = dy1 + 1;
  647 + }
  648 +
  649 + anchor.setDx1(dx1);
  650 + anchor.setDx2(dx2);
  651 + anchor.setDy1(dy1);
  652 + anchor.setDy2(dy2);
  653 + }
  654 +
  655 + drawing.createPicture(anchor, pictureIdx);
  656 + }
  657 +
  658 + private static int calculateContractBestFontSizeForRegion(Sheet sheet, String latex,
  659 + int rowIndex, int colIndex, int rowRange, int colRange,
  660 + int preferredFontSize) throws IOException {
  661 + int regionWidthPx = 0;
  662 + for (int c = colIndex; c < colIndex + colRange; c++) {
  663 + regionWidthPx += getColumnWidthPx(sheet, c);
  664 + }
  665 + int regionHeightPx = 0;
  666 + for (int r = rowIndex; r < rowIndex + rowRange; r++) {
  667 + regionHeightPx += getRowHeightPx(sheet, r);
  668 + }
  669 +
  670 + int paddingXPx = 3;
  671 + int paddingYPx = 1;
  672 + int availableWidthPx = Math.max(1, regionWidthPx - paddingXPx * 2);
  673 + int availableHeightPx = Math.max(1, regionHeightPx - paddingYPx * 2);
  674 + return renderBestFitLatexFontSize(latex, preferredFontSize, availableWidthPx, availableHeightPx);
  675 + }
  676 +
  677 + private static double calculateContractFitScaleForRegion(Sheet sheet, String latex,
  678 + int rowIndex, int colIndex, int rowRange, int colRange,
  679 + int fontSize) throws IOException {
  680 + int regionWidthPx = 0;
  681 + for (int c = colIndex; c < colIndex + colRange; c++) {
  682 + regionWidthPx += getColumnWidthPx(sheet, c);
  683 + }
  684 + int regionHeightPx = 0;
  685 + for (int r = rowIndex; r < rowIndex + rowRange; r++) {
  686 + regionHeightPx += getRowHeightPx(sheet, r);
  687 + }
  688 +
  689 + int paddingXPx = 6;
  690 + int paddingYPx = 4;
  691 + int availableWidthPx = Math.max(1, regionWidthPx - paddingXPx * 2);
  692 + int availableHeightPx = Math.max(1, regionHeightPx - paddingYPx * 2);
  693 + RenderedLatexImage renderedImage = renderLatexImage(latex, fontSize);
  694 + if (renderedImage.image == null) {
  695 + return 1.0;
  696 + }
  697 +
  698 + return Math.min(
  699 + 1.0,
  700 + Math.min(
  701 + (double) availableWidthPx / Math.max(1, renderedImage.image.getWidth()),
  702 + (double) availableHeightPx / Math.max(1, renderedImage.image.getHeight())
  703 + )
  704 + );
  705 + }
  706 +
420 private static int getColumnWidthPx(Sheet sheet, int colIndex) { 707 private static int getColumnWidthPx(Sheet sheet, int colIndex) {
421 int widthUnits = sheet.getColumnWidth(colIndex); 708 int widthUnits = sheet.getColumnWidth(colIndex);
422 return Math.max(1, (int) Math.round(widthUnits / 256.0 * 7.0 + 5)); 709 return Math.max(1, (int) Math.round(widthUnits / 256.0 * 7.0 + 5));
@@ -428,6 +715,155 @@ public class LatexFormulaExcelExporterUtil { @@ -428,6 +715,155 @@ public class LatexFormulaExcelExporterUtil {
428 return Math.max(1, (int) Math.round(points * 96.0 / 72.0)); 715 return Math.max(1, (int) Math.round(points * 96.0 / 72.0));
429 } 716 }
430 717
  718 + private static int renderBestFitLatexFontSize(String latex, int preferredFontSize,
  719 + int targetWidthPx, int targetHeightPx) throws IOException {
  720 + int minFontSize = 8;
  721 + int maxFontSize = Math.max(minFontSize, preferredFontSize + 40);
  722 + maxFontSize = Math.min(96, maxFontSize);
  723 + int left = minFontSize;
  724 + int right = maxFontSize;
  725 + int bestFontSize = minFontSize;
  726 +
  727 + while (left <= right) {
  728 + int mid = (left + right) >>> 1;
  729 + RenderedLatexImage candidate = renderLatexImage(latex, mid);
  730 + if (fitsWithin(candidate, targetWidthPx, targetHeightPx)) {
  731 + bestFontSize = mid;
  732 + left = mid + 1;
  733 + } else {
  734 + right = mid - 1;
  735 + }
  736 + }
  737 + return bestFontSize;
  738 + }
  739 +
  740 + private static RenderedLatexImage renderLatexImage(String latex, int fontSize) throws IOException {
  741 + byte[] imageBytes = latexToContractImageBytes(latex, fontSize);
  742 + BufferedImage image;
  743 + try (ByteArrayInputStream in = new ByteArrayInputStream(imageBytes)) {
  744 + image = ImageIO.read(in);
  745 + }
  746 + return new RenderedLatexImage(imageBytes, image);
  747 + }
  748 +
  749 + private static byte[] latexToContractImageBytes(String latex, int fontSize) throws IOException {
  750 + try {
  751 + double scale = 5.0;
  752 + TeXFormula formula = new TeXFormula(latex);
  753 + TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, fontSize);
  754 +
  755 + int baseWidth = icon.getIconWidth();
  756 + int baseHeight = icon.getIconHeight();
  757 + if (baseWidth <= 0 || baseHeight <= 0) {
  758 + return new byte[0];
  759 + }
  760 +
  761 + int maxWidth = 4200;
  762 + int maxHeight = 3200;
  763 + double widthScale = (double) maxWidth / baseWidth;
  764 + double heightScale = (double) maxHeight / baseHeight;
  765 + scale = Math.min(scale, Math.min(widthScale, heightScale));
  766 + scale = Math.max(1.0, scale);
  767 +
  768 + int scaledWidth = Math.max(1, (int) Math.ceil(baseWidth * scale));
  769 + int scaledHeight = Math.max(1, (int) Math.ceil(baseHeight * scale));
  770 + BufferedImage image = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
  771 + Graphics2D g2 = image.createGraphics();
  772 + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  773 + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  774 + g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  775 + g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  776 + g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
  777 + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  778 +
  779 + java.awt.Font renderFont = new java.awt.Font("宋体", java.awt.Font.BOLD, fontSize);
  780 + g2.setFont(renderFont);
  781 + g2.setColor(Color.WHITE);
  782 + g2.fillRect(0, 0, scaledWidth, scaledHeight);
  783 + g2.scale(scale, scale);
  784 +
  785 + JLabel label = new JLabel();
  786 + label.setFont(renderFont);
  787 + label.setForeground(Color.BLACK);
  788 + icon.paintIcon(label, g2, 0, 0);
  789 + g2.dispose();
  790 +
  791 + image = trimWhitespace(image, 248, 0);
  792 + ByteArrayOutputStream baos = new ByteArrayOutputStream();
  793 + ImageIO.write(image, "png", baos);
  794 + return baos.toByteArray();
  795 + } catch (Exception e) {
  796 + throw new IOException("合同规格图片渲染失败: " + e.getMessage(), e);
  797 + }
  798 + }
  799 +
  800 + private static boolean fitsWithin(RenderedLatexImage renderedImage, int targetWidthPx, int targetHeightPx) {
  801 + return renderedImage != null
  802 + && renderedImage.image != null
  803 + && renderedImage.image.getWidth() <= targetWidthPx
  804 + && renderedImage.image.getHeight() <= targetHeightPx;
  805 + }
  806 +
  807 + private static BufferedImage trimWhitespace(BufferedImage source, int whiteThreshold, int marginPx) {
  808 + if (source == null || source.getWidth() <= 0 || source.getHeight() <= 0) {
  809 + return source;
  810 + }
  811 +
  812 + int minX = source.getWidth();
  813 + int minY = source.getHeight();
  814 + int maxX = -1;
  815 + int maxY = -1;
  816 +
  817 + for (int y = 0; y < source.getHeight(); y++) {
  818 + for (int x = 0; x < source.getWidth(); x++) {
  819 + if (!isNearWhite(source.getRGB(x, y), whiteThreshold)) {
  820 + minX = Math.min(minX, x);
  821 + minY = Math.min(minY, y);
  822 + maxX = Math.max(maxX, x);
  823 + maxY = Math.max(maxY, y);
  824 + }
  825 + }
  826 + }
  827 +
  828 + if (maxX < minX || maxY < minY) {
  829 + return source;
  830 + }
  831 +
  832 + int cropMinX = Math.max(0, minX - marginPx);
  833 + int cropMinY = Math.max(0, minY - marginPx);
  834 + int cropMaxX = Math.min(source.getWidth() - 1, maxX + marginPx);
  835 + int cropMaxY = Math.min(source.getHeight() - 1, maxY + marginPx);
  836 + int cropWidth = cropMaxX - cropMinX + 1;
  837 + int cropHeight = cropMaxY - cropMinY + 1;
  838 +
  839 + if (cropWidth >= source.getWidth() && cropHeight >= source.getHeight()) {
  840 + return source;
  841 + }
  842 +
  843 + BufferedImage trimmed = new BufferedImage(cropWidth, cropHeight, BufferedImage.TYPE_INT_RGB);
  844 + Graphics2D g2 = trimmed.createGraphics();
  845 + g2.drawImage(source.getSubimage(cropMinX, cropMinY, cropWidth, cropHeight), 0, 0, null);
  846 + g2.dispose();
  847 + return trimmed;
  848 + }
  849 +
  850 + private static boolean isNearWhite(int rgb, int whiteThreshold) {
  851 + int red = (rgb >> 16) & 0xFF;
  852 + int green = (rgb >> 8) & 0xFF;
  853 + int blue = rgb & 0xFF;
  854 + return red >= whiteThreshold && green >= whiteThreshold && blue >= whiteThreshold;
  855 + }
  856 +
  857 + private static class RenderedLatexImage {
  858 + final byte[] imageBytes;
  859 + final BufferedImage image;
  860 +
  861 + private RenderedLatexImage(byte[] imageBytes, BufferedImage image) {
  862 + this.imageBytes = imageBytes;
  863 + this.image = image;
  864 + }
  865 + }
  866 +
431 private static class AnchorCoord { 867 private static class AnchorCoord {
432 final int index; 868 final int index;
433 final int offsetPx; 869 final int offsetPx;