-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrender-theme.ts
More file actions
97 lines (92 loc) · 3.65 KB
/
Copy pathrender-theme.ts
File metadata and controls
97 lines (92 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import type { Palette } from '../types/interfaces.js';
/** `0xfff2c8` → `"#fff2c8"`. */
export function cssHex(c: number): string {
return '#' + c.toString(16).padStart(6, '0');
}
/** Per-channel linear mix of two 0xRRGGBB colors; t clamped to [0,1]. */
export function blend(a: number, b: number, t: number): number {
const k = Math.min(1, Math.max(0, t));
const ch = (sa: number, sb: number) => Math.round(sa + (sb - sa) * k);
const ar = (a >> 16) & 0xff, ag = (a >> 8) & 0xff, ab = a & 0xff;
const br = (b >> 16) & 0xff, bg = (b >> 8) & 0xff, bb = b & 0xff;
return (ch(ar, br) << 16) | (ch(ag, bg) << 8) | ch(ab, bb);
}
/** Scale each channel toward black by factor f (0 = unchanged, 1 = black). */
export function darken(c: number, f: number): number {
const k = Math.min(1, Math.max(0, 1 - f));
const r = Math.round(((c >> 16) & 0xff) * k);
const g = Math.round(((c >> 8) & 0xff) * k);
const b = Math.round((c & 0xff) * k);
return (r << 16) | (g << 8) | b;
}
/**
* Rendering treatments derived from a Palette. Everything the 6-color
* palette cannot express: casing, shadows, seam/shore strokes, washes.
* All slots are plain data so callers can override any subset via
* `SvgOptions.theme`.
*/
export interface RenderTheme {
paper: string;
water: string | null; // null → water passes are skipped
waterEdge: string | null; // shore stroke; null when water is null
fieldFill: string; // paper blended 18% toward green
fieldFurrow: string; // plot border + pattern hatch lines (50% opacity)
greenFill: string; // parks
treeFill: string; // vegetation symbols; darkened green
roadCasing: string;
roadCore: string;
buildingFill: string;
buildingStroke: string;
landmarkFill: string; // castle/cathedral/market; reads darker than ordinary buildings
shadowColor: string;
shadowOpacity: number;
shadowOffset: { dx: number; dy: number };
arteryWidth: number;
roadWidth: number;
casingDelta: number; // casing extends this much per side beyond core
seamStroke: number; // same-color stroke on water patches
shoreWidth: number;
/** Symbol-library material tokens (batch001 authoring classes). */
smInk: string;
smStone: string;
smTimber: string;
smVoid: string;
smCanopy1: string;
smCanopy2: string;
}
export function themeFrom(palette: Palette): RenderTheme {
const green = palette.green ?? palette.medium;
const water = palette.water ?? null;
return {
paper: cssHex(palette.paper),
water: water === null ? null : cssHex(water),
waterEdge: water === null ? null : cssHex(darken(water, 0.2)),
fieldFill: cssHex(blend(palette.paper, green, 0.18)),
fieldFurrow: cssHex(green),
greenFill: cssHex(green),
treeFill: cssHex(darken(palette.tree ?? palette.green ?? palette.medium, 0.15)),
roadCasing: cssHex(palette.medium),
roadCore: cssHex(palette.paper),
buildingFill: cssHex(palette.light),
buildingStroke: cssHex(palette.dark),
landmarkFill: cssHex(
palette.light === palette.dark
? blend(palette.light, 0xffffff, 0.45) // degenerate 2-tone palette: old formula kept landmarks distinct
: blend(palette.light, palette.dark, 0.3),
),
shadowColor: cssHex(palette.dark),
shadowOpacity: 0.18,
shadowOffset: { dx: 0.4, dy: 0.6 },
arteryWidth: 2.4,
roadWidth: 1.6,
casingDelta: 0.3,
seamStroke: 0.5,
shoreWidth: 0.6,
smInk: cssHex(palette.dark),
smStone: cssHex(palette.light),
smTimber: cssHex(blend(palette.light, palette.medium, 0.35)),
smVoid: cssHex(blend(palette.dark, palette.medium, 0.5)),
smCanopy1: cssHex(darken(green, 0.15)),
smCanopy2: cssHex(green),
};
}