From 126e46e6ed244789b2f2610fc3e2bf51ed587473 Mon Sep 17 00:00:00 2001 From: minom Date: Fri, 4 Sep 2026 10:01:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AA=BF=E6=95=B4=E7=89=8C=E4=BD=8D=E7=89=88?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/admin/print/print_multi_new.aspx | 370 +++++++++++++-------------- 1 file changed, 175 insertions(+), 195 deletions(-) diff --git a/web/admin/print/print_multi_new.aspx b/web/admin/print/print_multi_new.aspx index 16a9488..20e75ea 100644 --- a/web/admin/print/print_multi_new.aspx +++ b/web/admin/print/print_multi_new.aspx @@ -353,176 +353,57 @@ let yang = ""; let bi = ""; if (d.elementID === 'title1') { - let h = Number(d.height); let w = Number(d.width); - - let s = Number(d.fontSize); - //let th = parseFloat(d.textHeight) || parseFloat(d.height); - //let v_gap = parseFloat(d.textSpan) || 0; - //let h_gap = 5; - //let shrinkLimit = 6; - //if (Number(d.height) < Number(500)) { - // shrinkLimit = 8; - //} - //// 1. 固定的長短名判斷 - //let items = mid.map(name => { - // let l = name.length; - // let isShort = (mid.length > 1) && ((l * 40) <= (th + 40)); - // return { name: name, l: l, isShort: isShort, isMulti: false, size: 0 }; - //}); - - //let long_s = parseFloat(d.fontSize); - //let short_s = parseFloat(d.fontSize); - - //// 🌟 核心修復:在排版前,預先剔除「幽靈扣血」區間! - //// 算出短名跟長名的「真實極限大小」,讓它們直接從極限起跑,絕不空轉。 - //let max_short_effective = shrinkLimit; - //let min_long_limit = parseFloat(d.fontSize); - - //items.forEach(item => { - // if (item.isShort) { - // let max_s_for_th = Math.floor(th / (item.l * 1.333)); - // if (max_s_for_th > max_short_effective) max_short_effective = max_s_for_th; - // } else { - // // 算出長名字不折行的極限大小 - // let max_s_for_h = Math.floor((h - 10) / (item.l * 1.333)); - // if (max_s_for_h < min_long_limit) min_long_limit = max_s_for_h; - // } - //}); - - //// 直接把虛胖的字體壓到物理極限 - //if (short_s > max_short_effective) short_s = max_short_effective; - //if (long_s > min_long_limit) long_s = min_long_limit; - - //let sizes = []; - //let shrink_turn = 'long'; - - //while (true) { - // let totalWidth = 0; - // let current_col_h = 0; - // let current_col_w = 0; - // let cols_width = []; - // let any_wrapped = false; - - // for (let i = 0; i < items.length; i++) { - // let item = items[i]; - // let s = item.isShort ? short_s : long_s; - - // // 讓 3個字 跟 4個字 的短名字能在格子內各自完美對齊 - // if (item.isShort) { - // let max_s_for_th = Math.floor(th / (item.l * 1.333)); - // if (s > max_s_for_th) s = max_s_for_th; - // } - - // item.size = s; - // let size_px = s * 1.333; - - // if (item.isShort) { - // let item_h = Math.max(th, item.l * size_px); - // if (current_col_h === 0) { - // current_col_h = item_h; - // current_col_w = size_px; - // } else if ((current_col_h + v_gap + item_h) <= h) { - // current_col_h += (v_gap + item_h); - // if (size_px > current_col_w) current_col_w = size_px; - // } else { - // cols_width.push(current_col_w); - // current_col_h = item_h; - // current_col_w = size_px; - // } - // } else { - // if (current_col_h > 0) { - // cols_width.push(current_col_w); - // current_col_h = 0; - // current_col_w = 0; - // } - - // let item_w = size_px; - // if ((item.l * size_px) > (h - 10)) { - // item.isMulti = true; - // any_wrapped = true; - // let chars_per_col = Math.floor((h - 10) / size_px); - // if (chars_per_col < 1) chars_per_col = 1; - // let item_cols = Math.ceil(item.l / chars_per_col); - // item_w = item_cols * (size_px * 1.1); - // } else { - // item.isMulti = false; - // } - - // cols_width.push(item_w); - // } - // } - - // if (current_col_h > 0) cols_width.push(current_col_w); - // totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0); - - // // 🌟 過關條件 - // if (totalWidth <= w && (!any_wrapped || long_s === 6)) { - // sizes = items.map(x => ({ name: x.name, size: x.size, isMulti: x.isMulti, isShort: x.isShort })); - // break; - // } - - // // 真實的輪流縮小機制 - // if (any_wrapped && long_s > shrinkLimit) { - // long_s--; // 高度爆框,只懲罰長名字 - // } else { - // // 寬度爆框,輪流真槍實彈互扣 - // let hasLong = items.some(x => !x.isShort); - // let hasShort = items.some(x => x.isShort); - - // if (hasLong && hasShort) { - // if (shrink_turn === 'long') { - // if (long_s > shrinkLimit) { long_s--; shrink_turn = 'short'; } - // else if (short_s > shrinkLimit) { short_s--; shrink_turn = 'long'; } - // else break; - // } else { - // if (short_s > shrinkLimit) { short_s--; shrink_turn = 'long'; } - // else if (long_s > shrinkLimit) { long_s--; shrink_turn = 'short'; } - // else break; - // } - // } else if (hasLong) { - // if (long_s > shrinkLimit) long_s--; - // else break; - // } else { - // if (short_s > shrinkLimit) short_s--; - // else break; - // } - // } - //} - - let th = Number(d.textHeight) || Number(d.height); + let th = Number(d.textHeight) || h; let v_gap = Number(d.textSpan) || 0; let h_gap = 5; let shrinkLimit = 6; - if (Number(d.height) < Number(500)) { - shrinkLimit =8; - }else if (Number(d.height) < Number(300)) { + + if (h < 500) { + shrinkLimit = 8; + } else if (h < 300) { shrinkLimit = 12; } + let long_s = Number(d.fontSize); let short_s = Number(d.fontSize); - if (Number(d.height)<500&&mid.length == 1 && mid[0].includes("暨無始劫以來累世冤親債主")) { - mid = mid[0].split("暨") + + if (h < 500 && mid.length == 1 && mid[0].includes("暨無始劫以來累世冤親債主")) { + mid = mid[0].split("暨"); mid[0] = mid[0] + "暨"; - console.log("mid:",mid) } - // 1. 沿用你確認成功的版本:用固定的 40px 當作長短名尺規,徹底解決短名字被強佔 800px 變三行的問題! + + // 1. 初始判定與特例條件計算 let items = mid.map(name => { let l = name.length; - let isShort = (mid.length > 1) &&(l * 40) <= (th + (v_gap/2)); + let isShort = (l * 40) <= (th + (v_gap / 2)); return { name: name, l: l, isShort: isShort, isMulti: false, size: 0 }; }); - + + let maxShortLen = Math.max(...items.filter(x => x.isShort).map(x => x.l), 1); + + let allShort = items.every(x => x.isShort); + + // 🚀 動態防呆:不寫死字數!用「實際字體大小」計算是否放得進半高。 + // 這樣 13 個字會自動出局,而 3 個字的小牌位絕對能穩穩過關。 + let fitsInHalf = items.every(x => (x.l * short_s * 1.333) <= (h / 2)); + + let isSpecial1 = allShort && fitsInHalf && items.length === 1; + let isSpecial2 = allShort && fitsInHalf && items.length === 2; + let isSpecial4 = allShort && fitsInHalf && (items.length === 4 || items.length===6||items.length===8); + let isSpecial = isSpecial1 || isSpecial2 || isSpecial4; + let sizes = []; let shrink_turn = 'long'; + // 2. 模擬排版與字體縮放運算 while (true) { let totalWidth = 0; let current_col_h = 0; let current_col_w = 0; let cols_width = []; - let any_wrapped = false; // 🌟 專門用來監測長名字有沒有折行 + let any_wrapped = false; for (let i = 0; i < items.length; i++) { let item = items[i]; @@ -531,7 +412,19 @@ let size_px = s * 1.333; if (item.isShort) { - let item_h = Math.max(th, item.l * size_px); + let item_h = Math.max(th, maxShortLen * size_px); + + // 壓縮高度釋放空間 (支援反向放大字體) + if (isSpecial) { + if (isSpecial1 || isSpecial2) item_h = h / 2; + if (isSpecial4) item_h = (h / 2) - (v_gap / 2); + } else { + let natural_h = maxShortLen * size_px; + if (item_h > natural_h * 1.5) { + item_h = natural_h * 1.5; + } + } + if (current_col_h === 0) { current_col_h = item_h; current_col_w = size_px; @@ -552,10 +445,9 @@ let item_w = size_px; - // 🌟 解決長名字折行:加上安全邊距 (h - 10),一旦超過就標記 any_wrapped if ((item.l * size_px) > (h - 10)) { item.isMulti = true; - any_wrapped = true; // 抓到長名字折行了! + any_wrapped = true; let chars_per_col = Math.floor((h - 10) / size_px); if (chars_per_col < 1) chars_per_col = 1; @@ -573,25 +465,20 @@ totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0); - // 🌟 核心修正:除了總寬度沒爆框,還必須【沒有發生長名字折行】(或長名字已經縮到極限 6pt 退無可退),才會滿意並停止縮小! if (totalWidth <= w && (!any_wrapped || long_s === shrinkLimit)) { sizes = items.map(x => { let final_s = x.size; if (x.isShort) { - // 算出這個字數能剛好塞進 th 的最大字體 - let max_s_for_th = Math.floor(th / (x.l * 1.333)); - // 如果統算出來的 short_s 太大,就把它壓低到剛好符合 th 高度 + let max_s_for_th = Math.floor(th / (maxShortLen * 1.333)); if (final_s > max_s_for_th) { final_s = max_s_for_th; } } - return { name: x.name, size: final_s, isMulti: x.isMulti, isShort: x.isShort }; }); break; } - // 沿用成功的輪流互相縮小機制 (-1pt) let hasLong = items.some(x => !x.isShort); let hasShort = items.some(x => x.isShort); @@ -614,48 +501,104 @@ } } - // 防呆處理 if (sizes.length === 0) { sizes = mid.map(name => { return { name: name, size: shrinkLimit, isMulti: false, isShort: true }; }); } - // 2. 建立外層的 Flexbox 容器 + + // 3. 建立外層 Flexbox 容器 + let flexDir = isSpecial2 ? "column" : "row"; + let flexJustify = isSpecial ? "center" : "flex-start"; + let flexAlign = "center"; + if (isSpecial4) { + v_gap=Number(h)/4 + } + let $namelist = $(`
`).css({ "writing-mode": "vertical-rl", display: "flex", - "flex-direction": "row", + "flex-direction": flexDir, "flex-wrap": "wrap", "align-content": "center", - "align-items": "center", // 確保元素靠上對齊 + "align-items": flexAlign, + "justify-content": flexJustify, margin: "auto", - width: `${d.width}px`, - height: `${d.height}px`, - border: "0px solid #ccc", + width: `${w}px`, + height: `${h}px`, + border: "0px solid transparent", padding: "1px", - "column-gap": `${d.textSpan}px`, - "font-family":d.fontFamily + "column-gap": `${v_gap}px`, + "font-family": d.fontFamily }); + // 4. 渲染單一名稱 sizes.forEach(item => { - let th = Number(d.textHeight) || Number(d.height) - let minH = item.isShort ? `${th}px` : `${d.height}px` ; + let size_px = item.size * 1.333; + + // 🚀 關鍵修正:統一使用 maxShortLen 算出標準高度,保證所有短名字的「格子」一模一樣大! + let natural_max_h = maxShortLen * size_px; + let boxH_num = item.isShort ? Math.max(th, natural_max_h) : h; + + if (isSpecial) { + if (isSpecial1 || isSpecial2) boxH_num = h / 2; + if (isSpecial4) boxH_num = (h / 2) - (v_gap ) - 2; + } else if (item.isShort) { + // 壓縮高度釋放空間時,同樣以統一的標準高度來判定 + if (boxH_num > natural_max_h * 1.5) { + boxH_num = natural_max_h * 1.5; + } + } + + let minH = `${boxH_num}px`; let alignStyle = item.isMulti || (!item.isMulti && !item.isShort) ? "start" : "justify"; - let $span= $(`${item.name}`).css({ - display: "block", - "min-height": minH, // 強制多行長名撐滿高度,避免其他名字墊底 - "max-height": `${d.height}px`, - width: "auto", + let spanDisplay = "block"; + let spanJustifyContent = "flex-start"; + + // 🚀 新增:只針對單一短名賦予 1/4 的 margin-top + let spanMarginTop = "0px"; + if (isSpecial1 || isSpecial2) { + spanMarginTop = `-${h / 4}px`; + } + + if (isSpecial1 || isSpecial2 || isSpecial4) { + alignStyle = "justify"; + spanJustifyContent = "space-between"; + } + + // 這裡依然保留各自的真實字數,只用來計算「字距」,不影響格子大小 + let nameLen = item.name.length; + let contentH = nameLen * size_px; + let emptySpace = boxH_num - contentH; + let gapsCount = nameLen > 1 ? (nameLen - 1) : 1; + let avgGap = emptySpace / gapsCount; + + // 防過度拉扯 (排除特例,保護單名/雙名的正常分散) + if (!isSpecial && alignStyle === "justify" && avgGap > (size_px * 1.5)) { + alignStyle = "justify"; + spanDisplay = "block"; + spanJustifyContent = "start"; + } + + let $span = $(`${item.name}`).css({ + "display": spanDisplay, + "justify-content": spanJustifyContent, + "align-items": "center", + "min-height": minH, + "max-height": `${h}px`, + "width": "auto", "line-height": "1.1", "text-align": alignStyle, "text-align-last": alignStyle, "margin-left": "5px", + "margin-top": spanMarginTop, // 🚀 套用動態外距 "text-justify": "inter-character", "word-break": "break-all" }); + $namelist.append($span); }); - + html = $namelist; } else if (d.elementID === "alive") { @@ -703,7 +646,7 @@ }) let content = $(``) .css({ - position: "absolute", right: "0mm", top: "0mm", fontSize: '16px', + position: "absolute", right: "0mm", top: "-1mm", fontSize: '12px', "z-index": 9999, "align-items": "center" @@ -765,7 +708,7 @@ } .row-wrapper{ width: ${totalPrWidth} mm; /* 關鍵:寫死精準寬度,不多也不少 */ - margin: 0 20px 0 20px; /* 關鍵:左右邊界一致置中 */ + margin: 20px 20px 0 20px; /* 關鍵:左右邊界一致置中 */ font-size: 0; } .tablet-paper { @@ -775,7 +718,7 @@ page-break-inline:avoid; break-after: page !important; page-break-after: always !important; - margin:7mm 1px 0 1px; + margin:2mm 1px 0 1px; border: none !important; background-repeat: no-repeat; background-size: 100% 100%; @@ -865,7 +808,7 @@ } .row-wrapper{ width: ${totalPrWidth} mm; /* 關鍵:寫死精準寬度,不多也不少 */ - margin: 0 20px 0 20px; /* 關鍵:左右邊界一致置中 */ + margin: 20px 20px 0 20px; /* 關鍵:左右邊界一致置中 */ font-size: 0; } .tablet-paper{ @@ -874,7 +817,7 @@ width: ${size.width}mm !important; height: ${size.height}mm !important; position: relative; - margin: 7mm 1px 0 1px; + margin: 2mm 1px 0 1px; background-image: url(${img.path}); background-size: 100% 100%; break-inline:avoid; @@ -1066,9 +1009,7 @@ let w = parseFloat(el.width); let th = parseFloat(el.textHeight) || h; - // 🌟 軸向修正:textSpan 其實是「垂直」堆疊間距! let v_gap = parseFloat(el.textSpan) || 0; - // 水平換行的間距,給予固定的安全值 5px let h_gap = 5; let shrinkLimit = 4; if (Number(el.height) < 300) { @@ -1077,6 +1018,12 @@ shrinkLimit = 6; } let s = parseFloat(el.fontSize); + if (names.length == 2) { + h = th + v_gap; + } + + // 🚀 新增:取得這批名單的最大字數,確保所有格子高度統一 + let maxLen = Math.max(...names.map(x => x.length), 1); while (s >= shrinkLimit) { let size_px = s * 1.333; @@ -1088,10 +1035,14 @@ for (let i = 0; i < names.length; i++) { let l = names[i].length; - // 實際佔用高度 - let item_h = Math.max(th, l * size_px); + // 🚀 修正:排版預測時,統一使用最大字數來計算所需高度 + let natural_max_h = maxLen * size_px; + let item_h = Math.max(th, natural_max_h); + + if (item_h > natural_max_h * 1.5) { + item_h = natural_max_h * 1.5; + } - // 1. 若單一名字高度超過容器,視為長名字折行 if (item_h > (h - 5)) { any_wrapped = true; if (current_col_h > 0) { @@ -1104,15 +1055,14 @@ let item_cols = Math.ceil(l / chars_per_col); cols_width.push(item_cols * (size_px * 1.1)); } else { - // 2. 短名字正常堆疊,並套用正確的 v_gap (30px) if (current_col_h === 0) { current_col_h = item_h; current_col_w = size_px; } else if (current_col_h + v_gap + item_h <= h) { - current_col_h += v_gap + item_h; // 高度夠,疊在同一行 + current_col_h += v_gap + item_h; if (size_px > current_col_w) current_col_w = size_px; } else { - cols_width.push(current_col_w); // 高度不夠,換行 + cols_width.push(current_col_w); current_col_h = item_h; current_col_w = size_px; } @@ -1121,7 +1071,6 @@ if (current_col_h > 0) cols_width.push(current_col_w); - // 總寬度計算 = 所有欄寬加總 + (欄數-1)*5px let totalWidth = cols_width.reduce((a, b) => a + b, 0) + (cols_width.length > 1 ? (cols_width.length - 1) * h_gap : 0); if (totalWidth <= w && (!any_wrapped || s === shrinkLimit)) { @@ -1145,28 +1094,59 @@ "border": "0px solid transparent", "padding": "0px", "font-family": el.fontFamily, - "column-gap": `${v_gap}px` // 直書模式下,column-gap 對應的就是垂直間距! + "column-gap": `${v_gap}px` }); let self = this; names.forEach(name => { - $namelist.append(self.renderLiveSpan(name, el, s)); + // 🚀 將 maxLen 傳遞給渲染函式 + $namelist.append(self.renderLiveSpan(name, el, s, maxLen)); }); return $namelist; }, - renderLiveSpan(name, el, fontSize) { + // 🚀 接收 maxLen 參數 + renderLiveSpan(name, el, fontSize, maxLen = 1) { let h = parseFloat(el.height); let th = parseFloat(el.textHeight) || h; + let size_px = fontSize * 1.333; + let l = name.length; + + let natural_max_h = maxLen * size_px; + let boxH_num = Math.max(th, natural_max_h); + + if (boxH_num > natural_max_h * 1.5) { + boxH_num = natural_max_h * 1.5; + } + + let minH = `${boxH_num}px`; + let alignStyle = "justify"; + let spanDisplay = "block"; + let spanJustifyContent = "flex-start"; + + let contentH = l * size_px; + let emptySpace = boxH_num - contentH; + let gapsCount = l > 1 ? (l - 1) : 1; + let avgGap = emptySpace / gapsCount; + + // 🚀 關鍵修正:當陽上名字字距過大觸發防過度拉扯時,改為「靠上對齊 (start)」而不是置中 (center) + if (alignStyle === "justify" && avgGap > (size_px * 1.5)) { + alignStyle = "justify"; // 改為靠上,讓字首切齊 + spanDisplay = "block"; + spanJustifyContent = "space-between"; // Flex 靠上 + } return $(`${name}`).css({ - "display": "block", - "min-height": `${th}px`, + "display": spanDisplay, + "justify-content": spanJustifyContent, + "align-items": "center", + "min-height": minH, "max-height": `${h}px`, "width": "auto", - "text-align": "justify", - "text-align-last": "justify", - "margin-left": "0px", // 配合 JS 計算的 h_gap = 5 + "text-align": alignStyle, + "text-align-last": alignStyle, + "margin-left": "0px", + "margin-top": "0px", "text-justify": "inter-character", "word-break": "break-all", "white-space": "pre-line"