@readium/navigator 1.3.3 → 2.0.0-beta.1

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 (55) hide show
  1. package/dist/index.js +3744 -2819
  2. package/dist/index.umd.cjs +16 -16
  3. package/package.json +9 -9
  4. package/src/epub/EpubNavigator.ts +184 -7
  5. package/src/epub/css/Properties.ts +376 -0
  6. package/src/epub/css/ReadiumCSS.ts +348 -0
  7. package/src/epub/css/index.ts +2 -0
  8. package/src/epub/frame/FrameBlobBuilder.ts +59 -9
  9. package/src/epub/frame/FrameManager.ts +25 -6
  10. package/src/epub/frame/FramePoolManager.ts +61 -2
  11. package/src/epub/fxl/FXLFramePoolManager.ts +3 -15
  12. package/src/epub/index.ts +3 -1
  13. package/src/epub/preferences/EpubDefaults.ts +154 -0
  14. package/src/epub/preferences/EpubPreferences.ts +183 -0
  15. package/src/epub/preferences/EpubPreferencesEditor.ts +501 -0
  16. package/src/epub/preferences/EpubSettings.ts +212 -0
  17. package/src/epub/preferences/guards.ts +86 -0
  18. package/src/epub/preferences/index.ts +4 -0
  19. package/src/helpers/dimensions.ts +13 -0
  20. package/src/helpers/index.ts +1 -0
  21. package/src/helpers/lineLength.ts +293 -0
  22. package/src/helpers/sML.ts +18 -1
  23. package/src/index.ts +2 -1
  24. package/src/preferences/Configurable.ts +16 -0
  25. package/src/preferences/Preference.ts +272 -0
  26. package/src/preferences/PreferencesEditor.ts +6 -0
  27. package/src/preferences/Types.ts +39 -0
  28. package/src/preferences/index.ts +4 -0
  29. package/types/src/epub/EpubNavigator.d.ts +27 -3
  30. package/types/src/epub/css/Properties.d.ts +177 -0
  31. package/types/src/epub/css/ReadiumCSS.d.ts +32 -0
  32. package/types/src/epub/css/index.d.ts +2 -0
  33. package/types/src/epub/frame/FrameBlobBuilder.d.ts +5 -1
  34. package/types/src/epub/frame/FrameManager.d.ts +5 -0
  35. package/types/src/epub/frame/FramePoolManager.d.ts +8 -1
  36. package/types/src/epub/fxl/FXLFramePoolManager.d.ts +1 -3
  37. package/types/src/epub/index.d.ts +2 -0
  38. package/types/src/epub/preferences/EpubDefaults.d.ts +82 -0
  39. package/types/src/epub/preferences/EpubPreferences.d.ts +86 -0
  40. package/types/src/epub/preferences/EpubPreferencesEditor.d.ts +53 -0
  41. package/types/src/epub/preferences/EpubSettings.d.ts +85 -0
  42. package/types/src/epub/preferences/guards.d.ts +9 -0
  43. package/types/src/epub/preferences/index.d.ts +4 -0
  44. package/types/src/helpers/dimensions.d.ts +7 -0
  45. package/types/src/helpers/index.d.ts +1 -0
  46. package/types/src/helpers/lineLength.d.ts +68 -0
  47. package/types/src/helpers/sML.d.ts +6 -1
  48. package/types/src/index.d.ts +1 -0
  49. package/types/src/preferences/Configurable.d.ts +13 -0
  50. package/types/src/preferences/Preference.d.ts +117 -0
  51. package/types/src/preferences/PreferencesEditor.d.ts +5 -0
  52. package/types/src/preferences/PreferencesSerializer.d.ts +5 -0
  53. package/types/src/preferences/Types.d.ts +24 -0
  54. package/types/src/preferences/index.d.ts +4 -0
  55. package/LICENSE +0 -28
