capture-flow 0.0.0-stage → 0.1.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -2
  3. package/bin/capture-flow.mjs +12 -0
  4. package/package.json +55 -4
  5. package/src/build.ts +186 -0
  6. package/src/capture.ts +77 -0
  7. package/src/cli.ts +139 -0
  8. package/src/index.ts +11 -0
  9. package/src/manifest.ts +98 -0
  10. package/src/render.ts +75 -0
  11. package/src/verify.ts +90 -0
  12. package/vendor/wajha/LICENSE +21 -0
  13. package/vendor/wajha/NOTICE +21 -0
  14. package/vendor/wajha/engine/background.ts +55 -0
  15. package/vendor/wajha/engine/bidi.ts +589 -0
  16. package/vendor/wajha/engine/cache.ts +96 -0
  17. package/vendor/wajha/engine/color.ts +125 -0
  18. package/vendor/wajha/engine/devices/android.ts +53 -0
  19. package/vendor/wajha/engine/devices/browser.ts +170 -0
  20. package/vendor/wajha/engine/devices/finish.ts +154 -0
  21. package/vendor/wajha/engine/devices/geometry.ts +326 -0
  22. package/vendor/wajha/engine/devices/index.ts +143 -0
  23. package/vendor/wajha/engine/devices/ipad.ts +21 -0
  24. package/vendor/wajha/engine/devices/iphone.ts +55 -0
  25. package/vendor/wajha/engine/devices/macbook.ts +187 -0
  26. package/vendor/wajha/engine/devices/mock/apps/calendar.ts +145 -0
  27. package/vendor/wajha/engine/devices/mock/apps/chat.ts +213 -0
  28. package/vendor/wajha/engine/devices/mock/apps/finance.ts +164 -0
  29. package/vendor/wajha/engine/devices/mock/apps/fitness.ts +145 -0
  30. package/vendor/wajha/engine/devices/mock/apps/index.ts +14 -0
  31. package/vendor/wajha/engine/devices/mock/apps/meditation.ts +167 -0
  32. package/vendor/wajha/engine/devices/mock/apps/music.ts +152 -0
  33. package/vendor/wajha/engine/devices/mock/apps/shop.ts +116 -0
  34. package/vendor/wajha/engine/devices/mock/apps/social.ts +152 -0
  35. package/vendor/wajha/engine/devices/mock/apps/tasks.ts +131 -0
  36. package/vendor/wajha/engine/devices/mock/apps/travel.ts +121 -0
  37. package/vendor/wajha/engine/devices/mock/art.ts +557 -0
  38. package/vendor/wajha/engine/devices/mock/chrome.ts +337 -0
  39. package/vendor/wajha/engine/devices/mock/icons.ts +614 -0
  40. package/vendor/wajha/engine/devices/mock/kit.ts +213 -0
  41. package/vendor/wajha/engine/devices/mock/rng.ts +40 -0
  42. package/vendor/wajha/engine/devices/mock/theme.ts +133 -0
  43. package/vendor/wajha/engine/devices/mock/widgets.ts +231 -0
  44. package/vendor/wajha/engine/devices/paths.ts +113 -0
  45. package/vendor/wajha/engine/devices/screen.ts +75 -0
  46. package/vendor/wajha/engine/devices/shadow.ts +124 -0
  47. package/vendor/wajha/engine/devices/slab.ts +200 -0
  48. package/vendor/wajha/engine/devices/vision.ts +43 -0
  49. package/vendor/wajha/engine/devices/watch.ts +14 -0
  50. package/vendor/wajha/engine/geometry.ts +152 -0
  51. package/vendor/wajha/engine/images.ts +96 -0
  52. package/vendor/wajha/engine/index.ts +20 -0
  53. package/vendor/wajha/engine/linebreak.ts +226 -0
  54. package/vendor/wajha/engine/markup.ts +84 -0
  55. package/vendor/wajha/engine/mock.ts +158 -0
  56. package/vendor/wajha/engine/paint.ts +474 -0
  57. package/vendor/wajha/engine/patterns.ts +263 -0
  58. package/vendor/wajha/engine/render.ts +364 -0
  59. package/vendor/wajha/engine/shapes.ts +260 -0
  60. package/vendor/wajha/engine/testing/canvasNode.ts +121 -0
  61. package/vendor/wajha/engine/testing/fakeDevices.ts +85 -0
  62. package/vendor/wajha/engine/text.ts +694 -0
  63. package/vendor/wajha/engine/types.ts +190 -0
  64. package/vendor/wajha/export/paths.ts +197 -0
  65. package/vendor/wajha/export/png.ts +223 -0
  66. package/vendor/wajha/export/types.ts +60 -0
  67. package/vendor/wajha/model/adapt.ts +889 -0
  68. package/vendor/wajha/model/catalog.ts +369 -0
  69. package/vendor/wajha/model/clone.ts +58 -0
  70. package/vendor/wajha/model/colorMath.ts +128 -0
  71. package/vendor/wajha/model/defaults.ts +386 -0
  72. package/vendor/wajha/model/geometry.ts +95 -0
  73. package/vendor/wajha/model/ids.ts +13 -0
  74. package/vendor/wajha/model/instantiate.ts +280 -0
  75. package/vendor/wajha/model/mirror.ts +43 -0
  76. package/vendor/wajha/model/resolve.ts +87 -0
  77. package/vendor/wajha/model/rules.ts +249 -0
  78. package/vendor/wajha/model/stub.ts +4 -0
  79. package/vendor/wajha/model/types.ts +456 -0
  80. package/vendor/wajha/model/typography.ts +28 -0
  81. package/vendor/wajha/model/validate.ts +917 -0
  82. package/vendor/wajha/templates/builtin/aurora.ts +163 -0
  83. package/vendor/wajha/templates/builtin/bubblegum.ts +132 -0
  84. package/vendor/wajha/templates/builtin/citrusPop.ts +124 -0
  85. package/vendor/wajha/templates/builtin/clayGarden.ts +114 -0
  86. package/vendor/wajha/templates/builtin/desertDune.ts +97 -0
  87. package/vendor/wajha/templates/builtin/glassPrism.ts +139 -0
  88. package/vendor/wajha/templates/builtin/launchpad.ts +140 -0
  89. package/vendor/wajha/templates/builtin/loudAndClear.ts +109 -0
  90. package/vendor/wajha/templates/builtin/midnightNeon.ts +108 -0
  91. package/vendor/wajha/templates/builtin/monoSwiss.ts +149 -0
  92. package/vendor/wajha/templates/builtin/noirGold.ts +149 -0
  93. package/vendor/wajha/templates/builtin/noor.ts +107 -0
  94. package/vendor/wajha/templates/builtin/obsidian.ts +137 -0
  95. package/vendor/wajha/templates/builtin/panoramaPeaks.ts +145 -0
  96. package/vendor/wajha/templates/builtin/panoramaRibbon.ts +138 -0
  97. package/vendor/wajha/templates/builtin/panoramaWaves.ts +138 -0
  98. package/vendor/wajha/templates/builtin/paperAndInk.ts +98 -0
  99. package/vendor/wajha/templates/builtin/sage.ts +94 -0
  100. package/vendor/wajha/templates/builtin/splitTone.ts +129 -0
  101. package/vendor/wajha/templates/builtin/sunsetBoulevard.ts +127 -0
  102. package/vendor/wajha/templates/index.ts +55 -0
  103. package/vendor/wajha/templates/kit.ts +761 -0
  104. package/vendor/wajha/ui/sortShots.ts +239 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Wajha data model. This file is the single source of truth for every
