Coverage Report

Created: 2026-10-28 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/classify/intproto.cpp
Line
Count
Source
1
/******************************************************************************
2
 ** Filename:    intproto.c
3
 ** Purpose:     Definition of data structures for integer protos.
4
 ** Author:      Dan Johnson
5
 **
6
 ** (c) Copyright Hewlett-Packard Company, 1988.
7
 ** Licensed under the Apache License, Version 2.0 (the "License");
8
 ** you may not use this file except in compliance with the License.
9
 ** You may obtain a copy of the License at
10
 ** http://www.apache.org/licenses/LICENSE-2.0
11
 ** Unless required by applicable law or agreed to in writing, software
12
 ** distributed under the License is distributed on an "AS IS" BASIS,
13
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 ** See the License for the specific language governing permissions and
15
 ** limitations under the License.
16
 ******************************************************************************/
17
/*-----------------------------------------------------------------------------
18
          Include Files and Type Defines
19
-----------------------------------------------------------------------------*/
20
21
#define _USE_MATH_DEFINES // for M_PI
22
23
// Include automatically generated configuration file if running autoconf.
24
#ifdef HAVE_CONFIG_H
25
#  include "config_auto.h"
26
#endif
27
28
#include "intproto.h"
29
30
#include "classify.h"
31
#include "fontinfo.h"
32
#include "mfoutline.h"
33
#include "picofeat.h"
34
#include "points.h"
35
#include "shapetable.h"
36
#ifndef GRAPHICS_DISABLED
37
#include "svmnode.h"
38
#endif
39
40
#include "helpers.h"
41
#include "tesserrstream.h" // for tesserr
42
43
#include <algorithm>
44
#include <cassert>
45
#include <cmath> // for M_PI, std::floor
46
#include <cstdio>
47
#include <cstdlib>         // for strtol
48
49
namespace tesseract {
50
51
/* match debug display constants*/
52
#define PROTO_PRUNER_SCALE (4.0)
53
54
#define INT_DESCENDER (0.0 * INT_CHAR_NORM_RANGE)
55
#define INT_BASELINE (0.25 * INT_CHAR_NORM_RANGE)
56
#define INT_XHEIGHT (0.75 * INT_CHAR_NORM_RANGE)
57
#define INT_CAPHEIGHT (1.0 * INT_CHAR_NORM_RANGE)
58
59
#define INT_XCENTER (0.5 * INT_CHAR_NORM_RANGE)
60
#define INT_YCENTER (0.5 * INT_CHAR_NORM_RANGE)
61
#define INT_XRADIUS (0.2 * INT_CHAR_NORM_RANGE)
62
#define INT_YRADIUS (0.2 * INT_CHAR_NORM_RANGE)
63
#define INT_MIN_X 0
64
#define INT_MIN_Y 0
65
#define INT_MAX_X INT_CHAR_NORM_RANGE
66
#define INT_MAX_Y INT_CHAR_NORM_RANGE
67
68
/** define pad used to snap near horiz/vertical protos to horiz/vertical */
69
12.9k
#define HV_TOLERANCE (0.0025) /* approx 0.9 degrees */
70
71
enum class SWITCH_TYPE { StartSwitch, EndSwitch, LastSwitch };
72
#define MAX_NUM_SWITCHES 3
73
74
struct FILL_SWITCH {
75
  SWITCH_TYPE Type;
76
  int8_t X, Y;
77
  int16_t YInit;
78
  int16_t Delta;
79
};
80
81
struct TABLE_FILLER {
82
  uint8_t NextSwitch;
83
  uint8_t AngleStart, AngleEnd;
84
  int8_t X;
85
  int16_t YStart, YEnd;
86
  int16_t StartDelta, EndDelta;
87
  FILL_SWITCH Switch[MAX_NUM_SWITCHES];
88
};
89
90
struct FILL_SPEC {
91
  int8_t X;
92
  int8_t YStart, YEnd;
93
  uint8_t AngleStart, AngleEnd;
94
};
95
96
/* constants for conversion from old inttemp format */
97
0
#define OLD_MAX_NUM_CONFIGS 32
98
0
#define OLD_WERDS_PER_CONFIG_VEC ((OLD_MAX_NUM_CONFIGS + BITS_PER_WERD - 1) / BITS_PER_WERD)
99
100
/*-----------------------------------------------------------------------------
101
            Macros
102
-----------------------------------------------------------------------------*/
103
/** macro for performing circular increments of bucket indices */
104
452k
#define CircularIncrement(i, r) (((i) < (r)-1) ? ((i)++) : ((i) = 0))
105
106
/** macro for mapping floats to ints without bounds checking */
107
207k
#define MapParam(P, O, N) (std::floor(((P) + (O)) * (N)))
108
109
/*---------------------------------------------------------------------------
110
            Private Function Prototypes
111
----------------------------------------------------------------------------*/
112
float BucketStart(int Bucket, float Offset, int NumBuckets);
113
114
float BucketEnd(int Bucket, float Offset, int NumBuckets);
115
116
void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
117
            uint32_t ClassCount, uint32_t WordIndex);
118
119
bool FillerDone(TABLE_FILLER *Filler);
120
121
void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
122
                        float Center, float Spread, bool debug);
123
124
void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
125
                      float Center, float Spread, bool debug);
126
127
void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad);
128
129
ScrollView::Color GetMatchColorFor(float Evidence);
130
131
void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill);
132
133
void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto,
134
                     TABLE_FILLER *Filler);
135
136
#ifndef GRAPHICS_DISABLED
137
void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature,
138
                      ScrollView::Color color);
139
140
void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, ScrollView::Color color);
141
#endif // !GRAPHICS_DISABLED
142
143
/*-----------------------------------------------------------------------------
144
        Global Data Definitions and Declarations
145
-----------------------------------------------------------------------------*/
146
147
#ifndef GRAPHICS_DISABLED
148
/* global display lists used to display proto and feature match information*/
149
static ScrollView *IntMatchWindow = nullptr;
150
static ScrollView *FeatureDisplayWindow = nullptr;
151
static ScrollView *ProtoDisplayWindow = nullptr;
152
#endif
153
154
/*-----------------------------------------------------------------------------
155
        Variables
156
-----------------------------------------------------------------------------*/
157
158
/* control knobs */
159
static INT_VAR(classify_num_cp_levels, 3, "Number of Class Pruner Levels");
160
static double_VAR(classify_cp_angle_pad_loose, 45.0, "Class Pruner Angle Pad Loose");
161
static double_VAR(classify_cp_angle_pad_medium, 20.0, "Class Pruner Angle Pad Medium");
162
static double_VAR(classify_cp_angle_pad_tight, 10.0, "CLass Pruner Angle Pad Tight");
163
static double_VAR(classify_cp_end_pad_loose, 0.5, "Class Pruner End Pad Loose");
164
static double_VAR(classify_cp_end_pad_medium, 0.5, "Class Pruner End Pad Medium");
165
static double_VAR(classify_cp_end_pad_tight, 0.5, "Class Pruner End Pad Tight");
166
static double_VAR(classify_cp_side_pad_loose, 2.5, "Class Pruner Side Pad Loose");
167
static double_VAR(classify_cp_side_pad_medium, 1.2, "Class Pruner Side Pad Medium");
168
static double_VAR(classify_cp_side_pad_tight, 0.6, "Class Pruner Side Pad Tight");
169
static double_VAR(classify_pp_angle_pad, 45.0, "Proto Pruner Angle Pad");
170
static double_VAR(classify_pp_end_pad, 0.5, "Proto Prune End Pad");
171
static double_VAR(classify_pp_side_pad, 2.5, "Proto Pruner Side Pad");
172
173
/**
174
 * This routine truncates Param to lie within the range
175
 * of Min-Max inclusive.
176
 *
177
 * @param Param   parameter value to be truncated
178
 * @param Min, Max  parameter limits (inclusive)
179
 *
180
 * @return Truncated parameter.
181
 */
182
47.9k
static int TruncateParam(float Param, int Min, int Max) {
183
47.9k
  int result;
184
47.9k
  if (Param < Min) {
185
965
    result = Min;
186
46.9k
  } else if (Param > Max) {
187
7.72k
    result = Max;
188
39.2k
  } else {
189
39.2k
    result = static_cast<int>(std::floor(Param));
190
39.2k
  }
191
47.9k
  return result;
192
47.9k
}
193
194
/*-----------------------------------------------------------------------------
195
              Public Code
196
-----------------------------------------------------------------------------*/
197
/// Builds a feature from an FCOORD for position with all the necessary
198
/// clipping and rounding.
199
INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(const FCOORD &pos, uint8_t theta)
200
246M
    : X(ClipToRange<int16_t>(static_cast<int16_t>(pos.x() + 0.5), 0, 255))
201
246M
    , Y(ClipToRange<int16_t>(static_cast<int16_t>(pos.y() + 0.5), 0, 255))
202
246M
    , Theta(theta)
203
246M
    , CP_misses(0) {}
204
/** Builds a feature from ints with all the necessary clipping and casting. */
205
INT_FEATURE_STRUCT::INT_FEATURE_STRUCT(int x, int y, int theta)
206
0
    : X(static_cast<uint8_t>(ClipToRange<int>(x, 0, UINT8_MAX)))
207
0
    , Y(static_cast<uint8_t>(ClipToRange<int>(y, 0, UINT8_MAX)))
208
0
    , Theta(static_cast<uint8_t>(ClipToRange<int>(theta, 0, UINT8_MAX)))
209
0
    , CP_misses(0) {}
210
211
/**
212
 * This routine adds a new class structure to a set of
213
 * templates. Classes have to be added to Templates in
214
 * the order of increasing ClassIds.
215
 *
216
 * @param Templates templates to add new class to
217
 * @param ClassId   class id to associate new class with
218
 * @param Class   class data structure to add to templates
219
 *
220
 * Globals: none
221
 */
