1diff --git a/android/src/main/java/org/wonday/pdf/viewer/PdfViewerView.kt b/android/src/main/java/org/wonday/pdf/viewer/PdfViewerView.kt
2index c51515507544fb7a56f029fff8ea2b1ee35cbe9d..2de8b3ecfa0b75d55f0868c700a0a56b83c17053 100644
3--- a/android/src/main/java/org/wonday/pdf/viewer/PdfViewerView.kt
4+++ b/android/src/main/java/org/wonday/pdf/viewer/PdfViewerView.kt
5@@ -21,6 +21,7 @@ import org.json.JSONArray
6 import org.json.JSONObject
7 import kotlin.math.abs
8 import kotlin.math.ceil
9+import kotlin.math.hypot
10 import kotlin.math.max
11 import kotlin.math.min
12 import kotlin.math.roundToInt
13@@ -40,6 +41,10 @@ data class PdfHighlightRect(val page: Int, val x: Float, val y: Float, val width
14 * Page and scale events are sent only once scrolling and zooming have settled.
15 */
16 class PdfViewerView(context: Context) : View(context) {
17+ private companion object {
18+ const val LINK_TAP_SLOP_DP = 10f
19+ }
20+
21 var onEvent: ((JSONObject) -> Unit)? = null
23 private val main = Handler(Looper.getMainLooper())
24@@ -109,10 +114,9 @@ class PdfViewerView(context: Context) : View(context) {
25 private val scratch = RectF()
27 private val gestures = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
28- override fun onDown(e: MotionEvent): Boolean {
29- stopMotion()
30- return true
31- }
32+ // Motion stops in onTouchEvent: GestureDetector calls onDown after onDoubleTap for the
33+ // same event, so stopping here would cancel the zoom the double tap just started.
34+ override fun onDown(e: MotionEvent): Boolean = true
36 override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean {
37 if (!scrollEnabled || scaling) return false
38@@ -694,6 +698,7 @@ class PdfViewerView(context: Context) : View(context) {
39 when (event.actionMasked) {
40 MotionEvent.ACTION_DOWN -> {
41 touching = true
42+ stopMotion()
43 if (scrollEnabled) parent?.requestDisallowInterceptTouchEvent(true)
44 }
45 MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> touching = false
46@@ -804,7 +809,11 @@ class PdfViewerView(context: Context) : View(context) {
47 val pointX = (x - rect.left) / scale
48 val pointY = (y - rect.top) / scale
49 val docPage = pageBase + index
50- val hit = linksFor(docPage).firstOrNull { it.contains(pointX, pointY) }
51+ // Links such as citations are only a few glyphs wide, so a tap near one still counts.
52+ val slop = LINK_TAP_SLOP_DP * density / scale
53+ val hit = linksFor(docPage)
54+ .filter { it.distanceTo(pointX, pointY) <= slop }
55+ .minByOrNull { it.distanceTo(pointX, pointY) }
56 when {
57 hit?.uri != null -> emit(JSONObject().put("type", "link").put("url", hit.uri))
58 hit != null && hit.page >= 0 -> goToPage(hit.page, true)
59@@ -818,7 +827,11 @@ class PdfViewerView(context: Context) : View(context) {
60 }
62 private class PageLinkHit(val x: Float, val y: Float, val width: Float, val height: Float, val page: Int, val uri: String?) {
63- fun contains(px: Float, py: Float) = px >= x && px <= x + width && py >= y && py <= y + height
64+ fun distanceTo(px: Float, py: Float): Float {
65+ val dx = max(0f, max(x - px, px - (x + width)))
66+ val dy = max(0f, max(y - py, py - (y + height)))
67+ return hypot(dx, dy)
68+ }
69 }
71 private fun linksFor(docPage: Int): List<PageLinkHit> = links.getOrPut(docPage) {