Generate SSD review drawing loops from typed Jerboa

ober

54fbfe7e43974056e8b827f5a50e5fae69b42cb4

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 467c1a0..a900983 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -600,7 +600,8 @@
                 createManualCells selectedOrManualCellIds
                 copyStringIds copyMutableStringIds
                 ssdSessionNextCellId ssdSessionNextGroupId
-                bboxForMutableCells)
+                bboxForMutableCells
+                forEachSsdSessionCell forEachSsdSessionGroup)
         (type Int32)
         (type FloatArray)
         (type SsdCell)
@@ -621,6 +622,30 @@
            (ocrWords : (MutableList OcrWord))
            (suppressedGroups : (MutableList SsdGroup))
            (mut ssdArea : (Nullable FloatArray))))
+        (def (forEachSsdSessionCell
+               (session : SsdSession)
+               (action : (-> SsdCell Unit))) : Unit
+          (begin
+            (for/fold ((ignored (int32 0)))
+                      ((i (in-range
+                            (int32 0)
+                            (list-size (SsdSession-cells session)))))
+              (begin
+                (invoke action (list-ref (SsdSession-cells session) i))
+                ignored))
+            (begin)))
+        (def (forEachSsdSessionGroup
+               (session : SsdSession)
+               (action : (-> SsdGroup Unit))) : Unit
+          (begin
+            (for/fold ((ignored (int32 0)))
+                      ((i (in-range
+                            (int32 0)
+                            (list-size (SsdSession-groups session)))))
+              (begin
+                (invoke action (list-ref (SsdSession-groups session) i))
+                ignored))
+            (begin)))
         (def (ssdSessionOrEmpty (session : (Nullable SsdSession))) : SsdSession
           (if (nullable-null? session)
             (make-SsdSession
@@ -11175,7 +11200,7 @@
        "        val sess = ssdSessionOrEmpty(session)"
        "        paint.style = Paint.Style.STROKE"
        "        paint.strokeWidth = cellStrokeWidth(scale)"
-       "        for (cell in sess.cells) {"
+       "        forEachSsdSessionCell(sess) { cell ->"
        "            if (ssdSessionGroupContainsCellId(sess, selectedGroupId, cell.id)) {"
        "                paint.color = reviewPrimaryColor()"
        "                canvas.drawRect(cell.x, cell.y, cell.x + cell.w, cell.y + cell.h, paint)"
@@ -11188,7 +11213,7 @@
        "        val sess = ssdSessionOrEmpty(session)"
        "        paint.style = Paint.Style.STROKE"
        "        paint.strokeWidth = groupBaseStrokeWidth(scale)"
-       "        for (group in sess.groups) {"
+       "        forEachSsdSessionGroup(sess) { group ->"
        "            val selected = groupIdSelected(group.id, selectedGroupId)"
        "            paint.color = groupStrokeColor(selected, group.status)"
        "            paint.strokeWidth = groupStrokeWidth(selected, scale)"
@@ -11217,10 +11242,10 @@
        "    private fun drawLabels(canvas: Canvas) {"
        "        if (sessionMissing(session)) return"
        "        val sess = ssdSessionOrEmpty(session)"
-       "        for (group in sess.groups) {"
+       "        forEachSsdSessionGroup(sess) { group ->"
        "            val selected = groupIdSelected(group.id, selectedGroupId)"
        "            val label = displayGroupCanvasLabel(group, selected)"
-       "            if (canvasLabelMissing(label)) continue"
+       "            if (canvasLabelPresent(label)) {"
        "            val center = floatArrayOf("
        "                (group.bbox[0] + group.bbox[2]) / 2f,"
        "                (group.bbox[1] + group.bbox[3]) / 2f"
@@ -11243,6 +11268,7 @@
        "            )"
        "            canvas.drawText(label, center[0], center[1] - 4f, textPaint)"
        "            canvas.restore()"
+       "            }"
        "        }"
        "    }"
        ""