@readium/navigator 2.1.1 → 2.2.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 (52) hide show
  1. package/dist/index.js +3912 -2721
  2. package/dist/index.umd.cjs +286 -71
  3. package/package.json +1 -1
  4. package/src/css/Properties.ts +47 -0
  5. package/src/css/index.ts +1 -0
  6. package/src/epub/css/Properties.ts +10 -48
  7. package/src/epub/preferences/EpubDefaults.ts +1 -1
  8. package/src/epub/preferences/EpubPreferences.ts +1 -1
  9. package/src/epub/preferences/EpubPreferencesEditor.ts +30 -23
  10. package/src/index.ts +3 -1
  11. package/src/preferences/Types.ts +40 -0
  12. package/src/{epub/preferences → preferences}/guards.ts +5 -6
  13. package/src/preferences/index.ts +2 -1
  14. package/src/webpub/WebPubBlobBuilder.ts +167 -0
  15. package/src/webpub/WebPubFrameManager.ts +156 -0
  16. package/src/webpub/WebPubFramePoolManager.ts +221 -0
  17. package/src/webpub/WebPubNavigator.ts +494 -0
  18. package/src/webpub/css/Properties.ts +71 -0
  19. package/src/webpub/css/WebPubCSS.ts +42 -0
  20. package/src/webpub/css/WebPubStylesheet.ts +204 -0
  21. package/src/webpub/css/index.ts +3 -0
  22. package/src/webpub/index.ts +6 -0
  23. package/src/webpub/preferences/WebPubDefaults.ts +61 -0
  24. package/src/webpub/preferences/WebPubPreferences.ts +88 -0
  25. package/src/webpub/preferences/WebPubPreferencesEditor.ts +193 -0
  26. package/src/webpub/preferences/WebPubSettings.ts +88 -0
  27. package/src/webpub/preferences/index.ts +4 -0
  28. package/types/src/css/Properties.d.ts +20 -0
  29. package/types/src/css/index.d.ts +1 -0
  30. package/types/src/epub/css/Properties.d.ts +1 -21
  31. package/types/src/index.d.ts +2 -0
  32. package/types/src/preferences/Types.d.ts +8 -0
  33. package/types/src/preferences/guards.d.ts +9 -0
  34. package/types/src/preferences/index.d.ts +1 -0
  35. package/types/src/web/WebPubBlobBuilder.d.ts +10 -0
  36. package/types/src/web/WebPubFrameManager.d.ts +20 -0
  37. package/types/src/web/WebPubNavigator.d.ts +48 -0
  38. package/types/src/web/index.d.ts +3 -0
  39. package/types/src/webpub/WebPubBlobBuilder.d.ts +16 -0
  40. package/types/src/webpub/WebPubFrameManager.d.ts +24 -0
  41. package/types/src/webpub/WebPubFramePoolManager.d.ts +23 -0
  42. package/types/src/webpub/WebPubNavigator.d.ts +70 -0
  43. package/types/src/webpub/css/Properties.d.ts +36 -0
  44. package/types/src/webpub/css/WebPubCSS.d.ts +10 -0
  45. package/types/src/webpub/css/WebPubStylesheet.d.ts +1 -0
  46. package/types/src/webpub/css/index.d.ts +3 -0
  47. package/types/src/webpub/index.d.ts +6 -0
  48. package/types/src/webpub/preferences/WebPubDefaults.d.ts +32 -0
  49. package/types/src/webpub/preferences/WebPubPreferences.d.ts +36 -0
  50. package/types/src/webpub/preferences/WebPubPreferencesEditor.d.ts +27 -0
  51. package/types/src/webpub/preferences/WebPubSettings.d.ts +35 -0
  52. package/types/src/webpub/preferences/index.d.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@readium/navigator",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "description": "Next generation SDK for publications in Web Apps",
6
6
  "author": "readium",
