@prose-reader/core 1.124.0 → 1.126.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -265,7 +265,7 @@ const createMovingSafePan$ = (reader) => {
265
265
  })
266
266
  );
267
267
  const resetLockViewportFree$ = createResetLock$(
268
- reader.navigation.viewportFree$
268
+ reader.viewportFree$
269
269
  ).pipe(take(1));
270
270
  const pageTurnMode$ = reader.settings.values$.pipe(
271
271
  map(() => reader.settings.values.computedPageTurnMode),
@@ -1515,7 +1515,9 @@ const themeEnhancer = (next) => (options) => {
1515
1515
  };
1516
1516
  reader.hookManager.register(`item.onDocumentLoad`, ({ itemId }) => {
1517
1517
  const item = reader.spineItemsManager.get(itemId);
1518
- item == null ? void 0 : item.upsertCSS(`prose-reader-theme`, getStyle());
1518
+ if ((item == null ? void 0 : item.item.renditionLayout) !== "pre-paginated") {
1519
+ item == null ? void 0 : item.upsertCSS(`prose-reader-theme`, getStyle());
1520
+ }
1519
1521
  });
1520
1522
  reader.spineItemsManager.items$.pipe(
1521
1523
  tap(
@@ -3417,15 +3419,16 @@ class CfiHandler {
3417
3419
  return this.resolveLast(doc, opts);
3418
3420
  }
3419
3421
  }
3420
- const extractProseMetadataFromCfi = (cfi) => {
3422
+ const parseCfi = (cfi) => {
3421
3423
  var _a, _b;
3422
- const [itemId] = ((_a = cfi.match(/\|(\[prose\~anchor[^\]]*\])+/gi)) == null ? void 0 : _a.map((s) => s.replace(/\|\[prose\~anchor\~/, ``).replace(/\]/, ``))) || [];
3424
+ const [itemIndex] = ((_a = cfi.match(/\|(\[prose\~anchor[^\]]*\])+/gi)) == null ? void 0 : _a.map((s) => s.replace(/\|\[prose\~anchor\~/, ``).replace(/\]/, ``))) || [];
3423
3425
  const [offset] = ((_b = cfi.match(/\|(\[prose\~offset[^\]]*\])+/gi)) == null ? void 0 : _b.map((s) => s.replace(/\|\[prose\~offset\~/, ``).replace(/\]/, ``))) || [];
3424
3426
  const cleanedCfi = cfi.replace(/\|(\[prose\~[^\]]*\~[^\]]*\])+/gi, ``);
3425
3427
  const foundOffset = parseInt(offset || ``);
3428
+ const foundItemIndex = parseInt(itemIndex || ``);
3426
3429
  return {
3427
3430
  cleanedCfi,
3428
- itemId: itemId ? decodeURIComponent(itemId) : itemId,
3431
+ itemIndex: isNaN(foundItemIndex) ? void 0 : foundItemIndex,
3429
3432
  offset: isNaN(foundOffset) ? void 0 : foundOffset
3430
3433
  };
3431
3434
  };
@@ -3436,9 +3439,8 @@ const resolveCfi = ({
3436
3439
  var _a, _b;
3437
3440
  if (!cfi) return void 0;
3438
3441
  const spineItem = spineItemsManager.getSpineItemFromCfi(cfi);
3439
- const spineItemIndex = spineItemsManager.getSpineItemIndex(spineItem) || 0;
3440
3442
  if (!spineItem) return void 0;
3441
- const { cleanedCfi, offset } = extractProseMetadataFromCfi(cfi);
3443
+ const { cleanedCfi, offset } = parseCfi(cfi);
3442
3444
  const cfiHandler = new CfiHandler(cleanedCfi, {});
3443
3445
  const rendererElement = (_a = spineItem.renderer.layers[0]) == null ? void 0 : _a.element;
3444
3446
  if (rendererElement instanceof HTMLIFrameElement) {
@@ -3449,20 +3451,17 @@ const resolveCfi = ({
3449
3451
  return {
3450
3452
  node,
3451
3453
  offset: offset ?? resolvedOffset,
3452
- spineItemIndex,
3453
3454
  spineItem
3454
3455
  };
3455
3456
  } catch (e) {
3456
3457
  Report.error(e);
3457
3458
  return {
3458
- spineItemIndex,
3459
3459
  spineItem
3460
3460
  };
3461
3461
  }
3462
3462
  }
3463
3463
  }
3464
3464
  return {
3465
- spineItemIndex,
3466
3465
  spineItem
3467
3466
  };
3468
3467
  };
@@ -5011,11 +5010,19 @@ class HookManager {
5011
5010
  return combineLatest(destroyFns);
5012
5011
  }
5013
5012
  }
5014
- const getItemAnchor = (spineItem) => `|[prose~anchor~${encodeURIComponent(spineItem.item.id)}]`;
5013
+ const getItemAnchor = (item) => `|[prose~anchor~${encodeURIComponent(item.index)}]`;
5015
5014
  const getRootCfi = (spineItem) => {
5016
- const itemAnchor = getItemAnchor(spineItem);
5015
+ const itemAnchor = getItemAnchor(spineItem.item);
5017
5016
  return `epubcfi(/0${itemAnchor})`;
5018
5017
  };
