#include <tesseract/resultiterator.h>
#include "helpers.h"
#include "pageres.h"
#include "tesseractclass.h"
#include "unicharset.h"
#include <allheaders.h>
#include <set>
#include <vector>
static const char *const kLRM = "\u200E";
static const char *const kRLM = "\u200F";
namespace tesseract {
ResultIterator::ResultIterator(const LTRResultIterator &resit) : LTRResultIterator(resit) {
in_minor_direction_ = false;
at_beginning_of_minor_run_ = false;
preserve_interword_spaces_ = false;
auto *p = ParamUtils::FindParam<BoolParam>(
"preserve_interword_spaces", GlobalParams()->bool_params, tesseract_->params()->bool_params);
if (p != nullptr) {
preserve_interword_spaces_ = (bool)(*p);
}
current_paragraph_is_ltr_ = CurrentParagraphIsLtr();
MoveToLogicalStartOfTextline();
}
ResultIterator *ResultIterator::StartOfParagraph(const LTRResultIterator &resit) {
return new ResultIterator(resit);
}
bool ResultIterator::ParagraphIsLtr() const {
return current_paragraph_is_ltr_;
}
bool ResultIterator::CurrentParagraphIsLtr() const {
if (!it_->word()) {
return true;
}
LTRResultIterator it(*this);
it.RestartParagraph();
bool leftmost_rtl = it.WordDirection() == DIR_RIGHT_TO_LEFT;
bool rightmost_ltr = it.WordDirection() == DIR_LEFT_TO_RIGHT;
int num_ltr, num_rtl;
num_rtl = leftmost_rtl ? 1 : 0;
num_ltr = (it.WordDirection() == DIR_LEFT_TO_RIGHT) ? 1 : 0;
for (it.Next(RIL_WORD); !it.Empty(RIL_WORD) && !it.IsAtBeginningOf(RIL_TEXTLINE);
it.Next(RIL_WORD)) {
StrongScriptDirection dir = it.WordDirection();
rightmost_ltr = (dir == DIR_LEFT_TO_RIGHT);
num_rtl += (dir == DIR_RIGHT_TO_LEFT) ? 1 : 0;
num_ltr += rightmost_ltr ? 1 : 0;
}
if (leftmost_rtl) {
return false;
}
if (rightmost_ltr) {
return true;
}
if (!it.Empty(RIL_WORD) && !it.IsAtBeginningOf(RIL_PARA)) {
do {
StrongScriptDirection dir = it.WordDirection();
num_rtl += (dir == DIR_RIGHT_TO_LEFT) ? 1 : 0;
num_ltr += (dir == DIR_LEFT_TO_RIGHT) ? 1 : 0;
} while (it.Next(RIL_WORD) && !it.IsAtBeginningOf(RIL_PARA));
}
return num_ltr >= num_rtl;
}
const int ResultIterator::kMinorRunStart = -1;
const int ResultIterator::kMinorRunEnd = -2;
const int ResultIterator::kComplexWord = -3;
void ResultIterator::CalculateBlobOrder(std::vector<int> *blob_indices) const {
bool context_is_ltr = current_paragraph_is_ltr_ ^ in_minor_direction_;
blob_indices->clear();
if (Empty(RIL_WORD)) {
return;
}
if (context_is_ltr || it_->word()->UnicharsInReadingOrder()) {
for (int i = 0; i < word_length_; i++) {
blob_indices->push_back(i);
}
return;
}
const int U_LTR = UNICHARSET::U_LEFT_TO_RIGHT;
const int U_RTL = UNICHARSET::U_RIGHT_TO_LEFT;
const int U_EURO_NUM = UNICHARSET::U_EUROPEAN_NUMBER;
const int U_EURO_NUM_SEP = UNICHARSET::U_EUROPEAN_NUMBER_SEPARATOR;
const int U_EURO_NUM_TERM = UNICHARSET::U_EUROPEAN_NUMBER_TERMINATOR;
const int U_COMMON_NUM_SEP = UNICHARSET::U_COMMON_NUMBER_SEPARATOR;
const int U_OTHER_NEUTRAL = UNICHARSET::U_OTHER_NEUTRAL;
std::vector<int> letter_types;
letter_types.reserve(word_length_);
for (int i = 0; i < word_length_; i++) {
letter_types.push_back(it_->word()->SymbolDirection(i));
}
for (int i = 0; i + 2 < word_length_; i++) {
if (letter_types[i] == U_EURO_NUM && letter_types[i + 2] == U_EURO_NUM &&
(letter_types[i + 1] == U_EURO_NUM_SEP || letter_types[i + 1] == U_COMMON_NUM_SEP)) {
letter_types[i + 1] = U_EURO_NUM;
}
}
for (int i = 0; i < word_length_; i++) {
if (letter_types[i] == U_EURO_NUM_TERM) {
int j = i + 1;
while (j < word_length_ && letter_types[j] == U_EURO_NUM_TERM) {
j++;
}
if (j < word_length_ && letter_types[j] == U_EURO_NUM) {
for (int k = i; k < j; k++) {
letter_types[k] = U_EURO_NUM;
}
}
j = i - 1;
while (j > -1 && letter_types[j] == U_EURO_NUM_TERM) {
j--;
}
if (j > -1 && letter_types[j] == U_EURO_NUM) {
for (int k = j; k <= i; k++) {
letter_types[k] = U_EURO_NUM;
}
}
}
}
for (int i = 0; i < word_length_;) {
int ti = letter_types[i];
if (ti == U_LTR || ti == U_EURO_NUM) {
int last_good = i;
for (int j = i + 1; j < word_length_; j++) {
int tj = letter_types[j];
if (tj == U_LTR || tj == U_EURO_NUM) {
last_good = j;
} else if (tj == U_COMMON_NUM_SEP || tj == U_OTHER_NEUTRAL) {
} else {
break;
}
}
for (int k = i; k <= last_good; k++) {
letter_types[k] = U_LTR;
}
i = last_good + 1;
} else {
letter_types[i] = U_RTL;
i++;
}
}
for (int i = word_length_ - 1; i >= 0;) {
if (letter_types[i] == U_RTL) {
blob_indices->push_back(i);
i--;
} else {
int j = i - 1;
for (; j >= 0 && letter_types[j] != U_RTL; j--) {
}
for (int k = j + 1; k <= i; k++) {
blob_indices->push_back(k);
}
i = j;
}
}
ASSERT_HOST(blob_indices->size() == static_cast<size_t>(word_length_));
}
static void PrintScriptDirs(const std::vector<StrongScriptDirection> &dirs) {
for (auto dir : dirs) {
switch (dir) {
case DIR_NEUTRAL:
tprintf("N ");
break;
case DIR_LEFT_TO_RIGHT:
tprintf("L ");
break;
case DIR_RIGHT_TO_LEFT:
tprintf("R ");
break;
case DIR_MIX:
tprintf("Z ");
break;
default:
tprintf("? ");
break;
}
}
tprintf("\n");
}
void ResultIterator::CalculateTextlineOrder(bool paragraph_is_ltr, const LTRResultIterator &resit,
std::vector<int> *word_indices) const {
std::vector<StrongScriptDirection> directions;
CalculateTextlineOrder(paragraph_is_ltr, resit, &directions, word_indices);
}
void ResultIterator::CalculateTextlineOrder(bool paragraph_is_ltr, const LTRResultIterator &resit,
std::vector<StrongScriptDirection> *dirs_arg,
std::vector<int> *word_indices) const {
std::vector<StrongScriptDirection> dirs;
std::vector<StrongScriptDirection> *directions;
directions = (dirs_arg != nullptr) ? dirs_arg : &dirs;
directions->clear();
LTRResultIterator ltr_it(resit);
ltr_it.RestartRow();
if (ltr_it.Empty(RIL_WORD)) {
return;
}
do {
directions->push_back(ltr_it.WordDirection());
} while (ltr_it.Next(RIL_WORD) && !ltr_it.IsAtBeginningOf(RIL_TEXTLINE));
word_indices->clear();
CalculateTextlineOrder(paragraph_is_ltr, *directions, word_indices);
}
void ResultIterator::CalculateTextlineOrder(bool paragraph_is_ltr,
const std::vector<StrongScriptDirection> &word_dirs,
std::vector<int> *reading_order) {
reading_order->clear();
if (word_dirs.empty()) {
return;
}
int minor_direction, major_direction, major_step, start, end;
if (paragraph_is_ltr) {
start = 0;
end = word_dirs.size();
major_step = 1;
major_direction = DIR_LEFT_TO_RIGHT;
minor_direction = DIR_RIGHT_TO_LEFT;
} else {
start = word_dirs.size() - 1;
end = -1;
major_step = -1;
major_direction = DIR_RIGHT_TO_LEFT;
minor_direction = DIR_LEFT_TO_RIGHT;
if (word_dirs[start] == DIR_NEUTRAL) {
int neutral_end = start;
while (neutral_end > 0 && word_dirs[neutral_end] == DIR_NEUTRAL) {
neutral_end--;
}
if (neutral_end >= 0 && word_dirs[neutral_end] == DIR_LEFT_TO_RIGHT) {
int left = neutral_end;
for (int i = left; i >= 0 && word_dirs[i] != DIR_RIGHT_TO_LEFT; i--) {
if (word_dirs[i] == DIR_LEFT_TO_RIGHT) {
left = i;
}
}
reading_order->push_back(kMinorRunStart);
for (unsigned i = left; i < word_dirs.size(); i++) {
reading_order->push_back(i);
if (word_dirs[i] == DIR_MIX) {
reading_order->push_back(kComplexWord);
}
}
reading_order->push_back(kMinorRunEnd);
start = left - 1;
}
}
}
for (int i = start; i != end;) {
if (word_dirs[i] == minor_direction) {
int j = i;
while (j != end && word_dirs[j] != major_direction) {
j += major_step;
}
if (j == end) {
j -= major_step;
}
while (j != i && word_dirs[j] != minor_direction) {
j -= major_step;
}
reading_order->push_back(kMinorRunStart);
for (int k = j; k != i; k -= major_step) {
reading_order->push_back(k);
}
reading_order->push_back(i);
reading_order->push_back(kMinorRunEnd);
i = j + major_step;
} else {
reading_order->push_back(i);
if (word_dirs[i] == DIR_MIX) {
reading_order->push_back(kComplexWord);
}
i += major_step;
}
}
}
int ResultIterator::LTRWordIndex() const {
int this_word_index = 0;
LTRResultIterator textline(*this);
textline.RestartRow();
while (!textline.PositionedAtSameWord(it_)) {
this_word_index++;
textline.Next(RIL_WORD);
}
return this_word_index;
}
void ResultIterator::MoveToLogicalStartOfWord() {
if (word_length_ == 0) {
BeginWord(0);
return;
}
std::vector<int> blob_order;
CalculateBlobOrder(&blob_order);
if (blob_order.empty() || blob_order[0] == 0) {
return;
}
BeginWord(blob_order[0]);
}
bool ResultIterator::IsAtFinalSymbolOfWord() const {
if (!it_->word()) {
return true;
}
std::vector<int> blob_order;
CalculateBlobOrder(&blob_order);
return blob_order.empty() || blob_order.back() == blob_index_;
}
bool ResultIterator::IsAtFirstSymbolOfWord() const {
if (!it_->word()) {
return true;
}
std::vector<int> blob_order;
CalculateBlobOrder(&blob_order);
return blob_order.empty() || blob_order[0] == blob_index_;
}
void ResultIterator::AppendSuffixMarks(std::string *text) const {
if (!it_->word()) {
return;
}
bool reading_direction_is_ltr = current_paragraph_is_ltr_ ^ in_minor_direction_;
std::vector<int> textline_order;
CalculateTextlineOrder(current_paragraph_is_ltr_, *this, &textline_order);
int this_word_index = LTRWordIndex();
size_t i = 0;
for (const auto word_index : textline_order) {
if (word_index == this_word_index) {
break;
}
i++;
}
if (i == textline_order.size()) {
return;
}
int last_non_word_mark = 0;
for (i++; i < textline_order.size() && textline_order[i] < 0; i++) {
last_non_word_mark = textline_order[i];
}
if (last_non_word_mark == kComplexWord) {
*text += reading_direction_is_ltr ? kLRM : kRLM;
} else if (last_non_word_mark == kMinorRunEnd) {
if (current_paragraph_is_ltr_) {
*text += kLRM;
} else {
*text += kRLM;
}
}
}
void ResultIterator::MoveToLogicalStartOfTextline() {
std::vector<int> word_indices;
RestartRow();
CalculateTextlineOrder(current_paragraph_is_ltr_, dynamic_cast<const LTRResultIterator &>(*this),
&word_indices);
unsigned i = 0;
for (; i < word_indices.size() && word_indices[i] < 0; i++) {
if (word_indices[i] == kMinorRunStart) {
in_minor_direction_ = true;
} else if (word_indices[i] == kMinorRunEnd) {
in_minor_direction_ = false;
}
}
if (in_minor_direction_) {
at_beginning_of_minor_run_ = true;
}
if (i >= word_indices.size()) {
return;
}
int first_word_index = word_indices[i];
for (int j = 0; j < first_word_index; j++) {
PageIterator::Next(RIL_WORD);
}
MoveToLogicalStartOfWord();
}
void ResultIterator::Begin() {
LTRResultIterator::Begin();
current_paragraph_is_ltr_ = CurrentParagraphIsLtr();
in_minor_direction_ = false;
at_beginning_of_minor_run_ = false;
MoveToLogicalStartOfTextline();
}
bool ResultIterator::Next(PageIteratorLevel level) {
if (it_->block() == nullptr) {
return false;
}
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
case RIL_TEXTLINE:
if (!PageIterator::Next(level)) {
return false;
}
if (IsWithinFirstTextlineOfParagraph()) {
current_paragraph_is_ltr_ = CurrentParagraphIsLtr();
}
in_minor_direction_ = false;
MoveToLogicalStartOfTextline();
return it_->block() != nullptr;
case RIL_SYMBOL: {
std::vector<int> blob_order;
CalculateBlobOrder(&blob_order);
unsigned next_blob = 0;
while (next_blob < blob_order.size() && blob_index_ != blob_order[next_blob]) {
next_blob++;
}
next_blob++;
if (next_blob < blob_order.size()) {
BeginWord(blob_order[next_blob]);
at_beginning_of_minor_run_ = false;
return true;
}
level = RIL_WORD;
}
case RIL_WORD:
{
if (it_->word() == nullptr) {
return Next(RIL_BLOCK);
}
std::vector<int> word_indices;
int this_word_index = LTRWordIndex();
CalculateTextlineOrder(current_paragraph_is_ltr_, *this, &word_indices);
int final_real_index = word_indices.size() - 1;
while (final_real_index > 0 && word_indices[final_real_index] < 0) {
final_real_index--;
}
for (int i = 0; i < final_real_index; i++) {
if (word_indices[i] == this_word_index) {
int j = i + 1;
for (; j < final_real_index && word_indices[j] < 0; j++) {
if (word_indices[j] == kMinorRunStart) {
in_minor_direction_ = true;
}
if (word_indices[j] == kMinorRunEnd) {
in_minor_direction_ = false;
}
}
at_beginning_of_minor_run_ = (word_indices[j - 1] == kMinorRunStart);
if (BidiDebug(3)) {
tprintf("Next(RIL_WORD): %d -> %d\n", this_word_index, word_indices[j]);
}
PageIterator::RestartRow();
for (int k = 0; k < word_indices[j]; k++) {
PageIterator::Next(RIL_WORD);
}
MoveToLogicalStartOfWord();
return true;
}
}
if (BidiDebug(3)) {
tprintf("Next(RIL_WORD): %d -> EOL\n", this_word_index);
}
return Next(RIL_TEXTLINE);
}
}
ASSERT_HOST(false);
return false;
}
bool ResultIterator::IsAtBeginningOf(PageIteratorLevel level) const {
if (it_->block() == nullptr) {
return false;
}
if (it_->word() == nullptr) {
return true;
}
if (level == RIL_SYMBOL) {
return true;
}
bool at_word_start = IsAtFirstSymbolOfWord();
if (level == RIL_WORD) {
return at_word_start;
}
ResultIterator line_start(*this);
line_start.MoveToLogicalStartOfTextline();
bool at_textline_start = at_word_start && *line_start.it_ == *it_;
if (level == RIL_TEXTLINE) {
return at_textline_start;
}
line_start.RestartRow();
bool at_block_start =
at_textline_start && line_start.it_->block() != line_start.it_->prev_block();
if (level == RIL_BLOCK) {
return at_block_start;
}
bool at_para_start =
at_block_start || (at_textline_start && line_start.it_->row()->row->para() !=
line_start.it_->prev_row()->row->para());
if (level == RIL_PARA) {
return at_para_start;
}
ASSERT_HOST(false);
return false;
}
* NOTE! This is an exact copy of PageIterator::IsAtFinalElement with the
* change that the variable next is now a ResultIterator instead of a
* PageIterator.
*/
bool ResultIterator::IsAtFinalElement(PageIteratorLevel level, PageIteratorLevel element) const {
if (Empty(element)) {
return true;
}
ResultIterator next(*this);
next.Next(element);
if (next.Empty(element)) {
return true;
}
while (element > level) {
element = static_cast<PageIteratorLevel>(element - 1);
if (!next.IsAtBeginningOf(element)) {
return false;
}
}
return true;
}
int ResultIterator::BlanksBeforeWord() const {
if (CurrentParagraphIsLtr()) {
return LTRResultIterator::BlanksBeforeWord();
}
return IsAtBeginningOf(RIL_TEXTLINE) ? 0 : 1;
}
* Returns the null terminated UTF-8 encoded text string for the current
* object at the given level. Use delete [] to free after use.
*/
char *ResultIterator::GetUTF8Text(PageIteratorLevel level) const {
if (it_->word() == nullptr) {
return nullptr;
}
std::string text;
switch (level) {
case RIL_BLOCK: {
ResultIterator pp(*this);
do {
pp.AppendUTF8ParagraphText(&text);
} while (pp.Next(RIL_PARA) && pp.it_->block() == it_->block());
} break;
case RIL_PARA:
AppendUTF8ParagraphText(&text);
break;
case RIL_TEXTLINE: {
ResultIterator it(*this);
it.MoveToLogicalStartOfTextline();
it.IterateAndAppendUTF8TextlineText(&text);
} break;
case RIL_WORD:
AppendUTF8WordText(&text);
break;
case RIL_SYMBOL: {
bool reading_direction_is_ltr = current_paragraph_is_ltr_ ^ in_minor_direction_;
if (at_beginning_of_minor_run_) {
text += reading_direction_is_ltr ? kLRM : kRLM;
}
text = it_->word()->BestUTF8(blob_index_, false);
if (IsAtFinalSymbolOfWord()) {
AppendSuffixMarks(&text);
}
} break;
}
return copy_string(text);
}
std::vector<std::vector<std::vector<std::pair<const char *, float>>>>
*ResultIterator::GetRawLSTMTimesteps() const {
if (it_->word() != nullptr) {
return &it_->word()->segmented_timesteps;
} else {
return nullptr;
}
}
std::vector<std::vector<std::pair<const char *, float>>> *ResultIterator::GetBestLSTMSymbolChoices()
const {
if (it_->word() != nullptr) {
return &it_->word()->CTC_symbol_choices;
} else {
return nullptr;
}
}
void ResultIterator::AppendUTF8WordText(std::string *text) const {
if (!it_->word()) {
return;
}
ASSERT_HOST(it_->word()->best_choice != nullptr);
bool reading_direction_is_ltr = current_paragraph_is_ltr_ ^ in_minor_direction_;
if (at_beginning_of_minor_run_) {
*text += reading_direction_is_ltr ? kLRM : kRLM;
}
std::vector<int> blob_order;
CalculateBlobOrder(&blob_order);
for (int i : blob_order) {
*text += it_->word()->BestUTF8(i, false);
}
AppendSuffixMarks(text);
}
void ResultIterator::IterateAndAppendUTF8TextlineText(std::string *text) {
if (Empty(RIL_WORD)) {
Next(RIL_WORD);
return;
}
if (BidiDebug(1)) {
std::vector<int> textline_order;
std::vector<StrongScriptDirection> dirs;
CalculateTextlineOrder(current_paragraph_is_ltr_, *this, &dirs, &textline_order);
tprintf("Strong Script dirs [%p/P=%s]: ",
static_cast<void *>(it_->row()),
current_paragraph_is_ltr_ ? "ltr" : "rtl");
PrintScriptDirs(dirs);
tprintf("Logical textline order [%p/P=%s]: ",
static_cast<void *>(it_->row()),
current_paragraph_is_ltr_ ? "ltr" : "rtl");
for (int i : textline_order) {
tprintf("%d ", i);
}
tprintf("\n");
}
int words_appended = 0;
do {
int numSpaces = preserve_interword_spaces_ ? it_->word()->word->space() : (words_appended > 0);
for (int i = 0; i < numSpaces; ++i) {
*text += " ";
}
AppendUTF8WordText(text);
words_appended++;
if (BidiDebug(2)) {
tprintf("Num spaces=%d, text=%s\n", numSpaces, text->c_str());
}
} while (Next(RIL_WORD) && !IsAtBeginningOf(RIL_TEXTLINE));
if (BidiDebug(1)) {
tprintf("%d words printed\n", words_appended);
}
*text += line_separator_;
if (IsAtBeginningOf(RIL_PARA)) {
*text += paragraph_separator_;
}
}
void ResultIterator::AppendUTF8ParagraphText(std::string *text) const {
ResultIterator it(*this);
it.RestartParagraph();
it.MoveToLogicalStartOfTextline();
if (it.Empty(RIL_WORD)) {
return;
}
do {
it.IterateAndAppendUTF8TextlineText(text);
} while (it.it_->block() != nullptr && !it.IsAtBeginningOf(RIL_PARA));
}
bool ResultIterator::BidiDebug(int min_level) const {
int debug_level = 1;
auto *p = ParamUtils::FindParam<IntParam>("bidi_debug", GlobalParams()->int_params,
tesseract_->params()->int_params);
if (p != nullptr) {
debug_level = (int32_t)(*p);
}
return debug_level >= min_level;
}
}