1#include "mupdf/fitz.h"
2#include "mupdf/ucdn.h"
3#include "html-imp.h"
4
5#include "hb.h"
6#include "hb-ft.h"
7#include <ft2build.h>
8
9#include <math.h>
10
11#undef DEBUG_HARFBUZZ
12
13enum { T, R, B, L };
14
15typedef struct string_walker
16{
17 fz_context *ctx;
18 hb_buffer_t *hb_buf;
19 int rtl;
20 const char *start;
21 const char *end;
22 const char *s;
23 fz_font *base_font;
24 int script;
25 int language;
26 int small_caps;
27 fz_font *font;
28 fz_font *next_font;
29 hb_glyph_position_t *glyph_pos;
30 hb_glyph_info_t *glyph_info;
31 unsigned int glyph_count;
32 int scale;
33} string_walker;
34
35static int quick_ligature_mov(fz_context *ctx, string_walker *walker, unsigned int i, unsigned int n, int unicode)
36{
37 unsigned int k;
38 for (k = i + n + 1; k < walker->glyph_count; ++k)
39 {
40 walker->glyph_info[k-n] = walker->glyph_info[k];
41 walker->glyph_pos[k-n] = walker->glyph_pos[k];
42 }
43 walker->glyph_count -= n;
44 return unicode;
45}
46
47static int quick_ligature(fz_context *ctx, string_walker *walker, unsigned int i)
48{
49 if (walker->glyph_info[i].codepoint == 'f' && i + 1 < walker->glyph_count && !fz_font_flags(walker->font)->is_mono)
50 {
51 if (walker->glyph_info[i+1].codepoint == 'f')
52 {
53 if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'i')
54 {
55 if (fz_encode_character(ctx, walker->font, 0xFB03))
56 return quick_ligature_mov(ctx, walker, i, 2, 0xFB03);
57 }
58 if (i + 2 < walker->glyph_count && walker->glyph_info[i+2].codepoint == 'l')
59 {
60 if (fz_encode_character(ctx, walker->font, 0xFB04))
61 return quick_ligature_mov(ctx, walker, i, 2, 0xFB04);
62 }
63 if (fz_encode_character(ctx, walker->font, 0xFB00))
64 return quick_ligature_mov(ctx, walker, i, 1, 0xFB00);
65 }
66 if (walker->glyph_info[i+1].codepoint == 'i')
67 {
68 if (fz_encode_character(ctx, walker->font, 0xFB01))
69 return quick_ligature_mov(ctx, walker, i, 1, 0xFB01);
70 }
71 if (walker->glyph_info[i+1].codepoint == 'l')
72 {
73 if (fz_encode_character(ctx, walker->font, 0xFB02))
74 return quick_ligature_mov(ctx, walker, i, 1, 0xFB02);
75 }
76 }
77 return walker->glyph_info[i].codepoint;
78}
79
80static 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)
81{
82 walker->ctx = ctx;
83 walker->hb_buf = hb_buf;
84 walker->rtl = rtl;
85 walker->start = text;
86 walker->end = text;
87 walker->s = text;
88 walker->base_font = font;
89 walker->script = script;
90 walker->language = language;
91 walker->font = NULL;
92 walker->next_font = NULL;
93 walker->small_caps = small_caps;
94}
95
96static void
97destroy_hb_shaper_data(fz_context *ctx, void *handle)
98{
99 fz_hb_lock(ctx);
100 hb_font_destroy(handle);
101 fz_hb_unlock(ctx);
102}
103
104static const hb_feature_t small_caps_feature[1] = {
105 { HB_TAG('s','m','c','p'), 1, 0, -1 }
106};
107
108static int walk_string(string_walker *walker)
109{
110 fz_context *ctx = walker->ctx;
111 FT_Face face;
112 int fterr;
113 int quickshape;
114 char lang[8];
115
116 walker->start = walker->end;
117 walker->end = walker->s;
118 walker->font = walker->next_font;
119
120 if (*walker->start == 0)
121 return 0;
122
123 /* Run through the string, encoding chars until we find one
124 * that requires a different fallback font. */
125 while (*walker->s)
126 {
127 int c;
128
129 walker->s += fz_chartorune(&c, walker->s);
130 (void)fz_encode_character_with_fallback(ctx, walker->base_font, c, walker->script, walker->language, &walker->next_font);
131 if (walker->next_font != walker->font)
132 {
133 if (walker->font != NULL)
134 break;
135 walker->font = walker->next_font;
136 }
137 walker->end = walker->s;
138 }
139
140 /* Disable harfbuzz shaping if script is common or LGC and there are no opentype tables. */
141 quickshape = 0;
142 if (walker->script <= 3 && !walker->rtl && !fz_font_flags(walker->font)->has_opentype)
143 quickshape = 1;
144
145 fz_hb_lock(ctx);
146 fz_try(ctx)
147 {
148 face = fz_font_ft_face(ctx, walker->font);
149 walker->scale = face->units_per_EM;
150 fterr = FT_Set_Char_Size(face, walker->scale, walker->scale, 72, 72);
151 if (fterr)
152 fz_throw(ctx, FZ_ERROR_GENERIC, "freetype setting character size: %s", ft_error_string(fterr));
153
154 hb_buffer_clear_contents(walker->hb_buf);
155 hb_buffer_set_direction(walker->hb_buf, walker->rtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
156 /* hb_buffer_set_script(walker->hb_buf, hb_ucdn_script_translate(walker->script)); */
157 if (walker->language)
158 {
159 fz_string_from_text_language(lang, walker->language);
160 hb_buffer_set_language(walker->hb_buf, hb_language_from_string(lang, (int)strlen(lang)));
161 }
162 /* hb_buffer_set_cluster_level(hb_buf, HB_BUFFER_CLUSTER_LEVEL_CHARACTERS); */
163
164 hb_buffer_add_utf8(walker->hb_buf, walker->start, walker->end - walker->start, 0, -1);
165
166 if (!quickshape)
167 {
168 fz_shaper_data_t *hb = fz_font_shaper_data(ctx, walker->font);
169 if (hb->shaper_handle == NULL)
170 {
171 Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
172 hb->destroy = destroy_hb_shaper_data;
173 hb->shaper_handle = hb_ft_font_create(face, NULL);
174 Memento_stopLeaking();
175 }
176
177 Memento_startLeaking(); /* HarfBuzz leaks harmlessly */
178 hb_buffer_guess_segment_properties(walker->hb_buf);
179 Memento_stopLeaking();
180
181 if (walker->small_caps)
182 hb_shape(hb->shaper_handle, walker->hb_buf, small_caps_feature, nelem(small_caps_feature));
183 else
184 hb_shape(hb->shaper_handle, walker->hb_buf, NULL, 0);
185 }
186
187 walker->glyph_pos = hb_buffer_get_glyph_positions(walker->hb_buf, &walker->glyph_count);
188 walker->glyph_info = hb_buffer_get_glyph_infos(walker->hb_buf, NULL);
189 }
190 fz_always(ctx)
191 {
192 fz_hb_unlock(ctx);
193 }
194 fz_catch(ctx)
195 {
196 fz_rethrow(ctx);
197 }
198
199 if (quickshape)
200 {
201 unsigned int i;
202 for (i = 0; i < walker->glyph_count; ++i)
203 {
204 int glyph, unicode;
205 unicode = quick_ligature(ctx, walker, i);
206 if (walker->small_caps)
207 glyph = fz_encode_character_sc(ctx, walker->font, unicode);
208 else
209 glyph = fz_encode_character(ctx, walker->font, unicode);
210 walker->glyph_info[i].codepoint = glyph;
211 walker->glyph_pos[i].x_offset = 0;
212 walker->glyph_pos[i].y_offset = 0;
213 walker->glyph_pos[i].x_advance = fz_advance_glyph(ctx, walker->font, glyph, 0) * face->units_per_EM;
214 walker->glyph_pos[i].y_advance = 0;
215 }
216 }
217
218 return 1;
219}
220
221static const char *get_node_text(fz_context *ctx, fz_html_flow *node)
222{
223 if (node->type == FLOW_WORD)
224 return node->content.text;
225 else if (node->type == FLOW_SPACE)
226 return " ";
227 else if (node->type == FLOW_SHYPHEN)
228 return "-";
229 else
230 return "";
231}
232
233static void measure_string(fz_context *ctx, fz_html_flow *node, hb_buffer_t *hb_buf)
234{
235 string_walker walker;
236 unsigned int i;
237 const char *s;
238 float em;
239
240 em = node->box->em;
241 node->x = 0;
242 node->y = 0;
243 node->w = 0;
244 node->h = fz_from_css_number_scale(node->box->style.line_height, em);
245
246 s = get_node_text(ctx, node);
247 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);
248 while (walk_string(&walker))
249 {
250 int x = 0;
251 for (i = 0; i < walker.glyph_count; i++)
252 x += walker.glyph_pos[i].x_advance;
253 node->w += x * em / walker.scale;
254 }
255}
256
257static float measure_line(fz_html_flow *node, fz_html_flow *end, float *baseline)
258{
259 float max_a = 0, max_d = 0, h = node->h;
260 while (node != end)
261 {
262 if (node->type == FLOW_IMAGE)
263 {
264 if (node->h > max_a)
265 max_a = node->h;
266 }
267 else
268 {
269 float a = node->box->em * 0.8f;
270 float d = node->box->em * 0.2f;
271 if (a > max_a) max_a = a;
272 if (d > max_d) max_d = d;
273 }
274 if (node->h > h) h = node->h;
275 if (max_a + max_d > h) h = max_a + max_d;
276 node = node->next;
277 }
278 *baseline = max_a + (h - max_a - max_d) / 2;
279 return h;
280}
281
282static 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)
283{
284 float x = box->x + indent;
285 float y = box->b;
286 float slop = page_w - line_w;
287 float justify = 0;
288 float va;
289 int n, i;
290 fz_html_flow *node;
291 fz_html_flow **reorder;
292 unsigned int min_level, max_level;
293
294 /* Count the number of nodes on the line */
295 for(i = 0, n = 0, node = start; node != end; node = node->next)
296 {
297 n++;
298 if (node->type == FLOW_SPACE && node->expand && !node->breaks_line)
299 i++;
300 }
301
302 if (align == TA_JUSTIFY)
303 {
304 justify = slop / i;
305 }
306 else if (align == TA_RIGHT)
307 x += slop;
308 else if (align == TA_CENTER)
309 x += slop / 2;
310
311 /* We need a block to hold the node pointers while we reorder */
312 reorder = fz_malloc_array(ctx, n, fz_html_flow*);
313 min_level = start->bidi_level;
314 max_level = start->bidi_level;
315 for(i = 0, node = start; node != end; i++, node = node->next)
316 {
317 reorder[i] = node;
318 if (node->bidi_level < min_level)
319 min_level = node->bidi_level;
320 if (node->bidi_level > max_level)
321 max_level = node->bidi_level;
322 }
323
324 /* Do we need to do any reordering? */
325 if (min_level != max_level || (min_level & 1))
326 {
327 /* The lowest level we swap is always a rtl one */
328 min_level |= 1;
329 /* Each time around the loop we swap runs of fragments that have
330 * levels >= max_level (and decrement max_level). */
331 do
332 {
333 int start = 0;
334 int end;
335 do
336 {
337 /* Skip until we find a level that's >= max_level */
338 while (start < n && reorder[start]->bidi_level < max_level)
339 start++;
340 /* If start >= n-1 then no more runs. */
341 if (start >= n-1)
342 break;
343 /* Find the end of the match */
344 i = start+1;
345 while (i < n && reorder[i]->bidi_level >= max_level)
346 i++;
347 /* Reverse from start to i-1 */
348 end = i-1;
349 while (start < end)
350 {
351 fz_html_flow *t = reorder[start];
352 reorder[start++] = reorder[end];
353 reorder[end--] = t;
354 }
355 start = i+1;
356 }
357 while (start < n);
358 max_level--;
359 }
360 while (max_level >= min_level);
361 }
362
363 for (i = 0; i < n; i++)
364 {
365 float w;
366
367 node = reorder[i];
368 w = node->w;
369
370 if (node->type == FLOW_SPACE && node->breaks_line)
371 w = 0;
372 else if (node->type == FLOW_SPACE && !node->breaks_line)
373 w += node->expand ? justify : 0;
374 else if (node->type == FLOW_SHYPHEN && !node->breaks_line)
375 w = 0;
376 else if (node->type == FLOW_SHYPHEN && node->breaks_line)
377 w = node->w;
378
379 node->x = x;
380 x += w;
381
382 switch (node->box->style.vertical_align)
383 {
384 default:
385 case VA_BASELINE:
386 va = 0;
387 break;
388 case VA_SUB:
389 va = node->box->em * 0.2f;
390 break;
391 case VA_SUPER:
392 va = node->box->em * -0.3f;
393 break;
394 case VA_TOP:
395 case VA_TEXT_TOP:
396 va = -baseline + node->box->em * 0.8f;
397 break;
398 case VA_BOTTOM:
399 case VA_TEXT_BOTTOM:
400 va = -baseline + line_h - node->box->em * 0.2f;
401 break;
402 }
403
404 if (node->type == FLOW_IMAGE)
405 node->y = y + baseline - node->h;
406 else
407 {
408 node->y = y + baseline + va;
409 node->h = node->box->em;
410 }
411 }
412
413 fz_free(ctx, reorder);
414}
415
416static void find_accumulated_margins(fz_context *ctx, fz_html_box *box, float *w, float *h)
417{
418 while (box)
419 {
420 /* TODO: take into account collapsed margins */
421 *h += box->margin[T] + box->padding[T] + box->border[T];
422 *h += box->margin[B] + box->padding[B] + box->border[B];
423 *w += box->margin[L] + box->padding[L] + box->border[L];
424 *w += box->margin[R] + box->padding[R] + box->border[R];
425 box = box->up;
426 }
427}
428
429static void flush_line(fz_context *ctx, fz_html_box *box, float page_h, float page_w, float line_w, int align, float indent, fz_html_flow *a, fz_html_flow *b)
430{
431 float avail, line_h, baseline;
432 line_h = measure_line(a, b, &baseline);
433 if (page_h > 0)
434 {
435 avail = page_h - fmodf(box->b, page_h);
436 if (line_h > avail)
437 box->b += avail;
438 }
439 layout_line(ctx, indent, page_w, line_w, align, a, b, box, baseline, line_h);
440 box->b += line_h;
441}
442
443static void layout_flow_inline(fz_context *ctx, fz_html_box *box, fz_html_box *top)
444{
445 while (box)
446 {
447 box->y = top->y;
448 box->em = fz_from_css_number(box->style.font_size, top->em, top->em, top->em);
449 if (box->down)
450 layout_flow_inline(ctx, box->down, box);
451 box = box->next;
452 }
453}
454
455static void layout_flow(fz_context *ctx, fz_html_box *box, fz_html_box *top, float page_h, hb_buffer_t *hb_buf)
456{
457 fz_html_flow *node, *line, *candidate;
458 float line_w, candidate_w, indent, break_w, nonbreak_w;
459 int line_align, align;
460
461 float em = box->em = fz_from_css_number(box->style.font_size, top->em, top->em, top->em);
462 indent = box->is_first_flow ? fz_from_css_number(top->style.text_indent, em, top->w, 0) : 0;
463 align = top->style.text_align;
464
465 if (box->markup_dir == FZ_BIDI_RTL)
466 {
467 if (align == TA_LEFT)
468 align = TA_RIGHT;
469 else if (align == TA_RIGHT)
470 align = TA_LEFT;
471 }
472
473 box->x = top->x;
474 box->y = top->b;
475 box->w = top->w;
476 box->b = box->y;
477
478 if (!box->flow_head)
479 return;
480
481 if (box->down)
482 layout_flow_inline(ctx, box->down, box);
483
484 for (node = box->flow_head; node; node = node->next)
485 {
486 node->breaks_line = 0; /* reset line breaks from previous layout */
487 if (node->type == FLOW_IMAGE)
488 {
489 float margin_w = 0, margin_h = 0;
490 float max_w, max_h;
491 float xs = 1, ys = 1, s;
492
493 find_accumulated_margins(ctx, box, &margin_w, &margin_h);
494 max_w = top->w - margin_w;
495 max_h = page_h - margin_h;
496
497 /* NOTE: We ignore the image DPI here, since most images in EPUB files have bogus values. */
498 node->w = node->content.image->w * 72 / 96;
499 node->h = node->content.image->h * 72 / 96;
500
501 node->w = fz_from_css_number(node->box->style.width, top->em, top->w - margin_w, node->w);
502 node->h = fz_from_css_number(node->box->style.height, top->em, page_h - margin_h, node->h);
503
504 /* Shrink image to fit on one page if needed */
505 if (max_w > 0 && node->w > max_w)
506 xs = max_w / node->w;
507 if (max_h > 0 && node->h > max_h)
508 ys = max_h / node->h;
509 s = fz_min(xs, ys);
510 node->w = node->w * s;
511 node->h = node->h * s;
512
513 }
514 else
515 {
516 measure_string(ctx, node, hb_buf);
517 }
518 }
519
520 node = box->flow_head;
521
522 candidate = NULL;
523 candidate_w = 0;
524 line = node;
525 line_w = indent;
526
527 while (node)
528 {
529 switch (node->type)
530 {
531 default:
532 case FLOW_WORD:
533 case FLOW_IMAGE:
534 nonbreak_w = break_w = node->w;
535 break;
536
537 case FLOW_SHYPHEN:
538 case FLOW_SBREAK:
539 case FLOW_SPACE:
540 nonbreak_w = break_w = 0;
541
542 /* Determine broken and unbroken widths of this node. */
543 if (node->type == FLOW_SPACE)
544 nonbreak_w = node->w;
545 else if (node->type == FLOW_SHYPHEN)
546 break_w = node->w;
547
548 /* If the broken node fits, remember it. */
549 /* Also remember it if we have no other candidate and need to break in desperation. */
550 if (line_w + break_w <= box->w || !candidate)
551 {
552 candidate = node;
553 candidate_w = line_w + break_w;
554 }
555 break;
556
557 case FLOW_BREAK:
558 nonbreak_w = break_w = 0;
559 candidate = node;
560 candidate_w = line_w;
561 break;
562 }
563
564 /* The current node either does not fit or we saw a hard break. */
565 /* Break the line if we have a candidate break point. */
566 if (node->type == FLOW_BREAK || (line_w + nonbreak_w > box->w && candidate))
567 {
568 candidate->breaks_line = 1;
569 if (candidate->type == FLOW_BREAK)
570 line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
571 else
572 line_align = align;
573 flush_line(ctx, box, page_h, box->w, candidate_w, line_align, indent, line, candidate->next);
574
575 line = candidate->next;
576 node = candidate->next;
577 candidate = NULL;
578 candidate_w = 0;
579 indent = 0;
580 line_w = 0;
581 }
582 else
583 {
584 line_w += nonbreak_w;
585 node = node->next;
586 }
587 }
588
589 if (line)
590 {
591 line_align = (align == TA_JUSTIFY) ? TA_LEFT : align;
592 flush_line(ctx, box, page_h, box->w, line_w, line_align, indent, line, NULL);
593 }
594}
595
596static int layout_block_page_break(fz_context *ctx, float *yp, float page_h, float vertical, int page_break)
597{
598 if (page_h <= 0)
599 return 0;
600 if (page_break == PB_ALWAYS || page_break == PB_LEFT || page_break == PB_RIGHT)
601 {
602 float avail = page_h - fmodf(*yp - vertical, page_h);
603 int number = (*yp + (page_h * 0.1f)) / page_h;
604 if (avail > 0 && avail < page_h)
605 {
606 *yp += avail - vertical;
607 if (page_break == PB_LEFT && (number & 1) == 0) /* right side pages are even */
608 *yp += page_h;
609 if (page_break == PB_RIGHT && (number & 1) == 1) /* left side pages are odd */
610 *yp += page_h;
611 return 1;
612 }
613 }
614 return 0;
615}
616
617static float layout_block(fz_context *ctx, fz_html_box *box, float em, float top_x, float *top_b, float top_w,
618 float page_h, float vertical, hb_buffer_t *hb_buf);
619
620static void layout_table(fz_context *ctx, fz_html_box *box, fz_html_box *top, float page_h, hb_buffer_t *hb_buf)
621{
622 fz_html_box *row, *cell, *child;
623 int col, ncol = 0;
624
625 box->em = fz_from_css_number(box->style.font_size, top->em, top->em, top->em);
626 box->x = top->x;
627 box->w = fz_from_css_number(box->style.width, box->em, top->w, top->w);
628 box->y = box->b = top->b;
629
630 for (row = box->down; row; row = row->next)
631 {
632 col = 0;
633 for (cell = row->down; cell; cell = cell->next)
634 ++col;
635 if (col > ncol)
636 ncol = col;
637 }
638
639 for (row = box->down; row; row = row->next)
640 {
641 col = 0;
642
643 row->em = fz_from_css_number(row->style.font_size, box->em, box->em, box->em);
644 row->x = box->x;
645 row->w = box->w;
646 row->y = row->b = box->b;
647
648 for (cell = row->down; cell; cell = cell->next)
649 {
650 float colw = row->w / ncol; // TODO: proper calculation
651
652 cell->em = fz_from_css_number(cell->style.font_size, row->em, row->em, row->em);
653 cell->y = cell->b = row->y;
654 cell->x = row->x + col * colw;
655 cell->w = colw;
656
657 for (child = cell->down; child; child = child->next)
658 {
659 if (child->type == BOX_BLOCK)
660 layout_block(ctx, child, cell->em, cell->x, &cell->b, cell->w, page_h, 0, hb_buf);
661 else if (child->type == BOX_FLOW)
662 layout_flow(ctx, child, cell, page_h, hb_buf);
663 cell->b = child->b;
664 }
665
666 if (cell->b > row->b)
667 row->b = cell->b;
668
669 ++col;
670 }
671
672 box->b = row->b;
673 }
674}
675
676static float layout_block(fz_context *ctx, fz_html_box *box, float em, float top_x, float *top_b, float top_w,
677 float page_h, float vertical, hb_buffer_t *hb_buf)
678{
679 fz_html_box *child;
680 float auto_width;
681 int first;
682
683 fz_css_style *style = &box->style;
684 float *margin = box->margin;
685 float *border = box->border;
686 float *padding = box->padding;
687
688 em = box->em = fz_from_css_number(style->font_size, em, em, em);
689
690 margin[0] = fz_from_css_number(style->margin[0], em, top_w, 0);
691 margin[1] = fz_from_css_number(style->margin[1], em, top_w, 0);
692 margin[2] = fz_from_css_number(style->margin[2], em, top_w, 0);
693 margin[3] = fz_from_css_number(style->margin[3], em, top_w, 0);
694
695 padding[0] = fz_from_css_number(style->padding[0], em, top_w, 0);
696 padding[1] = fz_from_css_number(style->padding[1], em, top_w, 0);
697 padding[2] = fz_from_css_number(style->padding[2], em, top_w, 0);
698 padding[3] = fz_from_css_number(style->padding[3], em, top_w, 0);
699
700 border[0] = style->border_style_0 ? fz_from_css_number(style->border_width[0], em, top_w, 0) : 0;
701 border[1] = style->border_style_1 ? fz_from_css_number(style->border_width[1], em, top_w, 0) : 0;
702 border[2] = style->border_style_2 ? fz_from_css_number(style->border_width[2], em, top_w, 0) : 0;
703 border[3] = style->border_style_3 ? fz_from_css_number(style->border_width[3], em, top_w, 0) : 0;
704
705 /* TODO: remove 'vertical' margin adjustments across automatic page breaks */
706
707 if (layout_block_page_break(ctx, top_b, page_h, vertical, style->page_break_before))
708 vertical = 0;
709
710 box->x = top_x + margin[L] + border[L] + padding[L];
711 auto_width = top_w - (margin[L] + margin[R] + border[L] + border[R] + padding[L] + padding[R]);
712 box->w = fz_from_css_number(style->width, em, auto_width, auto_width);
713
714 if (margin[T] > vertical)
715 margin[T] -= vertical;
716 else
717 margin[T] = 0;
718
719 if (padding[T] == 0 && border[T] == 0)
720 vertical += margin[T];
721 else
722 vertical = 0;
723
724 box->y = box->b = *top_b + margin[T] + border[T] + padding[T];
725
726 first = 1;
727 for (child = box->down; child; child = child->next)
728 {
729 if (child->type == BOX_BLOCK)
730 {
731 vertical = layout_block(ctx, child, em, box->x, &box->b, box->w, page_h, vertical, hb_buf);
732 if (first)
733 {
734 /* move collapsed parent/child top margins to parent */
735 margin[T] += child->margin[T];
736 box->y += child->margin[T];
737 child->margin[T] = 0;
738 first = 0;
739 }
740 box->b = child->b + child->padding[B] + child->border[B] + child->margin[B];
741 }
742 else if (child->type == BOX_TABLE)
743 {
744 layout_table(ctx, child, box, page_h, hb_buf);
745 first = 0;
746 box->b = child->b + child->padding[B] + child->border[B] + child->margin[B];
747 }
748 else if (child->type == BOX_BREAK)
749 {
750 box->b += fz_from_css_number_scale(style->line_height, em);
751 vertical = 0;
752 first = 0;
753 }
754 else if (child->type == BOX_FLOW)
755 {
756 layout_flow(ctx, child, box, page_h, hb_buf);
757 if (child->b > child->y)
758 {
759 box->b = child->b;
760 vertical = 0;
761 first = 0;
762 }
763 }
764 }
765
766 /* reserve space for the list mark */
767 if (box->list_item && box->y == box->b)
768 {
769 box->b += fz_from_css_number_scale(style->line_height, em);
770 vertical = 0;
771 }
772
773 if (layout_block_page_break(ctx, &box->b, page_h, 0, style->page_break_after))
774 {
775 vertical = 0;
776 margin[B] = 0;
777 }
778
779 if (box->y == box->b)
780 {
781 if (margin[B] > vertical)
782 margin[B] -= vertical;
783 else
784 margin[B] = 0;
785 }
786 else
787 {
788 box->b -= vertical;
789 vertical = fz_max(margin[B], vertical);
790 margin[B] = vertical;
791 }
792
793 return vertical;
794}
795
796void
797fz_layout_html(fz_context *ctx, fz_html *html, float w, float h, float em)
798{
799 fz_html_box *box = html->root;
800 hb_buffer_t *hb_buf = NULL;
801 int unlocked = 0;
802
803 fz_var(hb_buf);
804 fz_var(unlocked);
805
806 html->page_margin[T] = fz_from_css_number(html->root->style.margin[T], em, em, 0);
807 html->page_margin[B] = fz_from_css_number(html->root->style.margin[B], em, em, 0);
808 html->page_margin[L] = fz_from_css_number(html->root->style.margin[L], em, em, 0);
809 html->page_margin[R] = fz_from_css_number(html->root->style.margin[R], em, em, 0);
810
811 html->page_w = w - html->page_margin[L] - html->page_margin[R];
812 if (html->page_w <= 72)
813 html->page_w = 72; /* enforce a minimum page size! */
814 if (h > 0)
815 {
816 html->page_h = h - html->page_margin[T] - html->page_margin[B];
817 if (html->page_h <= 72)
818 html->page_h = 72; /* enforce a minimum page size! */
819 }
820 else
821 {
822 /* h 0 means no pagination */
823 html->page_h = 0;
824 }
825
826 fz_hb_lock(ctx);
827
828 fz_try(ctx)
829 {
830 hb_buf = hb_buffer_create();
831 unlocked = 1;
832 fz_hb_unlock(ctx);
833
834 box->em = em;
835 box->w = html->page_w;
836 box->b = box->y;
837
838 if (box->down)
839 {
840 layout_block(ctx, box->down, box->em, box->x, &box->b, box->w, html->page_h, 0, hb_buf);
841 box->b = box->down->b;
842 }
843 }
844 fz_always(ctx)
845 {
846 if (unlocked)
847 fz_hb_lock(ctx);
848 hb_buffer_destroy(hb_buf);
849 fz_hb_unlock(ctx);
850 }
851 fz_catch(ctx)
852 {
853 fz_rethrow(ctx);
854 }
855
856 if (h == 0)
857 html->page_h = box->b;
858
859#ifndef NDEBUG
860 if (fz_atoi(getenv("FZ_DEBUG_HTML")))
861 fz_debug_html(ctx, html->root);
862#endif
863}
864
865static void 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)
866{
867 fz_html_flow *node;
868 fz_text *text;
869 fz_matrix trm;
870 float color[3];
871 float prev_color[3];
872
873 /* FIXME: HB_DIRECTION_TTB? */
874
875 text = NULL;
876 prev_color[0] = 0;
877 prev_color[1] = 0;
878 prev_color[2] = 0;
879
880 for (node = box->flow_head; node; node = node->next)
881 {
882 fz_css_style *style = &node->box->style;
883
884 if (node->type == FLOW_IMAGE)
885 {
886 if (node->y >= page_bot || node->y + node->h <= page_top)
887 continue;
888 }
889 else
890 {
891 if (node->y > page_bot || node->y < page_top)
892 continue;
893 }
894
895 if (node->type == FLOW_WORD || node->type == FLOW_SPACE || node->type == FLOW_SHYPHEN)
896 {
897 string_walker walker;
898 const char *s;
899 float x, y;
900
901 if (node->type == FLOW_WORD && node->content.text == NULL)
902 continue;
903 if (node->type == FLOW_SPACE && node->breaks_line)
904 continue;
905 if (node->type == FLOW_SHYPHEN && !node->breaks_line)
906 continue;
907 if (style->visibility != V_VISIBLE)
908 continue;
909
910 color[0] = style->color.r / 255.0f;
911 color[1] = style->color.g / 255.0f;
912 color[2] = style->color.b / 255.0f;
913
914 if (color[0] != prev_color[0] || color[1] != prev_color[1] || color[2] != prev_color[2])
915 {
916 if (text)
917 {
918 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), prev_color, 1, fz_default_color_params);
919 fz_drop_text(ctx, text);
920 text = NULL;
921 }
922 prev_color[0] = color[0];
923 prev_color[1] = color[1];
924 prev_color[2] = color[2];
925 }
926
927 if (!text)
928 text = fz_new_text(ctx);
929
930 if (node->bidi_level & 1)
931 x = node->x + node->w;
932 else
933 x = node->x;
934 y = node->y;
935
936 trm.a = node->box->em;
937 trm.b = 0;
938 trm.c = 0;
939 trm.d = -node->box->em;
940 trm.e = x;
941 trm.f = y - page_top;
942
943 s = get_node_text(ctx, node);
944 init_string_walker(ctx, &walker, hb_buf, node->bidi_level & 1, style->font, node->script, node->markup_lang, style->small_caps, s);
945 while (walk_string(&walker))
946 {
947 float node_scale = node->box->em / walker.scale;
948 unsigned int i;
949 int c, k, n;
950
951 /* Flatten advance and offset into offset array. */
952 int x_advance = 0;
953 int y_advance = 0;
954 for (i = 0; i < walker.glyph_count; ++i)
955 {
956 walker.glyph_pos[i].x_offset += x_advance;
957 walker.glyph_pos[i].y_offset += y_advance;
958 x_advance += walker.glyph_pos[i].x_advance;
959 y_advance += walker.glyph_pos[i].y_advance;
960 }
961
962 if (node->bidi_level & 1)
963 x -= x_advance * node_scale;
964
965 /* Walk characters to find glyph clusters */
966 k = 0;
967 while (walker.start + k < walker.end)
968 {
969 n = fz_chartorune(&c, walker.start + k);
970
971 for (i = 0; i < walker.glyph_count; ++i)
972 {
973 if (walker.glyph_info[i].cluster == k)
974 {
975 trm.e = x + walker.glyph_pos[i].x_offset * node_scale;
976 trm.f = y - walker.glyph_pos[i].y_offset * node_scale - page_top;
977 fz_show_glyph(ctx, text, walker.font, trm,
978 walker.glyph_info[i].codepoint, c,
979 0, node->bidi_level, box->markup_dir, node->markup_lang);
980 c = -1; /* for subsequent glyphs in x-to-many mappings */
981 }
982 }
983
984 /* no glyph found (many-to-many or many-to-one mapping) */
985 if (c != -1)
986 {
987 fz_show_glyph(ctx, text, walker.font, trm,
988 -1, c,
989 0, node->bidi_level, box->markup_dir, node->markup_lang);
990 }
991
992 k += n;
993 }
994
995 if ((node->bidi_level & 1) == 0)
996 x += x_advance * node_scale;
997
998 y += y_advance * node_scale;
999 }
1000 }
1001 else if (node->type == FLOW_IMAGE)
1002 {
1003 if (text)
1004 {
1005 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, 1, fz_default_color_params);
1006 fz_drop_text(ctx, text);
1007 text = NULL;
1008 }
1009 if (style->visibility == V_VISIBLE)
1010 {
1011 fz_matrix itm = fz_pre_translate(ctm, node->x, node->y - page_top);
1012 itm = fz_pre_scale(itm, node->w, node->h);
1013 fz_fill_image(ctx, dev, node->content.image, itm, 1, fz_default_color_params);
1014 }
1015 }
1016 }
1017
1018 if (text)
1019 {
1020 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, 1, fz_default_color_params);
1021 fz_drop_text(ctx, text);
1022 text = NULL;
1023 }
1024}
1025
1026static 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)
1027{
1028 if (color.a > 0)
1029 {
1030 float rgb[3];
1031
1032 fz_path *path = fz_new_path(ctx);
1033
1034 fz_moveto(ctx, path, x0, y0 - page_top);
1035 fz_lineto(ctx, path, x1, y0 - page_top);
1036 fz_lineto(ctx, path, x1, y1 - page_top);
1037 fz_lineto(ctx, path, x0, y1 - page_top);
1038 fz_closepath(ctx, path);
1039
1040 rgb[0] = color.r / 255.0f;
1041 rgb[1] = color.g / 255.0f;
1042 rgb[2] = color.b / 255.0f;
1043
1044 fz_fill_path(ctx, dev, path, 0, ctm, fz_device_rgb(ctx), rgb, color.a / 255.0f, fz_default_color_params);
1045
1046 fz_drop_path(ctx, path);
1047 }
1048}
1049
1050static const char *roman_uc[3][10] = {
1051 { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
1052 { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
1053 { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
1054};
1055
1056static const char *roman_lc[3][10] = {
1057 { "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" },
1058 { "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" },
1059 { "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" },
1060};
1061
1062static void format_roman_number(fz_context *ctx, char *buf, int size, int n, const char *sym[3][10], const char *sym_m)
1063{
1064 int I = n % 10;
1065 int X = (n / 10) % 10;
1066 int C = (n / 100) % 10;
1067 int M = (n / 1000);
1068
1069 fz_strlcpy(buf, "", size);
1070 while (M--)
1071 fz_strlcat(buf, sym_m, size);
1072 fz_strlcat(buf, sym[2][C], size);
1073 fz_strlcat(buf, sym[1][X], size);
1074 fz_strlcat(buf, sym[0][I], size);
1075 fz_strlcat(buf, ". ", size);
1076}
1077
1078static void format_alpha_number(fz_context *ctx, char *buf, int size, int n, int alpha, int omega)
1079{
1080 int base = omega - alpha + 1;
1081 int tmp[40];
1082 int i, c;
1083
1084 if (alpha > 256) /* to skip final-s for greek */
1085 --base;
1086
1087 /* Bijective base-26 (base-24 for greek) numeration */
1088 i = 0;
1089 while (n > 0)
1090 {
1091 --n;
1092 c = n % base + alpha;
1093 if (alpha > 256 && c > alpha + 16) /* skip final-s for greek */
1094 ++c;
1095 tmp[i++] = c;
1096 n /= base;
1097 }
1098
1099 while (i > 0)
1100 buf += fz_runetochar(buf, tmp[--i]);
1101 *buf++ = '.';
1102 *buf++ = ' ';
1103 *buf = 0;
1104}
1105
1106static void format_list_number(fz_context *ctx, int type, int x, char *buf, int size)
1107{
1108 switch (type)
1109 {
1110 case LST_NONE: fz_strlcpy(buf, "", size); break;
1111 case LST_DISC: fz_snprintf(buf, size, "%C ", 0x2022); break; /* U+2022 BULLET */
1112 case LST_CIRCLE: fz_snprintf(buf, size, "%C ", 0x25CB); break; /* U+25CB WHITE CIRCLE */
1113 case LST_SQUARE: fz_snprintf(buf, size, "%C ", 0x25A0); break; /* U+25A0 BLACK SQUARE */
1114 default:
1115 case LST_DECIMAL: fz_snprintf(buf, size, "%d. ", x); break;
1116 case LST_DECIMAL_ZERO: fz_snprintf(buf, size, "%02d. ", x); break;
1117 case LST_LC_ROMAN: format_roman_number(ctx, buf, size, x, roman_lc, "m"); break;
1118 case LST_UC_ROMAN: format_roman_number(ctx, buf, size, x, roman_uc, "M"); break;
1119 case LST_LC_ALPHA: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
1120 case LST_UC_ALPHA: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
1121 case LST_LC_LATIN: format_alpha_number(ctx, buf, size, x, 'a', 'z'); break;
1122 case LST_UC_LATIN: format_alpha_number(ctx, buf, size, x, 'A', 'Z'); break;
1123 case LST_LC_GREEK: format_alpha_number(ctx, buf, size, x, 0x03B1, 0x03C9); break;
1124 case LST_UC_GREEK: format_alpha_number(ctx, buf, size, x, 0x0391, 0x03A9); break;
1125 }
1126}
1127
1128static fz_html_flow *find_list_mark_anchor(fz_context *ctx, fz_html_box *box)
1129{
1130 /* find first flow node in <li> tag */
1131 while (box)
1132 {
1133 if (box->type == BOX_FLOW)
1134 return box->flow_head;
1135 box = box->down;
1136 }
1137 return NULL;
1138}
1139
1140static 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)
1141{
1142 fz_font *font;
1143 fz_text *text;
1144 fz_matrix trm;
1145 fz_html_flow *line;
1146 float y, w;
1147 float color[3];
1148 const char *s;
1149 char buf[40];
1150 int c, g;
1151
1152 trm = fz_scale(box->em, -box->em);
1153
1154 line = find_list_mark_anchor(ctx, box);
1155 if (line)
1156 {
1157 y = line->y;
1158 }
1159 else
1160 {
1161 float h = fz_from_css_number_scale(box->style.line_height, box->em);
1162 float a = box->em * 0.8f;
1163 float d = box->em * 0.2f;
1164 if (a + d > h)
1165 h = a + d;
1166 y = box->y + a + (h - a - d) / 2;
1167 }
1168
1169 if (y > page_bot || y < page_top)
1170 return;
1171
1172 format_list_number(ctx, box->style.list_style_type, n, buf, sizeof buf);
1173
1174 s = buf;
1175 w = 0;
1176 while (*s)
1177 {
1178 s += fz_chartorune(&c, s);
1179 g = fz_encode_character_with_fallback(ctx, box->style.font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
1180 w += fz_advance_glyph(ctx, font, g, 0) * box->em;
1181 }
1182
1183 text = fz_new_text(ctx);
1184
1185 fz_try(ctx)
1186 {
1187 s = buf;
1188 trm.e = box->x - w;
1189 trm.f = y - page_top;
1190 while (*s)
1191 {
1192 s += fz_chartorune(&c, s);
1193 g = fz_encode_character_with_fallback(ctx, box->style.font, c, UCDN_SCRIPT_LATIN, FZ_LANG_UNSET, &font);
1194 fz_show_glyph(ctx, text, font, trm, g, c, 0, 0, FZ_BIDI_NEUTRAL, FZ_LANG_UNSET);
1195 trm.e += fz_advance_glyph(ctx, font, g, 0) * box->em;
1196 }
1197
1198 color[0] = box->style.color.r / 255.0f;
1199 color[1] = box->style.color.g / 255.0f;
1200 color[2] = box->style.color.b / 255.0f;
1201
1202 fz_fill_text(ctx, dev, text, ctm, fz_device_rgb(ctx), color, 1, fz_default_color_params);
1203 }
1204 fz_always(ctx)
1205 fz_drop_text(ctx, text);
1206 fz_catch(ctx)
1207 fz_rethrow(ctx);
1208}
1209
1210static void 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)
1211{
1212 float x0, y0, x1, y1;
1213
1214 float *border = box->border;
1215 float *padding = box->padding;
1216
1217 x0 = box->x - padding[L];
1218 y0 = box->y - padding[T];
1219 x1 = box->x + box->w + padding[R];
1220 y1 = box->b + padding[B];
1221
1222 if (y0 > page_bot || y1 < page_top)
1223 return;
1224
1225 if (box->style.visibility == V_VISIBLE)
1226 {
1227 draw_rect(ctx, dev, ctm, page_top, box->style.background_color, x0, y0, x1, y1);
1228
1229 if (border[T] > 0)
1230 draw_rect(ctx, dev, ctm, page_top, box->style.border_color[T], x0 - border[L], y0 - border[T], x1 + border[R], y0);
1231 if (border[B] > 0)
1232 draw_rect(ctx, dev, ctm, page_top, box->style.border_color[B], x0 - border[L], y1, x1 + border[R], y1 + border[B]);
1233 if (border[L] > 0)
1234 draw_rect(ctx, dev, ctm, page_top, box->style.border_color[L], x0 - border[L], y0 - border[T], x0, y1 + border[B]);
1235 if (border[R] > 0)
1236 draw_rect(ctx, dev, ctm, page_top, box->style.border_color[R], x1, y0 - border[T], x1 + border[R], y1 + border[B]);
1237
1238 if (box->list_item)
1239 draw_list_mark(ctx, box, page_top, page_bot, dev, ctm, box->list_item);
1240 }
1241
1242 for (box = box->down; box; box = box->next)
1243 {
1244 switch (box->type)
1245 {
1246 case BOX_TABLE:
1247 case BOX_TABLE_ROW:
1248 case BOX_TABLE_CELL:
1249 case BOX_BLOCK: draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf); break;
1250 case BOX_FLOW: draw_flow_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf); break;
1251 }
1252 }
1253}
1254
1255void
1256fz_draw_html(fz_context *ctx, fz_device *dev, fz_matrix ctm, fz_html *html, int page)
1257{
1258 hb_buffer_t *hb_buf = NULL;
1259 fz_html_box *box;
1260 int unlocked = 0;
1261 float page_top = page * html->page_h;
1262 float page_bot = (page + 1) * html->page_h;
1263
1264 fz_var(hb_buf);
1265 fz_var(unlocked);
1266
1267 draw_rect(ctx, dev, ctm, 0, html->root->style.background_color,
1268 0, 0,
1269 html->page_w + html->page_margin[L] + html->page_margin[R],
1270 html->page_h + html->page_margin[T] + html->page_margin[B]);
1271
1272 ctm = fz_pre_translate(ctm, html->page_margin[L], html->page_margin[T]);
1273
1274 fz_hb_lock(ctx);
1275 fz_try(ctx)
1276 {
1277 hb_buf = hb_buffer_create();
1278 fz_hb_unlock(ctx);
1279 unlocked = 1;
1280
1281 for (box = html->root->down; box; box = box->next)
1282 draw_block_box(ctx, box, page_top, page_bot, dev, ctm, hb_buf);
1283 }
1284 fz_always(ctx)
1285 {
1286 if (unlocked)
1287 fz_hb_lock(ctx);
1288 hb_buffer_destroy(hb_buf);
1289 fz_hb_unlock(ctx);
1290 }
1291 fz_catch(ctx)
1292 {
1293 fz_rethrow(ctx);
1294 }
1295}
1296