Wall of People Groups: nothing on the poster is cut off, and the size list reads the same in Arabic
The chart's rotated labels are sized from their own text and wrap; its theme headers fit their row. A long group name shrinks to fit (never below 6 pt) or takes two lines, and the photo gives way so the card's content never outgrows the card. The verse reference prints at 6 pt. The size labels are isolated left to right, so an RTL page no longer reorders '5 × 8 ft · 3×'.
This commit is contained in:
parent
5de01ce9eb
commit
a331f1f20e
2 changed files with 45 additions and 16 deletions
|
|
@ -108,14 +108,38 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
const upright = /^(zh|ja|ko)/.test(String(texts.lang || '')) // CJK sidebar labels stand upright
|
||||
const glueHere = texts.glueHere || 'glue here'
|
||||
const f = (v) => v.toFixed(3) + 'in'
|
||||
const W = plan.contentW
|
||||
const vPt = plan.text.versePt, nPt = plan.text.namePt
|
||||
// The reference prints at the verse's size, never below MIN_READABLE_PT (a reference that can't be read
|
||||
// is noise); it is only printed on sizes whose verses are readable.
|
||||
const rPt = Math.max(MIN_READABLE_PT, vPt * 0.8)
|
||||
// A long name shrinks to fit its line (bold ≈ 0.68 em a character, measured wide on purpose), down to MIN_READABLE_PT (or the
|
||||
// preset's own name size if that is smaller); a name still too long wraps to two lines.
|
||||
const nameInnerPt = plan.cw * 0.93 / PT
|
||||
const nameFit = (name) => {
|
||||
const want = nameInnerPt / (Math.max(1, name.length) * 0.68)
|
||||
const pt = Math.max(Math.min(nPt, MIN_READABLE_PT), Math.min(nPt, want))
|
||||
return { pt: +pt.toFixed(1), wrap: want < pt }
|
||||
}
|
||||
// The rotated sidebar labels of the chart are sized from their text: the longest word must fit the row's
|
||||
// height (a CJK label stands upright, one em a character; other scripts ≈ 0.72 em a capital with the
|
||||
// letter spacing), and the wrapped lines must fit the sidebar's width.
|
||||
const railH = plan.tableH * 0.33 - W * 0.006, railW = W * 0.06 - W * 0.004
|
||||
const railFs = (text) => {
|
||||
const words = String(text).split(/\s+/), em = upright ? 1.02 : 0.72
|
||||
const longest = Math.max(...words.map((w) => w.length)) * em
|
||||
const byH = railH / longest, byW = railW / (Math.min(words.length, 3) * 1.2)
|
||||
return Math.min(W * 0.0105, byH, byW)
|
||||
}
|
||||
const railLabel = (text) => `<span style="font-size:${f(railFs(text))}">${esc(text)}</span>`
|
||||
const card = (c) => {
|
||||
const g = groups[c.i], v = blessingFor(c.i)
|
||||
const img = g.imageUrl ? `<img src="${esc(g.imageUrl)}" alt="">` : '<div class="noimg"></div>'
|
||||
// Below MIN_READABLE_PT the verse would print as grey noise: that size shows name + photo only.
|
||||
const verse = plan.text.readable ? `<div class="vs" dir="auto">${esc(v.text)}</div><div class="rf" dir="auto">${esc(v.ref)}</div>` : ''
|
||||
const nf = nameFit(g.name || '')
|
||||
return `<div class="card" style="left:${f(c.x)};top:${f(c.y)};width:${f(c.w)};height:${f(c.h)}">`
|
||||
+ `<div class="nm" dir="auto">${esc(g.name || '')}</div>${img}${verse}</div>`
|
||||
+ `<div class="nm${nf.wrap ? ' w2' : ''}" dir="auto" style="font-size:${nf.pt}pt">${esc(g.name || '')}</div>${img}${verse}</div>`
|
||||
}
|
||||
const pages = plan.bands.map((bd) => {
|
||||
const s = bd.s, last = s === plan.bands.length - 1
|
||||
|
|
@ -127,11 +151,11 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
// every scripture, name and reference on the sheet (cards, chart, blessing, closing).
|
||||
const cell = (v) => `<p class="tv" dir="auto">${esc(v.text)}</p><p class="tr" dir="auto">${esc(v.ref)}</p>`
|
||||
const table = `<div class="themes" dir="${dir}">`
|
||||
+ `<div class="rl rl-h"><span>${esc(lab('themesLabel', 'Prayer themes'))}</span></div>`
|
||||
+ `<div class="rl rl-h">${railLabel(lab('themesLabel', 'Prayer themes'))}</div>`
|
||||
+ THEMES.map((t) => `<div class="th"><div class="ic">${ICONS[t.id]}</div><div class="tn">${esc(lab('theme_' + t.id, THEME_EN[t.id]))}</div></div>`).join('')
|
||||
+ `<div class="rl"><span>${esc(lab('oldTestament', 'Old Testament'))}</span></div>`
|
||||
+ `<div class="rl">${railLabel(lab('oldTestament', 'Old Testament'))}</div>`
|
||||
+ THEMES.map((t) => `<div class="td">${cell(t.ot)}</div>`).join('')
|
||||
+ `<div class="rl"><span>${esc(lab('newTestament', 'New Testament'))}</span></div>`
|
||||
+ `<div class="rl">${railLabel(lab('newTestament', 'New Testament'))}</div>`
|
||||
+ THEMES.map((t) => `<div class="td">${cell(t.nt)}</div>`).join('')
|
||||
+ '</div>'
|
||||
const head = bd.bannerY != null
|
||||
|
|
@ -149,7 +173,6 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts =
|
|||
return `<section class="sheet">${glue}${head}${plan.cards.filter((c) => c.s === s).map(card).join('')}${foot}${trim}`
|
||||
+ `<div class="tag">${s + 1} / ${P.sheets}</div></section>`
|
||||
})
|
||||
const W = plan.contentW
|
||||
const css = `@page{size:${P.sheetW}in ${P.sheetH}in;margin:0}
|
||||
*{box-sizing:border-box}html,body{margin:0;padding:0}
|
||||
body{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
||||
|
|
@ -160,11 +183,11 @@ body{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-print-col
|
|||
.hd .subt{margin:${f(plan.headH * 0.06)} auto 0;max-width:78%;font:${f(plan.headH * 0.085)}/1.35 -apple-system,'Segoe UI',Arial,sans-serif;color:#333}
|
||||
.themes{display:grid;grid-template-columns:${f(W * 0.06)} repeat(3,1fr);grid-template-rows:34% 33% 33%;height:100%;border:.03in solid #1c1c1c;border-radius:${f(W * 0.006)};overflow:hidden}
|
||||
.themes > div{border:.015in solid #cfcfcf;padding:${f(W * 0.006)} ${f(W * 0.008)};overflow:hidden}
|
||||
.rl{display:flex;align-items:center;justify-content:center;background:#1c1c1c;color:#fff}
|
||||
.rl span{writing-mode:vertical-rl;${upright ? 'text-orientation:upright;' : 'transform:rotate(180deg);'}font:700 ${f(W * 0.0105)} -apple-system,'Segoe UI',Arial,sans-serif;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap}
|
||||
.th{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:${f(W * 0.004)};background:#faf8f2}
|
||||
.th .ic{width:${f(W * 0.05)};height:${f(W * 0.05)};color:#6b4f1d}.th .ic svg{width:100%;height:100%}
|
||||
.th .tn{font:700 ${f(W * 0.014)} Georgia,serif;letter-spacing:.04em;text-transform:uppercase}
|
||||
.themes > .rl{display:flex;align-items:center;justify-content:center;background:#1c1c1c;color:#fff;padding:${f(W * 0.002)}}
|
||||
.rl span{writing-mode:vertical-rl;${upright ? 'text-orientation:upright;' : 'transform:rotate(180deg);'}font-weight:700;font-family:-apple-system,'Segoe UI',Arial,sans-serif;line-height:1.15;letter-spacing:.06em;text-transform:uppercase;text-align:center}
|
||||
.themes > .th{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:${f(W * 0.003)};background:#faf8f2;padding-top:${f(W * 0.003)};padding-bottom:${f(W * 0.003)}}
|
||||
.th .ic{width:${f(W * 0.028)};height:${f(W * 0.028)};color:#6b4f1d;flex:none}.th .ic svg{width:100%;height:100%}
|
||||
.th .tn{font:700 ${f(W * 0.012)}/1.1 Georgia,serif;letter-spacing:.04em;text-transform:uppercase}
|
||||
.td{display:flex;flex-direction:column;justify-content:center;text-align:center}
|
||||
.td .tv{margin:0;font-size:${f(W * 0.0078)};line-height:1.3;font-style:italic}
|
||||
.td .tr{margin:${f(W * 0.002)} 0 0;font-size:${f(W * 0.0062)};color:#666}
|
||||
|
|
@ -179,10 +202,13 @@ body{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-print-col
|
|||
.st.ctr{background:#faf8f2;border:.03in solid #1c1c1c}.st.ctr .sv{font-size:${f(W * 0.0064)};font-weight:600}.st.ctr .sn{color:#1c1c1c}
|
||||
.close{margin:${f(W * 0.008)} auto 0;max-width:80%;font:700 ${f(W * 0.0085)}/1.35 -apple-system,'Segoe UI',Arial,sans-serif}
|
||||
.card{position:absolute;padding:${f(plan.cw * 0.035)};display:flex;flex-direction:column;align-items:center;text-align:center;overflow:hidden}
|
||||
.card .nm{font:700 ${nPt}pt/1.1 -apple-system,'Segoe UI',Arial,sans-serif;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:none}
|
||||
.card img,.card .noimg{width:100%;height:${plan.text.readable ? 52 : 78}%;object-fit:cover;margin:${f(plan.cw * 0.02)} 0;background:#eee;flex:none}
|
||||
.card .nm{font-weight:700;line-height:1.1;font-family:-apple-system,'Segoe UI',Arial,sans-serif;width:100%;white-space:nowrap;overflow:hidden;flex:none}
|
||||
.card .nm.w2{white-space:normal;overflow-wrap:anywhere;hyphens:auto;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
||||
/* The photo takes whatever height the text leaves, so a card's content never outgrows the card. */
|
||||
.card img,.card .noimg{width:100%;flex:1 1 0;min-height:0;object-fit:cover;margin:${f(plan.cw * 0.02)} 0;background:#eee}
|
||||
.card .vs{font-size:${vPt}pt;line-height:1.15;font-style:italic}
|
||||
.card .rf{font-size:${(vPt * 0.8).toFixed(1)}pt;color:#777;margin-top:1pt}
|
||||
.card .vs,.card .rf{flex:none}
|
||||
.card .rf{font-size:${rPt.toFixed(1)}pt;color:#777;margin-top:1pt}
|
||||
.glue{position:absolute;left:0;width:100%;border-bottom:.02in dashed #b00;display:flex;align-items:center;justify-content:center;font:700 ${f(Math.min(0.5, b * 0.08))} sans-serif;color:#b00;background:rgba(176,0,0,.035);letter-spacing:.05em;text-transform:uppercase}
|
||||
.trim{position:absolute;left:0;bottom:0;width:100%;border-bottom:.02in solid #999}
|
||||
.tag{position:absolute;right:${f(Math.max(0.15, b * 0.1))};top:${f(Math.max(0.1, b * 0.08))};font:600 ${f(Math.max(0.12, b * 0.035))} sans-serif;color:#aaa}`
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ export default {
|
|||
return {
|
||||
mode: 'bless', // 'bless' (default) | 'social'
|
||||
posterPreset: DEFAULT_PRESET, // blessPoster.js PRESETS: 5×8 ft (3 sheets) by default
|
||||
posterPresets: Object.entries(PRESETS).map(([id, p]) => ({ id, label: p.label + (p.sheets > 1 ? ' · ' + p.sheets + '×' : '') })),
|
||||
// Each label is isolated left to right (U+2066 … U+2069): "5 × 8 ft · 3×" would otherwise be reordered
|
||||
// by an RTL page into "×ft · 3 8 × 5" (an <option> cannot hold a <bdi>).
|
||||
posterPresets: Object.entries(PRESETS).map(([id, p]) => ({ id, label: '\u2066' + p.label + (p.sheets > 1 ? ' · ' + p.sheets + '×' : '') + '\u2069' })),
|
||||
printing: false, // the HD poster print is loading photos (drives the button's busy state)
|
||||
cardWidth: 50,
|
||||
cardHeight: 70,
|
||||
|
|
@ -149,8 +151,9 @@ export default {
|
|||
subtitle: this.$t('wall.posterSubtitle', { n: groups.length.toLocaleString('en-US') }),
|
||||
glueHere: this.$t('wall.glueHere'),
|
||||
// The chart labels and the closing line are translated; the journey's station names print in
|
||||
// English (blessPoster defaults) until they have keys, and the verses are WEB English in every
|
||||
// locale for now (sheet row 103).
|
||||
// English (blessPoster defaults) until they have keys. The scripture (cards, chart, blessing,
|
||||
// journey) prints in the WEB's English in EVERY locale on purpose, until per-locale Bibles are
|
||||
// chosen (sheet row 103): a translated label must never sit on an untranslated verse by accident.
|
||||
labels: {
|
||||
themesLabel: this.$t('wall.themesLabel'),
|
||||
oldTestament: this.$t('wall.oldTestament'),
|
||||
|
|
|
|||
Loading…
Reference in a new issue