Coverage Report

Created: 2026-11-12 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mupdf/source/html/html-layout.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
//
15
// You should have received a copy of the GNU Affero General Public License
16
// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17
//
18
// Alternative licensing terms are available from the licensor.
19
// For commercial licensing, see <https://www.artifex.com/> or contact
20
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21
// CA 94129, USA, for further information.
22
23
#include "mupdf/fitz.h"
24
#include "mupdf/ucdn.h"
25
#include "html-imp.h"
26
27
#include "hb.h"
28
#include "hb-ft.h"
29
#include <ft2build.h>
30
31
#include <math.h>
32
#include <assert.h>
33
34
//#define DEBUG_HARFBUZZ
35
36
//#define DEBUG_DESPERATE_SPLITTING
37
38
/*
39
  Some notes on the layout code below and the concepts used.
40
41
  THE MODEL:
42
43
  The standard CSS box model is used here. Each box has a margin, a border, padding, and content, any of which can be zero in size.
44
45
  +-------------------------------------------------------------------+
46
  | margin                                                            |
47
  |        +-------------------------------------------------+        |
48
  |        | border                                          |        |
49
  |        |        +-------------------------------+        |        |
50
  |        |        | padding                       |        |        |
51
  |        |        |           +---------+         |        |        |
52
  |        |        |           | content |         |        |        |
53
  |        |        |           +---------+         |        |        |
54
  |        |        |                               |        |        |
55
  |        |        +-------------------------------+        |        |
56
  |        |                                                 |        |
57
  |        +-------------------------------------------------+        |
58
  |                                                                   |
59
  +-------------------------------------------------------------------+
60
61
  In our structures, the box->x,y,w,b refer to the bounding box of the content section alone. The margins/borders/paddings are applied
62
  to the outside of this automatically.
63
64
  HOW WE LAYOUT:
65
66
  Our implementation is a 'simple' recursive descent of the structure. We know the width of the area we are laying out into at each point,
67
  and we know the top/left coords of the enclosing block. So we start with a well defined left/top point, offset that for margins/borders/padding
68
  etc, calculate the width from the enclosing width (via the CSS). We set the height of the content to be zero to start with, and after laying
69
  out each of (any) child elements, we ensure that the base of our box is always large enough to enclose the child's boxes with the appropriate
70
  padding.
71
72
  VERTICAL MARGIN COLLAPSE:
73
74
  The big complexity in this code is the need to do 'vertical margin collapse'. Basically, if we have 2 blocks, laid out vertically from one
75
  another, the margin between them is collapsed to be the larger of margins of the two blocks, rather than the sum of the margins of the
76
  two blocks.
77
78
  So block A with a margin of 30, and block B with a margin of 20 will be separated by 30, not 50.
79
80
  To make matters more complicated, margin collapse can happen 'through' nestings of blocks, and even through empty blocks. It cannot
81
  happen through blocks with borders, or padding.
82
83
  Accordingly, as we recursively pass through our structure laying out, we keep a 'vertical' parameter, representing the amount of vertical
84
  space to be considered for collapse. This is set to zero if we hit borders or padding or non-zero height content boxes, and our margins are
85
  adjusted by it.
86
87
  While the margins of a block may be adjusted by a parent 'inheriting' those margins, the bbox of the content of a block will never be
88
  changed.
89
90
  NON-RESTARTING OPERATION:
91
92
  The standard layout operation as used for HTML documents and ebooks has us layout onto an infinitely long page, with the vertical
93
  offsets of boxes adjusted so that a given line of text (or image) never spans a page end (defined to be a multiple of page_h). This
94
  layout can proceed from start to finish with no need to stop. This is achieved by calling the code with restart == NULL.
95
96
  RESTARTING:
97
98
  In order to cope with laying out a text story into multiple (potentially differently sized) areas, we need to be able to stop the layout
99
  when the available area is full, and restart again later. We do this by having a restart record. The tree structure means we can't
100
  simply return a pointer to the node to continue processing at - we need to recursively redescend the tree until we reach the same
101
  position, whereupon we will continue. Also, because of the fact that the area being laid out into is potentially a completely different
102
  size, we need to recalculate widths etc, so at least part of the tree needs to be reprocessed when we restart.
103
104
  Accordingly, two ways to do this immediately suggest themselves. The first is to store the path down the tree so that we can quickly
105
  and efficiently skip whole areas of the tree that do not need reprocessing and redescend just the sections we need to. The second is to
106
  accept that we will reprocess the entire prefix of the tree each time, and just not actually do any work until we reach the exact
107
  box where we need to restart.
108
109
  We opt for the second one here, on the grounds that 1) it's easier to code, 2) it requires a constant amount of storage, and 3) that
110
  the trees we will be reprocessing will be relatively small. Should we ever need to revisit this decision, we can do so.
111
*/
112
113
enum { T, R, B, L };
114
115
/* === LAYOUT INLINE TEXT === */
116
117
typedef struct string_walker
118
{
119
  fz_context *ctx;
120
  hb_buffer_t *hb_buf;
121
  int rtl;
122
  const char *start;
123
  const char *end;
124
  const char *s;
125
  fz_font *base_font;
126
  int script;
127
  int language;
128
  int small_caps;
129
  fz_font *font;
130
  fz_font *next_font;
131
  hb_glyph_position_t *glyph_pos;
132
  hb_glyph_info_t *glyph_info;
133
  unsigned int glyph_count;
134
  int scale;
135
  int graphemes;
136
} string_walker;
137
138
static int quick_ligature(fz_context *ctx, string_walker *walker, unsigned int *in)
139
204k
{
140
204k
  unsigned int i = *in;
141
204k
  if (walker->glyph_info[i].codepoint == 'f' && i + 1 < walker->glyph_count && !fz_font_flags(walker->font)->is_mono)
142
394
  {
143
394
    if (walker->glyph_info[i+1].codepoint == 'f')
144
2
    {
145
2
      if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'i')
146
0
      {
147
0
        if (fz_encode_character(ctx, walker->font, 0xFB03))
148
0
          return *in += 3, 0xFB03;
149
0
      }
150
2
      if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'l')
151
0
      {
152
0
        if (fz_encode_character(ctx, walker->font, 0xFB04))
153
0
          return *in += 3, 0xFB04;
154
0
      }
155
2
      if (fz_encode_character(ctx, walker->font, 0xFB00))
156
2
        return *in += 2, 0xFB00;
157
2
    }
158
392
    if (walker->glyph_info[i+1].codepoint == 'i')
159
10
    {
160
10
      if (fz_encode_character(ctx, walker->font, 0xFB01))
161
10
        return *in += 2, 0xFB01;
162
10
    }
163
382
    if (walker->glyph_info[i+1].codepoint == 'l')
164
0
    {
165
0
      if (fz_encode_character(ctx, walker->font, 0xFB02))
166
0
        return *in += 2, 0xFB02;
167
0
    }
168
382
  }
169
204k
  return *in += 1, walker->glyph_info[i].codepoint;