222
6.14M
bool AddIntClass(INT_TEMPLATES_STRUCT *Templates, CLASS_ID ClassId, INT_CLASS_STRUCT *Class) {
223
6.14M
  int Pruner;
224
225
  // ClassId indexes Class[] and (divided by CLASSES_PER_CP) ClassPruners[],
226
  // so reject an out-of-range id instead of writing out of bounds. The
227
  // previous check was a debug-only assertion that release builds skip.
228
6.14M
  if (!LegalClassId(ClassId)) {
229
0
    tesserr << "Error: illegal class id " << ClassId << " in AddIntClass\n";
230
0
    return false;
231
0
  }
232
6.14M
  if (static_cast<unsigned>(ClassId) != Templates->NumClasses) {
233
    // Use tesserr (stderr) rather than tprintf (stdout): this is an error
234
    // message and stdout is the data channel of the training tools.
235
0
    tesserr << "Error: Please make sure that classes are added to templates"
236
0
            << " in increasing order of ClassIds\n";
237
0
    return false;
238
0
  }
239
6.14M
  ClassForClassId(Templates, ClassId) = Class;
240
6.14M
  Templates->NumClasses++;
241
242
6.14M
  if (Templates->NumClasses > MaxNumClassesIn(Templates)) {
243
192k
    Pruner = Templates->NumClassPruners++;
244
192k
    Templates->ClassPruners[Pruner] = new CLASS_PRUNER_STRUCT;
245
192k
    memset(Templates->ClassPruners[Pruner], 0, sizeof(CLASS_PRUNER_STRUCT));
246
192k
  }
247
6.14M
  return true;
248
6.14M
} /* AddIntClass */
249
250
/**
251
 * This routine returns the index of the next free config
252
 * in Class.
253
 *
254
 * @param Class class to add new configuration to
255
 *
256
 * Globals: none
257
 *
258
 * @return Index of next free config.
259
 */
260
1.13k
int AddIntConfig(INT_CLASS_STRUCT *Class) {
261
1.13k
  int Index;
262
263
1.13k
  assert(Class->NumConfigs < MAX_NUM_CONFIGS);
264
265
1.13k
  Index = Class->NumConfigs++;
266
1.13k
  Class->ConfigLengths[Index] = 0;
267
1.13k
  return Index;
268
1.13k
} /* AddIntConfig */
269
270
/**
271
 * This routine allocates the next free proto in Class and
272
 * returns its index.
273
 *
274
 * @param Class class to add new proto to
275
 *
276
 * Globals: none
277
 *
278
 * @return Proto index of new proto.
279
 */
280
11.9k
int AddIntProto(INT_CLASS_STRUCT *Class) {
281
11.9k
  if (Class->NumProtos >= MAX_NUM_PROTOS) {
282
1
    return (NO_PROTO);
283
1
  }
284
285
11.9k
  int Index = Class->NumProtos++;
286
287
11.9k
  if (Class->NumProtos > MaxNumIntProtosIn(Class)) {
288
37
    int ProtoSetId = Class->NumProtoSets++;
289
37
    auto ProtoSet = new PROTO_SET_STRUCT;
290
37
    Class->ProtoSets[ProtoSetId] = ProtoSet;
291
37
    memset(ProtoSet, 0, sizeof(*ProtoSet));
292
293
    /* reallocate space for the proto lengths and install in class */
294
37
    Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
295
37
  }
296
297
  // initialize proto so its length is zero and it isn't in any configs
298
11.9k
  Class->ProtoLengths[Index] = 0;
299
11.9k
  auto Proto = ProtoForProtoId(Class, Index);
300
11.9k
  Proto->Configs.fill(0);
301
302
11.9k
  return (Index);
303
11.9k
}
304
305
/**
306
 * This routine adds Proto to the class pruning tables
307
 * for the specified class in Templates.
308
 *
309
 * Globals:
310
 *  - classify_num_cp_levels number of levels used in the class pruner
311
 * @param Proto   floating-pt proto to add to class pruner
312
 * @param ClassId   class id corresponding to Proto
313
 * @param Templates set of templates containing class pruner
314
 */
315
void AddProtoToClassPruner(PROTO_STRUCT *Proto, CLASS_ID ClassId, INT_TEMPLATES_STRUCT *Templates)
316
859
#define MAX_LEVEL 2
317
859
{
318
859
  CLASS_PRUNER_STRUCT *Pruner;
319
859
  uint32_t ClassMask;
320
859
  uint32_t ClassCount;
321
859
  uint32_t WordIndex;
322
859
  int Level;
323
859
  float EndPad, SidePad, AnglePad;
324
859
  TABLE_FILLER TableFiller;
325
859
  FILL_SPEC FillSpec;
326
327
859
  Pruner = CPrunerFor(Templates, ClassId);
328
859
  WordIndex = CPrunerWordIndexFor(ClassId);
329
859
  ClassMask = CPrunerMaskFor(MAX_LEVEL, ClassId);
330
331
3.43k
  for (Level = classify_num_cp_levels - 1; Level >= 0; Level--) {
332
2.57k
    GetCPPadsForLevel(Level, &EndPad, &SidePad, &AnglePad);
333
2.57k
    ClassCount = CPrunerMaskFor(Level, ClassId);
334
2.57k
    InitTableFiller(EndPad, SidePad, AnglePad, Proto, &TableFiller);
335
336
14.6k
    while (!FillerDone(&TableFiller)) {
337
12.0k
      GetNextFill(&TableFiller, &FillSpec);
338
12.0k
      DoFill(&FillSpec, Pruner, ClassMask, ClassCount, WordIndex);
339
12.0k
    }
340
2.57k
  }
341
859
} /* AddProtoToClassPruner */
342
343
/**
344
 * This routine updates the proto pruner lookup tables
345
 * for Class to include a new proto identified by ProtoId
346
 * and described by Proto.
347
 * @param Proto floating-pt proto to be added to proto pruner
348
 * @param ProtoId id of proto
349
 * @param Class integer class that contains desired proto pruner
350
 * @param debug debug flag
351
 * @note Globals: none
352
 */
