The map sheets are generated once, in English, so in an Arabic or Chinese packet page 1's map labelled every country in Latin capitals, and its privacy line was English. The labels are now replaced as the sheet is lifted into the packet. They are recognised by the generator's own rule rather than a list: generate.mjs draws each label as esc(ISO3_NAME[iso3].toUpperCase()) from the zone data's ISO3_NAME table, so inverting that table under that transform names the country behind every label it can draw. The inverse is unambiguous (162 labels, no two countries sharing one), so a label is known exactly or left as drawn. The map's privacy line is word for word the packet's privacy_note, so it takes this language's. A width guard: the generator placed every label and then dropped any that would collide, measuring the English name, so a translation is fitted into that same box - estimated wider, it shrinks in proportion and cannot reach a neighbour the English name did not. On the Sudan poster, six Arabic names shrink (the longest, Saudi Arabia, from 15px to 10px); none of the narrower Chinese ones do. Capitals' tracking is dropped for scripts without capitals, where it breaks Arabic joining. English is untouched: its relabeller is null, and the lifted sheet is byte-identical with and without it. No sheet or poster is regenerated - the change is in the lift, so the tracked 3x4 posters page 1 now uses are relabelled as they are. Both print paths supply the relabeller: the UPG 100 profile from its own ISO3_NAME and the library's country-name resolver, the offline generator from the same table and the injected ISO-3 to ISO-2 table. check-map-labels (gates:pre), on the tracked Sudan poster: English byte-identical; in ar and zh no known English name left, halo matching fill, privacy localized; the guard fires for Arabic, not Chinese, and grows nothing; Arabic tracking off, Spanish kept; an unknown label left alone. Mutation-tested five ways, each caught.
93 lines
6.1 KiB
JavaScript
93 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* check-map-labels — the map on page 1 names its countries in the packet's language.
|
|
*
|
|
* Map sheets are generated once, in English; the packet relabels their country names (and the map's
|
|
* privacy line) at print time (library/packet/mapLabels.js + packet-render.js liftMap). Checked on a
|
|
* real tracked poster, the artwork page 1 actually lifts:
|
|
*
|
|
* (1) ENGLISH IS UNTOUCHED — lifting with and without the relabeller is byte-identical in English,
|
|
* and the relabeller for English is null.
|
|
* (2) KNOWN NAMES ARE ALL REPLACED — in ar and zh no known English label survives, every halo still
|
|
* matches its fill, and the privacy line takes the language's own sentence.
|
|
* (3) THE WIDTH GUARD FITS, NEVER GROWS — no label is ever set larger than the sheet's size, the guard
|
|
* FIRES for a translation wider than its English (ar), and does NOT for shorter ones (zh).
|
|
* (4) TRACKING OFF WHERE IT BREAKS SCRIPT — Arabic labels drop letter-spacing; Spanish capitals keep it.
|
|
* (5) UNKNOWN LABELS ARE LEFT AS DRAWN — never guessed.
|
|
*
|
|
* Exit 0 pass · 1 fail · 75 NOT MEASURED.
|
|
*/
|
|
import { readFileSync, existsSync } from 'node:fs'
|
|
import { resolve, dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
const REPO = resolve(HERE, '..')
|
|
const POSTER = join(REPO, 'data/posters/sudan.html')
|
|
if (!existsSync(POSTER)) {
|
|
console.error('MAP-LABELS: NOT MEASURED (exit 75) — no tracked poster at data/posters/sudan.html to lift')
|
|
process.exit(75)
|
|
}
|
|
const { createPacketRenderer } = await import(join(REPO, 'library/packet/packet-render.js'))
|
|
const { makeCountryLabelFor } = await import(join(REPO, 'library/packet/mapLabels.js'))
|
|
const { ISO3_NAME } = await import(join(REPO, 'plugin-designs/my-upg-100-list/data/territory-counts.js'))
|
|
const strings = JSON.parse(readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8'))
|
|
const a2 = JSON.parse(readFileSync(join(REPO, 'library/data/iso3-to-alpha2.json'), 'utf8')).map
|
|
const countryName = (iso3, lc) => { const t = a2[iso3]; try { return t ? new Intl.DisplayNames([lc], { type: 'region' }).of(t) : '' } catch { return '' } }
|
|
// A label the generator cannot draw, planted to prove unknowns are left alone.
|
|
const poster = readFileSync(POSTER, 'utf8').replace('</svg>', '<text x="1" y="1" text-anchor="middle" class="ctry-name">ATLANTIS</text></svg>')
|
|
|
|
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
const KNOWN = new Set(Object.values(ISO3_NAME).map((n) => esc(String(n).toUpperCase())))
|
|
const labels = (html) => [...html.matchAll(/<text([^>]*class="(ctry-name(?:-halo|-own)?)"[^>]*)>([^<]*)<\/text>/g)]
|
|
.map((m) => ({ attrs: m[1], cls: m[2], text: m[3], px: Number((m[1].match(/font-size:([\d.]+)px/) || [])[1]) || null }))
|
|
const base = Number((poster.match(/\.ctry-name\{[^}]*font-size:([\d.]+)px/) || [])[1]) || 15
|
|
const fails = [], notes = []
|
|
const ok = (c, msg, note) => { if (!c) fails.push(msg); else if (note) notes.push(` ok ${note}`) }
|
|
const lift = (lc) => createPacketRenderer({ strings, lang: lc }).liftMap(poster, { labelFor: makeCountryLabelFor({ iso3Name: ISO3_NAME, countryName, lang: lc }) })
|
|
|
|
// (1)
|
|
{
|
|
const R = createPacketRenderer({ strings, lang: 'en' })
|
|
ok(makeCountryLabelFor({ iso3Name: ISO3_NAME, countryName, lang: 'en' }) === null, 'the English relabeller is not null')
|
|
ok(R.liftMap(poster) === lift('en'), 'lifting in English CHANGED the sheet', 'English: the lifted sheet is byte-identical with and without the relabeller')
|
|
}
|
|
const out = { ar: lift('ar'), zh: lift('zh'), es: lift('es') }
|
|
// (2)
|
|
for (const lc of ['ar', 'zh']) {
|
|
const L = labels(out[lc])
|
|
const left = L.filter((l) => KNOWN.has(l.text)).map((l) => l.text)
|
|
const halos = L.filter((l) => l.cls === 'ctry-name-halo'), fills = L.filter((l) => l.cls !== 'ctry-name-halo')
|
|
ok(!left.length, `${lc}: English country names survived: ${[...new Set(left)].join(', ')}`)
|
|
ok(halos.length && halos.every((h, i) => fills[i] && fills[i].text === h.text), `${lc}: a halo no longer matches its fill — the outline and the name would disagree`)
|
|
ok(out[lc].includes(esc(strings[lc].privacy_note)), `${lc}: the map's privacy line was not localized`)
|
|
if (!left.length) notes.push(` ok ${lc}: ${halos.length} country labels relabelled, halo = fill, privacy line localized`)
|
|
}
|
|
// (3)
|
|
{
|
|
const all = [...labels(out.ar), ...labels(out.zh), ...labels(out.es)]
|
|
ok(all.every((l) => l.px === null || l.px <= base + 1e-9), 'a relabelled name was set LARGER than the sheet\'s label size')
|
|
const arShrunk = labels(out.ar).filter((l) => l.px !== null && l.px < base).length
|
|
const zhShrunk = labels(out.zh).filter((l) => l.px !== null && l.px < base).length
|
|
ok(arShrunk > 0, 'the width guard never fired in Arabic, where the Saudi Arabia label is far wider than its English')
|
|
ok(zhShrunk === 0, `the width guard shrank ${zhShrunk} Chinese label(s) that are narrower than their English`,
|
|
`width guard: shrinks ${arShrunk} Arabic labels to fit their English boxes, none of the narrower Chinese, and grows nothing`)
|
|
}
|
|
// (4)
|
|
{
|
|
const arFill = labels(out.ar).filter((l) => l.cls !== 'ctry-name-halo' && !KNOWN.has(l.text) && l.text !== 'ATLANTIS')
|
|
const esFill = labels(out.es).filter((l) => l.cls !== 'ctry-name-halo' && l.text !== 'ATLANTIS')
|
|
ok(arFill.length && arFill.every((l) => /letter-spacing:0/.test(l.attrs)), 'Arabic labels keep the capitals\' tracking, which breaks letter joining')
|
|
ok(esFill.every((l) => !/letter-spacing:0/.test(l.attrs)), 'Spanish capitals lost their tracking',
|
|
'tracking is dropped for Arabic and kept for Spanish capitals')
|
|
}
|
|
// (5)
|
|
ok(labels(out.ar).filter((l) => l.text === 'ATLANTIS').length === 1, 'an unknown label was altered or removed', 'an unknown label is left exactly as drawn')
|
|
|
|
for (const n of notes) console.log(n)
|
|
if (fails.length) {
|
|
console.error('MAP-LABELS: FAIL')
|
|
for (const f of fails) console.error(` ✗ ${f}`)
|
|
process.exit(1)
|
|
}
|
|
console.log('MAP-LABELS: PASS — the map names its countries in the packet\'s language, fits each name to its English box, and leaves English untouched.')
|