Move SSD review view nullable guards to typed Kotlin

ober

af4ba92a19a056323274a702476f762d2e7a327e

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 4ab121e..2e9c989 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -554,7 +554,7 @@
                 SsdSession-cells SsdSession-groups SsdSession-ocrWords
                 SsdSession-suppressedGroups
                 SsdSession-ssdArea SsdSession-ssdArea-set!
-                ssdSessionOrEmpty
+                ssdSessionOrEmpty ssdSessionAreaOrNone
                 ssdSessionCellInsideSsdArea ssdSessionGroupInsideSsdArea
                 ssdSessionSuppressedGroupPresent?
                 ssdSessionFindGroupById ssdSessionSelectedGroup
@@ -612,6 +612,10 @@
               (mutable-list-empty SsdGroup)
               (nullable-none FloatArray))
             (nullable-get session)))
+        (def (ssdSessionAreaOrNone (session : (Nullable SsdSession))) : (Nullable FloatArray)
+          (if (nullable-null? session)
+            (nullable-none FloatArray)
+            (SsdSession-ssdArea (nullable-get session))))
         (def (ssdSessionCellInsideSsdArea (session : SsdSession) (cell : SsdCell)) : Bool
           (if (nullable-null? (SsdSession-ssdArea session))
             #t
@@ -1257,6 +1261,8 @@
                 viewDimensionsReady previousViewSizeMissing
                 viewDimensionsMissing viewSizeChangeNeedsFit
                 bitmapLoaded viewSizeChangeNeedsFitForBitmap
+                nullableFloatArrayPresent nullableFloatArrayOrEmpty
+                nullableFirstFloatArrayPresent nullableFirstFloatArrayOrEmpty
                 nullableBitmapWidthOrZero nullableBitmapHeightOrZero
                 bitmapViewReady bitmapViewNotReady bitmapViewNotReadyForBitmap
                 viewportMatchesImage viewportImageMismatch
@@ -1341,6 +1347,21 @@
           (or (previousViewSizeMissing oldWidth oldHeight) (not hasBitmap)))
         (def (bitmapLoaded (bitmap : (Nullable Bitmap))) : Bool
           (not (nullable-null? bitmap)))
+        (def (nullableFloatArrayPresent (value : (Nullable FloatArray))) : Bool
+          (not (nullable-null? value)))
+        (def (nullableFloatArrayOrEmpty (value : (Nullable FloatArray))) : FloatArray
+          (if (nullable-null? value)
+            (float-array (float32 0.0) (float32 0.0) (float32 0.0) (float32 0.0))
+            (nullable-get value)))
+        (def (nullableFirstFloatArrayPresent (first : (Nullable FloatArray))
+                                             (second : (Nullable FloatArray))) : Bool
+          (or (nullableFloatArrayPresent first)
+              (nullableFloatArrayPresent second)))
+        (def (nullableFirstFloatArrayOrEmpty (first : (Nullable FloatArray))
+                                             (second : (Nullable FloatArray))) : FloatArray
+          (if (nullable-null? first)
+            (nullableFloatArrayOrEmpty second)
+            (nullable-get first)))
         (def (nullableBitmapWidthOrZero (bitmap : (Nullable Bitmap))) : Int32
           (if (nullable-null? bitmap)
             (int32 0)
@@ -8172,7 +8193,7 @@
 	       "    var session: SsdSession? = null"
 	       "        set(value) {"
 	       "            field = value"
-	       "            ssdArea = value?.ssdArea"
+	       "            ssdArea = ssdSessionAreaOrNone(value)"
 	       "            invalidate()"
 	       "        }"
 	       "    var selectMode: Boolean = false"
@@ -8240,7 +8261,8 @@
        "    override fun onDraw(canvas: Canvas) {"
        "        super.onDraw(canvas)"
        "        canvas.drawColor(reviewCanvasBackgroundColor())"
-       "        val img = bitmap ?: return"
+       "        if (!bitmapLoaded(bitmap)) return"
+       "        val img = checkNotNull(bitmap)"
        "        canvas.drawBitmap(img, matrix, null)"
        "        val saved = canvas.save()"
        "        canvas.concat(matrix)"
@@ -8311,7 +8333,8 @@
        "    }"
        ""
        "    fun fitImage() {"
-       "        val img = bitmap ?: return"
+       "        if (!bitmapLoaded(bitmap)) return"
+       "        val img = checkNotNull(bitmap)"
        "        if (viewDimensionsMissing(width, height)) return"
        "        val displayWidth = rotatedDisplayWidth(rotationDegrees, img.width, img.height)"
        "        val displayHeight = rotatedDisplayHeight(rotationDegrees, img.width, img.height)"
@@ -8331,12 +8354,14 @@
        "    }"
        ""
        "    fun captureViewport(): ViewportState? {"
-       "        val img = bitmap ?: return null"
+       "        if (!bitmapLoaded(bitmap)) return null"
+       "        val img = checkNotNull(bitmap)"
        "        return ViewportState(scale, offsetX, offsetY, rotationDegrees, img.width, img.height)"
        "    }"
        ""
        "    fun restoreViewport(viewport: ViewportState?) {"
-       "        val img = bitmap ?: return"
+       "        if (!bitmapLoaded(bitmap)) return"
+       "        val img = checkNotNull(bitmap)"
        "        if (!viewportStatePresent(viewport)) return"
        "        val savedViewport = viewportStateOrEmpty(viewport)"
        "        if (viewportImageMismatch(savedViewport, img.width, img.height, rotationDegrees)) return"
@@ -8371,8 +8396,10 @@
 	       "    }"
        ""
        "    private fun finishSelection() {"
-       "        val start = dragStart ?: return"
-       "        val end = dragEnd ?: return"
+       "        if (!nullableFloatArrayPresent(dragStart)) return"
+       "        if (!nullableFloatArrayPresent(dragEnd)) return"
+       "        val start = nullableFloatArrayOrEmpty(dragStart)"
+       "        val end = nullableFloatArrayOrEmpty(dragEnd)"
        "        val rect = normalizeRect(floatArrayOf(start[0], start[1], end[0], end[1]))"
        "        dragStart = null"
        "        dragEnd = null"
@@ -8404,7 +8431,8 @@
        "    }"
        ""
        "    private fun drawCells(canvas: Canvas) {"
-       "        val sess = session ?: return"
+       "        if (sessionMissing(session)) return"
+       "        val sess = ssdSessionOrEmpty(session)"
        "        paint.style = Paint.Style.STROKE"
        "        paint.strokeWidth = cellStrokeWidth(scale)"
        "        for (cell in sess.cells) {"
@@ -8416,7 +8444,8 @@
        "    }"
        ""
        "    private fun drawGroups(canvas: Canvas) {"
-       "        val sess = session ?: return"
+       "        if (sessionMissing(session)) return"
+       "        val sess = ssdSessionOrEmpty(session)"
        "        paint.style = Paint.Style.STROKE"
        "        paint.strokeWidth = groupBaseStrokeWidth(scale)"
        "        for (group in sess.groups) {"
@@ -8434,7 +8463,8 @@
 	       "    }"
 	       ""
 	       "    private fun drawSsdArea(canvas: Canvas) {"
-	       "        val area = ssdArea ?: return"
+	       "        if (!nullableFloatArrayPresent(ssdArea)) return"
+	       "        val area = nullableFloatArrayOrEmpty(ssdArea)"
 	       "        paint.style = Paint.Style.FILL"
 	       "        paint.color = ssdAreaFillColor()"
 	       "        canvas.drawRect(area[0], area[1], area[2], area[3], paint)"
@@ -8444,8 +8474,9 @@
 	       "        canvas.drawRect(area[0], area[1], area[2], area[3], paint)"
 	       "    }"
 	       ""
-	       "    private fun drawLabels(canvas: Canvas) {"
-       "        val sess = session ?: return"
+       "    private fun drawLabels(canvas: Canvas) {"
+       "        if (sessionMissing(session)) return"
+       "        val sess = ssdSessionOrEmpty(session)"
        "        for (group in sess.groups) {"
        "            val selected = groupIdSelected(group.id, selectedGroupId)"
        "            val label = displayGroupCanvasLabel(group, selected)"
@@ -8476,8 +8507,10 @@
        "    }"
        ""
        "    private fun drawDrag(canvas: Canvas) {"
-       "        val start = dragStart ?: return"
-       "        val end = dragEnd ?: return"
+       "        if (!nullableFloatArrayPresent(dragStart)) return"
+       "        if (!nullableFloatArrayPresent(dragEnd)) return"
+       "        val start = nullableFloatArrayOrEmpty(dragStart)"
+       "        val end = nullableFloatArrayOrEmpty(dragEnd)"
        "        val rect = normalizeRect(floatArrayOf(start[0], start[1], end[0], end[1]))"
        "        paint.style = Paint.Style.FILL"
        "        paint.color = dragFillColor()"
@@ -8495,7 +8528,8 @@
 	       ""
 	       "    private fun drawTouchPointer(canvas: Canvas) {"
 		       "        if (touchPointerNotReady(pointerActive, selectMode)) return"
-	       "        val anchor = dragEnd ?: dragStart ?: return"
+	       "        if (!nullableFirstFloatArrayPresent(dragEnd, dragStart)) return"
+	       "        val anchor = nullableFirstFloatArrayOrEmpty(dragEnd, dragStart)"
 	       "        val viewAnchor = imageToView(anchor[0], anchor[1])"
 	       "        paint.style = Paint.Style.STROKE"
 	       "        paint.strokeWidth = touchPointerStrokeWidth()"
@@ -8543,7 +8577,8 @@
 	       "    }"
 	       ""
 	       "    private fun clampOffsets() {"
-	       "        val img = bitmap ?: return"
+	       "        if (!bitmapLoaded(bitmap)) return"
+	       "        val img = checkNotNull(bitmap)"
 	       "        if (viewDimensionsMissing(width, height)) return"
 	       "        val displayWidth = rotatedDisplayWidthFloat(rotationDegrees, img.width, img.height)"
 	       "        val displayHeight = rotatedDisplayHeightFloat(rotationDegrees, img.width, img.height)"
@@ -8574,7 +8609,8 @@
        "    }"
        ""
        "    private fun hitGroup(x: Float, y: Float): String? {"
-       "        val sess = session ?: return null"
+       "        if (sessionMissing(session)) return null"
+       "        val sess = ssdSessionOrEmpty(session)"
        "        val pad = hitGroupPad(scale)"
        "        return hitGroupId(sess.groups, x, y, pad)"
        "    }"