Commit 3624c6bf for tesseract

commit 3624c6bf224719db284d97ccd3360d1e419254c0
Author: Stefan Weil <sw@weilnetz.de>
Date:   Fri Nov 13 17:30:19 2026 +0100

    textord: fix loop counters and a division by zero in GAPMAP

    - The loops over the gap map counted with an int16_t and tested
      i <= map_max. For map_max == INT16_MAX (a block which is 32767
      pixels wide with a bucket size of 1) that test never fails: the
      counter wraps, and the loop writes in front of the map and does not
      end in builds with g++. Use int for the counters.

    - The x-height of a row was narrowed to int16_t before it was added to
      the histogram. Values above 32767, which are possible with a large
      textord_min_xheight, became small or negative instead of being
      clipped to the range of the histogram by STATS::add(). That is how
      the bucket size got as small as 1 or 0.

    - A median x-height below 2 gave a bucket size of 0 and a division by
      zero. Use a bucket size of at least 1.

    The first two changes are the fix which was proposed by the scanner,
    the third one was suggested by its review.

    Based on the fix proposed by the Anthropic OSS Scanner
    (finding ANT-2026-R518F4VZ).

    Assisted-by: OpenCode / Claude Fable 5.2 (Anthropic)
    Signed-off-by: Stefan Weil <sw@weilnetz.de>

diff --git a/src/textord/gap_map.cpp b/src/textord/gap_map.cpp
index 4c20d117..2f460d59 100644
--- a/src/textord/gap_map.cpp
+++ b/src/textord/gap_map.cpp
@@ -46,7 +46,7 @@ GAPMAP::GAPMAP(     // Constructor
   STATS xht_stats(0, 127);
   int16_t min_quantum;
   int16_t max_quantum;
-  int16_t i;
+  int i; // wider than map_max, otherwise i <= map_max never fails at INT16_MAX

   /*
   Find left and right extremes and bucket size
@@ -63,7 +63,7 @@ GAPMAP::GAPMAP(     // Constructor
     row = row_it.data();
     if (!row->blob_list()->empty()) {
       total_rows++;
-      xht_stats.add(static_cast<int16_t>(floor(row->xheight + 0.5)), 1);
+      xht_stats.add(static_cast<int32_t>(floor(row->xheight + 0.5)), 1);
       blob_it.set_to_list(row->blob_list());
       start_of_row = blob_it.data()->bounding_box().left();
       end_of_row = blob_it.data_relative(-1)->bounding_box().right();
@@ -83,6 +83,9 @@ GAPMAP::GAPMAP(     // Constructor
     return;
   }
   bucket_size = static_cast<int16_t>(floor(xht_stats.median() + 0.5)) / 2;
+  if (bucket_size < 1) {
+    bucket_size = 1; // avoid a division by zero for a median x-height below 2
+  }
   map_max = (max_right - min_left) / bucket_size;
   map = new int16_t[map_max + 1];
   for (i = 0; i <= map_max; i++) {
@@ -169,7 +172,7 @@ bool GAPMAP::table_gap( // Is gap a table?
 ) {
   int16_t min_quantum;
   int16_t max_quantum;
-  int16_t i;
+  int i; // wider than map_max, otherwise i <= max_quantum never fails at INT16_MAX
   bool tab_found = false;

   if (!any_tabs) {