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
37namespace 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.
44LayoutUnit 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.
54LayoutUnit GridBaselineAlignment::marginUnderForChild(const RenderBox& child, GridAxis axis) const
55{
56 return isHorizontalBaselineAxis(axis) ? child.marginLeft() : child.marginBottom();
57}
58
59LayoutUnit 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
65LayoutUnit 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
78LayoutUnit 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
85bool 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
92bool GridBaselineAlignment::isHorizontalBaselineAxis(GridAxis axis) const
93{
94 return axis == GridRowAxis ? isHorizontalWritingMode(m_blockFlow) : !isHorizontalWritingMode(m_blockFlow);
95}
96
97bool GridBaselineAlignment::isOrthogonalChildForBaseline(const RenderBox& child) const
98{
99 return isHorizontalWritingMode(m_blockFlow) != child.isHorizontalWritingMode();
100}
101
102bool GridBaselineAlignment::isParallelToBaselineAxisForChild(const RenderBox& child, GridAxis axis) const
103{
104 return axis == GridColumnAxis ? !isOrthogonalChildForBaseline(child) : isOrthogonalChildForBaseline(child);
105}
106
107const 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
117void 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
144LayoutUnit 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
153void GridBaselineAlignment::clear(GridAxis baselineAxis)
154{
155 if (baselineAxis == GridColumnAxis)
156 m_rowAxisAlignmentContext.clear();
157 else
158 m_colAxisAlignmentContext.clear();
159}
160
161BaselineGroup::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
168void 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
176bool 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
191bool 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
205bool 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
212BaselineContext::BaselineContext(const RenderBox& child, ItemPosition preference, LayoutUnit ascent, LayoutUnit descent)
213{
214 ASSERT(isBaselinePosition(preference));
215 updateSharedGroup(child, preference, ascent, descent);
216}
217
218const 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
224void 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
233BaselineGroup& 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