@readium/navigator 2.4.0 → 2.5.0-beta.2

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 (34) 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 +2246 -3159
  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 +72 -32
  19. package/src/epub/css/ReadiumCSS.ts +11 -0
  20. package/src/epub/frame/FrameBlobBuilder.ts +23 -22
  21. package/src/epub/frame/FrameManager.ts +4 -0
  22. package/src/epub/fxl/FXLFramePoolManager.ts +5 -1
  23. package/src/epub/helpers/scriptMode.ts +52 -0
  24. package/src/epub/index.ts +1 -0
  25. package/src/injection/epubInjectables.ts +82 -9
  26. package/src/injection/webpubInjectables.ts +1 -1
  27. package/src/webpub/WebPubNavigator.ts +9 -4
  28. package/types/src/epub/EpubNavigator.d.ts +4 -3
  29. package/types/src/epub/css/ReadiumCSS.d.ts +2 -0
  30. package/types/src/epub/frame/FrameManager.d.ts +1 -0
  31. package/types/src/epub/helpers/scriptMode.d.ts +19 -0
  32. package/types/src/epub/index.d.ts +1 -0
  33. package/types/src/injection/epubInjectables.d.ts +4 -2
  34. 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.2",
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,16 @@ 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 isMongolianVertical = scriptMode === 'mongolian-vertical';
119
+ const isVertical = isCJKVertical || isMongolianVertical;
120
+ const isCJK = isCJKHorizontal || isCJKVertical;
111
121
  this._css = new ReadiumCSS({
112
- rsProperties: new RSProperties({}),
122
+ rsProperties: new RSProperties({ noVerticalPagination: isVertical || undefined }),
113
123
  userProperties: new UserProperties({}),
114
124
  lineLengths: new LineLengths({
115
125
  optimalChars: this._settings.optimalLineLength,
@@ -121,23 +131,24 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
121
131
  fontFace: this._settings.fontFamily,
122
132
  letterSpacing: this._settings.letterSpacing,
123
133
  wordSpacing: this._settings.wordSpacing,
134
+ isCJK: isCJK,
124
135
  // sample: this.pub.metadata.description
125
136
  }),
126
137
  container: container,
127
- constraint: this._settings.constraint
138
+ constraint: this._settings.constraint,
139
+ isCJKVertical: isVertical
128
140
  });
129
141
 
130
142
  this._layout = EpubNavigator.determineLayout(pub, !!this._settings.scroll);
131
143
  this.currentProgression = pub.metadata.effectiveReadingProgression;
132
144
 
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
- });
145
+ // Store user injectables config; Injector is created in load() once
146
+ // the async CSS rules promise has resolved.
147
+ this._injectablesConfig = configuration.injectables || { rules: [], allowedDomains: [] };
148
+ // Start loading Readium CSS rules asynchronously. The promise is
149
+ // awaited in load() before the Injector is created, ensuring the
150
+ // correct script-mode stylesheets are ready before the first frame.
151
+ this._readiumRulesPromise = createReadiumEpubRules(pub.metadata, pub.readingOrder.items);
141
152
 
142
153
  this._contentProtection = configuration.contentProtection || {};
143
154
 
@@ -201,6 +212,12 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
201
212
  if (layout === Layout.scrolled)
202
213
  return Layout.scrolled;
203
214
 
215
+ // CJK/Mongolian vertical writing: force scroll mode so the
216
+ // CJKVerticalSnapper is used and column-based pagination doesn't interfere.
217
+ const sm = getScriptMode(pub.metadata);
218
+ if (sm === 'cjk-vertical' || sm === 'mongolian-vertical')
219
+ return Layout.scrolled;
220
+
204
221
  if (layout === Layout.reflowable && scroll)
205
222
  return Layout.scrolled;
206
223
 
