Move SSD component cell helper to typed Kotlin

ober

903a65d54fbd6b10c031cd240ef0ceed5afb267e

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index a0d5515..12a24fe 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -169,7 +169,7 @@
                 Component-y2 Component-y2-set!
                 Component-area Component-area-set!
                 Component-detector
-                componentW componentH componentAdd)
+                componentW componentH componentAdd componentCell)
         (type Int32)
         (record Component
           ((mut x1 : Int32)
@@ -197,7 +197,33 @@
             (Component-y2-set! component
               (if (> y (Component-y2 component)) y (Component-y2 component)))
             (Component-area-set! component
-              (+ (Component-area component) (int32 1)))))))
+              (+ (Component-area component) (int32 1)))))
+        (def (componentCell (component : Component) (detector : String)) : (Nullable SsdCell)
+          (let ((w (componentW component))
+                (h (componentH component)))
+            (let ((bboxArea (* w h)))
+              (if (or (or (< (Component-area component) (int32 40))
+                          (> (Component-area component) (int32 2000)))
+                      (or (or (< w (int32 6))
+                              (> w (int32 80)))
+                          (or (< h (int32 6))
+                              (> h (int32 80)))))
+                (nullable-none SsdCell)
+                (let ((safeArea (if (> bboxArea (int32 1))
+                                  bboxArea
+                                  (int32 1))))
+                  (let ((density (/ (float32 (Component-area component))
+                                    (float32 safeArea))))
+                  (if (<= density (float32 0.45))
+                    (nullable-none SsdCell)
+                    (nullable-some
+                      (make-SsdCell
+                        ""
+                        (float32 (Component-x1 component))
+                        (float32 (Component-y1 component))
+                        (float32 w)
+                        (float32 h)
+                        detector)))))))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/SsdGroup.kt"
       (typed-library (com sfb ssdreview)
@@ -3117,16 +3143,6 @@
        "        return components"
        "    }"
        ""
-       "    private fun componentCell(component: Component, detector: String): SsdCell? {"
-       "        val w = componentW(component)"
-       "        val h = componentH(component)"
-       "        val bboxArea = w * h"
-       "        if (component.area !in 40..2000 || w !in 6..80 || h !in 6..80) return null"
-       "        val density = component.area.toFloat() / max(1, bboxArea).toFloat()"
-       "        if (density <= 0.45f) return null"
-       "        return SsdCell(\"\", component.x1.toFloat(), component.y1.toFloat(), w.toFloat(), h.toFloat(), detector)"
-       "    }"
-       ""
        "    private fun toneCell(component: Component, gray: IntArray, width: Int, height: Int): SsdCell? {"
        "        val cell = componentCell(component, \"android-tone\") ?: return null"
        "        val pad = 2"