Commit 6f197cfd for tesseract

commit 6f197cfdd14c69eadfd4af6ddeb5a970e31997c8
Author: Stefan Weil <sw@weilnetz.de>
Date:   Tue Oct 27 06:50:49 2026 +0100

    Fix C++17 build of constexpr FCOORD operators (#4665)

    56e873189 ("Use more constexpr") made the FCOORD operators constexpr but
    left the FCOORD default constructor non-constexpr, so the operators could
    never produce a constant expression. GCC rejects this as a hard error,
    breaking the C++17 fallback used by GCC 9.4/11 (issue #4664).

    Make the FCOORD constructors and ==/!= constexpr again. Add points_test.cc
    whose static_asserts fail to compile if the bug returns.

    Assisted-by: OpenCode / Qwen3.8-27B-Thinking (Alibaba Cloud)
    Signed-off-by: Stefan Weil <sw@weilnetz.de>

diff --git a/Makefile.am b/Makefile.am
index 2630dda8..fb69ab6f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1220,6 +1220,7 @@ endif # !DISABLED_LEGACY_ENGINE
 check_PROGRAMS += progress_test
 check_PROGRAMS += qrsequence_test
 check_PROGRAMS += ratngs_test
+check_PROGRAMS += points_test
 check_PROGRAMS += recodebeam_test
 check_PROGRAMS += recoder_test
 check_PROGRAMS += rect_test
@@ -1502,6 +1503,10 @@ progress_test_CPPFLAGS = $(unittest_CPPFLAGS)
 progress_test_LDFLAGS = $(LEPTONICA_LIBS)
 progress_test_LDADD = $(GTEST_LIBS) $(GMOCK_LIBS) $(TESS_LIBS) $(LEPTONICA_LIBS)

+points_test_SOURCES = unittest/points_test.cc
+points_test_CPPFLAGS = $(unittest_CPPFLAGS)
+points_test_LDADD = $(TESS_LIBS)
+
 qrsequence_test_SOURCES = unittest/qrsequence_test.cc
 qrsequence_test_CPPFLAGS = $(unittest_CPPFLAGS)
 qrsequence_test_LDADD = $(TESS_LIBS)
diff --git a/src/ccstruct/points.h b/src/ccstruct/points.h
index 87aa48b7..268bb670 100644
--- a/src/ccstruct/points.h
+++ b/src/ccstruct/points.h
@@ -184,14 +184,11 @@ ELISTIZEH(ICOORDELT)
 class TESS_API FCOORD {
 public:
   /// empty constructor
-  FCOORD() = default;
+  constexpr FCOORD() : xcoord(0.0f), ycoord(0.0f) {}
   /// constructor
   ///@param xvalue x value
   ///@param yvalue y value
-  FCOORD(float xvalue, float yvalue) {
-    xcoord = xvalue; // set coords
-    ycoord = yvalue;
-  }
+  constexpr FCOORD(float xvalue, float yvalue) : xcoord(xvalue), ycoord(yvalue) {}
   constexpr FCOORD(              // make from ICOORD
       ICOORD icoord) : xcoord(icoord.xcoord), ycoord(icoord.ycoord) {}

@@ -261,11 +258,11 @@ public:
   bool normalise();

   /// test equality
-  bool operator==(const FCOORD &other) const {
+  constexpr bool operator==(const FCOORD &other) const {
     return xcoord == other.xcoord && ycoord == other.ycoord;
   }
   /// test inequality
-  bool operator!=(const FCOORD &other) const {
+  constexpr bool operator!=(const FCOORD &other) const {
     return xcoord != other.xcoord || ycoord != other.ycoord;
   }
   /// rotate 90 deg anti
diff --git a/unittest/points_test.cc b/unittest/points_test.cc
new file mode 100644
index 00000000..7a1af5c4
--- /dev/null
+++ b/unittest/points_test.cc
@@ -0,0 +1,167 @@
+// (C) Copyright 2026, Stefan Weil
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "points.h"
+
+#include "include_gunit.h"
+
+namespace tesseract {
+
+namespace {
+
+// Helpers to use the compound assignment operators in constant expressions.
+constexpr ICOORD IPlus(ICOORD a, const ICOORD &b) {
+  a += b;
+  return a;
+}
+constexpr ICOORD IMinus(ICOORD a, const ICOORD &b) {
+  a -= b;
+  return a;
+}
+constexpr ICOORD IMul(ICOORD a, TDimension s) {
+  a *= s;
+  return a;
+}
+constexpr ICOORD IDiv(ICOORD a, TDimension s) {
+  a /= s;
+  return a;
+}
+constexpr FCOORD FPlus(FCOORD a, const FCOORD &b) {
+  a += b;
+  return a;
+}
+constexpr FCOORD FMinus(FCOORD a, const FCOORD &b) {
+  a -= b;
+  return a;
+}
+constexpr FCOORD FMul(FCOORD a, float s) {
+  a *= s;
+  return a;
+}
+
+} // namespace
+
+class PointsTest : public testing::Test {
+public:
+  void SetUp() override {}
+
+  void TearDown() override {}
+};
+
+// The coordinate arithmetic operators are declared constexpr. Make sure they
+// can actually produce constant expressions (this is required even in C++20,
+// see P1331R2) by using them in constant expressions. This used to fail to
+// compile, e.g. with GCC:
+//   error: uninitialized variable 'result' in 'constexpr' function
+// because the FCOORD default constructor was not constexpr, so the constexpr
+// FCOORD operators could never produce a constant expression (see issue
+// #4664).
+TEST_F(PointsTest, ICooordArithmeticIsConstant) {
+  static_assert(ICOORD(3, 4) == ICOORD(1, 2) + ICOORD(2, 2), "");
+  static_assert(ICOORD(1, 2) - ICOORD(2, 2) == ICOORD(-1, 0), "");
+  static_assert(!ICOORD(3, 4) == ICOORD(-4, 3), "");
+  static_assert(-ICOORD(3, 4) == ICOORD(-3, -4), "");
+  static_assert(ICOORD(3, 4) * 2 == ICOORD(6, 8), "");
+  static_assert(2 * ICOORD(3, 4) == ICOORD(6, 8), "");
+  static_assert(ICOORD(6, 8) / 2 == ICOORD(3, 4), "");
+  static_assert(ICOORD(3, 4) % ICOORD(1, 2) == 11, "");
+  static_assert(ICOORD(3, 4) * ICOORD(1, 2) == 2, "");
+  // Default constructor.
+  static_assert(ICOORD() == ICOORD(0, 0), "");
+  // Compound assignment operators.
+  static_assert(IPlus(ICOORD(1, 2), ICOORD(2, 2)) == ICOORD(3, 4), "");
+  static_assert(IMinus(ICOORD(3, 4), ICOORD(1, 2)) == ICOORD(2, 2), "");
+  static_assert(IMul(ICOORD(3, 4), 2) == ICOORD(6, 8), "");
+  static_assert(IDiv(ICOORD(6, 8), 2) == ICOORD(3, 4), "");
+  SUCCEED();
+}
+
+TEST_F(PointsTest, FCoordsArithmeticIsConstant) {
+  static_assert(FCOORD(1.5f, 2.5f) == FCOORD(1.0f, 2.0f) + FCOORD(0.5f, 0.5f),
+                "");
+  static_assert(FCOORD(1.0f, 2.0f) - FCOORD(0.5f, 0.5f) == FCOORD(0.5f, 1.5f),
+                "");
+  static_assert(!FCOORD(3.0f, 4.0f) == FCOORD(-4.0f, 3.0f), "");
+  static_assert(-FCOORD(3.0f, 4.0f) == FCOORD(-3.0f, -4.0f), "");
+  static_assert(FCOORD(3.0f, 4.0f) * 2.0f == FCOORD(6.0f, 8.0f), "");
+  static_assert(2.0f * FCOORD(3.0f, 4.0f) == FCOORD(6.0f, 8.0f), "");
+  static_assert(FCOORD(3.0f, 4.0f) % FCOORD(1.0f, 2.0f) == 11.0f, "");
+  static_assert(FCOORD(3.0f, 4.0f) * FCOORD(1.0f, 2.0f) == 2.0f, "");
+  // Default constructor and the constructor from ICOORD.
+  static_assert(FCOORD() == FCOORD(0.0f, 0.0f), "");
+  static_assert(FCOORD(ICOORD(3, 4)) == FCOORD(3.0f, 4.0f), "");
+  // Compound assignment operators.
+  static_assert(
+      FPlus(FCOORD(1.0f, 2.0f), FCOORD(0.5f, 0.5f)) == FCOORD(1.5f, 2.5f), "");
+  static_assert(
+      FMinus(FCOORD(3.0f, 4.0f), FCOORD(1.0f, 2.0f)) == FCOORD(2.0f, 2.0f), "");
+  static_assert(FMul(FCOORD(3.0f, 4.0f), 2.0f) == FCOORD(6.0f, 8.0f), "");
+  SUCCEED();
+}
+
+// Runtime sanity checks of the same operators.
+TEST_F(PointsTest, ICooordArithmetic) {
+  ICOORD a(3, 4);
+  ICOORD b(1, 2);
+
+  EXPECT_EQ(ICOORD(4, 6), a + b);
+  EXPECT_EQ(ICOORD(2, 2), a - b);
+  EXPECT_EQ(ICOORD(-4, 3), !a);
+  EXPECT_EQ(ICOORD(-3, -4), -a);
+  EXPECT_EQ(ICOORD(6, 8), a * 2);
+  EXPECT_EQ(ICOORD(6, 8), 2 * a);
+  EXPECT_EQ(ICOORD(3, 4), ICOORD(6, 8) / 2);
+  EXPECT_EQ(11, a % b); // scalar product
+  EXPECT_EQ(2, a * b);  // cross product
+
+  ICOORD c(1, 1);
+  c += a;
+  EXPECT_EQ(ICOORD(4, 5), c);
+  c -= b;
+  EXPECT_EQ(ICOORD(3, 3), c);
+  c *= 2;
+  EXPECT_EQ(ICOORD(6, 6), c);
+  c /= 2;
+  EXPECT_EQ(ICOORD(3, 3), c);
+
+  EXPECT_EQ(ICOORD(0, 0), ICOORD());
+}
+
+TEST_F(PointsTest, FCoordsArithmetic) {
+  FCOORD a(3.0f, 4.0f);
+  FCOORD b(1.0f, 2.0f);
+
+  EXPECT_EQ(FCOORD(4.0f, 6.0f), a + b);
+  EXPECT_EQ(FCOORD(2.0f, 2.0f), a - b);
+  EXPECT_EQ(FCOORD(-4.0f, 3.0f), !a);
+  EXPECT_EQ(FCOORD(-3.0f, -4.0f), -a);
+  EXPECT_EQ(FCOORD(6.0f, 8.0f), a * 2.0f);
+  EXPECT_EQ(FCOORD(6.0f, 8.0f), 2.0f * a);
+  EXPECT_FLOAT_EQ(11.0f, a % b); // scalar product
+  EXPECT_FLOAT_EQ(2.0f, a * b);  // cross product
+
+  FCOORD c(1.0f, 1.0f);
+  c += a;
+  EXPECT_EQ(FCOORD(4.0f, 5.0f), c);
+  c -= b;
+  EXPECT_EQ(FCOORD(3.0f, 3.0f), c);
+  c *= 2.0f;
+  EXPECT_EQ(FCOORD(6.0f, 6.0f), c);
+
+  FCOORD d(2.0f, 2.0f);
+  d /= 2.0f;
+  EXPECT_EQ(FCOORD(1.0f, 1.0f), d);
+
+  EXPECT_EQ(FCOORD(0.0f, 0.0f), FCOORD());
+  EXPECT_EQ(FCOORD(3.0f, 4.0f), FCOORD(ICOORD(3, 4)));
+}
+
+} // namespace tesseract