170
204k
}
171
172
static void init_string_walker(fz_context *ctx, string_walker *walker, hb_buffer_t *hb_buf, int rtl, fz_font *font, int script, int language, int small_caps, const char *text, int cluster_as_graphemes)
173
55.9k
{
174
55.9k
  walker->ctx = ctx;
175
55.9k
  walker->hb_buf = hb_buf;
176
55.9k
  walker->rtl = rtl;
177
55.9k
  walker->start = text;
178
55.9k
  walker->end = text;
179
55.9k
  walker->s = text;
180
55.9k
  walker->base_font = font;
181
55.9k
  walker->script = script;
182
55.9k
  walker->language = language;
183
55.9k
  walker->font = NULL;
184
55.9k
  walker->next_font = NULL;
185
55.9k
  walker->small_caps = small_caps;
186
55.9k
  walker->graphemes = cluster_as_graphemes;
187
55.9k
}
188
189
static void
190
destroy_hb_shaper_data(fz_context *ctx, void *handle)
191
61
{
192
61
  fz_hb_lock(ctx);
193
61
  hb_font_destroy(handle);
194
61
  fz_hb_unlock(ctx);
195
61
}
196
197
static const hb_feature_t small_caps_feature[1] = {
198
  { HB_TAG('s','m','c','p'), 1, 0, (unsigned int)-1 }
199
};
200
201
static int walk_string(string_walker *walker)
202
336k
{
203
336k
  fz_context *ctx = walker->ctx;
204
336k
  FT_Face face;
205
336k
  int fterr;
206
336k
  int quickshape;
207
336k
  char lang[8];
208
209
336k
  walker->start = walker->end;
210
336k
  walker->end = walker->s;
211
336k
  walker->font = walker->next_font;
212
213
336k
  if (*walker->start == 0)
214
55.9k
    return 0;
215
216
  /* Run through the string, encoding chars until we find one
217
   * that requires a different fallback font. */
218
559k
  while (*walker->s)
219
503k
  {
220
503k
    int c;
221
222
503k
    walker->s += fz_chartorune(&c, walker->s);
223
503k
    (void)fz_encode_character_with_fallback(ctx, walker->base_font, c, walker->script, walker->language, &walker->next_font);
224
503k
    if (walker->next_font != walker->font)
225
280k
    {
226
280k
      if (walker->font != NULL)
227
224k
        break;
228
55.9k
      walker->font = walker->next_font;
229
55.9k
    }
230
278k
    walker->end = walker->s;
231
278k
  }
232
233
  /* Disable harfbuzz shaping if script is common or LGC and there are no opentype tables. */
234
280k
  quickshape = 0;
235
280k
  if (walker->script <= 3 && !walker->rtl && !fz_font_flags(walker->font)->has_opentype)
236
138k
    quickshape = 1;
237
238
280k
  fz_hb_lock(ctx);
239
561k
  fz_try(ctx)
240
561k
  {
241
280k
    face = fz_font_ft_face(ctx, walker->font);
242
280k
    walker->scale = face->units_per_EM;
243
280k
    fterr = FT_Set_Char_Size(face, walker->scale, walker->scale, 72, 72);
244
280k
    if (fterr)
245
0
      fz_throw(ctx, FZ_ERROR_LIBRARY, "freetype setting character size: %s", ft_error_string(fterr));
246
247
280k
    hb_buffer_clear_contents(walker->hb_buf);
248
280k
    hb_buffer_set_direction(walker->hb_buf, walker->rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
249
    /* hb_buffer_set_script(walker->hb_buf, hb_ucdn_script_translate(walker->script)); */
250
280k
    if (walker->language)
251
0
    {
252
0
      fz_string_from_text_language(lang, walker->language);
253
0
      Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
254
0
      hb_buffer_set_language(walker->hb_buf, hb_language_from_string(lang, (int)strlen(lang)));
255
0
      Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
256
0
    }
257
280k
    if (walker->graphemes)
258
0
      hb_buffer_set_cluster_level(walker->hb_buf, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES);
259
280k
    else
260
280k
      hb_buffer_set_cluster_level(walker->hb_buf, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
261
262
280k
    hb_buffer_add_utf8(walker->hb_buf, walker->start, walker->end - walker->start, 0, -1);
263
264
280k
    if (!quickshape)
265
142k
    {
266
142k
      fz_shaper_data *hb = fz_font_shaper_data(ctx, walker->font);
267
142k
      Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
268
142k
      if (hb->shaper_handle == NULL)
269
61
      {
270
61
        hb->destroy = destroy_hb_shaper_data;
271
61
        hb->shaper_handle = hb_ft_font_create(face, NULL);
272
61
      }
273
274
142k
      hb_buffer_guess_segment_properties(walker->hb_buf);
275
276
142k
      if (walker->small_caps)
277
0
        hb_shape(hb->shaper_handle, walker->hb_buf, small_caps_feature, nelem(small_caps_feature));
278
142k
      else
279
142k
        hb_shape(hb->shaper_handle, walker->hb_buf, NULL, 0);
280
142k
      Memento_stopLeaking();
281
142k
    }
282
283
280k
    walker->glyph_pos = hb_buffer_get_glyph_positions(walker->hb_buf, &walker->glyph_count);
284
280k
    walker->glyph_info = hb_buffer_get_glyph_infos(walker->hb_buf, NULL);
285
280k
  }
286
561k
  fz_always(ctx)
287
280k
  {
288
280k
    fz_hb_unlock(ctx);
289
280k
  }
290
280k
  fz_catch(ctx)
291
0
  {
292
0
    fz_rethrow(ctx);
293
0
  }
294
295
280k
  if (quickshape)
296
138k
  {
297
138k
    unsigned int i, k;
298
342k
    for (i = k = 0; i < walker->glyph_count; ++k)
299
204k
    {
300
204k
      int glyph, unicode;
301
204k
      if (i != k)
302
8
      {
303
8
        walker->glyph_info[k] = walker->glyph_info[i];
304
8
        walker->glyph_pos[k] = walker->glyph_pos[i];
305
8
      }
306
204k
      unicode = quick_ligature(ctx, walker, &i);
307
204k
      if (walker->small_caps)
308
0
        glyph = fz_encode_character_sc(ctx, walker->font, unicode);
309
204k
      else
310
204k
        glyph = fz_encode_character(ctx, walker->font, unicode);
311
204k
      walker->glyph_info[k].codepoint = glyph;
312
204k
      walker->glyph_pos[k].x_offset = 0;
313
204k
      walker->glyph_pos[k].y_offset = 0;
314
204k
      walker->glyph_pos[k].x_advance = fz_advance_glyph(ctx, walker->font, glyph, 0) * face->units_per_EM;
315
204k
      walker->glyph_pos[k].y_advance = 0;
316
204k
    }
317
138k
    walker->glyph_count = k;
318
138k
  }
319
320
280k
  return 1;
321
280k
}
322
323
static const char *get_node_text(fz_context *ctx, fz_html_flow *node)
324
55.9k
{
325
55.9k
  if (node->type == FLOW_WORD)
326
48.7k
    return node->content.text;
327
7.20k
  else if (node->type == FLOW_SPACE)
328
7.19k
    return " ";
329
10
  else if (node->type == FLOW_SHYPHEN)
330
10
    return "-";
331
0
  else
332
0
    return "";
333
55.9k
}
334
335
static void measure_string_w(fz_context *ctx, fz_html_flow *node, hb_buffer_t *hb_buf)
336
29.4k
{
337
29.4k
  float em = node->box->s.layout.em;
338
29.4k
  string_walker walker;
339
29.4k
  unsigned int i;
340
29.4k
  const char *s;
341
29.4k
  node->w = 0;
342
29.4k
  s = get_node_text(ctx, node);
343
29.4k
  init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, node->box->style->font, node->script, node->markup_lang, node->box->style->small_caps, s, 0);
344
171k
  while (walk_string(&walker))
345
141k
  {
346
141k
    int x = 0;
347
395k
    for (i = 0; i < walker.glyph_count; i++)
348
253k
      x += walker.glyph_pos[i].x_advance;
349
141k
    node->w += x * em / walker.scale;
350
141k
  }
351
29.4k
}
352
353
static void measure_string_h(fz_context *ctx, fz_html_flow *node)
354
36.7k
{
355
36.7k
  float em = node->box->s.layout.em;
356
36.7k
  if (fz_css_number_defined(node->box->style->leading))
357
0
    node->h = fz_from_css_number(node->box->style->leading, em, em, 0);
358
36.7k
  else
359
36.7k
    node->h = fz_from_css_number_scale(node->box->style->line_height, em);
360
36.7k
}
361
362
363
static float measure_line(fz_html_flow *node, fz_html_flow *end, float *baseline)
364
6.93k
{
365
6.93k
  float max_a = 0, max_d = 0, h = node->h;
366
43.6k
  while (node != end)
367
36.7k
  {
368
36.7k
    if (node->type == FLOW_IMAGE)
369
0
    {
370
0
      if (node->h > max_a)
371
0
        max_a = node->h;
372
0
    }
373
36.7k
    else if (node->type != FLOW_SBREAK && node->type != FLOW_BREAK && node->type != FLOW_ANCHOR)
374
29.4k
    {
375
      // Clamp ascender/descender to line-height size.
376
      // TODO: This is not entirely to spec, but close enough.
377
29.4k
      float s = fz_min(node->box->s.layout.em, node->h);
378
29.4k
      float a = s * 0.8f;
379
29.4k
      float d = s * 0.2f;
380
29.4k
      if (a > max_a) max_a = a;
381
29.4k
      if (d > max_d) max_d = d;
382
29.4k
    }
383
36.7k
    if (node->h > h)
384
0
      h = node->h;
385
36.7k
    node = node->next;
386
36.7k
  }
387
6.93k
  *baseline = max_a + (h - max_a - max_d) / 2;
388
6.93k
  return h;
389
6.93k
}
390
391
static void layout_line(fz_context *ctx, float indent, float page_w, float line_w, int align, fz_html_flow *start, fz_html_flow *end, fz_html_box *box, float baseline, float line_h)
392
6.93k
{
393
6.93k
  float x = box->s.layout.x + indent;
394
6.93k
  float y = box->s.layout.b;
395
6.93k
  float slop = page_w - line_w;
396
6.93k
  float justify = 0;
397
6.93k
  float va;
398
6.93k
  int n, i;
399
6.93k
  fz_html_flow *node;
400
6.93k
  fz_html_flow **reorder;
401
6.93k
  unsigned int min_level, max_level;
402
403
  /* Count the number of nodes on the line */
404
43.6k
  for(i = 0, n = 0, node = start; node != end; node = node->next)
405
36.7k
  {
406
36.7k
    n++;
407
36.7k
    if (node->type == FLOW_SPACE && node->expand && !node->breaks_line)
408
2.13k
      i++;
409
36.7k
  }
410
411
6.93k
  if (align == TA_JUSTIFY)
412
0
    justify = slop / (i ? i : 1);
413
6.93k
  else if (align == TA_RIGHT)
414
0
    x += slop;
415
6.93k
  else if (align == TA_CENTER)
416
0
    x += slop / 2;
417
418
  /* We need a block to hold the node pointers while we reorder */
419
6.93k
  reorder = fz_malloc_array(ctx, n, fz_html_flow*);
420
6.93k
  min_level = start->bidi_level;
421
6.93k
  max_level = start->bidi_level;
422
43.6k
  for(i = 0, node = start; node != end; i++, node = node->next)
423
36.7k
  {
424
36.7k
    reorder[i] = node;
425
36.7k
    if (node->bidi_level < min_level)
426
591
      min_level = node->bidi_level;
427
36.7k
    if (node->bidi_level > max_level)
428
2.17k
      max_level = node->bidi_level;
429
36.7k
  }
430
431
  /* Do we need to do any reordering? */
432
6.93k
  if (min_level != max_level || (min_level & 1))
433
2.30k
  {
434
    /* The lowest level we swap is always a rtl one */
435
2.30k
    min_level |= 1;
436
    /* Each time around the loop we swap runs of fragments that have
437
     * levels >= max_level (and decrement max_level). */
438
2.30k
    do
439
3.77k
    {
440
3.77k
      int start_idx = 0;
441
3.77k
      int end_idx;
442
3.77k
      do
443
6.88k
      {
444
        /* Skip until we find a level that's >= max_level */
445
20.2k
        while (start_idx < n && reorder[start_idx]->bidi_level < max_level)
446
13.4k
          start_idx++;
447
        /* If start >= n-1 then no more runs. */
448
6.88k
        if (start_idx >= n-1)
449
2.63k
          break;
450
        /* Find the end of the match */
451
4.24k
        i = start_idx+1;
452
9.07k
        while (i < n && reorder[i]->bidi_level >= max_level)
453
4.82k
          i++;
454
        /* Reverse from start to i-1 */
455
4.24k
        end_idx = i-1;
456
7.34k
        while (start_idx < end_idx)
457
3.09k
        {
458
3.09k
          fz_html_flow *t = reorder[start_idx];
459
3.09k
          reorder[start_idx++] = reorder[end_idx];
460
3.09k
          reorder[end_idx--] = t;
461
3.09k
        }
462
4.24k
        start_idx = i+1;
463
4.24k
      }
464
4.24k
      while (start_idx < n);
465
3.77k
      max_level--;
466
3.77k
    }
467
3.77k
    while (max_level >= min_level);
468
2.30k
  }
469
470
43.6k
  for (i = 0; i < n; i++)
471
36.7k
  {
472
36.7k
    float w;
473
474
36.7k
    node = reorder[i];
475
36.7k
    w = node->w;
476
477
36.7k
    if (node->type == FLOW_SPACE && node->breaks_line)
478
2.92k
      w = 0;
479
33.8k
    else if (node->type == FLOW_SPACE && !node->breaks_line)
480
2.13k
      w += node->expand ? justify : 0;
481
31.6k
    else if (node->type == FLOW_SHYPHEN && !node->breaks_line)
482
2
      w = 0;
483
31.6k
    else if (node->type == FLOW_SHYPHEN && node->breaks_line)
484
4
      w = node->w;
485
486
36.7k
    node->x = x;
487
36.7k
    x += w;
488
489
36.7k
    if (node->type == FLOW_IMAGE)
490
0
    {
491
0
      switch (node->box->style->vertical_align)
492
0
      {
493
0
      default:
494
0
      case VA_BASELINE:
495
0
        node->y = y + baseline - node->h;
496
0
        break;
497
0
      case VA_SUB:
498
0
        node->y = y + baseline - node->h + node->box->s.layout.em / 5;
499
0
        break;
500
0
      case VA_SUPER:
501
0
        node->y = y + baseline - node->h - node->box->s.layout.em / 3;
502
0
        break;
503
0
      case VA_MIDDLE:
504
        // middle of the element with the baseline plus half the x-height of the parent
505
0
        node->y = y + baseline - node->box->s.layout.em * 0.25f - node->h * 0.5f;
506
0
        break;
507
0
      case VA_TOP:
508
0
        node->y = y;
509
0
        break;
510
0
      case VA_TEXT_TOP:
511
0
        node->y = y + baseline - node->box->s.layout.em * 0.8f;
512
0
        break;
513
0
      case VA_BOTTOM:
514
0
        node->y = y + line_h - node->h;
515
0
        break;
516
0
      case VA_TEXT_BOTTOM:
517
0
        node->y = y + baseline + node->box->s.layout.em * 0.2f - node->h;
518
0
        break;
519
0
      case VA_PERCENT:
520
0
      case VA_LENGTH:
521
0
        node->y = y + baseline - node->h -
522
0
          fz_from_css_number(
523
0
            node->box->style->vertical_align_number,
524
0
            node->box->s.layout.em,
525
0
            fz_from_css_number_scale(node->box->style->line_height, node->box->s.layout.em),
526
0
            0
527
0
          );
528
0
        break;
529
0
      }
530
0
    }
531
36.7k
    else
532
36.7k
    {
533
36.7k
      switch (node->box->style->vertical_align)
534
36.7k
      {
535
0
      default:
536
36.7k
      case VA_BASELINE:
537
36.7k
      case VA_SUB:
538
36.7k
      case VA_SUPER:
539
36.7k
      case VA_MIDDLE:
540
36.7k
        va = node->box->s.layout.baseline;
541
36.7k
        break;
542
543
0
      case VA_TOP:
544
0
      case VA_TEXT_TOP:
545
0
        va = -baseline + node->box->s.layout.em * 0.8f;
546
0
        break;
547
0
      case VA_BOTTOM:
548
0
      case VA_TEXT_BOTTOM:
549
0
        va = -baseline + line_h - node->box->s.layout.em * 0.2f;
550
0
        break;
551
0
      case VA_PERCENT:
552
0
      case VA_LENGTH:
553
0
        va = -fz_from_css_number(
554
0
          node->box->style->vertical_align_number,
555
0
          node->box->s.layout.em,
556
0
          fz_from_css_number_scale(node->box->style->line_height, node->box->s.layout.em),
557
0
          0
558
0
        );
559
0
        break;
560
36.7k
      }
561
36.7k
      node->y = y + baseline + va;
562
36.7k
      node->h = node->box->s.layout.em;
563
36.7k
    }
564
36.7k
  }
565
566
6.93k
  fz_free(ctx, reorder);
567
568
6.93k
  if (box->s.layout.x + box->s.layout.w < x)
569
133
    box->s.layout.w = x - box->s.layout.x;
570
6.93k
}
571
572
typedef struct
573
{
574
  fz_pool *pool;
575
576
  float page[4];
577
  /* The ancestor trbl is the trbl for the last positioned
578
   * node we have walked though. Boxes with "absolute" positioning
579
   * will be positioned relative to this. Initially this will be the
580
   * same as page. */
581
  float ancestor[4];
582
  /* bounds is the trbl that is the current bounds we're filling
583
   * into. This will initially be page, but can be changed by
584
   * positions/width/heights etc. Our next fill should be at
585
   * bounds[L],[T]. */
586
  float bounds[4];
587
  /* used should be updated on exit from any layout stage to
588
   * be the area used by this block. */
589
  float used[4];
590
591
  hb_buffer_t *hb_buf;
592
  fz_html_restarter *restart;
593
} layout_data;
594
595
/* "are we in a restarting context, and still skipping?" */
596
423
#define WE_ARE_SKIPPING(restart) ((restart) && (restart)->start != NULL)
597
598
/* "are we in a restarting context without having stopped for a restart yet?" */
599
92
#define SHOULD_STOP_FOR_RESTART(restart) ((restart) && (restart)->end == NULL)
600
601
/* "are we in a restarting context, and stopping already?" */
602
139
#define STOPPING_FOR_RESTART(restart) ((restart) && (restart)->end != NULL)
603
604
98
#define TRBLCPY(DST,SRC) \
605
98
do { DST[T] = SRC[T] ; DST[R] = SRC[R]; DST[B] = SRC[B]; DST[L] = SRC[L]; } while (0)
606
607
static int flush_line(fz_context *ctx, fz_html_box *box, layout_data *ld, float line_w, int align, float indent, fz_html_flow *a, fz_html_flow *b, fz_html_restarter *restart)
608
6.93k
{
609
6.93k
  float avail, line_h, baseline;
610
6.93k
  float page_w = ld->bounds[R] - ld->bounds[L];
611
6.93k
  float page_h = ld->page[B] - ld->page[T];
612
6.93k
  float page_top = ld->page[T];
613
6.93k
  line_h = measure_line(a, b, &baseline);
614
6.93k
  if (page_h > 0)
615
6.93k
  {
616
6.93k
    avail = page_h - fmodf(box->s.layout.b - page_top, page_h);
617
    /* If the line is larger than the available space skip to the start
618
     * of the next page. */
619
6.93k
    if (line_h > avail)
620
173
    {
621
173
      if (restart)
622
0
      {
623
0
        assert(restart->start == NULL);
624
625
0
        if (restart->potential)
626
0
          restart->end = restart->potential;
627
0
        else
628
0
        {
629
0
          restart->end = box;
630
0
          restart->end_flow = a;
631
0
        }
632
0
        restart->reason = FZ_HTML_RESTART_REASON_LINE_HEIGHT;
633
0
        return 1;
634
0
      }
635
173
      box->s.layout.b += avail;
636
173
    }
637
6.93k
  }
638
6.93k
  layout_line(ctx, indent, page_w, line_w, align, a, b, box, baseline, line_h);
639
6.93k
  box->s.layout.b += line_h;
640
6.93k
  if (box->s.layout.b > ld->used[B])
641
6.93k
    ld->used[B] = box->s.layout.b;
642
6.93k
  if (restart)
643
0
    restart->potential = NULL;
644
645
6.93k
  return 0;
646
6.93k
}
647
648
static void split_flow_at_byte_offset(fz_context *ctx, fz_pool *pool, fz_html_flow *flow, size_t offset)
649
0
{
650
0
  fz_html_flow *new_flow;
651
0
  char *text;
652
0
  size_t len;
653
654
0
  assert(flow->type == FLOW_WORD);
655
656
0
  assert(offset != 0);
657
0
  text = flow->content.text + offset;
658
0
  len = strlen(text);
659
0
  new_flow = fz_pool_alloc(ctx, pool, offsetof(fz_html_flow, content) + len+1);
660
0
  memcpy(new_flow, flow, offsetof(fz_html_flow, content));
661
0
  new_flow->next = flow->next;
662
0
  flow->next = new_flow;
663
0
  strcpy(new_flow->content.text, text);
664
0
  *text = 0;
665
0
}
666
667
/* node becomes last cluster, node->next becomes the rest */
668
static void split_flow_at_byte_offset_reverse(fz_context *ctx, fz_pool *pool, fz_html_flow *flow, size_t offset)
669
0
{
670
0
  fz_html_flow *new_flow;
671
0
  char *text;
672
0
  size_t len;
673
674
0
  assert(flow->type == FLOW_WORD);
675
676
0
  assert(offset != 0);
677
0
  text = flow->content.text + offset;
678
0
  len = strlen(text);
679
0
  new_flow = fz_pool_alloc(ctx, pool, offsetof(fz_html_flow, content) + offset+1);
680
0
  memcpy(new_flow, flow, offsetof(fz_html_flow, content));
681
0
  new_flow->next = flow->next;
682
0
  flow->next = new_flow;
683
0
  memcpy(new_flow->content.text, flow->content.text, offset);
684
0
  new_flow->content.text[offset] = 0;
685
0
  memmove(flow->content.text, text, len);
686
0
  flow->content.text[len] = 0;
687
0
}
688
689
static void break_word_for_overflow_wrap(fz_context *ctx, fz_html_flow *node, layout_data *ld, float max_w)
690
0
{
691
0
  hb_buffer_t *hb_buf = ld->hb_buf;
692
0
  const char *text = node->content.text;
693
0
  string_walker walker;
694
0
  float w = 0;
695
0
  unsigned int at = (unsigned int)-1;
696
0
  float em = node->box->s.layout.em;
697
698
0
  assert(node->type == FLOW_WORD);
699
0
  assert(node->atomic == 0);
700
701
  /* The entire flow doesn't fit on a line, so we need to break it in the middle.
702
   * We need to be careful not to break it in the middle of a cluster, as this
703
   * would really mess shaping up.
704
   *
705
   * For left 2 right text, this means split the first cluster into its own 'atomic'
706
   * node, and shorten the remainder.
707
   *
708
   * For right 2 left text, we split the last cluster into its own 'atomic' node
709
   * and shorten the remainder. This is fine, cos although it appears to change the
710
   * logical ordering for the text, we never extract from HTML, and the correct
711
   * appearance is preserved.
712
   *
713
   * Desperately breaking in the middle of a word like this should should
714
   * rarely (if ever) come up.
715
   *
716
   * TODO: Split at all the clusters in the word at once.
717
   */
718
719
  /* Walk string and split at the first cluster. */
720
0
  if ((node->bidi_level & 1) == 0)
721
0
  {
722
    /* Left 2 Right */
723
0
    init_string_walker(ctx, &walker, hb_buf, 0 /* L2R */, node->box->style->font, node->script, node->markup_lang, node->box->style->small_caps, text, 1);
724
0
    while (walk_string(&walker))
725
0
    {
726
0
      unsigned int i;
727
#ifdef DEBUG_DESPERATE_SPLITTING
728
      for (i = 0; i < walker.glyph_count; ++i)
729
      {
730
        uint32_t can_break_here = (hb_glyph_info_get_glyph_flags(&walker.glyph_info[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0;
731
        printf("%s(%x, %d)",
732
          can_break_here ? "|" : " ",
733
          walker.glyph_info[i].codepoint, walker.glyph_info[i].cluster);
734
      }
735
      printf("\n");
736
#endif
737
0
      for (i = 0; i < walker.glyph_count; ++i)
738
0
      {
739
0
        uint32_t can_break_here = (hb_glyph_info_get_glyph_flags(&walker.glyph_info[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0;
740
741
0
        if (can_break_here)
742
0
        {
743
          /* If this fragment would take us beyond the end, then give up. */
744
0
          if (w > max_w)
745
0
            break;
746
747
0
          at = walker.start + walker.glyph_info[i].cluster - text;
748
0
        }
749
750
0
        w += walker.glyph_pos[i].x_advance * em / walker.scale;
751
752
        /* Make sure we have the whole cluster */
753
0
        while (i+1 < walker.glyph_count && walker.glyph_info[i].cluster == walker.glyph_info[i+1].cluster)
754
0
        {
755
0
          i++;
756
0
          w += walker.glyph_pos[i].x_advance * em / walker.scale;
757
0
        }
758
0
      }
759
0
    }
760
0
    if (at != (unsigned int)-1 && at != 0 && at != strlen(text))
761
0
    {
762
      /* node becomes first cluster, node->next becomes the rest */
763
0
      split_flow_at_byte_offset(ctx, ld->pool, node, at);
764
0
      node->next->overflow_wrap = 1;
765
0
      measure_string_w(ctx, node, ld->hb_buf);
766
0
      measure_string_w(ctx, node->next, ld->hb_buf);
767
0
      return;
768
0
    }
769
0
  }
770
0
  else
771
0
  {
772
    /* Right 2 Left */
773
0
    init_string_walker(ctx, &walker, hb_buf, 1 /* R2L */, node->box->style->font, node->script, node->markup_lang, node->box->style->small_caps, text, 1);
774
0
    while (walk_string(&walker))
775
0
    {
776
0
      unsigned int i;
777
#ifdef DEBUG_DESPERATE_SPLITTING
778
      for (i = 0; i < walker.glyph_count; ++i)
779
      {
780
        uint32_t can_break_here = (hb_glyph_info_get_glyph_flags(&walker.glyph_info[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0;
781
        printf("%s(%x, %d)",
782
          can_break_here ? "|" : " ",
783
          walker.glyph_info[i].codepoint, walker.glyph_info[i].cluster);
784
      }
785
      printf("\n");
786
#endif
787
      /* Find the first cluster we can break before. */
788
789
      /* We can always break at the start of a fragment returned by walk_string
790
       * (unless it's the very first one!) */
791
0
      if (w > max_w)
792
0
        break;
793
0
      if (walker.start != text)
794
0
        at = walker.start - text;
795
796
0
      for(i = 0; i < walker.glyph_count; i++)
797
0
      {
798
0
        uint32_t can_break_here = (hb_glyph_info_get_glyph_flags(&walker.glyph_info[i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) == 0;
799
800
0
        w += walker.glyph_pos[i].x_advance * em / walker.scale;
801
802
0
        if (can_break_here)
803
0
        {
804
          /* If this fragment would take us beyond the end, then give up. */
805
0
          if (w > max_w)
806
0
            break;
807
808
0
          if (i == walker.glyph_count)
809
0
            at = walker.start - text;
810
0
          else
811
0
            at = walker.start + walker.glyph_info[i].cluster - text;
812
0
        }
813
0
      }
814
0
    }
815
0
    if (at != (unsigned int)-1 && at != 0 && at != strlen(text))
816
0
    {
817
      /* Split at the last point found */
818
      /* node becomes last cluster, node->next becomes the rest */
819
0
      split_flow_at_byte_offset_reverse(ctx, ld->pool, node, at);
820
0
      node->next->overflow_wrap = 1;
821
0
      measure_string_w(ctx, node, ld->hb_buf);
822
0
      measure_string_w(ctx, node->next, ld->hb_buf);
823
0
      return;
824
0
    }
825
0
  }
826
827
  /* Unless we've overflowed word is already only one cluster. Don't try breaking here again! */
828
0
  if (w <= max_w)
829
0
    node->atomic = 1;
830
0
}
831
832
/*
833
  Layout a BOX_FLOW.
834
835
  Flow box is in a BOX_BLOCK or BOX_TABLE_CELL context, and has no margin/padding/border.
836
837
  box: The BOX_FLOW to layout.
838
  top: The enclosing box.
839
*/
840
static void layout_flow(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
841
44
{
842
44
  fz_html_flow *node, *line, *candidate, *desperate;
843
44
  fz_html_flow *start_flow = NULL;
844
44
  float line_w, candidate_w, desperate_w, indent, bounds_w;
845
44
  int justify_align;
846
44
  int align;
847
44
  fz_html_restarter *restart = ld->restart;
848
849
44
  float em = box->s.layout.em;
850
44
  indent = box->is_first_flow ? fz_from_css_number(top->style->text_indent, em, ld->bounds[R] - ld->bounds[L], 0) : 0;
851
44
  align = top->style->text_align;
852
44
  justify_align = TA_LEFT;
853
854
44
  if (box->markup_dir == FZ_BIDI_RTL)
855
0
  {
856
0
    justify_align = TA_RIGHT;
857
0
    if (align == TA_START)
858
0
      align = TA_RIGHT;
859
0
    else if (align == TA_END)
860
0
      align = TA_LEFT;
861
0
  }
862
44
  else
863
44
  {
864
44
    if (align == TA_START)
865
44
      align = TA_LEFT;
866
0
    else if (align == TA_END)
867
0
      align = TA_RIGHT;
868
44
  }
869
870
  /* Position the box, initially zero height. */
871
44
  box->s.layout.x = ld->bounds[L];
872
44
  box->s.layout.y = ld->bounds[T];
873
44
  box->s.layout.w = ld->bounds[R] - ld->bounds[L];
874
44
  box->s.layout.b = ld->bounds[T];
875
876
44
  if (restart && restart->start)
877
0
  {
878
    /* If we are still skipping, and don't match, nothing to do. */
879
0
    if (restart->start != box)
880
0
      return;
881
    /* We match! Remember where we should start. */
882
0
    restart->start = NULL;
883
884
#ifdef DEBUG_LAYOUT_RESTARTING
885
    /* Output us some crufty debug */
886
    if (restart->start_flow == NULL)
887
      printf("<restart>");
888
    for (node = box->u.flow.head; node; node = node->next)
889
    {
890
      if (restart->start_flow == node)
891
        printf("<restart>");
892
      switch (node->type)
893
      {
894
      case FLOW_WORD:
895
        printf("[%s]", node->content.text);
896
        break;
897
      case FLOW_BREAK:
898
      case FLOW_SBREAK:
899
        printf("\n");
900
        break;
901
      case FLOW_SPACE:
902
        printf(" ");
903
        break;
904
      case FLOW_SHYPHEN:
905
        printf("-");
906
        break;
907
      default:
908
        printf("?");
909
        break;
910
      }
911
    }
912
    printf("\n");
913
#endif
914
0
  }
915
916
  /* If we have nothing to flow, nothing to do. */
917
44
  if (!box->u.flow.head)
918
2
    return;
919
920
  /* Measure the size of all the words and images in the flow. */
921
42
  node = box->u.flow.head;
922
923
  /* First, if we are restarting, skip over ones we've done already. */
924
42
  if (restart && restart->start_flow)
925
0
  {
926
0
    while(node && node != restart->start_flow)
927
0
    {
928
0
      indent = 0;
929
0
      node = node->next;
930
0
    }
931
0
    start_flow = node;
932
0
    restart->start_flow = NULL;
933
0
  }
934
935
  /* Now measure the size of the remaining nodes. */
936
36.7k
  for (; node; node = node->next)
937
36.7k
  {
938
36.7k
    node->breaks_line = 0; /* reset line breaks from previous layout */
939
940
36.7k
    if (node->type == FLOW_IMAGE)
941
0
    {
942
0
      float xs = 1, ys = 1, s;
943
0
      float aspect = 1;
944
0
      float max_w = ld->bounds[R] - ld->bounds[L]; // current block content box width
945
0
      float max_h = ld->page[B] - ld->page[T]; // page height (excluding page margins)
946
947
      /* NOTE: We ignore the image DPI here, since most images in EPUB files have bogus values. */
948
0
      node->w = node->content.image->w * 72.0f / 96.0f;
949
0
      node->h = node->content.image->h * 72.0f / 96.0f;
950
0
      aspect = node->h ? node->w / node->h : 0;
951
952
0
      if (node->box->style->width.unit != N_AUTO)
953
0
        node->w = fz_from_css_number(node->box->style->width, top->s.layout.em, max_w, node->w);
954
0
      if (node->box->style->height.unit != N_AUTO)
955
0
        node->h = fz_from_css_number(node->box->style->height, top->s.layout.em, max_h, node->h);
956
0
      if (node->box->style->width.unit == N_AUTO && node->box->style->height.unit != N_AUTO)
957
0
        node->w = node->h * aspect;
958
0
      if (node->box->style->width.unit != N_AUTO && node->box->style->height.unit == N_AUTO)
959
0
        node->h = (aspect == 0) ? 0 : (node->w / aspect);
960
961
      /* Shrink image to fit on one page if needed */
962
0
      if (max_w > 0 && node->w > max_w)
963
0
        xs = max_w / node->w;
964
0
      if (max_h > 0 && node->h > max_h)
965
0
        ys = max_h / node->h;
966
0
      s = fz_min(xs, ys);
967
0
      node->w = node->w * s;
968
0
      node->h = node->h * s;
969
970
0
    }
971
36.7k
    else if (node->type == FLOW_ANCHOR)
972
1
    {
973
1
      node->h = 0;
974
1
    }
975
36.7k
    else
976
36.7k
    {
977
      /* Note: already measured width in layout_update_widths */
978
36.7k
      measure_string_h(ctx, node);
979
36.7k
    }
980
36.7k
  }
981
982
42
  node = box->u.flow.head;
983
42
  if (start_flow)
984
0
  {
985
0
    line_w = start_flow == node ? indent : 0;
986
0
    node = start_flow;
987
0
  }
988
42
  else
989
42
  {
990
42
    line_w = indent;
991
42
  }
992
42
  line = node;
993
994
42
  candidate = NULL;
995
42
  candidate_w = 0;
996
42
  desperate = NULL;
997
42
  desperate_w = 0;
998
42
  bounds_w = ld->bounds[R] - ld->bounds[L];
999
1000
  /* Now collate the measured nodes into a line. */
1001
44.9k
  while (node)
1002
44.9k
  {
1003
1004
44.9k
    switch (node->type)
1005
44.9k
    {
1006
1
    default:
1007
      // always same width
1008
1
      line_w += node->w;
1009
1
      break;
1010
1011
32.5k
    case FLOW_WORD:
1012
      /* Allow desperate breaking in middle of word if overflow-wrap: break-word. */
1013
32.5k
      if (line_w + node->w > bounds_w && !candidate)
1014
3.28k
      {
1015
3.28k
        if (!node->atomic && node->box->style->overflow_wrap == OVERFLOW_WRAP_BREAK_WORD)
1016
0
        {
1017
0
          break_word_for_overflow_wrap(ctx, node, ld, bounds_w - line_w);
1018
0
        }
1019
3.28k
      }
1020
      /* Remember overflow-wrap word fragments, unless at the beginning of a line. */
1021
32.5k
      if (node->overflow_wrap && node != line)
1022
0
      {
1023
0
        desperate = node;
1024
0
        desperate_w = line_w;
1025
0
      }
1026
32.5k
      line_w += node->w;
1027
32.5k
      break;
1028
1029
3
    case FLOW_BREAK:
1030
7.29k
    case FLOW_SBREAK:
1031
      // always zero width
1032
7.29k
      candidate = node;
1033
7.29k
      candidate_w = line_w;
1034
7.29k
      break;
1035
1036
5.05k
    case FLOW_SPACE:
1037
      // zero width if broken, default width if unbroken
1038
5.05k
      candidate = node;
1039
5.05k
      candidate_w = line_w;
1040
5.05k
      line_w += node->w;
1041
5.05k
      break;
1042
1043
6
    case FLOW_SHYPHEN:
1044
      // default width if broken, zero width if unbroken
1045
6
      candidate = node;
1046
6
      candidate_w = line_w + node->w;
1047
6
      break;
1048
44.9k
    }
1049
1050
    /* If we see a hard break...
1051
     * or the line doesn't fit, and we have a candidate breakpoint...
1052
     * then we can flush the line so far. */
1053
44.9k
    if (node->type == FLOW_BREAK || (line_w > bounds_w && (candidate || desperate)))
1054
6.89k
    {
1055
6.89k
      int line_align = align;
1056
6.89k
      fz_html_flow *break_at;
1057
6.89k
      float break_w;
1058
1059
6.89k
      if (candidate)
1060
6.89k
      {
1061
6.89k
        if (candidate->type == FLOW_BREAK)
1062
3
          line_align = (align == TA_JUSTIFY) ? justify_align : align;
1063
6.89k
        candidate->breaks_line = 1;
1064
6.89k
        break_at = candidate->next;
1065
6.89k
        break_w = candidate_w;
1066
6.89k
      }
1067
0
      else
1068
0
      {
1069
0
        break_at = desperate;
1070
0
        break_w = desperate_w;
1071
0
      }
1072
1073
      /* text-indent on right side is already baked into break_w */
1074
6.89k
      if (box->markup_dir == FZ_BIDI_RTL)
1075
0
        indent = 0;
1076
1077
6.89k
      if (flush_line(ctx, box, ld, break_w, line_align, indent, line, break_at, restart))
1078
0
        return;
1079
1080
6.89k
      line = break_at;
1081
6.89k
      node = break_at;
1082
6.89k
      candidate = NULL;
1083
6.89k
      candidate_w = 0;
1084
6.89k
      desperate = NULL;
1085
6.89k
      desperate_w = 0;
1086
6.89k
      indent = 0;
1087
6.89k
      line_w = 0;
1088
6.89k
    }
1089
38.0k
    else if (restart && (restart->flags & FZ_HTML_RESTARTER_FLAGS_NO_OVERFLOW) && line_w > box->s.layout.w)
1090
0
    {
1091
      /* We are in 'no-overflow' mode, and the line doesn't fit.
1092
       * This means our first box is too wide to ever fit in a box of this width. */
1093
0
      assert(restart->start == NULL);
1094
1095
0
      restart->end = box;
1096
0
      restart->end_flow = start_flow;
1097
0
      restart->reason = FZ_HTML_RESTART_REASON_LINE_WIDTH;
1098
0
      return;
1099
0
    }
1100
38.0k
    else
1101
38.0k
    {
1102
38.0k
      node = node->next;
1103
38.0k
    }
1104
44.9k
  }
1105
1106
42
  if (line)
1107
42
  {
1108
42
    int line_align = (align == TA_JUSTIFY) ? justify_align : align;
1109
42
    flush_line(ctx, box, ld, line_w, line_align, indent, line, NULL, restart);
1110
42
  }
1111
1112
42
  ld->used[T] = box->s.layout.y;
1113
42
  ld->used[R] = box->s.layout.x + box->s.layout.w;
1114
42
  ld->used[B] = box->s.layout.b;
1115
42
  ld->used[L] = box->s.layout.x;
1116
42
}
1117
1118
static float advance_for_spacing(fz_context *ctx, layout_data *ld, float start_b, float spacing, int *eop)
1119
96
{
1120
96
  float page_h = ld->page[B] - ld->page[T];
1121
96
  float page_top = ld->page[T];
1122
96
  float avail = page_h - fmodf(start_b - page_top, page_h);
1123
96
  if (spacing > avail)
1124
1
  {
1125
1
    *eop = 1;
1126
1
    spacing = avail;
1127
1
  }
1128
96
  return start_b + spacing;
1129
96
}
1130
1131
static int layout_block_page_break(fz_context *ctx, layout_data *ld, float *yp, int page_break)
1132
96
{
1133
96
  fz_html_restarter *restart = ld->restart;
1134
96
  float page_h = ld->page[B] - ld->page[T];
1135
96
  float page_top = ld->page[T];
1136
96
  float avail;
1137
96
  int side; /* 0 = right, 1 = left */
1138
1139
  /* If we are skipping, nothing to do! */
1140
96
  if (WE_ARE_SKIPPING(restart))
1141
0
    return 0;
1142
1143
  /* If the page height is screwy (or the rectangle height), then bale. */
1144
96
  if (page_h <= 0)
1145
0
    return 0;
1146
1147
  /* If we aren't doing some kind of page break, bale. */
1148
96
  if (page_break != PB_ALWAYS && page_break != PB_LEFT && page_break != PB_RIGHT)
1149
96
    return 0;
1150
1151
  /* How much space left on the page? */
1152
0
  avail = page_h - fmodf(*yp - page_top, page_h);
1153
1154
  /* Which side of the page are we on? In restarting (i.e. story) contexts,
1155
   * this is passed in explicitly. In traditional contexts, we calculate it
1156
   * according to which page we're on. */
1157
0
  if (restart != NULL)
1158
0
    side = restart->left_page;
1159
0
  else
1160
0
    side = 1 & (int)((*yp - page_top) / page_h);
1161
1162
  /* If we haven't used ANY of the page, then we might not need to do anything. */
1163
0
  if (avail == page_h)
1164
0
  {
1165
0
    if (page_break == PB_ALWAYS)
1166
0
      return 0;
1167
0
    if (page_break == PB_LEFT && side == 1)
1168
0
      return 0;
1169
0
    if (page_break == PB_RIGHT && side == 0)
1170
0
      return 0;
1171
0
  }
1172
1173
  /* Move us to the next page. */
1174
0
  if (avail > 0)
1175
0
    *yp += avail, side ^= 1;
1176
1177
  /* Do we need to move further? */
1178
0
  if (page_break == PB_LEFT)
1179
0
  {
1180
    /* If we're restarting, make sure we only restart on a left page. */
1181
0
    if (SHOULD_STOP_FOR_RESTART(restart))
1182
0
      restart->end_flags = FZ_HTML_RESTARTER_START_END_FLAGS_SPECIFIC_SIDE | FZ_HTML_RESTARTER_START_END_FLAGS_LEFT_SIDE;
1183
0
    if (side == 0) /* right pages, side == 0 */
1184
0
      *yp += page_h;
1185
0
  }
1186
0
  if (page_break == PB_RIGHT)
1187
0
  {
1188
    /* If we're restarting, make sure we only restart on a right page. */
1189
0
    if (SHOULD_STOP_FOR_RESTART(restart))
1190
0
      restart->end_flags = FZ_HTML_RESTARTER_START_END_FLAGS_SPECIFIC_SIDE;
1191
0
    if (side == 1) /* left pages, side == 1 */
1192
0
      *yp += page_h;
1193
0
  }
1194
1195
0
  return 1;
1196
0
}
1197
1198
/* === POSITION LOGIC === */
1199
1200
/* We support the following 4 types of "position:" style.
1201
 * POS_STATIC: Nothing to do.
1202
 * POS_RELATIVE: Offset everything inside by a constant amount.
1203
 * POS_FIXED: Rewrite the block according to the top level block.
1204
 * POS_ABSOLUTE: Positioned relative to the nearest positioned ancestor.
1205
 */
1206
static void
1207
shift_box_contents(fz_html_box *box, float x, float y)
1208
0
{
1209
0
  fz_html_box *child;
1210
1211
0
  box->s.layout.x += x;
1212
0
  box->s.layout.y += y;
1213
0
  box->s.layout.b += y;
1214
0
  if (box->type == BOX_FLOW)
1215
0
  {
1216
0
    fz_html_flow *flow;
1217
0
    for (flow = box->u.flow.head; flow != NULL; flow = flow->next)
1218
0
      flow->x += x, flow->y += y;
1219
0
  }
1220
0
  for (child = box->down; child; child = child->next)
1221
0
  {
1222
0
    if (child->style->position == POS_FIXED || child->style->position == POS_ABSOLUTE)
1223
0
      return;
1224
0
    shift_box_contents(child, x, y);
1225
0
  }
1226
0
}
1227
1228
typedef struct
1229
{
1230
  float ancestor[4];
1231
  float bounds[4];
1232
  float inset[4];
1233
1234
  int has_width;
1235
  float width;
1236
  int has_height;
1237
  float height;
1238
} position_data;
1239
1240
/*  pre_position: called when we are about to layout a block.
1241
 *  This expects that all the ld trbls are valid. It stores as
1242
 *  many as need to be stored in position_data, recalculates
1243
 *  bounds and fill to allow for margins/borders/padding,
1244
 *  and sets the initial box position appropriately.
1245
 *
1246
 *  For positioned blocks, it calculates inset, and rewrites
1247
 *  ld->bounds and ld->fill as appropriate.
1248
 *
1249
 *  If this returns 1, then we are moving onto a new page.
1250
 */
1251
static int
1252
pre_position(fz_context *ctx, position_data *pd, layout_data *ld, fz_html_box *box, int ignore_width)
1253
48
{
1254
48
  float em = box->s.layout.em;
1255
48
  const float *margin = box->u.block.margin;
1256
48
  const float *border = box->u.block.border;
1257
48
  const float *padding = box->u.block.padding;
1258
48
  float auto_w, auto_h;
1259
48
  fz_html_restarter *restart = ld->restart;
1260
48
  int eop = 0;
1261
1262
  /* On entry, ld->bounds is the area we expect to fill this block into.
1263
   * ld->fill is the area of this that has been used to date.
1264
   */
1265
1266
  /* Store the bounds. */
1267
48
  TRBLCPY(pd->bounds, ld->bounds);
1268
1269
  /* We want to adjust bounds before we lay the children out so that
1270
   * any margin/borders/padding are all outside them. */
1271
1272
48
  if (WE_ARE_SKIPPING(restart))
1273
0
  {
1274
    /* We're still skipping, so any child should inherit 0 vertical margin from us. */
1275
0
  }
1276
48
  else
1277
48
  {
1278
    /* We're not skipping, so add in the spacings to the top edge of our box. */
1279
48
    ld->bounds[T] = advance_for_spacing(ctx, ld, ld->bounds[T], margin[T] + border[T] + padding[T], &eop);
1280
48
    if (eop && SHOULD_STOP_FOR_RESTART(restart))
1281
0
      return 1;
1282
48
  }
1283
1284
  /* Now calculate the bounds for any sub blocks. */
1285
48
  pd->has_width = fz_css_number_defined_not_auto(box->style->width);
1286
48
  pd->width = fz_from_css_number(box->style->width, em, ld->bounds[R] - ld->bounds[L], 0);
1287
48
  pd->has_height = fz_css_number_defined_not_auto(box->style->height);
1288
48
  pd->height = fz_from_css_number(box->style->height, em, ld->bounds[B] - ld->bounds[T], 0);
1289
1290
  /* Adjust horizontally for Width and spacings */
1291
48
  auto_w = ld->bounds[R] - ld->bounds[L] - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
1292
48
  ld->bounds[L] = ld->bounds[L] + margin[L] + border[L] + padding[L];
1293
48
  if (ignore_width)
1294
0
    ld->bounds[R] = ld->bounds[L] + auto_w;
1295
48
  else
1296
48
    ld->bounds[R] = ld->bounds[L] + fz_from_css_number(box->style->width, em, auto_w, auto_w);
1297
1298
  /* Adjust vertically for Height and spacings (noting that we have already done the top ones above!) */
1299
48
  auto_h = ld->bounds[B] - ld->bounds[T] - (margin[B] + border[B] + padding[B]);
1300
48
  ld->bounds[B] = ld->bounds[T] + fz_from_css_number(box->style->height, em, auto_h, auto_h);
1301
1302
  /* So bounds and fixed are calculated as they should be for positioned: static */
1303
48
  ld->used[T] = ld->bounds[T];
1304
48
  ld->used[R] = ld->bounds[L];
1305
48
  ld->used[B] = ld->bounds[T];
1306
48
  ld->used[L] = ld->bounds[L];
1307
1308
  /* So we know the position of the box in the positioned: static case. */
1309
48
  box->s.layout.y = ld->bounds[T];
1310
48
  box->s.layout.x = ld->bounds[L];
1311
48
  box->s.layout.b = ld->bounds[T];
1312
48
  box->s.layout.w = ld->bounds[R]-ld->bounds[L];
1313
1314
48
  if (!WE_ARE_SKIPPING(restart))
1315
48
  {
1316
48
    if (box->style->position == POS_STATIC || box->style->position == POS_RELATIVE)
1317
48
    {
1318
48
      if (pd->has_width && box->s.layout.w < pd->width)
1319
0
        box->s.layout.w = pd->width;
1320
48
      if (pd->has_height && box->s.layout.b - box->s.layout.y < pd->height)
1321
0
        box->s.layout.b = pd->height + box->s.layout.y;
1322
48
    }
1323
48
  }
1324
1325
  /* So in the common case, we're done! */
1326
48
  if (box->style->position == POS_STATIC)
1327
48
    return eop;
1328
1329
  /* For non-static positionings, this becomes our ancestor */
1330
0
  TRBLCPY(pd->ancestor, ld->ancestor);
1331
0
  TRBLCPY(ld->ancestor, ld->bounds);
1332
1333
  /* Calculate insets. */
1334
0
  pd->inset[T] = fz_from_css_number(box->style->inset[T], em, em, 0);
1335
0
  pd->inset[R] = fz_from_css_number(box->style->inset[R], em, em, 0);
1336
0
  pd->inset[B] = fz_from_css_number(box->style->inset[B], em, em, 0);
1337
0
  pd->inset[L] = fz_from_css_number(box->style->inset[L], em, em, 0);
1338
1339
0
  switch (box->style->position)
1340
0
  {
1341
0
  case POS_RELATIVE:
1342
    /* Nothing to do here. We'll adjust it later. */
1343
0
    break;
1344
0
  case POS_FIXED:
1345
0
  {
1346
0
    float page[4];
1347
1348
0
    page[T] = ld->page[T] + margin[T] + border[T] + padding[T];
1349
0
    page[R] = ld->page[R] - margin[R] - border[R] - padding[R];
1350
0
    page[B] = ld->page[B] - margin[B] - border[B] - padding[B];
1351
0
    page[L] = ld->page[L] + margin[L] + border[L] + padding[L];
1352
1353
0
    if (fz_css_number_defined(box->style->inset[L]))
1354
0
    {
1355
0
      ld->bounds[L] = page[L] + pd->inset[L];
1356
0
      if (pd->has_width)
1357
0
      {
1358
        /* Left and Width determine the box. */
1359
0
        ld->bounds[R] = ld->bounds[L] + pd->width;
1360
0
      }
1361
0
      else
1362
0
      {
1363
        /* Left and bounds right determine the box. */
1364
0
        ld->bounds[R] = page[R] - pd->inset[L];
1365
0
      }
1366
0
    }
1367
0
    else
1368
0
    {
1369
0
      if (pd->has_width)
1370
0
      {
1371
        /* Right and Width determine the box. */
1372
0
        ld->bounds[R] = page[R] - pd->inset[R];
1373
0
        ld->bounds[L] = ld->bounds[R] - pd->width;
1374
0
      }
1375
0
      else
1376
0
      {
1377
        /* Right and page define the box. */
1378
0
        ld->bounds[L] = page[L];
1379
0
        ld->bounds[R] = page[R] - pd->inset[R];
1380
0
      }
1381
0
    }
1382
0
    if (fz_css_number_defined(box->style->inset[T]))
1383
0
    {
1384
0
      ld->bounds[T] = page[T] + pd->inset[T];
1385
0
      if (pd->has_height)
1386
0
      {
1387
        /* Top and Height determine the box. */
1388
0
        ld->bounds[B] = ld->bounds[T] + pd->height;
1389
0
      }
1390
0
      else
1391
0
      {
1392
        /* Top and Bottom determine the box. */
1393
0
        ld->bounds[B] = page[B] - pd->inset[T];
1394
0
      }
1395
0
    }
1396
0
    else
1397
0
    {
1398
0
      if (pd->has_height)
1399
0
      {
1400
        /* Bottom and Height determine the box. */
1401
0
        ld->bounds[B] = page[B] - pd->inset[B];
1402
0
        ld->bounds[T] = ld->bounds[B] - pd->height;
1403
0
      }
1404
0
      else
1405
0
      {
1406
        /* Bottom and page define the box. */
1407
0
        ld->bounds[T] = page[T];
1408
0
        ld->bounds[B] = page[B] - pd->inset[B];
1409
0
      }
1410
0
    }
1411
0
    break;
1412
0
  }
1413
0
  case POS_ABSOLUTE:
1414
0
  {
1415
0
    if (fz_css_number_defined(box->style->inset[L]))
1416
0
    {
1417
0
      ld->bounds[L] = pd->ancestor[L] + pd->inset[L];
1418
0
      if (pd->has_width)
1419
0
      {
1420
        /* Left and Width determine the box. */
1421
0
        ld->bounds[R] = ld->bounds[L] + pd->width;
1422
0
      }
1423
0
      else
1424
0
      {
1425
        /* Left and right determine the box. */
1426
0
        ld->bounds[R] = pd->ancestor[R] - pd->inset[L];
1427
0
      }
1428
0
    }
1429
0
    else
1430
0
    {
1431
0
      if (pd->has_width)
1432
0
      {
1433
        /* Right and Width determine the box. */
1434
0
        ld->bounds[R] = pd->ancestor[R] - pd->inset[R];
1435
0
        ld->bounds[L] = ld->bounds[R] - pd->width;
1436
0
      }
1437
0
      else
1438
0
      {
1439
        /* Right and ancestor define the box. */
1440
0
        ld->bounds[L] = pd->ancestor[L];
1441
0
        ld->bounds[R] = pd->ancestor[R] - pd->inset[R];
1442
0
      }
1443
0
    }
1444
0
    if (fz_css_number_defined(box->style->inset[T]))
1445
0
    {
1446
0
      ld->bounds[T] = pd->ancestor[T] + pd->inset[T];
1447
0
      if (pd->has_height)
1448
0
      {
1449
        /* Top and Height determine the box. */
1450
0
        ld->bounds[B] = ld->bounds[T] + pd->height;
1451
0
      }
1452
0
      else
1453
0
      {
1454
        /* Top and Bottom determine the box. */
1455
0
        ld->bounds[B] = pd->ancestor[B] - pd->inset[T];
1456
0
      }
1457
0
    }
1458
0
    else
1459
0
    {
1460
0
      if (pd->has_height)
1461
0
      {
1462
        /* Bottom and Height determine the box. */
1463
0
        ld->bounds[B] = pd->ancestor[B] - pd->inset[B];
1464
0
        ld->bounds[T] = ld->bounds[B] - pd->height;
1465
0
      }
1466
0
      else
1467
0
      {
1468
        /* Bottom and ancestor define the box. */
1469
0
        ld->bounds[T] = pd->ancestor[T];
1470
0
        ld->bounds[B] = pd->ancestor[B] - pd->inset[B];
1471
0
      }
1472
0
    }
1473
0
    break;
1474
0
  }
1475
0
  }
1476
1477
0
  box->s.layout.x = ld->bounds[L];
1478
0
  box->s.layout.w = 0;
1479
0
  box->s.layout.y = ld->bounds[T];
1480
0
  box->s.layout.b = ld->bounds[T];
1481
1482
0
  ld->used[B] = ld->bounds[T];
1483
0
  ld->used[L] = ld->bounds[L]; /* Deliberate! */
1484
1485
0
  return eop;
1486
0
}
1487
1488
/*  post_position: called after we have laid out a block.
1489
 *  Restore anything that was stashed.
1490
 *  Rewrites ld->used as appropriate.
1491
 *
1492
 *  Returns eop
1493
 */
1494
static int
1495
post_position(fz_context *ctx, position_data *pd, layout_data *ld, fz_html_box *box, int widths_set)
1496
48
{
1497
48
  int eop = 0; /* dummy */
1498
48
  fz_html_restarter *restart = ld->restart;
1499
48
  float em = box->s.layout.em;
1500
48
  const float *margin = box->u.block.margin;
1501
48
  const float *border = box->u.block.border;
1502
48
  const float *padding = box->u.block.padding;
1503
1504
  /* Restore the bounds */
1505
48
  TRBLCPY(ld->bounds, pd->bounds);
1506
1507
48
  if (ld->used[B] > box->s.layout.b)
1508
0
    box->s.layout.b = ld->used[B];
1509
1510
  /* Unless we're still skipping, add the padding/border/margin on the bottom. */
1511
48
  if (!WE_ARE_SKIPPING(restart))
1512
48
  {
1513
48
    if (box->style->position == POS_STATIC || box->style->position == POS_RELATIVE)
1514
48
    {
1515
48
      if (pd->has_width && box->s.layout.w < pd->width && !widths_set)
1516
0
        box->s.layout.w = pd->width;
1517
48
      if (pd->has_height && box->s.layout.b - box->s.layout.y < pd->height)
1518
0
        box->s.layout.b = pd->height + box->s.layout.y;
1519
48
    }
1520
1521
48
    ld->used[B] = advance_for_spacing(ctx, ld,
1522
48
      ld->used[B],
1523
48
      box->u.block.padding[B] + box->u.block.border[B] + box->u.block.margin[B],
1524
48
      &eop);
1525
48
  }
1526
1527
  /* In the common case, used is just whatever we laid out to.*/
1528
48
  ld->used[T] = box->s.layout.y;
1529
48
  ld->used[R] = box->s.layout.x + box->s.layout.w + padding[R] + border[R] + margin[R];
1530
48
  ld->used[L] = box->s.layout.x;
1531
48
  if (ld->used[B] < box->s.layout.b)
1532
0
    ld->used[B] = box->s.layout.b;
1533
1534
  /* So in the common case, we're done! */
1535
48
  if (box->style->position == POS_STATIC)
1536
48
    return eop;
1537
1538
  /* Retore the stashed ancestor */
1539
0
  TRBLCPY(ld->ancestor, pd->ancestor);
1540
1541
0
  switch (box->style->position)
1542
0
  {
1543
0
  case POS_RELATIVE:
1544
0
  {
1545
0
    float x = fz_css_number_defined(box->style->inset[L]) ? pd->inset[L] : -pd->inset[R];
1546
0
    float y = fz_css_number_defined(box->style->inset[T]) ? pd->inset[T] : -pd->inset[B];
1547
1548
    /* Space is taken out of the flow as if this stuff had been laid out without
1549
     * relativeness. So we just need to move the content. */
1550
0
    shift_box_contents(box, x, y);
1551
1552
0
    break;
1553
0
  }
1554
0
  case POS_FIXED:
1555
0
  {
1556
    /* Some adjustments are required:
1557
     * At this point, width will be correct, height will not.
1558
     * Contents will be positioned as if from top/left.
1559
     */
1560
0
    float page[4];
1561
1562
0
    page[T] = ld->page[T] + margin[T] + border[T] + padding[T];
1563
0
    page[R] = ld->page[R] - margin[R] - border[R] - padding[R];
1564
0
    page[B] = ld->page[B] - margin[B] - border[B] - padding[B];
1565
0
    page[L] = ld->page[L] + margin[L] + border[L] + padding[L];
1566
1567
0
    if (fz_css_number_defined(box->style->inset[L]))
1568
0
    {
1569
0
      if (pd->has_width)
1570
0
      {
1571
        /* Already fine */
1572
0
        box->s.layout.w = pd->width;
1573
0
      }
1574
0
      else
1575
0
      {
1576
        /* Already fine */
1577
0
        (void) box;
1578
0
      }
1579
0
    }
1580
0
    else
1581
0
    {
1582
0
      if (pd->has_width)
1583
0
      {
1584
        /* Right and Width determine the box. */
1585
        /* Already fine */
1586
0
        box->s.layout.w = pd->width;
1587
0
      }
1588
0
      else
1589
0
      {
1590
        /* Right and page define the box. */
1591
        /* We laid this out on the left, cos we didn't know the width. Now we
1592
         * know the width, shift it right. */
1593
0
        shift_box_contents(box, page[R] - page[L] - box->s.layout.w - pd->inset[R], 0);
1594
0
      }
1595
0
    }
1596
0
    if (fz_css_number_defined(box->style->inset[T]))
1597
0
    {
1598
0
      if (fz_css_number_defined_not_auto(box->style->height))
1599
0
      {
1600
0
        float h = fz_from_css_number(box->style->height, em, em, 0);
1601
        /* Top and Height determine the box. */
1602
0
        box->s.layout.b = box->s.layout.y + h;
1603
0
      }
1604
0
      else
1605
0
      {
1606
        /* Top and page bottom determine the box. */
1607
        /* Leave the height as is. */
1608
0
        (void) box;
1609
0
      }
1610
0
    }
1611
0
    else
1612
0
    {
1613
0
      if (pd->has_height)
1614
0
      {
1615
        /* Bottom and Height determine the box. */
1616
0
        box->s.layout.b = box->s.layout.y + pd->height;
1617
0
      }
1618
0
      else
1619
0
      {
1620
        /* Bottom and page define the box. */
1621
0
        shift_box_contents(box, 0, page[B] - box->s.layout.b - pd->inset[B]);
1622
0
      }
1623
0
    }
1624
    /* As far as the caller is concerned, no space used. */
1625
0
    ld->used[T] = ld->used[B];
1626
0
    ld->used[L] = ld->used[R];
1627
0
    break;
1628
0
  }
1629
0
  case POS_ABSOLUTE:
1630
0
  {
1631
    /* Some adjustments are required:
1632
     * At this point, width will be correct, height will not.
1633
     * Contents will be positioned as if from top/left.
1634
     */
1635
0
    if (fz_css_number_defined(box->style->inset[L]))
1636
0
    {
1637
0
      if (pd->has_width)
1638
0
      {
1639
        /* Already fine */
1640
0
        box->s.layout.w = pd->width;
1641
0
      }
1642
0
      else
1643
0
      {
1644
        /* Already fine */
1645
0
        (void) box;
1646
0
      }
1647
0
    }
1648
0
    else
1649
0
    {
1650
0
      if (pd->has_width)
1651
0
      {
1652
        /* Right and Width determine the box. */
1653
        /* Already fine */
1654
0
        box->s.layout.w = pd->width;
1655
0
      }
1656
0
      else
1657
0
      {
1658
        /* Right and ancestor define the box. */
1659
        /* We laid this out on the left, cos we didn't know the width. Now we
1660
         * know the width, shift it right. */
1661
0
        shift_box_contents(box, ld->ancestor[R] - ld->ancestor[L] - box->s.layout.w - pd->inset[R], 0);
1662
0
      }
1663
0
    }
1664
0
    if (fz_css_number_defined(box->style->inset[T]))
1665
0
    {
1666
0
      if (pd->has_height)
1667
0
      {
1668
        /* Top and Height determine the box. */
1669
0
        box->s.layout.b = box->s.layout.y + pd->height;
1670
0
      }
1671
0
      else
1672
0
      {
1673
        /* Top and ancestor bottom determine the box. */
1674
        /* Leave the height as is. */
1675
0
        (void) box;
1676
0
      }
1677
0
    }
1678
0
    else
1679
0
    {
1680
0
      if (pd->has_height)
1681
0
      {
1682
        /* Bottom and Height determine the box. */
1683
0
        box->s.layout.b = box->s.layout.y + pd->height;
1684
0
      }
1685
0
      else
1686
0
      {
1687
        /* Bottom and ancestor bottom define the box. */
1688
0
        shift_box_contents(box, 0, ld->ancestor[B] - box->s.layout.b - pd->inset[B]);
1689
0
      }
1690
0
    }
1691
    /* As far as the caller is concerned, no space used. */
1692
0
    ld->used[T] = ld->used[B];
1693
0
    ld->used[L] = ld->used[R];
1694
0
    break;
1695
0
  }
1696
0
  }
1697
1698
0
  return eop;
1699
0
}
1700
1701
1702
static int layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box);
1703
static int layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box);
1704
1705
/* === LAYOUT TABLE === */
1706
1707
/* The column widths here should include any border widths and padding.
1708
 * This is consistent with how the 'width' figures are understood in HTML.
1709
 *
1710
 * For the border-collapse cases, this is modified slightly.
1711
 * Each column only contains half of each border as they are shared with
1712
 * their neighbours. The exception to this is for the edges of the table,
1713
 * when they contain the full widths.
1714
 */
1715
typedef struct {
1716
  int fixed;
1717
  float min, max, actual;
1718
} column_width;
1719
1720
typedef struct
1721
{
1722
  /* If set, this bit means this cell is either spanned, or omitted from the definition. */
1723
  unsigned int spanned : 1;
1724
  /* If set, this bit means that the cell is defined with a "fixed" width. */
1725
  unsigned int fixed : 1;
1726
  float minw;
1727
  float maxw;
1728
  uint16_t rowspan;
1729
  uint16_t colspan;
1730
  fz_html_box *box;
1731
} table_cell;
1732
1733
typedef struct
1734
{
1735
  /* How large the grid is allocated as */
1736
  int max_w;
1737
  int max_h;
1738
  /* How large the grid is populated */
1739
  int w;
1740
  int h;
1741
  /* Where are we in filling the grid */
1742
  int x;
1743
  int y;
1744
1745
  float spacing;
1746
1747
  table_cell *cells;
1748
1749
  float *row_b;
1750
  float *row_max_bottom_border;
1751
} table_grid;
1752
1753
static table_grid *
1754
new_table_grid(fz_context *ctx, float spacing)
1755
0
{
1756
0
  table_grid *grid = fz_malloc_struct(ctx, table_grid);
1757
1758
0
  grid->spacing = spacing;
1759
1760
0
  return grid;
1761
0
}
1762
1763
static void drop_table_grid(fz_context *ctx, table_grid *grid)
1764
0
{
1765
0
  if (grid)
1766
0
  {
1767
0
    fz_free(ctx, grid->cells);
1768
0
    fz_free(ctx, grid->row_b);
1769
0
    fz_free(ctx, grid->row_max_bottom_border);
1770
0
    fz_free(ctx, grid);
1771
0
  }
1772
0
}
1773
1774
static void resize_grid(fz_context *ctx, table_grid *grid, int w, int h)
1775
0
{
1776
0
  int y;
1777
1778
0
  assert(w >= grid->max_w && h >= grid->max_h);
1779
1780
0
  grid->cells = fz_realloc(ctx, grid->cells, sizeof(grid->cells[0]) * w * h);
1781
1782
0
  if (grid->max_w != w)
1783
0
  {
1784
0
    for (y = grid->max_h-1; y >= 0; y--)
1785
0
    {
1786
0
      memmove(&grid->cells[y * w], &grid->cells[y * grid->max_w], sizeof(grid->cells[0]) * grid->max_w);
1787
0
      memset(&grid->cells[y * w + grid->max_w], 0, sizeof(grid->cells[0]) * (w - grid->max_w));
1788
0
    }
1789
0
  }
1790
0
  if (grid->max_h != h)
1791
0
    memset(&grid->cells[w * grid->max_h], 0, sizeof(grid->cells[0]) * w * (h - grid->max_h));
1792
0
  grid->max_w = w;
1793
0
  grid->max_h = h;
1794
0
}
1795
1796
static table_cell *cell_at(fz_context *ctx, table_grid *grid, int x, int y)
1797
0
{
1798
0
  if (x >= grid->max_w || y >= grid->max_h)
1799
0
  {
1800
0
    int new_w = grid->max_w;
1801
0
    int new_h = grid->max_h;
1802
0
    if (new_w == 0)
1803
0
      new_w = 8;
1804
0
    if (new_h == 0)
1805
0
      new_h = 8;
1806
0
    while (x >= new_w)
1807
0
      new_w *= 2;
1808
0
    while (y >= new_h)
1809
0
      new_h *= 2;
1810
0
    resize_grid(ctx, grid, new_w, new_h);
1811
0
  }
1812
1813
0
  if (y >= grid->h)
1814
0
    grid->h = y+1;
1815
0
  if (x >= grid->w)
1816
0
  {
1817
    /* If we're widening the active area, make sure the unused cells above
1818
     * are marked as spanning. */
1819
0
    int x0, y0;
1820
0
    for (y0 = 0; y0 < grid->y-1; y0++)
1821
0
      for (x0 = grid->w; x0 <= x; x0++)
1822
0
        grid->cells[grid->max_w * y0 + x0].spanned = 1;
1823
0
    grid->w = x+1;
1824
0
  }
1825
1826
1827
0
  return &grid->cells[grid->max_w * y + x];
1828
0
}
1829
1830
static table_cell *next_table_cell(fz_context *ctx, table_grid *grid, fz_html_box *box, int colspan, int rowspan)
1831
0
{
1832
0
  table_cell *cell;
1833
0
  int x, y;
1834
1835
0
  while (1)
1836
0
  {
1837
0
    cell = cell_at(ctx, grid, grid->x, grid->y);
1838
0
    if (!cell->spanned)
1839
0
      break;
1840
0
    grid->x++;
1841
0
  }
1842
1843
0
  if (colspan < 1)
1844
0
    colspan = 1;
1845
0
  if (rowspan < 1)
1846
0
    rowspan = 1;
1847
1848
  /* Mark all as being spanned. */
1849
0
  for (y = 0; y < rowspan; y++)
1850
0
    for (x = 0; x < colspan; x++)
1851
0
    {
1852
0
      table_cell *cell2 = cell_at(ctx, grid, grid->x + x, grid->y + y);
1853
0
      cell2->spanned = 1;
1854
0
    }
1855
1856
  /* Reload the original cell (as it may have moved due to resizing) */
1857
0
  cell = cell_at(ctx, grid, grid->x, grid->y);
1858
0
  cell->spanned = 0;
1859
0
  cell->colspan = colspan;
1860
0
  cell->rowspan = rowspan;
1861
0
  cell->box = box;
1862
1863
0
  grid->x += colspan;
1864
1865
0
  return cell;
1866
0
}
1867
1868
static void next_table_row(fz_context *ctx, table_grid *grid)
1869
0
{
1870
0
  while (grid->x < grid->w)
1871
0
  {
1872
0
    table_cell *cell = cell_at(ctx, grid, grid->x, grid->y);
1873
0
    cell->spanned = 1;
1874
0
    grid->x++;
1875
0
  }
1876
0
  grid->x = 0;
1877
0
  grid->y++;
1878
0
}
1879
1880
static column_width *
1881
table_grid_complete(fz_context *ctx, table_grid *grid, float top)
1882
0
{
1883
0
  int w = grid->w;
1884
0
  int h = grid->h;
1885
0
  int x, y, x2;
1886
0
  column_width *colw = Memento_label(fz_calloc(ctx, grid->w, sizeof(column_width)), "column_width");
1887
1888
  /* Abuse fixed to serve double duty as meaning "no clue" */
1889
0
  for (x = 0; x < w; x++)
1890
0
    colw[x].fixed = -1;
1891
1892
  /* Look for widths, without colspan confusing them. */
1893
0
  for (x = 0; x < w; x++)
1894
0
  {
1895
0
    float minw = 0, maxw = 0;
1896
0
    int fixed = 0;
1897
0
    int found = 0;
1898
0
    for (y = 0; y < h; y++)
1899
0
    {
1900
      /* Can never throw, as x/y smaller than grid->w/h */
1901
0
      table_cell *cell = cell_at(ctx, grid, x, y);
1902
0
      if (cell->spanned || cell->colspan > 1)
1903
0
        continue;
1904
0
      if (cell->fixed)
1905
0
      {
1906
0
        fixed = 1;
1907
0
        minw = cell->minw;
1908
0
        maxw = cell->maxw;
1909
0
        found = 1;
1910
0
      }
1911
0
      else if (fixed)
1912
0
      {
1913
        /* We should ignore a fixed size if another cell's minimum width is larger. */
1914
0
        if (cell->minw > minw)
1915
0
        {
1916
0
          minw = cell->minw;
1917
0
          if (minw > maxw)
1918
0
            maxw = minw;
1919
0
        }
1920
0
      }
1921
0
      else
1922
0
      {
1923
        /* Logic looks wrong, but isn't! */
1924
0
        if (!found || cell->minw > minw)
1925
0
          minw = cell->minw;
1926
0
        if (cell->maxw > maxw)
1927
0
          maxw = cell->maxw;
1928
0
        found = 1;
1929
0
      }
1930
0
    }
1931
0
    if (found)
1932
0
    {
1933
0
      colw[x].min = minw;
1934
0
      colw[x].max = maxw;
1935
0
      colw[x].fixed = fixed;
1936
0
    }
1937
0
  }
1938
1939
  /* Now we look at colspanning cells. */
1940
0
  for (y = 0; y < h; y++)
1941
0
  {
1942
0
    for (x = 0; x < w; x++)
1943
0
    {
1944
0
      float minw;
1945
0
      int unknown, nonfixed;
1946
0
      table_cell *cell = cell_at(ctx, grid, x, y);
1947
0
      if (cell->spanned || cell->colspan == 1)
1948
0
        continue;
1949
1950
      /* So, we have a spanning cell starting at (x, y) */
1951
0
      minw = 0;
1952
0
      unknown = 0;
1953
0
      nonfixed = 0;
1954
0
      for (x2 = x; x2 < x + cell->colspan; x2++)
1955
0
      {
1956
0
        if (colw[x2].fixed == -1)
1957
0
        {
1958
0
          unknown++;
1959
0
          continue;
1960
0
        }
1961
0
        if (!colw[x2].fixed)
1962
0
          nonfixed++;
1963
0
        minw += colw[x2].min;
1964
0
      }
1965
1966
0
      if (minw < cell->minw)
1967
0
      {
1968
        /* We need to boost the minimum widths. */
1969
0
        float boost = (cell->minw - minw);
1970
0
        if (unknown > 0)
1971
0
        {
1972
          /* Divide the required space between 'unknown' columns
1973
           * that currently have no width. */
1974
0
          boost /= unknown;
1975
0
          for (x2 = x; x2 < x + cell->colspan; x2++)
1976
0
          {
1977
0
            if (colw[x2].fixed == -1)
1978
0
            {
1979
0
              colw[x2].fixed = cell->fixed;
1980
0
              colw[x2].min = boost;
1981
0
              colw[x2].max = colw[x2].min;
1982
0
            }
1983
0
          }
1984
0
        }
1985
0
        else if (nonfixed > 0)
1986
0
        {
1987
          /* Divide the required space between 'nonfixed' columns
1988
           * that don't have fixed widths. */
1989
0
          boost /= nonfixed;
1990
0
          for (x2 = x; x2 < x + cell->colspan; x2++)
1991
0
          {
1992
0
            if (colw[x2].fixed)
1993
0
              continue;
1994
0
            colw[x2].fixed = cell->fixed;
1995
0
            colw[x2].min += boost;
1996
0
            if (colw[x2].max < colw[x2].min)
1997
0
              colw[x2].max = colw[x2].min;
1998
0
          }
1999
0
        }
2000
0
        else
2001
0
        {
2002
          /* We need more space, but everything is fixed! There seems to be
2003
           * a contradiction here. Just live with it. */
2004
0
        }
2005
0
      }
2006
0
    }
2007
0
  }
2008
2009
  /* Everything should now be set.
2010
   * But it might not be.
2011
   * <table><tbody><tr><th colspan="3">A</th></tr>
2012
   *        <tr><td>B</td><td>C</td>
2013
   * </tbody></table>
2014
   * will give us a 3 column table, with no clue what the size should be for column 3.
2015
  */
2016
0
  for (x = 0; x < w; x++)
2017
0
  {
2018
0
    if (colw[x].fixed == -1)
2019
0
      colw[x].fixed = 0;
2020
0
  }
2021
2022
0
  fz_try(ctx)
2023
0
  {
2024
0
    grid->row_b = fz_malloc(ctx, grid->h * sizeof(float));
2025
0
    grid->row_max_bottom_border = fz_calloc(ctx, grid->h, sizeof(float));
2026
0
  }
2027
0
  fz_catch(ctx)
2028
0
  {
2029
0
    fz_free(ctx, colw);
2030
0
    fz_rethrow(ctx);
2031
0
  }
2032
2033
  /* Don't add any spacing in here, because we may be skipping!
2034
   * We just want a good enough value to use for 'min'. */
2035
0
  for (y = 0; y < h; y++)
2036
0
    grid->row_b[y] = top;
2037
2038
0
  return colw;
2039
0
}
2040
2041
static float table_cell_padding(fz_context *ctx, fz_html_box *box)
2042
0
{
2043
0
  return (
2044
0
    box->u.block.padding[L] + box->u.block.border[L] +
2045
0
    box->u.block.padding[R] + box->u.block.border[R]
2046
0
  );
2047
0
}
2048
2049
typedef struct
2050
{
2051
  float max_left_border;
2052
  float max_right_border;
2053
} border_collapse_info;
2054
2055
static float table_cell_padding_collapsed(fz_context *ctx, fz_html_box *box, border_collapse_info *bci, int leftmost, int rightmost)
2056
0
{
2057
0
  float left = leftmost ? bci->max_left_border : box->u.block.border[L]/2;
2058
0
  float right = rightmost ? bci->max_right_border : box->u.block.border[R]/2;
2059
0
  return (
2060
0
    box->u.block.padding[L] + left +
2061
0
    box->u.block.padding[R] + right
2062
0
  );
2063
0
}
2064
2065
static float block_padding(fz_context *ctx, fz_html_box *box)
2066
0
{
2067
0
  return (
2068
0
    box->u.block.padding[L] + box->u.block.border[L] + box->u.block.margin[L] +
2069
0
    box->u.block.padding[R] + box->u.block.border[R] + box->u.block.margin[R]
2070
0
  );
2071
0
}
2072
2073
static float largest_min_width(fz_context *ctx, fz_html_box *box)
2074
0
{
2075
  /* The "minimum" width is the largest word in a paragraph. */
2076
0
  float r_min = 0;
2077
0
  if (box->type == BOX_BLOCK)
2078
0
  {
2079
0
    fz_html_box *child = box->down;
2080
0
    while (child)
2081
0
    {
2082
0
      float min = largest_min_width(ctx, child);
2083
0
      if (min > r_min)
2084
0
        r_min = min;
2085
0
      child = child->next;
2086
0
    }
2087
0
    r_min += block_padding(ctx, box);
2088
0
  }
2089
0
  else if (box->type == BOX_FLOW)
2090
0
  {
2091
0
    fz_html_flow *flow;
2092
0
    for (flow = box->u.flow.head; flow; flow = flow->next)
2093
0
      if (flow->w > r_min)
2094
0
        r_min = flow->w;
2095
0
  }
2096
0
  else
2097
0
  {
2098
    // TODO: nested TABLE
2099
0
  }
2100
0
  return r_min;
2101
0
}
2102
2103
static float largest_max_width(fz_context *ctx, fz_html_box *box)
2104
0
{
2105
  /* The "maximum" width is the length of the longest paragraph laid out on one line. */
2106
0
  float r_max = 0;
2107
0
  if (box->type == BOX_BLOCK)
2108
0
  {
2109
0
    fz_html_box *child = box->down;
2110
0
    while (child)
2111
0
    {
2112
0
      float max = largest_max_width(ctx, child);
2113
0
      if (max > r_max)
2114
0
        r_max = max;
2115
0
      child = child->next;
2116
0
    }
2117
0
    r_max += block_padding(ctx, box);
2118
0
  }
2119
0
  else if (box->type == BOX_FLOW)
2120
0
  {
2121
0
    fz_html_flow *flow;
2122
0
    float max = 0;
2123
0
    for (flow = box->u.flow.head; flow; flow = flow->next)
2124
0
    {
2125
0
      max += flow->w;
2126
0
      if (flow->type == FLOW_BREAK)
2127
0
      {
2128
0
        if (max > r_max)
2129
0
          r_max = max;
2130
0
        max = 0;
2131
0
      }
2132
0
    }
2133
0
    if (max > r_max)
2134
0
      r_max = max;
2135
0
  }
2136
0
  else
2137
0
  {
2138
    // TODO: nested TABLE
2139
0
  }
2140
0
  return r_max;
2141
0
}
2142
2143
static void squish_block(fz_context *ctx, fz_html_box *box)
2144
0
{
2145
0
  fz_html_box *child;
2146
2147
0
  box->s.layout.b = box->s.layout.y;
2148
0
  if (box->type == BOX_FLOW)
2149
0
    return;
2150
0
  for (child = box->down; child; child = child->next)
2151
0
    squish_block(ctx, child);
2152
0
}
2153
2154
static void layout_table_row(fz_context *ctx, layout_data *ld, table_grid *grid, int celly, fz_html_box *row, int ncol, column_width *colw, float spacing, border_collapse_info *border_collapse)
2155
0
{
2156
0
  fz_html_box *cell, *child;
2157
0
  int col = 0;
2158
0
  float x = row->s.layout.x;
2159
0
  float y;
2160
0
  float saved_bounds[4];
2161
0
  float row_height = 0; /* Silence stupid compilers */
2162
0
  float em = row->s.layout.em;
2163
0
  int fixed_height = 0;
2164
0
  table_cell *tcell;
2165
0
  float max_cell_border_above, max_cell_border_below;
2166
2167
  /* Always layout the full row since we can't restart in the middle of a cell.
2168
   * If the row doesn't fit fully, we'll postpone it to the next page.
2169
   * FIXME: If the row doesn't fit then either, we should split it with the offset.
2170
   */
2171
0
  fz_html_restarter *save_restart = ld->restart;
2172
0
  ld->restart = NULL;
2173
2174
  /* Note: margin is ignored for table cells and rows */
2175
2176
0
  TRBLCPY(saved_bounds, ld->bounds);
2177
2178
0
  if (fz_css_number_defined_not_auto(row->style->height))
2179
0
  {
2180
0
    row_height = fz_from_css_number(row->style->height, em, em, 0);
2181
0
    fixed_height = 1;
2182
0
  }
2183
2184
  /* Find the maximum cell border in this row. */
2185
0
  max_cell_border_above = 0;
2186
0
  max_cell_border_below = 0;
2187
0
  for (cell = row->down; cell; cell = cell->next)
2188
0
  {
2189
0
    if (max_cell_border_above < cell->u.block.border[T])
2190
0
      max_cell_border_above = cell->u.block.border[T];
2191
0
    if (max_cell_border_below < cell->u.block.border[B])
2192
0
      max_cell_border_below = cell->u.block.border[B];
2193
0
  }
2194
2195
  /* For each cell in the row */
2196
0
  for (cell = row->down; cell; cell = cell->next)
2197
0
  {
2198
0
    float cell_pad = table_cell_padding(ctx, cell);
2199
0
    int colspan, rowspan;
2200
0
    float cellw;
2201
0
    int i;
2202
2203
    /* Find the cell record in the grid. */
2204
0
    while (1)
2205
0
    {
2206
0
      assert(col < grid->w && celly <= grid->h);
2207
0
      tcell = cell_at(ctx, grid, col, celly);
2208
0
      if (tcell->box == cell)
2209
0
        break;
2210
0
      x += colw[col++].actual + spacing;
2211
0
    }
2212
0
    colspan = tcell->colspan;
2213
0
    assert(colspan >= 1);
2214
0
    rowspan = tcell->rowspan;
2215
0
    assert(rowspan >= 1);
2216
2217
    /* Find the actual width of this cell */
2218
0
    cellw = 0;
2219
0
    for (i = 0; i < colspan; i++)
2220
0
      cellw += colw[col+i].actual + (i != 0 ? spacing : 0);
2221
2222
0
    x += spacing;
2223
2224
    /* Position the cell */
2225
0
    ld->bounds[T] = row->s.layout.y + max_cell_border_above + cell->u.block.padding[T];
2226
0
    ld->bounds[L] = x + cell->u.block.padding[L] + cell->u.block.border[L];
2227
0
    ld->bounds[R] = ld->bounds[L] + cellw - cell_pad;
2228
0
    ld->bounds[B] = cell->s.layout.y;
2229
0
    if (border_collapse)
2230
0
    {
2231
0
      if (col == 0)
2232
0
      {
2233
        /* Adjust the left hand edge so that all the borders line up */
2234
0
        ld->bounds[L] += border_collapse->max_left_border/2;
2235
0
      }
2236
      /* We share half the border with our predecessor, so move back a bit. */
2237
0
      ld->bounds[L] -= cell->u.block.border[L]/2;
2238
0
      if (col + colspan == grid->w)
2239
0
      {
2240
        /* Adjust the right hand edge so that all the borders line up */
2241
0
        ld->bounds[R] -= border_collapse->max_right_border/2;
2242
0
      }
2243
      /* We share half the border with our successor, so move on a bit. */
2244
0
      ld->bounds[R] += cell->u.block.border[R]/2;
2245
      /* Reuse T and B margins to allow for the required inner cell alignment spacing */
2246
0
      cell->u.block.margin[T] = (max_cell_border_above - cell->u.block.border[T])/2;
2247
0
      cell->u.block.margin[B] = (max_cell_border_below - cell->u.block.border[B])/2;
2248
0
    }
2249
0
    else
2250
0
    {
2251
      /* Reuse T and B margins to allow for the required inner cell alignment spacing */
2252
0
      cell->u.block.margin[T] = max_cell_border_above - cell->u.block.border[T];
2253
0
      cell->u.block.margin[B] = max_cell_border_below - cell->u.block.border[B];
2254
0
    }
2255
2256
0
    cell->s.layout.x = ld->bounds[L];
2257
0
    cell->s.layout.w = ld->bounds[R] - ld->bounds[L];
2258
0
    cell->s.layout.y = ld->bounds[T];
2259
2260
    /* Layout cell contents into the cell. */
2261
0
    for (child = cell->down; child; child = child->next)
2262
0
    {
2263
0
      if (child->type == BOX_BLOCK)
2264
0
      {
2265
0
        (void)layout_block(ctx, ld, child);
2266
0
      }
2267
0
      else if (child->type == BOX_TABLE)
2268
0
      {
2269
0
        (void)layout_table(ctx, ld, child);
2270
0
      }
2271
0
      else if (child->type == BOX_FLOW)
2272
0
      {
2273
0
        layout_flow(ctx, ld, child, cell);
2274
0
      }
2275
0
      ld->bounds[T] = ld->used[B];
2276
0
    }
2277
0
    cell->s.layout.b = ld->used[B];
2278
0
    if (fz_css_number_defined_not_auto(cell->style->height))
2279
0
    {
2280
0
      float cellheight = fz_from_css_number(cell->style->height, em, em, 0);
2281
0
      if (cell->s.layout.b < cell->s.layout.y + cellheight)
2282
0
      cell->s.layout.b = cell->s.layout.y + cellheight;
2283
0
    }
2284
0
    else if (fixed_height && cell->s.layout.b < cell->s.layout.y + row_height)
2285
0
      cell->s.layout.b = cell->s.layout.y + row_height;
2286
2287
    /* Advance to next column */
2288
0
    x += colw[col].actual;
2289
2290
    /* Update row_b (most importantly for this row, but ensure the others
2291
     * are plausibly increasing too). */
2292
0
    y = cell->s.layout.b + cell->u.block.padding[B] + cell->u.block.border[B] + cell->u.block.margin[B];
2293
0
    if (border_collapse)
2294
0
    {
2295
      /* Half our border is shared with the next cell. */
2296
0
      y -= cell->u.block.border[B]/2;
2297
0
    }
2298
0
    for (i = celly + rowspan - 1; i < grid->h; i++)
2299
0
    {
2300
0
      if (grid->row_b[i] < y)
2301
0
        grid->row_b[i] = y;
2302
0
      y += spacing;
2303
0
    }
2304
2305
0
    if (border_collapse)
2306
0
    {
2307
0
      if (grid->row_max_bottom_border[celly + rowspan - 1] < cell->u.block.border[B])
2308
0
        grid->row_max_bottom_border[celly + rowspan - 1] = cell->u.block.border[B];
2309
0
    }
2310
2311
0
    ++col;
2312
0
  }
2313
2314
0
  row->s.layout.b = grid->row_b[celly];
2315
2316
0
  ld->restart = save_restart;
2317
2318
0
  TRBLCPY(ld->bounds, saved_bounds);
2319
0
}
2320
2321
static inline int
2322
cell_rowspan(fz_html_box *cell)
2323
0
{
2324
0
  int r = cell->style->rowspan;
2325
0
  if (r < 1)
2326
0
    return 1;
2327
0
  return r;
2328
0
}
2329
2330
static void
2331
fixup_collapsed_cell_bottoms_for_row(fz_context *ctx, table_grid *grid, int row, fz_html_box *box)
2332
0
{
2333
0
  int row0;
2334
0
  fz_html_box *rowbox, *cell;
2335
2336
0
  for (row0 = 0, rowbox = box->down; row0 <= row && rowbox; row0++, rowbox = rowbox->next)
2337
0
  {
2338
0
    for (cell = rowbox->down; cell; cell = cell->next)
2339
0
    {
2340
0
      float y;
2341
0
      int rowspan = cell_rowspan(cell);
2342
0
      if (row0 + rowspan - 1 != row)
2343
0
        continue;
2344
2345
0
      y = grid->row_b[row0 + rowspan - 1] - (cell->u.block.padding[B] + cell->u.block.border[B]/2 + cell->u.block.margin[B]);
2346
0
      if (cell->style->vertical_align == VA_MIDDLE || cell->style->vertical_align == VA_BOTTOM)
2347
0
      {
2348
0
        float offset = (y - cell->s.layout.b);
2349
0
        if (cell->style->vertical_align == VA_MIDDLE)
2350
0
          offset /= 2;
2351
0
        shift_box_contents(cell, 0, offset);
2352
0
        cell->s.layout.y -= offset;
2353
0
      }
2354
0
      cell->s.layout.b = y;
2355
0
    }
2356
0
  }
2357
0
}
2358
2359
static void
2360
fixup_cell_bottoms(fz_context *ctx, table_grid *grid, fz_html_box *box)
2361
0
{
2362
0
  fz_html_box *rowbox, *cell;
2363
0
  int row;
2364
2365
  /* Second pass, set the bottoms of all cells in the tables. */
2366
0
  for (row = 0, rowbox = box; row <= grid->h && rowbox; row++, rowbox = rowbox->next)
2367
0
  {
2368
0
    for (cell = rowbox->down; cell; cell = cell->next)
2369
0
    {
2370
0
      float y;
2371
0
      int rowspan = cell_rowspan(cell);
2372
2373
0
      y = grid->row_b[row + rowspan - 1] - (cell->u.block.padding[B] + cell->u.block.border[B] + cell->u.block.margin[B]);
2374
0
      if (cell->style->vertical_align == VA_MIDDLE || cell->style->vertical_align == VA_BOTTOM)
2375
0
      {
2376
0
        float offset = (y - cell->s.layout.b);
2377
0
        if (cell->style->vertical_align == VA_MIDDLE)
2378
0
          offset /= 2;
2379
0
        shift_box_contents(cell, 0, offset);
2380
0
        cell->s.layout.y -= offset;
2381
0
      }
2382
0
      cell->s.layout.b = y;
2383
0
    }
2384
0
  }
2385
0
}
2386
2387
static void collapse_table_borders(fz_context *ctx, table_grid *grid, border_collapse_info *bci)
2388
0
{
2389
0
  int w = grid->w;
2390
0
  int h = grid->h;
2391
0
  int x, y, x1, y1;
2392
2393
  /* Collapse columns */
2394
0
  bci->max_right_border = 0;
2395
0
  for (x = 0; x < w-1; x++)
2396
0
  {
2397
0
    for (y = 0; y < h; y++)
2398
0
    {
2399
0
      table_cell *cell0 = cell_at(ctx, grid, x, y);
2400
0
      table_cell *cell1;
2401
0
      float b0, b1;
2402
2403
0
      if (cell0->spanned == 1 || cell0->box == NULL)
2404
0
        continue;
2405
2406
0
      cell0->box->collapsed_cell = 1;
2407
2408
0
      x1 = x + cell0->colspan;
2409
0
      if (x1 == w)
2410
0
      {
2411
0
        if (bci->max_right_border < cell0->box->u.block.border[R])
2412
0
          bci->max_right_border = cell0->box->u.block.border[R];
2413
0
        continue;
2414
0
      }
2415
2416
0
      cell1 = cell_at(ctx, grid, x1, y);
2417
0
      if (cell1->box == NULL)
2418
0
        continue;
2419
0
      b0 = cell0->box->u.block.border[R];
2420
0
      b1 = cell1->box->u.block.border[L];
2421
2422
0
      if (b0 < b1 && b1 >= 0)
2423
0
      {
2424
0
        cell0->box->u.block.border[R] = b1;
2425
0
        cell0->box->suppress_border |= 1<<R;
2426
0
      }
2427
0
      else
2428
0
      {
2429
0
        cell1->box->u.block.border[L] = b0;
2430
0
        cell1->box->suppress_border |= 1<<L;
2431
0
      }
2432
0
    }
2433
0
  }
2434
0
  for (y = 0; y < h; y++)
2435
0
  {
2436
0
    table_cell *cell0 = cell_at(ctx, grid, w-1, y);
2437
2438
0
    if (cell0->spanned == 1 || cell0->box == NULL)
2439
0
      continue;
2440
2441
0
    x1 = x + cell0->colspan;
2442
0
    if (x1 == w)
2443
0
    {
2444
0
      if (bci->max_right_border < cell0->box->u.block.border[R])
2445
0
        bci->max_right_border = cell0->box->u.block.border[R];
2446
0
      continue;
2447
0
    }
2448
0
  }
2449
0
  bci->max_left_border = 0;
2450
0
  for (y = 0; y < h; y++)
2451
0
  {
2452
0
    table_cell *cell0 = cell_at(ctx, grid, 0, y);
2453
2454
0
    if (cell0->spanned == 1 || cell0->box == NULL)
2455
0
      continue;
2456
0
    if (bci->max_left_border < cell0->box->u.block.border[L])
2457
0
      bci->max_left_border = cell0->box->u.block.border[L];
2458
0
  }
2459
2460
  /* Collapse rows */
2461
0
  for (y = 0; y < h-1; y++)
2462
0
  {
2463
0
    for (x = 0; x < w; x++)
2464
0
    {
2465
0
      table_cell *cell0 = cell_at(ctx, grid, x, y);
2466
0
      table_cell *cell1;
2467
0
      float b0, b1;
2468
2469
0
      if (cell0->spanned == 1 || cell0->box == NULL)
2470
0
        continue;
2471
2472
0
      y1 = y + cell0->rowspan;
2473
0
      if (y1 >= h)
2474
0
        continue;
2475
2476
0
      cell1 = cell_at(ctx, grid, x, y1);
2477
0
      if (cell1->box == NULL)
2478
0
        continue;
2479
0
      b0 = cell0->box->u.block.border[B];
2480
0
      b1 = cell1->box->u.block.border[T];
2481
2482
0
      if (b0 < b1 && b1 >= 0)
2483
0
      {
2484
0
        cell0->box->u.block.border[B] = b1;
2485
0
        cell0->box->suppress_border |= 1<<B;
2486
0
      }
2487
0
      else
2488
0
      {
2489
0
        cell1->box->u.block.border[T] = b0;
2490
0
        cell1->box->suppress_border |= 1<<T;
2491
0
      }
2492
0
    }
2493
0
  }
2494
0
}
2495
2496
static int layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box)
2497
0
{
2498
0
  fz_html_box *row;
2499
0
  int col, ncol;
2500
0
  float min_tabw, max_tabw;
2501
0
  column_width *colw = NULL;
2502
0
  fz_html_restarter *restart = ld->restart;
2503
0
  float spacing;
2504
0
  int eop = 0;
2505
0
  float avail_w;
2506
0
  table_grid *table;
2507
0
  int x, y;
2508
0
  int table_has_width;
2509
0
  int border_collapse = box->style->border_collapse;
2510
0
  border_collapse_info bci;
2511
2512
0
  position_data position;
2513
2514
0
  spacing = fz_from_css_number(box->style->border_spacing, box->s.layout.em, box->s.layout.w, 0);
2515
0
  if (border_collapse)
2516
0
    spacing = 0;
2517
2518
0
  if (restart)
2519
0
  {
2520
    /* We have reached the restart point */
2521
0
    if (restart->start == box)
2522
0
      restart->start = NULL;
2523
0
  }
2524
2525
  /* We honour positions on tables. We don't honour positions on elements of tables (like rows/cells etc). */
2526
0
  eop = pre_position(ctx, &position, ld, box, 1);
2527
2528
0
  if (eop)
2529
0
  {
2530
0
    if (restart && restart->end == NULL)
2531
0
    {
2532
0
      box->s.layout.b = box->s.layout.y;
2533
0
      if (restart->potential)
2534
0
        restart->end = restart->potential;
2535
0
      else
2536
0
        restart->end = box;
2537
0
      return 0;
2538
0
    }
2539
0
  }
2540
2541
  /* Add initial border-spacing */
2542
0
  ld->used[B] += spacing;
2543
0
  box->s.layout.b += spacing;
2544
2545
0
  table = new_table_grid(ctx, spacing);
2546
2547
0
  fz_var(colw);
2548
2549
0
  fz_try(ctx)
2550
0
  {
2551
    /* Table Autolayout algorithm from HTML */
2552
    /* https://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2 */
2553
2554
    /* Make the grid. */
2555
0
    for (row = box->down; row; row = row->next)
2556
0
    {
2557
0
      fz_html_box *cell;
2558
0
      for (cell = row->down; cell; cell = cell->next)
2559
0
        next_table_cell(ctx, table, cell, cell->style->colspan, cell->style->rowspan);
2560
0
      next_table_row(ctx, table);
2561
0
    }
2562
2563
    /* Collapse the cell borders if required. */
2564
0
    if (border_collapse)
2565
0
      collapse_table_borders(ctx, table, &bci);
2566
2567
    /* Now walk the cells, populating minw/maxw/fixed. */
2568
0
    for (y = 0; y < table->h; y++)
2569
0
    {
2570
0
      for (x = 0; x < table->w; x++)
2571
0
      {
2572
0
        fz_html_box *cell, *child;
2573
0
        float cell_pad;
2574
0
        table_cell *tc = cell_at(ctx, table, x, y);
2575
2576
0
        if (tc->spanned)
2577
0
          continue;
2578
2579
0
        cell = tc->box;
2580
0
        if (cell == NULL)
2581
0
          continue;
2582
2583
0
        if (border_collapse)
2584
0
          cell_pad = table_cell_padding_collapsed(ctx, cell, &bci, x == 0, x + tc->colspan == table->w);
2585
0
        else
2586
0
          cell_pad = table_cell_padding(ctx, cell);
2587
0
        if (fz_css_number_defined_not_auto(cell->style->width))
2588
0
        {
2589
0
          float em = box->s.layout.em;
2590
0
          float w = fz_from_css_number(cell->style->width, em, em, 0) + cell_pad;
2591
0
          tc->fixed = 1;
2592
0
          tc->minw = w;
2593
0
          tc->maxw = w;
2594
0
        }
2595
0
        else
2596
0
        {
2597
0
          float cellminw = 0;
2598
0
          float cellmaxw = 0;
2599
0
          for (child = cell->down; child; child = child->next)
2600
0
          {
2601
0
            float min = largest_min_width(ctx, child) + cell_pad;
2602
0
            float max = largest_max_width(ctx, child) + cell_pad;
2603
            /* The logic here looks screwy, but isn't! */
2604
0
            if (min > cellminw)
2605
0
              cellminw = min;
2606
0
            if (max > cellmaxw)
2607
0
              cellmaxw = max;
2608
0
          }
2609
0
          tc->fixed = 0;
2610
0
          tc->minw = cellminw;
2611
0
          tc->maxw = cellmaxw;
2612
0
        }
2613
0
      }
2614
0
    }
2615
2616
    /* Now we can calculate the column widths. */
2617
0
    colw = table_grid_complete(ctx, table, box->s.layout.b);
2618
0
    ncol = table->w;
2619
2620
0
    min_tabw = max_tabw = 0;
2621
0
    for (col = 0; col < ncol; ++col)
2622
0
    {
2623
0
      min_tabw += colw[col].min;
2624
0
      max_tabw += colw[col].max;
2625
0
    }
2626
0
    min_tabw += spacing * (ncol + 1);
2627
0
    max_tabw += spacing * (ncol + 1);
2628
2629
0
    avail_w = fz_from_css_number(box->style->width, box->s.layout.em, ld->bounds[R] - ld->bounds[L], ld->bounds[R] - ld->bounds[L]);
2630
0
    table_has_width = fz_css_number_defined_not_auto(box->style->width);
2631
2632
0
    if (min_tabw >= avail_w)
2633
0
    {
2634
      /* The minimum table width is equal to or wider than the available space.
2635
       * Assign the minimum widths and let the lines overflow...
2636
       */
2637
0
      box->s.layout.w = min_tabw;
2638
0
      for (col = 0; col < ncol; ++col)
2639
0
        colw[col].actual = colw[col].min;
2640
0
    }
2641
0
    else if (max_tabw <= avail_w && !table_has_width)
2642
0
    {
2643
      /* The maximum table width fits within the available space. */
2644
      /* Set the columns to their maximum widths. */
2645
0
      box->s.layout.w = max_tabw;
2646
0
      for (col = 0; col < ncol; ++col)
2647
0
        colw[col].actual = colw[col].max;
2648
0
    }
2649
0
    else
2650
0
    {
2651
      /* The maximum width of the table is greater than the available space, but
2652
       * the minimum table width is smaller. In this case, find the difference
2653
       * between the available space and the minimum table width, lets call it
2654
       * W. Lets also call D the difference between maximum and minimum width of
2655
       * the table.
2656
       *
2657
       * For each column, let d be the difference between maximum and minimum
2658
       * width of that column. Now set the column's width to the minimum width
2659
       * plus d times W over D. This makes columns with large differences
2660
       * between minimum and maximum widths wider than columns with smaller
2661
       * differences.
2662
       */
2663
0
      float W = (avail_w - min_tabw);
2664
0
      float D = (max_tabw - min_tabw);
2665
0
      box->s.layout.w = avail_w;
2666
0
      if (D == 0)
2667
0
      {
2668
0
        for (col = 0; col < ncol; ++col)
2669
0
          colw[col].actual = colw[col].min + W / ncol;
2670
0
      }
2671
0
      else
2672
0
      {
2673
0
        for (col = 0; col < ncol; ++col)
2674
0
          colw[col].actual = colw[col].min + (colw[col].max - colw[col].min) * W / D;
2675
0
      }
2676
0
    }
2677
2678
    /* Layout each row in turn. */
2679
0
    for (row = box->down, y = 0; row && y < table->h; row = row->next, y++)
2680
0
    {
2681
      /* Position the row, zero height for now. */
2682
0
      if (box->s.layout.b < table->row_b[y])
2683
0
        box->s.layout.b = table->row_b[y];
2684
0
      row->s.layout.x = box->s.layout.x;
2685
0
      row->s.layout.w = avail_w;
2686
0
      row->s.layout.y = row->s.layout.b = box->s.layout.b;
2687
2688
0
      if (restart && restart->start != NULL)
2689
0
      {
2690
0
        if (restart->start == row)
2691
0
          restart->start = NULL;
2692
0
        else
2693
0
        {
2694
0
          squish_block(ctx, row);
2695
0
          continue; /* still skipping */
2696
0
        }
2697
0
      }
2698
2699
0
      if (border_collapse && y > 0)
2700
0
      {
2701
        /* When border collapsing, every row's borders overlap the ones from
2702
         * the one above. */
2703
0
        row->s.layout.y -= table->row_max_bottom_border[y-1]/2;
2704
0
      }
2705
0
      layout_table_row(ctx, ld, table, y, row, ncol, colw, spacing, border_collapse ? &bci : NULL);
2706
0
      if (border_collapse)
2707
0
        fixup_collapsed_cell_bottoms_for_row(ctx, table, y, box);
2708
2709
      /* If the row doesn't fit on the current page, break here and put the row on the next page.
2710
       * Unless the row was at the very start of the page, in which case it'll overflow instead.
2711
       * FIXME: Don't overflow, draw twice with offset to break it abruptly at the page border!
2712
       */
2713
0
      if (ld->page[B] != ld->page[T])
2714
0
      {
2715
0
        float page_h = ld->page[B] - ld->page[T];
2716
0
        float avail = page_h - fmodf(row->s.layout.y - ld->page[T], page_h);
2717
0
        float used = row->s.layout.b - row->s.layout.y;
2718
0
        if (used > avail && avail < page_h)
2719
0
        {
2720
0
          if (restart)
2721
0
          {
2722
0
            restart->end = row;
2723
0
            goto exit;
2724
0
          }
2725
0
          else
2726
0
          {
2727
0
            row->s.layout.y += avail;
2728
0
            layout_table_row(ctx, ld, table, y, row, ncol, colw, spacing, border_collapse ? &bci : NULL);
2729
0
          }
2730
0
        }
2731
0
      }
2732
2733
0
      box->s.layout.b = row->s.layout.b + spacing;
2734
0
      ld->used[B] = box->s.layout.b;
2735
0
    }
2736
2737
    /* And align the cell bottoms. */
2738
0
    if (!border_collapse)
2739
0
      fixup_cell_bottoms(ctx, table, box->down);
2740
2741
0
    exit:;
2742
0
  }
2743
0
  fz_always(ctx)
2744
0
  {
2745
0
    drop_table_grid(ctx, table);
2746
0
    fz_free(ctx, colw);
2747
0
  }
2748
0
  fz_catch(ctx)
2749
0
    fz_rethrow(ctx);
2750
2751
0
  if (post_position(ctx, &position, ld, box, 1))
2752
0
  {
2753
0
    if (SHOULD_STOP_FOR_RESTART(restart))
2754
0
    {
2755
0
      if (restart->potential)
2756
0
        restart->end = restart->potential;
2757
0
      else
2758
0
      {
2759
        /* NEVER stop to restart on this object, or we will get into
2760
         * an infinite loop. We'd rather just risk losing the padding/margin/
2761
         * border at the bottom. Return 1, so we restart on the next box. */
2762
        /* DO NOT DO: restart->end = box; */
2763
0
        return 1;
2764
0
      }
2765
0
      return 0;
2766
0
    }
2767
0
  }
2768
2769
0
  if (layout_block_page_break(ctx, ld, &ld->used[B], box->style->page_break_after))
2770
0
  {
2771
0
    if (SHOULD_STOP_FOR_RESTART(restart))
2772
0
      return 1;
2773
0
  }
2774
2775
0
  return 0;
2776
0
}
2777
2778
/* === LAYOUT BLOCKS === */
2779
2780
/*
2781
  Layout a BOX_BLOCK.
2782
2783
  ctx: The ctx in use.
2784
  ld: The layout data.
2785
    We should fill into ld->bounds, and update ld->fill to be what we used before we return.
2786
    A lot of the time, ld->fill will be the same as box->u.layout on exit, but (because of
2787
    positioning) not always.
2788
  box: The BOX_BLOCK to layout.
2789
2790
  Return 1 if we should restart at the next block, 0 otherwise.
2791
*/
2792
static int layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box)
2793
48
{
2794
48
  fz_html_box *child;
2795
48
  fz_html_restarter *restart = ld->restart;
2796
2797
48
  const fz_css_style *style = box->style;
2798
48
  float em = box->s.layout.em;
2799
48
  int eop = 0;
2800
48
  position_data position;
2801
2802
48
  assert(fz_html_box_has_boxes(box));
2803
2804
  /* If restart is non-NULL, we are running in restartable mode. */
2805
48
  if (restart)
2806
0
  {
2807
    /* If restart->start == box, then we've been skipping, and this
2808
     * is the point at which we should restart. */
2809
0
    if (restart->start == box)
2810
0
    {
2811
0
      if (restart->start_flags & FZ_HTML_RESTARTER_START_END_FLAGS_SPECIFIC_SIDE)
2812
0
      {
2813
        /* If the side matches, then great. If not, we need to end here! */
2814
0
        if (!!(restart->start_flags & FZ_HTML_RESTARTER_START_END_FLAGS_LEFT_SIDE) != restart->left_page)
2815
0
        {
2816
          /* Skip straight to stopping! */
2817
0
          restart->end = box;
2818
0
          restart->end_flags = restart->start_flags;
2819
0
          return 1;
2820
0
        }
2821
0
      }
2822
      /* Set restart->start to be NULL to indicate that we aren't skipping
2823
       * any more. */
2824
0
      restart->start = NULL;
2825
0
    }
2826
2827
    /* In the event that no content fits, we want to restart with us, not with the
2828
     * content that doesn't fit. */
2829
0
    if (restart->potential == NULL)
2830
0
      restart->potential = box;
2831
0
  }
2832
2833
  /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
2834
48
  eop = layout_block_page_break(ctx, ld, &ld->used[B], style->page_break_before);
2835
2836
2837
  /* Cope with positioned blocks. */
2838
48
  eop |= pre_position(ctx, &position, ld, box, 0);
2839
2840
48
  if (eop)
2841
0
  {
2842
0
    if (SHOULD_STOP_FOR_RESTART(restart))
2843
0
    {
2844
0
      box->s.layout.b = box->s.layout.y = ld->used[B];
2845
0
      if (restart->potential)
2846
0
        restart->end = restart->potential;
2847
0
      else
2848
0
        restart->end = box;
2849
0
      return 0;
2850
0
    }
2851
0
  }
2852
2853
  /* At this point, fill is already setup. */
2854
2855
139
  for (child = box->down; child; child = child->next)
2856
91
  {
2857
91
    if (SHOULD_STOP_FOR_RESTART(restart))
2858
0
    {
2859
      /* If advancing over the bottom margin/border/padding of a child has previously
2860
       * brought us to the end of a page, this is where we end. */
2861
0
      if (eop)
2862
0
      {
2863
0
        if (restart->potential)
2864
0
          restart->end = restart->potential;
2865
0
        else
2866
0
          restart->end = child;
2867
0
        break;
2868
0
      }
2869
0
    }
2870
91
    ld->used[L] = ld->bounds[L];
2871
2872
91
    if (child->type == BOX_BLOCK)
2873
47
      eop = layout_block(ctx, ld, child);
2874
2875
44
    else if (child->type == BOX_TABLE)
2876
0
      eop = layout_table(ctx, ld, child);
2877
2878
44
    else if (child->type == BOX_FLOW)
2879
44
    {
2880
44
      layout_flow(ctx, ld, child, box);
2881
44
      if (!WE_ARE_SKIPPING(restart))
2882
44
      {
2883
44
        if (child->s.layout.b > child->s.layout.y)
2884
41
          box->s.layout.b = ld->used[B];
2885
44
      }
2886
44
    }
2887
2888
91
    if (!WE_ARE_SKIPPING(restart))
2889
91
    {
2890
91
      if (ld->used[B] > box->s.layout.b)
2891
47
        box->s.layout.b = ld->used[B];
2892
91
      if (ld->used[R] > box->s.layout.x + box->s.layout.w)
2893
55
        box->s.layout.w = ld->used[R] - box->s.layout.x;
2894
91
      if (ld->used[B] > box->s.layout.b)
2895
0
        box->s.layout.b = ld->used[B];
2896
91
    }
2897
2898
    /* Stop if we've reached the endpoint. */
2899
91
    if (STOPPING_FOR_RESTART(restart))
2900
0
      break;
2901
91
    if (restart && box->s.layout.b != box->s.layout.y)
2902
0
      restart->potential = NULL;
2903
2904
91
    ld->bounds[T] = ld->used[B];
2905
91
  }
2906
2907
48
  if (post_position(ctx, &position, ld, box, 0))
2908
1
  {
2909
1
    if (SHOULD_STOP_FOR_RESTART(restart))
2910
0
    {
2911
0
      if (restart->potential)
2912
0
        restart->end = restart->potential;
2913
0
      else
2914
0
      {
2915
        /* NEVER stop to restart on this object, or we will get into
2916
         * an infinite loop. We'd rather just risk losing the padding/margin/
2917
         * border at the bottom. Return 1, so we restart on the next box. */
2918
        /* DO NOT DO: restart->end = box; */
2919
0
        return 1;
2920
0
      }
2921
0
      return 0;
2922
0
    }
2923
1
  }
2924
2925
  /* If we're still skipping, exit. */
2926
48
  if (WE_ARE_SKIPPING(restart))
2927
0
    return 0;
2928
  /* If we've reached the endpoint, exit. */
2929
48
  if (STOPPING_FOR_RESTART(restart))
2930
0
    return 0;
2931
2932
  /* reserve space for the list mark */
2933
48
  if (box->list_item && box->s.layout.y == box->s.layout.b)
2934
0
  {
2935
0
    float delta = fz_from_css_number_scale(style->line_height, em);
2936
0
    box->s.layout.b += delta;
2937
0
    ld->used[B] += delta;
2938
0
  }
2939
2940
48
  if (layout_block_page_break(ctx, ld, &ld->used[B], style->page_break_after))
2941
0
  {
2942
    /* Tricky. We need to restart, but our restart marker should be on
2943
     * the NEXT one. */
2944
0
    if (SHOULD_STOP_FOR_RESTART(restart))
2945
0
      return 1;
2946
0
  }
2947
2948
48
  return 0;
2949
48
}
2950
2951
/* === LAYOUT === */
2952
2953
// Compute new em, padding, border, margin.
2954
// Also compute layout x and w.
2955
static void layout_update_styles(fz_context *ctx, fz_html_box *box, fz_html_box *top)
2956
340
{
2957
340
  float top_em = top->s.layout.em;
2958
340
  float top_baseline = top->s.layout.baseline;
2959
340
  float top_w = top->s.layout.w;
2960
1.76k
  while (box)
2961
1.42k
  {
2962
1.42k
    const fz_css_style *style = box->style;
2963
1.42k
    float em = box->s.layout.em = fz_from_css_number(style->font_size, top_em, top_em, top_em);
2964
2965
1.42k
    if (style->vertical_align == VA_SUPER)
2966
0
      box->s.layout.baseline = top_baseline - top_em / 3;
2967
1.42k
    else if (style->vertical_align == VA_SUB)
2968
0
      box->s.layout.baseline = top_baseline + top_em / 5;
2969
1.42k
    else
2970
1.42k
      box->s.layout.baseline = top_baseline;
2971
2972
1.42k
    if (box->type != BOX_INLINE && box->type != BOX_FLOW)
2973
47
    {
2974
47
      float *margin = box->u.block.margin;
2975
47
      float *border = box->u.block.border;
2976
47
      float *padding = box->u.block.padding;
2977
2978
47
      margin[T] = fz_from_css_number(style->margin[T], em, top_w, 0);
2979
47
      margin[R] = fz_from_css_number(style->margin[R], em, top_w, 0);
2980
47
      margin[B] = fz_from_css_number(style->margin[B], em, top_w, 0);
2981
47
      margin[L] = fz_from_css_number(style->margin[L], em, top_w, 0);
2982
2983
47
      padding[T] = fz_from_css_number(style->padding[T], em, top_w, 0);
2984
47
      padding[R] = fz_from_css_number(style->padding[R], em, top_w, 0);
2985
47
      padding[B] = fz_from_css_number(style->padding[B], em, top_w, 0);
2986
47
      padding[L] = fz_from_css_number(style->padding[L], em, top_w, 0);
2987
2988
47
      border[T] = style->border_style_0 ? fz_from_css_number(style->border_width[T], em, top_w, 0) : 0;
2989
47
      border[R] = style->border_style_1 ? fz_from_css_number(style->border_width[R], em, top_w, 0) : 0;
2990
47
      border[B] = style->border_style_2 ? fz_from_css_number(style->border_width[B], em, top_w, 0) : 0;
2991
47
      border[L] = style->border_style_3 ? fz_from_css_number(style->border_width[L], em, top_w, 0) : 0;
2992
2993
      // TODO: BLOCK nested inside TABLE!
2994
47
      if (box->type == BOX_BLOCK || box->type == BOX_TABLE)
2995
47
      {
2996
        /* Compute preliminary width (will be adjusted if block is nested in table cell later) */
2997
47
        float auto_w = top_w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
2998
47
        box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
2999
47
      }
3000
47
    }
3001
3002
1.37k
    else if (box->type == BOX_FLOW)
3003
44
    {
3004
44
      box->s.layout.x = top->s.layout.x;
3005
44
      box->s.layout.w = top->s.layout.w;
3006
44
    }
3007
3008
1.42k
    if (box->down)
3009
339
      layout_update_styles(ctx, box->down, box);
3010
3011
1.42k
    box = box->next;
3012
1.42k
  }
3013
340
}
3014
3015
static void layout_update_widths(fz_context *ctx, fz_html_box *box, fz_html_box *top, hb_buffer_t *hb_buf)
3016
340
{
3017
1.76k
  while (box)
3018
1.42k
  {
3019
1.42k
    if (box->type == BOX_FLOW)
3020
44
    {
3021
44
      fz_html_flow *node;
3022
3023
36.7k
      for (node = box->u.flow.head; node; node = node->next)
3024
36.7k
      {
3025
36.7k
        if (node->type == FLOW_IMAGE)
3026
0
        {
3027
          /* start with "native" size (only used for table width calculations) */
3028
0
          node->w = node->content.image->w * 72.0f / 96.0f;
3029
0
          node->w = fz_from_css_number(node->box->style->width, top->s.layout.em, node->w, node->w);
3030
0
        }
3031
36.7k
        else if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
3032
29.4k
          measure_string_w(ctx, node, hb_buf);
3033
36.7k
      }
3034
44
    }
3035
3036
1.42k
    if (box->down)
3037
339
      layout_update_widths(ctx, box->down, box, hb_buf);
3038
3039
1.42k
    box = box->next;
3040
1.42k
  }
3041
340
}
3042
3043
static int is_layout_box(fz_html_box *box)
3044
4.06k
{
3045
4.06k
  return box->type == BOX_BLOCK || box->type == BOX_TABLE;
3046
4.06k
}
3047
3048
static int is_empty_block_box(fz_html_box *box)
3049
52
{
3050
52
  fz_html_box *child;
3051
52
  if (box->type == BOX_BLOCK)
3052
52
  {
3053
52
    if (box->u.block.padding[T] != 0 || box->u.block.padding[B] != 0)
3054
1
      return 0;
3055
51
    if (box->u.block.border[T] != 0 || box->u.block.border[B] != 0)
3056
0
      return 0;
3057
51
    for (child = box->down; child; child = child->next)
3058
51
    {
3059
51
      if (child->type != BOX_BLOCK)
3060
46
        return 0;
3061
5
      if (!is_empty_block_box(child))
3062
5
        return 0;
3063
0
      if (child->u.block.margin[T] != 0 || child->u.block.margin[B] != 0)
3064
0
        return 0;
3065
0
    }
3066
0
    return 1;
3067
51
  }
3068
0
  return 0;
3069
52
}
3070
3071
static void layout_collapse_margin_with_children(fz_context *ctx, fz_html_box *here)
3072
1.42k
{
3073
1.42k
  fz_html_box *child, *first, *last = NULL;
3074
3075
1.42k
  first = NULL;
3076
2.84k
  for (child = here->down; child; child = child->next)
3077
1.42k
  {
3078
    /* Ignore absolute or fixed position blocks. */
3079
1.42k
    if (child->style->position != POS_FIXED && child->style->position != POS_ABSOLUTE)
3080
1.42k
    {
3081
1.42k
      if (first == NULL)
3082
339
        first = child;
3083
1.42k
      last = child;
3084
1.42k
    }
3085
1.42k
    layout_collapse_margin_with_children(ctx, child);
3086
1.42k
  }
3087
3088
1.42k
  if (here->style->position == POS_FIXED || here->style->position == POS_ABSOLUTE)
3089
0
    return;
3090
3091
1.42k
  if (is_layout_box(here))
3092
47
  {
3093
47
    if (first && is_layout_box(first))
3094
5
    {
3095
5
      if (first->u.block.border[T] == 0 && first->u.block.padding[T] == 0)
3096
5
      {
3097
5
        float m = fz_max(first->u.block.margin[T], here->u.block.margin[T]);
3098
5
        here->u.block.margin[T] = m;
3099
5
        first->u.block.margin[T] = 0;
3100
5
      }
3101
5
    }
3102
3103
47
    if (last && is_layout_box(last))
3104
5
    {
3105
5
      if (last->u.block.border[T] == 0 && last->u.block.padding[T] == 0)
3106
5
      {
3107
5
        float m = fz_max(last->u.block.margin[B], here->u.block.margin[B]);
3108
5
        here->u.block.margin[B] = m;
3109
5
        last->u.block.margin[B] = 0;
3110
5
      }
3111
5
    }
3112
47
  }
3113
1.42k
}
3114
3115
static void layout_collapse_margin_with_siblings(fz_context *ctx, fz_html_box *here)
3116
340
{
3117
340
  fz_html_box *prev = NULL;
3118
3119
1.76k
  while (here)
3120
1.42k
  {
3121
1.42k
    if (here->down)
3122
339
      layout_collapse_margin_with_siblings(ctx, here->down);
3123
3124
1.42k
    if (here->style->position != POS_ABSOLUTE && here->style->position != POS_FIXED)
3125
1.42k
    {
3126
1.42k
      if (prev && is_layout_box(prev) && is_layout_box(here))
3127
39
      {
3128
39
        float m = fz_max(prev->u.block.margin[B], here->u.block.margin[T]);
3129
39
        prev->u.block.margin[B] = m;
3130
39
        here->u.block.margin[T] = 0;
3131
39
      }
3132
1.42k
      prev = here;
3133
1.42k
    }
3134
1.42k
    here = here->next;
3135
1.42k
  }
3136
340
}
3137
3138
static void layout_collapse_margin_with_self(fz_context *ctx, fz_html_box *here)
3139
340
{
3140
1.76k
  while (here)
3141
1.42k
  {
3142
1.42k
    if (here->down)
3143
339
      layout_collapse_margin_with_self(ctx, here->down);
3144
3145
1.42k
    if (is_layout_box(here) && is_empty_block_box(here))
3146
0
    {
3147
0
      float m = fz_max(here->u.block.margin[T], here->u.block.margin[B]);
3148
0
      here->u.block.margin[T] = 0;
3149
0
      here->u.block.margin[B] = m;
3150
0
    }
3151
3152
1.42k
    here = here->next;
3153
1.42k
  }
3154
340
}
3155
3156
static void layout_collapse_margins(fz_context *ctx, fz_html_box *box, fz_html_box *top)
3157
1
{
3158
  // Vertical margins are collapsed in these cases:
3159
  // 1) Adjacent siblings
3160
  // 2) No content separating parent and descendants. The margin ends up outside the parent.
3161
  // 3) Empty blocks
3162
3163
1
  layout_collapse_margin_with_self(ctx, box);
3164
1
  layout_collapse_margin_with_children(ctx, box);
3165
1
  layout_collapse_margin_with_siblings(ctx, box);
3166
1
}
3167
3168
void
3169
fz_restartable_layout_html(fz_context *ctx, fz_html_tree *tree, float start_x, float start_y, float page_w, float page_h, float em, fz_html_restarter *restart)
3170
1
{
3171
1
  int unlocked = 0;
3172
1
  layout_data ld = { 0 };
3173
1
  fz_html_box *box = tree->root;
3174
3175
1
  fz_var(ld.hb_buf);
3176
1
  fz_var(unlocked);
3177
3178
  // nothing to layout
3179
1
  if (!box->down)
3180
0
  {
3181
0
    fz_warn(ctx, "html: nothing to layout");
3182
0
    box->s.layout.em = em;
3183
0
    box->s.layout.baseline = 0;
3184
0
    box->s.layout.x = start_x;
3185
0
    box->s.layout.w = page_w;
3186
0
    box->s.layout.y = start_y;
3187
0
    box->s.layout.b = start_y;
3188
0
    return;
3189
0
  }
3190
3191
1
  fz_hb_lock(ctx);
3192
3193
2
  fz_try(ctx)
3194
2
  {
3195
1
    Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
3196
1
    ld.hb_buf = hb_buffer_create();
3197
1
    Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
3198
1
    unlocked = 1;
3199
1
    fz_hb_unlock(ctx);
3200
3201
1
    ld.restart = restart;
3202
1
    ld.page[T] = start_y;
3203
1
    ld.page[R] = start_x + page_w;
3204
1
    ld.page[B] = start_y + page_h;
3205
1
    ld.page[L] = start_x;
3206
1
    TRBLCPY(ld.ancestor, ld.page);
3207
1
    TRBLCPY(ld.bounds, ld.page);
3208
1
    ld.pool = tree->pool;
3209
1
    if (restart)
3210
0
      restart->potential = NULL;
3211
3212
    // Update em/margin/padding/border if necessary.
3213
1
    if (box->s.layout.em != em || box->s.layout.x != start_x || box->s.layout.w != page_w)
3214
1
    {
3215
1
      box->s.layout.em = em;
3216
1
      box->s.layout.baseline = 0;
3217
1
      box->s.layout.x = start_x;
3218
1
      box->s.layout.w = page_w;
3219
1
      layout_update_styles(ctx, box->down, box);
3220
1
      layout_update_widths(ctx, box->down, box, ld.hb_buf);
3221
1
      layout_collapse_margins(ctx, box->down, box);
3222
1
    }
3223
3224
1
    assert(box->type == BOX_BLOCK);
3225
1
    layout_block(ctx, &ld, box);
3226
1
  }
3227
2
  fz_always(ctx)
3228
1
  {
3229
1
    if (unlocked)
3230
1
      fz_hb_lock(ctx);
3231
1
    hb_buffer_destroy(ld.hb_buf);
3232
1
    fz_hb_unlock(ctx);
3233
1
  }
3234
1
  fz_catch(ctx)
3235
0
  {
3236
0
    fz_rethrow(ctx);
3237
0
  }
3238
1
}
3239
3240
void
3241
fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
3242
1
{
3243
  /* Override requested page size if the meta viewport defines a width or height */
3244
1
  w = fz_max(w, html->meta_w);
3245
1
  h = fz_max(h, html->meta_h);
3246
3247
  /* If we're already laid out to the specifications we need,
3248
   * nothing to do. */
3249
1
  if (html->layout_w == w && html->layout_h == h && html->layout_em == em)
3250
0
    return;
3251
3252
1
  html->page_margin[T] = fz_from_css_number(html->tree.root->style->margin[T], em, em, 0);
3253
1
  html->page_margin[B] = fz_from_css_number(html->tree.root->style->margin[B], em, em, 0);
3254
1
  html->page_margin[L] = fz_from_css_number(html->tree.root->style->margin[L], em, em, 0);
3255
1
  html->page_margin[R] = fz_from_css_number(html->tree.root->style->margin[R], em, em, 0);
3256
3257
1
  html->page_w = w - html->page_margin[L] - html->page_margin[R];
3258
1
  if (html->page_w <= 72)
3259
0
    html->page_w = 72; /* enforce a minimum page size! */
3260
1
  if (h > 0)
3261
1
  {
3262
1
    html->page_h = h - html->page_margin[T] - html->page_margin[B];
3263
1
    if (html->page_h <= 72)
3264
0
      html->page_h = 72; /* enforce a minimum page size! */
3265
1
  }
3266
0
  else
3267
0
  {
3268
    /* h 0 means no pagination */
3269
0
    html->page_h = 0;
3270
0
  }
3271
3272
1
  fz_restartable_layout_html(ctx, &html->tree, 0, 0, html->page_w, html->page_h, em, NULL);
3273
3274
1
  if (h == 0)
3275
0
    html->page_h = html->tree.root->s.layout.b;
3276
3277
  /* Remember how we're laid out so we can avoid needless
3278
   * relayouts in future. */
3279
1
  html->layout_w = w;
3280
1
  html->layout_h = h;
3281
1
  html->layout_em = em;
3282
3283
1
#ifndef NDEBUG
3284
1
  if (fz_atoi(getenv("FZ_DEBUG_HTML")))
3285
0
    fz_debug_html(ctx, html->tree.root);
3286
1
#endif
3287
1
}
3288
3289
/* === DRAW === */
3290
3291
static void draw_rect(fz_context *ctx, fz_device *dev, fz_matrix ctm, float page_top, fz_css_color color, float x0, float y0, float x1, float y1)
3292
11.2k
{
3293
11.2k
  float rgb[3];
3294
11.2k
  fz_path *path;
3295
3296
11.2k
  if (color.a <= 0)
3297
175
    return;
3298
3299
11.1k
  path = fz_new_path(ctx);
3300
3301
22.2k
  fz_try(ctx)
3302
22.2k
  {
3303
11.1k
    fz_moveto(ctx, path, x0, y0 - page_top);
3304
11.1k
    fz_lineto(ctx, path, x1, y0 - page_top);
3305
11.1k
    fz_lineto(ctx, path, x1, y1 - page_top);
3306
11.1k
    fz_lineto(ctx, path, x0, y1 - page_top);
3307
11.1k
    fz_closepath(ctx, path);
3308
3309
11.1k
    rgb[0] = color.r / 255.0f;
3310
11.1k
    rgb[1] = color.g / 255.0f;
3311
11.1k
    rgb[2] = color.b / 255.0f;
3312
3313
11.1k
    fz_fill_path(ctx, dev, path, 0, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
3314
11.1k
  }
3315
22.2k
  fz_always(ctx)
3316
11.1k
    fz_drop_path(ctx, path);
3317
11.1k
  fz_catch(ctx)
3318
0
    fz_rethrow(ctx);
3319
11.1k
}
3320
3321
static void
3322
fudge_len(float *wp, float len, int count, float extra)
3323
0
{
3324
  /* We have a line of length len, in which we have to fit n*count+extra sections
3325
   * for some integer n, n > 1. We'd like to find n s.t. each of those sections is
3326
   * approximately w.
3327
   *
3328
   * So, ideally len = (n*count+extra)*w
3329
   * (len/w - extra)/count = n
3330
   * ((len - w*extra)/count/w = n
3331
   */
3332
0
  float w = *wp;
3333
0
  int n = 0;
3334
3335
0
  assert(w > 0 && count > 0);
3336
3337
0
  if (len > w*extra)
3338
0
    n = ((len - w*extra) + (count*w)/2) / (count*w);
3339
0
  if (n < 1)
3340
0
    n = 1;
3341
3342
0
  *wp = len / (n * count + extra);
3343
0
}
3344
3345
static void draw_dotted_line(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_css_color color, int dashed, float w,
3346
  float x0, float y0, float x1, float y1)
3347
0
{
3348
0
  float rgb[3];
3349
0
  fz_path *path;
3350
0
  fz_stroke_state *stroke;
3351
0
  float len;
3352
3353
0
  if (color.a <= 0 || w <= 0)
3354
0
    return;
3355
3356
  /* Rely on the fact that we only draw othogonal lines here! */
3357
0
  len = x1 - x0 + y1 - y0;
3358
0
  if (len < 0)
3359
0
    len = -len;
3360
3361
0
  path = fz_new_path(ctx);
3362
0
  stroke = NULL;
3363
3364
0
  fz_var(stroke);
3365
3366
0
  fz_try(ctx)
3367
0
  {
3368
0
    fz_moveto(ctx, path, x0, y0);
3369
0
    fz_lineto(ctx, path, x1, y1);
3370
3371
0
    rgb[0] = color.r / 255.0f;
3372
0
    rgb[1] = color.g / 255.0f;
3373
0
    rgb[2] = color.b / 255.0f;
3374
3375
0
    stroke = fz_new_stroke_state_with_dash_len(ctx, 2);
3376
0
    stroke->linewidth = w;
3377
0
    stroke->dash_len = 2;
3378
0
    if (dashed)
3379
0
    {
3380
0
      fudge_len(&w, len, 4, 1);
3381
0
      stroke->start_cap = FZ_LINECAP_SQUARE;
3382
0
      stroke->dash_cap = FZ_LINECAP_SQUARE;
3383
0
      stroke->end_cap = FZ_LINECAP_SQUARE;
3384
0
      stroke->dash_list[0] = w;
3385
0
      stroke->dash_list[1] = w*3;
3386
0
    }
3387
0
    else
3388
0
    {
3389
0
      fudge_len(&w, len, 2, 0.01f);
3390
0
      stroke->start_cap = FZ_LINECAP_ROUND;
3391
0
      stroke->dash_cap = FZ_LINECAP_ROUND;
3392
0
      stroke->end_cap = FZ_LINECAP_ROUND;
3393
0
      stroke->dash_list[0] = 0;
3394
0
      stroke->dash_list[1] = w*2;
3395
0
    }
3396
3397
0
    fz_stroke_path(ctx, dev, path, stroke, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
3398
0
  }
3399
0
  fz_always(ctx)
3400
0
  {
3401
0
    fz_drop_stroke_state(ctx, stroke);
3402
0
    fz_drop_path(ctx, path);
3403
0
  }
3404
0
  fz_catch(ctx)
3405
0
    fz_rethrow(ctx);
3406
0
}
3407
3408
static void draw_quad(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_css_color color,
3409
  float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3)
3410
0
{
3411
0
  float rgb[3];
3412
0
  fz_path *path;
3413
3414
0
  if (color.a <= 0)
3415
0
    return;
3416
3417
0
  path = fz_new_path(ctx);
3418
3419
0
  fz_try(ctx)
3420
0
  {
3421
0
    fz_moveto(ctx, path, x0, y0);
3422
0
    fz_lineto(ctx, path, x1, y1);
3423
0
    fz_lineto(ctx, path, x2, y2);
3424
0
    fz_lineto(ctx, path, x3, y3);
3425
0
    fz_closepath(ctx, path);
3426
3427
0
    rgb[0] = color.r / 255.0f;
3428
0
    rgb[1] = color.g / 255.0f;
3429
0
    rgb[2] = color.b / 255.0f;
3430
3431
0
    fz_fill_path(ctx, dev, path, 0, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
3432
0
  }
3433
0
  fz_always(ctx)
3434
0
    fz_drop_path(ctx, path);
3435
0
  fz_catch(ctx)
3436
0
    fz_rethrow(ctx);
3437
0
}
3438
3439
typedef struct
3440
{
3441
  float rgb[3];
3442
  float a;
3443
} unpacked_color;
3444
3445
static inline unpacked_color
3446
unpack_color(const fz_css_color src)
3447
390
{
3448
390
  unpacked_color dst;
3449
390
  dst.rgb[0] = src.r / 255.0f;
3450
390
  dst.rgb[1] = src.g / 255.0f;
3451
390
  dst.rgb[2] = src.b / 255.0f;
3452
390
  dst.a = src.a / 255.0f;
3453
390
  return dst;
3454
390
}
3455
3456
static inline int
3457
color_eq(fz_css_color a, fz_css_color b)
3458
52.8k
{
3459
52.8k
  return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
3460
52.8k
}
3461
3462
static const fz_css_color transparent = { 0, 0, 0, 0 };
3463
3464
static fz_css_color
3465
find_inline_background_color(fz_context *ctx, fz_html_box *box)
3466
26.5k
{
3467
48.7k
  while (box && box->type == BOX_INLINE)
3468
33.2k
  {
3469
33.2k
    if (box->style->background_color.a > 0)
3470
11.0k
      return box->style->background_color;
3471
22.2k
    box = box->up;
3472
22.2k
  }
3473
15.4k
  return (fz_css_color){ 0, 0, 0, 0 };
3474
26.5k
}
3475
3476
static fz_css_color
3477
brighten(fz_css_color c)
3478
0
{
3479
0
  int delta;
3480
0
  float S;
3481
0
  float C;
3482
0
  float H;
3483
3484
0
  if (c.r > c.g)
3485
0
  {
3486
0
    if (c.b > c.r)
3487
0
    {
3488
      /* b  > r > g */
3489
      /* All 6 cases are very similar, we'll just comment the first one. */
3490
0
      delta = c.b - c.g; /* Never zero */
3491
0
      S = delta/(float)c.b;
3492
      /* A proper conversion from RGB -> HSV would map H into a sextant;
3493
       * given we're going to convert back, we don't bother. */
3494
0
      H = (c.r - c.g)/(float)delta; /* Proper H would be: 4 <= H < 5 */
3495
0
      c.b = (c.b>>1) + 128;
3496
      /* v = cmax = c.b */
3497
0
      C = c.b * S;
3498
      /* m = cmax - C */
3499
0
      c.g = c.b - C;
3500
0
      c.r = c.g + C * H;
3501
0
      c.b = c.g + C;
3502
0
    }
3503
0
    else if (c.b > c.g)
3504
0
    {
3505
      /* r >= b > g */
3506
0
      delta = c.r - c.g; /* Never zero */
3507
0
      S = delta/(float)c.r;
3508
0
      H = (c.b - c.g)/(float)delta; /* Proper H would be: 5 <= H < 6 */
3509
0
      c.r = (c.r>>1) + 128;
3510
0
      C = c.r * S;
3511
0
      c.g = c.r - C;
3512
0
      c.b = c.g + C * H;
3513
0
      c.r = c.g + C;
3514
0
    }
3515
0
    else
3516
0
    {
3517
      /* r > g >= b */
3518
0
      delta = c.r - c.b; /* Never zero */
3519
0
      S = delta/(float)c.r;
3520
0
      H = (c.g - c.b)/(float)delta; /* Proper H would be: 0 <= H < 1 */
3521
0
      c.r = (c.r>>1) + 128;
3522
0
      C = c.r * S;
3523
0
      c.b = c.r - C;
3524
0
      c.g = c.b + C * H;
3525
0
      c.r = c.b + C;
3526
0
    }
3527
0
  }
3528
0
  else
3529
0
  {
3530
0
    if (c.b > c.g)
3531
0
    {
3532
      /* b > g >= r */
3533
0
      delta = c.b - c.r; /* Never zero */
3534
0
      S = delta/(float)c.b;
3535
0
      H = (c.g - c.r)/(float)delta; /* Proper H would be: 3 <= H < 4 */
3536
0
      c.b = (c.b>>1) + 128;
3537
0
      C = c.b * S;
3538
0
      c.r = c.b - C;
3539
0
      c.g = c.r + C * H;
3540
0
      c.b = c.r + C;
3541
0
    }
3542
0
    else if (c.r > c.b)
3543
0
    {
3544
      /* g >= r > b */
3545
0
      delta = c.g - c.b; /* Never zero */
3546
0
      S = delta/(float)c.g;
3547
0
      H = (c.r - c.b)/(float)delta; /* Proper H would be: 1 <= H < 2 */
3548
0
      c.g = (c.g>>1) + 128;
3549
0
      C = c.g * S;
3550
0
      c.b = c.g - C;
3551
0
      c.r = c.b + C * H;
3552
0
      c.g = c.b + C;
3553
0
    }
3554
0
    else if (c.g == c.b && c.g == c.r)
3555
0
    {
3556
      /* r = g = b */
3557
0
      H = 0;
3558
0
      c.g = (c.g>>1) + 128;
3559
0
      c.r = c.g;
3560
0
      c.b = c.g;
3561
0
    }
3562
0
    else
3563
0
    {
3564
      /* g >= b >= r */
3565
0
      delta = c.g - c.r; /* Never zero */
3566
0
      S = delta/(float)c.g;
3567
0
      H = (c.b - c.r)/(float)delta; /* Proper H would be: 2 <= H < 3 */
3568
0
      c.g = (c.g>>1) + 128;
3569
0
      C = c.g * S;
3570
0
      c.r = c.g - C;
3571
0
      c.b = c.r + C * H;
3572
0
      c.g = c.r + C;
3573
0
    }
3574
0
  }
3575
3576
0
  return c;
3577
0
}
3578
3579
static fz_css_color
3580
darken(fz_css_color c)
3581
0
{
3582
0
  int delta;
3583
0
  float S;
3584
0
  float C;
3585
0
  float H;
3586
3587
0
  if (c.r > c.g)
3588
0
  {
3589
0
    if (c.b > c.r)
3590
0
    {
3591
      /* b  > r > g */
3592
      /* All 6 cases are very similar, we'll just comment the first one. */
3593
0
      delta = c.b - c.g; /* Never zero */
3594
0
      S = delta/(float)c.b;
3595
      /* A proper conversion from RGB -> HSV would map H into a sextant;
3596
       * given we're going to convert back, we don't bother. */
3597
0
      H = (c.r - c.g)/(float)delta; /* Proper H would be: 4 <= H < 5 */
3598
0
      c.b >>= 1;
3599
      /* v = cmax = c.b */
3600
0
      C = c.b * S;
3601
      /* m = cmax - C */
3602
0
      c.g = c.b - C;
3603
0
      c.r = c.g + C * H;
3604
0
      c.b = c.g + C;
3605
0
    }
3606
0
    else if (c.b > c.g)
3607
0
    {
3608
      /* r >= b > g */
3609
0
      delta = c.r - c.g; /* Never zero */
3610
0
      S = delta/(float)c.r;
3611
0
      H = (c.b - c.g)/(float)delta; /* Proper H would be: 5 <= H < 6 */
3612
0
      c.r >>= 1;
3613
0
      C = c.r * S;
3614
0
      c.g = c.r - C;
3615
0
      c.b = c.g + C * H;
3616
0
      c.r = c.g + C;
3617
0
    }
3618
0
    else
3619
0
    {
3620
      /* r > g >= b */
3621
0
      delta = c.r - c.b; /* Never zero */
3622
0
      S = delta/(float)c.r;
3623
0
      H = (c.g - c.b)/(float)delta; /* Proper H would be: 0 <= H < 1 */
3624
0
      c.r >>= 1;
3625
0
      C = c.r * S;
3626
0
      c.b = c.r - C;
3627
0
      c.g = c.b + C * H;
3628
0
      c.r = c.b + C;
3629
0
    }
3630
0
  }
3631
0
  else
3632
0
  {
3633
0
    if (c.b > c.g)
3634
0
    {
3635
      /* b > g >= r */
3636
0
      delta = c.b - c.r; /* Never zero */
3637
0
      S = delta/(float)c.b;
3638
0
      H = (c.g - c.r)/(float)delta; /* Proper H would be: 3 <= H < 4 */
3639
0
      c.b >>= 1;
3640
0
      C = c.b * S;
3641
0
      c.r = c.b - C;
3642
0
      c.g = c.r + C * H;
3643
0
      c.b = c.r + C;
3644
0
    }
3645
0
    else if (c.r > c.b)
3646
0
    {
3647
      /* g >= r > b */
3648
0
      delta = c.g - c.b; /* Never zero */
3649
0
      S = delta/(float)c.g;
3650
0
      H = (c.r - c.b)/(float)delta; /* Proper H would be: 1 <= H < 2 */
3651
0
      c.g >>= 1;
3652
0
      C = c.g * S;
3653
0
      c.b = c.g - C;
3654
0
      c.r = c.b + C * H;
3655
0
      c.g = c.b + C;
3656
0
    }
3657
0
    else if (c.g == c.b && c.g == c.r)
3658
0
    {
3659
      /* r = g = b */
3660
0
      H = 0;
3661
0
      c.g >>= 1;
3662
0
      c.r = c.g;
3663
0
      c.b = c.g;
3664
0
    }
3665
0
    else
3666
0
    {
3667
      /* g >= b >= r */
3668
0
      delta = c.g - c.r; /* Never zero */
3669
0
      S = delta/(float)c.g;
3670
0
      H = (c.b - c.r)/(float)delta; /* Proper H would be: 2 <= H < 3 */
3671
0
      c.g >>= 1;
3672
0
      C = c.g * S;
3673
0
      c.r = c.g - C;
3674
0
      c.b = c.r + C * H;
3675
0
      c.g = c.r + C;
3676
0
    }
3677
0
  }
3678
3679
0
  return c;
3680
0
}
3681
3682
static int collapse_border_style(int style, int collapsed)
3683
42
{
3684
42
  if (collapsed)
3685
0
  {
3686
0
    if (style == BS_INSET)
3687
0
      style = BS_RIDGE;
3688
0
    if (style == BS_OUTSET)
3689
0
      style = BS_GROOVE;
3690
0
  }
3691
42
  return style;
3692
42
}
3693
3694
static void
3695
draw_border(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html_box *box, int edge, float x0, float y0, float x1, float y1)
3696
42
{
3697
42
  float *border = box->u.block.border;
3698
42
  const fz_css_style *style = box->style;
3699
42
  int collapsed = box->collapsed_cell;
3700
3701
42
  switch (edge)
3702
42
  {
3703
0
  case T:
3704
0
    switch (collapse_border_style(style->border_style_0, collapsed))
3705
0
    {
3706
0
    case BS_NONE:
3707
0
      return; /* Should never happen */
3708
0
    case BS_OUTSET:
3709
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[T]), x0 - border[L], y0 - border[T], x0, y0, x1, y0, x1 + border[R], y0 - border[T]);
3710
0
      break;
3711
0
    case BS_INSET:
3712
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[T]), x0 - border[L], y0 - border[T], x0, y0, x1, y0, x1 + border[R], y0 - border[T]);
3713
0
      break;
3714
0
    case BS_RIDGE:
3715
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[T]), x0 - border[L], y0 - border[T], x0 - border[L]/2, y0 - border[T]/2, x1 + border[R]/2, y0 - border[T]/2, x1 + border[R], y0 - border[T]);
3716
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[T]), x0 - border[L]/2, y0 - border[T]/2, x0, y0, x1, y0, x1 + border[R]/2, y0 - border[T]/2);
3717
0
      break;
3718
0
    case BS_GROOVE:
3719
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[T]), x0 - border[L], y0 - border[T], x0 - border[L]/2, y0 - border[T]/2, x1 + border[R]/2, y0 - border[T]/2, x1 + border[R], y0 - border[T]);
3720
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[T]), x0 - border[L]/2, y0 - border[T]/2, x0, y0, x1, y0, x1 + border[R]/2, y0 - border[T]/2);
3721
0
      break;
3722
0
    case BS_DOUBLE:
3723
0
      draw_quad(ctx, dev, ctm, style->border_color[T], x0 - border[L], y0 - border[T], x0 - 2*border[L]/3, y0 - 2*border[T]/3, x1 + 2*border[R]/3, y0 - 2*border[T]/3, x1 + border[R], y0 - border[T]);
3724
0
      draw_quad(ctx, dev, ctm, style->border_color[T], x0 - border[L]/3, y0 - border[T]/3, x0, y0, x1, y0, x1 + border[R]/3, y0 - border[T]/3);
3725
0
      break;
3726
0
    case BS_DOTTED:
3727
0
    {
3728
0
      float left = border[L] - border[T]/2;
3729
0
      float right = border[R] - border[T]/2;
3730
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[T], 0, border[T], x0 - left, y0 - border[T]/2, x1 + right, y0 - border[T]/2);
3731
0
      break;
3732
0
    }
3733
0
    case BS_DASHED:
3734
0
    {
3735
0
      float left = border[L] - border[T]/2;
3736
0
      float right = border[R] - border[T]/2;
3737
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[T], 1, border[T], x0 - left, y0 - border[T]/2, x1 + right, y0 - border[T]/2);
3738
0
      break;
3739
0
    }
3740
0
    case BS_SOLID:
3741
0
      draw_rect(ctx, dev, ctm, 0, style->border_color[T], x0 - border[L], y0 - border[T], x1 + border[R], y0);
3742
0
      break;
3743
0
    }
