From ecf9dbef1e929861b68fbdd50da7c59b76c8d9d7 Mon Sep 17 00:00:00 2001 From: hunterstich Date: Sat, 7 Feb 2026 13:50:08 -0500 Subject: [PATCH 1/2] Make pdf edge-to-edge and add logic to dodge window insets --- .../app/grapheneos/pdfviewer/PdfViewer.java | 55 ++++++++++++++++++- app/src/main/res/layout/pdfviewer.xml | 12 ++-- viewer/js/index.js | 7 +++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java index 936f70851..42a66e695 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java @@ -29,7 +29,11 @@ import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.OnApplyWindowInsetsListener; +import androidx.core.view.ViewCompat; import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; @@ -125,6 +129,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader private float mZoomRatio = 1f; private float mZoomFocusX = 0f; private float mZoomFocusY = 0f; + private float mInsetLeft = 0f; + private float mInsetTop = 0f; + private float mInsetRight = 0f; + private float mInsetBottom = 0f; private int mDocumentOrientationDegrees; private int mDocumentState; private String mEncryptedDocumentPassword; @@ -138,6 +146,13 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader private PasswordPromptFragment mPasswordPromptFragment; public PdfViewModel viewModel; + private final View.OnLayoutChangeListener appBarOnLayoutChangeListener = + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (binding.toolbar.getVisibility() == View.VISIBLE) { + mInsetTop = bottom - top; + } + }; + private final ActivityResultLauncher openDocumentLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result == null) return; @@ -218,6 +233,26 @@ public float getMaxZoomRatio() { return MAX_ZOOM_RATIO; } + @JavascriptInterface + public float getInsetLeft() { + return mInsetLeft; + } + + @JavascriptInterface + public float getInsetTop() { + return mInsetTop; + } + + @JavascriptInterface + public float getInsetRight() { + return mInsetRight; + } + + @JavascriptInterface + public float getInsetBottom() { + return mInsetBottom; + } + @JavascriptInterface public int getDocumentOrientationDegrees() { return mDocumentOrientationDegrees; @@ -279,7 +314,7 @@ private void showWebViewCrashed() { @SuppressLint({"SetJavaScriptEnabled"}) protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WindowCompat.setDecorFitsSystemWindows(getWindow(), false); + WindowCompat.enableEdgeToEdge(getWindow()); binding = PdfviewerBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -308,6 +343,21 @@ protected void onCreate(Bundle savedInstanceState) { // Margins for the toolbar are needed, so that content of the toolbar // is not covered by a system button navigation bar when in landscape. KtUtilsKt.applySystemBarMargins(binding.toolbar, false); + ViewCompat.setOnApplyWindowInsetsListener( + binding.webview, new OnApplyWindowInsetsListener() { + @Override + public @NonNull WindowInsetsCompat onApplyWindowInsets( + @NonNull View v, @NonNull WindowInsetsCompat insets) { + Insets allInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() + + WindowInsetsCompat.Type.displayCutout()); + mInsetLeft = allInsets.left; + mInsetRight = allInsets.right; + // Only set the bottom inset. The top will use the height of the app bar layout + // which includes the status bar/display cutout. + mInsetBottom = allInsets.bottom; + return insets; + } + }); binding.webview.setBackgroundColor(Color.TRANSPARENT); @@ -503,6 +553,8 @@ public void onZoomEnd() { mEncryptedDocumentPassword = savedInstanceState.getString(STATE_ENCRYPTED_DOCUMENT_PASSWORD); } + binding.appBarLayout.addOnLayoutChangeListener(appBarOnLayoutChangeListener); + binding.webviewAlertReload.setOnClickListener(v -> { webViewCrashed = false; recreate(); @@ -529,6 +581,7 @@ private void purgeWebView() { @Override protected void onDestroy() { super.onDestroy(); + binding.appBarLayout.removeOnLayoutChangeListener(appBarOnLayoutChangeListener); purgeWebView(); maybeCloseInputStream(); } diff --git a/app/src/main/res/layout/pdfviewer.xml b/app/src/main/res/layout/pdfviewer.xml index dc73b98af..b1e2bfd3d 100644 --- a/app/src/main/res/layout/pdfviewer.xml +++ b/app/src/main/res/layout/pdfviewer.xml @@ -4,7 +4,13 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + - - Date: Thu, 5 Mar 2026 09:24:47 -0500 Subject: [PATCH 2/2] Update setLayerTransform to take insets into account --- .../app/grapheneos/pdfviewer/PdfViewer.java | 2 +- viewer/js/index.js | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java index 42a66e695..2ecf96759 100644 --- a/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java +++ b/app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java @@ -349,7 +349,7 @@ binding.webview, new OnApplyWindowInsetsListener() { public @NonNull WindowInsetsCompat onApplyWindowInsets( @NonNull View v, @NonNull WindowInsetsCompat insets) { Insets allInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars() - + WindowInsetsCompat.Type.displayCutout()); + | WindowInsetsCompat.Type.displayCutout()); mInsetLeft = allInsets.left; mInsetRight = allInsets.right; // Only set the bottom inset. The top will use the height of the app bar layout diff --git a/viewer/js/index.js b/viewer/js/index.js index e9debbcfc..b736c1241 100644 --- a/viewer/js/index.js +++ b/viewer/js/index.js @@ -71,9 +71,23 @@ function display(newCanvas, zoom) { } function setLayerTransform(pageWidth, pageHeight, layerDiv) { + const cs = globalThis.getComputedStyle(canvas); + const insetLeft = parseFloat(cs.paddingLeft) || 0; + const insetTop = parseFloat(cs.paddingTop) || 0; + const insetRight = parseFloat(cs.paddingRight) || 0; + const insetBottom = parseFloat(cs.paddingBottom) || 0; + + const isOverflownY = canvas.clientHeight > document.body.clientHeight; + const isOverflownX = canvas.clientWidth > document.body.clientWidth; + // Translate the text layer to stay aligned with the rendered page including canvas insets and + // grid centering effects. const translate = { - X: Math.max(0, pageWidth - document.body.clientWidth) / 2, - Y: Math.max(0, pageHeight - document.body.clientHeight) / 2 + X: isOverflownX + ? insetLeft - (document.body.clientWidth - pageWidth) / 2 + : (insetLeft - insetRight) / 2, + Y: isOverflownY + ? insetTop - (document.body.clientHeight - pageHeight) / 2 + : (insetTop - insetBottom) / 2 }; layerDiv.style.translate = `${translate.X}px ${translate.Y}px`; }