@datatechsolutions/tympan-tokens 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +5 -5
- package/dist/contrast-report.md +95 -95
- package/dist/dtcg/astrlabe.dark-high.tokens.json +99 -43
- package/dist/dtcg/astrlabe.dark.tokens.json +137 -81
- package/dist/dtcg/astrlabe.light-high.tokens.json +146 -33
- package/dist/dtcg/astrlabe.light.tokens.json +211 -98
- package/dist/fonts.css +80 -80
- package/dist/index.d.ts +257 -58
- package/dist/index.js +568 -36
- package/dist/index.js.map +1 -1
- package/dist/print-themes/print-aquarela.css +7 -7
- package/dist/print-themes/print-art-nouveau.css +2 -2
- package/dist/print-themes/print-atlas-oficial.css +7 -7
- package/dist/print-themes/print-azulejo-modernista.css +2 -2
- package/dist/print-themes/print-bauhaus.css +2 -2
- package/dist/print-themes/print-blocos-coloridos.css +5 -5
- package/dist/print-themes/print-brutalista.css +8 -8
- package/dist/print-themes/print-caderno.css +14 -14
- package/dist/print-themes/print-cartao-postal.css +5 -5
- package/dist/print-themes/print-cientifico.css +3 -3
- package/dist/print-themes/print-concretismo.css +8 -8
- package/dist/print-themes/print-construtivismo.css +6 -6
- package/dist/print-themes/print-cordel.css +6 -6
- package/dist/print-themes/print-dados-br.css +12 -12
- package/dist/print-themes/print-dashboard.css +18 -18
- package/dist/print-themes/print-diagrama-modernista.css +3 -3
- package/dist/print-themes/print-divulgacao.css +3 -3
- package/dist/print-themes/print-fluxo-historico.css +3 -3
- package/dist/print-themes/print-grade-holandesa.css +2 -2
- package/dist/print-themes/print-graficos-1900.css +6 -6
- package/dist/print-themes/print-infografico-ilustrado.css +6 -6
- package/dist/print-themes/print-isotype.css +2 -2
- package/dist/print-themes/print-jornal-1959.css +5 -5
- package/dist/print-themes/print-jornal.css +6 -6
- package/dist/print-themes/print-mapa-de-metro.css +1 -1
- package/dist/print-themes/print-memphis.css +5 -5
- package/dist/print-themes/print-minimo-de-tinta.css +4 -4
- package/dist/print-themes/print-papel-recortado.css +2 -2
- package/dist/print-themes/print-papel-salmao.css +6 -6
- package/dist/print-themes/print-pictogramas.css +2 -2
- package/dist/print-themes/print-pop-art.css +2 -2
- package/dist/print-themes/print-prancheta-clara.css +5 -5
- package/dist/print-themes/print-prancheta.css +5 -5
- package/dist/print-themes/print-proporcao-modular.css +2 -2
- package/dist/print-themes/print-riso.css +4 -4
- package/dist/print-themes/print-semanario.css +13 -13
- package/dist/print-themes/print-sinalizacao.css +8 -8
- package/dist/print-themes/print-suico.css +4 -4
- package/dist/print-themes/print-tropicalia.css +3 -3
- package/dist/print-themes.css +133 -133
- package/dist/tokens.css +247 -124
- package/dist/tokens.json +124 -66
- package/dist/values.js +124 -66
- package/fonts/ibmplexsans/ibmplexsans-italic-cyrillic-400_500_600.woff2 +0 -0
- package/fonts/ibmplexsans/ibmplexsans-italic-cyrillic-ext-400_500_600.woff2 +0 -0
- package/fonts/ibmplexsans/ibmplexsans-italic-greek-400_500_600.woff2 +0 -0
- package/fonts/ibmplexsans/ibmplexsans-italic-latin-400_500_600.woff2 +0 -0
- package/fonts/ibmplexsans/ibmplexsans-italic-latin-ext-400_500_600.woff2 +0 -0
- package/fonts/ibmplexsans/ibmplexsans-italic-vietnamese-400_500_600.woff2 +0 -0
- package/fonts/manifest.json +998 -820
- package/fonts/nunitosans/nunitosans-normal-cyrillic-400_600_700_800.woff2 +0 -0
- package/fonts/nunitosans/nunitosans-normal-cyrillic-ext-400_600_700_800.woff2 +0 -0
- package/fonts/nunitosans/nunitosans-normal-latin-400_600_700_800.woff2 +0 -0
- package/fonts/nunitosans/nunitosans-normal-latin-ext-400_600_700_800.woff2 +0 -0
- package/fonts/nunitosans/nunitosans-normal-vietnamese-400_600_700_800.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-cyrillic-400_600.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-cyrillic-ext-400_600.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-greek-400_600.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-latin-400_600.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-latin-ext-400_600.woff2 +0 -0
- package/fonts/sourceserif4/sourceserif4-italic-vietnamese-400_600.woff2 +0 -0
- package/package.json +1 -1
- package/src/fonts.ts +2 -2
- package/src/index.ts +1 -0
- package/src/presets.ts +115 -16
- package/src/print-book-themes.ts +239 -0
- package/src/print-presets.ts +220 -10
- package/src/stylesheet.ts +5 -0
- package/src/theme.ts +35 -5
- /package/fonts/ibmplexsans/{ibmplexsans-italic-cyrillic.woff2 → ibmplexsans-italic-cyrillic-400.woff2} +0 -0
- /package/fonts/ibmplexsans/{ibmplexsans-italic-cyrillic-ext.woff2 → ibmplexsans-italic-cyrillic-ext-400.woff2} +0 -0
- /package/fonts/ibmplexsans/{ibmplexsans-italic-greek.woff2 → ibmplexsans-italic-greek-400.woff2} +0 -0
- /package/fonts/ibmplexsans/{ibmplexsans-italic-latin.woff2 → ibmplexsans-italic-latin-400.woff2} +0 -0
- /package/fonts/ibmplexsans/{ibmplexsans-italic-latin-ext.woff2 → ibmplexsans-italic-latin-ext-400.woff2} +0 -0
- /package/fonts/ibmplexsans/{ibmplexsans-italic-vietnamese.woff2 → ibmplexsans-italic-vietnamese-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-cyrillic.woff2 → sourceserif4-italic-cyrillic-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-cyrillic-ext.woff2 → sourceserif4-italic-cyrillic-ext-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-greek.woff2 → sourceserif4-italic-greek-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-latin.woff2 → sourceserif4-italic-latin-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-latin-ext.woff2 → sourceserif4-italic-latin-ext-400.woff2} +0 -0
- /package/fonts/sourceserif4/{sourceserif4-italic-vietnamese.woff2 → sourceserif4-italic-vietnamese-400.woff2} +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datatechsolutions/tympan-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Tympan design tokens: W3C DTCG source (colour, space, radius, type, elevation and glass, motion, z) and an OKLCH theme generator, with the tympan, fakhir, astrlabe, neutral and high-contrast presets in light and dark (plus opt-in UI themes derived from the print book styles), built to --ty-* CSS custom properties, JSON and a TypeScript export.",
|
|
5
5
|
"license": "FSL-1.1-ALv2",
|
|
6
6
|
"author": "Natalia Mesquita",
|
package/src/fonts.ts
CHANGED
|
@@ -16,8 +16,8 @@ import { SCRIPT_RULES } from './scripts.ts'
|
|
|
16
16
|
|
|
17
17
|
/** Base stacks of every preset: Source Serif 4, IBM Plex Sans, IBM Plex Mono. */
|
|
18
18
|
export const BASE_FONT_SPECS: readonly string[] = [
|
|
19
|
-
'Source Serif 4:ital,opsz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400',
|
|
20
|
-
'IBM Plex Sans:ital,wght@0,400;0,500;0,600;0,700;1,400',
|
|
19
|
+
'Source Serif 4:ital,opsz,wght@0,8..60,400;0,8..60,600;0,8..60,700;1,8..60,400;1,8..60,600',
|
|
20
|
+
'IBM Plex Sans:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600',
|
|
21
21
|
'IBM Plex Mono:ital,wght@0,400;0,500;0,600;0,700;1,400',
|
|
22
22
|
]
|
|
23
23
|
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './dtcg.ts'
|
|
|
6
6
|
export * from './stylesheet.ts'
|
|
7
7
|
export * from './flow.ts'
|
|
8
8
|
export * from './print-presets.ts'
|
|
9
|
+
export * from './print-book-themes.ts'
|
|
9
10
|
export * from './print-themes.ts'
|
|
10
11
|
export * from './print-aliases.ts'
|
|
11
12
|
export * from './fonts.ts'
|
package/src/presets.ts
CHANGED
|
@@ -110,8 +110,10 @@ export const fakhirPreset: ThemeConfig = {
|
|
|
110
110
|
/**
|
|
111
111
|
* The Astrlabe workflow engine's look (`data-ty-theme="astrlabe"`): slate
|
|
112
112
|
* neutrals, an indigo brand (indigo 500 as the accent family, one step darker
|
|
113
|
-
* where text needs 4.5:1 on light grounds),
|
|
114
|
-
*
|
|
113
|
+
* where text needs 4.5:1 on light grounds), liquid-glass surfaces (gradient
|
|
114
|
+
* fills, specular edges, a strong saturation boost at night), the rounder
|
|
115
|
+
* corners of that app (12 px controls, 20 px cards, 24 px sheets), its
|
|
116
|
+
* compact type scale and the sky, indigo and violet mark. The call to
|
|
115
117
|
* action is the indigo to purple gradient its primary buttons fill with, and
|
|
116
118
|
* text (headings included) uses the platform's own system fonts. The state
|
|
117
119
|
* contract holds on every ground: running is a cyan clearly off the indigo
|
|
@@ -142,22 +144,30 @@ export const astrlabePreset: ThemeConfig = {
|
|
|
142
144
|
'on-brand': '#ffffff',
|
|
143
145
|
bg: '#f8fafc',
|
|
144
146
|
surface: 'rgb(255 255 255 / 0.72)',
|
|
145
|
-
'surface-raised': 'rgb(255 255 255 / 0.
|
|
147
|
+
'surface-raised': 'rgb(255 255 255 / 0.86)',
|
|
146
148
|
'surface-solid': '#ffffff',
|
|
147
149
|
'surface-raised-solid': '#ffffff',
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
// The canvas plane: the app's pale lavender ground.
|
|
151
|
+
'surface-sunken': '#f2f3fa',
|
|
152
|
+
// The liquid divider: slate 400 at about a quarter.
|
|
153
|
+
line: 'rgb(148 163 184 / 0.26)',
|
|
150
154
|
'line-strong': 'rgb(100 116 139 / 0.45)',
|
|
151
155
|
ink: '#0f172a',
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
// Slate 600 for secondary text, as the app always had.
|
|
157
|
+
'ink-2': '#475569',
|
|
158
|
+
// Slate 500 fails 4.5:1 on the sunken ground by a hair; this is the
|
|
159
|
+
// nearest slate that holds AA there.
|
|
160
|
+
'ink-3': '#5b6779',
|
|
155
161
|
success: '#15803d',
|
|
156
162
|
// Cyan, deliberately off the indigo brand, for the running state.
|
|
157
163
|
info: '#0e7490',
|
|
158
164
|
warning: '#a16207',
|
|
159
165
|
danger: '#be123c',
|
|
160
|
-
neutral: '#
|
|
166
|
+
neutral: '#5b6779',
|
|
167
|
+
backdrop: 'rgb(15 23 42 / 0.4)',
|
|
168
|
+
// The three soft colour fields behind the glass: violet above, sky below.
|
|
169
|
+
'ambient-1': 'rgb(167 139 250 / 0.05)',
|
|
170
|
+
'ambient-2': 'rgb(56 189 248 / 0.03)',
|
|
161
171
|
},
|
|
162
172
|
dark: {
|
|
163
173
|
brand: '#818cf8',
|
|
@@ -165,28 +175,117 @@ export const astrlabePreset: ThemeConfig = {
|
|
|
165
175
|
'brand-strong': '#a5b4fc',
|
|
166
176
|
'brand-soft': 'rgb(99 102 241 / 0.2)',
|
|
167
177
|
'on-brand': '#1e1b4b',
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
178
|
+
// Slate 950, the app's night ground.
|
|
179
|
+
bg: '#020617',
|
|
180
|
+
surface: 'rgb(30 41 59 / 0.7)',
|
|
181
|
+
'surface-raised': 'rgb(30 41 59 / 0.78)',
|
|
171
182
|
'surface-solid': '#131c2f',
|
|
172
183
|
'surface-raised-solid': '#1b2539',
|
|
173
184
|
'surface-sunken': '#0b1222',
|
|
174
|
-
line: 'rgb(148 163 184 / 0.
|
|
185
|
+
line: 'rgb(148 163 184 / 0.24)',
|
|
175
186
|
'line-strong': 'rgb(148 163 184 / 0.36)',
|
|
176
|
-
ink: '#
|
|
187
|
+
ink: '#ffffff',
|
|
177
188
|
'ink-2': '#cbd5e1',
|
|
178
|
-
'ink-3': '#
|
|
189
|
+
'ink-3': '#94a3b8',
|
|
179
190
|
success: '#4ade80',
|
|
180
191
|
info: '#22d3ee',
|
|
181
192
|
warning: '#fbbf24',
|
|
182
193
|
danger: '#fb7185',
|
|
183
|
-
neutral: '#
|
|
194
|
+
neutral: '#94a3b8',
|
|
195
|
+
backdrop: 'rgb(0 0 0 / 0.5)',
|
|
196
|
+
'ambient-1': 'rgb(124 58 237 / 0.1)',
|
|
197
|
+
'ambient-2': 'rgb(2 132 199 / 0.08)',
|
|
184
198
|
},
|
|
185
199
|
},
|
|
186
200
|
ctaPins: {
|
|
187
201
|
light: ['#4f46e5', '#7c3aed', '#9333ea'],
|
|
188
202
|
dark: ['#4f46e5', '#7c3aed', '#9333ea'],
|
|
189
203
|
},
|
|
204
|
+
// 12 px controls, 20 px cards and panels, 24 px dialogs and hero sheets.
|
|
205
|
+
radii: { card: 20, sheet: 24 },
|
|
206
|
+
// Glass: a light boost on light grounds (more would amplify the ambient
|
|
207
|
+
// tint into a grey-lavender wash), a strong one on dark grounds.
|
|
208
|
+
glassSaturate: { light: 1.15, dark: 1.5 },
|
|
209
|
+
// The liquid-glass elevation: a soft drop under every sheet, a specular
|
|
210
|
+
// inner highlight on its top edge, and the role-named elevation scale.
|
|
211
|
+
shadowPins: {
|
|
212
|
+
light: {
|
|
213
|
+
sheet: [
|
|
214
|
+
{ x: 0, y: 4, blur: 16, spread: -2, color: 'rgb(0 0 0 / 0.1)' },
|
|
215
|
+
{ x: 0, y: 12, blur: 40, spread: -8, color: 'rgb(0 0 0 / 0.06)' },
|
|
216
|
+
],
|
|
217
|
+
'sheet-inset': [
|
|
218
|
+
{ x: 0, y: 1, blur: 2, spread: 0, color: 'rgb(255 255 255 / 0.7)', inset: true },
|
|
219
|
+
{ x: 0, y: -1, blur: 2, spread: 0, color: 'rgb(0 0 0 / 0.04)', inset: true },
|
|
220
|
+
],
|
|
221
|
+
raised: [
|
|
222
|
+
{ x: 0, y: 4, blur: 12, spread: -2, color: 'rgb(15 23 42 / 0.08)' },
|
|
223
|
+
{ x: 0, y: 2, blur: 4, spread: -2, color: 'rgb(15 23 42 / 0.04)' },
|
|
224
|
+
],
|
|
225
|
+
floating: [
|
|
226
|
+
{ x: 0, y: 12, blur: 24, spread: -8, color: 'rgb(15 23 42 / 0.12)' },
|
|
227
|
+
{ x: 0, y: 4, blur: 8, spread: -4, color: 'rgb(15 23 42 / 0.06)' },
|
|
228
|
+
],
|
|
229
|
+
modal: [
|
|
230
|
+
{ x: 0, y: 40, blur: 100, spread: -30, color: 'rgb(0 0 0 / 0.45)' },
|
|
231
|
+
{ x: 0, y: 16, blur: 40, spread: -12, color: 'rgb(0 0 0 / 0.18)' },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
dark: {
|
|
235
|
+
sheet: [{ x: 0, y: 12, blur: 40, spread: -12, color: 'rgb(0 0 0 / 0.5)' }],
|
|
236
|
+
'sheet-inset': [{ x: 0, y: 1, blur: 1, spread: 0, color: 'rgb(255 255 255 / 0.12)', inset: true }],
|
|
237
|
+
raised: [
|
|
238
|
+
{ x: 0, y: 4, blur: 12, spread: -2, color: 'rgb(0 0 0 / 0.5)' },
|
|
239
|
+
{ x: 0, y: 2, blur: 4, spread: -2, color: 'rgb(0 0 0 / 0.3)' },
|
|
240
|
+
],
|
|
241
|
+
floating: [
|
|
242
|
+
{ x: 0, y: 12, blur: 24, spread: -8, color: 'rgb(0 0 0 / 0.6)' },
|
|
243
|
+
{ x: 0, y: 4, blur: 8, spread: -4, color: 'rgb(0 0 0 / 0.4)' },
|
|
244
|
+
],
|
|
245
|
+
modal: [
|
|
246
|
+
{ x: 0, y: 40, blur: 100, spread: -30, color: 'rgb(0 0 0 / 0.7)' },
|
|
247
|
+
{ x: 0, y: 16, blur: 40, spread: -12, color: 'rgb(0 0 0 / 0.5)' },
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
modeStrings: {
|
|
252
|
+
light: {
|
|
253
|
+
// Lit from above: bright top and start edges, grey shaded ones (top end bottom start).
|
|
254
|
+
'glass-edge': 'rgb(255 255 255 / 0.9) rgb(200 200 210 / 0.3) rgb(200 200 210 / 0.25) rgb(255 255 255 / 0.7)',
|
|
255
|
+
'glass-edge-width': '1.5px',
|
|
256
|
+
// The inner highlight of raised and floating glass.
|
|
257
|
+
'glass-highlight': 'inset 0 1px 2px 0 rgb(255 255 255 / 0.8), inset 0 -1px 2px 0 rgb(0 0 0 / 0.05)',
|
|
258
|
+
'glass-fill': 'linear-gradient(135deg, rgb(255 255 255 / 0.8) 0%, rgb(220 225 240 / 0.62) 40%, rgb(240 245 255 / 0.68) 70%, rgb(255 255 255 / 0.8) 100%)',
|
|
259
|
+
'glass-fill-raised': 'linear-gradient(135deg, rgb(255 255 255 / 0.86) 0%, rgb(215 220 238 / 0.7) 40%, rgb(235 240 255 / 0.76) 70%, rgb(255 255 255 / 0.86) 100%)',
|
|
260
|
+
'mark-gradient': 'linear-gradient(135deg, #38bdf8 0%, #6366f1 40%, #a855f7 100%)',
|
|
261
|
+
'wordmark-gradient': 'linear-gradient(90deg, #0369a1, #4338ca, #7e22ce)',
|
|
262
|
+
},
|
|
263
|
+
dark: {
|
|
264
|
+
'glass-edge': 'rgb(255 255 255 / 0.2) rgb(255 255 255 / 0.06) rgb(255 255 255 / 0.04) rgb(255 255 255 / 0.12)',
|
|
265
|
+
'glass-edge-width': '1.5px',
|
|
266
|
+
'glass-highlight': 'inset 0 1px 1px 0 rgb(255 255 255 / 0.15)',
|
|
267
|
+
'glass-fill': 'linear-gradient(140deg, rgb(30 41 59 / 0.7) 0%, rgb(15 23 42 / 0.6) 45%, rgb(20 30 50 / 0.55) 100%)',
|
|
268
|
+
'glass-fill-raised': 'linear-gradient(140deg, rgb(30 41 59 / 0.78) 0%, rgb(15 23 42 / 0.68) 45%, rgb(20 30 50 / 0.62) 100%)',
|
|
269
|
+
'mark-gradient': 'linear-gradient(135deg, #38bdf8 0%, #6366f1 40%, #a855f7 100%)',
|
|
270
|
+
'wordmark-gradient': 'linear-gradient(90deg, #38bdf8, #818cf8, #a855f7)',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
// The app's type scale: 30 px display, 24 px page titles, 18 px sections, 16 px
|
|
274
|
+
// sub-sections, 14 px labels, tight tracking on titles.
|
|
275
|
+
strings: {
|
|
276
|
+
// The display step is the 30 px wordmark of the sign-in screens.
|
|
277
|
+
'font-size-display': '30px',
|
|
278
|
+
'font-line-height-display': '36px',
|
|
279
|
+
'font-size-h1': '24px',
|
|
280
|
+
'font-line-height-h1': '32px',
|
|
281
|
+
'font-size-h2': '18px',
|
|
282
|
+
'font-line-height-h2': '28px',
|
|
283
|
+
'font-size-h3': '16px',
|
|
284
|
+
'font-line-height-h3': '24px',
|
|
285
|
+
'font-size-label': '14px',
|
|
286
|
+
'font-line-height-label': '20px',
|
|
287
|
+
'font-tracking-h1': '-0.025em',
|
|
288
|
+
},
|
|
190
289
|
// The platform's own UI fonts, headings included (no web font to fetch).
|
|
191
290
|
fonts: {
|
|
192
291
|
display: ['-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Noto Sans', 'Arial', 'sans-serif'],
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// Book themes: how a publisher or a collection defines its own look for
|
|
2
|
+
// @datatechsolutions/tympan-print without touching tympan's code.
|
|
3
|
+
//
|
|
4
|
+
// A theme inherits a base (one of the 39 presets or another registered theme)
|
|
5
|
+
// and overrides any token of PrintStyleOverrides, plus what belongs to a
|
|
6
|
+
// publisher rather than to a style: its marks (publisher logo, collection
|
|
7
|
+
// seal), the page format and bleed, the running head and folio, and local
|
|
8
|
+
// font files. Register it by id and pass the id to PrintBook as `estilo`.
|
|
9
|
+
//
|
|
10
|
+
// A theme may come from JSON, either in the theme's own shape
|
|
11
|
+
// (`PrintBookThemeJson`) or as a DTCG tree (`printThemeFromDtcg`), so a brand's
|
|
12
|
+
// identity can live in the brand's repository.
|
|
13
|
+
|
|
14
|
+
import { fromDtcgColor, type DtcgColor } from './dtcg.ts'
|
|
15
|
+
import { toHex } from './color.ts'
|
|
16
|
+
import { SYSTEM_FONT_FAMILIES, fontFaceCss, type FontManifest } from './fonts.ts'
|
|
17
|
+
import {
|
|
18
|
+
mergePrintStyle,
|
|
19
|
+
printPresetById,
|
|
20
|
+
resolvePrintStyleName,
|
|
21
|
+
type PrintCoresParciais,
|
|
22
|
+
type PrintPagina,
|
|
23
|
+
type PrintStyle,
|
|
24
|
+
type PrintStyleOverrides,
|
|
25
|
+
} from './print-presets.ts'
|
|
26
|
+
|
|
27
|
+
/** A book theme: an id, a base and overrides. */
|
|
28
|
+
export interface PrintBookTheme extends PrintStyleOverrides {
|
|
29
|
+
/** Registry id, e.g. 'lakebrasil-editorial'. Use it as PrintBook's `estilo`. */
|
|
30
|
+
id: string
|
|
31
|
+
/** A preset name (or deprecated alias) or the id of another registered theme. */
|
|
32
|
+
base: string
|
|
33
|
+
/** What the theme interprets (shown in galleries). */
|
|
34
|
+
referencia?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The JSON form of a theme: the same fields, colours as hex strings or DTCG colour objects. */
|
|
38
|
+
export type PrintBookThemeJson = Omit<PrintBookTheme, 'cor' | 'pb'> & {
|
|
39
|
+
cor?: Record<string, unknown>
|
|
40
|
+
pb?: Record<string, unknown>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const REGISTRO = new Map<string, PrintBookTheme>()
|
|
44
|
+
|
|
45
|
+
/** A style with overrides and theme extras applied (same as mergePrintStyle, kept for readability). */
|
|
46
|
+
export const mergePrintTheme = mergePrintStyle
|
|
47
|
+
|
|
48
|
+
/** Resolves a theme id or a preset id to a complete PrintStyle (`name` = the theme id). */
|
|
49
|
+
export function printThemeById(id: string, visitados: string[] = []): PrintStyle {
|
|
50
|
+
const tema = REGISTRO.get(id)
|
|
51
|
+
if (!tema) return printPresetById(id)
|
|
52
|
+
if (visitados.includes(id)) throw new Error(`tympan: book theme cycle ${[...visitados, id].join(' → ')}`)
|
|
53
|
+
const base = printThemeById(tema.base, [...visitados, id])
|
|
54
|
+
const { id: _id, base: _base, referencia, ...over } = tema
|
|
55
|
+
return { ...mergePrintTheme(base, over), name: tema.id, label: tema.label ?? base.label, referencia: referencia ?? `Tema sobre ${base.name}: ${base.referencia}` }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when `id` names a registered theme or a preset. */
|
|
59
|
+
export function isPrintThemeId(id: string): boolean {
|
|
60
|
+
return REGISTRO.has(id) || resolvePrintStyleName(id) !== undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizarCores(c: Record<string, unknown> | undefined): PrintCoresParciais | undefined {
|
|
64
|
+
if (!c) return undefined
|
|
65
|
+
const cor = (v: unknown): string => (typeof v === 'string' ? v : toHex(fromDtcgColor(v as DtcgColor)))
|
|
66
|
+
const out: Record<string, unknown> = {}
|
|
67
|
+
for (const [k, v] of Object.entries(c)) {
|
|
68
|
+
if ((k === 'prova' || k === 'temas' || k === 'categorias') && v && typeof v === 'object') out[k] = Object.fromEntries(Object.entries(v).map(([e, x]) => [e, cor(x)]))
|
|
69
|
+
else if ((k === 'ornamento' || k === 'categorica' || k === 'divergente') && Array.isArray(v)) out[k] = v.map(cor)
|
|
70
|
+
else out[k] = cor(v)
|
|
71
|
+
}
|
|
72
|
+
return out as PrintCoresParciais
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Validates and normalises a theme read from JSON (colours may be hex or DTCG colour objects). */
|
|
76
|
+
export function printThemeFromJson(json: PrintBookThemeJson | string): PrintBookTheme {
|
|
77
|
+
const j = (typeof json === 'string' ? JSON.parse(json) : json) as PrintBookThemeJson
|
|
78
|
+
if (!j || typeof j.id !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(j.id)) throw new Error('tympan: a book theme needs an `id` in kebab case')
|
|
79
|
+
if (typeof j.base !== 'string') throw new Error(`tympan: book theme "${j.id}" needs a \`base\` (a preset or a registered theme)`)
|
|
80
|
+
const { cor, pb, ...rest } = j
|
|
81
|
+
const out: PrintBookTheme = { ...rest }
|
|
82
|
+
const c = normalizarCores(cor)
|
|
83
|
+
if (c) out.cor = c
|
|
84
|
+
const p = normalizarCores(pb)
|
|
85
|
+
if (p) out.pb = p
|
|
86
|
+
return out
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type No = Record<string, unknown>
|
|
90
|
+
|
|
91
|
+
/** Namespaces read for the theme's meta (id/name, base/extends): tympan's own and the short one brand repos use. */
|
|
92
|
+
const EXTS = ['br.com.datatechsolutions.tympan', 'dev.tympan.print'] as const
|
|
93
|
+
|
|
94
|
+
/** Unwraps a DTCG group into plain values: `{ $value }` leaves become their value; `$`-keys are dropped. */
|
|
95
|
+
function desembrulhar(n: unknown): unknown {
|
|
96
|
+
if (!n || typeof n !== 'object' || Array.isArray(n)) return n
|
|
97
|
+
const o = n as No
|
|
98
|
+
if ('$value' in o) {
|
|
99
|
+
const v = o.$value
|
|
100
|
+
if (o.$type === 'fontFamily' && Array.isArray(v)) return pilhaDeFontes(v as string[])
|
|
101
|
+
if (o.$type === 'color' && v && typeof v === 'object') return toHex(fromDtcgColor(v as DtcgColor))
|
|
102
|
+
if (o.$type === 'dimension' && v && typeof v === 'object' && 'unit' in (v as No)) return emMm(v as { value: number; unit: string })
|
|
103
|
+
return v
|
|
104
|
+
}
|
|
105
|
+
const out: No = {}
|
|
106
|
+
for (const [k, v] of Object.entries(o)) if (!k.startsWith('$')) out[k] = desembrulhar(v)
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const GENERICAS = /^(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-[a-z-]+)$/
|
|
111
|
+
const pilhaDeFontes = (v: string[]) => v.map((x) => (GENERICAS.test(x) || /^["']/.test(x) ? x : `"${x}"`)).join(', ')
|
|
112
|
+
const emMm = (v: { value: number; unit: string }) => (v.unit === 'px' ? (v.value * 25.4) / 96 : v.unit === 'pt' ? (v.value * 25.4) / 72 : v.unit === 'cm' ? v.value * 10 : v.value)
|
|
113
|
+
|
|
114
|
+
/** Figures from the meta: an object by role, or a phrase such as "tabular-nums em numero e rotulo". */
|
|
115
|
+
function lerAlgarismos(v: unknown): Record<string, 'tabular' | 'proporcional'> | undefined {
|
|
116
|
+
if (v && typeof v === 'object') return v as Record<string, 'tabular' | 'proporcional'>
|
|
117
|
+
if (typeof v !== 'string') return undefined
|
|
118
|
+
const tipo = /proporc|proportional/.test(v) ? 'proporcional' : /tabular/.test(v) ? 'tabular' : undefined
|
|
119
|
+
if (!tipo) return undefined
|
|
120
|
+
const papeis = ['numero', 'rotulo', 'corpo'].filter((p) => v.includes(p))
|
|
121
|
+
return Object.fromEntries((papeis.length ? papeis : ['numero']).map((p) => [p, tipo]))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* A theme from a DTCG (2025.10) tree. The tree mirrors PrintStyleOverrides: each group is a field
|
|
126
|
+
* (`label`, `fontes.<role>`, `googleFonts`, `cor.<role>`, `cor.prova.<state>`, `cor.categorica.<n>`,
|
|
127
|
+
* `cor.divergente.<n>`, `traco.largura`, `pb.*`, `estrutura.*`, `texto.*`, `marcas.*`, `pagina.*`, `folio.*`,
|
|
128
|
+
* `fontesLocais`…) and each `{ "$type", "$value" }` leaf becomes its value (colours as hex or DTCG colour
|
|
129
|
+
* objects, `fontFamily` arrays become CSS stacks, dimensions become mm). The theme's id and base come from
|
|
130
|
+
* the root `$extensions["dev.tympan.print"]` (or `["br.com.datatechsolutions.tympan"]`) as `name`/`id` and
|
|
131
|
+
* `extends`/`base`.
|
|
132
|
+
*/
|
|
133
|
+
export function printThemeFromDtcg(tree: No | string): PrintBookTheme {
|
|
134
|
+
const t = (typeof tree === 'string' ? JSON.parse(tree) : tree) as No
|
|
135
|
+
const exts = (t.$extensions ?? {}) as No
|
|
136
|
+
const meta = Object.assign({}, ...EXTS.map((e) => exts[e] as No | undefined).filter(Boolean).map((m) => ({ ...(m!.tema as No | undefined), ...m }))) as No
|
|
137
|
+
const id = (meta.id ?? meta.name) as string | undefined
|
|
138
|
+
const base = (meta.base ?? meta.extends) as string | undefined
|
|
139
|
+
if (!id || !base) throw new Error(`tympan: a DTCG book theme needs $extensions["dev.tympan.print"] with name and extends`)
|
|
140
|
+
const corpo = desembrulhar(t) as No
|
|
141
|
+
// Ordered lists written as groups ("1", "2"… or "a", "b"…) become arrays.
|
|
142
|
+
const cor = corpo.cor as No | undefined
|
|
143
|
+
for (const g of [cor, corpo.pb as No | undefined])
|
|
144
|
+
if (g) for (const k of ['categorica', 'divergente', 'ornamento']) if (g[k] && typeof g[k] === 'object' && !Array.isArray(g[k])) g[k] = Object.entries(g[k] as No).sort(([a], [b]) => a.localeCompare(b, 'en', { numeric: true })).map(([, v]) => v)
|
|
145
|
+
// Brand-repo extras in the meta: per-role weights, figures and the data palette.
|
|
146
|
+
const texto = { ...(corpo.texto as No | undefined) }
|
|
147
|
+
if (meta.pesos && typeof meta.pesos === 'object') texto.pesos = { ...(meta.pesos as No), ...(texto.pesos as No | undefined) }
|
|
148
|
+
const alg = lerAlgarismos(meta.algarismos)
|
|
149
|
+
if (alg) texto.algarismos = { ...alg, ...(texto.algarismos as No | undefined) }
|
|
150
|
+
if (Object.keys(texto).length) corpo.texto = texto
|
|
151
|
+
const dado = meta.dado as Record<string, string> | undefined
|
|
152
|
+
if (dado) {
|
|
153
|
+
const c = { ...(corpo.cor as No | undefined) }
|
|
154
|
+
if (dado.interesse && c.interesse === undefined) c.interesse = dado.interesse
|
|
155
|
+
if (dado.contexto) c.contexto = dado.contexto
|
|
156
|
+
if (dado.perdeu && dado.ganhou && c.divergente === undefined) c.divergente = [dado.perdeu, dado.ganhou]
|
|
157
|
+
corpo.cor = c
|
|
158
|
+
}
|
|
159
|
+
return printThemeFromJson({ ...corpo, id, base, ...(meta.referencia ? { referencia: meta.referencia as string } : {}) } as PrintBookThemeJson)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Registers (or replaces) a theme; accepts the theme object, its JSON or a DTCG tree. Returns the theme. */
|
|
163
|
+
export function registerPrintTheme(tema: PrintBookTheme | PrintBookThemeJson | No | string): PrintBookTheme {
|
|
164
|
+
const obj = typeof tema === 'string' ? (JSON.parse(tema) as No) : (tema as No)
|
|
165
|
+
const t = obj.$extensions ? printThemeFromDtcg(obj) : printThemeFromJson(obj as PrintBookThemeJson)
|
|
166
|
+
if (resolvePrintStyleName(t.id)) throw new Error(`tympan: book theme id "${t.id}" collides with a preset`)
|
|
167
|
+
REGISTRO.set(t.id, t)
|
|
168
|
+
printThemeById(t.id) // fails now on an unknown base or a cycle
|
|
169
|
+
return t
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Removes a registered theme (tests, hot reload). */
|
|
173
|
+
export function unregisterPrintTheme(id: string): boolean {
|
|
174
|
+
return REGISTRO.delete(id)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Ids of the registered themes, in registration order. */
|
|
178
|
+
export function registeredPrintThemes(): string[] {
|
|
179
|
+
return [...REGISTRO.keys()]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** `@font-face` rules for a style's local font files ('' when it has none). */
|
|
183
|
+
export function fontesLocaisCss(style: Pick<PrintStyle, 'fontesLocais'>): string {
|
|
184
|
+
return (style.fontesLocais ?? [])
|
|
185
|
+
.map(
|
|
186
|
+
(f) =>
|
|
187
|
+
`@font-face { font-family: "${f.familia.replace(/"/g, '')}"; src: url("${f.src.replace(/"/g, '%22')}") format("woff2"); font-weight: ${f.peso ?? 400}; font-style: ${f.estilo ?? 'normal'}; font-display: block; }`,
|
|
188
|
+
)
|
|
189
|
+
.join('\n')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Trim and bleed of a style (defaults 170 × 240 mm, 3 mm bleed). */
|
|
193
|
+
export function paginaDoEstilo(style: Pick<PrintStyle, 'pagina'>): Required<PrintPagina> {
|
|
194
|
+
const p = style.pagina ?? {}
|
|
195
|
+
return { largura: p.largura ?? 170, altura: p.altura ?? 240, sangria: p.sangria ?? 3, marcasCorte: p.marcasCorte ?? false }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A neutral example theme (registered nowhere by default): a small press over `semanario`, in two inks. */
|
|
199
|
+
export const TEMA_EXEMPLO_EDITORA: PrintBookTheme = {
|
|
200
|
+
id: 'editora-exemplo',
|
|
201
|
+
base: 'semanario',
|
|
202
|
+
label: 'Editora Exemplo · coleção ensaios',
|
|
203
|
+
fontes: { titulo: '"Source Serif 4", Georgia, serif' },
|
|
204
|
+
cor: { destaque: '#1f4e79', destaque2: '#b5562b', marcaTexto: '#e8eef5' },
|
|
205
|
+
pb: { destaque: '#333333', destaque2: '#8a8a8a' },
|
|
206
|
+
texto: { nomesProprios: 'preservar' },
|
|
207
|
+
folio: { folio: 'centro', cabeco: 'externo' },
|
|
208
|
+
pagina: { largura: 160, altura: 230, sangria: 3 },
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/** Families named in a style's font stacks (quotes removed), in role order, without generic and system faces. */
|
|
213
|
+
export function familiasDoEstilo(style: Pick<PrintStyle, 'fontes'>): string[] {
|
|
214
|
+
const out: string[] = []
|
|
215
|
+
for (const pilha of Object.values(style.fontes) as string[])
|
|
216
|
+
for (const f of pilha.split(',').map((x) => x.trim().replace(/^["']|["']$/g, '')))
|
|
217
|
+
if (f && !SYSTEM_FONT_FAMILIES.includes(f) && !out.includes(f)) out.push(f)
|
|
218
|
+
return out
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Families the style names (first of each stack) that have neither a bundled file nor a theme `fontesLocais`. */
|
|
222
|
+
export function familiasSemArquivo(style: Pick<PrintStyle, 'fontes' | 'fontesLocais'>, manifest: FontManifest): string[] {
|
|
223
|
+
const proprias = new Set((style.fontesLocais ?? []).map((f) => f.familia))
|
|
224
|
+
const primeiras = (Object.values(style.fontes) as string[]).map((p) => p.split(',')[0]!.trim().replace(/^["']|["']$/g, ''))
|
|
225
|
+
return [...new Set(primeiras)].filter((f) => !SYSTEM_FONT_FAMILIES.includes(f) && !proprias.has(f) && !manifest.families[f])
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @font-face rules for a style from the fonts bundled in this package (`fonts/manifest.json`): every face of
|
|
230
|
+
* every family its font stacks name (so a mono or label family outside `googleFonts` is not forgotten), with
|
|
231
|
+
* URLs `<base><file>`, then the theme's own `fontesLocais`, which win for their family.
|
|
232
|
+
*/
|
|
233
|
+
export function fontesEmpacotadasCss(style: Pick<PrintStyle, 'fontes' | 'googleFonts' | 'fontesLocais'>, manifest: FontManifest, base: string): string {
|
|
234
|
+
const proprias = new Set((style.fontesLocais ?? []).map((f) => f.familia))
|
|
235
|
+
const familias = new Set(familiasDoEstilo(style).filter((f) => manifest.families[f] && !proprias.has(f)))
|
|
236
|
+
// Every bundled spec of those families (faces are de-duplicated by fontFaceCss).
|
|
237
|
+
const specs = Object.keys(manifest.specs).filter((s) => familias.has(s.split(':')[0]!))
|
|
238
|
+
return [specs.length ? fontFaceCss(manifest, specs, base) : '', fontesLocaisCss(style)].filter(Boolean).join('\n')
|
|
239
|
+
}
|