3
+ * persisted shape (projects, templates, gallery index) and is shared by the
4
+ * engine, the store, storage, export and the UI.
5
+ *
6
+ * Units used everywhere in layouts:
7
+ * - Positions (`x`, `y`) are fractions of the canvas width / height (0..1).
8
+ * They mark the element's anchor (its center, except text which can be
9
+ * anchored at its top or bottom edge, see `TextElement.valign`).
10
+ * - Lengths (widths, font sizes, blur, shadow offsets, radii) are fractions
11
+ * of `u = min(canvasWidth, canvasHeight)`, so a layout scales cleanly
12
+ * between export sizes and orientations.
13
+ * - Angles are degrees, clockwise.
14
+ * - Colors are hex strings: "#RRGGBB" or "#RRGGBBAA".
15
+ */
16
+
17
+ export type LangCode = string;
18
+ export type Color = string;
19
+
20
+ /* ------------------------------------------------------------------ */
21
+ /* Device families and export sizes */
22
+ /* ------------------------------------------------------------------ */
23
+
24
+ export type FamilyId =
25
+ | 'iphone'
26
+ | 'iphone-landscape'
27
+ | 'ipad'
28
+ | 'ipad-landscape'
29
+ | 'android-phone'
30
+ | 'android-landscape'
31
+ | 'android-tablet'
32
+ | 'mac'
33
+ | 'watch'
34
+ | 'apple-tv'
35
+ | 'vision'
36
+ | 'play-feature';
37
+
38
+ export type StoreId = 'app-store' | 'google-play';
39
+
40
+ /** Rough canvas shape. Templates author one layout per class. */
41
+ export type AspectClass = 'tall' | 'tablet' | 'wide';
42
+
43
+ export type DeviceFrameId =
44
+ | 'iphone'
45
+ | 'ipad'
46
+ | 'android-phone'
47
+ | 'android-tablet'
48
+ | 'macbook'
49
+ | 'watch'
50
+ | 'tv'
51
+ | 'vision'
52
+ | 'browser'
53
+ | 'none';
54
+
55
+ export interface ExportSize {
56
+ /** Stable id, e.g. "ios-6.9". */
57
+ id: string;
58
+ family: FamilyId;
59
+ store: StoreId;
60
+ /** Human label, e.g. "iPhone 6.9″". */
61
+ label: string;
62
+ /** Devices this size covers, e.g. "iPhone 17 Pro Max, 16 Pro Max". */
63
+ devices: string;
64
+ width: number;
65
+ height: number;
66
+ /** The store requires this size (or one of its group) for the family. */
67
+ required: boolean;
68
+ /** Selected by default in the export dialog. */
69
+ default: boolean;
70
+ /** Folder segment used by export structures, e.g. "iPhone 6.9". */
71
+ folder: string;
72
+ /** fastlane folder for Google Play (supply); undefined for App Store. */
73
+ supplyFolder?: 'phoneScreenshots' | 'sevenInchScreenshots' | 'tenInchScreenshots' | 'featureGraphic';
74
+ }
75
+
76
+ export interface FamilyInfo {
77
+ id: FamilyId;
78
+ label: string;
79
+ shortLabel: string;
80
+ stores: StoreId[];
81
+ /** The master canvas the user designs on (pixels). */
82
+ design: { width: number; height: number };
83
+ aspectClass: AspectClass;
84
+ defaultFrame: DeviceFrameId;
85
+ /** Default screen aspect (height / width) when a slot has no screenshot. */
86
+ screenAspect: number;
87
+ /** Store limit of screenshots per locale. */
88
+ maxSlides: number;
89
+ /** Phones and tablets lie on their side on these canvases. */
90
+ landscape?: boolean;
91
+ sizes: ExportSize[];
92
+ }
93
+
94
+ /* ------------------------------------------------------------------ */
95
+ /* Paint */
96
+ /* ------------------------------------------------------------------ */
97
+
98
+ export interface GradientStop {
99
+ /** 0..1 */
100
+ offset: number;
101
+ color: Color;
102
+ }
103
+
104
+ export type Fill =
105
+ | { type: 'solid'; color: Color }
106
+ /** CSS semantics: angle 0 = towards top, 90 = towards right. */
107
+ | { type: 'linear'; angle: number; stops: GradientStop[] }
108
+ /** cx/cy: fractions of canvas width/height. radius: fraction of max(w, h). */
109
+ | { type: 'radial'; cx: number; cy: number; radius: number; stops: GradientStop[] }
110
+ /** Soft colour blobs over a base colour ("mesh gradient" look). */
111
+ | { type: 'mesh'; base: Color; points: MeshPoint[] }
112
+ | {
113
+ type: 'image';
114
+ assetId: string;
115
+ fit: 'cover' | 'contain' | 'tile';
116
+ /** 0..1 */
117
+ opacity: number;
118
+ /** Blur radius as a fraction of u. 0 = sharp. */
119
+ blur: number;
120
+ /** Optional colour wash drawn on top (use alpha in the hex). */
121
+ tint: Color | null;
122
+ };
123
+
124
+ export interface MeshPoint {
125
+ /** fractions of canvas width / height */
126
+ x: number;
127
+ y: number;
128
+ color: Color;
129
+ /** fraction of max(w, h) */
130
+ radius: number;
131
+ }
132
+
133
+ export type PatternKind = 'dots' | 'grid' | 'diagonal' | 'noise' | 'waves' | 'rings';
134
+
135
+ export interface Pattern {
136
+ kind: PatternKind;
137
+ color: Color;
138
+ /** 0..1 */
139
+ opacity: number;
140
+ /** Spacing / feature size as a fraction of u. */
141
+ scale: number;
142
+ }
143
+
144
+ export interface Background {
145
+ fill: Fill;
146
+ pattern: Pattern | null;
147
+ }
148
+
149
+ export interface Shadow {
150
+ color: Color;
151
+ /** fraction of u */
152
+ blur: number;
153
+ /** offsets, fraction of u */
154
+ x: number;
155
+ y: number;
156
+ }
157
+
158
+ /* ------------------------------------------------------------------ */
159
+ /* Slide elements */
160
+ /* ------------------------------------------------------------------ */
161
+
162
+ export interface ElementBase {
163
+ id: string;
164
+ /** Optional user-facing name shown in the layers list. */
165
+ name?: string;
166
+ /** Anchor position, fractions of canvas width / height. */
167
+ x: number;
168
+ y: number;
169
+ /** degrees clockwise */
170
+ rotation: number;
171
+ /** 0..1 */
172
+ opacity: number;
173
+ hidden?: boolean;
174
+ locked?: boolean;
175
+ }
176
+
177
+ export type FrameFinish = 'metal' | 'matte' | 'clay';
178
+
179
+ export interface FrameStyle {
180
+ /** Body colour of the device. */
181
+ color: Color;
182
+ finish: FrameFinish;
183
+ /** Inner bezel: realistic black, or same colour as the body (clay look). */
184
+ bezel: 'black' | 'match';
185
+ }
186
+
187
+ export interface DeviceElement extends ElementBase {
188
+ kind: 'device';
189
+ frame: DeviceFrameId;
190
+ style: FrameStyle;
191
+ /** Outer device width, fraction of u. Height follows the screen aspect. */
192
+ width: number;
193
+ /** Screenshot slot key in `Slide.shots[family]`. Usually "main". */
194
+ slot: string;
195
+ /** Screen corner radius for frame "none", fraction of device width. */
196
+ cornerRadius: number;
197
+ shadow: Shadow | null;
198
+ }
199
+
200
+ export interface TextStyle {
201
+ /** CSS font family name, e.g. "Inter Variable". */
202
+ font: string;
203
+ /** 100..900 */
204
+ weight: number;
205
+ /** Font size, fraction of u. */
206
+ size: number;
207
+ /** Line height multiplier, e.g. 1.1 */
208
+ lineHeight: number;
209
+ /** em, e.g. -0.02 */
210
+ letterSpacing: number;
211
+ color: Color;
212
+ /** Colour used for *highlighted* words. */
213
+ highlight: Color;
214
+ /** "color" recolours the words; "marker" paints a rounded pill behind them. */
215
+ highlightMode: 'color' | 'marker';
216
+ /** Logical alignment; start/end flip for right-to-left languages. */
217
+ align: 'start' | 'center' | 'end';
218
+ transform: 'none' | 'uppercase';
219
+ /**
220
+ * Maximum number of lines. Text that would need more lines shrinks (down
221
+ * to 60 % of `size`) until it fits. 0 = unlimited.
222
+ */
223
+ maxLines: number;
224
+ shadow: Shadow | null;
225
+ }
226
+
227
+ export interface TextElement extends ElementBase {
228
+ kind: 'text';
229
+ /** Key into `Slide.texts[lang]`, e.g. "headline" or "subtitle". */
230
+ key: string;
231
+ /** Text box width, fraction of u. Lines wrap inside it. */
232
+ width: number;
233
+ /** Which edge of the text block `y` refers to. */
234
+ valign: 'top' | 'middle' | 'bottom';
235
+ style: TextStyle;
236
+ }
237
+
238
+ export type ShapeKind = 'circle' | 'rect' | 'blob' | 'ring';
239
+
240
+ export interface ShapeElement extends ElementBase {
241
+ kind: 'shape';
242
+ shape: ShapeKind;
243
+ /** fractions of u */
244
+ width: number;
245
+ height: number;
246
+ fill: Fill;
247
+ /** Corner radius for rect, fraction of min(width, height). */
248
+ radius: number;
249
+ /** Ring stroke width, fraction of u. */
250
+ stroke: number;
251
+ /** Soft blur (glow), fraction of u. 0 = sharp. */
252
+ blur: number;
253
+ /** Seed for the blob outline. */
254
+ seed: number;
255
+ }
256
+
257
+ export interface ImageElement extends ElementBase {
258
+ kind: 'image';
259
+ assetId: string;
260
+ /** fraction of u; height follows the image aspect */
261
+ width: number;
262
+ /** fraction of the image width */
263
+ cornerRadius: number;
264
+ shadow: Shadow | null;
265
+ }
266
+
267
+ export type SlideElement = DeviceElement | TextElement | ShapeElement | ImageElement;
268
+ export type ElementKind = SlideElement['kind'];
269
+
270
+ /* ------------------------------------------------------------------ */
271
+ /* Slides, theme, project */
272
+ /* ------------------------------------------------------------------ */
273
+
274
+ export interface SlideLayout {
275
+ /** null = use the project theme background. */
276
+ background: Background | null;
277
+ /** Painter's order: first element is drawn first (bottom). */
278
+ elements: SlideElement[];
279
+ }
280
+
281
+ /** slot -> lang -> assetId */
282
+ export type ShotMap = Record<string, Record<LangCode, string>>;
283
+
284
+ export interface Slide {
285
+ id: string;
286
+ /**
287
+ * lang -> text key -> text. Shared by every device family.
288
+ * Markup: "*word*" = highlighted, "\n" = line break, "\*" = literal asterisk.
289
+ */
290
+ texts: Record<LangCode, Record<string, string>>;
291
+ /** family -> slot -> lang -> assetId */
292
+ shots: Partial<Record<FamilyId, ShotMap>>;
293
+ /**
294
+ * family -> layout, for the primary family and for families with a layout
295
+ * of their own (designed by the template, or edited). Any other family
296
+ * follows the primary: its layout is derived with `adaptLayout` whenever
297
+ * it is read, and stored on its first edit.
298
+ */
299
+ layouts: Partial<Record<FamilyId, SlideLayout>>;
300
+ }
301
+
302
+ export interface Theme {
303
+ background: Background;
304
+ /** Draw the background as one continuous canvas across all slides. */
305
+ panorama: boolean;
306
+ /** Suggested fonts; "Apply fonts" restyles every text element. */
307
+ fonts: { heading: string; body: string };
308
+ /** Swatches offered first in colour pickers. */
309
+ palette: Color[];
310
+ /** Appearance of generated mock screens used as placeholders. */
311
+ mockDark: boolean;
312
+ }
313
+
314
+ export interface AssetInfo {
315
+ /** "<sha256 hex>.<ext>" */
316
+ id: string;
317
+ /** Original file name. */
318
+ name: string;
319
+ width: number;
320
+ height: number;
321
+ bytes: number;
322
+ addedAt: number;
323
+ }
324
+
325
+ export type ExportStructure = 'stores' | 'fastlane' | 'flat';
326
+
327
+ export interface ExportSettings {
328
+ /** ExportSize ids. Empty = use each family's defaults. */
329
+ sizes: string[];
330
+ /** Empty = all project languages. */
331
+ languages: LangCode[];
332
+ format: 'png' | 'jpeg';
333
+ /** 0.5..1, JPEG only */
334
+ jpegQuality: number;
335
+ structure: ExportStructure;
336
+ }
337
+
338
+ export interface Project {
339
+ format: 'wajha.project';
340
+ version: 1;
341
+ id: string;
342
+ name: string;
343
+ createdAt: number;
344
+ updatedAt: number;
345
+ templateId: string | null;
346
+ theme: Theme;
347
+ /** Enabled families in display order. The first is the primary family. */
348
+ families: FamilyId[];
349
+ /** Project languages. The first is the base (fallback) language. */
350
+ languages: LangCode[];
351
+ /**
352
+ * Right-to-left languages draw every slide mirrored (see model/mirror.ts).
353
+ * On unless false; stored only when turned off.
354
+ */
355
+ mirrorRtl?: boolean;
356
+ /** Type settings per language (see model/typography.ts); only languages that have any. */
357
+ typography?: Partial<Record<LangCode, LanguageTypography>>;
358
+ slides: Slide[];
359
+ assets: Record<string, AssetInfo>;
360
+ /** Font files the project brought along (text styles name their family). */
361
+ fonts?: ProjectFont[];
362
+ export: ExportSettings;
363
+ }
364
+
365
+ /** How one language draws its text, over each text's own style. */
366
+ export interface LanguageTypography {
367
+ /** Font family for every text in this language; absent = each text's own font. */
368
+ font?: string;
369
+ /** Multiplies each text's line height; absent = 1. */
370
+ lineSpacing?: number;
371
+ }
372
+
373
+ /** A font file stored with the project, in projects/<id>/fonts/<file>. */
374
+ export interface ProjectFont {
375
+ /** The family text styles use, e.g. "Gilroy". */
376
+ family: string;
377
+ /** "<sha-256 hex>.<ttf|otf|woff|woff2>" */
378
+ file: string;
379
+ /** The file's original name, for people. */
380
+ name: string;
381
+ }
382
+
383
+ /** Small summary written next to project.json for the home screen. */
384
+ export interface ProjectMeta {
385
+ id: string;
386
+ name: string;
387
+ updatedAt: number;
388
+ createdAt: number;
389
+ slideCount: number;
390
+ families: FamilyId[];
391
+ languages: LangCode[];
392
+ templateId: string | null;
393
+ }
394
+
395
+ /* ------------------------------------------------------------------ */
396
+ /* Templates and the community gallery */
397
+ /* ------------------------------------------------------------------ */
398
+
399
+ export type TemplateCategory = 'minimal' | 'bold' | 'gradient' | 'dark' | 'playful' | 'panorama' | 'elegant';
400
+
401
+ export interface TemplateSlide {
402
+ /** Sample copy; must contain at least "en-US". */
403
+ texts: Record<LangCode, Record<string, string>>;
404
+ /** One layout per aspect class; "tall" is required, others are adapted. */
405
+ layouts: { tall: SlideLayout; tablet?: SlideLayout; wide?: SlideLayout };
406
+ }
407
+
408
+ export interface Template {
409
+ format: 'wajha.template';
410
+ version: 1;
411
+ /** kebab-case, globally unique, e.g. "sunset-glow" */
412
+ id: string;
413
+ name: string;
414
+ author: string;
415
+ description: string;
416
+ category: TemplateCategory;
417
+ tags: string[];
418
+ theme: Theme;
419
+ /** 1..10 slides. New projects start with these. */
420
+ slides: TemplateSlide[];
421
+ /** Image assets referenced by the template (gallery templates only). */
422
+ assets: Record<string, AssetInfo>;
423
+ }
424
+
425
+ export interface GalleryEntry {
426
+ id: string;
427
+ name: string;
428
+ author: string;
429
+ description: string;
430
+ category: TemplateCategory;
431
+ tags: string[];
432
+ /** Bumped by the gallery when the template changes. */
433
+ version: number;
434
+ /** Paths relative to the gallery base URL. */
435
+ template: string;
436
+ preview: string;
437
+ assets: string[];
438
+ bytes: number;
439
+ }
440
+
441
+ export interface GalleryIndex {
442
+ format: 'wajha.gallery';
443
+ version: 1;
444
+ updatedAt: string;
445
+ templates: GalleryEntry[];
446
+ }
447
+
448
+ /* ------------------------------------------------------------------ */
449
+ /* App settings */
450
+ /* ------------------------------------------------------------------ */
451
+
452
+ export interface AppSettings {
453
+ theme: 'system' | 'dark' | 'light';
454
+ /** Show the onboarding hints in the editor. */
455
+ hints: boolean;
456
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Per-language type. One layout serves every language, but scripts differ:
3
+ * Arabic needs taller lines than Latin, CJK reads better in its own face.
4
+ * A language may name one font for all of its text (weights stay per text,
5
+ * so headlines stay bold) and a line-spacing factor applied to every text's
6
+ * own line height. Absent settings leave each text as styled.
7
+ */
8
+ import type { LangCode, LanguageTypography, Project, TextStyle } from './types';
9
+
10
+ /** The type settings of `lang`, or null when it has none. */
11
+ export function languageTypography(project: Pick<Project, 'typography'> | null | undefined, lang: LangCode): LanguageTypography | null {
12
+ const all = project?.typography;
13
+ if (!all || typeof all !== 'object' || !Object.prototype.hasOwnProperty.call(all, lang)) return null;
14
+ const own = all[lang];
15
+ return own && typeof own === 'object' ? own : null;
16
+ }
17
+
18
+ /** A text style as drawn in a language with `type` settings. */
19
+ export function styleForLanguage(style: TextStyle, type: LanguageTypography | null | undefined): TextStyle {
20
+ if (!type) return style;
21
+ const font = typeof type.font === 'string' && type.font ? type.font : null;
22
+ const spacing = typeof type.lineSpacing === 'number' && Number.isFinite(type.lineSpacing) ? type.lineSpacing : 1;
23
+ if (!font && spacing === 1) return style;
24
+ const out = { ...style };
25
+ if (font) out.font = font;
26
+ if (spacing !== 1 && typeof style.lineHeight === 'number') out.lineHeight = style.lineHeight * spacing;
27
+ return out;
28
+ }