353
11.9k
void AddProtoToProtoPruner(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class, bool debug) {
354
11.9k
  float X, Y, Length;
355
11.9k
  float Pad;
356
357
11.9k
  if (ProtoId >= Class->NumProtos) {
358
0
    tprintf("AddProtoToProtoPruner:assert failed: %d < %d", ProtoId, Class->NumProtos);
359
0
  }
360
11.9k
  assert(ProtoId < Class->NumProtos);
361
362
11.9k
  int Index = IndexForProto(ProtoId);
363
11.9k
  auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
364
365
11.9k
  float Angle = Proto->Angle;
366
11.9k
#ifndef _WIN32
367
11.9k
  assert(!std::isnan(Angle));
368
11.9k
#endif
369
370
11.9k
  FillPPCircularBits(ProtoSet->ProtoPruner[PRUNER_ANGLE], Index, Angle + ANGLE_SHIFT,
371
11.9k
                     classify_pp_angle_pad / 360.0, debug);
372
373
11.9k
  Angle *= 2.0 * M_PI;
374
11.9k
  Length = Proto->Length;
375
376
11.9k
  X = Proto->X + X_SHIFT;
377
11.9k
  Pad = std::max(fabs(std::cos(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
378
11.9k
                 fabs(std::sin(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
379
380
11.9k
  FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_X], Index, X, Pad, debug);
381
382
11.9k
  Y = Proto->Y + Y_SHIFT;
383
11.9k
  Pad = std::max(fabs(std::sin(Angle)) * (Length / 2.0 + classify_pp_end_pad * GetPicoFeatureLength()),
384
11.9k
                 fabs(std::cos(Angle)) * (classify_pp_side_pad * GetPicoFeatureLength()));
385
386
11.9k
  FillPPLinearBits(ProtoSet->ProtoPruner[PRUNER_Y], Index, Y, Pad, debug);
387
11.9k
} /* AddProtoToProtoPruner */
388
389
/**
390
 * Returns a quantized bucket for the given param shifted by offset,
391
 * notionally (param + offset) * num_buckets, but clipped and casted to the
392
 * appropriate type.
393
 */
394
133k
uint8_t Bucket8For(float param, float offset, int num_buckets) {
395
133k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
396
133k
  return static_cast<uint8_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
397
133k
}
398
7.14k
uint16_t Bucket16For(float param, float offset, int num_buckets) {
399
7.14k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
400
7.14k
  return static_cast<uint16_t>(ClipToRange<int>(bucket, 0, num_buckets - 1));
401
7.14k
}
402
403
/**
404
 * Returns a quantized bucket for the given circular param shifted by offset,
405
 * notionally (param + offset) * num_buckets, but modded and casted to the
406
 * appropriate type.
407
 */
408
67.2k
uint8_t CircBucketFor(float param, float offset, int num_buckets) {
409
67.2k
  int bucket = IntCastRounded(MapParam(param, offset, num_buckets));
410
67.2k
  return static_cast<uint8_t>(Modulo(bucket, num_buckets));
411
67.2k
} /* CircBucketFor */
412
413
#ifndef GRAPHICS_DISABLED
414
/**
415
 * This routine clears the global feature and proto
416
 * display lists.
417
 *
418
 * Globals:
419
 * - FeatureShapes display list for features
420
 * - ProtoShapes display list for protos
421
 */
422
void UpdateMatchDisplay() {
423
  if (IntMatchWindow != nullptr) {
424
    IntMatchWindow->Update();
425
  }
426
} /* ClearMatchDisplay */
427
#endif
428
429
/**
430
 * This operation updates the config vectors of all protos
431
 * in Class to indicate that the protos with 1's in Config
432
 * belong to a new configuration identified by ConfigId.
433
 * It is assumed that the length of the Config bit vector is
434
 * equal to the number of protos in Class.
435
 * @param Config    config to be added to class
436
 * @param ConfigId  id to be used for new config
437
 * @param Class   class to add new config to
438
 */
439
1.13k
void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS_STRUCT *Class) {
440
1.13k
  int ProtoId;
441
1.13k
  INT_PROTO_STRUCT *Proto;
442
1.13k
  int TotalLength;
443
444
40.4k
  for (ProtoId = 0, TotalLength = 0; ProtoId < Class->NumProtos; ProtoId++) {
445
39.3k
    if (test_bit(Config, ProtoId)) {
446
15.8k
      Proto = ProtoForProtoId(Class, ProtoId);
447
15.8k
      SET_BIT(Proto->Configs, ConfigId);
448
15.8k
      TotalLength += Class->ProtoLengths[ProtoId];
449
15.8k
    }
450
39.3k
  }
451
1.13k
  Class->ConfigLengths[ConfigId] = TotalLength;
452
1.13k
} /* ConvertConfig */
453
454
/**
455
 * This routine converts Proto to integer format and
456
 * installs it as ProtoId in Class.
457
 * @param Proto floating-pt proto to be converted to integer format
458
 * @param ProtoId id of proto
459
 * @param Class integer class to add converted proto to
460
 */
461
11.9k
void Classify::ConvertProto(PROTO_STRUCT *Proto, int ProtoId, INT_CLASS_STRUCT *Class) {
462
11.9k
  assert(ProtoId < Class->NumProtos);
463
464
11.9k
  INT_PROTO_STRUCT *P = ProtoForProtoId(Class, ProtoId);
465
466
11.9k
  float Param = Proto->A * 128;
467
11.9k
  P->A = TruncateParam(Param, -128, 127);
468
469
11.9k
  Param = -Proto->B * 256;
470
11.9k
  P->B = TruncateParam(Param, 0, 255);
471
472
11.9k
  Param = Proto->C * 128;
473
11.9k
  P->C = TruncateParam(Param, -128, 127);
474
475
11.9k
  Param = Proto->Angle * 256;
476
11.9k
  if (Param < 0 || Param >= 256) {
477
0
    P->Angle = 0;
478
11.9k
  } else {
479
11.9k
    P->Angle = static_cast<uint8_t>(Param);
480
11.9k
  }
481
482
  /* round proto length to nearest integer number of pico-features */
483
11.9k
  Param = (Proto->Length / GetPicoFeatureLength()) + 0.5;
484
11.9k
  Class->ProtoLengths[ProtoId] = TruncateParam(Param, 1, 255);
485
11.9k
  if (classify_learning_debug_level >= 2) {
486
0
    tprintf("Converted ffeat to (A=%d,B=%d,C=%d,L=%d)", P->A, P->B, P->C,
487
0
            Class->ProtoLengths[ProtoId]);
488
0
  }
489
11.9k
} /* ConvertProto */
490
491
/**
492
 * This routine converts from the old floating point format
493
 * to the new integer format.
494
 * @param FloatProtos prototypes in old floating pt format
495
 * @param target_unicharset the UNICHARSET to use
496
 * @return New set of training templates in integer format.
497
 * @note Globals: none
498
 */
499
INT_TEMPLATES_STRUCT *Classify::CreateIntTemplates(CLASSES FloatProtos,
500
0
                                           const UNICHARSET &target_unicharset) {
501
0
  CLASS_TYPE FClass;
502
0
  INT_CLASS_STRUCT *IClass;
503
0
  int ProtoId;
504
0
  int ConfigId;
505
506
  // A unichar id indexes the fixed-size Class[] array (MAX_NUM_CLASSES
507
  // entries), so a set larger than that would write out of bounds in
508
  // AddIntClass below. Reject it up front rather than part-way through.
509
0
  if (target_unicharset.size() > MAX_NUM_CLASSES) {
510
0
    tesserr << "Error: unicharset has " << target_unicharset.size()
511
0
            << " characters, more than MAX_NUM_CLASSES " << MAX_NUM_CLASSES << "\n";
512
0
    return nullptr;
513
0
  }
514
515
0
  auto IntTemplates = new INT_TEMPLATES_STRUCT;
516
517
0
  for (unsigned ClassId = 0; ClassId < target_unicharset.size(); ClassId++) {
518
0
    FClass = &(FloatProtos[ClassId]);
519
0
    if (FClass->NumProtos == 0 && FClass->NumConfigs == 0 &&
520
0
        strcmp(target_unicharset.id_to_unichar(ClassId), " ") != 0) {
521
0
      tprintf("Warning: no protos/configs for %s in CreateIntTemplates()\n",
522
0
              target_unicharset.id_to_unichar(ClassId));
523
0
    }
524
0
    assert(UnusedClassIdIn(IntTemplates, ClassId));
525
0
    IClass = new INT_CLASS_STRUCT(FClass->NumProtos);
526
0
    unsigned fs_size = FClass->font_set.size();
527
0
    FontSet fs;
528
0
    fs.reserve(fs_size);
529
0
    for (unsigned i = 0; i < fs_size; ++i) {
530
0
      fs.push_back(FClass->font_set[i]);
531
0
    }
532
0
    IClass->font_set_id = this->fontset_table_.push_back(std::move(fs));
533
0
    if (!AddIntClass(IntTemplates, ClassId, IClass)) {
534
      // AddIntClass leaves the class with the caller on failure, and the
535
      // template set only holds the classes added so far, so clean up both
536
      // and report the failure instead of writing an incomplete set.
537
0
      delete IClass;
538
0
      delete IntTemplates;
539
0
      return nullptr;
540
0
    }
541
542
0
    for (ProtoId = 0; ProtoId < FClass->NumProtos; ProtoId++) {
543
0
      AddIntProto(IClass);
544
0
      ConvertProto(ProtoIn(FClass, ProtoId), ProtoId, IClass);
545
0
      AddProtoToProtoPruner(ProtoIn(FClass, ProtoId), ProtoId, IClass,
546
0
                            classify_learning_debug_level >= 2);
547
0
      AddProtoToClassPruner(ProtoIn(FClass, ProtoId), ClassId, IntTemplates);
548
0
    }
549
550
0
    for (ConfigId = 0; ConfigId < FClass->NumConfigs; ConfigId++) {
551
0
      AddIntConfig(IClass);
552
0
      ConvertConfig(FClass->Configurations[ConfigId], ConfigId, IClass);
553
0
    }
554
0
  }
555
0
  return (IntTemplates);
556
0
} /* CreateIntTemplates */
557
558
#ifndef GRAPHICS_DISABLED
559
/**
560
 * This routine renders the specified feature into a
561
 * global display list.
562
 *
563
 * Globals:
564
 * - FeatureShapes global display list for features
565
 * @param Feature   pico-feature to be displayed
566
 * @param Evidence  best evidence for this feature (0-1)
567
 */
568
void DisplayIntFeature(const INT_FEATURE_STRUCT *Feature, float Evidence) {
569
  ScrollView::Color color = GetMatchColorFor(Evidence);
570
  RenderIntFeature(IntMatchWindow, Feature, color);
571
  if (FeatureDisplayWindow) {
572
    RenderIntFeature(FeatureDisplayWindow, Feature, color);
573
  }
574
} /* DisplayIntFeature */
575
576
/**
577
 * This routine renders the specified proto into a
578
 * global display list.
579
 *
580
 * Globals:
581
 * - ProtoShapes global display list for protos
582
 * @param Class   class to take proto from
583
 * @param ProtoId   id of proto in Class to be displayed
584
 * @param Evidence  total evidence for proto (0-1)
585
 */
586
void DisplayIntProto(INT_CLASS_STRUCT *Class, PROTO_ID ProtoId, float Evidence) {
587
  ScrollView::Color color = GetMatchColorFor(Evidence);
588
  RenderIntProto(IntMatchWindow, Class, ProtoId, color);
589
  if (ProtoDisplayWindow) {
590
    RenderIntProto(ProtoDisplayWindow, Class, ProtoId, color);
591
  }
592
} /* DisplayIntProto */
593
#endif
594
595
/// This constructor creates a new integer class data structure
596
/// and returns it.  Sufficient space is allocated
597
/// to handle the specified number of protos and configs.
598
/// @param MaxNumProtos  number of protos to allocate space for
599
/// @param MaxNumConfigs number of configs to allocate space for
600
INT_CLASS_STRUCT::INT_CLASS_STRUCT(int MaxNumProtos) :
601
6.14M
  NumProtos(0),
602
6.14M
  NumProtoSets((MaxNumProtos + PROTOS_PER_PROTO_SET - 1) / PROTOS_PER_PROTO_SET),
603
6.14M
  NumConfigs(0),
604
6.14M
  ProtoLengths(MaxNumIntProtosIn(this))
605
6.14M
{
606
6.14M
  assert(NumProtoSets <= MAX_NUM_PROTO_SETS);
607
608
12.2M
  for (int i = 0; i < NumProtoSets; i++) {
609
    // allocate space for a proto set, install in class, and initialize
610
6.14M
    auto ProtoSet = new PROTO_SET_STRUCT;
611
6.14M
    memset(ProtoSet, 0, sizeof(*ProtoSet));
612
6.14M
    ProtoSets[i] = ProtoSet;
613
614
    // allocate space for the proto lengths and install in class
615
6.14M
  }
616
6.14M
}
617
618
6.14M
INT_CLASS_STRUCT::~INT_CLASS_STRUCT() {
619
  // NumProtoSets comes from an untrusted file, so never trust it to bound
620
  // the loop over the fixed-size ProtoSets[] array.
621
12.2M
  for (int i = 0; i < NumProtoSets && i < MAX_NUM_PROTO_SETS; i++) {
622
6.14M
    delete ProtoSets[i];
623
6.14M
  }
624
6.14M
}
625
626
/// This constructor allocates a new set of integer templates
627
/// initialized to hold 0 classes.
628
12.8k
INT_TEMPLATES_STRUCT::INT_TEMPLATES_STRUCT() : NumClasses(0), NumClassPruners(0) {
629
  // Class and ClassPruners are value-initialized to nullptr in-class.
630
12.8k
}
631
632
12.8k
INT_TEMPLATES_STRUCT::~INT_TEMPLATES_STRUCT() {
633
  // The counts come from an untrusted file, so never trust them to bound
634
  // the loops over the fixed-size arrays.
635
6.15M
  for (unsigned i = 0; i < NumClasses && i < MAX_NUM_CLASSES; i++) {
636
6.14M
    delete Class[i];
637
6.14M
  }
638
204k
  for (unsigned i = 0; i < NumClassPruners && i < MAX_NUM_CLASS_PRUNERS; i++) {
639
192k
    delete ClassPruners[i];
640
192k
  }
641
12.8k
}
642
643
/**
644
 * This routine reads a set of integer templates from
645
 * File.  File must already be open and must be in the
646
 * correct binary format.
647
 * @param  fp open file to read templates from
648
 * @return Pointer to integer templates read from File.
649
 * @note Globals: none
650
 */
651
4
INT_TEMPLATES_STRUCT *Classify::ReadIntTemplates(TFile *fp) {
652
4
  int j, w, x, y, z;
653
4
  INT_TEMPLATES_STRUCT *Templates;
654
4
  CLASS_PRUNER_STRUCT *Pruner;
655
4
  INT_CLASS_STRUCT *Class;
656
657
  /* variables for conversion from older inttemp formats */
658
4
  int b, bit_number, last_cp_bit_number, new_b, new_i, new_w;
659
4
  CLASS_ID class_id, max_class_id;
660
4
  std::vector<CLASS_ID> ClassIdFor(MAX_NUM_CLASSES);
661
4
  std::vector<CLASS_PRUNER_STRUCT *> TempClassPruner(MAX_NUM_CLASS_PRUNERS);
662
4
  uint32_t SetBitsForMask =          // word with NUM_BITS_PER_CLASS
663
4
      (1 << NUM_BITS_PER_CLASS) - 1; // set starting at bit 0
664
4
  uint32_t Mask, NewMask, ClassBits;
665
4
  unsigned MaxNumConfigs = MAX_NUM_CONFIGS;
666
4
  unsigned WerdsPerConfigVec = WERDS_PER_CONFIG_VEC;
667
668
  /* first read the high level template struct */
669
4
  Templates = new INT_TEMPLATES_STRUCT;
670
  // Read Templates in parts for 64 bit compatibility.
671
4
  uint32_t unicharset_size;
672
4
  if (fp->FReadEndian(&unicharset_size, sizeof(unicharset_size), 1) != 1) {
673
0
    tprintf("Bad read of inttemp!\n");
674
0
  }
675
4
  int32_t version_id = 0;
676
4
  if (fp->FReadEndian(&version_id, sizeof(version_id), 1) != 1 ||
677
4
      fp->FReadEndian(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1) != 1) {
678
0
    tprintf("Bad read of inttemp!\n");
679
0
  }
680
4
  if (version_id < 0) {
681
    // This file has a version id!
682
4
    version_id = -version_id;
683
4
    if (fp->FReadEndian(&Templates->NumClasses, sizeof(Templates->NumClasses), 1) != 1) {
684
0
      tprintf("Bad read of inttemp!\n");
685
0
    }
686
4
  } else {
687
0
    Templates->NumClasses = version_id;
688
0
  }
689
690
  // The counts read from the file are used as loop bounds that write into
691
  // fixed-size arrays (Class[], ClassPruners[], TempClassPruner[] and
692
  // IndexFor[]), so reject a corrupt or malicious file instead of writing
693
  // out of bounds.
694
4
  if (unicharset_size > MAX_NUM_CLASSES ||
695
4
      Templates->NumClassPruners > MAX_NUM_CLASS_PRUNERS ||
696
4
      Templates->NumClasses > MAX_NUM_CLASSES) {
697
0
    tprintf("Error: invalid counts in inttemp: unicharset_size=%u, NumClassPruners=%u, "
698
0
            "NumClasses=%u\n",
699
0
            unicharset_size, Templates->NumClassPruners, Templates->NumClasses);
700
0
    delete Templates;
701
0
    return nullptr;
702
0
  }
703
704
4
  if (version_id < 3) {
705
0
    MaxNumConfigs = OLD_MAX_NUM_CONFIGS;
706
0
    WerdsPerConfigVec = OLD_WERDS_PER_CONFIG_VEC;
707
0
  }
708
709
4
  if (version_id < 2) {
710
0
    std::vector<int16_t> IndexFor(MAX_NUM_CLASSES);
711
0
    if (fp->FReadEndian(&IndexFor[0], sizeof(IndexFor[0]), unicharset_size) != unicharset_size) {
712
0
      tprintf("Bad read of inttemp!\n");
713
0
    }
714
0
    if (fp->FReadEndian(&ClassIdFor[0], sizeof(ClassIdFor[0]), Templates->NumClasses) !=
715
0
        Templates->NumClasses) {
716
0
      tprintf("Bad read of inttemp!\n");
717
0
    }
718
    // Each class id indexes Class[] and (divided by CLASSES_PER_CP)
719
    // ClassPruners[]. A negative id does not raise max_class_id and therefore
720
    // evades the upper-bound check below. Validate every id, including oversized
721
    // positive values, before any class pruner is allocated or the id is used.
722
0
    for (unsigned i = 0; i < Templates->NumClasses; i++) {
723
0
      if (!LegalClassId(ClassIdFor[i])) {
724
0
        tprintf("Error: illegal class id %d in inttemp\n", ClassIdFor[i]);
725
0
        delete Templates;
726
0
        return nullptr;
727
0
      }
728
0
    }
729
0
  }
730
731
  /* then read in the class pruners */
732
4
  const unsigned kNumBuckets = NUM_CP_BUCKETS * NUM_CP_BUCKETS * NUM_CP_BUCKETS * WERDS_PER_CP_VECTOR;
733
20
  for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
734
16
    Pruner = new CLASS_PRUNER_STRUCT;
735
16
    if (fp->FReadEndian(Pruner, sizeof(Pruner->p[0][0][0][0]), kNumBuckets) != kNumBuckets) {
736
0
      tprintf("Bad read of inttemp!\n");
737
0
    }
738
16
    if (version_id < 2) {
739
0
      TempClassPruner[i] = Pruner;
740
16
    } else {
741
16
      Templates->ClassPruners[i] = Pruner;
742
16
    }
743
16
  }
744
745
  /* fix class pruners if they came from an old version of inttemp */
746
4
  if (version_id < 2) {
747
    // Allocate enough class pruners to cover all the class ids.
748
0
    max_class_id = 0;
749
0
    for (unsigned i = 0; i < Templates->NumClasses; i++) {
750
0
      if (ClassIdFor[i] > max_class_id) {
751
0
        max_class_id = ClassIdFor[i];
752
0
      }
753
0
    }
754
    // Class ids index Class[] and (divided by CLASSES_PER_CP) ClassPruners[],
755
    // so reject a corrupt or malicious file instead of writing out of bounds.
756
0
    if (max_class_id >= MAX_NUM_CLASSES) {
757
0
      tprintf("Error: class id %u in inttemp exceeds MAX_NUM_CLASSES\n", max_class_id);
758
0
      for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
759
0
        delete TempClassPruner[i];
760
0
      }
761
0
      delete Templates;
762
0
      return nullptr;
763
0
    }
764
0
    for (int i = 0; i <= CPrunerIdFor(max_class_id); i++) {
765
0
      Templates->ClassPruners[i] = new CLASS_PRUNER_STRUCT;
766
0
      memset(Templates->ClassPruners[i], 0, sizeof(CLASS_PRUNER_STRUCT));
767
0
    }
768
    // Convert class pruners from the old format (indexed by class index)
769
    // to the new format (indexed by class id).
770
0
    last_cp_bit_number = NUM_BITS_PER_CLASS * Templates->NumClasses - 1;
771
0
    for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
772
0
      for (x = 0; x < NUM_CP_BUCKETS; x++) {
773
0
        for (y = 0; y < NUM_CP_BUCKETS; y++) {
774
0
          for (z = 0; z < NUM_CP_BUCKETS; z++) {
775
0
            for (w = 0; w < WERDS_PER_CP_VECTOR; w++) {
776
0
              if (TempClassPruner[i]->p[x][y][z][w] == 0) {
777
0
                continue;
778
0
              }
779
0
              for (b = 0; b < BITS_PER_WERD; b += NUM_BITS_PER_CLASS) {
780
0
                bit_number = i * BITS_PER_CP_VECTOR + w * BITS_PER_WERD + b;
781
0
                if (bit_number > last_cp_bit_number) {
782
0
                  break; // the rest of the bits in this word are not used
783
0
                }
784
0
                class_id = ClassIdFor[bit_number / NUM_BITS_PER_CLASS];
785
                // Single out NUM_BITS_PER_CLASS bits relating to class_id.
786
0
                Mask = SetBitsForMask << b;
787
0
                ClassBits = TempClassPruner[i]->p[x][y][z][w] & Mask;
788
                // Move these bits to the new position in which they should
789
                // appear (indexed corresponding to the class_id).
790
0
                new_i = CPrunerIdFor(class_id);
791
0
                new_w = CPrunerWordIndexFor(class_id);
792
0
                new_b = CPrunerBitIndexFor(class_id) * NUM_BITS_PER_CLASS;
793
0
                if (new_b > b) {
794
0
                  ClassBits <<= (new_b - b);
795
0
                } else {
796
0
                  ClassBits >>= (b - new_b);
797
0
                }
798
                // Copy bits relating to class_id to the correct position
799
                // in Templates->ClassPruner.
800
0
                NewMask = SetBitsForMask << new_b;
801
0
                Templates->ClassPruners[new_i]->p[x][y][z][new_w] &= ~NewMask;
802
0
                Templates->ClassPruners[new_i]->p[x][y][z][new_w] |= ClassBits;
803
0
              }
804
0
            }
805
0
          }
806
0
        }
807
0
      }
808
0
    }
809
0
    for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
810
0
      delete TempClassPruner[i];
811
0
    }
