diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bfc295c2..cb71dc7b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
android:targetSandboxVersion="2">
+
+
mDocumentProperties;
private InputStream mInputStream;
+ private boolean isVerticalScrollMode = false;
+ private boolean mKeepScreenOn = false;
+ private PowerManager.WakeLock mWakeLock;
private PdfviewerBinding binding;
private TextView mTextView;
@@ -265,6 +272,22 @@ public void onLoaded() {
public String getPassword() {
return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : "";
}
+
+ @JavascriptInterface
+ public boolean isVerticalScrollMode() {
+ return isVerticalScrollMode;
+ }
+
+ @JavascriptInterface
+ public void onPageChanged(final int pageNumber) {
+ if (isVerticalScrollMode) {
+ mPage = pageNumber;
+ runOnUiThread(() -> {
+ invalidateOptionsMenu();
+ showPageNumber();
+ });
+ }
+ }
}
private void showWebViewCrashed() {
@@ -285,6 +308,11 @@ protected void onCreate(Bundle savedInstanceState) {
setSupportActionBar(binding.toolbar);
viewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PdfViewModel.class);
+ // Initialize wake lock
+ PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "PDFViewer:KeepScreenOn");
+ mWakeLock.setReferenceCounted(false);
+
viewModel.getOutline().observe(this, requested -> {
if (requested instanceof PdfViewModel.OutlineStatus.Requested) {
viewModel.setLoadingOutline();
@@ -502,8 +530,13 @@ public void onZoomEnd() {
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
mEncryptedDocumentPassword = savedInstanceState.getString(STATE_ENCRYPTED_DOCUMENT_PASSWORD);
+ isVerticalScrollMode = savedInstanceState.getBoolean(STATE_VERTICAL_SCROLL_MODE, false);
+ mKeepScreenOn = savedInstanceState.getBoolean(STATE_KEEP_SCREEN_ON, false);
}
+ // Apply wake lock state
+ updateWakeLock();
+
binding.webviewAlertReload.setOnClickListener(v -> {
webViewCrashed = false;
recreate();
@@ -530,6 +563,12 @@ private void purgeWebView() {
@Override
protected void onDestroy() {
super.onDestroy();
+
+ // Release wake lock if held
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+
purgeWebView();
maybeCloseInputStream();
}
@@ -570,6 +609,9 @@ private void setToolbarTitleWithDocumentName() {
protected void onResume() {
super.onResume();
+ // Reapply wake lock state when returning to activity
+ updateWakeLock();
+
if (!webViewCrashed) {
// The user could have left the activity to update the WebView
invalidateOptionsMenu();
@@ -585,6 +627,16 @@ protected void onResume() {
}
}
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ // Release wake lock when activity is not visible to save battery
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
private int getWebViewRelease() {
PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
String webViewVersionName = webViewPackage.versionName;
@@ -675,12 +727,40 @@ private static void enableDisableMenuItem(MenuItem item, boolean enable) {
public void onJumpToPageInDocument(final int selected_page) {
if (selected_page >= 1 && selected_page <= mNumPages && mPage != selected_page) {
mPage = selected_page;
- renderPage(0);
+ if (isVerticalScrollMode) {
+ binding.webview.evaluateJavascript("scrollToPageInDocument(" + selected_page + ")", null);
+ } else {
+ renderPage(0);
+ }
showPageNumber();
invalidateOptionsMenu();
}
}
+ private void toggleVerticalScrollMode() {
+ isVerticalScrollMode = !isVerticalScrollMode;
+ binding.webview.evaluateJavascript("setVerticalScrollMode(" + isVerticalScrollMode + ")", null);
+ invalidateOptionsMenu();
+ }
+
+ private void toggleKeepScreenOn() {
+ mKeepScreenOn = !mKeepScreenOn;
+ updateWakeLock();
+ invalidateOptionsMenu();
+ }
+
+ private void updateWakeLock() {
+ if (mKeepScreenOn) {
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ } else {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ }
+
private void showSystemUi() {
ViewKt.showSystemUi(binding.getRoot(), getWindow());
getSupportActionBar().show();
@@ -700,6 +780,8 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
savedInstanceState.putString(STATE_ENCRYPTED_DOCUMENT_PASSWORD, mEncryptedDocumentPassword);
+ savedInstanceState.putBoolean(STATE_VERTICAL_SCROLL_MODE, isVerticalScrollMode);
+ savedInstanceState.putBoolean(STATE_KEEP_SCREEN_ON, mKeepScreenOn);
}
private void showPageNumber() {
@@ -731,7 +813,7 @@ public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
R.id.action_next, R.id.action_previous, R.id.action_first, R.id.action_last,
R.id.action_rotate_clockwise, R.id.action_rotate_counterclockwise,
R.id.action_view_document_properties, R.id.action_share, R.id.action_save_as,
- R.id.action_outline));
+ R.id.action_outline, R.id.action_toggle_vertical_scroll, R.id.action_keep_screen_on));
if (BuildConfig.DEBUG) {
ids.add(R.id.debug_action_toggle_text_layer_visibility);
ids.add(R.id.debug_action_crash_webview);
@@ -757,14 +839,22 @@ public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
enableDisableMenuItem(menu.findItem(R.id.action_open),
!webViewCrashed && getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
- enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
- enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
+
+ // In vertical scroll mode, disable page navigation buttons
+ enableDisableMenuItem(menu.findItem(R.id.action_next), !isVerticalScrollMode && mPage < mNumPages);
+ enableDisableMenuItem(menu.findItem(R.id.action_previous), !isVerticalScrollMode && mPage > 1);
+ enableDisableMenuItem(menu.findItem(R.id.action_first), !isVerticalScrollMode);
+ enableDisableMenuItem(menu.findItem(R.id.action_last), !isVerticalScrollMode);
+
enableDisableMenuItem(menu.findItem(R.id.action_save_as), mUri != null);
enableDisableMenuItem(menu.findItem(R.id.action_view_document_properties),
mDocumentProperties != null);
menu.findItem(R.id.action_outline).setVisible(viewModel.hasOutline());
+ // Set checked state for toggleable menu items
+ menu.findItem(R.id.action_keep_screen_on).setChecked(mKeepScreenOn);
+
if (webViewCrashed) {
for (final int id : ids) {
enableDisableMenuItem(menu.findItem(id), false);
@@ -822,6 +912,12 @@ public boolean onOptionsItemSelected(MenuItem item) {
return true;
} else if (itemId == R.id.action_save_as) {
saveDocument();
+ } else if (itemId == R.id.action_toggle_vertical_scroll) {
+ toggleVerticalScrollMode();
+ return true;
+ } else if (itemId == R.id.action_keep_screen_on) {
+ toggleKeepScreenOn();
+ return true;
} else if (itemId == R.id.debug_action_toggle_text_layer_visibility) {
binding.webview.evaluateJavascript("toggleTextLayerVisibility()", null);
return true;
diff --git a/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml b/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml
new file mode 100644
index 00000000..ba72b4a6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_screen_lock_portrait_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_view_agenda_24dp.xml b/app/src/main/res/drawable/ic_view_agenda_24dp.xml
new file mode 100644
index 00000000..c8ec1452
--- /dev/null
+++ b/app/src/main/res/drawable/ic_view_agenda_24dp.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/menu/pdf_viewer.xml b/app/src/main/res/menu/pdf_viewer.xml
index 16cd9b28..f7ed2975 100644
--- a/app/src/main/res/menu/pdf_viewer.xml
+++ b/app/src/main/res/menu/pdf_viewer.xml
@@ -43,6 +43,12 @@
android:title="@string/action_jump_to_page"
app:showAsAction="ifRoom" />
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ef05b47d..a9deb7f8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -15,6 +15,8 @@
Outline
Properties
Close
+ Vertical scroll
+ Keep screen on
View nested outline entries
No outline available
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index ffbb825d..14e6b078 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -7,683 +7,847 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -697,57 +861,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -755,143 +933,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -921,9 +1137,11 @@
+
+
@@ -961,467 +1179,579 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1432,260 +1762,321 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1699,468 +2090,583 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2171,34 +2677,41 @@
+
+
+
+
+
+
+
@@ -2209,24 +2722,29 @@
+
+
+
+
+
@@ -2240,95 +2758,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2342,276 +2882,343 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2625,271 +3232,337 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2900,46 +3573,57 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -2948,6 +3632,7 @@
+
@@ -2956,90 +3641,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/viewer/css/pdf_viewer.css b/viewer/css/pdf_viewer.css
index d4b7e477..246e9aaa 100644
--- a/viewer/css/pdf_viewer.css
+++ b/viewer/css/pdf_viewer.css
@@ -11,6 +11,7 @@ canvas {
body {
background-color: #c0c0c0;
+ overflow-x: hidden;
}
#container {
@@ -33,6 +34,59 @@ body {
grid-column-start: 1;
}
+/* Vertical scrolling mode styles */
+#all-pages-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 20px;
+ width: 100%;
+ min-height: 100vh;
+ box-sizing: border-box;
+}
+
+.page-container {
+ position: relative;
+ margin-bottom: 20px;
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
+ background: white;
+ max-width: 100%;
+}
+
+.page-container::before {
+ content: "Page " attr(data-page-number);
+ position: absolute;
+ top: -25px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(0,0,0,0.7);
+ color: white;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-family: sans-serif;
+ z-index: 10;
+}
+
+.page-canvas {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+
+.page-text-layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ opacity: 0.2;
+ line-height: 1.0;
+ pointer-events: none;
+}
+
canvas {
display: inline-block;
position: relative;
diff --git a/viewer/js/index.js b/viewer/js/index.js
index 3eed8fee..b77ef63a 100644
--- a/viewer/js/index.js
+++ b/viewer/js/index.js
@@ -28,11 +28,22 @@ const maxCached = 6;
let isTextLayerVisible = false;
+// Vertical scrolling mode variables
+let isVerticalScrollMode = false;
+let allPagesContainer = null;
+let pageElements = [];
+let currentPageInView = 1;
+let scrollThrottledHandler = null;
+
function maybeRenderNextPage() {
if (renderPending) {
pageRendering = false;
renderPending = false;
- renderPage(channel.getPage(), renderPendingZoom, false);
+ if (isVerticalScrollMode) {
+ renderAllPages(renderPendingZoom);
+ } else {
+ renderPage(channel.getPage(), renderPendingZoom, false);
+ }
return true;
}
return false;
@@ -86,6 +97,257 @@ function getDefaultZoomRatio(page, orientationDegrees) {
return Math.max(Math.min(widthZoomRatio, heightZoomRatio, channel.getMaxZoomRatio()), channel.getMinZoomRatio());
}
+// Vertical scrolling mode functions
+function createAllPagesContainer() {
+ if (allPagesContainer) {
+ allPagesContainer.remove();
+ }
+
+ allPagesContainer = document.createElement("div");
+ allPagesContainer.id = "all-pages-container";
+ allPagesContainer.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ padding: 20px;
+ width: 100%;
+ min-height: 100vh;
+ `;
+
+ container.appendChild(allPagesContainer);
+
+ // Hide the single page canvas and text layer
+ canvas.style.display = "none";
+ textLayerDiv.style.display = "none";
+}
+
+function createPageElement(pageNumber) {
+ const pageContainer = document.createElement("div");
+ pageContainer.className = "page-container";
+ pageContainer.dataset.pageNumber = pageNumber;
+ pageContainer.style.cssText = `
+ position: relative;
+ margin-bottom: 20px;
+ box-shadow: 0 4px 8px rgba(0,0,0,0.3);
+ background: white;
+ `;
+
+ const pageCanvas = document.createElement("canvas");
+ pageCanvas.className = "page-canvas";
+ pageCanvas.style.cssText = `
+ display: block;
+ max-width: 100%;
+ height: auto;
+ `;
+
+ const pageTextLayer = document.createElement("div");
+ pageTextLayer.className = "textLayer page-text-layer";
+ pageTextLayer.style.cssText = `
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
+ opacity: 0.2;
+ line-height: 1.0;
+ --scale-factor: 1;
+ --user-unit: 1;
+ --total-scale-factor: calc(var(--scale-factor) * var(--user-unit));
+ --scale-round-x: 1px;
+ --scale-round-y: 1px;
+ `;
+
+ pageContainer.appendChild(pageCanvas);
+ pageContainer.appendChild(pageTextLayer);
+
+ return { pageContainer, pageCanvas, pageTextLayer };
+}
+
+async function renderAllPages(zoom = false) {
+ if (!pdfDoc || !isVerticalScrollMode) return;
+
+ pageRendering = true;
+
+ createAllPagesContainer();
+ pageElements = [];
+
+ const numPages = pdfDoc.numPages;
+ const containerWidth = container.clientWidth - 40; // Account for padding
+
+ // Calculate appropriate zoom for vertical layout
+ const firstPage = await pdfDoc.getPage(1);
+ const defaultZoom = getDefaultZoomRatio(firstPage, orientationDegrees);
+ const verticalZoom = Math.min(defaultZoom * 0.8, containerWidth / firstPage.getViewport({scale: 1}).width);
+
+ if (!zoom) {
+ zoomRatio = verticalZoom;
+ newZoomRatio = verticalZoom;
+ channel.setZoomRatio(verticalZoom);
+ } else {
+ zoomRatio = channel.getZoomRatio();
+ newZoomRatio = zoomRatio;
+ }
+
+ for (let pageNum = 1; pageNum <= numPages; pageNum++) {
+ try {
+ const page = await pdfDoc.getPage(pageNum);
+ const { pageContainer, pageCanvas, pageTextLayer } = createPageElement(pageNum);
+
+ allPagesContainer.appendChild(pageContainer);
+ pageElements.push({ pageContainer, pageCanvas, pageTextLayer, pageNum });
+
+ await renderSinglePageInVerticalMode(page, pageCanvas, pageTextLayer);
+
+ } catch (error) {
+ console.error(`Error rendering page ${pageNum}:`, error);
+ }
+ }
+
+ pageRendering = false;
+ setupScrollListener();
+ updateCurrentPageFromScroll();
+}
+
+async function renderSinglePageInVerticalMode(page, pageCanvas, pageTextLayer) {
+ const totalRotation = (orientationDegrees + page.rotate) % 360;
+ const viewport = page.getViewport({scale: newZoomRatio, rotation: totalRotation});
+
+ const ratio = globalThis.devicePixelRatio;
+
+ pageCanvas.height = viewport.height * ratio;
+ pageCanvas.width = viewport.width * ratio;
+ pageCanvas.style.height = viewport.height + "px";
+ pageCanvas.style.width = viewport.width + "px";
+
+ const context = pageCanvas.getContext("2d", { alpha: false });
+ context.scale(ratio, ratio);
+
+ // Render the page
+ await page.render({
+ canvasContext: context,
+ viewport: viewport
+ }).promise;
+
+ // Render text layer
+ const textLayer = new TextLayer({
+ textContentSource: page.streamTextContent(),
+ container: pageTextLayer,
+ viewport: viewport
+ });
+
+ await textLayer.render();
+
+ // Set text layer transform
+ pageTextLayer.style.transform = `scale(${newZoomRatio})`;
+ pageTextLayer.style.transformOrigin = "0 0";
+}
+
+function setupScrollListener() {
+ if (!isVerticalScrollMode) return;
+
+ // Remove existing listener first
+ if (scrollThrottledHandler) {
+ window.removeEventListener("scroll", scrollThrottledHandler);
+ }
+
+ scrollThrottledHandler = throttle(() => {
+ updateCurrentPageFromScroll();
+ }, 100);
+
+ window.addEventListener("scroll", scrollThrottledHandler);
+}
+
+function updateCurrentPageFromScroll() {
+ if (!isVerticalScrollMode || pageElements.length === 0) return;
+
+ const scrollY = window.scrollY;
+ const windowHeight = window.innerHeight;
+ const scrollCenter = scrollY + windowHeight / 2;
+
+ let closestPage = 1;
+ let closestDistance = Infinity;
+
+ pageElements.forEach(({ pageContainer, pageNum }) => {
+ const rect = pageContainer.getBoundingClientRect();
+ const pageCenter = rect.top + scrollY + rect.height / 2;
+ const distance = Math.abs(scrollCenter - pageCenter);
+
+ if (distance < closestDistance) {
+ closestDistance = distance;
+ closestPage = pageNum;
+ }
+ });
+
+ if (closestPage !== currentPageInView) {
+ currentPageInView = closestPage;
+ // Notify Android about page change
+ if (typeof channel !== "undefined" && channel.onPageChanged) {
+ try {
+ channel.onPageChanged(currentPageInView);
+ } catch (e) {
+ console.log("Error calling onPageChanged:", e);
+ }
+ }
+ }
+}
+
+function scrollToPage(pageNumber) {
+ if (!isVerticalScrollMode || !pageElements.length) return;
+
+ const pageElement = pageElements.find(p => p.pageNum === pageNumber);
+ if (pageElement) {
+ pageElement.pageContainer.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ }
+}
+
+function switchToVerticalMode() {
+ isVerticalScrollMode = true;
+ renderAllPages();
+}
+
+function switchToPageMode() {
+ isVerticalScrollMode = false;
+
+ // Clean up vertical mode elements
+ if (allPagesContainer) {
+ allPagesContainer.remove();
+ allPagesContainer = null;
+ }
+
+ pageElements = [];
+
+ // Show single page elements
+ canvas.style.display = "inline-block";
+ textLayerDiv.style.display = "block";
+
+ // Remove scroll listener
+ if (scrollThrottledHandler) {
+ window.removeEventListener("scroll", scrollThrottledHandler);
+ scrollThrottledHandler = null;
+ }
+
+ // Render current page
+ renderPage(channel.getPage(), false, false);
+}
+
+function throttle(func, limit) {
+ let inThrottle;
+ return function() {
+ const args = arguments;
+ const context = this;
+ if (!inThrottle) {
+ func.apply(context, args);
+ inThrottle = true;
+ setTimeout(() => inThrottle = false, limit);
+ }
+ };
+}
+
/**
* Does BFS traversal of all of the nodes in the outline tree to convert the tree so that the
* nodes are of a simpler form. The simple outline nodes have the following structure:
@@ -337,6 +599,16 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger = 0) {
}
globalThis.onRenderPage = function (zoom) {
+ if (isVerticalScrollMode) {
+ if (pageRendering) {
+ renderPending = true;
+ renderPendingZoom = zoom;
+ } else {
+ renderAllPages(zoom);
+ }
+ return;
+ }
+
if (pageRendering) {
if (newPageNumber === channel.getPage() && newZoomRatio === channel.getZoomRatio() &&
orientationDegrees === channel.getDocumentOrientationDegrees()) {
@@ -389,6 +661,29 @@ globalThis.toggleTextLayerVisibility = function () {
isTextLayerVisible = !isTextLayerVisible;
};
+// Vertical scrolling mode global functions
+globalThis.setVerticalScrollMode = function(enabled) {
+ if (enabled) {
+ switchToVerticalMode();
+ } else {
+ switchToPageMode();
+ }
+};
+
+globalThis.isVerticalScrollMode = function() {
+ return isVerticalScrollMode;
+};
+
+globalThis.getCurrentPageInView = function() {
+ return isVerticalScrollMode ? currentPageInView : channel.getPage();
+};
+
+globalThis.scrollToPageInDocument = function(pageNumber) {
+ if (isVerticalScrollMode) {
+ scrollToPage(pageNumber);
+ }
+};
+
globalThis.loadDocument = function () {
const pdfPassword = channel.getPassword();
const loadingTask = getDocument({
@@ -430,7 +725,14 @@ globalThis.loadDocument = function () {
}).catch(function(error) {
console.log("getOutline error: " + error);
});
- renderPage(channel.getPage(), false, false);
+
+ // Check if we should start in vertical scroll mode
+ const shouldUseVerticalMode = channel.isVerticalScrollMode && channel.isVerticalScrollMode();
+ if (shouldUseVerticalMode) {
+ switchToVerticalMode();
+ } else {
+ renderPage(channel.getPage(), false, false);
+ }
}, function (reason) {
console.error(reason.name + ": " + reason.message);
});