Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import app.grapheneos.pdfviewer.fragment.SetZoomFragment;
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
import app.grapheneos.pdfviewer.ktx.ViewKt;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesAsyncTaskLoader;
Expand Down Expand Up @@ -443,12 +444,12 @@ public boolean onTapUp() {

@Override
public void onZoom(float scaleFactor, float focusX, float focusY) {
zoom(scaleFactor, focusX, focusY, false);
onZoomPage(scaleFactor, focusX, focusY, false);
}

@Override
public void onZoomEnd() {
zoomEnd();
onZoomPageEnd();
}
});

Expand Down Expand Up @@ -643,15 +644,15 @@ private void shareDocument() {
}
}

private void zoom(float scaleFactor, float focusX, float focusY, boolean end) {
public void onZoomPage(float scaleFactor, float focusX, float focusY, boolean end) {
mZoomRatio = Math.min(Math.max(mZoomRatio * scaleFactor, MIN_ZOOM_RATIO), MAX_ZOOM_RATIO);
mZoomFocusX = focusX;
mZoomFocusY = focusY;
renderPage(end ? 1 : 2);
invalidateOptionsMenu();
}

private void zoomEnd() {
public void onZoomPageEnd() {
renderPage(1);
}

Expand Down Expand Up @@ -721,7 +722,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_set_zoom));
if (BuildConfig.DEBUG) {
ids.add(R.id.debug_action_toggle_text_layer_visibility);
ids.add(R.id.debug_action_crash_webview);
Expand Down Expand Up @@ -750,6 +751,7 @@ public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
enableDisableMenuItem(menu.findItem(R.id.action_save_as), mUri != null);
enableDisableMenuItem(menu.findItem(R.id.action_set_zoom), mUri != null);
enableDisableMenuItem(menu.findItem(R.id.action_view_document_properties),
mDocumentProperties != null);

Expand Down Expand Up @@ -807,6 +809,13 @@ public boolean onOptionsItemSelected(MenuItem item) {
new JumpToPageFragment()
.show(getSupportFragmentManager(), JumpToPageFragment.TAG);
return true;
} else if (itemId == R.id.action_set_zoom) {
SetZoomFragment zoomFragment = new SetZoomFragment(mZoomRatio, MIN_ZOOM_RATIO, MAX_ZOOM_RATIO);
// TODO: horizontally center the zooming focus.
// Need to get the coordinates of viewport top-center.
// zoomFragment.setZoomFocusX((float) binding.webview.getWidth() / 2);
zoomFragment.show(getSupportFragmentManager(), SetZoomFragment.TAG);
return true;
} else if (itemId == R.id.action_share) {
shareDocument();
return true;
Expand Down
125 changes: 125 additions & 0 deletions app/src/main/java/app/grapheneos/pdfviewer/fragment/SetZoomFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package app.grapheneos.pdfviewer.fragment

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.Gravity
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.TextView
import androidx.core.view.marginTop
import androidx.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.PdfViewer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlin.math.ln
import kotlin.math.pow

class SetZoomFragment(
private var mCurrentViewerZoomRatio: Double,
private var mMinZoomRatio: Double,
private var mMaxZoomRatio: Double,
) : DialogFragment() {

companion object {
const val TAG = "SetZoomFragment"
private const val STATE_SEEKBAR_CUR = "seekbar_cur"
private const val STATE_SEEKBAR_MIN = "seekbar_min"
private const val STATE_SEEKBAR_MAX = "seekbar_max"
private const val STATE_VIEWER_CUR = "viewer_cur"
private const val STATE_VIEWER_MIN = "viewer_min"
private const val STATE_VIEWER_MAX = "viewer_max"
private const val STATE_ZOOM_FOCUSX = "viewer_zoom_focusx"
private const val STATE_ZOOM_FOCUSY = "viewer_zoom_focusy"
private const val SEEKBAR_RESOLUTION = 1024
}

private val mSeekBar: SeekBar by lazy { SeekBar(requireActivity()) }
private val mZoomLevelText: TextView by lazy { TextView(requireActivity()) }

private var mZoomFocusX: Float = 0.0f
public fun setZoomFocusX(value: Float) {mZoomFocusX = value}
private var mZoomFocusY: Float = 0.0f
public fun setZoomFocusY(value: Float) {mZoomFocusY = value}

private fun progressToZoom(progress: Int): Double {
val progressClip = progress.coerceAtLeast(0).coerceAtMost(SEEKBAR_RESOLUTION);
return mMinZoomRatio * (mMaxZoomRatio / mMinZoomRatio).pow(progressClip.toDouble() / SEEKBAR_RESOLUTION)
}

private fun zoomToProgress(zoom: Double): Int {
val zoomClip = zoom.coerceAtLeast(mMinZoomRatio).coerceAtMost(mMaxZoomRatio);
return (SEEKBAR_RESOLUTION * ln(zoomClip / mMinZoomRatio) / ln(mMaxZoomRatio / mMinZoomRatio)).toInt()
}

fun refreshZoomText(progress: Int) {
mZoomLevelText.text = "${(progressToZoom(progress) * 100).toInt()}%"
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

val viewerActivity: PdfViewer = (requireActivity() as PdfViewer)

if (savedInstanceState != null) {
val progress = savedInstanceState.getInt(STATE_SEEKBAR_CUR)
mSeekBar.setMin(savedInstanceState.getInt(STATE_SEEKBAR_MIN))
mSeekBar.setMax(savedInstanceState.getInt(STATE_SEEKBAR_MAX))
mSeekBar.progress = progress
refreshZoomText(progress)
mCurrentViewerZoomRatio = savedInstanceState.getDouble(STATE_VIEWER_CUR)
mMinZoomRatio = savedInstanceState.getDouble(STATE_VIEWER_MIN)
mMaxZoomRatio = savedInstanceState.getDouble(STATE_VIEWER_MAX)
mZoomFocusX = savedInstanceState.getFloat(STATE_ZOOM_FOCUSX)
mZoomFocusY = savedInstanceState.getFloat(STATE_ZOOM_FOCUSY)
} else {
mSeekBar.setMin(0)
mSeekBar.setMax(SEEKBAR_RESOLUTION)
val progress = zoomToProgress(mCurrentViewerZoomRatio)
mSeekBar.setProgress(progress)
refreshZoomText(progress)
}
mSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
refreshZoomText(progress)
}

override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
val layout = LinearLayout(requireActivity())
layout.orientation = LinearLayout.VERTICAL
layout.gravity = Gravity.CENTER
val textParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
)
textParams.setMargins(0, 24, 0, 0) // Margin above the text
layout.addView(mZoomLevelText, textParams)
layout.addView(
mSeekBar, LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
)
)
return MaterialAlertDialogBuilder(requireActivity())
.setView(layout)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
mSeekBar.clearFocus()
val zoom = progressToZoom(mSeekBar.progress)
viewerActivity.onZoomPage((zoom / mCurrentViewerZoomRatio).toFloat(), mZoomFocusX, mZoomFocusY, true)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}