@@ -210,6 +227,17 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
210
227
  public async load() {
211
228
  if (!this.positions?.length)
212
229
  this.positions = await this.pub.positionsFromManifest();
230
+
231
+ // Build Injector now that async CSS loading has had time to resolve.
232
+ // (Started in the constructor, so this typically resolves immediately.)
233
+ if (!this._injector) {
234
+ const readiumRules = await this._readiumRulesPromise;
235
+ this._injector = new Injector({
236
+ rules: [...readiumRules, ...this._injectablesConfig.rules],
237
+ allowedDomains: this._injectablesConfig.allowedDomains
238
+ });
239
+ }
240
+
213
241
  if(this._layout === Layout.fixed) {
214
242
  this.framePool = new FXLFramePoolManager(
215
243
  this.container,
@@ -312,6 +340,10 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
312
340
  }
313
341
 
314
342
  private async commitCSS(css: ReadiumCSS) {
343
+ // framePool is only available after load() — guard against early calls
344
+ // from the ResizeObserver which is registered in the constructor.
345
+ if (!this.framePool) return;
346
+
315
347
  // Since we’re updating the CSS properties in injectables by removing
316
348
  // the existing properties that are not inside this object first,
317
349
  // then adding all from it, we don’t compare the previous properties here
@@ -341,6 +373,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
341
373
 
342
374
  if (this._layout === Layout.fixed) {
343
375
  this.container.style.width = `${ getContentWidth(parentEl) - this._settings.constraint }px`;
376
+ if (!this.framePool) return;
344
377
  (this.framePool as FXLFramePoolManager).resizeHandler();
345
378
  } else {
346
379
  // for reflow ReadiumCSS gets the width from columns + line-lengths
@@ -371,7 +404,7 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
371
404
  * TODO remove when settings management is incorporated
372
405
  */
373
406
  public get _cframes(): (FXLFrameManager | FrameManager | undefined)[] {
374
- return this.framePool.currentFrames;
407
+ return (this.framePool?.currentFrames ?? []).filter(f => !(f instanceof FrameManager && f.isDestroyed));
375
408
  }
376
409
 
377
410
  /**
@@ -524,12 +557,19 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
524
557
  if(this._layout === Layout.fixed) {
525
558
  return modules.filter((m) => FXLModules.includes(m));
526
559
  } else modules = modules.filter((m) => ReflowableModules.includes(m));
560
+
561
+ // CJK/Mongolian vertical: uses the X-axis snapper, never column or scroll snappers
562
+ const mode = getScriptMode(this.pub.metadata);
563
+ if (mode === 'cjk-vertical' || mode === 'mongolian-vertical') {
564
+ return modules.filter((m) => m !== "column_snapper" && m !== "scroll_snapper");
565
+ }
527
566
 
528
567
  // Horizontal vs. Vertical reading
568
+ const all = modules as ModuleName[];
529
569
  if (this._layout === Layout.scrolled)
530
- modules = modules.filter((m) => m !== "column_snapper");
570
+ modules = all.filter((m) => m !== "column_snapper" && m !== "cjk_vertical_snapper");
531
571
  else
532
- modules = modules.filter((m) => m !== "scroll_snapper");
572
+ modules = all.filter((m) => m !== "scroll_snapper" && m !== "cjk_vertical_snapper");
533
573
 
534
574
  return modules;
535
575
  }
@@ -655,21 +695,19 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
655
695
  let first = this.currentLocation;
656
696
  let last = undefined;
657
697
 
658
- // Find the last locator with a progression that's
659
- // smaller than or equal to the requested progression.
660
- potentialPositions.some((p, idx) => {
698
+ // Find the last locator whose progression is <= fromProgression.start.
699
+ // potentialPositions is ordered by progression ascending (0 → 1).
700
+ const idx = potentialPositions.findLastIndex((p) => {
661
701
  const pr = p.locations.progression ?? 0;
662
- if (fromProgression.start <= pr) {
663
- first = p;
702
+ return pr <= fromProgression.start;
703
+ });
664
704
 
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);
705
+ if (idx !== -1) {
706
+ first = potentialPositions[idx];
707
+ const nextPositions = potentialPositions.slice(idx + 1);
708
+ last = this.findLastPositionInProgressionRange(nextPositions, fromProgression);
709
+ }
668
710
 
669
- return true;
670
- }
671
- else return false;
672
- });
673
711
  return { first: first, last: last }
674
712
  }
675
713
 
@@ -749,9 +787,11 @@ export class EpubNavigator extends VisualNavigator implements Configurable<Confi
749
787
  }
750
788
 
751
789
  get viewport(): VisualNavigatorViewport {
752
- return this._layout === Layout.fixed
753
- ? (this.framePool as FXLFramePoolManager).viewport
754
- : this.reflowViewport;
790
+ if (this._layout === Layout.fixed) {
791
+ if (!this.framePool) return { readingOrder: [], progressions: new Map(), positions: null };
792
+ return (this.framePool as FXLFramePoolManager).viewport;
793
+ }
794
+ return this.reflowViewport;
755
795
  }
756
796
 
757
797
  get isScrollStart(): boolean {
@@ -9,6 +9,7 @@ export interface IReadiumCSS {
9
9
  lineLengths: LineLengths;
10
10
  container: HTMLElement;
11
11
  constraint: number;
12
+ isCJKVertical?: boolean;
12
13
  }
13
14
 
14
15
  export class ReadiumCSS {
@@ -18,6 +19,7 @@ export class ReadiumCSS {
18
19
  container: HTMLElement;
19
20
  containerParent: HTMLElement;
20
21
  constraint: number;
22
+ private readonly isCJKVertical: boolean;
21
23
  private cachedColCount: number | null | undefined;
22
24
  private effectiveContainerWidth: number;
23
25
 
@@ -28,6 +30,7 @@ export class ReadiumCSS {
28
30
  this.container = props.container;
29
31
  this.containerParent = props.container.parentElement || document.documentElement;
30
32
  this.constraint = props.constraint;
33
+ this.isCJKVertical = props.isCJKVertical ?? false;
31
34
  this.cachedColCount = props.userProperties.colCount;
32
35
  this.effectiveContainerWidth = getContentWidth(this.containerParent);
33
36
  }
@@ -131,6 +134,14 @@ export class ReadiumCSS {
131
134
  }
132
135
 
133
136
  private updateLayout(scale: number | null, ignoreCompensation: boolean | null, scroll: boolean | null, colCount?: number | null) {
137
+ // CJK vertical text flows along the block axis (height); the inline axis
138
+ // (width) must not be constrained by line-length at all — use the full
139
+ // parent width minus the known constraint.
140
+ if (this.isCJKVertical) {
141
+ const w = Math.round(getContentWidth(this.containerParent) - this.constraint);
142
+ return { colCount: undefined, effectiveContainerWidth: w, effectiveLineLength: w };
143
+ }
144
+
134
145
  const isScroll = scroll ?? this.userProperties.view === "scroll";
135
146
 
136
147
  if (isScroll) {
@@ -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,52 @@
1
+ import { Metadata, ReadingProgression } from "@readium/shared";
2
+
3
+ export type ScriptMode = 'ltr' | 'rtl' | 'cjk-horizontal' | 'cjk-vertical' | 'mongolian-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
+ * - For Mongolian (mn): Traditional Mongolian script (mn-Mong) uses
18
+ * writing-mode: vertical-lr. Cyrillic Mongolian (mn-Cyrl) is standard LTR.
19
+ * An explicit script subtag is required; bare `mn` defaults to LTR.
20
+ */
21
+ export function getScriptMode(metadata: Metadata): ScriptMode {
22
+ const primaryLang = metadata.languages?.[0]?.toLowerCase();
23
+ // Use explicit readingProgression only — effectiveReadingProgression
24
+ // auto-detects from language, which would create circular logic here.
25
+ const progression = metadata.readingProgression;
26
+
27
+ if (primaryLang) {
28
+ const isCJK = primaryLang.startsWith('zh') ||
29
+ primaryLang.startsWith('ja') ||
30
+ primaryLang.startsWith('ko');
31
+ if (isCJK) {
32
+ // Vertical requires explicit rtl progression. If progression
33
+ // conflicts (e.g. ltr) we fall back to horizontal.
34
+ return progression === ReadingProgression.rtl
35
+ ? 'cjk-vertical'
36
+ : 'cjk-horizontal';
37
+ }
38
+
39
+ // Traditional Mongolian script (mn-Mong): writing-mode vertical-lr.
40
+ // Requires an explicit Mong script subtag; mn-Cyrl and bare mn stay LTR.
41
+ if (primaryLang.startsWith('mn-mong')) return 'mongolian-vertical';
42
+
43
+ // RTL: language is authoritative. ar/fa/he → rtl regardless of
44
+ // what the OPF says about page-progression-direction.
45
+ const isRTLScript = primaryLang.startsWith('ar') ||
46
+ primaryLang.startsWith('fa') ||
47
+ primaryLang.startsWith('he');
48
+ if (isRTLScript) return 'rtl';
49
+ }
50
+
51
+ return 'ltr';
52
+ }
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,70 @@ 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
+ // Traditional Mongolian (vertical-lr) uses the same Readium CSS
91
+ // layout as CJK vertical-rl — it is an outlier handled by the
92
+ // same stylesheet set per the Readium CSS spec.
93
+ case 'mongolian-vertical': {
94
+ const [before, def, after] = await Promise.all([
95
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-before.css?raw"),
96
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-default.css?raw"),
97
+ import("@readium/css/css/dist/cjk-vertical/ReadiumCSS-after.css?raw"),
98
+ ]);
99
+ cssBeforeRaw = before.default;
100
+ cssDefaultRaw = def.default;
101
+ cssAfterRaw = after.default;
102
+ break;
103
+ }
104
+ default: {
105
+ const [before, def, after] = await Promise.all([
106
+ import("@readium/css/css/dist/ReadiumCSS-before.css?raw"),
107
+ import("@readium/css/css/dist/ReadiumCSS-default.css?raw"),
108
+ import("@readium/css/css/dist/ReadiumCSS-after.css?raw"),
109
+ ]);
110
+ cssBeforeRaw = before.default;
111
+ cssDefaultRaw = def.default;
112
+ cssAfterRaw = after.default;
113
+ break;
114
+ }
115
+ }
116
+
60
117
  // Readium CSS Before - prepended for reflowable
61
118
  prependInjectables.unshift({
62
119
  id: "readium-css-before",
63
120
  as: "link",
64
121
  target: "head",
65
- blob: new Blob([stripCSS(readiumCSSBefore)], { type: "text/css" }),
122
+ blob: new Blob([stripCSS(cssBeforeRaw)], { type: "text/css" }),
66
123
  rel: "stylesheet"
67
124
  });
68
125
 
@@ -73,7 +130,7 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
73
130
  id: "readium-css-default",
74
131
  as: "link",
75
132
  target: "head",
76
- blob: new Blob([stripCSS(readiumCSSDefault)], { type: "text/css" }),
133
+ blob: new Blob([stripCSS(cssDefaultRaw)], { type: "text/css" }),
77
134
  rel: "stylesheet",
78
135
  condition: (doc: Document) => !(doc.querySelector("link[rel='stylesheet']") || doc.querySelector("style") || doc.querySelector("[style]:not([style=''])"))
79
136
  },
@@ -82,10 +139,26 @@ export function createReadiumEpubRules(metadata: Metadata, readingOrderItems: Li
82
139
  id: "readium-css-after",
83
140
  as: "link",
84
141
  target: "head",
85
- blob: new Blob([stripCSS(readiumCSSAfter)], { type: "text/css" }),
142
+ blob: new Blob([stripCSS(cssAfterRaw)], { type: "text/css" }),
86
143
  rel: "stylesheet"
87
144
  }
88
145
  );
146
+
147
+ // EBPAJ fonts polyfill — CJK only, when EBPAJ metadata is present
148
+ if (scriptMode === 'cjk-horizontal' || scriptMode === 'cjk-vertical') {
149
+ const isEBPAJ = metadata.description === "ebpaj-guide-1.0" ||
150
+ metadata.otherMetadata?.["ebpaj:guide-version"] !== undefined;
151
+ if (isEBPAJ) {
152
+ const { default: ebpajRaw } = await import("@readium/css/css/dist/ReadiumCSS-ebpaj_fonts_patch.css?raw");
153
+ appendInjectables.push({
154
+ id: "readium-css-ebpaj",
155
+ as: "link",
156
+ target: "head",
157
+ blob: new Blob([stripCSS(ebpajRaw)], { type: "text/css" }),
158
+ rel: "stylesheet"
159
+ });
160
+ }
161
+ }
89
162
  }
