/src/tesseract/src/ccutil/tessdatamanager.cpp
Line | Count | Source |
1 | | /////////////////////////////////////////////////////////////////////// |
2 | | // File: tessdatamanager.cpp |
3 | | // Description: Functions to handle loading/combining tesseract data files. |
4 | | // Author: Daria Antonova |
5 | | // |
6 | | // (C) Copyright 2009, Google Inc. |
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 | | |
19 | | #ifdef HAVE_CONFIG_H |
20 | | # include "config_auto.h" |
21 | | #endif |
22 | | |
23 | | #include "tessdatamanager.h" |
24 | | |
25 | | #include <cstdio> |
26 | | #include <string> |
27 | | |
28 | | #if defined(HAVE_LIBARCHIVE) |
29 | | # include <archive.h> |
30 | | # include <archive_entry.h> |
31 | | #endif |
32 | | |
33 | | #include <tesseract/version.h> |
34 | | #include "errcode.h" |
35 | | #include "helpers.h" |
36 | | #include "params.h" |
37 | | #include "serialis.h" |
38 | | #include "tprintf.h" |
39 | | |
40 | | namespace tesseract { |
41 | | |
42 | 0 | TessdataManager::TessdataManager() : reader_(nullptr), is_loaded_(false), swap_(false) { |
43 | 0 | SetVersionString(TESSERACT_VERSION_STR); |
44 | 0 | } |
45 | | |
46 | | TessdataManager::TessdataManager(FileReader reader) |
47 | 4 | : reader_(reader), is_loaded_(false), swap_(false) { |
48 | 4 | SetVersionString(TESSERACT_VERSION_STR); |
49 | 4 | } |
50 | | |
51 | | // Lazily loads from the given filename. Won't actually read the file |
52 | | // until it needs it. |
53 | 0 | void TessdataManager::LoadFileLater(const char *data_file_name) { |
54 | 0 | Clear(); |
55 | 0 | data_file_name_ = data_file_name; |
56 | 0 | } |
57 | | |
58 | | #if defined(HAVE_LIBARCHIVE) |
59 | | bool TessdataManager::LoadArchiveFile(const char *filename) { |
60 | | bool result = false; |
61 | | archive *a = archive_read_new(); |
62 | | if (a != nullptr) { |
63 | | archive_read_support_filter_all(a); |
64 | | archive_read_support_format_all(a); |
65 | | if (archive_read_open_filename(a, filename, 8192) == ARCHIVE_OK) { |
66 | | archive_entry *ae; |
67 | | while (archive_read_next_header(a, &ae) == ARCHIVE_OK) { |
68 | | const char *component = archive_entry_pathname(ae); |
69 | | if (component != nullptr) { |
70 | | TessdataType type; |
71 | | if (TessdataTypeFromFileName(component, &type)) { |
72 | | int64_t size = archive_entry_size(ae); |
73 | | if (size > 0) { |
74 | | entries_[type].resize(size); |
75 | | if (archive_read_data(a, &entries_[type][0], size) == size) { |
76 | | is_loaded_ = true; |
77 | | } |
78 | | } |
79 | | } |
80 | | } |
81 | | } |
82 | | result = is_loaded_; |
83 | | } |
84 | | archive_read_free(a); |
85 | | } |
86 | | return result; |
87 | | } |
88 | | #endif |
89 | | |
90 | 4 | bool TessdataManager::Init(const char *data_file_name) { |
91 | 4 | std::vector<char> data; |
92 | 4 | if (reader_ == nullptr) { |
93 | | #if defined(HAVE_LIBARCHIVE) |
94 | | if (LoadArchiveFile(data_file_name)) { |
95 | | return true; |
96 | | } |
97 | | #endif |
98 | 4 | if (!LoadDataFromFile(data_file_name, &data)) { |
99 | 0 | return false; |
100 | 0 | } |
101 | 4 | } else { |
102 | 0 | if (!(*reader_)(data_file_name, &data)) { |
103 | 0 | return false; |
104 | 0 | } |
105 | 0 | } |
106 | 4 | return LoadMemBuffer(data_file_name, &data[0], data.size()); |
107 | 4 | } |
108 | | |
109 | | // Loads from the given memory buffer as if a file. |
110 | 4 | bool TessdataManager::LoadMemBuffer(const char *name, const char *data, int size) { |
111 | | // TODO: This method supports only the proprietary file format. |
112 | 4 | if (size < 0) { |
113 | 0 | return false; |
114 | 0 | } |
115 | 4 | Clear(); |
116 | 4 | data_file_name_ = name; |
117 | 4 | TFile fp; |
118 | 4 | fp.Open(data, size); |
119 | 4 | uint32_t num_entries; |
120 | 4 | if (!fp.DeSerialize(&num_entries)) { |
121 | 0 | return false; |
122 | 0 | } |
123 | 4 | swap_ = num_entries > kMaxNumTessdataEntries; |
124 | 4 | fp.set_swap(swap_); |
125 | 4 | if (swap_) { |
126 | 0 | ReverseN(&num_entries, sizeof(num_entries)); |
127 | 0 | } |
128 | 4 | if (num_entries > kMaxNumTessdataEntries) { |
129 | 0 | return false; |
130 | 0 | } |
131 | | // TODO: optimize (no init required). |
132 | 4 | std::vector<int64_t> offset_table(num_entries); |
133 | 4 | if (!fp.DeSerialize(&offset_table[0], num_entries)) { |
134 | 0 | return false; |
135 | 0 | } |
136 | 100 | for (unsigned i = 0; i < num_entries && i < TESSDATA_NUM_ENTRIES; ++i) { |
137 | 96 | if (offset_table[i] >= 0) { |
138 | 72 | if (offset_table[i] > size) { |
139 | 0 | return false; |
140 | 0 | } |
141 | 72 | int64_t entry_size = size - offset_table[i]; |
142 | 72 | unsigned j = i + 1; |
143 | 92 | while (j < num_entries && offset_table[j] == -1) { |
144 | 20 | ++j; |
145 | 20 | } |
146 | 72 | if (j < num_entries) { |
147 | 68 | if (offset_table[j] < 0 || offset_table[j] > size) { |
148 | 0 | return false; |
149 | 0 | } |
150 | 68 | entry_size = offset_table[j] - offset_table[i]; |
151 | 68 | } |
152 | 72 | if (entry_size < 0) { |
153 | 0 | return false; |
154 | 0 | } |
155 | 72 | entries_[i].resize(entry_size); |
156 | 72 | if (entry_size > 0 && !fp.DeSerialize(&entries_[i][0], entry_size)) { |
157 | 0 | return false; |
158 | 0 | } |
159 | 72 | } |
160 | 96 | } |
161 | 4 | if (entries_[TESSDATA_VERSION].empty()) { |
162 | 0 | SetVersionString("Pre-4.0.0"); |
163 | 0 | } |
164 | 4 | is_loaded_ = true; |
165 | 4 | return true; |
166 | 4 | } |
167 | | |
168 | | // Overwrites a single entry of the given type. |
169 | 0 | void TessdataManager::OverwriteEntry(TessdataType type, const char *data, int size) { |
170 | 0 | is_loaded_ = true; |
171 | 0 | entries_[type].resize(size); |
172 | 0 | memcpy(&entries_[type][0], data, size); |
173 | 0 | } |
174 | | |
175 | | // Saves to the given filename. |
176 | 0 | bool TessdataManager::SaveFile(const char *filename, FileWriter writer) const { |
177 | | // TODO: This method supports only the proprietary file format. |
178 | 0 | ASSERT_HOST(is_loaded_); |
179 | 0 | std::vector<char> data; |
180 | 0 | Serialize(&data); |
181 | 0 | if (writer == nullptr) { |
182 | 0 | return SaveDataToFile(data, filename); |
183 | 0 | } else { |
184 | 0 | return (*writer)(data, filename); |
185 | 0 | } |
186 | 0 | } |
187 | | |
188 | | // Serializes to the given vector. |
189 | 0 | void TessdataManager::Serialize(std::vector<char> *data) const { |
190 | | // TODO: This method supports only the proprietary file format. |
191 | 0 | ASSERT_HOST(is_loaded_); |
192 | | // Compute the offset_table and total size. |
193 | 0 | int64_t offset_table[TESSDATA_NUM_ENTRIES]; |
194 | 0 | int64_t offset = sizeof(int32_t) + sizeof(offset_table); |
195 | 0 | for (unsigned i = 0; i < TESSDATA_NUM_ENTRIES; ++i) { |
196 | 0 | if (entries_[i].empty()) { |
197 | 0 | offset_table[i] = -1; |
198 | 0 | } else { |
199 | 0 | offset_table[i] = offset; |
200 | 0 | offset += entries_[i].size(); |
201 | 0 | } |
202 | 0 | } |
203 | 0 | data->resize(offset, 0); |
204 | 0 | int32_t num_entries = TESSDATA_NUM_ENTRIES; |
205 | 0 | TFile fp; |
206 | 0 | fp.OpenWrite(data); |
207 | 0 | fp.Serialize(&num_entries); |
208 | 0 | fp.Serialize(&offset_table[0], countof(offset_table)); |
209 | 0 | for (const auto &entry : entries_) { |
210 | 0 | if (!entry.empty()) { |
211 | 0 | fp.Serialize(&entry[0], entry.size()); |
212 | 0 | } |
213 | 0 | } |
214 | 0 | } |
215 | | |
216 | | // Resets to the initial state, keeping the reader. |
217 | 8 | void TessdataManager::Clear() { |
218 | 192 | for (auto &entry : entries_) { |
219 | 192 | entry.clear(); |
220 | 192 | } |
221 | 8 | is_loaded_ = false; |
222 | 8 | } |
223 | | |
224 | | // Prints a directory of contents. |
225 | 0 | void TessdataManager::Directory() const { |
226 | 0 | printf("Version:%s\n", VersionString().c_str()); |
227 | 0 | auto offset = TESSDATA_NUM_ENTRIES * sizeof(int64_t); |
228 | 0 | for (unsigned i = 0; i < TESSDATA_NUM_ENTRIES; ++i) { |
229 | 0 | if (!entries_[i].empty()) { |
230 | 0 | printf("%u:%s:size=%zu, offset=%zu\n", i, kTessdataFileSuffixes[i], entries_[i].size(), |
231 | 0 | offset); |
232 | 0 | offset += entries_[i].size(); |
233 | 0 | } |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | // Opens the given TFile pointer to the given component type. |
238 | | // Returns false in case of failure. |
239 | 76 | bool TessdataManager::GetComponent(TessdataType type, TFile *fp) { |
240 | 76 | if (!is_loaded_ && !Init(data_file_name_.c_str())) { |
241 | 0 | return false; |
242 | 0 | } |
243 | 76 | const TessdataManager *const_this = this; |
244 | 76 | return const_this->GetComponent(type, fp); |
245 | 76 | } |
246 | | |
247 | | // As non-const version except it can't load the component if not already |
248 | | // loaded. |
249 | 84 | bool TessdataManager::GetComponent(TessdataType type, TFile *fp) const { |
250 | 84 | ASSERT_HOST(is_loaded_); |
251 | 84 | if (entries_[type].empty()) { |
252 | 16 | return false; |
253 | 16 | } |
254 | 68 | fp->Open(&entries_[type][0], entries_[type].size()); |
255 | 68 | fp->set_swap(swap_); |
256 | 68 | return true; |
257 | 84 | } |
258 | | |
259 | | // Returns the current version string. |
260 | 0 | std::string TessdataManager::VersionString() const { |
261 | 0 | return std::string(&entries_[TESSDATA_VERSION][0], entries_[TESSDATA_VERSION].size()); |
262 | 0 | } |
263 | | |
264 | | // Sets the version string to the given v_str. |
265 | 4 | void TessdataManager::SetVersionString(const std::string &v_str) { |
266 | 4 | entries_[TESSDATA_VERSION].resize(v_str.size()); |
267 | 4 | memcpy(&entries_[TESSDATA_VERSION][0], v_str.data(), v_str.size()); |
268 | 4 | } |
269 | | |
270 | | bool TessdataManager::CombineDataFiles(const char *language_data_path_prefix, |
271 | 0 | const char *output_filename) { |
272 | | // Load individual tessdata components from files. |
273 | 0 | for (auto filesuffix : kTessdataFileSuffixes) { |
274 | 0 | TessdataType type; |
275 | 0 | ASSERT_HOST(TessdataTypeFromFileSuffix(filesuffix, &type)); |
276 | 0 | std::string filename = language_data_path_prefix; |
277 | 0 | filename += filesuffix; |
278 | 0 | FILE *fp = fopen(filename.c_str(), "rb"); |
279 | 0 | if (fp != nullptr) { |
280 | 0 | fclose(fp); |
281 | 0 | if (!LoadDataFromFile(filename.c_str(), &entries_[type])) { |
282 | 0 | tprintf("Load of file %s failed!\n", filename.c_str()); |
283 | 0 | return false; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | } |
287 | 0 | is_loaded_ = true; |
288 | | |
289 | | // Make sure that the required components are present. |
290 | 0 | if (!IsBaseAvailable() && !IsLSTMAvailable()) { |
291 | 0 | tprintf( |
292 | 0 | "Error: traineddata file must contain at least (a unicharset file" |
293 | 0 | " and inttemp) OR an lstm file.\n"); |
294 | 0 | return false; |
295 | 0 | } |
296 | | // Write updated data to the output traineddata file. |
297 | 0 | return SaveFile(output_filename, nullptr); |
298 | 0 | } |
299 | | |
300 | | bool TessdataManager::OverwriteComponents(const char *new_traineddata_filename, |
301 | 0 | char **component_filenames, int num_new_components) { |
302 | | // Open the files with the new components. |
303 | | // TODO: This method supports only the proprietary file format. |
304 | 0 | for (int i = 0; i < num_new_components; ++i) { |
305 | 0 | TessdataType type; |
306 | 0 | if (TessdataTypeFromFileName(component_filenames[i], &type)) { |
307 | 0 | if (!LoadDataFromFile(component_filenames[i], &entries_[type])) { |
308 | 0 | tprintf("Failed to read component file:%s\n", component_filenames[i]); |
309 | 0 | return false; |
310 | 0 | } |
311 | 0 | } |
312 | 0 | } |
313 | | |
314 | | // Write updated data to the output traineddata file. |
315 | 0 | return SaveFile(new_traineddata_filename, nullptr); |
316 | 0 | } |
317 | | |
318 | 0 | bool TessdataManager::ExtractToFile(const char *filename) { |
319 | 0 | TessdataType type = TESSDATA_NUM_ENTRIES; |
320 | 0 | ASSERT_HOST(tesseract::TessdataManager::TessdataTypeFromFileName(filename, &type)); |
321 | 0 | if (entries_[type].empty()) { |
322 | 0 | return false; |
323 | 0 | } |
324 | 0 | return SaveDataToFile(entries_[type], filename); |
325 | 0 | } |
326 | | |
327 | 0 | bool TessdataManager::TessdataTypeFromFileSuffix(const char *suffix, TessdataType *type) { |
328 | 0 | for (unsigned i = 0; i < TESSDATA_NUM_ENTRIES; ++i) { |
329 | 0 | if (strcmp(kTessdataFileSuffixes[i], suffix) == 0) { |
330 | 0 | *type = static_cast<TessdataType>(i); |
331 | 0 | return true; |
332 | 0 | } |
333 | 0 | } |
334 | | #if !defined(NDEBUG) |
335 | | tprintf( |
336 | | "TessdataManager can't determine which tessdata" |
337 | | " component is represented by %s\n", |
338 | | suffix); |
339 | | #endif |
340 | 0 | return false; |
341 | 0 | } |
342 | | |
343 | 0 | bool TessdataManager::TessdataTypeFromFileName(const char *filename, TessdataType *type) { |
344 | | // Get the file suffix (extension) |
345 | 0 | const char *suffix = strrchr(filename, '.'); |
346 | 0 | if (suffix == nullptr || *(++suffix) == '\0') { |
347 | 0 | return false; |
348 | 0 | } |
349 | 0 | return TessdataTypeFromFileSuffix(suffix, type); |
350 | 0 | } |
351 | | |
352 | | } // namespace tesseract |