812
0
  }
813
814
  /* then read in each class */
815
456
  for (unsigned i = 0; i < Templates->NumClasses; i++) {
816
    /* first read in the high level struct for the class */
817
452
    Class = new INT_CLASS_STRUCT;
818
452
    if (fp->FReadEndian(&Class->NumProtos, sizeof(Class->NumProtos), 1) != 1 ||
819
452
        fp->FRead(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1) != 1 ||
820
452
        fp->FRead(&Class->NumConfigs, sizeof(Class->NumConfigs), 1) != 1) {
821
0
      tprintf("Bad read of inttemp!\n");
822
0
    }
823
452
    if (version_id == 0) {
824
      // Only version 0 writes 5 pointless pointers to the file.
825
0
      for (j = 0; j < 5; ++j) {
826
0
        int32_t junk;
827
0
        if (fp->FRead(&junk, sizeof(junk), 1) != 1) {
828
0
          tprintf("Bad read of inttemp!\n");
829
0
        }
830
0
      }
831
0
    }
832
452
    unsigned num_configs = version_id < 4 ? MaxNumConfigs : Class->NumConfigs;
833
    // Class->NumProtoSets is used as a loop bound that writes into the
834
    // fixed-size ProtoSets[] array, so reject a corrupt or malicious file
835
    // instead of writing out of bounds.
836
452
    if (Class->NumProtos > MAX_NUM_PROTOS || Class->NumProtoSets > MAX_NUM_PROTO_SETS ||
837
452
        num_configs > MaxNumConfigs) {
838
0
      tprintf("Error: invalid counts for class %u in inttemp: NumProtos=%u, NumProtoSets=%u, "
839
0
              "NumConfigs=%u\n",
840
0
              i, Class->NumProtos, Class->NumProtoSets, Class->NumConfigs);
841
0
      Class->NumProtoSets = 0; // no proto sets allocated yet; keep destructor safe
842
0
      delete Class;
843
0
      delete Templates;
844
0
      return nullptr;
845
0
    }
846
452
    if (fp->FReadEndian(Class->ConfigLengths.data(), sizeof(uint16_t), num_configs) != num_configs) {
847
0
      tprintf("Bad read of inttemp!\n");
848
0
    }
849
452
    if (version_id < 2) {
850
0
      ClassForClassId(Templates, ClassIdFor[i]) = Class;
851
452
    } else {
852
452
      ClassForClassId(Templates, i) = Class;
853
452
    }
854
855
    /* then read in the proto lengths */
856
452
    Class->ProtoLengths.clear();
857
452
    if (MaxNumIntProtosIn(Class) > 0) {
858
440
      Class->ProtoLengths.resize(MaxNumIntProtosIn(Class));
859
440
      if (fp->FRead(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class)) !=
860
440
          MaxNumIntProtosIn(Class)) {
861
0
        tprintf("Bad read of inttemp!\n");
862
0
      }
863
440
    }
864
865
    /* then read in the proto sets */
