Coverage Report

Created: 2026-10-13 07:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}