comparison mupdf-source/source/html/html-layout.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 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 #undef DEBUG_HARFBUZZ
35
36 /*
37 Some notes on the layout code below and the concepts used.
38
39 THE MODEL:
40
41 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.
42
43 +-------------------------------------------------------------------+
44 | margin |
45 | +-------------------------------------------------+ |
46 | | border | |
47 | | +-------------------------------+ | |
48 | | | padding | | |
49 | | | +---------+ | | |
50 | | | | content | | | |
51 | | | +---------+ | | |
52 | | | | | |
53 | | +-------------------------------+ | |
54 | | | |
55 | +-------------------------------------------------+ |
56 | |
57 +-------------------------------------------------------------------+
58
59 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
60 to the outside of this automatically.
61
62 HOW WE LAYOUT:
63
64 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,
65 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
66 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
67 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
68 padding.
69
70 VERTICAL MARGIN COLLAPSE:
71
72 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
73 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
74 two blocks.
75
76 So block A with a margin of 30, and block B with a margin of 20 will be separated by 30, not 50.
77
78 To make matters more complicated, margin collapse can happen 'through' nestings of blocks, and even through empty blocks. It cannot
79 happen through blocks with borders, or padding.
80
81 Accordingly, as we recursively pass through our structure laying out, we keep a 'vertical' parameter, representing the amount of vertical
82 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
83 adjusted by it.
84
85 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
86 changed.
87
88 NON-RESTARTING OPERATION:
89
90 The standard layout operation as used for HTML documents and ebooks has us layout onto an infinitely long page, with the vertical
91 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
92 layout can proceed from start to finish with no need to stop. This is achieved by calling the code with restart == NULL.
93
94 RESTARTING:
95
96 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
97 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
98 simply return a pointer to the node to continue processing at - we need to recursively redescend the tree until we reach the same
99 position, whereupon we will continue. Also, because of the fact that the area being laid out into is potentially a completely different
100 size, we need to recalculate widths etc, so at least part of the tree needs to be reprocessed when we restart.
101
102 Accordingly, two ways to do this immediately suggest themselves. The first is to store the path down the tree so that we can quickly
103 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
104 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
105 box where we need to restart.
106
107 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
108 the trees we will be reprocessing will be relatively small. Should we ever need to revisit this decision, we can do so.
109 */
110
111 enum { T, R, B, L };
112
113 /* === LAYOUT INLINE TEXT === */
114
115 typedef struct string_walker
116 {
117 fz_context *ctx;
118 hb_buffer_t *hb_buf;
119 int rtl;
120 const char *start;
121 const char *end;
122 const char *s;
123 fz_font *base_font;
124 int script;
125 int language;
126 int small_caps;
127 fz_font *font;
128 fz_font *next_font;
129 hb_glyph_position_t *glyph_pos;
130 hb_glyph_info_t *glyph_info;
131 unsigned int glyph_count;
132 int scale;
133 } string_walker;
134
135 static int quick_ligature_mov(fz_context *ctx, string_walker *walker, unsigned int i, unsigned int n, int unicode)
136 {
137 unsigned int k;
138 for (k = i + n + 1; k < walker->glyph_count; ++k)
139 {
140 walker->glyph_info[k-n] = walker->glyph_info[k];
141 walker->glyph_pos[k-n] = walker->glyph_pos[k];
142 }
143 walker->glyph_count -= n;
144 return unicode;
145 }
146
147 static int quick_ligature(fz_context *ctx, string_walker *walker, unsigned int i)
148 {
149 if (walker->glyph_info[i].codepoint == 'f' && i + 1 < walker->glyph_count && !fz_font_flags(walker->font)->is_mono)
150 {
151 if (walker->glyph_info[i+1].codepoint == 'f')
152 {
153 if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'i')
154 {
155 if (fz_encode_character(ctx, walker->font, 0xFB03))
156 return quick_ligature_mov(ctx, walker, i, 2, 0xFB03);
157 }
158 if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'l')
159 {
160 if (fz_encode_character(ctx, walker->font, 0xFB04))
161 return quick_ligature_mov(ctx, walker, i, 2, 0xFB04);
162 }
163 if (fz_encode_character(ctx, walker->font, 0xFB00))
164 return quick_ligature_mov(ctx, walker, i, 1, 0xFB00);
165 }
166 if (walker->glyph_info[i+1].codepoint == 'i')
167 {
168 if (fz_encode_character(ctx, walker->font, 0xFB01))
169 return quick_ligature_mov(ctx, walker, i, 1, 0xFB01);
170 }
171 if (walker->glyph_info[i+1].codepoint == 'l')
172 {
173 if (fz_encode_character(ctx, walker->font, 0xFB02))
174 return quick_ligature_mov(ctx, walker, i, 1, 0xFB02);
175 }
176 }
177 return walker->glyph_info[i].codepoint;
178 }
179
180 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)
181 {
182 walker->ctx = ctx;
183 walker->hb_buf = hb_buf;
184 walker->rtl = rtl;
185 walker->start = text;
186 walker->end = text;
187 walker->s = text;
188 walker->base_font = font;
189 walker->script = script;
190 walker->language = language;
191 walker->font = NULL;
192 walker->next_font = NULL;
193 walker->small_caps = small_caps;
194 }
195
196 static void
197 destroy_hb_shaper_data(fz_context *ctx, void *handle)
198 {
199 fz_hb_lock(ctx);
200 hb_font_destroy(handle);
201 fz_hb_unlock(ctx);
202 }
203
204 static const hb_feature_t small_caps_feature[1] = {
205 { HB_TAG('s','m','c','p'), 1, 0, (unsigned int)-1 }
206 };
207
208 static int walk_string(string_walker *walker)
209 {
210 fz_context *ctx = walker->ctx;
211 FT_Face face;
212 int fterr;
213 int quickshape;
214 char lang[8];
215
216 walker->start = walker->end;
217 walker->end = walker->s;
218 walker->font = walker->next_font;
219
220 if (*walker->start == 0)
221 return 0;
222
223 /* Run through the string, encoding chars until we find one
224 * that requires a different fallback font. */
225 while (*walker->s)
226 {
227 int c;
228
229 walker->s += fz_chartorune(&c, walker->s);
230 (void)fz_encode_character_with_fallback(ctx, walker->base_font, c, walker->script, walker->language, &walker->next_font);
231 if (walker->next_font != walker->font)
232 {
233 if (walker->font != NULL)
234 break;
235 walker->font = walker->next_font;
236 }
237 walker->end = walker->s;
238 }
239
240 /* Disable harfbuzz shaping if script is common or LGC and there are no opentype tables. */
241 quickshape = 0;
242 if (walker->script <= 3 && !walker->rtl && !fz_font_flags(walker->font)->has_opentype)
243 quickshape = 1;
244
245 fz_hb_lock(ctx);
246 fz_try(ctx)
247 {
248 face = fz_font_ft_face(ctx, walker->font);
249 walker->scale = face->units_per_EM;
250 fterr = FT_Set_Char_Size(face, walker->scale, walker->scale, 72, 72);
251 if (fterr)
252 fz_throw(ctx, FZ_ERROR_LIBRARY, "freetype setting character size: %s", ft_error_string(fterr));
253
254 hb_buffer_clear_contents(walker->hb_buf);
255 hb_buffer_set_direction(walker->hb_buf, walker->rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
256 /* hb_buffer_set_script(walker->hb_buf, hb_ucdn_script_translate(walker->script)); */
257 if (walker->language)
258 {
259 fz_string_from_text_language(lang, walker->language);
260 Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
261 hb_buffer_set_language(walker->hb_buf, hb_language_from_string(lang, (int)strlen(lang)));
262 Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
263 }
264 hb_buffer_set_cluster_level(walker->hb_buf, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS);
265
266 hb_buffer_add_utf8(walker->hb_buf, walker->start, walker->end - walker->start, 0, -1);
267
268 if (!quickshape)
269 {
270 fz_shaper_data_t *hb = fz_font_shaper_data(ctx, walker->font);
271 Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
272 if (hb->shaper_handle == NULL)
273 {
274 hb->destroy = destroy_hb_shaper_data;
275 hb->shaper_handle = hb_ft_font_create(face, NULL);
276 }
277
278 hb_buffer_guess_segment_properties(walker->hb_buf);
279
280 if (walker->small_caps)
281 hb_shape(hb->shaper_handle, walker->hb_buf, small_caps_feature, nelem(small_caps_feature));
282 else
283 hb_shape(hb->shaper_handle, walker->hb_buf, NULL, 0);
284 Memento_stopLeaking();
285 }
286
287 walker->glyph_pos = hb_buffer_get_glyph_positions(walker->hb_buf, &walker->glyph_count);
288 walker->glyph_info = hb_buffer_get_glyph_infos(walker->hb_buf, NULL);
289 }
290 fz_always(ctx)
291 {
292 fz_hb_unlock(ctx);
293 }
294 fz_catch(ctx)
295 {
296 fz_rethrow(ctx);
297 }
298
299 if (quickshape)
300 {
301 unsigned int i;
302 for (i = 0; i < walker->glyph_count; ++i)
303 {
304 int glyph, unicode;
305 unicode = quick_ligature(ctx, walker, i);
306 if (walker->small_caps)
307 glyph = fz_encode_character_sc(ctx, walker->font, unicode);
308 else
309 glyph = fz_encode_character(ctx, walker->font, unicode);
310 walker->glyph_info[i].codepoint = glyph;
311 walker->glyph_pos[i].x_offset = 0;
312 walker->glyph_pos[i].y_offset = 0;
313 walker->glyph_pos[i].x_advance = fz_advance_glyph(ctx, walker->font, glyph, 0) * face->units_per_EM;
314 walker->glyph_pos[i].y_advance = 0;
315 }
316 }
317
318 return 1;
319 }
320
321 static const char *get_node_text(fz_context *ctx, fz_html_flow *node)
322 {
323 if (node->type == FLOW_WORD)
324 return node->content.text;
325 else if (node->type == FLOW_SPACE)
326 return " ";
327 else if (node->type == FLOW_SHYPHEN)
328 return "-";
329 else
330 return "";
331 }
332
333 static void measure_string_w(fz_context *ctx, fz_html_flow *node, hb_buffer_t *hb_buf)
334 {
335 float em = node->box->s.layout.em;
336 string_walker walker;
337 unsigned int i;
338 const char *s;
339 node->w = 0;
340 s = get_node_text(ctx, node);
341 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);
342 while (walk_string(&walker))
343 {
344 int x = 0;
345 for (i = 0; i < walker.glyph_count; i++)
346 x += walker.glyph_pos[i].x_advance;
347 node->w += x * em / walker.scale;
348 }
349 }
350
351 static void measure_string_h(fz_context *ctx, fz_html_flow *node)
352 {
353 float em = node->box->s.layout.em;
354 if (fz_css_number_defined(node->box->style->leading))
355 node->h = fz_from_css_number(node->box->style->leading, em, em, 0);
356 else
357 node->h = fz_from_css_number_scale(node->box->style->line_height, em);
358 }
359
360
361 static float measure_line(fz_html_flow *node, fz_html_flow *end, float *baseline)
362 {
363 float max_a = 0, max_d = 0, h = node->h;
364 while (node != end)
365 {
366 if (node->type == FLOW_IMAGE)
367 {
368 if (node->h > max_a)
369 max_a = node->h;
370 }
371 else if (node->type != FLOW_SBREAK && node->type != FLOW_BREAK)
372 {
373 // Clamp ascender/descender to line-height size.
374 // TODO: This is not entirely to spec, but close enough.
375 float s = fz_min(node->box->s.layout.em, node->h);
376 float a = s * 0.8f;
377 float d = s * 0.2f;
378 if (a > max_a) max_a = a;
379 if (d > max_d) max_d = d;
380 }
381 if (node->h > h)
382 h = node->h;
383 node = node->next;
384 }
385 *baseline = max_a + (h - max_a - max_d) / 2;
386 return h;
387 }
388
389 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)
390 {
391 float x = box->s.layout.x + indent;
392 float y = box->s.layout.b;
393 float slop = page_w - line_w;
394 float justify = 0;
395 float va;
396 int n, i;
397 fz_html_flow *node;
398 fz_html_flow **reorder;
399 unsigned int min_level, max_level;
400
401 /* Count the number of nodes on the line */
402 for(i = 0, n = 0, node = start; node != end; node = node->next)
403 {
404 n++;
405 if (node->type == FLOW_SPACE && node->expand && !node->breaks_line)
406 i++;
407 }
408
409 if (align == TA_JUSTIFY)
410 {
411 justify = slop / (i ? i : 1);
412 }
413 else if (align == TA_RIGHT)
414 x += slop;
415 else if (align == TA_CENTER)
416 x += slop / 2;
417
418 /* We need a block to hold the node pointers while we reorder */
419 reorder = fz_malloc_array(ctx, n, fz_html_flow*);
420 min_level = start->bidi_level;
421 max_level = start->bidi_level;
422 for(i = 0, node = start; node != end; i++, node = node->next)
423 {
424 reorder[i] = node;
425 if (node->bidi_level < min_level)
426 min_level = node->bidi_level;
427 if (node->bidi_level > max_level)
428 max_level = node->bidi_level;
429 }
430
431 /* Do we need to do any reordering? */
432 if (min_level != max_level || (min_level & 1))
433 {
434 /* The lowest level we swap is always a rtl one */
435 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 do
439 {
440 int start_idx = 0;
441 int end_idx;
442 do
443 {
444 /* Skip until we find a level that's >= max_level */
445 while (start_idx < n && reorder[start_idx]->bidi_level < max_level)
446 start_idx++;
447 /* If start >= n-1 then no more runs. */
448 if (start_idx >= n-1)
449 break;
450 /* Find the end of the match */
451 i = start_idx+1;
452 while (i < n && reorder[i]->bidi_level >= max_level)
453 i++;
454 /* Reverse from start to i-1 */
455 end_idx = i-1;
456 while (start_idx < end_idx)
457 {
458 fz_html_flow *t = reorder[start_idx];
459 reorder[start_idx++] = reorder[end_idx];
460 reorder[end_idx--] = t;
461 }
462 start_idx = i+1;
463 }
464 while (start_idx < n);
465 max_level--;
466 }
467 while (max_level >= min_level);
468 }
469
470 for (i = 0; i < n; i++)
471 {
472 float w;
473
474 node = reorder[i];
475 w = node->w;
476
477 if (node->type == FLOW_SPACE && node->breaks_line)
478 w = 0;
479 else if (node->type == FLOW_SPACE && !node->breaks_line)
480 w += node->expand ? justify : 0;
481 else if (node->type == FLOW_SHYPHEN && !node->breaks_line)
482 w = 0;
483 else if (node->type == FLOW_SHYPHEN && node->breaks_line)
484 w = node->w;
485
486 node->x = x;
487 x += w;
488
489 switch (node->box->style->vertical_align)
490 {
491 default:
492 case VA_BASELINE:
493 case VA_SUB:
494 case VA_SUPER:
495 va = node->box->s.layout.baseline;
496 break;
497
498 case VA_TOP:
499 case VA_TEXT_TOP:
500 va = -baseline + node->box->s.layout.em * 0.8f;
501 break;
502 case VA_BOTTOM:
503 case VA_TEXT_BOTTOM:
504 va = -baseline + line_h - node->box->s.layout.em * 0.2f;
505 break;
506 }
507
508 if (node->type == FLOW_IMAGE)
509 node->y = y + baseline - node->h;
510 else
511 {
512 node->y = y + baseline + va;
513 node->h = node->box->s.layout.em;
514 }
515 }
516
517 fz_free(ctx, reorder);
518 }
519
520 typedef struct
521 {
522 fz_pool *pool;
523 float page_top;
524 float page_h;
525 hb_buffer_t *hb_buf;
526 fz_html_restarter *restart;
527 } layout_data;
528
529 static int flush_line(fz_context *ctx, fz_html_box *box, layout_data *ld, float page_w, float line_w, int align, float indent, fz_html_flow *a, fz_html_flow *b, fz_html_restarter *restart)
530 {
531 float avail, line_h, baseline;
532 float page_h = ld->page_h;
533 float page_top = ld->page_top;
534 line_h = measure_line(a, b, &baseline);
535 if (page_h > 0)
536 {
537 avail = page_h - fmodf(box->s.layout.b - page_top, page_h);
538 /* If the line is larger than the available space skip to the start
539 * of the next page. */
540 if (line_h > avail)
541 {
542 if (restart)
543 {
544 assert(restart->start == NULL);
545
546 if (restart->potential)
547 restart->end = restart->potential;
548 else
549 {
550 restart->end = box;
551 restart->end_flow = a;
552 }
553 restart->reason = FZ_HTML_RESTART_REASON_LINE_HEIGHT;
554 return 1;
555 }
556 box->s.layout.b += avail;
557 }
558 }
559 layout_line(ctx, indent, page_w, line_w, align, a, b, box, baseline, line_h);
560 box->s.layout.b += line_h;
561 if (restart)
562 restart->potential = NULL;
563
564 return 0;
565 }
566
567 static void break_word_for_overflow_wrap(fz_context *ctx, fz_html_flow *node, layout_data *ld)
568 {
569 hb_buffer_t *hb_buf = ld->hb_buf;
570 const char *text = node->content.text;
571 string_walker walker;
572
573 assert(node->type == FLOW_WORD);
574 assert(node->atomic == 0);
575
576 /* Split a word node after the first cluster (usually a character), and
577 * flag the second half as a valid node to break before if in desperate
578 * need. This may break earlier than necessary, but in that case we'll
579 * break the second half again when we come to it, until we find a
580 * suitable breaking point.
581 *
582 * We split after each clusters here so we can flag each fragment as
583 * "atomic" so we don't try breaking it again, and also to flag the
584 * following word fragment as a possible break point. Breaking at the
585 * exact desired point would make this more complicated than necessary.
586 *
587 * Desperately breaking in the middle of a word like this should should
588 * rarely (if ever) come up.
589 *
590 * TODO: Split at all the clusters in the word at once.
591 */
592
593 /* Walk string and split at the first cluster. */
594 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, text);
595 while (walk_string(&walker))
596 {
597 unsigned int i, a, b;
598 a = walker.glyph_info[0].cluster;
599 for (i = 0; i < walker.glyph_count; ++i)
600 {
601 b = walker.glyph_info[i].cluster;
602 if (b != a)
603 {
604 fz_html_split_flow(ctx, ld->pool, node, fz_runeidx(text, text + b));
605 node->atomic = 1;
606 node->next->overflow_wrap = 1;
607 measure_string_w(ctx, node, ld->hb_buf);
608 measure_string_w(ctx, node->next, ld->hb_buf);
609 return;
610 }
611 }
612 }
613
614 /* Word is already only one cluster. Don't try breaking here again! */
615 node->atomic = 1;
616 }
617
618 /*
619 Layout a BOX_FLOW.
620
621 Flow box is in a BOX_BLOCK or BOX_TABLE_CELL context, and has no margin/padding/border.
622
623 box: The BOX_FLOW to layout.
624 top: The enclosing box.
625 */
626 static void layout_flow(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
627 {
628 fz_html_flow *node, *line, *candidate, *desperate;
629 fz_html_flow *start_flow = NULL;
630 float line_w, candidate_w, desperate_w, indent;
631 int align;
632 fz_html_restarter *restart = ld->restart;
633
634 float em = box->s.layout.em;
635 indent = box->is_first_flow ? fz_from_css_number(top->style->text_indent, em, top->s.layout.w, 0) : 0;
636 align = top->style->text_align;
637
638 if (box->markup_dir == FZ_BIDI_RTL)
639 {
640 if (align == TA_LEFT)
641 align = TA_RIGHT;
642 else if (align == TA_RIGHT)
643 align = TA_LEFT;
644 }
645
646 /* Position the box, initially zero height. */
647 box->s.layout.x = top->s.layout.x;
648 box->s.layout.y = top->s.layout.b;
649 box->s.layout.w = top->s.layout.w;
650 box->s.layout.b = box->s.layout.y;
651
652 if (restart && restart->start)
653 {
654 /* If we are still skipping, and don't match, nothing to do. */
655 if (restart->start != box)
656 return;
657 /* We match! Remember where we should start. */
658 restart->start = NULL;
659
660 #ifdef DEBUG_LAYOUT_RESTARTING
661 /* Output us some crufty debug */
662 if (restart->start_flow == NULL)
663 printf("<restart>");
664 for (node = box->u.flow.head; node; node = node->next)
665 {
666 if (restart->start_flow == node)
667 printf("<restart>");
668 switch (node->type)
669 {
670 case FLOW_WORD:
671 printf("[%s]", node->content.text);
672 break;
673 case FLOW_BREAK:
674 case FLOW_SBREAK:
675 printf("\n");
676 break;
677 case FLOW_SPACE:
678 printf(" ");
679 break;
680 case FLOW_SHYPHEN:
681 printf("-");
682 break;
683 default:
684 printf("?");
685 break;
686 }
687 }
688 printf("\n");
689 #endif
690 }
691
692 /* If we have nothing to flow, nothing to do. */
693 if (!box->u.flow.head)
694 return;
695
696 /* Measure the size of all the words and images in the flow. */
697 node = box->u.flow.head;
698
699 /* First, if we are restarting, skip over ones we've done already. */
700 if (restart && restart->start_flow)
701 {
702 while(node && node != restart->start_flow)
703 {
704 indent = 0;
705 node = node->next;
706 }
707 start_flow = node;
708 restart->start_flow = NULL;
709 }
710
711 /* Now measure the size of the remaining nodes. */
712 for (; node; node = node->next)
713 {
714 node->breaks_line = 0; /* reset line breaks from previous layout */
715
716 if (node->type == FLOW_IMAGE)
717 {
718 float max_w, max_h;
719 float xs = 1, ys = 1, s;
720 float aspect = 1;
721
722 max_w = top->s.layout.w;
723 max_h = ld->page_h;
724
725 /* NOTE: We ignore the image DPI here, since most images in EPUB files have bogus values. */
726 node->w = node->content.image->w * 72.0f / 96.0f;
727 node->h = node->content.image->h * 72.0f / 96.0f;
728 aspect = node->h ? node->w / node->h : 0;
729
730 if (node->box->style->width.unit != N_AUTO)
731 node->w = fz_from_css_number(node->box->style->width, top->s.layout.em, top->s.layout.w, node->w);
732 if (node->box->style->height.unit != N_AUTO)
733 node->h = fz_from_css_number(node->box->style->height, top->s.layout.em, ld->page_h, node->h);
734 if (node->box->style->width.unit == N_AUTO && node->box->style->height.unit != N_AUTO)
735 node->w = node->h * aspect;
736 if (node->box->style->width.unit != N_AUTO && node->box->style->height.unit == N_AUTO)
737 node->h = (aspect == 0) ? 0 : (node->w / aspect);
738
739 /* Shrink image to fit on one page if needed */
740 if (max_w > 0 && node->w > max_w)
741 xs = max_w / node->w;
742 if (max_h > 0 && node->h > max_h)
743 ys = max_h / node->h;
744 s = fz_min(xs, ys);
745 node->w = node->w * s;
746 node->h = node->h * s;
747
748 }
749 else
750 {
751 /* Note: already measured width in layout_update_widths */
752 measure_string_h(ctx, node);
753 }
754 }
755
756 node = box->u.flow.head;
757 if (start_flow)
758 {
759 line_w = start_flow == node ? indent : 0;
760 node = start_flow;
761 }
762 else
763 {
764 line_w = indent;
765 }
766 line = node;
767
768 candidate = NULL;
769 candidate_w = 0;
770 desperate = NULL;
771 desperate_w = 0;
772
773 /* Now collate the measured nodes into a line. */
774 while (node)
775 {
776
777 switch (node->type)
778 {
779 default:
780 // always same width
781 line_w += node->w;
782 break;
783
784 case FLOW_WORD:
785 /* Allow desperate breaking in middle of word if overflow-wrap: break-word. */
786 if (line_w + node->w > box->s.layout.w && !candidate)
787 {
788 if (!node->atomic && node->box->style->overflow_wrap == OVERFLOW_WRAP_BREAK_WORD)
789 {
790 break_word_for_overflow_wrap(ctx, node, ld);
791 }
792 }
793 /* Remember overflow-wrap word fragments, unless at the beginning of a line. */
794 if (node->overflow_wrap && node != line)
795 {
796 desperate = node;
797 desperate_w = line_w;
798 }
799 line_w += node->w;
800 break;
801
802 case FLOW_BREAK:
803 case FLOW_SBREAK:
804 // always zero width
805 candidate = node;
806 candidate_w = line_w;
807 break;
808
809 case FLOW_SPACE:
810 // zero width if broken, default width if unbroken
811 candidate = node;
812 candidate_w = line_w;
813 line_w += node->w;
814 break;
815
816 case FLOW_SHYPHEN:
817 // default width if broken, zero width if unbroken
818 candidate = node;
819 candidate_w = line_w + node->w;
820 break;
821 }
822
823 /* If we see a hard break...
824 * or the line doesn't fit, and we have a candidate breakpoint...
825 * then we can flush the line so far. */
826 if (node->type == FLOW_BREAK || (line_w > box->s.layout.w && (candidate || desperate)))
827 {
828 int line_align = align;
829 fz_html_flow *break_at;
830 float break_w;
831
832 if (candidate)
833 {
834 if (candidate->type == FLOW_BREAK)
835 line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
836 candidate->breaks_line = 1;
837 break_at = candidate->next;
838 break_w = candidate_w;
839 }
840 else
841 {
842 break_at = desperate;
843 break_w = desperate_w;
844 }
845
846 if (flush_line(ctx, box, ld, box->s.layout.w, break_w, line_align, indent, line, break_at, restart))
847 return;
848
849 line = break_at;
850 node = break_at;
851 candidate = NULL;
852 candidate_w = 0;
853 desperate = NULL;
854 desperate_w = 0;
855 indent = 0;
856 line_w = 0;
857 }
858 else if (restart && (restart->flags & FZ_HTML_RESTARTER_FLAGS_NO_OVERFLOW) && line_w > box->s.layout.w)
859 {
860 /* We are in 'no-overflow' mode, and the line doesn't fit.
861 * This means our first box is too wide to ever fit in a box of this width. */
862 assert(restart->start == NULL);
863
864 restart->end = box;
865 restart->end_flow = start_flow;
866 restart->reason = FZ_HTML_RESTART_REASON_LINE_WIDTH;
867 return;
868 }
869 else
870 {
871 node = node->next;
872 }
873 }
874
875 if (line)
876 {
877 int line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
878 flush_line(ctx, box, ld, box->s.layout.w, line_w, line_align, indent, line, NULL, restart);
879 }
880 }
881
882 static float advance_for_spacing(fz_context *ctx, layout_data *ld, float start_b, float spacing, int *eop)
883 {
884 float page_h = ld->page_h;
885 float page_top = ld->page_top;
886 float avail = page_h - fmodf(start_b - page_top, page_h);
887 if (spacing > avail)
888 {
889 *eop = 1;
890 spacing = avail;
891 }
892 return start_b + spacing;
893 }
894
895 static int layout_block_page_break(fz_context *ctx, layout_data *ld, float *yp, int page_break)
896 {
897 float page_h = ld->page_h;
898 float page_top = ld->page_top;
899 if (page_h <= 0)
900 return 0;
901 if (page_break == PB_ALWAYS || page_break == PB_LEFT || page_break == PB_RIGHT)
902 {
903 float avail = page_h - fmodf(*yp - page_top, page_h);
904 int number = (*yp + (page_h * 0.1f)) / page_h;
905 if (avail > 0 && avail < page_h)
906 {
907 *yp += avail;
908 if (page_break == PB_LEFT && (number & 1) == 0) /* right side pages are even */
909 *yp += page_h;
910 if (page_break == PB_RIGHT && (number & 1) == 1) /* left side pages are odd */
911 *yp += page_h;
912 return 1;
913 }
914 }
915 return 0;
916 }
917
918 static void layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top);
919 static void layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top);
920
921 /* === LAYOUT TABLE === */
922
923 // TODO: apply CSS from colgroup and col definition to table cells
924 // TODO: use CSS border-collapse on table
925 // TODO: use CSS/HTML column-span/colspan on table-cell
926 // TODO: use CSS/HTML width on table-cell when computing maximum width
927
928 struct column_width {
929 float min, max, actual;
930 };
931
932 static float table_cell_padding(fz_context *ctx, fz_html_box *box)
933 {
934 return (
935 box->u.block.padding[L] + box->u.block.border[L] +
936 box->u.block.padding[R] + box->u.block.border[R]
937 );
938 }
939
940 static float block_padding(fz_context *ctx, fz_html_box *box)
941 {
942 return (
943 box->u.block.padding[L] + box->u.block.border[L] + box->u.block.margin[L] +
944 box->u.block.padding[R] + box->u.block.border[R] + box->u.block.margin[R]
945 );
946 }
947
948 static float largest_min_width(fz_context *ctx, fz_html_box *box)
949 {
950 /* The "minimum" width is the largest word in a paragraph. */
951 float r_min = 0;
952 if (box->type == BOX_BLOCK)
953 {
954 fz_html_box *child = box->down;
955 while (child)
956 {
957 float min = largest_min_width(ctx, child);
958 if (min > r_min)
959 r_min = min;
960 child = child->next;
961 }
962 r_min += block_padding(ctx, box);
963 }
964 else if (box->type == BOX_FLOW)
965 {
966 fz_html_flow *flow;
967 for (flow = box->u.flow.head; flow; flow = flow->next)
968 if (flow->w > r_min)
969 r_min = flow->w;
970 }
971 else
972 {
973 // TODO: nested TABLE
974 }
975 return r_min;
976 }
977
978 static float largest_max_width(fz_context *ctx, fz_html_box *box)
979 {
980 /* The "maximum" width is the length of the longest paragraph laid out on one line. */
981 float r_max = 0;
982 if (box->type == BOX_BLOCK)
983 {
984 fz_html_box *child = box->down;
985 while (child)
986 {
987 float max = largest_max_width(ctx, child);
988 if (max > r_max)
989 r_max = max;
990 child = child->next;
991 }
992 r_max += block_padding(ctx, box);
993 }
994 else if (box->type == BOX_FLOW)
995 {
996 fz_html_flow *flow;
997 float max = 0;
998 for (flow = box->u.flow.head; flow; flow = flow->next)
999 {
1000 max += flow->w;
1001 if (flow->type == FLOW_BREAK)
1002 {
1003 if (max > r_max)
1004 r_max = max;
1005 max = 0;
1006 }
1007 }
1008 if (max > r_max)
1009 r_max = max;
1010 }
1011 else
1012 {
1013 // TODO: nested TABLE
1014 }
1015 return r_max;
1016 }
1017
1018 static void squish_block(fz_context *ctx, fz_html_box *box)
1019 {
1020 fz_html_box *child;
1021
1022 box->s.layout.b = box->s.layout.y;
1023 if (box->type == BOX_FLOW)
1024 return;
1025 for (child = box->down; child; child = child->next)
1026 squish_block(ctx, child);
1027 }
1028
1029 static void layout_table_row(fz_context *ctx, layout_data *ld, fz_html_box *row, int ncol, struct column_width *colw, float spacing)
1030 {
1031 fz_html_box *cell, *child;
1032 int col = 0;
1033 float x = row->s.layout.x;
1034 float y;
1035
1036 /* Always layout the full row since we can't restart in the middle of a cell.
1037 * If the row doesn't fit fully, we'll postpone it to the next page.
1038 * FIXME: If the row doesn't fit then either, we should split it with the offset.
1039 */
1040 fz_html_restarter *save_restart = ld->restart;
1041 ld->restart = NULL;
1042
1043 /* Note: margin is ignored for table cells and rows */
1044
1045 /* For each cell in the row */
1046 for (cell = row->down; cell; cell = cell->next)
1047 {
1048 float cell_pad = table_cell_padding(ctx, cell);
1049
1050 x += spacing;
1051
1052 /* Position the cell */
1053 cell->s.layout.y = row->s.layout.y;
1054 cell->s.layout.x = x;
1055 cell->s.layout.w = colw[col].actual - cell_pad;
1056
1057 /* Adjust content box */
1058 cell->s.layout.x += cell->u.block.padding[L] + cell->u.block.border[L];
1059 cell->s.layout.y += cell->u.block.padding[T] + cell->u.block.border[T];
1060 cell->s.layout.b = cell->s.layout.y;
1061
1062 /* Layout cell contents into the cell. */
1063 for (child = cell->down; child; child = child->next)
1064 {
1065 if (child->type == BOX_BLOCK)
1066 {
1067 layout_block(ctx, ld, child, cell);
1068 cell->s.layout.b = child->s.layout.b;
1069 cell->s.layout.b += child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B];
1070 }
1071 else if (child->type == BOX_TABLE)
1072 {
1073 layout_table(ctx, ld, child, cell);
1074 cell->s.layout.b = child->s.layout.b;
1075 cell->s.layout.b += child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B];
1076 }
1077 else if (child->type == BOX_FLOW)
1078 {
1079 layout_flow(ctx, ld, child, cell);
1080 cell->s.layout.b = child->s.layout.b;
1081 }
1082 }
1083
1084 /* Advance to next column */
1085 x += colw[col].actual;
1086
1087 /* Adjust row height if necessary */
1088 y = cell->s.layout.b + cell->u.block.padding[B] + cell->u.block.border[B];
1089 if (y > row->s.layout.b)
1090 row->s.layout.b = y;
1091
1092 ++col;
1093 }
1094
1095 /* For each cell in the row - adjust final cell heights to fill the row */
1096 for (cell = row->down; cell; cell = cell->next)
1097 {
1098 cell->s.layout.b = row->s.layout.b - (cell->u.block.padding[B] + cell->u.block.border[B]);
1099 }
1100
1101 ld->restart = save_restart;
1102 }
1103
1104 static void layout_table(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
1105 {
1106 fz_html_box *row;
1107 int col, ncol = 0;
1108 float min_tabw, max_tabw;
1109 struct column_width *colw;
1110 fz_html_restarter *restart = ld->restart;
1111 float spacing;
1112 int eop = 0;
1113
1114 float em = box->s.layout.em;
1115 float *margin = box->u.block.margin;
1116 float *border = box->u.block.border;
1117 float *padding = box->u.block.padding;
1118 float auto_w;
1119
1120 spacing = fz_from_css_number(box->style->border_spacing, box->s.layout.em, box->s.layout.w, 0);
1121
1122 if (restart)
1123 {
1124 /* We have reached the restart point */
1125 if (restart->start == box)
1126 restart->start = NULL;
1127 }
1128
1129 /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
1130 if (restart && restart->start != NULL)
1131 {
1132 /* We're still skipping, don't check for pagebreak before! */
1133 }
1134 else if (layout_block_page_break(ctx, ld, &top->s.layout.b, box->style->page_break_before))
1135 eop = 1;
1136
1137 /* Position table in box flow, and add margins and padding */
1138 box->s.layout.y = advance_for_spacing(ctx, ld, top->s.layout.b, margin[T] + border[T] + padding[T], &eop);
1139
1140 if (eop)
1141 {
1142 if (restart && restart->end == NULL)
1143 {
1144 box->s.layout.b = box->s.layout.y;
1145 if (restart->potential)
1146 restart->end = restart->potential;
1147 else
1148 restart->end = box;
1149 return;
1150 }
1151 }
1152
1153 /* Finalize position and width. */
1154 auto_w = top->s.layout.w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
1155 box->s.layout.x = top->s.layout.x + margin[L] + border[L] + padding[L];
1156 box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
1157
1158 /* Add initial border-spacing */
1159 box->s.layout.b = box->s.layout.y + spacing;
1160
1161 /* Find the maximum number of columns. (Count 'col' for each row, biggest one gives ncol). */
1162 for (row = box->down; row; row = row->next)
1163 {
1164 fz_html_box *cell;
1165 col = 0;
1166 for (cell = row->down; cell; cell = cell->next)
1167 ++col;
1168 if (col > ncol)
1169 ncol = col;
1170 }
1171
1172 colw = fz_malloc_array(ctx, ncol, struct column_width);
1173
1174 // TODO: colgroups and colspan
1175
1176 fz_try(ctx)
1177 {
1178 /* Table Autolayout algorithm from HTML */
1179 /* https://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2 */
1180
1181 /* Calculate largest minimum and maximum column widths */
1182 for (col = 0; col < ncol; ++col)
1183 {
1184 colw[col].min = 0;
1185 colw[col].max = 0;
1186 }
1187
1188 for (row = box->down; row; row = row->next)
1189 {
1190 fz_html_box *cell, *child;
1191 for (col = 0, cell = row->down; cell; cell = cell->next, ++col)
1192 {
1193 float cell_pad = table_cell_padding(ctx, cell);
1194 for (child = cell->down; child; child = child->next)
1195 {
1196 float min = largest_min_width(ctx, child) + cell_pad;
1197 float max = largest_max_width(ctx, child) + cell_pad;
1198 if (min > colw[col].min)
1199 colw[col].min = min;
1200 if (max > colw[col].max)
1201 colw[col].max = max;
1202 }
1203 }
1204 }
1205
1206 min_tabw = max_tabw = 0;
1207 for (col = 0; col < ncol; ++col)
1208 {
1209 min_tabw += colw[col].min;
1210 max_tabw += colw[col].max;
1211 }
1212 min_tabw += spacing * (ncol + 1);
1213 max_tabw += spacing * (ncol + 1);
1214
1215 /* The minimum table width is equal to or wider than the available space.
1216 * In this case, assign the minimum widths and let the lines overflow...
1217 */
1218 if (min_tabw >= box->s.layout.w)
1219 {
1220 for (col = 0; col < ncol; ++col)
1221 colw[col].actual = colw[col].min;
1222 }
1223
1224 /* The maximum table width fits within the available space.
1225 * In this case, set the columns to their maximum widths.
1226 */
1227 else if (max_tabw <= box->s.layout.w)
1228 {
1229 box->s.layout.w = max_tabw;
1230 for (col = 0; col < ncol; ++col)
1231 colw[col].actual = colw[col].max;
1232 }
1233
1234 /* The maximum width of the table is greater than the available space, but
1235 * the minimum table width is smaller. In this case, find the difference
1236 * between the available space and the minimum table width, lets call it
1237 * W. Lets also call D the difference between maximum and minimum width of
1238 * the table.
1239 *
1240 * For each column, let d be the difference between maximum and minimum
1241 * width of that column. Now set the column's width to the minimum width
1242 * plus d times W over D. This makes columns with large differences
1243 * between minimum and maximum widths wider than columns with smaller
1244 * differences.
1245 */
1246 else
1247 {
1248 float W = (box->s.layout.w - min_tabw);
1249 float D = (max_tabw - min_tabw);
1250 for (col = 0; col < ncol; ++col)
1251 colw[col].actual = colw[col].min + (colw[col].max - colw[col].min) * W / D;
1252 }
1253
1254 /* Layout each row in turn. */
1255 for (row = box->down; row; row = row->next)
1256 {
1257 /* Position the row, zero height for now. */
1258 row->s.layout.x = box->s.layout.x;
1259 row->s.layout.w = box->s.layout.w;
1260 row->s.layout.y = row->s.layout.b = box->s.layout.b;
1261 row->s.layout.b = row->s.layout.y;
1262
1263 if (restart && restart->start != NULL)
1264 {
1265 if (restart->start == row)
1266 restart->start = NULL;
1267 else
1268 {
1269 squish_block(ctx, row);
1270 continue; /* still skipping */
1271 }
1272 }
1273
1274 layout_table_row(ctx, ld, row, ncol, colw, spacing);
1275
1276 /* If the row doesn't fit on the current page, break here and put the row on the next page.
1277 * Unless the row was at the very start of the page, in which case it'll overflow instead.
1278 * FIXME: Don't overflow, draw twice with offset to break it abruptly at the page border!
1279 */
1280 if (ld->page_h > 0)
1281 {
1282 float avail = ld->page_h - fmodf(row->s.layout.y - ld->page_top, ld->page_h);
1283 float used = row->s.layout.b - row->s.layout.y;
1284 if (used > avail && avail < ld->page_h)
1285 {
1286 if (restart)
1287 {
1288 restart->end = row;
1289 goto exit;
1290 }
1291 else
1292 {
1293 row->s.layout.y += avail;
1294 layout_table_row(ctx, ld, row, ncol, colw, spacing);
1295 }
1296 }
1297 }
1298
1299 box->s.layout.b = row->s.layout.b + spacing;
1300 }
1301 exit:;
1302 }
1303 fz_always(ctx)
1304 fz_free(ctx, colw);
1305 fz_catch(ctx)
1306 fz_rethrow(ctx);
1307
1308 if (restart && restart->start != NULL)
1309 {
1310 /* We're still skipping, don't check for pagebreak after! */
1311 }
1312 else if (layout_block_page_break(ctx, ld, &top->s.layout.b, box->style->page_break_after))
1313 {
1314 if (restart && restart->end == NULL)
1315 {
1316 if (restart->potential)
1317 restart->end = restart->potential;
1318 else
1319 restart->end = box;
1320 return;
1321 }
1322 }
1323 }
1324
1325 /* === LAYOUT BLOCKS === */
1326
1327 /*
1328 Layout a BOX_BLOCK.
1329
1330 ctx: The ctx in use.
1331 box: The BOX_BLOCK to layout.
1332 top_x: The x position for left of the topmost box.
1333 top_b: Pointer to the y position for the top of the topmost box on entry, updated to the y position for the bottom of the topmost box on exit.
1334 top_w: The width available for the topmost box.
1335 */
1336 static void layout_block(fz_context *ctx, layout_data *ld, fz_html_box *box, fz_html_box *top)
1337 {
1338 fz_html_box *child;
1339 fz_html_restarter *restart = ld->restart;
1340
1341 const fz_css_style *style = box->style;
1342 float em = box->s.layout.em;
1343 float *margin = box->u.block.margin;
1344 float *border = box->u.block.border;
1345 float *padding = box->u.block.padding;
1346 float auto_w;
1347 int eop = 0;
1348
1349 assert(fz_html_box_has_boxes(box));
1350
1351 /* If restart is non-NULL, we are running in restartable mode. */
1352 if (restart)
1353 {
1354 /* If restart->start == box, then we've been skipping, and this
1355 * is the point at which we should restart. */
1356 if (restart->start == box)
1357 {
1358 /* Set restart->start to be NULL to indicate that we aren't skipping
1359 * any more. */
1360 restart->start = NULL;
1361 }
1362
1363 /* In the event that no content fits, we want to restart with us, not with the
1364 * content that doesn't fit. */
1365 if (restart->potential == NULL)
1366 restart->potential = box;
1367 }
1368
1369 /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
1370 if (restart && restart->start != NULL)
1371 {
1372 /* We're still skipping, don't check for pagebreak before! */
1373 }
1374 else if (layout_block_page_break(ctx, ld, &top->s.layout.b, style->page_break_before))
1375 eop = 1;
1376
1377 /* Important to remember that box->{x,y,w,b} are the coordinates of the content. The
1378 * margin/border/paddings are all outside this. */
1379 box->s.layout.y = top->s.layout.b;
1380 if (restart && restart->start != NULL)
1381 {
1382 /* We're still skipping, so any child should inherit 0 vertical margin from us. */
1383 }
1384 else
1385 {
1386 /* We're not skipping, so add in the spacings to the top edge of our box. */
1387 box->s.layout.y = advance_for_spacing(ctx, ld, box->s.layout.y, margin[T] + border[T] + padding[T], &eop);
1388 }
1389 if (eop)
1390 {
1391 if (restart && restart->end == NULL)
1392 {
1393 box->s.layout.b = box->s.layout.y;
1394 if (restart->potential)
1395 restart->end = restart->potential;
1396 else
1397 restart->end = box;
1398 return;
1399 }
1400 }
1401
1402 /* Finalize position and width. */
1403 auto_w = top->s.layout.w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
1404 box->s.layout.x = top->s.layout.x + margin[L] + border[L] + padding[L];
1405 box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
1406
1407 /* Start with our content being zero height. */
1408 box->s.layout.b = box->s.layout.y;
1409
1410 for (child = box->down; child; child = child->next)
1411 {
1412 if (restart && restart->end == NULL)
1413 {
1414 /* If advancing over the bottom margin/border/padding of a child has previously
1415 * brought us to the end of a page, this is where we end. */
1416 if (eop)
1417 {
1418 if (restart->potential)
1419 restart->end = restart->potential;
1420 else
1421 restart->end = child;
1422 break;
1423 }
1424 }
1425
1426 if (child->type == BOX_BLOCK || child->type == BOX_TABLE)
1427 {
1428 assert(fz_html_box_has_boxes(child));
1429 if (child->type == BOX_BLOCK)
1430 layout_block(ctx, ld, child, box);
1431 else
1432 layout_table(ctx, ld, child, box);
1433
1434 /* Unless we're still skipping, the base of our box must now be at least as
1435 * far down as the child, plus the child's spacing. */
1436 if (!restart || restart->start == NULL)
1437 {
1438 box->s.layout.b = advance_for_spacing(ctx, ld,
1439 child->s.layout.b,
1440 child->u.block.padding[B] + child->u.block.border[B] + child->u.block.margin[B],
1441 &eop);
1442 }
1443 }
1444
1445 else if (child->type == BOX_FLOW)
1446 {
1447 layout_flow(ctx, ld, child, box);
1448 if (child->s.layout.b > child->s.layout.y)
1449 {
1450 if (!restart || restart->start == NULL)
1451 {
1452 box->s.layout.b = child->s.layout.b;
1453 }
1454 }
1455 }
1456
1457 /* Stop if we've reached the endpoint. */
1458 if (restart && restart->end != NULL)
1459 break;
1460 if (restart && box->s.layout.b != box->s.layout.y)
1461 restart->potential = NULL;
1462 }
1463
1464 /* If we're still skipping, exit with vertical=0. */
1465 /* If we've reached the endpoint, exit. */
1466 if (restart && (restart->start != NULL || restart->end != NULL))
1467 return;
1468
1469 /* reserve space for the list mark */
1470 if (box->list_item && box->s.layout.y == box->s.layout.b)
1471 {
1472 box->s.layout.b += fz_from_css_number_scale(style->line_height, em);
1473 }
1474
1475 if (restart && restart->start != NULL)
1476 {
1477 /* We're still skipping, don't check for pagebreak after! */
1478 }
1479 else if (layout_block_page_break(ctx, ld, &box->s.layout.b, style->page_break_after))
1480 {
1481 if (restart && restart->end == NULL)
1482 {
1483 if (restart->potential)
1484 restart->end = restart->potential;
1485 else
1486 restart->end = box;
1487 return;
1488 }
1489 }
1490 }
1491
1492 /* === LAYOUT === */
1493
1494 // Compute new em, padding, border, margin.
1495 // Also compute layout x and w.
1496 static void layout_update_styles(fz_context *ctx, fz_html_box *box, fz_html_box *top)
1497 {
1498 float top_em = top->s.layout.em;
1499 float top_baseline = top->s.layout.baseline;
1500 float top_w = top->s.layout.w;
1501 while (box)
1502 {
1503 const fz_css_style *style = box->style;
1504 float em = box->s.layout.em = fz_from_css_number(style->font_size, top_em, top_em, top_em);
1505
1506 if (style->vertical_align == VA_SUPER)
1507 box->s.layout.baseline = top_baseline - top_em / 3;
1508 else if (style->vertical_align == VA_SUB)
1509 box->s.layout.baseline = top_baseline + top_em / 5;
1510 else
1511 box->s.layout.baseline = top_baseline;
1512
1513 if (box->type != BOX_INLINE && box->type != BOX_FLOW)
1514 {
1515 float *margin = box->u.block.margin;
1516 float *border = box->u.block.border;
1517 float *padding = box->u.block.padding;
1518
1519 margin[T] = fz_from_css_number(style->margin[T], em, top_w, 0);
1520 margin[R] = fz_from_css_number(style->margin[R], em, top_w, 0);
1521 margin[B] = fz_from_css_number(style->margin[B], em, top_w, 0);
1522 margin[L] = fz_from_css_number(style->margin[L], em, top_w, 0);
1523
1524 padding[T] = fz_from_css_number(style->padding[T], em, top_w, 0);
1525 padding[R] = fz_from_css_number(style->padding[R], em, top_w, 0);
1526 padding[B] = fz_from_css_number(style->padding[B], em, top_w, 0);
1527 padding[L] = fz_from_css_number(style->padding[L], em, top_w, 0);
1528
1529 border[T] = style->border_style_0 ? fz_from_css_number(style->border_width[T], em, top_w, 0) : 0;
1530 border[R] = style->border_style_1 ? fz_from_css_number(style->border_width[R], em, top_w, 0) : 0;
1531 border[B] = style->border_style_2 ? fz_from_css_number(style->border_width[B], em, top_w, 0) : 0;
1532 border[L] = style->border_style_3 ? fz_from_css_number(style->border_width[L], em, top_w, 0) : 0;
1533
1534 // TODO: BLOCK nested inside TABLE!
1535 if (box->type == BOX_BLOCK || box->type == BOX_TABLE)
1536 {
1537 /* Compute preliminary width (will be adjusted if block is nested in table cell later) */
1538 float auto_w = top_w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
1539 box->s.layout.w = fz_from_css_number(box->style->width, em, auto_w, auto_w);
1540 }
1541 }
1542
1543 else if (box->type == BOX_FLOW)
1544 {
1545 box->s.layout.x = top->s.layout.x;
1546 box->s.layout.w = top->s.layout.w;
1547 }
1548
1549 if (box->down)
1550 layout_update_styles(ctx, box->down, box);
1551
1552 box = box->next;
1553 }
1554 }
1555
1556 static void layout_update_widths(fz_context *ctx, fz_html_box *box, fz_html_box *top, hb_buffer_t *hb_buf)
1557 {
1558 while (box)
1559 {
1560 if (box->type == BOX_FLOW)
1561 {
1562 fz_html_flow *node;
1563
1564 for (node = box->u.flow.head; node; node = node->next)
1565 {
1566 if (node->type == FLOW_IMAGE)
1567 /* start with "native" size (only used for table width calculations) */
1568 node->w = node->content.image->w * 72.0f / 96.0f;
1569 else if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
1570 measure_string_w(ctx, node, hb_buf);
1571 }
1572 }
1573
1574 if (box->down)
1575 layout_update_widths(ctx, box->down, box, hb_buf);
1576
1577 box = box->next;
1578 }
1579 }
1580
1581 static int is_layout_box(fz_html_box *box)
1582 {
1583 return box->type == BOX_BLOCK || box->type == BOX_TABLE;
1584 }
1585
1586 static int is_empty_block_box(fz_html_box *box)
1587 {
1588 fz_html_box *child;
1589 if (box->type == BOX_BLOCK)
1590 {
1591 if (box->u.block.padding[T] != 0 || box->u.block.padding[B] != 0)
1592 return 0;
1593 if (box->u.block.border[T] != 0 || box->u.block.border[B] != 0)
1594 return 0;
1595 for (child = box->down; child; child = child->next)
1596 {
1597 if (child->type != BOX_BLOCK)
1598 return 0;
1599 if (!is_empty_block_box(child))
1600 return 0;
1601 if (child->u.block.margin[T] != 0 || child->u.block.margin[B] != 0)
1602 return 0;
1603 }
1604 return 1;
1605 }
1606 return 0;
1607 }
1608
1609 static void layout_collapse_margin_with_children(fz_context *ctx, fz_html_box *here)
1610 {
1611 fz_html_box *child, *first, *last = NULL;
1612
1613 first = here->down;
1614 for (child = here->down; child; child = child->next)
1615 {
1616 layout_collapse_margin_with_children(ctx, child);
1617 last = child;
1618 }
1619
1620 if (is_layout_box(here))
1621 {
1622 if (first && is_layout_box(first))
1623 {
1624 if (first->u.block.border[T] == 0 && first->u.block.padding[T] == 0)
1625 {
1626 float m = fz_max(first->u.block.margin[T], here->u.block.margin[T]);
1627 here->u.block.margin[T] = m;
1628 first->u.block.margin[T] = 0;
1629 }
1630 }
1631
1632 if (last && is_layout_box(last))
1633 {
1634 if (last->u.block.border[T] == 0 && last->u.block.padding[T] == 0)
1635 {
1636 float m = fz_max(last->u.block.margin[B], here->u.block.margin[B]);
1637 here->u.block.margin[B] = m;
1638 last->u.block.margin[B] = 0;
1639 }
1640 }
1641 }
1642 }
1643
1644 static void layout_collapse_margin_with_siblings(fz_context *ctx, fz_html_box *here)
1645 {
1646 while (here)
1647 {
1648 fz_html_box *next = here->next;
1649
1650 if (here->down)
1651 layout_collapse_margin_with_siblings(ctx, here->down);
1652
1653 if (is_layout_box(here) && next && is_layout_box(next))
1654 {
1655 float m = fz_max(here->u.block.margin[B], next->u.block.margin[T]);
1656 here->u.block.margin[B] = m;
1657 next->u.block.margin[T] = 0;
1658 }
1659
1660 here = next;
1661 }
1662 }
1663
1664 static void layout_collapse_margin_with_self(fz_context *ctx, fz_html_box *here)
1665 {
1666 while (here)
1667 {
1668 if (here->down)
1669 layout_collapse_margin_with_self(ctx, here->down);
1670
1671 if (is_layout_box(here) && is_empty_block_box(here))
1672 {
1673 float m = fz_max(here->u.block.margin[T], here->u.block.margin[B]);
1674 here->u.block.margin[T] = 0;
1675 here->u.block.margin[B] = m;
1676 }
1677
1678 here = here->next;
1679 }
1680 }
1681
1682 static void layout_collapse_margins(fz_context *ctx, fz_html_box *box, fz_html_box *top)
1683 {
1684 // Vertical margins are collapsed in these cases:
1685 // 1) Adjacent siblings
1686 // 2) No content separating parent and descendants. The margin ends up outside the parent.
1687 // 3) Empty blocks
1688
1689 layout_collapse_margin_with_self(ctx, box);
1690 layout_collapse_margin_with_children(ctx, box);
1691 layout_collapse_margin_with_siblings(ctx, box);
1692 }
1693
1694 void
1695 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)
1696 {
1697 int unlocked = 0;
1698 layout_data ld = { 0 };
1699 fz_html_box *box = tree->root;
1700
1701 fz_var(ld.hb_buf);
1702 fz_var(unlocked);
1703
1704 // nothing to layout
1705 if (!box->down)
1706 {
1707 fz_warn(ctx, "html: nothing to layout");
1708 box->s.layout.em = em;
1709 box->s.layout.baseline = 0;
1710 box->s.layout.x = start_x;
1711 box->s.layout.w = page_w;
1712 box->s.layout.y = start_y;
1713 box->s.layout.b = start_y;
1714 return;
1715 }
1716
1717 fz_hb_lock(ctx);
1718
1719 fz_try(ctx)
1720 {
1721 Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
1722 ld.hb_buf = hb_buffer_create();
1723 Memento_stopLeaking(); /* HarfBuzz leaks harmlessly */
1724 unlocked = 1;
1725 fz_hb_unlock(ctx);
1726
1727 ld.restart = restart;
1728 ld.page_h = page_h;
1729 ld.page_top = start_y;
1730 ld.pool = tree->pool;
1731 if (restart)
1732 restart->potential = NULL;
1733
1734 // Update em/margin/padding/border if necessary.
1735 if (box->s.layout.em != em || box->s.layout.x != start_x || box->s.layout.w != page_w)
1736 {
1737 box->s.layout.em = em;
1738 box->s.layout.baseline = 0;
1739 box->s.layout.x = start_x;
1740 box->s.layout.w = page_w;
1741 layout_update_styles(ctx, box->down, box);
1742 layout_update_widths(ctx, box->down, box, ld.hb_buf);
1743 layout_collapse_margins(ctx, box->down, box);
1744 }
1745
1746 box->s.layout.y = start_y;
1747 box->s.layout.b = start_y;
1748
1749 assert(box->type == BOX_BLOCK);
1750 layout_block(ctx, &ld, box, box); // HACK: layout box with itself as parent!
1751 }
1752 fz_always(ctx)
1753 {
1754 if (unlocked)
1755 fz_hb_lock(ctx);
1756 hb_buffer_destroy(ld.hb_buf);
1757 fz_hb_unlock(ctx);
1758 }
1759 fz_catch(ctx)
1760 {
1761 fz_rethrow(ctx);
1762 }
1763 }
1764
1765 void
1766 fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
1767 {
1768 /* If we're already laid out to the specifications we need,
1769 * nothing to do. */
1770 if (html->layout_w == w && html->layout_h == h && html->layout_em == em)
1771 return;
1772
1773 html->page_margin[T] = fz_from_css_number(html->tree.root->style->margin[T], em, em, 0);
1774 html->page_margin[B] = fz_from_css_number(html->tree.root->style->margin[B], em, em, 0);
1775 html->page_margin[L] = fz_from_css_number(html->tree.root->style->margin[L], em, em, 0);
1776 html->page_margin[R] = fz_from_css_number(html->tree.root->style->margin[R], em, em, 0);
1777
1778 html->page_w = w - html->page_margin[L] - html->page_margin[R];
1779 if (html->page_w <= 72)
1780 html->page_w = 72; /* enforce a minimum page size! */
1781 if (h > 0)
1782 {
1783 html->page_h = h - html->page_margin[T] - html->page_margin[B];
1784 if (html->page_h <= 72)
1785 html->page_h = 72; /* enforce a minimum page size! */
1786 }
1787 else
1788 {
1789 /* h 0 means no pagination */
1790 html->page_h = 0;
1791 }
1792
1793 fz_restartable_layout_html(ctx, &html->tree, 0, 0, html->page_w, html->page_h, em, NULL);
1794
1795 if (h == 0)
1796 html->page_h = html->tree.root->s.layout.b;
1797
1798 /* Remember how we're laid out so we can avoid needless
1799 * relayouts in future. */
1800 html->layout_w = w;
1801 html->layout_h = h;
1802 html->layout_em = em;
1803
1804 #ifndef NDEBUG
1805 if (fz_atoi(getenv("FZ_DEBUG_HTML")))
1806 fz_debug_html(ctx, html->tree.root);
1807 #endif
1808 }
1809
1810 /* === DRAW === */
1811
1812 typedef struct
1813 {
1814 float rgb[3];
1815 float a;
1816 } unpacked_color;
1817
1818 static inline unpacked_color
1819 unpack_color(const fz_css_color src)
1820 {
1821 unpacked_color dst;
1822 dst.rgb[0] = src.r / 255.0f;
1823 dst.rgb[1] = src.g / 255.0f;
1824 dst.rgb[2] = src.b / 255.0f;
1825 dst.a = src.a / 255.0f;
1826 return dst;
1827 }
1828
1829 static inline int
1830 color_eq(fz_css_color a, fz_css_color b)
1831 {
1832 return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
1833 }
1834
1835 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)
1836 {
1837 fz_html_flow *node;
1838 fz_text *text = NULL;
1839 fz_path *line = NULL;
1840 fz_matrix trm;
1841 fz_css_color prev_color = { 0, 0, 0, 0 };
1842 fz_css_color prev_fill_color = { 0, 0, 0, 0 };
1843 fz_css_color prev_stroke_color = { 0, 0, 0, 0 };
1844 float line_width, prev_line_width = 0;
1845 int filling, prev_filling = 0;
1846 int stroking, prev_stroking = 0;
1847 int restartable_ended = 0;
1848 fz_stroke_state *ss = NULL;
1849 fz_stroke_state *line_ss = NULL;
1850
1851 fz_var(text);
1852 fz_var(line);
1853 fz_var(ss);
1854 fz_var(line_ss);
1855
1856 /* FIXME: HB_DIRECTION_TTB? */
1857
1858 if (restart && restart->start != NULL && restart->start != box)
1859 return 0;
1860
1861 fz_try(ctx)
1862 {
1863 for (node = box->u.flow.head; node; node = node->next)
1864 {
1865 const fz_css_style *style = node->box->style;
1866
1867 if (restart)
1868 {
1869 if (restart->start_flow != NULL)
1870 {
1871 if (restart->start_flow != node)
1872 continue;
1873 restart->start = NULL;
1874 restart->start_flow = NULL;
1875 }
1876
1877 if (restart->end == box && restart->end_flow == node)
1878 {
1879 restartable_ended = 1;
1880 break;
1881 }
1882 }
1883
1884 if (node->type == FLOW_IMAGE)
1885 {
1886 if (node->y >= page_bot || node->y + node->h <= page_top)
1887 continue;
1888 }
1889 else
1890 {
1891 if (node->y > page_bot || node->y < page_top)
1892 continue;
1893 }
1894
1895 if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
1896 {
1897 string_walker walker;
1898 const char *s;
1899 float x, y;
1900 float em;
1901
1902 if (node->type == FLOW_SPACE && node->breaks_line)
1903 continue;
1904 if (node->type == FLOW_SHYPHEN && !node->breaks_line)
1905 continue;
1906 if (style->visibility != V_VISIBLE)
1907 continue;
1908
1909 em = node->box->s.layout.em;
1910
1911 line_width = fz_from_css_number(style->text_stroke_width, em, em, 0);
1912 filling = style->text_fill_color.a != 0;
1913 stroking = style->text_stroke_color.a != 0;
1914 if (stroking)
1915 {
1916 if (ss == NULL)
1917 ss = fz_new_stroke_state(ctx);
1918 }
1919 if (line)
1920 {
1921 if (line_ss == NULL)
1922 line_ss = fz_new_stroke_state(ctx);
1923 }
1924
1925 if ( /* If we've changed whether we're filling... */
1926 filling != prev_filling ||
1927 /* Or we're filling and the color has changed... */
1928 (prev_filling && !color_eq(style->text_fill_color, prev_fill_color)) ||
1929 /* Or we've changed whether we're stroking... */
1930 stroking != prev_stroking ||
1931 /* Or we're stroking, and the color or linewidth has changed... */
1932 (prev_stroking && (!color_eq(style->text_stroke_color, prev_stroke_color) || line_width != prev_line_width)))
1933 {
1934 if (text)
1935 {
1936 if (prev_filling)
1937 {
1938 unpacked_color color = unpack_color(prev_fill_color);
1939 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
1940 }
1941 if (prev_stroking)
1942 {
1943 unpacked_color color = unpack_color(prev_stroke_color);
1944 ss->linewidth = prev_line_width;
1945 fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
1946 }
1947 fz_drop_text(ctx, text);
1948 text = NULL;
1949 }
1950 prev_filling = filling;
1951 prev_stroking = stroking;
1952 }
1953 prev_fill_color = style->text_fill_color;
1954 prev_stroke_color = style->text_stroke_color;
1955 prev_line_width = line_width;
1956
1957 if (!color_eq(style->color, prev_color))
1958 {
1959 if (line)
1960 {
1961 unpacked_color color = unpack_color(prev_color);
1962 fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
1963 fz_drop_path(ctx, line);
1964 line = NULL;
1965 }
1966 prev_color = style->color;
1967 }
1968
1969 if (style->text_decoration > 0)
1970 {
1971 if (!line)
1972 {
1973 line = fz_new_path(ctx);
1974 if (line_ss == NULL)
1975 line_ss = fz_new_stroke_state(ctx);
1976 }
1977 if (style->text_decoration & TD_UNDERLINE)
1978 {
1979 fz_moveto(ctx, line, node->x, node->y + 1.5f - page_top);
1980 fz_lineto(ctx, line, node->x + node->w, node->y + 1.5f - page_top);
1981 }
1982 if (style->text_decoration & TD_LINE_THROUGH)
1983 {
1984 fz_moveto(ctx, line, node->x, node->y - em * 0.3f - page_top);
1985 fz_lineto(ctx, line, node->x + node->w, node->y - em * 0.3f - page_top);
1986 }
1987 }
1988
1989 if (!text)
1990 text = fz_new_text(ctx);
1991
1992 if (node->bidi_level & 1)
1993 x = node->x + node->w;
1994 else
1995 x = node->x;
1996 y = node->y;
1997
1998 trm.a = em;
1999 trm.b = 0;
2000 trm.c = 0;
2001 trm.d = -em;
2002 trm.e = x;
2003 trm.f = y - page_top;
2004
2005 s = get_node_text(ctx, node);
2006 init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, style->font, node->script, node->markup_lang, style->small_caps, s);
2007 while (walk_string(&walker))
2008 {
2009 float node_scale = node->box->s.layout.em / walker.scale;
2010 unsigned int i;
2011 uint32_t k;
2012 int c, n;
2013
2014 /* Flatten advance and offset into offset array. */
2015 int x_advance = 0;
2016 int y_advance = 0;
2017 for (i = 0; i < walker.glyph_count; ++i)
2018 {
2019 walker.glyph_pos[i].x_offset += x_advance;
2020 walker.glyph_pos[i].y_offset += y_advance;
2021 x_advance += walker.glyph_pos[i].x_advance;
2022 y_advance += walker.glyph_pos[i].y_advance;
2023 }
2024
2025 if (node->bidi_level & 1)
2026 x -= x_advance * node_scale;
2027
2028 /* Walk characters to find glyph clusters */
2029 k = 0;
2030 while (walker.start + k < walker.end)
2031 {
2032 n = fz_chartorune(&c, walker.start + k);
2033
2034 for (i = 0; i < walker.glyph_count; ++i)
2035 {
2036 if (walker.glyph_info[i].cluster == k)
2037 {
2038 trm.e = x + walker.glyph_pos[i].x_offset * node_scale;
2039 trm.f = y - walker.glyph_pos[i].y_offset * node_scale - page_top;
2040 fz_show_glyph(ctx, text, walker.font, trm,
2041 walker.glyph_info[i].codepoint, c,
2042 0, node->bidi_level, box->markup_dir, node->markup_lang);
2043 c = -1; /* for subsequent glyphs in x-to-many mappings */
2044 }
2045 }
2046
2047 /* no glyph found (many-to-many or many-to-one mapping) */
2048 if (c != -1)
2049 {
2050 fz_show_glyph(ctx, text, walker.font, trm,
2051 -1, c,
2052 0, node->bidi_level, box->markup_dir, node->markup_lang);
2053 }
2054
2055 k += n;
2056 }
2057
2058 if ((node->bidi_level & 1) == 0)
2059 x += x_advance * node_scale;
2060
2061 y += y_advance * node_scale;
2062 }
2063 }
2064 else if (node->type == FLOW_IMAGE)
2065 {
2066 if (text)
2067 {
2068 if (filling)
2069 {
2070 unpacked_color color = unpack_color(prev_fill_color);
2071 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
2072 }
2073 if (stroking)
2074 {
2075 unpacked_color color = unpack_color(prev_stroke_color);
2076 ss->linewidth = line_width;
2077 fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
2078 }
2079 fz_drop_text(ctx, text);
2080 text = NULL;
2081 }
2082 if (style->visibility == V_VISIBLE)
2083 {
2084 float alpha = style->color.a / 255.0f;
2085 fz_matrix itm = fz_pre_translate(ctm, node->x, node->y - page_top);
2086 itm = fz_pre_scale(itm, node->w, node->h);
2087 fz_fill_image(ctx, dev, node->content.image, itm, alpha, fz_default_color_params);
2088 }
2089 }
2090 }
2091
2092 if (text)
2093 {
2094 if (filling)
2095 {
2096 unpacked_color color = unpack_color(prev_fill_color);
2097 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
2098 }
2099 if (stroking)
2100 {
2101 unpacked_color color = unpack_color(prev_stroke_color);
2102 ss->linewidth = prev_line_width;
2103 fz_stroke_text(ctx, dev, text, ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
2104 }
2105 fz_drop_text(ctx, text);
2106 text = NULL;
2107 }
2108
2109 if (line)
2110 {
2111 unpacked_color color = unpack_color(prev_color);
2112 fz_stroke_path(ctx, dev, line, line_ss, ctm, fz_device_rgb(ctx), color.rgb, color.a, fz_default_color_params);
2113 fz_drop_path(ctx, line);
2114 line = NULL;
2115 }
2116 }
2117 fz_always(ctx)
2118 {
2119 fz_drop_text(ctx, text);
2120 fz_drop_path(ctx, line);
2121 fz_drop_stroke_state(ctx, ss);
2122 fz_drop_stroke_state(ctx, line_ss);
2123 }
2124 fz_catch(ctx)
2125 fz_rethrow(ctx);
2126
2127 return restartable_ended;
2128 }
2129
2130 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)
2131 {
2132 if (color.a > 0)
2133 {
2134 float rgb[3];
2135
2136 fz_path *path = fz_new_path(ctx);
2137
2138 fz_moveto(ctx, path, x0, y0 - page_top);
2139 fz_lineto(ctx, path, x1, y0 - page_top);
2140 fz_lineto(ctx, path, x1, y1 - page_top);
2141 fz_lineto(ctx, path, x0, y1 - page_top);
2142 fz_closepath(ctx, path);
2143
2144 rgb[0] = color.r / 255.0f;
2145 rgb[1] = color.g / 255.0f;
2146 rgb[2] = color.b / 255.0f;
2147
2148 fz_fill_path(ctx, dev, path, 0, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
2149
2150 fz_drop_path(ctx, path);
2151 }
2152 }
2153
2154 static const char *roman_uc[3][10] = {
2155 { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
2156 { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
2157 { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
2158 };
2159
2160 static const char *roman_lc[3][10] = {
2161 { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
2162 { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
2163 { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" },
2164 };
2165
2166 static void format_roman_number(fz_context *ctx, char *buf, int size, int n, const char *sym[3][10], const char *sym_m)
2167 {
2168 int I = n % 10;
2169 int X = (n / 10) % 10;
2170 int C = (n / 100) % 10;
2171 int M = (n / 1000);
2172
2173 fz_strlcpy(buf, "", size);
2174 while (M--)
2175 fz_strlcat(buf, sym_m, size);
2176 fz_strlcat(buf, sym[2][C], size);
2177 fz_strlcat(buf, sym[1][X], size);
2178 fz_strlcat(buf, sym[0][I], size);
2179 fz_strlcat(buf, ". ", size);
2180 }
2181
2182 static void format_alpha_number(fz_context *ctx, char *buf, int size, int n, int alpha, int omega)
2183 {
2184 int base = omega - alpha + 1;
2185 int tmp[40];
2186 int i, c;
2187
2188 if (alpha > 256) /* to skip final-s for greek */
2189 --base;
2190
2191 /* Bijective base-26 (base-24 for greek) numeration */
2192 i = 0;
2193 while (n > 0)
2194 {
2195 --n;
2196 c = n % base + alpha;
2197 if (alpha > 256 && c > alpha + 16) /* skip final-s for greek */
2198 ++c;
2199 tmp[i++] = c;
2200 n /= base;
2201 }
2202
2203 while (i > 0)
2204 buf += fz_runetochar(buf, tmp[--i]);
2205 *buf++ = '.';
2206 *buf++ = ' ';
2207 *buf = 0;
2208 }
2209
2210 static void format_list_number(fz_context *ctx, int type, int x, char *buf, int size)
2211 {
2212 switch (type)
2213 {
2214 case LST_NONE: fz_strlcpy(buf, "", size); break;
2215 case LST_DISC: fz_snprintf(buf, size, "%C ", 0x2022); break; /* U+2022 BULLET */
2216 case LST_CIRCLE: fz_snprintf(buf, size, "%C ", 0x25CB); break; /* U+25CB WHITE CIRCLE */
2217 case LST_SQUARE: fz_snprintf(buf, size, "%C ", 0x25A0); break; /* U+25A0 BLACK SQUARE */
2218 default:
2219 case LST_DECIMAL: fz_snprintf(buf, size, "%d. ", x); break;
2220 case LST_DECIMAL_ZERO: fz_snprintf(buf, size, "%02d. ", x); break;
2221 case LST_LC_ROMAN: format_roman_number(ctx, buf, size, x, roman_lc, "m"); break;
2222 case LST_UC_ROMAN: format_roman_number(ctx, buf, size, x, roman_uc, "M"); break;
2223 case LST_LC_ALPHA: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
2224 case LST_UC_ALPHA: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
2225 case LST_LC_LATIN: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
2226 case LST_UC_LATIN: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
2227 case LST_LC_GREEK: format_alpha_number(ctx, buf, size, x, 0x03B1, 0x03C9); break;
2228 case LST_UC_GREEK: format_alpha_number(ctx, buf, size, x, 0x0391, 0x03A9); break;
2229 }
2230 }
2231
2232 static fz_html_flow *find_list_mark_anchor(fz_context *ctx, fz_html_box *box)
2233 {
2234 /* find first flow node in <li> tag */
2235 while (box)
2236 {
2237 if (box->type == BOX_FLOW)
2238 return box->u.flow.head;
2239 box = box->down;
2240 }
2241 return NULL;
2242 }
2243
2244 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)
2245 {
2246 fz_font *font;
2247 fz_text *text;
2248 fz_matrix trm;
2249 fz_html_flow *line;
2250 float y, w;
2251 float color[4];
2252 const char *s;
2253 char buf[40];
2254 int c, g;
2255
2256 trm = fz_scale(box->s.layout.em, -box->s.layout.em);
2257
2258 line = find_list_mark_anchor(ctx, box);
2259 if (line)
2260 {
2261 y = line->y;
2262 }
2263 else
2264 {
2265 float h = fz_from_css_number_scale(box->style->line_height, box->s.layout.em);
2266 float a = box->s.layout.em * 0.8f;
2267 float d = box->s.layout.em * 0.2f;
2268 if (a + d > h)
2269 h = a + d;
2270 y = box->s.layout.y + a + (h - a - d) / 2;
2271 }
2272
2273 if (y > page_bot || y < page_top)
2274 return;
2275
2276 format_list_number(ctx, box->style->list_style_type, n, buf, sizeof buf);
2277
2278 s = buf;
2279 w = 0;
2280 while (*s)
2281 {
2282 s += fz_chartorune(&c, s);
2283 g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
2284 w += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
2285 }
2286
2287 text = fz_new_text(ctx);
2288
2289 fz_try(ctx)
2290 {
2291 s = buf;
2292 trm.e = box->s.layout.x - w;
2293 trm.f = y - page_top;
2294 while (*s)
2295 {
2296 s += fz_chartorune(&c, s);
2297 g = fz_encode_character_with_fallback(ctx, box->style->font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
2298 fz_show_glyph(ctx, text, font, trm, g, c, 0, 0, FZ_BIDI_NEUTRAL, FZ_LANG_UNSET);
2299 trm.e += fz_advance_glyph(ctx, font, g, 0) * box->s.layout.em;
2300 }
2301
2302 color[0] = box->style->color.r / 255.0f;
2303 color[1] = box->style->color.g / 255.0f;
2304 color[2] = box->style->color.b / 255.0f;
2305 color[3] = box->style->color.a / 255.0f;
2306
2307 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, color[3], fz_default_color_params);
2308 }
2309 fz_always(ctx)
2310 fz_drop_text(ctx, text);
2311 fz_catch(ctx)
2312 fz_rethrow(ctx);
2313 }
2314
2315 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);
2316 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);
2317
2318 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)
2319 {
2320 int ret = 0;
2321 int str = fz_html_tag_to_structure(box->tag);
2322
2323 if (str != FZ_STRUCTURE_INVALID)
2324 fz_begin_structure(ctx, dev, str, box->tag, 0);
2325
2326 switch (box->type)
2327 {
2328 case BOX_TABLE_ROW:
2329 if (restart && restart->end == box)
2330 ret = 1;
2331 else if (draw_table_row(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
2332 ret = 1;
2333 break;
2334 case BOX_TABLE:
2335 case BOX_TABLE_CELL:
2336 case BOX_BLOCK:
2337 if (restart && restart->end == box)
2338 ret = 1;
2339 else if (draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
2340 ret = 1;
2341 break;
2342 case BOX_FLOW:
2343 if (draw_flow_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
2344 ret = 1;
2345 break;
2346 }
2347
2348 if (str != FZ_STRUCTURE_INVALID)
2349 fz_end_structure(ctx, dev);
2350
2351 return ret;
2352 }
2353
2354 static void
2355 do_borders(fz_context *ctx, fz_device *dev, fz_matrix ctm, float page_top, fz_html_box *box, int suppress)
2356 {
2357 float *border = box->u.block.border;
2358 float *padding = box->u.block.padding;
2359 float x0 = box->s.layout.x - padding[L];
2360 float y0 = box->s.layout.y - padding[T];
2361 float x1 = box->s.layout.x + box->s.layout.w + padding[R];
2362 float y1 = box->s.layout.b + padding[B];
2363
2364 if (border[T] > 0 && !(suppress & (1<<T)))
2365 draw_rect(ctx, dev, ctm, page_top, box->style->border_color[T], x0 - border[L], y0 - border[T], x1 + border[R], y0);
2366 if (border[R] > 0 && !(suppress & (1<<R)))
2367 draw_rect(ctx, dev, ctm, page_top, box->style->border_color[R], x1, y0 - border[T], x1 + border[R], y1 + border[B]);
2368 if (border[B] > 0 && !(suppress & (1<<B)))
2369 draw_rect(ctx, dev, ctm, page_top, box->style->border_color[B], x0 - border[L], y1, x1 + border[R], y1 + border[B]);
2370 if (border[L] > 0 && !(suppress & (1<<L)))
2371 draw_rect(ctx, dev, ctm, page_top, box->style->border_color[L], x0 - border[L], y0 - border[T], x0, y1 + border[B]);
2372 }
2373
2374 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)
2375 {
2376 fz_html_box *child;
2377 float x0, y0, x1, y1;
2378
2379 float *padding = box->u.block.padding;
2380 int stopped = 0;
2381 int skipping;
2382
2383 assert(fz_html_box_has_boxes(box));
2384 x0 = box->s.layout.x - padding[L];
2385 y0 = box->s.layout.y - padding[T];
2386 x1 = box->s.layout.x + box->s.layout.w + padding[R];
2387 y1 = box->s.layout.b + padding[B];
2388
2389 if (y0 > page_bot || y1 < page_top)
2390 return 0;
2391
2392 /* If we're skipping, is this the place we should restart? */
2393 if (restart)
2394 {
2395 if (restart->start == box)
2396 restart->start = NULL;
2397 if (restart->end == box)
2398 return 1;
2399 }
2400
2401 if (restart && restart->end == box)
2402 return 1;
2403
2404 /* Are we skipping? */
2405 skipping = (restart && restart->start != NULL);
2406
2407 /* Only draw the content if it's visible */
2408 if (box->style->visibility == V_VISIBLE)
2409 {
2410 int suppress;
2411
2412 /* We draw the background rectangle regardless if we are skipping or not, because
2413 * we might find the end-of-skip point inside this box. If there is no content
2414 * then the box height will be 0, so nothing will be drawn. */
2415 if (y1 > y0)
2416 draw_rect(ctx, dev, ctm, page_top, box->style->background_color, x0, y0, x1, y1);
2417
2418 if (!skipping)
2419 {
2420 /* Draw a selection of borders. */
2421 /* If we are restarting, don't do the bottom one yet. */
2422 suppress = restart ? (1<<B) : 0;
2423 do_borders(ctx, dev, ctm, page_top, box, suppress);
2424
2425 if (box->list_item)
2426 draw_list_mark(ctx, box, page_top, page_bot, dev, ctm, box->list_item);
2427 }
2428 }
2429
2430 for (child = box->down; child; child = child->next)
2431 {
2432 if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
2433 {
2434 stopped = 1;
2435 break;
2436 }
2437 }
2438
2439 if (box->style->visibility == V_VISIBLE && restart && restart->start == NULL)
2440 {
2441 /* We didn't draw (at least some of) the borders on the way down,
2442 * because we were in restart mode. */
2443
2444 /* We never want to draw the top one. Either it will have been drawn
2445 * before, or we were skipping at that point. */
2446 int suppress = (1<<T);
2447
2448 /* If we were skipping, and we're not any more, better draw in the
2449 * left and right ones we missed. */
2450 suppress += (skipping && restart->start == NULL) ? 0 : ((1<<L) + (1<<R));
2451
2452 /* If we've stopped now, don't do the bottom one either. */
2453 suppress += stopped ? (1<<B) : 0;
2454
2455 /* FIXME: background color? list mark is probably OK as we only want
2456 * it once. */
2457
2458 do_borders(ctx, dev, ctm, page_top, box, suppress);
2459 }
2460
2461 return stopped;
2462 }
2463
2464 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)
2465 {
2466 /* Table rows don't draw background colors or borders */
2467
2468 fz_html_box *child;
2469
2470 float y0 = box->s.layout.y;
2471 float y1 = box->s.layout.b;
2472 if (y0 > page_bot || y1 < page_top)
2473 return 0;
2474
2475 /* If we're skipping, is this the place we should restart? */
2476 if (restart)
2477 {
2478 if (restart->start == box)
2479 restart->start = NULL;
2480 if (restart->end == box)
2481 return 1;
2482 }
2483
2484 if (restart && restart->end == box)
2485 return 1;
2486
2487 for (child = box->down; child; child = child->next)
2488 if (draw_box(ctx, child, page_top, page_bot, dev, ctm, hb_buf, restart))
2489 return 1;
2490
2491 return 0;
2492 }
2493
2494 void
2495 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)
2496 {
2497 fz_html_box *box;
2498 hb_buffer_t *hb_buf = NULL;
2499 int unlocked = 0;
2500
2501 fz_var(hb_buf);
2502 fz_var(unlocked);
2503
2504 fz_hb_lock(ctx);
2505 fz_try(ctx)
2506 {
2507 hb_buf = hb_buffer_create();
2508 fz_hb_unlock(ctx);
2509 unlocked = 1;
2510
2511 for (box = top->down; box; box = box->next)
2512 if (draw_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf, restart))
2513 break;
2514 }
2515 fz_always(ctx)
2516 {
2517 if (unlocked)
2518 fz_hb_lock(ctx);
2519 hb_buffer_destroy(hb_buf);
2520 fz_hb_unlock(ctx);
2521 }
2522 fz_catch(ctx)
2523 {
2524 fz_rethrow(ctx);
2525 }
2526 }
2527
2528 void
2529 fz_draw_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html *html, int page)
2530 {
2531 float page_top = page * html->page_h;
2532 float page_bot = (page + 1) * html->page_h;
2533
2534 draw_rect(ctx, dev, ctm, 0, html->tree.root->style->background_color,
2535 0, 0,
2536 html->page_w + html->page_margin[L] + html->page_margin[R],
2537 html->page_h + html->page_margin[T] + html->page_margin[B]);
2538
2539 ctm = fz_pre_translate(ctm, html->page_margin[L], html->page_margin[T]);
2540
2541 fz_draw_restarted_html(ctx, dev, ctm, html->tree.root, page_top, page_bot, NULL);
2542 }
2543
2544 void fz_draw_story(fz_context *ctx, fz_story *story, fz_device *dev, fz_matrix ctm)
2545 {
2546 float page_top, page_bot;
2547 fz_html_box *b;
2548 fz_path *clip;
2549 fz_rect bbox;
2550
2551 if (story == NULL || story->complete)
2552 return;
2553
2554 bbox = story->bbox;
2555 b = story->tree.root;
2556 page_top = b->s.layout.y - b->u.block.margin[T] - b->u.block.border[T] - b->u.block.padding[T];
2557 page_bot = b->s.layout.b + b->u.block.margin[B] + b->u.block.border[B] + b->u.block.padding[B];
2558
2559 if (dev)
2560 {
2561 clip = fz_new_path(ctx);
2562 fz_try(ctx)
2563 {
2564 fz_moveto(ctx, clip, bbox.x0, bbox.y0);
2565 fz_lineto(ctx, clip, bbox.x1, bbox.y0);
2566 fz_lineto(ctx, clip, bbox.x1, bbox.y1);
2567 fz_lineto(ctx, clip, bbox.x0, bbox.y1);
2568 fz_closepath(ctx, clip);
2569 fz_clip_path(ctx, dev, clip, 0, ctm, bbox);
2570 }
2571 fz_always(ctx)
2572 fz_drop_path(ctx, clip);
2573 fz_catch(ctx)
2574 fz_rethrow(ctx);
2575 }
2576
2577 story->restart_place = story->restart_draw;
2578 if (dev)
2579 fz_draw_restarted_html(ctx, dev, ctm, story->tree.root->down, 0, page_bot+page_top, &story->restart_place);
2580 story->restart_place.start = story->restart_draw.end;
2581 story->restart_place.start_flow = story->restart_draw.end_flow;
2582 story->restart_place.end = NULL;
2583 story->restart_place.end_flow = NULL;
2584 story->rect_count++;
2585
2586 if (story->restart_place.start == NULL)
2587 story->complete = 1;
2588
2589 if (dev)
2590 fz_pop_clip(ctx, dev);
2591 }
2592
2593 void fz_reset_story(fz_context *ctx, fz_story *story)
2594 {
2595 if (story == NULL)
2596 return;
2597
2598 story->restart_place.start = NULL;
2599 story->restart_place.start_flow = NULL;
2600 story->restart_place.end = NULL;
2601 story->restart_place.end_flow = NULL;
2602 story->restart_draw.start = NULL;
2603 story->restart_draw.start_flow = NULL;
2604 story->restart_draw.end = NULL;
2605 story->restart_draw.end_flow = NULL;
2606 story->rect_count = 0;
2607 story->complete = 0;
2608 }
2609
2610 static char *
2611 gather_text(fz_context *ctx, fz_html_box *box)
2612 {
2613 fz_html_flow *node;
2614 char *text = NULL;
2615
2616 fz_var(text);
2617
2618 fz_try(ctx)
2619 {
2620 for (node = box->u.flow.head; node; node = node->next)
2621 {
2622 const fz_css_style *style = node->box->style;
2623
2624 if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
2625 {
2626 const char *s;
2627
2628 if (node->type == FLOW_SPACE && node->breaks_line)
2629 continue;
2630 if (node->type == FLOW_SHYPHEN && !node->breaks_line)
2631 continue;
2632 if (style->visibility != V_VISIBLE)
2633 continue;
2634
2635 s = get_node_text(ctx, node);
2636
2637 if (text)
2638 {
2639 size_t newsize = strlen(text) + strlen(s) + 1;
2640 text = fz_realloc(ctx, text, newsize);
2641 strcat(text, s);
2642 }
2643 else
2644 {
2645 text = fz_strdup(ctx, s);
2646 }
2647 }
2648 else if (node->type == FLOW_IMAGE)
2649 {
2650 }
2651 }
2652
2653 }
2654 fz_catch(ctx)
2655 {
2656 fz_free(ctx, text);
2657 fz_rethrow(ctx);
2658 }
2659
2660 return text;
2661 }
2662
2663 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);
2664
2665 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)
2666 {
2667 fz_html_box *child;
2668 float y0, y1;
2669
2670 float *padding = box->u.block.padding;
2671 int stopped = 0;
2672 int skipping;
2673 fz_story_element_position pos;
2674 int heading;
2675
2676 assert(fz_html_box_has_boxes(box));
2677 y0 = box->s.layout.y - padding[T];
2678 y1 = box->s.layout.b + padding[B];
2679
2680 if (y0 > page_bot || y1 < page_top)
2681 return 0;
2682
2683 /* If we're skipping, is this the place we should restart? */
2684 if (restart)
2685 {
2686 if (restart->start == box)
2687 restart->start = NULL;
2688 if (restart->end == box)
2689 return 1;
2690 }
2691
2692 if (restart && restart->end == box)
2693 return 1;
2694
2695 /* Are we skipping? */
2696 skipping = (restart && restart->start != NULL);
2697
2698 if (box->style->visibility == V_VISIBLE && !skipping)
2699 {
2700 heading = box->heading;
2701 if (heading || box->id != NULL || box->href)
2702 {
2703 /* We have a box worthy of a callback. */
2704 char *text = NULL;
2705 pos.text = NULL;
2706 if (heading && box->down)
2707 pos.text = text = gather_text(ctx, box->down);
2708 pos.depth = depth;
2709 pos.heading = heading;
2710 pos.open_close = 1;
2711 pos.id = box->id;
2712 pos.href = box->href;
2713 pos.rect.x0 = box->s.layout.x;
2714 pos.rect.y0 = box->s.layout.y;
2715 pos.rect.x1 = box->s.layout.x + box->s.layout.w;
2716 pos.rect.y1 = box->s.layout.b;
2717 pos.rectangle_num = rect_num;
2718 fz_try(ctx)
2719 cb(ctx, arg, &pos);
2720 fz_always(ctx)
2721 fz_free(ctx, text);
2722 fz_catch(ctx)
2723 fz_rethrow(ctx);
2724 pos.text = NULL;
2725 }
2726 }
2727
2728 for (child = box->down; child; child = child->next)
2729 {
2730 if (enumerate_box(ctx, child, page_top, page_bot, cb, arg, depth+1, rect_num, restart))
2731 {
2732 stopped = 1;
2733 break;
2734 }
2735 }
2736
2737 if (box->style->visibility == V_VISIBLE && !skipping)
2738 {
2739 if (heading || box->id != NULL || box->href)
2740 {
2741 /* We have a box worthy of a callback that needs closing. */
2742 pos.open_close = 2;
2743 pos.rectangle_num = rect_num;
2744 cb(ctx, arg, &pos);
2745 }
2746 }
2747
2748 return stopped;
2749 }
2750
2751 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)
2752 {
2753 fz_html_flow *node;
2754 int restartable_ended = 0;
2755
2756 /* FIXME: HB_DIRECTION_TTB? */
2757
2758 if (restart && restart->start != NULL && restart->start != box)
2759 return 0;
2760
2761 for (node = box->u.flow.head; node; node = node->next)
2762 {
2763 const fz_css_style *style = node->box->style;
2764
2765 if (restart)
2766 {
2767 if (restart->start_flow != NULL)
2768 {
2769 if (restart->start_flow != node)
2770 continue;
2771 restart->start = NULL;
2772 restart->start_flow = NULL;
2773 }
2774
2775 if (restart->end == box && restart->end_flow == node)
2776 {
2777 restartable_ended = 1;
2778 break;
2779 }
2780 }
2781
2782 if (node->type == FLOW_IMAGE)
2783 {
2784 if (node->y >= page_bot || node->y + node->h <= page_top)
2785 continue;
2786 }
2787 else
2788 {
2789 if (node->y > page_bot || node->y < page_top)
2790 continue;
2791 }
2792
2793 if (node->box->id || node->box->href)
2794 {
2795 /* We have a node to callback for. */
2796 fz_story_element_position pos;
2797
2798 pos.text = NULL;
2799 pos.depth = depth;
2800 pos.heading = 0;
2801 pos.open_close = 1 | 2;
2802 pos.id = node->box->id;
2803 pos.href = node->box->href;
2804 /* We only have the baseline and the em, so the bbox is a bit of a fudge. */
2805 pos.rect.x0 = node->x;
2806 pos.rect.y0 = node->y - node->h * 0.8f;
2807 pos.rect.x1 = node->x + node->w;
2808 pos.rect.y1 = node->y + node->h * 0.2f;
2809 pos.rectangle_num = rect_num;
2810 cb(ctx, arg, &pos);
2811 }
2812
2813 if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
2814 {
2815 }
2816 else if (node->type == FLOW_IMAGE)
2817 {
2818 if (style->visibility == V_VISIBLE)
2819 {
2820 /* FIXME: Maybe callback for images? */
2821 }
2822 }
2823 }
2824
2825 return restartable_ended;
2826 }
2827
2828 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)
2829 {
2830 switch (box->type)
2831 {
2832 case BOX_TABLE:
2833 case BOX_TABLE_ROW:
2834 case BOX_TABLE_CELL:
2835 case BOX_BLOCK:
2836 if (restart && restart->end == box)
2837 return 1;
2838 if (enumerate_block_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
2839 return 1;
2840 break;
2841 case BOX_FLOW:
2842 if (enumerate_flow_box(ctx, box, page_top, page_bot, cb, arg, depth, rect_num, restart))
2843 return 1;
2844 break;
2845 }
2846
2847 return 0;
2848 }
2849
2850 void fz_story_positions(fz_context *ctx, fz_story *story, fz_story_position_callback *cb, void *arg)
2851 {
2852 float page_top, page_bot;
2853 fz_html_box *b;
2854 fz_html_restarter restart;
2855 fz_html_box *box;
2856 fz_html_box *top;
2857
2858 if (story == NULL || story->complete)
2859 return;
2860
2861 b = story->tree.root;
2862 page_top = b->s.layout.y - b->u.block.margin[T] - b->u.block.border[T] - b->u.block.padding[T];
2863 page_bot = b->s.layout.b + b->u.block.margin[B] + b->u.block.border[B] + b->u.block.padding[B];
2864 top = story->tree.root->down;
2865
2866 restart = story->restart_draw;
2867
2868 for (box = top->down; box; box = box->next)
2869 if (enumerate_box(ctx, box, page_top, page_bot, cb, arg, 0, story->rect_count+1, &restart))
2870 break;
2871 }