/src/mupdf/source/fitz/stext-device.c
Line | Count | Source |
1 | | // Copyright (C) 2004-2026 Artifex Software, Inc. |
2 | | // |
3 | | // This file is part of MuPDF. |
4 | | // |
5 | | // MuPDF is free software: you can redistribute it and/or modify it under the |
6 | | // terms of the GNU Affero General Public License as published by the Free |
7 | | // Software Foundation, either version 3 of the License, or (at your option) |
8 | | // any later version. |
9 | | // |
10 | | // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY |
11 | | // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
12 | | // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
13 | | // details. |
14 | | // You should have received a copy of the GNU Affero General Public License |
15 | | // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html> |
16 | | // |
17 | | // Alternative licensing terms are available from the licensor. |
18 | | // For commercial licensing, see <https://www.artifex.com/> or contact |
19 | | // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
20 | | // CA 94129, USA, for further information. |
21 | | |
22 | | #include "mupdf/fitz.h" |
23 | | |
24 | | #include "glyphbox.h" |
25 | | |
26 | | #include "mupdf/ucdn.h" |
27 | | |
28 | | #include <float.h> |
29 | | #include <string.h> |
30 | | |
31 | | /* Simple layout structure */ |
32 | | |
33 | | fz_layout_block *fz_new_layout(fz_context *ctx) |
34 | 0 | { |
35 | 0 | fz_pool *pool = fz_new_pool(ctx); |
36 | 0 | fz_layout_block *block; |
37 | 0 | fz_try(ctx) |
38 | 0 | { |
39 | 0 | block = fz_pool_alloc(ctx, pool, sizeof (fz_layout_block)); |
40 | 0 | block->pool = pool; |
41 | 0 | block->head = NULL; |
42 | 0 | block->tailp = &block->head; |
43 | 0 | } |
44 | 0 | fz_catch(ctx) |
45 | 0 | { |
46 | 0 | fz_drop_pool(ctx, pool); |
47 | 0 | fz_rethrow(ctx); |
48 | 0 | } |
49 | 0 | return block; |
50 | 0 | } |
51 | | |
52 | | void fz_drop_layout(fz_context *ctx, fz_layout_block *block) |
53 | 0 | { |
54 | 0 | if (block) |
55 | 0 | fz_drop_pool(ctx, block->pool); |
56 | 0 | } |
57 | | |
58 | | void fz_add_layout_line(fz_context *ctx, fz_layout_block *block, float x, float y, float font_size, const char *p) |
59 | 0 | { |
60 | 0 | fz_layout_line *line = fz_pool_alloc(ctx, block->pool, sizeof (fz_layout_line)); |
61 | 0 | line->x = x; |
62 | 0 | line->y = y; |
63 | 0 | line->font_size = font_size; |
64 | 0 | line->p = p; |
65 | 0 | line->text = NULL; |
66 | 0 | line->next = NULL; |
67 | 0 | *block->tailp = line; |
68 | 0 | block->tailp = &line->next; |
69 | 0 | block->text_tailp = &line->text; |
70 | 0 | } |
71 | | |
72 | | void fz_add_layout_char(fz_context *ctx, fz_layout_block *block, float x, float advance, const char *p) |
73 | 0 | { |
74 | 0 | fz_layout_char *ch = fz_pool_alloc(ctx, block->pool, sizeof (fz_layout_char)); |
75 | 0 | ch->x = x; |
76 | 0 | ch->advance = advance; |
77 | 0 | ch->p = p; |
78 | 0 | ch->next = NULL; |
79 | 0 | *block->text_tailp = ch; |
80 | 0 | block->text_tailp = &ch->next; |
81 | 0 | } |
82 | | |
83 | | /* Extract text into blocks and lines. */ |
84 | | |
85 | 0 | #define PARAGRAPH_DIST 1.5f |
86 | 0 | #define SPACE_DIST 0.15f |
87 | 0 | #define SPACE_MAX_DIST 0.8f |
88 | 0 | #define BASE_MAX_DIST 0.8f |
89 | 0 | #define FAKE_BOLD_MAX_DIST 0.1f |
90 | | |
91 | | /* We keep a stack of the different metatexts that apply at any |
92 | | * given point (normally none!). Whenever we get some content |
93 | | * with a metatext in force, we really want to update the bounds |
94 | | * for that metatext. But running along the whole list each time |
95 | | * would be painful. So we just update the bounds for dev->metatext |
96 | | * and rely on metatext_bounds() propagating it upwards 'just in |
97 | | * time' for us to use metatexts other than the latest one. This |
98 | | * also means we need to propagate bounds upwards when we pop |
99 | | * a metatext. |
100 | | * |
101 | | * Why do we need bounds at all? Well, suppose we get: |
102 | | * /Span <</ActualText (c) >> BDC /Im0 Do EMC |
103 | | * Then where on the page do we put 'c' ? By collecting the |
104 | | * bounds, we can place 'c' wherever the image was. |
105 | | */ |
106 | | typedef struct metatext_t |
107 | | { |
108 | | fz_metatext type; |
109 | | char *text; |
110 | | fz_rect bounds; |
111 | | struct metatext_t *prev; |
112 | | } metatext_t; |
113 | | |
114 | | typedef struct |
115 | | { |
116 | | fz_point from; |
117 | | fz_point to; |
118 | | float thickness; |
119 | | fz_rect rect; |
120 | | int argb; |
121 | | fz_stext_block *blk; |
122 | | } rect_details; |
123 | | |
124 | | typedef struct |
125 | | { |
126 | | fz_device super; |
127 | | fz_stext_page *page; |
128 | | int id; |
129 | | fz_point pen, start; |
130 | | // maybe_bullet: True if the 'start' position recorded was done so after either some actualtext |
131 | | // on an image, or after a glyph that's known to be used for bullets. This is used to stop us |
132 | | // spotting an 'indented' paragraph, because it's possibly just a bulleted list. |
133 | | int maybe_bullet; |
134 | | fz_point lag_pen; |
135 | | fz_matrix trm; |
136 | | float lag_size; |
137 | | int lastchar; |
138 | | fz_stext_line *lastline; |
139 | | int lastbidi; |
140 | | int flags; |
141 | | uint32_t color; |
142 | | int last_was_fake_bold; |
143 | | const fz_text *lasttext; |
144 | | fz_stext_options opts; |
145 | | |
146 | | metatext_t *metatext; |
147 | | |
148 | | /* Store the last values we saw. We need this for flushing the actualtext. */ |
149 | | struct |
150 | | { |
151 | | int valid; |
152 | | int clipped; |
153 | | fz_matrix trm; |
154 | | int wmode; |
155 | | int bidi_level; |
156 | | fz_font *font; |
157 | | int flags; |
158 | | } last; |
159 | | |
160 | | /* The list of 'rects' seen during processing (if we're collecting styles). */ |
161 | | fz_list(rect_details, rects); |
162 | | |
163 | | fz_stext_block *lazy_vectors; |
164 | | fz_stext_block *lazy_vectors_tail; |
165 | | } fz_stext_device; |
166 | | |
167 | | const char *fz_stext_options_usage = |
168 | | "Structured text options:\n" |
169 | | "\tpreserve-images: keep images in output\n" |
170 | | "\tpreserve-ligatures: do not expand ligatures into constituent characters\n" |
171 | | "\tpreserve-spans: do not merge spans on the same line\n" |
172 | | "\tpreserve-whitespace: do not convert all whitespace into space characters\n" |
173 | | "\tinhibit-spaces: don't add spaces between gaps in the text\n" |
174 | | "\tparagraph-break: break blocks at paragraph boundaries\n" |
175 | | "\tdehyphenate: attempt to join up hyphenated words\n" |
176 | | "\tignore-actualtext: do not apply ActualText replacements\n" |
177 | | "\tuse-cid-for-unknown-unicode: use character code if unicode mapping fails\n" |
178 | | "\tuse-gid-for-unknown-unicode: use glyph index if unicode mapping fails\n" |
179 | | "\taccurate-bboxes: calculate char bboxes from the outlines\n" |
180 | | "\taccurate-ascenders: calculate ascender/descender from font glyphs\n" |
181 | | "\taccurate-side-bearings: expand char bboxes to completely include width of glyphs\n" |
182 | | "\tcollect-styles: attempt to detect text features (fake bold, strikeout, underlined etc)\n" |
183 | | "\tclip: do not include text that is completely clipped\n" |
184 | | "\tclip-rect=x0:y0:x1:y1 specify clipping rectangle within which to collect content\n" |
185 | | "\tstructured: collect structure markup\n" |
186 | | "\tvectors: include vector bboxes in output\n" |
187 | | "\tlazy-vectors: delay vectors that would otherwise split a text line\n" |
188 | | "\tfuzzy-vectors: merge abutting horizontal/vertical vectors\n" |
189 | | "\tsegment: attempt to segment the page\n" |
190 | | "\ttable-hunt: hunt for tables within a (segmented) page\n" |
191 | | "\tresolution: resolution to render at\n" |
192 | | "Also, the following options for table-hunt, which are currently experimental and subject to change without notice:\n" |
193 | | "\tvertically-collapse-bordered-cells: if true, consider the contents of completely bordered super-cells as being vertically collapsable\n" |
194 | | "\n"; |
195 | | |
196 | | /* Find the current actualtext, if any. Will abort if dev == NULL. */ |
197 | | static metatext_t * |
198 | | find_actualtext(fz_stext_device *dev) |
199 | 0 | { |
200 | 0 | metatext_t *mt = dev->metatext; |
201 | |
|
202 | 0 | while (mt && mt->type != FZ_METATEXT_ACTUALTEXT) |
203 | 0 | mt = mt->prev; |
204 | |
|
205 | 0 | return mt; |
206 | 0 | } |
207 | | |
208 | | /* Find the bounds of the given metatext. Will abort if mt or |
209 | | * dev are NULL. */ |
210 | | static fz_rect * |
211 | | metatext_bounds(metatext_t *mt, fz_stext_device *dev) |
212 | 0 | { |
213 | 0 | metatext_t *mt2 = dev->metatext; |
214 | |
|
215 | 0 | while (mt2 != mt) |
216 | 0 | { |
217 | 0 | mt2->prev->bounds = fz_union_rect(mt2->prev->bounds, mt2->bounds); |
218 | 0 | mt2 = mt2->prev; |
219 | 0 | } |
220 | |
|
221 | 0 | return &mt->bounds; |
222 | 0 | } |
223 | | |
224 | | /* Find the bounds of the current actualtext, or NULL if there |
225 | | * isn't one. Will abort if dev is NULL. */ |
226 | | static fz_rect * |
227 | | actualtext_bounds(fz_stext_device *dev) |
228 | 0 | { |
229 | 0 | metatext_t *mt = find_actualtext(dev); |
230 | |
|
231 | 0 | if (mt == NULL) |
232 | 0 | return NULL; |
233 | | |
234 | 0 | return metatext_bounds(mt, dev); |
235 | 0 | } |
236 | | |
237 | | fz_stext_page * |
238 | | fz_new_stext_page(fz_context *ctx, fz_rect mediabox) |
239 | 0 | { |
240 | 0 | fz_pool *pool = fz_new_pool(ctx); |
241 | 0 | fz_stext_page *page = NULL; |
242 | 0 | fz_try(ctx) |
243 | 0 | { |
244 | 0 | page = fz_pool_alloc(ctx, pool, sizeof(*page)); |
245 | 0 | page->refs = 1; |
246 | 0 | page->pool = pool; |
247 | 0 | page->mediabox = mediabox; |
248 | 0 | page->first_block = NULL; |
249 | 0 | page->last_block = NULL; |
250 | 0 | page->id_list = fz_new_pool_array(ctx, pool, fz_stext_page_details, 4); |
251 | 0 | } |
252 | 0 | fz_catch(ctx) |
253 | 0 | { |
254 | 0 | fz_drop_pool(ctx, pool); |
255 | 0 | fz_rethrow(ctx); |
256 | 0 | } |
257 | 0 | return page; |
258 | 0 | } |
259 | | |
260 | | void |
261 | | fz_release_stext_block_run_resources(fz_context *ctx, fz_stext_block *block) |
262 | 0 | { |
263 | 0 | fz_stext_line *line; |
264 | 0 | fz_stext_char *ch; |
265 | 0 | while (block) |
266 | 0 | { |
267 | 0 | switch (block->type) |
268 | 0 | { |
269 | 0 | case FZ_STEXT_BLOCK_IMAGE: |
270 | 0 | fz_drop_image(ctx, block->u.i.image); |
271 | 0 | block->u.i.image = NULL; |
272 | 0 | break; |
273 | 0 | case FZ_STEXT_BLOCK_TEXT: |
274 | 0 | for (line = block->u.t.first_line; line; line = line->next) |
275 | 0 | for (ch = line->first_char; ch; ch = ch->next) |
276 | 0 | { |
277 | 0 | fz_drop_font(ctx, ch->font); |
278 | 0 | ch->font = NULL; |
279 | 0 | } |
280 | 0 | break; |
281 | 0 | case FZ_STEXT_BLOCK_STRUCT: |
282 | 0 | fz_release_stext_block_run_resources(ctx, block->u.s.down->first_block); |
283 | 0 | break; |
284 | 0 | default: |
285 | 0 | break; |
286 | 0 | } |
287 | 0 | block = block->next; |
288 | 0 | } |
289 | 0 | } |
290 | | |
291 | | fz_stext_page_details *fz_stext_page_details_for_block(fz_context *ctx, fz_stext_page *page, fz_stext_block *block) |
292 | 0 | { |
293 | 0 | if (block == NULL || page == NULL) |
294 | 0 | fz_throw(ctx, FZ_ERROR_ARGUMENT, "page details require a page and a block"); |
295 | | |
296 | 0 | return (fz_stext_page_details *)fz_pool_array_lookup(ctx, page->id_list, block->id); |
297 | 0 | } |
298 | | |
299 | | fz_stext_page * |
300 | | fz_keep_stext_page(fz_context *ctx, fz_stext_page *page) |
301 | 0 | { |
302 | 0 | return fz_keep_imp(ctx, page, &page->refs); |
303 | 0 | } |
304 | | |
305 | | void |
306 | | fz_drop_stext_page(fz_context *ctx, fz_stext_page *page) |
307 | 0 | { |
308 | 0 | if (page == NULL) |
309 | 0 | return; |
310 | | |
311 | 0 | if (fz_drop_imp(ctx, page, &page->refs)) |
312 | 0 | { |
313 | 0 | fz_release_stext_block_run_resources(ctx, page->first_block); |
314 | 0 | fz_drop_pool(ctx, page->pool); |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * This adds a new block at the end of the page. This should not be used |
320 | | * to add 'struct' blocks to the page as those have to be added internally, |
321 | | * with more complicated pointer setup. |
322 | | */ |
323 | | static fz_stext_block * |
324 | | add_block_to_page(fz_context *ctx, fz_stext_page *page, int type, int id) |
325 | 0 | { |
326 | 0 | fz_stext_block *block = fz_pool_alloc(ctx, page->pool, sizeof *page->first_block); |
327 | 0 | block->bbox = fz_empty_rect; /* Fixes bug 703267. */ |
328 | 0 | block->prev = page->last_block; |
329 | 0 | block->type = type; |
330 | 0 | block->id = id; |
331 | 0 | if (page->last_struct) |
332 | 0 | { |
333 | 0 | if (page->last_struct->last_block) |
334 | 0 | { |
335 | 0 | block->prev = page->last_struct->last_block; |
336 | 0 | block->prev->next = block; |
337 | 0 | page->last_struct->last_block = block; |
338 | 0 | } |
339 | 0 | else |
340 | 0 | page->last_struct->last_block = page->last_struct->first_block = block; |
341 | 0 | } |
342 | 0 | else if (!page->last_block) |
343 | 0 | { |
344 | 0 | assert(!page->first_block); |
345 | 0 | page->first_block = page->last_block = block; |
346 | 0 | } |
347 | 0 | else |
348 | 0 | { |
349 | 0 | page->last_block->next = block; |
350 | 0 | page->last_block = block; |
351 | 0 | } |
352 | 0 | return block; |
353 | 0 | } |
354 | | |
355 | | static fz_stext_block * |
356 | | add_lazy_vector(fz_context *ctx, fz_stext_page *page, fz_stext_device *tdev, int id) |
357 | 0 | { |
358 | 0 | fz_stext_block *block = fz_pool_alloc(ctx, page->pool, sizeof *page->first_block); |
359 | 0 | block->bbox = fz_empty_rect; |
360 | 0 | block->prev = tdev->lazy_vectors_tail; |
361 | 0 | block->type = FZ_STEXT_BLOCK_VECTOR; |
362 | 0 | block->id = id; |
363 | |
|
364 | 0 | if (tdev->lazy_vectors == NULL) |
365 | 0 | tdev->lazy_vectors = block; |
366 | 0 | else |
367 | 0 | tdev->lazy_vectors_tail->next = block; |
368 | 0 | tdev->lazy_vectors_tail = block; |
369 | |
|
370 | 0 | return block; |
371 | 0 | } |
372 | | |
373 | | static void |
374 | | flush_lazy_vectors(fz_context *ctx, fz_stext_page *page, fz_stext_device *tdev) |
375 | 0 | { |
376 | 0 | if (tdev->lazy_vectors == NULL) |
377 | 0 | return; |
378 | | |
379 | 0 | if (page->last_struct) |
380 | 0 | { |
381 | 0 | if (page->last_struct->last_block) |
382 | 0 | { |
383 | 0 | page->last_struct->last_block->next = tdev->lazy_vectors; |
384 | 0 | tdev->lazy_vectors->prev = page->last_struct->last_block; |
385 | 0 | page->last_struct->last_block = tdev->lazy_vectors_tail; |
386 | 0 | } |
387 | 0 | else |
388 | 0 | { |
389 | 0 | page->last_struct->first_block = tdev->lazy_vectors; |
390 | 0 | page->last_struct->last_block = tdev->lazy_vectors_tail; |
391 | 0 | } |
392 | 0 | } |
393 | 0 | else if (!page->last_block) |
394 | 0 | { |
395 | 0 | page->first_block = tdev->lazy_vectors; |
396 | 0 | page->last_block = tdev->lazy_vectors_tail; |
397 | 0 | } |
398 | 0 | else |
399 | 0 | { |
400 | 0 | page->last_block->next = tdev->lazy_vectors; |
401 | 0 | tdev->lazy_vectors->prev = page->last_block; |
402 | 0 | page->last_block = tdev->lazy_vectors_tail; |
403 | 0 | } |
404 | |
|
405 | 0 | tdev->lazy_vectors = tdev->lazy_vectors_tail = NULL; |
406 | 0 | } |
407 | | |
408 | | static fz_stext_block * |
409 | | add_text_block_to_page(fz_context *ctx, fz_stext_page *page, int id) |
410 | 0 | { |
411 | 0 | return add_block_to_page(ctx, page, FZ_STEXT_BLOCK_TEXT, id); |
412 | 0 | } |
413 | | |
414 | | static fz_stext_block * |
415 | | add_image_block_to_page(fz_context *ctx, fz_stext_page *page, fz_matrix ctm, fz_image *image, int id) |
416 | 0 | { |
417 | 0 | fz_stext_block *block = add_block_to_page(ctx, page, FZ_STEXT_BLOCK_IMAGE, id); |
418 | 0 | block->u.i.transform = ctm; |
419 | 0 | block->u.i.image = fz_keep_image(ctx, image); |
420 | 0 | block->bbox = fz_transform_rect(fz_unit_rect, ctm); |
421 | 0 | return block; |
422 | 0 | } |
423 | | |
424 | | static fz_stext_line * |
425 | | add_line_to_block(fz_context *ctx, fz_stext_page *page, fz_stext_block *block, const fz_point *dir, int wmode, int bidi) |
426 | 0 | { |
427 | 0 | fz_stext_line *line = fz_pool_alloc(ctx, page->pool, sizeof *block->u.t.first_line); |
428 | 0 | line->prev = block->u.t.last_line; |
429 | 0 | if (!block->u.t.first_line) |
430 | 0 | block->u.t.first_line = block->u.t.last_line = line; |
431 | 0 | else |
432 | 0 | { |
433 | 0 | block->u.t.last_line->next = line; |
434 | 0 | block->u.t.last_line = line; |
435 | 0 | } |
436 | |
|
437 | 0 | line->dir = *dir; |
438 | 0 | line->wmode = wmode; |
439 | |
|
440 | 0 | return line; |
441 | 0 | } |
442 | | |
443 | 0 | #define NON_ACCURATE_GLYPH_ADDED_SPACE (-2) |
444 | 0 | #define NON_ACCURATE_GLYPH (-1) |
445 | | |
446 | | static fz_stext_char * |
447 | | add_char_to_line(fz_context *ctx, fz_stext_page *page, fz_stext_line *line, fz_matrix trm, fz_font *font, float size, int c, int glyph, fz_point *p, fz_point *q, int bidi, int color, int synthetic, int flags, int dev_flags) |
448 | 0 | { |
449 | 0 | fz_stext_char *ch = fz_pool_alloc(ctx, page->pool, sizeof *line->first_char); |
450 | 0 | fz_point a, d; |
451 | |
|
452 | 0 | if (!line->first_char) |
453 | 0 | line->first_char = line->last_char = ch; |
454 | 0 | else |
455 | 0 | { |
456 | 0 | line->last_char->next = ch; |
457 | 0 | line->last_char = ch; |
458 | 0 | } |
459 | |
|
460 | 0 | ch->c = c; |
461 | 0 | ch->argb = color; |
462 | 0 | ch->bidi = bidi; |
463 | 0 | ch->origin = *p; |
464 | 0 | ch->size = size; |
465 | 0 | ch->font = fz_keep_font(ctx, font); |
466 | 0 | ch->flags = flags | (synthetic ? FZ_STEXT_SYNTHETIC : 0) | (synthetic > 1 ? FZ_STEXT_SYNTHETIC_LARGE : 0); |
467 | 0 | if (font->flags.is_bold) |
468 | 0 | ch->flags |= FZ_STEXT_BOLD; |
469 | |
|
470 | 0 | if (line->wmode == 0) |
471 | 0 | { |
472 | 0 | fz_rect bounds; |
473 | 0 | int bounded = 0; |
474 | 0 | a.x = 0; |
475 | 0 | d.x = 0; |
476 | 0 | if (glyph == NON_ACCURATE_GLYPH_ADDED_SPACE) |
477 | 0 | { |
478 | | /* Added space, in accurate mode. */ |
479 | 0 | a.y = d.y = 0; |
480 | 0 | } |
481 | 0 | else if (glyph == NON_ACCURATE_GLYPH) |
482 | 0 | { |
483 | | /* Non accurate mode. */ |
484 | 0 | a.y = fz_font_ascender(ctx, font); |
485 | 0 | d.y = fz_font_descender(ctx, font); |
486 | 0 | } |
487 | 0 | else |
488 | 0 | { |
489 | | /* Any glyph in accurate mode */ |
490 | 0 | bounds = fz_bound_glyph(ctx, font, glyph, fz_identity); |
491 | 0 | bounded = 1; |
492 | 0 | a.y = bounds.y1; |
493 | 0 | d.y = bounds.y0; |
494 | 0 | } |
495 | 0 | if (dev_flags & FZ_STEXT_ACCURATE_SIDE_BEARINGS) |
496 | 0 | { |
497 | 0 | if (!bounded) |
498 | 0 | bounds = fz_bound_glyph(ctx, font, glyph, fz_identity); |
499 | 0 | if (a.x > bounds.x0) |
500 | 0 | a.x = bounds.x0; |
501 | 0 | if (d.y < bounds.x1) |
502 | 0 | d.y = bounds.x1; |
503 | 0 | } |
504 | 0 | } |
505 | 0 | else |
506 | 0 | { |
507 | 0 | a.x = 1; |
508 | 0 | d.x = 0; |
509 | 0 | a.y = 0; |
510 | 0 | d.y = 0; |
511 | 0 | } |
512 | 0 | a = fz_transform_vector(a, trm); |
513 | 0 | d = fz_transform_vector(d, trm); |
514 | |
|
515 | 0 | ch->quad.ll = fz_make_point(p->x + d.x, p->y + d.y); |
516 | 0 | ch->quad.ul = fz_make_point(p->x + a.x, p->y + a.y); |
517 | 0 | ch->quad.lr = fz_make_point(q->x + d.x, q->y + d.y); |
518 | 0 | ch->quad.ur = fz_make_point(q->x + a.x, q->y + a.y); |
519 | |
|
520 | 0 | return ch; |
521 | 0 | } |
522 | | |
523 | | static fz_stext_char *reverse_bidi_span(fz_stext_char *curr, fz_stext_char *tail) |
524 | 0 | { |
525 | 0 | fz_stext_char *prev, *next; |
526 | 0 | prev = tail; |
527 | 0 | while (curr != tail) |
528 | 0 | { |
529 | 0 | next = curr->next; |
530 | 0 | curr->next = prev; |
531 | 0 | prev = curr; |
532 | 0 | curr = next; |
533 | 0 | } |
534 | 0 | return prev; |
535 | 0 | } |
536 | | |
537 | | static void reverse_bidi_line(fz_stext_line *line) |
538 | 0 | { |
539 | 0 | fz_stext_char *a, *b, **prev; |
540 | 0 | prev = &line->first_char; |
541 | 0 | for (a = line->first_char; a; a = a->next) |
542 | 0 | { |
543 | 0 | if (a->bidi) |
544 | 0 | { |
545 | 0 | b = a; |
546 | 0 | while (b->next && b->next->bidi) |
547 | 0 | b = b->next; |
548 | 0 | if (a != b) |
549 | 0 | *prev = reverse_bidi_span(a, b->next); |
550 | 0 | } |
551 | 0 | prev = &a->next; |
552 | 0 | line->last_char = a; |
553 | 0 | } |
554 | 0 | } |
555 | | |
556 | | int fz_is_unicode_hyphen(int c) |
557 | 0 | { |
558 | | /* check for: hyphen-minus, soft hyphen, hyphen, and non-breaking hyphen */ |
559 | 0 | return (c == '-' || c == 0xAD || c == 0x2010 || c == 0x2011); |
560 | 0 | } |
561 | | |
562 | | static float |
563 | | vec_dot(const fz_point *a, const fz_point *b) |
564 | 0 | { |
565 | 0 | return a->x * b->x + a->y * b->y; |
566 | 0 | } |
567 | | |
568 | | static int may_add_space(int lastchar) |
569 | 0 | { |
570 | | /* Basic latin, greek, cyrillic, hebrew, arabic, |
571 | | * general punctuation, |
572 | | * superscripts and subscripts, |
573 | | * and currency symbols. |
574 | | */ |
575 | 0 | return (lastchar != ' ' && (lastchar < 0x700 || (lastchar >= 0x2000 && lastchar <= 0x20CF))); |
576 | 0 | } |
577 | | |
578 | 0 | #define FAKEBOLD_THRESHOLD_RECIP (1.0f / FAKE_BOLD_MAX_DIST) |
579 | | |
580 | | static int |
581 | | is_within_fake_bold_distance(float a, float b, float size) |
582 | 0 | { |
583 | 0 | a -= b; |
584 | 0 | if (a < 0) |
585 | 0 | a = -a; |
586 | |
|
587 | 0 | return FAKEBOLD_THRESHOLD_RECIP * a < size; |
588 | 0 | } |
589 | | |
590 | | static int |
591 | | font_equiv(fz_context *ctx, fz_font *f, fz_font *g) |
592 | 0 | { |
593 | 0 | unsigned char fdigest[16]; |
594 | 0 | unsigned char gdigest[16]; |
595 | |
|
596 | 0 | if (f == g) |
597 | 0 | return 1; |
598 | | |
599 | 0 | if (strcmp(f->name, g->name) != 0) |
600 | 0 | return 0; |
601 | | |
602 | 0 | if (f->buffer == NULL || g->buffer == NULL) |
603 | 0 | return 0; |
604 | | |
605 | 0 | fz_font_digest(ctx, f, fdigest); |
606 | 0 | fz_font_digest(ctx, g, gdigest); |
607 | |
|
608 | 0 | return (memcmp(fdigest, gdigest, 16) == 0); |
609 | 0 | } |
610 | | |
611 | | |
612 | | /* Utility to merge 2 colors. |
613 | | * |
614 | | * This conceptually works by blending col on top of *oldp to |
615 | | * update *oldp. |
616 | | */ |
617 | | static void |
618 | | merge_color(uint32_t *oldp, uint32_t col) |
619 | 0 | { |
620 | 0 | uint32_t old = *oldp; |
621 | 0 | uint32_t a = (col>>24) & 0xFF; |
622 | 0 | uint32_t r, g, b, mul; |
623 | |
|
624 | 0 | mul = a + (a>>7); |
625 | 0 | b = FZ_BLEND(col & 0xFF, old & 0xFF, mul); |
626 | 0 | g = FZ_BLEND((col>>8) & 0xFF, (old>>8) & 0xFF, mul); |
627 | 0 | r = FZ_BLEND((col>>16) & 0xFF, (old>>16) & 0xFF, mul); |
628 | 0 | a = FZ_BLEND(0xFF, (old>>24) & 0xFF, mul); |
629 | |
|
630 | 0 | *oldp = (a<<24) | (r<<16) | (g<<8) | b; |
631 | 0 | } |
632 | | |
633 | | static int |
634 | | check_for_fake_bold(fz_context *ctx, fz_stext_block *block, fz_font *font, int c, fz_point p, float size, int flags, uint32_t color) |
635 | 0 | { |
636 | 0 | fz_stext_line *line; |
637 | 0 | fz_stext_char *ch; |
638 | |
|
639 | 0 | for (; block != NULL; block = block->next) |
640 | 0 | { |
641 | 0 | if (block->type == FZ_STEXT_BLOCK_STRUCT) |
642 | 0 | { |
643 | 0 | if (block->u.s.down != NULL && check_for_fake_bold(ctx, block->u.s.down->first_block, font, c, p, size, flags, color)) |
644 | 0 | return 1; |
645 | 0 | } |
646 | 0 | else if (block->type == FZ_STEXT_BLOCK_TEXT) |
647 | 0 | { |
648 | 0 | for (line = block->u.t.first_line; line != NULL; line = line->next) |
649 | 0 | { |
650 | 0 | fz_stext_char *pr = NULL; |
651 | 0 | for (ch = line->first_char; ch != NULL; ch = ch->next) |
652 | 0 | { |
653 | | /* Not perfect, but it'll do! */ |
654 | 0 | if (ch->c == c && is_within_fake_bold_distance(ch->origin.x, p.x, size) && is_within_fake_bold_distance(ch->origin.y, p.y, size) && font_equiv(ctx, ch->font, font)) |
655 | 0 | { |
656 | | /* If we were filled before, and we are stroking now... */ |
657 | 0 | if ((ch->flags & (FZ_STEXT_FILLED | FZ_STEXT_STROKED)) == FZ_STEXT_FILLED && |
658 | 0 | (flags & (FZ_STEXT_FILLED | FZ_STEXT_STROKED)) == FZ_STEXT_STROKED) |
659 | 0 | { |
660 | | /* Update this to be filled + stroked, but don't specifically mark it as fake bold. */ |
661 | 0 | ch->flags |= flags; |
662 | | |
663 | | /* How should we merge the colors here? A typical case might be a glyph being filled in one colour, |
664 | | * with a thin stroke in another color - the effective color is therefore the fill color. BUT... if |
665 | | * the stroke is large compared with with the font size, it might be the other way around. |
666 | | * We could scale the alpha on this new color based upon stroke width/font_size... but at the moment |
667 | | * we have no way of judging the stroke size here. So for now, we just keep the original fill |
668 | | * color. */ |
669 | 0 | return 1; |
670 | 0 | } |
671 | | /* Overlaying spaces is tricksy. How can that count as boldening when it doesn't mark? We only accept these |
672 | | * as boldening if either the char before, or the char after were also boldened. */ |
673 | 0 | ch->flags |= flags; |
674 | |
|
675 | 0 | if (c == ' ') |
676 | 0 | { |
677 | 0 | if ((pr && (pr->flags & FZ_STEXT_BOLD) != 0) || |
678 | 0 | (ch->next && (ch->next->flags & FZ_STEXT_BOLD) != 0)) |
679 | 0 | { |
680 | | /* OK, we can be bold. */ |
681 | 0 | ch->flags |= FZ_STEXT_BOLD; |
682 | 0 | } |
683 | | /* Keep the later color */ |
684 | 0 | merge_color(&ch->argb, color); |
685 | | /* Whether we have recorded this as being bold or not, still |
686 | | * claim we did, so we swallow the space and don't reemit it. */ |
687 | 0 | return 1; |
688 | 0 | } |
689 | 0 | else |
690 | 0 | { |
691 | 0 | ch->flags |= FZ_STEXT_BOLD; |
692 | | /* Keep the later color */ |
693 | 0 | merge_color(&ch->argb, color); |
694 | 0 | return 1; |
695 | 0 | } |
696 | 0 | } |
697 | 0 | pr = ch; |
698 | 0 | } |
699 | 0 | } |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | 0 | return 0; |
704 | 0 | } |
705 | | |
706 | | static int |
707 | | plausible_bullet(int c) |
708 | 0 | { |
709 | 0 | return (c == '*' || |
710 | 0 | c == 0x00B7 || /* Middle Dot */ |
711 | 0 | c == 0x2022 || /* Bullet */ |
712 | 0 | c == 0x2023 || /* Triangular Bullet */ |
713 | 0 | c == 0x2043 || /* Hyphen Bullet */ |
714 | 0 | c == 0x204C || /* Back leftwards bullet */ |
715 | 0 | c == 0x204D || /* Back rightwards bullet */ |
716 | 0 | c == 0x2219 || /* Bullet operator */ |
717 | 0 | c == 0x25C9 || /* Fisheye */ |
718 | 0 | c == 0x25CB || /* White circle */ |
719 | 0 | c == 0x25CF || /* Black circle */ |
720 | 0 | c == 0x25D8 || /* Inverse Bullet */ |
721 | 0 | c == 0x25E6 || /* White Bullet */ |
722 | 0 | c == 0x2619 || /* Reversed Rotated Floral Heart Bullet / Fleuron */ |
723 | 0 | c == 0x261a || /* Black left pointing index */ |
724 | 0 | c == 0x261b || /* Black right pointing index */ |
725 | 0 | c == 0x261c || /* White left pointing index */ |
726 | 0 | c == 0x261d || /* White up pointing index */ |
727 | 0 | c == 0x261e || /* White right pointing index */ |
728 | 0 | c == 0x261f || /* White down pointing index */ |
729 | 0 | c == 0x2765 || /* Rotated Heavy Heart Black Heart Bullet */ |
730 | 0 | c == 0x2767 || /* Rotated Floral Heart Bullet / Fleuron */ |
731 | 0 | c == 0x29BE || /* Circled White Bullet */ |
732 | 0 | c == 0x29BF || /* Circled Bullet */ |
733 | 0 | c == 0x2660 || /* Black Spade suit */ |
734 | 0 | c == 0x2661 || /* White Heart suit */ |
735 | 0 | c == 0x2662 || /* White Diamond suit */ |
736 | 0 | c == 0x2663 || /* Black Club suit */ |
737 | 0 | c == 0x2664 || /* White Spade suit */ |
738 | 0 | c == 0x2665 || /* Black Heart suit */ |
739 | 0 | c == 0x2666 || /* Black Diamond suit */ |
740 | 0 | c == 0x2667 || /* White Clud suit */ |
741 | 0 | c == 0x1F446 || /* WHITE UP POINTING BACKHAND INDEX */ |
742 | 0 | c == 0x1F447 || /* WHITE DOWN POINTING BACKHAND INDEX */ |
743 | 0 | c == 0x1F448 || /* WHITE LEFT POINTING BACKHAND INDEX */ |
744 | 0 | c == 0x1F449 || /* WHITE RIGHT POINTING BACKHAND INDEX */ |
745 | 0 | c == 0x1f597 || /* White down pointing left hand index */ |
746 | 0 | c == 0x1F598 || /* SIDEWAYS WHITE LEFT POINTING INDEX */ |
747 | 0 | c == 0x1F599 || /* SIDEWAYS WHITE RIGHT POINTING INDEX */ |
748 | 0 | c == 0x1F59A || /* SIDEWAYS BLACK LEFT POINTING INDEX */ |
749 | 0 | c == 0x1F59B || /* SIDEWAYS BLACK RIGHT POINTING INDEX */ |
750 | 0 | c == 0x1F59C || /* BLACK LEFT POINTING BACKHAND INDEX */ |
751 | 0 | c == 0x1F59D || /* BLACK RIGHT POINTING BACKHAND INDEX */ |
752 | 0 | c == 0x1F59E || /* SIDEWAYS WHITE UP POINTING INDEX */ |
753 | 0 | c == 0x1F59F || /* SIDEWAYS WHITE DOWN POINTING INDEX */ |
754 | 0 | c == 0x1F5A0 || /* SIDEWAYS BLACK UP POINTING INDEX */ |
755 | 0 | c == 0x1F5A1 || /* SIDEWAYS BLACK DOWN POINTING INDEX */ |
756 | 0 | c == 0x1F5A2 || /* BLACK UP POINTING BACKHAND INDEX */ |
757 | 0 | c == 0x1F5A3 || /* BLACK DOWN POINTING BACKHAND INDEX */ |
758 | 0 | c == 0x1FBC1 || /* LEFT THIRD WHITE RIGHT POINTING INDEX */ |
759 | 0 | c == 0x1FBC2 || /* MIDDLE THIRD WHITE RIGHT POINTING INDEX */ |
760 | 0 | c == 0x1FBC3 || /* RIGHT THIRD WHITE RIGHT POINTING INDEX */ |
761 | 0 | c == 0xFFFD || /* UNICODE_REPLACEMENT_CHARACTER */ |
762 | 0 | (c >= '0' && c <= '9') || |
763 | 0 | 0); |
764 | 0 | } |
765 | | |
766 | | static void |
767 | | fz_add_stext_char_imp(fz_context *ctx, fz_stext_device *dev, fz_font *font, int c, int glyph, fz_matrix trm, float adv, int wmode, int bidi, int force_new_line, int flags) |
768 | 0 | { |
769 | 0 | fz_stext_page *page = dev->page; |
770 | 0 | fz_stext_block *cur_block; |
771 | 0 | fz_stext_line *cur_line = NULL; |
772 | |
|
773 | 0 | int new_para = 0; |
774 | 0 | int new_line = 1; |
775 | 0 | int add_space = 0; |
776 | 0 | fz_point dir, ndir, p, q; |
777 | 0 | float size, lag_size; |
778 | 0 | fz_point delta; |
779 | 0 | float spacing = 0; |
780 | 0 | float base_offset = 0; |
781 | 0 | float dist; |
782 | | |
783 | | /* Preserve RTL-ness only (and ignore level) so we can use bit 2 as "visual" tag for reordering pass. */ |
784 | 0 | bidi = bidi & 1; |
785 | | |
786 | | /* dir = direction vector for motion. ndir = normalised(dir) */ |
787 | 0 | if (wmode == 0) |
788 | 0 | { |
789 | 0 | dir.x = 1; |
790 | 0 | dir.y = 0; |
791 | 0 | } |
792 | 0 | else |
793 | 0 | { |
794 | 0 | dir.x = 0; |
795 | 0 | dir.y = -1; |
796 | 0 | } |
797 | 0 | dir = fz_transform_vector(dir, trm); |
798 | 0 | ndir = fz_normalize_vector(dir); |
799 | |
|
800 | 0 | size = fz_matrix_expansion(trm); |
801 | | /* lag_size is the minimum of the current chars size, and the previous chars |
802 | | * size. We use this when considering baseline deviations. */ |
803 | 0 | lag_size = dev->lag_size > 0 ? fz_min(dev->lag_size, size) : size; |
804 | | |
805 | | /* We need to identify where glyphs 'start' (p) and 'stop' (q). |
806 | | * Each glyph holds its 'start' position, and the next glyph in the |
807 | | * span (or span->max if there is no next glyph) holds its 'end' |
808 | | * position. |
809 | | * |
810 | | * For both horizontal and vertical motion, trm->{e,f} gives the |
811 | | * origin (usually the bottom left) of the glyph. |
812 | | * |
813 | | * In horizontal mode: |
814 | | * + p is bottom left. |
815 | | * + q is the bottom right |
816 | | * In vertical mode: |
817 | | * + p is top left (where it advanced from) |
818 | | * + q is bottom left |
819 | | */ |
820 | 0 | if (wmode == 0) |
821 | 0 | { |
822 | 0 | p.x = trm.e; |
823 | 0 | p.y = trm.f; |
824 | 0 | q.x = trm.e + adv * dir.x; |
825 | 0 | q.y = trm.f + adv * dir.y; |
826 | 0 | } |
827 | 0 | else |
828 | 0 | { |
829 | 0 | p.x = trm.e - adv * dir.x; |
830 | 0 | p.y = trm.f - adv * dir.y; |
831 | 0 | q.x = trm.e; |
832 | 0 | q.y = trm.f; |
833 | 0 | } |
834 | | |
835 | | //printf("%g,%g \"%c\" %g,%g\n", p.x, p.y, c, q.x, q.y); |
836 | |
|
837 | 0 | if ((dev->opts.flags & FZ_STEXT_COLLECT_STYLES) != 0) |
838 | 0 | { |
839 | 0 | if (glyph < 0) |
840 | 0 | { |
841 | 0 | if (dev->last_was_fake_bold) |
842 | 0 | return; |
843 | 0 | } |
844 | 0 | else if (check_for_fake_bold(ctx, page->first_block, font, c, p, size, flags, dev->color)) |
845 | 0 | { |
846 | 0 | dev->last_was_fake_bold = 1; |
847 | 0 | return; |
848 | 0 | } |
849 | 0 | dev->last_was_fake_bold = 0; |
850 | 0 | } |
851 | | |
852 | | /* Find current position to enter new text. */ |
853 | 0 | cur_block = page->last_struct ? page->last_struct->last_block : page->last_block; |
854 | 0 | if (cur_block && cur_block->type != FZ_STEXT_BLOCK_TEXT) |
855 | 0 | cur_block = NULL; |
856 | 0 | cur_line = cur_block ? cur_block->u.t.last_line : NULL; |
857 | | |
858 | | /* We use glyph == -2 to indicate the first no-glyph char from an actualtext. The position |
859 | | * is valid though, so we want to advance the pen for these. */ |
860 | | |
861 | | /* Don't advance pen or break lines for either no-glyph or marking non-spacing characters in a cluster */ |
862 | 0 | if (cur_line && (glyph == -1 || ucdn_get_general_category(c) == UCDN_GENERAL_CATEGORY_MN)) |
863 | 0 | { |
864 | 0 | add_char_to_line(ctx, page, cur_line, trm, font, size, c, (dev->flags & FZ_STEXT_ACCURATE_BBOXES) ? glyph : NON_ACCURATE_GLYPH, &dev->pen, &dev->pen, bidi, dev->color, 0, flags, dev->flags); |
865 | 0 | dev->lastbidi = bidi; |
866 | 0 | dev->lastchar = c; |
867 | 0 | dev->lastline = cur_line; |
868 | 0 | return; |
869 | 0 | } |
870 | | |
871 | 0 | if (cur_line == NULL || cur_line->wmode != wmode || vec_dot(&ndir, &cur_line->dir) < 0.999f) |
872 | 0 | { |
873 | | /* If the matrix has changed rotation, or the wmode is different (or if we don't have a line at all), |
874 | | * then we can't append to the current block/line. */ |
875 | 0 | new_para = 1; |
876 | 0 | new_line = 1; |
877 | 0 | } |
878 | 0 | else |
879 | 0 | { |
880 | | /* Detect fake bold where text is printed twice in the same place. */ |
881 | | /* Largely supplanted by the check_for_fake_bold mechanism above, |
882 | | * but we leave this in for backward compatibility as it's cheap, |
883 | | * and works even when FZ_STEXT_COLLECT_STYLES is not set. */ |
884 | 0 | dist = hypotf(p.x - dev->lag_pen.x, p.y - dev->lag_pen.y) / size; |
885 | | /* This can trigger improperly for glyphs that come from actualtext |
886 | | * as they are frequently overlaid. Therefore rely on glyph >= 0. */ |
887 | 0 | if (dist < FAKE_BOLD_MAX_DIST && c == dev->lastchar && glyph >= 0) |
888 | 0 | return; |
889 | | |
890 | | /* Calculate how far we've moved since the last character. */ |
891 | 0 | delta.x = p.x - dev->pen.x; |
892 | 0 | delta.y = p.y - dev->pen.y; |
893 | | |
894 | | /* The transform has not changed, so we know we're in the same |
895 | | * direction. Calculate 2 distances; how far off the previous |
896 | | * baseline we are, together with how far along the baseline |
897 | | * we are from the expected position. */ |
898 | 0 | spacing = (ndir.x * delta.x + ndir.y * delta.y) / size; |
899 | 0 | base_offset = (-ndir.y * delta.x + ndir.x * delta.y) / lag_size; |
900 | | |
901 | | /* Only a small amount off the baseline - we'll take this */ |
902 | 0 | if (fabsf(base_offset) < BASE_MAX_DIST) |
903 | 0 | { |
904 | | /* If mixed LTR and RTL content */ |
905 | 0 | if ((bidi & 1) != (dev->lastbidi & 1)) |
906 | 0 | { |
907 | | /* Ignore jumps within line when switching between LTR and RTL text. */ |
908 | 0 | new_line = 0; |
909 | 0 | } |
910 | | |
911 | | /* RTL */ |
912 | 0 | else if (bidi & 1) |
913 | 0 | { |
914 | 0 | fz_point logical_delta = fz_make_point(p.x - dev->lag_pen.x, p.y - dev->lag_pen.y); |
915 | 0 | float logical_spacing = (ndir.x * logical_delta.x + ndir.y * logical_delta.y) / size + adv; |
916 | | |
917 | | /* If the pen is where we would have been if we |
918 | | * had advanced backwards from the previous |
919 | | * character by this character's advance, we |
920 | | * are probably seeing characters emitted in |
921 | | * logical order. |
922 | | */ |
923 | 0 | if (fabsf(logical_spacing) < SPACE_DIST) |
924 | 0 | { |
925 | 0 | new_line = 0; |
926 | 0 | } |
927 | | |
928 | | /* However, if the pen has advanced to where we would expect it |
929 | | * in an LTR context, we're seeing them emitted in visual order |
930 | | * and should flag them for reordering! |
931 | | */ |
932 | 0 | else if (fabsf(spacing) < SPACE_DIST) |
933 | 0 | { |
934 | 0 | bidi = 3; /* mark line as visual */ |
935 | 0 | new_line = 0; |
936 | 0 | } |
937 | | |
938 | | /* And any other small jump could be a missing space. */ |
939 | 0 | else if (logical_spacing < 0 && logical_spacing > -SPACE_MAX_DIST) |
940 | 0 | { |
941 | 0 | if (wmode == 0 && may_add_space(dev->lastchar)) |
942 | 0 | add_space = 1; |
943 | 0 | new_line = 0; |
944 | 0 | } |
945 | 0 | else if (spacing < 0 && spacing > -SPACE_MAX_DIST) |
946 | 0 | { |
947 | | /* Motion is in line, but negative. We've probably got overlapping |
948 | | * chars here. Live with it. */ |
949 | 0 | new_line = 0; |
950 | 0 | } |
951 | 0 | else if (spacing > 0 && spacing < SPACE_MAX_DIST) |
952 | 0 | { |
953 | 0 | bidi = 3; /* mark line as visual */ |
954 | 0 | if (wmode == 0 && may_add_space(dev->lastchar)) |
955 | 0 | add_space = 1 + (spacing > SPACE_DIST*2); |
956 | 0 | new_line = 0; |
957 | 0 | } |
958 | | |
959 | 0 | else |
960 | 0 | { |
961 | | /* Motion is large and unexpected (probably a new table column). */ |
962 | 0 | new_line = 1; |
963 | 0 | } |
964 | 0 | } |
965 | | |
966 | | /* LTR or neutral character */ |
967 | 0 | else |
968 | 0 | { |
969 | 0 | if (fabsf(spacing) < SPACE_DIST) |
970 | 0 | { |
971 | | /* Motion is in line and small enough to ignore. */ |
972 | 0 | new_line = 0; |
973 | 0 | } |
974 | 0 | else if (spacing < 0 && spacing > -SPACE_MAX_DIST) |
975 | 0 | { |
976 | | /* Motion is in line, but negative. We've probably got overlapping |
977 | | * chars here. Live with it. */ |
978 | 0 | new_line = 0; |
979 | 0 | } |
980 | 0 | else if (spacing > 0 && spacing < SPACE_MAX_DIST) |
981 | 0 | { |
982 | | /* Motion is forward in line and large enough to warrant us adding a space. */ |
983 | 0 | if (wmode == 0 && may_add_space(dev->lastchar)) |
984 | 0 | add_space = 1 + (spacing > SPACE_DIST*2); |
985 | 0 | new_line = 0; |
986 | 0 | } |
987 | 0 | else |
988 | 0 | { |
989 | | /* Motion is large and unexpected (probably a new table column). */ |
990 | 0 | new_line = 1; |
991 | 0 | } |
992 | 0 | } |
993 | 0 | } |
994 | | |
995 | | /* Enough for a new line, but not enough for a new paragraph */ |
996 | 0 | else if (fabsf(base_offset) <= PARAGRAPH_DIST) |
997 | 0 | { |
998 | | /* Check indent to spot text-indent style paragraphs */ |
999 | 0 | if (wmode == 0 && cur_line) |
1000 | 0 | if ((p.x - dev->start.x) > 0.5f && !dev->maybe_bullet) |
1001 | 0 | new_para = 1; |
1002 | 0 | new_line = 1; |
1003 | 0 | } |
1004 | | |
1005 | | /* Way off the baseline - open a new paragraph */ |
1006 | 0 | else |
1007 | 0 | { |
1008 | 0 | new_para = 1; |
1009 | 0 | new_line = 1; |
1010 | 0 | } |
1011 | 0 | } |
1012 | | |
1013 | | /* Start a new block (but only at the beginning of a text object) */ |
1014 | | /* OR, if we have pending lazy vectors, and we are about to start a new line) */ |
1015 | 0 | if (new_para || !cur_block || (dev->lazy_vectors != NULL && (new_line || !cur_line || force_new_line))) |
1016 | 0 | { |
1017 | 0 | flush_lazy_vectors(ctx, page, dev); |
1018 | 0 | cur_block = add_text_block_to_page(ctx, page, dev->id); |
1019 | 0 | cur_line = cur_block->u.t.last_line; |
1020 | 0 | } |
1021 | |
|
1022 | 0 | if (new_line && (dev->flags & FZ_STEXT_DEHYPHENATE) && fz_is_unicode_hyphen(dev->lastchar) && dev->lastline != NULL) |
1023 | 0 | dev->lastline->flags |= FZ_STEXT_LINE_FLAGS_JOINED; |
1024 | | |
1025 | | /* Start a new line */ |
1026 | 0 | if (new_line || !cur_line || force_new_line) |
1027 | 0 | { |
1028 | 0 | cur_line = add_line_to_block(ctx, page, cur_block, &ndir, wmode, bidi); |
1029 | 0 | dev->start = p; |
1030 | 0 | if (glyph == -2) |
1031 | 0 | dev->maybe_bullet = 1; |
1032 | 0 | else |
1033 | 0 | dev->maybe_bullet = plausible_bullet(c); |
1034 | 0 | } |
1035 | | |
1036 | | /* Henceforth treat such non-glyphs in the usual way. */ |
1037 | 0 | if (glyph == -2) |
1038 | 0 | glyph = -1; |
1039 | | |
1040 | | /* Add synthetic space */ |
1041 | 0 | if (c != ' ' && add_space && !(dev->flags & FZ_STEXT_INHIBIT_SPACES)) |
1042 | 0 | add_char_to_line(ctx, page, cur_line, trm, font, size, ' ', (dev->flags & FZ_STEXT_ACCURATE_BBOXES) ? NON_ACCURATE_GLYPH_ADDED_SPACE : NON_ACCURATE_GLYPH, &dev->pen, &p, bidi, dev->color, add_space, flags, dev->flags); |
1043 | |
|
1044 | 0 | add_char_to_line(ctx, page, cur_line, trm, font, size, c, (dev->flags & FZ_STEXT_ACCURATE_BBOXES) ? glyph : NON_ACCURATE_GLYPH, &p, &q, bidi, dev->color, 0, flags, dev->flags); |
1045 | |
|
1046 | 0 | dev->lastchar = c; |
1047 | 0 | dev->lastbidi = bidi; |
1048 | 0 | dev->lastline = cur_line; |
1049 | 0 | dev->lag_pen = p; |
1050 | 0 | dev->lag_size = size; |
1051 | 0 | dev->pen = q; |
1052 | |
|
1053 | 0 | dev->trm = trm; |
1054 | 0 | } |
1055 | | |
1056 | | static void |
1057 | | fz_add_stext_char(fz_context *ctx, |
1058 | | fz_stext_device *dev, |
1059 | | fz_font *font, |
1060 | | int c, |
1061 | | int glyph, |
1062 | | fz_matrix trm, |
1063 | | float adv, |
1064 | | int wmode, |
1065 | | int bidi, |
1066 | | int force_new_line, |
1067 | | int flags) |
1068 | 0 | { |
1069 | | /* ignore when one unicode character maps to multiple glyphs */ |
1070 | 0 | if (c == -1) |
1071 | 0 | return; |
1072 | | |
1073 | 0 | if (dev->flags & FZ_STEXT_ACCURATE_ASCENDERS) |
1074 | 0 | fz_calculate_font_ascender_descender(ctx, font); |
1075 | |
|
1076 | 0 | if (!(dev->flags & FZ_STEXT_PRESERVE_LIGATURES)) |
1077 | 0 | { |
1078 | 0 | switch (c) |
1079 | 0 | { |
1080 | 0 | case 0xFB00: /* ff */ |
1081 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1082 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', -1, trm, 0, wmode, bidi, 0, flags); |
1083 | 0 | return; |
1084 | 0 | case 0xFB01: /* fi */ |
1085 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1086 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'i', -1, trm, 0, wmode, bidi, 0, flags); |
1087 | 0 | return; |
1088 | 0 | case 0xFB02: /* fl */ |
1089 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1090 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'l', -1, trm, 0, wmode, bidi, 0, flags); |
1091 | 0 | return; |
1092 | 0 | case 0xFB03: /* ffi */ |
1093 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1094 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', -1, trm, 0, wmode, bidi, 0, flags); |
1095 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'i', -1, trm, 0, wmode, bidi, 0, flags); |
1096 | 0 | return; |
1097 | 0 | case 0xFB04: /* ffl */ |
1098 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1099 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'f', -1, trm, 0, wmode, bidi, 0, flags); |
1100 | 0 | fz_add_stext_char_imp(ctx, dev, font, 'l', -1, trm, 0, wmode, bidi, 0, flags); |
1101 | 0 | return; |
1102 | 0 | case 0xFB05: /* long st */ |
1103 | 0 | case 0xFB06: /* st */ |
1104 | 0 | fz_add_stext_char_imp(ctx, dev, font, 's', glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1105 | 0 | fz_add_stext_char_imp(ctx, dev, font, 't', -1, trm, 0, wmode, bidi, 0, flags); |
1106 | 0 | return; |
1107 | 0 | } |
1108 | | |
1109 | | /* alphabetic and arabic presentation forms */ |
1110 | 0 | if ((c >= 0xfb00 && c <= 0xfdff) || (c >= 0xfe70 && c <= 0xfefc)) |
1111 | 0 | { |
1112 | 0 | uint32_t lig[18]; |
1113 | 0 | int i, n = ucdn_compat_decompose(c, lig); |
1114 | 0 | fz_add_stext_char_imp(ctx, dev, font, lig[0], glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1115 | 0 | for (i = 1; i < n; ++i) |
1116 | 0 | fz_add_stext_char_imp(ctx, dev, font, lig[i], -1, trm, 0, wmode, bidi, 0, flags); |
1117 | 0 | return; |
1118 | 0 | } |
1119 | 0 | } |
1120 | | |
1121 | 0 | if (!(dev->flags & FZ_STEXT_PRESERVE_WHITESPACE)) |
1122 | 0 | { |
1123 | 0 | switch (c) |
1124 | 0 | { |
1125 | 0 | case 0x0009: /* tab */ |
1126 | 0 | case 0x0020: /* space */ |
1127 | 0 | case 0x00A0: /* no-break space */ |
1128 | 0 | case 0x1680: /* ogham space mark */ |
1129 | 0 | case 0x180E: /* mongolian vowel separator */ |
1130 | 0 | case 0x2000: /* en quad */ |
1131 | 0 | case 0x2001: /* em quad */ |
1132 | 0 | case 0x2002: /* en space */ |
1133 | 0 | case 0x2003: /* em space */ |
1134 | 0 | case 0x2004: /* three-per-em space */ |
1135 | 0 | case 0x2005: /* four-per-em space */ |
1136 | 0 | case 0x2006: /* six-per-em space */ |
1137 | 0 | case 0x2007: /* figure space */ |
1138 | 0 | case 0x2008: /* punctuation space */ |
1139 | 0 | case 0x2009: /* thin space */ |
1140 | 0 | case 0x200A: /* hair space */ |
1141 | 0 | case 0x202F: /* narrow no-break space */ |
1142 | 0 | case 0x205F: /* medium mathematical space */ |
1143 | 0 | case 0x3000: /* ideographic space */ |
1144 | 0 | c = ' '; |
1145 | 0 | } |
1146 | 0 | } |
1147 | |
|
1148 | 0 | fz_add_stext_char_imp(ctx, dev, font, c, glyph, trm, adv, wmode, bidi, force_new_line, flags); |
1149 | 0 | } |
1150 | | |
1151 | | static fz_rect |
1152 | | current_clip(fz_context *ctx, fz_stext_device *dev) |
1153 | 0 | { |
1154 | 0 | fz_rect r = fz_infinite_rect; |
1155 | |
|
1156 | 0 | if (dev->flags & FZ_STEXT_CLIP) |
1157 | 0 | { |
1158 | 0 | r = fz_device_current_scissor(ctx, &dev->super); |
1159 | 0 | r = fz_intersect_rect(r, dev->page->mediabox); |
1160 | 0 | } |
1161 | 0 | if (dev->flags & FZ_STEXT_CLIP_RECT) |
1162 | 0 | r = fz_intersect_rect(r, dev->opts.clip); |
1163 | |
|
1164 | 0 | return r; |
1165 | 0 | } |
1166 | | |
1167 | | static void |
1168 | | do_extract(fz_context *ctx, fz_stext_device *dev, fz_text_span *span, fz_matrix ctm, int start, int end, int flags) |
1169 | 0 | { |
1170 | 0 | fz_font *font = span->font; |
1171 | 0 | fz_matrix tm = span->trm; |
1172 | 0 | float adv; |
1173 | 0 | int unicode; |
1174 | 0 | int i; |
1175 | |
|
1176 | 0 | for (i = start; i < end; i++) |
1177 | 0 | { |
1178 | 0 | if (dev->flags & (FZ_STEXT_CLIP | FZ_STEXT_CLIP_RECT)) |
1179 | 0 | { |
1180 | 0 | fz_rect r = current_clip(ctx, dev); |
1181 | 0 | if (fz_glyph_entirely_outside_box(ctx, &ctm, span, &span->items[i], &r)) |
1182 | 0 | { |
1183 | 0 | dev->last.clipped = 1; |
1184 | 0 | continue; |
1185 | 0 | } |
1186 | 0 | } |
1187 | 0 | dev->last.clipped = 0; |
1188 | | |
1189 | | /* Calculate new pen location and delta */ |
1190 | 0 | tm.e = span->items[i].x; |
1191 | 0 | tm.f = span->items[i].y; |
1192 | 0 | dev->last.trm = fz_concat(tm, ctm); |
1193 | 0 | dev->last.bidi_level = span->bidi_level; |
1194 | 0 | dev->last.wmode = span->wmode; |
1195 | 0 | if (font != dev->last.font) |
1196 | 0 | { |
1197 | 0 | fz_drop_font(ctx, dev->last.font); |
1198 | 0 | dev->last.font = fz_keep_font(ctx, font); |
1199 | 0 | } |
1200 | 0 | dev->last.valid = 1; |
1201 | 0 | dev->last.flags = flags; |
1202 | | |
1203 | | /* Calculate bounding box and new pen position based on font metrics */ |
1204 | 0 | if (span->items[i].gid >= 0) |
1205 | 0 | adv = span->items[i].adv; |
1206 | 0 | else |
1207 | 0 | adv = 0; |
1208 | |
|
1209 | 0 | unicode = span->items[i].ucs; |
1210 | 0 | if (unicode == FZ_REPLACEMENT_CHARACTER) |
1211 | 0 | { |
1212 | 0 | if (dev->flags & FZ_STEXT_USE_CID_FOR_UNKNOWN_UNICODE) |
1213 | 0 | { |
1214 | 0 | unicode = span->items[i].cid; |
1215 | 0 | flags |= FZ_STEXT_UNICODE_IS_CID; |
1216 | 0 | } |
1217 | 0 | else if (dev->flags & FZ_STEXT_USE_GID_FOR_UNKNOWN_UNICODE) |
1218 | 0 | { |
1219 | 0 | unicode = span->items[i].gid; |
1220 | 0 | flags |= FZ_STEXT_UNICODE_IS_GID; |
1221 | 0 | } |
1222 | 0 | } |
1223 | | |
1224 | | /* Send the chars we have through. */ |
1225 | 0 | fz_add_stext_char(ctx, dev, font, |
1226 | 0 | unicode, |
1227 | 0 | span->items[i].gid, |
1228 | 0 | dev->last.trm, |
1229 | 0 | adv, |
1230 | 0 | dev->last.wmode, |
1231 | 0 | dev->last.bidi_level, |
1232 | 0 | (i == 0) && (dev->flags & FZ_STEXT_PRESERVE_SPANS), |
1233 | 0 | flags); |
1234 | 0 | } |
1235 | 0 | } |
1236 | | |
1237 | | static int |
1238 | | rune_index(const char *utf8, size_t idx) |
1239 | 0 | { |
1240 | 0 | int rune; |
1241 | |
|
1242 | 0 | do |
1243 | 0 | { |
1244 | 0 | int len = fz_chartorune(&rune, utf8); |
1245 | 0 | if (rune == 0) |
1246 | 0 | return -1; |
1247 | 0 | utf8 += len; |
1248 | 0 | } |
1249 | 0 | while (idx--); |
1250 | | |
1251 | 0 | return rune; |
1252 | 0 | } |
1253 | | |
1254 | | static void |
1255 | | flush_actualtext(fz_context *ctx, fz_stext_device *dev, const char *actualtext, int i, int end) |
1256 | 0 | { |
1257 | 0 | int glyph = -2; |
1258 | |
|
1259 | 0 | if (*actualtext == 0) |
1260 | 0 | return; |
1261 | | |
1262 | 0 | if (!dev->last.valid) |
1263 | 0 | return; |
1264 | | |
1265 | 0 | if (dev->flags & (FZ_STEXT_CLIP | FZ_STEXT_CLIP_RECT)) |
1266 | 0 | if (dev->last.clipped) |
1267 | 0 | return; |
1268 | | |
1269 | 0 | while (end < 0 || (end >= 0 && i < end)) |
1270 | 0 | { |
1271 | 0 | int rune; |
1272 | 0 | actualtext += fz_chartorune(&rune, actualtext); |
1273 | |
|
1274 | 0 | if (rune == 0) |
1275 | 0 | break; |
1276 | | |
1277 | 0 | fz_add_stext_char(ctx, dev, dev->last.font, |
1278 | 0 | rune, |
1279 | 0 | glyph, |
1280 | 0 | dev->last.trm, |
1281 | 0 | 0, |
1282 | 0 | dev->last.wmode, |
1283 | 0 | dev->last.bidi_level, |
1284 | 0 | (i == 0) && (dev->flags & FZ_STEXT_PRESERVE_SPANS), |
1285 | 0 | dev->last.flags); |
1286 | 0 | i++; |
1287 | |
|
1288 | 0 | glyph = -1; /* -1 for all but first glyph in the actualtext run */ |
1289 | 0 | } |
1290 | 0 | } |
1291 | | |
1292 | | static void |
1293 | | do_extract_within_actualtext(fz_context *ctx, fz_stext_device *dev, fz_text_span *span, fz_matrix ctm, metatext_t *mt, int flags) |
1294 | 0 | { |
1295 | | /* We are within an actualtext block. This means we can't just add the chars |
1296 | | * as they are. We need to add the chars as they are meant to be. Sadly the |
1297 | | * actualtext mechanism doesn't help us at all with positioning. */ |
1298 | 0 | fz_font *font = span->font; |
1299 | 0 | fz_matrix tm = span->trm; |
1300 | 0 | float adv; |
1301 | 0 | int start, i, end; |
1302 | 0 | char *actualtext = mt->text; |
1303 | 0 | size_t z = fz_utflen(actualtext); |
1304 | | |
1305 | | /* If actualtext is empty, nothing to do! */ |
1306 | 0 | if (z == 0) |
1307 | 0 | return; |
1308 | | |
1309 | | /* Now, we HOPE that the creator of a PDF will minimise the actual text |
1310 | | * differences, so that we'll get: |
1311 | | * "Politicians <ActualText="lie">fib</ActualText>, always." |
1312 | | * rather than: |
1313 | | * "<ActualText="Politicians lie, always">Politicians fib, always.</ActualText> |
1314 | | * but experience with PDF files tells us that this won't always be the case. |
1315 | | * |
1316 | | * We try to minimise the actualtext section here, just in case. |
1317 | | */ |
1318 | | |
1319 | | /* Spot a matching prefix and send it. */ |
1320 | 0 | for (start = 0; start < span->len; start++) |
1321 | 0 | { |
1322 | 0 | int rune; |
1323 | 0 | int len = fz_chartorune(&rune, actualtext); |
1324 | 0 | if (span->items[start].ucs != rune || rune == 0) |
1325 | 0 | break; |
1326 | 0 | actualtext += len; z--; |
1327 | 0 | } |
1328 | 0 | if (start != 0) |
1329 | 0 | do_extract(ctx, dev, span, ctm, 0, start, flags); |
1330 | |
|
1331 | 0 | if (start == span->len) |
1332 | 0 | { |
1333 | | /* The prefix has consumed all this object. Just shorten the actualtext and we'll |
1334 | | * catch the rest next time. */ |
1335 | 0 | z = strlen(actualtext)+1; |
1336 | 0 | memmove(mt->text, actualtext, z); |
1337 | 0 | return; |
1338 | 0 | } |
1339 | | |
1340 | | /* We haven't consumed the whole string, so there must be runes left. |
1341 | | * Shut coverity up. */ |
1342 | 0 | assert(z != 0); |
1343 | | |
1344 | | /* Spot a matching postfix. Can't send it til the end. */ |
1345 | 0 | for (end = span->len; end > start; end--) |
1346 | 0 | { |
1347 | | /* Nasty n^2 algo here, cos backtracking through utf8 is not trivial. It'll do. */ |
1348 | 0 | int rune = rune_index(actualtext, z-1); |
1349 | 0 | if (span->items[end-1].ucs != rune) |
1350 | 0 | break; |
1351 | 0 | z--; |
1352 | 0 | } |
1353 | | /* So we can send end -> span->len at the end. */ |
1354 | | |
1355 | | /* So we have at least SOME chars that don't match. */ |
1356 | | /* Now, do the difficult bit in the middle.*/ |
1357 | | /* items[start..end] have to be sent with actualtext[start..z] */ |
1358 | 0 | for (i = start; i < end; i++) |
1359 | 0 | { |
1360 | 0 | fz_text_item *item = &span->items[i]; |
1361 | 0 | int rune = -1; |
1362 | |
|
1363 | 0 | if (dev->flags & (FZ_STEXT_CLIP | FZ_STEXT_CLIP_RECT)) |
1364 | 0 | { |
1365 | 0 | fz_rect r = current_clip(ctx, dev); |
1366 | 0 | if (fz_glyph_entirely_outside_box(ctx, &ctm, span, &span->items[i], &r)) |
1367 | 0 | { |
1368 | 0 | dev->last.clipped = 1; |
1369 | 0 | continue; |
1370 | 0 | } |
1371 | 0 | } |
1372 | 0 | dev->last.clipped = 0; |
1373 | |
|
1374 | 0 | if ((size_t)i < z) |
1375 | 0 | actualtext += fz_chartorune(&rune, actualtext); |
1376 | | |
1377 | | /* Calculate new pen location and delta */ |
1378 | 0 | tm.e = item->x; |
1379 | 0 | tm.f = item->y; |
1380 | 0 | dev->last.trm = fz_concat(tm, ctm); |
1381 | 0 | dev->last.bidi_level = span->bidi_level; |
1382 | 0 | dev->last.wmode = span->wmode; |
1383 | 0 | if (font != dev->last.font) |
1384 | 0 | { |
1385 | 0 | fz_drop_font(ctx, dev->last.font); |
1386 | 0 | dev->last.font = fz_keep_font(ctx, font); |
1387 | 0 | } |
1388 | 0 | dev->last.valid = 1; |
1389 | 0 | dev->last.flags = flags; |
1390 | | |
1391 | | /* Calculate bounding box and new pen position based on font metrics */ |
1392 | 0 | if (item->gid >= 0) |
1393 | 0 | adv = item->adv; |
1394 | 0 | else |
1395 | 0 | adv = 0; |
1396 | |
|
1397 | 0 | fz_add_stext_char(ctx, dev, font, |
1398 | 0 | rune, |
1399 | 0 | span->items[i].gid, |
1400 | 0 | dev->last.trm, |
1401 | 0 | adv, |
1402 | 0 | dev->last.wmode, |
1403 | 0 | dev->last.bidi_level, |
1404 | 0 | (i == 0) && (dev->flags & FZ_STEXT_PRESERVE_SPANS), |
1405 | 0 | flags); |
1406 | 0 | } |
1407 | | |
1408 | | /* If we haven't spotted a postfix by this point, then don't force ourselves to output |
1409 | | * any more of the actualtext at this point. We might get a new text object that matches |
1410 | | * more of it. */ |
1411 | 0 | if (end == span->len) |
1412 | 0 | { |
1413 | | /* Shorten actualtext and exit. */ |
1414 | 0 | z = strlen(actualtext)+1; |
1415 | 0 | memmove(mt->text, actualtext, z); |
1416 | 0 | return; |
1417 | 0 | } |
1418 | | |
1419 | | /* if this is the first text on the page, and the actual text suffix matches the entire |
1420 | | * span text, then no font will have been set above, so set the last used font to the |
1421 | | * span font since flush_actualtext() assumes that a font has been set. |
1422 | | */ |
1423 | 0 | if (!dev->last.font) |
1424 | 0 | dev->last.font = fz_keep_font(ctx, font); |
1425 | | |
1426 | | /* We found a matching postfix. It seems likely that this is going to be the only |
1427 | | * text object we get, so send any remaining actualtext now. */ |
1428 | 0 | flush_actualtext(ctx, dev, actualtext, i, i + (int)strlen(actualtext) - (span->len - end)); |
1429 | | |
1430 | | /* Send the postfix */ |
1431 | 0 | if (end != span->len) |
1432 | 0 | do_extract(ctx, dev, span, ctm, end, span->len, flags); |
1433 | |
|
1434 | 0 | mt->text[0] = 0; |
1435 | 0 | } |
1436 | | |
1437 | | static void |
1438 | | fz_stext_extract(fz_context *ctx, fz_stext_device *dev, fz_text_span *span, fz_matrix ctm, int flags) |
1439 | 0 | { |
1440 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1441 | 0 | metatext_t *mt = NULL; |
1442 | |
|
1443 | 0 | if (span->len == 0) |
1444 | 0 | return; |
1445 | | |
1446 | | /* Are we in an actualtext? */ |
1447 | 0 | if (!(tdev->opts.flags & FZ_STEXT_IGNORE_ACTUALTEXT)) |
1448 | 0 | mt = find_actualtext(dev); |
1449 | |
|
1450 | 0 | if (mt) |
1451 | 0 | do_extract_within_actualtext(ctx, dev, span, ctm, mt, flags); |
1452 | 0 | else |
1453 | 0 | do_extract(ctx, dev, span, ctm, 0, span->len, flags); |
1454 | 0 | } |
1455 | | |
1456 | | static uint32_t hexrgba_from_color(fz_context *ctx, fz_colorspace *colorspace, const float *color, float alpha) |
1457 | 0 | { |
1458 | 0 | float rgb[3]; |
1459 | 0 | fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params); |
1460 | 0 | return |
1461 | 0 | (((uint32_t) fz_clampi(alpha * 255 + 0.5f, 0, 255)) << 24) | |
1462 | 0 | (((uint32_t) fz_clampi(rgb[0] * 255 + 0.5f, 0, 255)) << 16) | |
1463 | 0 | (((uint32_t) fz_clampi(rgb[1] * 255 + 0.5f, 0, 255)) << 8) | |
1464 | 0 | (((uint32_t) fz_clampi(rgb[2] * 255 + 0.5f, 0, 255))); |
1465 | 0 | } |
1466 | | |
1467 | | static void |
1468 | | fz_stext_fill_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, |
1469 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
1470 | 0 | { |
1471 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1472 | 0 | fz_text_span *span; |
1473 | 0 | if (text == tdev->lasttext && (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) == 0) |
1474 | 0 | return; |
1475 | 0 | tdev->color = hexrgba_from_color(ctx, colorspace, color, alpha); |
1476 | 0 | for (span = text->head; span; span = span->next) |
1477 | 0 | fz_stext_extract(ctx, tdev, span, ctm, FZ_STEXT_FILLED); |
1478 | 0 | fz_drop_text(ctx, tdev->lasttext); |
1479 | 0 | tdev->lasttext = fz_keep_text(ctx, text); |
1480 | 0 | } |
1481 | | |
1482 | | static void |
1483 | | fz_stext_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, |
1484 | | fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) |
1485 | 0 | { |
1486 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1487 | 0 | fz_text_span *span; |
1488 | 0 | if (text == tdev->lasttext && (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) == 0) |
1489 | 0 | return; |
1490 | 0 | tdev->color = hexrgba_from_color(ctx, colorspace, color, alpha); |
1491 | 0 | for (span = text->head; span; span = span->next) |
1492 | 0 | fz_stext_extract(ctx, tdev, span, ctm, FZ_STEXT_STROKED); |
1493 | 0 | fz_drop_text(ctx, tdev->lasttext); |
1494 | 0 | tdev->lasttext = fz_keep_text(ctx, text); |
1495 | 0 | } |
1496 | | |
1497 | | static void |
1498 | | fz_stext_clip_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm, fz_rect scissor) |
1499 | 0 | { |
1500 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1501 | 0 | fz_text_span *span; |
1502 | 0 | if (text == tdev->lasttext && (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) == 0) |
1503 | 0 | return; |
1504 | 0 | tdev->color = 0; |
1505 | 0 | for (span = text->head; span; span = span->next) |
1506 | 0 | fz_stext_extract(ctx, tdev, span, ctm, FZ_STEXT_FILLED | FZ_STEXT_CLIPPED); |
1507 | 0 | fz_drop_text(ctx, tdev->lasttext); |
1508 | 0 | tdev->lasttext = fz_keep_text(ctx, text); |
1509 | 0 | } |
1510 | | |
1511 | | static void |
1512 | | fz_stext_clip_stroke_text(fz_context *ctx, fz_device *dev, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor) |
1513 | 0 | { |
1514 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1515 | 0 | fz_text_span *span; |
1516 | 0 | if (text == tdev->lasttext && (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) == 0) |
1517 | 0 | return; |
1518 | 0 | tdev->color = 0; |
1519 | 0 | for (span = text->head; span; span = span->next) |
1520 | 0 | fz_stext_extract(ctx, tdev, span, ctm, FZ_STEXT_STROKED | FZ_STEXT_CLIPPED); |
1521 | 0 | fz_drop_text(ctx, tdev->lasttext); |
1522 | 0 | tdev->lasttext = fz_keep_text(ctx, text); |
1523 | 0 | } |
1524 | | |
1525 | | static void |
1526 | | fz_stext_ignore_text(fz_context *ctx, fz_device *dev, const fz_text *text, fz_matrix ctm) |
1527 | 0 | { |
1528 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1529 | 0 | fz_text_span *span; |
1530 | 0 | if (text == tdev->lasttext && (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) == 0) |
1531 | 0 | return; |
1532 | 0 | tdev->color = 0; |
1533 | 0 | for (span = text->head; span; span = span->next) |
1534 | 0 | fz_stext_extract(ctx, tdev, span, ctm, 0); |
1535 | 0 | fz_drop_text(ctx, tdev->lasttext); |
1536 | 0 | tdev->lasttext = fz_keep_text(ctx, text); |
1537 | 0 | } |
1538 | | |
1539 | | static void |
1540 | | fz_stext_begin_metatext(fz_context *ctx, fz_device *dev, fz_metatext meta, const char *text) |
1541 | 0 | { |
1542 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1543 | 0 | metatext_t *mt = find_actualtext(tdev); |
1544 | 0 | char *new_text = NULL; |
1545 | |
|
1546 | 0 | if (mt != NULL && meta == FZ_METATEXT_ACTUALTEXT) |
1547 | 0 | flush_actualtext(ctx, tdev, mt->text, 0, -1); |
1548 | |
|
1549 | 0 | if (meta == FZ_METATEXT_ACTUALTEXT) |
1550 | 0 | tdev->last.valid = 0; |
1551 | |
|
1552 | 0 | new_text = text ? fz_strdup(ctx, text) : NULL; |
1553 | |
|
1554 | 0 | fz_try(ctx) |
1555 | 0 | { |
1556 | 0 | mt = fz_malloc_struct(ctx, metatext_t); |
1557 | |
|
1558 | 0 | mt->prev = tdev->metatext; |
1559 | 0 | tdev->metatext = mt; |
1560 | 0 | mt->type = meta; |
1561 | 0 | mt->text = new_text; |
1562 | 0 | mt->bounds = fz_empty_rect; |
1563 | 0 | } |
1564 | 0 | fz_catch(ctx) |
1565 | 0 | { |
1566 | 0 | fz_free(ctx, new_text); |
1567 | 0 | fz_rethrow(ctx); |
1568 | 0 | } |
1569 | 0 | } |
1570 | | |
1571 | | static void |
1572 | | pop_metatext(fz_context *ctx, fz_stext_device *dev) |
1573 | 0 | { |
1574 | 0 | metatext_t *prev; |
1575 | 0 | fz_rect bounds; |
1576 | |
|
1577 | 0 | if (!dev->metatext) |
1578 | 0 | return; |
1579 | | |
1580 | 0 | prev = dev->metatext->prev; |
1581 | 0 | bounds = dev->metatext->bounds; |
1582 | 0 | fz_free(ctx, dev->metatext->text); |
1583 | 0 | fz_free(ctx, dev->metatext); |
1584 | 0 | dev->metatext = prev; |
1585 | 0 | if (prev) |
1586 | 0 | prev->bounds = fz_union_rect(prev->bounds, bounds); |
1587 | 0 | } |
1588 | | |
1589 | | static void |
1590 | | fz_stext_end_metatext(fz_context *ctx, fz_device *dev) |
1591 | 0 | { |
1592 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1593 | 0 | fz_font *myfont = NULL; |
1594 | |
|
1595 | 0 | if (!tdev->metatext) |
1596 | 0 | return; /* Mismatched pop. Live with it. */ |
1597 | | |
1598 | 0 | if (tdev->metatext->type != FZ_METATEXT_ACTUALTEXT || (tdev->opts.flags & FZ_STEXT_IGNORE_ACTUALTEXT) != 0) |
1599 | 0 | { |
1600 | | /* We only deal with ActualText here. Just pop anything else off, |
1601 | | * and we're done. */ |
1602 | 0 | pop_metatext(ctx, tdev); |
1603 | 0 | return; |
1604 | 0 | } |
1605 | | |
1606 | | /* If we have a 'last' text position, send the content after that. */ |
1607 | 0 | if (tdev->last.valid) |
1608 | 0 | { |
1609 | 0 | tdev->last.trm.e = tdev->pen.x; |
1610 | 0 | tdev->last.trm.f = tdev->pen.y; |
1611 | |
|
1612 | 0 | flush_actualtext(ctx, tdev, tdev->metatext->text, 0, -1); |
1613 | 0 | pop_metatext(ctx, tdev); |
1614 | 0 | tdev->last.valid = 0; |
1615 | 0 | return; |
1616 | 0 | } |
1617 | | |
1618 | | /* Unless we have collected a rectangle for content that encloses the actual text, |
1619 | | * we can't do anything. */ |
1620 | 0 | if (fz_is_empty_rect(tdev->metatext->bounds)) |
1621 | 0 | { |
1622 | 0 | if ((dev->flags & (FZ_STEXT_CLIP | FZ_STEXT_CLIP_RECT)) == 0 && tdev->metatext->text[0]) |
1623 | 0 | fz_warn(ctx, "ActualText with no position. Text may be lost or mispositioned."); |
1624 | 0 | pop_metatext(ctx, tdev); |
1625 | 0 | return; |
1626 | 0 | } |
1627 | | |
1628 | | /* We have a rectangle, so send the text to fill that. */ |
1629 | 0 | tdev->last.trm.a = tdev->metatext->bounds.x1 - tdev->metatext->bounds.x0; |
1630 | 0 | tdev->last.trm.b = 0; |
1631 | 0 | tdev->last.trm.c = 0; |
1632 | 0 | tdev->last.trm.d = tdev->metatext->bounds.y0 - tdev->metatext->bounds.y1; |
1633 | 0 | tdev->last.trm.e = tdev->metatext->bounds.x0; |
1634 | 0 | tdev->last.trm.f = tdev->metatext->bounds.y1; |
1635 | 0 | tdev->last.valid = 1; |
1636 | |
|
1637 | 0 | fz_var(myfont); |
1638 | |
|
1639 | 0 | fz_try(ctx) |
1640 | 0 | { |
1641 | 0 | if (tdev->last.font == NULL) |
1642 | 0 | { |
1643 | 0 | myfont = fz_new_base14_font(ctx, "Helvetica"); |
1644 | 0 | tdev->last.font = myfont; |
1645 | 0 | } |
1646 | 0 | flush_actualtext(ctx, tdev, tdev->metatext->text, 0, -1); |
1647 | 0 | pop_metatext(ctx, tdev); |
1648 | 0 | } |
1649 | 0 | fz_always(ctx) |
1650 | 0 | { |
1651 | 0 | if (myfont) |
1652 | 0 | { |
1653 | 0 | tdev->last.font = NULL; |
1654 | 0 | fz_drop_font(ctx, myfont); |
1655 | 0 | } |
1656 | 0 | } |
1657 | 0 | fz_catch(ctx) |
1658 | 0 | fz_rethrow(ctx); |
1659 | 0 | } |
1660 | | |
1661 | | |
1662 | | /* Images and shadings */ |
1663 | | |
1664 | | static void |
1665 | | fz_stext_fill_image(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, float alpha, fz_color_params color_params) |
1666 | 0 | { |
1667 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1668 | 0 | fz_rect *bounds = actualtext_bounds(tdev); |
1669 | | |
1670 | | /* If there is an actualtext in force, update its bounds. */ |
1671 | 0 | if (bounds) |
1672 | 0 | { |
1673 | 0 | static const fz_rect unit = { 0, 0, 1, 1 }; |
1674 | 0 | *bounds = fz_union_rect(*bounds, fz_transform_rect(unit, ctm)); |
1675 | 0 | } |
1676 | | |
1677 | | /* Unless we are being told to preserve images, nothing to do here. */ |
1678 | 0 | if ((tdev->opts.flags & FZ_STEXT_PRESERVE_IMAGES) == 0) |
1679 | 0 | return; |
1680 | | |
1681 | | /* If the alpha is less than 50% then it's probably a watermark or effect or something. Skip it. */ |
1682 | 0 | if (alpha >= 0.5f) |
1683 | 0 | { |
1684 | 0 | fz_stext_block *block; |
1685 | 0 | flush_lazy_vectors(ctx, tdev->page, tdev); |
1686 | 0 | block = add_image_block_to_page(ctx, tdev->page, ctm, img, tdev->id); |
1687 | 0 | if (tdev->opts.flags & FZ_STEXT_CLIP) |
1688 | 0 | { |
1689 | 0 | fz_rect clip = fz_device_current_scissor(ctx, dev); |
1690 | 0 | clip = fz_intersect_rect(clip, tdev->page->mediabox); |
1691 | 0 | block->bbox = fz_intersect_rect(block->bbox, clip); |
1692 | 0 | } |
1693 | 0 | } |
1694 | 0 | } |
1695 | | |
1696 | | static void |
1697 | | fz_stext_fill_image_mask(fz_context *ctx, fz_device *dev, fz_image *img, fz_matrix ctm, |
1698 | | fz_colorspace *cspace, const float *color, float alpha, fz_color_params color_params) |
1699 | 0 | { |
1700 | 0 | fz_stext_fill_image(ctx, dev, img, ctm, alpha, color_params); |
1701 | 0 | } |
1702 | | |
1703 | | static fz_image * |
1704 | | fz_new_image_from_shade(fz_context *ctx, fz_shade *shade, fz_matrix *in_out_ctm, fz_color_params color_params, fz_rect scissor) |
1705 | 0 | { |
1706 | 0 | fz_matrix ctm = *in_out_ctm; |
1707 | 0 | fz_pixmap *pix; |
1708 | 0 | fz_image *img = NULL; |
1709 | 0 | fz_rect bounds; |
1710 | 0 | fz_irect bbox; |
1711 | |
|
1712 | 0 | bounds = fz_bound_shade(ctx, shade, ctm); |
1713 | 0 | bounds = fz_intersect_rect(bounds, scissor); |
1714 | 0 | bbox = fz_irect_from_rect(bounds); |
1715 | |
|
1716 | 0 | pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), bbox, NULL, !shade->use_background); |
1717 | 0 | fz_try(ctx) |
1718 | 0 | { |
1719 | 0 | if (shade->use_background) |
1720 | 0 | fz_fill_pixmap_with_color(ctx, pix, shade->colorspace, shade->background, color_params); |
1721 | 0 | else |
1722 | 0 | fz_clear_pixmap(ctx, pix); |
1723 | 0 | fz_paint_shade(ctx, shade, NULL, ctm, pix, color_params, bbox, NULL, NULL); |
1724 | 0 | img = fz_new_image_from_pixmap(ctx, pix, NULL); |
1725 | 0 | } |
1726 | 0 | fz_always(ctx) |
1727 | 0 | fz_drop_pixmap(ctx, pix); |
1728 | 0 | fz_catch(ctx) |
1729 | 0 | fz_rethrow(ctx); |
1730 | | |
1731 | 0 | in_out_ctm->a = pix->w; |
1732 | 0 | in_out_ctm->b = 0; |
1733 | 0 | in_out_ctm->c = 0; |
1734 | 0 | in_out_ctm->d = pix->h; |
1735 | 0 | in_out_ctm->e = pix->x; |
1736 | 0 | in_out_ctm->f = pix->y; |
1737 | 0 | return img; |
1738 | 0 | } |
1739 | | |
1740 | | static void |
1741 | | fz_stext_fill_shade(fz_context *ctx, fz_device *dev, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params) |
1742 | 0 | { |
1743 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
1744 | 0 | fz_matrix local_ctm; |
1745 | 0 | fz_rect scissor; |
1746 | 0 | fz_image *image; |
1747 | | |
1748 | | /* If we aren't preserving images, don't waste time making the shade. */ |
1749 | 0 | if ((tdev->opts.flags & FZ_STEXT_PRESERVE_IMAGES) == 0) |
1750 | 0 | { |
1751 | | /* But we do still need to handle actualtext bounds. */ |
1752 | 0 | fz_rect *bounds = actualtext_bounds(tdev); |
1753 | 0 | if (bounds) |
1754 | 0 | *bounds = fz_union_rect(*bounds, fz_bound_shade(ctx, shade, ctm)); |
1755 | 0 | return; |
1756 | 0 | } |
1757 | | |
1758 | 0 | local_ctm = ctm; |
1759 | 0 | scissor = fz_device_current_scissor(ctx, dev); |
1760 | 0 | if (dev->flags & FZ_STEXT_CLIP_RECT) |
1761 | 0 | scissor = fz_intersect_rect(scissor, tdev->opts.clip); |
1762 | 0 | scissor = fz_intersect_rect(scissor, tdev->page->mediabox); |
1763 | 0 | image = fz_new_image_from_shade(ctx, shade, &local_ctm, color_params, scissor); |
1764 | 0 | fz_try(ctx) |
1765 | 0 | fz_stext_fill_image(ctx, dev, image, local_ctm, alpha, color_params); |
1766 | 0 | fz_always(ctx) |
1767 | 0 | fz_drop_image(ctx, image); |
1768 | 0 | fz_catch(ctx) |
1769 | 0 | fz_rethrow(ctx); |
1770 | 0 | } |
1771 | | |
1772 | | static void |
1773 | | fixup_bboxes_and_bidi(fz_context *ctx, fz_stext_block *block) |
1774 | 0 | { |
1775 | 0 | fz_stext_line *line; |
1776 | 0 | fz_stext_char *ch; |
1777 | |
|
1778 | 0 | for ( ; block != NULL; block = block->next) |
1779 | 0 | { |
1780 | 0 | if (block->type == FZ_STEXT_BLOCK_STRUCT) |
1781 | 0 | { |
1782 | 0 | if (block->u.s.down) |
1783 | 0 | { |
1784 | 0 | fz_stext_block *block2; |
1785 | 0 | fixup_bboxes_and_bidi(ctx, block->u.s.down->first_block); |
1786 | 0 | for (block2 = block->u.s.down->first_block; block2 != NULL; block2 = block2->next) |
1787 | 0 | { |
1788 | 0 | block->bbox = fz_union_rect(block->bbox, block2->bbox); |
1789 | 0 | } |
1790 | 0 | } |
1791 | 0 | } |
1792 | 0 | if (block->type != FZ_STEXT_BLOCK_TEXT) |
1793 | 0 | continue; |
1794 | 0 | for (line = block->u.t.first_line; line; line = line->next) |
1795 | 0 | { |
1796 | 0 | int reorder = 0; |
1797 | 0 | for (ch = line->first_char; ch; ch = ch->next) |
1798 | 0 | { |
1799 | 0 | fz_rect ch_box = fz_rect_from_quad(ch->quad); |
1800 | 0 | if (ch == line->first_char) |
1801 | 0 | line->bbox = ch_box; |
1802 | 0 | else |
1803 | 0 | line->bbox = fz_union_rect(line->bbox, ch_box); |
1804 | 0 | if (ch->bidi == 3) |
1805 | 0 | reorder = 1; |
1806 | 0 | } |
1807 | 0 | block->bbox = fz_union_rect(block->bbox, line->bbox); |
1808 | 0 | if (reorder) |
1809 | 0 | reverse_bidi_line(line); |
1810 | 0 | } |
1811 | 0 | } |
1812 | 0 | } |
1813 | | |
1814 | | static void |
1815 | | advance_to_x(fz_point *a, fz_point b, float x) |
1816 | 0 | { |
1817 | 0 | a->y += (b.y - a->y) * (x - a->x) / (b.x - a->x); |
1818 | 0 | a->x = x; |
1819 | 0 | } |
1820 | | |
1821 | | static void |
1822 | | advance_to_y(fz_point *a, fz_point b, float y) |
1823 | 0 | { |
1824 | 0 | a->x += (b.x - a->x) * (y - a->y) / (b.y - a->y); |
1825 | 0 | a->y = y; |
1826 | 0 | } |
1827 | | |
1828 | | static int |
1829 | | line_crosses_rect(fz_point a, fz_point b, fz_rect r) |
1830 | 0 | { |
1831 | | /* Cope with trivial exclusions */ |
1832 | 0 | if (a.x < r.x0 && b.x < r.x0) |
1833 | 0 | return 0; |
1834 | 0 | if (a.x > r.x1 && b.x > r.x1) |
1835 | 0 | return 0; |
1836 | 0 | if (a.y < r.y0 && b.y < r.y0) |
1837 | 0 | return 0; |
1838 | 0 | if (a.y > r.y1 && b.y > r.y1) |
1839 | 0 | return 0; |
1840 | | |
1841 | 0 | if (a.x < r.x0) |
1842 | 0 | advance_to_x(&a, b, r.x0); |
1843 | 0 | if (a.x > r.x1) |
1844 | 0 | advance_to_x(&a, b, r.x1); |
1845 | 0 | if (a.y < r.y0) |
1846 | 0 | advance_to_y(&a, b, r.y0); |
1847 | 0 | if (a.y > r.y1) |
1848 | 0 | advance_to_y(&a, b, r.y1); |
1849 | |
|
1850 | 0 | return fz_is_point_inside_rect(a, r); |
1851 | 0 | } |
1852 | | |
1853 | | static float |
1854 | | calculate_ascent(fz_point p, fz_point origin, fz_point dir) |
1855 | 0 | { |
1856 | 0 | return fabsf((origin.x-p.x)*dir.y - (origin.y-p.y)*dir.x); |
1857 | 0 | } |
1858 | | |
1859 | | /* Create us a rect from the given quad, but extend it downwards |
1860 | | * to allow for underlines that pass under the glyphs. */ |
1861 | | static fz_rect expanded_rect_from_quad(fz_quad quad, fz_point dir, fz_point origin, float size) |
1862 | 0 | { |
1863 | | /* Consider the two rects from A and g respectively. |
1864 | | * |
1865 | | * ul +------+ ur or |
1866 | | * | /\ | ul +------+ ur |
1867 | | * | /__\ | | /''\ | |
1868 | | * |/ \| |( || |
1869 | | * ll +------+ lr | ''''|| |
1870 | | * | ''' | <-expected underline level |
1871 | | * ll +------+ lr |
1872 | | * |
1873 | | * So an underline won't cross A's rect, but will cross g's. |
1874 | | * We want to make a rect that includes a suitable amount of |
1875 | | * space underneath. The information we have available to us |
1876 | | * is summed up here: |
1877 | | * |
1878 | | * ul +---------+ ur |
1879 | | * | | |
1880 | | * | origin | |
1881 | | * |+----------> dir |
1882 | | * | | |
1883 | | * ll +---------+ lr |
1884 | | * |
1885 | | * Consider the distance from ul to the line that passes through |
1886 | | * the origin with direction dir. Similarly, consider the distance |
1887 | | * from ur to the same line. This can be thought of as the 'ascent' |
1888 | | * of this character. |
1889 | | * |
1890 | | * We'd like the distance from ul to ll to be greater than this, so |
1891 | | * as to ensure we cover the possible location where an underline |
1892 | | * might reasonably go. |
1893 | | * |
1894 | | * If we have a line (l) through point A with direction vector u, |
1895 | | * the distance between point P and line(l) is: |
1896 | | * |
1897 | | * d(P,l) = || AP x u || / || u || |
1898 | | * |
1899 | | * where x is the cross product. |
1900 | | * |
1901 | | * For us, because || dir || = 1: |
1902 | | * |
1903 | | * d(ul, origin) = || (origin-ul) x dir || |
1904 | | * |
1905 | | * The cross product is only defined in 3 (or 7!) dimensions, so |
1906 | | * extend both vectors into 3d by defining a 0 z component. |
1907 | | * |
1908 | | * (origin-ul) x dir = [ (origin.y - ul.y) . 0 - 0 . dir.y ] |
1909 | | * [ 0 . dir.x - (origin.x - ul.y) . 0 ] |
1910 | | * [ (origin.x - ul.x) . dir.y - (origin.y - ul.y) . dir.x ] |
1911 | | * |
1912 | | * So d(ul, origin) = abs(D) where D = (origin.x-ul.x).dir.y - (origin.y-ul.y).dir.x |
1913 | | */ |
1914 | 0 | float ascent = (calculate_ascent(quad.ul, origin, dir) + calculate_ascent(quad.ur, origin, dir)) / 2; |
1915 | 0 | fz_point left = { quad.ll.x - quad.ul.x, quad.ll.y - quad.ul.y }; |
1916 | 0 | fz_point right = { quad.lr.x - quad.ur.x, quad.lr.y - quad.ur.y }; |
1917 | 0 | float height = (hypotf(left.x, left.y) + hypotf(right.x, right.y))/2; |
1918 | 0 | int neg = 0; |
1919 | 0 | float extra_rise = 0; |
1920 | | |
1921 | | /* Spaces will have 0 ascent. underscores will have small ascent. |
1922 | | * We want a sane ascent to be able to spot strikeouts, but not |
1923 | | * so big that it incorporates lines above the text, like borders. */ |
1924 | 0 | if (ascent < 0.75*size) |
1925 | 0 | extra_rise = 0.75*size - ascent; |
1926 | | |
1927 | | /* We'd like height to be at least ascent + 1/4 size */ |
1928 | 0 | if (height < 0) |
1929 | 0 | neg = 1, height = -height; |
1930 | 0 | if (height < ascent + size * 0.25f) |
1931 | 0 | height = ascent + size * 0.25f; |
1932 | |
|
1933 | 0 | height -= ascent; |
1934 | 0 | if (neg) |
1935 | 0 | height = -height; |
1936 | 0 | quad.ll.x += - height * dir.y; |
1937 | 0 | quad.ll.y += height * dir.x; |
1938 | 0 | quad.lr.x += - height * dir.y; |
1939 | 0 | quad.lr.y += height * dir.x; |
1940 | 0 | quad.ul.x -= - extra_rise * dir.y; |
1941 | 0 | quad.ul.y -= extra_rise * dir.x; |
1942 | 0 | quad.ur.x -= - extra_rise * dir.y; |
1943 | 0 | quad.ur.y -= extra_rise * dir.x; |
1944 | |
|
1945 | 0 | return fz_rect_from_quad(quad); |
1946 | 0 | } |
1947 | | |
1948 | | static int feq(float a,float b) |
1949 | 0 | { |
1950 | 0 | #define EPSILON 0.00001 |
1951 | 0 | a -= b; |
1952 | 0 | if (a < 0) |
1953 | 0 | a = -a; |
1954 | 0 | return a < EPSILON; |
1955 | 0 | } |
1956 | | |
1957 | | static int |
1958 | | check_strikeout(fz_context *ctx, fz_stext_block *block, fz_point from, fz_point to, fz_point dir, float thickness) |
1959 | 0 | { |
1960 | 0 | int style_flags = 0; |
1961 | |
|
1962 | 0 | for ( ; block; block = block->next) |
1963 | 0 | { |
1964 | 0 | fz_stext_line *line; |
1965 | |
|
1966 | 0 | if (block->type != FZ_STEXT_BLOCK_TEXT) |
1967 | 0 | continue; |
1968 | | |
1969 | 0 | for (line = block->u.t.first_line; line != NULL; line = line->next) |
1970 | 0 | { |
1971 | 0 | fz_stext_char *ch; |
1972 | |
|
1973 | 0 | if ((!feq(line->dir.x, dir.x) || !feq(line->dir.y, dir.y)) && |
1974 | 0 | (!feq(line->dir.x, -dir.x) || !feq(line->dir.y, -dir.y))) |
1975 | 0 | continue; |
1976 | | |
1977 | | /* Matching directions... */ |
1978 | | |
1979 | | /* Unfortunately, we don't have a valid line->bbox at this point, so we need to check |
1980 | | * chars. - FIXME: Now we do! */ |
1981 | 0 | for (ch = line->first_char; ch; ch = ch->next) |
1982 | 0 | { |
1983 | 0 | fz_point up; |
1984 | 0 | float dx, dy, dot; |
1985 | 0 | fz_rect ch_box; |
1986 | |
|
1987 | 0 | ch_box = expanded_rect_from_quad(ch->quad, line->dir, ch->origin, ch->size); |
1988 | |
|
1989 | 0 | if (!line_crosses_rect(from, to, ch_box)) |
1990 | 0 | continue; |
1991 | | |
1992 | | /* If the thickness is more than a 1/4 of the size, it's a highlight, not a |
1993 | | * line! */ |
1994 | 0 | if (ch->size < thickness*4) |
1995 | 0 | { |
1996 | | /* Distinguish from a background fill */ |
1997 | 0 | if (thickness <= ch->size*1.5f) |
1998 | 0 | { |
1999 | 0 | ch->flags |= FZ_STEXT_HIGHLIGHT; |
2000 | 0 | style_flags |= FZ_STEXT_VECTOR_IS_HIGHLIGHT; |
2001 | 0 | } |
2002 | 0 | continue; |
2003 | 0 | } |
2004 | | |
2005 | | /* Is this a strikeout or an underline? */ |
2006 | | |
2007 | | /* The baseline moves from ch->origin in the direction line->dir */ |
2008 | 0 | up.x = line->dir.y; |
2009 | 0 | up.y = -line->dir.x; |
2010 | | |
2011 | | /* How far is our line displaced from the line through the origin? */ |
2012 | 0 | dx = from.x - ch->origin.x; |
2013 | 0 | dy = from.y - ch->origin.y; |
2014 | | /* Dot product with up. up is normalised */ |
2015 | 0 | dot = dx * up.x + dy * up.y; |
2016 | |
|
2017 | 0 | if (dot > 0 && dot <= 0.8f * ch->font->ascender * ch->size) |
2018 | 0 | { |
2019 | 0 | ch->flags |= FZ_STEXT_STRIKEOUT; |
2020 | 0 | style_flags |= FZ_STEXT_VECTOR_IS_STRIKEOUT; |
2021 | 0 | } |
2022 | 0 | else |
2023 | 0 | { |
2024 | 0 | ch->flags |= FZ_STEXT_UNDERLINE; |
2025 | 0 | style_flags |= FZ_STEXT_VECTOR_IS_UNDERLINE; |
2026 | 0 | } |
2027 | 0 | } |
2028 | 0 | } |
2029 | 0 | } |
2030 | |
|
2031 | 0 | return style_flags; |
2032 | 0 | } |
2033 | | |
2034 | | static void |
2035 | | check_rects_for_strikeout(fz_context *ctx, fz_stext_device *tdev, fz_stext_page *page) |
2036 | 0 | { |
2037 | 0 | int i, n = tdev->rects_len; |
2038 | |
|
2039 | 0 | for (i = 0; i < n; i++) |
2040 | 0 | { |
2041 | 0 | fz_point from = tdev->rects[i].from; |
2042 | 0 | fz_point to = tdev->rects[i].to; |
2043 | 0 | float thickness = tdev->rects[i].thickness; |
2044 | 0 | fz_point dir; |
2045 | 0 | int type; |
2046 | 0 | dir.x = to.x - from.x; |
2047 | 0 | dir.y = to.y - from.y; |
2048 | 0 | dir = fz_normalize_vector(dir); |
2049 | |
|
2050 | 0 | type = check_strikeout(ctx, page->first_block, from, to, dir, thickness); |
2051 | 0 | if (tdev->rects[i].blk) |
2052 | 0 | tdev->rects[i].blk->u.v.flags |= type; |
2053 | 0 | } |
2054 | 0 | } |
2055 | | |
2056 | | static void |
2057 | | fz_stext_close_device(fz_context *ctx, fz_device *dev) |
2058 | 0 | { |
2059 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2060 | 0 | fz_stext_page *page = tdev->page; |
2061 | |
|
2062 | 0 | if ((tdev->flags & FZ_STEXT_DEHYPHENATE) && fz_is_unicode_hyphen(tdev->lastchar) && tdev->lastline != NULL) |
2063 | 0 | tdev->lastline->flags |= FZ_STEXT_LINE_FLAGS_JOINED; |
2064 | |
|
2065 | 0 | flush_lazy_vectors(ctx, page, tdev); |
2066 | |
|
2067 | 0 | fixup_bboxes_and_bidi(ctx, page->first_block); |
2068 | |
|
2069 | 0 | if (tdev->opts.flags & FZ_STEXT_COLLECT_STYLES) |
2070 | 0 | check_rects_for_strikeout(ctx, tdev, page); |
2071 | | |
2072 | | /* TODO: smart sorting of blocks and lines in reading order */ |
2073 | | /* TODO: unicode NFC normalization */ |
2074 | |
|
2075 | 0 | if (tdev->opts.flags & FZ_STEXT_SEGMENT) |
2076 | 0 | fz_segment_stext_page(ctx, page); |
2077 | |
|
2078 | 0 | if (tdev->opts.flags & FZ_STEXT_PARAGRAPH_BREAK) |
2079 | 0 | fz_paragraph_break(ctx, page); |
2080 | |
|
2081 | 0 | if (tdev->opts.flags & FZ_STEXT_TABLE_HUNT) |
2082 | 0 | fz_table_hunt(ctx, page, NULL); |
2083 | 0 | } |
2084 | | |
2085 | | static void |
2086 | | fz_stext_drop_device(fz_context *ctx, fz_device *dev) |
2087 | 0 | { |
2088 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2089 | 0 | fz_drop_text(ctx, tdev->lasttext); |
2090 | 0 | fz_drop_font(ctx, tdev->last.font); |
2091 | 0 | while (tdev->metatext) |
2092 | 0 | pop_metatext(ctx, tdev); |
2093 | |
|
2094 | 0 | fz_free(ctx, tdev->rects); |
2095 | 0 | } |
2096 | | |
2097 | | static int |
2098 | | val_is_rect(const char *val, fz_rect *rp) |
2099 | 0 | { |
2100 | 0 | fz_rect r; |
2101 | 0 | const char *s; |
2102 | |
|
2103 | 0 | s = strchr(val, ':'); |
2104 | 0 | if (s == NULL || s == val) |
2105 | 0 | return 0; |
2106 | 0 | r.x0 = fz_atof(val); |
2107 | 0 | val = s+1; |
2108 | 0 | s = strchr(val, ':'); |
2109 | 0 | if (s == NULL || s == val) |
2110 | 0 | return 0; |
2111 | 0 | r.y0 = fz_atof(val); |
2112 | 0 | val = s+1; |
2113 | 0 | s = strchr(val, ':'); |
2114 | 0 | if (s == NULL || s == val) |
2115 | 0 | return 0; |
2116 | 0 | r.x1 = fz_atof(val); |
2117 | 0 | val = s+1; |
2118 | 0 | r.y1 = fz_atof(val); |
2119 | |
|
2120 | 0 | *rp = r; |
2121 | |
|
2122 | 0 | return 1; |
2123 | 0 | } |
2124 | | |
2125 | | void fz_init_stext_options(fz_context *ctx, fz_stext_options *opts) |
2126 | 0 | { |
2127 | 0 | memset(opts, 0, sizeof *opts); |
2128 | 0 | fz_init_table_hunt_options(ctx, &opts->table_hunt_options); |
2129 | |
|
2130 | 0 | opts->flags |= FZ_STEXT_CLIP; |
2131 | 0 | opts->scale = 1; |
2132 | 0 | } |
2133 | | |
2134 | | fz_stext_options * |
2135 | | fz_parse_stext_options(fz_context *ctx, fz_stext_options *opts, const char *string) |
2136 | 0 | { |
2137 | 0 | fz_options *options = fz_new_options(ctx, string); |
2138 | 0 | fz_try(ctx) |
2139 | 0 | { |
2140 | 0 | fz_init_stext_options(ctx, opts); |
2141 | 0 | fz_apply_stext_options(ctx, opts, options); |
2142 | 0 | fz_throw_on_unused_options(ctx, options, "stext"); |
2143 | 0 | } |
2144 | 0 | fz_always(ctx) |
2145 | 0 | fz_drop_options(ctx, options); |
2146 | 0 | fz_catch(ctx) |
2147 | 0 | fz_rethrow(ctx); |
2148 | 0 | return opts; |
2149 | 0 | } |
2150 | | |
2151 | | #define SETCLEARBOOL(A, B, C) \ |
2152 | 0 | (A) = (B) ? ((A) | (C)) : ((A) & ~(C)) |
2153 | | |
2154 | | void |
2155 | | fz_apply_stext_options(fz_context *ctx, fz_stext_options *opts, fz_options *string) |
2156 | 0 | { |
2157 | 0 | const char *val; |
2158 | 0 | float x; |
2159 | 0 | int b; |
2160 | | |
2161 | | /* when adding options, remember to update fz_stext_options_usage above */ |
2162 | |
|
2163 | 0 | if (fz_lookup_option_boolean(ctx, string, "preserve-ligatures", &b)) |
2164 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_PRESERVE_LIGATURES); |
2165 | 0 | if (fz_lookup_option_boolean(ctx, string, "preserve-whitespace", &b)) |
2166 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_PRESERVE_WHITESPACE); |
2167 | 0 | if (fz_lookup_option_boolean(ctx, string, "preserve-images", &b)) |
2168 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_PRESERVE_IMAGES); |
2169 | 0 | if (fz_lookup_option_boolean(ctx, string, "inhibit-spaces", &b)) |
2170 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_INHIBIT_SPACES); |
2171 | 0 | if (fz_lookup_option_boolean(ctx, string, "dehyphenate", &b)) |
2172 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_DEHYPHENATE); |
2173 | 0 | if (fz_lookup_option_boolean(ctx, string, "preserve-spans", &b)) |
2174 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_PRESERVE_SPANS); |
2175 | 0 | if (fz_lookup_option_boolean(ctx, string, "structured", &b)) |
2176 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_COLLECT_STRUCTURE); |
2177 | 0 | if (fz_lookup_option_boolean(ctx, string, "use-cid-for-unknown-unicode", &b)) |
2178 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_USE_CID_FOR_UNKNOWN_UNICODE); |
2179 | 0 | if (fz_lookup_option_boolean(ctx, string, "use-gid-for-unknown-unicode", &b)) |
2180 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_USE_GID_FOR_UNKNOWN_UNICODE); |
2181 | 0 | if (fz_lookup_option_boolean(ctx, string, "accurate-bboxes", &b)) |
2182 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_ACCURATE_BBOXES); |
2183 | 0 | if (fz_lookup_option_boolean(ctx, string, "vectors", &b)) |
2184 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_COLLECT_VECTORS); |
2185 | 0 | if (fz_lookup_option_boolean(ctx, string, "lazy-vectors", &b)) |
2186 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_LAZY_VECTORS); |
2187 | 0 | if (fz_lookup_option_boolean(ctx, string, "fuzzy-vectors", &b)) |
2188 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_FUZZY_VECTORS); |
2189 | 0 | if (fz_lookup_option_boolean(ctx, string, "ignore-actualtext", &b)) |
2190 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_IGNORE_ACTUALTEXT); |
2191 | 0 | if (fz_lookup_option_boolean(ctx, string, "segment", &b)) |
2192 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_SEGMENT); |
2193 | 0 | if (fz_lookup_option_boolean(ctx, string, "paragraph-break", &b)) |
2194 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_PARAGRAPH_BREAK); |
2195 | 0 | if (fz_lookup_option_boolean(ctx, string, "table-hunt", &b)) |
2196 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_TABLE_HUNT); |
2197 | 0 | if (fz_lookup_option_boolean(ctx, string, "collect-styles", &b)) |
2198 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_COLLECT_STYLES); |
2199 | 0 | if (fz_lookup_option_boolean(ctx, string, "accurate-ascenders", &b)) |
2200 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_ACCURATE_ASCENDERS); |
2201 | 0 | if (fz_lookup_option_boolean(ctx, string, "accurate-side-bearings", &b)) |
2202 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_ACCURATE_SIDE_BEARINGS); |
2203 | |
|
2204 | 0 | if (fz_lookup_option_boolean(ctx, string, "mediabox-clip", &b)) |
2205 | 0 | { |
2206 | 0 | fz_warn(ctx, "The 'mediabox-clip' option has been deprecated. Use 'clip' instead."); |
2207 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_CLIP); |
2208 | 0 | } |
2209 | 0 | if (fz_lookup_option_boolean(ctx, string, "clip", &b)) |
2210 | 0 | SETCLEARBOOL(opts->flags, b, FZ_STEXT_CLIP); |
2211 | |
|
2212 | 0 | if (fz_lookup_option(ctx, string, "clip-rect", &val) && val_is_rect(val, &opts->clip)) |
2213 | 0 | opts->flags |= FZ_STEXT_CLIP_RECT; |
2214 | |
|
2215 | 0 | if (fz_lookup_option_float(ctx, string, "resolution", &x)) |
2216 | 0 | opts->scale = x / 96.0f; /* HTML base resolution is 96ppi */ |
2217 | |
|
2218 | 0 | fz_apply_table_hunt_options(ctx, &opts->table_hunt_options, string); |
2219 | |
|
2220 | 0 | fz_validate_options(ctx, string, "stext"); |
2221 | 0 | } |
2222 | | |
2223 | | static void |
2224 | | check_for_strikeout(fz_context *ctx, fz_stext_device *tdev, fz_stext_page *page, fz_rect r, int argb, fz_stext_block *block) |
2225 | 0 | { |
2226 | 0 | float thickness; |
2227 | 0 | fz_point from, to; |
2228 | 0 | int i, n = tdev->rects_len; |
2229 | 0 | rect_details *rd; |
2230 | 0 | float w = r.x1 - r.x0; |
2231 | 0 | float h = r.y1 - r.y0; |
2232 | | |
2233 | | /* We know the path is a rectangle, or we wouldn't be here. */ |
2234 | 0 | if (w >= h) |
2235 | 0 | { |
2236 | 0 | thickness = h; |
2237 | 0 | from.x = r.x0; |
2238 | 0 | to.x = r.x1; |
2239 | 0 | from.y = to.y = (r.y0 + r.y1)/2; |
2240 | 0 | } |
2241 | 0 | else |
2242 | 0 | { |
2243 | 0 | thickness = w; |
2244 | 0 | from.y = r.y0; |
2245 | 0 | to.y = r.y1; |
2246 | 0 | from.x = to.x = (r.x0 + r.x1)/2; |
2247 | 0 | } |
2248 | | |
2249 | | /* If we've already had a rectangle of the same colour that covers this region |
2250 | | * then that was probably a cell background color, and this is probably a |
2251 | | * text string background fill. This is not a highlight, or underline or |
2252 | | * strikeout, so don't keep it. */ |
2253 | 0 | for (i = 0; i < n; i++) |
2254 | 0 | { |
2255 | 0 | rect_details *rct = &tdev->rects[i]; |
2256 | 0 | if (rct->argb == argb && fz_contains_rect(rct->rect, r)) |
2257 | 0 | return; |
2258 | 0 | } |
2259 | | |
2260 | | /* Add to the list of rects in the device. */ |
2261 | 0 | rd = fz_push_list(ctx, tdev->rects); |
2262 | 0 | rd->from = from; |
2263 | 0 | rd->to = to; |
2264 | 0 | rd->thickness = thickness; |
2265 | 0 | rd->rect = r; |
2266 | 0 | rd->argb = argb; |
2267 | 0 | rd->blk = block; |
2268 | 0 | } |
2269 | | |
2270 | | static fz_stext_block * |
2271 | | add_vector_imp(fz_context *ctx, fz_stext_page *page, fz_stext_device *tdev, fz_rect bbox, uint32_t flags, uint32_t argb, int id, float exp) |
2272 | 0 | { |
2273 | 0 | fz_stext_block *b; |
2274 | |
|
2275 | 0 | if (exp != 0) |
2276 | 0 | { |
2277 | 0 | bbox.x0 -= exp; |
2278 | 0 | bbox.y0 -= exp; |
2279 | 0 | bbox.x1 += exp; |
2280 | 0 | bbox.y1 += exp; |
2281 | 0 | } |
2282 | |
|
2283 | 0 | if (tdev->flags & (FZ_STEXT_CLIP_RECT | FZ_STEXT_CLIP)) |
2284 | 0 | { |
2285 | 0 | fz_rect r = current_clip(ctx, tdev); |
2286 | 0 | bbox = fz_intersect_rect(bbox, r); |
2287 | 0 | if (!fz_is_valid_rect(bbox)) |
2288 | 0 | return NULL; |
2289 | 0 | } |
2290 | | |
2291 | | /* Can we just add this one onto the previous one? */ |
2292 | | /* Only if it's a small rectangle... */ |
2293 | 0 | if ((flags & FZ_STEXT_VECTOR_IS_RECTANGLE) && bbox.x1 - bbox.x0 <= 2 && bbox.y1 - bbox.y0 <= 2) |
2294 | 0 | { |
2295 | 0 | fz_stext_block *prev; |
2296 | | /* Find b = the previous block. */ |
2297 | 0 | if (tdev->flags & FZ_STEXT_LAZY_VECTORS) |
2298 | 0 | b = tdev->lazy_vectors_tail; |
2299 | 0 | else if (page->last_struct) |
2300 | 0 | b = page->last_struct->last_block; |
2301 | 0 | else |
2302 | 0 | b = page->last_block; |
2303 | |
|
2304 | 0 | if (b && b->type == FZ_STEXT_BLOCK_VECTOR && b->u.v.argb == argb && b->u.v.flags == flags) |
2305 | 0 | { |
2306 | | /* Maybe we can join it? */ |
2307 | 0 | float fudge = 0.001f; |
2308 | 0 | if (b->bbox.x0 == bbox.x0 && b->bbox.x1 == bbox.x1 && b->bbox.y1 + fudge >= bbox.y0 && b->bbox.y0 - fudge <= bbox.y1) |
2309 | 0 | { |
2310 | | /* Stacks vertically. */ |
2311 | 0 | b->bbox.y0 = fz_min(b->bbox.y0, bbox.y0); |
2312 | 0 | b->bbox.y1 = fz_max(b->bbox.y1, bbox.y1); |
2313 | 0 | return b; |
2314 | 0 | } |
2315 | 0 | else if (b->bbox.y0 == bbox.y0 && b->bbox.y1 == bbox.y1 && b->bbox.x1 + fudge >= bbox.x0 && b->bbox.x0 - fudge <= bbox.x1) |
2316 | 0 | { |
2317 | | /* Stacks horizontally. */ |
2318 | 0 | b->bbox.x0 = fz_min(b->bbox.x0, bbox.x0); |
2319 | 0 | b->bbox.x1 = fz_max(b->bbox.x1, bbox.x1); |
2320 | 0 | return b; |
2321 | 0 | } |
2322 | | |
2323 | | /* So, we can't add our new vector onto the previous one. But can we merge the 2 previous ones? */ |
2324 | | /* The intent here is that we allow a set of vector 'blocks' to be merged together, perhaps: |
2325 | | * ABC |
2326 | | * Then we allow another set to be merged together, perhaps DE: |
2327 | | * ABC |
2328 | | * DE |
2329 | | * Then when we get another block that can't be merged into DE (perhaps F): |
2330 | | * ABC |
2331 | | * DE |
2332 | | * F |
2333 | | * We'll consider ABC and DE for merging. Whatevever block that F ends up |
2334 | | * in later (maybe FGH): |
2335 | | * ABC |
2336 | | * DE |
2337 | | * FGH |
2338 | | * will be considered for merging later. We can always do this "exactly" (if the blocks |
2339 | | * line up precisely), but to do this 'lossily', we guard it with 'FUZZY_VECTORS'. |
2340 | | */ |
2341 | 0 | prev = b->prev; |
2342 | 0 | while (prev && prev->type == FZ_STEXT_BLOCK_VECTOR && (prev->u.v.flags & FZ_STEXT_VECTOR_IS_RECTANGLE)) |
2343 | 0 | { |
2344 | | /* Lossless merging. */ |
2345 | 0 | if (prev->bbox.x0 == b->bbox.x0 && prev->bbox.x1 == b->bbox.x1 && prev->bbox.y1 + fudge >= b->bbox.y0 && prev->bbox.y0 - fudge <= b->bbox.y1) |
2346 | 0 | { |
2347 | | /* Stacks exactly vertically. Very rarely hit. */ |
2348 | 0 | prev->bbox.y0 = fz_min(prev->bbox.y0, b->bbox.y0); |
2349 | 0 | prev->bbox.y1 = fz_max(prev->bbox.y1, b->bbox.y1); |
2350 | 0 | return prev; |
2351 | 0 | } |
2352 | 0 | else if (prev->bbox.y0 == b->bbox.y0 && prev->bbox.y1 == b->bbox.y1 && prev->bbox.x1 + fudge >= b->bbox.x0 && prev->bbox.x0 - fudge <= b->bbox.x1) |
2353 | 0 | { |
2354 | | /* Stacks horizontally. Very rarely hit. */ |
2355 | 0 | prev->bbox.x0 = fz_min(prev->bbox.x0, b->bbox.x0); |
2356 | 0 | prev->bbox.x1 = fz_max(prev->bbox.x1, b->bbox.x1); |
2357 | 0 | return prev; |
2358 | 0 | } |
2359 | 0 | if (tdev->flags & FZ_STEXT_FUZZY_VECTORS) |
2360 | 0 | { |
2361 | | /* Be more forgiving in how we merge vectors */ |
2362 | | /* We need to be careful not to merge together differently oriented borders for table cells. |
2363 | | * C |
2364 | | * | |
2365 | | * v |
2366 | | * +-----+-----+ |
2367 | | * A-> | | | |
2368 | | * +-----+-----+ |
2369 | | * B-> | | | |
2370 | | * +-----+-----+ |
2371 | | * |
2372 | | * It'd be fine to merge borders A and B together, because it still signifies the same |
2373 | | * edges. It would NOT be fine to merge A and C together, because we'd lose the sense |
2374 | | * of them being borders, and just have a blob that covered the cell. |
2375 | | * The fudge2 logic below should hopefully allow for this, as well as allowing us to |
2376 | | * match blocks like: |
2377 | | * ABC |
2378 | | * DE FG |
2379 | | * HIJ |
2380 | | * KL MN |
2381 | | * OPQ |
2382 | | */ |
2383 | 0 | float fudge2 = 2; |
2384 | 0 | if ((fabsf(prev->bbox.x0 - b->bbox.x0) <= fudge2 || fabsf(prev->bbox.x1 - b->bbox.x1) <= fudge2) && prev->bbox.y1 + fudge >= b->bbox.y0 && prev->bbox.y0 - fudge <= b->bbox.y1) |
2385 | 0 | { |
2386 | | /* Stacks vertically. */ |
2387 | 0 | goto join; |
2388 | 0 | } |
2389 | 0 | else if ((fabsf(prev->bbox.y0 - b->bbox.y0) <= fudge2 || fabsf(prev->bbox.y1 - b->bbox.y1) <= fudge2) && prev->bbox.x1 + fudge >= b->bbox.x0 && prev->bbox.x0 - fudge <= b->bbox.x1) |
2390 | 0 | { |
2391 | | /* Stacks horizontally. */ |
2392 | 0 | join: |
2393 | 0 | prev->bbox.x0 = fz_min(prev->bbox.x0, b->bbox.x0); |
2394 | 0 | prev->bbox.x1 = fz_max(prev->bbox.x1, b->bbox.x1); |
2395 | 0 | prev->bbox.y0 = fz_min(prev->bbox.y0, b->bbox.y0); |
2396 | 0 | prev->bbox.y1 = fz_max(prev->bbox.y1, b->bbox.y1); |
2397 | | /* Unlink b (so, fiddle with b->prev, which is not necessarily prev!) */ |
2398 | 0 | b->prev->next = NULL; |
2399 | 0 | if (tdev->flags & FZ_STEXT_LAZY_VECTORS) |
2400 | 0 | tdev->lazy_vectors_tail = b->prev; |
2401 | 0 | else if (page->last_struct) |
2402 | 0 | page->last_struct->last_block = b->prev; |
2403 | 0 | else |
2404 | 0 | page->last_block = b->prev; |
2405 | 0 | break; |
2406 | 0 | } |
2407 | 0 | } |
2408 | | /* Now, allow for looking further back. */ |
2409 | 0 | prev = prev->prev; |
2410 | 0 | } |
2411 | 0 | } |
2412 | 0 | } |
2413 | | |
2414 | 0 | if (tdev->flags & FZ_STEXT_LAZY_VECTORS) |
2415 | 0 | b = add_lazy_vector(ctx, page, tdev, id); |
2416 | 0 | else |
2417 | 0 | b = add_block_to_page(ctx, page, FZ_STEXT_BLOCK_VECTOR, id); |
2418 | |
|
2419 | 0 | b->bbox = bbox; |
2420 | 0 | b->u.v.flags = flags; |
2421 | 0 | b->u.v.argb = argb; |
2422 | |
|
2423 | 0 | return b; |
2424 | 0 | } |
2425 | | |
2426 | | typedef struct |
2427 | | { |
2428 | | fz_stext_device *dev; |
2429 | | fz_matrix ctm; |
2430 | | uint32_t argb; |
2431 | | uint32_t flags; |
2432 | | fz_stext_page *page; |
2433 | | fz_rect seg_bounds; |
2434 | | fz_rect leftovers; |
2435 | | int count; |
2436 | | fz_point p[5]; |
2437 | | int id; |
2438 | | float exp; |
2439 | | fz_stext_block *prev_block; |
2440 | | const fz_stroke_state *stroke; |
2441 | | fz_stext_block *rect_block; |
2442 | | } split_path_data; |
2443 | | |
2444 | | static void |
2445 | | add_vector(fz_context *ctx, split_path_data *sp, fz_rect bbox, uint32_t flags) |
2446 | 0 | { |
2447 | 0 | fz_stext_block *b = NULL; |
2448 | |
|
2449 | 0 | if (sp->stroke == NULL && fz_is_empty_rect(bbox)) |
2450 | 0 | { |
2451 | | /* If we're filling empty bounds, do nothing. */ |
2452 | 0 | return; |
2453 | 0 | } |
2454 | 0 | if (!fz_is_valid_rect(bbox)) |
2455 | 0 | return; |
2456 | | |
2457 | 0 | flags |= sp->flags; |
2458 | 0 | if (sp->dev->flags & FZ_STEXT_COLLECT_VECTORS) |
2459 | 0 | { |
2460 | | /* Try to add the vector (this may resolve to an amendement to an existing vector block), |
2461 | | * or a new block, or even being dropped entirely if it's clipped away. */ |
2462 | 0 | b = add_vector_imp(ctx, sp->page, sp->dev, bbox, flags, sp->argb, sp->id, sp->exp); |
2463 | 0 | if (b) |
2464 | 0 | { |
2465 | | /* b is the block we just modified. If we have a previous block (and it's different to |
2466 | | * b), then mark that as continuing. */ |
2467 | 0 | if (sp->prev_block && sp->prev_block != b) |
2468 | 0 | sp->prev_block->u.v.flags |= FZ_STEXT_VECTOR_CONTINUES; |
2469 | | /* Remember b as being our previous block. */ |
2470 | 0 | sp->prev_block = b; |
2471 | 0 | } |
2472 | 0 | } |
2473 | |
|
2474 | 0 | if ((sp->dev->flags & FZ_STEXT_COLLECT_STYLES) && (flags & FZ_STEXT_VECTOR_IS_RECTANGLE)) |
2475 | 0 | check_for_strikeout(ctx, sp->dev, sp->page, bbox, sp->argb, b); |
2476 | 0 | } |
2477 | | |
2478 | | /* |
2479 | | This is called whenever we finish a segment (either a new move, or a close, or |
2480 | | the end of the path). We check for the last segment (represented by sp->count, and |
2481 | | sp->p[0 to 4]) being a rectangle or not; if it is, we send it. If it isn't it goes |
2482 | | into leftovers. |
2483 | | */ |
2484 | | static void |
2485 | | segment_finished(fz_context *ctx, split_path_data *sp) |
2486 | 0 | { |
2487 | 0 | int rect = 0; |
2488 | 0 | fz_rect bounds = sp->seg_bounds; |
2489 | |
|
2490 | 0 | sp->seg_bounds = fz_empty_rect; |
2491 | |
|
2492 | 0 | if (sp->count >= 0) |
2493 | 0 | { |
2494 | 0 | if (sp->count == 3) |
2495 | 0 | { |
2496 | | /* Allow for "moveto A, lineto B, lineto A, close" */ |
2497 | 0 | if (feq(sp->p[0].x, sp->p[2].x) && feq(sp->p[0].y, sp->p[2].y)) |
2498 | 0 | sp->count = 2; |
2499 | 0 | } |
2500 | 0 | if (sp->count == 2) |
2501 | 0 | { |
2502 | 0 | if (feq(sp->p[0].x, sp->p[1].x) || feq(sp->p[0].y, sp->p[1].y)) |
2503 | 0 | rect = 1; /* Count that as a rect */ |
2504 | 0 | } |
2505 | 0 | else if (sp->count == 4 || sp->count == 5) |
2506 | 0 | { |
2507 | 0 | if (feq(sp->p[0].x, sp->p[1].x) && feq(sp->p[2].x, sp->p[3].x) && feq(sp->p[0].y, sp->p[3].y) && feq(sp->p[1].y, sp->p[2].y)) |
2508 | 0 | rect = 1; |
2509 | 0 | else if (feq(sp->p[0].x, sp->p[3].x) && feq(sp->p[1].x, sp->p[2].x) && feq(sp->p[0].y, sp->p[1].y) && feq(sp->p[2].y, sp->p[3].y)) |
2510 | 0 | rect = 1; |
2511 | 0 | } |
2512 | 0 | if (rect) |
2513 | 0 | { |
2514 | 0 | if (sp->stroke) |
2515 | 0 | bounds = fz_expand_rect(bounds, sp->stroke->linewidth/2); |
2516 | 0 | add_vector(ctx, sp, bounds, FZ_STEXT_VECTOR_IS_RECTANGLE); |
2517 | 0 | return; |
2518 | 0 | } |
2519 | 0 | } |
2520 | | |
2521 | | /* We aren't a rectangle! */ |
2522 | 0 | if (sp->stroke) |
2523 | 0 | bounds = fz_adjust_rect_for_stroke(ctx, bounds, sp->stroke, fz_identity); |
2524 | |
|
2525 | 0 | if (sp->dev->flags & (FZ_STEXT_CLIP_RECT | FZ_STEXT_CLIP)) |
2526 | 0 | bounds = fz_intersect_rect(bounds, current_clip(ctx, sp->dev)); |
2527 | |
|
2528 | 0 | if (fz_is_valid_rect(bounds)) |
2529 | 0 | sp->leftovers = fz_union_rect(sp->leftovers, bounds); |
2530 | | |
2531 | | /* Remember we're not a rect. */ |
2532 | 0 | sp->count = -1; |
2533 | 0 | } |
2534 | | |
2535 | | static void |
2536 | | split_move(fz_context *ctx, void *arg, float x, float y) |
2537 | 0 | { |
2538 | 0 | split_path_data *sp = (split_path_data *)arg; |
2539 | 0 | fz_point p = fz_transform_point_xy(x, y, sp->ctm); |
2540 | |
|
2541 | 0 | segment_finished(ctx, sp); |
2542 | 0 | sp->p[0] = p; |
2543 | 0 | sp->count = 1; |
2544 | 0 | sp->seg_bounds.x0 = sp->seg_bounds.x1 = p.x; |
2545 | 0 | sp->seg_bounds.y0 = sp->seg_bounds.y1 = p.y; |
2546 | 0 | } |
2547 | | |
2548 | | static void |
2549 | | split_line(fz_context *ctx, void *arg, float x, float y) |
2550 | 0 | { |
2551 | 0 | split_path_data *sp = (split_path_data *)arg; |
2552 | 0 | fz_point p = fz_transform_point_xy(x, y, sp->ctm); |
2553 | |
|
2554 | 0 | sp->seg_bounds = fz_include_point_in_rect(sp->seg_bounds, p); |
2555 | |
|
2556 | 0 | if (sp->count >= 0) |
2557 | 0 | { |
2558 | | /* Check for lines to the same point. */ |
2559 | 0 | if (sp->count > 0 && feq(sp->p[sp->count-1].x, p.x) && feq(sp->p[sp->count-1].y, p.y)) |
2560 | 0 | return; |
2561 | | |
2562 | 0 | if (sp->count >= 2) |
2563 | 0 | { |
2564 | | /* Allow for multiple monotonic points in a horizontal or vertical line, |
2565 | | * such as seen in borders of tables where each column or row is written |
2566 | | * individually. (e.g. move 0 0 line 100 0 line 200 0 line 300 0) */ |
2567 | 0 | if (feq(p.x, sp->p[sp->count-1].x) && |
2568 | 0 | feq(sp->p[sp->count-1].x, sp->p[sp->count-2].x) && |
2569 | 0 | ((p.y <= sp->p[sp->count-1].y && sp->p[sp->count-1].y <= sp->p[sp->count-2].y) || |
2570 | 0 | (p.y >= sp->p[sp->count-1].y && sp->p[sp->count-1].y >= sp->p[sp->count-2].y))) |
2571 | 0 | { |
2572 | | /* y---->y---->y - Update the previous y */ |
2573 | 0 | sp->p[sp->count-1].y = p.y; |
2574 | 0 | return; |
2575 | 0 | } |
2576 | 0 | else if (feq(p.y, sp->p[sp->count-1].y) && |
2577 | 0 | feq(sp->p[sp->count-1].y, sp->p[sp->count-2].y) && |
2578 | 0 | ((p.x <= sp->p[sp->count-1].x && sp->p[sp->count-1].x <= sp->p[sp->count-2].x) || |
2579 | 0 | (p.x >= sp->p[sp->count-1].x && sp->p[sp->count-1].x >= sp->p[sp->count-2].x))) |
2580 | 0 | { |
2581 | | /* x---->x---->x - Remove the previous x */ |
2582 | 0 | sp->p[sp->count-1].x = p.x; |
2583 | 0 | return; |
2584 | 0 | } |
2585 | 0 | } |
2586 | | |
2587 | | /* If we're still maybe a rect, just record the point. */ |
2588 | 0 | if (sp->count < 4) |
2589 | 0 | { |
2590 | 0 | sp->p[sp->count++] = p; |
2591 | 0 | return; |
2592 | 0 | } |
2593 | | /* Check for close line? */ |
2594 | 0 | if (sp->count == 4) |
2595 | 0 | { |
2596 | 0 | if (feq(sp->p[0].x, p.x) && feq(sp->p[0].y, p.y)) |
2597 | 0 | { |
2598 | | /* We've just drawn a line back to the start point. */ |
2599 | | /* Needless saving of point, but it makes the logic |
2600 | | * easier elsewhere. */ |
2601 | 0 | sp->p[sp->count++] = p; |
2602 | 0 | return; |
2603 | 0 | } |
2604 | 0 | } |
2605 | | /* We can no longer be a rect. */ |
2606 | 0 | sp->count = -1; |
2607 | 0 | } |
2608 | 0 | } |
2609 | | |
2610 | | static void |
2611 | | split_curve(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3) |
2612 | 0 | { |
2613 | 0 | split_path_data *sp = (split_path_data *)arg; |
2614 | |
|
2615 | 0 | sp->seg_bounds = fz_include_point_in_rect(sp->seg_bounds, fz_transform_point_xy(x1, y1, sp->ctm)); |
2616 | 0 | sp->seg_bounds = fz_include_point_in_rect(sp->seg_bounds, fz_transform_point_xy(x2, y2, sp->ctm)); |
2617 | 0 | sp->seg_bounds = fz_include_point_in_rect(sp->seg_bounds, fz_transform_point_xy(x3, y3, sp->ctm)); |
2618 | | |
2619 | | /* We can no longer be a rect. */ |
2620 | 0 | sp->count = -1; |
2621 | 0 | } |
2622 | | |
2623 | | static void |
2624 | | split_close(fz_context *ctx, void *arg) |
2625 | 0 | { |
2626 | 0 | split_path_data *sp = (split_path_data *)arg; |
2627 | |
|
2628 | 0 | segment_finished(ctx, sp); |
2629 | 0 | sp->count = 0; |
2630 | 0 | } |
2631 | | |
2632 | | |
2633 | | static const |
2634 | | fz_path_walker split_path_rects = |
2635 | | { |
2636 | | split_move, |
2637 | | split_line, |
2638 | | split_curve, |
2639 | | split_close |
2640 | | }; |
2641 | | |
2642 | | static fz_stext_block * |
2643 | | add_vectors_from_path(fz_context *ctx, fz_stext_page *page, fz_stext_device *tdev, const fz_path *path, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp, const fz_stroke_state *stroke, float exp) |
2644 | 0 | { |
2645 | 0 | split_path_data sp; |
2646 | 0 | int id = tdev->id; |
2647 | 0 | int trailing_moves_acceptable = (stroke == NULL || stroke->end_cap != FZ_LINECAP_ROUND); |
2648 | |
|
2649 | 0 | sp.dev = tdev; |
2650 | 0 | sp.ctm = ctm; |
2651 | 0 | sp.argb = hexrgba_from_color(ctx, cs, color, alpha); |
2652 | 0 | sp.flags = stroke ? FZ_STEXT_VECTOR_IS_STROKED : 0; |
2653 | 0 | sp.page = page; |
2654 | 0 | sp.count = 0; |
2655 | 0 | sp.leftovers = fz_empty_rect; |
2656 | 0 | sp.seg_bounds = fz_empty_rect; |
2657 | 0 | sp.id = id; |
2658 | 0 | sp.exp = exp; |
2659 | 0 | sp.prev_block = NULL; |
2660 | 0 | sp.stroke = stroke; |
2661 | 0 | fz_walk_path(ctx, path, &split_path_rects, &sp); |
2662 | |
|
2663 | 0 | if (trailing_moves_acceptable && sp.count == 1) |
2664 | 0 | { |
2665 | | /* The last thing was a move, in a circumstance where it can't |
2666 | | * make any difference. For instance: |
2667 | | * |
2668 | | * 0 0 100 100 re |
2669 | | * 2000 800 m |
2670 | | * f |
2671 | | * |
2672 | | * or |
2673 | | * |
2674 | | * 0 0 100 100 re |
2675 | | * 2000 800 m |
2676 | | * S (when we aren't using a round endcap) |
2677 | | * |
2678 | | * Just drop it. */ |
2679 | 0 | } |
2680 | 0 | else |
2681 | 0 | segment_finished(ctx, &sp); |
2682 | | |
2683 | | /* And flush any leftovers (not a rect - by construction!) */ |
2684 | 0 | if (!fz_is_empty_rect(sp.leftovers)) |
2685 | 0 | { |
2686 | 0 | if (sp.stroke) |
2687 | 0 | sp.leftovers = fz_adjust_rect_for_stroke(ctx, sp.leftovers, sp.stroke, fz_identity); |
2688 | 0 | add_vector(ctx, &sp, sp.leftovers, 0); |
2689 | 0 | } |
2690 | |
|
2691 | 0 | return sp.rect_block; |
2692 | 0 | } |
2693 | | |
2694 | | static void |
2695 | | fz_stext_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 cp) |
2696 | 0 | { |
2697 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2698 | 0 | fz_stext_page *page = tdev->page; |
2699 | 0 | fz_rect path_bounds = fz_bound_path(ctx, path, NULL, ctm); |
2700 | 0 | fz_rect *bounds = actualtext_bounds(tdev); |
2701 | | |
2702 | | /* If we're in an actualtext, then update the bounds to include this content. */ |
2703 | 0 | if (bounds != NULL) |
2704 | 0 | *bounds = fz_union_rect(*bounds, path_bounds); |
2705 | |
|
2706 | 0 | if (tdev->flags & (FZ_STEXT_COLLECT_VECTORS | FZ_STEXT_COLLECT_STYLES)) |
2707 | 0 | add_vectors_from_path(ctx, page, tdev, path, ctm, cs, color, alpha, cp, NULL, 0); |
2708 | 0 | } |
2709 | | |
2710 | | static void |
2711 | | fz_stext_stroke_path(fz_context *ctx, fz_device *dev, const fz_path *path, const fz_stroke_state *ss, fz_matrix ctm, fz_colorspace *cs, const float *color, float alpha, fz_color_params cp) |
2712 | 0 | { |
2713 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2714 | 0 | fz_stext_page *page = tdev->page; |
2715 | 0 | fz_rect path_bounds = fz_bound_path(ctx, path, ss, ctm); |
2716 | 0 | fz_rect *bounds = actualtext_bounds((fz_stext_device *)dev); |
2717 | 0 | float exp = ss->linewidth / 2; |
2718 | | |
2719 | | /* If we're in an actualtext, then update the bounds to include this content. */ |
2720 | 0 | if (bounds != NULL) |
2721 | 0 | *bounds = fz_union_rect(*bounds, path_bounds); |
2722 | |
|
2723 | 0 | if (tdev->flags & (FZ_STEXT_COLLECT_VECTORS | FZ_STEXT_COLLECT_STYLES)) |
2724 | 0 | add_vectors_from_path(ctx, page, tdev, path, ctm, cs, color, alpha, cp, ss, exp); |
2725 | 0 | } |
2726 | | |
2727 | | static void |
2728 | | new_stext_struct(fz_context *ctx, fz_stext_page *page, fz_stext_block *block, fz_structure standard, const char *raw) |
2729 | 0 | { |
2730 | 0 | fz_stext_struct *str; |
2731 | 0 | size_t z; |
2732 | |
|
2733 | 0 | if (raw == NULL) |
2734 | 0 | raw = ""; |
2735 | 0 | z = strlen(raw); |
2736 | |
|
2737 | 0 | str = fz_pool_alloc(ctx, page->pool, offsetof(fz_stext_struct, raw) + z + 1); |
2738 | 0 | str->first_block = NULL; |
2739 | 0 | str->last_block = NULL; |
2740 | 0 | str->standard = standard; |
2741 | 0 | str->parent = page->last_struct; |
2742 | 0 | str->up = block; |
2743 | 0 | memcpy(str->raw, raw, z+1); |
2744 | |
|
2745 | 0 | block->u.s.down = str; |
2746 | 0 | } |
2747 | | |
2748 | | fz_stext_block * |
2749 | | fz_new_stext_struct(fz_context *ctx, fz_stext_page *page, fz_structure standard, const char *raw, int idx) |
2750 | 0 | { |
2751 | 0 | fz_stext_block *block; |
2752 | |
|
2753 | 0 | block = fz_pool_alloc(ctx, page->pool, sizeof *page->first_block); |
2754 | 0 | block->bbox = fz_empty_rect; |
2755 | 0 | block->prev = NULL; |
2756 | 0 | block->next = NULL; |
2757 | 0 | block->type = FZ_STEXT_BLOCK_STRUCT; |
2758 | 0 | block->u.s.index = idx; |
2759 | 0 | block->u.s.down = NULL; |
2760 | | /* If this throws, we leak newblock but it's within the pool, so it doesn't matter. */ |
2761 | 0 | new_stext_struct(ctx, page, block, standard, raw); |
2762 | |
|
2763 | 0 | return block; |
2764 | 0 | } |
2765 | | |
2766 | | |
2767 | | static void |
2768 | | fz_stext_begin_structure(fz_context *ctx, fz_device *dev, fz_structure standard, const char *raw, int idx) |
2769 | 0 | { |
2770 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2771 | 0 | fz_stext_page *page = tdev->page; |
2772 | 0 | fz_stext_block *block, *le, *gt, *newblock; |
2773 | |
|
2774 | 0 | if (raw == NULL) |
2775 | 0 | raw = ""; |
2776 | | |
2777 | | /* Find a pointer to the last block. */ |
2778 | 0 | if (page->last_block) |
2779 | 0 | { |
2780 | 0 | block = page->last_block; |
2781 | 0 | } |
2782 | 0 | else if (page->last_struct) |
2783 | 0 | { |
2784 | 0 | block = page->last_struct->last_block; |
2785 | 0 | } |
2786 | 0 | else |
2787 | 0 | { |
2788 | 0 | block = page->first_block; |
2789 | 0 | } |
2790 | | |
2791 | | /* So block is somewhere in the content chain. Let's try and find: |
2792 | | * le = the struct node <= idx before block in the content chain. |
2793 | | * ge = the struct node >= idx after block in the content chain. |
2794 | | * Search backwards to start with. |
2795 | | */ |
2796 | 0 | gt = NULL; |
2797 | 0 | le = block; |
2798 | 0 | while (le) |
2799 | 0 | { |
2800 | 0 | if (le->type == FZ_STEXT_BLOCK_STRUCT) |
2801 | 0 | { |
2802 | 0 | if (le->u.s.index > idx) |
2803 | 0 | gt = le; |
2804 | 0 | if (le->u.s.index <= idx) |
2805 | 0 | break; |
2806 | 0 | } |
2807 | 0 | le = le->prev; |
2808 | 0 | } |
2809 | | /* The following loop copes with finding gt (the smallest block with an index higher |
2810 | | * than we want) if we haven't found it already. The while loop in here was designed |
2811 | | * to cope with 'block' being in the middle of a list. In fact, the way the code is |
2812 | | * currently, block will always be at the end of a list, so the while won't do anything. |
2813 | | * But I'm loathe to remove it in case we ever change this code to start from wherever |
2814 | | * we did the last insertion. */ |
2815 | 0 | if (gt == NULL) |
2816 | 0 | { |
2817 | 0 | gt = block; |
2818 | 0 | while (gt) |
2819 | 0 | { |
2820 | 0 | if (gt->type == FZ_STEXT_BLOCK_STRUCT) |
2821 | 0 | { |
2822 | 0 | if (gt->u.s.index <= idx) |
2823 | 0 | le = gt; |
2824 | 0 | if (gt->u.s.index >= idx) |
2825 | 0 | break; |
2826 | 0 | } |
2827 | 0 | block = gt; |
2828 | 0 | gt = gt->next; |
2829 | 0 | } |
2830 | 0 | } |
2831 | |
|
2832 | 0 | if (le && le->u.s.index == idx) |
2833 | 0 | { |
2834 | | /* We want to move down into the le block. Does it have a struct |
2835 | | * attached yet? */ |
2836 | 0 | if (le->u.s.down == NULL) |
2837 | 0 | { |
2838 | | /* No. We need to create a new struct node. */ |
2839 | 0 | new_stext_struct(ctx, page, le, standard, raw); |
2840 | 0 | } |
2841 | 0 | else if (le->u.s.down->standard != standard || strcmp(raw, le->u.s.down->raw) != 0) |
2842 | 0 | { |
2843 | | /* Yes, but it doesn't match the one we expect! */ |
2844 | 0 | fz_warn(ctx, "Mismatched structure type!"); |
2845 | 0 | } |
2846 | 0 | page->last_struct = le->u.s.down; |
2847 | 0 | page->last_block = le->u.s.down->last_block; |
2848 | |
|
2849 | 0 | return; |
2850 | 0 | } |
2851 | | |
2852 | | /* We are going to need to create a new block. Create a complete unlinked one here. */ |
2853 | 0 | newblock = fz_new_stext_struct(ctx, page, standard, raw, idx); |
2854 | | |
2855 | | /* So now we just need to link it in somewhere. */ |
2856 | 0 | if (gt) |
2857 | 0 | { |
2858 | | /* Link it in before gt. */ |
2859 | 0 | newblock->prev = gt->prev; |
2860 | 0 | if (gt->prev) |
2861 | 0 | gt->prev->next = newblock; |
2862 | 0 | else if (page->last_struct) |
2863 | 0 | { |
2864 | | /* We're linking it in at the start under another struct! */ |
2865 | 0 | assert(page->last_struct->first_block == gt); |
2866 | 0 | assert(page->last_struct->last_block != NULL); |
2867 | 0 | page->last_struct->first_block = newblock; |
2868 | 0 | } |
2869 | 0 | else |
2870 | 0 | { |
2871 | | /* We're linking it in at the start of the page! */ |
2872 | 0 | assert(page->first_block == gt); |
2873 | 0 | page->first_block = newblock; |
2874 | 0 | } |
2875 | 0 | gt->prev = newblock; |
2876 | 0 | newblock->next = gt; |
2877 | 0 | newblock->id = gt->id; |
2878 | 0 | } |
2879 | 0 | else if (block) |
2880 | 0 | { |
2881 | | /* Link it in at the end of the list (i.e. after 'block') */ |
2882 | 0 | newblock->prev = block; |
2883 | 0 | block->next = newblock; |
2884 | 0 | if (page->last_struct) |
2885 | 0 | { |
2886 | 0 | assert(page->last_struct->last_block == block); |
2887 | 0 | page->last_struct->last_block = newblock; |
2888 | 0 | } |
2889 | 0 | else |
2890 | 0 | { |
2891 | 0 | assert(page->last_block == block); |
2892 | 0 | page->last_block = newblock; |
2893 | 0 | } |
2894 | 0 | newblock->id = block->id; |
2895 | 0 | } |
2896 | 0 | else if (page->last_struct) |
2897 | 0 | { |
2898 | | /* We have no blocks at all at this level. */ |
2899 | 0 | page->last_struct->first_block = newblock; |
2900 | 0 | page->last_struct->last_block = newblock; |
2901 | 0 | newblock->id = page->last_struct->up->id; |
2902 | 0 | } |
2903 | 0 | else |
2904 | 0 | { |
2905 | | /* We have no blocks at ANY level. */ |
2906 | 0 | page->first_block = newblock; |
2907 | | /* newblock will have an id of 0. Best we can do. */ |
2908 | 0 | } |
2909 | | /* Wherever we linked it in, that's where we want to continue adding content. */ |
2910 | 0 | page->last_struct = newblock->u.s.down; |
2911 | 0 | page->last_block = NULL; |
2912 | 0 | } |
2913 | | |
2914 | | static void |
2915 | | fz_stext_end_structure(fz_context *ctx, fz_device *dev) |
2916 | 0 | { |
2917 | 0 | fz_stext_device *tdev = (fz_stext_device*)dev; |
2918 | 0 | fz_stext_page *page = tdev->page; |
2919 | 0 | fz_stext_struct *str = page->last_struct; |
2920 | |
|
2921 | 0 | if (str == NULL) |
2922 | 0 | { |
2923 | 0 | fz_warn(ctx, "Structure out of sync"); |
2924 | 0 | return; |
2925 | 0 | } |
2926 | | |
2927 | 0 | page->last_struct = str->parent; |
2928 | 0 | if (page->last_struct == NULL) |
2929 | 0 | { |
2930 | 0 | page->last_block = page->first_block; |
2931 | | /* Yuck */ |
2932 | 0 | while (page->last_block->next) |
2933 | 0 | page->last_block = page->last_block->next; |
2934 | 0 | } |
2935 | 0 | else |
2936 | 0 | { |
2937 | 0 | page->last_block = page->last_struct->last_block; |
2938 | 0 | } |
2939 | 0 | } |
2940 | | |
2941 | | fz_device * |
2942 | | fz_new_stext_device(fz_context *ctx, fz_stext_page *page, const fz_stext_options *opts) |
2943 | 0 | { |
2944 | 0 | return fz_new_stext_device_for_page(ctx, page, opts, 0, 0, fz_empty_rect); |
2945 | 0 | } |
2946 | | |
2947 | | fz_device * |
2948 | | fz_new_stext_device_for_page(fz_context *ctx, fz_stext_page *page, const fz_stext_options *opts, int chapter_num, int page_num, fz_rect mediabox) |
2949 | 0 | { |
2950 | 0 | fz_stext_device *dev = fz_new_derived_device(ctx, fz_stext_device); |
2951 | |
|
2952 | 0 | dev->super.close_device = fz_stext_close_device; |
2953 | 0 | dev->super.drop_device = fz_stext_drop_device; |
2954 | |
|
2955 | 0 | dev->super.fill_text = fz_stext_fill_text; |
2956 | 0 | dev->super.stroke_text = fz_stext_stroke_text; |
2957 | 0 | dev->super.clip_text = fz_stext_clip_text; |
2958 | 0 | dev->super.clip_stroke_text = fz_stext_clip_stroke_text; |
2959 | 0 | dev->super.ignore_text = fz_stext_ignore_text; |
2960 | 0 | dev->super.begin_metatext = fz_stext_begin_metatext; |
2961 | 0 | dev->super.end_metatext = fz_stext_end_metatext; |
2962 | |
|
2963 | 0 | dev->super.fill_shade = fz_stext_fill_shade; |
2964 | 0 | dev->super.fill_image = fz_stext_fill_image; |
2965 | 0 | dev->super.fill_image_mask = fz_stext_fill_image_mask; |
2966 | |
|
2967 | 0 | if (opts) |
2968 | 0 | { |
2969 | 0 | dev->flags = opts->flags; |
2970 | 0 | if (opts->flags & FZ_STEXT_COLLECT_STRUCTURE) |
2971 | 0 | { |
2972 | 0 | dev->super.begin_structure = fz_stext_begin_structure; |
2973 | 0 | dev->super.end_structure = fz_stext_end_structure; |
2974 | 0 | } |
2975 | 0 | if (opts->flags & (FZ_STEXT_COLLECT_VECTORS | FZ_STEXT_COLLECT_STYLES)) |
2976 | 0 | { |
2977 | 0 | dev->super.fill_path = fz_stext_fill_path; |
2978 | 0 | dev->super.stroke_path = fz_stext_stroke_path; |
2979 | 0 | } |
2980 | 0 | } |
2981 | 0 | dev->page = page; |
2982 | 0 | dev->pen.x = 0; |
2983 | 0 | dev->pen.y = 0; |
2984 | 0 | dev->trm = fz_identity; |
2985 | 0 | dev->lastchar = ' '; |
2986 | 0 | dev->lastline = NULL; |
2987 | 0 | dev->lasttext = NULL; |
2988 | 0 | dev->lastbidi = 0; |
2989 | 0 | dev->last_was_fake_bold = 1; |
2990 | 0 | if (opts) |
2991 | 0 | dev->opts = *opts; |
2992 | | |
2993 | | /* If we are ignoring images, then it'd be nice to skip the decode costs. BUT we still need them to tell |
2994 | | * us the bounds for ActualText, so we can only actually skip them if we are ignoring actualtext too. */ |
2995 | 0 | if ((dev->flags & FZ_STEXT_PRESERVE_IMAGES) == 0 && (dev->opts.flags & FZ_STEXT_IGNORE_ACTUALTEXT) != 0) |
2996 | 0 | dev->super.hints |= FZ_DONT_DECODE_IMAGES; |
2997 | |
|
2998 | 0 | dev->rects_cap = 0; |
2999 | 0 | dev->rects_len = 0; |
3000 | 0 | dev->rects = NULL; |
3001 | | |
3002 | | /* Push a new id */ |
3003 | 0 | fz_try(ctx) |
3004 | 0 | { |
3005 | 0 | fz_stext_page_details *deets; |
3006 | 0 | size_t id; |
3007 | 0 | deets = fz_pool_array_append(ctx, page->id_list, &id); |
3008 | 0 | dev->id = (int)id; |
3009 | 0 | deets->mediabox = mediabox; |
3010 | 0 | deets->chapter = chapter_num; |
3011 | 0 | deets->page = page_num; |
3012 | 0 | } |
3013 | 0 | fz_catch(ctx) |
3014 | 0 | { |
3015 | 0 | fz_free(ctx, dev); |
3016 | 0 | fz_rethrow(ctx); |
3017 | 0 | } |
3018 | | |
3019 | 0 | page->mediabox = fz_union_rect(page->mediabox, mediabox); |
3020 | |
|
3021 | 0 | return (fz_device*)dev; |
3022 | 0 | } |