NetSurf
table.c
Go to the documentation of this file.
1/*
2 * Copyright 2005 James Bursa <bursa@users.sourceforge.net>
3 * Copyright 2005 Richard Wilson <info@tinct.net>
4 *
5 * This file is part of NetSurf, http://www.netsurf-browser.org/
6 *
7 * NetSurf is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; version 2 of the License.
10 *
11 * NetSurf is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/**
21 * \file
22 * implementation of HTML table processing and layout.
23 */
24
25#include <assert.h>
26#include <dom/dom.h>
27
28#include "utils/log.h"
29#include "utils/utils.h"
30#include "utils/talloc.h"
31#include "css/utils.h"
32
33#include "html/box.h"
34#include "html/table.h"
35
36/* Define to enable verbose table debug */
37#undef TABLE_DEBUG
38
39/**
40 * Container for border values during table border calculations
41 */
42struct border {
43 enum css_border_style_e style; /**< border-style */
44 enum css_border_color_e color; /**< border-color type */
45 css_color c; /**< border-color value */
46 css_fixed width; /**< border-width length */
47 css_unit unit; /**< border-width units */
48};
49
50
51/**
52 * Determine if a border style is more eyecatching than another
53 *
54 * \param unit_len_ctx Length conversion context
55 * \param a Reference border style
56 * \param a_src Source of \a a
57 * \param b Candidate border style
58 * \param b_src Source of \a b
59 * \return True if \a b is more eyecatching than \a a
60 */
61static bool
62table_border_is_more_eyecatching(const css_unit_ctx *unit_len_ctx,
63 const struct border *a,
64 box_type a_src,
65 const struct border *b,
66 box_type b_src)
67{
68 css_fixed awidth, bwidth;
69 int impact = 0;
70
71 /* See CSS 2.1 $17.6.2.1 */
72
73 /* 1 + 2 -- hidden beats everything, none beats nothing */
74 if (a->style == CSS_BORDER_STYLE_HIDDEN ||
75 b->style == CSS_BORDER_STYLE_NONE)
76 return false;
77
78 if (b->style == CSS_BORDER_STYLE_HIDDEN ||
79 a->style == CSS_BORDER_STYLE_NONE)
80 return true;
81
82 /* 3a -- wider borders beat narrow ones */
83 /* The widths must be absolute, which will be the case
84 * if they've come from a computed style. */
85 assert(a->unit != CSS_UNIT_EM && a->unit != CSS_UNIT_EX);
86 assert(b->unit != CSS_UNIT_EM && b->unit != CSS_UNIT_EX);
87 awidth = css_unit_len2device_px(NULL, unit_len_ctx, a->width, a->unit);
88 bwidth = css_unit_len2device_px(NULL, unit_len_ctx, b->width, b->unit);
89
90 if (awidth < bwidth)
91 return true;
92 else if (bwidth < awidth)
93 return false;
94
95 /* 3b -- sort by style */
96 switch (a->style) {
97 case CSS_BORDER_STYLE_DOUBLE: impact++; fallthrough;
98 case CSS_BORDER_STYLE_SOLID: impact++; fallthrough;
99 case CSS_BORDER_STYLE_DASHED: impact++; fallthrough;
100 case CSS_BORDER_STYLE_DOTTED: impact++; fallthrough;
101 case CSS_BORDER_STYLE_RIDGE: impact++; fallthrough;
102 case CSS_BORDER_STYLE_OUTSET: impact++; fallthrough;
103 case CSS_BORDER_STYLE_GROOVE: impact++; fallthrough;
104 case CSS_BORDER_STYLE_INSET: impact++; fallthrough;
105 default:
106 break;
107 }
108
109 switch (b->style) {
110 case CSS_BORDER_STYLE_DOUBLE: impact--; fallthrough;
111 case CSS_BORDER_STYLE_SOLID: impact--; fallthrough;
112 case CSS_BORDER_STYLE_DASHED: impact--; fallthrough;
113 case CSS_BORDER_STYLE_DOTTED: impact--; fallthrough;
114 case CSS_BORDER_STYLE_RIDGE: impact--; fallthrough;
115 case CSS_BORDER_STYLE_OUTSET: impact--; fallthrough;
116 case CSS_BORDER_STYLE_GROOVE: impact--; fallthrough;
117 case CSS_BORDER_STYLE_INSET: impact--; fallthrough;
118 default:
119 break;
120 }
121
122 if (impact < 0)
123 return true;
124 else if (impact > 0)
125 return false;
126
127 /* 4a -- sort by origin */
128 impact = 0;
129
130 /** \todo COL/COL_GROUP */
131 switch (a_src) {
132 case BOX_TABLE_CELL: impact++; fallthrough;
133 case BOX_TABLE_ROW: impact++; fallthrough;
134 case BOX_TABLE_ROW_GROUP: impact++; fallthrough;
135 case BOX_TABLE: impact++; fallthrough;
136 default:
137 break;
138 }
139
140 /** \todo COL/COL_GROUP */
141 switch (b_src) {
142 case BOX_TABLE_CELL: impact--; fallthrough;
143 case BOX_TABLE_ROW: impact--; fallthrough;
144 case BOX_TABLE_ROW_GROUP: impact--; fallthrough;
145 case BOX_TABLE: impact--; fallthrough;
146 default:
147 break;
148 }
149
150 if (impact < 0)
151 return true;
152 else if (impact > 0)
153 return false;
154
155 /* 4b -- furthest left (if direction: ltr) and towards top wins */
156 /** \todo Currently assumes b satisifies this */
157 return true;
158}
159
160
161/**
162 * Process a table
163 *
164 * \param unit_len_ctx Length conversion context
165 * \param table Table to process
166 * \param a Current border style for cell
167 * \param a_src Source of \a a
168 *
169 * \post \a a will be updated with most eyecatching style
170 * \post \a a_src will be updated also
171 */
172static void
173table_cell_top_process_table(const css_unit_ctx *unit_len_ctx,
174 struct box *table,
175 struct border *a,
176 box_type *a_src)
177{
178 struct border b;
179 box_type b_src;
180
181 /* Top border of table */
182 b.style = css_computed_border_top_style(table->style);
183 b.color = css_computed_border_top_color(table->style, &b.c);
184 css_computed_border_top_width(table->style, &b.width, &b.unit);
185 b.width = css_unit_len2device_px(table->style, unit_len_ctx,
186 b.width, b.unit);
187 b.unit = CSS_UNIT_PX;
188 b_src = BOX_TABLE;
189
190 if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
191 *a = b;
192 *a_src = b_src;
193 }
194}
195
196
197/**
198 * Process a row
199 *
200 * \param unit_len_ctx Length conversion context
201 * \param cell Cell being considered
202 * \param row Row to process
203 * \param a Current border style for cell
204 * \param a_src Source of \a a
205 * \return true if row has cells, false otherwise
206 *
207 * \post \a a will be updated with most eyecatching style
208 * \post \a a_src will be updated also
209 */
210static bool
211table_cell_top_process_row(const css_unit_ctx *unit_len_ctx,
212 struct box *cell,
213 struct box *row,
214 struct border *a,
215 box_type *a_src)
216{
217 struct border b;
218 box_type b_src;
219
220 /* Bottom border of row */
221 b.style = css_computed_border_bottom_style(row->style);
222 b.color = css_computed_border_bottom_color(row->style, &b.c);
223 css_computed_border_bottom_width(row->style, &b.width, &b.unit);
224 b.width = css_unit_len2device_px(row->style, unit_len_ctx,
225 b.width, b.unit);
226 b.unit = CSS_UNIT_PX;
227 b_src = BOX_TABLE_ROW;
228
229 if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
230 *a = b;
231 *a_src = b_src;
232 }
233
234 if (row->children == NULL) {
235 /* Row is empty, so consider its top border */
236 b.style = css_computed_border_top_style(row->style);
237 b.color = css_computed_border_top_color(row->style, &b.c);
238 css_computed_border_top_width(row->style, &b.width, &b.unit);
239 b.width = css_unit_len2device_px(row->style, unit_len_ctx,
240 b.width, b.unit);
241 b.unit = CSS_UNIT_PX;
242 b_src = BOX_TABLE_ROW;
243
244 if (table_border_is_more_eyecatching(unit_len_ctx,
245 a, *a_src, &b, b_src)) {
246 *a = b;
247 *a_src = b_src;
248 }
249
250 return false;
251 } else {
252 /* Process cells that are directly above the cell being
253 * considered. They may not be in this row, but in one of the
254 * rows above it in the case where rowspan > 1. */
255 struct box *c;
256 bool processed = false;
257
258 while (processed == false) {
259 for (c = row->children; c != NULL; c = c->next) {
260 /* Ignore cells to the left */
261 if (c->start_column + c->columns - 1 <
262 cell->start_column)
263 continue;
264 /* Ignore cells to the right */
265 if (c->start_column > cell->start_column +
266 cell->columns - 1)
267 continue;
268
269 /* Flag that we've processed a cell */
270 processed = true;
271
272 /* Consider bottom border */
273 b.style = css_computed_border_bottom_style(
274 c->style);
275 b.color = css_computed_border_bottom_color(
276 c->style, &b.c);
277 css_computed_border_bottom_width(c->style,
278 &b.width, &b.unit);
279 b.width = css_unit_len2device_px(
280 c->style, unit_len_ctx,
281 b.width, b.unit);
282 b.unit = CSS_UNIT_PX;
283 b_src = BOX_TABLE_CELL;
284
285 if (table_border_is_more_eyecatching(unit_len_ctx,
286 a,
287 *a_src,
288 &b,
289 b_src)) {
290 *a = b;
291 *a_src = b_src;
292 }
293 }
294
295 if (processed == false) {
296 /* There must be a preceding row */
297 assert(row->prev != NULL);
298
299 row = row->prev;
300 }
301 }
302 }
303
304 return true;
305}
306
307
308/**
309 * Process a group
310 *
311 * \param unit_len_ctx Length conversion context
312 * \param cell Cell being considered
313 * \param group Group to process
314 * \param a Current border style for cell
315 * \param a_src Source of \a a
316 * \return true if group has non-empty rows, false otherwise
317 *
318 * \post \a a will be updated with most eyecatching style
319 * \post \a a_src will be updated also
320 */
321static bool
322table_cell_top_process_group(const css_unit_ctx *unit_len_ctx,
323 struct box *cell,
324 struct box *group,
325 struct border *a,
326 box_type *a_src)
327{
328 struct border b;
329 box_type b_src;
330
331 /* Bottom border of group */
332 b.style = css_computed_border_bottom_style(group->style);
333 b.color = css_computed_border_bottom_color(group->style, &b.c);
334 css_computed_border_bottom_width(group->style, &b.width, &b.unit);
335 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
336 b.width, b.unit);
337 b.unit = CSS_UNIT_PX;
338 b_src = BOX_TABLE_ROW_GROUP;
339
340 if (table_border_is_more_eyecatching(unit_len_ctx, a, *a_src, &b, b_src)) {
341 *a = b;
342 *a_src = b_src;
343 }
344
345 if (group->last != NULL) {
346 /* Process rows in group, starting with last */
347 struct box *row = group->last;
348
349 while (table_cell_top_process_row(unit_len_ctx, cell, row,
350 a, a_src) == false) {
351 if (row->prev == NULL) {
352 return false;
353 } else {
354 row = row->prev;
355 }
356 }
357 } else {
358 /* Group is empty, so consider its top border */
359 b.style = css_computed_border_top_style(group->style);
360 b.color = css_computed_border_top_color(group->style, &b.c);
361 css_computed_border_top_width(group->style, &b.width, &b.unit);
362 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
363 b.width, b.unit);
364 b.unit = CSS_UNIT_PX;
365 b_src = BOX_TABLE_ROW_GROUP;
366
367 if (table_border_is_more_eyecatching(unit_len_ctx,
368 a, *a_src, &b, b_src)) {
369 *a = b;
370 *a_src = b_src;
371 }
372
373 return false;
374 }
375
376 return true;
377}
378
379
380/**
381 * Calculate used values of border-left-{style,color,width}
382 *
383 * \param unit_len_ctx Length conversion context
384 * \param cell Table cell to consider
385 */
386static void
387table_used_left_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
388{
389 struct border a, b;
390 box_type a_src, b_src;
391
392 /** \todo Need column and column_group, too */
393
394 /* Initialise to computed left border for cell */
395 a.style = css_computed_border_left_style(cell->style);
396 a.color = css_computed_border_left_color(cell->style, &a.c);
397 css_computed_border_left_width(cell->style, &a.width, &a.unit);
398 a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
399 a.width, a.unit);
400 a.unit = CSS_UNIT_PX;
401 a_src = BOX_TABLE_CELL;
402
403 if (cell->prev != NULL || cell->start_column != 0) {
404 /* Cell to the left -- consider its right border */
405 struct box *prev = NULL;
406
407 if (cell->prev == NULL) {
408 struct box *row;
409
410 /* Spanned from a previous row in current row group */
411 for (row = cell->parent; row != NULL; row = row->prev) {
412 for (prev = row->children; prev != NULL;
413 prev = prev->next) {
414 if (prev->start_column +
415 prev->columns ==
416 cell->start_column)
417 break;
418 }
419
420 if (prev != NULL)
421 break;
422 }
423
424 assert(prev != NULL);
425 } else {
426 prev = cell->prev;
427 }
428
429 b.style = css_computed_border_right_style(prev->style);
430 b.color = css_computed_border_right_color(prev->style, &b.c);
431 css_computed_border_right_width(prev->style, &b.width, &b.unit);
432 b.width = css_unit_len2device_px(prev->style, unit_len_ctx,
433 b.width, b.unit);
434 b.unit = CSS_UNIT_PX;
435 b_src = BOX_TABLE_CELL;
436
437 if (table_border_is_more_eyecatching(unit_len_ctx,
438 &a, a_src, &b, b_src)) {
439 a = b;
440 a_src = b_src;
441 }
442 } else {
443 /* First cell in row, so consider rows and row group */
444 struct box *row = cell->parent;
445 struct box *group = row->parent;
446 struct box *table = group->parent;
447 unsigned int rows = cell->rows;
448
449 while (rows-- > 0 && row != NULL) {
450 /* Spanned rows -- consider their left border */
451 b.style = css_computed_border_left_style(row->style);
452 b.color = css_computed_border_left_color(
453 row->style, &b.c);
454 css_computed_border_left_width(
455 row->style, &b.width, &b.unit);
456 b.width = css_unit_len2device_px(
457 row->style, unit_len_ctx,
458 b.width, b.unit);
459 b.unit = CSS_UNIT_PX;
460 b_src = BOX_TABLE_ROW;
461
462 if (table_border_is_more_eyecatching(unit_len_ctx,
463 &a, a_src, &b, b_src)) {
464 a = b;
465 a_src = b_src;
466 }
467
468 row = row->next;
469 }
470
471 /** \todo can cells span row groups? */
472
473 /* Row group -- consider its left border */
474 b.style = css_computed_border_left_style(group->style);
475 b.color = css_computed_border_left_color(group->style, &b.c);
476 css_computed_border_left_width(group->style, &b.width, &b.unit);
477 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
478 b.width, b.unit);
479 b.unit = CSS_UNIT_PX;
480 b_src = BOX_TABLE_ROW_GROUP;
481
482 if (table_border_is_more_eyecatching(unit_len_ctx,
483 &a, a_src, &b, b_src)) {
484 a = b;
485 a_src = b_src;
486 }
487
488 /* The table itself -- consider its left border */
489 b.style = css_computed_border_left_style(table->style);
490 b.color = css_computed_border_left_color(table->style, &b.c);
491 css_computed_border_left_width(table->style, &b.width, &b.unit);
492 b.width = css_unit_len2device_px(table->style, unit_len_ctx,
493 b.width, b.unit);
494 b.unit = CSS_UNIT_PX;
495 b_src = BOX_TABLE;
496
497 if (table_border_is_more_eyecatching(unit_len_ctx,
498 &a, a_src, &b, b_src)) {
499 a = b;
500 a_src = b_src;
501 }
502 }
503
504 /* a now contains the used left border for the cell */
505 cell->border[LEFT].style = a.style;
506 cell->border[LEFT].c = a.c;
507 cell->border[LEFT].width = FIXTOINT(css_unit_len2device_px(
508 cell->style, unit_len_ctx, a.width, a.unit));
509}
510
511
512/**
513 * Calculate used values of border-top-{style,color,width}
514 *
515 * \param unit_len_ctx Length conversion context
516 * \param cell Table cell to consider
517 */
518static void
519table_used_top_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
520{
521 struct border a, b;
522 box_type a_src, b_src;
523 struct box *row = cell->parent;
524 bool process_group = false;
525
526 /* Initialise to computed top border for cell */
527 a.style = css_computed_border_top_style(cell->style);
528 css_computed_border_top_color(cell->style, &a.c);
529 css_computed_border_top_width(cell->style, &a.width, &a.unit);
530 a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
531 a.width, a.unit);
532 a.unit = CSS_UNIT_PX;
533 a_src = BOX_TABLE_CELL;
534
535 /* Top border of row */
536 b.style = css_computed_border_top_style(row->style);
537 css_computed_border_top_color(row->style, &b.c);
538 css_computed_border_top_width(row->style, &b.width, &b.unit);
539 b.width = css_unit_len2device_px(row->style, unit_len_ctx,
540 b.width, b.unit);
541 b.unit = CSS_UNIT_PX;
542 b_src = BOX_TABLE_ROW;
543
544 if (table_border_is_more_eyecatching(unit_len_ctx, &a, a_src, &b, b_src)) {
545 a = b;
546 a_src = b_src;
547 }
548
549 if (row->prev != NULL) {
550 /* Consider row(s) above */
551 while (table_cell_top_process_row(unit_len_ctx, cell, row->prev,
552 &a, &a_src) == false) {
553 if (row->prev->prev == NULL) {
554 /* Consider row group */
555 process_group = true;
556 break;
557 } else {
558 row = row->prev;
559 }
560 }
561 } else {
562 process_group = true;
563 }
564
565 if (process_group) {
566 struct box *group = row->parent;
567
568 /* Top border of row group */
569 b.style = css_computed_border_top_style(group->style);
570 b.color = css_computed_border_top_color(group->style, &b.c);
571 css_computed_border_top_width(group->style, &b.width, &b.unit);
572 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
573 b.width, b.unit);
574 b.unit = CSS_UNIT_PX;
575 b_src = BOX_TABLE_ROW_GROUP;
576
577 if (table_border_is_more_eyecatching(unit_len_ctx,
578 &a, a_src, &b, b_src)) {
579 a = b;
580 a_src = b_src;
581 }
582
583 if (group->prev == NULL) {
584 /* Top border of table */
585 table_cell_top_process_table(unit_len_ctx,
586 group->parent, &a, &a_src);
587 } else {
588 /* Process previous group(s) */
589 while (table_cell_top_process_group(unit_len_ctx,
590 cell, group->prev,
591 &a, &a_src) == false) {
592 if (group->prev->prev == NULL) {
593 /* Top border of table */
594 table_cell_top_process_table(unit_len_ctx,
595 group->parent,
596 &a, &a_src);
597 break;
598 } else {
599 group = group->prev;
600 }
601 }
602 }
603 }
604
605 /* a now contains the used top border for the cell */
606 cell->border[TOP].style = a.style;
607 cell->border[TOP].c = a.c;
608 cell->border[TOP].width = FIXTOINT(css_unit_len2device_px(
609 cell->style, unit_len_ctx, a.width, a.unit));
610}
611
612/**
613 * Calculate used values of border-right-{style,color,width}
614 *
615 * \param unit_len_ctx Length conversion context
616 * \param cell Table cell to consider
617 */
618static void
619table_used_right_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
620{
621 struct border a, b;
622 box_type a_src, b_src;
623
624 /** \todo Need column and column_group, too */
625
626 /* Initialise to computed right border for cell */
627 a.style = css_computed_border_right_style(cell->style);
628 css_computed_border_right_color(cell->style, &a.c);
629 css_computed_border_right_width(cell->style, &a.width, &a.unit);
630 a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
631 a.width, a.unit);
632 a.unit = CSS_UNIT_PX;
633 a_src = BOX_TABLE_CELL;
634
635 if (cell->next != NULL || cell->start_column + cell->columns !=
636 cell->parent->parent->parent->columns) {
637 /* Cell is not at right edge of table -- no right border */
638 a.style = CSS_BORDER_STYLE_NONE;
639 a.width = 0;
640 a.unit = CSS_UNIT_PX;
641 } else {
642 /* Last cell in row, so consider rows and row group */
643 struct box *row = cell->parent;
644 struct box *group = row->parent;
645 struct box *table = group->parent;
646 unsigned int rows = cell->rows;
647
648 while (rows-- > 0 && row != NULL) {
649 /* Spanned rows -- consider their right border */
650 b.style = css_computed_border_right_style(row->style);
651 b.color = css_computed_border_right_color(row->style,
652 &b.c);
653 css_computed_border_right_width(row->style,
654 &b.width,
655 &b.unit);
656 b.width = css_unit_len2device_px(
657 row->style, unit_len_ctx,
658 b.width, b.unit);
659 b.unit = CSS_UNIT_PX;
660 b_src = BOX_TABLE_ROW;
661
662 if (table_border_is_more_eyecatching(unit_len_ctx,
663 &a, a_src,
664 &b, b_src)) {
665 a = b;
666 a_src = b_src;
667 }
668
669 row = row->next;
670 }
671
672 /** \todo can cells span row groups? */
673
674 /* Row group -- consider its right border */
675 b.style = css_computed_border_right_style(group->style);
676 b.color = css_computed_border_right_color(group->style, &b.c);
677 css_computed_border_right_width(group->style,
678 &b.width, &b.unit);
679 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
680 b.width, b.unit);
681 b.unit = CSS_UNIT_PX;
682 b_src = BOX_TABLE_ROW_GROUP;
683
684 if (table_border_is_more_eyecatching(unit_len_ctx,
685 &a, a_src, &b, b_src)) {
686 a = b;
687 a_src = b_src;
688 }
689
690 /* The table itself -- consider its right border */
691 b.style = css_computed_border_right_style(table->style);
692 b.color = css_computed_border_right_color(table->style, &b.c);
693 css_computed_border_right_width(table->style,
694 &b.width, &b.unit);
695 b.width = css_unit_len2device_px(table->style, unit_len_ctx,
696 b.width, b.unit);
697 b.unit = CSS_UNIT_PX;
698 b_src = BOX_TABLE;
699
700 if (table_border_is_more_eyecatching(unit_len_ctx,
701 &a, a_src,
702 &b, b_src)) {
703 a = b;
704 a_src = b_src;
705 }
706 }
707
708 /* a now contains the used right border for the cell */
709 cell->border[RIGHT].style = a.style;
710 cell->border[RIGHT].c = a.c;
711 cell->border[RIGHT].width = FIXTOINT(css_unit_len2device_px(
712 cell->style, unit_len_ctx, a.width, a.unit));
713}
714
715
716/**
717 * Calculate used values of border-bottom-{style,color,width}
718 *
719 * \param unit_len_ctx Length conversion context
720 * \param cell Table cell to consider
721 */
722static void
723table_used_bottom_border_for_cell(const css_unit_ctx *unit_len_ctx,
724 struct box *cell)
725{
726 struct border a, b;
727 box_type a_src, b_src;
728 struct box *row = cell->parent;
729 unsigned int rows = cell->rows;
730
731 /* Initialise to computed bottom border for cell */
732 a.style = css_computed_border_bottom_style(cell->style);
733 css_computed_border_bottom_color(cell->style, &a.c);
734 css_computed_border_bottom_width(cell->style, &a.width, &a.unit);
735 a.width = css_unit_len2device_px(cell->style, unit_len_ctx,
736 a.width, a.unit);
737 a.unit = CSS_UNIT_PX;
738 a_src = BOX_TABLE_CELL;
739
740 while (rows-- > 0 && row != NULL)
741 row = row->next;
742
743 /** \todo Can cells span row groups? */
744
745 if (row != NULL) {
746 /* Cell is not at bottom edge of table -- no bottom border */
747 a.style = CSS_BORDER_STYLE_NONE;
748 a.width = 0;
749 a.unit = CSS_UNIT_PX;
750 } else {
751 /* Cell at bottom of table, so consider row and row group */
752 struct box *row = cell->parent;
753 struct box *group = row->parent;
754 struct box *table = group->parent;
755
756 /* Bottom border of row */
757 b.style = css_computed_border_bottom_style(row->style);
758 b.color = css_computed_border_bottom_color(row->style, &b.c);
759 css_computed_border_bottom_width(row->style, &b.width, &b.unit);
760 b.width = css_unit_len2device_px(row->style, unit_len_ctx,
761 b.width, b.unit);
762 b.unit = CSS_UNIT_PX;
763 b_src = BOX_TABLE_ROW;
764
765 if (table_border_is_more_eyecatching(unit_len_ctx,
766 &a, a_src, &b, b_src)) {
767 a = b;
768 a_src = b_src;
769 }
770
771 /* Row group -- consider its bottom border */
772 b.style = css_computed_border_bottom_style(group->style);
773 b.color = css_computed_border_bottom_color(group->style, &b.c);
774 css_computed_border_bottom_width(group->style,
775 &b.width, &b.unit);
776 b.width = css_unit_len2device_px(group->style, unit_len_ctx,
777 b.width, b.unit);
778 b.unit = CSS_UNIT_PX;
779 b_src = BOX_TABLE_ROW_GROUP;
780
781 if (table_border_is_more_eyecatching(unit_len_ctx,
782 &a, a_src, &b, b_src)) {
783 a = b;
784 a_src = b_src;
785 }
786
787 /* The table itself -- consider its bottom border */
788 b.style = css_computed_border_bottom_style(table->style);
789 b.color = css_computed_border_bottom_color(table->style, &b.c);
790 css_computed_border_bottom_width(table->style,
791 &b.width, &b.unit);
792 b.width = css_unit_len2device_px(table->style, unit_len_ctx,
793 b.width, b.unit);
794 b.unit = CSS_UNIT_PX;
795 b_src = BOX_TABLE;
796
797 if (table_border_is_more_eyecatching(unit_len_ctx,
798 &a, a_src, &b, b_src)) {
799 a = b;
800 }
801 }
802
803 /* a now contains the used bottom border for the cell */
804 cell->border[BOTTOM].style = a.style;
805 cell->border[BOTTOM].c = a.c;
806 cell->border[BOTTOM].width = FIXTOINT(css_unit_len2device_px(
807 cell->style, unit_len_ctx, a.width, a.unit));
808}
809
810
811/* exported interface documented in html/table.h */
812bool
813table_calculate_column_types(const css_unit_ctx *unit_len_ctx, struct box *table)
814{
815 unsigned int i, j;
816 struct column *col;
817 struct box *row_group, *row, *cell;
818
819 if (table->col)
820 /* table->col already constructed, for example frameset table */
821 return true;
822
823 table->col = col = talloc_array(table, struct column, table->columns);
824 if (!col)
825 return false;
826
827 for (i = 0; i != table->columns; i++) {
828 col[i].type = COLUMN_WIDTH_UNKNOWN;
829 col[i].width = 0;
830 col[i].positioned = true;
831 }
832
833 /* 1st pass: cells with colspan 1 only */
834 for (row_group = table->children; row_group; row_group =row_group->next)
835 for (row = row_group->children; row; row = row->next)
836 for (cell = row->children; cell; cell = cell->next) {
837 enum css_width_e type;
838 css_fixed value = 0;
839 css_unit unit = CSS_UNIT_PX;
840
841 assert(cell->type == BOX_TABLE_CELL);
842 assert(cell->style);
843
844 if (cell->columns != 1)
845 continue;
846 i = cell->start_column;
847
848 if (css_computed_position(cell->style) !=
849 CSS_POSITION_ABSOLUTE &&
850 css_computed_position(cell->style) !=
851 CSS_POSITION_FIXED) {
852 col[i].positioned = false;
853 }
854
855 type = css_computed_width(cell->style, &value, &unit);
856
857 /* fixed width takes priority over any other width type */
858 if (col[i].type != COLUMN_WIDTH_FIXED &&
859 type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT) {
860 col[i].type = COLUMN_WIDTH_FIXED;
861 col[i].width = FIXTOINT(css_unit_len2device_px(
862 cell->style,
863 unit_len_ctx,
864 value, unit));
865 if (col[i].width < 0)
866 col[i].width = 0;
867 continue;
868 }
869
870 if (col[i].type != COLUMN_WIDTH_UNKNOWN)
871 continue;
872
873 if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT) {
874 col[i].type = COLUMN_WIDTH_PERCENT;
875 col[i].width = FIXTOINT(value);
876 if (col[i].width < 0)
877 col[i].width = 0;
878 } else if (type == CSS_WIDTH_AUTO) {
879 col[i].type = COLUMN_WIDTH_AUTO;
880 }
881 }
882
883 /* 2nd pass: cells which span multiple columns */
884 for (row_group = table->children; row_group; row_group =row_group->next)
885 for (row = row_group->children; row; row = row->next)
886 for (cell = row->children; cell; cell = cell->next) {
887 unsigned int fixed_columns = 0,
888 percent_columns = 0,
889 auto_columns = 0,
890 unknown_columns = 0;
891 int fixed_width = 0, percent_width = 0;
892 enum css_width_e type;
893 css_fixed value = 0;
894 css_unit unit = CSS_UNIT_PX;
895
896 if (cell->columns == 1)
897 continue;
898 i = cell->start_column;
899
900 for (j = i; j < i + cell->columns; j++) {
901 col[j].positioned = false;
902 }
903
904 /* count column types in spanned cells */
905 for (j = 0; j != cell->columns; j++) {
906 if (col[i + j].type == COLUMN_WIDTH_FIXED) {
907 fixed_width += col[i + j].width;
908 fixed_columns++;
909 } else if (col[i + j].type == COLUMN_WIDTH_PERCENT) {
910 percent_width += col[i + j].width;
911 percent_columns++;
912 } else if (col[i + j].type == COLUMN_WIDTH_AUTO) {
913 auto_columns++;
914 } else {
915 unknown_columns++;
916 }
917 }
918
919 if (!unknown_columns)
920 continue;
921
922 type = css_computed_width(cell->style, &value, &unit);
923
924 /* if cell is fixed width, and all spanned columns are fixed
925 * or unknown width, split extra width among unknown columns */
926 if (type == CSS_WIDTH_SET && unit != CSS_UNIT_PCT &&
927 fixed_columns + unknown_columns ==
928 cell->columns) {
929 int width = (FIXTOFLT(css_unit_len2device_px(
930 cell->style,
931 unit_len_ctx,
932 value, unit)) -
933 fixed_width) / unknown_columns;
934 if (width < 0)
935 width = 0;
936 for (j = 0; j != cell->columns; j++) {
937 if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
938 col[i + j].type = COLUMN_WIDTH_FIXED;
939 col[i + j].width = width;
940 }
941 }
942 }
943
944 /* as above for percentage width */
945 if (type == CSS_WIDTH_SET && unit == CSS_UNIT_PCT &&
946 percent_columns + unknown_columns ==
947 cell->columns) {
948 int width = (FIXTOFLT(value) -
949 percent_width) / unknown_columns;
950 if (width < 0)
951 width = 0;
952 for (j = 0; j != cell->columns; j++) {
953 if (col[i + j].type == COLUMN_WIDTH_UNKNOWN) {
954 col[i + j].type = COLUMN_WIDTH_PERCENT;
955 col[i + j].width = width;
956 }
957 }
958 }
959 }
960
961 /* use AUTO if no width type was specified */
962 for (i = 0; i != table->columns; i++) {
963 if (col[i].type == COLUMN_WIDTH_UNKNOWN)
964 col[i].type = COLUMN_WIDTH_AUTO;
965 }
966
967#ifdef TABLE_DEBUG
968 for (i = 0; i != table->columns; i++)
969 NSLOG(netsurf, INFO,
970 "table %p, column %u: type %s, width %i",
971 table,
972 i,
973 ((const char *[]){
974 "UNKNOWN",
975 "FIXED",
976 "AUTO",
977 "PERCENT",
978 "RELATIVE",
979 })[col[i].type],
980 col[i].width);
981#endif
982
983 return true;
984}
985
986
987/* exported interface documented in html/table.h */
988void table_used_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
989{
990 int side;
991
992 assert(cell->type == BOX_TABLE_CELL);
993
994 if (css_computed_border_collapse(cell->style) ==
995 CSS_BORDER_COLLAPSE_SEPARATE) {
996 css_fixed width = 0;
997 css_unit unit = CSS_UNIT_PX;
998
999 /* Left border */
1000 cell->border[LEFT].style =
1001 css_computed_border_left_style(cell->style);
1002 css_computed_border_left_color(cell->style,
1003 &cell->border[LEFT].c);
1004 css_computed_border_left_width(cell->style, &width, &unit);
1005 cell->border[LEFT].width =
1006 FIXTOINT(css_unit_len2device_px(
1007 cell->style, unit_len_ctx,
1008 width, unit));
1009
1010 /* Top border */
1011 cell->border[TOP].style =
1012 css_computed_border_top_style(cell->style);
1013 css_computed_border_top_color(cell->style,
1014 &cell->border[TOP].c);
1015 css_computed_border_top_width(cell->style, &width, &unit);
1016 cell->border[TOP].width =
1017 FIXTOINT(css_unit_len2device_px(
1018 cell->style, unit_len_ctx,
1019 width, unit));
1020
1021 /* Right border */
1022 cell->border[RIGHT].style =
1023 css_computed_border_right_style(cell->style);
1024 css_computed_border_right_color(cell->style,
1025 &cell->border[RIGHT].c);
1026 css_computed_border_right_width(cell->style, &width, &unit);
1027 cell->border[RIGHT].width =
1028 FIXTOINT(css_unit_len2device_px(
1029 cell->style, unit_len_ctx,
1030 width, unit));
1031
1032 /* Bottom border */
1033 cell->border[BOTTOM].style =
1034 css_computed_border_bottom_style(cell->style);
1035 css_computed_border_bottom_color(cell->style,
1036 &cell->border[BOTTOM].c);
1037 css_computed_border_bottom_width(cell->style, &width, &unit);
1038 cell->border[BOTTOM].width =
1039 FIXTOINT(css_unit_len2device_px(
1040 cell->style, unit_len_ctx,
1041 width, unit));
1042 } else {
1043 /* Left border */
1044 table_used_left_border_for_cell(unit_len_ctx, cell);
1045
1046 /* Top border */
1047 table_used_top_border_for_cell(unit_len_ctx, cell);
1048
1049 /* Right border */
1050 table_used_right_border_for_cell(unit_len_ctx, cell);
1051
1052 /* Bottom border */
1053 table_used_bottom_border_for_cell(unit_len_ctx, cell);
1054 }
1055
1056 /* Finally, ensure that any borders configured as
1057 * hidden or none have zero width. (c.f. layout_find_dimensions) */
1058 for (side = 0; side != 4; side++) {
1059 if (cell->border[side].style == CSS_BORDER_STYLE_HIDDEN ||
1060 cell->border[side].style ==
1061 CSS_BORDER_STYLE_NONE)
1062 cell->border[side].width = 0;
1063 }
1064}
Box interface.
box_type
Type of a struct box.
Definition: box.h:55
@ BOX_TABLE_CELL
Definition: box.h:61
@ BOX_TABLE_ROW_GROUP
Definition: box.h:62
@ BOX_TABLE
Definition: box.h:59
@ BOX_TABLE_ROW
Definition: box.h:60
@ TOP
Definition: box.h:98
@ BOTTOM
Definition: box.h:98
@ LEFT
Definition: box.h:98
@ RIGHT
Definition: box.h:98
const char * type
Definition: filetype.cpp:44
#define NSLOG(catname, level, logmsg, args...)
Definition: log.h:116
int width
Definition: gui.c:166
Container for border values during table border calculations.
Definition: table.c:42
enum css_border_color_e color
border-color type
Definition: table.c:44
css_unit unit
border-width units
Definition: table.c:47
enum css_border_style_e style
border-style
Definition: table.c:43
css_fixed width
border-width length
Definition: table.c:46
css_color c
border-color value
Definition: table.c:45
enum css_border_style_e style
border-style
Definition: box.h:105
css_color c
border-color value
Definition: box.h:106
int width
border-width (pixels)
Definition: box.h:107
Node in box tree.
Definition: box.h:177
struct box_border border[4]
Border: TOP, RIGHT, BOTTOM, LEFT.
Definition: box.h:327
struct box * parent
Parent box, or NULL.
Definition: box.h:236
struct column * col
Array of table column data for TABLE only.
Definition: box.h:407
struct box * children
First child box, or NULL.
Definition: box.h:226
struct box * prev
Previous sibling box, or NULL.
Definition: box.h:221
struct box * last
Last child box, or NULL.
Definition: box.h:231
struct box * next
Next sibling box, or NULL.
Definition: box.h:216
unsigned int start_column
Start column for TABLE_CELL only.
Definition: box.h:402
box_type type
Type of box.
Definition: box.h:181
css_computed_style * style
Style for this box.
Definition: box.h:205
unsigned int rows
Number of rows for TABLE only.
Definition: box.h:397
unsigned int columns
Number of columns for TABLE / TABLE_CELL.
Definition: box.h:392
Table column data.
Definition: box.h:114
bool positioned
Whether all of column's cells are css positioned.
Definition: box.h:145
int width
Preferred width of column.
Definition: box.h:130
enum column::@131 type
Type of column.
static void table_used_top_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
Calculate used values of border-top-{style,color,width}.
Definition: table.c:519
static bool table_cell_top_process_row(const css_unit_ctx *unit_len_ctx, struct box *cell, struct box *row, struct border *a, box_type *a_src)
Process a row.
Definition: table.c:211
static bool table_border_is_more_eyecatching(const css_unit_ctx *unit_len_ctx, const struct border *a, box_type a_src, const struct border *b, box_type b_src)
Determine if a border style is more eyecatching than another.
Definition: table.c:62
static void table_used_bottom_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
Calculate used values of border-bottom-{style,color,width}.
Definition: table.c:723
static void table_used_right_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
Calculate used values of border-right-{style,color,width}.
Definition: table.c:619
bool table_calculate_column_types(const css_unit_ctx *unit_len_ctx, struct box *table)
Determine the column width types for a table.
Definition: table.c:813
static void table_cell_top_process_table(const css_unit_ctx *unit_len_ctx, struct box *table, struct border *a, box_type *a_src)
Process a table.
Definition: table.c:173
static void table_used_left_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
Calculate used values of border-left-{style,color,width}.
Definition: table.c:387
static bool table_cell_top_process_group(const css_unit_ctx *unit_len_ctx, struct box *cell, struct box *group, struct border *a, box_type *a_src)
Process a group.
Definition: table.c:322
void table_used_border_for_cell(const css_unit_ctx *unit_len_ctx, struct box *cell)
Calculate used values of border-{trbl}-{style,color,width} for table cells.
Definition: table.c:988
Interface to HTML table processing and layout.
#define talloc_array(ctx, type, count)
Definition: talloc.h:95
Interface to a number of general purpose functionality.
#define fallthrough
switch fall through
Definition: utils.h:115