@edrlab/thorium-web 1.3.0 → 1.4.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 (111) hide show
  1. package/dist/{ThPreferencesAdapter-D0rzsGRl.d.mts → ThPreferencesAdapter-L1H6gzmu.d.mts} +23 -6
  2. package/dist/{ThSettingsWrapper-BXuRgdqp.d.mts → ThSettingsWrapper-DWEA4hYF.d.mts} +1 -1
  3. package/dist/{actions-BLAr0oaM.d.mts → actions-BjeRjaJU.d.mts} +1 -1
  4. package/dist/{actionsReducer-BhG1wicI.d.mts → actionsReducer-B32cq2mB.d.mts} +11 -4
  5. package/dist/{chunk-L4XGZAZ5.mjs → chunk-2YRT7RNW.mjs} +20 -3
  6. package/dist/chunk-2YRT7RNW.mjs.map +1 -0
  7. package/dist/{chunk-UCTMVCW7.mjs → chunk-6MONB2DN.mjs} +91 -63
  8. package/dist/chunk-6MONB2DN.mjs.map +1 -0
  9. package/dist/chunk-A575ZW4A.mjs +10 -0
  10. package/dist/chunk-A575ZW4A.mjs.map +1 -0
  11. package/dist/chunk-AE6P4KJB.mjs +13 -0
  12. package/dist/chunk-AE6P4KJB.mjs.map +1 -0
  13. package/dist/{chunk-OWHP7ONM.mjs → chunk-IVXRCKWR.mjs} +4 -4
  14. package/dist/{chunk-OWHP7ONM.mjs.map → chunk-IVXRCKWR.mjs.map} +1 -1
  15. package/dist/{chunk-RRVLWDT3.mjs → chunk-KVUG6BNI.mjs} +92 -27
  16. package/dist/chunk-KVUG6BNI.mjs.map +1 -0
  17. package/dist/{chunk-IYAFKTPL.mjs → chunk-NUXGQWED.mjs} +7 -5
  18. package/dist/chunk-NUXGQWED.mjs.map +1 -0
  19. package/dist/{chunk-4ODYHZKD.mjs → chunk-P6ILEQ5P.mjs} +49 -51
  20. package/dist/chunk-P6ILEQ5P.mjs.map +1 -0
  21. package/dist/{chunk-D7MFLHXV.mjs → chunk-PXXWEMNL.mjs} +951 -592
  22. package/dist/chunk-PXXWEMNL.mjs.map +1 -0
  23. package/dist/{chunk-T2E6MRVP.mjs → chunk-QUSGPT5M.mjs} +11 -12
  24. package/dist/chunk-QUSGPT5M.mjs.map +1 -0
  25. package/dist/{chunk-XBZWGRDM.mjs → chunk-SZAVAQ6S.mjs} +30 -6
  26. package/dist/chunk-SZAVAQ6S.mjs.map +1 -0
  27. package/dist/{chunk-3LDWKC5N.mjs → chunk-T5ENYSDJ.mjs} +5 -5
  28. package/dist/chunk-T5ENYSDJ.mjs.map +1 -0
  29. package/dist/{chunk-4TVEDX7L.mjs → chunk-TSLTLQ6O.mjs} +127 -41
  30. package/dist/chunk-TSLTLQ6O.mjs.map +1 -0
  31. package/dist/{chunk-ITDBOMY5.mjs → chunk-VENFFPK2.mjs} +3 -3
  32. package/dist/{chunk-ITDBOMY5.mjs.map → chunk-VENFFPK2.mjs.map} +1 -1
  33. package/dist/{chunk-YZ3KCMKY.mjs → chunk-WLVE3WNW.mjs} +238 -58
  34. package/dist/chunk-WLVE3WNW.mjs.map +1 -0
  35. package/dist/chunk-XRFLDNAY.mjs +3913 -0
  36. package/dist/chunk-XRFLDNAY.mjs.map +1 -0
  37. package/dist/components/Audio/index.d.mts +12 -12
  38. package/dist/components/Audio/index.mjs +14 -14
  39. package/dist/components/Epub/index.css +3 -3
  40. package/dist/components/Epub/index.css.map +1 -1
  41. package/dist/components/Epub/index.d.mts +12 -12
  42. package/dist/components/Epub/index.mjs +15 -15
  43. package/dist/components/Misc/index.css +2 -2
  44. package/dist/components/Misc/index.css.map +1 -1
  45. package/dist/components/Misc/index.mjs +4 -4
  46. package/dist/components/Reader/index.css +5 -5
  47. package/dist/components/Reader/index.css.map +1 -1
  48. package/dist/components/Reader/index.d.mts +11 -11
  49. package/dist/components/Reader/index.mjs +26 -24
  50. package/dist/components/Reader/index.mjs.map +1 -1
  51. package/dist/components/WebPub/index.css +3 -3
  52. package/dist/components/WebPub/index.css.map +1 -1
  53. package/dist/components/WebPub/index.d.mts +12 -12
  54. package/dist/components/WebPub/index.mjs +15 -15
  55. package/dist/core/Components/index.d.mts +11 -21
  56. package/dist/core/Components/index.mjs +1 -1
  57. package/dist/core/Helpers/index.d.mts +1 -1
  58. package/dist/core/Helpers/index.mjs +2 -3
  59. package/dist/core/Hooks/index.d.mts +12 -8
  60. package/dist/core/Hooks/index.mjs +1 -1
  61. package/dist/i18n/index.mjs +3 -6
  62. package/dist/lib/index.d.mts +49 -20
  63. package/dist/lib/index.mjs +3 -2
  64. package/dist/locales/da/thorium-shared.json +3 -0
  65. package/dist/locales/en/thorium-shared.json +24 -2
  66. package/dist/locales/en/thorium-web.json +2 -2
  67. package/dist/locales/es/thorium-shared.json +364 -0
  68. package/dist/locales/es/thorium-web.json +130 -0
  69. package/dist/locales/et/thorium-shared.json +121 -9
  70. package/dist/locales/et/thorium-web.json +32 -1
  71. package/dist/locales/fi/thorium-shared.json +42 -4
  72. package/dist/locales/fi/thorium-web.json +36 -2
  73. package/dist/locales/fr/thorium-shared.json +108 -1
  74. package/dist/locales/fr/thorium-web.json +121 -86
  75. package/dist/locales/it/thorium-shared.json +108 -1
  76. package/dist/locales/it/thorium-web.json +15 -2
  77. package/dist/locales/lt/thorium-web.json +10 -1
  78. package/dist/locales/pl/thorium-web.json +1 -1
  79. package/dist/locales/pt-BR/thorium-shared.json +6 -0
  80. package/dist/locales/pt-BR/thorium-web.json +88 -88
  81. package/dist/locales/pt-PT/thorium-shared.json +91 -0
  82. package/dist/locales/pt-PT/thorium-web.json +15 -3
  83. package/dist/locales/sv/thorium-shared.json +108 -2
  84. package/dist/locales/sv/thorium-web.json +15 -3
  85. package/dist/locales/tr/thorium-shared.json +42 -0
  86. package/dist/preferences/index.d.mts +59 -13
  87. package/dist/preferences/index.mjs +6 -6
  88. package/dist/{settingsReducer-Bu1zeveu.d.mts → settingsReducer-DLaT2wUB.d.mts} +14 -2
  89. package/dist/{ui-nBv8gfr0.d.mts → ui-DnZZhozX.d.mts} +1 -1
  90. package/dist/{useAudioNavigator-C5aW4-eT.d.mts → useAudioNavigator-CWXyNWq1.d.mts} +3 -1
  91. package/dist/{useContrast-2t429O9O.d.mts → useContrast-Bo7cDw_X.d.mts} +5 -1
  92. package/dist/{usePreferences-VaBf46eP.d.mts → usePreferences-D8NU1yhP.d.mts} +6 -8
  93. package/dist/{useReaderTransitions-JDzlBFsu.d.mts → useReaderTransitions-BQGzKeY2.d.mts} +61 -10
  94. package/package.json +12 -11
  95. package/dist/chunk-3LDWKC5N.mjs.map +0 -1
  96. package/dist/chunk-4ODYHZKD.mjs.map +0 -1
  97. package/dist/chunk-4TVEDX7L.mjs.map +0 -1
  98. package/dist/chunk-7CGMWOZN.mjs +0 -20
  99. package/dist/chunk-7CGMWOZN.mjs.map +0 -1
  100. package/dist/chunk-C236BQQB.mjs +0 -1725
  101. package/dist/chunk-C236BQQB.mjs.map +0 -1
  102. package/dist/chunk-D7MFLHXV.mjs.map +0 -1
  103. package/dist/chunk-IYAFKTPL.mjs.map +0 -1
  104. package/dist/chunk-L4XGZAZ5.mjs.map +0 -1
  105. package/dist/chunk-RRVLWDT3.mjs.map +0 -1
  106. package/dist/chunk-T2E6MRVP.mjs.map +0 -1
  107. package/dist/chunk-UCTMVCW7.mjs.map +0 -1
  108. package/dist/chunk-WECWPYZB.mjs +0 -1950
  109. package/dist/chunk-WECWPYZB.mjs.map +0 -1
  110. package/dist/chunk-XBZWGRDM.mjs.map +0 -1
  111. package/dist/chunk-YZ3KCMKY.mjs.map +0 -1
