@readium/navigator 2.4.0 → 2.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/ReadiumCSS-after-B_e3a-PY.js +592 -0
  2. package/dist/ReadiumCSS-after-C-T_0paD.js +530 -0
  3. package/dist/ReadiumCSS-after-lr-n3fz2.js +475 -0
  4. package/dist/ReadiumCSS-after-mXeKKPap.js +490 -0
  5. package/dist/ReadiumCSS-before-Bjd3POej.js +426 -0
  6. package/dist/ReadiumCSS-before-CfXPAGaQ.js +425 -0
  7. package/dist/ReadiumCSS-before-CrNWvuyE.js +425 -0
  8. package/dist/ReadiumCSS-before-KVen5ceo.js +425 -0
  9. package/dist/ReadiumCSS-default-BKAG5pGU.js +162 -0
  10. package/dist/ReadiumCSS-default-C63bYOYF.js +183 -0
  11. package/dist/ReadiumCSS-default-CclvbeNC.js +162 -0
  12. package/dist/ReadiumCSS-default-DnlgDaBu.js +180 -0
  13. package/dist/ReadiumCSS-ebpaj_fonts_patch-Dt2XliTg.js +82 -0
  14. package/dist/index.js +2267 -3201
  15. package/dist/index.umd.cjs +4432 -1083
  16. package/package.json +1 -1
  17. package/src/audio/index.ts +1 -1
  18. package/src/epub/EpubNavigator.ts +66 -31
  19. package/src/epub/frame/FrameBlobBuilder.ts +23 -22
  20. package/src/epub/frame/FrameManager.ts +4 -0
  21. package/src/epub/fxl/FXLFramePoolManager.ts +5 -1
  22. package/src/epub/helpers/scriptMode.ts +45 -0
  23. package/src/epub/index.ts +1 -0
  24. package/src/injection/epubInjectables.ts +78 -9
  25. package/src/injection/webpubInjectables.ts +1 -1
  26. package/types/src/epub/EpubNavigator.d.ts +4 -3
  27. package/types/src/epub/frame/FrameManager.d.ts +1 -0
  28. package/types/src/epub/helpers/scriptMode.d.ts +16 -0
  29. package/types/src/epub/index.d.ts +1 -0
  30. package/types/src/injection/epubInjectables.d.ts +4 -2
  31. package/types/src/injection/webpubInjectables.d.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "2.4.0",
3
+ "version": "2.5.0-beta.1",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -1,3 +1,3 @@
1
1
  export * from './engine/index.ts';
2
2
  export * from './preferences/index.ts';