3744
0
    break;
3745
0
  case R:
3746
0
    switch (collapse_border_style(style->border_style_1, collapsed))
3747
0
    {
3748
0
    case BS_NONE:
3749
0
      return; /* Should never happen */
3750
0
    case BS_OUTSET:
3751
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[R]), x1 + border[R], y0 - border[T], x1, y0, x1, y1, x1 + border[R], y1 + border[B]);
3752
0
      break;
3753
0
    case BS_INSET:
3754
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[R]), x1 + border[R], y0 - border[T], x1, y0, x1, y1, x1 + border[R], y1 + border[B]);
3755
0
      break;
3756
0
    case BS_RIDGE:
3757
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[R]), x1 + border[R], y0 - border[T], x1 + border[R]/2, y0 - border[T]/2, x1 + border[R]/2, y1 + border[B]/2, x1 + border[R], y1 + border[B]);
3758
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[R]), x1 + border[R]/2, y0 - border[T]/2, x1, y0, x1, y1, x1 + border[R]/2, y1 + border[B]/2);
3759
0
      break;
3760
0
    case BS_GROOVE:
3761
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[R]), x1 + border[R], y0 - border[T], x1 + border[R]/2, y0 - border[T]/2, x1 + border[R]/2, y1 + border[B]/2, x1 + border[R], y1 + border[B]);
3762
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[R]), x1 + border[R]/2, y0 - border[T]/2, x1, y0, x1, y1, x1 + border[R]/2, y1 + border[B]/2);
3763
0
      break;