@@ -0,0 +1,3913 @@
1
+ import { propsToCSSVars } from './chunk-2YRT7RNW.mjs';
2
+ import { useBreakpoints, useForcedColors, useMonochrome, useReducedMotion, useReducedTransparency } from './chunk-GFSLVQIG.mjs';
3
+ import { ThGlobalPreferencesContext } from './chunk-AE6P4KJB.mjs';
4
+ import { useAppSelector } from './chunk-A575ZW4A.mjs';
5
+ import { useColorScheme, useContrast } from './chunk-NQ2ZSGCX.mjs';
6
+ import { isSupportedLocale, supportedLocales } from './chunk-B3WDMWCT.mjs';
7
+ import { createContext, useMemo, useState, useCallback, useEffect, useContext, useRef } from 'react';
8
+ import { I18nProvider, useLocale } from 'react-aria';
9
+ import { jsx } from 'react/jsx-runtime';
10
+ import fontStacks from '@readium/css/css/vars/fontStacks.json';
11
+ import ReadiumCSSColors from '@readium/css/css/vars/colors.json';
12
+
13
+ // src/preferences/globalPreferences.ts
14
+ var createGlobalPreferences = (params) => {
15
+ if (params.locale) {
16
+ const languageCode = params.locale.split("-")[0];
17
+ if (!isSupportedLocale(languageCode)) {
18
+ console.warn(`Locale "${params.locale}" is not supported. Supported locales: ${supportedLocales.join(", ")}. Falling back to browser/OS language settings.`);
19
+ return { ...params, locale: void 0 };
20
+ }
21
+ }
22
+ return params;
23
+ };
24
+
25
+ // src/preferences/adapters/ThGlobalMemoryPreferencesAdapter.ts
26
+ var ThGlobalMemoryPreferencesAdapter = class {
27
+ preferences;
28
+ listeners = /* @__PURE__ */ new Set();
29
+ constructor(initialPreferences = {}) {
30
+ this.preferences = { ...initialPreferences };
31
+ }
32
+ getPreferences() {
33
+ return { ...this.preferences };
34
+ }
35
+ setPreferences(prefs) {
36
+ this.preferences = { ...prefs };
37
+ this.listeners.forEach((cb) => cb({ ...this.preferences }));
38
+ }
39
+ subscribe(callback) {
40
+ this.listeners.add(callback);
41
+ callback(this.getPreferences());
42
+ }
43
+ unsubscribe(callback) {
44
+ this.listeners.delete(callback);
45
+ }
46
+ };
47
+ var ThDirectionSetter = ({ children }) => {
48
+ const { direction } = useLocale();
49
+ useEffect(() => {
50
+ document.documentElement.dir = direction;
51
+ }, [direction]);
52
+ return children;
53
+ };
54
+ function ThGlobalPreferencesProvider({ adapter, initialPreferences, children }) {
55
+ const effectiveAdapter = useMemo(
56
+ () => adapter || new ThGlobalMemoryPreferencesAdapter(initialPreferences),
57
+ [adapter, initialPreferences]
58
+ );
59
+ const [preferences, setPreferences] = useState(
60
+ () => createGlobalPreferences(initialPreferences ?? {})
61
+ );
62
+ const handlePreferenceChange = useCallback((newPrefs) => {
63
+ setPreferences((prev) => {
64
+ const validated = createGlobalPreferences(newPrefs);
65
+ return JSON.stringify(prev) === JSON.stringify(validated) ? prev : validated;
66
+ });
67
+ }, []);
68
+ useEffect(() => {
69
+ effectiveAdapter.subscribe(handlePreferenceChange);
70
+ return () => effectiveAdapter.unsubscribe(handlePreferenceChange);
71
+ }, [effectiveAdapter, handlePreferenceChange]);
72
+ const contextValue = useMemo(() => ({
73
+ preferences,
74
+ updatePreferences: (newPrefs) => {
75
+ effectiveAdapter.setPreferences(newPrefs);
76
+ }
77
+ }), [preferences, effectiveAdapter]);
78
+ return /* @__PURE__ */ jsx(ThGlobalPreferencesContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(I18nProvider, { locale: preferences.locale, children: /* @__PURE__ */ jsx(ThDirectionSetter, { children }) }) });
79
+ }
80
+
81
+ // src/preferences/models/actions.ts
82
+ var ThActionsKeys = /* @__PURE__ */ ((ThActionsKeys3) => {
83
+ ThActionsKeys3["fullscreen"] = "fullscreen";
84
+ ThActionsKeys3["jumpToPosition"] = "jumpToPosition";
85
+ ThActionsKeys3["settings"] = "settings";
86
+ ThActionsKeys3["toc"] = "toc";
87
+ return ThActionsKeys3;
88
+ })(ThActionsKeys || {});
89
+ var ThDockingKeys = /* @__PURE__ */ ((ThDockingKeys3) => {
90
+ ThDockingKeys3["start"] = "dockingStart";
91
+ ThDockingKeys3["end"] = "dockingEnd";
92
+ ThDockingKeys3["transient"] = "dockingTransient";
93
+ return ThDockingKeys3;
94
+ })(ThDockingKeys || {});
95
+ var ThDockingTypes = /* @__PURE__ */ ((ThDockingTypes2) => {
96
+ ThDockingTypes2["none"] = "none";
97
+ ThDockingTypes2["both"] = "both";
98
+ ThDockingTypes2["start"] = "start";
99
+ ThDockingTypes2["end"] = "end";
100
+ return ThDockingTypes2;
101
+ })(ThDockingTypes || {});
102
+ var ThSheetTypes = /* @__PURE__ */ ((ThSheetTypes3) => {
103
+ ThSheetTypes3["popover"] = "popover";
104
+ ThSheetTypes3["compactPopover"] = "compactPopover";
105
+ ThSheetTypes3["modal"] = "modal";
106
+ ThSheetTypes3["fullscreen"] = "fullscreen";
107
+ ThSheetTypes3["dockedStart"] = "docked start";
108
+ ThSheetTypes3["dockedEnd"] = "docked end";
109
+ ThSheetTypes3["bottomSheet"] = "bottomSheet";
110
+ return ThSheetTypes3;
111
+ })(ThSheetTypes || {});
112
+ var ThSheetHeaderVariant = /* @__PURE__ */ ((ThSheetHeaderVariant2) => {
113
+ ThSheetHeaderVariant2["close"] = "close";
114
+ ThSheetHeaderVariant2["previous"] = "previous";
115
+ return ThSheetHeaderVariant2;
116
+ })(ThSheetHeaderVariant || {});
117
+ var defaultActionKeysObject = {
118
+ visibility: "partially" /* partially */,
119
+ shortcut: null
120
+ };
121
+ var defaultSettingsAction = {
122
+ visibility: "partially" /* partially */,
123
+ shortcut: null,
124
+ // `${ UnstableShortcutMetaKeywords.shift }+${ ShortcutMetaKeywords.alt }+P`,
125
+ sheet: {
126
+ defaultSheet: "popover" /* popover */,
127
+ breakpoints: {
128
+ ["compact" /* compact */]: "bottomSheet" /* bottomSheet */
129
+ }
130
+ },
131
+ docked: {
132
+ dockable: "none" /* none */,
133
+ width: 340
134
+ },
135
+ snapped: {
136
+ scrim: true,
137
+ peekHeight: 50,
138
+ minHeight: 30,
139
+ maxHeight: 100
140
+ }
141
+ };
142
+ var defaultFullscreenAction = {
143
+ visibility: "partially" /* partially */,
144
+ shortcut: null
145
+ };
146
+ var defaultTocAction = {
147
+ visibility: "partially" /* partially */,
148
+ shortcut: null,
149
+ // `${ UnstableShortcutMetaKeywords.shift }+${ ShortcutMetaKeywords.alt }+T`,
150
+ sheet: {
151
+ defaultSheet: "popover" /* popover */,
152
+ breakpoints: {
153
+ ["compact" /* compact */]: "fullscreen" /* fullscreen */,
154
+ ["medium" /* medium */]: "fullscreen" /* fullscreen */
155
+ }
156
+ },
157
+ docked: {
158
+ dockable: "both" /* both */,
159
+ dragIndicator: false,
160
+ width: 360,
161
+ minWidth: 320,
162
+ maxWidth: 450
163
+ }
164
+ };
165
+ var defaultJumpToPositionAction = {
166
+ visibility: "overflow" /* overflow */,
167
+ shortcut: null,
168
+ // `${ UnstableShortcutMetaKeywords.shift }+${ ShortcutMetaKeywords.alt }+J`,
169
+ sheet: {
170
+ defaultSheet: "popover" /* popover */,
171
+ breakpoints: {
172
+ ["compact" /* compact */]: "bottomSheet" /* bottomSheet */
173
+ }
174
+ },
175
+ docked: {
176
+ dockable: "none" /* none */
177
+ },
178
+ snapped: {
179
+ scrim: true,
180
+ minHeight: "content-height"
181
+ }
182
+ };
183
+
184
+ // src/preferences/models/settings.ts
185
+ var ThSettingsKeys = /* @__PURE__ */ ((ThSettingsKeys2) => {
186
+ ThSettingsKeys2["columns"] = "columns";
187
+ ThSettingsKeys2["fontFamily"] = "fontFamily";
188
+ ThSettingsKeys2["fontWeight"] = "fontWeight";
189
+ ThSettingsKeys2["hyphens"] = "hyphens";
190
+ ThSettingsKeys2["layout"] = "layout";
191
+ ThSettingsKeys2["letterSpacing"] = "letterSpacing";
192
+ ThSettingsKeys2["ligatures"] = "ligatures";
193
+ ThSettingsKeys2["lineHeight"] = "lineHeight";
194
+ ThSettingsKeys2["noRuby"] = "noRuby";
195
+ ThSettingsKeys2["paragraphIndent"] = "paragraphIndent";
196
+ ThSettingsKeys2["paragraphSpacing"] = "paragraphSpacing";
197
+ ThSettingsKeys2["publisherStyles"] = "publisherStyles";
198
+ ThSettingsKeys2["spacingGroup"] = "spacingGroup";
199
+ ThSettingsKeys2["spacingPresets"] = "spacingPresets";
200
+ ThSettingsKeys2["textAlign"] = "textAlign";
201
+ ThSettingsKeys2["textGroup"] = "textGroup";
202
+ ThSettingsKeys2["textNormalize"] = "textNormalize";
203
+ ThSettingsKeys2["theme"] = "theme";
204
+ ThSettingsKeys2["wordSpacing"] = "wordSpacing";
205
+ ThSettingsKeys2["zoom"] = "zoom";
206
+ return ThSettingsKeys2;
207
+ })(ThSettingsKeys || {});
208
+ var ThTextSettingsKeys = /* @__PURE__ */ ((ThTextSettingsKeys3) => {
209
+ ThTextSettingsKeys3["fontFamily"] = "fontFamily";
210
+ ThTextSettingsKeys3["fontWeight"] = "fontWeight";
211
+ ThTextSettingsKeys3["hyphens"] = "hyphens";
212
+ ThTextSettingsKeys3["ligatures"] = "ligatures";
213
+ ThTextSettingsKeys3["noRuby"] = "noRuby";
214
+ ThTextSettingsKeys3["textAlign"] = "textAlign";
215
+ ThTextSettingsKeys3["textNormalize"] = "textNormalize";
216
+ return ThTextSettingsKeys3;
217
+ })(ThTextSettingsKeys || {});
218
+ var ThSpacingSettingsKeys = /* @__PURE__ */ ((ThSpacingSettingsKeys3) => {
219
+ ThSpacingSettingsKeys3["letterSpacing"] = "letterSpacing";
220
+ ThSpacingSettingsKeys3["lineHeight"] = "lineHeight";
221
+ ThSpacingSettingsKeys3["paragraphIndent"] = "paragraphIndent";
222
+ ThSpacingSettingsKeys3["paragraphSpacing"] = "paragraphSpacing";
223
+ ThSpacingSettingsKeys3["publisherStyles"] = "publisherStyles";
224
+ ThSpacingSettingsKeys3["spacingPresets"] = "spacingPresets";
225
+ ThSpacingSettingsKeys3["wordSpacing"] = "wordSpacing";
226
+ return ThSpacingSettingsKeys3;
227
+ })(ThSpacingSettingsKeys || {});
228
+ var ThSettingsContainerKeys = /* @__PURE__ */ ((ThSettingsContainerKeys2) => {
229
+ ThSettingsContainerKeys2["initial"] = "initial";
230
+ ThSettingsContainerKeys2["text"] = "text";
231
+ ThSettingsContainerKeys2["spacing"] = "spacing";
232
+ return ThSettingsContainerKeys2;
233
+ })(ThSettingsContainerKeys || {});
234
+ var ThSettingsRangeVariant = /* @__PURE__ */ ((ThSettingsRangeVariant2) => {
235
+ ThSettingsRangeVariant2["slider"] = "slider";
236
+ ThSettingsRangeVariant2["incrementedSlider"] = "incrementedSlider";
237
+ ThSettingsRangeVariant2["numberField"] = "numberField";
238
+ ThSettingsRangeVariant2["sliderWithPresets"] = "sliderWithPresets";
239
+ ThSettingsRangeVariant2["presetsGroup"] = "presetsGroup";
240
+ return ThSettingsRangeVariant2;
241
+ })(ThSettingsRangeVariant || {});
242
+ var ThSettingsRangePlaceholder = /* @__PURE__ */ ((ThSettingsRangePlaceholder2) => {
243
+ ThSettingsRangePlaceholder2["range"] = "range";
244
+ ThSettingsRangePlaceholder2["none"] = "none";
245
+ return ThSettingsRangePlaceholder2;
246
+ })(ThSettingsRangePlaceholder || {});
247
+ var ThSpacingPresetKeys = /* @__PURE__ */ ((ThSpacingPresetKeys4) => {
248
+ ThSpacingPresetKeys4["publisher"] = "publisher";
249
+ ThSpacingPresetKeys4["tight"] = "tight";
250
+ ThSpacingPresetKeys4["balanced"] = "balanced";
251
+ ThSpacingPresetKeys4["loose"] = "loose";
252
+ ThSpacingPresetKeys4["accessible"] = "accessible";
253
+ ThSpacingPresetKeys4["custom"] = "custom";
254
+ return ThSpacingPresetKeys4;
255
+ })(ThSpacingPresetKeys || {});
256
+ var ThLayoutOptions = /* @__PURE__ */ ((ThLayoutOptions2) => {
257
+ ThLayoutOptions2["scroll"] = "scroll_option";
258
+ ThLayoutOptions2["paginated"] = "page_option";
259
+ return ThLayoutOptions2;
260
+ })(ThLayoutOptions || {});
261
+ var ThTextAlignOptions = /* @__PURE__ */ ((ThTextAlignOptions2) => {
262
+ ThTextAlignOptions2["publisher"] = "publisher";
263
+ ThTextAlignOptions2["start"] = "start";
264
+ ThTextAlignOptions2["justify"] = "justify";
265
+ return ThTextAlignOptions2;
266
+ })(ThTextAlignOptions || {});
267
+ var ThLineHeightOptions = /* @__PURE__ */ ((ThLineHeightOptions3) => {
268
+ ThLineHeightOptions3["publisher"] = "publisher";
269
+ ThLineHeightOptions3["small"] = "small";
270
+ ThLineHeightOptions3["medium"] = "medium";
271
+ ThLineHeightOptions3["large"] = "large";
272
+ return ThLineHeightOptions3;
273
+ })(ThLineHeightOptions || {});
274
+ var defaultTextSettingsMain = ["fontFamily" /* fontFamily */];
275
+ var defaultTextSettingsSubpanel = [
276
+ "fontFamily" /* fontFamily */,
277
+ "textAlign" /* textAlign */,
278
+ "hyphens" /* hyphens */,
279
+ "fontWeight" /* fontWeight */,
280
+ "textNormalize" /* textNormalize */,
281
+ "ligatures" /* ligatures */,
282
+ "noRuby" /* noRuby */
283
+ ];
284
+ var defaultSpacingSettingsMain = [
285
+ "spacingPresets" /* spacingPresets */
286
+ ];
287
+ var defaultSpacingSettingsSubpanel = [
288
+ "spacingPresets" /* spacingPresets */,
289
+ "lineHeight" /* lineHeight */,
290
+ "paragraphSpacing" /* paragraphSpacing */,
291
+ "paragraphIndent" /* paragraphIndent */,
292
+ "wordSpacing" /* wordSpacing */,
293
+ "letterSpacing" /* letterSpacing */
294
+ ];
295
+ var defaultSpacingPresetsOrder = [
296
+ "publisher" /* publisher */,
297
+ "accessible" /* accessible */,
298
+ "custom" /* custom */,
299
+ "tight" /* tight */,
300
+ "balanced" /* balanced */,
301
+ "loose" /* loose */
302
+ ];
303
+ var defaultParagraphSpacing = {
304
+ variant: "numberField" /* numberField */,
305
+ placeholder: "range" /* range */,
306
+ range: [0, 3],
307
+ step: 0.25
308
+ };
309
+ var defaultParagraphIndent = {
310
+ variant: "numberField" /* numberField */,
311
+ placeholder: "range" /* range */,
312
+ range: [0, 2],
313
+ step: 0.25
314
+ };
315
+ var defaultWordSpacing = {
316
+ variant: "numberField" /* numberField */,
317
+ placeholder: "range" /* range */,
318
+ range: [0, 1],
319
+ step: 0.1
320
+ };
321
+ var defaultLetterSpacing = {
322
+ variant: "numberField" /* numberField */,
323
+ placeholder: "range" /* range */,
324
+ range: [0, 0.5],
325
+ step: 0.05
326
+ };
327
+ var defaultLineHeights = {
328
+ ["small" /* small */]: 1.35,
329
+ ["medium" /* medium */]: 1.5,
330
+ ["large" /* large */]: 1.75
331
+ };
332
+ var defaultZoom = {
333
+ variant: "numberField" /* numberField */,
334
+ placeholder: "range" /* range */,
335
+ range: [0.7, 4],
336
+ step: 0.05
337
+ };
338
+ var defaultSpacingPresets = {
339
+ ["tight" /* tight */]: {
340
+ ["lineHeight" /* lineHeight */]: "small" /* small */,
341
+ ["paragraphSpacing" /* paragraphSpacing */]: 0.25,
342
+ ["paragraphIndent" /* paragraphIndent */]: 1
343
+ },
344
+ ["balanced" /* balanced */]: {
345
+ ["lineHeight" /* lineHeight */]: "medium" /* medium */,
346
+ ["paragraphSpacing" /* paragraphSpacing */]: 1,
347
+ ["paragraphIndent" /* paragraphIndent */]: 1
348
+ },
349
+ ["loose" /* loose */]: {
350
+ ["lineHeight" /* lineHeight */]: "large" /* large */,
351
+ ["paragraphSpacing" /* paragraphSpacing */]: 1.5,
352
+ ["paragraphIndent" /* paragraphIndent */]: 1
353
+ },
354
+ ["accessible" /* accessible */]: {
355
+ ["lineHeight" /* lineHeight */]: "large" /* large */,
356
+ ["paragraphSpacing" /* paragraphSpacing */]: 2.5,
357
+ ["paragraphIndent" /* paragraphIndent */]: 1,
358
+ ["letterSpacing" /* letterSpacing */]: 0.1,
359
+ ["wordSpacing" /* wordSpacing */]: 0.3
360
+ }
361
+ };
362
+
363
+ // src/preferences/models/audio.ts
364
+ var ThAudioActionKeys = /* @__PURE__ */ ((ThAudioActionKeys2) => {
365
+ ThAudioActionKeys2["toc"] = "audio.toc";
366
+ ThAudioActionKeys2["volume"] = "audio.volume";
367
+ ThAudioActionKeys2["playbackRate"] = "audio.playbackRate";
368
+ ThAudioActionKeys2["sleepTimer"] = "audio.sleepTimer";
369
+ ThAudioActionKeys2["remotePlayback"] = "audio.remotePlayback";
370
+ return ThAudioActionKeys2;
371
+ })(ThAudioActionKeys || {});
372
+ var ThAudioKeys = /* @__PURE__ */ ((ThAudioKeys2) => {
373
+ ThAudioKeys2["theme"] = "theme";
374
+ ThAudioKeys2["volume"] = "volume";
375
+ ThAudioKeys2["playbackRate"] = "playbackRate";
376
+ ThAudioKeys2["skipBackwardInterval"] = "skipBackwardInterval";
377
+ ThAudioKeys2["skipForwardInterval"] = "skipForwardInterval";
378
+ ThAudioKeys2["skipInterval"] = "skipInterval";
379
+ ThAudioKeys2["autoPlay"] = "autoPlay";
380
+ ThAudioKeys2["sleepTimer"] = "sleepTimer";
381
+ return ThAudioKeys2;
382
+ })(ThAudioKeys || {});
383
+ var ThSettingsTimerVariant = /* @__PURE__ */ ((ThSettingsTimerVariant2) => {
384
+ ThSettingsTimerVariant2["presetList"] = "presetList";
385
+ ThSettingsTimerVariant2["durationField"] = "durationField";
386
+ return ThSettingsTimerVariant2;
387
+ })(ThSettingsTimerVariant || {});
388
+ var defaultAudioVolume = {
389
+ variant: "slider" /* slider */,
390
+ range: [0, 1],
391
+ step: 0.1,
392
+ placeholder: "range" /* range */
393
+ };
394
+ var defaultAudioPlaybackRate = {
395
+ variant: "sliderWithPresets" /* sliderWithPresets */,
396
+ range: [0.5, 4],
397
+ step: 0.05,
398
+ placeholder: "range" /* range */,
399
+ presets: [0.75, 1, 1.25, 1.5, 1.75, 2]
400
+ };
401
+ var defaultAudioSkipBackwardInterval = {
402
+ variant: "presetsGroup" /* presetsGroup */,
403
+ range: [5, 60],
404
+ step: 5,
405
+ placeholder: "range" /* range */,
406
+ presets: [5, 10, 30]
407
+ };
408
+ var defaultAudioSkipForwardInterval = {
409
+ variant: "presetsGroup" /* presetsGroup */,
410
+ range: [5, 60],
411
+ step: 5,
412
+ placeholder: "range" /* range */,
413
+ presets: [5, 10, 30]
414
+ };
415
+ var defaultAudioSkipInterval = {
416
+ variant: "presetsGroup" /* presetsGroup */,
417
+ range: [5, 60],
418
+ step: 5,
419
+ placeholder: "range" /* range */,
420
+ presets: [5, 10, 30]
421
+ };
422
+ var defaultAudioSleepTimer = {
423
+ variant: "durationField" /* durationField */,
424
+ maxHours: 23
425
+ };
426
+ var defaultAudioSleepTimerPresetList = {
427
+ variant: "presetList" /* presetList */,
428
+ presets: [15, 30, 45, 60, 90, "endOfFragment", "endOfResource"]
429
+ };
430
+ var defaultAudioVolumeAction = {
431
+ visibility: "always" /* always */,
432
+ shortcut: null,
433
+ sheet: {
434
+ defaultSheet: "compactPopover" /* compactPopover */,
435
+ breakpoints: {}
436
+ },
437
+ docked: { dockable: "none" /* none */ }
438
+ };
439
+ var defaultAudioPlaybackRateAction = {
440
+ visibility: "always" /* always */,
441
+ shortcut: null,
442
+ sheet: {
443
+ defaultSheet: "compactPopover" /* compactPopover */,
444
+ breakpoints: { ["compact" /* compact */]: "bottomSheet" /* bottomSheet */ }
445
+ },
446
+ snapped: {
447
+ minHeight: "content-height"
448
+ },
449
+ docked: { dockable: "none" /* none */ }
450
+ };
451
+ var defaultAudioSleepTimerAction = {
452
+ visibility: "partially" /* partially */,
453
+ shortcut: null,
454
+ sheet: {
455
+ defaultSheet: "modal" /* modal */,
456
+ breakpoints: {
457
+ ["compact" /* compact */]: "bottomSheet" /* bottomSheet */,
458
+ ["medium" /* medium */]: "bottomSheet" /* bottomSheet */
459
+ }
460
+ },
461
+ snapped: {
462
+ minHeight: "content-height"
463
+ },
464
+ docked: { dockable: "none" /* none */ }
465
+ };
466
+ var defaultAudioRemotePlaybackAction = {
467
+ visibility: "always" /* always */,
468
+ shortcut: null
469
+ };
470
+ var defaultAudioTocAction = {
471
+ visibility: "partially" /* partially */,
472
+ shortcut: null,
473
+ sheet: {
474
+ defaultSheet: "modal" /* modal */,
475
+ breakpoints: {
476
+ ["compact" /* compact */]: "fullscreen" /* fullscreen */,
477
+ ["medium" /* medium */]: "fullscreen" /* fullscreen */
478
+ }
479
+ },
480
+ docked: {
481
+ dockable: "both" /* both */,
482
+ dragIndicator: false,
483
+ width: 360,
484
+ minWidth: 320,
485
+ maxWidth: 450
486
+ }
487
+ };
488
+
489
+ // src/preferences/helpers/buildThemeObject.ts
490
+ var buildThemeObject = ({
491
+ theme,
492
+ themeKeys,
493
+ systemThemes,
494
+ colorScheme
495
+ }) => {
496
+ if (!theme) {
497
+ return {};
498
+ }
499
+ if (theme === "auto" && colorScheme && systemThemes) {
500
+ theme = colorScheme === "dark" /* dark */ ? systemThemes.dark : systemThemes.light;
501
+ }
502
+ let themeProps = {};
503
+ const themeToken = themeKeys[theme];
504
+ if (themeToken) {
505
+ themeProps = {
506
+ backgroundColor: themeToken.background,
507
+ textColor: themeToken.text,
508
+ linkColor: themeToken.link,
509
+ selectionBackgroundColor: themeToken.select,
510
+ selectionTextColor: themeToken.onSelect,
511
+ visitedColor: themeToken.visited
512
+ };
513
+ } else {
514
+ console.warn(`Theme key "${String(theme)}" not found in themeKeys.`);
515
+ themeProps = {
516
+ backgroundColor: null,
517
+ textColor: null,
518
+ linkColor: null,
519
+ selectionBackgroundColor: null,
520
+ selectionTextColor: null,
521
+ visitedColor: null
522
+ };
523
+ }
524
+ return themeProps;
525
+ };
526
+
527
+ // src/preferences/helpers/fontPref/bunnyFonts.ts
528
+ var DEFAULT_FALLBACK = "sans-serif";
529
+ var createDefinitionsFromBunnyFonts = (params) => {
530
+ const { cssUrl, options } = params;
531
+ const { fallbacks, order, labels } = options || {};
532
+ const processedUrl = cssUrl.includes("@import") ? cssUrl.match(/@import\s+url\(['"]?([^'")]+)['"]?\)/i)?.[1] || cssUrl : cssUrl.includes("href=") ? cssUrl.match(/href=["']([^"']+)["']/)?.[1] || cssUrl : cssUrl;
533
+ const url = new URL(processedUrl);
534
+ if (!url.hostname.includes("fonts.bunny.net")) {
535
+ throw new Error("Invalid Bunny Fonts URL");
536
+ }
537
+ const familyParam = url.searchParams.get("family");
538
+ if (!familyParam) {
539
+ throw new Error("No family parameter found in Bunny Fonts URL");
540
+ }
541
+ const fontEntries = familyParam.split("|").map((familyStr) => {
542
+ const [familyName, weightsStr = ""] = familyStr.split(":");
543
+ if (!familyName) {
544
+ throw new Error(`Invalid font family format: ${familyStr}`);
545
+ }
546
+ const weightStyles = /* @__PURE__ */ new Map();
547
+ if (weightsStr) {
548
+ weightsStr.split(",").forEach((weightStr) => {
549
+ const isItalic = weightStr.endsWith("i");
550
+ const weightValue = parseInt(isItalic ? weightStr.slice(0, -1) : weightStr, 10);
551
+ if (!isNaN(weightValue)) {
552
+ if (!weightStyles.has(weightValue)) {
553
+ weightStyles.set(weightValue, /* @__PURE__ */ new Set());
554
+ }
555
+ weightStyles.get(weightValue)?.add(isItalic ? "italic" : "normal");
556
+ }
557
+ });
558
+ }
559
+ const weights = Array.from(weightStyles.keys()).sort((a, b) => a - b);
560
+ const hasItalic = Array.from(weightStyles.values()).some((styles2) => styles2.has("italic"));
561
+ const styles = hasItalic ? ["normal", "italic"] : ["normal"];
562
+ const fontId = familyName;
563
+ const familyDisplayName = familyName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
564
+ return [
565
+ fontId,
566
+ {
567
+ id: fontId,
568
+ name: familyDisplayName,
569
+ ...labels?.[fontId] && { label: labels[fontId] },
570
+ source: {
571
+ type: "custom",
572
+ provider: "bunny"
573
+ },
574
+ spec: {
575
+ family: familyDisplayName,
576
+ fallbacks: fallbacks?.[fontId] || [DEFAULT_FALLBACK],
577
+ weights: {
578
+ type: "static",
579
+ values: weights.length ? weights : [400]
580
+ },
581
+ styles
582
+ }
583
+ }
584
+ ];
585
+ });
586
+ const result = Object.fromEntries(fontEntries);
587
+ if (order && order.length > 0) {
588
+ const orderedResult = {};
589
+ order.forEach((fontId) => {
590
+ if (result[fontId]) {
591
+ orderedResult[fontId] = result[fontId];
592
+ }
593
+ });
594
+ Object.entries(result).forEach(([fontId, definition]) => {
595
+ if (!orderedResult[fontId]) {
596
+ orderedResult[fontId] = definition;
597
+ }
598
+ });
599
+ return orderedResult;
600
+ }
601
+ return result;
602
+ };
603
+
604
+ // src/preferences/helpers/fontPref/googleFonts.ts
605
+ var DEFAULT_FALLBACK2 = "sans-serif";
606
+ var DEFAULT_WIDTH_STEP = 20;
607
+ var DEFAULT_WEIGHT_STEP = 20;
608
+ var createDefinitionsFromGoogleFonts = (params) => {
609
+ const { cssUrl, options } = params;
610
+ const { widthStep = DEFAULT_WIDTH_STEP, weightStep = DEFAULT_WEIGHT_STEP, display, labels, fallbacks, order } = options || {};
611
+ const processedUrl = cssUrl.includes("@import") ? cssUrl.match(/@import\s+url\(['"]?([^'")]+)['"]?\)/i)?.[1] || cssUrl : cssUrl.includes("href=") ? cssUrl.match(/href=["']([^"']+)["']/)?.[1] || cssUrl : cssUrl;
612
+ const url = new URL(processedUrl);
613
+ if (!url.hostname.includes("fonts.googleapis.com")) {
614
+ throw new Error("Invalid Google Fonts URL");
615
+ }
616
+ const familyParams = url.searchParams.getAll("family");
617
+ if (familyParams.length === 0) {
618
+ throw new Error("No family parameter found in Google Fonts URL");
619
+ }
620
+ const families = familyParams.map((familyParam) => {
621
+ const decodedFamily = decodeURIComponent(familyParam);
622
+ const [familyName, axesStr] = decodedFamily.split(":");
623
+ if (!familyName) {
624
+ throw new Error(`Invalid family format: ${familyParam}`);
625
+ }
626
+ const family = {
627
+ name: familyName.replace(/\+/g, " "),
628
+ styles: ["normal"],
629
+ weights: { type: "static", values: [400] }
630
+ // Default weight
631
+ };
632
+ let hasExplicitWeights = false;
633
+ if (axesStr) {
634
+ const [axisNames, valuesStr] = axesStr.split("@");
635
+ if (axisNames && valuesStr) {
636
+ const axes = axisNames.split(",");
637
+ const variations = valuesStr.split(";");
638
+ variations.forEach((variation) => {
639
+ const values = variation.split(",");
640
+ axes.forEach((axis, index) => {
641
+ const value = values[index];
642
+ if (!value) return;
643
+ switch (axis) {
644
+ case "ital":
645
+ if (value === "1") {
646
+ family.styles = Array.from(/* @__PURE__ */ new Set([...family.styles, "italic"]));
647
+ }
648
+ break;
649
+ case "wght":
650
+ if (value.includes("..")) {
651
+ const [min, max] = value.split("..").map(Number);
652
+ if (!isNaN(min) && !isNaN(max)) {
653
+ family.weights = {
654
+ type: "variable",
655
+ min,
656
+ max,
657
+ step: weightStep
658
+ };
659
+ }
660
+ } else {
661
+ const weight = Number(value);
662
+ if (!isNaN(weight) && family.weights.type === "static") {
663
+ const currentWeights = family.weights.values;
664
+ const newWeights = !hasExplicitWeights ? [weight] : Array.from(/* @__PURE__ */ new Set([...currentWeights, weight])).sort((a, b) => a - b);
665
+ family.weights = {
666
+ type: "static",
667
+ values: newWeights
668
+ };
669
+ hasExplicitWeights = true;
670
+ }
671
+ }
672
+ break;
673
+ case "wdth":
674
+ if (value.includes("..")) {
675
+ const [min, max] = value.split("..").map(Number);
676
+ if (!isNaN(min) && !isNaN(max)) {
677
+ family.widths = {
678
+ min,
679
+ max,
680
+ step: widthStep
681
+ };
682
+ }
683
+ }
684
+ break;
685
+ }
686
+ });
687
+ });
688
+ }
689
+ }
690
+ return family;
691
+ });
692
+ const fontEntries = families.map((family) => {
693
+ const fontId = family.name.toLowerCase().replace(/\s+/g, "-");
694
+ return [
695
+ fontId,
696
+ {
697
+ id: fontId,
698
+ name: family.name,
699
+ ...labels?.[fontId] && { label: labels[fontId] },
700
+ source: { type: "custom", provider: "google" },
701
+ spec: {
702
+ family: family.name,
703
+ fallbacks: fallbacks?.[fontId] || [DEFAULT_FALLBACK2],
704
+ weights: family.weights,
705
+ styles: family.styles,
706
+ ...family.widths && { widths: family.widths },
707
+ ...display && { display }
708
+ }
709
+ }
710
+ ];
711
+ });
712
+ if (order && order.length > 0) {
713
+ const orderedEntries = [];
714
+ const fontMap = new Map(fontEntries);
715
+ for (const fontId of order) {
716
+ const fontEntry = fontMap.get(fontId);
717
+ if (fontEntry) {
718
+ orderedEntries.push([fontId, fontEntry]);
719
+ fontMap.delete(fontId);
720
+ }
721
+ }
722
+ for (const [fontId, fontEntry] of fontMap.entries()) {
723
+ orderedEntries.push([fontId, fontEntry]);
724
+ }
725
+ return Object.fromEntries(orderedEntries);
726
+ }
727
+ return Object.fromEntries(fontEntries);
728
+ };
729
+
730
+ // src/preferences/helpers/fontPref/localFonts.ts
731
+ var createDefinitionFromStaticFonts = (params) => {
732
+ const { id, name, files, family, label, fallbacks = ["sans-serif"] } = params;
733
+ if (!files || files.length === 0) {
734
+ throw new Error("No files provided to infer font specification");
735
+ }
736
+ if (!files.every((file) => file.weight !== void 0)) {
737
+ throw new Error("All files must have explicit weights for static font specification inference");
738
+ }
739
+ const weights = Array.from(new Set(files.map((file) => file.weight))).sort((a, b) => a - b);
740
+ const styles = Array.from(new Set(files.map((file) => file.style)));
741
+ const source = {
742
+ type: "custom",
743
+ provider: "local",
744
+ variant: "static",
745
+ files
746
+ };
747
+ const spec = {
748
+ family: family || name,
749
+ fallbacks,
750
+ weights: {
751
+ type: "static",
752
+ values: weights
753
+ },
754
+ styles
755
+ };
756
+ return {
757
+ id,
758
+ name,
759
+ ...label && { label },
760
+ source,
761
+ spec
762
+ };
763
+ };
764
+
765
+ // src/preferences/helpers/validateObjectKeys.ts
766
+ var validateObjectKeys = (orderArrays, keysObj, context, specialCase, fallback) => {
767
+ const allOrders = new Set(
768
+ orderArrays.flatMap((arr) => {
769
+ if (!specialCase) return arr;
770
+ return arr.filter((k) => {
771
+ if (Array.isArray(specialCase)) {
772
+ return !specialCase.includes(k);
773
+ } else {
774
+ return k !== specialCase;
775
+ }
776
+ });
777
+ })
778
+ );
779
+ const availableKeys = Object.keys(keysObj);
780
+ allOrders.forEach((key) => {
781
+ if (!availableKeys.includes(key)) {
782
+ if (fallback) keysObj[key] = fallback;
783
+ console.warn(
784
+ `Key "${key}" in ${context} order arrays not found in ${context}.keys.${fallback ? `
785
+ Using fallback: ${JSON.stringify(fallback)}` : ""}`
786
+ );
787
+ }
788
+ });
789
+ };
790
+
791
+ // src/preferences/models/fonts.ts
792
+ var readiumCSSFontCollection = {
793
+ oldStyle: {
794
+ id: "oldStyle",
795
+ name: "Old Style",
796
+ label: "reader.preferences.fontFamily.oldStyle.descriptive",
797
+ source: { type: "system" },
798
+ spec: {
799
+ family: fontStacks.RS__oldStyleTf,
800
+ weights: { type: "static", values: [400, 700] },
801
+ fallbacks: []
802
+ }
803
+ },
804
+ modern: {
805
+ id: "modern",
806
+ name: "Modern",
807
+ label: "reader.preferences.fontFamily.modern.descriptive",
808
+ source: { type: "system" },
809
+ spec: {
810
+ family: fontStacks.RS__modernTf,
811
+ weights: { type: "static", values: [400, 700] },
812
+ fallbacks: []
813
+ }
814
+ },
815
+ sans: {
816
+ id: "sans",
817
+ name: "Sans",
818
+ label: "reader.preferences.fontFamily.sans",
819
+ source: { type: "system" },
820
+ spec: {
821
+ family: fontStacks.RS__sansTf,
822
+ weights: { type: "static", values: [400, 700] },
823
+ fallbacks: []
824
+ }
825
+ },
826
+ humanist: {
827
+ id: "humanist",
828
+ name: "Humanist",
829
+ label: "reader.preferences.fontFamily.humanist.descriptive",
830
+ source: { type: "system" },
831
+ spec: {
832
+ family: fontStacks.RS__humanistTf,
833
+ weights: { type: "static", values: [400, 700] },
834
+ fallbacks: []
835
+ }
836
+ },
837
+ monospace: {
838
+ id: "monospace",
839
+ name: "Monospace",
840
+ label: "reader.preferences.fontFamily.monospace",
841
+ source: { type: "system" },
842
+ spec: {
843
+ family: fontStacks.RS__monospaceTf,
844
+ weights: { type: "static", values: [400, 700] },
845
+ fallbacks: []
846
+ }
847
+ }
848
+ };
849
+ var defaultFontCollection = {
850
+ ...createDefinitionsFromGoogleFonts({
851
+ cssUrl: "https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&family=Literata:ital,opsz,wght@0,7..72,200..900;1,7..72,200..900",
852
+ options: {
853
+ order: ["literata", "atkinson-hyperlegible-next"],
854
+ fallbacks: {
855
+ "literata": ["serif"],
856
+ "atkinson-hyperlegible-next": ["sans-serif"]
857
+ }
858
+ }
859
+ }),
860
+ luciole: createDefinitionFromStaticFonts({
861
+ id: "luciole",
862
+ name: "Luciole",
863
+ files: [
864
+ { path: "/fonts/Luciole/Luciole-Regular.woff2", weight: 400, style: "normal" },
865
+ { path: "/fonts/Luciole/Luciole-Italic.woff2", weight: 400, style: "italic" },
866
+ { path: "/fonts/Luciole/Luciole-Bold.woff2", weight: 700, style: "normal" },
867
+ { path: "/fonts/Luciole/Luciole-BoldItalic.woff2", weight: 700, style: "italic" }
868
+ ]
869
+ }),
870
+ ...readiumCSSFontCollection,
871
+ iAWriterDuo: createDefinitionFromStaticFonts({
872
+ id: "iAWriterDuo",
873
+ name: "iA Writer Duo",
874
+ label: "iA Writer Duospace",
875
+ fallbacks: ["monospace"],
876
+ files: [
877
+ { path: "/fonts/iAWriterDuo/iAWriterDuoS-Regular.woff2", weight: 400, style: "normal" },
878
+ { path: "/fonts/iAWriterDuo/iAWriterDuoS-Bold.woff2", weight: 700, style: "normal" },
879
+ { path: "/fonts/iAWriterDuo/iAWriterDuoS-Italic.woff2", weight: 400, style: "italic" },
880
+ { path: "/fonts/iAWriterDuo/iAWriterDuoS-BoldItalic.woff2", weight: 700, style: "italic" }
881
+ ]
882
+ }),
883
+ openDyslexic: createDefinitionFromStaticFonts({
884
+ id: "openDyslexic",
885
+ name: "Open Dyslexic",
886
+ files: [
887
+ { path: "/fonts/OpenDyslexic/OpenDyslexic-Regular.otf", weight: 400, style: "normal" },
888
+ { path: "/fonts/OpenDyslexic/OpenDyslexic-Italic.otf", weight: 400, style: "italic" },
889
+ { path: "/fonts/OpenDyslexic/OpenDyslexic-Bold.otf", weight: 700, style: "normal" },
890
+ { path: "/fonts/OpenDyslexic/OpenDyslexic-BoldItalic.otf", weight: 700, style: "italic" }
891
+ ]
892
+ }),
893
+ accessibleDfA: createDefinitionFromStaticFonts({
894
+ id: "accessibleDfA",
895
+ name: "Accessible DfA",
896
+ files: [
897
+ { path: "/fonts/AccessibleDfA/AccessibleDfA-Regular.woff2", weight: 400, style: "normal" },
898
+ { path: "/fonts/AccessibleDfA/AccessibleDfA-Italic.woff2", weight: 400, style: "italic" },
899
+ { path: "/fonts/AccessibleDfA/AccessibleDfA-Bold.woff2", weight: 700, style: "normal" }
900
+ ]
901
+ })
902
+ };
903
+ var tamilCollection = {
904
+ ...createDefinitionsFromGoogleFonts({
905
+ cssUrl: "https://fonts.googleapis.com/css2?family=Anek+Tamil:wght@100..800&family=Catamaran:wght@100..900&family=Hind+Madurai:wght@400;700&family=Mukta+Malar:wght@400;700&family=Noto+Sans+Tamil:wght@100..900&family=Noto+Serif+Tamil:ital,wght@0,100..900;1,100..900",
906
+ options: {
907
+ order: ["noto-sans-tamil", "noto-serif-tamil", "anek-tamil", "catamaran", "hind-madurai", "mukta-malar"],
908
+ labels: {
909
+ "noto-sans-tamil": "Noto Sans",
910
+ "noto-serif-tamil": "Noto Serif",
911
+ "anek-tamil": "\u0B85\u0BA9\u0BC7\u0B95\u0BCD \u0BA4\u0BAE\u0BBF\u0BB4\u0BCD",
912
+ "catamaran": "\u0B95\u0B9F\u0BCD\u0B9F\u0BC1\u0BAE\u0BB0\u0BA9\u0BCD",
913
+ "mukta-malar": "\u0BAE\u0BC1\u0B95\u0BCD\u0BA4 \u0BAE\u0BB2\u0BB0\u0BCD"
914
+ },
915
+ fallbacks: {
916
+ "noto-serif-tamil": ["serif"]
917
+ }
918
+ }
919
+ })
920
+ };
921
+ var sysFontDef = (id, name, family) => ({
922
+ id,
923
+ name,
924
+ source: { type: "system" },
925
+ spec: { family, fallbacks: [], weights: { type: "static", values: [400, 700] } }
926
+ });
927
+ var arabicFarsiCollection = {
928
+ ...createDefinitionsFromGoogleFonts({
929
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@100..900&family=Noto+Naskh+Arabic:wght@400..700&family=Scheherazade+New:wght@400..700",
930
+ options: {
931
+ order: ["noto-sans-arabic", "noto-naskh-arabic", "scheherazade-new"],
932
+ labels: {
933
+ "noto-sans-arabic": "Noto Sans",
934
+ "noto-naskh-arabic": "Noto Naskh"
935
+ },
936
+ fallbacks: {
937
+ "noto-naskh-arabic": ["serif"],
938
+ "scheherazade-new": ["serif"]
939
+ }
940
+ }
941
+ })
942
+ };
943
+ var hebrewCollection = {
944
+ ...createDefinitionsFromGoogleFonts({
945
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@100..900&family=Frank+Ruhl+Libre:wght@300..900&family=Heebo:wght@100..800",
946
+ options: {
947
+ order: ["noto-sans-hebrew", "frank-ruhl-libre", "heebo"],
948
+ labels: {
949
+ "frank-ruhl-libre": "Frank R\xFChl Libre"
950
+ },
951
+ fallbacks: {
952
+ "frank-ruhl-libre": ["serif"]
953
+ }
954
+ }
955
+ })
956
+ };
957
+ var chineseSimplifiedCollection = {
958
+ "chinese-hans-sans": sysFontDef("chinese-hans-sans", "Sans-Serif", '"PingFang SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif'),
959
+ "chinese-hans-serif": sysFontDef("chinese-hans-serif", "Serif", '"STSong", "SimSun", "AR PL UMing CN", serif'),
960
+ ...createDefinitionsFromGoogleFonts({
961
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100..900&family=Noto+Serif+SC:wght@200..900",
962
+ options: {
963
+ labels: {
964
+ "noto-sans-sc": "Noto Sans SC",
965
+ "noto-serif-sc": "Noto Serif SC"
966
+ },
967
+ fallbacks: {
968
+ "noto-serif-sc": ["serif"]
969
+ }
970
+ }
971
+ })
972
+ };
973
+ var chineseTraditionalCollection = {
974
+ "chinese-hant-sans": sysFontDef("chinese-hant-sans", "Sans-Serif", '"PingFang TC", "Microsoft JhengHei", "WenQuanYi Micro Hei", sans-serif'),
975
+ "chinese-hant-serif": sysFontDef("chinese-hant-serif", "Serif", '"MingLiU", "AR PL UMing TW", serif'),
976
+ ...createDefinitionsFromGoogleFonts({
977
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@100..900&family=Noto+Serif+TC:wght@200..900",
978
+ options: {
979
+ labels: {
980
+ "noto-sans-tc": "Noto Sans TC",
981
+ "noto-serif-tc": "Noto Serif TC"
982
+ },
983
+ fallbacks: {
984
+ "noto-serif-tc": ["serif"]
985
+ }
986
+ }
987
+ })
988
+ };
989
+ var japaneseCollection = {
990
+ "japanese-sans": sysFontDef("japanese-sans", "Sans-Serif", '"Hiragino Kaku Gothic ProN", "Yu Gothic", "Meiryo", sans-serif'),
991
+ "japanese-serif": sysFontDef("japanese-serif", "Serif", '"Hiragino Mincho ProN", "Yu Mincho", "MS PMincho", serif'),
992
+ ...createDefinitionsFromGoogleFonts({
993
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&family=Noto+Serif+JP:wght@200..900",
994
+ options: {
995
+ labels: {
996
+ "noto-sans-jp": "Noto Sans JP",
997
+ "noto-serif-jp": "Noto Serif JP"
998
+ },
999
+ fallbacks: {
1000
+ "noto-serif-jp": ["serif"]
1001
+ }
1002
+ }
1003
+ })
1004
+ };
1005
+ var japaneseVerticalCollection = {
1006
+ "japanese-v-serif": sysFontDef("japanese-v-serif", "Serif", '"Hiragino Mincho ProN", "Yu Mincho", "MS PMincho", serif'),
1007
+ "japanese-v-sans": sysFontDef("japanese-v-sans", "Sans-Serif", '"Hiragino Kaku Gothic ProN", "Yu Gothic", "Meiryo", sans-serif'),
1008
+ ...createDefinitionsFromGoogleFonts({
1009
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Serif+JP:wght@200..900&family=Shippori+Mincho:wght@400..800&family=Noto+Sans+JP:wght@100..900",
1010
+ options: {
1011
+ order: ["noto-serif-jp", "shippori-mincho", "noto-sans-jp"],
1012
+ labels: {
1013
+ "noto-serif-jp": "Noto Serif JP",
1014
+ "noto-sans-jp": "Noto Sans JP"
1015
+ },
1016
+ fallbacks: {
1017
+ "noto-serif-jp": ["serif"],
1018
+ "shippori-mincho": ["serif"]
1019
+ }
1020
+ }
1021
+ })
1022
+ };
1023
+ var koreanCollection = {
1024
+ "korean-sans": sysFontDef("korean-sans", "Sans-Serif", '"Apple SD Gothic Neo", "Malgun Gothic", "Dotum", sans-serif'),
1025
+ "korean-serif": sysFontDef("korean-serif", "Serif", '"Batang", "Gungsuh", "Apple Myungjo", serif'),
1026
+ ...createDefinitionsFromGoogleFonts({
1027
+ cssUrl: "https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&family=Noto+Serif+KR:wght@200..900&family=Nanum+Myeongjo:wght@400;700;800",
1028
+ options: {
1029
+ order: ["noto-sans-kr", "noto-serif-kr", "nanum-myeongjo"],
1030
+ labels: {
1031
+ "noto-sans-kr": "Noto Sans KR",
1032
+ "noto-serif-kr": "Noto Serif KR"
1033
+ },
1034
+ fallbacks: {
1035
+ "noto-serif-kr": ["serif"],
1036
+ "nanum-myeongjo": ["serif"]
1037
+ }
1038
+ }
1039
+ })
1040
+ };
1041
+
1042
+ // src/preferences/models/protection.ts
1043
+ var resolveContentProtectionConfig = (contentProtection, t) => {
1044
+ if (!contentProtection) return void 0;
1045
+ let resolvedWatermark;
1046
+ if (contentProtection.protectPrinting?.watermark) {
1047
+ if (typeof contentProtection.protectPrinting.watermark === "object" && "key" in contentProtection.protectPrinting.watermark) {
1048
+ resolvedWatermark = t(contentProtection.protectPrinting.watermark.key, {
1049
+ defaultValue: contentProtection.protectPrinting.watermark.fallback
1050
+ });
1051
+ } else if (typeof contentProtection.protectPrinting.watermark === "string") {
1052
+ resolvedWatermark = t(contentProtection.protectPrinting.watermark);
1053
+ }
1054
+ }
1055
+ const resolved = {
1056
+ protectCopy: contentProtection.protectCopy,
1057
+ disableContextMenu: contentProtection.disableContextMenu,
1058
+ disableDragAndDrop: contentProtection.disableDragAndDrop,
1059
+ protectPrinting: contentProtection.protectPrinting?.disable ? {
1060
+ disable: true,
1061
+ watermark: resolvedWatermark
1062
+ } : void 0,
1063
+ disableSelectAll: contentProtection.disableSelectAll,
1064
+ disableSave: contentProtection.disableSave,
1065
+ monitorDevTools: contentProtection.monitorDevTools
1066
+ // TODO: When we implement it in non-audio navigators, uncomment
1067
+ // disableRemotePlayback: contentProtection.disableRemotePlayback
1068
+ };
1069
+ return resolved;
1070
+ };
1071
+ var resolveAudioContentProtectionConfig = (contentProtection, t) => {
1072
+ if (!contentProtection) return void 0;
1073
+ let resolvedWatermark;
1074
+ if (contentProtection.protectPrinting?.watermark) {
1075
+ if (typeof contentProtection.protectPrinting.watermark === "object" && "key" in contentProtection.protectPrinting.watermark) {
1076
+ resolvedWatermark = t(contentProtection.protectPrinting.watermark.key, {
1077
+ defaultValue: contentProtection.protectPrinting.watermark.fallback
1078
+ });
1079
+ } else if (typeof contentProtection.protectPrinting.watermark === "string") {
1080
+ resolvedWatermark = t(contentProtection.protectPrinting.watermark);
1081
+ }
1082
+ }
1083
+ return {
1084
+ protectCopy: contentProtection.protectCopy,
1085
+ disableContextMenu: contentProtection.disableContextMenu,
1086
+ disableDragAndDrop: contentProtection.disableDragAndDrop,
1087
+ protectPrinting: contentProtection.protectPrinting?.disable ? {
1088
+ disable: true,
1089
+ watermark: resolvedWatermark
1090
+ } : void 0,
1091
+ disableSelectAll: contentProtection.disableSelectAll,
1092
+ disableSave: contentProtection.disableSave,
1093
+ monitorDevTools: contentProtection.monitorDevTools,
1094
+ disableRemotePlayback: contentProtection.disableRemotePlayback
1095
+ };
1096
+ };
1097
+ var defaultContentProtectionConfig = {
1098
+ protectCopy: false,
1099
+ disableContextMenu: false,
1100
+ disableDragAndDrop: false,
1101
+ protectPrinting: {
1102
+ disable: false,
1103
+ watermark: "reader.app.printingDisabled"
1104
+ },
1105
+ disableSelectAll: false,
1106
+ disableSave: false,
1107
+ monitorDevTools: false
1108
+ };
1109
+ var defaultAudioContentProtectionConfig = {
1110
+ protectCopy: false,
1111
+ disableContextMenu: false,
1112
+ disableDragAndDrop: false,
1113
+ protectPrinting: {
1114
+ disable: false,
1115
+ watermark: "reader.app.printingDisabled"
1116
+ },
1117
+ disableSelectAll: false,
1118
+ disableSave: false,
1119
+ monitorDevTools: false,
1120
+ disableRemotePlayback: false
1121
+ };
1122
+ var devContentProtectionConfig = {
1123
+ protectCopy: false,
1124
+ disableContextMenu: false,
1125
+ disableDragAndDrop: false,
1126
+ protectPrinting: {
1127
+ disable: false
1128
+ },
1129
+ disableSelectAll: false,
1130
+ disableSave: false,
1131
+ monitorDevTools: false,
1132
+ disableRemotePlayback: false
1133
+ };
1134
+ var ThThemeKeys = /* @__PURE__ */ ((ThThemeKeys3) => {
1135
+ ThThemeKeys3["light"] = "light";
1136
+ ThThemeKeys3["sepia"] = "sepia";
1137
+ ThThemeKeys3["dark"] = "dark";
1138
+ ThThemeKeys3["paper"] = "paper";
1139
+ ThThemeKeys3["contrast1"] = "contrast1";
1140
+ ThThemeKeys3["contrast2"] = "contrast2";
1141
+ ThThemeKeys3["contrast3"] = "contrast3";
1142
+ return ThThemeKeys3;
1143
+ })(ThThemeKeys || {});
1144
+ var lightTheme = {
1145
+ background: ReadiumCSSColors.RS__backgroundColor,
1146
+ // Color of background
1147
+ text: ReadiumCSSColors.RS__textColor,
1148
+ // Color of text
1149
+ link: "#0000ee",
1150
+ // Color of links
1151
+ visited: "#551a8b",
1152
+ // Color of visited links
1153
+ subdue: "#808080",
1154
+ // Color of subdued elements
1155
+ disable: "#808080",
1156
+ // color for :disabled
1157
+ hover: "#d9d9d9",
1158
+ // color of background for :hover
1159
+ onHover: ReadiumCSSColors.RS__textColor,
1160
+ // color of text for :hover
1161
+ select: "#b4d8fe",
1162
+ // color of selected background
1163
+ onSelect: "inherit",
1164
+ // color of selected text
1165
+ focus: "#0067f4",
1166
+ // color of :focus-visible
1167
+ elevate: "0px 0px 2px #808080",
1168
+ // drop shadow of containers
1169
+ immerse: "0.6"
1170
+ // opacity of immersive mode
1171
+ };
1172
+ var darkTheme = {
1173
+ background: "#000000",
1174
+ text: "#FEFEFE",
1175
+ link: "#63caff",
1176
+ visited: "#0099E5",
1177
+ subdue: "#808080",
1178
+ disable: "#808080",
1179
+ hover: "#404040",
1180
+ onHover: "#FEFEFE",
1181
+ select: "#b4d8fe",
1182
+ onSelect: "inherit",
1183
+ focus: "#0067f4",
1184
+ elevate: "0px 0px 2px #808080",
1185
+ immerse: "0.4"
1186
+ };
1187
+ var paperTheme = {
1188
+ background: "#faf4e8",
1189
+ text: "#121212",
1190
+ link: "#0000EE",
1191
+ visited: "#551A8B",
1192
+ subdue: "#8c8c8c",
1193
+ disable: "#8c8c8c",
1194
+ hover: "#edd7ab",
1195
+ onHover: "#121212",
1196
+ select: "#b4d8fe",
1197
+ onSelect: "inherit",
1198
+ focus: "#0067f4",
1199
+ elevate: "0px 0px 2px #8c8c8c",
1200
+ immerse: "0.5"
1201
+ };
1202
+ var sepiaTheme = {
1203
+ background: "#e9ddc8",
1204
+ text: "#000000",
1205
+ link: "#0000EE",
1206
+ visited: "#551A8B",
1207
+ subdue: "#8c8c8c",
1208
+ disable: "#8c8c8c",
1209
+ hover: "#ccb07f",
1210
+ onHover: "#000000",
1211
+ select: "#b4d8fe",
1212
+ onSelect: "inherit",
1213
+ focus: "#004099",
1214
+ elevate: "0px 0px 2px #8c8c8c",
1215
+ immerse: "0.45"
1216
+ };
1217
+ var contrast1Theme = {
1218
+ background: "#000000",
1219
+ text: "#ffff00",
1220
+ link: "#63caff",
1221
+ visited: "#0099E5",
1222
+ subdue: "#808000",
1223
+ disable: "#808000",
1224
+ hover: "#404040",
1225
+ onHover: "#ffff00",
1226
+ select: "#b4d8fe",
1227
+ onSelect: "inherit",
1228
+ focus: "#0067f4",
1229
+ elevate: "0px 0px 2px #808000",
1230
+ immerse: "0.4"
1231
+ };
1232
+ var contrast2Theme = {
1233
+ background: "#181842",
1234
+ text: "#ffffff",
1235
+ link: "#adcfff",
1236
+ visited: "#7ab2ff",
1237
+ subdue: "#808080",
1238
+ disable: "#808080",
1239
+ hover: "#4444bb",
1240
+ onHover: "#ffffff",
1241
+ select: "#b4d8fe",
1242
+ onSelect: "inherit",
1243
+ focus: "#6BA9FF",
1244
+ elevate: "0px 0px 2px #808080",
1245
+ immerse: "0.4"
1246
+ };
1247
+ var contrast3Theme = {
1248
+ background: "#c5e7cd",
1249
+ text: "#000000",
1250
+ link: "#0000EE",
1251
+ visited: "#551A8B",
1252
+ subdue: "#8c8c8c",
1253
+ disable: "#8c8c8c",
1254
+ hover: "#6fc383",
1255
+ onHover: "#000000",
1256
+ select: "#b4d8fe",
1257
+ onSelect: "inherit",
1258
+ focus: "#004099",
1259
+ elevate: "0px 0px 2px #8c8c8c",
1260
+ immerse: "0.45"
1261
+ };
1262
+
1263
+ // src/preferences/audioPreferences.ts
1264
+ var ThAudioAffordance = /* @__PURE__ */ ((ThAudioAffordance2) => {
1265
+ ThAudioAffordance2["timeline"] = "timeline";
1266
+ ThAudioAffordance2["readingOrder"] = "readingOrder";
1267
+ ThAudioAffordance2["toc"] = "toc";
1268
+ return ThAudioAffordance2;
1269
+ })(ThAudioAffordance || {});
1270
+ var validateRangePresets = (pref, context) => {
1271
+ if (pref.variant !== "sliderWithPresets" /* sliderWithPresets */ || !pref.presets?.length) return;
1272
+ const [min, max] = [Math.min(...pref.range), Math.max(...pref.range)];
1273
+ const step = pref.step;
1274
+ const tolerance = step * 1e-9;
1275
+ const invalid = pref.presets.filter((p) => {
1276
+ if (p < min || p > max) return true;
1277
+ const offset = (p - min) / step;
1278
+ return Math.abs(offset - Math.round(offset)) > tolerance;
1279
+ });
1280
+ if (invalid.length > 0) {
1281
+ console.warn(
1282
+ `${context}: presets [${invalid.join(", ")}] are not reachable with range=[${min}, ${max}] and step=${step}.`
1283
+ );
1284
+ }
1285
+ };
1286
+ var createAudioPreferences = (params) => {
1287
+ if (params.actions?.secondary) {
1288
+ validateObjectKeys(
1289
+ [params.actions.secondary.displayOrder],
1290
+ params.actions.secondary.keys,
1291
+ "actions.secondary"
1292
+ );
1293
+ }
1294
+ if (params.settings?.order) {
1295
+ const order = params.settings.order;
1296
+ const hasSkipInterval = order.includes("skipInterval" /* skipInterval */);
1297
+ const hasSplitIntervals = order.includes("skipBackwardInterval" /* skipBackwardInterval */) || order.includes("skipForwardInterval" /* skipForwardInterval */);
1298
+ if (hasSkipInterval && hasSplitIntervals) {
1299
+ console.warn(
1300
+ `settings.order contains both "${"skipInterval" /* skipInterval */}" and split interval keys. Use one or the other.`
1301
+ );
1302
+ }
1303
+ }
1304
+ if (params.theming?.themes) {
1305
+ validateObjectKeys(
1306
+ [params.theming.themes.audioOrder],
1307
+ params.theming.themes.keys,
1308
+ "theming.themes",
1309
+ "auto"
1310
+ );
1311
+ }
1312
+ if (params.theming?.layout?.publicationMetadata?.order) {
1313
+ const order = params.theming.layout.publicationMetadata.order;
1314
+ const titleVariants = [
1315
+ "title" /* title */,
1316
+ "titleWithSubtitle" /* titleWithSubtitle */,
1317
+ "subtitleWithTitle" /* subtitleWithTitle */
1318
+ ];
1319
+ const titleVariantsInOrder = order.filter((c) => titleVariants.includes(c));
1320
+ if (titleVariantsInOrder.length > 1) {
1321
+ console.warn(
1322
+ `publicationMetadata.order contains multiple title variants [${titleVariantsInOrder.join(", ")}]. Using first one only.`
1323
+ );
1324
+ const firstTitleIndex = order.findIndex((c) => titleVariants.includes(c));
1325
+ params.theming.layout.publicationMetadata.order = order.filter((component, index) => {
1326
+ if (component === "authors" /* authors */) return true;
1327
+ return index === firstTitleIndex;
1328
+ });
1329
+ }
1330
+ }
1331
+ Object.entries(params.settings?.keys ?? {}).forEach(([key, pref]) => {
1332
+ if (pref && typeof pref === "object" && "variant" in pref) {
1333
+ validateRangePresets(pref, `settings.keys.${key}`);
1334
+ }
1335
+ });
1336
+ return params;
1337
+ };
1338
+
1339
+ // src/preferences/defaultAudioPreferences.ts
1340
+ var defaultAudioPreferences = createAudioPreferences({
1341
+ theming: {
1342
+ header: {
1343
+ backLink: {
1344
+ variant: "arrow" /* arrow */,
1345
+ visibility: "partially",
1346
+ href: "/"
1347
+ }
1348
+ },
1349
+ icon: {
1350
+ size: 24,
1351
+ tooltipOffset: 10
1352
+ },
1353
+ layout: {
1354
+ compact: {
1355
+ order: [
1356
+ "cover" /* cover */,
1357
+ "metadata" /* metadata */,
1358
+ "playbackControls" /* playbackControls */,
1359
+ "progressBar" /* progressBar */,
1360
+ "mediaActions" /* mediaActions */
1361
+ ]
1362
+ },
1363
+ expanded: {
1364
+ start: [
1365
+ "cover" /* cover */,
1366
+ "metadata" /* metadata */
1367
+ ],
1368
+ end: [
1369
+ "playbackControls" /* playbackControls */,
1370
+ "progressBar" /* progressBar */,
1371
+ "mediaActions" /* mediaActions */
1372
+ ]
1373
+ },
1374
+ publicationMetadata: {
1375
+ order: [
1376
+ "titleWithSubtitle" /* titleWithSubtitle */
1377
+ ]
1378
+ },
1379
+ radius: 5,
1380
+ spacing: 20,
1381
+ progressBar: {
1382
+ variant: "segmented" /* segmented */
1383
+ },
1384
+ defaults: {
1385
+ dockingWidth: 340,
1386
+ scrim: "rgba(0, 0, 0, 0.2)"
1387
+ },
1388
+ constraints: {
1389
+ ["bottomSheet" /* bottomSheet */]: 600,
1390
+ ["popover" /* popover */]: 600,
1391
+ ["modal" /* modal */]: 600,
1392
+ cover: 300
1393
+ }
1394
+ },
1395
+ breakpoints: {
1396
+ ["compact" /* compact */]: 600,
1397
+ ["medium" /* medium */]: 840,
1398
+ ["expanded" /* expanded */]: 1200,
1399
+ ["large" /* large */]: 1600,
1400
+ ["xLarge" /* xLarge */]: null
1401
+ },
1402
+ themes: {
1403
+ audioOrder: [
1404
+ "auto",
1405
+ "light" /* light */,
1406
+ "dark" /* dark */
1407
+ ],
1408
+ systemThemes: {
1409
+ light: "light" /* light */,
1410
+ dark: "dark" /* dark */
1411
+ },
1412
+ keys: {
1413
+ ["light" /* light */]: lightTheme,
1414
+ ["dark" /* dark */]: darkTheme
1415
+ }
1416
+ }
1417
+ },
1418
+ actions: {
1419
+ primary: {
1420
+ displayOrder: [
1421
+ "audio.volume" /* volume */,
1422
+ "audio.playbackRate" /* playbackRate */,
1423
+ "audio.toc" /* toc */,
1424
+ "audio.sleepTimer" /* sleepTimer */
1425
+ ],
1426
+ keys: {
1427
+ ["audio.volume" /* volume */]: defaultAudioVolumeAction,
1428
+ ["audio.playbackRate" /* playbackRate */]: defaultAudioPlaybackRateAction,
1429
+ ["audio.toc" /* toc */]: defaultAudioTocAction,
1430
+ ["audio.sleepTimer" /* sleepTimer */]: defaultAudioSleepTimerAction
1431
+ }
1432
+ },
1433
+ secondary: {
1434
+ displayOrder: [
1435
+ "audio.remotePlayback" /* remotePlayback */,
1436
+ "settings" /* settings */
1437
+ ],
1438
+ collapse: {
1439
+ ["compact" /* compact */]: 2,
1440
+ ["medium" /* medium */]: 3
1441
+ },
1442
+ keys: {
1443
+ ["audio.remotePlayback" /* remotePlayback */]: defaultAudioRemotePlaybackAction,
1444
+ ["settings" /* settings */]: defaultSettingsAction
1445
+ }
1446
+ }
1447
+ },
1448
+ settings: {
1449
+ order: [
1450
+ "theme" /* theme */,
1451
+ "skipBackwardInterval" /* skipBackwardInterval */,
1452
+ "skipForwardInterval" /* skipForwardInterval */,
1453
+ "autoPlay" /* autoPlay */
1454
+ ],
1455
+ keys: {
1456
+ ["volume" /* volume */]: defaultAudioVolume,
1457
+ ["playbackRate" /* playbackRate */]: defaultAudioPlaybackRate,
1458
+ ["skipBackwardInterval" /* skipBackwardInterval */]: defaultAudioSkipBackwardInterval,
1459
+ ["skipForwardInterval" /* skipForwardInterval */]: defaultAudioSkipForwardInterval,
1460
+ ["sleepTimer" /* sleepTimer */]: defaultAudioSleepTimer
1461
+ }
1462
+ },
1463
+ contentProtection: defaultAudioContentProtectionConfig,
1464
+ affordances: {
1465
+ previous: "toc" /* toc */,
1466
+ next: "toc" /* toc */
1467
+ },
1468
+ shortcuts: {
1469
+ representation: "symbol" /* symbol */,
1470
+ joiner: "+"
1471
+ },
1472
+ docking: {
1473
+ displayOrder: [
1474
+ "dockingTransient" /* transient */,
1475
+ "dockingStart" /* start */,
1476
+ "dockingEnd" /* end */
1477
+ ],
1478
+ // Only toc is dockable; others have dockable:none so dock panels are TOC-only
1479
+ // Matches EPUB config: no docking on compact/medium (mobile/tablet portrait)
1480
+ dock: {
1481
+ ["compact" /* compact */]: "none" /* none */,
1482
+ ["medium" /* medium */]: "none" /* none */,
1483
+ ["expanded" /* expanded */]: "start" /* start */,
1484
+ ["large" /* large */]: "both" /* both */,
1485
+ ["xLarge" /* xLarge */]: "both" /* both */
1486
+ },
1487
+ collapse: true,
1488
+ keys: {
1489
+ ["dockingStart" /* start */]: { visibility: "overflow" /* overflow */, shortcut: null },
1490
+ ["dockingEnd" /* end */]: { visibility: "overflow" /* overflow */, shortcut: null },
1491
+ ["dockingTransient" /* transient */]: { visibility: "overflow" /* overflow */, shortcut: null }
1492
+ }
1493
+ }
1494
+ });
1495
+ var ThAudioPreferencesContext = createContext(null);
1496
+ var defaultAudioPreferencesContextValue = {
1497
+ preferences: defaultAudioPreferences,
1498
+ updatePreferences: () => {
1499
+ throw new Error("updatePreferences must be used within a ThAudioPreferencesProvider");
1500
+ }
1501
+ };
1502
+
1503
+ // src/preferences/adapters/ThAudioMemoryPreferencesAdapter.ts
1504
+ var ThAudioMemoryPreferencesAdapter = class {
1505
+ currentPreferences;
1506
+ listeners = /* @__PURE__ */ new Set();
1507
+ constructor(initialPreferences) {
1508
+ this.currentPreferences = { ...initialPreferences };
1509
+ }
1510
+ getPreferences() {
1511
+ return { ...this.currentPreferences };
1512
+ }
1513
+ setPreferences(prefs) {
1514
+ this.currentPreferences = { ...prefs };
1515
+ this.notifyListeners(this.currentPreferences);
1516
+ }
1517
+ subscribe(listener) {
1518
+ this.listeners.add(listener);
1519
+ }
1520
+ unsubscribe(listener) {
1521
+ this.listeners.delete(listener);
1522
+ }
1523
+ notifyListeners(prefs) {
1524
+ this.listeners.forEach((listener) => listener({ ...prefs }));
1525
+ }
1526
+ };
1527
+ function ThAudioPreferencesProvider({
1528
+ adapter,
1529
+ initialPreferences,
1530
+ devMode,
1531
+ children
1532
+ }) {
1533
+ const effectiveAdapter = useMemo(() => {
1534
+ let fallback = defaultAudioPreferencesContextValue.preferences;
1535
+ if (devMode && !initialPreferences) {
1536
+ fallback = { ...fallback, contentProtection: devContentProtectionConfig };
1537
+ }
1538
+ return adapter || new ThAudioMemoryPreferencesAdapter(
1539
+ initialPreferences || fallback
1540
+ );
1541
+ }, [adapter, initialPreferences, devMode]);
1542
+ const [preferences, setPreferences] = useState(
1543
+ (() => {
1544
+ let fallback = defaultAudioPreferencesContextValue.preferences;
1545
+ if (devMode && !initialPreferences) {
1546
+ fallback = { ...fallback, contentProtection: devContentProtectionConfig };
1547
+ }
1548
+ return initialPreferences || fallback;
1549
+ })()
1550
+ );
1551
+ const handlePreferenceChange = useCallback((newPrefs) => {
1552
+ setPreferences(
1553
+ (prev) => JSON.stringify(prev) === JSON.stringify(newPrefs) ? prev : newPrefs
1554
+ );
1555
+ }, []);
1556
+ useEffect(() => {
1557
+ effectiveAdapter.subscribe(handlePreferenceChange);
1558
+ return () => {
1559
+ effectiveAdapter.unsubscribe(handlePreferenceChange);
1560
+ };
1561
+ }, [effectiveAdapter, handlePreferenceChange]);
1562
+ const contextValue = useMemo(() => ({
1563
+ preferences,
1564
+ updatePreferences: (newPrefs) => {
1565
+ effectiveAdapter.setPreferences(newPrefs);
1566
+ }
1567
+ }), [preferences, effectiveAdapter]);
1568
+ return /* @__PURE__ */ jsx(ThAudioPreferencesContext.Provider, { value: contextValue, children });
1569
+ }
1570
+
1571
+ // src/preferences/preferences.ts
1572
+ var createPreferences = (params) => {
1573
+ if (params.actions) {
1574
+ validateObjectKeys(
1575
+ [
1576
+ params.actions.reflowOrder,
1577
+ params.actions.fxlOrder,
1578
+ params.actions.webPubOrder
1579
+ ],
1580
+ params.actions.keys,
1581
+ "actions"
1582
+ );
1583
+ }
1584
+ if (params.theming?.themes) {
1585
+ validateObjectKeys(
1586
+ [params.theming.themes.reflowOrder, params.theming.themes.fxlOrder],
1587
+ params.theming.themes.keys,
1588
+ "theming.themes",
1589
+ "auto"
1590
+ // Special case for themes
1591
+ );
1592
+ }
1593
+ if (params.settings.spacing?.presets) {
1594
+ validateObjectKeys(
1595
+ [params.settings.spacing.presets.reflowOrder],
1596
+ params.settings.spacing.presets.keys,
1597
+ "settings.spacing.presets",
1598
+ ["publisher", "custom"]
1599
+ );
1600
+ }
1601
+ if (params.settings?.spacing?.presets?.keys && params.settings?.keys) {
1602
+ const spacingSettings = params.settings.spacing.presets.keys;
1603
+ const spacingThemes = params.settings.spacing.presets.keys;
1604
+ const adjustSpacingValue = (key, value, context) => {
1605
+ const settingKey = Object.values(ThSettingsKeys).find((k) => k === key);
1606
+ if (!settingKey) {
1607
+ return value;
1608
+ }
1609
+ const setting = spacingSettings[settingKey];
1610
+ if (!setting) {
1611
+ return value;
1612
+ }
1613
+ let range;
1614
+ let step;
1615
+ if (setting && typeof setting === "object" && "range" in setting) {
1616
+ range = setting.range;
1617
+ step = setting.step;
1618
+ } else if (setting && typeof setting === "object") {
1619
+ return value;
1620
+ }
1621
+ let adjustedValue = value;
1622
+ if (range) {
1623
+ const [min, max] = range;
1624
+ if (adjustedValue < min) {
1625
+ console.warn(`Adjusting value ${value} for ${context.join(".")} to minimum allowed value ${min}`);
1626
+ adjustedValue = min;
1627
+ } else if (adjustedValue > max) {
1628
+ console.warn(`Adjusting value ${value} for ${context.join(".")} to maximum allowed value ${max}`);
1629
+ adjustedValue = max;
1630
+ }
1631
+ }
1632
+ if (step && range) {
1633
+ const [min] = range;
1634
+ const steps = Math.round((adjustedValue - min) / step);
1635
+ const steppedValue = parseFloat((min + steps * step).toFixed(10));
1636
+ const finalValue = Math.min(Math.max(steppedValue, range[0]), range[1]);
1637
+ if (Math.abs(finalValue - adjustedValue) > Number.EPSILON) {
1638
+ console.warn(`Adjusting value ${value} for ${context.join(".")} to nearest step value ${finalValue}`);
1639
+ adjustedValue = finalValue;
1640
+ }
1641
+ }
1642
+ return adjustedValue;
1643
+ };
1644
+ for (const [themeName, spacingTheme] of Object.entries(spacingThemes)) {
1645
+ if (spacingTheme && typeof spacingTheme === "object") {
1646
+ const adjustedTheme = {};
1647
+ let hasAdjustedValues = false;
1648
+ for (const [key, value] of Object.entries(spacingTheme)) {
1649
+ if (typeof value === "number") {
1650
+ const context = ["theming", "spacing", "keys", themeName, key];
1651
+ const adjustedValue = adjustSpacingValue(key, value, context);
1652
+ adjustedTheme[key] = adjustedValue;
1653
+ if (adjustedValue !== value) {
1654
+ hasAdjustedValues = true;
1655
+ }
1656
+ } else {
1657
+ adjustedTheme[key] = value;
1658
+ }
1659
+ }
1660
+ if (hasAdjustedValues) {
1661
+ spacingThemes[themeName] = adjustedTheme;
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ if (params.settings?.keys?.fontFamily) {
1667
+ const fontFamilyPref = params.settings.keys.fontFamily;
1668
+ const languageMap = /* @__PURE__ */ new Map();
1669
+ Object.entries(fontFamilyPref).forEach(([collectionName, collectionData]) => {
1670
+ if (collectionName === "default") return;
1671
+ const supportedLangs = "supportedLanguages" in collectionData ? collectionData.supportedLanguages : null;
1672
+ if (supportedLangs && Array.isArray(supportedLangs)) {
1673
+ supportedLangs.forEach((lang) => {
1674
+ if (!languageMap.has(lang)) {
1675
+ languageMap.set(lang, []);
1676
+ }
1677
+ languageMap.get(lang).push(collectionName);
1678
+ });
1679
+ }
1680
+ });
1681
+ languageMap.forEach((collections, language) => {
1682
+ if (collections.length > 1) {
1683
+ console.warn(`Language "${language}" is supported by multiple font collections: ${collections.join(", ")}. This may cause ambiguous font selection. Consider consolidating to a single collection per language.`);
1684
+ }
1685
+ });
1686
+ }
1687
+ const validateRangePresets2 = (pref, context) => {
1688
+ if (pref.variant !== "sliderWithPresets" /* sliderWithPresets */ || !pref.presets?.length) return;
1689
+ const [min, max] = [Math.min(...pref.range), Math.max(...pref.range)];
1690
+ const step = pref.step;
1691
+ const tolerance = step * 1e-9;
1692
+ const invalid = pref.presets.filter((p) => {
1693
+ if (p < min || p > max) return true;
1694
+ const offset = (p - min) / step;
1695
+ return Math.abs(offset - Math.round(offset)) > tolerance;
1696
+ });
1697
+ if (invalid.length > 0) {
1698
+ console.warn(`${context}: presets [${invalid.join(", ")}] are not reachable with range=[${min}, ${max}] and step=${step}.`);
1699
+ }
1700
+ };
1701
+ Object.entries(params.settings?.keys ?? {}).forEach(([key, pref]) => {
1702
+ if (pref && typeof pref === "object" && "variant" in pref) {
1703
+ validateRangePresets2(pref, `settings.keys.${key}`);
1704
+ }
1705
+ });
1706
+ return params;
1707
+ };
1708
+
1709
+ // src/preferences/defaultPreferences.ts
1710
+ var defaultPreferences = createPreferences({
1711
+ // direction: ThLayoutDirection.ltr,
1712
+ // locale: "en",
1713
+ experiments: {
1714
+ reflow: ["experimentalHeaderFiltering", "experimentalZoom"],
1715
+ webPub: ["experimentalHeaderFiltering", "experimentalZoom"]
1716
+ },
1717
+ metadata: {
1718
+ documentTitle: {
1719
+ format: "title" /* title */
1720
+ }
1721
+ },
1722
+ typography: {
1723
+ minimalLineLength: 40,
1724
+ // undefined | null | number of characters. If 2 cols will switch to 1 based on this
1725
+ optimalLineLength: 55,
1726
+ // number of characters. If auto layout, picks colCount based on this
1727
+ maximalLineLength: 70,
1728
+ // undefined | null | number of characters.
1729
+ pageGutter: 20
1730
+ },
1731
+ theming: {
1732
+ header: {
1733
+ backLink: {
1734
+ variant: "arrow" /* arrow */,
1735
+ visibility: "partially",
1736
+ href: "/"
1737
+ },
1738
+ runningHead: {
1739
+ format: {
1740
+ reflow: {
1741
+ default: {
1742
+ variants: "chapter" /* chapter */,
1743
+ displayInImmersive: true,
1744
+ displayInFullscreen: false
1745
+ },
1746
+ breakpoints: {
1747
+ ["compact" /* compact */]: {
1748
+ variants: "chapter" /* chapter */,
1749
+ displayInImmersive: false,
1750
+ displayInFullscreen: false
1751
+ }
1752
+ }
1753
+ },
1754
+ fxl: {
1755
+ default: {
1756
+ variants: "title" /* title */,
1757
+ displayInImmersive: true,
1758
+ displayInFullscreen: true
1759
+ },
1760
+ breakpoints: {
1761
+ ["compact" /* compact */]: {
1762
+ variants: "title" /* title */,
1763
+ displayInImmersive: false,
1764
+ displayInFullscreen: true
1765
+ }
1766
+ }
1767
+ },
1768
+ webPub: {
1769
+ default: {
1770
+ variants: "chapter" /* chapter */,
1771
+ displayInImmersive: true,
1772
+ displayInFullscreen: true
1773
+ }
1774
+ }
1775
+ }
1776
+ }
1777
+ },
1778
+ progression: {
1779
+ format: {
1780
+ reflow: {
1781
+ default: {
1782
+ variants: [
1783
+ "positionsPercentOfTotal" /* positionsPercentOfTotal */,
1784
+ "progressionOfResource" /* progressionOfResource */
1785
+ ],
1786
+ displayInImmersive: true,
1787
+ displayInFullscreen: false
1788
+ },
1789
+ breakpoints: {
1790
+ ["compact" /* compact */]: {
1791
+ variants: [
1792
+ "positionsOfTotal" /* positionsOfTotal */,
1793
+ "resourceProgression" /* resourceProgression */
1794
+ ],
1795
+ displayInImmersive: false,
1796
+ displayInFullscreen: false
1797
+ }
1798
+ }
1799
+ },
1800
+ fxl: {
1801
+ default: {
1802
+ variants: [
1803
+ "positionsOfTotal" /* positionsOfTotal */,
1804
+ "overallProgression" /* overallProgression */,
1805
+ "none" /* none */
1806
+ ],
1807
+ displayInImmersive: true,
1808
+ displayInFullscreen: true
1809
+ },
1810
+ breakpoints: {
1811
+ ["compact" /* compact */]: {
1812
+ variants: [
1813
+ "positions" /* positions */,
1814
+ "overallProgression" /* overallProgression */,
1815
+ "none" /* none */
1816
+ ],
1817
+ displayInImmersive: false,
1818
+ displayInFullscreen: true
1819
+ }
1820
+ }
1821
+ },
1822
+ webPub: {
1823
+ default: {
1824
+ variants: [
1825
+ "readingOrderIndex" /* readingOrderIndex */,
1826
+ "none" /* none */
1827
+ ],
1828
+ displayInImmersive: true,
1829
+ displayInFullscreen: true
1830
+ }
1831
+ }
1832
+ }
1833
+ },
1834
+ arrow: {
1835
+ size: 40,
1836
+ // Size of the left and right arrows in px
1837
+ offset: 5
1838
+ // offset of the arrows from the edges in px
1839
+ },
1840
+ icon: {
1841
+ size: 24,
1842
+ // Size of icons in px
1843
+ tooltipOffset: 10
1844
+ // offset of tooltip in px
1845
+ },
1846
+ layout: {
1847
+ ui: {
1848
+ reflow: "layered-ui" /* layered */,
1849
+ fxl: "layered-ui" /* layered */,
1850
+ webPub: "stacked-ui" /* stacked */
1851
+ },
1852
+ radius: 5,
1853
+ // border-radius of containers
1854
+ spacing: 20,
1855
+ // padding of containers/sheets
1856
+ defaults: {
1857
+ dockingWidth: 340,
1858
+ // default width of resizable panels
1859
+ scrim: "rgba(0, 0, 0, 0.2)"
1860
+ // default scrim/underlay bg-color
1861
+ },
1862
+ constraints: {
1863
+ ["bottomSheet" /* bottomSheet */]: 600,
1864
+ // Max-width of all bottom sheets
1865
+ ["popover" /* popover */]: 600,
1866
+ // Max-width of all popover sheets
1867
+ ["modal" /* modal */]: 600,
1868
+ // Max-width of all modal sheets
1869
+ pagination: 1024,
1870
+ // Max-width of pagination component
1871
+ dropdown: 250
1872
+ // Max-height of main UI dropdowns
1873
+ }
1874
+ },
1875
+ breakpoints: {
1876
+ // See https://m3.material.io/foundations/layout/applying-layout/window-size-classes
1877
+ ["compact" /* compact */]: 600,
1878
+ // Phone in portrait
1879
+ ["medium" /* medium */]: 840,
1880
+ // Tablet in portrait, Foldable in portrait (unfolded)
1881
+ ["expanded" /* expanded */]: 1200,
1882
+ // Phone in landscape, Tablet in landscape, Foldable in landscape (unfolded), Desktop
1883
+ ["large" /* large */]: 1600,
1884
+ // Desktop
1885
+ ["xLarge" /* xLarge */]: null
1886
+ // Desktop Ultra-wide
1887
+ },
1888
+ themes: {
1889
+ reflowOrder: [
1890
+ "auto",
1891
+ "light" /* light */,
1892
+ "paper" /* paper */,
1893
+ "sepia" /* sepia */,
1894
+ "dark" /* dark */,
1895
+ "contrast1" /* contrast1 */,
1896
+ "contrast2" /* contrast2 */,
1897
+ "contrast3" /* contrast3 */
1898
+ ],
1899
+ fxlOrder: [
1900
+ "auto",
1901
+ "light" /* light */,
1902
+ "dark" /* dark */
1903
+ ],
1904
+ systemThemes: {
1905
+ light: "light" /* light */,
1906
+ dark: "dark" /* dark */
1907
+ },
1908
+ keys: {
1909
+ ["light" /* light */]: lightTheme,
1910
+ ["dark" /* dark */]: darkTheme,
1911
+ ["paper" /* paper */]: paperTheme,
1912
+ ["sepia" /* sepia */]: sepiaTheme,
1913
+ ["contrast1" /* contrast1 */]: contrast1Theme,
1914
+ ["contrast2" /* contrast2 */]: contrast2Theme,
1915
+ ["contrast3" /* contrast3 */]: contrast3Theme
1916
+ }
1917
+ }
1918
+ },
1919
+ contentProtection: defaultContentProtectionConfig,
1920
+ affordances: {
1921
+ scroll: {
1922
+ hintInImmersive: true,
1923
+ toggleOnMiddlePointer: ["tap", "click"],
1924
+ hideOnForwardScroll: true,
1925
+ showOnBackwardScroll: true
1926
+ },
1927
+ paginated: {
1928
+ reflow: {
1929
+ default: {
1930
+ variant: "layered" /* layered */,
1931
+ discard: ["navigation"],
1932
+ hint: ["layoutChange"]
1933
+ },
1934
+ breakpoints: {
1935
+ ["large" /* large */]: {
1936
+ variant: "stacked" /* stacked */
1937
+ },
1938
+ ["xLarge" /* xLarge */]: {
1939
+ variant: "stacked" /* stacked */
1940
+ }
1941
+ }
1942
+ },
1943
+ fxl: {
1944
+ // Note FXL arrows are always layered
1945
+ // FXL navigator is using the window width to calculate the layout
1946
+ // so we need to force the layered variant to prevent layout issues
1947
+ default: {
1948
+ variant: "layered" /* layered */,
1949
+ discard: ["navigation"],
1950
+ hint: "none"
1951
+ }
1952
+ }
1953
+ }
1954
+ },
1955
+ shortcuts: {
1956
+ representation: "symbol" /* symbol */,
1957
+ joiner: "+"
1958
+ },
1959
+ actions: {
1960
+ reflowOrder: [
1961
+ "settings" /* settings */,
1962
+ "toc" /* toc */,
1963
+ "fullscreen" /* fullscreen */,
1964
+ "jumpToPosition" /* jumpToPosition */
1965
+ ],
1966
+ fxlOrder: [
1967
+ "settings" /* settings */,
1968
+ "toc" /* toc */,
1969
+ "fullscreen" /* fullscreen */,
1970
+ "jumpToPosition" /* jumpToPosition */
1971
+ ],
1972
+ webPubOrder: [
1973
+ "settings" /* settings */,
1974
+ "toc" /* toc */,
1975
+ "fullscreen" /* fullscreen */
1976
+ ],
1977
+ collapse: {
1978
+ // Number of partially icons to display
1979
+ // value "all" a keyword for the length of displayOrder above
1980
+ // Icons with visibility always are excluded from collapsing
1981
+ ["compact" /* compact */]: 2,
1982
+ ["medium" /* medium */]: 3
1983
+ },
1984
+ keys: {
1985
+ ["settings" /* settings */]: defaultSettingsAction,
1986
+ ["fullscreen" /* fullscreen */]: defaultFullscreenAction,
1987
+ ["toc" /* toc */]: defaultTocAction,
1988
+ ["jumpToPosition" /* jumpToPosition */]: defaultJumpToPositionAction
1989
+ }
1990
+ },
1991
+ docking: {
1992
+ displayOrder: [
1993
+ "dockingTransient" /* transient */,
1994
+ "dockingStart" /* start */,
1995
+ "dockingEnd" /* end */
1996
+ ],
1997
+ dock: {
1998
+ ["compact" /* compact */]: "none" /* none */,
1999
+ ["medium" /* medium */]: "none" /* none */,
2000
+ ["expanded" /* expanded */]: "start" /* start */,
2001
+ ["large" /* large */]: "both" /* both */,
2002
+ ["xLarge" /* xLarge */]: "both" /* both */
2003
+ },
2004
+ collapse: true,
2005
+ keys: {
2006
+ ["dockingStart" /* start */]: {
2007
+ visibility: "overflow" /* overflow */,
2008
+ shortcut: null
2009
+ },
2010
+ ["dockingEnd" /* end */]: {
2011
+ visibility: "overflow" /* overflow */,
2012
+ shortcut: null
2013
+ },
2014
+ ["dockingTransient" /* transient */]: {
2015
+ visibility: "overflow" /* overflow */,
2016
+ shortcut: null
2017
+ }
2018
+ }
2019
+ },
2020
+ settings: {
2021
+ reflowOrder: [
2022
+ "zoom" /* zoom */,
2023
+ "textGroup" /* textGroup */,
2024
+ "theme" /* theme */,
2025
+ "spacingGroup" /* spacingGroup */,
2026
+ "layout" /* layout */,
2027
+ "columns" /* columns */
2028
+ ],
2029
+ fxlOrder: [
2030
+ "theme" /* theme */,
2031
+ "columns" /* columns */
2032
+ ],
2033
+ webPubOrder: [
2034
+ "zoom" /* zoom */,
2035
+ "textGroup" /* textGroup */,
2036
+ "spacingGroup" /* spacingGroup */
2037
+ ],
2038
+ keys: {
2039
+ ["fontFamily" /* fontFamily */]: {
2040
+ default: defaultFontCollection,
2041
+ arabic: { supportedLanguages: ["ar", "fa"], fonts: arabicFarsiCollection },
2042
+ hebrew: { supportedLanguages: ["he"], fonts: hebrewCollection },
2043
+ "chinese-simplified": { supportedLanguages: ["zh", "zh-hans", "zh-cn"], fonts: chineseSimplifiedCollection },
2044
+ "chinese-traditional": { supportedLanguages: ["zh-hant", "zh-tw", "zh-hk"], fonts: chineseTraditionalCollection },
2045
+ japanese: { supportedLanguages: ["ja"], fonts: japaneseCollection },
2046
+ "japanese-vertical": { supportedLanguages: ["ja-v"], fonts: japaneseVerticalCollection },
2047
+ korean: { supportedLanguages: ["ko"], fonts: koreanCollection },
2048
+ tamil: { supportedLanguages: ["ta"], fonts: tamilCollection }
2049
+ },
2050
+ ["letterSpacing" /* letterSpacing */]: defaultLetterSpacing,
2051
+ ["lineHeight" /* lineHeight */]: {
2052
+ allowUnset: false,
2053
+ keys: defaultLineHeights
2054
+ },
2055
+ ["paragraphIndent" /* paragraphIndent */]: defaultParagraphIndent,
2056
+ ["paragraphSpacing" /* paragraphSpacing */]: defaultParagraphSpacing,
2057
+ ["wordSpacing" /* wordSpacing */]: defaultWordSpacing,
2058
+ ["zoom" /* zoom */]: defaultZoom
2059
+ },
2060
+ text: {
2061
+ header: "previous" /* previous */,
2062
+ main: defaultTextSettingsMain,
2063
+ subPanel: defaultTextSettingsSubpanel
2064
+ },
2065
+ spacing: {
2066
+ header: "previous" /* previous */,
2067
+ main: defaultSpacingSettingsMain,
2068
+ subPanel: defaultSpacingSettingsSubpanel,
2069
+ presets: {
2070
+ reflowOrder: defaultSpacingPresetsOrder,
2071
+ webPubOrder: defaultSpacingPresetsOrder,
2072
+ keys: defaultSpacingPresets
2073
+ }
2074
+ }
2075
+ }
2076
+ });
2077
+
2078
+ // src/preferences/ThPreferencesContext.ts
2079
+ var ThPreferencesContext = createContext(null);
2080
+ var defaultPreferencesContextValue = {
2081
+ preferences: defaultPreferences,
2082
+ updatePreferences: () => {
2083
+ throw new Error("updatePreferences must be used within a ThPreferencesProvider with an adapter");
2084
+ }
2085
+ };
2086
+
2087
+ // src/preferences/adapters/ThMemoryPreferencesAdapter.ts
2088
+ var ThMemoryPreferencesAdapter = class {
2089
+ currentPreferences;
2090
+ listeners = /* @__PURE__ */ new Set();
2091
+ constructor(initialPreferences) {
2092
+ this.currentPreferences = { ...initialPreferences };
2093
+ }
2094
+ getPreferences() {
2095
+ return { ...this.currentPreferences };
2096
+ }
2097
+ setPreferences(prefs) {
2098
+ this.currentPreferences = { ...prefs };
2099
+ this.notifyListeners(this.currentPreferences);
2100
+ }
2101
+ subscribe(listener) {
2102
+ this.listeners.add(listener);
2103
+ }
2104
+ unsubscribe(listener) {
2105
+ this.listeners.delete(listener);
2106
+ }
2107
+ notifyListeners(prefs) {
2108
+ this.listeners.forEach((listener) => listener({ ...prefs }));
2109
+ }
2110
+ };
2111
+ function ThPreferencesProvider({
2112
+ adapter,
2113
+ initialPreferences,
2114
+ devMode,
2115
+ children
2116
+ }) {
2117
+ const effectiveAdapter = useMemo(() => {
2118
+ let fallbackPreferences = defaultPreferencesContextValue.preferences;
2119
+ if (devMode && !initialPreferences) {
2120
+ fallbackPreferences = {
2121
+ ...fallbackPreferences,
2122
+ contentProtection: devContentProtectionConfig
2123
+ };
2124
+ }
2125
+ return adapter || new ThMemoryPreferencesAdapter(
2126
+ initialPreferences || fallbackPreferences
2127
+ );
2128
+ }, [adapter, initialPreferences, devMode]);
2129
+ const [preferences, setPreferences] = useState(
2130
+ (() => {
2131
+ let fallbackPreferences = defaultPreferencesContextValue.preferences;
2132
+ if (devMode && !initialPreferences) {
2133
+ fallbackPreferences = {
2134
+ ...fallbackPreferences,
2135
+ contentProtection: devContentProtectionConfig
2136
+ };
2137
+ }
2138
+ return initialPreferences || fallbackPreferences;
2139
+ })()
2140
+ );
2141
+ const handlePreferenceChange = useCallback((newPrefs) => {
2142
+ setPreferences((prev) => {
2143
+ return JSON.stringify(prev) === JSON.stringify(newPrefs) ? prev : newPrefs;
2144
+ });
2145
+ }, []);
2146
+ useEffect(() => {
2147
+ effectiveAdapter.subscribe(handlePreferenceChange);
2148
+ return () => {
2149
+ effectiveAdapter.unsubscribe(handlePreferenceChange);
2150
+ };
2151
+ }, [effectiveAdapter, handlePreferenceChange]);
2152
+ const contextValue = useMemo(() => ({
2153
+ preferences,
2154
+ updatePreferences: (newPrefs) => {
2155
+ effectiveAdapter.setPreferences(newPrefs);
2156
+ }
2157
+ }), [preferences, effectiveAdapter]);
2158
+ return /* @__PURE__ */ jsx(ThPreferencesContext.Provider, { value: contextValue, children });
2159
+ }
2160
+ var useActionsPreferences = () => {
2161
+ const audioCtx = useContext(ThAudioPreferencesContext);
2162
+ const readerCtx = useContext(ThPreferencesContext);
2163
+ const audioPrimaryKeys = audioCtx?.preferences.actions.primary.keys;
2164
+ const audioSecondaryKeys = audioCtx?.preferences.actions.secondary.keys;
2165
+ const audioDocking = audioCtx?.preferences.docking;
2166
+ const audioActionsKeys = useMemo(() => {
2167
+ if (!audioPrimaryKeys && !audioSecondaryKeys) return null;
2168
+ return { ...audioPrimaryKeys, ...audioSecondaryKeys };
2169
+ }, [audioPrimaryKeys, audioSecondaryKeys]);
2170
+ const audioResult = useMemo(() => {
2171
+ if (!audioCtx || !audioActionsKeys || !audioDocking) return null;
2172
+ return { docking: audioDocking, actionsKeys: audioActionsKeys };
2173
+ }, [audioCtx, audioDocking, audioActionsKeys]);
2174
+ const readerResult = useMemo(() => {
2175
+ if (!readerCtx) return null;
2176
+ return {
2177
+ docking: readerCtx.preferences.docking,
2178
+ actionsKeys: readerCtx.preferences.actions.keys
2179
+ };
2180
+ }, [readerCtx]);
2181
+ if (audioResult) return audioResult;
2182
+ if (readerResult) return readerResult;
2183
+ throw new Error("useActionsPreferences must be used within a ThPreferencesProvider or ThAudioPreferencesProvider");
2184
+ };
2185
+ var useAudioActionsPreferences = () => {
2186
+ const audioCtx = useContext(ThAudioPreferencesContext);
2187
+ if (!audioCtx) {
2188
+ throw new Error("useAudioActionsPreferences must be used within a ThAudioPreferencesProvider");
2189
+ }
2190
+ return {
2191
+ docking: audioCtx.preferences.docking,
2192
+ primaryActionsKeys: audioCtx.preferences.actions.primary.keys,
2193
+ secondaryActionsKeys: audioCtx.preferences.actions.secondary.keys
2194
+ };
2195
+ };
2196
+ function useAudioPreferences() {
2197
+ const context = useContext(ThAudioPreferencesContext);
2198
+ if (!context) {
2199
+ throw new Error("useAudioPreferences must be used within a ThAudioPreferencesProvider");
2200
+ }
2201
+ return {
2202
+ preferences: context.preferences,
2203
+ updatePreferences: context.updatePreferences
2204
+ };
2205
+ }
2206
+
2207
+ // src/preferences/services/createBunnyFontResources.ts
2208
+ var buildBunnyFontsUrl = ({
2209
+ family,
2210
+ weights,
2211
+ styles = ["normal"]
2212
+ }) => {
2213
+ if (weights.type !== "static") {
2214
+ throw new Error("Bunny Fonts only supports static fonts");
2215
+ }
2216
+ const weightValues = weights.values;
2217
+ const variants = /* @__PURE__ */ new Set();
2218
+ for (const weight of weightValues) {
2219
+ variants.add(weight.toString());
2220
+ if (styles.includes("italic")) {
2221
+ variants.add(`${weight}i`);
2222
+ }
2223
+ }
2224
+ const variantList = Array.from(variants).sort();
2225
+ const familyParam = family.replace(/ /g, "-").toLowerCase();
2226
+ const variantParam = variantList.join(",");
2227
+ return `https://fonts.bunny.net/css?family=${familyParam}:${variantParam}`;
2228
+ };
2229
+ var createBunnyFontResources = (font) => {
2230
+ if (font.source.type !== "custom" || font.source.provider !== "bunny" || font.spec.weights.type !== "static") {
2231
+ return null;
2232
+ }
2233
+ const { family, weights, styles } = font.spec;
2234
+ const url = buildBunnyFontsUrl({
2235
+ family,
2236
+ weights,
2237
+ styles
2238
+ });
2239
+ return {
2240
+ as: "link",
2241
+ rel: "stylesheet",
2242
+ url
2243
+ };
2244
+ };
2245
+
2246
+ // src/preferences/services/createGoogleFontResources.ts
2247
+ var buildGoogleFontsV2Url = ({
2248
+ family,
2249
+ weights,
2250
+ styles = ["normal"],
2251
+ widths,
2252
+ display = "block",
2253
+ text
2254
+ }) => {
2255
+ if (text) {
2256
+ return `https://fonts.googleapis.com/css2?family=${family.replace(/ /g, "+")}&text=${encodeURIComponent(text)}`;
2257
+ }
2258
+ const hasItalic = styles.includes("italic");
2259
+ const hasWidth = !!widths;
2260
+ const weightValues = weights.type === "static" ? weights.values.join(",") : `${weights.min}..${weights.max}`;
2261
+ const widthValues = hasWidth && widths ? `${widths.min}..${widths.max}` : void 0;
2262
+ const familyParam = family.replace(/ /g, "+");
2263
+ let axesParam;
2264
+ if (hasItalic && hasWidth) {
2265
+ const variants = [
2266
+ `0,${widthValues},${weightValues}`,
2267
+ // normal
2268
+ `1,${widthValues},${weightValues}`
2269
+ // italic
2270
+ ];
2271
+ axesParam = `:ital,wdth,wght@${variants.join(";")}`;
2272
+ } else if (hasItalic) {
2273
+ const variants = [
2274
+ `0,${weightValues}`,
2275
+ // normal
2276
+ `1,${weightValues}`
2277
+ // italic
2278
+ ];
2279
+ axesParam = `:ital,wght@${variants.join(";")}`;
2280
+ } else if (hasWidth) {
2281
+ axesParam = `:wdth,wght@${widthValues},${weightValues}`;
2282
+ } else {
2283
+ axesParam = `:wght@${weightValues}`;
2284
+ }
2285
+ const displayParam = display ? `&display=${display}` : "";
2286
+ return `https://fonts.googleapis.com/css2?family=${familyParam}${axesParam}${displayParam}`;
2287
+ };
2288
+ var createGoogleFontResources = (font, text) => {
2289
+ if (font.source.type !== "custom" || font.source.provider !== "google") {
2290
+ return null;
2291
+ }
2292
+ const { family, weights, display, styles, widths } = font.spec;
2293
+ const url = buildGoogleFontsV2Url({
2294
+ family,
2295
+ weights,
2296
+ display,
2297
+ styles,
2298
+ widths,
2299
+ text
2300
+ });
2301
+ return {
2302
+ as: "link",
2303
+ rel: "stylesheet",
2304
+ url
2305
+ };
2306
+ };
2307
+
2308
+ // src/preferences/services/createLocalFontResources.ts
2309
+ var getFontFormat = (path) => {
2310
+ const ext = path.split(".").pop()?.toLowerCase();
2311
+ switch (ext) {
2312
+ case "woff":
2313
+ return "woff";
2314
+ case "woff2":
2315
+ return "woff2";
2316
+ case "ttf":
2317
+ return "truetype";
2318
+ case "otf":
2319
+ return "opentype";
2320
+ case "eot":
2321
+ return "embedded-opentype";
2322
+ case "svg":
2323
+ return "svg";
2324
+ default:
2325
+ return "woff2";
2326
+ }
2327
+ };
2328
+ var createLocalFontResources = (font) => {
2329
+ if (font.source.type !== "custom" || font.source.provider !== "local") {
2330
+ return null;
2331
+ }
2332
+ const { family, weights, display, widths } = font.spec;
2333
+ const fontFiles = font.source.files || [];
2334
+ const cssContent = fontFiles.map((fontFile) => {
2335
+ const format = getFontFormat(fontFile.path);
2336
+ const fontUrl = new URL(fontFile.path, window.location.origin).toString();
2337
+ const isVariable = font.source.type === "custom" && font.source.provider === "local" && "variant" in font.source && font.source.variant === "variable";
2338
+ const rules = [
2339
+ `@font-face {`,
2340
+ ` font-family: "${family}";`,
2341
+ ` src: url("${fontUrl}") format("${format}");`
2342
+ ];
2343
+ if (isVariable && weights.type === "variable") {
2344
+ rules.push(` font-weight: ${weights.min} ${weights.max};`);
2345
+ } else if ("weight" in fontFile) {
2346
+ rules.push(` font-weight: ${fontFile.weight};`);
2347
+ }
2348
+ if ("style" in fontFile) {
2349
+ rules.push(` font-style: ${fontFile.style};`);
2350
+ }
2351
+ if (isVariable && widths) {
2352
+ rules.push(` font-stretch: ${widths.min}% ${widths.max}%;`);
2353
+ }
2354
+ if (display) {
2355
+ rules.push(` font-display: ${display};`);
2356
+ } else {
2357
+ rules.push(` font-display: block;`);
2358
+ }
2359
+ return rules.join("\n") + "\n}";
2360
+ }).filter(Boolean).join("\n\n");
2361
+ const blob = new Blob([cssContent], { type: "text/css" });
2362
+ return {
2363
+ as: "link",
2364
+ rel: "stylesheet",
2365
+ blob
2366
+ };
2367
+ };
2368
+
2369
+ // src/preferences/services/fonts.ts
2370
+ var createFontService = (fontFamilyPref) => {
2371
+ const allSupportedLanguages = [];
2372
+ const parsedFonts = /* @__PURE__ */ new Map();
2373
+ const bunnyFonts = /* @__PURE__ */ new Map();
2374
+ const googleFonts = /* @__PURE__ */ new Map();
2375
+ const localFonts = /* @__PURE__ */ new Map();
2376
+ const resolveFontLanguage = (bcp47Tag, scriptMode = "ltr") => {
2377
+ if (!bcp47Tag) return "default";
2378
+ if (allSupportedLanguages.includes(bcp47Tag)) {
2379
+ return bcp47Tag;
2380
+ }
2381
+ const parts = bcp47Tag.split(/[-_]/);
2382
+ const language = parts[0].toLowerCase();
2383
+ const scriptOrRegion = parts[1]?.toLowerCase();
2384
+ if (scriptOrRegion) {
2385
+ const langScriptOrRegion = `${language}-${scriptOrRegion}`;
2386
+ if (allSupportedLanguages.includes(langScriptOrRegion)) {
2387
+ return langScriptOrRegion;
2388
+ }
2389
+ }
2390
+ if (language === "ja" && !scriptOrRegion) {
2391
+ if (scriptMode === "cjk-vertical" && allSupportedLanguages.includes("ja-v")) {
2392
+ return "ja-v";
2393
+ }
2394
+ if (allSupportedLanguages.includes("ja")) {
2395
+ return "ja";
2396
+ }
2397
+ }
2398
+ const shouldFilter = language === "mn" && (scriptOrRegion === "mong" || scriptOrRegion === "cyrl") || language === "zh" && (scriptOrRegion === "hant" || scriptOrRegion === "tw" || scriptOrRegion === "hk");
2399
+ if (!shouldFilter && allSupportedLanguages.includes(language)) {
2400
+ return language;
2401
+ }
2402
+ return "default";
2403
+ };
2404
+ Object.entries(fontFamilyPref).forEach(([collectionName, collectionData]) => {
2405
+ const fontCollection = "fonts" in collectionData ? collectionData.fonts : collectionData;
2406
+ if ("supportedLanguages" in collectionData) {
2407
+ const reducedLanguages = collectionData.supportedLanguages.map((lang) => {
2408
+ const parts = lang.split(/[-_]/);
2409
+ const language = parts[0].toLowerCase();
2410
+ const scriptOrRegion = parts[1]?.toLowerCase();
2411
+ return scriptOrRegion ? `${language}-${scriptOrRegion}` : language;
2412
+ });
2413
+ allSupportedLanguages.push(...reducedLanguages);
2414
+ }
2415
+ bunnyFonts.set(collectionName, []);
2416
+ googleFonts.set(collectionName, []);
2417
+ localFonts.set(collectionName, []);
2418
+ const collectionBunnyFonts = bunnyFonts.get(collectionName);
2419
+ const collectionGoogleFonts = googleFonts.get(collectionName);
2420
+ const collectionLocalFonts = localFonts.get(collectionName);
2421
+ Object.entries(fontCollection).forEach(([id, font]) => {
2422
+ const fontFamily = font.spec.family;
2423
+ let fontStack = fontFamily;
2424
+ if (font.source.type === "custom") {
2425
+ switch (font.source.provider) {
2426
+ case "bunny":
2427
+ collectionBunnyFonts.push(font);
2428
+ break;
2429
+ case "google":
2430
+ collectionGoogleFonts.push(font);
2431
+ break;
2432
+ case "local":
2433
+ collectionLocalFonts.push(font);
2434
+ break;
2435
+ }
2436
+ }
2437
+ const wrapIfNeeded = (name) => {
2438
+ const trimmed = name.trim();
2439
+ if (!trimmed) return "";
2440
+ if (trimmed.includes(" ") && !/^['"].*['"]$/.test(trimmed)) {
2441
+ return `"${trimmed}"`;
2442
+ }
2443
+ return trimmed;
2444
+ };
2445
+ const wrappedFontFamily = wrapIfNeeded(fontFamily);
2446
+ if (font.spec.fallbacks?.length) {
2447
+ const uniqueFallbacks = [...new Set(
2448
+ font.spec.fallbacks.filter((fallback) => fallback.toLowerCase() !== fontFamily.toLowerCase()).map(wrapIfNeeded)
2449
+ )];
2450
+ if (uniqueFallbacks.length > 0) {
2451
+ fontStack = [wrappedFontFamily, ...uniqueFallbacks].join(", ");
2452
+ }
2453
+ }
2454
+ parsedFonts.set(id, {
2455
+ fontStack: fontStack || wrappedFontFamily,
2456
+ fontFamily: wrappedFontFamily,
2457
+ weights: font.spec.weights || null,
2458
+ widths: font.spec.widths || null
2459
+ });
2460
+ });
2461
+ });
2462
+ const defaultBunnyFonts = bunnyFonts.get("default") || [];
2463
+ const defaultGoogleFonts = googleFonts.get("default") || [];
2464
+ const defaultLocalFonts = localFonts.get("default") || [];
2465
+ const processFonts = (bunnyFontsList, googleFontsList, localFontsList, optimize = false) => {
2466
+ const result = {
2467
+ allowedDomains: [],
2468
+ prepend: [],
2469
+ append: []
2470
+ };
2471
+ const bunnyResources = bunnyFontsList.map((font) => createBunnyFontResources(font)).filter((resource) => resource !== null);
2472
+ if (bunnyResources.length > 0) {
2473
+ result.allowedDomains.push(
2474
+ "https://fonts.bunny.net"
2475
+ );
2476
+ result.prepend.push(
2477
+ {
2478
+ as: "link",
2479
+ rel: "preconnect",
2480
+ url: "https://fonts.bunny.net"
2481
+ }
2482
+ );
2483
+ result.append.push(...bunnyResources);
2484
+ }
2485
+ const googleResources = googleFontsList.map((font) => createGoogleFontResources(font, optimize ? font.name : void 0)).filter((resource) => resource !== null);
2486
+ if (googleResources.length > 0) {
2487
+ result.allowedDomains.push(
2488
+ "https://fonts.googleapis.com",
2489
+ "https://fonts.gstatic.com"
2490
+ );
2491
+ result.prepend.push(
2492
+ {
2493
+ as: "link",
2494
+ rel: "preconnect",
2495
+ url: "https://fonts.googleapis.com"
2496
+ },
2497
+ {
2498
+ as: "link",
2499
+ rel: "preconnect",
2500
+ url: "https://fonts.gstatic.com",
2501
+ attributes: { crossOrigin: "anonymous" }
2502
+ }
2503
+ );
2504
+ result.append.push(...googleResources);
2505
+ }
2506
+ const localResources = localFontsList.map(createLocalFontResources).filter((resource) => resource !== null);
2507
+ if (localResources.length > 0) {
2508
+ result.allowedDomains.push(window.location.origin);
2509
+ result.append.push(...localResources);
2510
+ }
2511
+ return result.append.length > 0 ? result : null;
2512
+ };
2513
+ const getInjectables = (options, optimize = false) => {
2514
+ if (options && "key" in options) {
2515
+ const { key } = options;
2516
+ if (!key || !(key in fontFamilyPref)) {
2517
+ return null;
2518
+ }
2519
+ return processFonts(bunnyFonts.get(key) || [], googleFonts.get(key) || [], localFonts.get(key) || [], optimize);
2520
+ }
2521
+ if (options && "language" in options) {
2522
+ const { language: publicationLanguage } = options;
2523
+ for (const [collectionName, collectionData] of Object.entries(fontFamilyPref)) {
2524
+ if (collectionName === "default") continue;
2525
+ const supportedLangs = "supportedLanguages" in collectionData ? collectionData.supportedLanguages : null;
2526
+ if (supportedLangs && Array.isArray(supportedLangs) && publicationLanguage && supportedLangs.includes(publicationLanguage)) {
2527
+ return processFonts(bunnyFonts.get(collectionName) || [], googleFonts.get(collectionName) || [], localFonts.get(collectionName) || [], optimize);
2528
+ }
2529
+ }
2530
+ }
2531
+ return processFonts(defaultBunnyFonts, defaultGoogleFonts, defaultLocalFonts, optimize);
2532
+ };
2533
+ const getFontMetadata = (fontId) => {
2534
+ const parsed = parsedFonts.get(fontId);
2535
+ return parsed || { fontStack: null, fontFamily: null, weights: null, widths: null };
2536
+ };
2537
+ const getFontCollection = (options) => {
2538
+ if (options && "key" in options) {
2539
+ const { key } = options;
2540
+ if (!key || !(key in fontFamilyPref)) {
2541
+ return fontFamilyPref.default;
2542
+ }
2543
+ if (key === "default") {
2544
+ return fontFamilyPref.default;
2545
+ }
2546
+ const prefRecord = fontFamilyPref;
2547
+ const collection = prefRecord[key];
2548
+ if (collection && "fonts" in collection) {
2549
+ return collection.fonts;
2550
+ }
2551
+ return fontFamilyPref.default;
2552
+ }
2553
+ if (options && "language" in options) {
2554
+ const { language: publicationLanguage } = options;
2555
+ for (const [collectionName, collectionData] of Object.entries(fontFamilyPref)) {
2556
+ if (collectionName === "default") continue;
2557
+ const collection = "fonts" in collectionData ? collectionData : { fonts: collectionData };
2558
+ const supportedLangs = "supportedLanguages" in collection ? collection.supportedLanguages : null;
2559
+ if (supportedLangs?.includes(publicationLanguage)) {
2560
+ return collection.fonts;
2561
+ }
2562
+ }
2563
+ return fontFamilyPref.default;
2564
+ }
2565
+ return fontFamilyPref.default;
2566
+ };
2567
+ return {
2568
+ getInjectables,
2569
+ getFontMetadata,
2570
+ getFontCollection,
2571
+ resolveFontLanguage
2572
+ };
2573
+ };
2574
+
2575
+ // src/preferences/hooks/usePreferences.ts
2576
+ function usePreferences() {
2577
+ const context = useContext(ThPreferencesContext);
2578
+ if (!context) {
2579
+ throw new Error("usePreferences must be used within a ThPreferencesProvider");
2580
+ }
2581
+ const fontService = createFontService(context.preferences.settings.keys.fontFamily);
2582
+ return {
2583
+ preferences: context.preferences,
2584
+ updatePreferences: context.updatePreferences,
2585
+ getFontInjectables: (options, optimize) => {
2586
+ return fontService.getInjectables(options, optimize);
2587
+ },
2588
+ getFontsList: (options) => {
2589
+ return fontService.getFontCollection(options);
2590
+ },
2591
+ getFontMetadata: (fontId) => {
2592
+ return fontService.getFontMetadata(fontId);
2593
+ },
2594
+ resolveFontLanguage: (bcp47Tag, scriptMode) => {
2595
+ return fontService.resolveFontLanguage(bcp47Tag, scriptMode);
2596
+ }
2597
+ };
2598
+ }
2599
+
2600
+ // src/preferences/hooks/usePreferenceKeys.ts
2601
+ var usePreferenceKeys = () => {
2602
+ const { preferences } = usePreferences();
2603
+ const reflowActionKeys = preferences.actions.reflowOrder;
2604
+ const fxlActionKeys = preferences.actions.fxlOrder;
2605
+ const webPubActionKeys = preferences.actions.webPubOrder;
2606
+ const reflowThemeKeys = preferences.theming.themes.reflowOrder;
2607
+ const fxlThemeKeys = preferences.theming.themes.fxlOrder;
2608
+ const reflowSettingsKeys = preferences.settings.reflowOrder;
2609
+ const fxlSettingsKeys = preferences.settings.fxlOrder;
2610
+ const webPubSettingsKeys = preferences.settings.webPubOrder;
2611
+ const mainTextSettingsKeys = preferences.settings.text?.main ?? defaultTextSettingsMain;
2612
+ const subPanelTextSettingsKeys = preferences.settings.text?.subPanel ?? defaultTextSettingsSubpanel;
2613
+ const mainSpacingSettingsKeys = preferences.settings.spacing?.main ?? defaultSpacingSettingsMain;
2614
+ const subPanelSpacingSettingsKeys = preferences.settings.spacing?.subPanel ?? defaultSpacingSettingsSubpanel;
2615
+ const reflowSpacingPresetKeys = preferences.settings.spacing?.presets?.reflowOrder ?? defaultSpacingPresetsOrder;
2616
+ const fxlSpacingPresetKeys = [];
2617
+ const webPubSpacingPresetKeys = preferences.settings.spacing?.presets?.webPubOrder ?? defaultSpacingPresetsOrder;
2618
+ return {
2619
+ reflowActionKeys,
2620
+ fxlActionKeys,
2621
+ webPubActionKeys,
2622
+ reflowThemeKeys,
2623
+ fxlThemeKeys,
2624
+ reflowSettingsKeys,
2625
+ fxlSettingsKeys,
2626
+ webPubSettingsKeys,
2627
+ mainTextSettingsKeys,
2628
+ subPanelTextSettingsKeys,
2629
+ mainSpacingSettingsKeys,
2630
+ subPanelSpacingSettingsKeys,
2631
+ reflowSpacingPresetKeys,
2632
+ fxlSpacingPresetKeys,
2633
+ webPubSpacingPresetKeys
2634
+ };
2635
+ };
2636
+ var EXCLUDED_CJK = [
2637
+ "textAlign" /* textAlign */,
2638
+ "hyphens" /* hyphens */,
2639
+ "ligatures" /* ligatures */,
2640
+ "paragraphIndent" /* paragraphIndent */,
2641
+ "wordSpacing" /* wordSpacing */,
2642
+ "textNormalize" /* textNormalize */
2643
+ ];
2644
+ var EXCLUDED_BY_SCRIPT_MODE = {
2645
+ "ltr": [
2646
+ "noRuby" /* noRuby */
2647
+ ],
2648
+ "rtl": [
2649
+ "hyphens" /* hyphens */,
2650
+ "letterSpacing" /* letterSpacing */,
2651
+ "textNormalize" /* textNormalize */,
2652
+ "noRuby" /* noRuby */
2653
+ ],
2654
+ "cjk-horizontal": EXCLUDED_CJK,
2655
+ "cjk-vertical": [...EXCLUDED_CJK, "layout" /* layout */],
2656
+ "mongolian-vertical": [...EXCLUDED_CJK, "layout" /* layout */]
2657
+ };
2658
+ var useFilteredPreferenceKeys = () => {
2659
+ const keys = usePreferenceKeys();
2660
+ const scriptMode = useAppSelector((state) => state.publication.scriptMode);
2661
+ const isFXL = useAppSelector((state) => state.publication.isFXL);
2662
+ return useMemo(() => {
2663
+ const excluded = [
2664
+ ...EXCLUDED_BY_SCRIPT_MODE[scriptMode] ?? [],
2665
+ ...(scriptMode === "cjk-vertical" || scriptMode === "mongolian-vertical") && !isFXL ? ["columns" /* columns */] : []
2666
+ ];
2667
+ if (excluded.length === 0) return keys;
2668
+ const filter = (arr) => arr.filter((k) => !excluded.includes(k));
2669
+ return {
2670
+ ...keys,
2671
+ reflowSettingsKeys: filter(keys.reflowSettingsKeys),
2672
+ fxlSettingsKeys: filter(keys.fxlSettingsKeys),
2673
+ webPubSettingsKeys: filter(keys.webPubSettingsKeys),
2674
+ mainTextSettingsKeys: filter(keys.mainTextSettingsKeys),
2675
+ subPanelTextSettingsKeys: filter(keys.subPanelTextSettingsKeys),
2676
+ mainSpacingSettingsKeys: filter(keys.mainSpacingSettingsKeys),
2677
+ subPanelSpacingSettingsKeys: filter(keys.subPanelSpacingSettingsKeys)
2678
+ };
2679
+ }, [keys, scriptMode, isFXL]);
2680
+ };
2681
+
2682
+ // src/core/Helpers/prefixString.ts
2683
+ var PREFIXES = {
2684
+ short: "th",
2685
+ full: "thorium_web"
2686
+ };
2687
+ var prefixString = (str, variant = "short") => {
2688
+ return `${PREFIXES[variant]}-${str}`;
2689
+ };
2690
+
2691
+ // node_modules/.pnpm/colorthief@3.3.1_sharp@0.34.5/node_modules/colorthief/dist/index.js
2692
+ var __defProp = Object.defineProperty;
2693
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2694
+ var __esm = (fn, res) => function __init() {
2695
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
2696
+ };
2697
+ var __export = (target, all) => {
2698
+ for (var name in all)
2699
+ __defProp(target, name, { get: all[name], enumerable: true });
2700
+ };
2701
+ function srgbToLinear(c) {
2702
+ const s = c / 255;
2703
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
2704
+ }
2705
+ function linearToSrgb(c) {
2706
+ const s = c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
2707
+ return Math.round(Math.max(0, Math.min(255, s * 255)));
2708
+ }
2709
+ function rgbToOklch(r, g, b) {
2710
+ const lr = srgbToLinear(r);
2711
+ const lg = srgbToLinear(g);
2712
+ const lb = srgbToLinear(b);
2713
+ const l_ = 0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb;
2714
+ const m_ = 0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb;
2715
+ const s_ = 0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb;
2716
+ const l3 = Math.cbrt(l_);
2717
+ const m3 = Math.cbrt(m_);
2718
+ const s3 = Math.cbrt(s_);
2719
+ const L = 0.2104542553 * l3 + 0.793617785 * m3 - 0.0040720468 * s3;
2720
+ const a = 1.9779984951 * l3 - 2.428592205 * m3 + 0.4505937099 * s3;
2721
+ const bLab = 0.0259040371 * l3 + 0.7827717662 * m3 - 0.808675766 * s3;
2722
+ const C = Math.sqrt(a * a + bLab * bLab);
2723
+ let H = Math.atan2(bLab, a) * (180 / Math.PI);
2724
+ if (H < 0) H += 360;
2725
+ return { l: L, c: C, h: H };
2726
+ }
2727
+ function oklchToRgb(l, c, h) {
2728
+ const hRad = h * (Math.PI / 180);
2729
+ const a = c * Math.cos(hRad);
2730
+ const bLab = c * Math.sin(hRad);
2731
+ const l3 = l + 0.3963377774 * a + 0.2158037573 * bLab;
2732
+ const m3 = l - 0.1055613458 * a - 0.0638541728 * bLab;
2733
+ const s3 = l - 0.0894841775 * a - 1.291485548 * bLab;
2734
+ const l_ = l3 * l3 * l3;
2735
+ const m_ = m3 * m3 * m3;
2736
+ const s_ = s3 * s3 * s3;
2737
+ const lr = 4.0767416621 * l_ - 3.3077115913 * m_ + 0.2309699292 * s_;
2738
+ const lg = -1.2684380046 * l_ + 2.6097574011 * m_ - 0.3413193965 * s_;
2739
+ const lb = -0.0041960863 * l_ - 0.7034186147 * m_ + 1.707614701 * s_;
2740
+ return [linearToSrgb(lr), linearToSrgb(lg), linearToSrgb(lb)];
2741
+ }
2742
+ function pixelsRgbToOklchScaled(pixels) {
2743
+ const out = new Array(pixels.length);
2744
+ for (let i = 0; i < pixels.length; i++) {
2745
+ const [r, g, b] = pixels[i];
2746
+ const { l, c, h } = rgbToOklch(r, g, b);
2747
+ out[i] = [
2748
+ Math.round(l * 255),
2749
+ Math.round(c / 0.4 * 255),
2750
+ Math.round(h / 360 * 255)
2751
+ ];
2752
+ }
2753
+ return out;
2754
+ }
2755
+ function paletteOklchScaledToRgb(colors) {
2756
+ return colors.map(({ color: [ls, cs, hs], population }) => {
2757
+ const l = ls / 255;
2758
+ const c = cs / 255 * 0.4;
2759
+ const h = hs / 255 * 360;
2760
+ return { color: oklchToRgb(l, c, h), population };
2761
+ });
2762
+ }
2763
+ var init_color_space = __esm({
2764
+ "src/color-space.ts"() {
2765
+ }
2766
+ });
2767
+ function rgbToHsl(r, g, b) {
2768
+ const r1 = r / 255;
2769
+ const g1 = g / 255;
2770
+ const b1 = b / 255;
2771
+ const max = Math.max(r1, g1, b1);
2772
+ const min = Math.min(r1, g1, b1);
2773
+ const l = (max + min) / 2;
2774
+ let h = 0;
2775
+ let s = 0;
2776
+ if (max !== min) {
2777
+ const d = max - min;
2778
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2779
+ if (max === r1) {
2780
+ h = ((g1 - b1) / d + (g1 < b1 ? 6 : 0)) / 6;
2781
+ } else if (max === g1) {
2782
+ h = ((b1 - r1) / d + 2) / 6;
2783
+ } else {
2784
+ h = ((r1 - g1) / d + 4) / 6;
2785
+ }
2786
+ }
2787
+ return {
2788
+ h: Math.round(h * 360),
2789
+ s: Math.round(s * 100),
2790
+ l: Math.round(l * 100)
2791
+ };
2792
+ }
2793
+ function relativeLuminance(r, g, b) {
2794
+ const toLinear = (c) => {
2795
+ const s = c / 255;
2796
+ return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
2797
+ };
2798
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
2799
+ }
2800
+ function contrastRatio(l1, l2) {
2801
+ const lighter = Math.max(l1, l2);
2802
+ const darker = Math.min(l1, l2);
2803
+ return (lighter + 0.05) / (darker + 0.05);
2804
+ }
2805
+ function createColor(r, g, b, population, proportion = 0) {
2806
+ return new ColorImpl(r, g, b, population, proportion);
2807
+ }
2808
+ var ColorImpl;
2809
+ var init_color = __esm({
2810
+ "src/color.ts"() {
2811
+ init_color_space();
2812
+ ColorImpl = class {
2813
+ constructor(r, g, b, population, proportion) {
2814
+ this._r = r;
2815
+ this._g = g;
2816
+ this._b = b;
2817
+ this.population = population;
2818
+ this.proportion = proportion;
2819
+ }
2820
+ rgb() {
2821
+ return { r: this._r, g: this._g, b: this._b };
2822
+ }
2823
+ hex() {
2824
+ const toHex = (n) => n.toString(16).padStart(2, "0");
2825
+ return `#${toHex(this._r)}${toHex(this._g)}${toHex(this._b)}`;
2826
+ }
2827
+ hsl() {
2828
+ if (!this._hsl) {
2829
+ this._hsl = rgbToHsl(this._r, this._g, this._b);
2830
+ }
2831
+ return this._hsl;
2832
+ }
2833
+ oklch() {
2834
+ if (!this._oklch) {
2835
+ this._oklch = rgbToOklch(this._r, this._g, this._b);
2836
+ }
2837
+ return this._oklch;
2838
+ }
2839
+ css(format = "rgb") {
2840
+ switch (format) {
2841
+ case "hsl": {
2842
+ const { h, s, l } = this.hsl();
2843
+ return `hsl(${h}, ${s}%, ${l}%)`;
2844
+ }
2845
+ case "oklch": {
2846
+ const { l, c, h } = this.oklch();
2847
+ return `oklch(${l.toFixed(3)} ${c.toFixed(3)} ${h.toFixed(1)})`;
2848
+ }
2849
+ case "rgb":
2850
+ default:
2851
+ return `rgb(${this._r}, ${this._g}, ${this._b})`;
2852
+ }
2853
+ }
2854
+ array() {
2855
+ return [this._r, this._g, this._b];
2856
+ }
2857
+ toString() {
2858
+ return this.hex();
2859
+ }
2860
+ get textColor() {
2861
+ return this.isDark ? "#ffffff" : "#000000";
2862
+ }
2863
+ get luminance() {
2864
+ if (this._luminance === void 0) {
2865
+ this._luminance = relativeLuminance(this._r, this._g, this._b);
2866
+ }
2867
+ return this._luminance;
2868
+ }
2869
+ get isDark() {
2870
+ return this.luminance <= 0.179;
2871
+ }
2872
+ get isLight() {
2873
+ return !this.isDark;
2874
+ }
2875
+ get contrast() {
2876
+ if (!this._contrast) {
2877
+ const lum = this.luminance;
2878
+ const white = contrastRatio(lum, 1);
2879
+ const black = contrastRatio(lum, 0);
2880
+ const foreground = this.isDark ? createColor(255, 255, 255, 0, 0) : createColor(0, 0, 0, 0, 0);
2881
+ this._contrast = {
2882
+ white: Math.round(white * 100) / 100,
2883
+ black: Math.round(black * 100) / 100,
2884
+ foreground
2885
+ };
2886
+ }
2887
+ return this._contrast;
2888
+ }
2889
+ };
2890
+ }
2891
+ });
2892
+ var pipeline_exports = {};
2893
+ __export(pipeline_exports, {
2894
+ computeFallbackColor: () => computeFallbackColor,
2895
+ createPixelArray: () => createPixelArray,
2896
+ extractPalette: () => extractPalette,
2897
+ validateOptions: () => validateOptions
2898
+ });
2899
+ function validateOptions(options) {
2900
+ let { colorCount, quality } = options;
2901
+ if (typeof colorCount === "undefined" || !Number.isInteger(colorCount)) {
2902
+ colorCount = 10;
2903
+ } else if (colorCount === 1) {
2904
+ throw new Error(
2905
+ "colorCount should be between 2 and 20. To get one color, call getColor() instead of getPalette()"
2906
+ );
2907
+ } else {
2908
+ colorCount = Math.max(colorCount, 2);
2909
+ colorCount = Math.min(colorCount, 20);
2910
+ }
2911
+ if (typeof quality === "undefined" || !Number.isInteger(quality) || quality < 1) {
2912
+ quality = 10;
2913
+ }
2914
+ const ignoreWhite = options.ignoreWhite !== void 0 ? !!options.ignoreWhite : true;
2915
+ const whiteThreshold = typeof options.whiteThreshold === "number" ? options.whiteThreshold : 250;
2916
+ const alphaThreshold = typeof options.alphaThreshold === "number" ? options.alphaThreshold : 125;
2917
+ const minSaturation = typeof options.minSaturation === "number" ? Math.max(0, Math.min(1, options.minSaturation)) : 0;
2918
+ const colorSpace = options.colorSpace ?? "oklch";
2919
+ return {
2920
+ colorCount,
2921
+ quality,
2922
+ ignoreWhite,
2923
+ whiteThreshold,
2924
+ alphaThreshold,
2925
+ minSaturation,
2926
+ colorSpace
2927
+ };
2928
+ }
2929
+ function createPixelArray(data, pixelCount, quality, filterOptions) {
2930
+ const {
2931
+ ignoreWhite = true,
2932
+ whiteThreshold = 250,
2933
+ alphaThreshold = 125,
2934
+ minSaturation = 0
2935
+ } = filterOptions;
2936
+ const pixelArray = [];
2937
+ for (let i = 0; i < pixelCount; i += quality) {
2938
+ const offset = i * 4;
2939
+ const r = data[offset];
2940
+ const g = data[offset + 1];
2941
+ const b = data[offset + 2];
2942
+ const a = data[offset + 3];
2943
+ if (a !== void 0 && a < alphaThreshold) continue;
2944
+ if (ignoreWhite && r > whiteThreshold && g > whiteThreshold && b > whiteThreshold)
2945
+ continue;
2946
+ if (minSaturation > 0) {
2947
+ const max = Math.max(r, g, b);
2948
+ if (max === 0 || (max - Math.min(r, g, b)) / max < minSaturation)
2949
+ continue;
2950
+ }
2951
+ pixelArray.push([r, g, b]);
2952
+ }
2953
+ return pixelArray;
2954
+ }
2955
+ function computeFallbackColor(data, pixelCount, quality) {
2956
+ let rTotal = 0;
2957
+ let gTotal = 0;
2958
+ let bTotal = 0;
2959
+ let count = 0;
2960
+ for (let i = 0; i < pixelCount; i += quality) {
2961
+ const offset = i * 4;
2962
+ rTotal += data[offset];
2963
+ gTotal += data[offset + 1];
2964
+ bTotal += data[offset + 2];
2965
+ count++;
2966
+ }
2967
+ if (count === 0) return null;
2968
+ return [
2969
+ Math.round(rTotal / count),
2970
+ Math.round(gTotal / count),
2971
+ Math.round(bTotal / count)
2972
+ ];
2973
+ }
2974
+ function extractPalette(data, width, height, opts, quantizer) {
2975
+ const pixelCount = width * height;
2976
+ const filterOptions = {
2977
+ ignoreWhite: opts.ignoreWhite,
2978
+ whiteThreshold: opts.whiteThreshold,
2979
+ alphaThreshold: opts.alphaThreshold,
2980
+ minSaturation: opts.minSaturation
2981
+ };
2982
+ let pixelArray = createPixelArray(data, pixelCount, opts.quality, filterOptions);
2983
+ if (pixelArray.length === 0) {
2984
+ pixelArray = createPixelArray(data, pixelCount, opts.quality, {
2985
+ ...filterOptions,
2986
+ ignoreWhite: false
2987
+ });
2988
+ }
2989
+ if (pixelArray.length === 0) {
2990
+ pixelArray = createPixelArray(data, pixelCount, opts.quality, {
2991
+ ...filterOptions,
2992
+ ignoreWhite: false,
2993
+ alphaThreshold: 0
2994
+ });
2995
+ }
2996
+ let quantized;
2997
+ if (opts.colorSpace === "oklch") {
2998
+ const scaled = pixelsRgbToOklchScaled(pixelArray);
2999
+ quantized = paletteOklchScaledToRgb(
3000
+ quantizer.quantize(scaled, opts.colorCount)
3001
+ );
3002
+ } else {
3003
+ quantized = quantizer.quantize(pixelArray, opts.colorCount);
3004
+ }
3005
+ if (quantized.length > 0) {
3006
+ const totalPopulation = quantized.reduce((sum, q) => sum + q.population, 0);
3007
+ return quantized.map(
3008
+ ({ color: [r, g, b], population }) => createColor(r, g, b, population, totalPopulation > 0 ? population / totalPopulation : 0)
3009
+ );
3010
+ }
3011
+ const fallback = computeFallbackColor(data, pixelCount, opts.quality);
3012
+ return fallback ? [createColor(fallback[0], fallback[1], fallback[2], 1, 1)] : null;
3013
+ }
3014
+ var init_pipeline = __esm({
3015
+ "src/pipeline.ts"() {
3016
+ init_color();
3017
+ init_color_space();
3018
+ }
3019
+ });
3020
+ var browser_exports = {};
3021
+ __export(browser_exports, {
3022
+ BrowserPixelLoader: () => BrowserPixelLoader
3023
+ });
3024
+ var BrowserPixelLoader;
3025
+ var init_browser = __esm({
3026
+ "src/loaders/browser.ts"() {
3027
+ BrowserPixelLoader = class {
3028
+ async load(source) {
3029
+ if (typeof HTMLImageElement !== "undefined" && source instanceof HTMLImageElement) {
3030
+ return this.loadFromImage(source);
3031
+ }
3032
+ if (typeof HTMLCanvasElement !== "undefined" && source instanceof HTMLCanvasElement) {
3033
+ return this.loadFromCanvas(source);
3034
+ }
3035
+ if (typeof ImageData !== "undefined" && source instanceof ImageData) {
3036
+ return {
3037
+ data: source.data,
3038
+ width: source.width,
3039
+ height: source.height
3040
+ };
3041
+ }
3042
+ if (typeof HTMLVideoElement !== "undefined" && source instanceof HTMLVideoElement) {
3043
+ return this.loadFromVideo(source);
3044
+ }
3045
+ if (typeof ImageBitmap !== "undefined" && source instanceof ImageBitmap) {
3046
+ return this.loadFromImageBitmap(source);
3047
+ }
3048
+ if (typeof OffscreenCanvas !== "undefined" && source instanceof OffscreenCanvas) {
3049
+ return this.loadFromOffscreenCanvas(source);
3050
+ }
3051
+ throw new Error(
3052
+ "Unsupported source type. Expected HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageData, ImageBitmap, or OffscreenCanvas."
3053
+ );
3054
+ }
3055
+ loadFromImage(img) {
3056
+ if (!img.complete) {
3057
+ throw new Error(
3058
+ 'Image has not finished loading. Wait for the "load" event before calling getColor/getPalette.'
3059
+ );
3060
+ }
3061
+ if (!img.naturalWidth) {
3062
+ throw new Error(
3063
+ "Image has no dimensions. It may not have loaded successfully."
3064
+ );
3065
+ }
3066
+ const canvas = document.createElement("canvas");
3067
+ const ctx = canvas.getContext("2d");
3068
+ const width = canvas.width = img.naturalWidth;
3069
+ const height = canvas.height = img.naturalHeight;
3070
+ ctx.drawImage(img, 0, 0, width, height);
3071
+ try {
3072
+ const imageData = ctx.getImageData(0, 0, width, height);
3073
+ return { data: imageData.data, width, height };
3074
+ } catch (e) {
3075
+ if (e instanceof DOMException && e.name === "SecurityError") {
3076
+ const err = new Error(
3077
+ 'Image is tainted by cross-origin data. Add crossorigin="anonymous" to the <img> tag and ensure the server sends appropriate CORS headers.'
3078
+ );
3079
+ err.cause = e;
3080
+ throw err;
3081
+ }
3082
+ throw e;
3083
+ }
3084
+ }
3085
+ loadFromCanvas(canvas) {
3086
+ const ctx = canvas.getContext("2d");
3087
+ const { width, height } = canvas;
3088
+ const imageData = ctx.getImageData(0, 0, width, height);
3089
+ return { data: imageData.data, width, height };
3090
+ }
3091
+ loadFromVideo(video) {
3092
+ if (video.readyState < 2) {
3093
+ throw new Error(
3094
+ 'Video is not ready. Wait for the "loadeddata" or "canplay" event before calling getColor/getPalette.'
3095
+ );
3096
+ }
3097
+ const width = video.videoWidth;
3098
+ const height = video.videoHeight;
3099
+ if (!width || !height) {
3100
+ throw new Error(
3101
+ "Video has no dimensions. It may not have loaded successfully."
3102
+ );
3103
+ }
3104
+ const canvas = document.createElement("canvas");
3105
+ const ctx = canvas.getContext("2d");
3106
+ canvas.width = width;
3107
+ canvas.height = height;
3108
+ ctx.drawImage(video, 0, 0, width, height);
3109
+ const imageData = ctx.getImageData(0, 0, width, height);
3110
+ return { data: imageData.data, width, height };
3111
+ }
3112
+ loadFromOffscreenCanvas(canvas) {
3113
+ const ctx = canvas.getContext("2d");
3114
+ if (!ctx) {
3115
+ throw new Error(
3116
+ "Could not get 2D context from OffscreenCanvas."
3117
+ );
3118
+ }
3119
+ const { width, height } = canvas;
3120
+ const imageData = ctx.getImageData(0, 0, width, height);
3121
+ return { data: imageData.data, width, height };
3122
+ }
3123
+ loadFromImageBitmap(bitmap) {
3124
+ const canvas = document.createElement("canvas");
3125
+ const ctx = canvas.getContext("2d");
3126
+ canvas.width = bitmap.width;
3127
+ canvas.height = bitmap.height;
3128
+ ctx.drawImage(bitmap, 0, 0);
3129
+ const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
3130
+ return { data: imageData.data, width: bitmap.width, height: bitmap.height };
3131
+ }
3132
+ };
3133
+ }
3134
+ });
3135
+ var node_exports = {};
3136
+ __export(node_exports, {
3137
+ NodePixelLoader: () => NodePixelLoader,
3138
+ createNodeLoader: () => createNodeLoader
3139
+ });
3140
+ function createNodeLoader(options) {
3141
+ return new NodePixelLoader(options);
3142
+ }
3143
+ var NodePixelLoader;
3144
+ var WORKER_SOURCE;
3145
+ var manager_exports = {};
3146
+ __export(manager_exports, {
3147
+ extractInWorker: () => extractInWorker,
3148
+ isWorkerSupported: () => isWorkerSupported,
3149
+ terminateWorker: () => terminateWorker
3150
+ });
3151
+ function isWorkerSupported() {
3152
+ return typeof Worker !== "undefined";
3153
+ }
3154
+ function getOrCreateWorker() {
3155
+ if (worker) return worker;
3156
+ if (!isWorkerSupported()) {
3157
+ throw new Error("Web Workers are not supported in this environment.");
3158
+ }
3159
+ blobUrl = URL.createObjectURL(
3160
+ new Blob([WORKER_SOURCE], { type: "application/javascript" })
3161
+ );
3162
+ worker = new Worker(blobUrl);
3163
+ worker.onmessage = (e) => {
3164
+ const { id, palette, error } = e.data;
3165
+ const entry = pending.get(id);
3166
+ if (!entry) return;
3167
+ pending.delete(id);
3168
+ if (error) {
3169
+ entry.reject(new Error(error));
3170
+ } else {
3171
+ const raw = palette;
3172
+ const totalPopulation = raw.reduce((sum, q) => sum + q.population, 0);
3173
+ const colors = raw.map(({ color: [r, g, b], population }) => createColor(r, g, b, population, totalPopulation > 0 ? population / totalPopulation : 0));
3174
+ entry.resolve(colors);
3175
+ }
3176
+ };
3177
+ worker.onerror = (e) => {
3178
+ for (const [, entry] of pending) {
3179
+ entry.reject(new Error(e.message));
3180
+ }
3181
+ pending.clear();
3182
+ };
3183
+ return worker;
3184
+ }
3185
+ function extractInWorker(pixels, maxColors, signal) {
3186
+ return new Promise((resolve, reject) => {
3187
+ if (signal?.aborted) {
3188
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
3189
+ return;
3190
+ }
3191
+ const id = nextId++;
3192
+ pending.set(id, { resolve, reject });
3193
+ const onAbort = () => {
3194
+ pending.delete(id);
3195
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
3196
+ };
3197
+ signal?.addEventListener("abort", onAbort, { once: true });
3198
+ try {
3199
+ const w = getOrCreateWorker();
3200
+ w.postMessage({ id, pixels, maxColors });
3201
+ } catch (err) {
3202
+ pending.delete(id);
3203
+ signal?.removeEventListener("abort", onAbort);
3204
+ reject(err);
3205
+ }
3206
+ });
3207
+ }
3208
+ function terminateWorker() {
3209
+ if (worker) {
3210
+ worker.terminate();
3211
+ worker = null;
3212
+ }
3213
+ if (blobUrl) {
3214
+ URL.revokeObjectURL(blobUrl);
3215
+ blobUrl = null;
3216
+ }
3217
+ for (const [, entry] of pending) {
3218
+ entry.reject(new Error("Worker terminated"));
3219
+ }
3220
+ pending.clear();
3221
+ }
3222
+ var worker;
3223
+ var blobUrl;
3224
+ var nextId;
3225
+ var pending;
3226
+ init_pipeline();
3227
+ init_pipeline();
3228
+ init_color();
3229
+ createColor(255, 255, 255, 0);
3230
+ createColor(0, 0, 0, 0);
3231
+ var SIGBITS = 5;
3232
+ var RSHIFT = 8 - SIGBITS;
3233
+ var MAX_ITERATIONS = 1e3;
3234
+ var FRACT_BY_POPULATIONS = 0.75;
3235
+ var HISTO_SIZE = 1 << 3 * SIGBITS;
3236
+ function getColorIndex(r, g, b) {
3237
+ return (r << 2 * SIGBITS) + (g << SIGBITS) + b;
3238
+ }
3239
+ var VBox = class _VBox {
3240
+ constructor(r1, r2, g1, g2, b1, b2, histo) {
3241
+ this.r1 = r1;
3242
+ this.r2 = r2;
3243
+ this.g1 = g1;
3244
+ this.g2 = g2;
3245
+ this.b1 = b1;
3246
+ this.b2 = b2;
3247
+ this.histo = histo;
3248
+ }
3249
+ volume(force = false) {
3250
+ if (this._volume === void 0 || force) {
3251
+ this._volume = (this.r2 - this.r1 + 1) * (this.g2 - this.g1 + 1) * (this.b2 - this.b1 + 1);
3252
+ }
3253
+ return this._volume;
3254
+ }
3255
+ count(force = false) {
3256
+ if (this._count === void 0 || force) {
3257
+ let npix = 0;
3258
+ for (let i = this.r1; i <= this.r2; i++) {
3259
+ for (let j = this.g1; j <= this.g2; j++) {
3260
+ for (let k = this.b1; k <= this.b2; k++) {
3261
+ npix += this.histo[getColorIndex(i, j, k)] || 0;
3262
+ }
3263
+ }
3264
+ }
3265
+ this._count = npix;
3266
+ }
3267
+ return this._count;
3268
+ }
3269
+ copy() {
3270
+ return new _VBox(this.r1, this.r2, this.g1, this.g2, this.b1, this.b2, this.histo);
3271
+ }
3272
+ avg(force = false) {
3273
+ if (this._avg === void 0 || force) {
3274
+ const mult = 1 << RSHIFT;
3275
+ if (this.r1 === this.r2 && this.g1 === this.g2 && this.b1 === this.b2) {
3276
+ this._avg = [
3277
+ this.r1 << RSHIFT,
3278
+ this.g1 << RSHIFT,
3279
+ this.b1 << RSHIFT
3280
+ ];
3281
+ } else {
3282
+ let ntot = 0;
3283
+ let rsum = 0;
3284
+ let gsum = 0;
3285
+ let bsum = 0;
3286
+ for (let i = this.r1; i <= this.r2; i++) {
3287
+ for (let j = this.g1; j <= this.g2; j++) {
3288
+ for (let k = this.b1; k <= this.b2; k++) {
3289
+ const hval = this.histo[getColorIndex(i, j, k)] || 0;
3290
+ ntot += hval;
3291
+ rsum += hval * (i + 0.5) * mult;
3292
+ gsum += hval * (j + 0.5) * mult;
3293
+ bsum += hval * (k + 0.5) * mult;
3294
+ }
3295
+ }
3296
+ }
3297
+ if (ntot) {
3298
+ this._avg = [
3299
+ ~~(rsum / ntot),
3300
+ ~~(gsum / ntot),
3301
+ ~~(bsum / ntot)
3302
+ ];
3303
+ } else {
3304
+ this._avg = [
3305
+ ~~(mult * (this.r1 + this.r2 + 1) / 2),
3306
+ ~~(mult * (this.g1 + this.g2 + 1) / 2),
3307
+ ~~(mult * (this.b1 + this.b2 + 1) / 2)
3308
+ ];
3309
+ }
3310
+ }
3311
+ }
3312
+ return this._avg;
3313
+ }
3314
+ };
3315
+ var PQueue = class {
3316
+ constructor(comparator) {
3317
+ this.comparator = comparator;
3318
+ this.contents = [];
3319
+ this.sorted = false;
3320
+ }
3321
+ sort() {
3322
+ this.contents.sort(this.comparator);
3323
+ this.sorted = true;
3324
+ }
3325
+ push(item) {
3326
+ this.contents.push(item);
3327
+ this.sorted = false;
3328
+ }
3329
+ peek(index) {
3330
+ if (!this.sorted) this.sort();
3331
+ return this.contents[index ?? this.contents.length - 1];
3332
+ }
3333
+ pop() {
3334
+ if (!this.sorted) this.sort();
3335
+ return this.contents.pop();
3336
+ }
3337
+ size() {
3338
+ return this.contents.length;
3339
+ }
3340
+ map(fn) {
3341
+ return this.contents.map(fn);
3342
+ }
3343
+ };
3344
+ function getHisto(pixels) {
3345
+ const histo = new Uint32Array(HISTO_SIZE);
3346
+ for (const pixel of pixels) {
3347
+ const rval = pixel[0] >> RSHIFT;
3348
+ const gval = pixel[1] >> RSHIFT;
3349
+ const bval = pixel[2] >> RSHIFT;
3350
+ histo[getColorIndex(rval, gval, bval)]++;
3351
+ }
3352
+ return histo;
3353
+ }
3354
+ function vboxFromPixels(pixels, histo) {
3355
+ let rmin = 1e6;
3356
+ let rmax = 0;
3357
+ let gmin = 1e6;
3358
+ let gmax = 0;
3359
+ let bmin = 1e6;
3360
+ let bmax = 0;
3361
+ for (const pixel of pixels) {
3362
+ const rval = pixel[0] >> RSHIFT;
3363
+ const gval = pixel[1] >> RSHIFT;
3364
+ const bval = pixel[2] >> RSHIFT;
3365
+ if (rval < rmin) rmin = rval;
3366
+ else if (rval > rmax) rmax = rval;
3367
+ if (gval < gmin) gmin = gval;
3368
+ else if (gval > gmax) gmax = gval;
3369
+ if (bval < bmin) bmin = bval;
3370
+ else if (bval > bmax) bmax = bval;
3371
+ }
3372
+ return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
3373
+ }
3374
+ function medianCutApply(histo, vbox) {
3375
+ if (!vbox.count()) return void 0;
3376
+ if (vbox.count() === 1) return [vbox.copy(), null];
3377
+ const rw = vbox.r2 - vbox.r1 + 1;
3378
+ const gw = vbox.g2 - vbox.g1 + 1;
3379
+ const bw = vbox.b2 - vbox.b1 + 1;
3380
+ const maxw = Math.max(rw, gw, bw);
3381
+ let total = 0;
3382
+ const partialsum = [];
3383
+ const lookaheadsum = [];
3384
+ if (maxw === rw) {
3385
+ for (let i = vbox.r1; i <= vbox.r2; i++) {
3386
+ let sum = 0;
3387
+ for (let j = vbox.g1; j <= vbox.g2; j++) {
3388
+ for (let k = vbox.b1; k <= vbox.b2; k++) {
3389
+ sum += histo[getColorIndex(i, j, k)] || 0;
3390
+ }
3391
+ }
3392
+ total += sum;
3393
+ partialsum[i] = total;
3394
+ }
3395
+ } else if (maxw === gw) {
3396
+ for (let i = vbox.g1; i <= vbox.g2; i++) {
3397
+ let sum = 0;
3398
+ for (let j = vbox.r1; j <= vbox.r2; j++) {
3399
+ for (let k = vbox.b1; k <= vbox.b2; k++) {
3400
+ sum += histo[getColorIndex(j, i, k)] || 0;
3401
+ }
3402
+ }
3403
+ total += sum;
3404
+ partialsum[i] = total;
3405
+ }
3406
+ } else {
3407
+ for (let i = vbox.b1; i <= vbox.b2; i++) {
3408
+ let sum = 0;
3409
+ for (let j = vbox.r1; j <= vbox.r2; j++) {
3410
+ for (let k = vbox.g1; k <= vbox.g2; k++) {
3411
+ sum += histo[getColorIndex(j, k, i)] || 0;
3412
+ }
3413
+ }
3414
+ total += sum;
3415
+ partialsum[i] = total;
3416
+ }
3417
+ }
3418
+ partialsum.forEach((d, i) => {
3419
+ lookaheadsum[i] = total - d;
3420
+ });
3421
+ function doCut(color) {
3422
+ const dim1 = color + "1";
3423
+ const dim2 = color + "2";
3424
+ for (let i = vbox[dim1]; i <= vbox[dim2]; i++) {
3425
+ if (partialsum[i] > total / 2) {
3426
+ const vbox1 = vbox.copy();
3427
+ const vbox2 = vbox.copy();
3428
+ const left = i - vbox[dim1];
3429
+ const right = vbox[dim2] - i;
3430
+ let d2;
3431
+ if (left <= right) {
3432
+ d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));
3433
+ } else {
3434
+ d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));
3435
+ }
3436
+ while (!partialsum[d2]) d2++;
3437
+ let count2 = lookaheadsum[d2];
3438
+ while (!count2 && partialsum[d2 - 1]) count2 = lookaheadsum[--d2];
3439
+ vbox1[dim2] = d2;
3440
+ vbox2[dim1] = vbox1[dim2] + 1;
3441
+ return [vbox1, vbox2];
3442
+ }
3443
+ }
3444
+ return void 0;
3445
+ }
3446
+ if (maxw === rw) return doCut("r");
3447
+ if (maxw === gw) return doCut("g");
3448
+ return doCut("b");
3449
+ }
3450
+ function iterate(pq, target, histo) {
3451
+ let ncolors = pq.size();
3452
+ let niters = 0;
3453
+ while (niters < MAX_ITERATIONS) {
3454
+ if (ncolors >= target) return;
3455
+ niters++;
3456
+ const vbox = pq.pop();
3457
+ if (!vbox.count()) {
3458
+ pq.push(vbox);
3459
+ continue;
3460
+ }
3461
+ const result = medianCutApply(histo, vbox);
3462
+ if (!result || !result[0]) return;
3463
+ pq.push(result[0]);
3464
+ if (result[1]) {
3465
+ pq.push(result[1]);
3466
+ ncolors++;
3467
+ }
3468
+ }
3469
+ }
3470
+ function quantize(pixels, maxColors) {
3471
+ if (!pixels.length || maxColors < 2 || maxColors > 256) return [];
3472
+ const seenColors = /* @__PURE__ */ new Set();
3473
+ const uniqueColors = [];
3474
+ for (const color of pixels) {
3475
+ const key = color.join(",");
3476
+ if (!seenColors.has(key)) {
3477
+ seenColors.add(key);
3478
+ uniqueColors.push(color);
3479
+ }
3480
+ }
3481
+ if (uniqueColors.length <= maxColors) {
3482
+ const countMap = /* @__PURE__ */ new Map();
3483
+ for (const color of pixels) {
3484
+ const key = color.join(",");
3485
+ countMap.set(key, (countMap.get(key) || 0) + 1);
3486
+ }
3487
+ return uniqueColors.map((color) => ({
3488
+ color,
3489
+ population: countMap.get(color.join(","))
3490
+ }));
3491
+ }
3492
+ const histo = getHisto(pixels);
3493
+ const vbox = vboxFromPixels(pixels, histo);
3494
+ const pq = new PQueue((a, b) => a.count() - b.count());
3495
+ pq.push(vbox);
3496
+ iterate(pq, FRACT_BY_POPULATIONS * maxColors, histo);
3497
+ const pq2 = new PQueue((a, b) => a.count() * a.volume() - b.count() * b.volume());
3498
+ while (pq.size()) {
3499
+ pq2.push(pq.pop());
3500
+ }
3501
+ iterate(pq2, maxColors, histo);
3502
+ const results = [];
3503
+ while (pq2.size()) {
3504
+ const box = pq2.pop();
3505
+ results.push({
3506
+ color: box.avg(),
3507
+ population: box.count()
3508
+ });
3509
+ }
3510
+ return results;
3511
+ }
3512
+ var MmcqQuantizer = class {
3513
+ async init() {
3514
+ }
3515
+ quantize(pixels, maxColors) {
3516
+ return quantize(pixels, maxColors);
3517
+ }
3518
+ };
3519
+ init_browser();
3520
+ init_pipeline();
3521
+ new BrowserPixelLoader();
3522
+ var defaultQuantizer = new MmcqQuantizer();
3523
+ function getColorSync(source, options) {
3524
+ const palette = getPaletteSync(source, { colorCount: 5, ...options });
3525
+ return palette ? palette[0] : null;
3526
+ }
3527
+ function getPaletteSync(source, options) {
3528
+ const opts = validateOptions(options ?? {});
3529
+ const quantizer = options?.quantizer ?? defaultQuantizer;
3530
+ const pixels = loadPixelsSync(source);
3531
+ return extractPalette(
3532
+ pixels.data,
3533
+ pixels.width,
3534
+ pixels.height,
3535
+ opts,
3536
+ quantizer
3537
+ );
3538
+ }
3539
+ function loadPixelsSync(source) {
3540
+ if (typeof HTMLImageElement !== "undefined" && source instanceof HTMLImageElement) {
3541
+ return loadFromImage(source);
3542
+ }
3543
+ if (typeof HTMLCanvasElement !== "undefined" && source instanceof HTMLCanvasElement) {
3544
+ return loadFromCanvas(source);
3545
+ }
3546
+ if (typeof ImageData !== "undefined" && source instanceof ImageData) {
3547
+ return { data: source.data, width: source.width, height: source.height };
3548
+ }
3549
+ if (typeof HTMLVideoElement !== "undefined" && source instanceof HTMLVideoElement) {
3550
+ return loadFromVideo(source);
3551
+ }
3552
+ if (typeof ImageBitmap !== "undefined" && source instanceof ImageBitmap) {
3553
+ return loadFromImageBitmap(source);
3554
+ }
3555
+ if (typeof OffscreenCanvas !== "undefined" && source instanceof OffscreenCanvas) {
3556
+ return loadFromOffscreenCanvas(source);
3557
+ }
3558
+ throw new Error(
3559
+ "Unsupported source type. Expected HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageData, ImageBitmap, or OffscreenCanvas."
3560
+ );
3561
+ }
3562
+ function loadFromImage(img) {
3563
+ if (!img.complete) {
3564
+ throw new Error(
3565
+ 'Image has not finished loading. Wait for the "load" event before calling getColorSync/getPaletteSync.'
3566
+ );
3567
+ }
3568
+ if (!img.naturalWidth) {
3569
+ throw new Error(
3570
+ "Image has no dimensions. It may not have loaded successfully."
3571
+ );
3572
+ }
3573
+ const canvas = document.createElement("canvas");
3574
+ const ctx = canvas.getContext("2d");
3575
+ const width = canvas.width = img.naturalWidth;
3576
+ const height = canvas.height = img.naturalHeight;
3577
+ ctx.drawImage(img, 0, 0, width, height);
3578
+ try {
3579
+ const imageData = ctx.getImageData(0, 0, width, height);
3580
+ return { data: imageData.data, width, height };
3581
+ } catch (e) {
3582
+ if (e instanceof DOMException && e.name === "SecurityError") {
3583
+ const err = new Error(
3584
+ 'Image is tainted by cross-origin data. Add crossorigin="anonymous" to the <img> tag and ensure the server sends appropriate CORS headers.'
3585
+ );
3586
+ err.cause = e;
3587
+ throw err;
3588
+ }
3589
+ throw e;
3590
+ }
3591
+ }
3592
+ function loadFromCanvas(canvas) {
3593
+ const ctx = canvas.getContext("2d");
3594
+ const { width, height } = canvas;
3595
+ const imageData = ctx.getImageData(0, 0, width, height);
3596
+ return { data: imageData.data, width, height };
3597
+ }
3598
+ function loadFromVideo(video) {
3599
+ if (video.readyState < 2) {
3600
+ throw new Error(
3601
+ 'Video is not ready. Wait for the "loadeddata" or "canplay" event before calling getColorSync/getPaletteSync.'
3602
+ );
3603
+ }
3604
+ const width = video.videoWidth;
3605
+ const height = video.videoHeight;
3606
+ if (!width || !height) {
3607
+ throw new Error(
3608
+ "Video has no dimensions. It may not have loaded successfully."
3609
+ );
3610
+ }
3611
+ const canvas = document.createElement("canvas");
3612
+ const ctx = canvas.getContext("2d");
3613
+ canvas.width = width;
3614
+ canvas.height = height;
3615
+ ctx.drawImage(video, 0, 0, width, height);
3616
+ const imageData = ctx.getImageData(0, 0, width, height);
3617
+ return { data: imageData.data, width, height };
3618
+ }
3619
+ function loadFromOffscreenCanvas(canvas) {
3620
+ const ctx = canvas.getContext("2d");
3621
+ if (!ctx) {
3622
+ throw new Error(
3623
+ "Could not get 2D context from OffscreenCanvas."
3624
+ );
3625
+ }
3626
+ const { width, height } = canvas;
3627
+ const imageData = ctx.getImageData(0, 0, width, height);
3628
+ return { data: imageData.data, width, height };
3629
+ }
3630
+ function loadFromImageBitmap(bitmap) {
3631
+ const canvas = document.createElement("canvas");
3632
+ const ctx = canvas.getContext("2d");
3633
+ canvas.width = bitmap.width;
3634
+ canvas.height = bitmap.height;
3635
+ ctx.drawImage(bitmap, 0, 0);
3636
+ const imageData = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
3637
+ return { data: imageData.data, width: bitmap.width, height: bitmap.height };
3638
+ }
3639
+ init_color();
3640
+
3641
+ // src/preferences/helpers/getDominantColor.ts
3642
+ var getDominantColor = (img) => {
3643
+ return new Promise((resolve, reject) => {
3644
+ if (!img.complete || img.naturalWidth === 0) {
3645
+ reject(new Error("Image not loaded"));
3646
+ return;
3647
+ }
3648
+ try {
3649
+ const color = getColorSync(img);
3650
+ if (color) {
3651
+ const hex = color.hex();
3652
+ if (!/^#[0-9a-fA-F]{6}$/.test(hex)) {
3653
+ reject(new Error(`Invalid color extracted: ${hex}`));
3654
+ } else {
3655
+ resolve(hex);
3656
+ }
3657
+ } else {
3658
+ reject(new Error("No color extracted"));
3659
+ }
3660
+ } catch (error) {
3661
+ reject(new Error(`Color extraction failed: ${error.message}`));
3662
+ }
3663
+ });
3664
+ };
3665
+
3666
+ // src/preferences/helpers/themeGeneration.ts
3667
+ var hexToHsl = (hex) => {
3668
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
3669
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
3670
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
3671
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
3672
+ const l = (max + min) / 2;
3673
+ if (max === min) return [0, 0, l];
3674
+ const d = max - min;
3675
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3676
+ const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / d + 2) / 6 : ((r - g) / d + 4) / 6;
3677
+ return [h, s, l];
3678
+ };
3679
+ var hslToHex = (h, s, l) => {
3680
+ const a = s * Math.min(l, 1 - l);
3681
+ const f = (n) => {
3682
+ const k = (n + h * 12) % 12;
3683
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
3684
+ return Math.round(255 * color).toString(16).padStart(2, "0");
3685
+ };
3686
+ return `#${f(0)}${f(8)}${f(4)}`;
3687
+ };
3688
+ var luminance = (hex) => {
3689
+ const toLinear = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
3690
+ const r = toLinear(parseInt(hex.slice(1, 3), 16) / 255);
3691
+ const g = toLinear(parseInt(hex.slice(3, 5), 16) / 255);
3692
+ const b = toLinear(parseInt(hex.slice(5, 7), 16) / 255);
3693
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
3694
+ };
3695
+ var contrastRatio2 = (a, b) => {
3696
+ const l1 = luminance(a), l2 = luminance(b);
3697
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
3698
+ };
3699
+ var isLightColor = (hexColor) => luminance(hexColor) > 0.179;
3700
+ var shadeColor = (hex, isLight) => {
3701
+ const [h, s] = hexToHsl(hex);
3702
+ return isLight ? hslToHex(h, Math.min(s, 0.25), 0.93) : hslToHex(h, Math.min(s, 0.3), 0.13);
3703
+ };
3704
+ var generateThemeFromColor = (color) => {
3705
+ const [h, s] = hexToHsl(color);
3706
+ const isLight = isLightColor(color);
3707
+ const background = shadeColor(color, isLight);
3708
+ const text = contrastRatio2(background, "#ffffff") >= contrastRatio2(background, "#000000") ? "#ffffff" : "#000000";
3709
+ const subdue = isLight ? hslToHex(h, Math.min(s, 0.15), 0.55) : hslToHex(h, Math.min(s, 0.15), 0.55);
3710
+ const hover = isLight ? hslToHex(h, Math.min(s, 0.2), 0.83) : hslToHex(h, Math.min(s, 0.2), 0.22);
3711
+ const elevate = `0px 0px 2px ${isLight ? hslToHex(h, 0.1, 0.7) : hslToHex(h, 0.1, 0.08)}`;
3712
+ const link = isLight ? hslToHex(h, 0.7, 0.35) : hslToHex(h, 0.8, 0.72);
3713
+ const visited = isLight ? hslToHex(h, 0.5, 0.28) : hslToHex(h, 0.55, 0.6);
3714
+ const select = hslToHex(h, 0.6, isLight ? 0.8 : 0.35);
3715
+ const focus = hslToHex(h, 0.9, isLight ? 0.4 : 0.65);
3716
+ return {
3717
+ background,
3718
+ text,
3719
+ link,
3720
+ visited,
3721
+ subdue,
3722
+ disable: subdue,
3723
+ hover,
3724
+ onHover: text,
3725
+ select,
3726
+ onSelect: "inherit",
3727
+ focus,
3728
+ elevate,
3729
+ immerse: isLight ? "0.6" : "0.4"
3730
+ };
3731
+ };
3732
+ var extractThemeFromImage = async (imageUrl) => {
3733
+ const img = new Image();
3734
+ img.crossOrigin = "anonymous";
3735
+ await new Promise((resolve, reject) => {
3736
+ img.onload = () => {
3737
+ if (img.naturalWidth === 0) {
3738
+ reject(new Error("Image loaded but has no content (blocked or corrupt)"));
3739
+ } else {
3740
+ resolve();
3741
+ }
3742
+ };
3743
+ img.onerror = () => reject(new Error(`Failed to load image: ${imageUrl}`));
3744
+ img.src = imageUrl;
3745
+ });
3746
+ const dominantColor = await getDominantColor(img);
3747
+ return generateThemeFromColor(dominantColor);
3748
+ };
3749
+
3750
+ // src/helpers/proxyUrl.ts
3751
+ var proxyUrl = (url) => {
3752
+ if (!url) return void 0;
3753
+ try {
3754
+ const parsed = new URL(url);
3755
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
3756
+ return `/api/proxy?url=${encodeURIComponent(url)}`;
3757
+ }
3758
+ } catch {
3759
+ }
3760
+ return url;
3761
+ };
3762
+
3763
+ // src/preferences/hooks/useTheming.ts
3764
+ var useTheming = ({
3765
+ theme,
3766
+ systemKeys,
3767
+ themeKeys,
3768
+ breakpointsMap,
3769
+ initProps,
3770
+ coverUrl,
3771
+ autoThemeSource,
3772
+ onBreakpointChange,
3773
+ onColorSchemeChange,
3774
+ onContrastChange,
3775
+ onForcedColorsChange,
3776
+ onMonochromeChange,
3777
+ onReducedMotionChange,
3778
+ onReducedTransparencyChange,
3779
+ onCoverThemeGenerated
3780
+ }) => {
3781
+ const [coverThemeTokens, setCoverThemeTokens] = useState(null);
3782
+ const [coverThemeFailed, setCoverThemeFailed] = useState(false);
3783
+ const breakpoints = useBreakpoints(breakpointsMap, onBreakpointChange);
3784
+ const colorScheme = useColorScheme(onColorSchemeChange);
3785
+ const colorSchemeRef = useRef(colorScheme);
3786
+ const contrast = useContrast(onContrastChange);
3787
+ const forcedColors = useForcedColors(onForcedColorsChange);
3788
+ const monochrome = useMonochrome(onMonochromeChange);
3789
+ const reducedMotion = useReducedMotion(onReducedMotionChange);
3790
+ const reducedTransparency = useReducedTransparency(onReducedTransparencyChange);
3791
+ useEffect(() => {
3792
+ if (autoThemeSource === "cover" && coverUrl && !coverThemeTokens) {
3793
+ const extractTheme = async () => {
3794
+ try {
3795
+ const themeTokens = await extractThemeFromImage(proxyUrl(coverUrl) ?? coverUrl);
3796
+ setCoverThemeTokens(themeTokens);
3797
+ onCoverThemeGenerated?.(themeTokens);
3798
+ } catch (error) {
3799
+ console.warn("Failed to extract cover theme:", error);
3800
+ setCoverThemeFailed(true);
3801
+ }
3802
+ };
3803
+ extractTheme();
3804
+ }
3805
+ }, [autoThemeSource, coverUrl, coverThemeTokens, onCoverThemeGenerated]);
3806
+ const updateThemeColorMetaTag = useCallback((color) => {
3807
+ if (typeof document === "undefined") return;
3808
+ let metaTag = document.querySelector("meta[name='theme-color']");
3809
+ if (!metaTag) {
3810
+ metaTag = document.createElement("meta");
3811
+ metaTag.setAttribute("name", "theme-color");
3812
+ document.head.appendChild(metaTag);
3813
+ }
3814
+ metaTag.setAttribute("content", color);
3815
+ }, []);
3816
+ const initThemingCustomProps = useCallback(() => {
3817
+ for (let p in initProps) {
3818
+ document.documentElement.style.setProperty(p, initProps[p]);
3819
+ }
3820
+ }, [initProps]);
3821
+ const inferThemeAuto = useCallback(() => {
3822
+ if (autoThemeSource === "cover") {
3823
+ if (coverThemeTokens) return "cover";
3824
+ if (!coverThemeFailed) return void 0;
3825
+ }
3826
+ return colorSchemeRef.current === "dark" /* dark */ ? systemKeys?.dark : systemKeys?.light;
3827
+ }, [systemKeys, autoThemeSource, coverThemeTokens, coverThemeFailed]);
3828
+ const setThemeCustomProps = useCallback((t) => {
3829
+ if (!t) {
3830
+ return;
3831
+ }
3832
+ if (t === "auto") {
3833
+ const autoTheme = inferThemeAuto();
3834
+ if (!autoTheme) {
3835
+ return;
3836
+ }
3837
+ t = autoTheme;
3838
+ }
3839
+ let themeTokens;
3840
+ if (t === "cover" && coverThemeTokens) {
3841
+ themeTokens = coverThemeTokens;
3842
+ } else {
3843
+ themeTokens = themeKeys[t];
3844
+ }
3845
+ if (!themeTokens) {
3846
+ return;
3847
+ }
3848
+ const props = propsToCSSVars(themeTokens, { prefix: prefixString("theme") });
3849
+ for (let p in props) {
3850
+ document.documentElement.style.setProperty(p, props[p]);
3851
+ }
3852
+ updateThemeColorMetaTag(themeTokens.background);
3853
+ }, [inferThemeAuto, updateThemeColorMetaTag, themeKeys, coverThemeTokens]);
3854
+ useEffect(() => {
3855
+ initThemingCustomProps();
3856
+ }, [initThemingCustomProps]);
3857
+ useEffect(() => {
3858
+ colorSchemeRef.current = colorScheme;
3859
+ setThemeCustomProps(theme);
3860
+ }, [setThemeCustomProps, theme, colorScheme]);
3861
+ useEffect(() => {
3862
+ if (!coverThemeTokens || theme !== "auto") return;
3863
+ const props = propsToCSSVars(coverThemeTokens, { prefix: prefixString("theme") });
3864
+ for (let p in props) {
3865
+ document.documentElement.style.setProperty(p, props[p]);
3866
+ }
3867
+ updateThemeColorMetaTag(coverThemeTokens.background);
3868
+ }, [coverThemeTokens, theme, updateThemeColorMetaTag]);
3869
+ const themeResolved = autoThemeSource !== "cover" || !coverUrl || !!coverThemeTokens || coverThemeFailed;
3870
+ return {
3871
+ inferThemeAuto,
3872
+ theme,
3873
+ breakpoints,
3874
+ colorScheme,
3875
+ contrast,
3876
+ forcedColors,
3877
+ monochrome,
3878
+ reducedMotion,
3879
+ reducedTransparency,
3880
+ coverThemeTokens,
3881
+ themeResolved
3882
+ };
3883
+ };
3884
+ var useSharedPreferences = () => {
3885
+ const audioCtx = useContext(ThAudioPreferencesContext);
3886
+ const readerCtx = useContext(ThPreferencesContext);
3887
+ const ctx = audioCtx ?? readerCtx;
3888
+ if (!ctx) throw new Error("useSharedPreferences must be used within a ThPreferencesProvider or ThAudioPreferencesProvider");
3889
+ const prefs = ctx.preferences;
3890
+ return {
3891
+ shortcuts: prefs.shortcuts,
3892
+ docking: prefs.docking,
3893
+ theming: {
3894
+ icon: prefs.theming.icon,
3895
+ header: prefs.theming.header ? { backLink: prefs.theming.header.backLink } : void 0,
3896
+ themes: {
3897
+ systemThemes: prefs.theming.themes.systemThemes,
3898
+ keys: prefs.theming.themes.keys,
3899
+ audioOrder: audioCtx?.preferences.theming.themes.audioOrder,
3900
+ reflowOrder: readerCtx?.preferences.theming.themes.reflowOrder,
3901
+ fxlOrder: readerCtx?.preferences.theming.themes.fxlOrder
3902
+ },
3903
+ layout: {
3904
+ defaults: prefs.theming.layout.defaults
3905
+ },
3906
+ breakpoints: prefs.theming.breakpoints
3907
+ }
3908
+ };
3909
+ };
3910
+
3911
+ export { ThActionsKeys, ThAudioActionKeys, ThAudioAffordance, ThAudioKeys, ThAudioMemoryPreferencesAdapter, ThAudioPreferencesContext, ThAudioPreferencesProvider, ThDockingKeys, ThDockingTypes, ThGlobalMemoryPreferencesAdapter, ThGlobalPreferencesProvider, ThLayoutOptions, ThLineHeightOptions, ThMemoryPreferencesAdapter, ThPreferencesContext, ThPreferencesProvider, ThSettingsContainerKeys, ThSettingsKeys, ThSettingsRangePlaceholder, ThSettingsRangeVariant, ThSettingsTimerVariant, ThSheetHeaderVariant, ThSheetTypes, ThSpacingPresetKeys, ThSpacingSettingsKeys, ThTextAlignOptions, ThTextSettingsKeys, ThThemeKeys, arabicFarsiCollection, buildThemeObject, chineseSimplifiedCollection, chineseTraditionalCollection, contrast1Theme, contrast2Theme, contrast3Theme, createAudioPreferences, createDefinitionFromStaticFonts, createDefinitionsFromBunnyFonts, createDefinitionsFromGoogleFonts, createGlobalPreferences, createPreferences, darkTheme, defaultActionKeysObject, defaultAudioContentProtectionConfig, defaultAudioPlaybackRate, defaultAudioPlaybackRateAction, defaultAudioPreferences, defaultAudioPreferencesContextValue, defaultAudioRemotePlaybackAction, defaultAudioSkipBackwardInterval, defaultAudioSkipForwardInterval, defaultAudioSkipInterval, defaultAudioSleepTimer, defaultAudioSleepTimerAction, defaultAudioSleepTimerPresetList, defaultAudioTocAction, defaultAudioVolume, defaultAudioVolumeAction, defaultContentProtectionConfig, defaultFontCollection, defaultFullscreenAction, defaultJumpToPositionAction, defaultLetterSpacing, defaultLineHeights, defaultParagraphIndent, defaultParagraphSpacing, defaultPreferences, defaultPreferencesContextValue, defaultSettingsAction, defaultSpacingPresets, defaultSpacingPresetsOrder, defaultSpacingSettingsMain, defaultSpacingSettingsSubpanel, defaultTextSettingsMain, defaultTextSettingsSubpanel, defaultTocAction, defaultWordSpacing, defaultZoom, devContentProtectionConfig, hebrewCollection, japaneseCollection, japaneseVerticalCollection, koreanCollection, lightTheme, paperTheme, prefixString, proxyUrl, readiumCSSFontCollection, resolveAudioContentProtectionConfig, resolveContentProtectionConfig, sepiaTheme, tamilCollection, useActionsPreferences, useAudioActionsPreferences, useAudioPreferences, useFilteredPreferenceKeys, usePreferenceKeys, usePreferences, useSharedPreferences, useTheming, validateObjectKeys };
3912
+ //# sourceMappingURL=chunk-XRFLDNAY.mjs.map
3913
+ //# sourceMappingURL=chunk-XRFLDNAY.mjs.map