90
163
 
91
164
  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,11 +2,12 @@ import { Feature, Link, Locator, Publication, ReadingProgression, LocatorLocatio
2
2
  import { VisualNavigator, VisualNavigatorViewport, ProgressionRange } from "../Navigator.ts";
3
3
  import { Configurable } from "../preferences/Configurable.ts";
4
4
  import { WebPubFramePoolManager } from "./WebPubFramePoolManager.ts";
5
- import { BasicTextSelection, CommsEventKey, ContextMenuEvent, FrameClickEvent, KeyboardEventData, ModuleLibrary, ModuleName, SuspiciousActivityEvent, WebPubModules } from "@readium/navigator-html-injectables";
5
+ import { BasicTextSelection, CommsEventKey, ContextMenuEvent, FrameClickEvent, KeyboardEventData, ModuleName, SuspiciousActivityEvent, WebPubModules } from "@readium/navigator-html-injectables";
6
6
  import * as path from "path-browserify";
7
7
  import { WebPubFrameManager } from "./WebPubFrameManager.ts";
8
8
 
9
9
  import { ManagerEventKey } from "../epub/EpubNavigator.ts";
10
+ import { getScriptMode } from "../epub/helpers/scriptMode.ts";
10
11
  import { WebPubCSS } from "./css/WebPubCSS.ts";
11
12
  import { WebUserProperties, WebRSProperties } from "./css/Properties.ts";
12
13
  import { IWebPubPreferences, WebPubPreferences } from "./preferences/WebPubPreferences.ts";
@@ -367,10 +368,14 @@ export class WebPubNavigator extends VisualNavigator implements Configurable<Web
367
368
  }