3764
0
    case BS_DOUBLE:
3765
0
      draw_quad(ctx, dev, ctm, style->border_color[R], x1 + border[R], y0 - border[T], x1 + 2*border[R]/3, y0 - 2*border[T]/3, x1 + 2*border[R]/3, y1 + 2*border[B]/3, x1 + border[R], y1 + border[B]);
3766
0
      draw_quad(ctx, dev, ctm, style->border_color[R], x1 + border[R]/3, y0 - border[T]/3, x1, y0, x1, y1, x1 + border[R]/3, y1 + border[B]/3);
3767
0
      break;
3768
0
    case BS_DOTTED:
3769
0
    {
3770
0
      float top = border[T] - border[R]/2;
3771
0
      float bottom = border[B] - border[R]/2;
3772
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[R], 0, border[R], x1 + border[R]/2, y0 - top, x1 + border[R]/2, y1 + bottom);
3773
0
      break;
3774
0
    }
3775
0
    case BS_DASHED:
3776
0
    {
3777
0
      float top = border[T] - border[R]/2;
3778
0
      float bottom = border[B] - border[R]/2;
3779
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[R], 1, border[R], x1 + border[R]/2, y0 - top, x1 + border[R]/2, y1 + bottom);
3780
0
      break;
3781
0
    }