866
1.24k
    for (j = 0; j < Class->NumProtoSets; j++) {
867
796
      auto ProtoSet = new PROTO_SET_STRUCT;
868
796
      unsigned num_buckets = NUM_PP_PARAMS * NUM_PP_BUCKETS * WERDS_PER_PP_VECTOR;
869
796
      if (fp->FReadEndian(&ProtoSet->ProtoPruner, sizeof(ProtoSet->ProtoPruner[0][0][0]),
870
796
                          num_buckets) != num_buckets) {
871
0
        tprintf("Bad read of inttemp!\n");
872
0
      }
873
51.7k
      for (x = 0; x < PROTOS_PER_PROTO_SET; x++) {
874
50.9k
        if (fp->FRead(&ProtoSet->Protos[x].A, sizeof(ProtoSet->Protos[x].A), 1) != 1 ||
875
50.9k
            fp->FRead(&ProtoSet->Protos[x].B, sizeof(ProtoSet->Protos[x].B), 1) != 1 ||
876
50.9k
            fp->FRead(&ProtoSet->Protos[x].C, sizeof(ProtoSet->Protos[x].C), 1) != 1 ||
877
50.9k
            fp->FRead(&ProtoSet->Protos[x].Angle, sizeof(ProtoSet->Protos[x].Angle), 1) != 1) {
878
0
          tprintf("Bad read of inttemp!\n");
879
0
        }
880
50.9k
        if (fp->FReadEndian(ProtoSet->Protos[x].Configs.data(),
881
50.9k
                            sizeof(ProtoSet->Protos[x].Configs[0]), WerdsPerConfigVec) !=
882
50.9k
            WerdsPerConfigVec) {
883
0
          tprintf("Bad read of inttemp!\n");
884
0
        }
885
50.9k
      }
886
796
      Class->ProtoSets[j] = ProtoSet;
887
796
    }
888
452
    if (version_id < 4) {
889
0
      Class->font_set_id = -1;
890
452
    } else {
891
452
      fp->FReadEndian(&Class->font_set_id, sizeof(Class->font_set_id), 1);
892
452
    }
893
452
  }
894
895
4
  if (version_id < 2) {
896
    /* add an empty nullptr class with class id 0 */
897
0
    assert(UnusedClassIdIn(Templates, 0));
898
0
    ClassForClassId(Templates, 0) = new INT_CLASS_STRUCT(1);
899
0
    ClassForClassId(Templates, 0)->font_set_id = -1;
900
0
    Templates->NumClasses++;
901
    /* make sure the classes are contiguous */
902
0
    for (unsigned i = 0; i < MAX_NUM_CLASSES; i++) {
903
0
      if (i < Templates->NumClasses) {
904
0
        if (ClassForClassId(Templates, i) == nullptr) {
905
0
          fprintf(stderr, "Non-contiguous class ids in inttemp\n");
906
0
          exit(1);
907
0
        }
908
0
      } else {
909
0
        if (ClassForClassId(Templates, i) != nullptr) {
910
0
          fprintf(stderr, "Class id %u exceeds NumClassesIn (Templates) %u\n", i,
911
0
                  Templates->NumClasses);
912
0
          exit(1);
913
0
        }
914
0
      }
915
0
    }
916
0
  }
917
4
  if (version_id >= 4) {
918
4
    using namespace std::placeholders; // for _1, _2
919
4
    this->fontinfo_table_.read(fp, std::bind(read_info, _1, _2));
920
4
    if (version_id >= 5) {
921
4
      this->fontinfo_table_.read(fp, std::bind(read_spacing_info, _1, _2));
922
4
    }
923
444
    this->fontset_table_.read(fp, [](auto *f, auto *fs) { return f->DeSerialize(*fs); } );
924
4
  }
925
926
4
  return (Templates);
927
4
} /* ReadIntTemplates */
928
929
#ifndef GRAPHICS_DISABLED
930
/**
931
 * This routine sends the shapes in the global display
932
 * lists to the match debugger window.
933
 *
934
 * Globals:
935
 * - FeatureShapes display list containing feature matches
936
 * - ProtoShapes display list containing proto matches
937
 */
938
void Classify::ShowMatchDisplay() {
939
  InitIntMatchWindowIfReqd();
940
  if (ProtoDisplayWindow) {
941
    ProtoDisplayWindow->Clear();
942
  }
943
  if (FeatureDisplayWindow) {
944
    FeatureDisplayWindow->Clear();
945
  }
946
  ClearFeatureSpaceWindow(static_cast<NORM_METHOD>(static_cast<int>(classify_norm_method)),
947
                          IntMatchWindow);
948
  IntMatchWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
949
  if (ProtoDisplayWindow) {
950
    ProtoDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
951
  }
952
  if (FeatureDisplayWindow) {
953
    FeatureDisplayWindow->ZoomToRectangle(INT_MIN_X, INT_MIN_Y, INT_MAX_X, INT_MAX_Y);
954
  }
955
} /* ShowMatchDisplay */
956
957
/// Clears the given window and draws the featurespace guides for the
958
/// appropriate normalization method.
959
void ClearFeatureSpaceWindow(NORM_METHOD norm_method, ScrollView *window) {
960
  window->Clear();
961
962
  window->Pen(ScrollView::GREY);
963
  // Draw the feature space limit rectangle.
964
  window->Rectangle(0, 0, INT_MAX_X, INT_MAX_Y);
965
  if (norm_method == baseline) {
966
    window->SetCursor(0, INT_DESCENDER);
967
    window->DrawTo(INT_MAX_X, INT_DESCENDER);
968
    window->SetCursor(0, INT_BASELINE);
969
    window->DrawTo(INT_MAX_X, INT_BASELINE);
970
    window->SetCursor(0, INT_XHEIGHT);
971
    window->DrawTo(INT_MAX_X, INT_XHEIGHT);
972
    window->SetCursor(0, INT_CAPHEIGHT);
973
    window->DrawTo(INT_MAX_X, INT_CAPHEIGHT);
974
  } else {
975
    window->Rectangle(INT_XCENTER - INT_XRADIUS, INT_YCENTER - INT_YRADIUS,
976
                      INT_XCENTER + INT_XRADIUS, INT_YCENTER + INT_YRADIUS);
977
  }
978
}
979
#endif
980
981
/**
982
 * This routine writes Templates to File.  The format
983
 * is an efficient binary format.  File must already be open
984
 * for writing.
985
 * @param File open file to write templates to
986
 * @param Templates templates to save into File
987
 * @param target_unicharset the UNICHARSET to use
988
 */
989
void Classify::WriteIntTemplates(FILE *File, INT_TEMPLATES_STRUCT *Templates,
990
0
                                 const UNICHARSET &target_unicharset) {
991
0
  INT_CLASS_STRUCT *Class;
992
0
  uint32_t unicharset_size = target_unicharset.size();
993
0
  int version_id = -5; // When negated by the reader -1 becomes +1 etc.
994
995
0
  if (Templates->NumClasses != unicharset_size) {
996
0
    tprintf(
997
0
        "Warning: executing WriteIntTemplates() with %d classes in"
998
0
        " Templates, while target_unicharset size is %" PRIu32 "\n",
999
0
        Templates->NumClasses, unicharset_size);
1000
0
  }
1001
1002
  /* first write the high level template struct */
1003
0
  fwrite(&unicharset_size, sizeof(unicharset_size), 1, File);
1004
0
  fwrite(&version_id, sizeof(version_id), 1, File);
1005
0
  fwrite(&Templates->NumClassPruners, sizeof(Templates->NumClassPruners), 1, File);
1006
0
  fwrite(&Templates->NumClasses, sizeof(Templates->NumClasses), 1, File);
1007
1008
  /* then write out the class pruners */
1009
0
  for (unsigned i = 0; i < Templates->NumClassPruners; i++) {
1010
0
    fwrite(Templates->ClassPruners[i], sizeof(CLASS_PRUNER_STRUCT), 1, File);
1011
0
  }
1012
1013
  /* then write out each class */
1014
0
  for (unsigned i = 0; i < Templates->NumClasses; i++) {
1015
0
    Class = Templates->Class[i];
1016
1017
    /* first write out the high level struct for the class */
1018
0
    fwrite(&Class->NumProtos, sizeof(Class->NumProtos), 1, File);
1019
0
    fwrite(&Class->NumProtoSets, sizeof(Class->NumProtoSets), 1, File);
1020
0
    ASSERT_HOST(Class->NumConfigs == this->fontset_table_.at(Class->font_set_id).size());
1021
0
    fwrite(&Class->NumConfigs, sizeof(Class->NumConfigs), 1, File);
1022
0
    for (int j = 0; j < Class->NumConfigs; ++j) {
1023
0
      fwrite(&Class->ConfigLengths[j], sizeof(uint16_t), 1, File);
1024
0
    }
1025
1026
    /* then write out the proto lengths */
1027
0
    if (MaxNumIntProtosIn(Class) > 0) {
1028
0
      fwrite(&Class->ProtoLengths[0], sizeof(uint8_t), MaxNumIntProtosIn(Class), File);
1029
0
    }
1030
1031
    /* then write out the proto sets */
1032
0
    for (int j = 0; j < Class->NumProtoSets; j++) {
1033
0
      fwrite(Class->ProtoSets[j], sizeof(PROTO_SET_STRUCT), 1, File);
1034
0
    }
1035
1036
    /* then write the fonts info */
1037
0
    fwrite(&Class->font_set_id, sizeof(int), 1, File);
1038
0
  }
1039
1040
  /* Write the fonts info tables */
1041
0
  using namespace std::placeholders; // for _1, _2
1042
0
  this->fontinfo_table_.write(File, std::bind(write_info, _1, _2));
1043
0
  this->fontinfo_table_.write(File, std::bind(write_spacing_info, _1, _2));
1044
0
  this->fontset_table_.write(File, std::bind(write_set, _1, _2));
1045
0
} /* WriteIntTemplates */
1046
1047
/*-----------------------------------------------------------------------------
1048
              Private Code
1049
-----------------------------------------------------------------------------*/
1050
/**
1051
 * This routine returns the parameter value which
1052
 * corresponds to the beginning of the specified bucket.
1053
 * The bucket number should have been generated using the
1054
 * BucketFor() function with parameters Offset and NumBuckets.
1055
 * @param Bucket    bucket whose start is to be computed
1056
 * @param Offset    offset used to map params to buckets
1057
 * @param NumBuckets  total number of buckets
1058
 * @return Param value corresponding to start position of Bucket.
1059
 * @note Globals: none
1060
 */
1061
1.98k
float BucketStart(int Bucket, float Offset, int NumBuckets) {
1062
1.98k
  return static_cast<float>(Bucket) / NumBuckets - Offset;
1063
1064
1.98k
} /* BucketStart */
1065
1066
/**
1067
 * This routine returns the parameter value which
1068
 * corresponds to the end of the specified bucket.
1069
 * The bucket number should have been generated using the
1070
 * BucketFor() function with parameters Offset and NumBuckets.
1071
 * @param Bucket    bucket whose end is to be computed
1072
 * @param Offset    offset used to map params to buckets
1073
 * @param NumBuckets  total number of buckets
1074
 * @return Param value corresponding to end position of Bucket.
1075
 * @note Globals: none
1076
 */