@@ -0,0 +1,501 @@
1
+ import { EPUBLayout, Metadata, ReadingProgression } from "@readium/shared";
2
+ import { IPreferencesEditor } from "../../preferences/PreferencesEditor";
3
+ import { EpubPreferences } from "./EpubPreferences";
4
+ import { EpubSettings } from "./EpubSettings";
5
+ import { BooleanPreference, EnumPreference, Preference, RangePreference } from "../../preferences/Preference";
6
+ import {
7
+ LayoutStrategy,
8
+ TextAlignment,
9
+ Theme,
10
+ fontSizeRangeConfig,
11
+ fontWeightRangeConfig,
12
+ fontWidthRangeConfig
13
+ } from "../../preferences/Types";
14
+
15
+ import dayMode from "@readium/css/css/vars/day.json";
16
+
17
+ // WIP: will change cos’ of all the missing pieces
18
+ export class EpubPreferencesEditor implements IPreferencesEditor {
19
+ preferences: EpubPreferences;
20
+ private settings: EpubSettings;
21
+ private metadata: Metadata | null;
22
+ private layout: EPUBLayout;
23
+
24
+ constructor(initialPreferences: EpubPreferences, settings: EpubSettings, metadata: Metadata) {
25
+ this.preferences = initialPreferences;
26
+ this.settings = settings;
27
+ this.metadata = metadata;
28
+ this.layout = this.metadata?.getPresentation()?.layout || EPUBLayout.reflowable;
29
+ }
30
+
31
+ clear() {
32
+ this.preferences = new EpubPreferences({ optimalLineLength: 65 });
33
+ }
34
+
35
+ private updatePreference<K extends keyof EpubPreferences>(key: K, value: EpubPreferences[K]) {
36
+ this.preferences[key] = value;
37
+ }
38
+
39
+ get backgroundColor(): Preference<string> {
40
+ return new Preference<string>({
41
+ initialValue: this.preferences.backgroundColor,
42
+ effectiveValue: this.settings.backgroundColor || dayMode.RS__backgroundColor,
43
+ isEffective: this.preferences.backgroundColor !== null,
44
+ onChange: (newValue: string | null | undefined) => {
45
+ this.updatePreference("backgroundColor", newValue || null);
46
+ }
47
+ });
48
+ }
49
+ get blendFilter(): BooleanPreference {
50
+ return new BooleanPreference({
51
+ initialValue: this.preferences.blendFilter,
52
+ effectiveValue: this.settings.blendFilter || false,
53
+ isEffective: this.preferences.blendFilter !== null,
54
+ onChange: (newValue: boolean | null | undefined) => {
55
+ this.updatePreference("blendFilter", newValue || null);
56
+ }
57
+ });
58
+ }
59
+
60
+ get columnCount(): Preference<number> {
61
+ return new Preference<number>({
62
+ initialValue: this.preferences.columnCount,
63
+ effectiveValue: this.settings.columnCount || null,
64
+ isEffective: this.layout === EPUBLayout.reflowable && !this.settings.scroll,
65
+ onChange: (newValue: number | null | undefined) => {
66
+ this.updatePreference("columnCount", newValue || null);
67
+ }
68
+ });
69
+ }
70
+
71
+ get constraint(): Preference<number> {
72
+ return new Preference<number>({
73
+ initialValue: this.preferences.constraint,
74
+ effectiveValue: this.preferences.constraint || 0,
75
+ isEffective: true,
76
+ onChange: (newValue: number | null | undefined) => {
77
+ this.updatePreference("constraint", newValue || null);
78
+ }
79
+ })
80
+ }
81
+
82
+ get darkenFilter(): RangePreference<number> {
83
+ return new RangePreference<number>({
84
+ initialValue: typeof this.preferences.darkenFilter === "boolean" ? 100 : this.preferences.darkenFilter,
85
+ effectiveValue: typeof this.settings.darkenFilter === "boolean" ? 100 : this.settings.darkenFilter || 0,
86
+ isEffective: this.settings.darkenFilter !== null,
87
+ onChange: (newValue: number | boolean | null | undefined) => {
88
+ this.updatePreference("darkenFilter", newValue || null);
89
+ },
90
+ supportedRange: [0, 100],
91
+ step: 1
92
+ });
93
+ }
94
+
95
+ get deprecatedFontSize(): BooleanPreference {
96
+ return new BooleanPreference({
97
+ initialValue: this.preferences.deprecatedFontSize,
98
+ effectiveValue: CSS.supports("zoom", "1") ? this.settings.deprecatedFontSize || false : true,
99
+ isEffective: this.layout === EPUBLayout.reflowable,
100
+ onChange: (newValue: boolean | null | undefined) => {
101
+ this.updatePreference("deprecatedFontSize", newValue || null);
102
+ }
103
+ });
104
+ }
105
+
106
+ get fontFamily(): Preference<string> {
107
+ return new Preference<string>({
108
+ initialValue: this.preferences.fontFamily,
109
+ effectiveValue: this.settings.fontFamily || null,
110
+ isEffective: this.layout === EPUBLayout.reflowable,
111
+ onChange: (newValue: string | null | undefined) => {
112
+ this.updatePreference("fontFamily", newValue || null);
113
+ }
114
+ });
115
+ }
116
+
117
+ get fontSize(): RangePreference<number> {
118
+ return new RangePreference<number>({
119
+ initialValue: this.preferences.fontSize,
120
+ effectiveValue: this.settings.fontSize || 1,
121
+ isEffective: this.layout === EPUBLayout.reflowable,
122
+ onChange: (newValue: number | null | undefined) => {
123
+ this.updatePreference("fontSize", newValue || null);
124
+ },
125
+ supportedRange: fontSizeRangeConfig.range,
126
+ step: fontSizeRangeConfig.step
127
+ });
128
+ }
129
+
130
+ get fontSizeNormalize(): BooleanPreference {
131
+ return new BooleanPreference({
132
+ initialValue: this.preferences.fontSizeNormalize,
133
+ effectiveValue: this.settings.fontSizeNormalize || false,
134
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.fontSizeNormalize !== null,
135
+ onChange: (newValue: boolean | null | undefined) => {
136
+ this.updatePreference("fontSizeNormalize", newValue || null);
137
+ }
138
+ });
139
+ }
140
+
141
+ get fontOpticalSizing(): BooleanPreference {
142
+ return new BooleanPreference({
143
+ initialValue: this.preferences.fontOpticalSizing,
144
+ effectiveValue: this.settings.fontOpticalSizing || true,
145
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.fontOpticalSizing !== null,
146
+ onChange: (newValue: boolean | null | undefined) => {
147
+ this.updatePreference("fontOpticalSizing", newValue || null);
148
+ }
149
+ });
150
+ }
151
+
152
+ get fontOverride(): BooleanPreference {
153
+ return new BooleanPreference({
154
+ initialValue: this.preferences.fontOverride,
155
+ effectiveValue: this.settings.fontOverride || false,
156
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.fontOverride !== null,
157
+ onChange: (newValue: boolean | null | undefined) => {
158
+ this.updatePreference("fontOverride", newValue || null);
159
+ }
160
+ });
161
+ }
162
+
163
+ get fontWeight(): RangePreference<number> {
164
+ return new RangePreference<number>({
165
+ initialValue: this.preferences.fontWeight,
166
+ effectiveValue: this.settings.fontWeight || 400,
167
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.fontWeight !== null,
168
+ onChange: (newValue: number | null | undefined) => {
169
+ this.updatePreference("fontWeight", newValue || null);
170
+ },
171
+ supportedRange: fontWeightRangeConfig.range,
172
+ step: fontWeightRangeConfig.step
173
+ });
174
+ }
175
+
176
+ get fontWidth(): RangePreference<number> {
177
+ return new RangePreference<number>({
178
+ initialValue: this.preferences.fontWidth,
179
+ effectiveValue: this.settings.fontWidth || 100,
180
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.fontWidth !== null,
181
+ onChange: (newValue: number | null | undefined) => {
182
+ this.updatePreference("fontWidth", newValue || null);
183
+ },
184
+ supportedRange: fontWidthRangeConfig.range,
185
+ step: fontWidthRangeConfig.step
186
+ });
187
+ }
188
+
189
+ get hyphens(): BooleanPreference {
190
+ return new BooleanPreference({
191
+ initialValue: this.preferences.hyphens,
192
+ effectiveValue: this.settings.hyphens || false,
193
+ isEffective: this.layout === EPUBLayout.reflowable && this.metadata?.effectiveReadingProgression === ReadingProgression.ltr && this.preferences.hyphens !== null,
194
+ onChange: (newValue: boolean | null | undefined) => {
195
+ this.updatePreference("hyphens", newValue || null);
196
+ }
197
+ });
198
+ }
199
+
200
+ get invertFilter(): RangePreference<number> {
201
+ return new RangePreference<number>({
202
+ initialValue: typeof this.preferences.invertFilter === "boolean" ? 100 : this.preferences.invertFilter,
203
+ effectiveValue: typeof this.settings.invertFilter === "boolean" ? 100 : this.settings.invertFilter || 0,
204
+ isEffective: this.settings.invertFilter !== null,
205
+ onChange: (newValue: number | boolean | null | undefined) => {
206
+ this.updatePreference("invertFilter", newValue || null);
207
+ },
208
+ supportedRange: [0, 100],
209
+ step: 1
210
+ });
211
+ }
212
+
213
+ get invertGaijiFilter(): RangePreference<number> {
214
+ return new RangePreference<number>({
215
+ initialValue: typeof this.preferences.invertGaijiFilter === "boolean" ? 100 : this.preferences.invertGaijiFilter,
216
+ effectiveValue: typeof this.settings.invertGaijiFilter === "boolean" ? 100 : this.settings.invertGaijiFilter || 0,
217
+ isEffective: this.preferences.invertGaijiFilter !== null,
218
+ onChange: (newValue: number | boolean | null | undefined) => {
219
+ this.updatePreference("invertGaijiFilter", newValue || null);
220
+ },
221
+ supportedRange: [0, 100],
222
+ step: 1
223
+ });
224
+ }
225
+
226
+ get iPadOSPatch(): BooleanPreference {
227
+ return new BooleanPreference({
228
+ initialValue: this.preferences.iPadOSPatch,
229
+ effectiveValue: this.settings.iPadOSPatch || false,
230
+ isEffective: this.layout === EPUBLayout.reflowable,
231
+ onChange: (newValue: boolean | null | undefined) => {
232
+ this.updatePreference("iPadOSPatch", newValue || null);
233
+ }
234
+ });
235
+ }
236
+
237
+ get layoutStrategy(): EnumPreference<LayoutStrategy> {
238
+ return new EnumPreference<LayoutStrategy>({
239
+ initialValue: this.preferences.layoutStrategy,
240
+ effectiveValue: this.settings.layoutStrategy,
241
+ isEffective: this.layout === EPUBLayout.reflowable,
242
+ onChange: (newValue: LayoutStrategy | null | undefined) => {
243
+ this.updatePreference("layoutStrategy", newValue || null);
244
+ },
245
+ supportedValues: Object.values(LayoutStrategy)
246
+ })
247
+ }
248
+
249
+ get letterSpacing(): RangePreference<number> {
250
+ return new RangePreference<number>({
251
+ initialValue: this.preferences.letterSpacing,
252
+ effectiveValue: this.settings.letterSpacing || 0,
253
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.letterSpacing !== null,
254
+ onChange: (newValue: number | null | undefined) => {
255
+ this.updatePreference("letterSpacing", newValue || null);
256
+ },
257
+ supportedRange: [0, 1],
258
+ step: .125
259
+ });
260
+ }
261
+
262
+ get ligatures(): BooleanPreference {
263
+ return new BooleanPreference({
264
+ initialValue: this.preferences.ligatures,
265
+ effectiveValue: this.settings.ligatures || true,
266
+ isEffective: this.layout === EPUBLayout.reflowable
267
+ && this.metadata?.languages?.some(lang => lang === "ar" || lang === "fa")
268
+ && this.preferences.ligatures !== null || false,
269
+ onChange: (newValue: boolean | null | undefined) => {
270
+ this.updatePreference("ligatures", newValue || null);
271
+ }
272
+ });
273
+ }
274
+
275
+ get lineHeight(): RangePreference<number> {
276
+ return new RangePreference<number>({
277
+ initialValue: this.preferences.lineHeight,
278
+ effectiveValue: this.settings.lineHeight,
279
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.lineHeight !== null,
280
+ onChange: (newValue: number | null | undefined) => {
281
+ this.updatePreference("lineHeight", newValue || null);
282
+ },
283
+ supportedRange: [1, 2],
284
+ step: .1
285
+ });
286
+ }
287
+
288
+ get lineLength(): RangePreference<number> {
289
+ return new RangePreference<number>({
290
+ initialValue: this.preferences.lineLength,
291
+ effectiveValue: this.settings.lineLength || this.settings.optimalLineLength,
292
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.lineLength !== null,
293
+ onChange: (newValue: number | null | undefined) => {
294
+ this.updatePreference("lineLength", newValue || null);
295
+ },
296
+ supportedRange: [20, 100],
297
+ step: 1
298
+ });
299
+ }
300
+
301
+ get linkColor(): Preference<string> {
302
+ return new Preference<string>({
303
+ initialValue: this.preferences.linkColor,
304
+ effectiveValue: this.settings.linkColor || dayMode.RS__linkColor,
305
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.linkColor !== null,
306
+ onChange: (newValue: string | null | undefined) => {
307
+ this.updatePreference("linkColor", newValue || null);
308
+ }
309
+ });
310
+ }
311
+
312
+ get maximalLineLength(): RangePreference<number> {
313
+ return new RangePreference<number>({
314
+ initialValue: this.preferences.maximalLineLength,
315
+ effectiveValue: this.settings.maximalLineLength,
316
+ isEffective: this.layout === EPUBLayout.reflowable,
317
+ onChange: (newValue: number | null | undefined) => {
318
+ this.updatePreference("maximalLineLength", newValue);
319
+ },
320
+ supportedRange: [20, 100],
321
+ step: 1
322
+ });
323
+ }
324
+
325
+ get minimalLineLength(): RangePreference<number> {
326
+ return new RangePreference<number>({
327
+ initialValue: this.preferences.minimalLineLength,
328
+ effectiveValue: this.settings.minimalLineLength,
329
+ isEffective: this.layout === EPUBLayout.reflowable,
330
+ onChange: (newValue: number | null | undefined) => {
331
+ this.updatePreference("minimalLineLength", newValue);
332
+ },
333
+ supportedRange: [20, 100],
334
+ step: 1
335
+ });
336
+ }
337
+
338
+ get noRuby(): BooleanPreference {
339
+ return new BooleanPreference({
340
+ initialValue: this.preferences.noRuby,
341
+ effectiveValue: this.settings.noRuby || false,
342
+ isEffective: this.layout === EPUBLayout.reflowable && this.metadata?.languages?.includes("ja") || false,
343
+ onChange: (newValue: boolean | null | undefined) => {
344
+ this.updatePreference("noRuby", newValue || null);
345
+ }
346
+ });
347
+ }
348
+
349
+ get optimalLineLength(): RangePreference<number> {
350
+ return new RangePreference<number>({
351
+ initialValue: this.preferences.optimalLineLength,
352
+ effectiveValue: this.settings.optimalLineLength,
353
+ isEffective: this.layout === EPUBLayout.reflowable && !this.settings.lineLength,
354
+ onChange: (newValue: number | null | undefined) => {
355
+ this.updatePreference("optimalLineLength", newValue as number);
356
+ },
357
+ supportedRange: [20, 100],
358
+ step: 1
359
+ });
360
+ }
361
+
362
+ get pageGutter(): Preference<number> {
363
+ return new Preference<number>({
364
+ initialValue: this.preferences.pageGutter,
365
+ effectiveValue: this.settings.pageGutter,
366
+ isEffective: this.layout === EPUBLayout.reflowable,
367
+ onChange: (newValue: number | null | undefined) => {
368
+ this.updatePreference("pageGutter", newValue || null);
369
+ }
370
+ });
371
+ }
372
+
373
+ get paragraphIndent(): RangePreference<number> {
374
+ return new RangePreference<number>({
375
+ initialValue: this.preferences.paragraphIndent,
376
+ effectiveValue: this.settings.paragraphIndent || 0,
377
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.paragraphIndent !== null,
378
+ onChange: (newValue: number | null | undefined) => {
379
+ this.updatePreference("paragraphIndent", newValue || null);
380
+ },
381
+ supportedRange: [0, 3],
382
+ step: .25
383
+ });
384
+ }
385
+
386
+ get paragraphSpacing(): RangePreference<number> {
387
+ return new RangePreference<number>({
388
+ initialValue: this.preferences.paragraphSpacing,
389
+ effectiveValue: this.settings.paragraphSpacing || 0,
390
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.paragraphSpacing !== null,
391
+ onChange: (newValue: number | null | undefined) => {
392
+ this.updatePreference("paragraphSpacing", newValue || null);
393
+ },
394
+ supportedRange: [0, 3],
395
+ step: .25
396
+ });
397
+ }
398
+
399
+ get scroll(): BooleanPreference {
400
+ return new BooleanPreference({
401
+ initialValue: this.preferences.scroll,
402
+ effectiveValue: this.settings.scroll || false,
403
+ isEffective: this.layout === EPUBLayout.reflowable,
404
+ onChange: (newValue: boolean | null | undefined) => {
405
+ this.updatePreference("scroll", newValue || null);
406
+ }
407
+ });
408
+ }
409
+
410
+ get selectionBackgroundColor(): Preference<string> {
411
+ return new Preference<string>({
412
+ initialValue: this.preferences.selectionBackgroundColor,
413
+ effectiveValue: this.settings.selectionBackgroundColor || dayMode.RS__selectionBackgroundColor,
414
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.selectionBackgroundColor !== null,
415
+ onChange: (newValue: string | null | undefined) => {
416
+ this.updatePreference("selectionBackgroundColor", newValue || null);
417
+ }
418
+ });
419
+ }
420
+
421
+ get selectionTextColor(): Preference<string> {
422
+ return new Preference<string>({
423
+ initialValue: this.preferences.selectionTextColor,
424
+ effectiveValue: this.settings.selectionTextColor || dayMode.RS__selectionTextColor,
425
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.selectionTextColor !== null,
426
+ onChange: (newValue: string | null | undefined) => {
427
+ this.updatePreference("selectionTextColor", newValue || null);
428
+ }
429
+ });
430
+ }
431
+
432
+ get textAlign(): EnumPreference<TextAlignment> {
433
+ return new EnumPreference<TextAlignment>({
434
+ initialValue: this.preferences.textAlign,
435
+ effectiveValue: this.settings.textAlign || TextAlignment.start,
436
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.textAlign !== null,
437
+ onChange: (newValue: TextAlignment | null | undefined) => {
438
+ this.updatePreference("textAlign", newValue || null);
439
+ },
440
+ supportedValues: Object.values(TextAlignment)
441
+ });
442
+ }
443
+
444
+ get textColor(): Preference<string> {
445
+ return new Preference<string>({
446
+ initialValue: this.preferences.textColor,
447
+ effectiveValue: this.settings.textColor || dayMode.RS__textColor,
448
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.textColor !== null,
449
+ onChange: (newValue: string | null | undefined) => {
450
+ this.updatePreference("textColor", newValue || null);
451
+ }
452
+ });
453
+ }
454
+
455
+ get textNormalization(): BooleanPreference {
456
+ return new BooleanPreference({
457
+ initialValue: this.preferences.textNormalization,
458
+ effectiveValue: this.settings.textNormalization || false,
459
+ isEffective: this.layout === EPUBLayout.reflowable,
460
+ onChange: (newValue: boolean | null | undefined) => {
461
+ this.updatePreference("textNormalization", newValue || null);
462
+ }
463
+ });
464
+ }
465
+
466
+ get theme(): EnumPreference<Theme> {
467
+ return new EnumPreference<Theme>({
468
+ initialValue: this.preferences.theme,
469
+ effectiveValue: this.settings.theme || Theme.day,
470
+ isEffective: this.layout === EPUBLayout.reflowable,
471
+ onChange: (newValue: Theme | null | undefined) => {
472
+ this.updatePreference("theme", newValue || null);
473
+ },
474
+ supportedValues: Object.values(Theme)
475
+ });
476
+ }
477
+
478
+ get visitedColor(): Preference<string> {
479
+ return new Preference<string>({
480
+ initialValue: this.preferences.visitedColor,
481
+ effectiveValue: this.settings.visitedColor || dayMode.RS__visitedColor,
482
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.visitedColor !== null,
483
+ onChange: (newValue: string | null | undefined) => {
484
+ this.updatePreference("visitedColor", newValue || null);
485
+ }
486
+ });
487
+ }
488
+
489
+ get wordSpacing(): RangePreference<number> {
490
+ return new RangePreference<number>({
491
+ initialValue: this.preferences.wordSpacing,
492
+ effectiveValue: this.settings.wordSpacing || 0,
493
+ isEffective: this.layout === EPUBLayout.reflowable && this.preferences.wordSpacing !== null,
494
+ onChange: (newValue: number | null | undefined) => {
495
+ this.updatePreference("wordSpacing", newValue || null);
496
+ },
497
+ supportedRange: [0, 2],
498
+ step: 0.125
499
+ });
500
+ }
501
+ }