diff --git a/plugin-designs/wall-of-people-groups/src/blessPoster.js b/plugin-designs/wall-of-people-groups/src/blessPoster.js index 894cb439b..b9a7402cb 100644 --- a/plugin-designs/wall-of-people-groups/src/blessPoster.js +++ b/plugin-designs/wall-of-people-groups/src/blessPoster.js @@ -41,9 +41,9 @@ export function planPoster(presetId, n) { const b = P.border const W = P.sheetW - 2 * b // Top of sheet 1: the title + DOXA line, then the prayer-themes table. Bottom of the last sheet: the - // priestly blessing, the priesthood of believers and the closing line. All scale with the width. + // priestly blessing, the journey from Abraham to the church and the closing line. All scale with the width. const headH = W * 0.075, tableH = W * 0.17 - const bannerH = headH + tableH, footerH = W * 0.16 + const bannerH = headH + tableH, footerH = W * 0.2 // Each sheet's content band, in the sheet's own coordinates: y0..y1. const bands = [] for (let s = 0; s < P.sheets; s++) { @@ -81,6 +81,8 @@ export function planPoster(presetId, n) { // The closing line under the footer (agent-written); translated via texts.labels.closing when available. const CLOSING_EN = 'Jesus Christ is the cornerstone, and we are living stones. These people groups are the reward of his suffering. Take up our priestly calling: bless them in his name, and join the global family in prayer.' +// The journey's station names (agent-written); translated via texts.labels.journey_ when available. +const JOURNEY_EN = { called: 'Called', promised: 'Promised', redeemed: 'Redeemed', cornerstone: 'The cornerstone', children: 'Children of Abraham', priesthood: 'A royal priesthood', sent: 'Sent to the lost' } const THEME_EN = { treasure: 'Treasure', mountain: 'Mountain', sea: 'Sea' } // Three simple line icons for the prayer themes (stroke = currentColor). const ICONS = { @@ -111,16 +113,19 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts = const g = groups[c.i], v = blessingFor(c.i) const img = g.imageUrl ? `` : '
' // Below MIN_READABLE_PT the verse would print as grey noise: that size shows name + photo only. - const verse = plan.text.readable ? `
${esc(v.text)}
${esc(v.ref)}
` : '' + const verse = plan.text.readable ? `
${esc(v.text)}
${esc(v.ref)}
` : '' return `
` - + `
${esc(g.name || '')}
${img}${verse}
` + + `
${esc(g.name || '')}
${img}${verse}` } const pages = plan.bands.map((bd) => { const s = bd.s, last = s === plan.bands.length - 1 const glue = s > 0 ? `
${esc(glueHere)} ▲ ${s} / ${P.sheets}
` : '' // THE STORY, top to bottom: in Jesus' name → three prayer themes (OT + NT) → every people group → - // the priestly blessing → the priesthood of believers → the closing line, at the foundation. - const cell = (v) => `

${esc(v.text)}

${esc(v.ref)}

` + // the priestly blessing → the calling to bless, Abraham to the church with Christ at its centre → + // the closing line, at the foundation. Each station's text takes dir="auto": the grid runs right to left + // in an RTL locale, while an English verse inside it keeps its punctuation at the end. The same holds for + // every scripture, name and reference on the sheet (cards, chart, blessing, closing). + const cell = (v) => `

${esc(v.text)}

${esc(v.ref)}

` const table = `
` + `
${esc(lab('themesLabel', 'Prayer themes'))}
` + THEMES.map((t) => `
${ICONS[t.id]}
${esc(lab('theme_' + t.id, THEME_EN[t.id]))}
`).join('') @@ -136,9 +141,9 @@ export function buildBlessPosterHtml(groups, presetId = DEFAULT_PRESET, texts = + `
${table}
` : '' const foot = bd.footerY != null ? `
` - + `
${POSTER.blessing.lines.map((l) => `

${esc(l)}

`).join('')}

${esc(POSTER.blessing.ref)}

` - + `
${POSTER.priesthood.map((v) => `

${esc(v.text)} ${esc(v.ref)}

`).join('')}
` - + `

${esc(lab('closing', CLOSING_EN))}

` + + `
${POSTER.blessing.lines.map((l) => `

${esc(l)}

`).join('')}

${esc(POSTER.blessing.ref)}

` + + `
${POSTER.journey.map((v) => `

${esc(lab('journey_' + v.id, JOURNEY_EN[v.id]))}

${esc(v.text)}

${esc(v.ref)}

`).join('')}
` + + `

${esc(lab('closing', CLOSING_EN))}

` + `
` : '' const trim = !last && P.sheets > 1 ? '
' : '' return `
${glue}${head}${plan.cards.filter((c) => c.s === s).map(card).join('')}${foot}${trim}` @@ -166,15 +171,18 @@ body{font-family:Georgia,'Times New Roman',serif;color:#1c1c1c;-webkit-print-col .ftr{position:absolute;left:${f(b)};width:${f(W)};display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;border-top:.03in solid #1c1c1c} .bless p{margin:0;font-size:${f(W * 0.0145)};line-height:1.25;font-weight:600} .bless .br{font-size:${f(W * 0.0075)};font-weight:400;color:#666;margin-top:${f(W * 0.002)}} -.priest{margin-top:${f(W * 0.008)};max-width:88%}.priest p{margin:${f(W * 0.002)} 0;font-size:${f(W * 0.0068)};font-style:italic;line-height:1.3} -.priest span{font-style:normal;color:#666;font-size:.8em} +.jny{display:grid;grid-template-columns:repeat(7,1fr);gap:${f(W * 0.006)};margin-top:${f(W * 0.01)};width:100%;align-items:stretch} +.st{display:flex;flex-direction:column;justify-content:center;padding:${f(W * 0.003)} ${f(W * 0.004)};border-top:.02in solid #c9b58a} +.st .sn{margin:0 0 ${f(W * 0.002)};font:700 ${f(W * 0.0062)} Georgia,serif;letter-spacing:.05em;text-transform:uppercase;color:#6b4f1d} +.st .sv{margin:0;font-size:${f(W * 0.0056)};font-style:italic;line-height:1.3} +.st .sr{margin:${f(W * 0.002)} 0 0;font-size:${f(W * 0.0048)};color:#666} +.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 .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} -.ftr{position:absolute;left:${f(b)};width:${f(W)};display:flex;align-items:center;justify-content:center;text-align:center} .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}` diff --git a/plugin-designs/wall-of-people-groups/src/blessings.js b/plugin-designs/wall-of-people-groups/src/blessings.js index 618454c07..687113479 100644 --- a/plugin-designs/wall-of-people-groups/src/blessings.js +++ b/plugin-designs/wall-of-people-groups/src/blessings.js @@ -73,11 +73,18 @@ export const POSTER = { withName('{Name} lift up his face toward you, and give you peace.'), ], }, - // The second footer: the priesthood of believers. - priesthood: [ - { ref: '1 Peter 2:5', text: 'You also as living stones are built up as a spiritual house, to be a holy priesthood, to offer up spiritual sacrifices, acceptable to God through Jesus Christ.' }, - { ref: '1 Peter 2:9', text: 'But you are a chosen race, a royal priesthood, a holy nation, a people for God’s own possession, that you may proclaim the excellence of him who called you out of darkness into his marvelous light.' }, - ], + // The second footer: the calling to bless the nations as a journey, Abraham to the church, with Christ + // the cornerstone at its centre (the `centre` station). Whole sentences of the WEB, joined by " … " where + // a station quotes two of them; a verse's own closing speech mark is dropped (as on the cards). + journey: [ + { id: 'called', ref: 'Genesis 12:2–3', text: 'I will make of you a great nation. I will bless you and make your name great. You will be a blessing. … All the families of the earth will be blessed through you.' }, + { id: 'promised', ref: 'Genesis 15:5–6', text: '“Look now toward the sky, and count the stars, if you are able to count them.” He said to Abram, “So your offspring will be.” He believed in {NAME}, who credited it to him for righteousness.' }, + { id: 'redeemed', ref: 'Galatians 3:13–14', text: 'Christ redeemed us from the curse of the law, having become a curse for us. … that the blessing of Abraham might come on the Gentiles through Christ Jesus, that we might receive the promise of the Spirit through faith.' }, + { id: 'cornerstone', centre: true, ref: '1 Peter 2:4–5', text: 'Come to him, a living stone, rejected indeed by men, but chosen by God, precious. You also as living stones are built up as a spiritual house, to be a holy priesthood, to offer up spiritual sacrifices, acceptable to God through Jesus Christ.' }, + { id: 'children', ref: 'Galatians 3:7, 29', text: 'Know therefore that those who are of faith are children of Abraham. … If you are Christ’s, then you are Abraham’s offspring and heirs according to promise.' }, + { id: 'priesthood', ref: '1 Peter 2:9', text: 'But you are a chosen race, a royal priesthood, a holy nation, a people for God’s own possession, that you may proclaim the excellence of him who called you out of darkness into his marvelous light.' }, + { id: 'sent', ref: 'Luke 19:10', text: 'For the Son of Man came to seek and to save that which was lost.' }, + ].map((v) => ({ ...v, text: withName(v.text) })), // Kept for reference: the earlier footer candidates (sheet rows 99), superseded by the Driver's choice. subBanner: { ref: 'Proverbs 18:21', text: 'Death and life are in the power of the tongue; those who love it will eat its fruit.' }, footer: { ref: 'Proverbs 11:11', text: 'By the blessing of the upright, the city is exalted, but it is overthrown by the mouth of the wicked.' }, diff --git a/plugin-designs/wall-of-people-groups/src/wallCanvas.js b/plugin-designs/wall-of-people-groups/src/wallCanvas.js index 663a7e99f..dc9139538 100644 --- a/plugin-designs/wall-of-people-groups/src/wallCanvas.js +++ b/plugin-designs/wall-of-people-groups/src/wallCanvas.js @@ -18,6 +18,10 @@ import { wallD3 } from './wallD3.js' import { wallGrid } from './wallLod.js' import { blessingFor, wrapText } from './blessings.js' +// 'rtl' when a string's first strong character is Hebrew/Arabic-script, else 'ltr' (canvas has no dir="auto"). +const RTL_FIRST = /^[^A-Za-z\u00C0-\u024F\u0370-\u052F\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]*[\u0590-\u08FF\uFB1D-\uFDFF\uFE70-\uFEFF]/ +export const textDir = (s) => (RTL_FIRST.test(String(s || '')) ? 'rtl' : 'ltr') + const ICON = { heart: 'M12 20.5s-7.2-4.4-9.2-8.8C1.3 8.3 3.2 4.6 6.7 4.6c2 0 3.5 1.1 4.3 2.4.8-1.3 2.3-2.4 4.3-2.4 3.5 0 5.4 3.7 3.9 7.1-2 4.4-9.2 8.8-9.2 8.8z', comment: 'M4 4.5h16v11.5H9.5L4 20.5z', @@ -170,6 +174,9 @@ export class WallCanvas { ctx.fillStyle = groupLink(g) ? '#0095f6' : '#262626' const name = g.name || g.post_title || this.o.labels('wall.unknownGroup') const lines = ctx.measureText(name).width > w * 0.95 ? wrapText(name, Math.max(6, Math.floor(name.length / 2) + 2), 2) : [name] + // Each data string is drawn in its OWN direction, not the page's: an English verse on an RTL page + // keeps its full stop at the end (canvas direction otherwise inherits the page's rtl). + ctx.direction = textDir(name) lines.forEach((ln, k) => ctx.fillText(ln, x + w / 2, y + h * 0.06 + k * nameSize * 1.1, w * 0.96)) if (this.mode === 'bless') { // The verse under the photo (blessings.js, WEB, verbatim), then its reference. @@ -177,14 +184,17 @@ export class WallCanvas { const b = blessingFor(i), vl = wrapText(b.text, per, 4) const vy = imgY + imgH + vSize * 1.5 ctx.font = `italic ${vSize}px Georgia, "Times New Roman", serif`; ctx.fillStyle = '#262626' + ctx.direction = textDir(b.text) vl.forEach((ln, k) => ctx.fillText(ln, x + w / 2, vy + k * vSize * 1.25, w * 0.96)) ctx.font = `${vSize * 0.82}px -apple-system, "Segoe UI", Arial, sans-serif`; ctx.fillStyle = '#8e8e8e' + ctx.direction = textDir(b.ref) ctx.fillText(b.ref, x + w / 2, vy + vl.length * vSize * 1.25 + vSize * 0.4, w * 0.96) return } // SOCIAL: status, then heart / comment / share, the prayer count and "view prayers". ctx.font = `${Math.max(3, w * 0.055)}px -apple-system, "Segoe UI", Arial, sans-serif`; ctx.fillStyle = '#8e8e8e' - if (g.status) ctx.fillText(g.status, x + w / 2, y + h * 0.14, w * 0.96) + if (g.status) { ctx.direction = textDir(g.status); ctx.fillText(g.status, x + w / 2, y + h * 0.14, w * 0.96) } + ctx.direction = 'inherit' // the translated counters below follow the page const L = this.socialLayout(R) const count = this.o.prayCount(g) ctx.lineWidth = 1.8; ctx.lineJoin = 'round'; ctx.lineCap = 'round'