@@ -0,0 +1,47 @@
1
+ export type BodyHyphens = "auto" | "none";
2
+ export type BoxSizing = "content-box" | "border-box";
3
+ export type FontOpticalSizing = "auto" | "none";
4
+ export type FontWidth = "ultra-condensed" | "extra-condensed" | "condensed" | "semi-condensed" | "normal" | "semi-expanded" | "expanded" | "extra-expanded" | "ultra-expanded" | number;
5
+ export type Ligatures = "common-ligatures" | "none";
6
+ export type TypeScale = 1 | 1.067 | 1.125 | 1.2 | 1.25 | 1.333 | 1.414 | 1.5 | 1.618;
7
+ export type View = "paged" | "scroll";
8
+
9
+ export abstract class Properties {
10
+ constructor() {}
11
+
12
+ protected toFlag(name: string) {
13
+ return `readium-${ name }-on`;
14
+ }
15
+
16
+ protected toUnitless(value: number) {
17
+ return value.toString();
18
+ }
19
+
20
+ protected toPercentage(value: number, ratio: boolean = false) {
21
+ if (ratio || value > 0 && value <= 1) {
22
+ return `${ Math.round(value * 100) }%`;
23
+ } else {
24
+ return `${ value }%`;
25
+ }
26
+ }
27
+
28
+ protected toVw(value: number) {
29
+ const percentage = Math.round(value * 100);
30
+ return `${ Math.min(percentage, 100) }vw`;
31
+ }
32
+
33
+ protected toVh(value: number) {
34
+ const percentage = Math.round(value * 100);
35
+ return `${ Math.min(percentage, 100) }vh`;
36
+ }
37
+
38
+ protected toPx(value: number) {
39
+ return `${ value }px`;
40
+ }
41
+
42
+ protected toRem(value: number) {
43
+ return `${ value }rem`;
44
+ }
45
+
46
+ abstract toCSSProperties(): { [key: string]: string };
47
+ }
@@ -0,0 +1 @@
1
+ export * from "./Properties";
@@ -1,52 +1,14 @@
1
1
  import { TextAlignment } from "../../preferences/Types";
2
-
3
- export type BodyHyphens = "auto" | "none";
4
- export type BoxSizing = "content-box" | "border-box";
5
- export type FontOpticalSizing = "auto" | "none";
6
- export type FontWidth = "ultra-condensed" | "extra-condensed" | "condensed" | "semi-condensed" | "normal" | "semi-expanded" | "expanded" | "extra-expanded" | "ultra-expanded" | number;
7
- export type Ligatures = "common-ligatures" | "none";
8
- export type TypeScale = 1 | 1.067 | 1.125 | 1.2 | 1.25 | 1.333 | 1.414 | 1.5 | 1.618;
9
- export type View = "paged" | "scroll";
10
-
11
- abstract class Properties {
12
- constructor() {}
13
-
14
- protected toFlag(name: string) {
15
- return `readium-${ name }-on`;
16
- }
17
-
18
- protected toUnitless(value: number) {
19
- return value.toString();
20
- }
21
-
22
- protected toPercentage(value: number, ratio: boolean = false) {
23
- if (ratio || value > 0 && value <= 1) {
24
- return `${ Math.round(value * 100) }%`;
25
- } else {
26
- return `${ value }%`;
27
- }
28
- }
29
-
30
- protected toVw(value: number) {
31
- const percentage = Math.round(value * 100);
32
- return `${ Math.min(percentage, 100) }vw`;
33
- }
34
-
35
- protected toVh(value: number) {
36
- const percentage = Math.round(value * 100);
37
- return `${ Math.min(percentage, 100) }vh`;
38
- }
39
-
40
- protected toPx(value: number) {
41
- return `${ value }px`;
42
- }
43
-
44
- protected toRem(value: number) {
45
- return `${ value }rem`;
46
- }
47
-
48
- abstract toCSSProperties(): { [key: string]: string };
49
- }
2
+ import {
3
+ BodyHyphens,
4
+ BoxSizing,
5
+ FontOpticalSizing,
6
+ FontWidth,
7
+ Ligatures,
8
+ Properties,
9
+ TypeScale,
10
+ View
11
+ } from "../../css/Properties";
50
12
 
51
13
  export interface IUserProperties {
52
14
  advancedSettings?: boolean | null;
@@ -15,7 +15,7 @@ import {
15
15
  ensureString,
16
16
  ensureValueInRange,
17
17
  withFallback
18
- } from "./guards";
18
+ } from "../../preferences/guards";
19
19
 
20
20
  import { sMLWithRequest } from "../../helpers";
21
21
 
@@ -14,7 +14,7 @@ import {
14
14
  ensureNonNegative,
15
15
  ensureString,
16
16
  ensureValueInRange
17
- } from "./guards";
17
+ } from "../../preferences/guards";
18
18
 
