Enforce minimum zoom so page always fills viewport

Dynamic min zoom computed from page and view dimensions — page can
never be smaller than the visible area. Combined with pan clamping
from previous commit, the page always covers the full canvas with
no non-drawable area visible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 15:09:57 -07:00
parent 61aaa9ebde
commit 2b567aa038

View File

@@ -93,7 +93,7 @@ class PadCanvasView(context: Context) : View(context) {
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val newZoom = (zoom * detector.scaleFactor)
.coerceIn(CanvasState.MIN_ZOOM, CanvasState.MAX_ZOOM)
.coerceIn(minZoom(), CanvasState.MAX_ZOOM)
if (newZoom != zoom) {
val focusX = detector.focusX
val focusY = detector.focusY
@@ -639,6 +639,23 @@ class PadCanvasView(context: Context) : View(context) {
// --- Coordinate transforms ---
/**
* Minimum zoom so the page always fills the viewport.
* At fitScale (zoom=1), the page fills the screen width.
* We need both dimensions covered, so min zoom = max(1, viewH/(pageH*fitScale)).
*/
private fun minZoom(): Float {
val pageW = canvasState.pageSize.widthPt.toFloat()
val pageH = canvasState.pageSize.heightPt.toFloat()
val viewW = width.toFloat().coerceAtLeast(1f)
val viewH = height.toFloat().coerceAtLeast(1f)
val fitScale = viewW / pageW
// Page must fill both dimensions
val minForWidth = 1f // zoom=1 means page width = view width
val minForHeight = viewH / (pageH * fitScale)
return maxOf(minForWidth, minForHeight)
}
private fun rebuildViewMatrix() {
viewMatrix.reset()
val pageW = canvasState.pageSize.widthPt.toFloat()