UPG100 poster header: the three columns end at about the same height (WIP: verifying)
This commit is contained in:
parent
cee32f2732
commit
4ec5905492
1 changed files with 31 additions and 7 deletions
|
|
@ -875,15 +875,24 @@ function boldLines(text, width) {
|
|||
}
|
||||
function headColumnsBand(top, facts, approxLists) {
|
||||
const GRID_L = BAND_MARGIN_G, GRID_W = PW - 2 * BAND_MARGIN_G;
|
||||
const CW = (GRID_W - 2 * HC.gut) / 3;
|
||||
const AVAIL = GRID_W - 2 * HC.gut;
|
||||
// The measure leaves room for a fallback font (see HC_FALLBACK): a block set to it stays inside its column.
|
||||
const TW = (CW - 10 - BAND_TEXT_PAD / 2) * 0.92;
|
||||
const measure = (cw) => (cw - 10 - BAND_TEXT_PAD / 2) * 0.92;
|
||||
// ★ EVEN COLUMNS (2026-10-03): when the facts column runs longer than the two text columns it is WIDENED (and
|
||||
// shorter, it is narrowed) so the three end at about the same height. The two side columns stay equal, so they
|
||||
// still mirror each other round the centre. The centre's share is searched from 26% to 46% of the three-column
|
||||
// width; the split whose TALLEST column is shortest wins, and a tie goes to the split nearest equal widths, so a
|
||||
// zone that is already balanced keeps equal columns. measureHeader calls this same function, so the map below
|
||||
// gets exactly the height that is left.
|
||||
// Uppercase, letterspaced keys: measured as capitals plus the .14em tracking.
|
||||
const keyW = (k) => textW(k.toUpperCase(), HC.keyFs) + k.length * HC.keyFs * 0.14;
|
||||
// Each column's height at a given measure (the same arithmetic the drawing below advances y by).
|
||||
const proseH = (col, tw) => HC.headLh + col[0].paras.reduce((h, t, k) => h + (k ? HC.gap : 0) + boldLines(t, tw).length * HC.lh, 0);
|
||||
const factsH = (tw) => facts.reduce((h, [, value], k) => h + (k ? HC.gap : 0) + HC.keyLh + wrapPx(value, HC.fs, tw).length * HC.lh, 0);
|
||||
// Column order (2026-10-02): Jesus' strategy | the facts | what the series is.
|
||||
const cols = [
|
||||
[{ head: 'Jesus’ Strategy to Feed the 5000', paras: strategyParas() }],
|
||||
facts.map(([key, value]) => ({ key, lines: wrapPx(value, HC.fs, TW) })),
|
||||
null, // the facts column, wrapped below at the centre's measure
|
||||
[{ head: POSTER_TEXT.sideBoxHeading || 'What is a UPG100 List?', paras: [
|
||||
(POSTER_TEXT.sideBoxBody
|
||||
|| 'This map is one of a {approxLists}-map set called the {seriesName} — a large, overwhelming target in the thousands, organised into clear groupings and displayed on maps and prayer lists.')
|
||||
|
|
@ -891,9 +900,22 @@ function headColumnsBand(top, facts, approxLists) {
|
|||
...whatIsMore(),
|
||||
] }],
|
||||
];
|
||||
// The split: equal thirds first, then centre shares on a 1% step ordered by their distance from a third, so
|
||||
// among splits whose tallest column is equally short, the one nearest equal widths is found first and kept.
|
||||
const shares = Array.from({ length: 21 }, (_, k) => 0.26 + k * 0.01).sort((a, b) => Math.abs(a - 1 / 3) - Math.abs(b - 1 / 3));
|
||||
let best = null;
|
||||
for (const cwC of [1 / 3, ...shares].map((f) => AVAIL * f)) {
|
||||
const cwS = (AVAIL - cwC) / 2;
|
||||
const tall = Math.max(proseH(cols[0], measure(cwS)), factsH(measure(cwC)), proseH(cols[2], measure(cwS)));
|
||||
if (!best || tall < best.tall - 0.5) best = { cwC, cwS, tall };
|
||||
}
|
||||
const widths = [best.cwS, best.cwC, best.cwS];
|
||||
const lefts = [GRID_L, GRID_L + best.cwS + HC.gut, GRID_L + best.cwS + HC.gut + best.cwC + HC.gut];
|
||||
cols[1] = facts.map(([key, value]) => ({ key, lines: wrapPx(value, HC.fs, measure(best.cwC)) }));
|
||||
let frags = '', bottom = top;
|
||||
cols.forEach((col, i) => {
|
||||
const centre = GRID_L + i * (CW + HC.gut) + CW / 2;
|
||||
const CW = widths[i], TW = measure(CW);
|
||||
const centre = lefts[i] + CW / 2;
|
||||
if (col[0] && col[0].key !== undefined) { // the facts column: a bold key above each value
|
||||
const blockW = Math.min(TW, Math.max(...col.map((r) => Math.max(keyW(r.key), ...r.lines.map((ln) => textW(ln, HC.fs))))));
|
||||
const x = (centre - blockW * HC_FALLBACK / 2).toFixed(1);
|
||||
|
|
@ -922,7 +944,7 @@ function headColumnsBand(top, facts, approxLists) {
|
|||
}
|
||||
});
|
||||
for (let i = 1; i < 3; i++) {
|
||||
const rx = (GRID_L + i * (CW + HC.gut) - HC.gut / 2).toFixed(1);
|
||||
const rx = (lefts[i] - HC.gut / 2).toFixed(1);
|
||||
frags += `<line x1="${rx}" y1="${top - 14}" x2="${rx}" y2="${bottom - 12}" class="hcolrule"/>`;
|
||||
}
|
||||
return { frags, bottom };
|
||||
|
|
@ -2368,7 +2390,7 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
let cy0 = bodyTop;
|
||||
strategyParas().forEach((text, k) => {
|
||||
if (k) cy0 += 5;
|
||||
cy0 = paraInto(push, text, { x: b1x[0], y: cy0, w: B1_TW, fs: B1_FS, lh: B1_LH, cls: 'hsrc2' });
|
||||
cy0 = paraInto(push, String(text).replace(/\*\*/g, ''), { x: b1x[0], y: cy0, w: B1_TW, fs: B1_FS, lh: B1_LH, cls: 'hsrc2' });
|
||||
});
|
||||
|
||||
let cy1 = bodyTop;
|
||||
|
|
@ -2379,9 +2401,11 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
|| 'This map is one of a {approxLists}-map set called the {seriesName} — a large, overwhelming target in the thousands, organised into clear groupings and displayed on maps and prayer lists.')
|
||||
.replace('{approxLists}', String(approxLists))
|
||||
.replace('{seriesName}', SERIES_NAME);
|
||||
// A **...** bold marker is the band's (boldLines); this classic header prints plain, so the markers are dropped.
|
||||
const plain = (t) => String(t).replace(/\*\*/g, '');
|
||||
[whatIs, ...whatIsMore()].forEach((text, k) => {
|
||||
if (k) cy1 += 5;
|
||||
cy1 = paraInto(push, text, { x: b1x[1], y: cy1, w: B1_TW, fs: B1_FS, lh: B1_LH, cls: 'hsrc2' });
|
||||
cy1 = paraInto(push, plain(text), { x: b1x[1], y: cy1, w: B1_TW, fs: B1_FS, lh: B1_LH, cls: 'hsrc2' });
|
||||
});
|
||||
|
||||
const b1Bot = Math.max(cy0, cy1);
|
||||
|
|
|
|||
Loading…
Reference in a new issue