diff --git a/android/ocr/OcrUtils.cpp b/android/ocr/OcrUtils.cpp index 343d698..eac82eb 100644 --- a/android/ocr/OcrUtils.cpp +++ b/android/ocr/OcrUtils.cpp @@ -11,34 +11,37 @@ namespace { -// Возвращает путь, который надо передавать в `TessBaseAPI::Init`, либо пустую -// строку, если ни в одной из ожидаемых раскладок rus.traineddata не найден. -// На Windows (vcpkg) Tesseract 5 без подкаталога `tessdata/` иногда падает -// молча — пробуем оба варианта и логируем результат. -QString resolveTessdataPath() { - static QString cached; - static bool resolved = false; - if (resolved) return cached; - resolved = true; +// Кэш байт rus.traineddata. Грузим файл через Qt (Unicode-aware пути) и +// отдаём Tesseract в виде in-memory blob через Init(data, size, lang, ...). +// Это обходит проблему, что на Windows Tesseract открывает traineddata через +// `fopen`, который использует ANSI-кодировку (cp1251 на русской системе). +// Если путь к exe содержит кириллицу (например, OneDrive\Рабочий стол) — +// fopen не находит файл и Init возвращает -1. Memory-init не зависит от пути. +const QByteArray &tessdataBytes() { + static QByteArray bytes; + static bool tried = false; + if (tried) return bytes; + tried = true; const QString appDir = QCoreApplication::applicationDirPath(); const QStringList candidates = { - appDir + "/tessdata", // канонический путь - appDir, // legacy: traineddata лежат рядом с exe + appDir + "/tessdata/rus.traineddata", // канонический путь + appDir + "/rus.traineddata", // legacy: рядом с exe }; - for (const QString &dir : candidates) { - const QString file = dir + "/rus.traineddata"; - if (QFile::exists(file)) { - // Tesseract принимает datapath как РОДИТЕЛЯ tessdata-каталога, - // но также понимает каталог, где лежат сами .traineddata. - cached = QDir::toNativeSeparators(dir); - qDebug() << "[OcrUtils] tessdata located at" << cached; - return cached; + for (const QString &path : candidates) { + QFile f(path); + if (!f.open(QIODevice::ReadOnly)) continue; + bytes = f.readAll(); + f.close(); + if (!bytes.isEmpty()) { + qDebug() << "[OcrUtils] loaded rus.traineddata from" << path + << "(" << bytes.size() << "bytes)"; + return bytes; } } qWarning() << "[OcrUtils] rus.traineddata NOT FOUND. Checked:" << candidates << "— OCR будет работать в fallback-режиме без поиска текста."; - return {}; + return bytes; } @@ -128,17 +131,20 @@ QPoint findTextCenter(const QByteArray &png, if (inverted) prepared = inverted; } - const QString tessdata = resolveTessdataPath(); - if (tessdata.isEmpty()) { + const QByteArray &td = tessdataBytes(); + if (td.isEmpty()) { if (inverted) pixDestroy(&inverted); pixDestroy(&gray); return {-1, -1}; } auto *api = new tesseract::TessBaseAPI(); - if (api->Init(tessdata.toUtf8().constData(), "rus")) { - qWarning() << "[OcrUtils] Tesseract init failed for datapath" << tessdata - << "— rus.traineddata присутствует, но не загрузился" - << "(возможно, повреждён или несовместимая версия)"; + // Init(data, size, lang, ...) — грузим traineddata из памяти, чтобы + // обойти fopen на путях с кириллицей (Windows ANSI-API). + if (api->Init(td.constData(), td.size(), "rus", + tesseract::OEM_DEFAULT, + nullptr, 0, nullptr, nullptr, false, nullptr)) { + qWarning() << "[OcrUtils] Tesseract Init(memory) failed —" + << "traineddata повреждён или несовместимая версия"; delete api; if (inverted) pixDestroy(&inverted); pixDestroy(&gray); diff --git a/tessdata/eng.traineddata b/tessdata/eng.traineddata new file mode 100644 index 0000000..f4744c2 Binary files /dev/null and b/tessdata/eng.traineddata differ diff --git a/tessdata/rus.traineddata b/tessdata/rus.traineddata new file mode 100644 index 0000000..8b71c2d Binary files /dev/null and b/tessdata/rus.traineddata differ