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