Move SSD viewport geometry to typed Kotlin

ober

8dacedefd1e5334ce8f137908d77fae5b0eb4bb2

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index abf2dd6..5a79ed8 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1140,7 +1140,11 @@
                 clampInitialScale clampScale clampGroupFitScale
                 clampViewportOffset clampImageCoordinate
                 focusGroupScale groupCenterPoint
-                fitImageScale fitImageOffsetX fitImageOffsetY)
+                fitImageScale fitImageOffsetX fitImageOffsetY
+                rotatedDisplayWidth rotatedDisplayHeight
+                rotatedDisplayWidthFloat rotatedDisplayHeightFloat
+                viewportCenterOffsetDelta)
+        (type Int32)
         (type Float32)
         (type FloatArray)
         (def (selectionCellMatches? (rect : FloatArray) (cell : SsdCell)) : Bool
@@ -1203,6 +1207,27 @@
         (def (fitImageOffsetY (viewportHeight : Float32)
                               (scaledHeight : Float32)) : Float32
           (/ (- viewportHeight scaledHeight) (float32 2.0)))
+        (def (rotationKeepsDimensions? (rotationDegrees : Int32)) : Bool
+          (= (mod rotationDegrees (int32 180)) (int32 0)))
+        (def (rotatedDisplayWidth (rotationDegrees : Int32)
+                                  (imageWidth : Int32)
+                                  (imageHeight : Int32)) : Int32
+          (if (rotationKeepsDimensions? rotationDegrees) imageWidth imageHeight))
+        (def (rotatedDisplayHeight (rotationDegrees : Int32)
+                                   (imageWidth : Int32)
+                                   (imageHeight : Int32)) : Int32
+          (if (rotationKeepsDimensions? rotationDegrees) imageHeight imageWidth))
+        (def (rotatedDisplayWidthFloat (rotationDegrees : Int32)
+                                       (imageWidth : Int32)
+                                       (imageHeight : Int32)) : Float32
+          (float32 (rotatedDisplayWidth rotationDegrees imageWidth imageHeight)))
+        (def (rotatedDisplayHeightFloat (rotationDegrees : Int32)
+                                        (imageWidth : Int32)
+                                        (imageHeight : Int32)) : Float32
+          (float32 (rotatedDisplayHeight rotationDegrees imageWidth imageHeight)))
+        (def (viewportCenterOffsetDelta (viewportSize : Int32)
+                                        (center : Float32)) : Float32
+          (- (/ (float32 viewportSize) (float32 2.0)) center))
         (def (maxFloat32Value (a : Float32) (b : Float32)) : Float32
           (if (> a b) a b))
         (def (minFloat32Value (a : Float32) (b : Float32)) : Float32
@@ -6853,8 +6878,8 @@
        "    fun fitImage() {"
        "        val img = bitmap ?: return"
        "        if (width <= 0 || height <= 0) return"
-       "        val displayWidth = if (rotationDegrees % 180 == 0) img.width else img.height"
-       "        val displayHeight = if (rotationDegrees % 180 == 0) img.height else img.width"
+       "        val displayWidth = rotatedDisplayWidth(rotationDegrees, img.width, img.height)"
+       "        val displayHeight = rotatedDisplayHeight(rotationDegrees, img.width, img.height)"
        "        scale = fitImageScale(width.toFloat(), height.toFloat(), displayWidth.toFloat(), displayHeight.toFloat())"
        "        val scaledWidth = displayWidth.toFloat() * scale"
        "        val scaledHeight = displayHeight.toFloat() * scale"
@@ -6894,8 +6919,8 @@
 	       "        updateMatrix()"
 	       "        val groupCenter = groupCenterPoint(group)"
 	       "        val center = imageToView(groupCenter[0], groupCenter[1])"
-	       "        offsetX += width / 2f - center[0]"
-	       "        offsetY += height / 2f - center[1]"
+	       "        offsetX += viewportCenterOffsetDelta(width, center[0])"
+	       "        offsetY += viewportCenterOffsetDelta(height, center[1])"
 	       "        clampOffsets()"
 	       "        updateMatrix()"
 	       "        invalidate()"
@@ -7086,8 +7111,8 @@
 	       "    private fun clampOffsets() {"
 	       "        val img = bitmap ?: return"
 	       "        if (width <= 0 || height <= 0) return"
-	       "        val displayWidth = if (rotationDegrees % 180 == 0) img.width.toFloat() else img.height.toFloat()"
-	       "        val displayHeight = if (rotationDegrees % 180 == 0) img.height.toFloat() else img.width.toFloat()"
+	       "        val displayWidth = rotatedDisplayWidthFloat(rotationDegrees, img.width, img.height)"
+	       "        val displayHeight = rotatedDisplayHeightFloat(rotationDegrees, img.width, img.height)"
 	       "        val scaledWidth = displayWidth * scale"
 	       "        val scaledHeight = displayHeight * scale"
 	       "        offsetX = clampViewportOffset(offsetX, width.toFloat(), scaledWidth)"