5018
+ const generateCfi = (node, offset, item) => {
5019
+ const offsetAnchor = `[prose~offset~${offset || 0}]`;
5020
+ return CfiHandler.generate(
5021
+ node,
5022
+ offset,
5023
+ `${getItemAnchor(item)}|${offsetAnchor}`
5024
+ );
5025
+ };
5019
5026
  const generateCfiForSpineItemPage = Report.measurePerformance(
5020
5027
  `getCfi`,
5021
5028
  10,
@@ -5030,13 +5037,11 @@ const generateCfiForSpineItemPage = Report.measurePerformance(
5030
5037
  spineItem
5031
5038
  );
5032
5039
  const rendererElement = (_a = spineItem.renderer.layers[0]) == null ? void 0 : _a.element;
5033
- const itemAnchor = getItemAnchor(spineItem);
5034
- const offset = `|[prose~offset~${(nodeOrRange == null ? void 0 : nodeOrRange.offset) || 0}]`;
5035
5040
  if (nodeOrRange && rendererElement instanceof HTMLIFrameElement && ((_b = rendererElement.contentWindow) == null ? void 0 : _b.document)) {
5036
- const cfiString = CfiHandler.generate(
5041
+ const cfiString = generateCfi(
5037
5042
  nodeOrRange.node,
5038
- 0,
5039
- `${itemAnchor}${offset}`
5043
+ nodeOrRange.offset,
5044
+ spineItem.item
5040
5045
  );
5041
5046
  return cfiString.trim();
5042
5047
  }
@@ -5160,1144 +5165,1136 @@ const generateCfiFromRange = ({
5160
5165
  end,
5161
5166
  endNode
5162
5167
  }, item) => {
5163
- const startCFI = CfiHandler.generate(
5164
- startNode,
5165
- start,
5166
- `|[prose~anchor~${encodeURIComponent(item.id)}]`
5167
- );
5168
- const endCFI = CfiHandler.generate(
5169
- endNode,
5170
- end,
5171
- `|[prose~anchor~${encodeURIComponent(item.id)}]`
5172
- );
5168
+ const startCFI = generateCfi(startNode, start, item);
5169
+ const endCFI = generateCfi(endNode, end, item);
5173
5170
  return { start: startCFI, end: endCFI };
5174
5171
  };
5175
- class SpineItemsManager extends DestroyableClass {
5176
- constructor(context, settings) {
5177
- super();
5178
- this.context = context;
5179
- this.settings = settings;
5180
- this.orderedSpineItemsSubject = new BehaviorSubject([]);
5181
- this.items$ = this.orderedSpineItemsSubject.asObservable();
5172
+ class DocumentRenderer {
5173
+ constructor(params) {
5174
+ this.triggerSubject = new Subject();
5175
+ this.stateSubject = new BehaviorSubject(`idle`);
5176
+ this.unload$ = this.triggerSubject.pipe(
5177
+ withLatestFrom(this.stateSubject),
5178
+ filter$1(
5179
+ ([trigger, state]) => trigger === `unload` && state !== "idle" && state !== "unloading"
5180
+ ),
5181
+ map$1(() => void 0),
5182
+ share()
5183
+ );
5184
+ this.load$ = this.triggerSubject.pipe(
5185
+ withLatestFrom(this.stateSubject),
5186
+ filter$1(
5187
+ ([trigger, state]) => trigger === `load` && state !== "ready" && state !== "loaded" && state !== "loading"
5188
+ ),
5189
+ map$1(() => void 0),
5190
+ share()
5191
+ );
5192
+ this.layers = [];
5193
+ this.destroy$ = this.triggerSubject.pipe(
5194
+ filter$1((trigger) => trigger === `destroy`)
5195
+ );
5196
+ this.context = params.context;
5197
+ this.settings = params.settings;
5198
+ this.hookManager = params.hookManager;
5199
+ this.item = params.item;
5200
+ this.containerElement = params.containerElement;
5201
+ this.resourcesHandler = params.resourcesHandler;
5202
+ this.load$.pipe(
5203
+ switchMap(() => {
5204
+ this.stateSubject.next(`loading`);
5205
+ const createDocument$ = this.onCreateDocument().pipe(
5206
+ endWith(null),
5207
+ first$1()
5208
+ );
5209
+ return createDocument$.pipe(
5210
+ tap$1(() => {
5211
+ this.hookManager.execute(`item.onDocumentCreated`, this.item.id, {
5212
+ itemId: this.item.id,
5213
+ layers: this.layers
5214
+ });
5215
+ }),
5216
+ switchMap(() => {
5217
+ const loadDocument$ = this.onLoadDocument().pipe(
5218
+ endWith(null),
5219
+ first$1()
5220
+ );
5221
+ return loadDocument$;
5222
+ }),
5223
+ waitForSwitch(this.context.bridgeEvent.viewportFree$),
5224
+ switchMap(() => {
5225
+ const hookResults = this.hookManager.execute(`item.onDocumentLoad`, this.item.id, {
5226
+ itemId: this.item.id,
5227
+ layers: this.layers
5228
+ }).filter(
5229
+ (result) => result instanceof Observable
5230
+ );
5231
+ return combineLatest([of(null), ...hookResults]).pipe(first$1());
5232
+ }),
5233
+ tap$1(() => {
5234
+ this.stateSubject.next(`loaded`);
5235
+ this.stateSubject.next(`ready`);
5236
+ }),
5237
+ takeUntil(merge(this.destroy$, this.unload$))
5238
+ );
5239
+ })
5240
+ ).subscribe();
5241
+ this.unload$.pipe(
5242
+ switchMap(() => {
5243
+ this.stateSubject.next(`unloading`);
5244
+ return this.context.bridgeEvent.viewportFree$.pipe(
5245
+ first$1(),
5246
+ tap$1(() => {
5247
+ this.hookManager.destroy(`item.onDocumentLoad`, this.item.id);
5248
+ }),
5249
+ switchMap(() => {
5250
+ const unload$ = this.onUnload().pipe(endWith(null), first$1());
5251
+ return unload$;
5252
+ }),
5253
+ tap$1(() => {
5254
+ this.stateSubject.next(`idle`);
5255
+ }),
5256
+ takeUntil(merge(this.destroy$, this.load$))
5257
+ );
5258
+ })
5259
+ ).subscribe();
5182
5260
  }
5183
- get(indexOrId) {
5184
- if (typeof indexOrId === `number`) {
5185
- return this.orderedSpineItemsSubject.value[indexOrId];
5186
- }
5187
- if (typeof indexOrId === `string`) {
5188
- return this.orderedSpineItemsSubject.value.find(
5189
- ({ item }) => item.id === indexOrId
5190
- );
5191
- }
5192
- return indexOrId;
5261
+ get state$() {
5262
+ return this.stateSubject;
5193
5263
  }
5194
- comparePositionOf(toCompare, withItem) {
5195
- const toCompareIndex = this.getSpineItemIndex(toCompare) ?? 0;
5196
- const withIndex = this.getSpineItemIndex(withItem) ?? 0;
5197
- return toCompareIndex > withIndex ? `after` : toCompareIndex === withIndex ? `same` : `before`;
5264
+ load() {
5265
+ this.triggerSubject.next(`load`);
5198
5266
  }
5199
- getSpineItemIndex(spineItem) {
5200
- if (!spineItem) return void 0;
5201
- const index = this.orderedSpineItemsSubject.value.indexOf(spineItem);
5202
- return index < 0 ? void 0 : index;
5267
+ unload() {
5268
+ this.triggerSubject.next(`unload`);
5203
5269
  }
5204
- addMany(spineItems) {
5205
- this.orderedSpineItemsSubject.next([
5206
- ...this.orderedSpineItemsSubject.getValue(),
5207
- ...spineItems
5208
- ]);
5270
+ destroy() {
5271
+ this.triggerSubject.next(`destroy`);
5272
+ this.triggerSubject.complete();
5273
+ this.stateSubject.complete();
5209
5274
  }
5210
- // @todo move
5211
- getSpineItemFromCfi(cfi) {
5212
- const { itemId } = extractProseMetadataFromCfi(cfi);
5213
- if (itemId) {
5214
- const { itemId: itemId2 } = extractProseMetadataFromCfi(cfi);
5215
- const spineItem = (itemId2 ? this.get(itemId2) : void 0) || this.get(0);
5216
- return spineItem;
5217
- }
5275
+ get writingMode() {
5218
5276
  return void 0;
5219
5277
  }
5220
- get items() {
5221
- return this.orderedSpineItemsSubject.value;
5278
+ get readingDirection() {
5279
+ return void 0;
5222
5280
  }
5223
- /**
5224
- * @todo handle reload, remove subscription to each items etc. See add()
5225
- */
5226
- destroyItems() {
5227
- this.orderedSpineItemsSubject.value.forEach((item) => item.destroy());
5281
+ }
5282
+ class DefaultRenderer extends DocumentRenderer {
5283
+ onUnload() {
5284
+ return EMPTY;
5285
+ }
5286
+ onCreateDocument() {
5287
+ return EMPTY;
5288
+ }
5289
+ onLoadDocument() {
5290
+ return EMPTY;
5291
+ }
5292
+ layout() {
5293
+ return void 0;
5228
5294
  }
5229
5295
  }
5230
- const getSpineItemFromPosition = ({
5231
- position,
5232
- spineItemsManager,
5233
- spineLayout,
5234
- settings
5235
- }) => {
5236
- const spineItem = spineItemsManager.items.find((item) => {
5237
- const { left, right, bottom, top } = spineLayout.getAbsolutePositionOf(item);
5238
- const isWithinXAxis = position.x >= left && position.x < right;
5239
- if (settings.values.computedPageTurnDirection === `horizontal`) {
5240
- return isWithinXAxis;
5241
- } else {
5242
- return isWithinXAxis && position.y >= top && position.y < bottom;
5243
- }
5244
- });
5245
- if (position.x === 0 && !spineItem) {
5246
- return spineItemsManager.items[0];
5296
+ const defaultGetResource = (item) => new URL(item.href);
5297
+ class ResourceHandler {
5298
+ constructor(item, settings) {
5299
+ this.item = item;
5300
+ this.settings = settings;
5247
5301
  }
5248
- return spineItem;
5249
- };
5250
- const isItemVisibleByThresholdForPosition = ({
5251
- itemHeight,
5252
- itemWidth,
5253
- visibleWidthOfItem,
5254
- visibleHeightOfItem,
5255
- threshold
5256
- }) => {
5257
- const visibleWidthRatioOfSpineItem = visibleWidthOfItem / itemWidth;
5258
- const visibleHeightRatioOfSpineItem = visibleHeightOfItem / itemHeight;
5259
- const isItemVisibleEnough = visibleWidthRatioOfSpineItem >= threshold && visibleHeightRatioOfSpineItem >= threshold;
5260
- return isItemVisibleEnough;
5261
- };
5262
- const isItemVisibleOnScreenByThresholdForPosition = ({
5263
- visibleWidthOfItem,
5264
- visibleHeightOfItem,
5265
- threshold,
5266
- context
5267
- }) => {
5268
- const widthRatioOfSpaceTakenInScreen = visibleWidthOfItem / context.state.visibleAreaRect.width;
5269
- const heightRatioOfSpaceTakenInScreen = visibleHeightOfItem / context.state.visibleAreaRect.height;
5270
- const isItemVisibleEnoughOnScreen = heightRatioOfSpaceTakenInScreen >= threshold && widthRatioOfSpaceTakenInScreen >= threshold;
5271
- return isItemVisibleEnoughOnScreen;
5272
- };
5273
- const getItemVisibilityForPosition = ({
5274
- itemPosition: {
5275
- bottom,
5276
- left,
5277
- right,
5278
- top,
5279
- width: itemWidth,
5280
- height: itemHeight
5281
- },
5282
- threshold,
5283
- viewportPosition,
5284
- restrictToScreen,
5285
- context
5286
- }) => {
5287
- const viewportLeft = viewportPosition.x;
5288
- const viewportRight = viewportPosition.x + (context.state.visibleAreaRect.width - 1);
5289
- const viewportTop = viewportPosition.y;
5290
- const viewportBottom = Math.max(
5291
- viewportPosition.y + (context.state.visibleAreaRect.height - 1),
5292
- 0
5293
- );
5294
- const visibleWidthOfItem = Math.max(
5295
- 0,
5296
- Math.min(right, viewportRight) - Math.max(left, viewportLeft)
5297
- );
5298
- const visibleHeightOfItem = Math.max(
5299
- 0,
5300
- Math.min(bottom, viewportBottom) - Math.max(top, viewportTop)
5301
- );
5302
- const itemIsOnTheOuterEdge = visibleWidthOfItem <= 0 || visibleHeightOfItem <= 0;
5303
- if (itemIsOnTheOuterEdge) return { visible: false };
5304
- const isItemVisibleEnoughOnScreen = isItemVisibleOnScreenByThresholdForPosition({
5305
- threshold,
5306
- visibleHeightOfItem,
5307
- visibleWidthOfItem,
5308
- context
5309
- });
5310
- if (restrictToScreen) {
5311
- return { visible: isItemVisibleEnoughOnScreen };
5302
+ async getResource() {
5303
+ var _a, _b;
5304
+ const resource = await lastValueFrom(
5305
+ ((_b = (_a = this.settings.values).getResource) == null ? void 0 : _b.call(_a, this.item)) ?? of(void 0)
5306
+ );
5307
+ return resource ?? defaultGetResource(this.item);
5312
5308
  }
5313
- const isItemVisibleEnough = isItemVisibleByThresholdForPosition({
5314
- itemHeight,
5315
- itemWidth,
5316
- threshold,
5317
- visibleHeightOfItem,
5318
- visibleWidthOfItem
5319
- });
5320
- return {
5321
- visible: isItemVisibleEnough || isItemVisibleEnoughOnScreen
5322
- };
5323
- };
5324
- const getVisibleSpineItemsFromPosition = ({
5325
- position,
5326
- threshold,
5327
- restrictToScreen,
5328
- spineItemsManager,
5329
- context,
5330
- settings,
5331
- spineLayout
5332
- }) => {
5333
- const fallbackSpineItem = getSpineItemFromPosition({
5334
- position,
5335
- settings,
5336
- spineItemsManager,
5337
- spineLayout
5338
- }) || spineItemsManager.get(0);
5339
- const spineItemsVisible = spineItemsManager.items.reduce(
5340
- (acc, spineItem) => {
5341
- const itemPosition = spineLayout.getAbsolutePositionOf(spineItem);
5342
- const { visible } = getItemVisibilityForPosition({
5343
- itemPosition,
5344
- threshold,
5345
- viewportPosition: position,
5346
- restrictToScreen,
5347
- context
5348
- });
5349
- if (visible) {
5350
- return [...acc, spineItem];
5309
+ async fetchResource() {
5310
+ const resource = await this.getResource();
5311
+ if (resource instanceof Response) return resource;
5312
+ if (resource instanceof URL) return fetch(resource);
5313
+ return resource;
5314
+ }
5315
+ }
5316
+ class SpineItem {
5317
+ constructor(item, parentElement, context, settings, hookManager, index) {
5318
+ var _a, _b;
5319
+ this.item = item;
5320
+ this.parentElement = parentElement;
5321
+ this.context = context;
5322
+ this.settings = settings;
5323
+ this.hookManager = hookManager;
5324
+ this.index = index;
5325
+ this.adjustPositionOfElement = ({
5326
+ right,
5327
+ left,
5328
+ top
5329
+ }) => {
5330
+ if (right !== void 0) {
5331
+ this.containerElement.style.right = `${right}px`;
5332
+ } else {
5333
+ this.containerElement.style.removeProperty(`right`);
5351
5334
  }
5352
- return acc;
5353
- },
5354
- []
5355
- );
5356
- const beginItem = spineItemsVisible[0] ?? fallbackSpineItem;
5357
- const endItem = spineItemsVisible[spineItemsVisible.length - 1] ?? beginItem;
5358
- if (!beginItem || !endItem) return void 0;
5359
- const beginItemIndex = spineItemsManager.getSpineItemIndex(beginItem);
5360
- const endItemIndex = spineItemsManager.getSpineItemIndex(endItem);
5361
- return {
5362
- beginIndex: beginItemIndex ?? 0,
5363
- endIndex: endItemIndex ?? 0
5364
- };
5365
- };
5366
- const getSpinePositionFromSpineItemPosition = ({
5367
- spineItemPosition,
5368
- itemLayout: { left, top }
5369
- }) => {
5370
- return {
5371
- x: left + spineItemPosition.x,
5372
- y: top + spineItemPosition.y
5373
- };
5374
- };
5375
- const getSpineInfoFromAbsolutePageIndex = ({
5376
- absolutePageIndex,
5377
- spineLayout,
5378
- spineItemsManager,
5379
- context,
5380
- settings
5381
- }) => {
5382
- const items = spineItemsManager.items;
5383
- const { found, currentAbsolutePage } = items.reduce(
5384
- (acc, item) => {
5385
- if (acc.found) return acc;
5386
- const itemLayout = spineLayout.getAbsolutePositionOf(item);
5387
- const numberOfPages = getSpineItemNumberOfPages({
5388
- isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5389
- itemHeight: itemLayout.height,
5390
- itemWidth: itemLayout.width,
5391
- context,
5392
- settings
5393
- });
5394
- const possiblePageIndex = absolutePageIndex - acc.currentAbsolutePage;
5395
- const currentAbsolutePage2 = acc.currentAbsolutePage + numberOfPages;
5396
- if (possiblePageIndex <= numberOfPages - 1) {
5397
- return {
5398
- ...acc,
5399
- currentAbsolutePage: currentAbsolutePage2,
5400
- found: { item, pageIndex: possiblePageIndex }
5401
- };
5335
+ if (left !== void 0) {
5336
+ this.containerElement.style.left = `${left}px`;
5337
+ } else {
5338
+ this.containerElement.style.removeProperty(`left`);
5339
+ }
5340
+ if (top !== void 0) {
5341
+ this.containerElement.style.top = `${top}px`;
5342
+ } else {
5343
+ this.containerElement.style.removeProperty(`top`);
5402
5344
  }
5403
- return {
5404
- ...acc,
5405
- currentAbsolutePage: currentAbsolutePage2
5406
- };
5407
- },
5408
- { currentAbsolutePage: 0 }
5409
- );
5410
- if (found) {
5411
- return {
5412
- spineItem: found.item,
5413
- pageIndex: found.pageIndex,
5414
- itemIndex: spineItemsManager.getSpineItemIndex(found.item) ?? 0,
5415
- currentAbsolutePage
5416
5345
  };
5417
- }
5418
- return void 0;
5419
- };
5420
- const getAbsolutePageIndexFromPageIndex = ({
5421
- pageIndex,
5422
- spineItemOrId,
5423
- spineLayout,
5424
- spineItemsManager,
5425
- context,
5426
- settings
5427
- }) => {
5428
- const items = spineItemsManager.items;
5429
- const spineItem = spineItemsManager.get(spineItemOrId);
5430
- if (!spineItem) return void 0;
5431
- const { currentAbsolutePage } = items.reduce(
5432
- (acc, item) => {
5433
- if (acc.found) return acc;
5434
- const itemLayout = spineLayout.getAbsolutePositionOf(item);
5435
- const numberOfPages = getSpineItemNumberOfPages({
5436
- isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5437
- itemHeight: itemLayout.height,
5438
- itemWidth: itemLayout.width,
5439
- context,
5440
- settings
5441
- });
5442
- if (spineItem === item) {
5443
- if (pageIndex <= numberOfPages - 1) {
5444
- return {
5445
- currentAbsolutePage: acc.currentAbsolutePage + pageIndex,
5446
- found: true
5447
- };
5346
+ this.getBoundingRectOfElementFromSelector = (selector) => {
5347
+ var _a2, _b2, _c, _d, _e;
5348
+ const frameElement = (_a2 = this.renderer.layers[0]) == null ? void 0 : _a2.element;
5349
+ if (frameElement && frameElement instanceof HTMLIFrameElement && selector) {
5350
+ if (selector.startsWith(`#`)) {
5351
+ return (_c = (_b2 = frameElement.contentDocument) == null ? void 0 : _b2.getElementById(selector.replace(`#`, ``))) == null ? void 0 : _c.getBoundingClientRect();
5448
5352
  }
5353
+ return (_e = (_d = frameElement.contentDocument) == null ? void 0 : _d.querySelector(selector)) == null ? void 0 : _e.getBoundingClientRect();
5449
5354
  }
5450
- return {
5451
- ...acc,
5452
- currentAbsolutePage: acc.currentAbsolutePage + numberOfPages
5453
- };
5454
- },
5455
- { currentAbsolutePage: 0, found: false }
5456
- );
5457
- return currentAbsolutePage;
5458
- };
5459
- const createSpineLocator = ({
5460
- spineItemsManager,
5461
- context,
5462
- spineItemLocator,
5463
- settings,
5464
- spineLayout
5465
- }) => {
5466
- const getSpineItemPositionFromSpinePosition = Report.measurePerformance(
5467
- `getSpineItemPositionFromSpinePosition`,
5468
- 10,
5469
- (position, spineItem) => {
5470
- const { left, top } = spineLayout.getAbsolutePositionOf(spineItem);
5471
- return {
5472
- /**
5473
- * when using spread the item could be on the right and therefore will be negative
5474
- * @example
5475
- * 400 (position = viewport), page of 200
5476
- * 400 - 600 = -200.
5477
- * However we can assume we are at 0, because we in fact can see the beginning of the item
5478
- */
5479
- x: Math.max(position.x - left, 0),
5480
- y: Math.max(position.y - top, 0)
5481
- };
5482
- },
5483
- { disable: true }
5484
- );
5485
- const getSpinePositionFromSpineItem = (spineItem) => {
5486
- return getSpinePositionFromSpineItemPosition({
5487
- spineItemPosition: { x: 0, y: 0 },
5488
- itemLayout: spineLayout.getAbsolutePositionOf(spineItem)
5489
- });
5490
- };
5491
- const getSpineItemFromIframe = (iframe) => {
5492
- return spineItemsManager.items.find((item) => {
5493
- var _a;
5494
- return ((_a = item.renderer.layers[0]) == null ? void 0 : _a.element) === iframe;
5495
- });
5496
- };
5497
- const getSpineItemPageIndexFromNode = (node, offset, spineItemOrIndex) => {
5498
- if (typeof spineItemOrIndex === `number`) {
5499
- const spineItem = spineItemsManager.get(spineItemOrIndex);
5500
- return spineItem ? spineItemLocator.getSpineItemPageIndexFromNode(
5501
- node,
5502
- offset || 0,
5503
- spineItem
5504
- ) : void 0;
5505
- }
5506
- return spineItemLocator.getSpineItemPageIndexFromNode(
5507
- node,
5508
- offset || 0,
5509
- spineItemOrIndex
5510
- );
5511
- };
5512
- const getVisiblePagesFromViewportPosition = ({
5513
- position,
5514
- threshold,
5515
- spineItem,
5516
- restrictToScreen
5517
- }) => {
5518
- const { height, width } = spineItem.getElementDimensions();
5519
- const numberOfPages = spineItemLocator.getSpineItemNumberOfPages({
5520
- isUsingVerticalWriting: !!spineItem.isUsingVerticalWriting(),
5521
- itemHeight: height,
5522
- itemWidth: width
5523
- });
5524
- const pages = Array.from(Array(numberOfPages)).map((_, index) => {
5525
- const spineItemPosition = spineItemLocator.getSpineItemPositionFromPageIndex({
5526
- pageIndex: index,
5527
- isUsingVerticalWriting: !!spineItem.isUsingVerticalWriting(),
5528
- itemLayout: spineItem.getElementDimensions()
5529
- });
5530
- const spinePosition = getSpinePositionFromSpineItemPosition({
5531
- spineItemPosition,
5532
- itemLayout: spineLayout.getAbsolutePositionOf(spineItem)
5355
+ };
5356
+ this.layout = ({
5357
+ blankPagePosition,
5358
+ minimumWidth,
5359
+ spreadPosition
5360
+ }) => {
5361
+ this.hookManager.execute(`item.onBeforeLayout`, void 0, {
5362
+ blankPagePosition,
5363
+ item: this.item,
5364
+ minimumWidth
5533
5365
  });
5534
- return {
5535
- index,
5536
- absolutePosition: {
5537
- width: context.getPageSize().width,
5538
- height: context.getPageSize().height,
5539
- left: spinePosition.x,
5540
- top: spinePosition.y,
5541
- bottom: spinePosition.y + context.getPageSize().height,
5542
- right: spinePosition.x + context.getPageSize().width
5543
- }
5366
+ const { height, width } = this.renderer.layout({
5367
+ blankPagePosition,
5368
+ minPageSpread: minimumWidth / this.context.getPageSize().width,
5369
+ spreadPosition
5370
+ }) ?? { width: 0, height: 0 };
5371
+ const minHeight = Math.max(height, this.context.getPageSize().height);
5372
+ const minWidth = Math.max(width, minimumWidth);
5373
+ this.containerElement.style.width = `${minWidth}px`;
5374
+ this.containerElement.style.height = `${minHeight}px`;
5375
+ this.hookManager.execute(`item.onAfterLayout`, void 0, {
5376
+ blankPagePosition,
5377
+ item: this.item,
5378
+ minimumWidth
5379
+ });
5380
+ return { width: minWidth, height: minHeight };
5381
+ };
5382
+ this.load = () => this.renderer.load();
5383
+ this.unload = () => {
5384
+ this.renderer.unload();
5385
+ };
5386
+ this.getElementDimensions = () => {
5387
+ const rect = this.containerElement.getBoundingClientRect();
5388
+ const normalizedValues = {
5389
+ ...rect,
5390
+ // we want to round to first decimal because it's possible to have half pixel
5391
+ // however browser engine can also gives back x.yyyy based on their precision
5392
+ width: Math.round(rect.width * 10) / 10,
5393
+ height: Math.round(rect.height * 10) / 10
5544
5394
  };
5545
- });
5546
- const pagesVisible = pages.reduce(
5547
- (acc, { absolutePosition, index }) => {
5548
- const { visible } = getItemVisibilityForPosition({
5549
- context,
5550
- viewportPosition: position,
5551
- restrictToScreen,
5552
- threshold,
5553
- itemPosition: absolutePosition
5554
- });
5555
- if (visible) {
5556
- return [...acc, index];
5557
- }
5558
- return acc;
5559
- },
5560
- []
5561
- );
5562
- const beginPageIndex = pagesVisible[0];
5563
- const endPageIndex = pagesVisible[pagesVisible.length - 1] ?? beginPageIndex;
5564
- if (beginPageIndex === void 0 || endPageIndex === void 0)
5565
- return void 0;
5566
- return {
5567
- beginPageIndex,
5568
- endPageIndex
5395
+ return normalizedValues;
5569
5396
  };
5570
- };
5571
- const isPositionWithinSpineItem = (position, spineItem) => {
5572
- const { bottom, left, right, top } = spineLayout.getAbsolutePositionOf(spineItem);
5573
- return position.x >= left && position.x <= right && position.y <= bottom && position.y >= top;
5574
- };
5575
- const getSafeSpineItemPositionFromUnsafeSpineItemPosition = (unsafePosition, spineItem) => {
5576
- const { height, width } = spineLayout.getAbsolutePositionOf(spineItem);
5577
- return {
5578
- x: Math.min(Math.max(0, unsafePosition.x), width),
5579
- y: Math.min(Math.max(0, unsafePosition.y), height)
5397
+ this.destroy = () => {
5398
+ this.destroySubject$.next();
5399
+ this.containerElement.remove();
5400
+ this.destroySubject$.complete();
5401
+ this.renderer.destroy();
5580
5402
  };
5581
- };
5582
- return {
5583
- getSpinePositionFromSpineItemPosition: ({
5584
- spineItem,
5585
- spineItemPosition
5586
- }) => {
5587
- const itemLayout = spineLayout.getAbsolutePositionOf(spineItem);
5588
- return getSpinePositionFromSpineItemPosition({
5589
- itemLayout,
5590
- spineItemPosition
5591
- });
5592
- },
5593
- getAbsolutePageIndexFromPageIndex: (params) => getAbsolutePageIndexFromPageIndex({
5594
- ...params,
5595
- context,
5596
- settings,
5597
- spineItemsManager,
5598
- spineLayout
5599
- }),
5600
- getSpineInfoFromAbsolutePageIndex: (params) => getSpineInfoFromAbsolutePageIndex({
5601
- ...params,
5602
- context,
5603
- settings,
5604
- spineItemsManager,
5605
- spineLayout
5606
- }),
5607
- getSpinePositionFromSpineItem,
5608
- getSpineItemPositionFromSpinePosition,
5609
- getSpineItemFromPosition: (position) => getSpineItemFromPosition({
5610
- position,
5611
- settings,
5612
- spineItemsManager,
5613
- spineLayout
5614
- }),
5615
- getSpineItemFromIframe,
5616
- getSpineItemPageIndexFromNode,
5617
- getVisibleSpineItemsFromPosition: (params) => getVisibleSpineItemsFromPosition({
5403
+ this.isUsingVerticalWriting = () => {
5404
+ var _a2;
5405
+ return !!((_a2 = this.renderer.writingMode) == null ? void 0 : _a2.startsWith(`vertical`));
5406
+ };
5407
+ this.destroySubject$ = new Subject();
5408
+ this.containerElement = createContainerElement(
5409
+ parentElement,
5410
+ item,
5411
+ hookManager
5412
+ );
5413
+ parentElement.appendChild(this.containerElement);
5414
+ const rendererFactory = (_b = (_a = this.settings.values).getRenderer) == null ? void 0 : _b.call(_a, item);
5415
+ this.resourcesHandler = new ResourceHandler(item, this.settings);
5416
+ const rendererParams = {
5618
5417
  context,
5619
5418
  settings,
5620
- spineItemsManager,
5621
- spineLayout,
5622
- ...params
5623
- }),
5624
- getVisiblePagesFromViewportPosition,
5625
- isPositionWithinSpineItem,
5626
- spineItemLocator,
5627
- getSafeSpineItemPositionFromUnsafeSpineItemPosition
5628
- };
5629
- };
5630
- const loadItems = ({
5631
- spineItemsManager,
5632
- settings
5633
- }) => (stream) => stream.pipe(
5634
- tap$1(([beginIndex, endIndex]) => {
5635
- const { numberOfAdjacentSpineItemToPreLoad } = settings.values;
5636
- const leftMaximumIndex = beginIndex - numberOfAdjacentSpineItemToPreLoad;
5637
- const rightMaximumIndex = endIndex + numberOfAdjacentSpineItemToPreLoad;
5638
- spineItemsManager.items.forEach((orderedSpineItem, index) => {
5639
- const isWithinRange = index >= leftMaximumIndex && index <= rightMaximumIndex;
5640
- if (isWithinRange) {
5641
- orderedSpineItem.load();
5642
- } else {
5643
- orderedSpineItem.unload();
5644
- }
5645
- });
5646
- })
5647
- );
5648
- const mapToItemsToLoad = ({ spineLocator }) => (stream) => stream.pipe(
5649
- map$1((position) => {
5650
- const { beginIndex = 0, endIndex = 0 } = spineLocator.getVisibleSpineItemsFromPosition({
5651
- position,
5652
- threshold: 0
5653
- }) || {};
5654
- return [beginIndex, endIndex];
5655
- })
5656
- );
5657
- class SpineItemsLoader extends DestroyableClass {
5658
- constructor(context, spineItemsManager, spineLocator, settings, spineLayout) {
5659
- super();
5660
- this.context = context;
5661
- this.spineItemsManager = spineItemsManager;
5662
- this.spineLocator = spineLocator;
5663
- this.settings = settings;
5664
- this.spineLayout = spineLayout;
5665
- const navigationUpdate$ = this.context.bridgeEvent.navigation$;
5666
- const layoutHasChanged$ = this.spineLayout.layout$.pipe(
5667
- filter$1(({ hasChanged }) => hasChanged)
5419
+ hookManager,
5420
+ item,
5421
+ containerElement: this.containerElement,
5422
+ resourcesHandler: this.resourcesHandler
5423
+ };
5424
+ this.renderer = rendererFactory ? rendererFactory(rendererParams) : new DefaultRenderer(rendererParams);
5425
+ const contentLayoutChange$ = merge(
5426
+ this.unloaded$.pipe(map(() => ({ isFirstLayout: false }))),
5427
+ this.ready$.pipe(map(() => ({ isFirstLayout: true })))
5668
5428
  );
5669
- const numberOfAdjacentSpineItemToPreLoad$ = settings.values$.pipe(
5670
- map$1(
5671
- ({ numberOfAdjacentSpineItemToPreLoad }) => numberOfAdjacentSpineItemToPreLoad
5672
- ),
5673
- skip$1(1),
5674
- distinctUntilChanged$1()
5429
+ this.contentLayout$ = contentLayoutChange$.pipe(
5430
+ withLatestFrom$1(this.isReady$),
5431
+ map(([data, isReady]) => ({
5432
+ isFirstLayout: data.isFirstLayout,
5433
+ isReady
5434
+ }))
5675
5435
  );
5676
- const loadSpineItems$ = merge(
5677
- navigationUpdate$,
5678
- layoutHasChanged$,
5679
- numberOfAdjacentSpineItemToPreLoad$
5680
- ).pipe(
5681
- // this can be changed by whatever we want and SHOULD not break navigation.
5682
- // Ideally loading faster is better but loading too close to user navigating can
5683
- // be dangerous.
5684
- debounceTime$1(100, animationFrameScheduler),
5685
- waitForSwitch(this.context.bridgeEvent.viewportFree$),
5686
- withLatestFrom(this.context.bridgeEvent.navigation$),
5687
- map$1(([, navigation]) => navigation.position),
5688
- mapToItemsToLoad({ spineLocator }),
5689
- loadItems({ spineItemsManager, settings })
5436
+ }
5437
+ get element() {
5438
+ return this.containerElement;
5439
+ }
5440
+ /**
5441
+ * @important
5442
+ * Do not use this value for layout and navigation. It will be in possible conflict
5443
+ * with the global reading direction. A book should not mix them anyway. A page can have
5444
+ * a different reading direction for style reason but it should be in theory pre-paginated.
5445
+ * For example an english page in a japanese manga. That's expected and will
5446
+ * be confined to a single page.
5447
+ */
5448
+ get readingDirection() {
5449
+ return this.renderer.readingDirection;
5450
+ }
5451
+ get isReady() {
5452
+ return this.renderer.state$.getValue() === "ready";
5453
+ }
5454
+ get ready$() {
5455
+ return this.renderer.state$.pipe(
5456
+ distinctUntilChanged(),
5457
+ filter((state) => state === "ready")
5690
5458
  );
5691
- loadSpineItems$.pipe(takeUntil(this.destroy$)).subscribe();
5692
5459
  }
5693
- }
5694
- class SpineItemsObserver extends DestroyableClass {
5695
- constructor(spineItemsManager, spineLocator) {
5696
- super();
5697
- this.spineItemsManager = spineItemsManager;
5698
- this.spineLocator = spineLocator;
5699
- this.itemIsReady$ = this.spineItemsManager.items$.pipe(
5700
- switchMap((items) => {
5701
- const itemsIsReady$ = items.map(
5702
- (item) => item.isReady$.pipe(map$1((isReady) => ({ item, isReady })))
5703
- );
5704
- return merge(...itemsIsReady$);
5705
- }),
5706
- share()
5460
+ get loaded$() {
5461
+ return this.renderer.state$.pipe(
5462
+ distinctUntilChanged(),
5463
+ filter((state) => state === "loaded")
5707
5464
  );
5708
- this.itemResize$ = this.spineItemsManager.items$.pipe(
5709
- switchMap((items) => {
5710
- const resize$ = items.map(
5711
- (item) => observeResize(item.element).pipe(
5712
- map$1((entries) => ({ entries, item }))
5713
- )
5714
- );
5715
- return merge(...resize$);
5716
- }),
5717
- share()
5718
- );
5719
- this.itemLoaded$ = this.spineItemsManager.items$.pipe(
5720
- switchMap((items) => {
5721
- const itemsIsReady$ = items.map(
5722
- (item) => item.loaded$.pipe(map$1(() => item))
5723
- );
5724
- return merge(...itemsIsReady$);
5725
- })
5465
+ }
5466
+ get unloaded$() {
5467
+ return this.renderer.state$.pipe(
5468
+ distinctUntilChanged(),
5469
+ filter((state) => state !== "idle"),
5470
+ switchMap$1(
5471
+ () => this.renderer.state$.pipe(
5472
+ filter((state) => state === `idle`),
5473
+ first()
5474
+ )
5475
+ )
5726
5476
  );
5727
5477
  }
5478
+ get isReady$() {
5479
+ return this.renderer.state$.pipe(map((state) => state === "ready"));
5480
+ }
5481
+ /**
5482
+ * Helper that will inject CSS into the document frame.
5483
+ *
5484
+ * @important
5485
+ * The document needs to be detected as a frame.
5486
+ */
5487
+ upsertCSS(id, style, prepend) {
5488
+ this.renderer.layers.forEach((layer) => {
5489
+ if (layer.element instanceof HTMLIFrameElement) {
5490
+ upsertCSS(layer.element, id, style, prepend);
5491
+ }
5492
+ });
5493
+ }
5728
5494
  }
5729
- const convertSpinePositionToLayoutPosition = ({
5730
- position,
5731
- pageSize
5732
- }) => {
5733
- return {
5734
- x: position.x,
5735
- y: position.y,
5736
- left: position.x,
5737
- top: position.y,
5738
- width: pageSize.width,
5739
- height: pageSize.height,
5740
- bottom: position.y + pageSize.height,
5741
- right: position.x + pageSize.width
5742
- };
5495
+ const createContainerElement = (containerElement, item, hookManager) => {
5496
+ const element = containerElement.ownerDocument.createElement(`div`);
5497
+ element.classList.add(`spineItem`);
5498
+ element.classList.add(`spineItem-${item.renditionLayout}`);
5499
+ element.style.cssText = `
5500
+ position: absolute;
5501
+ overflow: hidden;
5502
+ `;
5503
+ hookManager.execute("item.onBeforeContainerCreated", void 0, { element });
5504
+ return element;
5743
5505
  };
5744
- const NAMESPACE = `SpineLayout`;
5745
- class SpineLayout extends DestroyableClass {
5746
- constructor(spineItemsManager, context, settings) {
5506
+ class SpineItemsManager extends DestroyableClass {
5507
+ constructor(context, settings) {
5747
5508
  super();
5748
- this.spineItemsManager = spineItemsManager;
5749
5509
  this.context = context;
5750
5510
  this.settings = settings;
5751
- this.itemLayoutInformation = [];
5752
- this.layoutSubject = new Subject();
5753
- spineItemsManager.items$.pipe(
5754
- switchMap((items) => {
5755
- const itemsLayout$ = items.map(
5756
- (spineItem) => spineItem.contentLayout$.pipe(
5757
- tap$1(() => {
5758
- this.layout();
5759
- })
5760
- )
5761
- );
5762
- const writingModeUpdate$ = items.map(
5763
- (spineItem) => spineItem.loaded$.pipe(
5764
- tap$1(() => {
5765
- if (spineItem.isUsingVerticalWriting()) {
5766
- this.context.update({
5767
- hasVerticalWriting: true
5768
- });
5769
- } else {
5770
- this.context.update({
5771
- hasVerticalWriting: false
5772
- });
5773
- }
5774
- })
5775
- )
5776
- );
5777
- return merge(...itemsLayout$, ...writingModeUpdate$);
5778
- })
5779
- ).pipe(takeUntil(this.destroy$)).subscribe();
5780
- this.layout$ = this.layoutSubject.pipe(
5781
- map$1((hasChanged) => ({ hasChanged })),
5782
- startWith$1({ hasChanged: true }),
5783
- map$1(({ hasChanged }) => {
5784
- const items = spineItemsManager.items;
5785
- const spineItemsPagesAbsolutePositions = items.map((item) => {
5786
- const itemLayout = this.getAbsolutePositionOf(item);
5787
- const numberOfPages = getSpineItemNumberOfPages({
5788
- isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5789
- itemHeight: itemLayout.height,
5790
- itemWidth: itemLayout.width,
5791
- context,
5792
- settings
5793
- });
5794
- const pages2 = new Array(numberOfPages).fill(void 0);
5795
- return pages2.map(
5796
- (_, pageIndex) => convertSpinePositionToLayoutPosition({
5797
- pageSize: this.context.getPageSize(),
5798
- position: getSpinePositionFromSpineItemPosition({
5799
- itemLayout,
5800
- spineItemPosition: getSpineItemPositionFromPageIndex({
5801
- isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5802
- itemLayout,
5803
- pageIndex,
5804
- context
5805
- })
5806
- })
5807
- })
5808
- );
5809
- });
5810
- const pages = spineItemsPagesAbsolutePositions.reduce(
5811
- (acc, itemPages, itemIndex) => {
5812
- const itemPagesInfo = itemPages.map(
5813
- (absolutePosition, pageIndex) => ({
5814
- itemIndex,
5815
- absolutePageIndex: itemIndex + pageIndex,
5816
- absolutePosition
5817
- })
5818
- );
5819
- return [...acc, ...itemPagesInfo];
5820
- },
5821
- []
5822
- );
5823
- return {
5824
- hasChanged,
5825
- spineItemsAbsolutePositions: items.map(
5826
- (item) => this.getAbsolutePositionOf(item)
5827
- ),
5828
- spineItemsPagesAbsolutePositions,
5829
- pages
5830
- };
5831
- }),
5832
- shareReplay$1(1)
5833
- );
5511
+ this.orderedSpineItemsSubject = new BehaviorSubject([]);
5512
+ this.items$ = this.orderedSpineItemsSubject.asObservable();
5513
+ }
5514
+ get(indexOrId) {
5515
+ if (typeof indexOrId === `number`) {
5516
+ return this.orderedSpineItemsSubject.value[indexOrId];
5517
+ }
5518
+ if (typeof indexOrId === `string`) {
5519
+ return this.orderedSpineItemsSubject.value.find(
5520
+ ({ item }) => item.id === indexOrId
5521
+ );
5522
+ }
5523
+ return indexOrId;
5524
+ }
5525
+ comparePositionOf(toCompare, withItem) {
5526
+ const toCompareIndex = this.getSpineItemIndex(toCompare) ?? 0;
5527
+ const withIndex = this.getSpineItemIndex(withItem) ?? 0;
5528
+ return toCompareIndex > withIndex ? `after` : toCompareIndex === withIndex ? `same` : `before`;
5529
+ }
5530
+ getSpineItemIndex(spineItemOrId) {
5531
+ const spineItem = spineItemOrId instanceof SpineItem ? spineItemOrId : this.get(spineItemOrId);
5532
+ if (!spineItem) return void 0;
5533
+ const index = this.orderedSpineItemsSubject.value.indexOf(spineItem);
5534
+ return index < 0 ? void 0 : index;
5535
+ }
5536
+ addMany(spineItems) {
5537
+ this.orderedSpineItemsSubject.next([
5538
+ ...this.orderedSpineItemsSubject.getValue(),
5539
+ ...spineItems
5540
+ ]);
5541
+ }
5542
+ // @todo move
5543
+ getSpineItemFromCfi(cfi) {
5544
+ const { itemIndex } = parseCfi(cfi);
5545
+ if (itemIndex !== void 0) {
5546
+ return this.get(itemIndex);
5547
+ }
5548
+ return void 0;
5549
+ }
5550
+ get items() {
5551
+ return this.orderedSpineItemsSubject.value;
5834
5552
  }
5835
5553
  /**
5836
- * @todo
5837
- * move this logic to the spine
5838
- *
5839
- * @todo
5840
- * make sure to check how many times it is being called and try to reduce number of layouts
5841
- * it is called eery time an item is being unload (which can adds up quickly for big books)
5554
+ * @todo handle reload, remove subscription to each items etc. See add()
5842
5555
  */
5843
- layout() {
5844
- const manifest = this.context.manifest;
5845
- const newItemLayoutInformation = [];
5846
- const isGloballyPrePaginated = (manifest == null ? void 0 : manifest.renditionLayout) === `pre-paginated`;
5847
- this.spineItemsManager.items.reduce(
5848
- ({ horizontalOffset, verticalOffset }, item, index) => {
5849
- let minimumWidth = this.context.getPageSize().width;
5850
- let blankPagePosition = `none`;
5851
- const itemStartOnNewScreen = horizontalOffset % this.context.state.visibleAreaRect.width === 0;
5852
- const isLastItem = index === this.spineItemsManager.items.length - 1;
5853
- if (this.context.state.isUsingSpreadMode) {
5854
- if (!isGloballyPrePaginated && item.item.renditionLayout === `reflowable` && !isLastItem) {
5855
- minimumWidth = this.context.getPageSize().width * 2;
5856
- }
5857
- if (!isGloballyPrePaginated && item.item.renditionLayout === `reflowable` && isLastItem && itemStartOnNewScreen) {
5858
- minimumWidth = this.context.getPageSize().width * 2;
5859
- }
5860
- const lastItemStartOnNewScreenInAPrepaginatedBook = itemStartOnNewScreen && isLastItem && isGloballyPrePaginated;
5861
- if (item.item.pageSpreadRight && itemStartOnNewScreen && !this.context.isRTL()) {
5862
- blankPagePosition = `before`;
5863
- minimumWidth = this.context.getPageSize().width * 2;
5864
- } else if (item.item.pageSpreadLeft && itemStartOnNewScreen && this.context.isRTL()) {
5865
- blankPagePosition = `before`;
5866
- minimumWidth = this.context.getPageSize().width * 2;
5867
- } else if (lastItemStartOnNewScreenInAPrepaginatedBook) {
5868
- if (this.context.isRTL()) {
5869
- blankPagePosition = `before`;
5870
- } else {
5871
- blankPagePosition = `after`;
5872
- }
5873
- minimumWidth = this.context.getPageSize().width * 2;
5874
- }
5875
- }
5876
- const { width, height } = item.layout({
5877
- minimumWidth,
5878
- blankPagePosition,
5879
- spreadPosition: this.context.state.isUsingSpreadMode ? itemStartOnNewScreen ? this.context.isRTL() ? `right` : `left` : this.context.isRTL() ? `left` : `right` : `none`
5880
- });
5881
- if (this.settings.values.computedPageTurnDirection === `vertical`) {
5882
- const currentValidEdgeYForVerticalPositioning = itemStartOnNewScreen ? verticalOffset : verticalOffset - this.context.state.visibleAreaRect.height;
5883
- const currentValidEdgeXForVerticalPositioning = itemStartOnNewScreen ? 0 : horizontalOffset;
5884
- if (this.context.isRTL()) {
5885
- item.adjustPositionOfElement({
5886
- top: currentValidEdgeYForVerticalPositioning,
5887
- left: currentValidEdgeXForVerticalPositioning
5888
- });
5889
- } else {
5890
- item.adjustPositionOfElement({
5891
- top: currentValidEdgeYForVerticalPositioning,
5892
- left: currentValidEdgeXForVerticalPositioning
5893
- });
5894
- }
5895
- const newEdgeX = width + currentValidEdgeXForVerticalPositioning;
5896
- const newEdgeY = height + currentValidEdgeYForVerticalPositioning;
5897
- newItemLayoutInformation.push({
5898
- left: currentValidEdgeXForVerticalPositioning,
5899
- right: newEdgeX,
5900
- top: currentValidEdgeYForVerticalPositioning,
5901
- bottom: newEdgeY,
5902
- height,
5903
- width,
5904
- x: currentValidEdgeXForVerticalPositioning,
5905
- y: currentValidEdgeYForVerticalPositioning
5906
- });
5907
- return {
5908
- horizontalOffset: newEdgeX,
5909
- verticalOffset: newEdgeY
5910
- };
5911
- }
5912
- item.adjustPositionOfElement(
5913
- this.context.isRTL() ? { right: horizontalOffset, top: 0 } : { left: horizontalOffset, top: 0 }
5914
- );
5915
- const left = this.context.isRTL() ? this.context.state.visibleAreaRect.width - horizontalOffset - width : horizontalOffset;
5916
- newItemLayoutInformation.push({
5917
- right: this.context.isRTL() ? this.context.state.visibleAreaRect.width - horizontalOffset : horizontalOffset + width,
5918
- left,
5919
- x: left,
5920
- top: verticalOffset,
5921
- bottom: height,
5922
- height,
5923
- width,
5924
- y: verticalOffset
5925
- });
5926
- return {
5927
- horizontalOffset: horizontalOffset + width,
5928
- verticalOffset: 0
5929
- };
5930
- },
5931
- { horizontalOffset: 0, verticalOffset: 0 }
5932
- );
5933
- const hasLayoutChanges = this.itemLayoutInformation.length !== newItemLayoutInformation.length || this.itemLayoutInformation.some(
5934
- (old, index) => !isShallowEqual(old, newItemLayoutInformation[index])
5935
- );
5936
- this.itemLayoutInformation = newItemLayoutInformation;
5937
- Report.log(NAMESPACE, `layout`, {
5938
- hasLayoutChanges,
5939
- itemLayoutInformation: this.itemLayoutInformation
5940
- });
5941
- this.layoutSubject.next(hasLayoutChanges);
5942
- }
5943
- /**
5944
- * It's important to not use x,y since we need the absolute position of each element. Otherwise x,y would be relative to
5945
- * current window (viewport).
5946
- */
5947
- getAbsolutePositionOf(spineItemOrIndex) {
5948
- const fallback = {
5949
- left: 0,
5950
- right: 0,
5951
- top: 0,
5952
- bottom: 0,
5953
- width: 0,
5954
- height: 0,
5955
- x: 0,
5956
- y: 0
5957
- };
5958
- const spineItem = this.spineItemsManager.get(spineItemOrIndex);
5959
- const indexOfItem = spineItem ? this.spineItemsManager.items.indexOf(spineItem) : -1;
5960
- const layoutInformation = this.itemLayoutInformation[indexOfItem];
5961
- return layoutInformation || fallback;
5962
- }
5963
- destroy() {
5964
- super.destroy();
5965
- this.layoutSubject.complete();
5966
- }
5967
- }
5968
- class DocumentRenderer {
5969
- constructor(context, settings, hookManager, item, containerElement, resourcesHandler) {
5970
- this.context = context;
5971
- this.settings = settings;
5972
- this.hookManager = hookManager;
5973
- this.item = item;
5974
- this.containerElement = containerElement;
5975
- this.resourcesHandler = resourcesHandler;
5976
- this.stateSubject = new BehaviorSubject(`idle`);
5977
- this.triggerSubject = new Subject();
5978
- this.layers = [];
5979
- this.unload$ = this.triggerSubject.pipe(
5980
- withLatestFrom(this.stateSubject),
5981
- filter$1(
5982
- ([trigger, state]) => trigger === `unload` && state !== "idle" && state !== "unloading"
5983
- ),
5984
- map$1(() => void 0),
5985
- share()
5986
- );
5987
- this.load$ = this.triggerSubject.pipe(
5988
- withLatestFrom(this.stateSubject),
5989
- filter$1(
5990
- ([trigger, state]) => trigger === `load` && state !== "ready" && state !== "loaded" && state !== "loading"
5991
- ),
5992
- map$1(() => void 0),
5993
- share()
5994
- );
5995
- this.destroy$ = this.triggerSubject.pipe(
5996
- filter$1((trigger) => trigger === `destroy`)
5997
- );
5998
- this.load$.pipe(
5999
- switchMap(() => {
6000
- this.stateSubject.next(`loading`);
6001
- const createDocument$ = this.onCreateDocument().pipe(
6002
- endWith(null),
6003
- first$1()
6004
- );
6005
- return createDocument$.pipe(
6006
- tap$1(() => {
6007
- this.hookManager.execute(`item.onDocumentCreated`, this.item.id, {
6008
- itemId: this.item.id,
6009
- layers: this.layers
6010
- });
6011
- }),
6012
- switchMap(() => {
6013
- const loadDocument$ = this.onLoadDocument().pipe(
6014
- endWith(null),
6015
- first$1()
6016
- );
6017
- return loadDocument$;
6018
- }),
6019
- waitForSwitch(this.context.bridgeEvent.viewportFree$),
6020
- switchMap(() => {
6021
- const hookResults = this.hookManager.execute(`item.onDocumentLoad`, this.item.id, {
6022
- itemId: this.item.id,
6023
- layers: this.layers
6024
- }).filter(
6025
- (result) => result instanceof Observable
6026
- );
6027
- return combineLatest([of(null), ...hookResults]).pipe(first$1());
6028
- }),
6029
- tap$1(() => {
6030
- this.stateSubject.next(`loaded`);
6031
- this.stateSubject.next(`ready`);
6032
- }),
6033
- takeUntil(merge(this.destroy$, this.unload$))
6034
- );
6035
- })
6036
- ).subscribe();
6037
- this.unload$.pipe(
6038
- switchMap(() => {
6039
- this.stateSubject.next(`unloading`);
6040
- return this.context.bridgeEvent.viewportFree$.pipe(
6041
- first$1(),
6042
- tap$1(() => {
6043
- this.hookManager.destroy(`item.onDocumentLoad`, this.item.id);
6044
- }),
6045
- switchMap(() => {
6046
- const unload$ = this.onUnload().pipe(endWith(null), first$1());
6047
- return unload$;
6048
- }),
6049
- tap$1(() => {
6050
- this.stateSubject.next(`idle`);
6051
- }),
6052
- takeUntil(merge(this.destroy$, this.load$))
6053
- );
6054
- })
6055
- ).subscribe();
6056
- }
6057
- get state$() {
6058
- return this.stateSubject;
6059
- }
6060
- load() {
6061
- this.triggerSubject.next(`load`);
6062
- }
6063
- unload() {
6064
- this.triggerSubject.next(`unload`);
6065
- }
6066
- destroy() {
6067
- this.triggerSubject.next(`destroy`);
6068
- this.triggerSubject.complete();
6069
- this.stateSubject.complete();
6070
- }
6071
- get writingMode() {
6072
- return void 0;
6073
- }
6074
- get readingDirection() {
6075
- return void 0;
5556
+ destroyItems() {
5557
+ this.orderedSpineItemsSubject.value.forEach((item) => item.destroy());
6076
5558
  }
6077
5559
  }
6078
- class DefaultRenderer extends DocumentRenderer {
6079
- onUnload() {
6080
- return EMPTY;
6081
- }
6082
- onCreateDocument() {
6083
- return EMPTY;
5560
+ const getSpineItemFromPosition = ({
5561
+ position,
5562
+ spineItemsManager,
5563
+ spineLayout,
5564
+ settings
5565
+ }) => {
5566
+ const spineItem = spineItemsManager.items.find((item) => {
5567
+ const { left, right, bottom, top } = spineLayout.getAbsolutePositionOf(item);
5568
+ const isWithinXAxis = position.x >= left && position.x < right;
5569
+ if (settings.values.computedPageTurnDirection === `horizontal`) {
5570
+ return isWithinXAxis;
5571
+ } else {
5572
+ return isWithinXAxis && position.y >= top && position.y < bottom;
5573
+ }
5574
+ });
5575
+ if (position.x === 0 && !spineItem) {
5576
+ return spineItemsManager.items[0];
6084
5577
  }
6085
- onLoadDocument() {
6086
- return EMPTY;
5578
+ return spineItem;
5579
+ };
5580
+ const isItemVisibleByThresholdForPosition = ({
5581
+ itemHeight,
5582
+ itemWidth,
5583
+ visibleWidthOfItem,
5584
+ visibleHeightOfItem,
5585
+ threshold
5586
+ }) => {
5587
+ const visibleWidthRatioOfSpineItem = visibleWidthOfItem / itemWidth;
5588
+ const visibleHeightRatioOfSpineItem = visibleHeightOfItem / itemHeight;
5589
+ const isItemVisibleEnough = visibleWidthRatioOfSpineItem >= threshold && visibleHeightRatioOfSpineItem >= threshold;
5590
+ return isItemVisibleEnough;
5591
+ };
5592
+ const isItemVisibleOnScreenByThresholdForPosition = ({
5593
+ visibleWidthOfItem,
5594
+ visibleHeightOfItem,
5595
+ threshold,
5596
+ context
5597
+ }) => {
5598
+ const widthRatioOfSpaceTakenInScreen = visibleWidthOfItem / context.state.visibleAreaRect.width;
5599
+ const heightRatioOfSpaceTakenInScreen = visibleHeightOfItem / context.state.visibleAreaRect.height;
5600
+ const isItemVisibleEnoughOnScreen = heightRatioOfSpaceTakenInScreen >= threshold && widthRatioOfSpaceTakenInScreen >= threshold;
5601
+ return isItemVisibleEnoughOnScreen;
5602
+ };
5603
+ const getItemVisibilityForPosition = ({
5604
+ itemPosition: {
5605
+ bottom,
5606
+ left,
5607
+ right,
5608
+ top,
5609
+ width: itemWidth,
5610
+ height: itemHeight
5611
+ },
5612
+ threshold,
5613
+ viewportPosition,
5614
+ restrictToScreen,
5615
+ context
5616
+ }) => {
5617
+ const viewportLeft = viewportPosition.x;
5618
+ const viewportRight = viewportPosition.x + (context.state.visibleAreaRect.width - 1);
5619
+ const viewportTop = viewportPosition.y;
5620
+ const viewportBottom = Math.max(
5621
+ viewportPosition.y + (context.state.visibleAreaRect.height - 1),
5622
+ 0
5623
+ );
5624
+ const visibleWidthOfItem = Math.max(
5625
+ 0,
5626
+ Math.min(right, viewportRight) - Math.max(left, viewportLeft)
5627
+ );
5628
+ const visibleHeightOfItem = Math.max(
5629
+ 0,
5630
+ Math.min(bottom, viewportBottom) - Math.max(top, viewportTop)
5631
+ );
5632
+ const itemIsOnTheOuterEdge = visibleWidthOfItem <= 0 || visibleHeightOfItem <= 0;
5633
+ if (itemIsOnTheOuterEdge) return { visible: false };
5634
+ const isItemVisibleEnoughOnScreen = isItemVisibleOnScreenByThresholdForPosition({
5635
+ threshold,
5636
+ visibleHeightOfItem,
5637
+ visibleWidthOfItem,
5638
+ context
5639
+ });
5640
+ if (restrictToScreen) {
5641
+ return { visible: isItemVisibleEnoughOnScreen };
6087
5642
  }
6088
- layout() {
6089
- return void 0;
5643
+ const isItemVisibleEnough = isItemVisibleByThresholdForPosition({
5644
+ itemHeight,
5645
+ itemWidth,
5646
+ threshold,
5647
+ visibleHeightOfItem,
5648
+ visibleWidthOfItem
5649
+ });
5650
+ return {
5651
+ visible: isItemVisibleEnough || isItemVisibleEnoughOnScreen
5652
+ };
5653
+ };
5654
+ const getVisibleSpineItemsFromPosition = ({
5655
+ position,
5656
+ threshold,
5657
+ restrictToScreen,
5658
+ spineItemsManager,
5659
+ context,
5660
+ settings,
5661
+ spineLayout
5662
+ }) => {
5663
+ const fallbackSpineItem = getSpineItemFromPosition({
5664
+ position,
5665
+ settings,
5666
+ spineItemsManager,
5667
+ spineLayout
5668
+ }) || spineItemsManager.get(0);
5669
+ const spineItemsVisible = spineItemsManager.items.reduce(
5670
+ (acc, spineItem) => {
5671
+ const itemPosition = spineLayout.getAbsolutePositionOf(spineItem);
5672
+ const { visible } = getItemVisibilityForPosition({
5673
+ itemPosition,
5674
+ threshold,
5675
+ viewportPosition: position,
5676
+ restrictToScreen,
5677
+ context
5678
+ });
5679
+ if (visible) {
5680
+ return [...acc, spineItem];
5681
+ }
5682
+ return acc;
5683
+ },
5684
+ []
5685
+ );
5686
+ const beginItem = spineItemsVisible[0] ?? fallbackSpineItem;
5687
+ const endItem = spineItemsVisible[spineItemsVisible.length - 1] ?? beginItem;
5688
+ if (!beginItem || !endItem) return void 0;
5689
+ const beginItemIndex = spineItemsManager.getSpineItemIndex(beginItem);
5690
+ const endItemIndex = spineItemsManager.getSpineItemIndex(endItem);
5691
+ return {
5692
+ beginIndex: beginItemIndex ?? 0,
5693
+ endIndex: endItemIndex ?? 0
5694
+ };
5695
+ };
5696
+ const getSpinePositionFromSpineItemPosition = ({
5697
+ spineItemPosition,
5698
+ itemLayout: { left, top }
5699
+ }) => {
5700
+ return {
5701
+ x: left + spineItemPosition.x,
5702
+ y: top + spineItemPosition.y
5703
+ };
5704
+ };
5705
+ const getSpineInfoFromAbsolutePageIndex = ({
5706
+ absolutePageIndex,
5707
+ spineLayout,
5708
+ spineItemsManager,
5709
+ context,
5710
+ settings
5711
+ }) => {
5712
+ const items = spineItemsManager.items;
5713
+ const { found, currentAbsolutePage } = items.reduce(
5714
+ (acc, item) => {
5715
+ if (acc.found) return acc;
5716
+ const itemLayout = spineLayout.getAbsolutePositionOf(item);
5717
+ const numberOfPages = getSpineItemNumberOfPages({
5718
+ isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5719
+ itemHeight: itemLayout.height,
5720
+ itemWidth: itemLayout.width,
5721
+ context,
5722
+ settings
5723
+ });
5724
+ const possiblePageIndex = absolutePageIndex - acc.currentAbsolutePage;
5725
+ const currentAbsolutePage2 = acc.currentAbsolutePage + numberOfPages;
5726
+ if (possiblePageIndex <= numberOfPages - 1) {
5727
+ return {
5728
+ ...acc,
5729
+ currentAbsolutePage: currentAbsolutePage2,
5730
+ found: { item, pageIndex: possiblePageIndex }
5731
+ };
5732
+ }
5733
+ return {
5734
+ ...acc,
5735
+ currentAbsolutePage: currentAbsolutePage2
5736
+ };
5737
+ },
5738
+ { currentAbsolutePage: 0 }
5739
+ );
5740
+ if (found) {
5741
+ return {
5742
+ spineItem: found.item,
5743
+ pageIndex: found.pageIndex,
5744
+ itemIndex: spineItemsManager.getSpineItemIndex(found.item) ?? 0,
5745
+ currentAbsolutePage
5746
+ };
6090
5747
  }
6091
- }
6092
- const defaultGetResource = (item) => new URL(item.href);
6093
- class ResourceHandler {
6094
- constructor(item, settings) {
6095
- this.item = item;
5748
+ return void 0;
5749
+ };
5750
+ const getAbsolutePageIndexFromPageIndex = ({
5751
+ pageIndex,
5752
+ spineItemOrId,
5753
+ spineLayout,
5754
+ spineItemsManager,
5755
+ context,
5756
+ settings
5757
+ }) => {
5758
+ const items = spineItemsManager.items;
5759
+ const spineItem = spineItemsManager.get(spineItemOrId);
5760
+ if (!spineItem) return void 0;
5761
+ const { currentAbsolutePage } = items.reduce(
5762
+ (acc, item) => {
5763
+ if (acc.found) return acc;
5764
+ const itemLayout = spineLayout.getAbsolutePositionOf(item);
5765
+ const numberOfPages = getSpineItemNumberOfPages({
5766
+ isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
5767
+ itemHeight: itemLayout.height,
5768
+ itemWidth: itemLayout.width,
5769
+ context,
5770
+ settings
5771
+ });
5772
+ if (spineItem === item) {
5773
+ if (pageIndex <= numberOfPages - 1) {
5774
+ return {
5775
+ currentAbsolutePage: acc.currentAbsolutePage + pageIndex,
5776
+ found: true
5777
+ };
5778
+ }
5779
+ }
5780
+ return {
5781
+ ...acc,
5782
+ currentAbsolutePage: acc.currentAbsolutePage + numberOfPages
5783
+ };
5784
+ },
5785
+ { currentAbsolutePage: 0, found: false }
5786
+ );
5787
+ return currentAbsolutePage;
5788
+ };
5789
+ const createSpineLocator = ({
5790
+ spineItemsManager,
5791
+ context,
5792
+ spineItemLocator,
5793
+ settings,
5794
+ spineLayout
5795
+ }) => {
5796
+ const getSpineItemPositionFromSpinePosition = Report.measurePerformance(
5797
+ `getSpineItemPositionFromSpinePosition`,
5798
+ 10,
5799
+ (position, spineItem) => {
5800
+ const { left, top } = spineLayout.getAbsolutePositionOf(spineItem);
5801
+ return {
5802
+ /**
5803
+ * when using spread the item could be on the right and therefore will be negative
5804
+ * @example
5805
+ * 400 (position = viewport), page of 200
5806
+ * 400 - 600 = -200.
5807
+ * However we can assume we are at 0, because we in fact can see the beginning of the item
5808
+ */
5809
+ x: Math.max(position.x - left, 0),
5810
+ y: Math.max(position.y - top, 0)
5811
+ };
5812
+ },
5813
+ { disable: true }
5814
+ );
5815
+ const getSpinePositionFromSpineItem = (spineItem) => {
5816
+ return getSpinePositionFromSpineItemPosition({
5817
+ spineItemPosition: { x: 0, y: 0 },
5818
+ itemLayout: spineLayout.getAbsolutePositionOf(spineItem)
5819
+ });
5820
+ };
5821
+ const getSpineItemFromIframe = (iframe) => {
5822
+ return spineItemsManager.items.find((item) => {
5823
+ var _a;
5824
+ return ((_a = item.renderer.layers[0]) == null ? void 0 : _a.element) === iframe;
5825
+ });
5826
+ };
5827
+ const getSpineItemPageIndexFromNode = (node, offset, spineItemOrIndex) => {
5828
+ if (typeof spineItemOrIndex === `number`) {
5829
+ const spineItem = spineItemsManager.get(spineItemOrIndex);
5830
+ return spineItem ? spineItemLocator.getSpineItemPageIndexFromNode(
5831
+ node,
5832
+ offset || 0,
5833
+ spineItem
5834
+ ) : void 0;
5835
+ }
5836
+ return spineItemLocator.getSpineItemPageIndexFromNode(
5837
+ node,
5838
+ offset || 0,
5839
+ spineItemOrIndex
5840
+ );
5841
+ };
5842
+ const getVisiblePagesFromViewportPosition = ({
5843
+ position,
5844
+ threshold,
5845
+ spineItem,
5846
+ restrictToScreen
5847
+ }) => {
5848
+ const { height, width } = spineItem.getElementDimensions();
5849
+ const numberOfPages = spineItemLocator.getSpineItemNumberOfPages({
5850
+ isUsingVerticalWriting: !!spineItem.isUsingVerticalWriting(),
5851
+ itemHeight: height,
5852
+ itemWidth: width
5853
+ });
5854
+ const pages = Array.from(Array(numberOfPages)).map((_, index) => {
5855
+ const spineItemPosition = spineItemLocator.getSpineItemPositionFromPageIndex({
5856
+ pageIndex: index,
5857
+ isUsingVerticalWriting: !!spineItem.isUsingVerticalWriting(),
5858
+ itemLayout: spineItem.getElementDimensions()
5859
+ });
5860
+ const spinePosition = getSpinePositionFromSpineItemPosition({
5861
+ spineItemPosition,
5862
+ itemLayout: spineLayout.getAbsolutePositionOf(spineItem)
5863
+ });
5864
+ return {
5865
+ index,
5866
+ absolutePosition: {
5867
+ width: context.getPageSize().width,
5868
+ height: context.getPageSize().height,
5869
+ left: spinePosition.x,
5870
+ top: spinePosition.y,
5871
+ bottom: spinePosition.y + context.getPageSize().height,
5872
+ right: spinePosition.x + context.getPageSize().width
5873
+ }
5874
+ };
5875
+ });
5876
+ const pagesVisible = pages.reduce(
5877
+ (acc, { absolutePosition, index }) => {
5878
+ const { visible } = getItemVisibilityForPosition({
5879
+ context,
5880
+ viewportPosition: position,
5881
+ restrictToScreen,
5882
+ threshold,
5883
+ itemPosition: absolutePosition
5884
+ });
5885
+ if (visible) {
5886
+ return [...acc, index];
5887
+ }
5888
+ return acc;
5889
+ },
5890
+ []
5891
+ );
5892
+ const beginPageIndex = pagesVisible[0];
5893
+ const endPageIndex = pagesVisible[pagesVisible.length - 1] ?? beginPageIndex;
5894
+ if (beginPageIndex === void 0 || endPageIndex === void 0)
5895
+ return void 0;
5896
+ return {
5897
+ beginPageIndex,
5898
+ endPageIndex
5899
+ };
5900
+ };
5901
+ const isPositionWithinSpineItem = (position, spineItem) => {
5902
+ const { bottom, left, right, top } = spineLayout.getAbsolutePositionOf(spineItem);
5903
+ return position.x >= left && position.x <= right && position.y <= bottom && position.y >= top;
5904
+ };
5905
+ const getSafeSpineItemPositionFromUnsafeSpineItemPosition = (unsafePosition, spineItem) => {
5906
+ const { height, width } = spineLayout.getAbsolutePositionOf(spineItem);
5907
+ return {
5908
+ x: Math.min(Math.max(0, unsafePosition.x), width),
5909
+ y: Math.min(Math.max(0, unsafePosition.y), height)
5910
+ };
5911
+ };
5912
+ return {
5913
+ getSpinePositionFromSpineItemPosition: ({
5914
+ spineItem,
5915
+ spineItemPosition
5916
+ }) => {
5917
+ const itemLayout = spineLayout.getAbsolutePositionOf(spineItem);
5918
+ return getSpinePositionFromSpineItemPosition({
5919
+ itemLayout,
5920
+ spineItemPosition
5921
+ });
5922
+ },
5923
+ getAbsolutePageIndexFromPageIndex: (params) => getAbsolutePageIndexFromPageIndex({
5924
+ ...params,
5925
+ context,
5926
+ settings,
5927
+ spineItemsManager,
5928
+ spineLayout
5929
+ }),
5930
+ getSpineInfoFromAbsolutePageIndex: (params) => getSpineInfoFromAbsolutePageIndex({
5931
+ ...params,
5932
+ context,
5933
+ settings,
5934
+ spineItemsManager,
5935
+ spineLayout
5936
+ }),
5937
+ getSpinePositionFromSpineItem,
5938
+ getSpineItemPositionFromSpinePosition,
5939
+ getSpineItemFromPosition: (position) => getSpineItemFromPosition({
5940
+ position,
5941
+ settings,
5942
+ spineItemsManager,
5943
+ spineLayout
5944
+ }),
5945
+ getSpineItemFromIframe,
5946
+ getSpineItemPageIndexFromNode,
5947
+ getVisibleSpineItemsFromPosition: (params) => getVisibleSpineItemsFromPosition({
5948
+ context,
5949
+ settings,
5950
+ spineItemsManager,
5951
+ spineLayout,
5952
+ ...params
5953
+ }),
5954
+ getVisiblePagesFromViewportPosition,
5955
+ isPositionWithinSpineItem,
5956
+ spineItemLocator,
5957
+ getSafeSpineItemPositionFromUnsafeSpineItemPosition
5958
+ };
5959
+ };
5960
+ const loadItems = ({
5961
+ spineItemsManager,
5962
+ settings
5963
+ }) => (stream) => stream.pipe(
5964
+ tap$1(([beginIndex, endIndex]) => {
5965
+ const { numberOfAdjacentSpineItemToPreLoad } = settings.values;
5966
+ const leftMaximumIndex = beginIndex - numberOfAdjacentSpineItemToPreLoad;
5967
+ const rightMaximumIndex = endIndex + numberOfAdjacentSpineItemToPreLoad;
5968
+ spineItemsManager.items.forEach((orderedSpineItem, index) => {
5969
+ const isWithinRange = index >= leftMaximumIndex && index <= rightMaximumIndex;
5970
+ if (isWithinRange) {
5971
+ orderedSpineItem.load();
5972
+ } else {
5973
+ orderedSpineItem.unload();
5974
+ }
5975
+ });
5976
+ })
5977
+ );
5978
+ const mapToItemsToLoad = ({ spineLocator }) => (stream) => stream.pipe(
5979
+ map$1((position) => {
5980
+ const { beginIndex = 0, endIndex = 0 } = spineLocator.getVisibleSpineItemsFromPosition({
5981
+ position,
5982
+ threshold: 0
5983
+ }) || {};
5984
+ return [beginIndex, endIndex];
5985
+ })
5986
+ );
5987
+ class SpineItemsLoader extends DestroyableClass {
5988
+ constructor(context, spineItemsManager, spineLocator, settings, spineLayout) {
5989
+ super();
5990
+ this.context = context;
5991
+ this.spineItemsManager = spineItemsManager;
5992
+ this.spineLocator = spineLocator;
6096
5993
  this.settings = settings;
6097
- }
6098
- async getResource() {
6099
- var _a, _b;
6100
- const resource = await lastValueFrom(
6101
- ((_b = (_a = this.settings.values).getResource) == null ? void 0 : _b.call(_a, this.item)) ?? of(void 0)
5994
+ this.spineLayout = spineLayout;
5995
+ const navigationUpdate$ = this.context.bridgeEvent.navigation$;
5996
+ const layoutHasChanged$ = this.spineLayout.layout$.pipe(
5997
+ filter$1(({ hasChanged }) => hasChanged)
6102
5998
  );
6103
- return resource ?? defaultGetResource(this.item);
6104
- }
6105
- async fetchResource() {
6106
- const resource = await this.getResource();
6107
- if (resource instanceof Response) return resource;
6108
- if (resource instanceof URL) return fetch(resource);
6109
- return resource;
5999
+ const numberOfAdjacentSpineItemToPreLoad$ = settings.values$.pipe(
6000
+ map$1(
6001
+ ({ numberOfAdjacentSpineItemToPreLoad }) => numberOfAdjacentSpineItemToPreLoad
6002
+ ),
6003
+ skip$1(1),
6004
+ distinctUntilChanged$1()
6005
+ );
6006
+ const loadSpineItems$ = merge(
6007
+ navigationUpdate$,
6008
+ layoutHasChanged$,
6009
+ numberOfAdjacentSpineItemToPreLoad$
6010
+ ).pipe(
6011
+ // this can be changed by whatever we want and SHOULD not break navigation.
6012
+ // Ideally loading faster is better but loading too close to user navigating can
6013
+ // be dangerous.
6014
+ debounceTime$1(100, animationFrameScheduler),
6015
+ waitForSwitch(this.context.bridgeEvent.viewportFree$),
6016
+ withLatestFrom(this.context.bridgeEvent.navigation$),
6017
+ map$1(([, navigation]) => navigation.position),
6018
+ mapToItemsToLoad({ spineLocator }),
6019
+ loadItems({ spineItemsManager, settings })
6020
+ );
6021
+ loadSpineItems$.pipe(takeUntil(this.destroy$)).subscribe();
6110
6022
  }
6111
6023
  }
6112
- class SpineItem {
6113
- constructor(item, parentElement, context, settings, hookManager, index) {
6114
- var _a, _b;
6115
- this.item = item;
6116
- this.parentElement = parentElement;
6117
- this.context = context;
6118
- this.settings = settings;
6119
- this.hookManager = hookManager;
6120
- this.index = index;
6121
- this.adjustPositionOfElement = ({
6122
- right,
6123
- left,
6124
- top
6125
- }) => {
6126
- if (right !== void 0) {
6127
- this.containerElement.style.right = `${right}px`;
6128
- } else {
6129
- this.containerElement.style.removeProperty(`right`);
6130
- }
6131
- if (left !== void 0) {
6132
- this.containerElement.style.left = `${left}px`;
6133
- } else {
6134
- this.containerElement.style.removeProperty(`left`);
6135
- }
6136
- if (top !== void 0) {
6137
- this.containerElement.style.top = `${top}px`;
6138
- } else {
6139
- this.containerElement.style.removeProperty(`top`);
6140
- }
6141
- };
6142
- this.getBoundingRectOfElementFromSelector = (selector) => {
6143
- var _a2, _b2, _c, _d, _e;
6144
- const frameElement = (_a2 = this.renderer.layers[0]) == null ? void 0 : _a2.element;
6145
- if (frameElement && frameElement instanceof HTMLIFrameElement && selector) {
6146
- if (selector.startsWith(`#`)) {
6147
- return (_c = (_b2 = frameElement.contentDocument) == null ? void 0 : _b2.getElementById(selector.replace(`#`, ``))) == null ? void 0 : _c.getBoundingClientRect();
6148
- }
6149
- return (_e = (_d = frameElement.contentDocument) == null ? void 0 : _d.querySelector(selector)) == null ? void 0 : _e.getBoundingClientRect();
6150
- }
6151
- };
6152
- this.layout = ({
6153
- blankPagePosition,
6154
- minimumWidth,
6155
- spreadPosition
6156
- }) => {
6157
- this.hookManager.execute(`item.onBeforeLayout`, void 0, {
6158
- blankPagePosition,
6159
- item: this.item,
6160
- minimumWidth
6161
- });
6162
- const { height, width } = this.renderer.layout({
6163
- blankPagePosition,
6164
- minPageSpread: minimumWidth / this.context.getPageSize().width,
6165
- spreadPosition
6166
- }) ?? { width: 0, height: 0 };
6167
- const minHeight = Math.max(height, this.context.getPageSize().height);
6168
- const minWidth = Math.max(width, minimumWidth);
6169
- this.containerElement.style.width = `${minWidth}px`;
6170
- this.containerElement.style.height = `${minHeight}px`;
6171
- this.hookManager.execute(`item.onAfterLayout`, void 0, {
6172
- blankPagePosition,
6173
- item: this.item,
6174
- minimumWidth
6175
- });
6176
- return { width: minWidth, height: minHeight };
6177
- };
6178
- this.load = () => this.renderer.load();
6179
- this.unload = () => {
6180
- this.renderer.unload();
6181
- };
6182
- this.getElementDimensions = () => {
6183
- const rect = this.containerElement.getBoundingClientRect();
6184
- const normalizedValues = {
6185
- ...rect,
6186
- // we want to round to first decimal because it's possible to have half pixel
6187
- // however browser engine can also gives back x.yyyy based on their precision
6188
- width: Math.round(rect.width * 10) / 10,
6189
- height: Math.round(rect.height * 10) / 10
6190
- };
6191
- return normalizedValues;
6192
- };
6193
- this.destroy = () => {
6194
- this.destroySubject$.next();
6195
- this.containerElement.remove();
6196
- this.destroySubject$.complete();
6197
- this.renderer.destroy();
6198
- };
6199
- this.isUsingVerticalWriting = () => {
6200
- var _a2;
6201
- return !!((_a2 = this.renderer.writingMode) == null ? void 0 : _a2.startsWith(`vertical`));
6202
- };
6203
- this.destroySubject$ = new Subject();
6204
- this.containerElement = createContainerElement(
6205
- parentElement,
6206
- item,
6207
- hookManager
6024
+ class SpineItemsObserver extends DestroyableClass {
6025
+ constructor(spineItemsManager, spineLocator) {
6026
+ super();
6027
+ this.spineItemsManager = spineItemsManager;
6028
+ this.spineLocator = spineLocator;
6029
+ this.itemIsReady$ = this.spineItemsManager.items$.pipe(
6030
+ switchMap((items) => {
6031
+ const itemsIsReady$ = items.map(
6032
+ (item) => item.isReady$.pipe(map$1((isReady) => ({ item, isReady })))
6033
+ );
6034
+ return merge(...itemsIsReady$);
6035
+ }),
6036
+ share()
6208
6037
  );
6209
- parentElement.appendChild(this.containerElement);
6210
- const RendererClass = ((_b = (_a = this.settings.values).getRenderer) == null ? void 0 : _b.call(_a, item)) ?? DefaultRenderer;
6211
- this.resourcesHandler = new ResourceHandler(item, this.settings);
6212
- this.renderer = new RendererClass(
6213
- context,
6214
- settings,
6215
- hookManager,
6216
- item,
6217
- this.containerElement,
6218
- this.resourcesHandler
6038
+ this.itemResize$ = this.spineItemsManager.items$.pipe(
6039
+ switchMap((items) => {
6040
+ const resize$ = items.map(
6041
+ (item) => observeResize(item.element).pipe(
6042
+ map$1((entries) => ({ entries, item }))
6043
+ )
6044
+ );
6045
+ return merge(...resize$);
6046
+ }),
6047
+ share()
6219
6048
  );
6220
- const contentLayoutChange$ = merge(
6221
- this.unloaded$.pipe(map(() => ({ isFirstLayout: false }))),
6222
- this.ready$.pipe(map(() => ({ isFirstLayout: true })))
6049
+ this.itemLoaded$ = this.spineItemsManager.items$.pipe(
6050
+ switchMap((items) => {
6051
+ const itemsIsReady$ = items.map(
6052
+ (item) => item.loaded$.pipe(map$1(() => item))
6053
+ );
6054
+ return merge(...itemsIsReady$);
6055
+ })
6223
6056
  );
6224
- this.contentLayout$ = contentLayoutChange$.pipe(
6225
- withLatestFrom$1(this.isReady$),
6226
- map(([data, isReady]) => ({
6227
- isFirstLayout: data.isFirstLayout,
6228
- isReady
6229
- }))
6057
+ }
6058
+ }
6059
+ const convertSpinePositionToLayoutPosition = ({
6060
+ position,
6061
+ pageSize
6062
+ }) => {
6063
+ return {
6064
+ x: position.x,
6065
+ y: position.y,
6066
+ left: position.x,
6067
+ top: position.y,
6068
+ width: pageSize.width,
6069
+ height: pageSize.height,
6070
+ bottom: position.y + pageSize.height,
6071
+ right: position.x + pageSize.width
6072
+ };
6073
+ };
6074
+ const NAMESPACE = `SpineLayout`;
6075
+ class SpineLayout extends DestroyableClass {
6076
+ constructor(spineItemsManager, context, settings) {
6077
+ super();
6078
+ this.spineItemsManager = spineItemsManager;
6079
+ this.context = context;
6080
+ this.settings = settings;
6081
+ this.itemLayoutInformation = [];
6082
+ this.layoutSubject = new Subject();
6083
+ spineItemsManager.items$.pipe(
6084
+ switchMap((items) => {
6085
+ const itemsLayout$ = items.map(
6086
+ (spineItem) => spineItem.contentLayout$.pipe(
6087
+ tap$1(() => {
6088
+ this.layout();
6089
+ })
6090
+ )
6091
+ );
6092
+ const writingModeUpdate$ = items.map(
6093
+ (spineItem) => spineItem.loaded$.pipe(
6094
+ tap$1(() => {
6095
+ if (spineItem.isUsingVerticalWriting()) {
6096
+ this.context.update({
6097
+ hasVerticalWriting: true
6098
+ });
6099
+ } else {
6100
+ this.context.update({
6101
+ hasVerticalWriting: false
6102
+ });
6103
+ }
6104
+ })
6105
+ )
6106
+ );
6107
+ return merge(...itemsLayout$, ...writingModeUpdate$);
6108
+ })
6109
+ ).pipe(takeUntil(this.destroy$)).subscribe();
6110
+ this.layout$ = this.layoutSubject.pipe(
6111
+ map$1((hasChanged) => ({ hasChanged })),
6112
+ startWith$1({ hasChanged: true }),
6113
+ map$1(({ hasChanged }) => {
6114
+ const items = spineItemsManager.items;
6115
+ const spineItemsPagesAbsolutePositions = items.map((item) => {
6116
+ const itemLayout = this.getAbsolutePositionOf(item);
6117
+ const numberOfPages = getSpineItemNumberOfPages({
6118
+ isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
6119
+ itemHeight: itemLayout.height,
6120
+ itemWidth: itemLayout.width,
6121
+ context,
6122
+ settings
6123
+ });
6124
+ const pages2 = new Array(numberOfPages).fill(void 0);
6125
+ return pages2.map(
6126
+ (_, pageIndex) => convertSpinePositionToLayoutPosition({
6127
+ pageSize: this.context.getPageSize(),
6128
+ position: getSpinePositionFromSpineItemPosition({
6129
+ itemLayout,
6130
+ spineItemPosition: getSpineItemPositionFromPageIndex({
6131
+ isUsingVerticalWriting: !!item.isUsingVerticalWriting(),
6132
+ itemLayout,
6133
+ pageIndex,
6134
+ context
6135
+ })
6136
+ })
6137
+ })
6138
+ );
6139
+ });
6140
+ const pages = spineItemsPagesAbsolutePositions.reduce(
6141
+ (acc, itemPages, itemIndex) => {
6142
+ const itemPagesInfo = itemPages.map(
6143
+ (absolutePosition, pageIndex) => ({
6144
+ itemIndex,
6145
+ absolutePageIndex: itemIndex + pageIndex,
6146
+ absolutePosition
6147
+ })
6148
+ );
6149
+ return [...acc, ...itemPagesInfo];
6150
+ },
6151
+ []
6152
+ );
6153
+ return {
6154
+ hasChanged,
6155
+ spineItemsAbsolutePositions: items.map(
6156
+ (item) => this.getAbsolutePositionOf(item)
6157
+ ),
6158
+ spineItemsPagesAbsolutePositions,
6159
+ pages
6160
+ };
6161
+ }),
6162
+ shareReplay$1(1)
6230
6163
  );
6231
6164
  }
6232
- get element() {
6233
- return this.containerElement;
6234
- }
6235
6165
  /**
6236
- * @important
6237
- * Do not use this value for layout and navigation. It will be in possible conflict
6238
- * with the global reading direction. A book should not mix them anyway. A page can have
6239
- * a different reading direction for style reason but it should be in theory pre-paginated.
6240
- * For example an english page in a japanese manga. That's expected and will
6241
- * be confined to a single page.
6166
+ * @todo
6167
+ * move this logic to the spine
6168
+ *
6169
+ * @todo
6170
+ * make sure to check how many times it is being called and try to reduce number of layouts
6171
+ * it is called eery time an item is being unload (which can adds up quickly for big books)
6242
6172
  */
6243
- get readingDirection() {
6244
- return this.renderer.readingDirection;
6245
- }
6246
- get isReady() {
6247
- return this.renderer.state$.getValue() === "ready";
6248
- }
6249
- get ready$() {
6250
- return this.renderer.state$.pipe(
6251
- distinctUntilChanged(),
6252
- filter((state) => state === "ready")
6253
- );
6254
- }
6255
- get loaded$() {
6256
- return this.renderer.state$.pipe(
6257
- distinctUntilChanged(),
6258
- filter((state) => state === "loaded")
6173
+ layout() {
6174
+ const manifest = this.context.manifest;
6175
+ const newItemLayoutInformation = [];
6176
+ const isGloballyPrePaginated = (manifest == null ? void 0 : manifest.renditionLayout) === `pre-paginated`;
6177
+ this.spineItemsManager.items.reduce(
6178
+ ({ horizontalOffset, verticalOffset }, item, index) => {
6179
+ let minimumWidth = this.context.getPageSize().width;
6180
+ let blankPagePosition = `none`;
6181
+ const itemStartOnNewScreen = horizontalOffset % this.context.state.visibleAreaRect.width === 0;
6182
+ const isLastItem = index === this.spineItemsManager.items.length - 1;
6183
+ if (this.context.state.isUsingSpreadMode) {
6184
+ if (!isGloballyPrePaginated && item.item.renditionLayout === `reflowable` && !isLastItem) {
6185
+ minimumWidth = this.context.getPageSize().width * 2;
6186
+ }
6187
+ if (!isGloballyPrePaginated && item.item.renditionLayout === `reflowable` && isLastItem && itemStartOnNewScreen) {
6188
+ minimumWidth = this.context.getPageSize().width * 2;
6189
+ }
6190
+ const lastItemStartOnNewScreenInAPrepaginatedBook = itemStartOnNewScreen && isLastItem && isGloballyPrePaginated;
6191
+ if (item.item.pageSpreadRight && itemStartOnNewScreen && !this.context.isRTL()) {
6192
+ blankPagePosition = `before`;
6193
+ minimumWidth = this.context.getPageSize().width * 2;
6194
+ } else if (item.item.pageSpreadLeft && itemStartOnNewScreen && this.context.isRTL()) {
6195
+ blankPagePosition = `before`;
6196
+ minimumWidth = this.context.getPageSize().width * 2;
6197
+ } else if (lastItemStartOnNewScreenInAPrepaginatedBook) {
6198
+ if (this.context.isRTL()) {
6199
+ blankPagePosition = `before`;
6200
+ } else {
6201
+ blankPagePosition = `after`;
6202
+ }
6203
+ minimumWidth = this.context.getPageSize().width * 2;
6204
+ }
6205
+ }
6206
+ const { width, height } = item.layout({
6207
+ minimumWidth,
6208
+ blankPagePosition,
6209
+ spreadPosition: this.context.state.isUsingSpreadMode ? itemStartOnNewScreen ? this.context.isRTL() ? `right` : `left` : this.context.isRTL() ? `left` : `right` : `none`
6210
+ });
6211
+ if (this.settings.values.computedPageTurnDirection === `vertical`) {
6212
+ const currentValidEdgeYForVerticalPositioning = itemStartOnNewScreen ? verticalOffset : verticalOffset - this.context.state.visibleAreaRect.height;
6213
+ const currentValidEdgeXForVerticalPositioning = itemStartOnNewScreen ? 0 : horizontalOffset;
6214
+ if (this.context.isRTL()) {
6215
+ item.adjustPositionOfElement({
6216
+ top: currentValidEdgeYForVerticalPositioning,
6217
+ left: currentValidEdgeXForVerticalPositioning
6218
+ });
6219
+ } else {
6220
+ item.adjustPositionOfElement({
6221
+ top: currentValidEdgeYForVerticalPositioning,
6222
+ left: currentValidEdgeXForVerticalPositioning
6223
+ });
6224
+ }
6225
+ const newEdgeX = width + currentValidEdgeXForVerticalPositioning;
6226
+ const newEdgeY = height + currentValidEdgeYForVerticalPositioning;
6227
+ newItemLayoutInformation.push({
6228
+ left: currentValidEdgeXForVerticalPositioning,
6229
+ right: newEdgeX,
6230
+ top: currentValidEdgeYForVerticalPositioning,
6231
+ bottom: newEdgeY,
6232
+ height,
6233
+ width,
6234
+ x: currentValidEdgeXForVerticalPositioning,
6235
+ y: currentValidEdgeYForVerticalPositioning
6236
+ });
6237
+ return {
6238
+ horizontalOffset: newEdgeX,
6239
+ verticalOffset: newEdgeY
6240
+ };
6241
+ }
6242
+ item.adjustPositionOfElement(
6243
+ this.context.isRTL() ? { right: horizontalOffset, top: 0 } : { left: horizontalOffset, top: 0 }
6244
+ );
6245
+ const left = this.context.isRTL() ? this.context.state.visibleAreaRect.width - horizontalOffset - width : horizontalOffset;
6246
+ newItemLayoutInformation.push({
6247
+ right: this.context.isRTL() ? this.context.state.visibleAreaRect.width - horizontalOffset : horizontalOffset + width,
6248
+ left,
6249
+ x: left,
6250
+ top: verticalOffset,
6251
+ bottom: height,
6252
+ height,
6253
+ width,
6254
+ y: verticalOffset
6255
+ });
6256
+ return {
6257
+ horizontalOffset: horizontalOffset + width,
6258
+ verticalOffset: 0
6259
+ };
6260
+ },
6261
+ { horizontalOffset: 0, verticalOffset: 0 }
6259
6262
  );
6260
- }
6261
- get unloaded$() {
6262
- return this.renderer.state$.pipe(
6263
- distinctUntilChanged(),
6264
- filter((state) => state !== "idle"),
6265
- switchMap$1(
6266
- () => this.renderer.state$.pipe(
6267
- filter((state) => state === `idle`),
6268
- first()
6269
- )
6270
- )
6263
+ const hasLayoutChanges = this.itemLayoutInformation.length !== newItemLayoutInformation.length || this.itemLayoutInformation.some(
6264
+ (old, index) => !isShallowEqual(old, newItemLayoutInformation[index])
6271
6265
  );
6272
- }
6273
- get isReady$() {
6274
- return this.renderer.state$.pipe(map((state) => state === "ready"));
6266
+ this.itemLayoutInformation = newItemLayoutInformation;
6267
+ Report.log(NAMESPACE, `layout`, {
6268
+ hasLayoutChanges,
6269
+ itemLayoutInformation: this.itemLayoutInformation
6270
+ });
6271
+ this.layoutSubject.next(hasLayoutChanges);
6275
6272
  }
6276
6273
  /**
6277
- * Helper that will inject CSS into the document frame.
6278
- *
6279
- * @important
6280
- * The document needs to be detected as a frame.
6274
+ * It's important to not use x,y since we need the absolute position of each element. Otherwise x,y would be relative to
6275
+ * current window (viewport).
6281
6276
  */
6282
- upsertCSS(id, style, prepend) {
6283
- this.renderer.layers.forEach((layer) => {
6284
- if (layer.element instanceof HTMLIFrameElement) {
6285
- upsertCSS(layer.element, id, style, prepend);
6286
- }
6287
- });
6277
+ getAbsolutePositionOf(spineItemOrIndex) {
6278
+ const fallback = {
6279
+ left: 0,
6280
+ right: 0,
6281
+ top: 0,
6282
+ bottom: 0,
6283
+ width: 0,
6284
+ height: 0,
6285
+ x: 0,
6286
+ y: 0
6287
+ };
6288
+ const spineItem = this.spineItemsManager.get(spineItemOrIndex);
6289
+ const indexOfItem = spineItem ? this.spineItemsManager.items.indexOf(spineItem) : -1;
6290
+ const layoutInformation = this.itemLayoutInformation[indexOfItem];
6291
+ return layoutInformation || fallback;
6292
+ }
6293
+ destroy() {
6294
+ super.destroy();
6295
+ this.layoutSubject.complete();
6288
6296
  }
6289
6297
  }
6290
- const createContainerElement = (containerElement, item, hookManager) => {
6291
- const element = containerElement.ownerDocument.createElement(`div`);
6292
- element.classList.add(`spineItem`);
6293
- element.classList.add(`spineItem-${item.renditionLayout}`);
6294
- element.style.cssText = `
6295
- position: absolute;
6296
- overflow: hidden;
6297
- `;
6298
- hookManager.execute("item.onBeforeContainerCreated", void 0, { element });
6299
- return element;
6300
- };
6301
6298
  class Spine extends DestroyableClass {
6302
6299
  constructor(parentElement$, context, pagination, spineItemsManager, spineItemLocator, settings, hookManager) {
6303
6300
  super();
@@ -6375,6 +6372,17 @@ class Spine extends DestroyableClass {
6375
6372
  this.elementSubject.complete();
6376
6373
  }
6377
6374
  }
6375
+ const generateCfiFromSelection = ({
6376
+ item,
6377
+ selection
6378
+ }) => {
6379
+ const anchorCfi = selection.anchorNode ? generateCfi(selection.anchorNode, selection.anchorOffset, item) : void 0;
6380
+ const focusCfi = selection.focusNode ? generateCfi(selection.focusNode, selection.focusOffset, item) : void 0;
6381
+ return {
6382
+ anchorCfi,
6383
+ focusCfi
6384
+ };
6385
+ };
6378
6386
  const createReader = (inputSettings) => {
6379
6387
  const stateSubject$ = new BehaviorSubject({
6380
6388
  supportedPageTurnAnimation: [`fade`, `none`, `slide`],
@@ -6523,6 +6531,8 @@ const createReader = (inputSettings) => {
6523
6531
  hookManager,
6524
6532
  cfi: {
6525
6533
  generateCfiFromRange,
6534
+ generateCfiFromSelection,
6535
+ parseCfi,
6526
6536
  generateCfiForSpineItemPage: (params) => generateCfiForSpineItemPage({
6527
6537
  ...params,
6528
6538
  spineItemLocator
@@ -6530,7 +6540,6 @@ const createReader = (inputSettings) => {
6530
6540
  resolveCfi: (params) => resolveCfi({ ...params, spineItemsManager })
6531
6541
  },
6532
6542
  navigation: {
6533
- viewportFree$: context.bridgeEvent.viewportFree$,
6534
6543
  viewportBusy$: context.bridgeEvent.viewportBusy$,
6535
6544
  getViewportPosition: () => navigator2.viewportNavigator.viewportPosition,
6536
6545
  getNavigation: navigator2.getNavigation.bind(navigator2),
@@ -6552,6 +6561,7 @@ const createReader = (inputSettings) => {
6552
6561
  element$,
6553
6562
  layout$: spine.spineLayout.layout$,
6554
6563
  viewportState$: context.bridgeEvent.viewportState$,
6564
+ viewportFree$: context.bridgeEvent.viewportFree$,
6555
6565
  /**
6556
6566
  * Dispatched when the reader has loaded a book and is rendering a book.
6557
6567
  * Using navigation API and getting information about current content will
@@ -6872,13 +6882,13 @@ const mediaEnhancer = (next) => (options) => {
6872
6882
  ...options,
6873
6883
  getRenderer(item) {
6874
6884
  var _a;
6875
- const MaybeRenderer = (_a = options.getRenderer) == null ? void 0 : _a.call(options, item);
6885
+ const maybeFactory = (_a = options.getRenderer) == null ? void 0 : _a.call(options, item);
6876
6886
  const mimeType = item.mediaType ?? detectMimeTypeFromName(item.href);
6877
6887
  const isImageType = !!(mimeType == null ? void 0 : mimeType.startsWith(`image/`));
6878
- if (!MaybeRenderer && isImageType) {
6879
- return ImageRenderer;
6888
+ if (!maybeFactory && isImageType) {
6889
+ return (props) => new ImageRenderer(props);
6880
6890
  }
6881
- return MaybeRenderer;
6891
+ return maybeFactory;
6882
6892
  }
6883
6893
  });
6884
6894
  const frameObserver = new IntersectionObserver(
@@ -7308,7 +7318,9 @@ const eventsEnhancer = (next) => (options) => {
7308
7318
  `item.onDocumentLoad`,
7309
7319
  ({ destroy, layers, itemId }) => {
7310
7320
  var _a;
7311
- const frame = (_a = layers[0]) == null ? void 0 : _a.element;
7321
+ const frame = (_a = layers.find(
7322
+ (layer) => layer.element instanceof HTMLIFrameElement
7323
+ )) == null ? void 0 : _a.element;
7312
7324
  if (!(frame instanceof HTMLIFrameElement)) return;
7313
7325
  const item = reader.spineItemsManager.get(itemId);
7314
7326
  if (!item) return;
@@ -7978,15 +7990,8 @@ const renderReflowable = ({
7978
7990
  };
7979
7991
  };
7980
7992
  class HtmlRenderer extends DocumentRenderer {
7981
- constructor(context, settings, hookManager, item, containerElement, resourcesHandler) {
7982
- super(
7983
- context,
7984
- settings,
7985
- hookManager,
7986
- item,
7987
- containerElement,
7988
- resourcesHandler
7989
- );
7993
+ constructor() {
7994
+ super(...arguments);
7990
7995
  this.isImageType = () => {
7991
7996
  const mimeType = this.item.mediaType ?? detectMimeTypeFromName(this.item.href);
7992
7997
  return !!(mimeType == null ? void 0 : mimeType.startsWith(`image/`));
@@ -8105,30 +8110,59 @@ const htmlEnhancer = (next) => (options) => {
8105
8110
  ...options,
8106
8111
  getRenderer(item) {
8107
8112
  var _a;
8108
- const MaybeRenderer = (_a = options.getRenderer) == null ? void 0 : _a.call(options, item);
8109
- return MaybeRenderer ?? HtmlRenderer;
8113
+ const maybeFactory = (_a = options.getRenderer) == null ? void 0 : _a.call(options, item);
8114
+ return maybeFactory ?? ((props) => new HtmlRenderer(props));
8110
8115
  }
8111
8116
  });
8112
8117
  return reader;
8113
8118
  };
8114
- const generateCfis = ({
8115
- itemId,
8116
- selection
8119
+ const getRangeFromSelection = (anchor, focus) => {
8120
+ var _a;
8121
+ const range = (_a = anchor.node.ownerDocument) == null ? void 0 : _a.createRange();
8122
+ const comparison = anchor.node.compareDocumentPosition(focus.node);
8123
+ if (!range) return void 0;
8124
+ try {
8125
+ if (comparison & Node.DOCUMENT_POSITION_PRECEDING) {
8126
+ range.setStart(focus.node, focus.offset || 0);
8127
+ range.setEnd(anchor.node, anchor.offset || 0);
8128
+ } else if (comparison & Node.DOCUMENT_POSITION_FOLLOWING) {
8129
+ range.setStart(anchor.node, anchor.offset || 0);
8130
+ range.setEnd(focus.node, focus.offset || 0);
8131
+ } else {
8132
+ const startOffset = Math.min(anchor.offset || 0, focus.offset || 0);
8133
+ const endOffset = Math.max(anchor.offset || 0, focus.offset || 0);
8134
+ range.setStart(anchor.node, startOffset);
8135
+ range.setEnd(anchor.node, endOffset);
8136
+ }
8137
+ } catch (e) {
8138
+ Report.warn("Failed to create range from selection", e, {
8139
+ anchor,
8140
+ focus
8141
+ });
8142
+ }
8143
+ return range;
8144
+ };
8145
+ const createRangeFromSelection = ({
8146
+ selection,
8147
+ spineItem
8117
8148
  }) => {
8118
- const anchorCfi = selection.anchorNode ? CfiHandler.generate(
8119
- selection.anchorNode,
8120
- selection.anchorOffset,
8121
- `|[prose~anchor~${encodeURIComponent(itemId)}]`
8122
- ) : void 0;
8123
- const focusCfi = selection.focusNode ? CfiHandler.generate(
8124
- selection.focusNode,
8125
- selection.focusOffset,
8126
- `|[prose~anchor~${encodeURIComponent(itemId)}]`
8127
- ) : void 0;
8128
- return {
8129
- anchorCfi,
8130
- focusCfi
8131
- };
8149
+ if (!spineItem.isReady) return void 0;
8150
+ const { anchorNode, anchorOffset, focusNode, focusOffset } = selection;
8151
+ if (!anchorNode || !focusNode) {
8152
+ return void 0;
8153
+ }
8154
+ try {
8155
+ return getRangeFromSelection(
8156
+ { node: anchorNode, offset: anchorOffset },
8157
+ { node: focusNode, offset: focusOffset }
8158
+ );
8159
+ } catch (e) {
8160
+ Report.warn("Failed to create range from selection", e, {
8161
+ selection,
8162
+ spineItem
8163
+ });
8164
+ return void 0;
8165
+ }
8132
8166
  };
8133
8167
  class SelectionTracker extends DestroyableClass {
8134
8168
  constructor(document2) {
@@ -8161,6 +8195,7 @@ const selectionEnhancer = (next) => (options) => {
8161
8195
  ({ itemId, layers, destroy$, destroy }) => {
8162
8196
  var _a, _b;
8163
8197
  const frame = (_a = layers[0]) == null ? void 0 : _a.element;
8198
+ const itemIndex = reader.spineItemsManager.getSpineItemIndex(itemId) ?? 0;
8164
8199
  if (frame instanceof HTMLIFrameElement) {
8165
8200
  const frameDoc = frame.contentDocument || ((_b = frame.contentWindow) == null ? void 0 : _b.document);
8166
8201
  if (frameDoc) {
@@ -8172,7 +8207,7 @@ const selectionEnhancer = (next) => (options) => {
8172
8207
  selectionSubject.next({
8173
8208
  document: frameDoc,
8174
8209
  selection,
8175
- itemId
8210
+ itemIndex
8176
8211
  });
8177
8212
  } else {
8178
8213
  selectionSubject.next(void 0);
@@ -8186,7 +8221,7 @@ const selectionEnhancer = (next) => (options) => {
8186
8221
  {
8187
8222
  document: frameDoc,
8188
8223
  selection,
8189
- itemId
8224
+ itemIndex
8190
8225
  }
8191
8226
  ]);
8192
8227
  })
@@ -8233,8 +8268,8 @@ const selectionEnhancer = (next) => (options) => {
8233
8268
  selectionEnd$,
8234
8269
  selectionAfterPointerUp$,
8235
8270
  lastSelectionOnPointerdown$,
8236
- generateCfis,
8237
- getSelection: () => selectionSubject.getValue()
8271
+ getSelection: () => selectionSubject.getValue(),
8272
+ createRangeFromSelection
8238
8273
  },
8239
8274
  destroy: () => {
8240
8275
  selectionSubject.complete();
@@ -8294,8 +8329,15 @@ export {
8294
8329
  SettingsManager3 as SettingsManager,
8295
8330
  SpineItem,
8296
8331
  createReaderWithEnhancers as createReader,
8332
+ getAttributeValueFromString,
8333
+ getFrameViewportInfo,
8334
+ injectCSS,
8297
8335
  isHtmlElement,
8298
8336
  isShallowEqual2 as isShallowEqual,
8337
+ removeCSS,
8338
+ upsertCSS,
8339
+ waitForFrameLoad,
8340
+ waitForFrameReady,
8299
8341
  waitForSwitch
8300
8342
  };
8301
8343
  //# sourceMappingURL=index.js.map