From 3048076d897d1025d0454cc367ce7f92b471665b Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 16 Apr 2026 17:17:49 +0900 Subject: [PATCH] =?UTF-8?q?[1427]=20=EC=B6=9C=EB=A0=A5=ED=95=9C=20?= =?UTF-8?q?=EA=B2=AC=EC=A0=81=EC=84=9C=20=EB=8F=84=EB=A9=B4=EC=9D=98=20?= =?UTF-8?q?=EA=B0=80=EB=A1=9C=20=EC=84=B8=EB=A1=9C=20=EB=B9=84=EC=9C=A8?= =?UTF-8?q?=EC=9D=B4,=20=ED=99=94=EB=A9=B4=EA=B3=BC=20=EB=8B=A4=EB=A5=B4?= =?UTF-8?q?=EB=8B=A4=20&=20local=EC=97=90=EC=84=9C=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qcast/biz/estimate/EstimateService.java | 148 +++++++++++++++++- 1 file changed, 146 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java index cebc07d9..210d1684 100644 --- a/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java +++ b/src/main/java/com/interplug/qcast/biz/estimate/EstimateService.java @@ -26,10 +26,18 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.util.Units; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.springframework.beans.factory.annotation.Autowired; @@ -1868,6 +1876,9 @@ public class EstimateService { InputStream in = new ByteArrayInputStream(excelBytes); workbook = WorkbookFactory.create(in); // JXLS POI 엑셀로 재변환 + // 이미지 가로세로 비율 보정 + adjustImageAspectRatio(workbook); + // SchDrawingFlg (1 : 견적서,2 : 발전시뮬레이션, 3 : 도면, 4 : 가대) // ex) 1|2|3|4 if (!StringUtils.isEmpty(estimateRequest.getSchDrawingFlg())) { @@ -3002,12 +3013,145 @@ public class EstimateService { } } + /** + * 엑셀 워크북의 모든 시트에서 이미지의 가로세로 비율을 보정 + * JXLS jx:image 가 셀 영역에 맞춰 이미지를 늘리기 때문에 원본 비율로 재조정 + */ + private void adjustImageAspectRatio(Workbook workbook) { + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + Sheet sheet = workbook.getSheetAt(i); + if (!(sheet instanceof XSSFSheet)) continue; + + XSSFDrawing drawing = ((XSSFSheet) sheet).getDrawingPatriarch(); + if (drawing == null) continue; + + for (XSSFShape shape : drawing.getShapes()) { + if (!(shape instanceof XSSFPicture)) continue; + + XSSFPicture picture = (XSSFPicture) shape; + XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor(); + PictureData pictureData = picture.getPictureData(); + if (pictureData == null) continue; + + // 원본 이미지 크기 (EMU 단위) + java.awt.Dimension imgDim = picture.getImageDimension(); + if (imgDim == null || imgDim.width == 0 || imgDim.height == 0) continue; + + double imgRatio = (double) imgDim.width / imgDim.height; + + // 현재 앵커 영역의 실제 크기 계산 (EMU 단위) + long cellWidthEmu = 0; + for (int col = anchor.getCol1(); col < anchor.getCol2(); col++) { + int colWidth = sheet.getColumnWidth(col); // 1/256 of character width + cellWidthEmu += (long) (colWidth / 256.0 * Units.DEFAULT_CHARACTER_WIDTH * Units.EMU_PER_PIXEL); + } + // dx 오프셋 보정 + cellWidthEmu = cellWidthEmu - anchor.getDx1() + anchor.getDx2(); + + long cellHeightEmu = 0; + for (int row = anchor.getRow1(); row < anchor.getRow2(); row++) { + Row r = sheet.getRow(row); + float heightPt = (r != null) ? r.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); + cellHeightEmu += Units.toEMU(heightPt); + } + // dy 오프셋 보정 + cellHeightEmu = cellHeightEmu - anchor.getDy1() + anchor.getDy2(); + + if (cellWidthEmu <= 0 || cellHeightEmu <= 0) continue; + + double cellRatio = (double) cellWidthEmu / cellHeightEmu; + + if (imgRatio > cellRatio) { + // 이미지가 더 넓음 → 너비에 맞추고 높이를 줄임 + long newHeightEmu = (long) (cellWidthEmu / imgRatio); + long verticalPadding = (cellHeightEmu - newHeightEmu) / 2; + + // dy1 오프셋으로 세로 중앙 정렬 + anchor.setDy1((int) (anchor.getDy1() + verticalPadding)); + // row2/dy2 재계산 + long remainHeight = newHeightEmu; + int newRow2 = anchor.getRow1(); + // dy1 오프셋이 있는 첫 행 처리 + Row firstRow = sheet.getRow(newRow2); + float firstRowPt = (firstRow != null) ? firstRow.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); + long firstRowAvail = Units.toEMU(firstRowPt) - anchor.getDy1(); + if (remainHeight <= firstRowAvail) { + anchor.setRow2(newRow2); + anchor.setDy2((int) (anchor.getDy1() + remainHeight)); + } else { + remainHeight -= firstRowAvail; + newRow2++; + while (remainHeight > 0) { + Row r = sheet.getRow(newRow2); + float hPt = (r != null) ? r.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); + long rowEmu = Units.toEMU(hPt); + if (remainHeight <= rowEmu) { + anchor.setRow2(newRow2); + anchor.setDy2((int) remainHeight); + remainHeight = 0; + } else { + remainHeight -= rowEmu; + newRow2++; + } + } + } + } else { + // 이미지가 더 높음 → 높이에 맞추고 너비를 줄임 + long newWidthEmu = (long) (cellHeightEmu * imgRatio); + long horizontalPadding = (cellWidthEmu - newWidthEmu) / 2; + + // dx1 오프셋으로 가로 중앙 정렬 + anchor.setDx1((int) (anchor.getDx1() + horizontalPadding)); + // col2/dx2 재계산 + long remainWidth = newWidthEmu; + int newCol2 = anchor.getCol1(); + // dx1 오프셋이 있는 첫 열 처리 + int firstColW = sheet.getColumnWidth(newCol2); + long firstColEmu = (long) (firstColW / 256.0 * Units.DEFAULT_CHARACTER_WIDTH * Units.EMU_PER_PIXEL); + long firstColAvail = firstColEmu - anchor.getDx1(); + if (remainWidth <= firstColAvail) { + anchor.setCol2(newCol2); + anchor.setDx2((int) (anchor.getDx1() + remainWidth)); + } else { + remainWidth -= firstColAvail; + newCol2++; + while (remainWidth > 0) { + int colW = sheet.getColumnWidth(newCol2); + long colEmu = (long) (colW / 256.0 * Units.DEFAULT_CHARACTER_WIDTH * Units.EMU_PER_PIXEL); + if (remainWidth <= colEmu) { + anchor.setCol2(newCol2); + anchor.setDx2((int) remainWidth); + remainWidth = 0; + } else { + remainWidth -= colEmu; + newCol2++; + } + } + } + } + } + } + } + + @Autowired + private org.springframework.core.env.Environment environment; + private byte[] loadDrawingImage(URL url) { try { URLConnection connection = url.openConnection(); + // local 프로파일일 때만 SSL 우회 + if (environment.matchesProfiles("local") && connection instanceof javax.net.ssl.HttpsURLConnection httpsConn) { + javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("TLSv1.2"); + sc.init(null, new javax.net.ssl.TrustManager[]{new javax.net.ssl.X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } + public void checkClientTrusted(java.security.cert.X509Certificate[] c, String t) {} + public void checkServerTrusted(java.security.cert.X509Certificate[] c, String t) {} + }}, null); + httpsConn.setSSLSocketFactory(sc.getSocketFactory()); + httpsConn.setHostnameVerifier((h, s) -> true); + } if (connection instanceof HttpURLConnection) { - HttpURLConnection httpConnection = (HttpURLConnection) connection; - if (httpConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { + if (((HttpURLConnection) connection).getResponseCode() != HttpURLConnection.HTTP_OK) { return null; } }