3782
0
    case BS_SOLID:
3783
0
      draw_rect(ctx, dev, ctm, 0, style->border_color[R], x1, y0 - border[T], x1 + border[R], y1 + border[B]);
3784
0
      break;
3785
0
    }
3786
0
    break;
3787
1
  case B:
3788
1
    switch (collapse_border_style(style->border_style_2, collapsed))
3789
1
    {
3790
0
    case BS_NONE:
3791
0
      return; /* Should never happen */
3792
0
    case BS_OUTSET:
3793
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[B]), x0 - border[L], y1 + border[B], x0, y1, x1, y1, x1 + border[R], y1 + border[B]);
3794
0
      break;
3795
0
    case BS_INSET:
3796
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[B]), x0 - border[L], y1 + border[B], x0, y1, x1, y1, x1 + border[R], y1 + border[B]);
3797
0
      break;
3798
0
    case BS_RIDGE:
3799
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[B]), x0 - border[L], y1 + border[B], x0 - border[L]/2, y1 + border[B]/2, x1 + border[R]/2, y1 + border[B]/2, x1 + border[R], y1 + border[B]);
3800
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[B]), x0 - border[L]/2, y1 + border[B]/2, x0, y1, x1, y1, x1 + border[R]/2, y1 + border[B]/2);
3801
0
      break;
