1 #include "mupdf/fitz.h"
2
3 #include <assert.h>
4 #include <string.h>
5
6 #undef DEBUG_OCR
7
8 #if !defined(HAVE_LEPTONICA) || !defined(HAVE_TESSERACT)
9 #ifndef OCR_DISABLED
10 #define OCR_DISABLED
11 #endif
12 #endif
13
14 #ifndef OCR_DISABLED
15 #include "tessocr.h"
16
17 /*
18
19 This device can be used in 2 modes, with or without a list.
20
21 In both modes the OCR device is created with a target device. The
22 caller runs the page to the device, and the device processes the calls
23 and (eventually) calls through to the target.
24
25 In both modes, all incoming calls are forwarded to an internal draw
26 device to render the page, so the page rendering is always complete.
27 The incoming calls are also forwarded (mostly, eventually) to the
28 target. Where the 2 modes differ is in the timing/content of those
29 forwarded calls.
30
31 In the first mode (without a list), the device instantly forwards all
32 non-text calls to the target. When the OCR device is closed, an OCR pass
33 is performed, and the recovered text is forwarded to the target. All
34 recovered text is listed as Courier, and ends up on top of the content.
35
36 This is fine for text extraction and probably for most cases of document
37 conversion. It's no good for correcting the unicode values within a
38 document though.
39
40 So, we have concocted a second way of working, using a display list. In
41 this mode, as well as rendering every device call that comes in, it
42 forwards them to a display list (and not the target). When the device
43 is closed we OCR the text image, and store the results. We then play
44 the list back through a 'rewrite' device to the target. The rewrite
45 device rewrites the text objects with the correct unicode values. Any
46 characters given by the OCR pass that aren't used by the rewrite step
47 are then sent through as invisible text.
48
49 This means that all the target device sees is the exact same graphical
50 objects in the exact same order, but with corrected unicode values.
51 Also, any text that appears in the document as a result of images or
52 line art is sent through as 'invisible' text at the end, so it will work
53 for cut/paste or search.
54
55 Or, at least, that was the plan. Unfortunately, it turns out that
56 Tesseract (with the LSTM engine (the most modern one)) is really bad at
57 giving bounding boxes for characters. It seems that the neural network
58 can say "hey, there is an 'X'", but it can't actually say where the X
59 occurred within the word. So tesseract knows where the words are, and
60 knows the order of the letters within the word, but basically guesses
61 at bboxes for the letters.
62
63 Because of this, we can't rely on character bboxes from tesseract to be
64 correct. We have to work off the word bboxes alone, together with the
65 order in which characters are passed to us.
66
67 So, as Tesseract gives us data, we store the word bbox, together with
68 the list of chars within that word.
69
70 When we play the list back through the display device, we then have to
71 rewrite text objects based on which word they are in. For the first
72 version, we'll make the extremely dodgy assumption that characters
73 come in the same order within the word.
74
75 For future versions we may want to collect bboxes for each text char
76 on our initial list building pass, collate those into matching 'words'
77 and sort them accordingly.
78 */
79
80
81 typedef struct word_record_s {
82 int len;
83 fz_rect bbox;
84 int n;
85 int unicode[1];
86 } word_record;
87
88 typedef struct fz_ocr_device_s
89 {
90 fz_device super;
91
92 fz_device *target;
93 fz_display_list *list;
94 fz_device *list_dev;
95 fz_device *draw_dev;
96 fz_pixmap *pixmap;
97
98 fz_rect mediabox;
99 fz_matrix ctm;
100
101 fz_rect word_bbox;
102 fz_font *font;
103
104 /* Current word */
105 int char_max;
106 int char_len;
107 int *chars;
108
109 /* Entire page */
110 int words_max;
111 int words_len;
112 word_record **words;
113
114 char *language;
115 } fz_ocr_device;
116
117 static void
fz_ocr_fill_path(fz_context * ctx,fz_device * dev,const fz_path * path,int even_odd,fz_matrix ctm,fz_colorspace * colorspace,const float * color,float alpha,fz_color_params color_params)118 fz_ocr_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm,
119 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
120 {
121 fz_ocr_device *ocr = (fz_ocr_device *)dev;
122
123 fz_fill_path(ctx, ocr->list_dev, path, even_odd, ctm, colorspace, color, alpha, color_params);
124 fz_fill_path(ctx, ocr->draw_dev, path, even_odd, ctm, colorspace, color, alpha, color_params);
125 }
126
127 static void
fz_ocr_stroke_path(fz_context * ctx,fz_device * dev,const fz_path * path,const fz_stroke_state * stroke,fz_matrix ctm,fz_colorspace * colorspace,const float * color,float alpha,fz_color_params color_params)128 fz_ocr_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke,
129 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
130 {
131 fz_ocr_device *ocr = (fz_ocr_device *)dev;
132
133 fz_stroke_path(ctx, ocr->list_dev, path, stroke, ctm, colorspace, color, alpha, color_params);
134 fz_stroke_path(ctx, ocr->draw_dev, path, stroke, ctm, colorspace, color, alpha, color_params);
135 }
136
137 static void
fz_ocr_fill_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm,fz_colorspace * colorspace,const float * color,float alpha,fz_color_params color_params)138 fz_ocr_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm,
139 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
140 {
141 fz_ocr_device *ocr = (fz_ocr_device *)dev;
142
143 if (ocr->list_dev != ocr->target)
144 fz_fill_text(ctx, ocr->list_dev, text, ctm, colorspace, color, alpha, color_params);
145 fz_fill_text(ctx, ocr->draw_dev, text, ctm, colorspace, color, alpha, color_params);
146 }
147
148 static void
fz_ocr_stroke_text(fz_context * ctx,fz_device * dev,const fz_text * text,const fz_stroke_state * stroke,fz_matrix ctm,fz_colorspace * colorspace,const float * color,float alpha,fz_color_params color_params)149 fz_ocr_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke,
150 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
151 {
152 fz_ocr_device *ocr = (fz_ocr_device *)dev;
153
154 if (ocr->list_dev != ocr->target)
155 fz_stroke_text(ctx, ocr->list_dev, text, stroke, ctm, colorspace, color, alpha, color_params);
156 fz_stroke_text(ctx, ocr->draw_dev, text, stroke, ctm, colorspace, color, alpha, color_params);
157 }
158
159 static void
fz_ocr_fill_shade(fz_context * ctx,fz_device * dev,fz_shade * shade,fz_matrix ctm,float alpha,fz_color_params color_params)160 fz_ocr_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
161 {
162 fz_ocr_device *ocr = (fz_ocr_device *)dev;
163
164 fz_fill_shade(ctx, ocr->list_dev, shade, ctm, alpha, color_params);
165 fz_fill_shade(ctx, ocr->draw_dev, shade, ctm, alpha, color_params);
166 }
167
168 static void
fz_ocr_fill_image(fz_context * ctx,fz_device * dev,fz_image * image,fz_matrix ctm,float alpha,fz_color_params color_params)169 fz_ocr_fill_image(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
170 {
171 fz_ocr_device *ocr = (fz_ocr_device *)dev;
172
173 fz_fill_image(ctx, ocr->list_dev, image, ctm, alpha, color_params);
174 fz_fill_image(ctx, ocr->draw_dev, image, ctm, alpha, color_params);
175 }
176
177 static void
fz_ocr_fill_image_mask(fz_context * ctx,fz_device * dev,fz_image * image,fz_matrix ctm,fz_colorspace * colorspace,const float * color,float alpha,fz_color_params color_params)178 fz_ocr_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm,
179 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
180 {
181 fz_ocr_device *ocr = (fz_ocr_device *)dev;
182
183 fz_fill_image_mask(ctx, ocr->list_dev, image, ctm, colorspace, color, alpha, color_params);
184 fz_fill_image_mask(ctx, ocr->draw_dev, image, ctm, colorspace, color, alpha, color_params);
185 }
186
187 static void
fz_ocr_clip_path(fz_context * ctx,fz_device * dev,const fz_path * path,int even_odd,fz_matrix ctm,fz_rect scissor)188 fz_ocr_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
189 {
190 fz_ocr_device *ocr = (fz_ocr_device *)dev;
191
192 fz_clip_path(ctx, ocr->list_dev, path, even_odd, ctm, scissor);
193 fz_clip_path(ctx, ocr->draw_dev, path, even_odd, ctm, scissor);
194 }
195
196 static void
fz_ocr_clip_stroke_path(fz_context * ctx,fz_device * dev,const fz_path * path,const fz_stroke_state * stroke,fz_matrix ctm,fz_rect scissor)197 fz_ocr_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
198 {
199 fz_ocr_device *ocr = (fz_ocr_device *)dev;
200
201 fz_clip_stroke_path(ctx, ocr->list_dev, path, stroke, ctm, scissor);
202 fz_clip_stroke_path(ctx, ocr->draw_dev, path, stroke, ctm, scissor);
203 }
204
205 static void
fz_ocr_clip_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm,fz_rect scissor)206 fz_ocr_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
207 {
208 fz_ocr_device *ocr = (fz_ocr_device *)dev;
209
210 if (ocr->list_dev != ocr->target)
211 fz_clip_text(ctx, ocr->list_dev, text, ctm, scissor);
212 fz_clip_text(ctx, ocr->draw_dev, text, ctm, scissor);
213 }
214
215 static void
fz_ocr_clip_stroke_text(fz_context * ctx,fz_device * dev,const fz_text * text,const fz_stroke_state * stroke,fz_matrix ctm,fz_rect scissor)216 fz_ocr_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
217 {
218 fz_ocr_device *ocr = (fz_ocr_device *)dev;
219
220 if (ocr->list_dev != ocr->target)
221 fz_clip_stroke_text(ctx, ocr->list_dev, text, stroke, ctm, scissor);
222 fz_clip_stroke_text(ctx, ocr->draw_dev, text, stroke, ctm, scissor);
223 }
224
225 static void
fz_ocr_ignore_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm)226 fz_ocr_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
227 {
228 fz_ocr_device *ocr = (fz_ocr_device *)dev;
229
230 /* Ignore text is generally used when text has been sent as
231 * part of other graphics - such as line art or images. As such
232 * we'll pick up the 'true' unicode values of such text in the
233 * OCR phase. We therefore send text to the list device (so
234 * it can be rewritten), but not direct to the target. */
235 if (ocr->list_dev != ocr->target)
236 fz_ignore_text(ctx, ocr->list_dev, text, ctm);
237 fz_ignore_text(ctx, ocr->draw_dev, text, ctm);
238 }
239
240 static void
fz_ocr_clip_image_mask(fz_context * ctx,fz_device * dev,fz_image * image,fz_matrix ctm,fz_rect scissor)241 fz_ocr_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *image, fz_matrix ctm, fz_rect scissor)
242 {
243 fz_ocr_device *ocr = (fz_ocr_device *)dev;
244
245 fz_clip_image_mask(ctx, ocr->list_dev, image, ctm, scissor);
246 fz_clip_image_mask(ctx, ocr->draw_dev, image, ctm, scissor);
247 }
248
249 static void
fz_ocr_pop_clip(fz_context * ctx,fz_device * dev)250 fz_ocr_pop_clip(fz_context *ctx, fz_device *dev)
251 {
252 fz_ocr_device *ocr = (fz_ocr_device *)dev;
253
254 fz_pop_clip(ctx, ocr->list_dev);
255 fz_pop_clip(ctx, ocr->draw_dev);
256 }
257
258 static void
fz_ocr_begin_mask(fz_context * ctx,fz_device * dev,fz_rect rect,int luminosity,fz_colorspace * colorspace,const float * color,fz_color_params color_params)259 fz_ocr_begin_mask(fz_context *ctx, fz_device *dev, fz_rect rect, int luminosity, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
260 {
261 fz_ocr_device *ocr = (fz_ocr_device *)dev;
262
263 fz_begin_mask(ctx, ocr->list_dev, rect, luminosity, colorspace, color, color_params);
264 fz_begin_mask(ctx, ocr->draw_dev, rect, luminosity, colorspace, color, color_params);
265 }
266
267 static void
fz_ocr_end_mask(fz_context * ctx,fz_device * dev)268 fz_ocr_end_mask(fz_context *ctx, fz_device *dev)
269 {
270 fz_ocr_device *ocr = (fz_ocr_device *)dev;
271
272 fz_end_mask(ctx, ocr->list_dev);
273 fz_end_mask(ctx, ocr->draw_dev);
274 }
275
276 static void
fz_ocr_begin_group(fz_context * ctx,fz_device * dev,fz_rect rect,fz_colorspace * cs,int isolated,int knockout,int blendmode,float alpha)277 fz_ocr_begin_group(fz_context *ctx, fz_device *dev, fz_rect rect, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
278 {
279 fz_ocr_device *ocr = (fz_ocr_device *)dev;
280
281 fz_begin_group(ctx, ocr->list_dev, rect, cs, isolated, knockout, blendmode, alpha);
282 fz_begin_group(ctx, ocr->draw_dev, rect, cs, isolated, knockout, blendmode, alpha);
283 }
284
285 static void
fz_ocr_end_group(fz_context * ctx,fz_device * dev)286 fz_ocr_end_group(fz_context *ctx, fz_device *dev)
287 {
288 fz_ocr_device *ocr = (fz_ocr_device *)dev;
289
290 fz_pop_clip(ctx, ocr->list_dev);
291 fz_pop_clip(ctx, ocr->draw_dev);
292 }
293
294 static int
fz_ocr_begin_tile(fz_context * ctx,fz_device * dev,fz_rect area,fz_rect view,float xstep,float ystep,fz_matrix ctm,int id)295 fz_ocr_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id)
296 {
297 fz_ocr_device *ocr = (fz_ocr_device *)dev;
298
299 /* Always pass 0 as tile id here so that neither device can
300 * disagree about whether the contents need to be sent. */
301 (void)fz_begin_tile_id(ctx, ocr->list_dev, area, view, xstep, ystep, ctm, 0);
302 (void)fz_begin_tile_id(ctx, ocr->draw_dev, area, view, xstep, ystep, ctm, 0);
303
304 return 0;
305 }
306
307 static void
fz_ocr_end_tile(fz_context * ctx,fz_device * dev)308 fz_ocr_end_tile(fz_context *ctx, fz_device *dev)
309 {
310 fz_ocr_device *ocr = (fz_ocr_device *)dev;
311
312 fz_end_tile(ctx, ocr->list_dev);
313 fz_end_tile(ctx, ocr->draw_dev);
314 }
315
316 static void
fz_ocr_render_flags(fz_context * ctx,fz_device * dev,int set,int clear)317 fz_ocr_render_flags(fz_context *ctx, fz_device *dev, int set, int clear)
318 {
319 fz_ocr_device *ocr = (fz_ocr_device *)dev;
320
321 fz_render_flags(ctx, ocr->list_dev, set, clear);
322 fz_render_flags(ctx, ocr->draw_dev, set, clear);
323 }
324
325 static void
fz_ocr_set_default_colorspaces(fz_context * ctx,fz_device * dev,fz_default_colorspaces * cs)326 fz_ocr_set_default_colorspaces(fz_context *ctx, fz_device *dev, fz_default_colorspaces *cs)
327 {
328 fz_ocr_device *ocr = (fz_ocr_device *)dev;
329
330 fz_set_default_colorspaces(ctx, ocr->list_dev, cs);
331 fz_set_default_colorspaces(ctx, ocr->draw_dev, cs);
332 }
333
334 static void
fz_ocr_begin_layer(fz_context * ctx,fz_device * dev,const char * layer_name)335 fz_ocr_begin_layer(fz_context *ctx, fz_device *dev, const char *layer_name)
336 {
337 fz_ocr_device *ocr = (fz_ocr_device *)dev;
338
339 fz_begin_layer(ctx, ocr->list_dev, layer_name);
340 fz_begin_layer(ctx, ocr->draw_dev, layer_name);
341 }
342
343 static void
fz_ocr_end_layer(fz_context * ctx,fz_device * dev)344 fz_ocr_end_layer(fz_context *ctx, fz_device *dev)
345 {
346 fz_ocr_device *ocr = (fz_ocr_device *)dev;
347
348 fz_end_layer(ctx, ocr->list_dev);
349 fz_end_layer(ctx, ocr->draw_dev);
350 }
351
352 static void
drop_ocr_device(fz_context * ctx,fz_ocr_device * ocr)353 drop_ocr_device(fz_context *ctx, fz_ocr_device *ocr)
354 {
355 int i;
356
357 if (ocr == NULL)
358 return;
359
360 if (ocr->list_dev != ocr->target)
361 fz_drop_device(ctx, ocr->list_dev);
362 fz_drop_display_list(ctx, ocr->list);
363 fz_drop_device(ctx, ocr->draw_dev);
364 fz_drop_pixmap(ctx, ocr->pixmap);
365 for (i = 0; i < ocr->words_len; i++)
366 fz_free(ctx, ocr->words[i]);
367 fz_free(ctx, ocr->words);
368 fz_free(ctx, ocr->chars);
369 fz_free(ctx, ocr->language);
370 }
371
372 static void
flush_word(fz_context * ctx,fz_ocr_device * ocr)373 flush_word(fz_context *ctx, fz_ocr_device *ocr)
374 {
375 float color = 1;
376 fz_color_params params = { 0 };
377 int i;
378 fz_text *text = NULL;
379 fz_matrix trm;
380 float step;
381 fz_rect char_bbox;
382
383 if (ocr->char_len == 0)
384 return;
385
386 /* If we're not sending direct to the target device, then insert
387 * all the chars we've found into a table so we can rewrite
388 * the text objects that come from the list device on the fly.
389 */
390 if (ocr->list_dev != ocr->target)
391 {
392 word_record *word;
393
394 if (ocr->words_len == ocr->words_max)
395 {
396 int new_max = ocr->words_max * 2;
397 if (new_max == 0)
398 new_max = 32;
399 ocr->words = fz_realloc_array(ctx, ocr->words, new_max, word_record *);
400 ocr->words_max = new_max;
401 }
402 word = (word_record *)Memento_label(fz_malloc(ctx, sizeof(word_record) + sizeof(int) * (ocr->char_len-1)), "word_record");
403 word->len = ocr->char_len;
404 word->bbox = ocr->word_bbox;
405 word->n = 0;
406 memcpy(word->unicode, ocr->chars, ocr->char_len * sizeof(int));
407 ocr->words[ocr->words_len++] = word;
408 ocr->char_len = 0;
409 return;
410 }
411 /* FIXME: Look at font-name. */
412 /* All this is a bit horrid, because the detection of sizes for
413 * the glyphs depends on the width of the glyphs. Use Courier
414 * because it's monospaced. */
415 if (ocr->font == NULL)
416 ocr->font = fz_new_base14_font(ctx, "Courier");
417
418 fz_var(text);
419
420 fz_try(ctx)
421 {
422 text = fz_new_text(ctx);
423
424 /* Divide the word box into equal lengths. */
425 /* This falls down when we have words with chars of
426 * different widths in, but it's acceptable for these
427 * purposes. */
428 /* FIXME: This assumes L2R motion of text. */
429 step = (ocr->word_bbox.x1 - ocr->word_bbox.x0) / ocr->char_len;
430 char_bbox.x1 = ocr->word_bbox.x0;
431 char_bbox.y0 = ocr->word_bbox.y0;
432 char_bbox.y1 = ocr->word_bbox.y1;
433 for (i = 0; i < ocr->char_len; i++)
434 {
435 char_bbox.x0 = char_bbox.x1;
436 char_bbox.x1 += step;
437 /* Horrid constants that happen to work with Courier. */
438 trm.a = 10.0f/6 * (char_bbox.x1 - char_bbox.x0);
439 trm.b = 0;
440 trm.c = 0;
441 trm.d = (char_bbox.y1 - char_bbox.y0);
442 trm.e = char_bbox.x0;
443 trm.f = char_bbox.y0;
444 fz_show_glyph(ctx, text, ocr->font, trm,
445 ocr->chars[i], ocr->chars[i],
446 0, 0, FZ_BIDI_LTR, 0);
447 }
448
449 fz_fill_text(ctx, ocr->target, text, fz_identity,
450 fz_device_gray(ctx), &color, 1, params);
451 }
452 fz_always(ctx)
453 {
454 fz_drop_text(ctx, text);
455 }
456 fz_catch(ctx)
457 fz_rethrow(ctx);
458
459 ocr->char_len = 0;
460 }
461
462 static void
char_callback(fz_context * ctx,void * arg,int unicode,const char * font_name,const int * line_bbox,const int * word_bbox,const int * char_bbox,int pointsize)463 char_callback(fz_context *ctx, void *arg, int unicode,
464 const char *font_name,
465 const int *line_bbox, const int *word_bbox,
466 const int *char_bbox, int pointsize)
467 {
468 fz_ocr_device *ocr = (fz_ocr_device *)arg;
469 fz_rect bbox = { word_bbox[0]-1, word_bbox[1]-1, word_bbox[2]+1, word_bbox[3]+1 };
470
471 if (unicode == 'b')
472 {
473 fz_device *device = fz_new_draw_device(ctx, fz_identity, ocr->pixmap);
474 fz_path *path = fz_new_path(ctx);
475 fz_color_params params = {0};
476 fz_stroke_state stroke = { 0 };
477 float color = 0;
478 stroke.linewidth = 1;
479 fz_rectto(ctx, path, char_bbox[0], char_bbox[1], char_bbox[2], char_bbox[3]);
480 fz_stroke_path(ctx, device, path, &stroke, fz_identity, fz_device_gray(ctx), &color, 1, params);
481 fz_drop_path(ctx, path);
482 fz_close_device(ctx, device);
483 fz_drop_device(ctx, device);
484 }
485
486 if (bbox.x0 != ocr->word_bbox.x0 ||
487 bbox.y0 != ocr->word_bbox.y0 ||
488 bbox.x1 != ocr->word_bbox.x1 ||
489 bbox.y1 != ocr->word_bbox.y1)
490 {
491 flush_word(ctx, ocr);
492 ocr->word_bbox = bbox;
493 }
494
495 if (ocr->char_max == ocr->char_len)
496 {
497 int new_max = ocr->char_max * 2;
498 if (new_max == 0)
499 new_max = 32;
500 ocr->chars = fz_realloc_array(ctx, ocr->chars, new_max, int);
501 ocr->char_max = new_max;
502 }
503
504 ocr->chars[ocr->char_len++] = unicode;
505 }
506
507
508 typedef struct
509 {
510 fz_device super;
511
512 fz_device *target;
513 int words_len;
514 word_record **words;
515 int current;
516 } fz_rewrite_device;
517
518 static fz_text_span *
fz_clone_text_span(fz_context * ctx,const fz_text_span * span)519 fz_clone_text_span(fz_context *ctx, const fz_text_span *span)
520 {
521 fz_text_span *cspan;
522
523 if (span == NULL)
524 return NULL;
525
526 cspan = fz_malloc_struct(ctx, fz_text_span);
527 *cspan = *span;
528 cspan->cap = cspan->len;
529 cspan->items = fz_malloc_no_throw(ctx, sizeof(*cspan->items) * cspan->len);
530 if (cspan->items == NULL)
531 {
532 fz_free(ctx, cspan);
533 fz_throw(ctx, FZ_ERROR_MEMORY, "Failed to malloc while cloning text span");
534 }
535 memcpy(cspan->items, span->items, sizeof(*cspan->items) * cspan->len);
536 fz_keep_font(ctx, cspan->font);
537
538 return cspan;
539 }
540
541 #ifdef DEBUG_OCR
542 static void
debug_word(fz_context * ctx,word_record * word)543 debug_word(fz_context *ctx, word_record *word)
544 {
545 int i;
546
547 fz_write_printf(ctx, fz_stdout(ctx), " %g %g %g %g:",
548 word->bbox.x0,
549 word->bbox.y0,
550 word->bbox.x1,
551 word->bbox.y1);
552
553 for (i = 0; i < word->n; i++)
554 {
555 int unicode = word->unicode[i];
556 if (unicode >= 32 && unicode < 127)
557 fz_write_printf(ctx, fz_stdout(ctx), "%c", unicode);
558 else
559 fz_write_printf(ctx, fz_stdout(ctx), "<%04x>", unicode);
560 }
561 if (word->n < word->len)
562 {
563 int unicode = word->unicode[i++];
564 if (unicode >= 32 && unicode < 127)
565 fz_write_printf(ctx, fz_stdout(ctx), "{%c}", unicode);
566 else
567 fz_write_printf(ctx, fz_stdout(ctx), "{<%04x>}", unicode);
568 for (; i < word->len; i++)
569 {
570 int unicode = word->unicode[i];
571 if (unicode >= 32 && unicode < 127)
572 fz_write_printf(ctx, fz_stdout(ctx), "%c", unicode);
573 else
574 fz_write_printf(ctx, fz_stdout(ctx), "<%04x>", unicode);
575 }
576 }
577 fz_write_printf(ctx, fz_stdout(ctx), "\n");
578 }
579 #endif
580
581 static void
rewrite_char(fz_context * ctx,fz_rewrite_device * dev,fz_matrix ctm,fz_text_item * item,fz_point vadv)582 rewrite_char(fz_context *ctx, fz_rewrite_device *dev, fz_matrix ctm, fz_text_item *item, fz_point vadv)
583 {
584 int i, start;
585 fz_point p = { item->x, item->y };
586
587 /* No point in trying to rewrite spaces! */
588 if (item->ucs == 32)
589 return;
590
591 p = fz_transform_point(p, ctm);
592 p.x += vadv.x/2;
593 p.y += vadv.y/2;
594
595 #ifdef DEBUG_OCR
596 fz_write_printf(ctx, fz_stdout(ctx), "Looking for '%c' at %g %g\n", item->ucs, p.x, p.y);
597 #endif
598
599 start = dev->current;
600 for (i = start; i < dev->words_len; i++)
601 {
602 #ifdef DEBUG_OCR
603 debug_word(ctx, dev->words[i]);
604 #endif
605 if (dev->words[i]->n >= dev->words[i]->len)
606 continue;
607 if (dev->words[i]->bbox.x0 <= p.x &&
608 dev->words[i]->bbox.x1 >= p.x &&
609 dev->words[i]->bbox.y0 <= p.y &&
610 dev->words[i]->bbox.y1 >= p.y)
611 {
612 item->ucs = dev->words[i]->unicode[dev->words[i]->n++];
613 dev->current = i;
614 return;
615 }
616 }
617 for (i = 0; i < start; i++)
618 {
619 #ifdef DEBUG_OCR
620 debug_word(ctx, dev->words[i]);
621 #endif
622 if (dev->words[i]->n >= dev->words[i]->len)
623 continue;
624 if (dev->words[i]->bbox.x0 <= p.x &&
625 dev->words[i]->bbox.x1 >= p.x &&
626 dev->words[i]->bbox.y0 <= p.y &&
627 dev->words[i]->bbox.y1 >= p.y)
628 {
629 item->ucs = dev->words[i]->unicode[dev->words[i]->n++];
630 dev->current = i;
631 return;
632 }
633 }
634 }
635
636 static fz_text_span *
rewrite_span(fz_context * ctx,fz_rewrite_device * dev,fz_matrix ctm,const fz_text_span * span)637 rewrite_span(fz_context *ctx, fz_rewrite_device *dev, fz_matrix ctm, const fz_text_span *span)
638 {
639 fz_text_span *rspan = fz_clone_text_span(ctx, span);
640 int wmode = span->wmode;
641 int i;
642 fz_point dir;
643 fz_matrix trm = span->trm;
644
645 trm.e = 0;
646 trm.f = 0;
647 trm = fz_concat(trm, ctm);
648
649 if (wmode == 0)
650 {
651 dir.x = 1;
652 dir.y = 0;
653 }
654 else
655 {
656 dir.x = 0;
657 dir.y = -1;
658 }
659 dir = fz_transform_vector(dir, trm);
660
661 /* And do the actual rewriting */
662 for (i = 0; i < rspan->len; i++) {
663 float advance = fz_advance_glyph(ctx, span->font, rspan->items[i].gid, wmode);
664 fz_point vadv = { dir.x * advance, dir.y * advance };
665 rewrite_char(ctx, dev, ctm, &rspan->items[i], vadv);
666 }
667
668 return rspan;
669 }
670
671 static fz_text *
rewrite_text(fz_context * ctx,fz_rewrite_device * dev,fz_matrix ctm,const fz_text * text)672 rewrite_text(fz_context *ctx, fz_rewrite_device *dev, fz_matrix ctm, const fz_text *text)
673 {
674 fz_text *rtext = fz_new_text(ctx);
675 fz_text_span *span = text->head;
676 fz_text_span **dspan = &rtext->head;
677
678 fz_try(ctx)
679 {
680 while (span)
681 {
682 *dspan = rewrite_span(ctx, dev, ctm, span);
683 rtext->tail = *dspan;
684 dspan = &(*dspan)->next;
685 span = span->next;
686 }
687 }
688 fz_catch(ctx)
689 {
690 fz_drop_text(ctx, rtext);
691 fz_rethrow(ctx);
692 }
693
694 return rtext;
695 }
696
697 static void
rewrite_fill_path(fz_context * ctx,fz_device * dev,const fz_path * path,int even_odd,fz_matrix ctm,fz_colorspace * cs,const float * color,float alpha,fz_color_params params)698 rewrite_fill_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params params)
699 {
700 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
701
702 fz_fill_path(ctx, rewrite->target, path, even_odd, ctm, cs, color, alpha, params);
703 }
704
705 static void
rewrite_stroke_path(fz_context * ctx,fz_device * dev,const fz_path * path,const fz_stroke_state * stroke,fz_matrix ctm,fz_colorspace * cs,const float * color,float alpha,fz_color_params params)706 rewrite_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params params)
707 {
708 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
709
710 fz_stroke_path(ctx, rewrite->target, path, stroke, ctm, cs, color, alpha, params);
711 }
712
713 static void
rewrite_clip_path(fz_context * ctx,fz_device * dev,const fz_path * path,int even_odd,fz_matrix ctm,fz_rect scissor)714 rewrite_clip_path(fz_context *ctx, fz_device *dev, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
715 {
716 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
717
718 fz_clip_path(ctx, rewrite->target, path, even_odd, ctm, scissor);
719 }
720
721 static void
rewrite_clip_stroke_path(fz_context * ctx,fz_device * dev,const fz_path * path,const fz_stroke_state * stroke,fz_matrix ctm,fz_rect scissor)722 rewrite_clip_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
723 {
724 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
725
726 fz_clip_stroke_path(ctx, rewrite->target, path, stroke, ctm, scissor);
727 }
728
729 static void
rewrite_fill_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm,fz_colorspace * cs,const float * color,float alpha,fz_color_params params)730 rewrite_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params params)
731 {
732 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
733 fz_text *rtext = rewrite_text(ctx, rewrite, ctm, text);
734
735 fz_try(ctx)
736 fz_fill_text(ctx, rewrite->target, rtext, ctm, cs, color, alpha, params);
737 fz_always(ctx)
738 fz_drop_text(ctx, rtext);
739 fz_catch(ctx)
740 fz_rethrow(ctx);
741 }
742
743 static void
rewrite_stroke_text(fz_context * ctx,fz_device * dev,const fz_text * text,const fz_stroke_state * stroke,fz_matrix ctm,fz_colorspace * cs,const float * color,float alpha,fz_color_params params)744 rewrite_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params params)
745 {
746 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
747 fz_text *rtext = rewrite_text(ctx, rewrite, ctm, text);
748
749 fz_try(ctx)
750 fz_stroke_text(ctx, rewrite->target, rtext, stroke, ctm, cs, color, alpha, params);
751 fz_always(ctx)
752 fz_drop_text(ctx, rtext);
753 fz_catch(ctx)
754 fz_rethrow(ctx);
755 }
756
757 static void
rewrite_clip_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm,fz_rect scissor)758 rewrite_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor)
759 {
760 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
761 fz_text *rtext = rewrite_text(ctx, rewrite, ctm, text);
762
763 fz_try(ctx)
764 fz_clip_text(ctx, rewrite->target, rtext, ctm, scissor);
765 fz_always(ctx)
766 fz_drop_text(ctx, rtext);
767 fz_catch(ctx)
768 fz_rethrow(ctx);
769 }
770
771 static void
rewrite_clip_stroke_text(fz_context * ctx,fz_device * dev,const fz_text * text,const fz_stroke_state * stroke,fz_matrix ctm,fz_rect scissor)772 rewrite_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor)
773 {
774 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
775 fz_text *rtext = rewrite_text(ctx, rewrite, ctm, text);
776
777 fz_try(ctx)
778 fz_clip_stroke_text(ctx, rewrite->target, rtext, stroke, ctm, scissor);
779 fz_always(ctx)
780 fz_drop_text(ctx, rtext);
781 fz_catch(ctx)
782 fz_rethrow(ctx);
783 }
784
785 static void
rewrite_ignore_text(fz_context * ctx,fz_device * dev,const fz_text * text,fz_matrix ctm)786 rewrite_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm)
787 {
788 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
789 fz_text *rtext = rewrite_text(ctx, rewrite, ctm, text);
790
791 fz_try(ctx)
792 fz_ignore_text(ctx, rewrite->target, rtext, ctm);
793 fz_always(ctx)
794 fz_drop_text(ctx, rtext);
795 fz_catch(ctx)
796 fz_rethrow(ctx);
797 }
798
799 static void
rewrite_fill_shade(fz_context * ctx,fz_device * dev,fz_shade * shd,fz_matrix ctm,float alpha,fz_color_params color_params)800 rewrite_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shd, fz_matrix ctm, float alpha, fz_color_params color_params)
801 {
802 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
803
804 fz_fill_shade(ctx, rewrite->target, shd, ctm, alpha, color_params);
805 }
806
807 static void
rewrite_fill_image(fz_context * ctx,fz_device * dev,fz_image * img,fz_matrix ctm,float alpha,fz_color_params color_params)808 rewrite_fill_image(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, float alpha, fz_color_params color_params)
809 {
810 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
811
812 fz_fill_image(ctx, rewrite->target, img, ctm, alpha, color_params);
813 }
814
815 static void
rewrite_fill_image_mask(fz_context * ctx,fz_device * dev,fz_image * img,fz_matrix ctm,fz_colorspace * cs,const float * color,float alpha,fz_color_params color_params)816 rewrite_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params color_params)
817 {
818 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
819
820 fz_fill_image_mask(ctx, rewrite->target, img, ctm, cs, color, alpha, color_params);
821 }
822
823 static void
rewrite_clip_image_mask(fz_context * ctx,fz_device * dev,fz_image * img,fz_matrix ctm,fz_rect scissor)824 rewrite_clip_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, fz_rect scissor)
825 {
826 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
827
828 fz_clip_image_mask(ctx, rewrite->target, img, ctm, scissor);
829 }
830
831 static void
rewrite_pop_clip(fz_context * ctx,fz_device * dev)832 rewrite_pop_clip(fz_context *ctx, fz_device *dev)
833 {
834 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
835
836 fz_pop_clip(ctx, rewrite->target);
837 }
838
839 static void
rewrite_begin_mask(fz_context * ctx,fz_device * dev,fz_rect area,int luminosity,fz_colorspace * cs,const float * bc,fz_color_params params)840 rewrite_begin_mask(fz_context *ctx, fz_device *dev, fz_rect area, int luminosity, fz_colorspace *cs, const float *bc, fz_color_params params)
841 {
842 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
843
844 fz_begin_mask(ctx, rewrite->target, area, luminosity, cs, bc, params);
845 }
846
847 static void
rewrite_end_mask(fz_context * ctx,fz_device * dev)848 rewrite_end_mask(fz_context *ctx, fz_device *dev)
849 {
850 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
851
852 fz_end_mask(ctx, rewrite->target);
853 }
854
855 static void
rewrite_begin_group(fz_context * ctx,fz_device * dev,fz_rect area,fz_colorspace * cs,int isolated,int knockout,int blendmode,float alpha)856 rewrite_begin_group(fz_context *ctx, fz_device *dev, fz_rect area, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
857 {
858 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
859
860 fz_begin_group(ctx, rewrite->target, area, cs, isolated, knockout, blendmode, alpha);
861 }
862
863 static void
rewrite_end_group(fz_context * ctx,fz_device * dev)864 rewrite_end_group(fz_context *ctx, fz_device *dev)
865 {
866 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
867
868 fz_end_group(ctx, rewrite->target);
869 }
870
871 static int
rewrite_begin_tile(fz_context * ctx,fz_device * dev,fz_rect area,fz_rect view,float xstep,float ystep,fz_matrix ctm,int id)872 rewrite_begin_tile(fz_context *ctx, fz_device *dev, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id)
873 {
874 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
875
876 return fz_begin_tile_id(ctx, rewrite->target, area, view, xstep, ystep, ctm, id);
877 }
878
879 static void
rewrite_end_tile(fz_context * ctx,fz_device * dev)880 rewrite_end_tile(fz_context *ctx, fz_device *dev)
881 {
882 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
883
884 fz_end_tile(ctx, rewrite->target);
885 }
886
887 static void
rewrite_render_flags(fz_context * ctx,fz_device * dev,int set,int clear)888 rewrite_render_flags(fz_context *ctx, fz_device *dev, int set, int clear)
889 {
890 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
891
892 fz_render_flags(ctx, rewrite->target, set, clear);
893 }
894
895 static void
rewrite_set_default_colorspaces(fz_context * ctx,fz_device * dev,fz_default_colorspaces * cs)896 rewrite_set_default_colorspaces(fz_context *ctx, fz_device *dev, fz_default_colorspaces *cs)
897 {
898 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
899
900 fz_set_default_colorspaces(ctx, rewrite->target, cs);
901 }
902
903 static void
rewrite_begin_layer(fz_context * ctx,fz_device * dev,const char * layer_name)904 rewrite_begin_layer(fz_context *ctx, fz_device *dev, const char *layer_name)
905 {
906 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
907
908 fz_begin_layer(ctx, rewrite->target, layer_name);
909 }
910
911 static void
rewrite_end_layer(fz_context * ctx,fz_device * dev)912 rewrite_end_layer(fz_context *ctx, fz_device *dev)
913 {
914 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
915
916 fz_end_layer(ctx, rewrite->target);
917 }
918
919 static void
rewrite_close(fz_context * ctx,fz_device * dev)920 rewrite_close(fz_context *ctx, fz_device *dev)
921 {
922 fz_rewrite_device *rewrite = (fz_rewrite_device *)dev;
923 fz_font *font;
924 fz_text *text = NULL;
925 fz_matrix trm;
926 int i, j;
927
928 /* All this is a bit horrid, because the detection of sizes for
929 * the glyphs depends on the width of the glyphs. Use Courier
930 * because it's monospaced. */
931 font = fz_new_base14_font(ctx, "Courier");
932
933 fz_var(text);
934
935 fz_try(ctx)
936 {
937 text = fz_new_text(ctx);
938
939 for (i = 0; i < rewrite->words_len; i++)
940 {
941 word_record *word = rewrite->words[i];
942 fz_rect char_bbox;
943 float step;
944
945 if (word->n >= word->len)
946 continue;
947 step = (word->bbox.x1 - word->bbox.x0) / word->len;
948 char_bbox.x1 = word->bbox.x0;
949 char_bbox.y0 = word->bbox.y0;
950 char_bbox.y1 = word->bbox.y1;
951 for (j = 0; j < word->len; j++)
952 {
953 char_bbox.x0 = char_bbox.x1;
954 char_bbox.x1 += step;
955 /* Horrid constants that happen to work with Courier. */
956 trm.a = 10.0f/6 * (char_bbox.x1 - char_bbox.x0);
957 trm.b = 0;
958 trm.c = 0;
959 trm.d = (char_bbox.y1 - char_bbox.y0);
960 trm.e = char_bbox.x0;
961 trm.f = char_bbox.y0;
962 fz_show_glyph(ctx, text, font, trm,
963 word->unicode[j], word->unicode[j],
964 0, 0, FZ_BIDI_LTR, 0);
965 }
966 }
967
968 fz_ignore_text(ctx, rewrite->target, text, fz_identity);
969 }
970 fz_always(ctx)
971 {
972 fz_drop_text(ctx, text);
973 fz_drop_font(ctx, font);
974 }
975 fz_catch(ctx)
976 fz_rethrow(ctx);
977 }
978
979 static fz_device *
new_rewrite_device(fz_context * ctx,fz_device * target,word_record ** words,int words_len)980 new_rewrite_device(fz_context *ctx, fz_device *target, word_record **words, int words_len)
981 {
982 fz_rewrite_device *rewrite;
983
984 rewrite = fz_new_derived_device(ctx, fz_rewrite_device);
985
986 rewrite->super.close_device = rewrite_close;
987
988 rewrite->super.fill_path = rewrite_fill_path;
989 rewrite->super.stroke_path = rewrite_stroke_path;
990 rewrite->super.clip_path = rewrite_clip_path;
991 rewrite->super.clip_stroke_path = rewrite_clip_stroke_path;
992
993 rewrite->super.fill_text = rewrite_fill_text;
994 rewrite->super.stroke_text = rewrite_stroke_text;
995 rewrite->super.clip_text = rewrite_clip_text;
996 rewrite->super.clip_stroke_text = rewrite_clip_stroke_text;
997 rewrite->super.ignore_text = rewrite_ignore_text;
998
999 rewrite->super.fill_shade = rewrite_fill_shade;
1000 rewrite->super.fill_image = rewrite_fill_image;
1001 rewrite->super.fill_image_mask = rewrite_fill_image_mask;
1002 rewrite->super.clip_image_mask = rewrite_clip_image_mask;
1003
1004 rewrite->super.pop_clip = rewrite_pop_clip;
1005
1006 rewrite->super.begin_mask = rewrite_begin_mask;
1007 rewrite->super.end_mask = rewrite_end_mask;
1008 rewrite->super.begin_group = rewrite_begin_group;
1009 rewrite->super.end_group = rewrite_end_group;
1010
1011 rewrite->super.begin_tile = rewrite_begin_tile;
1012 rewrite->super.end_tile = rewrite_end_tile;
1013
1014 rewrite->super.render_flags = rewrite_render_flags;
1015 rewrite->super.set_default_colorspaces = rewrite_set_default_colorspaces;
1016
1017 rewrite->super.begin_layer = rewrite_begin_layer;
1018 rewrite->super.end_layer = rewrite_end_layer;
1019
1020 rewrite->target = target;
1021 rewrite->words = words;
1022 rewrite->words_len = words_len;
1023 rewrite->current = 0;
1024
1025 return &rewrite->super;
1026 }
1027
1028 static void
fz_ocr_close_device(fz_context * ctx,fz_device * dev)1029 fz_ocr_close_device(fz_context *ctx, fz_device *dev)
1030 {
1031 fz_ocr_device *ocr = (fz_ocr_device *)dev;
1032 void *tessapi;
1033 fz_device *rewrite_device;
1034 fz_rect bbox;
1035
1036 fz_close_device(ctx, ocr->draw_dev);
1037
1038 /* Now run the OCR */
1039 tessapi = ocr_init(ctx, ocr->language);
1040
1041 fz_try(ctx)
1042 {
1043 ocr_recognise(ctx, tessapi, ocr->pixmap, char_callback, ocr);
1044 flush_word(ctx, ocr);
1045 }
1046 fz_always(ctx)
1047 ocr_fin(ctx, tessapi);
1048 fz_catch(ctx)
1049 fz_rethrow(ctx);
1050
1051 fz_save_pixmap_as_png(ctx, ocr->pixmap, "ass.png");
1052
1053 /* If we're not using a list, we're done! */
1054 if (ocr->list_dev == ocr->target)
1055 return;
1056
1057 fz_close_device(ctx, ocr->list_dev);
1058
1059 bbox = fz_transform_rect(ocr->mediabox, ocr->ctm);
1060 rewrite_device = new_rewrite_device(ctx, ocr->target, ocr->words, ocr->words_len);
1061 fz_try(ctx)
1062 {
1063 fz_run_display_list(ctx, ocr->list, rewrite_device,
1064 fz_identity, bbox, NULL);
1065 }
1066 fz_always(ctx)
1067 {
1068 fz_close_device(ctx, rewrite_device);
1069 fz_drop_device(ctx, rewrite_device);
1070 }
1071 fz_catch(ctx)
1072 fz_rethrow(ctx);
1073 }
1074
1075 static void
fz_ocr_drop_device(fz_context * ctx,fz_device * dev)1076 fz_ocr_drop_device(fz_context *ctx, fz_device *dev)
1077 {
1078 drop_ocr_device(ctx, (fz_ocr_device *)dev);
1079 }
1080 #endif
1081
1082 fz_device *
fz_new_ocr_device(fz_context * ctx,fz_device * target,fz_matrix ctm,fz_rect mediabox,int with_list,const char * language)1083 fz_new_ocr_device(fz_context *ctx, fz_device *target, fz_matrix ctm, fz_rect mediabox, int with_list, const char *language)
1084 {
1085 #ifdef OCR_DISABLED
1086 fz_throw(ctx, FZ_ERROR_GENERIC, "OCR Disabled in this build");
1087 #else
1088 fz_ocr_device *dev;
1089
1090 if (target == NULL)
1091 fz_throw(ctx, FZ_ERROR_GENERIC, "OCR devices require a target");
1092
1093 dev = fz_new_derived_device(ctx, fz_ocr_device);
1094
1095 dev->super.close_device = fz_ocr_close_device;
1096 dev->super.drop_device = fz_ocr_drop_device;
1097
1098 dev->super.fill_path = fz_ocr_fill_path;
1099 dev->super.stroke_path = fz_ocr_stroke_path;
1100 dev->super.clip_path = fz_ocr_clip_path;
1101 dev->super.clip_stroke_path = fz_ocr_clip_stroke_path;
1102
1103 dev->super.fill_text = fz_ocr_fill_text;
1104 dev->super.stroke_text = fz_ocr_stroke_text;
1105 dev->super.clip_text = fz_ocr_clip_text;
1106 dev->super.clip_stroke_text = fz_ocr_clip_stroke_text;
1107 dev->super.ignore_text = fz_ocr_ignore_text;
1108
1109 dev->super.fill_shade = fz_ocr_fill_shade;
1110 dev->super.fill_image = fz_ocr_fill_image;
1111 dev->super.fill_image_mask = fz_ocr_fill_image_mask;
1112 dev->super.clip_image_mask = fz_ocr_clip_image_mask;
1113
1114 dev->super.pop_clip = fz_ocr_pop_clip;
1115
1116 dev->super.begin_mask = fz_ocr_begin_mask;
1117 dev->super.end_mask = fz_ocr_end_mask;
1118 dev->super.begin_group = fz_ocr_begin_group;
1119 dev->super.end_group = fz_ocr_end_group;
1120
1121 dev->super.begin_tile = fz_ocr_begin_tile;
1122 dev->super.end_tile = fz_ocr_end_tile;
1123
1124 dev->super.render_flags = fz_ocr_render_flags;
1125 dev->super.set_default_colorspaces = fz_ocr_set_default_colorspaces;
1126 dev->super.begin_layer = fz_ocr_begin_layer;
1127 dev->super.end_layer = fz_ocr_end_layer;
1128
1129 fz_try(ctx)
1130 {
1131 fz_rect bbox;
1132 fz_irect ibox;
1133 fz_point res;
1134
1135 dev->target = target;
1136 dev->mediabox = mediabox;
1137 dev->ctm = ctm;
1138
1139 bbox = fz_transform_rect(mediabox, ctm);
1140 ibox = fz_round_rect(bbox);
1141 /* Fudge the width to be a multiple of 4. */
1142 ibox.x1 += (4-(ibox.x1-ibox.x0)) & 3;
1143 dev->pixmap = fz_new_pixmap_with_bbox(ctx, fz_device_gray(ctx),
1144 ibox, NULL, 0);
1145 fz_clear_pixmap(ctx, dev->pixmap);
1146 res = fz_transform_point_xy(72, 72, ctm);
1147 if (res.x < 0)
1148 res.x = -res.x;
1149 if (res.x < 1)
1150 res.x = 1;
1151 if (res.y < 0)
1152 res.y = -res.y;
1153 if (res.y < 1)
1154 res.y = 1;
1155 fz_set_pixmap_resolution(ctx, dev->pixmap, res.x, res.y);
1156
1157 dev->language = fz_strdup(ctx, language ? language : "eng");
1158
1159 dev->draw_dev = fz_new_draw_device(ctx, fz_identity, dev->pixmap);
1160 if (with_list)
1161 {
1162 dev->list = fz_new_display_list(ctx, mediabox);
1163 dev->list_dev = fz_new_list_device(ctx, dev->list);
1164 } else
1165 dev->list_dev = dev->target;
1166 }
1167 fz_catch(ctx)
1168 {
1169 drop_ocr_device(ctx, dev);
1170 fz_rethrow(ctx);
1171 }
1172
1173 return (fz_device*)dev;
1174 #endif
1175 }
1176