1 // Copyright (c) 2016 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/grid_baseline_alignment.h"
6 
7 #include "third_party/blink/renderer/core/style/computed_style.h"
8 
9 namespace blink {
10 
11 // This function gives the margin 'over' based on the baseline-axis,
12 // since in grid we can can 2-dimensional alignment by baseline. In
13 // horizontal writing-mode, the row-axis is the horizontal axis. When
14 // we use this axis to move the grid items so that they are
15 // baseline-aligned, we want their "horizontal" margin (right); the
16 // same will happen when using the column-axis under vertical writing
17 // mode, we also want in this case the 'right' margin.
MarginOverForChild(const LayoutBox & child,GridAxis axis) const18 LayoutUnit GridBaselineAlignment::MarginOverForChild(const LayoutBox& child,
19                                                      GridAxis axis) const {
20   return IsHorizontalBaselineAxis(axis) ? child.MarginRight()
21                                         : child.MarginTop();
22 }
23 
24 // This function gives the margin 'under' based on the baseline-axis,
25 // since in grid we can can 2-dimensional alignment by baseline. In
26 // horizontal writing-mode, the row-axis is the horizontal axis. When
27 // we use this axis to move the grid items so that they are
28 // baseline-aligned, we want their "horizontal" margin (left); the
29 // same will happen when using the column-axis under vertical writing
30 // mode, we also want in this case the 'left' margin.
MarginUnderForChild(const LayoutBox & child,GridAxis axis) const31 LayoutUnit GridBaselineAlignment::MarginUnderForChild(const LayoutBox& child,
32                                                       GridAxis axis) const {
33   return IsHorizontalBaselineAxis(axis) ? child.MarginLeft()
34                                         : child.MarginBottom();
35 }
36 
LogicalAscentForChild(const LayoutBox & child,GridAxis baseline_axis) const37 LayoutUnit GridBaselineAlignment::LogicalAscentForChild(
38     const LayoutBox& child,
39     GridAxis baseline_axis) const {
40   LayoutUnit ascent = AscentForChild(child, baseline_axis);
41   return IsDescentBaselineForChild(child, baseline_axis)
42              ? DescentForChild(child, ascent, baseline_axis)
43              : ascent;
44 }
45 
AscentForChild(const LayoutBox & child,GridAxis baseline_axis) const46 LayoutUnit GridBaselineAlignment::AscentForChild(const LayoutBox& child,
47                                                  GridAxis baseline_axis) const {
48   LayoutUnit margin = IsDescentBaselineForChild(child, baseline_axis)
49                           ? MarginUnderForChild(child, baseline_axis)
50                           : MarginOverForChild(child, baseline_axis);
51   LayoutUnit baseline = IsParallelToBaselineAxisForChild(child, baseline_axis)
52                             ? child.FirstLineBoxBaseline()
53                             : LayoutUnit(-1);
54   // We take border-box's under edge if no valid baseline.
55   if (baseline == -1) {
56     if (IsHorizontalBaselineAxis(baseline_axis)) {
57       return IsFlippedBlocksWritingMode(block_flow_)
58                  ? child.Size().Width().ToInt() + margin
59                  : margin;
60     }
61     return child.Size().Height() + margin;
62   }
63   return baseline + margin;
64 }
65 
DescentForChild(const LayoutBox & child,LayoutUnit ascent,GridAxis baseline_axis) const66 LayoutUnit GridBaselineAlignment::DescentForChild(
67     const LayoutBox& child,
68     LayoutUnit ascent,
69     GridAxis baseline_axis) const {
70   if (IsParallelToBaselineAxisForChild(child, baseline_axis))
71     return child.MarginLogicalHeight() + child.LogicalHeight() - ascent;
72   return child.MarginLogicalWidth() + child.LogicalWidth() - ascent;
73 }
74 
IsDescentBaselineForChild(const LayoutBox & child,GridAxis baseline_axis) const75 bool GridBaselineAlignment::IsDescentBaselineForChild(
76     const LayoutBox& child,
77     GridAxis baseline_axis) const {
78   return IsHorizontalBaselineAxis(baseline_axis) &&
79          ((child.StyleRef().IsFlippedBlocksWritingMode() &&
80            !IsFlippedBlocksWritingMode(block_flow_)) ||
81           (child.StyleRef().IsFlippedLinesWritingMode() &&
82            IsFlippedBlocksWritingMode(block_flow_)));
83 }
84 
IsHorizontalBaselineAxis(GridAxis axis) const85 bool GridBaselineAlignment::IsHorizontalBaselineAxis(GridAxis axis) const {
86   return axis == kGridRowAxis ? IsHorizontalWritingMode(block_flow_)
87                               : !IsHorizontalWritingMode(block_flow_);
88 }
89 
IsOrthogonalChildForBaseline(const LayoutBox & child) const90 bool GridBaselineAlignment::IsOrthogonalChildForBaseline(
91     const LayoutBox& child) const {
92   return IsHorizontalWritingMode(block_flow_) !=
93          child.IsHorizontalWritingMode();
94 }
95 
IsParallelToBaselineAxisForChild(const LayoutBox & child,GridAxis axis) const96 bool GridBaselineAlignment::IsParallelToBaselineAxisForChild(
97     const LayoutBox& child,
98     GridAxis axis) const {
99   return axis == kGridColumnAxis ? !IsOrthogonalChildForBaseline(child)
100                                  : IsOrthogonalChildForBaseline(child);
101 }
102 
GetBaselineGroupForChild(ItemPosition preference,unsigned shared_context,const LayoutBox & child,GridAxis baseline_axis) const103 const BaselineGroup& GridBaselineAlignment::GetBaselineGroupForChild(
104     ItemPosition preference,
105     unsigned shared_context,
106     const LayoutBox& child,
107     GridAxis baseline_axis) const {
108   DCHECK(IsBaselinePosition(preference));
109   bool is_row_axis_context = baseline_axis == kGridColumnAxis;
110   auto& contexts_map = is_row_axis_context ? row_axis_alignment_context_
111                                            : col_axis_alignment_context_;
112   auto* context = contexts_map.at(shared_context);
113   DCHECK(context);
114   return context->GetSharedGroup(child, preference);
115 }
116 
UpdateBaselineAlignmentContext(ItemPosition preference,unsigned shared_context,const LayoutBox & child,GridAxis baseline_axis)117 void GridBaselineAlignment::UpdateBaselineAlignmentContext(
118     ItemPosition preference,
119     unsigned shared_context,
120     const LayoutBox& child,
121     GridAxis baseline_axis) {
122   DCHECK(IsBaselinePosition(preference));
123   DCHECK(!child.NeedsLayout());
124 
125   // Determine Ascent and Descent values of this child with respect to
126   // its grid container.
127   LayoutUnit ascent = AscentForChild(child, baseline_axis);
128   LayoutUnit descent = DescentForChild(child, ascent, baseline_axis);
129   if (IsDescentBaselineForChild(child, baseline_axis))
130     std::swap(ascent, descent);
131 
132   // Looking up for a shared alignment context perpendicular to the
133   // baseline axis.
134   bool is_row_axis_context = baseline_axis == kGridColumnAxis;
135   auto& contexts_map = is_row_axis_context ? row_axis_alignment_context_
136                                            : col_axis_alignment_context_;
137   auto add_result = contexts_map.insert(shared_context, nullptr);
138 
139   // Looking for a compatible baseline-sharing group.
140   if (add_result.is_new_entry) {
141     add_result.stored_value->value =
142         std::make_unique<BaselineContext>(child, preference, ascent, descent);
143   } else {
144     auto* context = add_result.stored_value->value.get();
145     context->UpdateSharedGroup(child, preference, ascent, descent);
146   }
147 }
148 
BaselineOffsetForChild(ItemPosition preference,unsigned shared_context,const LayoutBox & child,GridAxis baseline_axis) const149 LayoutUnit GridBaselineAlignment::BaselineOffsetForChild(
150     ItemPosition preference,
151     unsigned shared_context,
152     const LayoutBox& child,
153     GridAxis baseline_axis) const {
154   DCHECK(IsBaselinePosition(preference));
155   auto& group = GetBaselineGroupForChild(preference, shared_context, child,
156                                          baseline_axis);
157   if (group.size() > 1) {
158     return group.MaxAscent() - LogicalAscentForChild(child, baseline_axis);
159   }
160   return LayoutUnit();
161 }
162 
Clear(GridAxis baseline_axis)163 void GridBaselineAlignment::Clear(GridAxis baseline_axis) {
164   if (baseline_axis == kGridColumnAxis)
165     row_axis_alignment_context_.clear();
166   else
167     col_axis_alignment_context_.clear();
168 }
169 
BaselineGroup(WritingMode block_flow,ItemPosition child_preference)170 BaselineGroup::BaselineGroup(WritingMode block_flow,
171                              ItemPosition child_preference)
172     : max_ascent_(0), max_descent_(0), items_() {
173   block_flow_ = block_flow;
174   preference_ = child_preference;
175 }
176 
Update(const LayoutBox & child,LayoutUnit ascent,LayoutUnit descent)177 void BaselineGroup::Update(const LayoutBox& child,
178                            LayoutUnit ascent,
179                            LayoutUnit descent) {
180   if (items_.insert(&child).is_new_entry) {
181     max_ascent_ = std::max(max_ascent_, ascent);
182     max_descent_ = std::max(max_descent_, descent);
183   }
184 }
185 
IsOppositeBlockFlow(WritingMode block_flow) const186 bool BaselineGroup::IsOppositeBlockFlow(WritingMode block_flow) const {
187   switch (block_flow) {
188     case WritingMode::kHorizontalTb:
189       return false;
190     case WritingMode::kVerticalLr:
191       return block_flow_ == WritingMode::kVerticalRl;
192     case WritingMode::kVerticalRl:
193       return block_flow_ == WritingMode::kVerticalLr;
194     default:
195       NOTREACHED();
196       return false;
197   }
198 }
199 
IsOrthogonalBlockFlow(WritingMode block_flow) const200 bool BaselineGroup::IsOrthogonalBlockFlow(WritingMode block_flow) const {
201   switch (block_flow) {
202     case WritingMode::kHorizontalTb:
203       return block_flow_ != WritingMode::kHorizontalTb;
204     case WritingMode::kVerticalLr:
205     case WritingMode::kVerticalRl:
206       return block_flow_ == WritingMode::kHorizontalTb;
207     default:
208       NOTREACHED();
209       return false;
210   }
211 }
212 
IsCompatible(WritingMode child_block_flow,ItemPosition child_preference) const213 bool BaselineGroup::IsCompatible(WritingMode child_block_flow,
214                                  ItemPosition child_preference) const {
215   DCHECK(IsBaselinePosition(child_preference));
216   DCHECK_GT(size(), 0);
217   return ((block_flow_ == child_block_flow ||
218            IsOrthogonalBlockFlow(child_block_flow)) &&
219           preference_ == child_preference) ||
220          (IsOppositeBlockFlow(child_block_flow) &&
221           preference_ != child_preference);
222 }
223 
BaselineContext(const LayoutBox & child,ItemPosition preference,LayoutUnit ascent,LayoutUnit descent)224 BaselineContext::BaselineContext(const LayoutBox& child,
225                                  ItemPosition preference,
226                                  LayoutUnit ascent,
227                                  LayoutUnit descent) {
228   DCHECK(IsBaselinePosition(preference));
229   UpdateSharedGroup(child, preference, ascent, descent);
230 }
231 
GetSharedGroup(const LayoutBox & child,ItemPosition preference) const232 const BaselineGroup& BaselineContext::GetSharedGroup(
233     const LayoutBox& child,
234     ItemPosition preference) const {
235   DCHECK(IsBaselinePosition(preference));
236   return const_cast<BaselineContext*>(this)->FindCompatibleSharedGroup(
237       child, preference);
238 }
239 
UpdateSharedGroup(const LayoutBox & child,ItemPosition preference,LayoutUnit ascent,LayoutUnit descent)240 void BaselineContext::UpdateSharedGroup(const LayoutBox& child,
241                                         ItemPosition preference,
242                                         LayoutUnit ascent,
243                                         LayoutUnit descent) {
244   DCHECK(IsBaselinePosition(preference));
245   BaselineGroup& group = FindCompatibleSharedGroup(child, preference);
246   group.Update(child, ascent, descent);
247 }
248 
249 // TODO Properly implement baseline-group compatibility
250 // See https://github.com/w3c/csswg-drafts/issues/721
FindCompatibleSharedGroup(const LayoutBox & child,ItemPosition preference)251 BaselineGroup& BaselineContext::FindCompatibleSharedGroup(
252     const LayoutBox& child,
253     ItemPosition preference) {
254   WritingMode block_direction = child.StyleRef().GetWritingMode();
255   for (auto& group : shared_groups_) {
256     if (group.IsCompatible(block_direction, preference))
257       return group;
258   }
259   shared_groups_.push_front(BaselineGroup(block_direction, preference));
260   return shared_groups_[0];
261 }
262 
263 }  // namespace blink
264