修正排版

This commit is contained in:
2026-09-07 15:09:46 +08:00
parent 666b3e0c16
commit 22e7c008ef
2 changed files with 491 additions and 138 deletions
+344 -5
View File
@@ -1314,7 +1314,7 @@ html, body, * {
this.elements.forEach(el => { this.elements.forEach(el => {
const names = el.text.split('\n').filter(s => s.trim()); const names = el.text.split('\n').filter(s => s.trim());
const pages = Math.max(1, Math.ceil(names.length / self.rosterLimit)); const pages = Math.max(1, Math.ceil(names.length / self.rosterLimit));
const slice = names.slice(0 * self.rosterLimit, (0 + 1) * self.rosterLimit); const slice = names;//.slice(0 * self.rosterLimit, (0 + 1) * self.rosterLimit);
$paper.append(self.createEl(el, slice)); $paper.append(self.createEl(el, slice));
}); });
//$canvas.append($paper); //$canvas.append($paper);
@@ -1461,7 +1461,7 @@ html, body, * {
} }
return 12; // 極限最小字體 return 12; // 極限最小字體
}, },
renderNameList(names, el, rowWidth) { /*renderNameList(names, el, rowWidth) {
let h = Number(el.height); let h = Number(el.height);
let w = Number(el.width); let w = Number(el.width);
let th = Number(el.textHeight) || h; let th = Number(el.textHeight) || h;
@@ -1592,6 +1592,220 @@ html, body, * {
// $namelist.append(self.renderNameSpan(n, el, rowWidth)) // $namelist.append(self.renderNameSpan(n, el, rowWidth))
//}) //})
//return $namelist; //return $namelist;
},*/
renderNameList(names, el) {
let h = Number(el.height);
let w = Number(el.width);
let th = Number(el.textHeight) || h;
let v_gap = Number(el.textSpan) || 0;
let h_gap = 5;
let shrinkLimit = 6;
if (h < 500) {
shrinkLimit = 8;
} else if (h < 300) {
shrinkLimit = 12;
}
let long_s = Number(el.style.fontSize);
let short_s = Number(el.style.fontSize);
if (h < 500 && names.length == 1 && names[0].includes("暨無始劫以來累世冤親債主")) {
names = names[0].split("暨");
names[0] = names[0] + "暨";
}
// 1. 初始判定與特例條件計算
let items = names.map(name => {
let l = name.length;
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);
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;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let s = item.isShort ? short_s : long_s;
item.size = s;
let size_px = s * 1.333;
if (item.isShort) {
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;
} 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 === shrinkLimit)) {
sizes = items.map(x => {
let final_s = x.size;
if (x.isShort) {
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;
}
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;
}
}
if (sizes.length === 0) {
sizes = names.map(name => ({ name: name, size: shrinkLimit, isMulti: false, isShort: true }));
}
let flexDir = isSpecial2 ? "column" : "row";
let flexJustify = isSpecial ? "center" : "flex-start";
let flexAlign = "center";
if (isSpecial4) v_gap = Number(h) / 4;
let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical-rl",
display: "flex",
"flex-direction": flexDir,
"flex-wrap": "wrap",
"align-content": "center",
"align-items": flexAlign,
"justify-content": flexJustify,
margin: "auto",
width: `${w}px`,
height: `${h}px`,
border: "0px solid transparent",
padding: "1px",
"column-gap": `${v_gap}px`,
"font-family": el.style.fontFamily
});
sizes.forEach(item => {
let size_px = item.size * 1.333;
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 spanDisplay = "block";
let spanJustifyContent = "flex-start";
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 = $(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).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);
});
return $namelist;
}, },
renderNameSpan(item, el, colWidth) { renderNameSpan(item, el, colWidth) {
let th = Number(el.textHeight) || Number(el.height) let th = Number(el.textHeight) || Number(el.height)
@@ -1646,7 +1860,7 @@ html, body, * {
// "text-justify": "inter-character", // "text-justify": "inter-character",
//});// "margin-bottom": `${el.textSpan}px`, //});// "margin-bottom": `${el.textSpan}px`,
}, },
renderLiveList(names, el) { /* renderLiveList(names, el) {
//console.log("renderLiveList:", names); //console.log("renderLiveList:", names);
//$("#imp-text").updateActive("text",names) //$("#imp-text").updateActive("text",names)
//let $namelist = $(`<div class='liveList'></div>`).css({ //let $namelist = $(`<div class='liveList'></div>`).css({
@@ -1772,7 +1986,132 @@ html, body, * {
// "margin-left": "5px", // "margin-left": "5px",
// "text-justify": "inter-character", // "text-justify": "inter-character",
//});//"margin-bottom": `${el.textSpan}px`, //});//"margin-bottom": `${el.textSpan}px`,
},*/
renderLiveList(names, el) {
let h = parseFloat(el.height);
let w = parseFloat(el.width);
let th = parseFloat(el.textHeight) || h;
let v_gap = parseFloat(el.textSpan) || 0;
let h_gap = 5;
let shrinkLimit = 4;
if (Number(el.height) < 300) shrinkLimit = 8;
else if (Number(el.height) < 500) shrinkLimit = 6;
let s = parseFloat(el.style.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;
let current_col_h = 0;
let current_col_w = 0;
let cols_width = [];
let any_wrapped = false;
for (let i = 0; i < names.length; i++) {
let l = names[i].length;
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;
if (item_h > (h - 5)) {
any_wrapped = true;
if (current_col_h > 0) {
cols_width.push(current_col_w);
current_col_h = 0;
current_col_w = 0;
}
let chars_per_col = Math.floor((h - 5) / size_px);
if (chars_per_col < 1) chars_per_col = 1;
let item_cols = Math.ceil(l / chars_per_col);
cols_width.push(item_cols * (size_px * 1.1));
} else {
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;
}
}
}
if (current_col_h > 0) cols_width.push(current_col_w);
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)) break;
s -= 1;
}
if (s < shrinkLimit) s = shrinkLimit;
let $namelist = $(`<div class='liveList'></div>`).css({
"writing-mode": "vertical-rl",
"display": "flex",
"flex-flow": "wrap",
"align-content": "center",
"align-items": "center",
"margin": "auto",
"width": `${w}px`,
"height": `${h}px`,
"border": "0px solid transparent",
"padding": "0px",
"font-family": el.style.fontFamily,
"column-gap": `${v_gap}px`
});
let self = this;
names.forEach(name => {
$namelist.append(self.renderLiveSpan(name, el, s, maxLen));
});
return $namelist;
}, },
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;
if (alignStyle === "justify" && avgGap > (size_px * 1.5)) {
alignStyle = "justify";
spanDisplay = "block";
spanJustifyContent = "space-between";
}
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
"display": spanDisplay,
"justify-content": spanJustifyContent,
"align-items": "center",
"min-height": minH,
"max-height": `${h}px`,
"width": "auto",
"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"
});
},
// 品字佈局邏輯:一上二下 // 品字佈局邏輯:一上二下
renderRoster(names, el, rowWidth) { renderRoster(names, el, rowWidth) {
if (!names.length) return ''; if (!names.length) return '';
@@ -1900,7 +2239,7 @@ html, body, * {
} else if (this.activeId === "title1") { } else if (this.activeId === "title1") {
let names = el.text.split('\n').filter(s => s.trim()); let names = el.text.split('\n').filter(s => s.trim());
let slice = names.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit); let slice = names;//.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit);
//let perRow = Math.floor(Number(el.height) / Number(el.textHeight)); //let perRow = Math.floor(Number(el.height) / Number(el.textHeight));
//let rows = Math.ceil(Number(slice.length) / Number(perRow));//總行數 //let rows = Math.ceil(Number(slice.length) / Number(perRow));//總行數
@@ -1933,7 +2272,7 @@ html, body, * {
}).html(html); }).html(html);
} else if (this.activeId === "alive") { } else if (this.activeId === "alive") {
let names = el.text.split('\n').filter(s => s.trim()); let names = el.text.split('\n').filter(s => s.trim());
let slice = names.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit); let slice = names;//.slice(0 * this.rosterLimit, (0 + 1) * this.rosterLimit);
//let perRow = Math.floor(Number(el.height) / Number(el.textHeight)); //let perRow = Math.floor(Number(el.height) / Number(el.textHeight));
//let rows = Math.ceil(Number(slice.length) / Number(perRow));//總行數 //let rows = Math.ceil(Number(slice.length) / Number(perRow));//總行數
//let rowWidth = Math.floor(Number(el.width) / rows); //let rowWidth = Math.floor(Number(el.width) / rows);
+21 -7
View File
@@ -373,17 +373,20 @@
mid = mid[0].split("暨"); mid = mid[0].split("暨");
mid[0] = mid[0] + "暨"; mid[0] = mid[0] + "暨";
} }
const init_fontSize = Number(d.fontSize) > 40 ? 40 : Number(d.fontSize);
//const init_size_px = init_fontSize * 1.333;
// 1. 初始判定與特例條件計算 // 1. 初始判定與特例條件計算
let items = mid.map(name => { let items = mid.map(name => {
let l = name.length; let l = name.length;
let isShort = (l * 40) <= (th + (v_gap / 2)); //應該用初始最大值而不是40
let isShort = (l * init_fontSize) <= (th + (v_gap / 2));
return { name: name, l: l, isShort: isShort, isMulti: false, size: 0 }; 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 maxShortLen = Math.max(...items.filter(x => x.isShort).map(x => x.l), 1);
let allShort = items.every(x => x.isShort); let allShort = items.every(x => x.isShort);
console.log("全短名:",items,allShort);
// 🚀 動態防呆:不寫死字數!用「實際字體大小」計算是否放得進半高。 // 🚀 動態防呆:不寫死字數!用「實際字體大小」計算是否放得進半高。
// 這樣 13 個字會自動出局,而 3 個字的小牌位絕對能穩穩過關。 // 這樣 13 個字會自動出局,而 3 個字的小牌位絕對能穩穩過關。
@@ -417,7 +420,7 @@
// 壓縮高度釋放空間 (支援反向放大字體) // 壓縮高度釋放空間 (支援反向放大字體)
if (isSpecial) { if (isSpecial) {
if (isSpecial1 || isSpecial2) item_h = h / 2; if (isSpecial1 || isSpecial2) item_h = h / 2;
if (isSpecial4) item_h = (h / 2) - (v_gap / 2); if (isSpecial4) item_h = (h / 2) - (v_gap )-2;
} else { } else {
let natural_h = maxShortLen * size_px; let natural_h = maxShortLen * size_px;
if (item_h > natural_h * 1.5) { if (item_h > natural_h * 1.5) {
@@ -469,7 +472,18 @@
sizes = items.map(x => { sizes = items.map(x => {
let final_s = x.size; let final_s = x.size;
if (x.isShort) { if (x.isShort) {
let max_s_for_th = Math.floor(th / (maxShortLen * 1.333)); //let max_s_for_th = Math.floor(th / (maxShortLen * 1.333));
//if (final_s > max_s_for_th) {
// final_s = max_s_for_th;
//}
// 🚀 關鍵修正:依照是否為特例,賦予實際可用的高度上限,解除原先 th 造成的字體壓抑
let available_h = th;
if (isSpecial) {
if (isSpecial1 || isSpecial2) available_h = h / 2;
if (isSpecial4) available_h = (h / 2) - v_gap - 2;
}
let max_s_for_th = Math.floor(available_h / (maxShortLen * 1.333));
if (final_s > max_s_for_th) { if (final_s > max_s_for_th) {
final_s = max_s_for_th; final_s = max_s_for_th;
} }
@@ -511,9 +525,9 @@
let flexDir = isSpecial2 ? "column" : "row"; let flexDir = isSpecial2 ? "column" : "row";
let flexJustify = isSpecial ? "center" : "flex-start"; let flexJustify = isSpecial ? "center" : "flex-start";
let flexAlign = "center"; let flexAlign = "center";
if (isSpecial4) { //if (isSpecial4) {
v_gap=Number(h)/4 // v_gap=Number(h)/4
} //}
let $namelist = $(`<div class='nameList'></div>`).css({ let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical-rl", "writing-mode": "vertical-rl",