diff --git a/apps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareCaptureLayer.tsx b/apps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareCaptureLayer.tsx index 8410ad60..85d362aa 100644 --- a/apps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareCaptureLayer.tsx +++ b/apps/web/src/app/tournament/[id]/result/_components/receipt-share-dialog/ReceiptShareCaptureLayer.tsx @@ -5,12 +5,16 @@ import PikiLogoCart from '@/assets/images/piki-logo-cart.svg'; import type { RankedProductT } from '../../../_common/_types/tournament'; import ReceiptPaper from '../ReceiptPaper'; -/** 종이 폭은 고정하고 zoom 으로 내용만 키운다 — 렌더 폭이 줄어 결과 폭은 동일하다 */ +/** 좁은 폭으로 레이아웃한 뒤 zoom 으로 확대해 최종 폭을 만든다 */ const RECEIPT_PAPER_WIDTH_PX = 370; const RECEIPT_ZOOM = 1.42; const RECEIPT_RENDER_WIDTH_PX = RECEIPT_PAPER_WIDTH_PX / RECEIPT_ZOOM; -const RECEIPT_BOX_HEIGHT_PX = 748; +/** 이 높이를 넘길 때만 배율을 낮춘다 */ +const RECEIPT_MAX_HEIGHT_PX = 737; + +/** ReceiptPaper 하단 물결(h-4.5). absolute 라 종이 높이에 안 잡힌다 */ +const RECEIPT_ZIGZAG_HEIGHT_PX = 18; type ReceiptShareCaptureLayerProps = { tournamentId: number; @@ -23,34 +27,49 @@ const ReceiptShareCaptureLayer = forwardRef(null); const zoomRef = useRef(null); + const logoAreaRef = useRef(null); - /** 내용이 넘치면 zoom 을 낮춘다. 종이 폭이 좁아지지 않게 렌더 폭도 함께 보정한다. */ + /** + * 배율을 낮추면 레이아웃 폭이 넓어져 긴 상품명의 줄바꿈이 달라지고, 그러면 높이가 다시 + * 변한다. 한 번만 계산하면 이 되먹임 때문에 결과가 어긋나므로 값이 안정될 때까지 반복한다. + * 줄바꿈은 계단식이라 두 값을 오갈 수 있어, 그중 상한을 넘지 않는 최대 배율을 고른다. + */ useLayoutEffect(() => { const paper = paperRef.current; const zoomWrap = zoomRef.current; if (!paper || !zoomWrap) return; - /** 이전 축소가 남아 있으면 높이를 잘못 재므로 기준값으로 되돌리고 측정 */ - zoomWrap.style.zoom = String(RECEIPT_ZOOM); - zoomWrap.style.width = `${RECEIPT_RENDER_WIDTH_PX}px`; - const paperHeight = paper.scrollHeight; - if (!paperHeight) return; + let best = 0; + let candidate = RECEIPT_ZOOM; + + for (let i = 0; i < 6; i += 1) { + zoomWrap.style.zoom = String(candidate); + zoomWrap.style.width = `${RECEIPT_PAPER_WIDTH_PX / candidate}px`; + + const height = paper.scrollHeight * candidate; + if (!height) return; + + if (height <= RECEIPT_MAX_HEIGHT_PX) best = Math.max(best, candidate); - const fitZoom = Math.min(RECEIPT_ZOOM, RECEIPT_BOX_HEIGHT_PX / paperHeight); + const next = Math.min(RECEIPT_ZOOM, (candidate * RECEIPT_MAX_HEIGHT_PX) / height); + if (Math.abs(next - candidate) < 0.002) break; + candidate = next; + } + + const fitZoom = best || candidate; zoomWrap.style.zoom = String(fitZoom); zoomWrap.style.width = `${RECEIPT_PAPER_WIDTH_PX / fitZoom}px`; + + /** 물결 높이를 여백으로 되돌려야 로고가 물결 끝 기준으로 가운데 온다 */ + if (logoAreaRef.current) { + logoAreaRef.current.style.paddingTop = `${RECEIPT_ZIGZAG_HEIGHT_PX * fitZoom}px`; + } }, [result, tournamentName, date]); return (
-
-
+
+
- + + {/* 종이 아래 남는 공간의 한가운데에 로고를 둔다 */} +
+ +
);