@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.
- package/dist/ReadiumCSS-after-B_e3a-PY.js +592 -0
- package/dist/ReadiumCSS-after-C-T_0paD.js +530 -0
- package/dist/ReadiumCSS-after-lr-n3fz2.js +475 -0
- package/dist/ReadiumCSS-after-mXeKKPap.js +490 -0
- package/dist/ReadiumCSS-before-Bjd3POej.js +426 -0
- package/dist/ReadiumCSS-before-CfXPAGaQ.js +425 -0
- package/dist/ReadiumCSS-before-CrNWvuyE.js +425 -0
- package/dist/ReadiumCSS-before-KVen5ceo.js +425 -0
- package/dist/ReadiumCSS-default-BKAG5pGU.js +162 -0
- package/dist/ReadiumCSS-default-C63bYOYF.js +183 -0
- package/dist/ReadiumCSS-default-CclvbeNC.js +162 -0
- package/dist/ReadiumCSS-default-DnlgDaBu.js +180 -0
- package/dist/ReadiumCSS-ebpaj_fonts_patch-Dt2XliTg.js +82 -0
- package/dist/index.js +2246 -3159
- package/dist/index.umd.cjs +4432 -1083
- package/package.json +1 -1
- package/src/audio/index.ts +1 -1
- package/src/epub/EpubNavigator.ts +72 -32
- package/src/epub/css/ReadiumCSS.ts +11 -0
- package/src/epub/frame/FrameBlobBuilder.ts +23 -22
- package/src/epub/frame/FrameManager.ts +4 -0
- package/src/epub/fxl/FXLFramePoolManager.ts +5 -1
- package/src/epub/helpers/scriptMode.ts +52 -0
- package/src/epub/index.ts +1 -0
- package/src/injection/epubInjectables.ts +82 -9
- package/src/injection/webpubInjectables.ts +1 -1
- package/src/webpub/WebPubNavigator.ts +9 -4
- package/types/src/epub/EpubNavigator.d.ts +4 -3
- package/types/src/epub/css/ReadiumCSS.d.ts +2 -0
- package/types/src/epub/frame/FrameManager.d.ts +1 -0
- package/types/src/epub/helpers/scriptMode.d.ts +19 -0
- package/types/src/epub/index.d.ts +1 -0
- package/types/src/injection/epubInjectables.d.ts +4 -2
- package/types/src/injection/webpubInjectables.d.ts +1 -1
package/package.json
CHANGED
package/src/audio/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.
|
|
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 =
|
|
570
|
+
modules = all.filter((m) => m !== "column_snapper" && m !== "cjk_vertical_snapper");
|
|
531
571
|
else
|
|
532
|
-
modules =
|
|
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
|
|
659
|
-
//
|
|
660
|
-
potentialPositions.
|
|
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
|
-
|
|
663
|
-
|
|
702
|
+
return pr <= fromProgression.start;
|
|
703
|
+
});
|
|
664
704
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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 =
|
|
140
|
-
const bodyLang =
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
148
|
+
doc.documentElement.lang = primaryLanguage;
|
|
149
|
+
doc.documentElement.setAttribute("xml:lang", primaryLanguage);
|
|
149
150
|
}
|
|
150
151
|
} else if (
|
|
151
152
|
mediaType === MediaType.HTML &&
|
|
152
|
-
!
|
|
153
|
+
!doc.documentElement.lang
|
|
153
154
|
) {
|
|
154
|
-
|
|
155
|
+
doc.documentElement.lang = primaryLanguage;
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// https://github.com/readium/readium-css/blob/develop/docs/CSS03-injection_and_pagination.md#be-cautious-the-direction-propagates
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
!
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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(
|
|
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(
|
|
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(
|
|
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 [
|
|
@@ -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,
|
|
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
|
-
|
|
371
|
+
const modules = WebPubModules.slice();
|
|
371
372
|
|
|
372
|
-
|
|
373
|
-
|
|
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
|
|
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);
|