Mercurial > hgrepos > Python2 > PyMuPDF
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 } |