19
19
  export interface IEpubPreferences {
20
20
  backgroundColor?: string | null,
@@ -5,9 +5,16 @@ import { EpubSettings } from "./EpubSettings";
5
5
  import { BooleanPreference, EnumPreference, Preference, RangePreference } from "../../preferences/Preference";
6
6
  import {
7
7
  TextAlignment,
8
+ filterRangeConfig,
8
9
  fontSizeRangeConfig,
9
10
  fontWeightRangeConfig,
10
- fontWidthRangeConfig
11
+ fontWidthRangeConfig,
12
+ letterSpacingRangeConfig,
13
+ lineHeightRangeConfig,
14
+ lineLengthRangeConfig,
15
+ paragraphIndentRangeConfig,
16
+ paragraphSpacingRangeConfig,
17
+ wordSpacingRangeConfig
11
18
  } from "../../preferences/Types";
12
19
 
13
20
  import defaultColors from "@readium/css/css/vars/colors.json";
@@ -85,8 +92,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
85
92
  onChange: (newValue: number | boolean | null | undefined) => {
86
93
  this.updatePreference("darkenFilter", newValue || null);
87
94
  },
88
- supportedRange: [0, 100],
89
- step: 1
95
+ supportedRange: filterRangeConfig.range,
96
+ step: filterRangeConfig.step
90
97
  });
91
98
  }
92
99
 
@@ -192,8 +199,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
192
199
  onChange: (newValue: number | boolean | null | undefined) => {
193
200
  this.updatePreference("invertFilter", newValue || null);
194
201
  },
195
- supportedRange: [0, 100],
196
- step: 1
202
+ supportedRange: filterRangeConfig.range,
203
+ step: filterRangeConfig.step
197
204
  });
198
205
  }
199
206
 
@@ -205,8 +212,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
205
212
  onChange: (newValue: number | boolean | null | undefined) => {
206
213
  this.updatePreference("invertGaijiFilter", newValue || null);
207
214
  },
208
- supportedRange: [0, 100],
209
- step: 1
215
+ supportedRange: filterRangeConfig.range,
216
+ step: filterRangeConfig.step
210
217
  });
211
218
  }
212
219
 
@@ -240,8 +247,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
240
247
  onChange: (newValue: number | null | undefined) => {
241
248
  this.updatePreference("letterSpacing", newValue || null);
242
249
  },
243
- supportedRange: [0, 1],
244
- step: .125
250
+ supportedRange: letterSpacingRangeConfig.range,
251
+ step: letterSpacingRangeConfig.step
245
252
  });
246
253
  }
247
254
 
@@ -266,8 +273,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
266
273
  onChange: (newValue: number | null | undefined) => {
267
274
  this.updatePreference("lineHeight", newValue || null);
268
275
  },
269
- supportedRange: [1, 2],
270
- step: .1
276
+ supportedRange: lineHeightRangeConfig.range,
277
+ step: lineHeightRangeConfig.step
271
278
  });
272
279
  }
273
280
 
@@ -290,8 +297,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
290
297
  onChange: (newValue: number | null | undefined) => {
291
298
  this.updatePreference("maximalLineLength", newValue);
292
299
  },
293
- supportedRange: [20, 100],
294
- step: 1
300
+ supportedRange: lineLengthRangeConfig.range,
301
+ step: lineLengthRangeConfig.step
295
302
  });
296
303
  }
297
304
 
@@ -303,8 +310,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
303
310
  onChange: (newValue: number | null | undefined) => {
304
311
  this.updatePreference("minimalLineLength", newValue);
305
312
  },
306
- supportedRange: [20, 100],
307
- step: 1
313
+ supportedRange: lineLengthRangeConfig.range,
314
+ step: lineLengthRangeConfig.step
308
315
  });
309
316
  }
310
317
 
@@ -327,8 +334,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
327
334
  onChange: (newValue: number | null | undefined) => {
328
335
  this.updatePreference("optimalLineLength", newValue as number);
329
336
  },
330
- supportedRange: [20, 100],
331
- step: 1
337
+ supportedRange: lineLengthRangeConfig.range,
338
+ step: lineLengthRangeConfig.step
332
339
  });
333
340
  }
334
341
 
@@ -351,8 +358,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
351
358
  onChange: (newValue: number | null | undefined) => {
352
359
  this.updatePreference("paragraphIndent", newValue || null);
353
360
  },
354
- supportedRange: [0, 3],
355
- step: .25
361
+ supportedRange: paragraphIndentRangeConfig.range,
362
+ step: paragraphIndentRangeConfig.step
356
363
  });
357
364
  }
358
365
 
@@ -364,8 +371,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
364
371
  onChange: (newValue: number | null | undefined) => {
365
372
  this.updatePreference("paragraphSpacing", newValue || null);
366
373
  },
367
- supportedRange: [0, 3],
368
- step: .25
374
+ supportedRange: paragraphSpacingRangeConfig.range,
375
+ step: paragraphSpacingRangeConfig.step
369
376
  });
370
377
  }
371
378
 
