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
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class PdfViewerEdgeToEdgeTest {
scenario,
"""
(function() {
const canvas = document.getElementById('content');
const canvas = document.getElementById('container');
const value = parseFloat(getComputedStyle(canvas)['$propertyName']) || 0;
return value * globalThis.devicePixelRatio;
})()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ class PdfViewerRobot(private val composeRule: ComposeTestRule) {

fun assertCanvasRendered(scenario: ActivityScenario<PdfViewer>) {
val result = PdfViewerTestUtils.evaluateJs(scenario,
"parseInt(document.getElementById('content').style.width) > 0 " +
"&& parseInt(document.getElementById('content').style.height) > 0"
"parseInt(globalThis.currentPageCanvas().style.width) > 0 " +
"&& parseInt(globalThis.currentPageCanvas().style.height) > 0"
)
assertTrue("Canvas should have non-zero CSS dimensions after rendering", result == "true")
}
Expand All @@ -338,28 +338,28 @@ class PdfViewerRobot(private val composeRule: ComposeTestRule) {

fun getCanvasWidth(scenario: ActivityScenario<PdfViewer>): Int {
val result = PdfViewerTestUtils.evaluateJs(scenario,
"document.getElementById('content').width"
"globalThis.currentPageCanvas().width"
)
return result.toInt()
}

fun getCanvasHeight(scenario: ActivityScenario<PdfViewer>): Int {
val result = PdfViewerTestUtils.evaluateJs(scenario,
"document.getElementById('content').height"
"globalThis.currentPageCanvas().height"
)
return result.toInt()
}

fun getCanvasCssWidth(scenario: ActivityScenario<PdfViewer>): Int {
val result = PdfViewerTestUtils.evaluateJs(scenario,
"parseInt(document.getElementById('content').style.width) || 0"
"parseInt(globalThis.currentPageCanvas().style.width) || 0"
)
return result.toInt()
}

fun getCanvasCssHeight(scenario: ActivityScenario<PdfViewer>): Int {
val result = PdfViewerTestUtils.evaluateJs(scenario,
"parseInt(document.getElementById('content').style.height) || 0"
"parseInt(globalThis.currentPageCanvas().style.height) || 0"
)
return result.toInt()
}
Expand Down Expand Up @@ -564,9 +564,9 @@ class PdfViewerRobot(private val composeRule: ComposeTestRule) {
fun assertTextLayerAligned(scenario: ActivityScenario<PdfViewer>) {
val result = PdfViewerTestUtils.evaluateJs(scenario, """
(function() {
var text = document.getElementById('text');
var text = globalThis.currentPageTextLayer();
var container = document.getElementById('container');
var canvas = document.getElementById('content');
var canvas = globalThis.currentPageCanvas();
if (!text || !container || !canvas) return 'missing_elements';
if (text.hidden) return 'text_hidden';
var scaleFactor = container.style.getPropertyValue('--scale-factor');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ object PdfViewerTestUtils {
) {
try {
val result = evaluateJs(scenario,
"parseInt(document.getElementById('content').style.width) > 0 " +
"&& parseInt(document.getElementById('content').style.height) > 0 " +
"parseInt(globalThis.currentPageCanvas().style.width) > 0 " +
"&& parseInt(globalThis.currentPageCanvas().style.height) > 0 " +
"&& parseFloat(document.getElementById('container').style.getPropertyValue('--scale-factor')) > 0 " +
"&& !document.getElementById('text').hidden"
"&& !globalThis.currentPageTextLayer().hidden"
)
Log.d("WAIT", " canvas check result=$result, elapsed=${System.currentTimeMillis() - start}ms")
result == "true"
Expand All @@ -153,7 +153,7 @@ object PdfViewerTestUtils {
) {
try {
val result = evaluateJs(scenario,
"document.getElementById('text').textContent"
"globalThis.currentPageTextLayer().textContent"
)
Log.d("WAIT", " text layer result=${result.take(80)}, elapsed=${System.currentTimeMillis() - start}ms")
result.contains(expected)
Expand All @@ -165,7 +165,7 @@ object PdfViewerTestUtils {
Log.d("WAIT", "assertTextLayerContent: done in ${System.currentTimeMillis() - start}ms")
} catch (_: AssertionError) {
val actual = try {
evaluateJs(scenario, "document.getElementById('text').textContent")
evaluateJs(scenario, "globalThis.currentPageTextLayer().textContent")
} catch (e: Throwable) {
"JS evaluation failed: ${e.message}"
}
Expand Down Expand Up @@ -251,8 +251,8 @@ object PdfViewerTestUtils {
"within ${timeout}ms"
}
) {
val w = evaluateJs(scenario, "document.getElementById('content').width").toIntOrNull()
val h = evaluateJs(scenario, "document.getElementById('content').height").toIntOrNull()
val w = evaluateJs(scenario, "globalThis.currentPageCanvas().width").toIntOrNull()
val h = evaluateJs(scenario, "globalThis.currentPageCanvas().height").toIntOrNull()
w != null && h != null && (w != previousWidth || h != previousHeight)
}
}
Expand All @@ -272,11 +272,11 @@ object PdfViewerTestUtils {
) {
val w = evaluateJs(
scenario,
"parseInt(document.getElementById('content').style.width) || 0"
"parseInt(globalThis.currentPageCanvas().style.width) || 0"
).toIntOrNull()
val h = evaluateJs(
scenario,
"parseInt(document.getElementById('content').style.height) || 0"
"parseInt(globalThis.currentPageCanvas().style.height) || 0"
).toIntOrNull()
w != null && h != null && (w != previousWidth || h != previousHeight)
}
Expand Down Expand Up @@ -366,7 +366,7 @@ object PdfViewerTestUtils {
scenario, """
(function() {
var range = document.createRange();
range.selectNodeContents(document.getElementById('text'));
range.selectNodeContents(globalThis.currentPageTextLayer());
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/app/grapheneos/pdfviewer/PdfJsChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ class PdfJsChannel(private val viewModel: PdfViewModel) {
@JavascriptInterface
fun getPage(): Int = viewModel.page.value

@JavascriptInterface
fun setCurrentPage(page: Int) {
viewModel.setPage(page)
viewModel.showPageIndicator()
}

@JavascriptInterface
fun getZoomRatio(): Float = viewModel.zoomRatio

Expand Down Expand Up @@ -62,6 +68,12 @@ class PdfJsChannel(private val viewModel: PdfViewModel) {
@JavascriptInterface
fun getDocumentOrientationDegrees(): Int = viewModel.documentOrientationDegrees.value

@JavascriptInterface
fun getPageFitMode(): Int = viewModel.pageFitMode.value

@JavascriptInterface
fun getContinuousMode(): Boolean = viewModel.continuousMode.value

@JavascriptInterface
fun setNumPages(numPages: Int) {
viewModel.setNumPages(numPages)
Expand Down
98 changes: 96 additions & 2 deletions app/src/main/java/app/grapheneos/pdfviewer/PdfViewerScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
Expand Down Expand Up @@ -216,6 +217,8 @@ fun PdfViewerScreen(
val webViewCrashed by viewModel.webViewCrashed.collectAsStateWithLifecycle()
val numPages by viewModel.numPages.collectAsStateWithLifecycle()
val page by viewModel.page.collectAsStateWithLifecycle()
val pageFitMode by viewModel.pageFitMode.collectAsStateWithLifecycle()
val continuousMode by viewModel.continuousMode.collectAsStateWithLifecycle()
val documentName by viewModel.documentName.collectAsStateWithLifecycle()
val documentProperties by viewModel.documentProperties.collectAsStateWithLifecycle()
val outlineStatus by viewModel.outline.collectAsStateWithLifecycle()
Expand Down Expand Up @@ -313,6 +316,29 @@ fun PdfViewerScreen(

DisposableEffect(webView) {
val wv = webView ?: return@DisposableEffect onDispose {}
var zoomRenderInFlight = false
var zoomRenderPending = false
var zoomRenderEndPending = false

fun dispatchPendingZoomRender() {
if (zoomRenderInFlight || !zoomRenderPending) return

val zoom = if (zoomRenderEndPending) 1 else 2
zoomRenderPending = false
zoomRenderEndPending = false
zoomRenderInFlight = true
wv.evaluateJavascript("onRenderPage($zoom)") {
zoomRenderInFlight = false
dispatchPendingZoomRender()
}
}

fun requestZoomRender(end: Boolean) {
zoomRenderPending = true
zoomRenderEndPending = zoomRenderEndPending || end
dispatchPendingZoomRender()
}

GestureHelper.attach(context, wv, object : GestureHelper.GestureListener {
override fun onTapUp(): Boolean {
if (viewModel.uri.value == null) return false
Expand Down Expand Up @@ -349,15 +375,16 @@ fun PdfViewerScreen(
}

override fun onZoom(scaleFactor: Float, focusX: Float, focusY: Float) {
viewModel.setPageFitMode(0)
viewModel.zoomRatio = (viewModel.zoomRatio * scaleFactor)
.coerceIn(MIN_ZOOM_RATIO, MAX_ZOOM_RATIO)
viewModel.zoomFocusX = focusX
viewModel.zoomFocusY = focusY
wv.evaluateJavascript("onRenderPage(2)", null)
requestZoomRender(end = false)
}

override fun onZoomEnd() {
wv.evaluateJavascript("onRenderPage(1)", null)
requestZoomRender(end = true)
}
})
onDispose {
Expand Down Expand Up @@ -478,6 +505,8 @@ fun PdfViewerScreen(
enabled = enabled,
page = page,
numPages = numPages,
pageFitMode = pageFitMode,
continuousMode = continuousMode,
hasOutline = viewModel.hasOutline(),
hasDocumentProperties = documentProperties != null,
hasUri = uri != null,
Expand All @@ -495,6 +524,12 @@ fun PdfViewerScreen(
onFirst = { jumpToPage(viewModel, webView, 1) },
onLast = { jumpToPage(viewModel, webView, numPages) },
onJumpToPage = { showJumpToPage = true },
onFitFree = { setPageFitMode(viewModel, webView, 0) },
onFitPage = { setPageFitMode(viewModel, webView, 1) },
onFitWidth = { setPageFitMode(viewModel, webView, 2) },
onContinuousModeChange = {
setContinuousMode(viewModel, webView, !continuousMode)
},
onRotateClockwise = { rotateDocument(viewModel, webView, 90) },
onRotateCounterClockwise = { rotateDocument(viewModel, webView, -90) },
onOutline = { showOutline = true },
Expand Down Expand Up @@ -722,6 +757,19 @@ internal fun jumpToPage(viewModel: PdfViewModel, webView: WebView?, selectedPage
}
}

private fun setPageFitMode(viewModel: PdfViewModel, webView: WebView?, mode: Int) {
webView ?: return
viewModel.setPageFitMode(mode)
viewModel.zoomRatio = 0f
webView.evaluateJavascript("onRenderPage(0)", null)
}

private fun setContinuousMode(viewModel: PdfViewModel, webView: WebView?, enabled: Boolean) {
webView ?: return
viewModel.setContinuousMode(enabled)
webView.evaluateJavascript("setContinuousMode($enabled)", null)
}

private fun rotateDocument(viewModel: PdfViewModel, webView: WebView?, offset: Int) {
webView ?: return
var degrees = (viewModel.documentOrientationDegrees.value + offset) % 360
Expand Down Expand Up @@ -759,6 +807,8 @@ private fun PdfTopAppBar(
enabled: Boolean,
page: Int,
numPages: Int,
pageFitMode: Int,
continuousMode: Boolean,
hasOutline: Boolean,
hasDocumentProperties: Boolean,
hasUri: Boolean,
Expand All @@ -770,6 +820,10 @@ private fun PdfTopAppBar(
onFirst: () -> Unit,
onLast: () -> Unit,
onJumpToPage: () -> Unit,
onFitFree: () -> Unit,
onFitPage: () -> Unit,
onFitWidth: () -> Unit,
onContinuousModeChange: () -> Unit,
onRotateClockwise: () -> Unit,
onRotateCounterClockwise: () -> Unit,
onOutline: () -> Unit,
Expand Down Expand Up @@ -852,6 +906,46 @@ private fun PdfTopAppBar(
)
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.action_fit_free)) },
onClick = { onMenuToggle(false); onFitFree() },
enabled = enabled,
leadingIcon = {
if (pageFitMode == 0) {
Icon(Icons.Default.Check, contentDescription = null)
}
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.action_fit_page)) },
onClick = { onMenuToggle(false); onFitPage() },
enabled = enabled,
leadingIcon = {
if (pageFitMode == 1) {
Icon(Icons.Default.Check, contentDescription = null)
}
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.action_fit_width)) },
onClick = { onMenuToggle(false); onFitWidth() },
enabled = enabled,
leadingIcon = {
if (pageFitMode == 2) {
Icon(Icons.Default.Check, contentDescription = null)
}
}
)
DropdownMenuItem(
text = { Text(stringResource(R.string.action_continuous_scroll)) },
onClick = { onMenuToggle(false); onContinuousModeChange() },
enabled = enabled,
leadingIcon = {
if (continuousMode) {
Icon(Icons.Default.Check, contentDescription = null)
}
}
)
}
DropdownMenuItem(
text = { Text(stringResource(R.string.action_rotate_clockwise)) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class PdfViewModel(
private const val STATE_URI: String = "uri"
private const val STATE_PAGE: String = "page"
private const val STATE_DOCUMENT_ORIENTATION_DEGREES: String = "documentOrientationDegrees"
private const val STATE_PAGE_FIT_MODE: String = "pageFitMode"
private const val STATE_CONTINUOUS_MODE: String = "continuousMode"
private const val STATE_DOCUMENT_PROPERTIES = "documentProperties"
private const val STATE_DOCUMENT_NAME = "documentName"
}
Expand All @@ -57,6 +59,13 @@ class PdfViewModel(
savedStateHandle[STATE_DOCUMENT_ORIENTATION_DEGREES] = value
}

val pageFitMode: StateFlow<Int> = savedStateHandle.getStateFlow(STATE_PAGE_FIT_MODE, 1)
fun setPageFitMode(value: Int) { savedStateHandle[STATE_PAGE_FIT_MODE] = value }

val continuousMode: StateFlow<Boolean> =
savedStateHandle.getStateFlow(STATE_CONTINUOUS_MODE, true)
fun setContinuousMode(value: Boolean) { savedStateHandle[STATE_CONTINUOUS_MODE] = value }

val documentProperties: StateFlow<Map<DocumentProperty, String>?> =
savedStateHandle.getStateFlow(STATE_DOCUMENT_PROPERTIES, null)

Expand Down Expand Up @@ -285,6 +294,8 @@ class PdfViewModel(
_numPages.value = 0
zoomRatio = 0f
setDocumentOrientationDegrees(0)
setPageFitMode(1)
setContinuousMode(true)
encryptedDocumentPassword = ""
clearOutline()
clearDocumentProperties()
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<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_fit_free">Free zoom</string>
<string name="action_fit_page">Fit page</string>
<string name="action_fit_width">Fit width</string>
<string name="action_continuous_scroll">Continuous scrolling</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
Loading