1 | /* |
2 | * Copyright (C) 2018 Igalia S.L. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions are |
6 | * met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above |
11 | * copyright notice, this list of conditions and the following disclaimer |
12 | * in the documentation and/or other materials provided with the |
13 | * distribution. |
14 | * * Neither the name of Google Inc. nor the names of its |
15 | * contributors may be used to endorse or promote products derived from |
16 | * this software without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | */ |
30 | |
31 | #include "config.h" |
32 | #include "GridBaselineAlignment.h" |
33 | |
34 | #include "RenderBox.h" |
35 | #include "RenderStyle.h" |
36 | |
37 | namespace WebCore { |
38 | |
39 | // This function gives the margin 'over' based on the baseline-axis, since in grid we can have 2-dimensional |
40 | // alignment by baseline. In horizontal writing-mode, the row-axis is the horizontal axis. When we use this |
41 | // axis to move the grid items so that they are baseline-aligned, we want their "horizontal" margin (right); |
42 | // the same will happen when using the column-axis under vertical writing mode, we also want in this case the |
43 | // 'right' margin. |
44 | LayoutUnit GridBaselineAlignment::marginOverForChild(const RenderBox& child, GridAxis axis) const |
45 | { |
46 | return isHorizontalBaselineAxis(axis) ? child.marginRight() : child.marginTop(); |
47 | } |
48 | |
49 | // This function gives the margin 'under' based on the baseline-axis, since in grid we can can 2-dimensional |
50 | // alignment by baseline. In horizontal writing-mode, the row-axis is the horizontal axis. When we use this |
51 | // axis to move the grid items so that they are baseline-aligned, we want their "horizontal" margin (left); |
52 | // the same will happen when using the column-axis under vertical writing mode, we also want in this case the |
53 | // 'left' margin. |
54 | LayoutUnit GridBaselineAlignment::marginUnderForChild(const RenderBox& child, GridAxis axis) const |
55 | { |
56 | return isHorizontalBaselineAxis(axis) ? child.marginLeft() : child.marginBottom(); |
57 | } |
58 | |
59 | LayoutUnit GridBaselineAlignment::logicalAscentForChild(const RenderBox& child, GridAxis baselineAxis) const |
60 | { |
61 | LayoutUnit ascent = ascentForChild(child, baselineAxis); |
62 | return isDescentBaselineForChild(child, baselineAxis) ? descentForChild(child, ascent, baselineAxis) : ascent; |
63 | } |
64 | |
65 | LayoutUnit GridBaselineAlignment::ascentForChild(const RenderBox& child, GridAxis baselineAxis) const |
66 | { |
67 | LayoutUnit margin = isDescentBaselineForChild(child, baselineAxis) ? marginUnderForChild(child, baselineAxis) : marginOverForChild(child, baselineAxis); |
68 | LayoutUnit baseline(isParallelToBaselineAxisForChild(child, baselineAxis) ? child.firstLineBaseline().valueOr(-1) : -1); |
69 | // We take border-box's under edge if no valid baseline. |
70 | if (baseline == -1) { |
71 | if (isHorizontalBaselineAxis(baselineAxis)) |
72 | return isFlippedWritingMode(m_blockFlow) ? child.size().width().toInt() + margin : margin; |
73 | return child.size().height() + margin; |
74 | } |
75 | return baseline + margin; |
76 | } |
77 | |
78 | LayoutUnit GridBaselineAlignment::descentForChild(const RenderBox& child, LayoutUnit ascent, GridAxis baselineAxis) const |
79 | { |
80 | if (isParallelToBaselineAxisForChild(child, baselineAxis)) |
81 | return child.marginLogicalHeight() + child.logicalHeight() - ascent; |
82 | return child.marginLogicalWidth() + child.logicalWidth() - ascent; |
83 | } |
84 | |
85 | bool GridBaselineAlignment::isDescentBaselineForChild(const RenderBox& child, GridAxis baselineAxis) const |
86 | { |
87 | return isHorizontalBaselineAxis(baselineAxis) |
88 | && ((child.style().isFlippedBlocksWritingMode() && !isFlippedWritingMode(m_blockFlow)) |
89 | || (child.style().isFlippedLinesWritingMode() && isFlippedWritingMode(m_blockFlow))); |
90 | } |
91 | |
92 | bool GridBaselineAlignment::isHorizontalBaselineAxis(GridAxis axis) const |
93 | { |
94 | return axis == GridRowAxis ? isHorizontalWritingMode(m_blockFlow) : !isHorizontalWritingMode(m_blockFlow); |
95 | } |
96 | |
97 | bool GridBaselineAlignment::isOrthogonalChildForBaseline(const RenderBox& child) const |
98 | { |
99 | return isHorizontalWritingMode(m_blockFlow) != child.isHorizontalWritingMode(); |
100 | } |
101 | |
102 | bool GridBaselineAlignment::isParallelToBaselineAxisForChild(const RenderBox& child, GridAxis axis) const |
103 | { |
104 | return axis == GridColumnAxis ? !isOrthogonalChildForBaseline(child) : isOrthogonalChildForBaseline(child); |
105 | } |
106 | |
107 | const BaselineGroup& GridBaselineAlignment::baselineGroupForChild(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis) const |
108 | { |
109 | ASSERT(isBaselinePosition(preference)); |
110 | bool isRowAxisContext = baselineAxis == GridColumnAxis; |
111 | auto& contextsMap = isRowAxisContext ? m_rowAxisAlignmentContext : m_colAxisAlignmentContext; |
112 | auto* context = contextsMap.get(sharedContext); |
113 | ASSERT(context); |
114 | return context->sharedGroup(child, preference); |
115 | } |
116 | |
117 | void GridBaselineAlignment::updateBaselineAlignmentContext(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis) |
118 | { |
119 | ASSERT(isBaselinePosition(preference)); |
120 | ASSERT(!child.needsLayout()); |
121 | |
122 | // Determine Ascent and Descent values of this child with respect to |
123 | // its grid container. |
124 | LayoutUnit ascent = ascentForChild(child, baselineAxis); |
125 | LayoutUnit descent = descentForChild(child, ascent, baselineAxis); |
126 | if (isDescentBaselineForChild(child, baselineAxis)) |
127 | std::swap(ascent, descent); |
128 | |
129 | // Looking up for a shared alignment context perpendicular to the |
130 | // baseline axis. |
131 | bool isRowAxisContext = baselineAxis == GridColumnAxis; |
132 | auto& contextsMap = isRowAxisContext ? m_rowAxisAlignmentContext : m_colAxisAlignmentContext; |
133 | auto addResult = contextsMap.add(sharedContext, nullptr); |
134 | |
135 | // Looking for a compatible baseline-sharing group. |
136 | if (addResult.isNewEntry) |
137 | addResult.iterator->value = std::make_unique<BaselineContext>(child, preference, ascent, descent); |
138 | else { |
139 | auto* context = addResult.iterator->value.get(); |
140 | context->updateSharedGroup(child, preference, ascent, descent); |
141 | } |
142 | } |
143 | |
144 | LayoutUnit GridBaselineAlignment::baselineOffsetForChild(ItemPosition preference, unsigned sharedContext, const RenderBox& child, GridAxis baselineAxis) const |
145 | { |
146 | ASSERT(isBaselinePosition(preference)); |
147 | auto& group = baselineGroupForChild(preference, sharedContext, child, baselineAxis); |
148 | if (group.size() > 1) |
149 | return group.maxAscent() - logicalAscentForChild(child, baselineAxis); |
150 | return LayoutUnit(); |
151 | } |
152 | |
153 | void GridBaselineAlignment::clear(GridAxis baselineAxis) |
154 | { |
155 | if (baselineAxis == GridColumnAxis) |
156 | m_rowAxisAlignmentContext.clear(); |
157 | else |
158 | m_colAxisAlignmentContext.clear(); |
159 | } |
160 | |
161 | BaselineGroup::BaselineGroup(WritingMode blockFlow, ItemPosition childPreference) |
162 | : m_maxAscent(0), m_maxDescent(0), m_items() |
163 | { |
164 | m_blockFlow = blockFlow; |
165 | m_preference = childPreference; |
166 | } |
167 | |
168 | void BaselineGroup::update(const RenderBox& child, LayoutUnit ascent, LayoutUnit descent) |
169 | { |
170 | if (m_items.add(&child).isNewEntry) { |
171 | m_maxAscent = std::max(m_maxAscent, ascent); |
172 | m_maxDescent = std::max(m_maxDescent, descent); |
173 | } |
174 | } |
175 | |
176 | bool BaselineGroup::isOppositeBlockFlow(WritingMode blockFlow) const |
177 | { |
178 | switch (blockFlow) { |
179 | case WritingMode::TopToBottomWritingMode: |
180 | return false; |
181 | case WritingMode::LeftToRightWritingMode: |
182 | return m_blockFlow == WritingMode::RightToLeftWritingMode; |
183 | case WritingMode::RightToLeftWritingMode: |
184 | return m_blockFlow == WritingMode::LeftToRightWritingMode; |
185 | default: |
186 | ASSERT_NOT_REACHED(); |
187 | return false; |
188 | } |
189 | } |
190 | |
191 | bool BaselineGroup::isOrthogonalBlockFlow(WritingMode blockFlow) const |
192 | { |
193 | switch (blockFlow) { |
194 | case WritingMode::TopToBottomWritingMode: |
195 | return m_blockFlow != WritingMode::TopToBottomWritingMode; |
196 | case WritingMode::LeftToRightWritingMode: |
197 | case WritingMode::RightToLeftWritingMode: |
198 | return m_blockFlow == WritingMode::TopToBottomWritingMode; |
199 | default: |
200 | ASSERT_NOT_REACHED(); |
201 | return false; |
202 | } |
203 | } |
204 | |
205 | bool BaselineGroup::isCompatible(WritingMode childBlockFlow, ItemPosition childPreference) const |
206 | { |
207 | ASSERT(isBaselinePosition(childPreference)); |
208 | ASSERT(size() > 0); |
209 | return ((m_blockFlow == childBlockFlow || isOrthogonalBlockFlow(childBlockFlow)) && m_preference == childPreference) || (isOppositeBlockFlow(childBlockFlow) && m_preference != childPreference); |
210 | } |
211 | |
212 | BaselineContext::BaselineContext(const RenderBox& child, ItemPosition preference, LayoutUnit ascent, LayoutUnit descent) |
213 | { |
214 | ASSERT(isBaselinePosition(preference)); |
215 | updateSharedGroup(child, preference, ascent, descent); |
216 | } |
217 | |
218 | const BaselineGroup& BaselineContext::sharedGroup(const RenderBox& child, ItemPosition preference) const |
219 | { |
220 | ASSERT(isBaselinePosition(preference)); |
221 | return const_cast<BaselineContext*>(this)->findCompatibleSharedGroup(child, preference); |
222 | } |
223 | |
224 | void BaselineContext::updateSharedGroup(const RenderBox& child, ItemPosition preference, LayoutUnit ascent, LayoutUnit descent) |
225 | { |
226 | ASSERT(isBaselinePosition(preference)); |
227 | BaselineGroup& group = findCompatibleSharedGroup(child, preference); |
228 | group.update(child, ascent, descent); |
229 | } |
230 | |
231 | // FIXME: Properly implement baseline-group compatibility. |
232 | // See https://github.com/w3c/csswg-drafts/issues/721 |
233 | BaselineGroup& BaselineContext::findCompatibleSharedGroup(const RenderBox& child, ItemPosition preference) |
234 | { |
235 | WritingMode blockDirection = child.style().writingMode(); |
236 | for (auto& group : m_sharedGroups) { |
237 | if (group.isCompatible(blockDirection, preference)) |
238 | return group; |
239 | } |
240 | m_sharedGroups.insert(0, BaselineGroup(blockDirection, preference)); |
241 | return m_sharedGroups[0]; |
242 | } |
243 | |
244 | } // namespace WebCore |
245 | |