368
369
 
369
370
  private determineModules(): ModuleName[] {
370
- let modules = Array.from(ModuleLibrary.keys()) as ModuleName[];
371
+ const modules = WebPubModules.slice();
371
372
 
372
- // For WebPub, use the predefined WebPubModules array and filter
373
- return modules.filter((m) => WebPubModules.includes(m));
373
+ const mode = getScriptMode(this.pub.metadata);
374
+ if (mode === 'cjk-vertical' || mode === 'mongolian-vertical') {
375
+ return modules.map((m) => m === "webpub_snapper" ? "cjk_vertical_snapper" : m);
376
+ }
377
+
378
+ return modules;
374
379
  }
375
380
 
376
381
  private attachListener() {
@@ -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;
@@ -7,6 +7,7 @@ export interface IReadiumCSS {
7
7
  lineLengths: LineLengths;
8
8
  container: HTMLElement;
9
9
  constraint: number;
10
+ isCJKVertical?: boolean;
10
11
  }
11
12
  export declare class ReadiumCSS {
12
13
  rsProperties: RSProperties;
@@ -15,6 +16,7 @@ export declare class ReadiumCSS {
15
16
  container: HTMLElement;
16
17
  containerParent: HTMLElement;
17
18
  constraint: number;
19
+ private readonly isCJKVertical;
18
20
  private cachedColCount;
19
21
  private effectiveContainerWidth;
20
22
  constructor(props: IReadiumCSS);
@@ -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;