3802
0
    case BS_GROOVE:
3803
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[B]), x0 - border[L], y1 + border[B], x0 - border[L]/2, y1 + border[B]/2, x1 + border[R]/2, y1 + border[B]/2, x1 + border[R], y1 + border[B]);
3804
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[B]), x0 - border[L]/2, y1 + border[B]/2, x0, y1, x1, y1, x1 + border[R]/2, y1 + border[B]/2);
3805
0
      break;
3806
0
    case BS_DOUBLE:
3807
0
      draw_quad(ctx, dev, ctm, style->border_color[B], x0 - border[L], y1 + border[B], x0 - 2*border[L]/3, y1 + 2*border[B]/3, x1 + 2*border[R]/3, y1 + 2*border[B]/3, x1 + border[R], y1 + border[B]);
3808
0
      draw_quad(ctx, dev, ctm, style->border_color[B], x0 - border[L]/3, y1 + border[B]/3, x0, y1, x1, y1, x1 + border[R]/3, y1 + border[B]/3);
3809
0
      break;
3810
0
    case BS_DOTTED:
3811
0
    {
3812
0
      float left = border[L] - border[B]/2;
3813
0
      float right = border[R] - border[B]/2;
3814
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[B], 0, border[B], x1 + right, y1 + border[B]/2, x0 - left, y1 + border[B]/2);
3815
0
      break;
