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