1 // Copyright 2017 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/paint/collapsed_border_painter.h"
6
7 #include "third_party/blink/renderer/core/paint/block_painter.h"
8 #include "third_party/blink/renderer/core/paint/object_painter.h"
9 #include "third_party/blink/renderer/core/paint/paint_info.h"
10 #include "third_party/blink/renderer/core/paint/scoped_paint_state.h"
11 #include "third_party/blink/renderer/core/paint/table_cell_painter.h"
12
13 namespace blink {
14
SetupBorders()15 void CollapsedBorderPainter::SetupBorders() {
16 const auto* values = cell_.GetCollapsedBorderValues();
17 DCHECK(values);
18 if (values->StartBorder().IsVisible()) {
19 start_.value = &values->StartBorder();
20 start_.inner_width = cell_.CollapsedInnerBorderStart();
21 start_.outer_width = cell_.CollapsedOuterBorderStart();
22 } else {
23 start_.value = nullptr;
24 }
25
26 if (values->EndBorder().IsVisible()) {
27 end_.value = &values->EndBorder();
28 end_.inner_width = cell_.CollapsedInnerBorderEnd();
29 end_.outer_width = cell_.CollapsedOuterBorderEnd();
30 } else {
31 end_.value = nullptr;
32 }
33
34 if (values->BeforeBorder().IsVisible()) {
35 before_.value = &values->BeforeBorder();
36 before_.inner_width = cell_.CollapsedInnerBorderBefore();
37 before_.outer_width = cell_.CollapsedOuterBorderBefore();
38 } else {
39 before_.value = nullptr;
40 }
41
42 if (values->AfterBorder().IsVisible()) {
43 after_.value = &values->AfterBorder();
44 after_.inner_width = cell_.CollapsedInnerBorderAfter();
45 after_.outer_width = cell_.CollapsedOuterBorderAfter();
46 } else {
47 after_.value = nullptr;
48 }
49
50 // At first, let all borders paint the joints. This is to keep the current
51 // behavior for web tests e.g. css2.1/t170602-bdr-conflict-w-01-d.html.
52 // TODO(crbug.com/672216): Determine the best way to deal with this.
53 if (start_.value && before_.value) {
54 start_.begin_outset = before_.outer_width;
55 before_.begin_outset = start_.outer_width;
56 }
57 if (end_.value && before_.value) {
58 end_.begin_outset = before_.outer_width;
59 before_.end_outset = end_.outer_width;
60 }
61 if (start_.value && after_.value) {
62 start_.end_outset = after_.outer_width;
63 after_.begin_outset = start_.outer_width;
64 }
65 if (after_.value && end_.value) {
66 end_.end_outset = after_.outer_width;
67 after_.end_outset = end_.outer_width;
68 }
69
70 // TODO(crbug.com/727173): We have a lot of bugs about mixed row direction.
71 // Just bail out not to optimize duplicated borders.
72 if (!cell_.Row()->HasSameDirectionAs(&table_) ||
73 !cell_.Section()->HasSameDirectionAs(&table_))
74 return;
75
76 // Skip painting the start border if it will be painted by the preceding cell
77 // as its end border.
78 if (start_.value) {
79 const auto* cell_preceding = table_.CellPreceding(cell_);
80 if (cell_.StartsAtSameRow(cell_preceding) &&
81 cell_preceding->ResolvedRowSpan() >= cell_.ResolvedRowSpan() &&
82 // |cell_preceding| didn't paint the border if it is invisible.
83 cell_preceding->StyleRef().Visibility() == EVisibility::kVisible) {
84 start_.value = nullptr;
85 // Otherwise we'll still paint the shared border twice which may cause
86 // incorrect border conflict resolution for row/col spanning cells.
87 // TODO(crbug.com/2902 etc.): Paint collapsed borders by grid cells.
88 }
89 }
90
91 // Skip painting the before border if it will be painted by the above cell
92 // as its after border. If we break page before the row with non-zero strut
93 // (which means a gap between this row and the row above), or if we are
94 // painting the top row of a footer that repeats on each page we need to paint
95 // the before border separately.
96 // TODO(crbug.com/751177) : This will double-paint the top border of the
97 // footer on the last page.
98 if (before_.value && !cell_.Row()->PaginationStrut()) {
99 const auto* cell_above = table_.CellAbove(cell_);
100 if (cell_.StartsAtSameColumn(cell_above) &&
101 cell_above->ColSpan() >= cell_.ColSpan() &&
102 // |cell_above| didn't paint the border if it is invisible.
103 cell_above->StyleRef().Visibility() == EVisibility::kVisible &&
104 cell_above->Row()->HasSameDirectionAs(&table_)) {
105 bool cell_is_top_of_repeating_footer =
106 cell_.Section()->IsRepeatingFooterGroup() &&
107 (!cell_above || cell_above->Section() != cell_.Section());
108 if (!cell_is_top_of_repeating_footer)
109 before_.value = nullptr;
110 // Otherwise we'll still paint the shared border twice which may cause
111 // incorrect border conflict resolution for row/col spanning cells.
112 // TODO(crbug.com/2902 etc.): Paint collapsed borders by grid cells.
113 }
114 }
115 }
116
GetCollapsedBorderValues(const LayoutTableCell * cell)117 static const CollapsedBorderValues* GetCollapsedBorderValues(
118 const LayoutTableCell* cell) {
119 return cell ? cell->GetCollapsedBorderValues() : nullptr;
120 }
121
AdjustJoints()122 void CollapsedBorderPainter::AdjustJoints() {
123 // TODO(crbug.com/727173): We have a lot of bugs about mixed row direction.
124 // Just bail out not to adjust the joints.
125 if (!cell_.Row()->HasSameDirectionAs(&table_) ||
126 !cell_.Section()->HasSameDirectionAs(&table_))
127 return;
128
129 // If we break page before the row with non-zero strut, we need to paint the
130 // before border as if there is no cell above.
131 const auto* cell_above =
132 cell_.Row()->PaginationStrut() ? nullptr : table_.CellAbove(cell_);
133 if (cell_above && (!cell_above->Row()->HasSameDirectionAs(&table_) ||
134 !cell_above->Section()->HasSameDirectionAs(&table_)))
135 cell_above = nullptr;
136
137 const auto* cell_below = table_.CellBelow(cell_);
138 if (cell_below && (!cell_below->Row()->HasSameDirectionAs(&table_) ||
139 !cell_below->Section()->HasSameDirectionAs(&table_)))
140 cell_above = nullptr;
141
142 const auto* cell_preceding = table_.CellPreceding(cell_);
143 const auto* cell_following = table_.CellFollowing(cell_);
144
145 const auto* borders_preceding = GetCollapsedBorderValues(cell_preceding);
146 const auto* borders_following = GetCollapsedBorderValues(cell_following);
147 const auto* borders_above = GetCollapsedBorderValues(cell_above);
148 const auto* borders_below = GetCollapsedBorderValues(cell_below);
149
150 // These variables indicate whether |cell_| forms joints at the corners
151 // with the borders of the adjacent cells. For example,
152 // |before_start_adjoins_preceding| indicates that |cell_| shares the
153 // before-start (logical top-left) corner with |cell_preceding_|.
154 bool before_start_adjoins_preceding =
155 borders_preceding && cell_.StartsAtSameRow(cell_preceding);
156 bool before_start_adjoins_above =
157 borders_above && cell_.StartsAtSameColumn(cell_above);
158 bool before_end_adjoins_above =
159 borders_above && cell_.EndsAtSameColumn(cell_above);
160 bool before_end_adjoins_following =
161 borders_following && cell_.StartsAtSameRow(cell_following);
162 bool after_end_adjoins_following =
163 borders_following && cell_.EndsAtSameRow(cell_following);
164 bool after_end_adjoins_below =
165 borders_below && cell_.EndsAtSameColumn(cell_below);
166 bool after_start_adjoins_below =
167 borders_below && cell_.StartsAtSameColumn(cell_below);
168 bool after_start_adjoins_preceding =
169 borders_preceding && cell_.EndsAtSameRow(cell_preceding);
170
171 if (start_.value) {
172 if (start_.value->CoversJoint(
173 before_.value,
174 before_start_adjoins_preceding ? &borders_preceding->BeforeBorder()
175 : nullptr,
176 before_start_adjoins_above ? &borders_above->StartBorder()
177 : nullptr)) {
178 if (before_start_adjoins_preceding) {
179 start_.begin_outset = std::max<int>(
180 start_.begin_outset, cell_preceding->CollapsedOuterBorderBefore());
181 }
182 } else {
183 start_.begin_outset =
184 -std::max<int>(cell_.CollapsedInnerBorderBefore(),
185 before_start_adjoins_preceding
186 ? cell_preceding->CollapsedInnerBorderBefore()
187 : 0);
188 }
189 if (start_.value->CoversJoint(
190 after_.value,
191 after_start_adjoins_preceding ? &borders_preceding->AfterBorder()
192 : nullptr,
193 after_start_adjoins_below ? &borders_below->StartBorder()
194 : nullptr)) {
195 if (after_start_adjoins_preceding) {
196 start_.end_outset = std::max<int>(
197 start_.end_outset, cell_preceding->CollapsedOuterBorderAfter());
198 }
199 } else {
200 start_.end_outset =
201 -std::max<int>(cell_.CollapsedInnerBorderAfter(),
202 after_start_adjoins_preceding
203 ? cell_preceding->CollapsedInnerBorderAfter()
204 : 0);
205 }
206 }
207
208 if (end_.value) {
209 if (end_.value->CoversJoint(
210 before_.value,
211 before_end_adjoins_following ? &borders_following->BeforeBorder()
212 : nullptr,
213 before_end_adjoins_above ? &borders_above->EndBorder() : nullptr)) {
214 if (before_end_adjoins_following) {
215 end_.begin_outset = std::max<int>(
216 end_.begin_outset, cell_following->CollapsedOuterBorderBefore());
217 }
218 } else {
219 end_.begin_outset =
220 -std::max<int>(cell_.CollapsedInnerBorderBefore(),
221 before_end_adjoins_following
222 ? cell_following->CollapsedInnerBorderBefore()
223 : 0);
224 }
225 if (end_.value->CoversJoint(
226 after_.value,
227 after_end_adjoins_following ? &borders_following->AfterBorder()
228 : nullptr,
229 after_end_adjoins_below ? &borders_below->EndBorder() : nullptr)) {
230 if (after_end_adjoins_following) {
231 end_.end_outset = std::max<int>(
232 end_.end_outset, cell_following->CollapsedOuterBorderAfter());
233 }
234 } else {
235 end_.end_outset =
236 -std::max<int>(cell_.CollapsedInnerBorderAfter(),
237 after_end_adjoins_following
238 ? cell_following->CollapsedInnerBorderAfter()
239 : 0);
240 }
241 }
242
243 if (before_.value) {
244 if (before_.value->CoversJoint(
245 start_.value,
246 before_start_adjoins_preceding ? &borders_preceding->BeforeBorder()
247 : nullptr,
248 before_start_adjoins_above ? &borders_above->StartBorder()
249 : nullptr)) {
250 if (before_start_adjoins_above) {
251 before_.begin_outset = std::max<int>(
252 before_.begin_outset, cell_above->CollapsedOuterBorderStart());
253 }
254 } else {
255 before_.begin_outset = -std::max<int>(
256 cell_.CollapsedInnerBorderStart(),
257 before_start_adjoins_above ? cell_above->CollapsedInnerBorderStart()
258 : 0);
259 }
260 if (before_.value->CoversJoint(
261 end_.value,
262 before_end_adjoins_following ? &borders_following->BeforeBorder()
263 : nullptr,
264 before_end_adjoins_above ? &borders_above->EndBorder() : nullptr)) {
265 if (before_end_adjoins_above) {
266 before_.end_outset = std::max<int>(
267 before_.end_outset, cell_above->CollapsedOuterBorderEnd());
268 }
269 } else {
270 before_.end_outset = -std::max<int>(
271 cell_.CollapsedInnerBorderEnd(),
272 before_end_adjoins_above ? cell_above->CollapsedInnerBorderEnd() : 0);
273 }
274 }
275
276 if (after_.value) {
277 if (after_.value->CoversJoint(
278 start_.value,
279 after_start_adjoins_preceding ? &borders_preceding->AfterBorder()
280 : nullptr,
281 after_start_adjoins_below ? &borders_below->StartBorder()
282 : nullptr)) {
283 if (after_start_adjoins_below) {
284 after_.begin_outset = std::max<int>(
285 after_.begin_outset, cell_below->CollapsedOuterBorderStart());
286 }
287 } else {
288 after_.begin_outset = -std::max<int>(
289 cell_.CollapsedInnerBorderStart(),
290 after_start_adjoins_below ? cell_below->CollapsedInnerBorderStart()
291 : 0);
292 }
293 if (after_.value->CoversJoint(
294 end_.value,
295 after_end_adjoins_following ? &borders_following->AfterBorder()
296 : nullptr,
297 after_end_adjoins_below ? &borders_below->EndBorder() : nullptr)) {
298 if (after_end_adjoins_below) {
299 after_.end_outset = std::max<int>(
300 after_.end_outset, cell_below->CollapsedOuterBorderEnd());
301 }
302 } else {
303 after_.end_outset = -std::max<int>(
304 cell_.CollapsedInnerBorderEnd(),
305 after_end_adjoins_below ? cell_below->CollapsedInnerBorderEnd() : 0);
306 }
307 }
308 }
309
AdjustForWritingModeAndDirection()310 void CollapsedBorderPainter::AdjustForWritingModeAndDirection() {
311 const auto& style = cell_.TableStyle();
312 if (!style.IsLeftToRightDirection()) {
313 std::swap(start_, end_);
314 std::swap(before_.begin_outset, before_.end_outset);
315 std::swap(after_.begin_outset, after_.end_outset);
316 }
317 if (!style.IsHorizontalWritingMode()) {
318 std::swap(after_, end_);
319 std::swap(before_, start_);
320 if (style.IsFlippedBlocksWritingMode()) {
321 std::swap(start_, end_);
322 std::swap(before_.begin_outset, before_.end_outset);
323 std::swap(after_.begin_outset, after_.end_outset);
324 }
325 }
326 }
327
CollapsedBorderStyle(EBorderStyle style)328 static EBorderStyle CollapsedBorderStyle(EBorderStyle style) {
329 if (style == EBorderStyle::kOutset)
330 return EBorderStyle::kGroove;
331 if (style == EBorderStyle::kInset)
332 return EBorderStyle::kRidge;
333 return style;
334 }
335
PaintCollapsedBorders(const PaintInfo & paint_info)336 void CollapsedBorderPainter::PaintCollapsedBorders(
337 const PaintInfo& paint_info) {
338 if (cell_.StyleRef().Visibility() != EVisibility::kVisible)
339 return;
340
341 if (!cell_.GetCollapsedBorderValues())
342 return;
343
344 GraphicsContext& context = paint_info.context;
345
346 SetupBorders();
347 if (table_.NeedsAdjustCollapsedBorderJoints())
348 AdjustJoints();
349 AdjustForWritingModeAndDirection();
350 // Now left=start_, right=end_, before_=top, after_=bottom.
351
352 // Collapsed borders are half inside and half outside of |rect|.
353 ScopedPaintState paint_state(cell_, paint_info);
354 IntRect rect = PixelSnappedIntRect(
355 TableCellPainter(cell_).PaintRectNotIncludingVisualOverflow(
356 paint_state.PaintOffset()));
357 // |paint_rect| covers the whole collapsed borders.
358 IntRect paint_rect = rect;
359 paint_rect.Expand(IntRectOutsets(before_.outer_width, end_.outer_width,
360 after_.outer_width, start_.outer_width));
361
362 // We never paint diagonals at the joins. We simply let the border with the
363 // highest precedence paint on top of borders with lower precedence.
364 if (before_.value) {
365 ObjectPainter::DrawLineForBoxSide(
366 context, rect.X() - before_.begin_outset,
367 rect.Y() - before_.outer_width, rect.MaxX() + before_.end_outset,
368 rect.Y() + before_.inner_width, BoxSide::kTop,
369 before_.value->GetColor(), CollapsedBorderStyle(before_.value->Style()),
370 0, 0, true);
371 }
372 if (after_.value) {
373 ObjectPainter::DrawLineForBoxSide(
374 context, rect.X() - after_.begin_outset,
375 rect.MaxY() - after_.inner_width, rect.MaxX() + after_.end_outset,
376 rect.MaxY() + after_.outer_width, BoxSide::kBottom,
377 after_.value->GetColor(), CollapsedBorderStyle(after_.value->Style()),
378 0, 0, true);
379 }
380 if (start_.value) {
381 ObjectPainter::DrawLineForBoxSide(
382 context, rect.X() - start_.outer_width, rect.Y() - start_.begin_outset,
383 rect.X() + start_.inner_width, rect.MaxY() + start_.end_outset,
384 BoxSide::kLeft, start_.value->GetColor(),
385 CollapsedBorderStyle(start_.value->Style()), 0, 0, true);
386 }
387 if (end_.value) {
388 ObjectPainter::DrawLineForBoxSide(
389 context, rect.MaxX() - end_.inner_width, rect.Y() - end_.begin_outset,
390 rect.MaxX() + end_.outer_width, rect.MaxY() + end_.end_outset,
391 BoxSide::kRight, end_.value->GetColor(),
392 CollapsedBorderStyle(end_.value->Style()), 0, 0, true);
393 }
394 }
395
396 } // namespace blink
397