pdfjs-reader-core 0.5.10 → 0.5.12

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.cjs CHANGED
@@ -1903,6 +1903,15 @@ function detectDeviceCapabilities() {
1903
1903
  screenSize
1904
1904
  };
1905
1905
  }
1906
+ function isIOSMobile() {
1907
+ if (typeof window === "undefined" || typeof navigator === "undefined") {
1908
+ return false;
1909
+ }
1910
+ const ua = navigator.userAgent || "";
1911
+ if (/iPhone|iPad|iPod/i.test(ua)) return true;
1912
+ const isAppleTouch = /Mac/i.test(ua) && typeof navigator.maxTouchPoints === "number" && navigator.maxTouchPoints > 1;
1913
+ return isAppleTouch;
1914
+ }
1906
1915
  function getRenderConfig(quality = "auto", capabilities) {
1907
1916
  const caps = capabilities ?? detectDeviceCapabilities();
1908
1917
  if (quality === "auto") {
@@ -1917,8 +1926,10 @@ function getRenderConfig(quality = "auto", capabilities) {
1917
1926
  switch (quality) {
1918
1927
  case "low":
1919
1928
  return {
1920
- canvasScaleFactor: Math.min(1, caps.devicePixelRatio * 0.5),
1921
- maxCanvasDimension: 2048,
1929
+ canvasScaleFactor: caps.devicePixelRatio,
1930
+ // 3072² ≈ 9 MP — fits a full-DPR US Letter (DPR=3 → 1836×2376) at
1931
+ // default scale and caps at deep zoom on memory-constrained iPhones.
1932
+ maxCanvasDimension: 3072,
1922
1933
  overscanPages: 1,
1923
1934
  maxPagesInMemory: 3,
1924
1935
  scrollDebounceMs: 100,
@@ -1931,7 +1942,8 @@ function getRenderConfig(quality = "auto", capabilities) {
1931
1942
  };
1932
1943
  case "medium":
1933
1944
  return {
1934
- canvasScaleFactor: Math.min(1.5, caps.devicePixelRatio * 0.75),
1945
+ canvasScaleFactor: caps.devicePixelRatio,
1946
+ // 4096² ≈ 16 MP — within iOS Safari per-canvas budget across versions.
1935
1947
  maxCanvasDimension: 4096,
1936
1948
  overscanPages: 2,
1937
1949
  maxPagesInMemory: 5,
@@ -5218,9 +5230,37 @@ var init_ThumbnailPanel = __esm({
5218
5230
  onClick,
5219
5231
  scale
5220
5232
  }) {
5233
+ const buttonRef = (0, import_react18.useRef)(null);
5221
5234
  const canvasRef = (0, import_react18.useRef)(null);
5222
5235
  const [isRendered, setIsRendered] = (0, import_react18.useState)(false);
5236
+ const [shouldRender, setShouldRender] = (0, import_react18.useState)(false);
5223
5237
  (0, import_react18.useEffect)(() => {
5238
+ if (shouldRender) return;
5239
+ const el = buttonRef.current;
5240
+ if (!el) return;
5241
+ if (typeof IntersectionObserver === "undefined") {
5242
+ setShouldRender(true);
5243
+ return;
5244
+ }
5245
+ const observer = new IntersectionObserver(
5246
+ (entries) => {
5247
+ for (const entry of entries) {
5248
+ if (entry.isIntersecting) {
5249
+ setShouldRender(true);
5250
+ observer.disconnect();
5251
+ break;
5252
+ }
5253
+ }
5254
+ },
5255
+ // 200px root margin so the page is fetched just before it scrolls
5256
+ // into view, hiding the render latency.
5257
+ { rootMargin: "200px" }
5258
+ );
5259
+ observer.observe(el);
5260
+ return () => observer.disconnect();
5261
+ }, [shouldRender]);
5262
+ (0, import_react18.useEffect)(() => {
5263
+ if (!shouldRender) return;
5224
5264
  let cancelled = false;
5225
5265
  const renderThumbnail = async () => {
5226
5266
  const canvas = canvasRef.current;
@@ -5254,10 +5294,11 @@ var init_ThumbnailPanel = __esm({
5254
5294
  return () => {
5255
5295
  cancelled = true;
5256
5296
  };
5257
- }, [document2, pageNumber, scale]);
5297
+ }, [document2, pageNumber, scale, shouldRender]);
5258
5298
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
5259
5299
  "button",
5260
5300
  {
5301
+ ref: buttonRef,
5261
5302
  onClick,
5262
5303
  className: cn(
5263
5304
  "thumbnail-item",
@@ -9480,6 +9521,11 @@ var init_VirtualizedDocumentContainer = __esm({
9480
9521
  selectHighlight,
9481
9522
  activeColor
9482
9523
  } = useHighlights();
9524
+ const [defaultDims, setDefaultDims] = (0, import_react38.useState)({
9525
+ width: DEFAULT_PAGE_WIDTH2,
9526
+ height: DEFAULT_PAGE_HEIGHT2
9527
+ });
9528
+ const [dimsVersion, setDimsVersion] = (0, import_react38.useState)(0);
9483
9529
  (0, import_react38.useEffect)(() => {
9484
9530
  if (document2 !== documentRef.current) {
9485
9531
  documentRef.current = document2;
@@ -9488,39 +9534,47 @@ var init_VirtualizedDocumentContainer = __esm({
9488
9534
  setPageObjects(/* @__PURE__ */ new Map());
9489
9535
  }
9490
9536
  }, [document2]);
9537
+ (0, import_react38.useEffect)(() => {
9538
+ pageDimensionsCache.current.clear();
9539
+ setDimsVersion((v) => v + 1);
9540
+ }, [rotation]);
9491
9541
  (0, import_react38.useEffect)(() => {
9492
9542
  if (!document2 || numPages === 0) return;
9493
- const calculatePageInfos = async () => {
9494
- const infos = [];
9495
- let currentTop = 0;
9496
- for (let i = 1; i <= numPages; i++) {
9497
- let dimensions = pageDimensionsCache.current.get(i);
9498
- if (!dimensions) {
9499
- try {
9500
- const page = pageCache.current.get(i) || await document2.getPage(i);
9501
- if (!pageCache.current.has(i)) {
9502
- pageCache.current.set(i, page);
9503
- }
9504
- const viewport = page.getViewport({ scale: 1, rotation });
9505
- dimensions = { width: viewport.width, height: viewport.height };
9506
- pageDimensionsCache.current.set(i, dimensions);
9507
- } catch {
9508
- dimensions = { width: DEFAULT_PAGE_WIDTH2, height: DEFAULT_PAGE_HEIGHT2 };
9509
- }
9510
- }
9511
- const scaledHeight = Math.floor(dimensions.height * scale);
9512
- infos.push({
9513
- pageNumber: i,
9514
- top: currentTop,
9515
- height: scaledHeight
9516
- });
9517
- currentTop += scaledHeight + pageGap;
9543
+ let cancelled = false;
9544
+ (async () => {
9545
+ try {
9546
+ const page = pageCache.current.get(1) ?? await document2.getPage(1);
9547
+ if (cancelled) return;
9548
+ pageCache.current.set(1, page);
9549
+ const viewport = page.getViewport({ scale: 1, rotation });
9550
+ const dims = { width: viewport.width, height: viewport.height };
9551
+ pageDimensionsCache.current.set(1, dims);
9552
+ setDefaultDims(dims);
9553
+ setDimsVersion((v) => v + 1);
9554
+ } catch {
9518
9555
  }
9519
- setPageInfos(infos);
9520
- setTotalHeight(currentTop - pageGap);
9556
+ })();
9557
+ return () => {
9558
+ cancelled = true;
9521
9559
  };
9522
- calculatePageInfos();
9523
- }, [document2, numPages, scale, rotation, pageGap]);
9560
+ }, [document2, numPages, rotation]);
9561
+ (0, import_react38.useEffect)(() => {
9562
+ if (!document2 || numPages === 0) {
9563
+ setPageInfos([]);
9564
+ setTotalHeight(0);
9565
+ return;
9566
+ }
9567
+ const infos = [];
9568
+ let currentTop = 0;
9569
+ for (let i = 1; i <= numPages; i++) {
9570
+ const dimensions = pageDimensionsCache.current.get(i) ?? defaultDims;
9571
+ const scaledHeight = Math.floor(dimensions.height * scale);
9572
+ infos.push({ pageNumber: i, top: currentTop, height: scaledHeight });
9573
+ currentTop += scaledHeight + pageGap;
9574
+ }
9575
+ setPageInfos(infos);
9576
+ setTotalHeight(Math.max(0, currentTop - pageGap));
9577
+ }, [document2, numPages, scale, rotation, pageGap, defaultDims, dimsVersion]);
9524
9578
  const updateVisiblePages = (0, import_react38.useCallback)(() => {
9525
9579
  if (!scrollContainerRef.current || pageInfos.length === 0) return;
9526
9580
  const container = scrollContainerRef.current;
@@ -9568,6 +9622,7 @@ var init_VirtualizedDocumentContainer = __esm({
9568
9622
  const loadPages = async () => {
9569
9623
  const newPageObjects = new Map(pageObjects);
9570
9624
  let hasChanges = false;
9625
+ let dimsChanged = false;
9571
9626
  for (const pageNum of visiblePages) {
9572
9627
  if (!newPageObjects.has(pageNum)) {
9573
9628
  try {
@@ -9578,6 +9633,14 @@ var init_VirtualizedDocumentContainer = __esm({
9578
9633
  }
9579
9634
  newPageObjects.set(pageNum, page);
9580
9635
  hasChanges = true;
9636
+ if (!pageDimensionsCache.current.has(pageNum)) {
9637
+ const vp = page.getViewport({ scale: 1, rotation });
9638
+ pageDimensionsCache.current.set(pageNum, {
9639
+ width: vp.width,
9640
+ height: vp.height
9641
+ });
9642
+ dimsChanged = true;
9643
+ }
9581
9644
  } catch (error) {
9582
9645
  const errorMessage = error instanceof Error ? error.message : String(error);
9583
9646
  const isDocumentDestroyed = errorMessage.includes("destroyed") || errorMessage.includes("sendWithStream") || errorMessage.includes("sendWithPromise") || errorMessage.includes("Cannot read properties of null");
@@ -9597,9 +9660,12 @@ var init_VirtualizedDocumentContainer = __esm({
9597
9660
  if (hasChanges) {
9598
9661
  setPageObjects(newPageObjects);
9599
9662
  }
9663
+ if (dimsChanged) {
9664
+ setDimsVersion((v) => v + 1);
9665
+ }
9600
9666
  };
9601
9667
  loadPages();
9602
- }, [document2, visiblePages, pageObjects]);
9668
+ }, [document2, visiblePages, pageObjects, rotation]);
9603
9669
  (0, import_react38.useEffect)(() => {
9604
9670
  if (!scrollToPageRequest || !scrollContainerRef.current || pageInfos.length === 0) return;
9605
9671
  const { page, requestId, behavior } = scrollToPageRequest;
@@ -10164,7 +10230,7 @@ var init_DualPageContainer = __esm({
10164
10230
  });
10165
10231
 
10166
10232
  // src/components/PDFViewer/BookModeContainer.tsx
10167
- var import_react41, import_react_pageflip, import_jsx_runtime27, BookPage, BookModeContainer;
10233
+ var import_react41, import_react_pageflip, import_jsx_runtime27, BOOK_MODE_OVERSCAN, BookPage, BookModeContainer;
10168
10234
  var init_BookModeContainer = __esm({
10169
10235
  "src/components/PDFViewer/BookModeContainer.tsx"() {
10170
10236
  "use strict";
@@ -10175,6 +10241,7 @@ var init_BookModeContainer = __esm({
10175
10241
  init_hooks();
10176
10242
  init_utils();
10177
10243
  import_jsx_runtime27 = require("react/jsx-runtime");
10244
+ BOOK_MODE_OVERSCAN = 4;
10178
10245
  BookPage = import_react41.default.forwardRef(function BookPage2({ pageNumber, page, scale, rotation, width, height }, ref) {
10179
10246
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { ref, className: "book-page", "data-page-number": pageNumber, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { style: { width, height, overflow: "hidden" }, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10180
10247
  PDFPage,
@@ -10207,9 +10274,10 @@ var init_BookModeContainer = __esm({
10207
10274
  } = usePDFViewer();
10208
10275
  const scrollToPageRequest = useViewerStore((s) => s.scrollToPageRequest);
10209
10276
  const { viewerStore } = usePDFViewerStores();
10210
- const [pages, setPages] = (0, import_react41.useState)([]);
10277
+ const [pages, setPages] = (0, import_react41.useState)(/* @__PURE__ */ new Map());
10211
10278
  const [rawPageDims, setRawPageDims] = (0, import_react41.useState)({ width: 612, height: 792 });
10212
- const [isLoadingPages, setIsLoadingPages] = (0, import_react41.useState)(false);
10279
+ const [hasFirstPage, setHasFirstPage] = (0, import_react41.useState)(false);
10280
+ const pageRequestsRef = (0, import_react41.useRef)(/* @__PURE__ */ new Set());
10213
10281
  const containerRef = (0, import_react41.useRef)(null);
10214
10282
  const [containerSize, setContainerSize] = (0, import_react41.useState)({ width: 0, height: 0 });
10215
10283
  const flipBookRef = (0, import_react41.useRef)(null);
@@ -10226,41 +10294,67 @@ var init_BookModeContainer = __esm({
10226
10294
  return () => ro.disconnect();
10227
10295
  }, []);
10228
10296
  (0, import_react41.useEffect)(() => {
10229
- if (!document2) {
10230
- setPages([]);
10231
- return;
10232
- }
10297
+ setPages(/* @__PURE__ */ new Map());
10298
+ pageRequestsRef.current.clear();
10299
+ setHasFirstPage(false);
10300
+ }, [document2]);
10301
+ (0, import_react41.useEffect)(() => {
10302
+ if (!document2 || numPages === 0) return;
10303
+ const start = Math.max(1, currentPage - BOOK_MODE_OVERSCAN);
10304
+ const end = Math.min(numPages, currentPage + BOOK_MODE_OVERSCAN);
10305
+ const isFirstLoad = !hasFirstPage;
10233
10306
  let cancelled = false;
10234
- const loadAllPages = async () => {
10235
- setIsLoadingPages(true);
10236
- try {
10237
- const pagePromises = [];
10238
- for (let i = 1; i <= numPages; i++) {
10239
- pagePromises.push(document2.getPage(i));
10307
+ const wanted = [];
10308
+ if (isFirstLoad && !pages.has(1) && !pageRequestsRef.current.has(1)) {
10309
+ wanted.push(1);
10310
+ pageRequestsRef.current.add(1);
10311
+ }
10312
+ for (let i = start; i <= end; i++) {
10313
+ if (pages.has(i) || pageRequestsRef.current.has(i)) continue;
10314
+ wanted.push(i);
10315
+ pageRequestsRef.current.add(i);
10316
+ }
10317
+ if (wanted.length === 0) return;
10318
+ (async () => {
10319
+ const results = await Promise.allSettled(
10320
+ wanted.map((i) => document2.getPage(i).then((p) => [i, p]))
10321
+ );
10322
+ if (cancelled) return;
10323
+ const next = new Map(pages);
10324
+ let firstPageJustLoaded = null;
10325
+ for (const r of results) {
10326
+ if (r.status === "fulfilled") {
10327
+ const [i, page] = r.value;
10328
+ next.set(i, page);
10329
+ if (i === 1) firstPageJustLoaded = page;
10240
10330
  }
10241
- const results = await Promise.allSettled(pagePromises);
10242
- if (!cancelled) {
10243
- const loaded2 = results.map((r) => r.status === "fulfilled" ? r.value : null);
10244
- setPages(loaded2);
10245
- const firstPage = loaded2[0];
10246
- if (firstPage) {
10247
- const vp = firstPage.getViewport({ scale: 1, rotation });
10248
- setRawPageDims({ width: vp.width, height: vp.height });
10249
- }
10331
+ }
10332
+ for (const i of next.keys()) {
10333
+ if (i !== 1 && (i < start || i > end)) {
10334
+ next.delete(i);
10250
10335
  }
10251
- } catch {
10252
- } finally {
10253
- if (!cancelled) setIsLoadingPages(false);
10254
10336
  }
10255
- };
10256
- loadAllPages();
10337
+ setPages(next);
10338
+ if (firstPageJustLoaded) {
10339
+ const vp = firstPageJustLoaded.getViewport({ scale: 1, rotation });
10340
+ setRawPageDims({ width: vp.width, height: vp.height });
10341
+ setHasFirstPage(true);
10342
+ }
10343
+ for (const i of wanted) {
10344
+ pageRequestsRef.current.delete(i);
10345
+ }
10346
+ })();
10257
10347
  return () => {
10258
10348
  cancelled = true;
10349
+ for (const i of wanted) {
10350
+ pageRequestsRef.current.delete(i);
10351
+ }
10259
10352
  };
10260
- }, [document2, numPages, rotation]);
10353
+ }, [document2, numPages, currentPage, rotation, pages, hasFirstPage]);
10261
10354
  (0, import_react41.useEffect)(() => {
10262
- if (pages[0]) {
10263
- const vp = pages[0].getViewport({ scale: 1, rotation });
10355
+ const firstPage = pages.get(1);
10356
+ if (firstPage) {
10357
+ const vp = firstPage.getViewport({ scale: 1, rotation });
10264
10358
  setRawPageDims({ width: vp.width, height: vp.height });
10265
10359
  }
10266
10360
  }, [pages, rotation]);
@@ -10311,8 +10405,28 @@ var init_BookModeContainer = __esm({
10311
10405
  sepia: "bg-amber-50"
10312
10406
  };
10313
10407
  const themeClass = theme === "dark" ? "dark" : theme === "sepia" ? "sepia" : "";
10314
- const ready = !!document2 && !isLoadingPages && pages.length > 0;
10408
+ const ready = !!document2 && hasFirstPage;
10315
10409
  const hasContainer = containerSize.width > 0 && containerSize.height > 0;
10410
+ const pageChildren = (0, import_react41.useMemo)(() => {
10411
+ if (numPages === 0) return null;
10412
+ const out = new Array(numPages);
10413
+ for (let i = 0; i < numPages; i++) {
10414
+ const pageNumber = i + 1;
10415
+ out[i] = /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10416
+ BookPage,
10417
+ {
10418
+ pageNumber,
10419
+ page: pages.get(pageNumber) ?? null,
10420
+ scale: renderScale,
10421
+ rotation,
10422
+ width: displayWidth,
10423
+ height: displayHeight
10424
+ },
10425
+ pageNumber
10426
+ );
10427
+ }
10428
+ return out;
10429
+ }, [numPages, pages, renderScale, rotation, displayWidth, displayHeight]);
10316
10430
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
10317
10431
  "div",
10318
10432
  {
@@ -10362,18 +10476,7 @@ var init_BookModeContainer = __esm({
10362
10476
  autoSize: false,
10363
10477
  renderOnlyPageLengthChange: false,
10364
10478
  disableFlipByClick: false,
10365
- children: pages.map((page, index) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
10366
- BookPage,
10367
- {
10368
- pageNumber: index + 1,
10369
- page,
10370
- scale: renderScale,
10371
- rotation,
10372
- width: displayWidth,
10373
- height: displayHeight
10374
- },
10375
- index
10376
- ))
10479
+ children: pageChildren
10377
10480
  }
10378
10481
  )
10379
10482
  ]
@@ -14996,6 +15099,7 @@ function SubtitleBar({ text }) {
14996
15099
  // src/director/storyboard-engine.ts
14997
15100
  init_narration_store();
14998
15101
  init_camera_math();
15102
+ init_mobile_config();
14999
15103
  var DEFAULT_MIN_OVERLAY_MS = 3500;
15000
15104
  var StoryboardEngine = class {
15001
15105
  constructor(deps) {
@@ -15015,6 +15119,7 @@ var StoryboardEngine = class {
15015
15119
  this.overlayRemovalTimers = /* @__PURE__ */ new Map();
15016
15120
  this.currentStoryboardId = 0;
15017
15121
  this.deps = deps;
15122
+ this.singleActiveOverlay = deps.singleActiveOverlay ?? isIOSMobile();
15018
15123
  }
15019
15124
  /**
15020
15125
  * Execute a new storyboard. Cancels in-flight steps from the previous storyboard
@@ -15184,6 +15289,10 @@ var StoryboardEngine = class {
15184
15289
  createdAt: Date.now(),
15185
15290
  expiresAt: Date.now() + visibleMs
15186
15291
  };
15292
+ if (this.singleActiveOverlay) {
15293
+ this.cancelAllRemovalTimers();
15294
+ narrationStore.getState().clearOverlays();
15295
+ }
15187
15296
  narrationStore.getState().addOverlay(overlay);
15188
15297
  const timer = setTimeout(() => {
15189
15298
  narrationStore.getState().removeOverlay(overlay.id);