3816
0
    }
3817
0
    case BS_DASHED:
3818
0
    {
3819
0
      float left = border[L] - border[B]/2;
3820
0
      float right = border[R] - border[B]/2;
3821
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[B], 1, border[B], x1 + right, y1 + border[B]/2, x0 - left, y1 + border[B]/2);
3822
0
      break;
3823
0
    }
3824
1
    case BS_SOLID:
3825
1
      draw_rect(ctx, dev, ctm, 0, style->border_color[B], x0 - border[L], y1, x1 + border[R], y1 + border[B]);
3826
1
      break;
3827
1
    }
3828
1
    break;
3829
41
  case L:
3830
41
    switch (collapse_border_style(style->border_style_3, collapsed))
3831
41
    {
3832
0
    case BS_NONE:
3833
0
      return; /* Should never happen */
3834
0
    case BS_OUTSET:
3835
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[L]), x0 - border[L], y0 - border[T], x0, y0, x0, y1, x0 - border[L], y1 + border[B]);
3836
0
      break;
3837
0
    case BS_INSET:
3838
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[L]), x0 - border[L], y0 - border[T], x0, y0, x0, y1, x0 - border[L], y1 + border[B]);
3839
0
      break;
3840
0
    case BS_RIDGE:
3841
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[L]), x0 - border[L], y0 - border[T], x0 - border[L]/2, y0 - border[T]/2, x0 - border[L]/2, y1 + border[B]/2, x0 - border[L], y1 + border[B]);
3842
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[L]), x0 - border[L]/2, y0 - border[T]/2, x0, y0, x0, y1, x0 - border[L]/2, y1 + border[B]/2);
3843
0
      break;
3844
0
    case BS_GROOVE:
3845
0
      draw_quad(ctx, dev, ctm, darken(style->border_color[L]), x0 - border[L], y0 - border[T], x0 - border[L]/2, y0 - border[T]/2, x0 - border[L]/2, y1 + border[B]/2, x0 - border[L], y1 + border[B]);
3846
0
      draw_quad(ctx, dev, ctm, brighten(style->border_color[L]), x0 - border[L]/2, y0 - border[T]/2, x0, y0, x0, y1, x0 - border[L]/2, y1 + border[B]/2);
3847
0
      break;
3848
0
    case BS_DOUBLE:
3849
0
      draw_quad(ctx, dev, ctm, style->border_color[L], x0 - border[L], y0 - border[T], x0 - 2*border[L]/3, y0 - 2*border[T]/3, x0 - 2*border[L]/3, y1 + 2*border[B]/3, x0 - border[L], y1 + border[B]);
3850
0
      draw_quad(ctx, dev, ctm, style->border_color[L], x0 - border[L]/3, y0 - border[T]/3, x0, y0, x0, y1, x0 - border[L]/3, y1 + border[B]/3);
3851
0
      break;
3852
0
    case BS_DOTTED:
3853
0
    {
3854
0
      float top = border[T] - border[L]/2;
3855
0
      float bottom = border[B] - border[L]/2;
3856
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[L], 0, border[L], x0 - border[L]/2, y1 + bottom, x0 - border[L]/2, y0 - top);
3857
0
      break;
3858
0
    }
3859
0
    case BS_DASHED:
3860
0
    {
3861
0
      float top = border[T] - border[L]/2;
3862
0
      float bottom = border[B] - border[L]/2;
3863
0
      draw_dotted_line(ctx, dev, ctm, style->border_color[L], 1, border[L], x0 - border[L]/2, y1 + bottom, x0 - border[L]/2, y0 - top);
3864
0
      break;
3865
0
    }
3866
41
    case BS_SOLID:
3867
41
      draw_rect(ctx, dev, ctm, 0, style->border_color[L], x0 - border[L], y0 - border[T], x0, y1 + border[B]);
3868
41
      break;
3869
41
    }
3870
41
    break;
3871
42
  }
