/src/tesseract/src/textord/fpchop.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * File: fpchop.cpp (Formerly fp_chop.c) |
3 | | * Description: Code to chop fixed pitch text into character cells. |
4 | | * Author: Ray Smith |
5 | | * |
6 | | * (C) Copyright 1993, Hewlett-Packard Ltd. |
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 | | // Include automatically generated configuration file if running autoconf. |
20 | | #ifdef HAVE_CONFIG_H |
21 | | # include "config_auto.h" |
22 | | #endif |
23 | | |
24 | | #include "fpchop.h" |
25 | | |
26 | | #include "blobbox.h" |
27 | | #include "drawtord.h" |
28 | | #include "statistc.h" |
29 | | #include "topitch.h" |
30 | | #include "tovars.h" |
31 | | |
32 | | namespace tesseract { |
33 | | |
34 | | INT_VAR(textord_fp_chop_error, 2, "Max allowed bending of chop cells"); |
35 | | |
36 | | static WERD *add_repeated_word(WERD_IT *rep_it, int16_t &rep_left, int16_t &prev_chop_coord, |
37 | | uint8_t &blanks, float pitch, WERD_IT *word_it); |
38 | | |
39 | | static void fixed_chop_cblob(C_BLOB *blob, int16_t chop_coord, float pitch_error, |
40 | | C_OUTLINE_LIST *left_outlines, C_OUTLINE_LIST *right_outlines); |
41 | | |
42 | | static void fixed_split_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error, |
43 | | C_OUTLINE_IT *left_it, C_OUTLINE_IT *right_it); |
44 | | |
45 | | static bool fixed_chop_coutline(C_OUTLINE *srcline, int16_t chop_coord, float pitch_error, |
46 | | C_OUTLINE_FRAG_LIST *left_frags, C_OUTLINE_FRAG_LIST *right_frags); |
47 | | |
48 | | static void save_chop_cfragment(int16_t head_index, ICOORD head_pos, int16_t tail_index, |
49 | | ICOORD tail_pos, C_OUTLINE *srcline, C_OUTLINE_FRAG_LIST *frags); |
50 | | |
51 | | static void add_frag_to_list(C_OUTLINE_FRAG *frag, C_OUTLINE_FRAG_LIST *frags); |
52 | | |
53 | | static void close_chopped_cfragments(C_OUTLINE_FRAG_LIST *frags, C_OUTLINE_LIST *children, |
54 | | float pitch_error, C_OUTLINE_IT *dest_it); |
55 | | |
56 | | static C_OUTLINE *join_chopped_fragments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top); |
57 | | |
58 | | static void join_segments(C_OUTLINE_FRAG *bottom, C_OUTLINE_FRAG *top); |
59 | | |
60 | | /********************************************************************** |
61 | | * fixed_pitch_words |
62 | | * |
63 | | * Make a ROW from a fixed pitch TO_ROW. |
64 | | **********************************************************************/ |
65 | | ROW *fixed_pitch_words( // find lines |
66 | | TO_ROW *row // row to do |
67 | 5.60k | ) { |
68 | 5.60k | bool bol; // start of line |
69 | 5.60k | uint8_t blanks; // in front of word |
70 | 5.60k | uint8_t new_blanks; // blanks in empty cell |
71 | 5.60k | int16_t chop_coord; // chop boundary |
72 | 5.60k | int16_t prev_chop_coord; // start of cell |
73 | 5.60k | int16_t rep_left; // left edge of rep word |
74 | 5.60k | ROW *real_row; // output row |
75 | 5.60k | C_OUTLINE_LIST left_coutlines; |
76 | 5.60k | C_OUTLINE_LIST right_coutlines; |
77 | 5.60k | C_BLOB_LIST cblobs; |
78 | 5.60k | C_BLOB_IT cblob_it = &cblobs; |
79 | 5.60k | WERD_LIST words; |
80 | 5.60k | WERD_IT word_it = &words; // new words |
81 | | // repeated blobs |
82 | 5.60k | WERD_IT rep_it = &row->rep_words; |
83 | 5.60k | WERD *word; // new word |
84 | 5.60k | int32_t xstarts[2]; // row ends |
85 | 5.60k | int32_t prev_x; // end of prev blob |
86 | | // iterator |
87 | 5.60k | BLOBNBOX_IT box_it = row->blob_list(); |
88 | | // boundaries |
89 | 5.60k | ICOORDELT_IT cell_it = &row->char_cells; |
90 | | |
91 | | #ifndef GRAPHICS_DISABLED |
92 | | if (textord_show_page_cuts && to_win != nullptr) { |
93 | | plot_row_cells(to_win, ScrollView::RED, row, 0, &row->char_cells); |
94 | | } |
95 | | #endif |
96 | | |
97 | 5.60k | prev_x = -INT16_MAX; |
98 | 5.60k | bol = true; |
99 | 5.60k | blanks = 0; |
100 | 5.60k | if (rep_it.empty()) { |
101 | 5.60k | rep_left = INT16_MAX; |
102 | 5.60k | } else { |
103 | 0 | rep_left = rep_it.data()->bounding_box().left(); |
104 | 0 | } |
105 | 5.60k | if (box_it.empty()) { |
106 | 0 | return nullptr; // empty row |
107 | 0 | } |
108 | 5.60k | xstarts[0] = box_it.data()->bounding_box().left(); |
109 | 5.60k | if (rep_left < xstarts[0]) { |
110 | 0 | xstarts[0] = rep_left; |
111 | 0 | } |
112 | 5.60k | if (cell_it.empty() || row->char_cells.singleton()) { |
113 | 0 | tprintf("Row without enough char cells!\n"); |
114 | 0 | tprintf("Leftmost blob is at (%d,%d)\n", box_it.data()->bounding_box().left(), |
115 | 0 | box_it.data()->bounding_box().bottom()); |
116 | 0 | return nullptr; |
117 | 0 | } |
118 | 5.60k | ASSERT_HOST(!cell_it.empty() && !row->char_cells.singleton()); |
119 | 5.60k | prev_chop_coord = cell_it.data()->x(); |
120 | 5.60k | word = nullptr; |
121 | 5.60k | while (rep_left < cell_it.data()->x()) { |
122 | 0 | word = |
123 | 0 | add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it); |
124 | 0 | } |
125 | 5.60k | cell_it.mark_cycle_pt(); |
126 | 5.60k | if (prev_chop_coord >= cell_it.data()->x()) { |
127 | 5.60k | cell_it.forward(); |
128 | 5.60k | } |
129 | 44.4k | for (; !cell_it.cycled_list(); cell_it.forward()) { |
130 | 38.8k | chop_coord = cell_it.data()->x(); |
131 | 124k | while (!box_it.empty() && box_it.data()->bounding_box().left() <= chop_coord) { |
132 | 85.5k | if (box_it.data()->bounding_box().right() > prev_x) { |
133 | 52.0k | prev_x = box_it.data()->bounding_box().right(); |
134 | 52.0k | } |
135 | 85.5k | split_to_blob(box_it.extract(), chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines, |
136 | 85.5k | &right_coutlines); |
137 | 85.5k | box_it.forward(); |
138 | 94.9k | while (!box_it.empty() && box_it.data()->cblob() == nullptr) { |
139 | 9.42k | delete box_it.extract(); |
140 | 9.42k | box_it.forward(); |
141 | 9.42k | } |
142 | 85.5k | } |
143 | 38.8k | if (!right_coutlines.empty() && left_coutlines.empty()) { |
144 | 1.60k | split_to_blob(nullptr, chop_coord, textord_fp_chop_error + 0.5f, &left_coutlines, |
145 | 1.60k | &right_coutlines); |
146 | 1.60k | } |
147 | 38.8k | if (!left_coutlines.empty()) { |
148 | 36.8k | cblob_it.add_after_then_move(new C_BLOB(&left_coutlines)); |
149 | 36.8k | } else { |
150 | 2.03k | if (rep_left < chop_coord) { |
151 | 0 | if (rep_left > prev_chop_coord) { |
152 | 0 | new_blanks = |
153 | 0 | static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / row->fixed_pitch + 0.5)); |
154 | 0 | } else { |
155 | 0 | new_blanks = 0; |
156 | 0 | } |
157 | 2.03k | } else { |
158 | 2.03k | if (chop_coord > prev_chop_coord) { |
159 | 2.03k | new_blanks = |
160 | 2.03k | static_cast<uint8_t>(floor((chop_coord - prev_chop_coord) / row->fixed_pitch + 0.5)); |
161 | 2.03k | } else { |
162 | 0 | new_blanks = 0; |
163 | 0 | } |
164 | 2.03k | } |
165 | 2.03k | if (!cblob_it.empty()) { |
166 | 1.04k | if (blanks < 1 && word != nullptr && !word->flag(W_REP_CHAR)) { |
167 | 6 | blanks = 1; |
168 | 6 | } |
169 | 1.04k | word = new WERD(&cblobs, blanks, nullptr); |
170 | 1.04k | cblob_it.set_to_list(&cblobs); |
171 | 1.04k | word->set_flag(W_DONT_CHOP, true); |
172 | 1.04k | word_it.add_after_then_move(word); |
173 | 1.04k | if (bol) { |
174 | 534 | word->set_flag(W_BOL, true); |
175 | 534 | bol = false; |
176 | 534 | } |
177 | 1.04k | blanks = new_blanks; |
178 | 1.04k | } else { |
179 | 991 | blanks += new_blanks; |
180 | 991 | } |
181 | 2.03k | while (rep_left < chop_coord) { |
182 | 0 | word = add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, |
183 | 0 | &word_it); |
184 | 0 | } |
185 | 2.03k | } |
186 | 38.8k | if (prev_chop_coord < chop_coord) { |
187 | 38.8k | prev_chop_coord = chop_coord; |
188 | 38.8k | } |
189 | 38.8k | } |
190 | 5.60k | if (!cblob_it.empty()) { |
191 | 5.55k | word = new WERD(&cblobs, blanks, nullptr); |
192 | 5.55k | word->set_flag(W_DONT_CHOP, true); |
193 | 5.55k | word_it.add_after_then_move(word); |
194 | 5.55k | if (bol) { |
195 | 5.07k | word->set_flag(W_BOL, true); |
196 | 5.07k | } |
197 | 5.55k | } |
198 | 5.60k | ASSERT_HOST(word != nullptr); |
199 | 5.60k | while (!rep_it.empty()) { |
200 | 0 | add_repeated_word(&rep_it, rep_left, prev_chop_coord, blanks, row->fixed_pitch, &word_it); |
201 | 0 | } |
202 | | // at end of line |
203 | 5.60k | word_it.data()->set_flag(W_EOL, true); |
204 | 5.60k | if (prev_chop_coord > prev_x) { |
205 | 2.47k | prev_x = prev_chop_coord; |
206 | 2.47k | } |
207 | 5.60k | xstarts[1] = prev_x + 1; |
208 | 5.60k | real_row = |
209 | 5.60k | new ROW(row, static_cast<int16_t>(row->kern_size), static_cast<int16_t>(row->space_size)); |
210 | 5.60k | word_it.set_to_list(real_row->word_list()); |
211 | | // put words in row |
212 | 5.60k | word_it.add_list_after(&words); |
213 | 5.60k | real_row->recalc_bounding_box(); |
214 | 5.60k | return real_row; |
215 | 5.60k | } |
216 | | |
217 | | /********************************************************************** |
218 | | * add_repeated_word |
219 | | * |
220 | | * Add repeated word into the row at the given point. |
221 | | **********************************************************************/ |
222 | | |
223 | | static WERD *add_repeated_word( // move repeated word |
224 | | WERD_IT *rep_it, // repeated words |
225 | | int16_t &rep_left, // left edge of word |
226 | | int16_t &prev_chop_coord, // previous word end |
227 | | uint8_t &blanks, // no of blanks |
228 | | float pitch, // char cell size |
229 | | WERD_IT *word_it // list of words |
230 | 0 | ) { |
231 | 0 | WERD *word; // word to move |
232 | 0 | int16_t new_blanks; // extra blanks |
233 | |
|
234 | 0 | if (rep_left > prev_chop_coord) { |
235 | 0 | new_blanks = static_cast<uint8_t>(floor((rep_left - prev_chop_coord) / pitch + 0.5)); |
236 | 0 | blanks += new_blanks; |
237 | 0 | } |
238 | 0 | word = rep_it->extract(); |
239 | 0 | prev_chop_coord = word->bounding_box().right(); |
240 | 0 | word_it->add_after_then_move(word); |
241 | 0 | word->set_blanks(blanks); |
242 | 0 | rep_it->forward(); |
243 | 0 | if (rep_it->empty()) { |
244 | 0 | rep_left = INT16_MAX; |
245 | 0 | } else { |
246 | 0 | rep_left = rep_it->data()->bounding_box().left(); |
247 | 0 | } |
248 | 0 | blanks = 0; |
249 | 0 | return word; |
250 | 0 | } |
251 | | |
252 | | /********************************************************************** |
253 | | * split_to_blob |
254 | | * |
255 | | * Split a BLOBNBOX across a vertical chop line and put the pieces |
256 | | * into a left outline list and a right outline list. |
257 | | **********************************************************************/ |
258 | | |
259 | | void split_to_blob( // split the blob |
260 | | BLOBNBOX *blob, // blob to split |
261 | | int16_t chop_coord, // place to chop |
262 | | float pitch_error, // allowed deviation |
263 | | C_OUTLINE_LIST *left_coutlines, // for cblobs |
264 | 144k | C_OUTLINE_LIST *right_coutlines) { |
265 | 144k | C_BLOB *real_cblob; // cblob to chop |
266 | | |
267 | 144k | if (blob != nullptr) { |
268 | 102k | real_cblob = blob->remove_cblob(); |
269 | 102k | } else { |
270 | 41.4k | real_cblob = nullptr; |
271 | 41.4k | } |
272 | 144k | if (!right_coutlines->empty() || real_cblob != nullptr) { |
273 | 144k | fixed_chop_cblob(real_cblob, chop_coord, pitch_error, left_coutlines, right_coutlines); |
274 | 144k | } |
275 | | |
276 | 144k | delete blob; |
277 | 144k | } |
278 | | |
279 | | /********************************************************************** |
280 | | * fixed_chop_cblob |
281 | | * |
282 | | * Chop the given cblob (if any) and the existing right outlines to |
283 | | * produce a list of outlines left of the chop point and more to the right. |
284 | | **********************************************************************/ |
285 | | |
286 | | static void fixed_chop_cblob( // split the blob |
287 | | C_BLOB *blob, // blob to split |
288 | | int16_t chop_coord, // place to chop |
289 | | float pitch_error, // allowed deviation |
290 | | C_OUTLINE_LIST *left_outlines, // left half of chop |
291 | | C_OUTLINE_LIST *right_outlines // right half of chop |
292 | 144k | ) { |
293 | 144k | C_OUTLINE *old_right; // already there |
294 | 144k | C_OUTLINE_LIST new_outlines; // new right ones |
295 | | // output iterator |
296 | 144k | C_OUTLINE_IT left_it = left_outlines; |
297 | | // in/out iterator |
298 | 144k | C_OUTLINE_IT right_it = right_outlines; |
299 | 144k | C_OUTLINE_IT new_it = &new_outlines; |
300 | 144k | C_OUTLINE_IT blob_it; // outlines in blob |
301 | | |
302 | 144k | if (!right_it.empty()) { |
303 | 110k | while (!right_it.empty()) { |
304 | 59.6k | old_right = right_it.extract(); |
305 | 59.6k | right_it.forward(); |
306 | 59.6k | fixed_split_coutline(old_right, chop_coord, pitch_error, &left_it, &new_it); |
307 | 59.6k | } |
308 | 50.6k | right_it.add_list_before(&new_outlines); |
309 | 50.6k | } |
310 | 144k | if (blob != nullptr) { |
311 | 102k | blob_it.set_to_list(blob->out_list()); |
312 | 205k | for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) { |
313 | 103k | fixed_split_coutline(blob_it.extract(), chop_coord, pitch_error, &left_it, &right_it); |
314 | 103k | } |
315 | 102k | delete blob; |
316 | 102k | } |
317 | 144k | } |
318 | | |
319 | | /********************************************************************** |
320 | | * fixed_split_outline |
321 | | * |
322 | | * Chop the given outline (if necessary) placing the fragments which |
323 | | * fall either side of the chop line into the appropriate list. |
324 | | **********************************************************************/ |
325 | | |
326 | | static void fixed_split_coutline( // chop the outline |
327 | | C_OUTLINE *srcline, // source outline |
328 | | int16_t chop_coord, // place to chop |
329 | | float pitch_error, // allowed deviation |
330 | | C_OUTLINE_IT *left_it, // left half of chop |
331 | | C_OUTLINE_IT *right_it // right half of chop |
332 | 162k | ) { |
333 | 162k | C_OUTLINE *child; // child outline |
334 | 162k | TBOX srcbox; // box of outline |
335 | 162k | C_OUTLINE_LIST left_ch; // left children |
336 | 162k | C_OUTLINE_LIST right_ch; // right children |
337 | 162k | C_OUTLINE_FRAG_LIST left_frags; // chopped fragments |
338 | 162k | C_OUTLINE_FRAG_LIST right_frags; |
339 | 162k | ; |
340 | 162k | C_OUTLINE_IT left_ch_it = &left_ch; |
341 | | // for whole children |
342 | 162k | C_OUTLINE_IT right_ch_it = &right_ch; |
343 | | // for holes |
344 | 162k | C_OUTLINE_IT child_it = srcline->child(); |
345 | | |
346 | 162k | srcbox = srcline->bounding_box(); |
347 | 162k | if (srcbox.left() + srcbox.right() <= chop_coord * 2 && |
348 | 111k | srcbox.right() < chop_coord + pitch_error) { |
349 | | // Whole outline is in the left side or not far over the chop_coord, |
350 | | // so put the whole thing on the left. |
351 | 105k | left_it->add_after_then_move(srcline); |
352 | 105k | } else if (srcbox.left() + srcbox.right() > chop_coord * 2 && |
353 | 50.9k | srcbox.left() > chop_coord - pitch_error) { |
354 | | // Whole outline is in the right side or not far over the chop_coord, |
355 | | // so put the whole thing on the right. |
356 | 25.6k | right_it->add_before_stay_put(srcline); |
357 | 31.3k | } else { |
358 | | // Needs real chopping. |
359 | 31.3k | if (fixed_chop_coutline(srcline, chop_coord, pitch_error, &left_frags, &right_frags)) { |
360 | 122k | for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { |
361 | 91.5k | child = child_it.extract(); |
362 | 91.5k | srcbox = child->bounding_box(); |
363 | 91.5k | if (srcbox.right() < chop_coord) { |
364 | | // Whole child is on the left. |
365 | 12.1k | left_ch_it.add_after_then_move(child); |
366 | 79.4k | } else if (srcbox.left() > chop_coord) { |
367 | | // Whole child is on the right. |
368 | 71.4k | right_ch_it.add_after_then_move(child); |
369 | 71.4k | } else { |
370 | | // No pitch_error is allowed when chopping children to prevent |
371 | | // impossible outlines from being created. |
372 | 7.95k | if (fixed_chop_coutline(child, chop_coord, 0.0f, &left_frags, &right_frags)) { |
373 | 5.87k | delete child; |
374 | 5.87k | } else { |
375 | 2.07k | if (srcbox.left() + srcbox.right() <= chop_coord * 2) { |
376 | 0 | left_ch_it.add_after_then_move(child); |
377 | 2.07k | } else { |
378 | 2.07k | right_ch_it.add_after_then_move(child); |
379 | 2.07k | } |
380 | 2.07k | } |
381 | 7.95k | } |
382 | 91.5k | } |
383 | 31.3k | close_chopped_cfragments(&left_frags, &left_ch, pitch_error, left_it); |
384 | 31.3k | close_chopped_cfragments(&right_frags, &right_ch, pitch_error, right_it); |
385 | 31.3k | ASSERT_HOST(left_ch.empty() && right_ch.empty()); |
386 | | // No children left. |
387 | 31.3k | delete srcline; // Smashed up. |
388 | 31.3k | } else { |
389 | | // Chop failed. Just use middle coord. |
390 | 0 | if (srcbox.left() + srcbox.right() <= chop_coord * 2) { |
391 | 0 | left_it->add_after_then_move(srcline); // Stick whole in left. |
392 | 0 | } else { |
393 | 0 | right_it->add_before_stay_put(srcline); |
394 | 0 | } |
395 | 0 | } |
396 | 31.3k | } |
397 | 162k | } |
398 | | |
399 | | /********************************************************************** |
400 | | * fixed_chop_coutline |
401 | | * |
402 | | * Chop the given coutline (if necessary) placing the fragments which |
403 | | * fall either side of the chop line into the appropriate list. |
404 | | * If the coutline lies too heavily to one side to chop, false is returned. |
405 | | **********************************************************************/ |
406 | | |
407 | | static bool fixed_chop_coutline( // chop the outline |
408 | | C_OUTLINE *srcline, // source outline |
409 | | int16_t chop_coord, // place to chop |
410 | | float pitch_error, // allowed deviation |
411 | | C_OUTLINE_FRAG_LIST *left_frags, // left half of chop |
412 | | C_OUTLINE_FRAG_LIST *right_frags // right half of chop |
413 | 39.3k | ) { |
414 | 39.3k | bool first_frag; // fragment |
415 | 39.3k | int16_t left_edge; // of outline |
416 | 39.3k | int16_t startindex; // in first fragment |
417 | 39.3k | int32_t length; // of outline |
418 | 39.3k | int16_t stepindex; // into outline |
419 | 39.3k | int16_t head_index; // start of fragment |
420 | 39.3k | ICOORD head_pos; // start of fragment |
421 | 39.3k | int16_t tail_index; // end of fragment |
422 | 39.3k | ICOORD tail_pos; // end of fragment |
423 | 39.3k | ICOORD pos; // current point |
424 | 39.3k | int16_t first_index = 0; // first tail |
425 | 39.3k | ICOORD first_pos; // first tail |
426 | | |
427 | 39.3k | length = srcline->pathlength(); |
428 | 39.3k | pos = srcline->start_pos(); |
429 | 39.3k | left_edge = pos.x(); |
430 | 39.3k | tail_index = 0; |
431 | 39.3k | tail_pos = pos; |
432 | 13.2M | for (stepindex = 0; stepindex < length; stepindex++) { |
433 | 13.1M | if (pos.x() < left_edge) { |
434 | 102k | left_edge = pos.x(); |
435 | 102k | tail_index = stepindex; |
436 | 102k | tail_pos = pos; |
437 | 102k | } |
438 | 13.1M | pos += srcline->step(stepindex); |
439 | 13.1M | } |
440 | 39.3k | if (left_edge >= chop_coord - pitch_error) { |
441 | 2.07k | return false; // not worth it |
442 | 2.07k | } |
443 | | |
444 | 37.2k | startindex = tail_index; |
445 | 37.2k | first_frag = true; |
446 | 37.2k | head_index = tail_index; |
447 | 37.2k | head_pos = tail_pos; |
448 | 96.3k | do { |
449 | 2.29M | do { |
450 | 2.29M | tail_pos += srcline->step(tail_index); |
451 | 2.29M | tail_index++; |
452 | 2.29M | if (tail_index == length) { |
453 | 32.5k | tail_index = 0; |
454 | 32.5k | } |
455 | 2.29M | } while (tail_pos.x() != chop_coord && tail_index != startindex); |
456 | 96.3k | if (tail_index == startindex) { |
457 | 37.2k | if (first_frag) { |
458 | 0 | return false; // doesn't cross line |
459 | 37.2k | } else { |
460 | 37.2k | break; |
461 | 37.2k | } |
462 | 37.2k | } |
463 | 59.1k | ASSERT_HOST(head_index != tail_index); |
464 | 59.1k | if (!first_frag) { |
465 | 21.9k | save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, left_frags); |
466 | 37.2k | } else { |
467 | 37.2k | first_index = tail_index; |
468 | 37.2k | first_pos = tail_pos; |
469 | 37.2k | first_frag = false; |
470 | 37.2k | } |
471 | 110k | while (srcline->step(tail_index).x() == 0) { |
472 | 51.4k | tail_pos += srcline->step(tail_index); |
473 | 51.4k | tail_index++; |
474 | 51.4k | if (tail_index == length) { |
475 | 988 | tail_index = 0; |
476 | 988 | } |
477 | 51.4k | } |
478 | 59.1k | head_index = tail_index; |
479 | 59.1k | head_pos = tail_pos; |
480 | 112k | while (srcline->step(tail_index).x() > 0) { |
481 | 10.7M | do { |
482 | 10.7M | tail_pos += srcline->step(tail_index); |
483 | 10.7M | tail_index++; |
484 | 10.7M | if (tail_index == length) { |
485 | 3.66k | tail_index = 0; |
486 | 3.66k | } |
487 | 10.7M | } while (tail_pos.x() != chop_coord); |
488 | 53.4k | ASSERT_HOST(head_index != tail_index); |
489 | 53.4k | save_chop_cfragment(head_index, head_pos, tail_index, tail_pos, srcline, right_frags); |
490 | 108k | while (srcline->step(tail_index).x() == 0) { |
491 | 55.2k | tail_pos += srcline->step(tail_index); |
492 | 55.2k | tail_index++; |
493 | 55.2k | if (tail_index == length) { |
494 | 20 | tail_index = 0; |
495 | 20 | } |
496 | 55.2k | } |
497 | 53.4k | head_index = tail_index; |
498 | 53.4k | head_pos = tail_pos; |
499 | 53.4k | } |
500 | 59.1k | } while (tail_index != startindex); |
501 | 37.2k | save_chop_cfragment(head_index, head_pos, first_index, first_pos, srcline, left_frags); |
502 | 37.2k | return true; // did some chopping |
503 | 37.2k | } |
504 | | |
505 | | /********************************************************************** |
506 | | * save_chop_cfragment |
507 | | * |
508 | | * Store the given fragment in the given fragment list. |
509 | | **********************************************************************/ |
510 | | |
511 | | static void save_chop_cfragment( // chop the outline |
512 | | int16_t head_index, // head of fragment |
513 | | ICOORD head_pos, // head of fragment |
514 | | int16_t tail_index, // tail of fragment |
515 | | ICOORD tail_pos, // tail of fragment |
516 | | C_OUTLINE *srcline, // source of edgesteps |
517 | | C_OUTLINE_FRAG_LIST *frags // fragment list |
518 | 112k | ) { |
519 | 112k | int16_t jump; // gap across end |
520 | 112k | int16_t stepcount; // total steps |
521 | 112k | C_OUTLINE_FRAG *head; // head of fragment |
522 | 112k | C_OUTLINE_FRAG *tail; // tail of fragment |
523 | 112k | int16_t tail_y; // ycoord of tail |
524 | | |
525 | 112k | ASSERT_HOST(tail_pos.x() == head_pos.x()); |
526 | 112k | ASSERT_HOST(tail_index != head_index); |
527 | 112k | stepcount = tail_index - head_index; |
528 | 112k | if (stepcount < 0) { |
529 | 36.2k | stepcount += srcline->pathlength(); |
530 | 36.2k | } |
531 | 112k | jump = tail_pos.y() - head_pos.y(); |
532 | 112k | if (jump < 0) { |
533 | 55.3k | jump = -jump; |
534 | 55.3k | } |
535 | 112k | if (jump == stepcount) { |
536 | 0 | return; // its a nop |
537 | 0 | } |
538 | 112k | tail_y = tail_pos.y(); |
539 | 112k | head = new C_OUTLINE_FRAG(head_pos, tail_pos, srcline, head_index, tail_index); |
540 | 112k | tail = new C_OUTLINE_FRAG(head, tail_y); |
541 | 112k | head->other_end = tail; |
542 | 112k | add_frag_to_list(head, frags); |
543 | 112k | add_frag_to_list(tail, frags); |
544 | 112k | } |
545 | | |
546 | | /********************************************************************** |
547 | | * C_OUTLINE_FRAG::C_OUTLINE_FRAG |
548 | | * |
549 | | * Constructors for C_OUTLINE_FRAG. |
550 | | **********************************************************************/ |
551 | | |
552 | | C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment |
553 | | ICOORD start_pt, // start coord |
554 | | ICOORD end_pt, // end coord |
555 | | C_OUTLINE *outline, // source of steps |
556 | 112k | int16_t start_index, int16_t end_index) { |
557 | 112k | start = start_pt; |
558 | 112k | end = end_pt; |
559 | 112k | ycoord = start_pt.y(); |
560 | 112k | stepcount = end_index - start_index; |
561 | 112k | if (stepcount < 0) { |
562 | 36.2k | stepcount += outline->pathlength(); |
563 | 36.2k | } |
564 | 112k | ASSERT_HOST(stepcount > 0); |
565 | 112k | steps = new DIR128[stepcount]; |
566 | 112k | if (end_index > start_index) { |
567 | 10.5M | for (int i = start_index; i < end_index; ++i) { |
568 | 10.4M | steps[i - start_index] = outline->step_dir(i); |
569 | 10.4M | } |
570 | 76.3k | } else { |
571 | 36.2k | int len = outline->pathlength(); |
572 | 36.2k | int i = start_index; |
573 | 1.60M | for (; i < len; ++i) { |
574 | 1.57M | steps[i - start_index] = outline->step_dir(i); |
575 | 1.57M | } |
576 | 36.2k | if (end_index > 0) { |
577 | 1.00M | for (; i < end_index + len; ++i) { |
578 | 973k | steps[i - start_index] = outline->step_dir(i - len); |
579 | 973k | } |
580 | 34.6k | } |
581 | 36.2k | } |
582 | 112k | other_end = nullptr; |
583 | 112k | delete close(); |
584 | 112k | } |
585 | | |
586 | | C_OUTLINE_FRAG::C_OUTLINE_FRAG( // record fragment |
587 | | C_OUTLINE_FRAG *head, // other end |
588 | 112k | int16_t tail_y) { |
589 | 112k | ycoord = tail_y; |
590 | 112k | other_end = head; |
591 | 112k | start = head->start; |
592 | 112k | end = head->end; |
593 | 112k | steps = nullptr; |
594 | 112k | stepcount = 0; |
595 | 112k | } |
596 | | |
597 | | /********************************************************************** |
598 | | * add_frag_to_list |
599 | | * |
600 | | * Insert the fragment in the list at the appropriate place to keep |
601 | | * them in ascending ycoord order. |
602 | | **********************************************************************/ |
603 | | |
604 | | static void add_frag_to_list( // ordered add |
605 | | C_OUTLINE_FRAG *frag, // fragment to add |
606 | | C_OUTLINE_FRAG_LIST *frags // fragment list |
607 | 225k | ) { |
608 | | // output list |
609 | 225k | C_OUTLINE_FRAG_IT frag_it = frags; |
610 | | |
611 | 225k | if (!frags->empty()) { |
612 | 494k | for (frag_it.mark_cycle_pt(); !frag_it.cycled_list(); frag_it.forward()) { |
613 | 423k | if (frag_it.data()->ycoord > frag->ycoord || |
614 | 332k | (frag_it.data()->ycoord == frag->ycoord && frag->other_end->ycoord < frag->ycoord)) { |
615 | 90.7k | frag_it.add_before_then_move(frag); |
616 | 90.7k | return; |
617 | 90.7k | } |
618 | 423k | } |
619 | 162k | } |
620 | 134k | frag_it.add_to_end(frag); |
621 | 134k | } |
622 | | |
623 | | /********************************************************************** |
624 | | * close_chopped_cfragments |
625 | | * |
626 | | * Clear the given list of fragments joining them up into outlines. |
627 | | * Each outline made soaks up any of the child outlines which it encloses. |
628 | | **********************************************************************/ |
629 | | |
630 | | static void close_chopped_cfragments( // chop the outline |
631 | | C_OUTLINE_FRAG_LIST *frags, // list to clear |
632 | | C_OUTLINE_LIST *children, // potential children |
633 | | float pitch_error, // allowed shrinkage |
634 | | C_OUTLINE_IT *dest_it // output list |
635 | 62.7k | ) { |
636 | | // iterator |
637 | 62.7k | C_OUTLINE_FRAG_IT frag_it = frags; |
638 | 62.7k | C_OUTLINE_FRAG *bottom_frag; // bottom of cut |
639 | 62.7k | C_OUTLINE_FRAG *top_frag; // top of cut |
640 | 62.7k | C_OUTLINE *outline; // new outline |
641 | 62.7k | C_OUTLINE *child; // current child |
642 | 62.7k | C_OUTLINE_IT child_it = children; |
643 | 62.7k | C_OUTLINE_IT olchild_it; // children of outline |
644 | | |
645 | 175k | while (!frag_it.empty()) { |
646 | 112k | frag_it.move_to_first(); |
647 | | // get bottom one |
648 | 112k | bottom_frag = frag_it.extract(); |
649 | 112k | frag_it.forward(); |
650 | 112k | top_frag = frag_it.data(); // look at next |
651 | 112k | if ((bottom_frag->steps == nullptr && top_frag->steps == nullptr) || |
652 | 112k | (bottom_frag->steps != nullptr && top_frag->steps != nullptr)) { |
653 | 0 | if (frag_it.data_relative(1)->ycoord == top_frag->ycoord) { |
654 | 0 | frag_it.forward(); |
655 | 0 | } |
656 | 0 | } |
657 | 112k | top_frag = frag_it.extract(); |
658 | 112k | if (top_frag->other_end != bottom_frag) { |
659 | 36.0k | outline = join_chopped_fragments(bottom_frag, top_frag); |
660 | 36.0k | ASSERT_HOST(outline == nullptr); |
661 | 76.5k | } else { |
662 | 76.5k | outline = join_chopped_fragments(bottom_frag, top_frag); |
663 | 76.5k | if (outline != nullptr) { |
664 | 76.5k | olchild_it.set_to_list(outline->child()); |
665 | 172k | for (child_it.mark_cycle_pt(); !child_it.cycled_list(); child_it.forward()) { |
666 | 96.3k | child = child_it.data(); |
667 | 96.3k | if (*child < *outline) { |
668 | 85.6k | olchild_it.add_to_end(child_it.extract()); |
669 | 85.6k | } |
670 | 96.3k | } |
671 | 76.5k | if (outline->bounding_box().width() > pitch_error) { |
672 | 70.1k | dest_it->add_after_then_move(outline); |
673 | 70.1k | } else { |
674 | 6.34k | delete outline; // Make it disappear. |
675 | 6.34k | } |
676 | 76.5k | } |
677 | 76.5k | } |
678 | 112k | } |
679 | 62.7k | while (!child_it.empty()) { |
680 | 0 | dest_it->add_after_then_move(child_it.extract()); |
681 | 0 | child_it.forward(); |
682 | 0 | } |
683 | 62.7k | } |
684 | | |
685 | | /********************************************************************** |
686 | | * join_chopped_fragments |
687 | | * |
688 | | * Join the two lists of POLYPTs such that neither OUTLINE_FRAG |
689 | | * operand keeps responsibility for the fragment. |
690 | | **********************************************************************/ |
691 | | |
692 | | static C_OUTLINE *join_chopped_fragments( // join pieces |
693 | | C_OUTLINE_FRAG *bottom, // bottom of cut |
694 | | C_OUTLINE_FRAG *top // top of cut |
695 | 112k | ) { |
696 | 112k | C_OUTLINE *outline; // closed loop |
697 | | |
698 | 112k | if (bottom->other_end == top) { |
699 | 76.5k | if (bottom->steps == nullptr) { |
700 | 39.2k | outline = top->close(); // turn to outline |
701 | 39.2k | } else { |
702 | 37.3k | outline = bottom->close(); |
703 | 37.3k | } |
704 | 76.5k | delete top; |
705 | 76.5k | delete bottom; |
706 | 76.5k | return outline; |
707 | 76.5k | } |
708 | 36.0k | if (bottom->steps == nullptr) { |
709 | 19.9k | ASSERT_HOST(top->steps != nullptr); |
710 | 19.9k | join_segments(bottom->other_end, top); |
711 | 19.9k | } else { |
712 | 16.1k | ASSERT_HOST(top->steps == nullptr); |
713 | 16.1k | join_segments(top->other_end, bottom); |
714 | 16.1k | } |
715 | 36.0k | top->other_end->other_end = bottom->other_end; |
716 | 36.0k | bottom->other_end->other_end = top->other_end; |
717 | 36.0k | delete bottom; |
718 | 36.0k | delete top; |
719 | 36.0k | return nullptr; |
720 | 112k | } |
721 | | |
722 | | /********************************************************************** |
723 | | * join_segments |
724 | | * |
725 | | * Join the two edgestep fragments such that the second comes after |
726 | | * the first and the gap between them is closed. |
727 | | **********************************************************************/ |
728 | | |
729 | | static void join_segments( // join pieces |
730 | | C_OUTLINE_FRAG *bottom, // bottom of cut |
731 | | C_OUTLINE_FRAG *top // top of cut |
732 | 36.0k | ) { |
733 | 36.0k | DIR128 *steps; // new steps |
734 | 36.0k | int32_t stepcount; // no of steps |
735 | 36.0k | int16_t fake_count; // fake steps |
736 | 36.0k | DIR128 fake_step; // step entry |
737 | | |
738 | 36.0k | ASSERT_HOST(bottom->end.x() == top->start.x()); |
739 | 36.0k | fake_count = top->start.y() - bottom->end.y(); |
740 | 36.0k | if (fake_count < 0) { |
741 | 16.1k | fake_count = -fake_count; |
742 | 16.1k | fake_step = 32; |
743 | 19.9k | } else { |
744 | 19.9k | fake_step = 96; |
745 | 19.9k | } |
746 | | |
747 | 36.0k | stepcount = bottom->stepcount + fake_count + top->stepcount; |
748 | 36.0k | steps = new DIR128[stepcount]; |
749 | 36.0k | memmove(steps, bottom->steps, bottom->stepcount); |
750 | 36.0k | memset(steps + bottom->stepcount, fake_step.get_dir(), fake_count); |
751 | 36.0k | memmove(steps + bottom->stepcount + fake_count, top->steps, top->stepcount); |
752 | 36.0k | delete[] bottom->steps; |
753 | 36.0k | bottom->steps = steps; |
754 | 36.0k | bottom->stepcount = stepcount; |
755 | 36.0k | bottom->end = top->end; |
756 | 36.0k | bottom->other_end->end = top->end; |
757 | 36.0k | } |
758 | | |
759 | | /********************************************************************** |
760 | | * C_OUTLINE_FRAG::close |
761 | | * |
762 | | * Join the ends of this fragment and turn it into an outline. |
763 | | **********************************************************************/ |
764 | | |
765 | 189k | C_OUTLINE *C_OUTLINE_FRAG::close() { // join pieces |
766 | 189k | DIR128 *new_steps; // new steps |
767 | 189k | int32_t new_stepcount; // no of steps |
768 | 189k | int16_t fake_count; // fake steps |
769 | 189k | DIR128 fake_step; // step entry |
770 | | |
771 | 189k | ASSERT_HOST(start.x() == end.x()); |
772 | 189k | fake_count = start.y() - end.y(); |
773 | 189k | if (fake_count < 0) { |
774 | 94.5k | fake_count = -fake_count; |
775 | 94.5k | fake_step = 32; |
776 | 94.5k | } else { |
777 | 94.5k | fake_step = 96; |
778 | 94.5k | } |
779 | | |
780 | 189k | new_stepcount = stepcount + fake_count; |
781 | 189k | if (new_stepcount > C_OUTLINE::kMaxOutlineLength) { |
782 | 0 | return nullptr; // Can't join them |
783 | 0 | } |
784 | 189k | new_steps = new DIR128[new_stepcount]; |
785 | 189k | memmove(new_steps, steps, stepcount); |
786 | 189k | memset(new_steps + stepcount, fake_step.get_dir(), fake_count); |
787 | 189k | auto *result = new C_OUTLINE(start, new_steps, static_cast<int16_t>(new_stepcount)); |
788 | 189k | delete[] new_steps; |
789 | 189k | return result; |
790 | 189k | } |
791 | | |
792 | | /********************************************************************** |
793 | | * C_OUTLINE_FRAG::operator= |
794 | | * |
795 | | * Copy this fragment. |
796 | | **********************************************************************/ |
797 | | |
798 | | // join pieces |
799 | | C_OUTLINE_FRAG &C_OUTLINE_FRAG::operator=(const C_OUTLINE_FRAG &src // fragment to copy |
800 | 0 | ) { |
801 | 0 | delete[] steps; |
802 | |
|
803 | 0 | stepcount = src.stepcount; |
804 | 0 | steps = new DIR128[stepcount]; |
805 | 0 | memmove(steps, src.steps, stepcount); |
806 | 0 | start = src.start; |
807 | 0 | end = src.end; |
808 | 0 | ycoord = src.ycoord; |
809 | 0 | return *this; |
810 | 0 | } |
811 | | |
812 | | } // namespace tesseract |