1077
993
float BucketEnd(int Bucket, float Offset, int NumBuckets) {
1078
993
  return static_cast<float>(Bucket + 1) / NumBuckets - Offset;
1079
993
} /* BucketEnd */
1080
1081
/**
1082
 * This routine fills in the section of a class pruner
1083
 * corresponding to a single x value for a single proto of
1084
 * a class.
1085
 * @param FillSpec  specifies which bits to fill in pruner
1086
 * @param Pruner    class pruner to be filled
1087
 * @param ClassMask indicates which bits to change in each word
1088
 * @param ClassCount  indicates what to change bits to
1089
 * @param WordIndex indicates which word to change
1090
 */
1091
void DoFill(FILL_SPEC *FillSpec, CLASS_PRUNER_STRUCT *Pruner, uint32_t ClassMask,
1092
12.0k
            uint32_t ClassCount, uint32_t WordIndex) {
1093
12.0k
  int X, Y, Angle;
1094
12.0k
  uint32_t OldWord;
1095
1096
12.0k
  X = FillSpec->X;
1097
12.0k
  if (X < 0) {
1098
0
    X = 0;
1099
0
  }
1100
12.0k
  if (X >= NUM_CP_BUCKETS) {
1101
0
    X = NUM_CP_BUCKETS - 1;
1102
0
  }
1103
1104
12.0k
  if (FillSpec->YStart < 0) {
1105
11
    FillSpec->YStart = 0;
1106
11
  }
1107
12.0k
  if (FillSpec->YEnd >= NUM_CP_BUCKETS) {
1108
215
    FillSpec->YEnd = NUM_CP_BUCKETS - 1;
1109
215
  }
1110
1111
73.9k
  for (Y = FillSpec->YStart; Y <= FillSpec->YEnd; Y++) {
1112
322k
    for (Angle = FillSpec->AngleStart;; CircularIncrement(Angle, NUM_CP_BUCKETS)) {
1113
322k
      OldWord = Pruner->p[X][Y][Angle][WordIndex];
1114
322k
      if (ClassCount > (OldWord & ClassMask)) {
1115
162k
        OldWord &= ~ClassMask;
1116
162k
        OldWord |= ClassCount;
1117
162k
        Pruner->p[X][Y][Angle][WordIndex] = OldWord;
1118
162k
      }
1119
322k
      if (Angle == FillSpec->AngleEnd) {
1120
61.9k
        break;
1121
61.9k
      }
1122
322k
    }
1123
61.9k
  }
1124
12.0k
} /* DoFill */
1125
1126
/**
1127
 * Return true if the specified table filler is done, i.e.
1128
 * if it has no more lines to fill.
1129
 * @param Filler    table filler to check if done
1130
 * @return true if no more lines to fill, false otherwise.
1131
 * @note Globals: none
1132
 */
1133
14.6k
bool FillerDone(TABLE_FILLER *Filler) {
1134
14.6k
  FILL_SWITCH *Next;
1135
1136
14.6k
  Next = &(Filler->Switch[Filler->NextSwitch]);
1137
1138
14.6k
  return Filler->X > Next->X && Next->Type == SWITCH_TYPE::LastSwitch;
1139
1140
14.6k
} /* FillerDone */
1141
1142
/**
1143
 * This routine sets Bit in each bit vector whose
1144
 * bucket lies within the range Center +- Spread.  The fill
1145
 * is done for a circular dimension, i.e. bucket 0 is adjacent
1146
 * to the last bucket.  It is assumed that Center and Spread
1147
 * are expressed in a circular coordinate system whose range
1148
 * is 0 to 1.
1149
 * @param ParamTable  table of bit vectors, one per param bucket
1150
 * @param Bit bit position in vectors to be filled
1151
 * @param Center center of filled area
1152
 * @param Spread spread of filled area
1153
 * @param debug debug flag
1154
 */
1155
void FillPPCircularBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1156
11.9k
                        float Center, float Spread, bool debug) {
1157
11.9k
  int i, FirstBucket, LastBucket;
1158
1159
11.9k
  if (Spread > 0.5) {
1160
0
    Spread = 0.5;
1161
0
  }
1162
1163
11.9k
  FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1164
11.9k
  if (FirstBucket < 0) {
1165
1.47k
    FirstBucket += NUM_PP_BUCKETS;
1166
1.47k
  }
1167
1168
11.9k
  LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1169
11.9k
  if (LastBucket >= NUM_PP_BUCKETS) {
1170
268
    LastBucket -= NUM_PP_BUCKETS;
1171
268
  }
1172
11.9k
  if (debug) {
1173
0
    tprintf("Circular fill from %d to %d", FirstBucket, LastBucket);
1174
0
  }
1175
203k
  for (i = FirstBucket; true; CircularIncrement(i, NUM_PP_BUCKETS)) {
1176
203k
    SET_BIT(ParamTable[i], Bit);
1177
1178
    /* exit loop after we have set the bit for the last bucket */
1179
203k
    if (i == LastBucket) {
1180
11.9k
      break;
1181
11.9k
    }
1182
203k
  }
1183
1184
11.9k
} /* FillPPCircularBits */
1185
1186
/**
1187
 * This routine sets Bit in each bit vector whose
1188
 * bucket lies within the range Center +- Spread.  The fill
1189
 * is done for a linear dimension, i.e. there is no wrap-around
1190
 * for this dimension.  It is assumed that Center and Spread
1191
 * are expressed in a linear coordinate system whose range
1192
 * is approximately 0 to 1.  Values outside this range will
1193
 * be clipped.
1194
 * @param ParamTable table of bit vectors, one per param bucket
1195
 * @param Bit bit number being filled
1196
 * @param Center center of filled area
1197
 * @param Spread spread of filled area
1198
 * @param debug debug flag
1199
 */
1200
void FillPPLinearBits(uint32_t ParamTable[NUM_PP_BUCKETS][WERDS_PER_PP_VECTOR], int Bit,
1201
23.9k
                      float Center, float Spread, bool debug) {
1202
23.9k
  int i, FirstBucket, LastBucket;
1203
1204
23.9k
  FirstBucket = static_cast<int>(std::floor((Center - Spread) * NUM_PP_BUCKETS));
1205
23.9k
  if (FirstBucket < 0) {
1206
1.24k
    FirstBucket = 0;
1207
1.24k
  }
1208
1209
23.9k
  LastBucket = static_cast<int>(std::floor((Center + Spread) * NUM_PP_BUCKETS));
1210
23.9k
  if (LastBucket >= NUM_PP_BUCKETS) {
1211
4.14k
    LastBucket = NUM_PP_BUCKETS - 1;
1212
4.14k
  }
1213
1214
23.9k
  if (debug) {
1215
0
    tprintf("Linear fill from %d to %d", FirstBucket, LastBucket);
1216
0
  }
1217
337k
  for (i = FirstBucket; i <= LastBucket; i++) {
1218
313k
    SET_BIT(ParamTable[i], Bit);
1219
313k
  }
1220
1221
23.9k
} /* FillPPLinearBits */
1222
1223
/*---------------------------------------------------------------------------*/
1224
#ifndef GRAPHICS_DISABLED
1225
/**
1226
 * This routine prompts the user with Prompt and waits
1227
 * for the user to enter something in the debug window.
1228
 * @param Prompt prompt to print while waiting for input from window
1229
 * @param adaptive_on
1230
 * @param pretrained_on
1231
 * @param shape_id
1232
 * @return Character entered in the debug window.
1233
 * @note Globals: none
1234
 */
1235
CLASS_ID Classify::GetClassToDebug(const char *Prompt, bool *adaptive_on, bool *pretrained_on,
1236
                                   int *shape_id) {
1237
  tprintf("%s\n", Prompt);
1238
  SVEventType ev_type;
1239
  int unichar_id = INVALID_UNICHAR_ID;
1240
  // Wait until a click or popup event.
1241
  do {
1242
    auto ev = IntMatchWindow->AwaitEvent(SVET_ANY);
1243
    ev_type = ev->type;
1244
    if (ev_type == SVET_POPUP) {
1245
      if (ev->command_id == IDA_SHAPE_INDEX) {
1246
        if (shape_table_ != nullptr) {
1247
          char* endptr = nullptr;
1248
          long shape_id_long = strtol(ev->parameter.c_str(), &endptr, 10);
1249
          if (endptr == ev->parameter.c_str() || *endptr != '\0' ||
1250
              shape_id_long < INT_MIN || shape_id_long > INT_MAX) {
1251
            tesserr << "Invalid shape index: " << ev->parameter << "\n";
1252
            return INVALID_UNICHAR_ID;
1253
          }
1254
          *shape_id = static_cast<int>(shape_id_long);
1255
          *adaptive_on = false;
1256
          *pretrained_on = true;
1257
          if (*shape_id >= 0 && static_cast<unsigned>(*shape_id) < shape_table_->NumShapes()) {
1258
            int font_id;
1259
            shape_table_->GetFirstUnicharAndFont(*shape_id, &unichar_id, &font_id);
1260
            tesserr << "Shape " << *shape_id << ", first unichar=" << unichar_id
1261
                    << ", font=" << font_id << "\n";
1262
            return unichar_id;
1263
          }
1264
          tesserr << "Shape index '" << ev->parameter << "' not found in shape table\n";
1265
        } else {
1266
          tesserr << "No shape table loaded!\n";
1267
        }
1268
      } else {
1269
        if (unicharset.contains_unichar(ev->parameter.c_str())) {
1270
          unichar_id = unicharset.unichar_to_id(ev->parameter.c_str());
1271
          if (ev->command_id == IDA_ADAPTIVE) {
1272
            *adaptive_on = true;
1273
            *pretrained_on = false;
1274
            *shape_id = -1;
1275
          } else if (ev->command_id == IDA_STATIC) {
1276
            *adaptive_on = false;
1277
            *pretrained_on = true;
1278
          } else {
1279
            *adaptive_on = true;
1280
            *pretrained_on = true;
1281
          }
1282
          if (ev->command_id == IDA_ADAPTIVE || shape_table_ == nullptr) {
1283
            *shape_id = -1;
1284
            return unichar_id;
1285
          }
1286
          for (unsigned s = 0; s < shape_table_->NumShapes(); ++s) {
1287
            if (shape_table_->GetShape(s).ContainsUnichar(unichar_id)) {
1288
              tesserr << shape_table_->DebugStr(s) << "\n";
1289
            }
1290
          }
1291
        } else {
1292
          tesserr << "Char class '" << ev->parameter << "' not found in unicharset";
1293
        }
1294
      }
1295
    }
1296
  } while (ev_type != SVET_CLICK);
1297
  return 0;
1298
} /* GetClassToDebug */
1299
1300
#endif
1301
1302
/**
1303
 * This routine copies the appropriate global pad variables
1304
 * into EndPad, SidePad, and AnglePad.  This is a kludge used
1305
 * to get around the fact that global control variables cannot
1306
 * be arrays.  If the specified level is illegal, the tightest
1307
 * possible pads are returned.
1308
 * @param Level   "tightness" level to return pads for
1309
 * @param EndPad    place to put end pad for Level
1310
 * @param SidePad   place to put side pad for Level
1311
 * @param AnglePad  place to put angle pad for Level
1312
 */
