CSSAlignUtils.cpp (8573B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Utility code for performing CSS Box Alignment */ 8 9 #include "CSSAlignUtils.h" 10 11 #include "ReflowInput.h" 12 #include "nsIFrame.h" 13 #include "nsIFrameInlines.h" 14 15 namespace mozilla { 16 17 StyleAlignFlags CSSAlignUtils::UsedAlignmentForAbsPos(nsIFrame* aFrame, 18 StyleAlignFlags aFlags, 19 LogicalAxis aLogicalAxis, 20 WritingMode aCBWM) { 21 MOZ_ASSERT(aFrame->IsAbsolutelyPositioned()); 22 23 // Extract and strip the flag bits 24 StyleAlignFlags alignmentFlags = aFlags & StyleAlignFlags::FLAG_BITS; 25 aFlags &= ~StyleAlignFlags::FLAG_BITS; 26 27 if (aFlags == StyleAlignFlags::NORMAL) { 28 // "the 'normal' keyword behaves as 'start' on replaced 29 // absolutely-positioned boxes, and behaves as 'stretch' on all other 30 // absolutely-positioned boxes." 31 // https://drafts.csswg.org/css-align/#align-abspos 32 // https://drafts.csswg.org/css-align/#justify-abspos 33 aFlags = aFrame->HasReplacedSizing() ? StyleAlignFlags::START 34 : StyleAlignFlags::STRETCH; 35 } else if (aFlags == StyleAlignFlags::FLEX_START) { 36 aFlags = StyleAlignFlags::START; 37 } else if (aFlags == StyleAlignFlags::FLEX_END) { 38 aFlags = StyleAlignFlags::END; 39 } else if (aFlags == StyleAlignFlags::LEFT || 40 aFlags == StyleAlignFlags::RIGHT) { 41 if (aLogicalAxis == LogicalAxis::Inline) { 42 const bool isLeft = (aFlags == StyleAlignFlags::LEFT); 43 aFlags = (isLeft == aCBWM.IsBidiLTR()) ? StyleAlignFlags::START 44 : StyleAlignFlags::END; 45 } else { 46 aFlags = StyleAlignFlags::START; 47 } 48 } else if (aFlags == StyleAlignFlags::BASELINE) { 49 aFlags = StyleAlignFlags::START; 50 } else if (aFlags == StyleAlignFlags::LAST_BASELINE) { 51 aFlags = StyleAlignFlags::END; 52 } 53 54 return (aFlags | alignmentFlags); 55 } 56 57 static nscoord SpaceToFill(WritingMode aWM, const LogicalSize& aSize, 58 nscoord aMargin, LogicalAxis aAxis, 59 nscoord aCBSize) { 60 nscoord size = aSize.Size(aAxis, aWM); 61 return aCBSize - (size + aMargin); 62 } 63 64 nscoord CSSAlignUtils::AlignJustifySelf( 65 const StyleAlignFlags& aAlignment, LogicalAxis aAxis, 66 AlignJustifyFlags aFlags, nscoord aBaselineAdjust, nscoord aCBSize, 67 const ReflowInput& aRI, const LogicalSize& aChildSize, 68 const Maybe<AnchorAlignInfo>& aAnchorInfo) { 69 MOZ_ASSERT(aAlignment != StyleAlignFlags::AUTO, 70 "auto values should have resolved already"); 71 MOZ_ASSERT(aAlignment != StyleAlignFlags::LEFT && 72 aAlignment != StyleAlignFlags::RIGHT, 73 "caller should map that to the corresponding START/END"); 74 75 const bool isSameSide = aFlags.contains(AlignJustifyFlag::SameSide); 76 77 StyleAlignFlags alignment = aAlignment; 78 // Map some alignment values to 'start' / 'end'. 79 if (alignment == StyleAlignFlags::SELF_START) { 80 // align/justify-self: self-start 81 alignment = 82 MOZ_LIKELY(isSameSide) ? StyleAlignFlags::START : StyleAlignFlags::END; 83 } else if (alignment == StyleAlignFlags::SELF_END) { 84 alignment = 85 MOZ_LIKELY(isSameSide) ? StyleAlignFlags::END : StyleAlignFlags::START; 86 // flex-start/flex-end are the same as start/end, in most contexts. 87 // (They have special behavior in flex containers, so flex containers 88 // should map them to some other value before calling this method.) 89 } else if (alignment == StyleAlignFlags::FLEX_START) { 90 alignment = StyleAlignFlags::START; 91 } else if (alignment == StyleAlignFlags::FLEX_END) { 92 alignment = StyleAlignFlags::END; 93 } 94 95 // Get the item's margin corresponding to the container's start/end side. 96 WritingMode wm = aRI.GetWritingMode(); 97 // If we're handling the margin box, it's already included in the incoming 98 // size. 99 const LogicalMargin margin = 100 aFlags.contains(AlignJustifyFlag::AligningMarginBox) 101 ? LogicalMargin(wm) 102 : aRI.ComputedLogicalMargin(wm); 103 const auto startSide = MakeLogicalSide( 104 aAxis, MOZ_LIKELY(isSameSide) ? LogicalEdge::Start : LogicalEdge::End); 105 const nscoord marginStart = margin.Side(startSide, wm); 106 const auto endSide = GetOppositeSide(startSide); 107 const nscoord marginEnd = margin.Side(endSide, wm); 108 109 bool hasAutoMarginStart; 110 bool hasAutoMarginEnd; 111 const auto* styleMargin = aRI.mStyleMargin; 112 const auto anchorResolutionParams = AnchorPosResolutionParams::From(&aRI); 113 if (aFlags.contains(AlignJustifyFlag::IgnoreAutoMargins) || 114 aFlags.contains(AlignJustifyFlag::AligningMarginBox)) { 115 // (Note: ReflowInput will have treated "auto" margins as 0, so we 116 // don't need to do anything special to avoid expanding them.) 117 hasAutoMarginStart = hasAutoMarginEnd = false; 118 } else if (aAxis == LogicalAxis::Block) { 119 hasAutoMarginStart = 120 styleMargin->GetMargin(LogicalSide::BStart, wm, anchorResolutionParams) 121 ->IsAuto(); 122 hasAutoMarginEnd = 123 styleMargin->GetMargin(LogicalSide::BEnd, wm, anchorResolutionParams) 124 ->IsAuto(); 125 } else { /* aAxis == LogicalAxis::Inline */ 126 hasAutoMarginStart = 127 styleMargin->GetMargin(LogicalSide::IStart, wm, anchorResolutionParams) 128 ->IsAuto(); 129 hasAutoMarginEnd = 130 styleMargin->GetMargin(LogicalSide::IEnd, wm, anchorResolutionParams) 131 ->IsAuto(); 132 } 133 134 // https://drafts.csswg.org/css-align-3/#overflow-values 135 // This implements <overflow-position> = 'safe'. 136 // And auto-margins: https://drafts.csswg.org/css-grid/#auto-margins 137 if ((MOZ_UNLIKELY(aFlags.contains(AlignJustifyFlag::OverflowSafe)) && 138 alignment != StyleAlignFlags::START) || 139 hasAutoMarginStart || hasAutoMarginEnd) { 140 nscoord space = 141 SpaceToFill(wm, aChildSize, marginStart + marginEnd, aAxis, aCBSize); 142 // XXX we might want to include == 0 here as an optimization - 143 // I need to see what the baseline/last baseline code looks like first. 144 if (space < 0) { 145 // "Overflowing elements ignore their auto margins and overflow 146 // in the end directions" 147 alignment = StyleAlignFlags::START; 148 } else if (hasAutoMarginEnd) { 149 alignment = hasAutoMarginStart ? StyleAlignFlags::CENTER 150 : (isSameSide ? StyleAlignFlags::START 151 : StyleAlignFlags::END); 152 } else if (hasAutoMarginStart) { 153 alignment = isSameSide ? StyleAlignFlags::END : StyleAlignFlags::START; 154 } 155 } 156 157 // Determine the offset for the child frame (its border-box) which will 158 // achieve the requested alignment. 159 nscoord offset = 0; 160 if (alignment == StyleAlignFlags::BASELINE || 161 alignment == StyleAlignFlags::LAST_BASELINE) { 162 const bool isFirstBaselineSharingGroup = 163 !aFlags.contains(AlignJustifyFlag::LastBaselineSharingGroup); 164 if (MOZ_LIKELY(isFirstBaselineSharingGroup)) { 165 offset = marginStart + aBaselineAdjust; 166 } else { 167 nscoord size = aChildSize.Size(aAxis, wm); 168 offset = aCBSize - (size + marginEnd) - aBaselineAdjust; 169 } 170 } else if (alignment == StyleAlignFlags::STRETCH || 171 alignment == StyleAlignFlags::START) { 172 // ComputeSize() deals with stretch 173 offset = marginStart; 174 } else if (alignment == StyleAlignFlags::END) { 175 nscoord size = aChildSize.Size(aAxis, wm); 176 offset = aCBSize - (size + marginEnd); 177 } else if (alignment == StyleAlignFlags::ANCHOR_CENTER && aAnchorInfo) { 178 const nscoord anchorSize = aAnchorInfo->mAnchorSize; 179 const nscoord anchorStart = aAnchorInfo->mAnchorStart; 180 const nscoord size = aChildSize.Size(aAxis, wm); 181 182 // Offset relative to the anchors center, accounting for margins 183 offset = anchorStart + (anchorSize - size + marginStart - marginEnd) / 2; 184 } else { 185 // ANCHOR_CENTER with no Anchor is treated like CENTER. 186 MOZ_ASSERT(alignment == StyleAlignFlags::CENTER || 187 alignment == StyleAlignFlags::ANCHOR_CENTER, 188 "unknown align-/justify-self value"); 189 nscoord size = aChildSize.Size(aAxis, wm); 190 offset = (aCBSize - size + marginStart - marginEnd) / 2; 191 } 192 193 return offset; 194 } 195 196 } // namespace mozilla