1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_borders.h"
6
7 #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
8 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
9 #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
10 #include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
11 #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table.h"
12 #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_column_visitor.h"
13 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_helpers.h"
14 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_types.h"
15 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_layout_algorithm_utils.h"
16 #include "third_party/blink/renderer/core/style/computed_style.h"
17
18 namespace blink {
19
20 namespace {
21
22 // https://www.w3.org/TR/css-tables-3/#conflict-resolution-for-collapsed-borders
IsSourceMoreSpecificThanEdge(EBorderStyle source_style,LayoutUnit source_width,const NGTableBorders::Edge & edge)23 bool IsSourceMoreSpecificThanEdge(EBorderStyle source_style,
24 LayoutUnit source_width,
25 const NGTableBorders::Edge& edge) {
26 if (edge.edge_side == NGTableBorders::EdgeSide::kDoNotFill)
27 return false;
28
29 if (!edge.style || source_style == EBorderStyle::kHidden)
30 return true;
31
32 EBorderStyle edge_border_style =
33 NGTableBorders::BorderStyle(edge.style.get(), edge.edge_side);
34 if (edge_border_style == EBorderStyle::kHidden)
35 return false;
36
37 LayoutUnit edge_width =
38 NGTableBorders::BorderWidth(edge.style.get(), edge.edge_side);
39 if (source_width < edge_width)
40 return false;
41 if (source_width > edge_width)
42 return true;
43 return source_style > edge_border_style;
44 }
45
46 // Side of the style the collapsed border belongs to.
47 enum class LogicalEdgeSide { kInlineStart, kInlineEnd, kBlockStart, kBlockEnd };
48
LogicalEdgeToPhysical(LogicalEdgeSide logical_side,WritingDirectionMode table_writing_direction)49 NGTableBorders::EdgeSide LogicalEdgeToPhysical(
50 LogicalEdgeSide logical_side,
51 WritingDirectionMode table_writing_direction) {
52 // https://www.w3.org/TR/css-writing-modes-4/#logical-to-physical
53 switch (logical_side) {
54 case LogicalEdgeSide::kInlineStart:
55 switch (table_writing_direction.GetWritingMode()) {
56 case WritingMode::kHorizontalTb:
57 return table_writing_direction.Direction() == TextDirection::kLtr
58 ? NGTableBorders::EdgeSide::kLeft
59 : NGTableBorders::EdgeSide::kRight;
60 case WritingMode::kVerticalLr:
61 case WritingMode::kVerticalRl:
62 case WritingMode::kSidewaysRl:
63 return table_writing_direction.Direction() == TextDirection::kLtr
64 ? NGTableBorders::EdgeSide::kTop
65 : NGTableBorders::EdgeSide::kBottom;
66 case WritingMode::kSidewaysLr:
67 return table_writing_direction.Direction() == TextDirection::kLtr
68 ? NGTableBorders::EdgeSide::kBottom
69 : NGTableBorders::EdgeSide::kTop;
70 }
71 case LogicalEdgeSide::kInlineEnd:
72 switch (table_writing_direction.GetWritingMode()) {
73 case WritingMode::kHorizontalTb:
74 return table_writing_direction.Direction() == TextDirection::kLtr
75 ? NGTableBorders::EdgeSide::kRight
76 : NGTableBorders::EdgeSide::kLeft;
77 case WritingMode::kVerticalLr:
78 case WritingMode::kVerticalRl:
79 case WritingMode::kSidewaysRl:
80 return table_writing_direction.Direction() == TextDirection::kLtr
81 ? NGTableBorders::EdgeSide::kBottom
82 : NGTableBorders::EdgeSide::kTop;
83 case WritingMode::kSidewaysLr:
84 return table_writing_direction.Direction() == TextDirection::kLtr
85 ? NGTableBorders::EdgeSide::kTop
86 : NGTableBorders::EdgeSide::kBottom;
87 }
88 case LogicalEdgeSide::kBlockStart:
89 switch (table_writing_direction.GetWritingMode()) {
90 case WritingMode::kHorizontalTb:
91 return NGTableBorders::EdgeSide::kTop;
92 case WritingMode::kVerticalLr:
93 case WritingMode::kSidewaysLr:
94 return NGTableBorders::EdgeSide::kLeft;
95 case WritingMode::kVerticalRl:
96 case WritingMode::kSidewaysRl:
97 return NGTableBorders::EdgeSide::kRight;
98 }
99 case LogicalEdgeSide::kBlockEnd:
100 switch (table_writing_direction.GetWritingMode()) {
101 case WritingMode::kHorizontalTb:
102 return NGTableBorders::EdgeSide::kBottom;
103 case WritingMode::kVerticalLr:
104 case WritingMode::kSidewaysLr:
105 return NGTableBorders::EdgeSide::kRight;
106 case WritingMode::kVerticalRl:
107 case WritingMode::kSidewaysRl:
108 return NGTableBorders::EdgeSide::kLeft;
109 }
110 }
111 }
112
113 class ColBordersMarker {
114 STACK_ALLOCATED();
115
116 public:
VisitCol(const NGLayoutInputNode & column,wtf_size_t start_column_index,wtf_size_t span)117 void VisitCol(const NGLayoutInputNode& column,
118 wtf_size_t start_column_index,
119 wtf_size_t span) {
120 for (wtf_size_t i = 0; i < span; ++i) {
121 wtf_size_t current_column_index = start_column_index + i;
122 borders.MergeBorders(0, current_column_index, table_row_count, 1,
123 column.Style(), NGTableBorders::EdgeSource::kColumn,
124 box_order, table_writing_direction);
125 }
126 }
EnterColgroup(const NGLayoutInputNode & colgroup,wtf_size_t start_column_index)127 void EnterColgroup(const NGLayoutInputNode& colgroup,
128 wtf_size_t start_column_index) {}
LeaveColgroup(const NGLayoutInputNode & colgroup,wtf_size_t start_column_index,wtf_size_t span,bool has_children)129 void LeaveColgroup(const NGLayoutInputNode& colgroup,
130 wtf_size_t start_column_index,
131 wtf_size_t span,
132 bool has_children) {}
ColBordersMarker(wtf_size_t table_row_count,wtf_size_t box_order,WritingDirectionMode table_writing_direction,NGTableBorders & borders)133 ColBordersMarker(wtf_size_t table_row_count,
134 wtf_size_t box_order,
135 WritingDirectionMode table_writing_direction,
136 NGTableBorders& borders)
137 : table_row_count(table_row_count),
138 box_order(box_order),
139 table_writing_direction(table_writing_direction),
140 borders(borders) {}
141 const wtf_size_t table_row_count;
142 const wtf_size_t box_order;
143 const WritingDirectionMode table_writing_direction;
144 NGTableBorders& borders;
145 };
146
147 class ColgroupBordersMarker {
148 STACK_ALLOCATED();
149
150 public:
VisitCol(const NGLayoutInputNode & column,wtf_size_t start_column_index,wtf_size_t span)151 void VisitCol(const NGLayoutInputNode& column,
152 wtf_size_t start_column_index,
153 wtf_size_t span) {}
EnterColgroup(const NGLayoutInputNode & colgroup,wtf_size_t start_column_index)154 void EnterColgroup(const NGLayoutInputNode& colgroup,
155 wtf_size_t start_column_index) {}
LeaveColgroup(const NGLayoutInputNode & colgroup,wtf_size_t start_column_index,wtf_size_t span,bool has_children)156 void LeaveColgroup(const NGLayoutInputNode& colgroup,
157 wtf_size_t start_column_index,
158 wtf_size_t span,
159 bool has_children) {
160 borders.MergeBorders(0, start_column_index, table_row_count, span,
161 colgroup.Style(), NGTableBorders::EdgeSource::kColumn,
162 box_order, table_writing_direction);
163 }
ColgroupBordersMarker(wtf_size_t table_row_count,wtf_size_t box_order,WritingDirectionMode table_writing_direction,NGTableBorders & borders)164 ColgroupBordersMarker(wtf_size_t table_row_count,
165 wtf_size_t box_order,
166 WritingDirectionMode table_writing_direction,
167 NGTableBorders& borders)
168 : table_row_count(table_row_count),
169 box_order(box_order),
170 table_writing_direction(table_writing_direction),
171 borders(borders) {}
172 const wtf_size_t table_row_count;
173 const wtf_size_t box_order;
174 const WritingDirectionMode table_writing_direction;
175 NGTableBorders& borders;
176 };
177
178 } // namespace
179
ComputeTableBorders(const NGBlockNode & table)180 scoped_refptr<NGTableBorders> NGTableBorders::ComputeTableBorders(
181 const NGBlockNode& table) {
182 const ComputedStyle& table_style = table.Style();
183 NGBoxStrut intrinsic_borders(LayoutUnit(table_style.BorderStartWidth()),
184 LayoutUnit(table_style.BorderEndWidth()),
185 LayoutUnit(table_style.BorderBeforeWidth()),
186 LayoutUnit(table_style.BorderAfterWidth()));
187 scoped_refptr<NGTableBorders> table_borders =
188 base::MakeRefCounted<NGTableBorders>(table_style, intrinsic_borders);
189
190 if (table_style.BorderCollapse() != EBorderCollapse::kCollapse)
191 return table_borders;
192
193 NGTableGroupedChildren grouped_children(table);
194 bool hide_empty_cells = table_style.EmptyCells() == EEmptyCells::kHide;
195 WritingDirectionMode table_writing_direction =
196 table.Style().GetWritingDirection();
197 wtf_size_t box_order = 0;
198 wtf_size_t table_column_count = 0;
199 wtf_size_t table_row_index = 0;
200
201 // Mark cell borders.
202 bool found_multispan_cells = false;
203 for (const NGBlockNode section : grouped_children) {
204 wtf_size_t section_start_row = table_row_index;
205 NGColspanCellTabulator tabulator;
206 for (NGBlockNode row = To<NGBlockNode>(section.FirstChild()); row;
207 row = To<NGBlockNode>(row.NextSibling())) {
208 tabulator.StartRow();
209 for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell;
210 cell = To<NGBlockNode>(cell.NextSibling())) {
211 tabulator.FindNextFreeColumn();
212 // https://stackoverflow.com/questions/18758373/why-do-the-css-property-border-collapse-and-empty-cells-conflict
213 if (hide_empty_cells && !To<NGBlockNode>(cell).FirstChild()) {
214 tabulator.ProcessCell(cell);
215 continue;
216 }
217 wtf_size_t cell_colspan = cell.TableCellColspan();
218 found_multispan_cells |=
219 cell.TableCellRowspan() > 1 || cell_colspan > 1;
220 // Rowspan has to be limited by section size. Since we do not know
221 // section size, we have to rerun cell distribution with limited
222 // rowspans.
223 table_column_count = std::max(
224 table_column_count, NGTableAlgorithmHelpers::ComputeMaxColumn(
225 tabulator.CurrentColumn(), cell_colspan,
226 table.Style().IsFixedTableLayout()));
227 if (!found_multispan_cells) {
228 table_borders->MergeBorders(
229 table_row_index, tabulator.CurrentColumn(),
230 cell.TableCellRowspan(), cell_colspan, cell.Style(),
231 NGTableBorders::EdgeSource::kCell, ++box_order,
232 table_writing_direction);
233 }
234 tabulator.ProcessCell(cell);
235 }
236 tabulator.EndRow();
237 ++table_row_index;
238 }
239 table_borders->AddSection(section_start_row,
240 table_row_index - section_start_row);
241 }
242 table_borders->SetLastColumnIndex(table_column_count);
243
244 wtf_size_t table_row_count = table_row_index;
245 table_row_index = 0;
246
247 // Mark cell borders again with limited rowspan.
248 // If any cells have rowspan, need to redistribute cell borders.
249 if (found_multispan_cells) {
250 wtf_size_t section_index = 0;
251 for (NGBlockNode section : grouped_children) {
252 NGColspanCellTabulator tabulator;
253 for (NGBlockNode row = To<NGBlockNode>(section.FirstChild()); row;
254 row = To<NGBlockNode>(row.NextSibling())) {
255 tabulator.StartRow();
256 for (NGBlockNode cell = To<NGBlockNode>(row.FirstChild()); cell;
257 cell = To<NGBlockNode>(cell.NextSibling())) {
258 tabulator.FindNextFreeColumn();
259 if (hide_empty_cells && !To<NGBlockNode>(cell).FirstChild()) {
260 tabulator.ProcessCell(cell);
261 continue;
262 }
263 table_borders->MergeBorders(
264 table_row_index, tabulator.CurrentColumn(),
265 cell.TableCellRowspan(), cell.TableCellColspan(), cell.Style(),
266 NGTableBorders::EdgeSource::kCell, ++box_order,
267 table_writing_direction, section_index);
268 tabulator.ProcessCell(cell);
269 }
270 tabulator.EndRow();
271 ++table_row_index;
272 }
273 ++section_index;
274 }
275 }
276
277 // Mark row borders.
278 table_row_index = 0;
279 for (NGBlockNode section : grouped_children) {
280 for (NGBlockNode row = To<NGBlockNode>(section.FirstChild()); row;
281 row = To<NGBlockNode>(row.NextSibling())) {
282 table_borders->MergeBorders(table_row_index, 0, 1, table_column_count,
283 row.Style(), NGTableBorders::EdgeSource::kRow,
284 ++box_order, table_writing_direction);
285 ++table_row_index;
286 }
287 }
288
289 // Mark section borders.
290 // It is tempting to traverse sections at the same time as rows,
291 // but it would cause precedence errors.
292 wtf_size_t section_index = 0;
293 for (NGBlockNode section : grouped_children) {
294 NGTableBorders::Section section_info =
295 table_borders->GetSection(section_index);
296 table_borders->MergeBorders(
297 section_info.start_row, 0, section_info.row_count, table_column_count,
298 section.Style(), NGTableBorders::EdgeSource::kSection, ++box_order,
299 table_writing_direction);
300 ++section_index;
301 }
302
303 // Mark column borders.
304 // COL borders have precedence over COLGROUP borders.
305 // We have to traverse COL first, then COLGROUP.
306 ColBordersMarker col_borders_marker(table_row_count, ++box_order,
307 table_writing_direction,
308 *table_borders.get());
309 VisitLayoutNGTableColumn(
310 const_cast<Vector<NGBlockNode>&>(grouped_children.columns),
311 table_column_count, &col_borders_marker);
312 ColgroupBordersMarker colgroup_borders_marker(table_row_count, ++box_order,
313 table_writing_direction,
314 *table_borders.get());
315 VisitLayoutNGTableColumn(
316 const_cast<Vector<NGBlockNode>&>(grouped_children.columns),
317 table_column_count, &colgroup_borders_marker);
318
319 // Mark table borders.
320 table_borders->MergeBorders(0, 0, table_row_count, table_column_count,
321 table_style, NGTableBorders::EdgeSource::kTable,
322 ++box_order, table_writing_direction);
323
324 table_borders->ComputeCollapsedTableBorderPadding(table_row_count,
325 table_column_count);
326 return table_borders;
327 }
328
NGTableBorders(const ComputedStyle & table_style,const NGBoxStrut & table_border)329 NGTableBorders::NGTableBorders(const ComputedStyle& table_style,
330 const NGBoxStrut& table_border)
331 : is_collapsed_(table_style.BorderCollapse() ==
332 EBorderCollapse::kCollapse) {
333 if (!is_collapsed_) {
334 cached_table_border_ = table_border;
335 }
336 }
337
338 #if DCHECK_IS_ON()
DumpEdges()339 String NGTableBorders::DumpEdges() {
340 if (edges_per_row_ == 0)
341 return "No edges";
342
343 StringBuilder edge_string;
344 wtf_size_t row_count = edges_.size() / edges_per_row_;
345 for (wtf_size_t row = 0; row < row_count; ++row) {
346 for (wtf_size_t i = 0; i < edges_per_row_; ++i) {
347 const auto& edge = edges_[edges_per_row_ * row + i];
348 if (edge.style) {
349 switch (edge.edge_side) {
350 case EdgeSide::kTop:
351 edge_string.Append('-');
352 break;
353 case EdgeSide::kBottom:
354 edge_string.Append('_');
355 break;
356 case EdgeSide::kLeft:
357 edge_string.Append('[');
358 break;
359 case EdgeSide::kRight:
360 edge_string.Append(']');
361 break;
362 case EdgeSide::kDoNotFill:
363 edge_string.Append('?');
364 break;
365 }
366 } else { // no style.
367 if (edge.edge_side == EdgeSide::kDoNotFill)
368 edge_string.Append('X');
369 else
370 edge_string.Append('.');
371 }
372 if (i & 1) // i is odd.
373 edge_string.Append(' ');
374 }
375 edge_string.Append('\n');
376 }
377 return edge_string.ToString();
378 }
379
ShowEdges()380 void NGTableBorders::ShowEdges() {
381 LOG(INFO) << "\n" << DumpEdges().Utf8();
382 }
383
384 #endif
385
GetCellBorders(wtf_size_t row,wtf_size_t column,wtf_size_t rowspan,wtf_size_t colspan) const386 NGBoxStrut NGTableBorders::GetCellBorders(wtf_size_t row,
387 wtf_size_t column,
388 wtf_size_t rowspan,
389 wtf_size_t colspan) const {
390 NGBoxStrut border_strut;
391 if (edges_per_row_ == 0)
392 return border_strut;
393 DCHECK_EQ(edges_.size() % edges_per_row_, 0u);
394 if (column * 2 >= edges_per_row_ || row >= edges_.size() / edges_per_row_)
395 return border_strut;
396
397 // Compute inline border widths.
398 wtf_size_t first_inline_start_edge = row * edges_per_row_ + column * 2;
399 wtf_size_t first_inline_end_edge = first_inline_start_edge + colspan * 2;
400 for (wtf_size_t i = 0; i < rowspan; ++i) {
401 wtf_size_t start_edge_index = first_inline_start_edge + i * edges_per_row_;
402 border_strut.inline_start =
403 std::max(border_strut.inline_start, CanPaint(start_edge_index)
404 ? BorderWidth(start_edge_index)
405 : LayoutUnit());
406 if (start_edge_index >= edges_.size())
407 break;
408 wtf_size_t end_edge_index = first_inline_end_edge + i * edges_per_row_;
409 border_strut.inline_end = std::max(
410 border_strut.inline_end,
411 CanPaint(end_edge_index) ? BorderWidth(end_edge_index) : LayoutUnit());
412 }
413 // Compute block border widths.
414 wtf_size_t start_edge_column_index = column * 2 + 1;
415 for (wtf_size_t i = 0; i < colspan; ++i) {
416 wtf_size_t current_column_index = start_edge_column_index + i * 2;
417 if (current_column_index >= edges_per_row_)
418 break;
419 wtf_size_t start_edge_index = row * edges_per_row_ + current_column_index;
420 border_strut.block_start =
421 std::max(border_strut.block_start, CanPaint(start_edge_index)
422 ? BorderWidth(start_edge_index)
423 : LayoutUnit());
424 wtf_size_t end_edge_index = start_edge_index + rowspan * edges_per_row_;
425 border_strut.block_end = std::max(
426 border_strut.block_end,
427 CanPaint(end_edge_index) ? BorderWidth(end_edge_index) : LayoutUnit());
428 }
429 DCHECK(is_collapsed_);
430 // If borders are not divisible by 2, two half borders will not add up
431 // to original border size (off by 1/64px). This is ok, because
432 // pixel snapping will round to physical pixels.
433 border_strut.block_start /= 2;
434 border_strut.block_end /= 2;
435 border_strut.inline_start /= 2;
436 border_strut.inline_end /= 2;
437 return border_strut;
438 }
439
ComputeCollapsedTableBorderPadding(wtf_size_t table_row_count,wtf_size_t table_column_count)440 void NGTableBorders::ComputeCollapsedTableBorderPadding(
441 wtf_size_t table_row_count,
442 wtf_size_t table_column_count) {
443 DCHECK(is_collapsed_);
444 // https://www.w3.org/TR/CSS2/tables.html#collapsing-borders
445 // block[start|end] borders are computed by traversing all the edges.
446 // inline[start|end] borders are computed by looking at first/last edge.
447 if (edges_per_row_ == 0) {
448 cached_table_border_ = NGBoxStrut();
449 return;
450 }
451 DCHECK_GE((table_column_count + 1) * 2, edges_per_row_);
452 // We still need visual border rect.
453 NGBoxStrut borders =
454 GetCellBorders(0, 0, table_row_count, table_column_count);
455 collapsed_visual_inline_start_ = borders.inline_start;
456 collapsed_visual_inline_end_ = borders.inline_end;
457 wtf_size_t inline_start_edge = 0;
458 wtf_size_t inline_end_edge = 2 * table_column_count;
459 borders.inline_start = CanPaint(inline_start_edge)
460 ? BorderWidth(inline_start_edge) / 2
461 : LayoutUnit();
462 borders.inline_end = CanPaint(inline_end_edge)
463 ? BorderWidth(inline_end_edge) / 2
464 : LayoutUnit();
465 cached_table_border_ = borders;
466 }
467
CellBorder(const NGBlockNode & cell,wtf_size_t row,wtf_size_t column,wtf_size_t section,WritingDirectionMode table_writing_direction) const468 NGBoxStrut NGTableBorders::CellBorder(
469 const NGBlockNode& cell,
470 wtf_size_t row,
471 wtf_size_t column,
472 wtf_size_t section,
473 WritingDirectionMode table_writing_direction) const {
474 if (is_collapsed_) {
475 return GetCellBorders(row, column,
476 ClampRowspan(section, row, cell.TableCellRowspan()),
477 ClampColspan(column, cell.TableCellColspan()));
478 }
479 return ComputeBorders(
480 NGConstraintSpaceBuilder(table_writing_direction.GetWritingMode(),
481 table_writing_direction, /* is_new_fc */ false)
482 .ToConstraintSpace(),
483 cell);
484 }
485
486 // As we are determining the intrinsic size of the table at this stage,
487 // %-padding resolves against an indefinite size.
CellPaddingForMeasure(const ComputedStyle & cell_style,WritingDirectionMode table_writing_direction) const488 NGBoxStrut NGTableBorders::CellPaddingForMeasure(
489 const ComputedStyle& cell_style,
490 WritingDirectionMode table_writing_direction) const {
491 if (!cell_style.MayHavePadding())
492 return NGBoxStrut();
493 return ComputePadding(
494 NGConstraintSpaceBuilder(table_writing_direction.GetWritingMode(),
495 table_writing_direction,
496 /* is_new_fc */ false)
497 .ToConstraintSpace(),
498 cell_style);
499 }
500
MergeBorders(wtf_size_t cell_start_row,wtf_size_t cell_start_column,wtf_size_t rowspan,wtf_size_t colspan,const ComputedStyle & source_style,EdgeSource source,const wtf_size_t box_order,WritingDirectionMode table_writing_direction,wtf_size_t section_index)501 void NGTableBorders::MergeBorders(wtf_size_t cell_start_row,
502 wtf_size_t cell_start_column,
503 wtf_size_t rowspan,
504 wtf_size_t colspan,
505 const ComputedStyle& source_style,
506 EdgeSource source,
507 const wtf_size_t box_order,
508 WritingDirectionMode table_writing_direction,
509 wtf_size_t section_index) {
510 DCHECK(is_collapsed_);
511 // Can be 0 in empty table parts.
512 if (rowspan == 0 || colspan == 0)
513 return;
514
515 wtf_size_t clamped_colspan = ClampColspan(cell_start_column, colspan);
516 wtf_size_t clamped_rowspan =
517 source == EdgeSource::kCell
518 ? ClampRowspan(section_index, cell_start_row, rowspan)
519 : rowspan;
520 bool mark_inner_borders = source == EdgeSource::kCell &&
521 (clamped_rowspan > 1 || clamped_colspan > 1);
522
523 if (mark_inner_borders) {
524 EnsureCellColumnFits(cell_start_column + clamped_colspan - 1);
525 EnsureCellRowFits(cell_start_row + clamped_rowspan - 1);
526 } else {
527 PhysicalToLogical<EBorderStyle> border_style(
528 table_writing_direction, source_style.BorderTopStyle(),
529 source_style.BorderRightStyle(), source_style.BorderBottomStyle(),
530 source_style.BorderLeftStyle());
531 if (border_style.InlineStart() == EBorderStyle::kNone &&
532 border_style.InlineEnd() == EBorderStyle::kNone &&
533 border_style.BlockStart() == EBorderStyle::kNone &&
534 border_style.BlockEnd() == EBorderStyle::kNone) {
535 return;
536 }
537 // Only need to ensure edges that will be assigned exist.
538 if (border_style.InlineEnd() == EBorderStyle::kNone &&
539 border_style.BlockStart() == EBorderStyle::kNone &&
540 border_style.BlockEnd() == EBorderStyle::kNone) {
541 EnsureCellColumnFits(cell_start_column);
542 } else {
543 EnsureCellColumnFits(cell_start_column + clamped_colspan - 1);
544 }
545 if (border_style.InlineStart() == EBorderStyle::kNone &&
546 border_style.InlineEnd() == EBorderStyle::kNone &&
547 border_style.BlockEnd() == EBorderStyle::kNone) {
548 EnsureCellRowFits(cell_start_row);
549 } else {
550 EnsureCellRowFits(cell_start_row + clamped_rowspan - 1);
551 }
552 }
553 MergeRowAxisBorder(cell_start_row, cell_start_column, clamped_colspan,
554 source_style, box_order,
555 LogicalEdgeToPhysical(LogicalEdgeSide::kBlockStart,
556 table_writing_direction));
557 MergeRowAxisBorder(cell_start_row + clamped_rowspan, cell_start_column,
558 clamped_colspan, source_style, box_order,
559 LogicalEdgeToPhysical(LogicalEdgeSide::kBlockEnd,
560 table_writing_direction));
561 MergeColumnAxisBorder(cell_start_row, cell_start_column, clamped_rowspan,
562 source_style, box_order,
563 LogicalEdgeToPhysical(LogicalEdgeSide::kInlineStart,
564 table_writing_direction));
565 MergeColumnAxisBorder(cell_start_row, cell_start_column + clamped_colspan,
566 clamped_rowspan, source_style, box_order,
567 LogicalEdgeToPhysical(LogicalEdgeSide::kInlineEnd,
568 table_writing_direction));
569 if (mark_inner_borders) {
570 MarkInnerBordersAsDoNotFill(cell_start_row, cell_start_column,
571 clamped_rowspan, clamped_colspan);
572 }
573 }
574
MergeRowAxisBorder(wtf_size_t start_row,wtf_size_t start_column,wtf_size_t colspan,const ComputedStyle & source_style,const wtf_size_t box_order,EdgeSide physical_side)575 void NGTableBorders::MergeRowAxisBorder(wtf_size_t start_row,
576 wtf_size_t start_column,
577 wtf_size_t colspan,
578 const ComputedStyle& source_style,
579 const wtf_size_t box_order,
580 EdgeSide physical_side) {
581 EBorderStyle source_border_style = BorderStyle(&source_style, physical_side);
582 if (source_border_style == EBorderStyle::kNone)
583 return;
584 LayoutUnit source_border_width = BorderWidth(&source_style, physical_side);
585 wtf_size_t start_edge = edges_per_row_ * start_row + start_column * 2 + 1;
586 wtf_size_t end_edge = start_edge + colspan * 2;
587 for (wtf_size_t current_edge = start_edge; current_edge < end_edge;
588 current_edge += 2) {
589 // https://www.w3.org/TR/css-tables-3/#border-specificity
590 if (IsSourceMoreSpecificThanEdge(source_border_style, source_border_width,
591 edges_[current_edge])) {
592 edges_[current_edge].style = &source_style;
593 edges_[current_edge].edge_side = physical_side;
594 edges_[current_edge].box_order = box_order;
595 }
596 }
597 }
598
MergeColumnAxisBorder(wtf_size_t start_row,wtf_size_t start_column,wtf_size_t rowspan,const ComputedStyle & source_style,const wtf_size_t box_order,EdgeSide physical_side)599 void NGTableBorders::MergeColumnAxisBorder(wtf_size_t start_row,
600 wtf_size_t start_column,
601 wtf_size_t rowspan,
602 const ComputedStyle& source_style,
603 const wtf_size_t box_order,
604 EdgeSide physical_side) {
605 EBorderStyle source_border_style = BorderStyle(&source_style, physical_side);
606 if (source_border_style == EBorderStyle::kNone)
607 return;
608 LayoutUnit source_border_width = BorderWidth(&source_style, physical_side);
609 wtf_size_t start_edge = edges_per_row_ * start_row + start_column * 2;
610 wtf_size_t end_edge = start_edge + (rowspan * edges_per_row_);
611 for (wtf_size_t current_edge = start_edge; current_edge < end_edge;
612 current_edge += edges_per_row_) {
613 // https://www.w3.org/TR/css-tables-3/#border-specificity
614 if (IsSourceMoreSpecificThanEdge(source_border_style, source_border_width,
615 edges_[current_edge])) {
616 edges_[current_edge].style = &source_style;
617 edges_[current_edge].edge_side = physical_side;
618 edges_[current_edge].box_order = box_order;
619 }
620 }
621 }
622
623 // Rowspanned/colspanned cells need to mark inner edges as do-not-fill to
624 // prevent tables parts from drawing into them.
MarkInnerBordersAsDoNotFill(wtf_size_t start_row,wtf_size_t start_column,wtf_size_t rowspan,wtf_size_t colspan)625 void NGTableBorders::MarkInnerBordersAsDoNotFill(wtf_size_t start_row,
626 wtf_size_t start_column,
627 wtf_size_t rowspan,
628 wtf_size_t colspan) {
629 // Mark block axis edges.
630 wtf_size_t start_edge = (start_column * 2) + 2;
631 wtf_size_t end_edge = start_edge + (colspan - 1) * 2;
632 for (wtf_size_t row = start_row;
633 row < start_row + rowspan && start_edge != end_edge; ++row) {
634 wtf_size_t row_offset = row * edges_per_row_;
635 for (wtf_size_t edge = row_offset + start_edge;
636 edge < row_offset + end_edge; edge += 2) {
637 // DCHECK(!edges_[edge].style) is true in most tables. But,
638 // when two cells overlap each other, (really an error)
639 // style might already be assigned.
640 if (!edges_[edge].style)
641 edges_[edge].edge_side = EdgeSide::kDoNotFill;
642 }
643 }
644 // Mark inline axis edges.
645 start_edge = start_column * 2 + 1;
646 end_edge = start_edge + colspan * 2;
647 for (wtf_size_t row = start_row + 1; row < start_row + rowspan; ++row) {
648 wtf_size_t row_offset = row * edges_per_row_;
649 for (wtf_size_t edge = row_offset + start_edge;
650 edge < row_offset + end_edge; edge += 2) {
651 if (!edges_[edge].style)
652 edges_[edge].edge_side = EdgeSide::kDoNotFill;
653 }
654 }
655 }
656
657 // Inline edges are edges between columns.
EnsureCellColumnFits(wtf_size_t cell_column)658 void NGTableBorders::EnsureCellColumnFits(wtf_size_t cell_column) {
659 wtf_size_t desired_edges_per_row = (cell_column + 2) * 2;
660 if (desired_edges_per_row <= edges_per_row_)
661 return;
662
663 // When number of columns changes, all rows have to be resized.
664 // Edges must be copied to new positions. This can be expensive.
665 // Most tables do not change number of columns after the 1st row.
666 wtf_size_t row_count =
667 edges_per_row_ == 0 ? 1 : edges_.size() / edges_per_row_;
668 edges_.resize(row_count * desired_edges_per_row);
669 for (wtf_size_t row_index = row_count - 1; row_index > 0; --row_index) {
670 wtf_size_t new_edge = desired_edges_per_row - 1;
671 bool done = false;
672 // while loop is necessary to count down with unsigned.
673 do {
674 wtf_size_t new_edge_index = row_index * desired_edges_per_row + new_edge;
675 if (new_edge < edges_per_row_) {
676 wtf_size_t old_edge_index = row_index * edges_per_row_ + new_edge;
677 DCHECK_LT(row_index * edges_per_row_ + new_edge, edges_.size());
678 edges_[new_edge_index] = edges_[old_edge_index];
679 } else {
680 edges_[new_edge_index].style = nullptr;
681 edges_[new_edge_index].edge_side = EdgeSide::kTop;
682 }
683 done = new_edge-- == 0;
684 } while (!done);
685 }
686 // Previous loop does not clear out new cells in the first row.
687 for (wtf_size_t edge_index = edges_per_row_;
688 edge_index < desired_edges_per_row; ++edge_index) {
689 edges_[edge_index].style = nullptr;
690 edges_[edge_index].edge_side = EdgeSide::kTop;
691 }
692 edges_per_row_ = desired_edges_per_row;
693 }
694
695 // Block edges are edges between rows.
EnsureCellRowFits(wtf_size_t cell_row)696 void NGTableBorders::EnsureCellRowFits(wtf_size_t cell_row) {
697 DCHECK_NE(edges_per_row_, 0u);
698 wtf_size_t current_block_edges = edges_.size() / edges_per_row_;
699 wtf_size_t desired_block_edges = cell_row + 2;
700 if (desired_block_edges <= current_block_edges)
701 return;
702 edges_.resize(desired_block_edges * edges_per_row_);
703 }
704
705 } // namespace blink
706