| 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 | |