override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(STATE_SEEKBAR_CUR, mSeekBar.progress)
outState.putInt(STATE_SEEKBAR_MIN, mSeekBar.min)
outState.putInt(STATE_SEEKBAR_MAX, mSeekBar.max)
outState.putDouble(STATE_VIEWER_CUR, mCurrentViewerZoomRatio)
outState.putDouble(STATE_VIEWER_MIN, mMinZoomRatio)
outState.putDouble(STATE_VIEWER_MAX, mMaxZoomRatio)
outState.putFloat(STATE_ZOOM_FOCUSX, mZoomFocusX)
outState.putFloat(STATE_ZOOM_FOCUSY, mZoomFocusY)
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_zoom_in_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M12.43 9.18h-1.438V7.754a0.83 0.83 0 0 0 -0.832 -0.828 0.826 0.826 0 0 0 -0.816 0.828V9.18H7.906a0.844 0.844 0 0 0 -0.828 0.828c0.012 0.46 0.383 0.832 0.828 0.832h1.438v1.426c0 0.457 0.37 0.828 0.816 0.828a0.83 0.83 0 0 0 0.832 -0.828V10.84h1.438a0.827 0.827 0 0 0 0.816 -0.832 0.826 0.826 0 0 0 -0.816 -0.828m0 0" />
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M19.773 17.059l-3.68 -3.692c-0.046 -0.05 -0.109 -0.097 -0.171 -0.148a6.4 6.4 0 0 0 0.828 -3.172c0 -3.617 -2.945 -6.555 -6.566 -6.555a6.56 6.56 0 0 0 -6.555 6.555c0 3.617 2.937 6.566 6.555 6.566a6.5 6.5 0 0 0 2.828 -0.644c0.058 0.101 0.133 0.187 0.222 0.273l3.68 3.68c0.793 0.781 2.082 0.781 2.86 0a2.02 2.02 0 0 0 0 -2.863m-9.59 -2.973a4.037 4.037 0 0 1 -4.027 -4.04 4.034 4.034 0 0 1 4.028 -4.026c2.23 0 4.039 1.808 4.039 4.027a4.04 4.04 0 0 1 -4.04 4.039m0 0" />
</vector>
6 changes: 6 additions & 0 deletions app/src/main/res/menu/pdf_viewer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
android:title="@string/action_jump_to_page"
app:showAsAction="ifRoom" />

<item
android:id="@+id/action_set_zoom"
android:icon="@drawable/ic_zoom_in_24dp"
android:title="@string/action_set_zoom"
app:showAsAction="ifRoom" />

<item
android:id="@+id/action_rotate_clockwise"
android:icon="@drawable/ic_rotate_right_24dp"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<string name="action_first">First page</string>
<string name="action_last">Last page</string>
<string name="action_jump_to_page">Jump to page</string>
<string name="action_set_zoom">Zoom</string>
<string name="action_rotate_clockwise">Rotate clockwise</string>
<string name="action_rotate_counterclockwise">Rotate counterclockwise</string>
<string name="action_share">Share</string>
Expand Down