1313
2.57k
void GetCPPadsForLevel(int Level, float *EndPad, float *SidePad, float *AnglePad) {
1314
2.57k
  switch (Level) {
1315
859
    case 0:
1316
859
      *EndPad = classify_cp_end_pad_loose * GetPicoFeatureLength();
1317
859
      *SidePad = classify_cp_side_pad_loose * GetPicoFeatureLength();
1318
859
      *AnglePad = classify_cp_angle_pad_loose / 360.0;
1319
859
      break;
1320
1321
859
    case 1:
1322
859
      *EndPad = classify_cp_end_pad_medium * GetPicoFeatureLength();
1323
859
      *SidePad = classify_cp_side_pad_medium * GetPicoFeatureLength();
1324
859
      *AnglePad = classify_cp_angle_pad_medium / 360.0;
1325
859
      break;
1326
1327
859
    case 2:
1328
859
      *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1329
859
      *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1330
859
      *AnglePad = classify_cp_angle_pad_tight / 360.0;
1331
859
      break;
1332
1333
0
    default:
1334
0
      *EndPad = classify_cp_end_pad_tight * GetPicoFeatureLength();
1335
0
      *SidePad = classify_cp_side_pad_tight * GetPicoFeatureLength();
1336
0
      *AnglePad = classify_cp_angle_pad_tight / 360.0;
1337
0
      break;
1338
2.57k
  }
1339
2.57k
  if (*AnglePad > 0.5) {
1340
0
    *AnglePad = 0.5;
1341
0
  }
1342
1343
2.57k
} /* GetCPPadsForLevel */
1344
1345
/**
1346
 * @param Evidence  evidence value to return color for
1347
 * @return Color which corresponds to specified Evidence value.
1348
 * @note Globals: none
1349
 */
1350
0
ScrollView::Color GetMatchColorFor(float Evidence) {
1351
0
  assert(Evidence >= 0.0);
1352
0
  assert(Evidence <= 1.0);
1353
1354
0
  if (Evidence >= 0.90) {
1355
0
    return ScrollView::WHITE;
1356
0
  } else if (Evidence >= 0.75) {
1357
0
    return ScrollView::GREEN;
1358
0
  } else if (Evidence >= 0.50) {
1359
0
    return ScrollView::RED;
1360
0
  } else {
1361
0
    return ScrollView::BLUE;
1362
0
  }
1363
0
} /* GetMatchColorFor */
1364
1365
/**
1366
 * This routine returns (in Fill) the specification of
1367
 * the next line to be filled from Filler.  FillerDone() should
1368
 * always be called before GetNextFill() to ensure that we
1369
 * do not run past the end of the fill table.
1370
 * @param Filler    filler to get next fill spec from
1371
 * @param Fill    place to put spec for next fill
1372
 */
1373
12.0k
void GetNextFill(TABLE_FILLER *Filler, FILL_SPEC *Fill) {
1374
12.0k
  FILL_SWITCH *Next;
1375
1376
  /* compute the fill assuming no switches will be encountered */
1377
12.0k
  Fill->AngleStart = Filler->AngleStart;
1378
12.0k
  Fill->AngleEnd = Filler->AngleEnd;
1379
12.0k
  Fill->X = Filler->X;
1380
12.0k
  Fill->YStart = Filler->YStart >> 8;
1381
12.0k
  Fill->YEnd = Filler->YEnd >> 8;
1382
1383
  /* update the fill info and the filler for ALL switches at this X value */
1384
12.0k
  Next = &(Filler->Switch[Filler->NextSwitch]);
1385
14.0k
  while (Filler->X >= Next->X) {
1386
4.56k
    Fill->X = Filler->X = Next->X;
1387
4.56k
    if (Next->Type == SWITCH_TYPE::StartSwitch) {
1388
993
      Fill->YStart = Next->Y;
1389
993
      Filler->StartDelta = Next->Delta;
1390
993
      Filler->YStart = Next->YInit;
1391
3.57k
    } else if (Next->Type == SWITCH_TYPE::EndSwitch) {
1392
993
      Fill->YEnd = Next->Y;
1393
993
      Filler->EndDelta = Next->Delta;
1394
993
      Filler->YEnd = Next->YInit;
1395
2.57k
    } else { /* Type must be LastSwitch */
1396
2.57k
      break;
1397
2.57k
    }
1398
1.98k
    Filler->NextSwitch++;
1399
1.98k
    Next = &(Filler->Switch[Filler->NextSwitch]);
1400
1.98k
  }
1401
1402
  /* prepare the filler for the next call to this routine */
1403
12.0k
  Filler->X++;
1404
12.0k
  Filler->YStart += Filler->StartDelta;
1405
12.0k
  Filler->YEnd += Filler->EndDelta;
1406
1407
12.0k
} /* GetNextFill */
1408
1409
/**
1410
 * This routine computes a data structure (Filler)
1411
 * which can be used to fill in a rectangle surrounding
1412
 * the specified Proto. Results are returned in Filler.
1413
 *
1414
 * @param EndPad, SidePad, AnglePad padding to add to proto
1415
 * @param Proto       proto to create a filler for
1416
 * @param Filler        place to put table filler
1417
 */
1418
void InitTableFiller(float EndPad, float SidePad, float AnglePad, PROTO_STRUCT *Proto, TABLE_FILLER *Filler)
1419
10.1k
#define XS X_SHIFT
1420
9.12k
#define YS Y_SHIFT
1421
5.15k
#define AS ANGLE_SHIFT
1422
24.3k
#define NB NUM_CP_BUCKETS
1423
2.57k
{
1424
2.57k
  float Angle;
1425
2.57k
  float X, Y, HalfLength;
1426
2.57k
  float Cos, Sin;
1427
2.57k
  float XAdjust, YAdjust;
1428
2.57k
  FPOINT Start, Switch1, Switch2, End;
1429
2.57k
  int S1 = 0;
1430
2.57k
  int S2 = 1;
1431
1432
2.57k
  Angle = Proto->Angle;
1433
2.57k
  X = Proto->X;
1434
2.57k
  Y = Proto->Y;
1435
2.57k
  HalfLength = Proto->Length / 2.0;
1436
1437
2.57k
  Filler->AngleStart = CircBucketFor(Angle - AnglePad, AS, NB);
1438
2.57k
  Filler->AngleEnd = CircBucketFor(Angle + AnglePad, AS, NB);
1439
2.57k
  Filler->NextSwitch = 0;
1440
1441
2.57k
  if (fabs(Angle - 0.0) < HV_TOLERANCE || fabs(Angle - 0.5) < HV_TOLERANCE) {
1442
    /* horizontal proto - handle as special case */
1443
588
    Filler->X = Bucket8For(X - HalfLength - EndPad, XS, NB);
1444
588
    Filler->YStart = Bucket16For(Y - SidePad, YS, NB * 256);
1445
588
    Filler->YEnd = Bucket16For(Y + SidePad, YS, NB * 256);
1446
588
    Filler->StartDelta = 0;
1447
588
    Filler->EndDelta = 0;
1448
588
    Filler->Switch[0].Type = SWITCH_TYPE::LastSwitch;
1449
588
    Filler->Switch[0].X = Bucket8For(X + HalfLength + EndPad, XS, NB);
1450
1.98k
  } else if (fabs(Angle - 0.25) < HV_TOLERANCE || fabs(Angle - 0.75) < HV_TOLERANCE) {
1451
    /* vertical proto - handle as special case */
1452
996
    Filler->X = Bucket8For(X - SidePad, XS, NB);
1453
996
    Filler->YStart = Bucket16For(Y - HalfLength - EndPad, YS, NB * 256);
1454
996
    Filler->YEnd = Bucket16For(Y + HalfLength + EndPad, YS, NB * 256);
1455
996
    Filler->StartDelta = 0;
1456
996
    Filler->EndDelta = 0;
1457
996
    Filler->Switch[0].Type = SWITCH_TYPE::LastSwitch;
1458
996
    Filler->Switch[0].X = Bucket8For(X + SidePad, XS, NB);
1459
996
  } else {
1460
    /* diagonal proto */
1461
1462
993
    if ((Angle > 0.0 && Angle < 0.25) || (Angle > 0.5 && Angle < 0.75)) {
1463
      /* rising diagonal proto */
1464
468
      Angle *= 2.0 * M_PI;
1465
468
      Cos = fabs(std::cos(Angle));
1466
468
      Sin = fabs(std::sin(Angle));
1467
1468
      /* compute the positions of the corners of the acceptance region */
1469
468
      Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1470
468
      Start.y = Y - (HalfLength + EndPad) * Sin + SidePad * Cos;
1471
468
      End.x = 2.0 * X - Start.x;
1472
468
      End.y = 2.0 * Y - Start.y;
1473
468
      Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1474
468
      Switch1.y = Y - (HalfLength + EndPad) * Sin - SidePad * Cos;
1475
468
      Switch2.x = 2.0 * X - Switch1.x;
1476
468
      Switch2.y = 2.0 * Y - Switch1.y;
1477
1478
468
      if (Switch1.x > Switch2.x) {
1479
246
        S1 = 1;
1480
246
        S2 = 0;
1481
246
      }
1482
1483
      /* translate into bucket positions and deltas */
1484
468
      Filler->X = Bucket8For(Start.x, XS, NB);
1485
468
      Filler->StartDelta = -static_cast<int16_t>((Cos / Sin) * 256);
1486
468
      Filler->EndDelta = static_cast<int16_t>((Sin / Cos) * 256);
1487
1488
468
      XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1489
468
      YAdjust = XAdjust * Cos / Sin;
1490
468
      Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1491
468
      YAdjust = XAdjust * Sin / Cos;
1492
468
      Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1493
1494
468
      Filler->Switch[S1].Type = SWITCH_TYPE::StartSwitch;
1495
468
      Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1496
468
      Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1497
468
      XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1498
468
      YAdjust = XAdjust * Sin / Cos;
1499
468
      Filler->Switch[S1].YInit = Bucket16For(Switch1.y - YAdjust, YS, NB * 256);
1500
468
      Filler->Switch[S1].Delta = Filler->EndDelta;
1501
1502
468
      Filler->Switch[S2].Type = SWITCH_TYPE::EndSwitch;
1503
468
      Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1504
468
      Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1505
468
      XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1506
468
      YAdjust = XAdjust * Cos / Sin;
1507
468
      Filler->Switch[S2].YInit = Bucket16For(Switch2.y + YAdjust, YS, NB * 256);
1508
468
      Filler->Switch[S2].Delta = Filler->StartDelta;
1509
1510
468
      Filler->Switch[2].Type = SWITCH_TYPE::LastSwitch;
1511
468
      Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1512
525
    } else {
1513
      /* falling diagonal proto */
1514
525
      Angle *= 2.0 * M_PI;
1515
525
      Cos = fabs(std::cos(Angle));
1516
525
      Sin = fabs(std::sin(Angle));
1517
1518
      /* compute the positions of the corners of the acceptance region */
1519
525
      Start.x = X - (HalfLength + EndPad) * Cos - SidePad * Sin;
1520
525
      Start.y = Y + (HalfLength + EndPad) * Sin - SidePad * Cos;
1521
525
      End.x = 2.0 * X - Start.x;
1522
525
      End.y = 2.0 * Y - Start.y;
1523
525
      Switch1.x = X - (HalfLength + EndPad) * Cos + SidePad * Sin;
1524
525
      Switch1.y = Y + (HalfLength + EndPad) * Sin + SidePad * Cos;
1525
525
      Switch2.x = 2.0 * X - Switch1.x;
1526
525
      Switch2.y = 2.0 * Y - Switch1.y;
1527
1528
525
      if (Switch1.x > Switch2.x) {
1529
334
        S1 = 1;
1530
334
        S2 = 0;
1531
334
      }
1532
1533
      /* translate into bucket positions and deltas */
1534
525
      Filler->X = Bucket8For(Start.x, XS, NB);
1535
525
      Filler->StartDelta = static_cast<int16_t>(
1536
525
          ClipToRange<int>(-IntCastRounded((Sin / Cos) * 256), INT16_MIN, INT16_MAX));
1537
525
      Filler->EndDelta = static_cast<int16_t>(
1538
525
          ClipToRange<int>(IntCastRounded((Cos / Sin) * 256), INT16_MIN, INT16_MAX));
1539
1540
525
      XAdjust = BucketEnd(Filler->X, XS, NB) - Start.x;
1541
525
      YAdjust = XAdjust * Sin / Cos;
1542
525
      Filler->YStart = Bucket16For(Start.y - YAdjust, YS, NB * 256);
1543
525
      YAdjust = XAdjust * Cos / Sin;
1544
525
      Filler->YEnd = Bucket16For(Start.y + YAdjust, YS, NB * 256);
1545
1546
525
      Filler->Switch[S1].Type = SWITCH_TYPE::EndSwitch;
1547
525
      Filler->Switch[S1].X = Bucket8For(Switch1.x, XS, NB);
1548
525
      Filler->Switch[S1].Y = Bucket8For(Switch1.y, YS, NB);
1549
525
      XAdjust = Switch1.x - BucketStart(Filler->Switch[S1].X, XS, NB);
1550
525
      YAdjust = XAdjust * Sin / Cos;
1551
525
      Filler->Switch[S1].YInit = Bucket16For(Switch1.y + YAdjust, YS, NB * 256);
1552
525
      Filler->Switch[S1].Delta = Filler->StartDelta;
1553
1554
525
      Filler->Switch[S2].Type = SWITCH_TYPE::StartSwitch;
1555
525
      Filler->Switch[S2].X = Bucket8For(Switch2.x, XS, NB);
1556
525
      Filler->Switch[S2].Y = Bucket8For(Switch2.y, YS, NB);
1557
525
      XAdjust = Switch2.x - BucketStart(Filler->Switch[S2].X, XS, NB);
1558
525
      YAdjust = XAdjust * Cos / Sin;
1559
525
      Filler->Switch[S2].YInit = Bucket16For(Switch2.y - YAdjust, YS, NB * 256);
1560
525
      Filler->Switch[S2].Delta = Filler->EndDelta;
1561
1562
525
      Filler->Switch[2].Type = SWITCH_TYPE::LastSwitch;
1563
525
      Filler->Switch[2].X = Bucket8For(End.x, XS, NB);
1564
525
    }
1565
993
  }
1566
2.57k
} /* InitTableFiller */
1567
1568
/*---------------------------------------------------------------------------*/
1569
#ifndef GRAPHICS_DISABLED
1570
/**
1571
 * This routine renders the specified feature into ShapeList.
1572
 * @param window to add feature rendering to
1573
 * @param Feature feature to be rendered
1574
 * @param color color to use for feature rendering
1575
 * @return New shape list with rendering of Feature added.
1576
 * @note Globals: none
1577
 */