3872
42
}
3873
3874
static void
3875
do_borders(fz_context *ctx, fz_device *dev, fz_matrix ctm, float page_top, fz_html_box *box, int suppress)
3876
606
{
3877
606
  float cell_adjust_top = box->type == BOX_TABLE_CELL ? box->u.block.margin[T] : 0;
3878
606
  float cell_adjust_right = box->type == BOX_TABLE_CELL ? box->u.block.margin[R] : 0;
3879
606
  float cell_adjust_bot = box->type == BOX_TABLE_CELL ? box->u.block.margin[B] : 0;
3880
606
  float cell_adjust_left = box->type == BOX_TABLE_CELL ? box->u.block.margin[L] : 0;
3881
606
  float *border = box->u.block.border;
3882
606
  float *padding = box->u.block.padding;
3883
606
  float x0 = box->s.layout.x - padding[L] - cell_adjust_left;
3884
606
  float y0 = box->s.layout.y - padding[T] - page_top - cell_adjust_top;
3885
606
  float x1 = box->s.layout.x + box->s.layout.w + padding[R] + cell_adjust_right;
3886
606
  float y1 = box->s.layout.b + padding[B] - page_top + cell_adjust_bot;
3887
3888
606
  suppress |= box->suppress_border;
3889
3890
606
  if (border[T] > 0 && !(suppress & (1<<T)))
3891
0
    draw_border(ctx, dev, ctm, box, T, x0, y0, x1, y1);
3892
606
  if (border[R] > 0 && !(suppress & (1<<R)))
3893
0
    draw_border(ctx, dev, ctm, box, R, x0, y0, x1, y1);
3894
606
  if (border[B] > 0 && !(suppress & (1<<B)))
3895
1
    draw_border(ctx, dev, ctm, box, B, x0, y0, x1, y1);
3896
606
  if (border[L] > 0 && !(suppress & (1<<L)))
3897
41
    draw_border(ctx, dev, ctm, box, L, x0, y0, x1, y1);
3898
606
}
3899
3900
static const char *roman_uc[3][10] = {
3901
  { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
3902
  { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
3903
  { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
3904
};
3905
3906
static const char *roman_lc[3][10] = {
3907
  { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
3908
  { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
3909
  { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" },
3910
};
3911
3912
static void format_roman_number(fz_context *ctx, char *buf, int size, int n, const char *sym[3][10], const char *sym_m)
3913
0
{
3914
0
  int I = n % 10;
3915
0
  int X = (n / 10) % 10;
3916
0
  int C = (n / 100) % 10;
3917
0
  int M = (n / 1000);
3918
3919
0
  fz_strlcpy(buf, "", size);
3920
0
  while (M--)
3921
0
    fz_strlcat(buf, sym_m, size);
3922
0
  fz_strlcat(buf, sym[2][C], size);
3923
0
  fz_strlcat(buf, sym[1][X], size);
3924
0
  fz_strlcat(buf, sym[0][I], size);
3925
0
  fz_strlcat(buf, ". ", size);
3926
0
}
3927
3928
static void format_alpha_number(fz_context *ctx, char *buf, int size, int n, int alpha, int omega)
3929
0
{
3930
0
  int base = omega - alpha + 1;
3931
0
  int tmp[40];
3932
0
  int i, c;
3933
3934
0
  if (alpha > 256) /* to skip final-s for greek */
3935
0
    --base;
3936
3937
  /* Bijective base-26 (base-24 for greek) numeration */
3938
0
  i = 0;
3939
0
  while (n > 0)
3940
0
  {
3941
0
    --n;
3942
0
    c = n % base + alpha;
3943
0
    if (alpha > 256 && c > alpha + 16) /* skip final-s for greek */
3944
0
      ++c;
3945
0
    tmp[i++] = c;
3946
0
    n /= base;
3947
0
  }
3948
3949
0
  while (i > 0)
3950
0
    buf += fz_runetochar(buf, tmp[--i]);
3951
0
  *buf++ = '.';
3952
0
  *buf++ = ' ';
3953
0
  *buf = 0;
3954
0
}
3955
3956
static void format_list_number(fz_context *ctx, int type, int x, char *buf, int size)
3957
0
{
3958
0
  switch (type)
3959
0
  {
3960
0
  case LST_NONE: fz_strlcpy(buf, "", size); break;
3961
0
  case LST_DISC: fz_snprintf(buf, size, "%C  ", 0x2022); break; /* U+2022 BULLET */
3962
0
  case LST_CIRCLE: fz_snprintf(buf, size, "%C  ", 0x25CB); break; /* U+25CB WHITE CIRCLE */
3963
0
  case LST_SQUARE: fz_snprintf(buf, size, "%C  ", 0x25A0); break; /* U+25A0 BLACK SQUARE */
3964
0
  default:
3965
0
  case LST_DECIMAL: fz_snprintf(buf, size, "%d. ", x); break;
3966
0
  case LST_DECIMAL_ZERO: fz_snprintf(buf, size, "%02d. ", x); break;
3967
0
  case LST_LC_ROMAN: format_roman_number(ctx, buf, size, x, roman_lc, "m"); break;
3968
0
  case LST_UC_ROMAN: format_roman_number(ctx, buf, size, x, roman_uc, "M"); break;
3969
0
  case LST_LC_ALPHA: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
3970
0
  case LST_UC_ALPHA: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
3971
0
  case LST_LC_LATIN: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
3972
0
  case LST_UC_LATIN: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
3973
0
  case LST_LC_GREEK: format_alpha_number(ctx, buf, size, x, 0x03B1, 0x03C9); break;
3974
0
  case LST_UC_GREEK: format_alpha_number(ctx, buf, size, x, 0x0391, 0x03A9); break;
3975
0
  }
3976
0
}
3977
3978
static fz_html_flow *find_list_mark_anchor(fz_context *ctx, fz_html_box *box)
3979
0
{
3980
  /* find first flow node in <li> tag */
3981
0
  while (box)
3982
0
  {
3983
0
    if (box->type == BOX_FLOW)
3984
0
      return box->u.flow.head;
3985
0
    box = box->down;
3986
0
  }
3987
0
  return NULL;
3988
0
}
3989
3990
static void draw_list_mark(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, int n)
3991
0
{
3992
0
  fz_font *font;
3993
0
  fz_text *text;
3994
0
  fz_matrix trm;
3995
0
  fz_html_flow *line;
3996
0
  float y, w;
3997
0
  float color[4];
3998
0
  const char *s;
3999
0
  char buf[40];
4000
0
  int c, g;
4001
4002
0
  trm = fz_scale(box->s.layout.em, -box->s.layout.em);
4003
4004
0
  line = find_list_mark_anchor(ctx, box);
4005
0
  if (line)
4006
0
  {
4007
0
    y = line->y;
4008
0
  }
4009
0
  else
4010
0
  {
4011
0
    float h = fz_from_css_number_scale(box->style->line_height, box->s.layout.em);
4012
0
    float a = box->s.layout.em * 0.8f;
4013
0
    float d = box->s.layout.em * 0.2f;
4014
0
    if (a + d > h)
4015
0
      h = a + d;
4016
0
    y = box->s.layout.y + a + (h - a - d) / 2;
4017
0
  }
4018
4019
0
  if (y > page_bot || y < page_top)
4020
0
    return;
4021
4022
0
  format_list_number(ctx, box->style->list_style_type, n, buf, sizeof buf);
4023
4024
0
  s = buf;
4025
0
  w = 0;
4026
0
  while (*s)
4027
0
  {
4028
0
    s += fz_chartorune(&c, s);
4029
0
    g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
4030
0
    w += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
4031
0
  }
4032
4033
0
  text = fz_new_text(ctx);
4034
4035
0
  fz_try(ctx)
4036
0
  {
4037
0
    s = buf;
4038
0
    trm.e = box->s.layout.x - w;
4039
0
    trm.f = y - page_top;
4040
0
    while (*s)
4041
0
    {
4042
0
      s += fz_chartorune(&c, s);
4043
0
      g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
4044
0
      fz_show_glyph(ctx, text, font, trm, g, c, 0, 0, FZ_BIDI_NEUTRAL, FZ_LANG_UNSET);
4045
0
      trm.e += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
4046
0
    }
4047
4048
0
    color[0] = box->style->color.r / 255.0f;
4049
0
    color[1] = box->style->color.g / 255.0f;
4050
0
    color[2] = box->style->color.b / 255.0f;
4051
0
    color[3] = box->style->color.a / 255.0f;
4052
4053
0
    fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, color[3], fz_default_color_params);
4054
0
  }
4055
0
  fz_always(ctx)
4056
0
    fz_drop_text(ctx, text);
4057
0
  fz_catch(ctx)
4058
0
    fz_rethrow(ctx);
4059
0
}
4060
4061
static void
4062
draw_skipped_bg_and_borders(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, int first_entry)
4063
0
{
4064
0
  float x0, y0, x1, y1;
4065
0
  float *padding;
4066
0
  float cell_padding_top, cell_padding_bot;
4067
4068
0
  if (!box)
4069
0
    return;
4070
4071
0
  draw_skipped_bg_and_borders(ctx, box->up, page_top, page_bot, dev, ctm, first_entry && box->up->down == box);
4072
4073
0
  if (!fz_html_box_has_boxes(box))
4074
0
    return;
4075
4076
  /* We always want to draw our background color. */
4077
0
  padding = box->u.block.padding;
4078
0
  cell_padding_top = box->type == BOX_TABLE_CELL ? box->u.block.margin[T] : 0;
4079
0
  cell_padding_bot = box->type == BOX_TABLE_CELL ? box->u.block.margin[B] : 0;
4080
4081
0
  x0 = box->s.layout.x - padding[L];
4082
0
  y0 = box->s.layout.y - padding[T];
4083
0
  x1 = box->s.layout.x + box->s.layout.w + padding[R];
4084
0
  y1 = box->s.layout.b + padding[B];
4085
4086
0
  if (y1 > y0)
4087
0
    draw_rect(ctx, dev, ctm, page_top, box->style->background_color, x0, y0 - cell_padding_top, x1, y1 + cell_padding_bot);
4088
4089
  /* We may need to draw some borders. We always draw left and right. We never draw bottom. */
4090
  /* Potentially we could avoid drawing the top border if we aren't the first child of our parent,
4091
   * but drawing it doesn't really hurt us, as either it'll be off the page, or it'll look odd
4092
   * without it. */
4093
0
  do_borders(ctx, dev, ctm, page_top, box, (1<<B));
4094
4095
  /* Only print the list mark if the restart point is the first entry in this box. */
4096
0
  if (box->list_item && first_entry)
4097
0
    draw_list_mark(ctx, box, page_top, page_bot, dev, ctm, box->list_item);
4098
4099
0
}
4100
4101
static int draw_flow_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
4102
580
{
4103
580
  fz_html_flow *node;
4104
580
  fz_text *text = NULL;
4105
580
  fz_path *line = NULL;
4106
580
  fz_matrix trm;
4107
580
  fz_css_color prev_color = { 0, 0, 0, 0 };
4108
580
  fz_css_color prev_fill_color = { 0, 0, 0, 0 };
4109
580
  fz_css_color prev_stroke_color = { 0, 0, 0, 0 };
4110
580
  float line_width, prev_line_width = 0;
4111
580
  int filling, prev_filling = 0;
4112
580
  int stroking, prev_stroking = 0;
4113
580
  int restartable_ended = 0;
4114
580
  fz_stroke_state *ss = NULL;
4115
580
  fz_stroke_state *line_ss = NULL;
4116
4117
580
  fz_var(text);
4118
580
  fz_var(line);
4119
580
  fz_var(ss);
4120
580
  fz_var(line_ss);
4121
4122
  /* FIXME: HB_DIRECTION_TTB? */
4123
4124
580
  if (restart && restart->start != NULL && restart->start != box)
4125
0
    return 0;
4126
4127
1.16k
  fz_try(ctx)
4128
1.16k
  {
4129
378k
    for (node = box->u.flow.head; node; node = node->next)
4130
377k
    {
4131
377k
      const fz_css_style *style = node->box->style;
4132
4133
377k
      if (restart)
4134
0
      {
4135
0
        if (restart->start_flow != NULL)
4136
0
        {
4137
0
          if (restart->start_flow != node)
4138
0
            continue;
4139
0
          draw_skipped_bg_and_borders(ctx, box, page_top, page_bot, dev, ctm, node == box->u.flow.head);
4140
0
          restart->start = NULL;
4141
0
          restart->start_flow = NULL;
4142
0
        }
4143
4144
0
        if (restart->end == box && restart->end_flow == node)
4145
0
        {
4146
0
          restartable_ended = 1;
4147
0
          break;
4148
0
        }
4149
0
      }
4150
4151
377k
      if (node->type == FLOW_IMAGE)
4152
0
      {
4153
0
        if (node->y >= page_bot || node->y + node->h <= page_top)
4154
0
          continue;
4155
0
      }
4156
377k
      else
4157
377k
      {
4158
377k
        if (node->y > page_bot || node->y < page_top)
4159
340k
          continue;
4160
377k
      }
4161
4162
36.7k
      if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
4163
29.4k
      {
4164
29.4k
        string_walker walker;
4165
29.4k
        fz_css_color bg;
4166
29.4k
        const char *s;
4167
29.4k
        float x, y;
4168
29.4k
        float em;
4169
4170
29.4k
        if (node->type == FLOW_SPACE && node->breaks_line)
4171
2.92k
          continue;
4172
26.5k
        if (node->type == FLOW_SHYPHEN && !node->breaks_line)
4173
2
          continue;
4174
26.5k
        if (style->visibility != V_VISIBLE)
4175
0
          continue;
4176
4177
26.5k
        em = node->box->s.layout.em;
4178
4179
26.5k
        bg = find_inline_background_color(ctx, node->box);
4180
26.5k
        if (bg.a > 0)
4181
11.0k
          draw_rect(ctx, dev, ctm, page_top, bg,
4182
11.0k
            node->x, node->y - 0.8f * node->h, node->x + node->w, node->y + 0.2f * node->h);
4183
4184
26.5k
        line_width = fz_from_css_number(style->text_stroke_width, em, em, 0);
4185
26.5k
        filling = style->text_fill_color.a != 0;
4186
26.5k
        stroking = style->text_stroke_color.a != 0;
4187
26.5k
        if (stroking)
4188
0
        {
4189
0
          if (ss == NULL)
4190
0
            ss = fz_new_stroke_state(ctx);
4191
0
        }
4192
26.5k
        if (line)
4193
20.9k
        {
4194
20.9k
          if (line_ss == NULL)
4195
0
            line_ss = fz_new_stroke_state(ctx);
4196
20.9k
        }
4197
4198
26.5k
        if (  /* If we've changed whether we're filling... */
4199
26.5k
          filling != prev_filling ||
4200
          /* Or we're filling and the color has changed... */
4201
26.3k
          (prev_filling && !color_eq(style->text_fill_color, prev_fill_color)) ||
4202
          /* Or we've changed whether we're stroking... */
4203
26.3k
          stroking != prev_stroking ||
4204
          /* Or we're stroking, and the color or linewidth has changed... */
4205
26.3k
          (prev_stroking && (!color_eq(style->text_stroke_color, prev_stroke_color) || line_width != prev_line_width)))
4206
216
        {
4207
216
          if (text)
4208
2
          {
4209
2
            if (prev_filling)
4210
2
            {
4211
2
              unpacked_color color = unpack_color(prev_fill_color);
4212
2
              fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4213
2
            }
4214
2
            if (prev_stroking)
4215
0
            {
4216
0
              unpacked_color color = unpack_color(prev_stroke_color);
4217
0
              ss->linewidth = prev_line_width;
4218
0
              fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4219
0
            }
4220
2
            fz_drop_text(ctx, text);
4221
2
            text = NULL;
4222
2
          }
4223
216
          prev_filling = filling;
4224
216
          prev_stroking = stroking;
4225
216
        }
4226
26.5k
        prev_fill_color = style->text_fill_color;
4227
26.5k
        prev_stroke_color = style->text_stroke_color;
4228
26.5k
        prev_line_width = line_width;
4229
4230
26.5k
        if (!color_eq(style->color, prev_color))
4231
216
        {
4232
216
          if (line)
4233
1
          {
4234
1
            unpacked_color color = unpack_color(prev_color);
4235
1
            fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4236
1
            fz_drop_path(ctx, line);
4237
1
            line = NULL;
4238
1
          }
4239
216
          prev_color = style->color;
4240
216
        }
4241
4242
26.5k
        if (style->text_decoration > 0)
4243
16.3k
        {
4244
16.3k
          if (!line)
4245
174
          {
4246
174
            line = fz_new_path(ctx);
4247
174
            if (line_ss == NULL)
4248
174
              line_ss = fz_new_stroke_state(ctx);
4249
174
          }
4250
16.3k
          if (style->text_decoration & TD_UNDERLINE)
4251
1
          {
4252
1
            fz_moveto(ctx, line, node->x, node->y + 1.5f - page_top);
4253
1
            fz_lineto(ctx, line, node->x + node->w, node->y + 1.5f - page_top);
4254
1
          }
4255
16.3k
          if (style->text_decoration & TD_LINE_THROUGH)
4256
16.3k
          {
4257
16.3k
            fz_moveto(ctx, line, node->x, node->y - em * 0.3f - page_top);
4258
16.3k
            fz_lineto(ctx, line, node->x + node->w, node->y - em * 0.3f - page_top);
4259
16.3k
          }
4260
16.3k
        }
4261
4262
26.5k
        if (!text)
4263
216
          text = fz_new_text(ctx);
4264
4265
26.5k
        if (node->bidi_level & 1)
4266
1.80k
          x = node->x + node->w;
4267
24.7k
        else
4268
24.7k
          x = node->x;
4269
26.5k
        y = node->y;
4270
4271
26.5k
        trm.a = em;
4272
26.5k
        trm.b = 0;
4273
26.5k
        trm.c = 0;
4274
26.5k
        trm.d = -em;
4275
26.5k
        trm.e = x;
4276
26.5k
        trm.f = y - page_top;
4277
4278
26.5k
        s = get_node_text(ctx, node);
4279
26.5k
        init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, style->font, node->script, node->markup_lang, style->small_caps, s, 0);
4280
165k
        while (walk_string(&walker))
4281
138k
        {
4282
138k
          float node_scale = node->box->s.layout.em / walker.scale;
4283
138k
          unsigned int i;
4284
138k
          uint32_t k;
4285
138k
          int c, n;
4286
4287
          /* Flatten advance and offset into offset array. */
4288
138k
          int x_advance = 0;
4289
138k
          int y_advance = 0;
4290
389k
          for (i = 0; i < walker.glyph_count; ++i)
4291
250k
          {
4292
250k
            walker.glyph_pos[i].x_offset += x_advance;
4293
250k
            walker.glyph_pos[i].y_offset += y_advance;
4294
250k
            x_advance += walker.glyph_pos[i].x_advance;
4295
250k
            y_advance += walker.glyph_pos[i].y_advance;
4296
250k
          }
4297
4298
138k
          if (node->bidi_level & 1)
4299
2.08k
            x -= x_advance * node_scale;
4300
4301
          /* Walk characters to find glyph clusters */
4302
138k
          k = 0;
4303
389k
          while (walker.start + k < walker.end)
4304
250k
          {
4305
250k
            n = fz_chartorune(&c, walker.start + k);
4306
4307
            /* render with hyphen-minus glyph, but encode unicode text as soft-hyphen */
4308
250k
            if (node->type == FLOW_SHYPHEN)
4309
4
              c = 0xAD;
4310
4311
945k
            for (i = 0; i < walker.glyph_count; ++i)
4312
694k
            {
4313
694k
              if (walker.glyph_info[i].cluster == k)
4314
250k
              {
4315
250k
                trm.e = x + walker.glyph_pos[i].x_offset * node_scale;
4316
250k
                trm.f = y - walker.glyph_pos[i].y_offset * node_scale - page_top;
4317
250k
                fz_show_glyph(ctx, text, walker.font, trm,
4318
250k
                    walker.glyph_info[i].codepoint, c,
4319
250k
                    0, node->bidi_level, box->markup_dir, node->markup_lang);
4320
250k
                c = -1; /* for subsequent glyphs in x-to-many mappings */
4321
250k
              }
4322
694k
            }
4323
4324
            /* no glyph found (many-to-many or many-to-one mapping) */
4325
250k
            if (c != -1)
4326
6
            {
4327
6
              fz_show_glyph(ctx, text, walker.font, trm,
4328
6
                  -1, c,
4329
6
                  0, node->bidi_level, box->markup_dir, node->markup_lang);
4330
6
            }
4331
4332
250k
            k += n;
4333
250k
          }
4334
4335
138k
          if ((node->bidi_level & 1) == 0)
4336
136k
            x += x_advance * node_scale;
4337
4338
138k
          y += y_advance * node_scale;
4339
138k
        }
4340
26.5k
      }
4341
7.29k
      else if (node->type == FLOW_IMAGE)
4342
0
      {
4343
0
        if (text)
4344
0
        {
4345
0
          if (filling)
4346
0
          {
4347
0
            unpacked_color color = unpack_color(prev_fill_color);
4348
0
            fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4349
0
          }
4350
0
          if (stroking)
4351
0
          {
4352
0
            unpacked_color color = unpack_color(prev_stroke_color);
4353
0
            ss->linewidth = line_width;
4354
0
            fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4355
0
          }
4356
0
          fz_drop_text(ctx, text);
4357
0
          text = NULL;
4358
0
        }
4359
0
        if (style->visibility == V_VISIBLE)
4360
0
        {
4361
0
          float alpha = style->color.a / 255.0f;
4362
0
          fz_matrix itm = fz_pre_translate(ctm, node->x, node->y - page_top);
4363
0
          itm = fz_pre_scale(itm, node->w, node->h);
4364
0
          fz_fill_image(ctx, dev, node->content.image, itm, alpha, fz_default_color_params);
4365
0
        }
4366
0
      }
4367
36.7k
    }
4368
4369
580
    if (text)
4370
214
    {
4371
214
      if (filling)
4372
214
      {
4373
214
        unpacked_color color = unpack_color(prev_fill_color);
4374
214
        fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4375
214
      }
4376
214
      if (stroking)
4377
0
      {
4378
0
        unpacked_color color = unpack_color(prev_stroke_color);
4379
0
        ss->linewidth = prev_line_width;
4380
0
        fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4381
0
      }
4382
214
      fz_drop_text(ctx, text);
4383
214
      text = NULL;
4384
214
    }
4385
4386
580
    if (line)
4387
173
    {
4388
173
      unpacked_color color = unpack_color(prev_color);
4389
173
      fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
4390
173
      fz_drop_path(ctx, line);
4391
173
      line = NULL;
4392
173
    }
4393
580
  }
4394
1.16k
  fz_always(ctx)
4395
580
  {
4396
580
    fz_drop_text(ctx, text);
4397
580
    fz_drop_path(ctx, line);
4398
580
    fz_drop_stroke_state(ctx, ss);
4399
580
    fz_drop_stroke_state(ctx, line_ss);
4400
580
  }
4401
580
  fz_catch(ctx)
4402
0
    fz_rethrow(ctx);
4403
4404
580
  return restartable_ended;
4405
580
}
4406
4407
static int draw_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart);
4408
4409
static int draw_block_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart, fz_css_color bg1, fz_css_color bg2)
4410
7.56k
{
4411
7.56k
  fz_html_box *child;
4412
7.56k
  float x0, y0, x1, y1;
4413
4414
7.56k
  float *padding = box->u.block.padding;
4415
7.56k
  int stopped = 0;
4416
7.56k
  int skipping;
4417
7.56k
  float cell_padding_top = box->type == BOX_TABLE_CELL ? box->u.block.margin[T] : 0;
4418
7.56k
  float cell_padding_bot = box->type == BOX_TABLE_CELL ? box->u.block.margin[B] : 0;
4419
4420
7.56k
  assert(fz_html_box_has_boxes(box));
4421
7.56k
  x0 = box->s.layout.x - padding[L];
4422
7.56k
  y0 = box->s.layout.y - padding[T];
4423
7.56k
  x1 = box->s.layout.x + box->s.layout.w + padding[R];
4424
7.56k
  y1 = box->s.layout.b + padding[B];
4425
4426
7.56k
  if (y0 > page_bot || y1 < page_top)
4427
6.96k
    return 0;
4428
4429
  /* If we're skipping, is this the place we should restart? */
4430
606
  if (restart)
4431
0
  {
4432
0
    if (restart->start == box)
4433
0
    {
4434
0
      draw_skipped_bg_and_borders(ctx, box, page_top, page_bot, dev, ctm, box->up->down == box);
4435
0
      restart->start = NULL;
4436
0
    }
4437
0
    if (restart->end == box)
4438
0
      return 1;
4439
0
  }
4440
4441
606
  if (restart && restart->end == box)
4442
0
    return 1;
4443
4444
  /* Are we skipping? */
4445
606
  skipping = (restart && restart->start != NULL);
4446
4447
  /* Only draw the content if it's visible, and we're not skipping it. */
4448
606
  if (box->style->visibility == V_VISIBLE && !skipping)
4449
606
  {
4450
606
    int suppress;
4451
4452
606
    if (y1 > y0)
4453
606
    {
4454
606
      if (bg1.a > 0)
4455
0
        draw_rect(ctx, dev, ctm, page_top, bg1, x0, y0 - cell_padding_top, x1, y1 + cell_padding_bot);
4456
606
      if (bg2.a > 0)
4457
0
        draw_rect(ctx, dev, ctm, page_top, bg2, x0, y0 - cell_padding_top, x1, y1 + cell_padding_bot);
4458
606
    }
4459
4460
    /* Draw a selection of borders. */
4461
    /* If we are in restarting mode, don't do the bottom one yet, as that
4462
     * might not be on this page. */
4463
606
    suppress = restart ? (1<<B) : 0;
4464
606
    do_borders(ctx, dev, ctm, page_top, box, suppress);
4465
4466
606
    if (box->list_item)
4467
0
      draw_list_mark(ctx, box, page_top, page_bot, dev, ctm, box->list_item);
4468
606
  }
4469
4470
8.57k
  for (child = box->down; child; child = child->next)
4471
7.97k
  {
4472
7.97k
    if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
4473
0
    {
4474
0
      stopped = 1;
4475
0
      break;
4476
0
    }
4477
7.97k
  }
4478
4479
606
  if (box->style->visibility == V_VISIBLE && /* visible*/
4480
606
    restart && restart->start == NULL && /* in restarting mode, and no longer skipping */
4481
0
    !stopped) /* we haven't stopped yet */
4482
0
  {
4483
    /* We need to draw the bottom border. */
4484
0
    do_borders(ctx, dev, ctm, page_top, box, (1<<T) | (1<<L) | (1<<R));
4485
0
  }
4486
4487
606
  return stopped;
4488
606
}
4489
4490
static int draw_table_row(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
4491
0
{
4492
  /* Table rows don't draw background colors or borders */
4493
4494
0
  fz_html_box *child;
4495
4496
0
  float y0 = box->s.layout.y;
4497
0
  float y1 = box->s.layout.b;
4498
0
  if (y0 > page_bot || y1 < page_top)
4499
0
    return 0;
4500
4501
  /* If we're skipping, is this the place we should restart? */
4502
0
  if (restart)
4503
0
  {
4504
0
    if (restart->start == box)
4505
0
    {
4506
0
      draw_skipped_bg_and_borders(ctx, box, page_top, page_bot, dev, ctm, box->up->down == box);
4507
0
      restart->start = NULL;
4508
0
    }
4509
0
    if (restart->end == box)
4510
0
      return 1;
4511
0
  }
4512
4513
0
  if (restart && restart->end == box)
4514
0
    return 1;
4515
4516
0
  for (child = box->down; child; child = child->next)
4517
0
    if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
4518
0
      return 1;
4519
4520
0
  return 0;
4521
0
}
4522
4523
static fz_html_box *find_table_row(fz_html_box *box)
4524
0
{
4525
0
  while (box && box->type != BOX_TABLE_ROW)
4526
0
    box = box->up;
4527
0
  return box;
4528
0
}
4529
4530
static int draw_table_cell(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
4531
0
{
4532
  /* Table cells draw table row's background color as well as their own */
4533
0
  fz_html_box *row = find_table_row(box);
4534
0
  if (row)
4535
0
    return draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart, row->style->background_color, box->style->background_color);
4536
0
  return draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart, transparent, box->style->background_color);
4537
0
}
4538
4539
static int draw_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_device *dev, fz_matrix ctm, hb_buffer_t *hb_buf, fz_html_restarter *restart)
4540
8.14k
{
4541
8.14k
  int ret = 0;
4542
8.14k
  int str = fz_html_tag_to_structure(box->tag);
4543
4544
8.14k
  if (str != FZ_STRUCTURE_INVALID)
4545
7.39k
    fz_begin_structure(ctx, dev, str, box->tag, 0);
4546
4547
8.14k
  switch (box->type)
4548
8.14k
  {
4549
0
  case BOX_TABLE_ROW:
4550
0
    if (restart && restart->end == box)
4551
0
      ret = 1;
4552
0
    else if (draw_table_row(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
4553
0
      ret = 1;
4554
0
    break;
4555
0
  case BOX_TABLE_CELL:
4556
0
    if (restart && restart->end == box)
4557
0
      ret = 1;
4558
0
    else if (draw_table_cell(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
4559
0
      ret = 1;
4560
0
    break;
4561
0
  case BOX_TABLE:
4562
7.56k
  case BOX_BLOCK:
4563
7.56k
    if (restart && restart->end == box)
4564
0
      ret = 1;
4565
7.56k
    else if (draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart, box->style->background_color, transparent))
4566
0
      ret = 1;
4567
7.56k
    break;
4568
580
  case BOX_FLOW:
4569
580
    if (draw_flow_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
4570
0
      ret = 1;
4571
580
    break;
4572
8.14k
  }
4573
4574
8.14k
  if (str != FZ_STRUCTURE_INVALID)
4575
7.39k
    fz_end_structure(ctx, dev);
4576
4577
8.14k
  return ret;
4578
8.14k
}
4579
4580
void
4581
fz_draw_restarted_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html_box *top, float page_top, float page_bot, fz_html_restarter *restart)
4582
175
{
4583
175
  fz_html_box *box;
4584
175
  hb_buffer_t *hb_buf = NULL;
4585
175
  int unlocked = 0;
4586
4587
175
  fz_var(hb_buf);
4588
175
  fz_var(unlocked);
4589
4590
175
  fz_hb_lock(ctx);
4591
350
  fz_try(ctx)
4592
350
  {
4593
175
    hb_buf = hb_buffer_create();
4594
175
    fz_hb_unlock(ctx);
4595
175
    unlocked = 1;
4596
4597
350
    for (box = top->down; box; box = box->next)
4598
175
      if (draw_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
4599
0
        break;
4600
175
  }
4601
350
  fz_always(ctx)
4602
175
  {
4603
175
    if (unlocked)
4604
175
      fz_hb_lock(ctx);
4605
175
    hb_buffer_destroy(hb_buf);
4606
175
    fz_hb_unlock(ctx);
4607
175
  }
4608
175
  fz_catch(ctx)
4609
0
  {
4610
0
    fz_rethrow(ctx);
4611
0
  }
4612
175
}
4613
4614
void
4615
fz_draw_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html *html, int page)
4616
175
{
4617
175
  float page_top = page * html->page_h;
4618
175
  float page_bot = (page + 1) * html->page_h;
4619
4620
175
  draw_rect(ctx, dev, ctm, 0, html->tree.root->style->background_color,
4621
175
      0, 0,
4622
175
      html->page_w + html->page_margin[L] + html->page_margin[R],
4623
175
      html->page_h + html->page_margin[T] + html->page_margin[B]);
4624
4625
175
  ctm = fz_pre_translate(ctm, html->page_margin[L], html->page_margin[T]);
4626
4627
175
  fz_draw_restarted_html(ctx, dev, ctm, html->tree.root, page_top, page_bot, NULL);
4628
175
}
4629
4630
void fz_draw_story(fz_context *ctx, fz_story *story, fz_device *dev, fz_matrix ctm)
4631
0
{
4632
0
  float page_top, page_bot;
4633
0
  fz_path *clip;
4634
0
  fz_rect bbox;
4635
4636
0
  if (story == NULL || story->complete)
4637
0
    return;
4638
4639
0
  bbox = story->bbox;
4640
0
  page_top = bbox.y0;
4641
0
  page_bot = bbox.y1;
4642
4643
0
  if (dev)
4644
0
  {
4645
0
    clip = fz_new_path(ctx);
4646
0
    fz_try(ctx)
4647
0
    {
4648
0
      fz_moveto(ctx, clip, bbox.x0, bbox.y0);
4649
0
      fz_lineto(ctx, clip, bbox.x1, bbox.y0);
4650
0
      fz_lineto(ctx, clip, bbox.x1, bbox.y1);
4651
0
      fz_lineto(ctx, clip, bbox.x0, bbox.y1);
4652
0
      fz_closepath(ctx, clip);
4653
0
      fz_clip_path(ctx, dev, clip, 0, ctm, bbox);
4654
0
    }
4655
0
    fz_always(ctx)
4656
0
      fz_drop_path(ctx, clip);
4657
0
    fz_catch(ctx)
4658
0
      fz_rethrow(ctx);
4659
0
  }
4660
4661
0
  story->restart_draw.left_page ^= 1;
4662
0
  story->restart_place = story->restart_draw;
4663
0
  if (dev)
4664
0
    fz_draw_restarted_html(ctx, dev, ctm, story->tree.root->down, 0, page_bot+page_top, &story->restart_place);
4665
0
  story->restart_place.start = story->restart_draw.end;
4666
0
  story->restart_place.start_flow = story->restart_draw.end_flow;
4667
0
  story->restart_place.start_flags = story->restart_draw.end_flags;
4668
0
  story->restart_place.end = NULL;
4669
0
  story->restart_place.end_flow = NULL;
4670
0
  story->restart_place.end_flags = 0;
4671
0
  story->rect_count++;
4672
4673
0
  if (story->restart_place.start == NULL)
4674
0
    story->complete = 1;
4675
4676
0
  if (dev)
4677
0
    fz_pop_clip(ctx, dev);
4678
0
}
4679
4680
void fz_reset_story(fz_context *ctx, fz_story *story)
4681
0
{
4682
0
  if (story == NULL)
4683
0
    return;
4684
4685
0
  story->restart_place.start = NULL;
4686
0
  story->restart_place.start_flow = NULL;
4687
0
  story->restart_place.start_flags = 0;
4688
0
  story->restart_place.end = NULL;
4689
0
  story->restart_place.end_flow = NULL;
4690
0
  story->restart_place.end_flags = 0;
4691
0
  story->restart_place.left_page = 0;
4692
0
  story->restart_draw.start = NULL;
4693
0
  story->restart_draw.start_flow = NULL;
4694
0
  story->restart_draw.start_flags = 0;
4695
0
  story->restart_draw.end = NULL;
4696
0
  story->restart_draw.end_flow = NULL;
4697
0
  story->restart_draw.end_flags = 0;
4698
0
  story->restart_draw.left_page = 0;
4699
0
  story->rect_count = 0;
4700
0
  story->complete = 0;
4701
0
}
4702
4703
static char *
4704
gather_text(fz_context *ctx, fz_html_box *box)
4705
0
{
4706
0
  fz_html_flow *node;
4707
0
  char *text = NULL;
4708
4709
0
  fz_var(text);
4710
4711
0
  fz_try(ctx)
4712
0
  {
4713
0
    for (node = box->u.flow.head; node; node = node->next)
4714
0
    {
4715
0
      const fz_css_style *style = node->box->style;
4716
4717
0
      if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
4718
0
      {
4719
0
        const char *s;
4720
4721
0
        if (node->type == FLOW_SPACE && node->breaks_line)
4722
0
          continue;
4723
0
        if (node->type == FLOW_SHYPHEN && !node->breaks_line)
4724
0
          continue;
4725
0
        if (style->visibility != V_VISIBLE)
4726
0
          continue;
4727
4728
0
        s = get_node_text(ctx, node);
4729
4730
0
        if (text)
4731
0
        {
4732
0
          size_t newsize = strlen(text) + strlen(s) + 1;
4733
0
          text = fz_realloc(ctx, text, newsize);
4734
0
          strcat(text, s);
4735
0
        }
4736
0
        else
4737
0
        {
4738
0
          text = fz_strdup(ctx, s);
4739
0
        }
4740
0
      }
4741
0
      else if (node->type == FLOW_IMAGE)
4742
0
      {
4743
0
      }
4744
0
    }
4745
4746
0
  }
4747
0
  fz_catch(ctx)
4748
0
  {
4749
0
    fz_free(ctx, text);
4750
0
    fz_rethrow(ctx);
4751
0
  }
4752
4753
0
  return text;
4754
0
}
4755
4756
static int enumerate_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart);
4757
4758
static int enumerate_block_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
4759
0
{
4760
0
  fz_html_box *child;
4761
0
  float y0, y1;
4762
4763
0
  float *padding = box->u.block.padding;
4764
0
  int stopped = 0;
4765
0
  int skipping;
4766
0
  fz_story_element_position pos;
4767
0
  int heading = 0; /* Stupid compiler requires initialization */
4768
4769
0
  assert(fz_html_box_has_boxes(box));
4770
0
  y0 = box->s.layout.y - padding[T];
4771
0
  y1 = box->s.layout.b + padding[B];
4772
4773
0
  if (y0 > page_bot || y1 < page_top)
4774
0
    return 0;
4775
4776
  /* If we're skipping, is this the place we should restart? */
4777
0
  if (restart)
4778
0
  {
4779
0
    if (restart->start == box)
4780
0
      restart->start = NULL;
4781
0
    if (restart->end == box)
4782
0
      return 1;
4783
0
  }
4784
4785
0
  if (restart && restart->end == box)
4786
0
    return 1;
4787
4788
  /* Are we skipping? */
4789
0
  skipping = (restart && restart->start != NULL);
4790
4791
0
  if (box->style->visibility == V_VISIBLE && !skipping)
4792
0
  {
4793
0
    heading = box->heading;
4794
0
    if (heading || box->id != NULL || box->href)
4795
0
    {
4796
      /* We have a box worthy of a callback. */
4797
0
      char *text = NULL;
4798
0
      pos.text = NULL;
4799
0
      if (heading && box->down)
4800
0
        pos.text = text = gather_text(ctx, box->down);
4801
0
      pos.depth = depth;
4802
0
      pos.heading = heading;
4803
0
      pos.open_close = 1;
4804
0
      pos.id = box->id;
4805
0
      pos.href = box->href;
4806
0
      pos.rect.x0 = box->s.layout.x;
4807
0
      pos.rect.y0 = box->s.layout.y;
4808
0
      pos.rect.x1 = box->s.layout.x + box->s.layout.w;
4809
0
      pos.rect.y1 = box->s.layout.b;
4810
0
      pos.rectangle_num = rect_num;
4811
0
      fz_try(ctx)
4812
0
        cb(ctx, arg, &pos);
4813
0
      fz_always(ctx)
4814
0
        fz_free(ctx, text);
4815
0
      fz_catch(ctx)
4816
0
        fz_rethrow(ctx);
4817
0
      pos.text = NULL;
4818
0
    }
4819
0
  }
4820
4821
0
  for (child = box->down; child; child = child->next)
4822
0
  {
4823
0
    if (enumerate_box(ctx, child, page_top, page_bot, cb, arg, depth+1, rect_num, restart))
4824
0
    {
4825
0
      stopped = 1;
4826
0
      break;
4827
0
    }
4828
0
  }
4829
4830
0
  if (box->style->visibility == V_VISIBLE && !skipping)
4831
0
  {
4832
0
    if (heading || box->id != NULL || box->href)
4833
0
    {
4834
      /* We have a box worthy of a callback that needs closing. */
4835
0
      pos.open_close = 2;
4836
0
      pos.rectangle_num = rect_num;
4837
0
      cb(ctx, arg, &pos);
4838
0
    }
4839
0
  }
4840
4841
0
  return stopped;
4842
0
}
4843
4844
static int enumerate_flow_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
4845
0
{
4846
0
  fz_html_flow *node;
4847
0
  int restartable_ended = 0;
4848
4849
  /* FIXME: HB_DIRECTION_TTB? */
4850
4851
0
  if (restart && restart->start != NULL && restart->start != box)
4852
0
    return 0;
4853
4854
0
  for (node = box->u.flow.head; node; node = node->next)
4855
0
  {
4856
0
    const fz_css_style *style = node->box->style;
4857
4858
0
    if (restart)
4859
0
    {
4860
0
      if (restart->start_flow != NULL)
4861
0
      {
4862
0
        if (restart->start_flow != node)
4863
0
          continue;
4864
0
        restart->start = NULL;
4865
0
        restart->start_flow = NULL;
4866
0
      }
4867
4868
0
      if (restart->end == box && restart->end_flow == node)
4869
0
      {
4870
0
        restartable_ended = 1;
4871
0
        break;
4872
0
      }
4873
0
    }
4874
4875
0
    if (node->type == FLOW_IMAGE)
4876
0
    {
4877
0
      if (node->y >= page_bot || node->y + node->h <= page_top)
4878
0
        continue;
4879
0
    }
4880
0
    else
4881
0
    {
4882
0
      if (node->y > page_bot || node->y < page_top)
4883
0
        continue;
4884
0
    }
4885
4886
0
    if (node->box->id || node->box->href)
4887
0
    {
4888
      /* We have a node to callback for. */
4889
0
      fz_story_element_position pos;
4890
4891
0
      pos.text = NULL;
4892
0
      pos.depth = depth;
4893
0
      pos.heading = 0;
4894
0
      pos.open_close = 1 | 2;
4895
0
      pos.id = node->box->id;
4896
0
      pos.href = node->box->href;
4897
      /* We only have the baseline and the em, so the bbox is a bit of a fudge. */
4898
0
      pos.rect.x0 = node->x;
4899
0
      pos.rect.y0 = node->y - node->h * 0.8f;
4900
0
      pos.rect.x1 = node->x + node->w;
4901
0
      pos.rect.y1 = node->y + node->h * 0.2f;
4902
0
      pos.rectangle_num = rect_num;
4903
0
      cb(ctx, arg, &pos);
4904
0
    }
4905
4906
0
    if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
4907
0
    {
4908
0
    }
4909
0
    else if (node->type == FLOW_IMAGE)
4910
0
    {
4911
0
      if (style->visibility == V_VISIBLE)
4912
0
      {
4913
        /* FIXME: Maybe callback for images? */
4914
0
      }
4915
0
    }
4916
0
  }
4917
4918
0
  return restartable_ended;
4919
0
}
4920
4921
static int enumerate_box(fz_context *ctx, fz_html_box *box, float page_top, float page_bot, fz_story_position_callback *cb, void *arg, int depth, int rect_num, fz_html_restarter *restart)
4922
0
{
4923
0
  switch (box->type)
4924
0
  {
4925
0
  case BOX_TABLE:
4926
0
  case BOX_TABLE_ROW:
4927
0
  case BOX_TABLE_CELL:
4928
0
  case BOX_BLOCK:
4929
0
    if (restart && restart->end == box)
4930
0
      return 1;
4931
0
    if (enumerate_block_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
4932
0
      return 1;
4933
0
    break;
4934
0
  case BOX_FLOW:
4935
0
    if (enumerate_flow_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
4936
0
      return 1;
4937
0
    break;
4938
0
  }
4939
4940
0
  return 0;
4941
0
}
4942
4943
void fz_story_positions(fz_context *ctx, fz_story *story, fz_story_position_callback *cb, void *arg)
4944
0
{
4945
0
  float page_top, page_bot;
4946
0
  fz_html_box *b;
4947
0
  fz_html_restarter restart;
4948
0
  fz_html_box *box;
4949
0
  fz_html_box *top;
4950
4951
0
  if (story == NULL || story->complete)
4952
0
    return;
4953
4954
0
  b = story->tree.root;
4955
0
  page_top = b->s.layout.y - b->u.block.margin[T] - b->u.block.border[T] - b->u.block.padding[T];
4956
0
  page_bot = b->s.layout.b + b->u.block.margin[B] + b->u.block.border[B] + b->u.block.padding[B];
4957
0
  top = story->tree.root->down;
4958
4959
0
  restart = story->restart_draw;
4960
4961
0
  for (box = top->down; box; box = box->next)
4962
0
    if (enumerate_box(ctx, box, page_top, page_bot, cb, arg, 0, story->rect_count+1, &restart))
4963
0
      break;
4964
0
}