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