1578
void RenderIntFeature(ScrollView *window, const INT_FEATURE_STRUCT *Feature,
1579
                      ScrollView::Color color) {
1580
  float X, Y, Dx, Dy, Length;
1581
1582
  window->Pen(color);
1583
  assert(Feature != nullptr);
1584
  assert(color != 0);
1585
1586
  X = Feature->X;
1587
  Y = Feature->Y;
1588
  Length = GetPicoFeatureLength() * 0.7 * INT_CHAR_NORM_RANGE;
1589
  // The -PI has no significant effect here, but the value of Theta is computed
1590
  // using BinaryAnglePlusPi in intfx.cpp.
1591
  Dx = (Length / 2.0) * cos((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
1592
  Dy = (Length / 2.0) * sin((Feature->Theta / 256.0) * 2.0 * M_PI - M_PI);
1593
1594
  window->SetCursor(X, Y);
1595
  window->DrawTo(X + Dx, Y + Dy);
1596
} /* RenderIntFeature */
1597
1598
/**
1599
 * This routine extracts the parameters of the specified
1600
 * proto from the class description and adds a rendering of
1601
 * the proto onto the ShapeList.
1602
 *
1603
 * @param window ScrollView instance
1604
 * @param Class class that proto is contained in
1605
 * @param ProtoId id of proto to be rendered
1606
 * @param color color to render proto in
1607
 *
1608
 * Globals: none
1609
 *
1610
 * @return New shape list with a rendering of one proto added.
1611
 */
1612
void RenderIntProto(ScrollView *window, INT_CLASS_STRUCT *Class, PROTO_ID ProtoId,
1613
                    ScrollView::Color color) {
1614
  INT_PROTO_STRUCT *Proto;
1615
  int ProtoSetIndex;
1616
  int ProtoWordIndex;
1617
  float Length;
1618
  int Xmin, Xmax, Ymin, Ymax;
1619
  float X, Y, Dx, Dy;
1620
  uint32_t ProtoMask;
1621
  int Bucket;
1622
1623
  assert(ProtoId >= 0);
1624
  assert(Class != nullptr);
1625
  assert(ProtoId < Class->NumProtos);
1626
  assert(color != 0);
1627
  window->Pen(color);
1628
1629
  auto ProtoSet = Class->ProtoSets[SetForProto(ProtoId)];
1630
  ProtoSetIndex = IndexForProto(ProtoId);
1631
  Proto = &(ProtoSet->Protos[ProtoSetIndex]);
1632
  Length = (Class->ProtoLengths[ProtoId] * GetPicoFeatureLength() * INT_CHAR_NORM_RANGE);
1633
  ProtoMask = PPrunerMaskFor(ProtoId);
1634
  ProtoWordIndex = PPrunerWordIndexFor(ProtoId);
1635
1636
  // find the x and y extent of the proto from the proto pruning table
1637
  Xmin = Ymin = NUM_PP_BUCKETS;
1638
  Xmax = Ymax = 0;
1639
  for (Bucket = 0; Bucket < NUM_PP_BUCKETS; Bucket++) {
1640
    if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_X][Bucket][ProtoWordIndex]) {
1641
      UpdateRange(Bucket, &Xmin, &Xmax);
1642
    }
1643
1644
    if (ProtoMask & ProtoSet->ProtoPruner[PRUNER_Y][Bucket][ProtoWordIndex]) {
1645
      UpdateRange(Bucket, &Ymin, &Ymax);
1646
    }
1647
  }
1648
  X = (Xmin + Xmax + 1) / 2.0 * PROTO_PRUNER_SCALE;
1649
  Y = (Ymin + Ymax + 1) / 2.0 * PROTO_PRUNER_SCALE;
1650
  // The -PI has no significant effect here, but the value of Theta is computed
1651
  // using BinaryAnglePlusPi in intfx.cpp.
1652
  Dx = (Length / 2.0) * cos((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
1653
  Dy = (Length / 2.0) * sin((Proto->Angle / 256.0) * 2.0 * M_PI - M_PI);
1654
1655
  window->SetCursor(X - Dx, Y - Dy);
1656
  window->DrawTo(X + Dx, Y + Dy);
1657
} /* RenderIntProto */
1658
#endif
1659
1660
#ifndef GRAPHICS_DISABLED
1661
/**
1662
 * Initializes the int matcher window if it is not already
1663
 * initialized.
1664
 */
1665
void InitIntMatchWindowIfReqd() {
1666
  if (IntMatchWindow == nullptr) {
1667
    IntMatchWindow = CreateFeatureSpaceWindow("IntMatchWindow", 50, 200);
1668
    auto *popup_menu = new SVMenuNode();
1669
1670
    popup_menu->AddChild("Debug Adapted classes", IDA_ADAPTIVE, "x", "Class to debug");
1671
    popup_menu->AddChild("Debug Static classes", IDA_STATIC, "x", "Class to debug");
1672
    popup_menu->AddChild("Debug Both", IDA_BOTH, "x", "Class to debug");
1673
    popup_menu->AddChild("Debug Shape Index", IDA_SHAPE_INDEX, "0", "Index to debug");
1674
    popup_menu->BuildMenu(IntMatchWindow, false);
1675
  }
1676
}
1677
1678
/**
1679
 * Initializes the proto display window if it is not already
1680
 * initialized.
1681
 */
1682
void InitProtoDisplayWindowIfReqd() {
1683
  if (ProtoDisplayWindow == nullptr) {
1684
    ProtoDisplayWindow = CreateFeatureSpaceWindow("ProtoDisplayWindow", 550, 200);
1685
  }
1686
}
1687
1688
/**
1689
 * Initializes the feature display window if it is not already
1690
 * initialized.
1691
 */
1692
void InitFeatureDisplayWindowIfReqd() {
1693
  if (FeatureDisplayWindow == nullptr) {
1694
    FeatureDisplayWindow = CreateFeatureSpaceWindow("FeatureDisplayWindow", 50, 700);
1695
  }
1696
}
1697
1698
/// Creates a window of the appropriate size for displaying elements
1699
/// in feature space.
1700
ScrollView *CreateFeatureSpaceWindow(const char *name, int xpos, int ypos) {
1701
  return new ScrollView(name, xpos, ypos, 520, 520, 260, 260, true);
1702
}
1703
#endif // !GRAPHICS_DISABLED
1704
1705
} // namespace tesseract