@@ -501,8 +508,8 @@ export class EpubPreferencesEditor implements IPreferencesEditor {
501
508
  onChange: (newValue: number | null | undefined) => {
502
509
  this.updatePreference("wordSpacing", newValue || null);
503
510
  },
504
- supportedRange: [0, 2],
505
- step: 0.125
511
+ supportedRange: wordSpacingRangeConfig.range,
512
+ step: wordSpacingRangeConfig.step
506
513
  });
507
514
  }
508
515
  }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './Navigator';
2
+ export * from './webpub';
2
3
  export * from './epub';
3
4
  export * from './audio';
4
5
  export * from './helpers';
5
- export * from './preferences';
6
+ export * from './preferences';
7
+ export * from './css';
@@ -10,6 +10,11 @@ export type RangeConfig = {
10
10
  step: number
11
11
  }
12
12
 
13
+ export const filterRangeConfig: RangeConfig = {
14
+ range: [0, 100],
15
+ step: 1
16
+ }
17
+
13
18
  export const fontSizeRangeConfig: RangeConfig = {
14
19
  range: [0.7, 4],
15
20
  step: 0.05
@@ -23,4 +28,39 @@ export const fontWeightRangeConfig: RangeConfig = {
23
28
  export const fontWidthRangeConfig: RangeConfig = {
24
29
  range: [50, 250],
25
30
  step: 10
31
+ }
32
+
33
+ export const letterSpacingRangeConfig: RangeConfig = {
34
+ range: [0, 1],
35
+ step: .125
36
+ }
37
+
38
+ export const lineHeightRangeConfig: RangeConfig = {
39
+ range: [1, 2],
40
+ step: .1
41
+ }
42
+
43
+ export const lineLengthRangeConfig: RangeConfig = {
44
+ range: [20, 100],
45
+ step: 1
46
+ }
47
+
48
+ export const paragraphIndentRangeConfig: RangeConfig = {
49
+ range: [0, 3],
50
+ step: .25
51
+ }
52
+
53
+ export const paragraphSpacingRangeConfig: RangeConfig = {
54
+ range: [0, 3],
55
+ step: .25
56
+ }
57
+
58
+ export const wordSpacingRangeConfig: RangeConfig = {
59
+ range: [0, 2],
60
+ step: .125
61
+ }
62
+
63
+ export const zoomRangeConfig: RangeConfig = {
64
+ range: [0.7, 4],
65
+ step: 0.05
26
66
  }
@@ -29,14 +29,13 @@ export function ensureString(value: string | null | undefined): string | null |
29
29
  }
30
30
 
31
31
  export function ensureBoolean(value: boolean | null | undefined): boolean | null | undefined {
32
- return typeof value === "boolean"
33
- ? value
34
- : value === undefined || value === null
35
- ? value
32
+ return typeof value === "boolean"
33
+ ? value
34
+ : value === undefined || value === null
35
+ ? value
36
36
  : undefined;
37
37
  }
38
38
 
39
-
40
39
  export function ensureEnumValue<T extends string>(value: T | null | undefined, enumType: Record<T, string>): T | null | undefined {
41
40
  if (value === undefined) {
42
41
  return undefined;
@@ -83,4 +82,4 @@ export function ensureValueInRange(value: number | null | undefined, range: [num
83
82
 
84
83
  export function withFallback<T>(value: T | null | undefined, defaultValue: T | null): T | null {
85
84
  return value === undefined ? defaultValue : value;
86
- }
85
+ }
@@ -1,4 +1,5 @@
1
1
  export * from "./Configurable";
2
2
  export * from "./Preference";
3
3
  export * from "./PreferencesEditor";
4
- export * from "./Types";
4
+ export * from "./Types";
5
+ export * from "./guards";
@@ -0,0 +1,167 @@
1
+ import { Link, Publication } from "@readium/shared";
2
+ import { webPubStylesheet } from "./css/WebPubStylesheet";
3
+
4
+ // Utilities (matching FrameBlobBuilder pattern)
5
+ const blobify = (source: string, type: string) => URL.createObjectURL(new Blob([source], { type }));
6
+ const stripJS = (source: string) => source.replace(/\/\/.*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n/g, "").replace(/\s+/g, " ");
7
+ const scriptify = (doc: Document, source: string) => {
8
+ const s = doc.createElement("script");
9
+ s.dataset.readium = "true";
10
+ s.src = source.startsWith("blob:") ? source : blobify(source, "text/javascript");
11
+ return s;
12
+ }
13
+ const styleify = (doc: Document, source: string) => {
14
+ const s = doc.createElement("style");
15
+ s.dataset.readium = "true";
16
+ s.textContent = source;
17
+ return s;
18
+ }
19
+
20
+ type CacheFunction = () => string;
21
+ const resourceBlobCache = new Map<string, string>();
22
+ const cached = (key: string, cacher: CacheFunction) => {
23
+ if (resourceBlobCache.has(key)) return resourceBlobCache.get(key)!;
24
+ const value = cacher();
25
+ resourceBlobCache.set(key, value);
26
+ return value;
27
+ };
28
+
29
+ const cssSelectorGenerator = (doc: Document) => scriptify(doc, cached("css-selector-generator", () => blobify(
30
+ "!function(t,e){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=e():\"function\"==typeof define&&define.amd?define([],e):\"object\"==typeof exports?exports._readium_cssSelectorGenerator=e():t._readium_cssSelectorGenerator=e()}(self,(()=>(()=>{\"use strict\";var t,e,n={d:(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(t,\"__esModule\",{value:!0})}},o={};function r(t){return t&&t instanceof Element}function i(t=\"unknown problem\",...e){console.warn(`CssSelectorGenerator: ${t}`,...e)}n.r(o),n.d(o,{default:()=>z,getCssSelector:()=>U}),function(t){t.NONE=\"none\",t.DESCENDANT=\"descendant\",t.CHILD=\"child\"}(t||(t={})),function(t){t.id=\"id\",t.class=\"class\",t.tag=\"tag\",t.attribute=\"attribute\",t.nthchild=\"nthchild\",t.nthoftype=\"nthoftype\"}(e||(e={}));const c={selectors:[e.id,e.class,e.tag,e.attribute],includeTag:!1,whitelist:[],blacklist:[],combineWithinSelector:!0,combineBetweenSelectors:!0,root:null,maxCombinations:Number.POSITIVE_INFINITY,maxCandidates:Number.POSITIVE_INFINITY};function u(t){return t instanceof RegExp}function s(t){return[\"string\",\"function\"].includes(typeof t)||u(t)}function l(t){return Array.isArray(t)?t.filter(s):[]}function a(t){const e=[Node.DOCUMENT_NODE,Node.DOCUMENT_FRAGMENT_NODE,Node.ELEMENT_NODE];return function(t){return t instanceof Node}(t)&&e.includes(t.nodeType)}function f(t,e){if(a(t))return t.contains(e)||i(\"element root mismatch\",\"Provided root does not contain the element. This will most likely result in producing a fallback selector using element\'s real root node. If you plan to use the selector using provided root (e.g. `root.querySelector`), it will nto work as intended.\"),t;const n=e.getRootNode({composed:!1});return a(n)?(n!==document&&i(\"shadow root inferred\",\"You did not provide a root and the element is a child of Shadow DOM. This will produce a selector using ShadowRoot as a root. If you plan to use the selector using document as a root (e.g. `document.querySelector`), it will not work as intended.\"),n):e.ownerDocument.querySelector(\":root\")}function d(t){return\"number\"==typeof t?t:Number.POSITIVE_INFINITY}function m(t=[]){const[e=[],...n]=t;return 0===n.length?e:n.reduce(((t,e)=>t.filter((t=>e.includes(t)))),e)}function p(t){return[].concat(...t)}function h(t){const e=t.map((t=>{if(u(t))return e=>t.test(e);if(\"function\"==typeof t)return e=>{const n=t(e);return\"boolean\"!=typeof n?(i(\"pattern matcher function invalid\",\"Provided pattern matching function does not return boolean. It\'s result will be ignored.\",t),!1):n};if(\"string\"==typeof t){const e=new RegExp(\"^\"+t.replace(\/[|\\\\{}()[\\]^$+?.]\/g,\"\\\\$&\").replace(\/\\*\/g,\".+\")+\"$\");return t=>e.test(t)}return i(\"pattern matcher invalid\",\"Pattern matching only accepts strings, regular expressions and\/or functions. This item is invalid and will be ignored.\",t),()=>!1}));return t=>e.some((e=>e(t)))}function g(t,e,n){const o=Array.from(f(n,t[0]).querySelectorAll(e));return o.length===t.length&&t.every((t=>o.includes(t)))}function y(t,e){e=null!=e?e:function(t){return t.ownerDocument.querySelector(\":root\")}(t);const n=[];let o=t;for(;r(o)&&o!==e;)n.push(o),o=o.parentElement;return n}function b(t,e){return m(t.map((t=>y(t,e))))}const N={[t.NONE]:{type:t.NONE,value:\"\"},[t.DESCENDANT]:{type:t.DESCENDANT,value:\" > \"},[t.CHILD]:{type:t.CHILD,value:\" \"}},S=new RegExp([\"^$\",\"\\\\s\"].join(\"|\")),E=new RegExp([\"^$\"].join(\"|\")),w=[e.nthoftype,e.tag,e.id,e.class,e.attribute,e.nthchild],v=h([\"class\",\"id\",\"ng-*\"]);function C({nodeName:t}){return`[${t}]`}function O({nodeName:t,nodeValue:e}){return`[${t}=\'${L(e)}\']`}function T(t){const e=Array.from(t.attributes).filter((e=>function({nodeName:t},e){const n=e.tagName.toLowerCase();return!([\"input\",\"option\"].includes(n)&&\"value\"===t||v(t))}(e,t)));return[...e.map(C),...e.map(O)]}function I(t){return(t.getAttribute(\"class\")||\"\").trim().split(\/\\s+\/).filter((t=>!E.test(t))).map((t=>`.${L(t)}`))}function x(t){const e=t.getAttribute(\"id\")||\"\",n=`#${L(e)}`,o=t.getRootNode({composed:!1});return!S.test(e)&&g([t],n,o)?[n]:[]}function j(t){const e=t.parentNode;if(e){const n=Array.from(e.childNodes).filter(r).indexOf(t);if(n>-1)return[`:nth-child(${n+1})`]}return[]}function A(t){return[L(t.tagName.toLowerCase())]}function D(t){const e=[...new Set(p(t.map(A)))];return 0===e.length||e.length>1?[]:[e[0]]}function $(t){const e=D([t])[0],n=t.parentElement;if(n){const o=Array.from(n.children).filter((t=>t.tagName.toLowerCase()===e)),r=o.indexOf(t);if(r>-1)return[`${e}:nth-of-type(${r+1})`]}return[]}function R(t=[],{maxResults:e=Number.POSITIVE_INFINITY}={}){const n=[];let o=0,r=k(1);for(;r.length<=t.length&&o<e;)o+=1,n.push(r.map((e=>t[e]))),r=P(r,t.length-1);return n}function P(t=[],e=0){const n=t.length;if(0===n)return[];const o=[...t];o[n-1]+=1;for(let t=n-1;t>=0;t--)if(o[t]>e){if(0===t)return k(n+1);o[t-1]++,o[t]=o[t-1]+1}return o[n-1]>e?k(n+1):o}function k(t=1){return Array.from(Array(t).keys())}const _=\":\".charCodeAt(0).toString(16).toUpperCase(),M=\/[ !\"#$%&\'()\\[\\]{|}<>*+,.\/;=?@^`~\\\\]\/;function L(t=\"\"){var e,n;return null!==(n=null===(e=null===CSS||void 0===CSS?void 0:CSS.escape)||void 0===e?void 0:e.call(CSS,t))&&void 0!==n?n:function(t=\"\"){return t.split(\"\").map((t=>\":\"===t?`\\\\${_} `:M.test(t)?`\\\\${t}`:escape(t).replace(\/%\/g,\"\\\\\"))).join(\"\")}(t)}const q={tag:D,id:function(t){return 0===t.length||t.length>1?[]:x(t[0])},class:function(t){return m(t.map(I))},attribute:function(t){return m(t.map(T))},nthchild:function(t){return m(t.map(j))},nthoftype:function(t){return m(t.map($))}},F={tag:A,id:x,class:I,attribute:T,nthchild:j,nthoftype:$};function V(t){return t.includes(e.tag)||t.includes(e.nthoftype)?[...t]:[...t,e.tag]}function Y(t={}){const n=[...w];return t[e.tag]&&t[e.nthoftype]&&n.splice(n.indexOf(e.tag),1),n.map((e=>{return(o=t)[n=e]?o[n].join(\"\"):\"\";var n,o})).join(\"\")}function B(t,e,n=\"\",o){const r=function(t,e){return\"\"===e?t:function(t,e){return[...t.map((t=>e+\" \"+t)),...t.map((t=>e+\" > \"+t))]}(t,e)}(function(t,e,n){const o=function(t,e){const{blacklist:n,whitelist:o,combineWithinSelector:r,maxCombinations:i}=e,c=h(n),u=h(o);return function(t){const{selectors:e,includeTag:n}=t,o=[].concat(e);return n&&!o.includes(\"tag\")&&o.push(\"tag\"),o}(e).reduce(((e,n)=>{const o=function(t,e){var n;return(null!==(n=q[e])&&void 0!==n?n:()=>[])(t)}(t,n),s=function(t=[],e,n){return t.filter((t=>n(t)||!e(t)))}(o,c,u),l=function(t=[],e){return t.sort(((t,n)=>{const o=e(t),r=e(n);return o&&!r?-1:!o&&r?1:0}))}(s,u);return e[n]=r?R(l,{maxResults:i}):l.map((t=>[t])),e}),{})}(t,n),r=function(t,e){return function(t){const{selectors:e,combineBetweenSelectors:n,includeTag:o,maxCandidates:r}=t,i=n?R(e,{maxResults:r}):e.map((t=>[t]));return o?i.map(V):i}(e).map((e=>function(t,e){const n={};return t.forEach((t=>{const o=e[t];o.length>0&&(n[t]=o)})),function(t={}){let e=[];return Object.entries(t).forEach((([t,n])=>{e=n.flatMap((n=>0===e.length?[{[t]:n}]:e.map((e=>Object.assign(Object.assign({},e),{[t]:n})))))})),e}(n).map(Y)}(e,t))).filter((t=>t.length>0))}(o,n),i=p(r);return[...new Set(i)]}(t,o.root,o),n);for(const e of r)if(g(t,e,o.root))return e;return null}function G(t){return{value:t,include:!1}}function W({selectors:t,operator:n}){let o=[...w];t[e.tag]&&t[e.nthoftype]&&(o=o.filter((t=>t!==e.tag)));let r=\"\";return o.forEach((e=>{(t[e]||[]).forEach((({value:t,include:e})=>{e&&(r+=t)}))})),n.value+r}function H(n){return[\":root\",...y(n).reverse().map((n=>{const o=function(e,n,o=t.NONE){const r={};return n.forEach((t=>{Reflect.set(r,t,function(t,e){return F[e](t)}(e,t).map(G))})),{element:e,operator:N[o],selectors:r}}(n,[e.nthchild],t.DESCENDANT);return o.selectors.nthchild.forEach((t=>{t.include=!0})),o})).map(W)].join(\"\")}function U(t,n={}){const o=function(t){const e=(Array.isArray(t)?t:[t]).filter(r);return[...new Set(e)]}(t),i=function(t,n={}){const o=Object.assign(Object.assign({},c),n);return{selectors:(r=o.selectors,Array.isArray(r)?r.filter((t=>{return n=e,o=t,Object.values(n).includes(o);var n,o})):[]),whitelist:l(o.whitelist),blacklist:l(o.blacklist),root:f(o.root,t),combineWithinSelector:!!o.combineWithinSelector,combineBetweenSelectors:!!o.combineBetweenSelectors,includeTag:!!o.includeTag,maxCombinations:d(o.maxCombinations),maxCandidates:d(o.maxCandidates)};var r}(o[0],n);let u=\"\",s=i.root;function a(){return function(t,e,n=\"\",o){if(0===t.length)return null;const r=[t.length>1?t:[],...b(t,e).map((t=>[t]))];for(const t of r){const e=B(t,0,n,o);if(e)return{foundElements:t,selector:e}}return null}(o,s,u,i)}let m=a();for(;m;){const{foundElements:t,selector:e}=m;if(g(o,e,i.root))return e;s=t[0],u=e,m=a()}return o.length>1?o.map((t=>U(t,i))).join(\", \"):function(t){return t.map(H).join(\", \")}(o)}const z=U;return o})()));",
31
+ "text/javascript"
32
+ )));
33
+
34
+ const readiumPropertiesScript = `
35
+ window._readium_blockedEvents = [];
36
+ window._readium_blockEvents = false; // WebPub doesn't need event blocking
37
+ window._readium_eventBlocker = null;
38
+ `;
39
+
40
+ const rBefore = (doc: Document) => scriptify(doc, cached("webpub-js-before", () => blobify(stripJS(readiumPropertiesScript), "text/javascript")));
41
+ const rAfter = (doc: Document) => scriptify(doc, cached("webpub-js-after", () => blobify(stripJS(`
42
+ if(window.onload) window.onload = new Proxy(window.onload, {
43
+ apply: function(target, receiver, args) {
44
+ if(!window._readium_blockEvents) {
45
+ Reflect.apply(target, receiver, args);
46
+ return;
47
+ }
48
+ _readium_blockedEvents.push([0, target, receiver, args]);
49
+ }
50
+ });`), "text/javascript")));
51
+
52
+ export class WebPubBlobBuilder {
53
+ private readonly item: Link;
54
+ private readonly burl: string;
55
+ private readonly pub: Publication;
56
+ private readonly cssProperties?: { [key: string]: string };
57
+
58
+ constructor(pub: Publication, baseURL: string, item: Link, cssProperties?: { [key: string]: string }) {
59
+ this.pub = pub;
60
+ this.item = item;
61
+ this.burl = item.toURL(baseURL) || "";
62
+ this.cssProperties = cssProperties;
63
+ }
64
+
65
+ public async build(): Promise<string> {
66
+ if (!this.item.mediaType.isHTML) {
67
+ throw new Error(`Unsupported media type for WebPub: ${this.item.mediaType.string}`);
68
+ }
69
+
70
+ return await this.buildHtmlFrame();
71
+ }
72
+
73
+ private async buildHtmlFrame(): Promise<string> {
74
+ // Load the HTML resource
75
+ const txt = await this.pub.get(this.item).readAsString();
76
+ if(!txt) throw new Error(`Failed reading item ${this.item.href}`);
77
+ const doc = new DOMParser().parseFromString(
78
+ txt,
79
+ this.item.mediaType.string as DOMParserSupportedType
80
+ );
81
+ const perror = doc.querySelector("parsererror");
82
+ if(perror) {
83
+ const details = perror.querySelector("div");
84
+ throw new Error(`Failed parsing item ${this.item.href}: ${details?.textContent || perror.textContent}`);
85
+ }
86
+ return this.finalizeDOM(doc, this.burl, this.item.mediaType, txt, this.cssProperties);
87
+ }
88
+
89
+ private hasExecutable(doc: Document): boolean {
90
+ return (
91
+ !!doc.querySelector("script") ||
92
+ !!doc.querySelector("body[onload]:not(body[onload=''])")
93
+ );
94
+ }
95
+
96
+ private setProperties(cssProperties: { [key: string]: string }, doc: Document) {
97
+ for (const key in cssProperties) {
98
+ const value = cssProperties[key];
99
+ if (value) doc.documentElement.style.setProperty(key, value);
100
+ }
101
+ }
102
+
103
+ private finalizeDOM(doc: Document, base: string | undefined, mediaType: any, txt?: string, cssProperties?: { [key: string]: string }): string {
104
+ if(!doc) return "";
105
+
106
+ // Add WebPubCSS stylesheet at end of head (like EPUB ReadiumCSS-after)
107
+ const webPubStyle = styleify(doc, webPubStylesheet);
108
+ doc.head.appendChild(webPubStyle);
109
+ if (cssProperties) {
110
+ this.setProperties(cssProperties, doc);
111
+ }
112
+
113
+ doc.body.querySelectorAll("img").forEach((img) => {
114
+ img.setAttribute("fetchpriority", "high");
115
+ });
116
+
117
+ if(base !== undefined) {
118
+ const b = doc.createElement("base");
119
+ b.href = base;
120
+ b.dataset.readium = "true";
121
+ doc.head.firstChild!.before(b);
122
+ }
123
+
124
+ const hasExecutable = this.hasExecutable(doc);
125
+ if(hasExecutable) doc.head.firstChild!.before(rBefore(doc));
126
+ doc.head.firstChild!.before(cssSelectorGenerator(doc));
127
+ if(hasExecutable) doc.head.appendChild(rAfter(doc));
128
+
129
+ // Serialize properly based on content type
130
+ let serializedContent: string;
131
+
132
+ if (mediaType.string === "application/xhtml+xml") {
133
+ // XHTML: Use XMLSerializer for proper XML formatting
134
+ serializedContent = new XMLSerializer().serializeToString(doc);
135
+ } else {
136
+ // HTML: Use custom HTML serialization to preserve HTML formatting
137
+ serializedContent = this.serializeAsHTML(doc, txt || "");
138
+ }
139
+
140
+ // Make blob from doc
141
+ return URL.createObjectURL(
142
+ new Blob([serializedContent], {
143
+ type: mediaType.isHTML
144
+ ? mediaType.string
145
+ : "application/xhtml+xml",
146
+ })
147
+ );
148
+ }
149
+
150
+ private serializeAsHTML(doc: Document, txt: string): string {
151
+ // For HTML content, try to preserve the original HTML structure
152
+ // while injecting our scripts
153
+
154
+ // Extract the original DOCTYPE if present
155
+ const doctypeMatch = txt.match(/<!DOCTYPE[^>]*>/i);
156
+ const doctype = doctypeMatch ? doctypeMatch[0] + "\n" : "";
157
+
158
+ // Get the HTML element and serialize it as HTML
159
+ const htmlElement = doc.documentElement;
160
+ let htmlContent = htmlElement.outerHTML;
161
+
162
+ // Try to preserve the original HTML structure
163
+ // This is a best-effort approach since there's no perfect HTML serializer
164
+
165
+ return doctype + htmlContent;
166
+ }
167
+ }