Move SSD group recompute to typed Kotlin

ober

12580746f157d2f010fd3c44eba502853dc0f1c6

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index d28f5de..6b0d0d4 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -330,7 +330,8 @@
                 SsdSession-cells SsdSession-groups SsdSession-ocrWords
                 SsdSession-suppressedGroups
                 SsdSession-ssdArea SsdSession-ssdArea-set!
-                ssdSessionCellInsideSsdArea ssdSessionGroupInsideSsdArea)
+                ssdSessionCellInsideSsdArea ssdSessionGroupInsideSsdArea
+                ssdSessionFindCellById ssdSessionRecomputeGroup)
         (type Int32)
         (type FloatArray)
         (type SsdCell)
@@ -362,7 +363,59 @@
             #t
             (rectCenterInside
               (SsdGroup-bbox group)
-              (nullable-get (SsdSession-ssdArea session)))))))
+              (nullable-get (SsdSession-ssdArea session)))))
+        (def (ssdSessionFindCellById (session : SsdSession)
+                                     (id : String)) : (Nullable SsdCell)
+          (for/fold ((found (nullable-none SsdCell)))
+                    ((i (in-range (int32 0) (list-size (SsdSession-cells session)))))
+            (if (nullable-null? found)
+              (let ((cell (list-ref (SsdSession-cells session) i)))
+                (if (equal? (SsdCell-id cell) id)
+                  (nullable-some cell)
+                  found))
+              found)))
+        (def (ssdSessionGroupCells (session : SsdSession)
+                                   (group : SsdGroup)) : (MutableList SsdCell)
+          (for/fold ((out (mutable-list-empty SsdCell)))
+                    ((i (in-range (int32 0) (list-size (SsdGroup-cellIds group)))))
+            (let ((cell (ssdSessionFindCellById
+                          session
+                          (list-ref (SsdGroup-cellIds group) i))))
+              (if (nullable-null? cell)
+                out
+                (begin
+                  (mutable-list-add! out (nullable-get cell))
+                  out)))))
+        (def (bboxForMutableCells (cells : (MutableList SsdCell))) : FloatArray
+          (if (= (list-size cells) (int32 0))
+            (float-array (float32 0.0) (float32 0.0) (float32 0.0) (float32 0.0))
+            (let ((first (list-ref cells (int32 0))))
+              (float-array
+                (for/fold ((x (SsdCell-x first)))
+                          ((i (in-range (int32 1) (list-size cells))))
+                  (let ((cell (list-ref cells i)))
+                    (if (< (SsdCell-x cell) x) (SsdCell-x cell) x)))
+                (for/fold ((y (SsdCell-y first)))
+                          ((i (in-range (int32 1) (list-size cells))))
+                  (let ((cell (list-ref cells i)))
+                    (if (< (SsdCell-y cell) y) (SsdCell-y cell) y)))
+                (for/fold ((x2 (+ (SsdCell-x first) (SsdCell-w first))))
+                          ((i (in-range (int32 1) (list-size cells))))
+                  (let ((cell (list-ref cells i)))
+                    (let ((right (+ (SsdCell-x cell) (SsdCell-w cell))))
+                      (if (> right x2) right x2))))
+                (for/fold ((y2 (+ (SsdCell-y first) (SsdCell-h first))))
+                          ((i (in-range (int32 1) (list-size cells))))
+                  (let ((cell (list-ref cells i)))
+                    (let ((bottom (+ (SsdCell-y cell) (SsdCell-h cell))))
+                      (if (> bottom y2) bottom y2))))))))
+        (def (ssdSessionRecomputeGroup (session : SsdSession) (group : SsdGroup)) : Unit
+          (let ((groupCells (ssdSessionGroupCells session group)))
+            (begin
+              (SsdGroup-count-set! group (list-size groupCells))
+              (if (= (list-size groupCells) (int32 0))
+                (SsdGroup-count-set! group (SsdGroup-count group))
+                (SsdGroup-bbox-set! group (bboxForMutableCells groupCells))))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/SourceKey.kt"
       (typed-library (com sfb ssdreview)
@@ -3374,19 +3427,6 @@
        ""
        "fun ssdSessionCellsById(session: SsdSession): Map<String, SsdCell> = session.cells.associateBy { it.id }"
        ""
-       "fun ssdSessionRecomputeGroup(session: SsdSession, group: SsdGroup) {"
-       "    val groupCells = group.cellIds.mapNotNull { id -> session.cells.firstOrNull { it.id == id } }"
-       "    group.count = groupCells.size"
-       "    if (groupCells.isEmpty()) {"
-       "        return"
-       "    }"
-       "    val x1 = groupCells.minOf { it.x }"
-       "    val y1 = groupCells.minOf { it.y }"
-       "    val x2 = groupCells.maxOf { it.x + it.w }"
-       "    val y2 = groupCells.maxOf { it.y + it.h }"
-       "    group.bbox = floatArrayOf(x1, y1, x2, y2)"
-       "}"
-       ""
        "fun ssdSessionSetSsdArea(session: SsdSession, area: FloatArray): Int {"
        "    session.ssdArea = floatArrayOf("
        "        min(area[0], area[2]),"