"Fossies" - the Fresh Open Source Software Archive

Member "gimagereader-3.3.1/qt/src/Recognizer.cc" (28 Jul 2019, 21891 Bytes) of package /linux/privat/gimagereader-3.3.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Recognizer.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.3.0_vs_3.3.1.

    1 /* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-  */
    2 /*
    3  * Recognizer.hh
    4  * Copyright (C) 2013-2019 Sandro Mani <manisandro@gmail.com>
    5  *
    6  * gImageReader is free software: you can redistribute it and/or modify it
    7  * under the terms of the GNU General Public License as published by the
    8  * Free Software Foundation, either version 3 of the License, or
    9  * (at your option) any later version.
   10  *
   11  * gImageReader is distributed in the hope that it will be useful, but
   12  * WITHOUT ANY WARRANTY; without even the implied warranty of
   13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   14  * See the GNU General Public License for more details.
   15  *
   16  * You should have received a copy of the GNU General Public License along
   17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
   18  */
   19 
   20 #include <QClipboard>
   21 #include <QGridLayout>
   22 #include <QIcon>
   23 #include <QLabel>
   24 #include <QMessageBox>
   25 #include <QtSpell.hpp>
   26 #include <algorithm>
   27 #include <csignal>
   28 #include <cstring>
   29 #include <iostream>
   30 #include <sstream>
   31 #define USE_STD_NAMESPACE
   32 #include <tesseract/baseapi.h>
   33 #include <tesseract/ocrclass.h>
   34 #include <tesseract/strngs.h>
   35 #include <tesseract/genericvector.h>
   36 #undef USE_STD_NAMESPACE
   37 #include <QMouseEvent>
   38 #include <unistd.h>
   39 #include <setjmp.h>
   40 
   41 #ifdef Q_OS_WIN
   42 #include <fcntl.h>
   43 #define pipe(fds) _pipe(fds, 5000, _O_BINARY)
   44 #endif
   45 
   46 #include "ConfigSettings.hh"
   47 #include "Displayer.hh"
   48 #include "MainWindow.hh"
   49 #include "OutputEditor.hh"
   50 #include "Recognizer.hh"
   51 #include "Utils.hh"
   52 
   53 class Recognizer::ProgressMonitor : public MainWindow::ProgressMonitor {
   54 public:
   55     ETEXT_DESC desc;
   56 
   57     ProgressMonitor(int nPages) : MainWindow::ProgressMonitor(nPages) {
   58         desc.progress = 0;
   59         desc.cancel = cancelCallback;
   60         desc.cancel_this = this;
   61     }
   62     int getProgress() const override {
   63         QMutexLocker locker(&mMutex);
   64         return 100.0 * ((mProgress + desc.progress / 100.0) / mTotal);
   65     }
   66     static bool cancelCallback(void* instance, int /*words*/) {
   67         ProgressMonitor* monitor = reinterpret_cast<ProgressMonitor*>(instance);
   68         QMutexLocker locker(&monitor->mMutex);
   69         return monitor->mCancelled;
   70     }
   71 };
   72 
   73 
   74 Recognizer::Recognizer(const UI_MainWindow& _ui) :
   75     ui(_ui) {
   76     QAction* currentPageAction = new QAction(_("Current Page"), this);
   77     currentPageAction->setData(static_cast<int>(PageSelection::Current));
   78 
   79     QAction* multiplePagesAction = new QAction(_("Multiple Pages..."), this);
   80     multiplePagesAction->setData(static_cast<int>(PageSelection::Multiple));
   81 
   82     m_menuPages = new QMenu(ui.toolButtonRecognize);
   83     m_menuPages->addAction(currentPageAction);
   84     m_menuPages->addAction(multiplePagesAction);
   85 
   86     m_pagesDialog = new QDialog(MAIN);
   87     m_pagesDialogUi.setupUi(m_pagesDialog);
   88 
   89     m_charListDialog = new QDialog(MAIN);
   90     m_charListDialogUi.setupUi(m_charListDialog);
   91 
   92     ui.toolButtonRecognize->setText(QString("%1\n%2").arg(m_modeLabel).arg(m_langLabel));
   93     ui.menuLanguages->installEventFilter(this);
   94 
   95     connect(ui.toolButtonRecognize, SIGNAL(clicked()), this, SLOT(recognizeButtonClicked()));
   96     connect(currentPageAction, SIGNAL(triggered()), this, SLOT(recognizeCurrentPage()));
   97     connect(multiplePagesAction, SIGNAL(triggered()), this, SLOT(recognizeMultiplePages()));
   98     connect(m_pagesDialogUi.lineEditPageRange, SIGNAL(textChanged(QString)), this, SLOT(clearLineEditPageRangeStyle()));
   99     connect(m_charListDialogUi.radioButtonBlacklist, SIGNAL(toggled(bool)), m_charListDialogUi.lineEditBlacklist, SLOT(setEnabled(bool)));
  100     connect(m_charListDialogUi.radioButtonWhitelist, SIGNAL(toggled(bool)), m_charListDialogUi.lineEditWhitelist, SLOT(setEnabled(bool)));
  101 
  102     ADD_SETTING(VarSetting<QString>("language", "eng:en_EN"));
  103     ADD_SETTING(ComboSetting("ocrregionstrategy", m_pagesDialogUi.comboBoxRecognitionArea, 0));
  104     ADD_SETTING(SwitchSetting("ocraddsourcefilename", m_pagesDialogUi.checkBoxPrependFilename));
  105     ADD_SETTING(SwitchSetting("ocraddsourcepage", m_pagesDialogUi.checkBoxPrependPage));
  106     ADD_SETTING(LineEditSetting("ocrcharwhitelist", m_charListDialogUi.lineEditWhitelist));
  107     ADD_SETTING(LineEditSetting("ocrcharblacklist", m_charListDialogUi.lineEditBlacklist));
  108     ADD_SETTING(SwitchSetting("ocrblacklistenabled", m_charListDialogUi.radioButtonBlacklist, true));
  109     ADD_SETTING(SwitchSetting("ocrwhitelistenabled", m_charListDialogUi.radioButtonWhitelist, false));
  110     ADD_SETTING(VarSetting<int>("psm", 6));
  111 }
  112 
  113 QStringList Recognizer::getAvailableLanguages() const {
  114     tesseract::TessBaseAPI tess = initTesseract();
  115     GenericVector<STRING> availLanguages;
  116     tess.GetAvailableLanguagesAsVector(&availLanguages);
  117     QStringList result;
  118     for(int i = 0; i < availLanguages.size(); ++i) {
  119         result.append(availLanguages[i].string());
  120     }
  121     qSort(result.begin(), result.end(), [](const QString & s1, const QString & s2) {
  122         bool s1Script = s1.startsWith("script") || s1.left(1) == s1.left(1).toUpper();
  123         bool s2Script = s2.startsWith("script") || s2.left(1) == s2.left(1).toUpper();
  124         if(s1Script != s2Script) {
  125             return !s1Script;
  126         } else {
  127             return s1 < s2;
  128         }
  129     });
  130     return result;
  131 }
  132 
  133 tesseract::TessBaseAPI Recognizer::initTesseract(const char* language, bool* ok) const {
  134     // unfortunately tesseract creates deliberate aborts when an error occurs
  135     std::signal(SIGABRT, MainWindow::tesseractCrash);
  136     QByteArray current = setlocale(LC_ALL, NULL);
  137     setlocale(LC_ALL, "C");
  138     tesseract::TessBaseAPI tess;
  139     int ret = tess.Init(nullptr, language);
  140     setlocale(LC_NUMERIC, current.constData());
  141 
  142     if(ok) {
  143         *ok = ret != -1;
  144     }
  145     return tess;
  146 }
  147 
  148 void Recognizer::updateLanguagesMenu() {
  149     ui.menuLanguages->clear();
  150     delete m_langMenuRadioGroup;
  151     m_langMenuRadioGroup = new QActionGroup(this);
  152     delete m_langMenuCheckGroup;
  153     m_langMenuCheckGroup = new QActionGroup(this);
  154     m_langMenuCheckGroup->setExclusive(false);
  155     delete m_psmCheckGroup;
  156     m_psmCheckGroup = new QActionGroup(this);
  157     connect(m_psmCheckGroup, SIGNAL(triggered(QAction*)), this, SLOT(psmSelected(QAction*)));
  158     m_menuMultilanguage = nullptr;
  159     m_curLang = Config::Lang();
  160     QAction* curitem = nullptr;
  161     QAction* activeitem = nullptr;
  162     bool haveOsd = false;
  163 
  164     QStringList parts = ConfigSettings::get<VarSetting<QString>>("language")->getValue().split(":");
  165     Config::Lang curlang = {parts.empty() ? "eng" : parts[0], parts.size() < 2 ? "" : parts[1], parts.size() < 3 ? "" : parts[2]};
  166 
  167     QList<QString> dicts = QtSpell::Checker::getLanguageList();
  168 
  169     QStringList availLanguages = getAvailableLanguages();
  170 
  171     if(availLanguages.empty()) {
  172         QMessageBox::warning(MAIN, _("No languages available"), _("No tesseract languages are available for use. Recognition will not work."));
  173         m_langLabel = "";
  174         ui.toolButtonRecognize->setText(QString("%1\n%2").arg(m_modeLabel).arg(m_langLabel));
  175     }
  176 
  177     // Add menu items for languages, with spelling submenu if available
  178     for(const QString& langprefix : availLanguages) {
  179         if(langprefix == "osd") {
  180             haveOsd = true;
  181             continue;
  182         }
  183         Config::Lang lang = {langprefix, QString(), QString()};
  184         if(!MAIN->getConfig()->searchLangSpec(lang)) {
  185             lang.name = lang.prefix;
  186         }
  187         QList<QString> spelldicts;
  188         if(!lang.code.isEmpty()) {
  189             for(const QString& dict : dicts) {
  190                 if(dict.left(2) == lang.code.left(2)) {
  191                     spelldicts.append(dict);
  192                 }
  193             }
  194             std::sort(spelldicts.begin(), spelldicts.end());
  195         }
  196         if(!spelldicts.empty()) {
  197             QAction* item = new QAction(lang.name, ui.menuLanguages);
  198             QMenu* submenu = new QMenu();
  199             for(const QString& dict : spelldicts) {
  200                 Config::Lang itemlang = {lang.prefix, dict, lang.name};
  201                 curitem = new QAction(QtSpell::Checker::decodeLanguageCode(dict), m_langMenuRadioGroup);
  202                 curitem->setCheckable(true);
  203                 curitem->setData(QVariant::fromValue(itemlang));
  204                 connect(curitem, SIGNAL(triggered()), this, SLOT(setLanguage()));
  205                 if(curlang.prefix == lang.prefix && (
  206                             curlang.code == dict ||
  207                             (!activeitem && (curlang.code == dict.left(2) || curlang.code.isEmpty())))) {
  208                     curlang = itemlang;
  209                     activeitem = curitem;
  210                 }
  211                 submenu->addAction(curitem);
  212             }
  213             item->setMenu(submenu);
  214             ui.menuLanguages->addAction(item);
  215         } else {
  216             curitem = new QAction(lang.name, m_langMenuRadioGroup);
  217             curitem->setCheckable(true);
  218             curitem->setData(QVariant::fromValue(lang));
  219             connect(curitem, SIGNAL(triggered()), this, SLOT(setLanguage()));
  220             if(curlang.prefix == lang.prefix) {
  221                 curlang = lang;
  222                 activeitem = curitem;
  223             }
  224             ui.menuLanguages->addAction(curitem);
  225         }
  226     }
  227 
  228     // Add multilanguage menu
  229     bool isMultilingual = false;
  230     if(!availLanguages.isEmpty()) {
  231         ui.menuLanguages->addSeparator();
  232         m_multilingualAction = new QAction(_("Multilingual"), m_langMenuRadioGroup);
  233         m_multilingualAction->setCheckable(true);
  234         m_menuMultilanguage = new QMenu();
  235         isMultilingual = curlang.prefix.contains('+');
  236         QStringList sellangs = curlang.prefix.split('+', QString::SkipEmptyParts);
  237         for(const QString& langprefix : availLanguages) {
  238             if(langprefix == "osd") {
  239                 continue;
  240             }
  241             Config::Lang lang = {langprefix, "", ""};
  242             if(!MAIN->getConfig()->searchLangSpec(lang)) {
  243                 lang.name = lang.prefix;
  244             }
  245             QAction* item = new QAction(lang.name, m_langMenuCheckGroup);
  246             item->setCheckable(true);
  247             item->setData(QVariant::fromValue(lang.prefix));
  248             item->setChecked(isMultilingual && sellangs.contains(lang.prefix));
  249             connect(item, SIGNAL(triggered()), this, SLOT(setMultiLanguage()));
  250             m_menuMultilanguage->addAction(item);
  251         }
  252         m_menuMultilanguage->installEventFilter(this);
  253         m_multilingualAction->setMenu(m_menuMultilanguage);
  254         ui.menuLanguages->addAction(m_multilingualAction);
  255     }
  256     if(isMultilingual) {
  257         activeitem = m_multilingualAction;
  258         setMultiLanguage();
  259     } else if(activeitem == nullptr) {
  260         activeitem = curitem;
  261     }
  262     if(activeitem) {
  263         activeitem->trigger();
  264     }
  265 
  266     // Add PSM items
  267     ui.menuLanguages->addSeparator();
  268     QMenu* psmMenu = new QMenu();
  269     int activePsm = ConfigSettings::get<VarSetting<int>>("psm")->getValue();
  270 
  271     struct PsmEntry {
  272         QString label;
  273         tesseract::PageSegMode psmMode;
  274         bool requireOsd;
  275     };
  276     QVector<PsmEntry> psmModes = {
  277         PsmEntry{_("Automatic page segmentation"), tesseract::PSM_AUTO, false},
  278         PsmEntry{_("Page segmentation with orientation and script detection"), tesseract::PSM_AUTO_OSD, true},
  279         PsmEntry{_("Assume single column of text"), tesseract::PSM_SINGLE_COLUMN, false},
  280         PsmEntry{_("Assume single block of vertically aligned text"), tesseract::PSM_SINGLE_BLOCK_VERT_TEXT, false},
  281         PsmEntry{_("Assume a single uniform block of text"), tesseract::PSM_SINGLE_BLOCK, false},
  282         PsmEntry{_("Assume a line of text"), tesseract::PSM_SINGLE_LINE, false},
  283         PsmEntry{_("Assume a single word"), tesseract::PSM_SINGLE_WORD, false},
  284         PsmEntry{_("Assume a single word in a circle"), tesseract::PSM_CIRCLE_WORD, false},
  285         PsmEntry{_("Sparse text in no particular order"), tesseract::PSM_SPARSE_TEXT, false},
  286         PsmEntry{_("Sparse text with orientation and script detection"), tesseract::PSM_SPARSE_TEXT_OSD, true}
  287     };
  288     for(const auto& entry : psmModes) {
  289         QAction* item = psmMenu->addAction(entry.label);
  290         item->setData(entry.psmMode);
  291         item->setEnabled(!entry.requireOsd || haveOsd);
  292         item->setCheckable(true);
  293         item->setChecked(activePsm == entry.psmMode);
  294         m_psmCheckGroup->addAction(item);
  295     }
  296 
  297     QAction* psmAction = new QAction(_("Page segmentation mode"), ui.menuLanguages);
  298     psmAction->setMenu(psmMenu);
  299     ui.menuLanguages->addAction(psmAction);
  300     ui.menuLanguages->addAction(_("Character whitelist / blacklist..."), this, SLOT(manageCharacterLists()));
  301 
  302 
  303     // Add installer item
  304     ui.menuLanguages->addSeparator();
  305     ui.menuLanguages->addAction(_("Manage languages..."), MAIN, SLOT(manageLanguages()));
  306 }
  307 
  308 void Recognizer::setLanguage() {
  309     QAction* item = qobject_cast<QAction*>(QObject::sender());
  310     if(item->isChecked()) {
  311         Config::Lang lang = item->data().value<Config::Lang>();
  312         if(!lang.code.isEmpty()) {
  313             m_langLabel = QString("%1 (%2)").arg(lang.name, lang.code);
  314         } else {
  315             m_langLabel = QString("%1").arg(lang.name);
  316         }
  317         ui.toolButtonRecognize->setText(QString("%1\n%2").arg(m_modeLabel).arg(m_langLabel));
  318         m_curLang = lang;
  319         ConfigSettings::get<VarSetting<QString>>("language")->setValue(lang.prefix + ":" + lang.code);
  320         emit languageChanged(m_curLang);
  321     }
  322 }
  323 
  324 void Recognizer::setMultiLanguage() {
  325     m_multilingualAction->setChecked(true);
  326     QString langs;
  327     for(QAction* action : m_langMenuCheckGroup->actions()) {
  328         if(action->isChecked()) {
  329             langs += action->data().toString() + "+";
  330         }
  331     }
  332     if(langs.isEmpty()) {
  333         langs = "eng+";
  334     }
  335     langs = langs.left(langs.length() - 1);
  336     m_langLabel = langs;
  337     ui.toolButtonRecognize->setText(QString("%1\n%2").arg(m_modeLabel).arg(m_langLabel));
  338     m_curLang = {langs, "", "Multilingual"};
  339     ConfigSettings::get<VarSetting<QString>>("language")->setValue(langs + ":");
  340     emit languageChanged(m_curLang);
  341 }
  342 
  343 void Recognizer::setRecognizeMode(const QString& mode) {
  344     m_modeLabel = mode;
  345     ui.toolButtonRecognize->setText(QString("%1\n%2").arg(m_modeLabel).arg(m_langLabel));
  346 }
  347 
  348 void Recognizer::clearLineEditPageRangeStyle() {
  349     qobject_cast<QLineEdit*>(QObject::sender())->setStyleSheet("");
  350 }
  351 
  352 void Recognizer::psmSelected(QAction* action) {
  353     ConfigSettings::get<VarSetting<int>>("psm")->setValue(action->data().toInt());
  354 }
  355 
  356 void Recognizer::manageCharacterLists() {
  357     m_charListDialog->exec();
  358 }
  359 
  360 QList<int> Recognizer::selectPages(bool& autodetectLayout) {
  361     int nPages = MAIN->getDisplayer()->getNPages();
  362 
  363     m_pagesDialogUi.lineEditPageRange->setText(QString("1-%1").arg(nPages));
  364     m_pagesDialogUi.lineEditPageRange->setFocus();
  365     m_pagesDialogUi.labelRecognitionArea->setVisible(MAIN->getDisplayer()->allowAutodetectOCRAreas());
  366     m_pagesDialogUi.comboBoxRecognitionArea->setVisible(MAIN->getDisplayer()->allowAutodetectOCRAreas());
  367     m_pagesDialogUi.groupBoxPrepend->setVisible(MAIN->getDisplayer()->allowAutodetectOCRAreas());
  368 
  369     m_pagesDialogUi.comboBoxRecognitionArea->setItemText(0, MAIN->getDisplayer()->hasMultipleOCRAreas() ? _("Current selection") : _("Entire page"));
  370 
  371     QRegExp validateRegEx("^[\\d,\\-\\s]+$");
  372     QList<int> pages;
  373     while(m_pagesDialog->exec() == QDialog::Accepted) {
  374         pages.clear();
  375         QString text = m_pagesDialogUi.lineEditPageRange->text();
  376         if(validateRegEx.indexIn(text) != -1) {
  377             text.replace(QRegExp("\\s+"), "");
  378             for(const QString& block : text.split(',', QString::SkipEmptyParts)) {
  379                 QStringList ranges = block.split('-', QString::SkipEmptyParts);
  380                 if(ranges.size() == 1) {
  381                     int page = ranges[0].toInt();
  382                     if(page > 0 && page <= nPages) {
  383                         pages.append(page);
  384                     }
  385                 } else if(ranges.size() == 2) {
  386                     int start = std::max(1, ranges[0].toInt());
  387                     int end = std::min(nPages, ranges[1].toInt());
  388                     for(int page = start; page <= end; ++page) {
  389                         pages.append(page);
  390                     }
  391                 } else {
  392                     pages.clear();
  393                     break;
  394                 }
  395             }
  396         }
  397         if(pages.empty()) {
  398             m_pagesDialogUi.lineEditPageRange->setStyleSheet("background: #FF7777; color: #FFFFFF;");
  399         } else {
  400             break;
  401         }
  402     }
  403     std::sort(pages.begin(), pages.end());
  404     autodetectLayout = m_pagesDialogUi.comboBoxRecognitionArea->isVisible() ? m_pagesDialogUi.comboBoxRecognitionArea->currentIndex() == 1 : false;
  405     return pages;
  406 }
  407 
  408 void Recognizer::recognizeButtonClicked() {
  409     int nPages = MAIN->getDisplayer()->getNPages();
  410     if(nPages == 1) {
  411         recognize({MAIN->getDisplayer()->getCurrentPage()});
  412     } else {
  413         ui.toolButtonRecognize->setCheckable(true);
  414         ui.toolButtonRecognize->setChecked(true);
  415         m_menuPages->popup(ui.toolButtonRecognize->mapToGlobal(QPoint(0, ui.toolButtonRecognize->height())));
  416         ui.toolButtonRecognize->setChecked(false);
  417         ui.toolButtonRecognize->setCheckable(false);
  418     }
  419 }
  420 
  421 void Recognizer::recognizeCurrentPage() {
  422     recognize({MAIN->getDisplayer()->getCurrentPage()});
  423 }
  424 
  425 void Recognizer::recognizeMultiplePages() {
  426     bool autodetectLayout = false;
  427     QList<int> pages = selectPages(autodetectLayout);
  428     recognize(pages, autodetectLayout);
  429 }
  430 
  431 void Recognizer::recognize(const QList<int>& pages, bool autodetectLayout) {
  432     bool prependFile = pages.size() > 1 && ConfigSettings::get<SwitchSetting>("ocraddsourcefilename")->getValue();
  433     bool prependPage = pages.size() > 1 && ConfigSettings::get<SwitchSetting>("ocraddsourcepage")->getValue();
  434     bool ok = false;
  435     tesseract::TessBaseAPI tess = initTesseract(m_curLang.prefix.toLocal8Bit().constData(), &ok);
  436     if(ok) {
  437         QString failed;
  438         tess.SetPageSegMode(static_cast<tesseract::PageSegMode>(m_psmCheckGroup->checkedAction()->data().toInt()));
  439         if(m_charListDialogUi.radioButtonWhitelist->isChecked()) {
  440             tess.SetVariable("tessedit_char_whitelist", m_charListDialogUi.lineEditWhitelist->text().toLocal8Bit());
  441         }
  442         if(m_charListDialogUi.radioButtonBlacklist->isChecked()) {
  443             tess.SetVariable("tessedit_char_blacklist", m_charListDialogUi.lineEditBlacklist->text().toLocal8Bit());
  444         }
  445         OutputEditor::ReadSessionData* readSessionData = MAIN->getOutputEditor()->initRead(tess);
  446         ProgressMonitor monitor(pages.size());
  447         MAIN->showProgress(&monitor);
  448         Utils::busyTask([&] {
  449             int npages = pages.size();
  450             int idx = 0;
  451             QString prevFile;
  452             for(int page : pages) {
  453                 monitor.desc.progress = 0;
  454                 ++idx;
  455                 QMetaObject::invokeMethod(MAIN, "pushState", Qt::QueuedConnection, Q_ARG(MainWindow::State, MainWindow::State::Busy), Q_ARG(QString, _("Recognizing page %1 (%2 of %3)").arg(page).arg(idx).arg(npages)));
  456 
  457                 PageData pageData;
  458                 pageData.success = false;
  459                 QMetaObject::invokeMethod(this, "setPage", Qt::BlockingQueuedConnection, Q_RETURN_ARG(PageData, pageData), Q_ARG(int, page), Q_ARG(bool, autodetectLayout));
  460                 if(!pageData.success) {
  461                     failed.append(_("\n- Page %1: failed to render page").arg(page));
  462                     MAIN->getOutputEditor()->readError(_("\n[Failed to recognize page %1]\n"), readSessionData);
  463                     continue;
  464                 }
  465                 readSessionData->file = pageData.filename;
  466                 readSessionData->page = pageData.page;
  467                 readSessionData->angle = pageData.angle;
  468                 readSessionData->resolution = pageData.resolution;
  469                 bool firstChunk = true;
  470                 bool newFile = readSessionData->file != prevFile;
  471                 prevFile = readSessionData->file;
  472                 for(const QImage& image : pageData.ocrAreas) {
  473                     readSessionData->prependPage = prependPage && firstChunk;
  474                     readSessionData->prependFile = prependFile && (readSessionData->prependPage || newFile);
  475                     firstChunk = false;
  476                     newFile = false;
  477                     tess.SetImage(image.bits(), image.width(), image.height(), 4, image.bytesPerLine());
  478                     tess.SetSourceResolution(MAIN->getDisplayer()->getCurrentResolution());
  479                     tess.Recognize(&monitor.desc);
  480                     if(!monitor.cancelled()) {
  481                         MAIN->getOutputEditor()->read(tess, readSessionData);
  482                     }
  483                 }
  484                 QMetaObject::invokeMethod(MAIN, "popState", Qt::QueuedConnection);
  485                 monitor.increaseProgress();
  486                 if(monitor.cancelled()) {
  487                     break;
  488                 }
  489             }
  490             return true;
  491         }, _("Recognizing..."));
  492         MAIN->hideProgress();
  493         MAIN->getOutputEditor()->finalizeRead(readSessionData);
  494         if(!failed.isEmpty()) {
  495             QMessageBox::critical(MAIN, _("Recognition errors occurred"), _("The following errors occurred:%1").arg(failed));
  496         }
  497     }
  498 }
  499 
  500 bool Recognizer::recognizeImage(const QImage& image, OutputDestination dest) {
  501     bool ok = false;
  502     tesseract::TessBaseAPI tess = initTesseract(m_curLang.prefix.toLocal8Bit().constData(), &ok);
  503     if(!ok) {
  504         QMessageBox::critical(MAIN, _("Recognition errors occurred"), _("Failed to initialize tesseract"));
  505         return false;
  506     }
  507     tess.SetImage(image.bits(), image.width(), image.height(), 4, image.bytesPerLine());
  508     ProgressMonitor monitor(1);
  509     MAIN->showProgress(&monitor);
  510     if(dest == OutputDestination::Buffer) {
  511         OutputEditor::ReadSessionData* readSessionData = MAIN->getOutputEditor()->initRead(tess);
  512         readSessionData->file = MAIN->getDisplayer()->getCurrentImage(readSessionData->page);
  513         readSessionData->angle = MAIN->getDisplayer()->getCurrentAngle();
  514         readSessionData->resolution = MAIN->getDisplayer()->getCurrentResolution();
  515         Utils::busyTask([&] {
  516             tess.Recognize(&monitor.desc);
  517             if(!monitor.cancelled()) {
  518                 MAIN->getOutputEditor()->read(tess, readSessionData);
  519             }
  520             return true;
  521         }, _("Recognizing..."));
  522         MAIN->getOutputEditor()->finalizeRead(readSessionData);
  523     } else if(dest == OutputDestination::Clipboard) {
  524         QString output;
  525         if(Utils::busyTask([&] {
  526         tess.Recognize(&monitor.desc);
  527             if(!monitor.cancelled()) {
  528                 char* text = tess.GetUTF8Text();
  529                 output = QString::fromUtf8(text);
  530                 delete[] text;
  531                 return true;
  532             }
  533             return false;
  534         }, _("Recognizing..."))) {
  535             QApplication::clipboard()->setText(output);
  536         }
  537     }
  538     MAIN->hideProgress();
  539     return true;
  540 }
  541 
  542 Recognizer::PageData Recognizer::setPage(int page, bool autodetectLayout) {
  543     PageData pageData;
  544     pageData.success = MAIN->getDisplayer()->setup(&page);
  545     if(pageData.success) {
  546         if(autodetectLayout) {
  547             MAIN->getDisplayer()->autodetectOCRAreas();
  548         }
  549         pageData.filename = MAIN->getDisplayer()->getCurrentImage(pageData.page);
  550         pageData.angle = MAIN->getDisplayer()->getCurrentAngle();
  551         pageData.resolution = MAIN->getDisplayer()->getCurrentResolution();
  552         pageData.ocrAreas = MAIN->getDisplayer()->getOCRAreas();
  553     }
  554     return pageData;
  555 }
  556 
  557 bool Recognizer::eventFilter(QObject* obj, QEvent* ev) {
  558     if(obj == ui.menuLanguages && ev->type() == QEvent::MouseButtonPress) {
  559         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(ev);
  560         QAction* actionAtPos = ui.menuLanguages->actionAt(mouseEvent->pos());
  561         if(actionAtPos && actionAtPos == m_multilingualAction) {
  562             m_multilingualAction->toggle();
  563             if(m_multilingualAction->isChecked()) {
  564                 setMultiLanguage();
  565             }
  566             return true;
  567         }
  568     } else if(obj == m_menuMultilanguage && (ev->type() == QEvent::MouseButtonPress || ev->type() == QEvent::MouseButtonRelease)) {
  569         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(ev);
  570         QAction* action = m_menuMultilanguage->actionAt(mouseEvent->pos());
  571         if(action) {
  572             if(ev->type() == QEvent::MouseButtonRelease) {
  573                 action->trigger();
  574             }
  575             return true;
  576         }
  577     }
  578     return QObject::eventFilter(obj, ev);
  579 }