增加信眾牌位範本及疏文

This commit is contained in:
2026-08-25 17:52:36 +08:00
parent a8d3990784
commit fa1ed1f827
14 changed files with 943 additions and 375 deletions
+249 -241
View File
@@ -187,7 +187,7 @@
</div>
<script type="text/javascript">
const designer = {
zoomScale:1,
zoomScale:0.5,
code: {
style: []
},
@@ -1498,223 +1498,158 @@
return txt;
},
renderNameList(names, d, newFontSize, style, colWidth) {
let self = this;
let h = Number(d.height);
let w = Number(d.width);
let th = Number(d.textHeight) || h;
let gap = 5; // 對應 span 的 margin-left
let h = parseFloat(d.height);
let w = parseFloat(d.width);
let th = parseFloat(d.textHeight) || h;
let v_gap = parseFloat(d.textSpan) || 0;
let h_gap = 5;
let s = Number(d.fontSize);
let sizes = [];
// 1. 模擬排版:找出能讓全體塞入的最佳字體組合
while (s >= 12) {
let short_s_px = s * 1.333;
let hasShort = names.some(n => (n.length * short_s_px) <= th);
let long_s = hasShort? Math.floor(s * 0.75):s; // 長字體最多縮小 25%
if (long_s < 12) long_s = 12;
let long_s_px = long_s * 1.333;
let currentColHeight = 0;
let totalWidth = 0;
let tempSizes = [];
let anyWrapped = false;
for (let i = 0; i < names.length; i++) {
let name = names[i];
let l = name.length;
//let isMulti = (l * short_s_px) > th;
let isShort = (l * short_s_px) <= th;
let size = isShort ? s : long_s;//isMulti ? long_s : s;
let size_px = size * 1.333; //isMulti ? long_s_px : short_s_px;
let isMulti = !isShort && (l * size_px >= h);
if (isMulti) anyWrapped = true;
tempSizes.push({ name: name, size: size, isMulti: isMulti, isShort: isShort });
if (isMulti) {
// 多行長名
let charsPerCol = Math.floor(h / size_px);
if (charsPerCol < 1) charsPerCol = 1;
let lines = Math.ceil(l / charsPerCol);
let nameW = lines * (size_px * 1.1); // 1.1倍行距
if (currentColHeight > 0) currentColHeight = 0;
totalWidth += nameW + gap;
} else {
if (isShort) {
// 單行短名
if (currentColHeight > 0 && (currentColHeight + th + gap) <= h) {
currentColHeight += th + gap;
} else {
currentColHeight = th;
totalWidth += short_s_px + gap;
}
} else {
currentColHeight = 0;
totalWidth += size_px + gap;
}
}
}
if (totalWidth > 0) totalWidth -= gap;
// 總寬度未爆框,確定最佳組合
if (totalWidth <= w) {
if (hasShort) {
sizes = tempSizes;
break;
} else {
if (!anyWrapped || s === 12) {
sizes = tempSizes;
break;
}
}
}
s -= 1;
let shrinkLimit = 6;
if (h < 500) {
shrinkLimit = 8;
} else if (h < 300) {
shrinkLimit = 12;
}
// 處理極端情況
if (sizes.length === 0) {
sizes = names.map(name => {
return { name: name, size: 12, isMulti: (name.length * 12 * 1.333) > th };
});
let long_s = parseFloat(d.fontSize);
let short_s = parseFloat(d.fontSize);
if (names.length == 1 && names[0].includes("暨無始劫以來累世冤親債主")) {
names = names[0].split("暨");
names[0] = names[0] + "暨";
}
let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical-rl",
display: "flex",
"flex-direction": "row",
"flex-wrap": "wrap",
"align-content": "center",
"align-items": "center", // 確保元素靠上對齊
margin: "auto",
width: `${d.width}px`,
height: `${d.height}px`,
border: "0px solid #ccc",
padding: "1px",
"column-gap": `${d.textSpan}px`,
"font-family": d.fontFamily
// 1. 固定的長短名判斷
let items = names.map(name => {
let l = name.length;
let isShort = (names.length > 1) && ((l * 40) <= (th + (v_gap / 2)));
return { name: name, l: l, isShort: isShort, isMulti: false, size: 0 };
});
sizes.forEach(item => {
let th = Number(d.textHeight) || Number(d.height)
//let minH = item.isMulti ? `${d.height}px` : `${d.textHeight}px`;
let minH = item.isShort ? `${th}px` : `${d.height}px`
;
let alignStyle = item.isMulti ? "start" : "justify";
// 🌟 剔除「幽靈扣血」區間:直接從真實極限起跑
//let max_short_effective = shrinkLimit;
//let min_long_limit = parseFloat(d.fontSize);
let $span= $(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).css({
display: "block",
"min-height": minH, // 強制多行長名撐滿高度,避免其他名字墊底
"max-height": `${d.height}px`,
width: "auto",
"line-height": "1.1",
"text-align": alignStyle,
"text-align-last": alignStyle,
"margin-left": "5px",
"text-justify": "inter-character",
"word-break": "break-all"
});
$namelist.append($span);
})
//let $namelist = $(`<div class='nameList'></div>`).css({
// "writing-mode": "vertical rl",
// display: "flex",
// "flex-direction": "row",
// "flex-wrap": "wrap",
// margin: "auto",
// width: `${d.width}px`,
// height: `${d.height}px`,
// border: "0px solid #ccc",
// padding: "1px",
// "font-family": style.fontFamily,
// //"letter-spacing": "0.1em",
// "column-gap": `${d.textSpan}px`,
// //"row-gap": "1px",
// "align-items": "center",
// "justify-content": "start",
//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;
// }
//});
//mid.forEach(z => {
// //let info = self.getTextInfo(z, newFontSize);
// //let spanWidth = mid.length > 1 ? newFontSize : d.width
// //let height = d.textHeight;
// //if (info.width >= d.textHeight) {
// // height = d.height;
// //}
// //let $span = $(`<span class="nameSpan">${z}</span>`).css({
// // display: "block",
// // "min-height": `${height}px`,
// // "max-height": `${d.height}px`,
// // "max-width": `${rowWidth}px`,
// // //width: `${newFontSize}px`,
// // "text-align": "justify",
// // "text-align-last": "justify",
// // //"margin-bottom": "20px",
// // "margin-left": "5px",
// // "text-justify": "inter-character",
// // "white-space": "pre-line",
// // //"justify-content":"center"
// //});
// // 為「這個名字」獨立計算出專屬的最完美字體大小
// let indSize = this.calcIndividualFontSize(z, d, colWidth);
// // 2. 判斷是否發生了折行 (名字長度 * 單字像素高度 > 分配到的最大高度)
// let s_px = indSize * 1.333;
// let isMultiLine = (z.length * s_px) > Number(d.textHeight);
//if (short_s > max_short_effective) short_s = max_short_effective;
//if (long_s > min_long_limit) long_s = min_long_limit;
// // 3. 根據是否折行動態決定對齊方式
// // 單行:分散對齊 (justify) / 多行:置中對齊 (center) 或是改為 靠上對齊 (start)
// let alignStyle = isMultiLine ? "start" : "justify";
// let $span = $(`<span style="font-size: ${indSize}pt;">${z}</span>`).css({
// display: "block",
// "min-height": `${d.textHeight}px`,
// "max-height": `${d.height}px`,
// width: "auto",
// "line-height": "1.1",
// "text-align": alignStyle, // ★ 動態套用對齊方式
// "text-align-last": alignStyle, // ★ 動態套用最後一行對齊方式
// "margin-left": "5px",
// "text-justify": "inter-character",
// "word-break": "break-all"
// });
// $namelist.append($span);
//});
return $namelist;
},
renderLiveList(names, el) {
let h = Number(el.height);
let w = Number(el.width);
let th = Number(el.textHeight) || h;
let sizes = [];
let shrink_turn = 'long';
// 1. 單純計算總共有幾行 (列)
let namesPerCol = Math.floor(h / th);
if (namesPerCol < 1) namesPerCol = 1;
while (true) {
let totalWidth = 0;
let current_col_h = 0;
let current_col_w = 0;
let cols_width = [];
let any_wrapped = false;
let cols = Math.ceil(names.length / namesPerCol); // 總行數
for (let i = 0; i < items.length; i++) {
let item = items[i];
let s = item.isShort ? short_s : long_s;
// 2. 根據行數,縮小字體直到總寬度放得下
let s = Number(el.fontSize);
while (s >= 4) {
let s_px = s * (4 / 3); // pt 轉 px
let lh = 1.1; // 行高
let gap = 0; // 對應 span 的 margin-left
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;
}
// 總寬度 = 總行數 * (單行像素寬度 + 間距)
let totalWidth = cols * (s_px * lh + gap);
item.size = s;
let size_px = s * 1.333;
// 放得下就停止縮小
if (totalWidth <= w) {
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 === shrinkLimit)) {
sizes = items.map(x => {
let final_s = x.size;
if (x.isShort) {
let max_s_for_th = Math.floor(th / (x.l * 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;
}
s -= 1;
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;
}
}
}
if (s < 4) s = 4;
if (sizes.length === 0) {
sizes = names.map(name => {
return { name: name, size: shrinkLimit, isMulti: false, isShort: true };
});
}
// 3. 建立外層 Flexbox 容器
let $namelist = $(`<div class='liveList'></div>`).css({
let $namelist = $(`<div class='nameList'></div>`).css({
"writing-mode": "vertical-rl",
display: "flex",
"flex-direction": "row",
@@ -1724,66 +1659,139 @@
margin: "auto",
width: `${w}px`,
height: `${h}px`,
border: "0px solid #ccc",
padding: "0px",
border: "0px solid transparent",
padding: "1px",
"column-gap": `${v_gap}px`,
"font-family": d.fontFamily
});
sizes.forEach(item => {
let minH = item.isShort ? `${th}px` : `${h}px`;
let alignStyle = item.isMulti || (!item.isMulti && !item.isShort) ? "start" : "justify";
let $span = $(`<span style="font-size: ${item.size}pt;">${item.name}</span>`).css({
display: "block",
"min-height": minH,
"max-height": `${h}px`,
width: "auto",
"line-height": "1.1",
"text-align": alignStyle,
"text-align-last": alignStyle,
"margin-left": "5px",
"text-justify": "inter-character",
"word-break": "break-all"
});
$namelist.append($span);
});
return $namelist;
},
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 (h < 300) {
shrinkLimit = 8;
} else if (h < 500) {
shrinkLimit = 6;
}
let s = parseFloat(el.fontSize);
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 item_h = Math.max(th, l * size_px);
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.fontFamily,
"column-gap": `${el.textSpan}px`,
"column-gap": `${v_gap}px`
});
let self = this;
names.forEach(name => {
$namelist.append(self.renderLiveSpan(name, el, s));
});
//let $namelist = $(`<div class='liveList'></div>`).css({
// "writing-mode": "vertical rl",
// display: "flex",
// "flex-direction": "row",
// "flex-wrap": "wrap",
// margin: "auto",
// width: `${el.width}px`,
// height: `${el.height}px`,
// border: "0px solid #ccc",
// padding: "1px",
// "font-family": el.fontFamily,
// //"letter-spacing": "0.1em",
// "column-gap": `${el.textSpan}px`,
// //"row-gap": "1px",
// "align-items": "center",
//});
//let self = this;
//names.forEach(n => {
// $namelist.append(self.renderLiveSpan(n, el,rowWidth))
//})
return $namelist;
},
renderLiveSpan(name, el, fontSize) {
let th = Number(el.textHeight) || Number(el.height);
let h = parseFloat(el.height);
let th = parseFloat(el.textHeight) || h;
return $(`<span style="font-size: ${fontSize}pt;">${name}</span>`).css({
display: "block",
"min-height": `${th}px`, // ★ 固定分配高度,交給 Flexbox 自動排版
"max-height": `${el.height}px`,
width: `${fontSize * 1.33}`,
//"line-height": "1.1",
"text-align": "justify", // 統一分散對齊
"display": "block",
"min-height": `${th}px`,
"max-height": `${h}px`,
"width": "auto",
"text-align": "justify",
"text-align-last": "justify",
//"margin-left": "5px",
"margin-left": "0px",
"text-justify": "inter-character",
"word-break": "break-all",
"white-space": "pre-line"
});
//return $(`<span class="liveSpan">${name}</span>`).css({
// display: "block",
// "min-height": `${el.textHeight}px`,
// "max-height": `${el.height}px`,
// width: `${colWidth}px`,
// height: `${el.textHeight}px`,
// "text-align": "justify",
// "text-align-last": "justify",
// //"margin-bottom": "10px",
// "text-justify": "inter-character",
// "white-space": "pre-line"
//});
},
// 品字佈局邏輯:一上二下
renderRoster(names, el) {