3
- export * from './AudioNavigator.ts';
3
+ export * from './AudioNavigator.ts';
@@ -2,8 +2,7 @@ import { Layout, Link, Locator, Profile, Publication, ReadingProgression } from
2
2
  import { Configurable, ConfigurableSettings, LineLengths, ProgressionRange, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
- import { CommsEventKey, ContextMenuEvent, FXLModules, KeyboardEventData, ModuleLibrary, ModuleName, ReflowableModules } from "@readium/navigator-html-injectables";
6
- import { BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
5
+ import { CommsEventKey, ContextMenuEvent, FXLModules, KeyboardEventData, ModuleLibrary, ModuleName, ReflowableModules, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
7
6
  import * as path from "path-browserify";
8
7
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
9
8
  import { FrameManager } from "./frame/FrameManager.ts";
@@ -16,10 +15,11 @@ import { RSProperties, UserProperties } from "./css/Properties.ts";
16
15
  import { getContentWidth } from "../helpers/dimensions.ts";
17
16
  import { Injector } from "../injection/Injector.ts";
18
17
  import { createReadiumEpubRules } from "../injection/epubInjectables.ts";
19
- import { IInjectablesConfig } from "../injection/Injectable.ts";
18
+ import { IInjectableRule, IInjectablesConfig } from "../injection/Injectable.ts";
20
19
  import { IContentProtectionConfig, IKeyboardPeripheralsConfig } from "../Navigator.ts";
21
20
  import { NavigatorProtector, NAVIGATOR_SUSPICIOUS_ACTIVITY_EVENT } from "../protection/NavigatorProtector.ts";
22
21
  import { KeyboardPeripherals, NAVIGATOR_KEYBOARD_PERIPHERAL_EVENT } from "../peripherals/KeyboardPeripherals.ts";
22
+ import { getScriptMode } from "./helpers/scriptMode.ts";
23
23
 
24
24
  export type ManagerEventKey = "zoom";
25
25
 
@@ -80,7 +80,9 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
80
80
  private _settings: EpubSettings;
81
81
  private _css: ReadiumCSS;
82
82
  private _preferencesEditor: EpubPreferencesEditor | null = null;
83
- private readonly _injector: Injector | null = null;
83
+ private _injector: Injector | null = null;
84
+ private readonly _readiumRulesPromise: Promise<IInjectableRule[]>;
85
+ private readonly _injectablesConfig: IInjectablesConfig;
84
86
  private readonly _contentProtection: IContentProtectionConfig;
85
87
  private readonly _keyboardPeripherals: IKeyboardPeripheralsConfig;
86
88
  private readonly _navigatorProtector: NavigatorProtector | null = null;
@@ -108,8 +110,14 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
108
110
  this._preferences = new EpubPreferences(configuration.preferences);
109
111
  this._defaults = new EpubDefaults(configuration.defaults);
110
112
  this._settings = new EpubSettings(this._preferences, this._defaults);
113
+ // For CJK vertical, force --RS__disablePagination for the entire session.
114
+ // ReadiumCSS.update() never sets noVerticalPagination, so this persists.
115
+ const scriptMode = getScriptMode(pub.metadata);
116
+ const isCJKHorizontal = scriptMode === 'cjk-horizontal';
117
+ const isCJKVertical = scriptMode === 'cjk-vertical';
118
+ const isCJK = isCJKHorizontal || isCJKVertical;
111
119
  this._css = new ReadiumCSS({
112
- rsProperties: new RSProperties({}),
120
+ rsProperties: new RSProperties({ noVerticalPagination: isCJKVertical || undefined }),
113
121
  userProperties: new UserProperties({}),
114
122
  lineLengths: new LineLengths({
115
123
  optimalChars: this._settings.optimalLineLength,
@@ -121,6 +129,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
121
129
  fontFace: this._settings.fontFamily,
122
130
  letterSpacing: this._settings.letterSpacing,
123
131
  wordSpacing: this._settings.wordSpacing,
132
+ isCJK: isCJK,
124
133
  // sample: this.pub.metadata.description
125
134
  }),
126
135
  container: container,
@@ -130,14 +139,13 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
130
139
  this._layout = EpubNavigator.determineLayout(pub, !!this._settings.scroll);
131
140
  this.currentProgression = pub.metadata.effectiveReadingProgression;
132
141
 
133
- // Combine Readium rules with user-provided injectables
134
- const readiumRules = createReadiumEpubRules(pub.metadata, pub.readingOrder.items);
135
- const userConfig = configuration.injectables || { rules: [], allowedDomains: [] };
136
-
137
- this._injector = new Injector({
138
- rules: [...readiumRules, ...userConfig.rules],
139
- allowedDomains: userConfig.allowedDomains
140
- });
142
+ // Store user injectables config; Injector is created in load() once
143
+ // the async CSS rules promise has resolved.
144
+ this._injectablesConfig = configuration.injectables || { rules: [], allowedDomains: [] };
145
+ // Start loading Readium CSS rules asynchronously. The promise is
146
+ // awaited in load() before the Injector is created, ensuring the
147
+ // correct script-mode stylesheets are ready before the first frame.
148
+ this._readiumRulesPromise = createReadiumEpubRules(pub.metadata, pub.readingOrder.items);
141
149
 
142
150
  this._contentProtection = configuration.contentProtection || {};
143
151
 
@@ -201,6 +209,11 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
201
209
  if (layout === Layout.scrolled)
202
210
  return Layout.scrolled;
203
211
 
212
+ // CJK vertical writing: force scroll mode so the ScrollSnapper is
213
+ // used and column-based pagination doesn't interfere.
214
+ if (getScriptMode(pub.metadata) === 'cjk-vertical')
215
+ return Layout.scrolled;
216
+
204
217
  if (layout === Layout.reflowable && scroll)
205
218
  return Layout.scrolled;
206
219
 
@@ -210,6 +223,17 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
210
223
  public async load() {
211
224
  if (!this.positions?.length)
212
225
  this.positions = await this.pub.positionsFromManifest();
226
+
227
+ // Build Injector now that async CSS loading has had time to resolve.
228
+ // (Started in the constructor, so this typically resolves immediately.)
229
+ if (!this._injector) {
230
+ const readiumRules = await this._readiumRulesPromise;
231
+ this._injector = new Injector({
232
+ rules: [...readiumRules, ...this._injectablesConfig.rules],
233
+ allowedDomains: this._injectablesConfig.allowedDomains
234
+ });
235
+ }
236
+
213
237
  if(this._layout === Layout.fixed) {
214
238
  this.framePool = new FXLFramePoolManager(
215
239
  this.container,
@@ -312,6 +336,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
312
336
  }
313
337
 
314
338
  private async commitCSS(css: ReadiumCSS) {
339
+ // framePool is only available after load() — guard against early calls
340
+ // from the ResizeObserver which is registered in the constructor.
341
+ if (!this.framePool) return;
342
+
315
343
  // Since we’re updating the CSS properties in injectables by removing
316
344
  // the existing properties that are not inside this object first,
317
345
  // then adding all from it, we don’t compare the previous properties here
@@ -341,6 +369,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
341
369
 
342
370
  if (this._layout === Layout.fixed) {
343
371
  this.container.style.width = `${ getContentWidth(parentEl) - this._settings.constraint }px`;
372
+ if (!this.framePool) return;
344
373
  (this.framePool as FXLFramePoolManager).resizeHandler();
345
374
  } else {
346
375
  // for reflow ReadiumCSS gets the width from columns + line-lengths
@@ -371,7 +400,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
371
400
  * TODO remove when settings management is incorporated
372
401
  */
373
402
  public get _cframes(): (FXLFrameManager | FrameManager | undefined)[] {
374
- return this.framePool.currentFrames;
403
+ return (this.framePool?.currentFrames ?? []).filter(f => !(f instanceof FrameManager && f.isDestroyed));
375
404
  }
376
405
 
377
406
  /**
@@ -524,12 +553,18 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
524
553
  if(this._layout === Layout.fixed) {
525
554
  return modules.filter((m) => FXLModules.includes(m));
526
555
  } else modules = modules.filter((m) => ReflowableModules.includes(m));
556
+
557
+ // CJK vertical: uses its own X-axis snapper, never column or scroll snappers
558
+ if (getScriptMode(this.pub.metadata) === 'cjk-vertical') {
559
+ return modules.filter((m) => m !== "column_snapper" && m !== "scroll_snapper");
560
+ }
527
561
 
528
562
  // Horizontal vs. Vertical reading
563
+ const all = modules as ModuleName[];
529
564
  if (this._layout === Layout.scrolled)
530
- modules = modules.filter((m) => m !== "column_snapper");
565
+ modules = all.filter((m) => m !== "column_snapper" && m !== "cjk_vertical_snapper");
531
566
  else
532
- modules = modules.filter((m) => m !== "scroll_snapper");
567
+ modules = all.filter((m) => m !== "scroll_snapper" && m !== "cjk_vertical_snapper");
533
568
 
534
569
  return modules;
535
570
  }
@@ -655,21 +690,19 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
655
690
  let first = this.currentLocation;
656
691
  let last = undefined;
657
692
 
658
- // Find the last locator with a progression that's
659
- // smaller than or equal to the requested progression.
660
- potentialPositions.some((p, idx) => {
693
+ // Find the last locator whose progression is <= fromProgression.start.
694
+ // potentialPositions is ordered by progression ascending (0 → 1).
695
+ const idx = potentialPositions.findLastIndex((p) => {
661
696
  const pr = p.locations.progression ?? 0;
662
- if (fromProgression.start <= pr) {
663
- first = p;
697
+ return pr <= fromProgression.start;
698
+ });
664
699
 
665
- // If there’s a match, check the last in view, from the next progression
666
- const nextPositions = potentialPositions.splice(idx + 1, potentialPositions.length);
667
- last = this.findLastPositionInProgressionRange(nextPositions, fromProgression);
700
+ if (idx !== -1) {
701
+ first = potentialPositions[idx];
702
+ const nextPositions = potentialPositions.slice(idx + 1);
703
+ last = this.findLastPositionInProgressionRange(nextPositions, fromProgression);
704
+ }
668
705
 
669
- return true;
670
- }
671
- else return false;
672
- });
673
706
  return { first: first, last: last }
674
707
  }
675
708
 
@@ -749,9 +782,11 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
749
782
  }
750
783
 
751
784
  get viewport(): VisualNavigatorViewport {
752
- return this._layout === Layout.fixed
753
- ? (this.framePool as FXLFramePoolManager).viewport
754
- : this.reflowViewport;
785
+ if (this._layout === Layout.fixed) {
786
+ if (!this.framePool) return { readingOrder: [], progressions: new Map(), positions: null };
787
+ return (this.framePool as FXLFramePoolManager).viewport;
788
+ }
789
+ return this.reflowViewport;
755
790
  }
756
791
 
757
792
  get isScrollStart(): boolean {
@@ -1,5 +1,6 @@
1
- import { Link, MediaType, Publication } from "@readium/shared";
1
+ import { Link, MediaType, Publication, ReadingProgression } from "@readium/shared";
2
2
  import { Injector } from "../../injection/Injector.ts";
3
+ import { getScriptMode } from "../helpers/scriptMode.ts";
3
4
 
4
5
  const csp = (domains: string[]) => {
5
6
  const d = domains.join(" ");
@@ -136,37 +137,37 @@ export default class FrameBlobBuider {
136
137
  if (mediaType === MediaType.XHTML) {
137
138
  // InDesign is infamous for setting xml:lang on the body instead of the root element
138
139
  // So we have to check whether lang is set on the body and move it to the root element
139
- const rootLang = document.documentElement.lang || document.documentElement.getAttribute("xml:lang");
140
- const bodyLang = document.body.lang || document.body.getAttribute("xml:lang");
140
+ const rootLang = doc.documentElement.lang || doc.documentElement.getAttribute("xml:lang");
141
+ const bodyLang = doc.body.lang || doc.body.getAttribute("xml:lang");
141
142
  if (bodyLang && !rootLang) {
142
- document.documentElement.lang = bodyLang;
143
- document.documentElement.setAttribute("xml:lang", bodyLang);
144
- document.body.removeAttribute("xml:lang");
145
- document.body.removeAttribute("lang");
143
+ doc.documentElement.lang = bodyLang;
144
+ doc.documentElement.setAttribute("xml:lang", bodyLang);
145
+ doc.body.removeAttribute("xml:lang");
146
+ doc.body.removeAttribute("lang");
146
147
  } else if (!rootLang) {
147
- document.documentElement.lang = primaryLanguage;
148
- document.documentElement.setAttribute("xml:lang", primaryLanguage);
148
+ doc.documentElement.lang = primaryLanguage;
149
+ doc.documentElement.setAttribute("xml:lang", primaryLanguage);
149
150
  }
150
151
  } else if (
151
152
  mediaType === MediaType.HTML &&
152
- !document.documentElement.lang
153
+ !doc.documentElement.lang
153
154
  ) {
154
- document.documentElement.lang = primaryLanguage;
155
+ doc.documentElement.lang = primaryLanguage;
155
156
  }
156
157
  }
157
158
 
158
- // We need to ensure that dir is set on the root element if rtl
159
- // Since body can bubble up, we also need to check it’s not here.
160
- // https://github.com/readium/readium-css/blob/develop/docs/CSS03-injection_and_pagination.md#be-cautious-the-direction-propagates
161
-
162
- // TODO: ReadiumCSS stylesheets are injected as LTR/default no matter what so disabled ATM
163
- /* if (
164
- !document.documentElement.dir &&
165
- !document.body.dir &&
166
- this.pub.metadata.effectiveReadingProgression === ReadingProgression.rtl
159
+ // Set dir="rtl" on the root element for RTL publications if the author
160
+ // has not already declared a direction on html or body.
161
+ // See: https://github.com/readium/readium-css/blob/develop/docs/CSS03-injection_and_pagination.md#be-cautious-the-direction-propagates
162
+ // CJK modes must NEVER receive a dir attribute — writing-mode handles
163
+ // directionality visually, and dir="rtl" would break vertical layout.
164
+ const scriptMode = getScriptMode(this.pub.metadata);
165
+ if (scriptMode === "rtl" &&
166
+ !doc.documentElement.dir &&
167
+ !doc.body.dir
167
168
  ) {
168
- document.documentElement.dir = this.pub.metadata.effectiveReadingProgression;
169
- } */
169
+ doc.documentElement.dir = ReadingProgression.rtl;
170
+ }
170
171
 
171
172
  if (base !== undefined) {
172
173
  // Set all URL bases. Very convenient!
@@ -173,6 +173,10 @@ export class FrameManager {
173
173
  return this.frame.getBoundingClientRect();
174
174
  }
175
175
 
176
+ get isDestroyed() {
177
+ return this.destroyed;
178
+ }
179
+
176
180
  get window() {
177
181
  if(this.destroyed || !this.frame.contentWindow) throw Error("Trying to use frame window when it doesn't exist");
178
182
  return this.frame.contentWindow;
@@ -633,7 +633,11 @@ export class FXLFramePoolManager {
633
633
  progressions: new Map(),
634
634
  positions: null
635
635
  };
636
- const currentSpread = this.spreader.currentSpread(this.currentSlide, this.perPage);
636
+ // Mirror currentFrames: for single-page mode use currentSlide directly,
637
+ // otherwise use the spreader (which indexes by spread, not by item).
638
+ const currentSpread = this.perPage < 2
639
+ ? [this.pub.readingOrder.items[this.currentSlide]]
640
+ : this.spreader.currentSpread(this.currentSlide, this.perPage);
637
641
  currentSpread.forEach(link => {
638
642
  viewport.readingOrder.push(link.href);
639
643
  viewport.progressions.set(link.href, { start: 0, end: 1 }); // FXL always uses [0,1] progression
@@ -0,0 +1,45 @@
1
+ import { Metadata, ReadingProgression } from "@readium/shared";
2
+
3
+ export type ScriptMode = 'ltr' | 'rtl' | 'cjk-horizontal' | 'cjk-vertical';
4
+
5
+ /**
6
+ * Derives the script mode from publication metadata.
7
+ *
8
+ * Rules:
9
+ * - Only the first language in the array is used. A Latin book containing
10
+ * some Japanese is still Latin.
11
+ * - For CJK (zh/ja/ko): both language AND explicit reading progression are
12
+ * required. CJK vertical = explicit rtl + CJK language. CJK horizontal =
13
+ * CJK language with ltr or unset progression.
14
+ * - For RTL (ar/fa/he): language wins. If the primary language is a RTL
15
+ * script, RTL mode is applied regardless of the explicit progression
16
+ * direction declared in the OPF.
17
+ */
18
+ export function getScriptMode(metadata: Metadata): ScriptMode {
19
+ const primaryLang = metadata.languages?.[0]?.toLowerCase();
20
+ // Use explicit readingProgression only — effectiveReadingProgression
21
+ // auto-detects from language, which would create circular logic here.
22
+ const progression = metadata.readingProgression;
23
+
24
+ if (primaryLang) {
25
+ const isCJK = primaryLang.startsWith('zh') ||
26
+ primaryLang.startsWith('ja') ||
27
+ primaryLang.startsWith('ko');
28
+ if (isCJK) {
29
+ // Vertical requires explicit rtl progression. If progression
30
+ // conflicts (e.g. ltr) we fall back to horizontal.
31
+ return progression === ReadingProgression.rtl
32
+ ? 'cjk-vertical'
33
+ : 'cjk-horizontal';
34
+ }
35
+
36
+ // RTL: language is authoritative. ar/fa/he → rtl regardless of
37
+ // what the OPF says about page-progression-direction.
38
+ const isRTLScript = primaryLang.startsWith('ar') ||
39
+ primaryLang.startsWith('fa') ||
40
+ primaryLang.startsWith('he');
41
+ if (isRTLScript) return 'rtl';
42
+ }
43
+
44
+ return 'ltr';
45
+ }
package/src/epub/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./frame/index.ts";
3
3
  export * from "./fxl/index.ts";
4
4
  export * from "./preferences/index.ts";
5
5
  export * from "./css/index.ts";
6
+ export { getScriptMode, type ScriptMode } from "./helpers/scriptMode.ts";
@@ -1,19 +1,18 @@
1
1
  import { IInjectableRule, IInjectable } from "../injection/Injectable.ts";
2
2
  import { stripJS, stripCSS } from "../helpers/minify.ts";
3
3
  import { Metadata, Layout, Link } from "@readium/shared";
4
-
5
- import readiumCSSAfter from "@readium/css/css/dist/ReadiumCSS-after.css?raw";
6
- import readiumCSSBefore from "@readium/css/css/dist/ReadiumCSS-before.css?raw";
7
- import readiumCSSDefault from "@readium/css/css/dist/ReadiumCSS-default.css?raw";
4
+ import { getScriptMode } from "../epub/helpers/scriptMode.ts";
8
5
 
9
6
  import cssSelectorGeneratorContent from "../dom/_readium_cssSelectorGenerator.js?raw";
10
7
  import executionPreventionContent from "../dom/_readium_executionPrevention.js?raw";
11
8
  import onloadProxyContent from "../dom/_readium_executionCleanup.js?raw";
12
9
 
13
10
  /**
14
- * Creates injectable rules for EPUB content documents
11
+ * Creates injectable rules for EPUB content documents.
12
+ * Async so that script-specific Readium CSS stylesheets can be imported
13
+ * dynamically — only the variant that is actually needed is bundled.
15
14
  */
16
- export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Link[]): IInjectableRule[] {
15
+ export async function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Link[]): Promise<IInjectableRule[]> {
17
16
  const isFixedLayout = metadata.effectiveLayout === Layout.fixed;
18
17
 
19
18
  const htmlHrefs = readingOrderItems
@@ -57,12 +56,66 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
57
56
 
58
57
  // Only add Readium CSS for reflowable documents
59
58
  if (!isFixedLayout) {
59
+ const scriptMode = getScriptMode(metadata);
60
+
61
+ // Dynamically import only the CSS variant we need
62
+ let cssBeforeRaw: string;
63
+ let cssDefaultRaw: string;
64
+ let cssAfterRaw: string;
65
+
66
+ switch (scriptMode) {
67
+ case 'rtl': {
68
+ const [before, def, after] = await Promise.all([
69
+ import("@readium/css/css/dist/rtl/ReadiumCSS-before.css?raw"),
70
+ import("@readium/css/css/dist/rtl/ReadiumCSS-default.css?raw"),
71
+ import("@readium/css/css/dist/rtl/ReadiumCSS-after.css?raw"),
72
+ ]);
73
+ cssBeforeRaw = before.default;
74
+ cssDefaultRaw = def.default;
75
+ cssAfterRaw = after.default;
76
+ break;
77
+ }
78
+ case 'cjk-horizontal': {
79
+ const [before, def, after] = await Promise.all([
80
+ import("@readium/css/css/dist/cjk-horizontal/ReadiumCSS-before.css?raw"),
81
+ import("@readium/css/css/dist/cjk-horizontal/ReadiumCSS-default.css?raw"),
82
+ import("@readium/css/css/dist/cjk-horizontal/ReadiumCSS-after.css?raw"),
83
+ ]);
84
+ cssBeforeRaw = before.default;
85
+ cssDefaultRaw = def.default;
86
+ cssAfterRaw = after.default;
87
+ break;
88
+ }
89
+ case 'cjk-vertical': {
90
+ const [before, def, after] = await Promise.all([
91
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-before.css?raw"),
92
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-default.css?raw"),
93
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-after.css?raw"),
94
+ ]);
95
+ cssBeforeRaw = before.default;
96
+ cssDefaultRaw = def.default;
97
+ cssAfterRaw = after.default;
98
+ break;
99
+ }
100
+ default: {
101
+ const [before, def, after] = await Promise.all([
102
+ import("@readium/css/css/dist/ReadiumCSS-before.css?raw"),
103
+ import("@readium/css/css/dist/ReadiumCSS-default.css?raw"),
104
+ import("@readium/css/css/dist/ReadiumCSS-after.css?raw"),
105
+ ]);
106
+ cssBeforeRaw = before.default;
107
+ cssDefaultRaw = def.default;
108
+ cssAfterRaw = after.default;
109
+ break;
110
+ }
111
+ }
112
+
60
113
  // Readium CSS Before - prepended for reflowable
61
114
  prependInjectables.unshift({
62
115
  id: "readium-css-before",
63
116
  as: "link",
64
117
  target: "head",
65
- blob: new Blob([stripCSS(readiumCSSBefore)], { type: "text/css" }),
118
+ blob: new Blob([stripCSS(cssBeforeRaw)], { type: "text/css" }),
66
119
  rel: "stylesheet"
67
120
  });
68
121
 
@@ -73,7 +126,7 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
73
126
  id: "readium-css-default",
74
127
  as: "link",
75
128
  target: "head",
76
- blob: new Blob([stripCSS(readiumCSSDefault)], { type: "text/css" }),
129
+ blob: new Blob([stripCSS(cssDefaultRaw)], { type: "text/css" }),
77
130
  rel: "stylesheet",
78
131
  condition: (doc: Document) => !(doc.querySelector("link[rel='stylesheet']") || doc.querySelector("style") || doc.querySelector("[style]:not([style=''])"))
79
132
  },
@@ -82,10 +135,26 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
82
135
  id: "readium-css-after",
83
136
  as: "link",
84
137
  target: "head",
85
- blob: new Blob([stripCSS(readiumCSSAfter)], { type: "text/css" }),
138
+ blob: new Blob([stripCSS(cssAfterRaw)], { type: "text/css" }),
86
139
  rel: "stylesheet"
87
140
  }
88
141
  );
142
+
143
+ // EBPAJ fonts polyfill — CJK only, when EBPAJ metadata is present
144
+ if (scriptMode === 'cjk-horizontal' || scriptMode === 'cjk-vertical') {
145
+ const isEBPAJ = metadata.description === "ebpaj-guide-1.0" ||
146
+ metadata.otherMetadata?.["ebpaj:guide-version"] !== undefined;
147
+ if (isEBPAJ) {
148
+ const { default: ebpajRaw } = await import("@readium/css/css/dist/ReadiumCSS-ebpaj_fonts_patch.css?raw");
149
+ appendInjectables.push({
150
+ id: "readium-css-ebpaj",
151
+ as: "link",
152
+ target: "head",
153
+ blob: new Blob([stripCSS(ebpajRaw)], { type: "text/css" }),
154
+ rel: "stylesheet"
155
+ });
156
+ }
157
+ }
89
158
  }
90
159
 
91
160
  return [
@@ -1,4 +1,4 @@
1
- import { IInjectableRule, IInjectable } from "../injection/Injectable";
1
+ import { IInjectableRule, IInjectable } from "../injection/Injectable.ts";
2
2
  import { stripJS, stripCSS } from "../helpers/minify.ts";
3
3
  import { Link } from "@readium/shared";
4
4
 
@@ -2,8 +2,7 @@ import { Layout, Link, Locator, Publication, ReadingProgression } from "@readium
2
2
  import { Configurable, ConfigurableSettings, VisualNavigator, VisualNavigatorViewport } from "../index.ts";
3
3
  import { FramePoolManager } from "./frame/FramePoolManager.ts";
4
4
  import { FXLFramePoolManager } from "./fxl/FXLFramePoolManager.ts";
5
- import { CommsEventKey, ContextMenuEvent, KeyboardEventData } from "@readium/navigator-html-injectables";
6
- import { BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
5
+ import { CommsEventKey, ContextMenuEvent, KeyboardEventData, BasicTextSelection, FrameClickEvent, SuspiciousActivityEvent } from "@readium/navigator-html-injectables";
7
6
  import { FXLFrameManager } from "./fxl/FXLFrameManager.ts";
8
7
  import { FrameManager } from "./frame/FrameManager.ts";
9
8
  import { IEpubPreferences, EpubPreferences } from "./preferences/EpubPreferences.ts";
@@ -50,7 +49,9 @@ export declare class EpubNavigator extends VisualNavigator implements Configurab
50
49
  private _settings;
51
50
  private _css;
52
51
  private _preferencesEditor;
53
- private readonly _injector;
52
+ private _injector;
53
+ private readonly _readiumRulesPromise;
54
+ private readonly _injectablesConfig;
54
55
  private readonly _contentProtection;
55
56
  private readonly _keyboardPeripherals;
56
57
  private readonly _navigatorProtector;
@@ -22,6 +22,7 @@ export declare class FrameManager {
22
22
  }): void;
23
23
  get iframe(): HTMLIFrameElement;
24
24
  get realSize(): DOMRect;
25
+ get isDestroyed(): boolean;
25
26
  get window(): Window;
26
27
  get atLeft(): boolean;
27
28
  get atRight(): boolean;
@@ -0,0 +1,16 @@
1
+ import { Metadata } from "@readium/shared";
2
+ export type ScriptMode = 'ltr' | 'rtl' | 'cjk-horizontal' | 'cjk-vertical';
3
+ /**
4
+ * Derives the script mode from publication metadata.
5
+ *
6
+ * Rules:
7
+ * - Only the first language in the array is used. A Latin book containing
8
+ * some Japanese is still Latin.
9
+ * - For CJK (zh/ja/ko): both language AND explicit reading progression are
10
+ * required. CJK vertical = explicit rtl + CJK language. CJK horizontal =
11
+ * CJK language with ltr or unset progression.
12
+ * - For RTL (ar/fa/he): language wins. If the primary language is a RTL
13
+ * script, RTL mode is applied regardless of the explicit progression
14
+ * direction declared in the OPF.
15
+ */
16
+ export declare function getScriptMode(metadata: Metadata): ScriptMode;
@@ -3,3 +3,4 @@ export * from "./frame/index.ts";
3
3
  export * from "./fxl/index.ts";
4
4
  export * from "./preferences/index.ts";
5
5
  export * from "./css/index.ts";
6
+ export { getScriptMode, type ScriptMode } from "./helpers/scriptMode.ts";
@@ -1,6 +1,8 @@
1
1
  import { IInjectableRule } from "../injection/Injectable.ts";
2
2
  import { Metadata, Link } from "@readium/shared";
3
3
  /**
4
- * Creates injectable rules for EPUB content documents
4
+ * Creates injectable rules for EPUB content documents.
5
+ * Async so that script-specific Readium CSS stylesheets can be imported
6
+ * dynamically — only the variant that is actually needed is bundled.
5
7
  */
6
- export declare function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Link[]): IInjectableRule[];
8
+ export declare function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Link[]): Promise<IInjectableRule[]>;
@@ -1,4 +1,4 @@
1
- import { IInjectableRule } from "../injection/Injectable";
1
+ import { IInjectableRule } from "../injection/Injectable.ts";
2
2
  import { Link } from "@readium/shared";
3
3
  /**
4
4
  * Creates injectable rules for WebPub content documents