Commit f017d551 for tesseract

commit f017d551a8d955e8428be548858f688bd659b0d8
Author: Stefan Weil <sw@weilnetz.de>
Date:   Thu Jun 18 18:19:50 2026 +0200

    Replace char* parameter in SVEvent with std::string

    Change SVEvent::parameter from manual char* management to std::string
    for automatic memory management and safer string handling.

    This eliminates:
    - Manual new/delete in copy constructor
    - strcpy without bounds checking
    - Null pointer checks

    Update all call sites:
    - paramsd.cpp: WriteParams takes const char* (simplified with const_cast removed)
    - pgedit.cpp, intproto.cpp, shapeclassifier.cpp: use .c_str() where needed
    - colfind.cpp: nullptr check changed to .empty() check

    Replace tprintf with tesserr stream in:
    - intproto.cpp, shapeclassifier.cpp (eliminates need for .c_str())

    Build verified successfully.

    Assisted-by: minimax-m2.7 (MiniMax)
    Signed-off-by: Stefan Weil <sw@weilnetz.de>

diff --git a/src/ccmain/paramsd.cpp b/src/ccmain/paramsd.cpp
index 14f220f8..21e28841 100644
--- a/src/ccmain/paramsd.cpp
+++ b/src/ccmain/paramsd.cpp
@@ -268,14 +268,13 @@ SVMenuNode *ParamsEditor::BuildListOfAllLeaves(tesseract::Tesseract *tess) {
 // Event listener. Waits for SVET_POPUP events and processes them.
 void ParamsEditor::Notify(const SVEvent *sve) {
   if (sve->type == SVET_POPUP) { // only catch SVET_POPUP!
-    char *param = sve->parameter;
     if (sve->command_id == writeCommands[0]) {
-      WriteParams(param, false);
+      WriteParams(sve->parameter, false);
     } else if (sve->command_id == writeCommands[1]) {
-      WriteParams(param, true);
+      WriteParams(sve->parameter, true);
     } else {
       ParamContent *vc = ParamContent::GetParamContentById(sve->command_id);
-      vc->SetValue(param);
+      vc->SetValue(sve->parameter.c_str());
       sv_window_->AddMessageF("Setting %s to %s", vc->GetName(), vc->GetValue().c_str());
     }
   }
@@ -315,10 +314,10 @@ ParamsEditor::ParamsEditor(tesseract::Tesseract *tess, ScrollView *sv) {
 }

 // Write all (changed_) parameters to a config file.
-void ParamsEditor::WriteParams(char *filename, bool changes_only) {
+void ParamsEditor::WriteParams(const std::string &filename, bool changes_only) {
   FILE *fp; // input file
   // if file exists
-  if ((fp = fopen(filename, "rb")) != nullptr) {
+  if ((fp = fopen(filename.c_str(), "rb")) != nullptr) {
     fclose(fp);
     std::stringstream msg;
     msg << "Overwrite file " << filename << "? (Y/N)";
@@ -328,9 +327,9 @@ void ParamsEditor::WriteParams(char *filename, bool changes_only) {
     } // don't write
   }

-  fp = fopen(filename, "wb"); // can we write to it?
+  fp = fopen(filename.c_str(), "wb"); // can we write to it?
   if (fp == nullptr) {
-    sv_window_->AddMessageF("Can't write to file %s", filename);
+    sv_window_->AddMessageF("Can't write to file %s", filename.c_str());
     return;
   }
   for (auto &iter : vcMap) {
diff --git a/src/ccmain/paramsd.h b/src/ccmain/paramsd.h
index f898d51b..e5fe36aa 100644
--- a/src/ccmain/paramsd.h
+++ b/src/ccmain/paramsd.h
@@ -26,6 +26,8 @@
 #  include "elst.h"       // for ELIST_ITERATOR, ELISTIZEH, ELIST_LINK
 #  include "scrollview.h" // for ScrollView (ptr only), SVEvent (ptr only)

+#include <string>
+
 namespace tesseract {

 class SVMenuNode;
@@ -119,7 +121,7 @@ private:
   SVMenuNode *BuildListOfAllLeaves(tesseract::Tesseract *tess);

   // Write all (changed_) parameters to a config file.
-  void WriteParams(char *filename, bool changes_only);
+  void WriteParams(const std::string &filename, bool changes_only);

   ScrollView *sv_window_;
 };
diff --git a/src/ccmain/pgedit.cpp b/src/ccmain/pgedit.cpp
index ccf85699..b3028a02 100644
--- a/src/ccmain/pgedit.cpp
+++ b/src/ccmain/pgedit.cpp
@@ -256,9 +256,9 @@ void PGEventHandler::Notify(const SVEvent *event) {
   else if (event->type == SVET_EXIT) {
     stillRunning = false;
   } else if (event->type == SVET_MENU) {
-    if (strcmp(event->parameter, "true") == 0) {
+    if (event->parameter == "true") {
       myval = 'T';
-    } else if (strcmp(event->parameter, "false") == 0) {
+    } else if (event->parameter == "false") {
       myval = 'F';
     }
     tess_->process_cmd_win_event(event->command_id, &myval);
diff --git a/src/classify/intproto.cpp b/src/classify/intproto.cpp
index 0d2f3746..ced6263c 100644
--- a/src/classify/intproto.cpp
+++ b/src/classify/intproto.cpp
@@ -38,11 +38,13 @@
 #endif

 #include "helpers.h"
+#include "tesserrstream.h" // for tesserr

 #include <algorithm>
 #include <cassert>
 #include <cmath> // for M_PI, std::floor
 #include <cstdio>
+#include <cstdlib>         // for strtol

 namespace tesseract {

@@ -1174,22 +1176,30 @@ CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *
     if (ev_type == SVET_POPUP) {
       if (ev->command_id == IDA_SHAPE_INDEX) {
         if (shape_table_ != nullptr) {
-          *shape_id = atoi(ev->parameter);
+          char* endptr = nullptr;
+          long shape_id_long = strtol(ev->parameter.c_str(), &endptr, 10);
+          if (endptr == ev->parameter.c_str() || *endptr != '\0' ||
+              shape_id_long < INT_MIN || shape_id_long > INT_MAX) {
+            tesserr << "Invalid shape index: " << ev->parameter << "\n";
+            return INVALID_UNICHAR_ID;
+          }
+          *shape_id = static_cast<int>(shape_id_long);
           *adaptive_on = false;
           *pretrained_on = true;
           if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) {
             int font_id;
             shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id);
-            tprintf("Shape %d, first unichar=%d, font=%d\n", *shape_id, unichar_id, font_id);
+            tesserr << "Shape " << *shape_id << ", first unichar=" << unichar_id
+                    << ", font=" << font_id << "\n";
             return unichar_id;
           }
-          tprintf("Shape index '%s' not found in shape table\n", ev->parameter);
+          tesserr << "Shape index '" << ev->parameter << "' not found in shape table\n";
         } else {
-          tprintf("No shape table loaded!\n");
+          tesserr << "No shape table loaded!\n";
         }
       } else {
-        if (unicharset.contains_unichar(ev->parameter)) {
-          unichar_id = unicharset.unichar_to_id(ev->parameter);
+        if (unicharset.contains_unichar(ev->parameter.c_str())) {
+          unichar_id = unicharset.unichar_to_id(ev->parameter.c_str());
           if (ev->command_id == IDA_ADAPTIVE) {
             *adaptive_on = true;
             *pretrained_on = false;
@@ -1207,11 +1217,11 @@ CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *
           }
           for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) {
             if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
-              tprintf("%s\n", shape_table_->DebugStr(s).c_str());
+              tesserr << shape_table_->DebugStr(s) << "\n";
             }
           }
         } else {
-          tprintf("Char class '%s' not found in unicharset", ev->parameter);
+          tesserr << "Char class '" << ev->parameter << "' not found in unicharset";
         }
       }
     }
diff --git a/src/classify/shapeclassifier.cpp b/src/classify/shapeclassifier.cpp
index 96c8e02b..6e34c20d 100644
--- a/src/classify/shapeclassifier.cpp
+++ b/src/classify/shapeclassifier.cpp
@@ -30,7 +30,7 @@
 #ifndef GRAPHICS_DISABLED
 #include "svmnode.h"
 #endif
-#include "tprintf.h"
+#include "tesserrstream.h" // for tesserr
 #include "trainingsample.h"

 namespace tesseract {
@@ -137,10 +137,10 @@ void ShapeClassifier::DebugDisplay(const TrainingSample &sample, Image page_pix,
       auto ev = debug_win->AwaitEvent(SVET_ANY);
       ev_type = ev->type;
       if (ev_type == SVET_POPUP) {
-        if (unicharset.contains_unichar(ev->parameter)) {
-          unichar_id = unicharset.unichar_to_id(ev->parameter);
+        if (unicharset.contains_unichar(ev->parameter.c_str())) {
+          unichar_id = unicharset.unichar_to_id(ev->parameter.c_str());
         } else {
-          tprintf("Char class '%s' not found in unicharset", ev->parameter);
+          tesserr << "Char class '" << ev->parameter << "' not found in unicharset";
         }
       }
     } while (unichar_id == old_unichar_id && ev_type != SVET_CLICK && ev_type != SVET_DESTROY);
diff --git a/src/textord/colfind.cpp b/src/textord/colfind.cpp
index 9e1ce820..8fec3241 100644
--- a/src/textord/colfind.cpp
+++ b/src/textord/colfind.cpp
@@ -477,8 +477,8 @@ int ColumnFinder::FindBlocks(PageSegMode pageseg_mode, Image scaled_color, int s
     do {
       waiting = false;
       auto event = blocks_win_->AwaitEvent(SVET_ANY);
-      if (event->type == SVET_INPUT && event->parameter != nullptr) {
-        if (*event->parameter == 'd') {
+      if (event->type == SVET_INPUT && !event->parameter.empty()) {
+        if (event->parameter[0] == 'd') {
           result = -1;
         } else {
           blocks->clear();
diff --git a/src/viewer/scrollview.cpp b/src/viewer/scrollview.cpp
index 054880b5..f34cb6e5 100644
--- a/src/viewer/scrollview.cpp
+++ b/src/viewer/scrollview.cpp
@@ -63,8 +63,7 @@ std::unique_ptr<SVEvent> SVEvent::copy() const {
   auto any = std::unique_ptr<SVEvent>(new SVEvent);
   any->command_id = command_id;
   any->counter = counter;
-  any->parameter = new char[strlen(parameter) + 1];
-  strcpy(any->parameter, parameter);
+  any->parameter = parameter;
   any->type = type;
   any->x = x;
   any->y = y;
@@ -114,10 +113,9 @@ void ScrollView::MessageReceiver() {

     if (cur->window != nullptr) {
       auto length = strlen(p);
-      cur->parameter = new char[length + 1];
-      strcpy(cur->parameter, p);
-      if (length > 0) { // remove the last \n
-        cur->parameter[length - 1] = '\0';
+      cur->parameter = std::string(p, length);
+      if (length > 0 && cur->parameter.back() == '\n') { // remove the last \n
+        cur->parameter.pop_back();
       }
       cur->type = static_cast<SVEventType>(ev_type);
       // Correct selection coordinates so x,y is the min pt and size is +ve.
@@ -719,8 +717,8 @@ char *ScrollView::ShowInputDialog(const char *msg) {
   SendMsg("showInputDialog(\"%s\")", msg);
   // wait till an input event (all others are thrown away)
   auto ev = AwaitEvent(SVET_INPUT);
-  char *p = new char[strlen(ev->parameter) + 1];
-  strcpy(p, ev->parameter);
+  char *p = new char[ev->parameter.size() + 1];
+  std::strcpy(p, ev->parameter.c_str());
   return p;
 }

diff --git a/src/viewer/scrollview.h b/src/viewer/scrollview.h
index b2dc9b2f..b931436b 100644
--- a/src/viewer/scrollview.h
+++ b/src/viewer/scrollview.h
@@ -67,13 +67,10 @@ enum SVEventType {
 };

 struct SVEvent {
-  ~SVEvent() {
-    delete[] parameter;
-  }
   std::unique_ptr<SVEvent> copy() const;
   SVEventType type = SVET_DESTROY; // What kind of event.
   ScrollView *window = nullptr;    // Window event relates to.
-  char *parameter = nullptr;       // Any string that might have been passed as argument.
+  std::string parameter;           // Any string that might have been passed as argument.
   int x = 0;                       // Coords of click or selection.
   int y = 0;
   int x_size = 0; // Size of selection.