This commit is contained in:
commit
8653147cfc
1 changed files with 65 additions and 5 deletions
|
|
@ -255,7 +255,7 @@ const FORMATS = {
|
|||
// and those already derive from MAP_BREATH/HEADER rather than hardcoded offsets.
|
||||
// Serves China (154 groups) and is the sheet the shelved granular 4-column table
|
||||
// (F93) was waiting on — build it once, both are served.
|
||||
'3x4': { w: 1500, h: 2000, gutter: 330, page: '3in 4in' },
|
||||
'3x4': { w: 1500, h: 2000, gutter: 330, page: '3in 4in', pageWidthIn: 3 },
|
||||
// Square: a 3ft x 3ft sheet. The map loses height relative to 3x4, so the
|
||||
// gutters widen slightly — with fewer label rows per column the names can take
|
||||
// more width, and the map stays close to square rather than becoming a letterbox.
|
||||
|
|
@ -273,7 +273,23 @@ const FORMATS = {
|
|||
// be line wrapped — like 1 and 6 and 34, 49, 44." At 330 the name column is
|
||||
// 222px and only "Vellaiyan Kuppam Padaiyaci" (265px) still wraps, which is a
|
||||
// name that genuinely overruns any sane column.
|
||||
'3x3': { w: 1500, h: 1500, gutter: 330, page: '3in 3in' },
|
||||
'3x3': { w: 1500, h: 1500, gutter: 330, page: '3in 3in', pageWidthIn: 3 },
|
||||
// LETTER PORTRAIT — the same sheet, sized for paper a normal printer can take.
|
||||
// Brief: "keep the layout of the poster the same with the same text, just
|
||||
// formatted for a vertical piece of paper."
|
||||
//
|
||||
// WIDTH STAYS 1500 for the reason the 3x4 note gives above: every width-derived
|
||||
// measurement (the 330 gutter, the name columns, the header budget) was calibrated
|
||||
// at 1500 and carries over UNCHANGED. Height is the only variable: 8.5x11 at
|
||||
// width 1500 -> 1500 * (11/8.5) = 1941. That lands between 3x3 (1500) and 3x4
|
||||
// (2000), so the map keeps roughly the 3x3 proportions with ~29% more height for
|
||||
// the label columns — the sheet gets taller, nothing gets re-tuned.
|
||||
//
|
||||
// `page` is what the generated HTML puts in @page{size:...}. Naming the real
|
||||
// paper size (rather than an inch pair) lets the browser's own Letter preset
|
||||
// match it, which is what makes an ordinary printer produce a full-bleed-free,
|
||||
// correctly-scaled sheet instead of "fit to page" guesswork.
|
||||
letter: { w: 1500, h: 1941, gutter: 330, page: 'letter portrait', pageWidthIn: 8.5 },
|
||||
};
|
||||
const FMT = FORMATS[process.env.FORMAT || '3x4'] || FORMATS['3x4'];
|
||||
// Stamped at generation time. Overridable so a rebuild can reproduce an existing
|
||||
|
|
@ -2055,9 +2071,34 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
const credit = (x, y, anchor, txt) =>
|
||||
`<text x="${x.toFixed(1)}" y="${y.toFixed(1)}" text-anchor="${anchor}" class="attrib-halo">${txt}</text>`
|
||||
+ `<text x="${x.toFixed(1)}" y="${y.toFixed(1)}" text-anchor="${anchor}" class="attrib">${txt}</text>`;
|
||||
// ATTRIBUTION CORRECTED 2026-09-23. This said "(c) Mapbox (c) OpenStreetMap",
|
||||
// which was inherited boilerplate and simply not true of this sheet: the header
|
||||
// of this file states NO Mapbox, NO token, NO external fetch at render time, and
|
||||
// the basemap is drawn from GeoJSON bundled in the repo. Crediting a vendor that
|
||||
// contributes nothing implies a dependency that does not exist \u2014 the kind of line
|
||||
// that makes a later reader add a token "to comply".
|
||||
// The real source is geoBoundaries (the adm0/adm1 files carry its shapeGroup /
|
||||
// shapeName fields), CC BY 4.0, which REQUIRES attribution \u2014 so the credit stays,
|
||||
// it just names who actually earned it. OpenStreetMap is named only when OSM
|
||||
// street tiles are actually fetched (BASEMAP=1, city sheets).
|
||||
const basemapCredit = BASEMAP
|
||||
? '\u00a9 geoBoundaries \u00b7 \u00a9 OpenStreetMap'
|
||||
: '\u00a9 geoBoundaries (CC BY 4.0)';
|
||||
// \u2605 PRIVACY LINE. The people-group coordinates arrive already generalised from
|
||||
// the data publisher \u2014 they are deliberately NOT precise locations. A printed
|
||||
// sheet travels far beyond the person who generated it, so it has to carry that
|
||||
// fact itself rather than rely on the reader knowing it.
|
||||
// Worded as provenance ("generalised at the source"), not as a claim about
|
||||
// processing we perform here, because we do no rounding of our own.
|
||||
const privacyNote = 'Marker positions are approximate \u00b7 people-group locations are generalised at the source for privacy';
|
||||
// The privacy line gets its OWN row, 13px above the credits. Sharing the credit
|
||||
// row put it at 516-984px against a stamp ending at 543 \u2014 a measured collision.
|
||||
// It is also a different KIND of text: the other two are ~200px provenance
|
||||
// stamps, this is a sentence a reader must actually READ, so it needs the room.
|
||||
const mapCredits =
|
||||
credit(FRAME_X + 8, FRAME_Y + FRAME_H - 7, 'start', `Generated ${POSTER_DATE} \u00b7 data from doxa.life`)
|
||||
+ credit(FRAME_X + FRAME_W - 8, FRAME_Y + FRAME_H - 7, 'end', '\u00a9 Mapbox \u00a9 OpenStreetMap');
|
||||
credit(FRAME_X + FRAME_W / 2, FRAME_Y + FRAME_H - 20, 'middle', privacyNote)
|
||||
+ credit(FRAME_X + 8, FRAME_Y + FRAME_H - 7, 'start', `Generated ${POSTER_DATE} \u00b7 data from doxa.life`)
|
||||
+ credit(FRAME_X + FRAME_W - 8, FRAME_Y + FRAME_H - 7, 'end', basemapCredit);
|
||||
|
||||
// FIT THE TITLE TO THE SHEET. Region names vary a lot in length ("West India"
|
||||
// vs "Northeast India + Bangladesh + Bhutan"), and the qualifier adds a fixed
|
||||
|
|
@ -2443,7 +2484,26 @@ async function renderPoster(zone, pins, zoneFC) {
|
|||
/* Haloed so the credits stay legible wherever they land on the map. */
|
||||
.attrib-halo{font-family:'Segoe UI',system-ui,sans-serif;font-size:9px;fill:none;
|
||||
stroke:#EEF3F4;stroke-width:2.6;stroke-linejoin:round;letter-spacing:.02em}
|
||||
@media print{@page{size:${FMT.page};margin:0}html,body{background:#fff}.stage{margin:0}}
|
||||
/* PRINT. The stage is sized in CSS PIXELS (1500 wide) while @page declares a
|
||||
PHYSICAL size, so a browser maps 1500px -> 96dpi -> 15.6in and TILES that
|
||||
across the sheet: the 3x3 sheet prints as FOUR pages, not one. That went
|
||||
unnoticed because these posters were built to be SCREENSHOT by shoot.mjs at
|
||||
deviceScaleFactor 4, never printed from the browser.
|
||||
Scaling the stage to the page's own width makes one sheet = one page on any
|
||||
format, which is what a home printer needs. transform-origin top-left so the
|
||||
scale collapses toward the page corner rather than the element centre. */
|
||||
@media print{
|
||||
@page{size:${FMT.page};margin:0}
|
||||
html,body{background:#fff}
|
||||
/* A FIXED ratio, computed at generate time — not a vw calc. a vw/px ratio does not
|
||||
resolve against the PAGE box in print, so the calc silently evaluated to a
|
||||
no-op and the sheet still tiled. The page width in CSS px is the paper width
|
||||
in inches x 96; the scale is that over the stage width. */
|
||||
.stage{margin:0;transform:scale(${(FMT.pageWidthIn * 96 / FMT.w).toFixed(6)});transform-origin:top left}
|
||||
/* The scaled stage still reserves its ORIGINAL box, which pushes a blank second
|
||||
page. Collapse the flow height to the scaled height. */
|
||||
html,body{width:${(FMT.pageWidthIn * 96).toFixed(0)}px;height:${(FMT.pageWidthIn * 96 * FMT.h / FMT.w).toFixed(0)}px;overflow:hidden}
|
||||
}
|
||||
</style></head><body><div class="stage">${svg}</div></body></html>`;
|
||||
return { html, title, octiOK, crossings, diagFrac };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue