@luii/node-tesseract-ocr 2.1.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/commands.hpp CHANGED
@@ -18,14 +18,21 @@
18
18
 
19
19
  #include "monitor.hpp"
20
20
  #include "utils.hpp"
21
+ #include <allheaders.h>
22
+ #include <atomic>
23
+ #include <cstddef>
24
+ #include <cstdint>
25
+ #include <exception>
26
+ #include <iostream>
21
27
  #include <memory>
22
28
  #include <napi.h>
23
29
  #include <optional>
24
- #include <stdexcept>
30
+ #include <ostream>
25
31
  #include <string>
26
32
  #include <tesseract/baseapi.h>
27
33
  #include <tesseract/ocrclass.h>
28
34
  #include <tesseract/publictypes.h>
35
+ #include <tesseract/renderer.h>
29
36
  #include <unordered_map>
30
37
  #include <variant>
31
38
  #include <vector>
@@ -52,20 +59,27 @@ struct ResultString {
52
59
  std::string value;
53
60
  };
54
61
 
62
+ struct ResultBuffer {
63
+ std::vector<uint8_t> value;
64
+ };
65
+
55
66
  using ObjectValue = std::variant<bool, int, double, float, std::string,
56
- std::vector<std::string>>;
67
+ std::vector<std::string>, std::vector<uint8_t>,
68
+ std::vector<int>>;
57
69
 
58
70
  struct ResultObject {
59
71
  std::unordered_map<std::string, ObjectValue> value;
60
72
  };
61
73
 
74
+ using ArrayValue = std::variant<std::vector<int>, std::vector<std::string>>;
75
+
62
76
  struct ResultArray {
63
- std::vector<std::string> value;
77
+ ArrayValue value;
64
78
  };
65
79
 
66
80
  using Result =
67
81
  std::variant<ResultVoid, ResultBool, ResultInt, ResultDouble, ResultFloat,
68
- ResultString, ResultArray, ResultObject>;
82
+ ResultString, ResultArray, ResultBuffer, ResultObject>;
69
83
 
70
84
  template <class... Ts> struct match : Ts... {
71
85
  using Ts::operator()...;
@@ -73,6 +87,15 @@ template <class... Ts> struct match : Ts... {
73
87
 
74
88
  template <class... Ts> match(Ts...) -> match<Ts...>;
75
89
 
90
+ template <typename T>
91
+ static Napi::Array VectorToNapiArray(Napi::Env env, const std::vector<T> &vec) {
92
+ Napi::Array arr = Napi::Array::New(env, vec.size());
93
+ for (size_t i = 0; i < vec.size(); ++i) {
94
+ arr.Set(static_cast<uint32_t>(i), vec[i]);
95
+ }
96
+ return arr;
97
+ }
98
+
76
99
  static Napi::Value ToNapiValue(Napi::Env env, const ObjectValue &v) {
77
100
  return std::visit(
78
101
  match{
@@ -80,15 +103,18 @@ static Napi::Value ToNapiValue(Napi::Env env, const ObjectValue &v) {
80
103
  [&](int i) -> Napi::Value { return Napi::Number::New(env, i); },
81
104
  [&](double d) -> Napi::Value { return Napi::Number::New(env, d); },
82
105
  [&](float f) -> Napi::Value { return Napi::Number::New(env, f); },
83
- [&](const std::string &s) -> Napi::Value {
106
+ [&](const std::string &s) -> Napi::Value { // String
84
107
  return Napi::String::New(env, s);
85
108
  },
86
- [&](const std::vector<std::string> &vec) -> Napi::Value {
87
- Napi::Array arr = Napi::Array::New(env, vec.size());
88
- for (size_t i = 0; i < vec.size(); ++i) {
89
- arr.Set(static_cast<uint32_t>(i), vec[i]);
90
- }
91
- return arr;
109
+ [&](const std::vector<uint8_t> &vec) -> Napi::Value { // Buffer
110
+ return Napi::Buffer<uint8_t>::Copy(env, vec.data(), vec.size());
111
+ },
112
+ [&](const std::vector<int> &vec) -> Napi::Value {
113
+ return VectorToNapiArray(env, vec);
114
+ },
115
+ [&](const std::vector<std::string> &vec)
116
+ -> Napi::Value { // string array
117
+ return VectorToNapiArray(env, vec);
92
118
  },
93
119
  },
94
120
  v);
@@ -112,19 +138,16 @@ inline Napi::Value MatchResult(Napi::Env env, const Result &r) {
112
138
  [&](const ResultString &v) -> Napi::Value {
113
139
  return Napi::String::New(env, v.value);
114
140
  },
141
+ [&](const ResultBuffer &v) -> Napi::Value {
142
+ return Napi::Buffer<uint8_t>::Copy(env, v.value.data(),
143
+ v.value.size());
144
+ },
115
145
  [&](const ResultArray &v) -> Napi::Value {
116
- const size_t n = v.value.size();
117
-
118
- if (n > std::numeric_limits<uint32_t>::max()) {
119
- // return Napi::RangeError::New(
120
- // env, "ResultArray too large for JS array");
121
- }
122
-
123
- Napi::Array array = Napi::Array::New(env, v.value.size());
124
- for (size_t i = 0; i < v.value.size(); i++) {
125
- array.Set(static_cast<uint32_t>(i), v.value[i]);
126
- }
127
- return array;
146
+ return std::visit(
147
+ [&](const auto &vec) -> Napi::Value {
148
+ return VectorToNapiArray(env, vec);
149
+ },
150
+ v.value);
128
151
  },
129
152
  [&](const ResultObject &v) -> Napi::Value {
130
153
  Napi::Object obj = Napi::Object::New(env);
@@ -137,6 +160,179 @@ inline Napi::Value MatchResult(Napi::Env env, const Result &r) {
137
160
  r);
138
161
  }
139
162
 
163
+ inline void RequireInitialized(const std::atomic<bool> &initialized,
164
+ const char *method) {
165
+ if (!initialized.load(std::memory_order_acquire)) {
166
+ throw_runtime("{}: call init(...) first", method);
167
+ }
168
+ }
169
+
170
+ struct CommandVersion {
171
+ Result invoke(tesseract::TessBaseAPI &api) const {
172
+ return ResultString{api.Version()};
173
+ }
174
+ };
175
+
176
+ struct CommandIsInitialized {
177
+ Result invoke(tesseract::TessBaseAPI &,
178
+ const std::atomic<bool> &initialized) const {
179
+ return ResultBool{initialized.load(std::memory_order_acquire)};
180
+ }
181
+ };
182
+
183
+ struct CommandSetInputName {
184
+ std::string input_name;
185
+ Result invoke(tesseract::TessBaseAPI &api) const {
186
+ api.SetInputName(input_name.c_str());
187
+ return ResultVoid{};
188
+ }
189
+ };
190
+
191
+ struct CommandGetInputName {
192
+ Result invoke(tesseract::TessBaseAPI &api) const {
193
+ return ResultString{api.GetInputName()};
194
+ }
195
+ };
196
+
197
+ struct CommandSetInputImage {
198
+ std::vector<uint8_t> bytes;
199
+ Result invoke(tesseract::TessBaseAPI &api,
200
+ const std::atomic<bool> &initialized) const {
201
+ RequireInitialized(initialized, "setInputImage");
202
+ if (bytes.size() == 0) {
203
+ throw_runtime("setInputImage: input buffer is empty");
204
+ }
205
+
206
+ Pix *pix = pixReadMem(bytes.data(), bytes.size());
207
+ if (pix == nullptr) {
208
+ throw_runtime("setInputImage: failed to decode image buffer");
209
+ }
210
+
211
+ // TessBaseAPI::SetInputImage takes ownership of pix.
212
+ api.SetInputImage(pix);
213
+ return ResultVoid{};
214
+ }
215
+ };
216
+
217
+ struct CommandGetInputImage {
218
+ Result invoke(tesseract::TessBaseAPI &api,
219
+ const std::atomic<bool> &initialized) const {
220
+ RequireInitialized(initialized, "getInputImage");
221
+ Pix *source = api.GetInputImage();
222
+
223
+ std::cout << source << std::endl;
224
+
225
+ if (source == nullptr) {
226
+ throw_runtime("getInputImage: TessBaseAPI::GetInputImage returned null");
227
+ }
228
+
229
+ // GetInputImage has no caller-ownership contract; work on a clone.
230
+ Pix *pix = pixClone(source);
231
+ if (pix == nullptr) {
232
+ throw_runtime("getInputImage: failed to clone source image");
233
+ }
234
+
235
+ l_uint32 *data = pixGetData(pix);
236
+ l_int32 wpl = pixGetWpl(pix);
237
+ l_int32 h = pixGetHeight(pix);
238
+
239
+ size_t bytecount = wpl * 4 * h;
240
+ const uint8_t *start = reinterpret_cast<const uint8_t *>(data);
241
+ std::vector<uint8_t> buffer(start, start + bytecount);
242
+ pixDestroy(&pix);
243
+
244
+ return ResultBuffer{buffer};
245
+ }
246
+ };
247
+
248
+ struct CommandGetSourceYResolution {
249
+ Result invoke(tesseract::TessBaseAPI &api,
250
+ const std::atomic<bool> &initialized) const {
251
+ RequireInitialized(initialized, "getSourceYResolution");
252
+ int source_y_resolution = api.GetSourceYResolution();
253
+ return ResultInt{source_y_resolution};
254
+ }
255
+ };
256
+
257
+ struct CommandGetDataPath {
258
+ Result invoke(tesseract::TessBaseAPI &api,
259
+ const std::atomic<bool> &initialized) const {
260
+ RequireInitialized(initialized, "getDataPath");
261
+ const char *data_path = api.GetDatapath();
262
+
263
+ if (data_path == nullptr) {
264
+ throw_runtime("getDataPath: TessBaseAPI::GetDatapath returned null");
265
+ }
266
+
267
+ return ResultString{data_path};
268
+ }
269
+ };
270
+
271
+ struct CommandSetOutputName {
272
+ std::string output_name;
273
+ Result invoke(tesseract::TessBaseAPI &api,
274
+ const std::atomic<bool> &initialized) const {
275
+ RequireInitialized(initialized, "setOutputName");
276
+ if (output_name.empty()) {
277
+ throw_runtime("setOutputName: output name is empty");
278
+ }
279
+
280
+ api.SetOutputName(output_name.c_str());
281
+ return ResultVoid{};
282
+ }
283
+ };
284
+
285
+ struct CommandClearPersistentCache {
286
+ Result invoke(tesseract::TessBaseAPI &api,
287
+ const std::atomic<bool> &initialized) const {
288
+ RequireInitialized(initialized, "clearPersistentCache");
289
+ api.ClearPersistentCache();
290
+ return ResultVoid{};
291
+ }
292
+ };
293
+
294
+ struct CommandClearAdaptiveClassifier {
295
+ Result invoke(tesseract::TessBaseAPI &api,
296
+ const std::atomic<bool> &initialized) const {
297
+ RequireInitialized(initialized, "clearAdaptiveClassifier");
298
+ api.ClearAdaptiveClassifier();
299
+ return ResultVoid{};
300
+ }
301
+ };
302
+
303
+ struct CommandGetThresholdedImage {
304
+ Result invoke(tesseract::TessBaseAPI &api,
305
+ const std::atomic<bool> &initialized) const {
306
+ RequireInitialized(initialized, "getThresholdedImage");
307
+ Pix *pix = api.GetThresholdedImage();
308
+
309
+ if (pix == nullptr) {
310
+ throw_runtime("getThresholdedImage: TessBaseAPI::GetThresholdedImage "
311
+ "returned null");
312
+ }
313
+
314
+ l_uint32 *data = pixGetData(pix);
315
+ l_int32 wpl = pixGetWpl(pix);
316
+ l_int32 h = pixGetHeight(pix);
317
+
318
+ size_t bytecount = wpl * 4 * h;
319
+ const uint8_t *start = reinterpret_cast<const uint8_t *>(data);
320
+ std::vector<uint8_t> buffer(start, start + bytecount);
321
+ pixDestroy(&pix);
322
+
323
+ return ResultBuffer{buffer};
324
+ }
325
+ };
326
+
327
+ struct CommandGetThresholdedImageScaleFactor {
328
+ Result invoke(tesseract::TessBaseAPI &api,
329
+ const std::atomic<bool> &initialized) const {
330
+ RequireInitialized(initialized, "getThresholdedImageScaleFactor");
331
+ int scale_factor = api.GetThresholdedImageScaleFactor();
332
+ return ResultInt{scale_factor};
333
+ }
334
+ };
335
+
140
336
  struct CommandInit {
141
337
  std::string data_path, language;
142
338
  tesseract::OcrEngineMode oem{tesseract::OEM_DEFAULT};
@@ -146,15 +342,17 @@ struct CommandInit {
146
342
  std::vector<std::string> vars_values;
147
343
  bool set_only_non_debug_params{false};
148
344
 
149
- Result invoke(tesseract::TessBaseAPI &api) const {
345
+ Result invoke(tesseract::TessBaseAPI &api,
346
+ std::atomic<bool> &initialized) const {
150
347
  const std::vector<std::string> *vv = vars_vec.empty() ? nullptr : &vars_vec;
151
348
  const std::vector<std::string> *vval =
152
349
  vars_values.empty() ? nullptr : &vars_values;
153
350
 
154
351
  if ((vv == nullptr) != (vval == nullptr) ||
155
352
  (vv && vv->size() != vval->size())) {
156
- throw std::runtime_error(
157
- "vars_vec and vars_values must both be set and same length");
353
+ throw_runtime(
354
+ "init: vars_vec and vars_values must either both be empty or have "
355
+ "the same length");
158
356
  }
159
357
 
160
358
  if (api.Init(data_path.empty() ? nullptr : data_path.c_str(),
@@ -163,15 +361,18 @@ struct CommandInit {
163
361
  : const_cast<char **>(configs.data()),
164
362
  static_cast<int>(configs.size()), vv, vval,
165
363
  set_only_non_debug_params) != 0) {
166
- throw std::runtime_error("tesseract::TessBaseAPI::Init failed");
364
+ throw_runtime("init: TessBaseAPI::Init returned non-zero status");
167
365
  }
168
366
 
367
+ initialized.store(true, std::memory_order_release);
169
368
  return ResultVoid{};
170
369
  }
171
370
  };
172
371
 
173
372
  struct CommandInitForAnalysePage {
174
- Result invoke(tesseract::TessBaseAPI &api) const {
373
+ Result invoke(tesseract::TessBaseAPI &api,
374
+ const std::atomic<bool> &initialized) const {
375
+ RequireInitialized(initialized, "initForAnalysePage");
175
376
  api.InitForAnalysePage();
176
377
  return ResultVoid{};
177
378
  }
@@ -179,13 +380,15 @@ struct CommandInitForAnalysePage {
179
380
 
180
381
  struct CommandAnalyseLayout {
181
382
  bool merge_similar_words = false;
182
- Result invoke(tesseract::TessBaseAPI &api) const {
383
+ Result invoke(tesseract::TessBaseAPI &api,
384
+ const std::atomic<bool> &initialized) const {
385
+ RequireInitialized(initialized, "analyseLayout");
183
386
 
184
387
  tesseract::PageIterator *p_iter = api.AnalyseLayout(merge_similar_words);
185
388
 
186
389
  // returns nullptr on error or empty page
187
390
  if (p_iter == nullptr) {
188
- throw std::runtime_error("tesseract::TessBaseAPI::AnalyseLayout failed");
391
+ throw_runtime("analyseLayout: TessBaseAPI::AnalyseLayout returned null");
189
392
  }
190
393
 
191
394
  // Convert PageIterator to a feasible object here
@@ -194,21 +397,273 @@ struct CommandAnalyseLayout {
194
397
  }
195
398
  };
196
399
 
400
+ struct EncodedImageBuffer {
401
+ std::vector<uint8_t> bytes;
402
+ };
403
+
404
+ struct ProcessPagesSession {
405
+ std::unique_ptr<tesseract::TessPDFRenderer> renderer;
406
+ std::string output_base;
407
+ int timeout_millisec{0};
408
+ bool textonly{false};
409
+ int next_page_index{0};
410
+ };
411
+
412
+ struct CommandBeginProcessPages {
413
+ std::string output_base;
414
+ std::string title;
415
+ int timeout_millisec{0}; // 0 = unlimited timeout
416
+ bool textonly{false};
417
+ Result invoke(tesseract::TessBaseAPI &api,
418
+ std::optional<ProcessPagesSession> &session,
419
+ const std::atomic<bool> &initialized) const {
420
+ RequireInitialized(initialized, "beginProcessPages");
421
+ if (session.has_value()) {
422
+ throw_runtime(
423
+ "beginProcessPages called while a session is already active");
424
+ }
425
+ if (title.empty()) {
426
+ throw_runtime("beginProcessPages: title cannot be empty");
427
+ }
428
+
429
+ const char *input_name = api.GetInputName();
430
+ std::string effective_output_base = output_base;
431
+ if (effective_output_base.empty()) {
432
+ if (input_name == nullptr || *input_name == '\0') {
433
+ throw_runtime("beginProcessPages: output_base is empty and "
434
+ "TessBaseAPI::GetInputName() returned null/empty");
435
+ }
436
+ effective_output_base = input_name;
437
+ }
438
+
439
+ auto renderer = std::make_unique<tesseract::TessPDFRenderer>(
440
+ effective_output_base.c_str(), api.GetDatapath(), textonly);
441
+ if (!renderer->happy()) {
442
+ throw_runtime("beginProcessPages: renderer is not healthy");
443
+ }
444
+ if (!renderer->BeginDocument(title.c_str())) {
445
+ throw_runtime("beginProcessPages: could not begin document");
446
+ }
447
+
448
+ session.emplace();
449
+ session->renderer = std::move(renderer);
450
+ session->output_base = std::move(effective_output_base);
451
+ session->timeout_millisec = timeout_millisec;
452
+ session->textonly = textonly;
453
+ session->next_page_index = 0;
454
+ return ResultVoid{};
455
+ }
456
+ };
457
+
458
+ struct CommandAddProcessPage {
459
+ EncodedImageBuffer page;
460
+ std::string filename;
461
+ std::shared_ptr<MonitorContext> monitor_context;
462
+ Result invoke(tesseract::TessBaseAPI &api,
463
+ std::optional<ProcessPagesSession> &session,
464
+ const std::atomic<bool> &initialized) const {
465
+ RequireInitialized(initialized, "addProcessPage");
466
+ if (!session.has_value()) {
467
+ throw_runtime("addProcessPage: called without an active session");
468
+ }
469
+ if (!session->renderer->happy()) {
470
+ throw_runtime("addProcessPage: renderer is not healthy");
471
+ }
472
+ if (page.bytes.empty()) {
473
+ throw_runtime("addProcessPage: buffer is empty");
474
+ }
475
+
476
+ Pix *pix = pixReadMem(page.bytes.data(), page.bytes.size());
477
+ if (pix == nullptr) {
478
+ throw_runtime("addProcessPage: failed to decode image buffer");
479
+ }
480
+
481
+ if (pixGetColormap(pix) != nullptr) {
482
+ Pix *no_cmap = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
483
+ if (no_cmap == nullptr) {
484
+ pixDestroy(&pix);
485
+ throw_runtime("addProcessPage: failed to remove image colormap");
486
+ }
487
+ if (no_cmap != pix) {
488
+ pixDestroy(&pix);
489
+ pix = no_cmap;
490
+ }
491
+ }
492
+
493
+ if (pixGetSpp(pix) == 4) {
494
+ Pix *no_alpha = pixRemoveAlpha(pix);
495
+ if (no_alpha == nullptr) {
496
+ pixDestroy(&pix);
497
+ throw_runtime("addProcessPage: failed to remove alpha channel");
498
+ }
499
+ if (no_alpha != pix) {
500
+ pixDestroy(&pix);
501
+ pix = no_alpha;
502
+ }
503
+ }
504
+
505
+ const int depth = pixGetDepth(pix);
506
+ if (depth > 0 && depth < 8) {
507
+ Pix *normalized = pixConvertTo8(pix, false);
508
+ if (normalized == nullptr) {
509
+ pixDestroy(&pix);
510
+ throw_runtime(
511
+ "addProcessPage: failed to normalize low-bit-depth image");
512
+ }
513
+ if (normalized != pix) {
514
+ pixDestroy(&pix);
515
+ pix = normalized;
516
+ }
517
+ }
518
+
519
+ const int x_res = pixGetXRes(pix);
520
+ const int y_res = pixGetYRes(pix);
521
+ if (x_res <= 0 || y_res <= 0) {
522
+ pixSetResolution(pix, 300, 300);
523
+ }
524
+
525
+ const char *effective_filename =
526
+ filename.empty() ? nullptr : filename.c_str();
527
+ api.SetInputName(effective_filename);
528
+ api.SetImage(pix);
529
+
530
+ bool failed = false;
531
+ MonitorHandle handle{monitor_context};
532
+ auto *monitor = monitor_context ? &handle.monitor : nullptr;
533
+
534
+ if (session->timeout_millisec > 0) {
535
+ tesseract::ETEXT_DESC timeout_only_monitor{};
536
+ if (monitor != nullptr) {
537
+ monitor->set_deadline_msecs(session->timeout_millisec);
538
+ } else {
539
+ timeout_only_monitor.cancel = nullptr;
540
+ timeout_only_monitor.cancel_this = nullptr;
541
+ timeout_only_monitor.set_deadline_msecs(session->timeout_millisec);
542
+ monitor = &timeout_only_monitor;
543
+ }
544
+ failed = api.Recognize(monitor) < 0;
545
+ } else if (api.GetPageSegMode() == tesseract::PSM_OSD_ONLY ||
546
+ api.GetPageSegMode() == tesseract::PSM_AUTO_ONLY) {
547
+ tesseract::PageIterator *it = api.AnalyseLayout();
548
+ if (it == nullptr) {
549
+ failed = true;
550
+ } else {
551
+ delete it;
552
+ }
553
+ } else {
554
+ failed = api.Recognize(monitor) < 0;
555
+ }
556
+
557
+ if (session->renderer && !failed) {
558
+ failed = !session->renderer->AddImage(&api);
559
+ }
560
+ pixDestroy(&pix);
561
+
562
+ if (!failed) {
563
+ session->next_page_index++;
564
+ return ResultVoid{};
565
+ }
566
+
567
+ throw_runtime("addProcessPage: ProcessPage failed at page {}",
568
+ session->next_page_index);
569
+ return ResultVoid{};
570
+ }
571
+ };
572
+
573
+ struct CommandFinishProcessPages {
574
+ Result invoke(tesseract::TessBaseAPI &,
575
+ std::optional<ProcessPagesSession> &session,
576
+ const std::atomic<bool> &initialized) const {
577
+ RequireInitialized(initialized, "finishProcessPages");
578
+ if (!session.has_value()) {
579
+ throw_runtime("finishProcessPages: called without an active session");
580
+ }
581
+ if (!session->renderer->happy()) {
582
+ throw_runtime("finishProcessPages: renderer is not healthy");
583
+ }
584
+ if (!session->renderer->EndDocument()) {
585
+ throw_runtime("finishProcessPages: could not finalize document");
586
+ }
587
+
588
+ std::string output_filepath = session->output_base + ".pdf";
589
+ session.reset();
590
+ return ResultString{std::move(output_filepath)};
591
+ }
592
+ };
593
+
594
+ struct CommandAbortProcessPages {
595
+ std::string reason;
596
+ Result invoke(tesseract::TessBaseAPI &,
597
+ std::optional<ProcessPagesSession> &session) const {
598
+ session.reset();
599
+ return ResultVoid{};
600
+ }
601
+ };
602
+
603
+ struct CommandGetProcessPagesStatus {
604
+ Result invoke(tesseract::TessBaseAPI &,
605
+ std::optional<ProcessPagesSession> &session) const {
606
+ if (!session.has_value()) {
607
+ return ResultObject{{
608
+ {"active", false},
609
+ {"healthy", false},
610
+ {"processedPages", 0},
611
+ {"nextPageIndex", 0},
612
+ {"outputBase", std::string{}},
613
+ {"timeoutMillisec", 0},
614
+ {"textonly", false},
615
+ }};
616
+ }
617
+
618
+ return ResultObject{{
619
+ {"active", true},
620
+ {"healthy", session->renderer->happy()},
621
+ {"processedPages", session->next_page_index},
622
+ {"nextPageIndex", session->next_page_index},
623
+ {"outputBase", session->output_base},
624
+ {"timeoutMillisec", session->timeout_millisec},
625
+ {"textonly", session->textonly},
626
+ }};
627
+ }
628
+ };
629
+
630
+ struct CommandSetDebugVariable {
631
+ std::string name, value;
632
+ Result invoke(tesseract::TessBaseAPI &api,
633
+ const std::atomic<bool> &initialized) const {
634
+ RequireInitialized(initialized, "setDebugVariable");
635
+ if (name.empty()) {
636
+ throw_runtime("setDebugVariable: variable name is empty");
637
+ } else if (value.empty()) {
638
+ throw_runtime("setDebugVariable: variable value is empty");
639
+ }
640
+ return ResultBool{api.SetDebugVariable(name.c_str(), value.c_str())};
641
+ }
642
+ };
643
+
197
644
  struct CommandSetVariable {
198
645
  std::string name, value;
199
- Result invoke(tesseract::TessBaseAPI &api) const {
646
+ Result invoke(tesseract::TessBaseAPI &api,
647
+ const std::atomic<bool> &initialized) const {
648
+ RequireInitialized(initialized, "setVariable");
649
+ if (name.empty()) {
650
+ throw_runtime("setVariable: variable name is empty");
651
+ } else if (value.empty()) {
652
+ throw_runtime("setVariable: variable value is empty");
653
+ }
200
654
  return ResultBool{api.SetVariable(name.c_str(), value.c_str())};
201
655
  }
202
656
  };
203
657
 
204
658
  struct CommandGetIntVariable {
205
659
  std::string name;
206
- Result invoke(tesseract::TessBaseAPI &api) const {
660
+ Result invoke(tesseract::TessBaseAPI &api,
661
+ const std::atomic<bool> &initialized) const {
662
+ RequireInitialized(initialized, "getIntVariable");
207
663
  int value;
208
664
  if (!api.GetIntVariable(name.c_str(), &value)) {
209
- throw_runtime(
210
- "tesseract::TessBaseAPI::GetIntVariable: Variable '{}' was not found",
211
- name.c_str());
665
+ throw_runtime("getIntVariable: variable '{}' was not found",
666
+ name.c_str());
212
667
  }
213
668
 
214
669
  return ResultInt{value};
@@ -217,11 +672,12 @@ struct CommandGetIntVariable {
217
672
 
218
673
  struct CommandGetBoolVariable {
219
674
  std::string name;
220
- Result invoke(tesseract::TessBaseAPI &api) const {
675
+ Result invoke(tesseract::TessBaseAPI &api,
676
+ const std::atomic<bool> &initialized) const {
677
+ RequireInitialized(initialized, "getBoolVariable");
221
678
  bool value;
222
679
  if (!api.GetBoolVariable(name.c_str(), &value)) {
223
- throw_runtime("tesseract::TessBaseAPI::GetBoolVariable: Variable '{}' "
224
- "was not found",
680
+ throw_runtime("getBoolVariable: variable '{}' was not found",
225
681
  name.c_str());
226
682
  }
227
683
  return ResultBool{value};
@@ -230,11 +686,12 @@ struct CommandGetBoolVariable {
230
686
 
231
687
  struct CommandGetDoubleVariable {
232
688
  std::string name;
233
- Result invoke(tesseract::TessBaseAPI &api) const {
689
+ Result invoke(tesseract::TessBaseAPI &api,
690
+ const std::atomic<bool> &initialized) const {
691
+ RequireInitialized(initialized, "getDoubleVariable");
234
692
  double value;
235
693
  if (!api.GetDoubleVariable(name.c_str(), &value)) {
236
- throw_runtime("tesseract::TessBaseAPI::GetDoubleVariable: Variable '{}' "
237
- "was not found",
694
+ throw_runtime("getDoubleVariable: variable '{}' was not found",
238
695
  name.c_str());
239
696
  }
240
697
  return ResultDouble{value};
@@ -243,30 +700,27 @@ struct CommandGetDoubleVariable {
243
700
 
244
701
  struct CommandGetStringVariable {
245
702
  std::string name;
246
- Result invoke(tesseract::TessBaseAPI &api) const {
703
+ Result invoke(tesseract::TessBaseAPI &api,
704
+ const std::atomic<bool> &initialized) const {
705
+ RequireInitialized(initialized, "getStringVariable");
247
706
  auto value = api.GetStringVariable(name.c_str());
248
707
  if (value == nullptr) {
249
- throw_runtime("tesseract::TessBaseAPI::GetStringVariable: Variable '{}' "
250
- "was not found",
708
+ throw_runtime("getStringVariable: variable '{}' was not found",
251
709
  name.c_str());
252
710
  }
253
711
  return ResultString{value};
254
712
  }
255
713
  };
256
714
 
257
- // struct CommandPrintVariables {
258
- // Result invoke(tesseract::TessBaseAPI &api) const {
259
- // api.PrintVariables(FILE *fp);
260
- // }
261
- // };
262
-
263
715
  struct CommandSetImage {
264
716
  std::vector<uint8_t> bytes;
265
717
  int width = 0;
266
718
  int height = 0;
267
719
  int bytes_per_pixel = 0; // bpp/8
268
720
  int bytes_per_line = 0;
269
- Result invoke(tesseract::TessBaseAPI &api) const {
721
+ Result invoke(tesseract::TessBaseAPI &api,
722
+ const std::atomic<bool> &initialized) const {
723
+ RequireInitialized(initialized, "setImage");
270
724
  api.SetImage(bytes.data(), width, height, bytes_per_pixel, bytes_per_line);
271
725
  return ResultVoid{};
272
726
  }
@@ -274,12 +728,14 @@ struct CommandSetImage {
274
728
 
275
729
  struct CommandSetPageMode {
276
730
  tesseract::PageSegMode psm;
277
- Result invoke(tesseract::TessBaseAPI &api) const {
731
+ Result invoke(tesseract::TessBaseAPI &api,
732
+ const std::atomic<bool> &initialized) const {
733
+ RequireInitialized(initialized, "setPageMode");
278
734
  if (psm < 0 || psm >= tesseract::PageSegMode::PSM_COUNT) {
279
735
 
280
- throw_runtime(
281
- "tesseract::TessBaseAPI::SetPageMode out range; received: {}",
282
- static_cast<int>(psm));
736
+ throw_runtime("setPageMode: page segmentation mode is out of range; "
737
+ "received {}",
738
+ static_cast<int>(psm));
283
739
  }
284
740
  api.SetPageSegMode(psm);
285
741
  return ResultVoid{};
@@ -288,7 +744,9 @@ struct CommandSetPageMode {
288
744
 
289
745
  struct CommandSetRectangle {
290
746
  int left, top, width, height;
291
- Result invoke(tesseract::TessBaseAPI &api) const {
747
+ Result invoke(tesseract::TessBaseAPI &api,
748
+ const std::atomic<bool> &initialized) const {
749
+ RequireInitialized(initialized, "setRectangle");
292
750
  api.SetRectangle(left, top, width, height);
293
751
  return ResultVoid{};
294
752
  }
@@ -296,7 +754,9 @@ struct CommandSetRectangle {
296
754
 
297
755
  struct CommandSetSourceResolution {
298
756
  int ppi;
299
- Result invoke(tesseract::TessBaseAPI &api) const {
757
+ Result invoke(tesseract::TessBaseAPI &api,
758
+ const std::atomic<bool> &initialized) const {
759
+ RequireInitialized(initialized, "setSourceResolution");
300
760
  api.SetSourceResolution(ppi);
301
761
  return ResultVoid{};
302
762
  }
@@ -304,11 +764,14 @@ struct CommandSetSourceResolution {
304
764
 
305
765
  struct CommandRecognize {
306
766
  std::shared_ptr<MonitorContext> monitor_context;
307
- Result invoke(tesseract::TessBaseAPI &api) const {
767
+ Result invoke(tesseract::TessBaseAPI &api,
768
+ const std::atomic<bool> &initialized) const {
769
+ RequireInitialized(initialized, "recognize");
308
770
  MonitorHandle handle{monitor_context};
309
771
  auto *monitor = monitor_context ? &handle.monitor : nullptr;
310
772
  if (api.Recognize(monitor) != 0) {
311
- throw std::runtime_error("tesseract::TessBaseAPI::Recognize failed");
773
+ throw_runtime(
774
+ "recognize: TessBaseAPI::Recognize returned non-zero status");
312
775
  }
313
776
  return ResultVoid{};
314
777
  }
@@ -321,7 +784,9 @@ struct CommandRecognize {
321
784
  // };
322
785
 
323
786
  struct CommandDetectOrientationScript {
324
- Result invoke(tesseract::TessBaseAPI &api) const {
787
+ Result invoke(tesseract::TessBaseAPI &api,
788
+ const std::atomic<bool> &initialized) const {
789
+ RequireInitialized(initialized, "detectOrientationScript");
325
790
  int orient_deg;
326
791
  float orient_conf;
327
792
  const char *script_name;
@@ -329,8 +794,9 @@ struct CommandDetectOrientationScript {
329
794
 
330
795
  if (!api.DetectOrientationScript(&orient_deg, &orient_conf, &script_name,
331
796
  &script_conf)) {
332
- throw std::runtime_error(
333
- "tesseract::TessBaseAPI::DetectOrientationScript failed");
797
+ throw_runtime(
798
+ "detectOrientationScript: TessBaseAPI::DetectOrientationScript "
799
+ "returned false");
334
800
  }
335
801
 
336
802
  return ResultObject{{
@@ -343,16 +809,122 @@ struct CommandDetectOrientationScript {
343
809
  };
344
810
 
345
811
  struct CommandMeanTextConf {
346
- Result invoke(tesseract::TessBaseAPI &api) const {
812
+ Result invoke(tesseract::TessBaseAPI &api,
813
+ const std::atomic<bool> &initialized) const {
814
+ RequireInitialized(initialized, "meanTextConf");
347
815
  return ResultInt{api.MeanTextConf()};
348
816
  }
349
817
  };
350
818
 
819
+ struct CommandGetPAGEText {
820
+ int page_number;
821
+ std::shared_ptr<MonitorContext> monitor_context;
822
+ Result invoke(tesseract::TessBaseAPI &api,
823
+ const std::atomic<bool> &initialized) const {
824
+ RequireInitialized(initialized, "getPAGEText");
825
+ MonitorHandle handle{monitor_context};
826
+ auto *monitor = monitor_context ? &handle.monitor : nullptr;
827
+ char *page_text = api.GetPAGEText(monitor, page_number);
828
+ if (!page_text) {
829
+ throw_runtime("getPAGEText: TessBaseAPI::GetPAGEText returned null");
830
+ }
831
+ std::string text = std::string{page_text};
832
+
833
+ delete[] page_text;
834
+ return ResultString(text);
835
+ }
836
+ };
837
+
838
+ struct CommandGetLSTMBoxText {
839
+ int page_number;
840
+ Result invoke(tesseract::TessBaseAPI &api,
841
+ const std::atomic<bool> &initialized) const {
842
+ RequireInitialized(initialized, "getLSTMBoxText");
843
+ char *lstm_box_text = api.GetLSTMBoxText(page_number);
844
+ if (!lstm_box_text) {
845
+ throw_runtime(
846
+ "getLSTMBoxText: TessBaseAPI::GetLSTMBoxText returned null");
847
+ }
848
+ std::string text = std::string{lstm_box_text};
849
+
850
+ delete[] lstm_box_text;
851
+ return ResultString(text);
852
+ }
853
+ };
854
+
855
+ struct CommandGetBoxText {
856
+ int page_number;
857
+ Result invoke(tesseract::TessBaseAPI &api,
858
+ const std::atomic<bool> &initialized) const {
859
+ RequireInitialized(initialized, "getBoxText");
860
+ char *box_text = api.GetBoxText(page_number);
861
+ if (!box_text) {
862
+ throw_runtime("getBoxText: TessBaseAPI::GetBoxText returned null");
863
+ }
864
+ std::string text = std::string{box_text};
865
+
866
+ delete[] box_text;
867
+ return ResultString(text);
868
+ }
869
+ };
870
+
871
+ struct CommandGetWordStrBoxText {
872
+ int page_number;
873
+ Result invoke(tesseract::TessBaseAPI &api,
874
+ const std::atomic<bool> &initialized) const {
875
+ RequireInitialized(initialized, "getWordStrBoxText");
876
+ char *word_str_box_text = api.GetWordStrBoxText(page_number);
877
+ if (!word_str_box_text) {
878
+ throw_runtime(
879
+ "getWordStrBoxText: TessBaseAPI::GetWordStrBoxText returned null");
880
+ }
881
+ std::string text = std::string{word_str_box_text};
882
+
883
+ delete[] word_str_box_text;
884
+ return ResultString(text);
885
+ }
886
+ };
887
+
888
+ struct CommandGetOSDText {
889
+ int page_number;
890
+ Result invoke(tesseract::TessBaseAPI &api,
891
+ const std::atomic<bool> &initialized) const {
892
+ RequireInitialized(initialized, "getOSDText");
893
+ char *ost_text = api.GetOsdText(page_number);
894
+ if (!ost_text) {
895
+ throw_runtime("getOSDText: TessBaseAPI::GetOsdText returned null");
896
+ }
897
+ std::string text = std::string{ost_text};
898
+
899
+ delete[] ost_text;
900
+ return ResultString(text);
901
+ }
902
+ };
903
+
904
+ struct CommandAllWordConfidences {
905
+ Result invoke(tesseract::TessBaseAPI &api,
906
+ const std::atomic<bool> &initialized) const {
907
+ RequireInitialized(initialized, "allWordConfidences");
908
+ int *all_word_confidences = api.AllWordConfidences();
909
+
910
+ std::vector<int> confidences;
911
+ if (all_word_confidences != nullptr) {
912
+ for (int i = 0; all_word_confidences[i] != -1; ++i) {
913
+ confidences.push_back(all_word_confidences[i]);
914
+ }
915
+ }
916
+ delete[] all_word_confidences;
917
+ return ResultArray{confidences};
918
+ }
919
+ };
920
+
351
921
  struct CommandGetUTF8Text {
352
- Result invoke(tesseract::TessBaseAPI &api) const {
922
+ Result invoke(tesseract::TessBaseAPI &api,
923
+ const std::atomic<bool> &initialized) const {
924
+ RequireInitialized(initialized, "getUTF8Text");
353
925
  char *utf8_text = api.GetUTF8Text();
354
926
  if (!utf8_text) {
355
- throw_runtime("GetUTF8Text returned null");
927
+ throw_runtime("getUTF8Text: TessBaseAPI::GetUTF8Text returned null");
356
928
  }
357
929
  std::string text = std::string{utf8_text};
358
930
 
@@ -364,13 +936,15 @@ struct CommandGetUTF8Text {
364
936
  struct CommandGetHOCRText {
365
937
  int page_number;
366
938
  std::shared_ptr<MonitorContext> monitor_context;
367
- Result invoke(tesseract::TessBaseAPI &api) const {
939
+ Result invoke(tesseract::TessBaseAPI &api,
940
+ const std::atomic<bool> &initialized) const {
941
+ RequireInitialized(initialized, "getHOCRText");
368
942
 
369
943
  MonitorHandle handle{monitor_context};
370
944
  auto *monitor = monitor_context ? &handle.monitor : nullptr;
371
945
  char *hocr_text = api.GetHOCRText(monitor, page_number);
372
946
  if (!hocr_text) {
373
- throw_runtime("GetHOCRText returned null");
947
+ throw_runtime("getHOCRText: TessBaseAPI::GetHOCRText returned null");
374
948
  }
375
949
 
376
950
  std::string text = std::string{hocr_text};
@@ -382,10 +956,12 @@ struct CommandGetHOCRText {
382
956
 
383
957
  struct CommandGetTSVText {
384
958
  int page_number;
385
- Result invoke(tesseract::TessBaseAPI &api) const {
959
+ Result invoke(tesseract::TessBaseAPI &api,
960
+ const std::atomic<bool> &initialized) const {
961
+ RequireInitialized(initialized, "getTSVText");
386
962
  char *tsv_text = api.GetTSVText(page_number);
387
963
  if (!tsv_text) {
388
- throw_runtime("GetTSVText returned null");
964
+ throw_runtime("getTSVText: TessBaseAPI::GetTSVText returned null");
389
965
  }
390
966
  std::string text = std::string{tsv_text};
391
967
 
@@ -395,10 +971,12 @@ struct CommandGetTSVText {
395
971
  };
396
972
 
397
973
  struct CommandGetUNLVText {
398
- Result invoke(tesseract::TessBaseAPI &api) const {
974
+ Result invoke(tesseract::TessBaseAPI &api,
975
+ const std::atomic<bool> &initialized) const {
976
+ RequireInitialized(initialized, "getUNLVText");
399
977
  char *unlv_text = api.GetUNLVText();
400
978
  if (!unlv_text) {
401
- throw_runtime("GetUNLVText returned null");
979
+ throw_runtime("getUNLVText: TessBaseAPI::GetUNLVText returned null");
402
980
  }
403
981
  std::string text = std::string{unlv_text};
404
982
  delete[] unlv_text;
@@ -409,12 +987,14 @@ struct CommandGetUNLVText {
409
987
  struct CommandGetALTOText {
410
988
  int page_number;
411
989
  std::shared_ptr<MonitorContext> monitor_context;
412
- Result invoke(tesseract::TessBaseAPI &api) const {
990
+ Result invoke(tesseract::TessBaseAPI &api,
991
+ const std::atomic<bool> &initialized) const {
992
+ RequireInitialized(initialized, "getALTOText");
413
993
  MonitorHandle handle{monitor_context};
414
994
  auto *monitor = monitor_context ? &handle.monitor : nullptr;
415
995
  char *alto_text = api.GetAltoText(monitor, page_number);
416
996
  if (!alto_text) {
417
- throw_runtime("GetALTOText returned null");
997
+ throw_runtime("getALTOText: TessBaseAPI::GetAltoText returned null");
418
998
  }
419
999
  std::string text = std::string{alto_text};
420
1000
  delete[] alto_text;
@@ -424,12 +1004,15 @@ struct CommandGetALTOText {
424
1004
  };
425
1005
 
426
1006
  struct CommandGetInitLanguages {
427
- Result invoke(tesseract::TessBaseAPI &api) const {
1007
+ Result invoke(tesseract::TessBaseAPI &api,
1008
+ const std::atomic<bool> &initialized) const {
1009
+ RequireInitialized(initialized, "getInitLanguages");
428
1010
  const char *p_init_languages = api.GetInitLanguagesAsString();
429
1011
 
430
1012
  if (p_init_languages == nullptr) {
431
- // TODO: put a better error message here
432
- // throw std::runtime_error("Uhhhhm");
1013
+ throw_runtime("getInitLanguages: TessBaseAPI::GetInitLanguagesAsString "
1014
+ "returned null; call init(...) first with at least one "
1015
+ "valid language");
433
1016
  }
434
1017
 
435
1018
  std::string init_languages = std::string{p_init_languages};
@@ -439,7 +1022,9 @@ struct CommandGetInitLanguages {
439
1022
  };
440
1023
 
441
1024
  struct CommandGetLoadedLanguages {
442
- Result invoke(tesseract::TessBaseAPI &api) const {
1025
+ Result invoke(tesseract::TessBaseAPI &api,
1026
+ const std::atomic<bool> &initialized) const {
1027
+ RequireInitialized(initialized, "getLoadedLanguages");
443
1028
  std::vector<std::string> langs;
444
1029
  api.GetLoadedLanguagesAsVector(&langs);
445
1030
  return ResultArray{langs};
@@ -455,29 +1040,42 @@ struct CommandGetAvailableLanguages {
455
1040
  };
456
1041
 
457
1042
  struct CommandClear {
458
- Result invoke(tesseract::TessBaseAPI &api) const {
1043
+ Result invoke(tesseract::TessBaseAPI &api,
1044
+ const std::atomic<bool> &initialized) const {
1045
+ RequireInitialized(initialized, "clear");
459
1046
  api.Clear();
460
1047
  return ResultVoid{};
461
1048
  }
462
1049
  };
463
1050
 
464
1051
  struct CommandEnd {
465
- Result invoke(tesseract::TessBaseAPI &api) const {
1052
+ Result invoke(tesseract::TessBaseAPI &api,
1053
+ std::atomic<bool> &initialized) const {
466
1054
  api.End();
1055
+ initialized.store(false, std::memory_order_release);
467
1056
  return ResultVoid{};
468
1057
  }
469
1058
  };
470
1059
 
471
1060
  using Command = std::variant<
472
- CommandInit, CommandInitForAnalysePage, CommandAnalyseLayout,
473
- CommandSetVariable, CommandGetIntVariable, CommandGetBoolVariable,
474
- CommandGetDoubleVariable, CommandGetStringVariable, CommandSetPageMode,
475
- CommandSetRectangle, CommandSetSourceResolution, CommandSetImage,
476
- CommandRecognize, CommandDetectOrientationScript, CommandMeanTextConf,
477
- CommandGetUTF8Text, CommandGetHOCRText, CommandGetTSVText,
478
- CommandGetUNLVText, CommandGetALTOText, CommandGetInitLanguages,
479
- CommandGetLoadedLanguages, CommandGetAvailableLanguages, CommandClear,
480
- CommandEnd>;
1061
+ CommandVersion, CommandIsInitialized, CommandInit,
1062
+ CommandInitForAnalysePage, CommandSetVariable, CommandSetDebugVariable,
1063
+ CommandGetIntVariable, CommandGetBoolVariable, CommandGetDoubleVariable,
1064
+ CommandGetStringVariable, CommandSetInputName, CommandGetInputName,
1065
+ CommandSetOutputName, CommandGetDataPath, CommandSetInputImage,
1066
+ CommandGetInputImage, CommandSetPageMode, CommandSetRectangle,
1067
+ CommandSetSourceResolution, CommandGetSourceYResolution, CommandSetImage,
1068
+ CommandGetThresholdedImage, CommandGetThresholdedImageScaleFactor,
1069
+ CommandRecognize, CommandAnalyseLayout, CommandDetectOrientationScript,
1070
+ CommandMeanTextConf, CommandAllWordConfidences, CommandGetUTF8Text,
1071
+ CommandGetHOCRText, CommandGetTSVText, CommandGetUNLVText,
1072
+ CommandGetALTOText, CommandGetPAGEText, CommandGetLSTMBoxText,
1073
+ CommandGetBoxText, CommandGetWordStrBoxText, CommandGetOSDText,
1074
+ CommandBeginProcessPages, CommandAddProcessPage, CommandFinishProcessPages,
1075
+ CommandAbortProcessPages, CommandGetProcessPagesStatus,
1076
+ CommandGetInitLanguages, CommandGetLoadedLanguages,
1077
+ CommandGetAvailableLanguages, CommandClearPersistentCache,
1078
+ CommandClearAdaptiveClassifier, CommandClear, CommandEnd>;
481
1079
 
482
1080
  struct Job {
483
1081
  Command command;
@@ -485,4 +1083,6 @@ struct Job {
485
1083
 
486
1084
  std::optional<Result> result;
487
1085
  std::optional<std::string> error;
1086
+ std::optional<std::string> error_code;
1087
+ std::optional<std::string> error_method;
488
1088
  };