diff options
Diffstat (limited to '')
-rw-r--r-- | layout/generic/WritingModes.h | 2236 |
1 files changed, 2236 insertions, 0 deletions
diff --git a/layout/generic/WritingModes.h b/layout/generic/WritingModes.h new file mode 100644 index 0000000000..6fa00873bb --- /dev/null +++ b/layout/generic/WritingModes.h @@ -0,0 +1,2236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WritingModes_h_ +#define WritingModes_h_ + +#include <ostream> + +#include "mozilla/intl/BidiEmbeddingLevel.h" +#include "mozilla/ComputedStyle.h" +#include "mozilla/EnumeratedRange.h" + +#include "nsRect.h" +#include "nsBidiUtils.h" +#include "nsStyleStruct.h" + +// It is the caller's responsibility to operate on logical-coordinate objects +// with matched writing modes. Failure to do so will be a runtime bug; the +// compiler can't catch it, but in debug mode, we'll throw an assertion. +// NOTE that in non-debug builds, a writing mode mismatch error will NOT be +// detected, yet the results will be nonsense (and may lead to further layout +// failures). Therefore, it is important to test (and fuzz-test) writing-mode +// support using debug builds. + +// Methods in logical-coordinate classes that take another logical-coordinate +// object as a parameter should call CHECK_WRITING_MODE on it to verify that +// the writing modes match. +// (In some cases, there are internal (private) methods that don't do this; +// such methods should only be used by other methods that have already checked +// the writing modes.) +// The check ignores the StyleWritingMode::VERTICAL_SIDEWAYS and +// StyleWritingMode::TEXT_SIDEWAYS bit of writing mode, because +// this does not affect the interpretation of logical coordinates. + +#define CHECK_WRITING_MODE(param) \ + NS_ASSERTION(param.IgnoreSideways() == GetWritingMode().IgnoreSideways(), \ + "writing-mode mismatch") + +namespace mozilla { + +namespace widget { +struct IMENotification; +} // namespace widget + +// Logical axis, edge, side and corner constants for use in various places. +enum LogicalAxis : uint8_t { + eLogicalAxisBlock = 0x0, + eLogicalAxisInline = 0x1 +}; +enum LogicalEdge { eLogicalEdgeStart = 0x0, eLogicalEdgeEnd = 0x1 }; +enum LogicalSide : uint8_t { + eLogicalSideBStart = (eLogicalAxisBlock << 1) | eLogicalEdgeStart, // 0x0 + eLogicalSideBEnd = (eLogicalAxisBlock << 1) | eLogicalEdgeEnd, // 0x1 + eLogicalSideIStart = (eLogicalAxisInline << 1) | eLogicalEdgeStart, // 0x2 + eLogicalSideIEnd = (eLogicalAxisInline << 1) | eLogicalEdgeEnd // 0x3 +}; +constexpr auto AllLogicalSides() { + return mozilla::MakeInclusiveEnumeratedRange(eLogicalSideBStart, + eLogicalSideIEnd); +} + +enum LogicalCorner { + eLogicalCornerBStartIStart = 0, + eLogicalCornerBStartIEnd = 1, + eLogicalCornerBEndIEnd = 2, + eLogicalCornerBEndIStart = 3 +}; + +// Physical axis constants. +enum PhysicalAxis { eAxisVertical = 0x0, eAxisHorizontal = 0x1 }; + +// Represents zero or more physical axes. +enum class PhysicalAxes : uint8_t { + None = 0x0, + Horizontal = 0x1, + Vertical = 0x2, + Both = Horizontal | Vertical, +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PhysicalAxes) + +inline LogicalAxis GetOrthogonalAxis(LogicalAxis aAxis) { + return aAxis == eLogicalAxisBlock ? eLogicalAxisInline : eLogicalAxisBlock; +} + +inline bool IsInline(LogicalSide aSide) { return aSide & 0x2; } +inline bool IsBlock(LogicalSide aSide) { return !IsInline(aSide); } +inline bool IsEnd(LogicalSide aSide) { return aSide & 0x1; } +inline bool IsStart(LogicalSide aSide) { return !IsEnd(aSide); } + +inline LogicalAxis GetAxis(LogicalSide aSide) { + return IsInline(aSide) ? eLogicalAxisInline : eLogicalAxisBlock; +} + +inline LogicalEdge GetEdge(LogicalSide aSide) { + return IsEnd(aSide) ? eLogicalEdgeEnd : eLogicalEdgeStart; +} + +inline LogicalEdge GetOppositeEdge(LogicalEdge aEdge) { + // This relies on the only two LogicalEdge enum values being 0 and 1. + return LogicalEdge(1 - aEdge); +} + +inline LogicalSide MakeLogicalSide(LogicalAxis aAxis, LogicalEdge aEdge) { + return LogicalSide((aAxis << 1) | aEdge); +} + +inline LogicalSide GetOppositeSide(LogicalSide aSide) { + return MakeLogicalSide(GetAxis(aSide), GetOppositeEdge(GetEdge(aSide))); +} + +enum LogicalSideBits { + eLogicalSideBitsNone = 0, + eLogicalSideBitsBStart = 1 << eLogicalSideBStart, + eLogicalSideBitsBEnd = 1 << eLogicalSideBEnd, + eLogicalSideBitsIEnd = 1 << eLogicalSideIEnd, + eLogicalSideBitsIStart = 1 << eLogicalSideIStart, + eLogicalSideBitsBBoth = eLogicalSideBitsBStart | eLogicalSideBitsBEnd, + eLogicalSideBitsIBoth = eLogicalSideBitsIStart | eLogicalSideBitsIEnd, + eLogicalSideBitsAll = eLogicalSideBitsBBoth | eLogicalSideBitsIBoth +}; + +enum LineRelativeDir { + eLineRelativeDirOver = eLogicalSideBStart, + eLineRelativeDirUnder = eLogicalSideBEnd, + eLineRelativeDirLeft = eLogicalSideIStart, + eLineRelativeDirRight = eLogicalSideIEnd +}; + +/** + * mozilla::WritingMode is an immutable class representing a + * writing mode. + * + * It efficiently stores the writing mode and can rapidly compute + * interesting things about it for use in layout. + * + * Writing modes are computed from the CSS 'direction', + * 'writing-mode', and 'text-orientation' properties. + * See CSS3 Writing Modes for more information + * http://www.w3.org/TR/css3-writing-modes/ + */ +class WritingMode { + public: + /** + * Absolute inline flow direction + */ + enum InlineDir { + eInlineLTR = 0x00, // text flows horizontally left to right + eInlineRTL = 0x02, // text flows horizontally right to left + eInlineTTB = 0x01, // text flows vertically top to bottom + eInlineBTT = 0x03, // text flows vertically bottom to top + }; + + /** + * Absolute block flow direction + */ + enum BlockDir { + eBlockTB = 0x00, // horizontal lines stack top to bottom + eBlockRL = 0x01, // vertical lines stack right to left + eBlockLR = 0x05, // vertical lines stack left to right + }; + + /** + * Line-relative (bidi-relative) inline flow direction + */ + enum BidiDir { + eBidiLTR = 0x00, // inline flow matches bidi LTR text + eBidiRTL = 0x10, // inline flow matches bidi RTL text + }; + + /** + * Unknown writing mode (should never actually be stored or used anywhere). + */ + enum { eUnknownWritingMode = 0xff }; + + /** + * Return the absolute inline flow direction as an InlineDir + */ + InlineDir GetInlineDir() const { + return InlineDir(mWritingMode.bits & eInlineMask); + } + + /** + * Return the absolute block flow direction as a BlockDir + */ + BlockDir GetBlockDir() const { + return BlockDir(mWritingMode.bits & eBlockMask); + } + + /** + * Return the line-relative inline flow direction as a BidiDir + */ + BidiDir GetBidiDir() const { + return BidiDir((mWritingMode & StyleWritingMode::RTL).bits); + } + + /** + * Return true if the inline flow direction is against physical direction + * (i.e. right-to-left or bottom-to-top). + * This occurs when writing-mode is sideways-lr OR direction is rtl (but not + * if both of those are true). + */ + bool IsInlineReversed() const { + return !!(mWritingMode & StyleWritingMode::INLINE_REVERSED); + } + + /** + * Return true if bidi direction is LTR. (Convenience method) + */ + bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); } + + /** + * Return true if bidi direction is RTL. (Convenience method) + */ + bool IsBidiRTL() const { return eBidiRTL == GetBidiDir(); } + + /** + * True if it is vertical and vertical-lr, or is horizontal and bidi LTR. + */ + bool IsPhysicalLTR() const { + return IsVertical() ? IsVerticalLR() : IsBidiLTR(); + } + + /** + * True if it is vertical and vertical-rl, or is horizontal and bidi RTL. + */ + bool IsPhysicalRTL() const { + return IsVertical() ? IsVerticalRL() : IsBidiRTL(); + } + + /** + * True if vertical-mode block direction is LR (convenience method). + */ + bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); } + + /** + * True if vertical-mode block direction is RL (convenience method). + */ + bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); } + + /** + * True if vertical writing mode, i.e. when + * writing-mode: vertical-lr | vertical-rl. + */ + bool IsVertical() const { + return !!(mWritingMode & StyleWritingMode::VERTICAL); + } + + /** + * True if line-over/line-under are inverted from block-start/block-end. + * This is true only when writing-mode is vertical-lr. + */ + bool IsLineInverted() const { + return !!(mWritingMode & StyleWritingMode::LINE_INVERTED); + } + + /** + * Block-axis flow-relative to line-relative factor. + * May be used as a multiplication factor for block-axis coordinates + * to convert between flow- and line-relative coordinate systems (e.g. + * positioning an over- or under-line decoration). + */ + int FlowRelativeToLineRelativeFactor() const { + return IsLineInverted() ? -1 : 1; + } + + /** + * True if vertical sideways writing mode, i.e. when + * writing-mode: sideways-lr | sideways-rl. + */ + bool IsVerticalSideways() const { + return !!(mWritingMode & StyleWritingMode::VERTICAL_SIDEWAYS); + } + + /** + * True if this is writing-mode: sideways-rl (convenience method). + */ + bool IsSidewaysRL() const { return IsVerticalRL() && IsVerticalSideways(); } + + /** + * True if this is writing-mode: sideways-lr (convenience method). + */ + bool IsSidewaysLR() const { return IsVerticalLR() && IsVerticalSideways(); } + + /** + * True if either text-orientation or writing-mode will force all text to be + * rendered sideways in vertical lines, in which case we should prefer an + * alphabetic baseline; otherwise, the default is centered. + * + * Note that some glyph runs may be rendered sideways even if this is false, + * due to text-orientation:mixed resolution, but in that case the dominant + * baseline remains centered. + */ + bool IsSideways() const { + return !!(mWritingMode & (StyleWritingMode::VERTICAL_SIDEWAYS | + StyleWritingMode::TEXT_SIDEWAYS)); + } + +#ifdef DEBUG + // Used by CHECK_WRITING_MODE to compare modes without regard for the + // StyleWritingMode::VERTICAL_SIDEWAYS or StyleWritingMode::TEXT_SIDEWAYS + // flags. + WritingMode IgnoreSideways() const { + return WritingMode( + mWritingMode.bits & + ~(StyleWritingMode::VERTICAL_SIDEWAYS | StyleWritingMode::TEXT_SIDEWAYS) + .bits); + } +#endif + + /** + * Return true if boxes with this writing mode should use central baselines. + */ + bool IsCentralBaseline() const { return IsVertical() && !IsSideways(); } + + /** + * Return true if boxes with this writing mode should use alphabetical + * baselines. + */ + bool IsAlphabeticalBaseline() const { return !IsCentralBaseline(); } + + static mozilla::PhysicalAxis PhysicalAxisForLogicalAxis( + uint8_t aWritingModeValue, LogicalAxis aAxis) { + // This relies on bit 0 of a writing-value mode indicating vertical + // orientation and bit 0 of a LogicalAxis value indicating the inline axis, + // so that it can correctly form mozilla::PhysicalAxis values using bit + // manipulation. + static_assert(uint8_t(StyleWritingModeProperty::HorizontalTb) == 0 && + uint8_t(StyleWritingModeProperty::VerticalRl) == 1 && + uint8_t(StyleWritingModeProperty::VerticalLr) == 3 && + eLogicalAxisBlock == 0 && eLogicalAxisInline == 1 && + eAxisVertical == 0 && eAxisHorizontal == 1, + "unexpected writing-mode, logical axis or physical axis " + "constant values"); + return mozilla::PhysicalAxis((aWritingModeValue ^ aAxis) & 0x1); + } + + mozilla::PhysicalAxis PhysicalAxis(LogicalAxis aAxis) const { + // This will set wm to either StyleWritingModel::HorizontalTB or + // StyleWritingModeProperty::VerticalRL, and not the other two (real + // and hypothetical) values. But this is fine; we only need to + // distinguish between vertical and horizontal in + // PhysicalAxisForLogicalAxis. + const auto wm = (mWritingMode & StyleWritingMode::VERTICAL).bits; + return PhysicalAxisForLogicalAxis(wm, aAxis); + } + + static mozilla::Side PhysicalSideForBlockAxis(uint8_t aWritingModeValue, + LogicalEdge aEdge) { + // indexes are StyleWritingModeProperty values, which are the same as these + // two-bit values: + // bit 0 = the StyleWritingMode::VERTICAL value + // bit 1 = the StyleWritingMode::VERTICAL_LR value + static const mozilla::Side kLogicalBlockSides[][2] = { + {eSideTop, eSideBottom}, // horizontal-tb + {eSideRight, eSideLeft}, // vertical-rl + {eSideBottom, eSideTop}, // (horizontal-bt) + {eSideLeft, eSideRight}, // vertical-lr + }; + + // Ignore the SidewaysMask bit of the writing-mode value, as this has no + // effect on the side mappings. + aWritingModeValue &= ~kWritingModeSidewaysMask; + + // What's left of the writing-mode should be in the range 0-3: + NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value"); + + return kLogicalBlockSides[aWritingModeValue][aEdge]; + } + + mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const { + // indexes are four-bit values: + // bit 0 = the StyleWritingMode::VERTICAL value + // bit 1 = the StyleWritingMode::INLINE_REVERSED value + // bit 2 = the StyleWritingMode::VERTICAL_LR value + // bit 3 = the StyleWritingMode::LINE_INVERTED value + // Not all of these combinations can actually be specified via CSS: there + // is no horizontal-bt writing-mode, and no text-orientation value that + // produces "inverted" text. (The former 'sideways-left' value, no longer + // in the spec, would have produced this in vertical-rl mode.) + static const mozilla::Side kLogicalInlineSides[][2] = { + {eSideLeft, eSideRight}, // horizontal-tb ltr + {eSideTop, eSideBottom}, // vertical-rl ltr + {eSideRight, eSideLeft}, // horizontal-tb rtl + {eSideBottom, eSideTop}, // vertical-rl rtl + {eSideRight, eSideLeft}, // (horizontal-bt) (inverted) ltr + {eSideTop, eSideBottom}, // sideways-lr rtl + {eSideLeft, eSideRight}, // (horizontal-bt) (inverted) rtl + {eSideBottom, eSideTop}, // sideways-lr ltr + {eSideLeft, eSideRight}, // horizontal-tb (inverted) rtl + {eSideTop, eSideBottom}, // vertical-rl (inverted) rtl + {eSideRight, eSideLeft}, // horizontal-tb (inverted) ltr + {eSideBottom, eSideTop}, // vertical-rl (inverted) ltr + {eSideLeft, eSideRight}, // (horizontal-bt) ltr + {eSideTop, eSideBottom}, // vertical-lr ltr + {eSideRight, eSideLeft}, // (horizontal-bt) rtl + {eSideBottom, eSideTop}, // vertical-lr rtl + }; + + // Inline axis sides depend on all three of writing-mode, text-orientation + // and direction, which are encoded in the StyleWritingMode::VERTICAL, + // StyleWritingMode::INLINE_REVERSED, StyleWritingMode::VERTICAL_LR and + // StyleWritingMode::LINE_INVERTED bits. Use these four bits to index into + // kLogicalInlineSides. + MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 && + StyleWritingMode::INLINE_REVERSED.bits == 0x02 && + StyleWritingMode::VERTICAL_LR.bits == 0x04 && + StyleWritingMode::LINE_INVERTED.bits == 0x08, + "unexpected mask values"); + int index = mWritingMode.bits & 0x0F; + return kLogicalInlineSides[index][aEdge]; + } + + /** + * Returns the physical side corresponding to the specified logical side, + * given the current writing mode. + */ + mozilla::Side PhysicalSide(LogicalSide aSide) const { + if (IsBlock(aSide)) { + MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 && + StyleWritingMode::VERTICAL_LR.bits == 0x04, + "unexpected mask values"); + const uint8_t wm = + ((mWritingMode & StyleWritingMode::VERTICAL_LR).bits >> 1) | + (mWritingMode & StyleWritingMode::VERTICAL).bits; + return PhysicalSideForBlockAxis(wm, GetEdge(aSide)); + } + + return PhysicalSideForInlineAxis(GetEdge(aSide)); + } + + /** + * Returns the logical side corresponding to the specified physical side, + * given the current writing mode. + * (This is the inverse of the PhysicalSide() method above.) + */ + LogicalSide LogicalSideForPhysicalSide(mozilla::Side aSide) const { + // clang-format off + // indexes are four-bit values: + // bit 0 = the StyleWritingMode::VERTICAL value + // bit 1 = the StyleWritingMode::INLINE_REVERSED value + // bit 2 = the StyleWritingMode::VERTICAL_LR value + // bit 3 = the StyleWritingMode::LINE_INVERTED value + static const LogicalSide kPhysicalToLogicalSides[][4] = { + // top right + // bottom left + { eLogicalSideBStart, eLogicalSideIEnd, + eLogicalSideBEnd, eLogicalSideIStart }, // horizontal-tb ltr + { eLogicalSideIStart, eLogicalSideBStart, + eLogicalSideIEnd, eLogicalSideBEnd }, // vertical-rl ltr + { eLogicalSideBStart, eLogicalSideIStart, + eLogicalSideBEnd, eLogicalSideIEnd }, // horizontal-tb rtl + { eLogicalSideIEnd, eLogicalSideBStart, + eLogicalSideIStart, eLogicalSideBEnd }, // vertical-rl rtl + { eLogicalSideBEnd, eLogicalSideIStart, + eLogicalSideBStart, eLogicalSideIEnd }, // (horizontal-bt) (inv) ltr + { eLogicalSideIStart, eLogicalSideBEnd, + eLogicalSideIEnd, eLogicalSideBStart }, // vertical-lr sw-left rtl + { eLogicalSideBEnd, eLogicalSideIEnd, + eLogicalSideBStart, eLogicalSideIStart }, // (horizontal-bt) (inv) rtl + { eLogicalSideIEnd, eLogicalSideBEnd, + eLogicalSideIStart, eLogicalSideBStart }, // vertical-lr sw-left ltr + { eLogicalSideBStart, eLogicalSideIEnd, + eLogicalSideBEnd, eLogicalSideIStart }, // horizontal-tb (inv) rtl + { eLogicalSideIStart, eLogicalSideBStart, + eLogicalSideIEnd, eLogicalSideBEnd }, // vertical-rl sw-left rtl + { eLogicalSideBStart, eLogicalSideIStart, + eLogicalSideBEnd, eLogicalSideIEnd }, // horizontal-tb (inv) ltr + { eLogicalSideIEnd, eLogicalSideBStart, + eLogicalSideIStart, eLogicalSideBEnd }, // vertical-rl sw-left ltr + { eLogicalSideBEnd, eLogicalSideIEnd, + eLogicalSideBStart, eLogicalSideIStart }, // (horizontal-bt) ltr + { eLogicalSideIStart, eLogicalSideBEnd, + eLogicalSideIEnd, eLogicalSideBStart }, // vertical-lr ltr + { eLogicalSideBEnd, eLogicalSideIStart, + eLogicalSideBStart, eLogicalSideIEnd }, // (horizontal-bt) rtl + { eLogicalSideIEnd, eLogicalSideBEnd, + eLogicalSideIStart, eLogicalSideBStart }, // vertical-lr rtl + }; + // clang-format on + + MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 && + StyleWritingMode::INLINE_REVERSED.bits == 0x02 && + StyleWritingMode::VERTICAL_LR.bits == 0x04 && + StyleWritingMode::LINE_INVERTED.bits == 0x08, + "unexpected mask values"); + int index = mWritingMode.bits & 0x0F; + return kPhysicalToLogicalSides[index][aSide]; + } + + /** + * Returns the logical side corresponding to the specified + * line-relative direction, given the current writing mode. + */ + LogicalSide LogicalSideForLineRelativeDir(LineRelativeDir aDir) const { + auto side = static_cast<LogicalSide>(aDir); + if (IsInline(side)) { + return IsBidiLTR() ? side : GetOppositeSide(side); + } + return !IsLineInverted() ? side : GetOppositeSide(side); + } + + /** + * Default constructor gives us a horizontal, LTR writing mode. + * XXX We will probably eliminate this and require explicit initialization + * in all cases once transition is complete. + */ + WritingMode() : mWritingMode{0} {} + + /** + * Construct writing mode based on a ComputedStyle. + */ + explicit WritingMode(const ComputedStyle* aComputedStyle) { + NS_ASSERTION(aComputedStyle, "we need an ComputedStyle here"); + mWritingMode = aComputedStyle->WritingMode(); + } + + /** + * This function performs fixup for elements with 'unicode-bidi: plaintext', + * where inline directionality is derived from the Unicode bidi categories + * of the element's content, and not the CSS 'direction' property. + * + * The WritingMode constructor will have already incorporated the 'direction' + * property into our flag bits, so such elements need to use this method + * (after resolving the bidi level of their content) to update the direction + * bits as needed. + * + * If it turns out that our bidi direction already matches what plaintext + * resolution determined, there's nothing to do here. If it didn't (i.e. if + * the rtl-ness doesn't match), then we correct the direction by flipping the + * same bits that get flipped in the constructor's CSS 'direction'-based + * chunk. + * + * XXX change uint8_t to UBiDiLevel after bug 924851 + */ + void SetDirectionFromBidiLevel(mozilla::intl::BidiEmbeddingLevel level) { + if (level.IsRTL() == IsBidiLTR()) { + mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED; + } + } + + /** + * Compare two WritingModes for equality. + */ + bool operator==(const WritingMode& aOther) const { + return mWritingMode == aOther.mWritingMode; + } + + bool operator!=(const WritingMode& aOther) const { + return mWritingMode != aOther.mWritingMode; + } + + /** + * Check whether two modes are orthogonal to each other. + */ + bool IsOrthogonalTo(const WritingMode& aOther) const { + return IsVertical() != aOther.IsVertical(); + } + + /** + * Returns true if this WritingMode's aLogicalAxis has the same physical + * start side as the parallel axis of WritingMode |aOther|. + * + * @param aLogicalAxis The axis to compare from this WritingMode. + * @param aOther The other WritingMode (from which we'll choose the axis + * that's parallel to this WritingMode's aLogicalAxis, for + * comparison). + */ + bool ParallelAxisStartsOnSameSide(LogicalAxis aLogicalAxis, + const WritingMode& aOther) const { + mozilla::Side myStartSide = + this->PhysicalSide(MakeLogicalSide(aLogicalAxis, eLogicalEdgeStart)); + + // Figure out which of aOther's axes is parallel to |this| WritingMode's + // aLogicalAxis, and get its physical start side as well. + LogicalAxis otherWMAxis = aOther.IsOrthogonalTo(*this) + ? GetOrthogonalAxis(aLogicalAxis) + : aLogicalAxis; + mozilla::Side otherWMStartSide = + aOther.PhysicalSide(MakeLogicalSide(otherWMAxis, eLogicalEdgeStart)); + + NS_ASSERTION(myStartSide % 2 == otherWMStartSide % 2, + "Should end up with sides in the same physical axis"); + return myStartSide == otherWMStartSide; + } + + uint8_t GetBits() const { return mWritingMode.bits; } + + private: + friend class LogicalPoint; + friend class LogicalSize; + friend struct LogicalSides; + friend class LogicalMargin; + friend class LogicalRect; + + friend struct IPC::ParamTraits<WritingMode>; + // IMENotification cannot store this class directly since this has some + // constructors. Therefore, it stores mWritingMode and recreate the + // instance from it. + friend struct widget::IMENotification; + + /** + * Return a WritingMode representing an unknown value. + */ + static inline WritingMode Unknown() { + return WritingMode(eUnknownWritingMode); + } + + /** + * Constructing a WritingMode with an arbitrary value is a private operation + * currently only used by the Unknown() and IgnoreSideways() methods. + */ + explicit WritingMode(uint8_t aValue) : mWritingMode{aValue} {} + + StyleWritingMode mWritingMode; + + enum Masks { + // Masks for output enums + eInlineMask = 0x03, // VERTICAL | INLINE_REVERSED + eBlockMask = 0x05, // VERTICAL | VERTICAL_LR + }; +}; + +inline std::ostream& operator<<(std::ostream& aStream, const WritingMode& aWM) { + return aStream << (aWM.IsVertical() + ? aWM.IsVerticalLR() ? aWM.IsBidiLTR() + ? aWM.IsSideways() + ? "sw-lr-ltr" + : "v-lr-ltr" + : aWM.IsSideways() ? "sw-lr-rtl" + : "v-lr-rtl" + : aWM.IsBidiLTR() + ? aWM.IsSideways() ? "sw-rl-ltr" : "v-rl-ltr" + : aWM.IsSideways() ? "sw-rl-rtl" + : "v-rl-rtl" + : aWM.IsBidiLTR() ? "h-ltr" + : "h-rtl"); +} + +/** + * Logical-coordinate classes: + * + * There are three sets of coordinate space: + * - physical (top, left, bottom, right) + * relative to graphics coord system + * - flow-relative (block-start, inline-start, block-end, inline-end) + * relative to block/inline flow directions + * - line-relative (line-over, line-left, line-under, line-right) + * relative to glyph orientation / inline bidi directions + * See CSS3 Writing Modes for more information + * http://www.w3.org/TR/css3-writing-modes/#abstract-box + * + * For shorthand, B represents the block-axis + * I represents the inline-axis + * + * The flow-relative geometric classes store coords in flow-relative space. + * They use a private ns{Point,Size,Rect,Margin} member to store the actual + * coordinate values, but reinterpret them as logical instead of physical. + * This allows us to easily perform calculations in logical space (provided + * writing modes of the operands match), by simply mapping to nsPoint (etc) + * methods. + * + * Physical-coordinate accessors/setters are responsible to translate these + * internal logical values as necessary. + * + * In DEBUG builds, the logical types store their WritingMode and check + * that the same WritingMode is passed whenever callers ask them to do a + * writing-mode-dependent operation. Non-DEBUG builds do NOT check this, + * to avoid the overhead of storing WritingMode fields. + * + * Open question: do we need a different set optimized for line-relative + * math, for use in nsLineLayout and the like? Or is multiplying values + * by FlowRelativeToLineRelativeFactor() enough? + */ + +/** + * Flow-relative point + */ +class LogicalPoint { + public: + explicit LogicalPoint(WritingMode aWritingMode) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mPoint(0, 0) { + } + + // Construct from a writing mode and individual coordinates (which MUST be + // values in that writing mode, NOT physical coordinates!) + LogicalPoint(WritingMode aWritingMode, nscoord aI, nscoord aB) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mPoint(aI, aB) { + } + + // Construct from a writing mode and a physical point, within a given + // containing rectangle's size (defining the conversion between LTR + // and RTL coordinates, and between TTB and BTT coordinates). + LogicalPoint(WritingMode aWritingMode, const nsPoint& aPoint, + const nsSize& aContainerSize) +#ifdef DEBUG + : mWritingMode(aWritingMode) +#endif + { + if (aWritingMode.IsVertical()) { + I() = aWritingMode.IsInlineReversed() ? aContainerSize.height - aPoint.y + : aPoint.y; + B() = aWritingMode.IsVerticalLR() ? aPoint.x + : aContainerSize.width - aPoint.x; + } else { + I() = aWritingMode.IsInlineReversed() ? aContainerSize.width - aPoint.x + : aPoint.x; + B() = aPoint.y; + } + } + + /** + * Read-only (const) access to the logical coordinates. + */ + nscoord I(WritingMode aWritingMode) const // inline-axis + { + CHECK_WRITING_MODE(aWritingMode); + return mPoint.x; + } + nscoord B(WritingMode aWritingMode) const // block-axis + { + CHECK_WRITING_MODE(aWritingMode); + return mPoint.y; + } + nscoord Pos(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM); + } + nscoord LineRelative(WritingMode aWritingMode, + const nsSize& aContainerSize) const // line-axis + { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsBidiLTR()) { + return I(); + } + return (aWritingMode.IsVertical() ? aContainerSize.height + : aContainerSize.width) - + I(); + } + + /** + * These non-const accessors return a reference (lvalue) that can be + * assigned to by callers. + */ + nscoord& I(WritingMode aWritingMode) // inline-axis + { + CHECK_WRITING_MODE(aWritingMode); + return mPoint.x; + } + nscoord& B(WritingMode aWritingMode) // block-axis + { + CHECK_WRITING_MODE(aWritingMode); + return mPoint.y; + } + nscoord& Pos(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM); + } + + /** + * Return a physical point corresponding to our logical coordinates, + * converted according to our writing mode. + */ + nsPoint GetPhysicalPoint(WritingMode aWritingMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return nsPoint( + aWritingMode.IsVerticalLR() ? B() : aContainerSize.width - B(), + aWritingMode.IsInlineReversed() ? aContainerSize.height - I() : I()); + } else { + return nsPoint( + aWritingMode.IsInlineReversed() ? aContainerSize.width - I() : I(), + B()); + } + } + + /** + * Return the equivalent point in a different writing mode. + */ + LogicalPoint ConvertTo(WritingMode aToMode, WritingMode aFromMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aFromMode); + return aToMode == aFromMode + ? *this + : LogicalPoint(aToMode, + GetPhysicalPoint(aFromMode, aContainerSize), + aContainerSize); + } + + bool operator==(const LogicalPoint& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return mPoint == aOther.mPoint; + } + + bool operator!=(const LogicalPoint& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return mPoint != aOther.mPoint; + } + + LogicalPoint operator+(const LogicalPoint& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + // In non-debug builds, LogicalPoint does not store the WritingMode, + // so the first parameter here (which will always be eUnknownWritingMode) + // is ignored. + return LogicalPoint(GetWritingMode(), mPoint.x + aOther.mPoint.x, + mPoint.y + aOther.mPoint.y); + } + + LogicalPoint& operator+=(const LogicalPoint& aOther) { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + I() += aOther.I(); + B() += aOther.B(); + return *this; + } + + LogicalPoint operator-(const LogicalPoint& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + // In non-debug builds, LogicalPoint does not store the WritingMode, + // so the first parameter here (which will always be eUnknownWritingMode) + // is ignored. + return LogicalPoint(GetWritingMode(), mPoint.x - aOther.mPoint.x, + mPoint.y - aOther.mPoint.y); + } + + LogicalPoint& operator-=(const LogicalPoint& aOther) { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + I() -= aOther.I(); + B() -= aOther.B(); + return *this; + } + + friend std::ostream& operator<<(std::ostream& aStream, + const LogicalPoint& aPoint) { + return aStream << aPoint.mPoint; + } + + private: + friend class LogicalRect; + + /** + * NOTE that in non-DEBUG builds, GetWritingMode() always returns + * eUnknownWritingMode, as the current mode is not stored in the logical- + * geometry classes. Therefore, this method is private; it is used ONLY + * by the DEBUG-mode checking macros in this class and its friends; + * other code is not allowed to ask a logical point for its writing mode, + * as this info will simply not be available in non-DEBUG builds. + * + * Also, in non-DEBUG builds, CHECK_WRITING_MODE does nothing, and the + * WritingMode parameter to logical methods will generally be optimized + * away altogether. + */ +#ifdef DEBUG + WritingMode GetWritingMode() const { return mWritingMode; } +#else + WritingMode GetWritingMode() const { return WritingMode::Unknown(); } +#endif + + // We don't allow construction of a LogicalPoint with no writing mode. + LogicalPoint() = delete; + + // Accessors that don't take or check a WritingMode value. + // These are for internal use only; they are called by methods that have + // themselves already checked the WritingMode passed by the caller. + nscoord I() const // inline-axis + { + return mPoint.x; + } + nscoord B() const // block-axis + { + return mPoint.y; + } + + nscoord& I() // inline-axis + { + return mPoint.x; + } + nscoord& B() // block-axis + { + return mPoint.y; + } + +#ifdef DEBUG + WritingMode mWritingMode; +#endif + + // We use an nsPoint to hold the coordinates, but reinterpret its .x and .y + // fields as the inline and block directions. Hence, this is not exposed + // directly, but only through accessors that will map them according to the + // writing mode. + nsPoint mPoint; +}; + +/** + * Flow-relative size + */ +class LogicalSize { + public: + explicit LogicalSize(WritingMode aWritingMode) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mSize(0, 0) { + } + + LogicalSize(WritingMode aWritingMode, nscoord aISize, nscoord aBSize) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mSize(aISize, aBSize) { + } + + LogicalSize(WritingMode aWritingMode, const nsSize& aPhysicalSize) +#ifdef DEBUG + : mWritingMode(aWritingMode) +#endif + { + if (aWritingMode.IsVertical()) { + ISize() = aPhysicalSize.height; + BSize() = aPhysicalSize.width; + } else { + ISize() = aPhysicalSize.width; + BSize() = aPhysicalSize.height; + } + } + + void SizeTo(WritingMode aWritingMode, nscoord aISize, nscoord aBSize) { + CHECK_WRITING_MODE(aWritingMode); + mSize.SizeTo(aISize, aBSize); + } + + /** + * Dimensions in logical and physical terms + */ + nscoord ISize(WritingMode aWritingMode) const // inline-size + { + CHECK_WRITING_MODE(aWritingMode); + return mSize.width; + } + nscoord BSize(WritingMode aWritingMode) const // block-size + { + CHECK_WRITING_MODE(aWritingMode); + return mSize.height; + } + nscoord Size(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM); + } + + nscoord Width(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? BSize() : ISize(); + } + nscoord Height(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? ISize() : BSize(); + } + + /** + * Writable references to the logical dimensions + */ + nscoord& ISize(WritingMode aWritingMode) // inline-size + { + CHECK_WRITING_MODE(aWritingMode); + return mSize.width; + } + nscoord& BSize(WritingMode aWritingMode) // block-size + { + CHECK_WRITING_MODE(aWritingMode); + return mSize.height; + } + nscoord& Size(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM); + } + + /** + * Return an nsSize containing our physical dimensions + */ + nsSize GetPhysicalSize(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? nsSize(BSize(), ISize()) + : nsSize(ISize(), BSize()); + } + + /** + * Return a LogicalSize representing this size in a different writing mode + */ + LogicalSize ConvertTo(WritingMode aToMode, WritingMode aFromMode) const { +#ifdef DEBUG + // In DEBUG builds make sure to return a LogicalSize with the + // expected writing mode + CHECK_WRITING_MODE(aFromMode); + return aToMode == aFromMode + ? *this + : LogicalSize(aToMode, GetPhysicalSize(aFromMode)); +#else + // optimization for non-DEBUG builds where LogicalSize doesn't store + // the writing mode + return (aToMode == aFromMode || !aToMode.IsOrthogonalTo(aFromMode)) + ? *this + : LogicalSize(aToMode, BSize(), ISize()); +#endif + } + + /** + * Test if a size is (0, 0). + */ + bool IsAllZero() const { return ISize() == 0 && BSize() == 0; } + + /** + * Various binary operators on LogicalSize. These are valid ONLY for operands + * that share the same writing mode. + */ + bool operator==(const LogicalSize& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return mSize == aOther.mSize; + } + + bool operator!=(const LogicalSize& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return mSize != aOther.mSize; + } + + LogicalSize operator+(const LogicalSize& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return LogicalSize(GetWritingMode(), ISize() + aOther.ISize(), + BSize() + aOther.BSize()); + } + LogicalSize& operator+=(const LogicalSize& aOther) { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + ISize() += aOther.ISize(); + BSize() += aOther.BSize(); + return *this; + } + + LogicalSize operator-(const LogicalSize& aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return LogicalSize(GetWritingMode(), ISize() - aOther.ISize(), + BSize() - aOther.BSize()); + } + LogicalSize& operator-=(const LogicalSize& aOther) { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + ISize() -= aOther.ISize(); + BSize() -= aOther.BSize(); + return *this; + } + + friend std::ostream& operator<<(std::ostream& aStream, + const LogicalSize& aSize) { + return aStream << aSize.mSize; + } + + private: + friend class LogicalRect; + + LogicalSize() = delete; + +#ifdef DEBUG + WritingMode GetWritingMode() const { return mWritingMode; } +#else + WritingMode GetWritingMode() const { return WritingMode::Unknown(); } +#endif + + nscoord ISize() const // inline-size + { + return mSize.width; + } + nscoord BSize() const // block-size + { + return mSize.height; + } + + nscoord& ISize() // inline-size + { + return mSize.width; + } + nscoord& BSize() // block-size + { + return mSize.height; + } + +#ifdef DEBUG + WritingMode mWritingMode; +#endif + nsSize mSize; +}; + +/** + * LogicalSides represents a set of logical sides. + */ +struct LogicalSides final { + explicit LogicalSides(WritingMode aWritingMode) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mBits(0) { + } + LogicalSides(WritingMode aWritingMode, LogicalSideBits aSideBits) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mBits(aSideBits) { + MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits"); + } + bool IsEmpty() const { return mBits == 0; } + bool BStart() const { return mBits & eLogicalSideBitsBStart; } + bool BEnd() const { return mBits & eLogicalSideBitsBEnd; } + bool IStart() const { return mBits & eLogicalSideBitsIStart; } + bool IEnd() const { return mBits & eLogicalSideBitsIEnd; } + bool Contains(LogicalSideBits aSideBits) const { + MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits"); + return (mBits & aSideBits) == aSideBits; + } + LogicalSides operator|(LogicalSides aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return *this | LogicalSideBits(aOther.mBits); + } + LogicalSides operator|(LogicalSideBits aSideBits) const { + return LogicalSides(GetWritingMode(), LogicalSideBits(mBits | aSideBits)); + } + LogicalSides& operator|=(LogicalSides aOther) { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return *this |= LogicalSideBits(aOther.mBits); + } + LogicalSides& operator|=(LogicalSideBits aSideBits) { + mBits |= aSideBits; + return *this; + } + bool operator==(LogicalSides aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return mBits == aOther.mBits; + } + bool operator!=(LogicalSides aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + return !(*this == aOther); + } + +#ifdef DEBUG + WritingMode GetWritingMode() const { return mWritingMode; } +#else + WritingMode GetWritingMode() const { return WritingMode::Unknown(); } +#endif + + private: +#ifdef DEBUG + WritingMode mWritingMode; +#endif + uint8_t mBits; +}; + +/** + * Flow-relative margin + */ +class LogicalMargin { + public: + explicit LogicalMargin(WritingMode aWritingMode) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mMargin(0, 0, 0, 0) { + } + + LogicalMargin(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd, + nscoord aBEnd, nscoord aIStart) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mMargin(aBStart, aIEnd, aBEnd, aIStart) { + } + + LogicalMargin(WritingMode aWritingMode, const nsMargin& aPhysicalMargin) +#ifdef DEBUG + : mWritingMode(aWritingMode) +#endif + { + if (aWritingMode.IsVertical()) { + if (aWritingMode.IsVerticalLR()) { + mMargin.top = aPhysicalMargin.left; + mMargin.bottom = aPhysicalMargin.right; + } else { + mMargin.top = aPhysicalMargin.right; + mMargin.bottom = aPhysicalMargin.left; + } + if (aWritingMode.IsInlineReversed()) { + mMargin.left = aPhysicalMargin.bottom; + mMargin.right = aPhysicalMargin.top; + } else { + mMargin.left = aPhysicalMargin.top; + mMargin.right = aPhysicalMargin.bottom; + } + } else { + mMargin.top = aPhysicalMargin.top; + mMargin.bottom = aPhysicalMargin.bottom; + if (aWritingMode.IsInlineReversed()) { + mMargin.left = aPhysicalMargin.right; + mMargin.right = aPhysicalMargin.left; + } else { + mMargin.left = aPhysicalMargin.left; + mMargin.right = aPhysicalMargin.right; + } + } + } + + nscoord IStart(WritingMode aWritingMode) const // inline-start margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.left; + } + nscoord IEnd(WritingMode aWritingMode) const // inline-end margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.right; + } + nscoord BStart(WritingMode aWritingMode) const // block-start margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.top; + } + nscoord BEnd(WritingMode aWritingMode) const // block-end margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.bottom; + } + nscoord Start(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM); + } + nscoord End(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM); + } + + nscoord& IStart(WritingMode aWritingMode) // inline-start margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.left; + } + nscoord& IEnd(WritingMode aWritingMode) // inline-end margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.right; + } + nscoord& BStart(WritingMode aWritingMode) // block-start margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.top; + } + nscoord& BEnd(WritingMode aWritingMode) // block-end margin + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.bottom; + } + nscoord& Start(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM); + } + nscoord& End(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM); + } + + nscoord IStartEnd(WritingMode aWritingMode) const // inline margins + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.LeftRight(); + } + nscoord BStartEnd(WritingMode aWritingMode) const // block margins + { + CHECK_WRITING_MODE(aWritingMode); + return mMargin.TopBottom(); + } + nscoord StartEnd(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? IStartEnd(aWM) : BStartEnd(aWM); + } + + nscoord Side(LogicalSide aSide, WritingMode aWM) const { + switch (aSide) { + case eLogicalSideBStart: + return BStart(aWM); + case eLogicalSideBEnd: + return BEnd(aWM); + case eLogicalSideIStart: + return IStart(aWM); + case eLogicalSideIEnd: + return IEnd(aWM); + } + + MOZ_ASSERT_UNREACHABLE("We should handle all sides!"); + return BStart(aWM); + } + nscoord& Side(LogicalSide aSide, WritingMode aWM) { + switch (aSide) { + case eLogicalSideBStart: + return BStart(aWM); + case eLogicalSideBEnd: + return BEnd(aWM); + case eLogicalSideIStart: + return IStart(aWM); + case eLogicalSideIEnd: + return IEnd(aWM); + } + + MOZ_ASSERT_UNREACHABLE("We should handle all sides!"); + return BStart(aWM); + } + + /* + * Return margin values for line-relative sides, as defined in + * http://www.w3.org/TR/css-writing-modes-3/#line-directions: + * + * line-left + * Nominally the side from which LTR text would start. + * line-right + * Nominally the side from which RTL text would start. (Opposite of + * line-left.) + */ + nscoord LineLeft(WritingMode aWritingMode) const { + // We don't need to CHECK_WRITING_MODE here because the IStart or IEnd + // accessor that we call will do it. + return aWritingMode.IsBidiLTR() ? IStart(aWritingMode) : IEnd(aWritingMode); + } + nscoord LineRight(WritingMode aWritingMode) const { + return aWritingMode.IsBidiLTR() ? IEnd(aWritingMode) : IStart(aWritingMode); + } + + /** + * Return a LogicalSize representing the total size of the inline- + * and block-dimension margins. + */ + LogicalSize Size(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return LogicalSize(aWritingMode, IStartEnd(), BStartEnd()); + } + + /** + * Accessors for physical margins, using our writing mode to convert from + * logical values. + */ + nscoord Top(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() + ? (aWritingMode.IsInlineReversed() ? IEnd() : IStart()) + : BStart(); + } + + nscoord Bottom(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() + ? (aWritingMode.IsInlineReversed() ? IStart() : IEnd()) + : BEnd(); + } + + nscoord Left(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() + ? (aWritingMode.IsVerticalLR() ? BStart() : BEnd()) + : (aWritingMode.IsInlineReversed() ? IEnd() : IStart()); + } + + nscoord Right(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() + ? (aWritingMode.IsVerticalLR() ? BEnd() : BStart()) + : (aWritingMode.IsInlineReversed() ? IStart() : IEnd()); + } + + nscoord LeftRight(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? BStartEnd() : IStartEnd(); + } + + nscoord TopBottom(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? IStartEnd() : BStartEnd(); + } + + void SizeTo(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd, + nscoord aBEnd, nscoord aIStart) { + CHECK_WRITING_MODE(aWritingMode); + mMargin.SizeTo(aBStart, aIEnd, aBEnd, aIStart); + } + + /** + * Return an nsMargin containing our physical coordinates + */ + nsMargin GetPhysicalMargin(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() + ? (aWritingMode.IsVerticalLR() + ? (aWritingMode.IsInlineReversed() + ? nsMargin(IEnd(), BEnd(), IStart(), BStart()) + : nsMargin(IStart(), BEnd(), IEnd(), BStart())) + : (aWritingMode.IsInlineReversed() + ? nsMargin(IEnd(), BStart(), IStart(), BEnd()) + : nsMargin(IStart(), BStart(), IEnd(), BEnd()))) + : (aWritingMode.IsInlineReversed() + ? nsMargin(BStart(), IStart(), BEnd(), IEnd()) + : nsMargin(BStart(), IEnd(), BEnd(), IStart())); + } + + /** + * Return a LogicalMargin representing this margin in a different + * writing mode + */ + LogicalMargin ConvertTo(WritingMode aToMode, WritingMode aFromMode) const { + CHECK_WRITING_MODE(aFromMode); + return aToMode == aFromMode + ? *this + : LogicalMargin(aToMode, GetPhysicalMargin(aFromMode)); + } + + LogicalMargin& ApplySkipSides(LogicalSides aSkipSides) { + CHECK_WRITING_MODE(aSkipSides.GetWritingMode()); + if (aSkipSides.BStart()) { + BStart() = 0; + } + if (aSkipSides.BEnd()) { + BEnd() = 0; + } + if (aSkipSides.IStart()) { + IStart() = 0; + } + if (aSkipSides.IEnd()) { + IEnd() = 0; + } + return *this; + } + + bool IsAllZero() const { + return (mMargin.left == 0 && mMargin.top == 0 && mMargin.right == 0 && + mMargin.bottom == 0); + } + + bool operator==(const LogicalMargin& aMargin) const { + CHECK_WRITING_MODE(aMargin.GetWritingMode()); + return mMargin == aMargin.mMargin; + } + + bool operator!=(const LogicalMargin& aMargin) const { + CHECK_WRITING_MODE(aMargin.GetWritingMode()); + return mMargin != aMargin.mMargin; + } + + LogicalMargin operator+(const LogicalMargin& aMargin) const { + CHECK_WRITING_MODE(aMargin.GetWritingMode()); + return LogicalMargin(GetWritingMode(), BStart() + aMargin.BStart(), + IEnd() + aMargin.IEnd(), BEnd() + aMargin.BEnd(), + IStart() + aMargin.IStart()); + } + + LogicalMargin operator+=(const LogicalMargin& aMargin) { + CHECK_WRITING_MODE(aMargin.GetWritingMode()); + mMargin += aMargin.mMargin; + return *this; + } + + LogicalMargin operator-(const LogicalMargin& aMargin) const { + CHECK_WRITING_MODE(aMargin.GetWritingMode()); + return LogicalMargin(GetWritingMode(), BStart() - aMargin.BStart(), + IEnd() - aMargin.IEnd(), BEnd() - aMargin.BEnd(), + IStart() - aMargin.IStart()); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const LogicalMargin& aMargin) { + return aStream << aMargin.mMargin; + } + + private: + friend class LogicalRect; + + LogicalMargin() = delete; + +#ifdef DEBUG + WritingMode GetWritingMode() const { return mWritingMode; } +#else + WritingMode GetWritingMode() const { return WritingMode::Unknown(); } +#endif + + nscoord IStart() const // inline-start margin + { + return mMargin.left; + } + nscoord IEnd() const // inline-end margin + { + return mMargin.right; + } + nscoord BStart() const // block-start margin + { + return mMargin.top; + } + nscoord BEnd() const // block-end margin + { + return mMargin.bottom; + } + + nscoord& IStart() // inline-start margin + { + return mMargin.left; + } + nscoord& IEnd() // inline-end margin + { + return mMargin.right; + } + nscoord& BStart() // block-start margin + { + return mMargin.top; + } + nscoord& BEnd() // block-end margin + { + return mMargin.bottom; + } + + nscoord IStartEnd() const // inline margins + { + return mMargin.LeftRight(); + } + nscoord BStartEnd() const // block margins + { + return mMargin.TopBottom(); + } + +#ifdef DEBUG + WritingMode mWritingMode; +#endif + nsMargin mMargin; +}; + +/** + * Flow-relative rectangle + */ +class LogicalRect { + public: + explicit LogicalRect(WritingMode aWritingMode) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mIStart(0), + mBStart(0), + mISize(0), + mBSize(0) { + } + + LogicalRect(WritingMode aWritingMode, nscoord aIStart, nscoord aBStart, + nscoord aISize, nscoord aBSize) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mIStart(aIStart), + mBStart(aBStart), + mISize(aISize), + mBSize(aBSize) { + } + + LogicalRect(WritingMode aWritingMode, const LogicalPoint& aOrigin, + const LogicalSize& aSize) + : +#ifdef DEBUG + mWritingMode(aWritingMode), +#endif + mIStart(aOrigin.mPoint.x), + mBStart(aOrigin.mPoint.y), + mISize(aSize.mSize.width), + mBSize(aSize.mSize.height) { + CHECK_WRITING_MODE(aOrigin.GetWritingMode()); + CHECK_WRITING_MODE(aSize.GetWritingMode()); + } + + LogicalRect(WritingMode aWritingMode, const nsRect& aRect, + const nsSize& aContainerSize) +#ifdef DEBUG + : mWritingMode(aWritingMode) +#endif + { + if (aWritingMode.IsVertical()) { + mBStart = aWritingMode.IsVerticalLR() + ? aRect.X() + : aContainerSize.width - aRect.XMost(); + mIStart = aWritingMode.IsInlineReversed() + ? aContainerSize.height - aRect.YMost() + : aRect.Y(); + mBSize = aRect.Width(); + mISize = aRect.Height(); + } else { + mIStart = aWritingMode.IsInlineReversed() + ? aContainerSize.width - aRect.XMost() + : aRect.X(); + mBStart = aRect.Y(); + mISize = aRect.Width(); + mBSize = aRect.Height(); + } + } + + /** + * Inline- and block-dimension geometry. + */ + nscoord IStart(WritingMode aWritingMode) const // inline-start edge + { + CHECK_WRITING_MODE(aWritingMode); + return mIStart; + } + nscoord IEnd(WritingMode aWritingMode) const // inline-end edge + { + CHECK_WRITING_MODE(aWritingMode); + return mIStart + mISize; + } + nscoord ISize(WritingMode aWritingMode) const // inline-size + { + CHECK_WRITING_MODE(aWritingMode); + return mISize; + } + + nscoord BStart(WritingMode aWritingMode) const // block-start edge + { + CHECK_WRITING_MODE(aWritingMode); + return mBStart; + } + nscoord BEnd(WritingMode aWritingMode) const // block-end edge + { + CHECK_WRITING_MODE(aWritingMode); + return mBStart + mBSize; + } + nscoord BSize(WritingMode aWritingMode) const // block-size + { + CHECK_WRITING_MODE(aWritingMode); + return mBSize; + } + + nscoord Start(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM); + } + nscoord End(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM); + } + nscoord Size(LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM); + } + + /** + * Writable (reference) accessors are only available for the basic logical + * fields (Start and Size), not derivatives like End. + */ + nscoord& IStart(WritingMode aWritingMode) // inline-start edge + { + CHECK_WRITING_MODE(aWritingMode); + return mIStart; + } + nscoord& ISize(WritingMode aWritingMode) // inline-size + { + CHECK_WRITING_MODE(aWritingMode); + return mISize; + } + nscoord& BStart(WritingMode aWritingMode) // block-start edge + { + CHECK_WRITING_MODE(aWritingMode); + return mBStart; + } + nscoord& BSize(WritingMode aWritingMode) // block-size + { + CHECK_WRITING_MODE(aWritingMode); + return mBSize; + } + nscoord& Start(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM); + } + nscoord& Size(LogicalAxis aAxis, WritingMode aWM) { + return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM); + } + + /** + * Accessors for line-relative coordinates + */ + nscoord LineLeft(WritingMode aWritingMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsBidiLTR()) { + return IStart(); + } + nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height + : aContainerSize.width; + return containerISize - IEnd(); + } + nscoord LineRight(WritingMode aWritingMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsBidiLTR()) { + return IEnd(); + } + nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height + : aContainerSize.width; + return containerISize - IStart(); + } + + /** + * Physical coordinates of the rect. + */ + nscoord X(WritingMode aWritingMode, nscoord aContainerWidth) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return aWritingMode.IsVerticalLR() ? mBStart : aContainerWidth - BEnd(); + } + return aWritingMode.IsInlineReversed() ? aContainerWidth - IEnd() : mIStart; + } + + nscoord Y(WritingMode aWritingMode, nscoord aContainerHeight) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return aWritingMode.IsInlineReversed() ? aContainerHeight - IEnd() + : mIStart; + } + return mBStart; + } + + nscoord Width(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? mBSize : mISize; + } + + nscoord Height(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return aWritingMode.IsVertical() ? mISize : mBSize; + } + + nscoord XMost(WritingMode aWritingMode, nscoord aContainerWidth) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return aWritingMode.IsVerticalLR() ? BEnd() : aContainerWidth - mBStart; + } + return aWritingMode.IsInlineReversed() ? aContainerWidth - mIStart : IEnd(); + } + + nscoord YMost(WritingMode aWritingMode, nscoord aContainerHeight) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return aWritingMode.IsInlineReversed() ? aContainerHeight - mIStart + : IEnd(); + } + return BEnd(); + } + + bool IsEmpty() const { return mISize <= 0 || mBSize <= 0; } + + bool IsAllZero() const { + return (mIStart == 0 && mBStart == 0 && mISize == 0 && mBSize == 0); + } + + bool IsZeroSize() const { return (mISize == 0 && mBSize == 0); } + + void SetEmpty() { mISize = mBSize = 0; } + + bool IsEqualEdges(const LogicalRect aOther) const { + CHECK_WRITING_MODE(aOther.GetWritingMode()); + bool result = mIStart == aOther.mIStart && mBStart == aOther.mBStart && + mISize == aOther.mISize && mBSize == aOther.mBSize; + + // We want the same result as nsRect, so assert we get it. + MOZ_ASSERT(result == + nsRect(mIStart, mBStart, mISize, mBSize) + .IsEqualEdges(nsRect(aOther.mIStart, aOther.mBStart, + aOther.mISize, aOther.mBSize))); + return result; + } + + LogicalPoint Origin(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return LogicalPoint(aWritingMode, IStart(), BStart()); + } + void SetOrigin(WritingMode aWritingMode, const LogicalPoint& aPoint) { + IStart(aWritingMode) = aPoint.I(aWritingMode); + BStart(aWritingMode) = aPoint.B(aWritingMode); + } + + LogicalSize Size(WritingMode aWritingMode) const { + CHECK_WRITING_MODE(aWritingMode); + return LogicalSize(aWritingMode, ISize(), BSize()); + } + + LogicalRect operator+(const LogicalPoint& aPoint) const { + CHECK_WRITING_MODE(aPoint.GetWritingMode()); + return LogicalRect(GetWritingMode(), IStart() + aPoint.I(), + BStart() + aPoint.B(), ISize(), BSize()); + } + + LogicalRect& operator+=(const LogicalPoint& aPoint) { + CHECK_WRITING_MODE(aPoint.GetWritingMode()); + mIStart += aPoint.mPoint.x; + mBStart += aPoint.mPoint.y; + return *this; + } + + LogicalRect operator-(const LogicalPoint& aPoint) const { + CHECK_WRITING_MODE(aPoint.GetWritingMode()); + return LogicalRect(GetWritingMode(), IStart() - aPoint.I(), + BStart() - aPoint.B(), ISize(), BSize()); + } + + LogicalRect& operator-=(const LogicalPoint& aPoint) { + CHECK_WRITING_MODE(aPoint.GetWritingMode()); + mIStart -= aPoint.mPoint.x; + mBStart -= aPoint.mPoint.y; + return *this; + } + + void MoveBy(WritingMode aWritingMode, const LogicalPoint& aDelta) { + CHECK_WRITING_MODE(aWritingMode); + CHECK_WRITING_MODE(aDelta.GetWritingMode()); + IStart() += aDelta.I(); + BStart() += aDelta.B(); + } + + void Inflate(nscoord aD) { +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Inflate(aD); +#endif + mIStart -= aD; + mBStart -= aD; + mISize += 2 * aD; + mBSize += 2 * aD; + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + void Inflate(nscoord aDI, nscoord aDB) { +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Inflate(aDI, aDB); +#endif + mIStart -= aDI; + mBStart -= aDB; + mISize += 2 * aDI; + mBSize += 2 * aDB; + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + void Inflate(WritingMode aWritingMode, const LogicalMargin& aMargin) { + CHECK_WRITING_MODE(aWritingMode); + CHECK_WRITING_MODE(aMargin.GetWritingMode()); +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Inflate(aMargin.mMargin); +#endif + mIStart -= aMargin.mMargin.left; + mBStart -= aMargin.mMargin.top; + mISize += aMargin.mMargin.LeftRight(); + mBSize += aMargin.mMargin.TopBottom(); + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + + void Deflate(nscoord aD) { +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Deflate(aD); +#endif + mIStart += aD; + mBStart += aD; + mISize = std::max(0, mISize - 2 * aD); + mBSize = std::max(0, mBSize - 2 * aD); + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + void Deflate(nscoord aDI, nscoord aDB) { +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Deflate(aDI, aDB); +#endif + mIStart += aDI; + mBStart += aDB; + mISize = std::max(0, mISize - 2 * aDI); + mBSize = std::max(0, mBSize - 2 * aDB); + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + void Deflate(WritingMode aWritingMode, const LogicalMargin& aMargin) { + CHECK_WRITING_MODE(aWritingMode); + CHECK_WRITING_MODE(aMargin.GetWritingMode()); +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug(mIStart, mBStart, mISize, mBSize); + rectDebug.Deflate(aMargin.mMargin); +#endif + mIStart += aMargin.mMargin.left; + mBStart += aMargin.mMargin.top; + mISize = std::max(0, mISize - aMargin.mMargin.LeftRight()); + mBSize = std::max(0, mBSize - aMargin.mMargin.TopBottom()); + MOZ_ASSERT( + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + } + + /** + * Return an nsRect containing our physical coordinates within the given + * container size. + */ + nsRect GetPhysicalRect(WritingMode aWritingMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aWritingMode); + if (aWritingMode.IsVertical()) { + return nsRect(aWritingMode.IsVerticalLR() ? BStart() + : aContainerSize.width - BEnd(), + aWritingMode.IsInlineReversed() + ? aContainerSize.height - IEnd() + : IStart(), + BSize(), ISize()); + } else { + return nsRect(aWritingMode.IsInlineReversed() + ? aContainerSize.width - IEnd() + : IStart(), + BStart(), ISize(), BSize()); + } + } + + /** + * Return a LogicalRect representing this rect in a different writing mode + */ + LogicalRect ConvertTo(WritingMode aToMode, WritingMode aFromMode, + const nsSize& aContainerSize) const { + CHECK_WRITING_MODE(aFromMode); + return aToMode == aFromMode + ? *this + : LogicalRect(aToMode, + GetPhysicalRect(aFromMode, aContainerSize), + aContainerSize); + } + + /** + * Set *this to be the rectangle containing the intersection of aRect1 + * and aRect2, return whether the intersection is non-empty. + */ + bool IntersectRect(const LogicalRect& aRect1, const LogicalRect& aRect2) { + CHECK_WRITING_MODE(aRect1.mWritingMode); + CHECK_WRITING_MODE(aRect2.mWritingMode); +#ifdef DEBUG + // Compute using nsRect and assert the results match + nsRect rectDebug; + rectDebug.IntersectRect( + nsRect(aRect1.mIStart, aRect1.mBStart, aRect1.mISize, aRect1.mBSize), + nsRect(aRect2.mIStart, aRect2.mBStart, aRect2.mISize, aRect2.mBSize)); +#endif + + nscoord iEnd = std::min(aRect1.IEnd(), aRect2.IEnd()); + mIStart = std::max(aRect1.mIStart, aRect2.mIStart); + mISize = iEnd - mIStart; + + nscoord bEnd = std::min(aRect1.BEnd(), aRect2.BEnd()); + mBStart = std::max(aRect1.mBStart, aRect2.mBStart); + mBSize = bEnd - mBStart; + + if (mISize < 0 || mBSize < 0) { + mISize = 0; + mBSize = 0; + } + + MOZ_ASSERT( + (rectDebug.IsEmpty() && (mISize == 0 || mBSize == 0)) || + rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize))); + return mISize > 0 && mBSize > 0; + } + + friend std::ostream& operator<<(std::ostream& aStream, + const LogicalRect& aRect) { + return aStream << '(' << aRect.IStart() << ',' << aRect.BStart() << ',' + << aRect.ISize() << ',' << aRect.BSize() << ')'; + } + + private: + LogicalRect() = delete; + +#ifdef DEBUG + WritingMode GetWritingMode() const { return mWritingMode; } +#else + WritingMode GetWritingMode() const { return WritingMode::Unknown(); } +#endif + + nscoord IStart() const // inline-start edge + { + return mIStart; + } + nscoord IEnd() const // inline-end edge + { + return mIStart + mISize; + } + nscoord ISize() const // inline-size + { + return mISize; + } + + nscoord BStart() const // block-start edge + { + return mBStart; + } + nscoord BEnd() const // block-end edge + { + return mBStart + mBSize; + } + nscoord BSize() const // block-size + { + return mBSize; + } + + nscoord& IStart() // inline-start edge + { + return mIStart; + } + nscoord& ISize() // inline-size + { + return mISize; + } + nscoord& BStart() // block-start edge + { + return mBStart; + } + nscoord& BSize() // block-size + { + return mBSize; + } + +#ifdef DEBUG + WritingMode mWritingMode; +#endif + // Inline- and block-geometry dimension + nscoord mIStart; // inline-start edge + nscoord mBStart; // block-start edge + nscoord mISize; // inline-size + nscoord mBSize; // block-size +}; + +template <typename T> +const T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) const { + return Get(aWM.PhysicalSide(aSide)); +} + +template <typename T> +const T& StyleRect<T>::GetIStart(WritingMode aWM) const { + return Get(aWM, eLogicalSideIStart); +} + +template <typename T> +const T& StyleRect<T>::GetBStart(WritingMode aWM) const { + return Get(aWM, eLogicalSideBStart); +} + +template <typename T> +const T& StyleRect<T>::GetIEnd(WritingMode aWM) const { + return Get(aWM, eLogicalSideIEnd); +} + +template <typename T> +const T& StyleRect<T>::GetBEnd(WritingMode aWM) const { + return Get(aWM, eLogicalSideBEnd); +} + +template <typename T> +T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) { + return Get(aWM.PhysicalSide(aSide)); +} + +template <typename T> +T& StyleRect<T>::GetIStart(WritingMode aWM) { + return Get(aWM, eLogicalSideIStart); +} + +template <typename T> +T& StyleRect<T>::GetBStart(WritingMode aWM) { + return Get(aWM, eLogicalSideBStart); +} + +template <typename T> +T& StyleRect<T>::GetIEnd(WritingMode aWM) { + return Get(aWM, eLogicalSideIEnd); +} + +template <typename T> +T& StyleRect<T>::GetBEnd(WritingMode aWM) { + return Get(aWM, eLogicalSideBEnd); +} + +template <typename T> +const T& StyleRect<T>::Start(mozilla::LogicalAxis aAxis, + mozilla::WritingMode aWM) const { + return Get(aWM, aAxis == mozilla::eLogicalAxisInline + ? mozilla::eLogicalSideIStart + : mozilla::eLogicalSideBStart); +} + +template <typename T> +const T& StyleRect<T>::End(mozilla::LogicalAxis aAxis, + mozilla::WritingMode aWM) const { + return Get(aWM, aAxis == mozilla::eLogicalAxisInline + ? mozilla::eLogicalSideIEnd + : mozilla::eLogicalSideBEnd); +} + +inline AspectRatio AspectRatio::ConvertToWritingMode( + const WritingMode& aWM) const { + return aWM.IsVertical() ? Inverted() : *this; +} + +} // namespace mozilla + +// Definitions of inline methods for nsStylePosition, declared in +// nsStyleStruct.h but not defined there because they need WritingMode. +inline const mozilla::StyleSize& nsStylePosition::ISize(WritingMode aWM) const { + return aWM.IsVertical() ? mHeight : mWidth; +} +inline const mozilla::StyleSize& nsStylePosition::MinISize( + WritingMode aWM) const { + return aWM.IsVertical() ? mMinHeight : mMinWidth; +} +inline const mozilla::StyleMaxSize& nsStylePosition::MaxISize( + WritingMode aWM) const { + return aWM.IsVertical() ? mMaxHeight : mMaxWidth; +} +inline const mozilla::StyleSize& nsStylePosition::BSize(WritingMode aWM) const { + return aWM.IsVertical() ? mWidth : mHeight; +} +inline const mozilla::StyleSize& nsStylePosition::MinBSize( + WritingMode aWM) const { + return aWM.IsVertical() ? mMinWidth : mMinHeight; +} +inline const mozilla::StyleMaxSize& nsStylePosition::MaxBSize( + WritingMode aWM) const { + return aWM.IsVertical() ? mMaxWidth : mMaxHeight; +} +inline const mozilla::StyleSize& nsStylePosition::Size( + mozilla::LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == mozilla::eLogicalAxisInline ? ISize(aWM) : BSize(aWM); +} +inline const mozilla::StyleSize& nsStylePosition::MinSize( + mozilla::LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == mozilla::eLogicalAxisInline ? MinISize(aWM) : MinBSize(aWM); +} +inline const mozilla::StyleMaxSize& nsStylePosition::MaxSize( + mozilla::LogicalAxis aAxis, WritingMode aWM) const { + return aAxis == mozilla::eLogicalAxisInline ? MaxISize(aWM) : MaxBSize(aWM); +} + +inline bool nsStylePosition::ISizeDependsOnContainer(WritingMode aWM) const { + const auto& iSize = ISize(aWM); + return iSize.IsAuto() || ISizeCoordDependsOnContainer(iSize); +} +inline bool nsStylePosition::MinISizeDependsOnContainer(WritingMode aWM) const { + // NOTE: For a flex item, "min-inline-size:auto" is supposed to behave like + // "min-content", which does depend on the container, so you might think we'd + // need a special case for "flex item && min-inline-size:auto" here. However, + // we don't actually need that special-case code, because flex items are + // explicitly supposed to *ignore* their min-inline-size (i.e. behave like + // it's 0) until the flex container explicitly considers it. So -- since the + // flex container doesn't rely on this method, we don't need to worry about + // special behavior for flex items' "min-inline-size:auto" values here. + return ISizeCoordDependsOnContainer(MinISize(aWM)); +} +inline bool nsStylePosition::MaxISizeDependsOnContainer(WritingMode aWM) const { + // NOTE: The comment above MinISizeDependsOnContainer about flex items + // applies here, too. + return ISizeCoordDependsOnContainer(MaxISize(aWM)); +} +// Note that these functions count `auto` as depending on the container +// since that's the case for absolutely positioned elements. +// However, some callers do not care about this case and should check +// for it, since it is the most common case. +// FIXME: We should probably change the assumption to be the other way +// around. +inline bool nsStylePosition::BSizeDependsOnContainer(WritingMode aWM) const { + const auto& bSize = BSize(aWM); + return bSize.BehavesLikeInitialValueOnBlockAxis() || + BSizeCoordDependsOnContainer(bSize); +} +inline bool nsStylePosition::MinBSizeDependsOnContainer(WritingMode aWM) const { + return BSizeCoordDependsOnContainer(MinBSize(aWM)); +} +inline bool nsStylePosition::MaxBSizeDependsOnContainer(WritingMode aWM) const { + return BSizeCoordDependsOnContainer(MaxBSize(aWM)); +} + +inline bool nsStyleMargin::HasBlockAxisAuto(mozilla::WritingMode aWM) const { + return mMargin.GetBStart(aWM).IsAuto() || mMargin.GetBEnd(aWM).IsAuto(); +} + +inline bool nsStyleMargin::HasInlineAxisAuto(mozilla::WritingMode aWM) const { + return mMargin.GetIStart(aWM).IsAuto() || mMargin.GetIEnd(aWM).IsAuto(); +} +inline bool nsStyleMargin::HasAuto(mozilla::LogicalAxis aAxis, + mozilla::WritingMode aWM) const { + return aAxis == mozilla::eLogicalAxisInline ? HasInlineAxisAuto(aWM) + : HasBlockAxisAuto(aWM); +} + +inline mozilla::StyleAlignFlags nsStylePosition::UsedSelfAlignment( + mozilla::LogicalAxis aAxis, const mozilla::ComputedStyle* aParent) const { + return aAxis == mozilla::eLogicalAxisBlock ? UsedAlignSelf(aParent)._0 + : UsedJustifySelf(aParent)._0; +} + +inline mozilla::StyleContentDistribution nsStylePosition::UsedContentAlignment( + mozilla::LogicalAxis aAxis) const { + return aAxis == mozilla::eLogicalAxisBlock ? mAlignContent : mJustifyContent; +} + +inline mozilla::StyleContentDistribution nsStylePosition::UsedTracksAlignment( + mozilla::LogicalAxis aAxis, uint32_t aIndex) const { + using T = mozilla::StyleAlignFlags; + const auto& tracksAlignment = + aAxis == mozilla::eLogicalAxisBlock ? mAlignTracks : mJustifyTracks; + if (MOZ_LIKELY(tracksAlignment.IsEmpty())) { + // An empty array encodes the initial value, 'normal', which behaves as + // 'start' for Grid containers. + return mozilla::StyleContentDistribution{T::START}; + } + + // If there are fewer values than tracks, then the last value is used for all + // the remaining tracks. + const auto& ta = tracksAlignment.AsSpan(); + auto align = ta[std::min<size_t>(aIndex, ta.Length() - 1)]; + if (align.primary == T::NORMAL) { + align = mozilla::StyleContentDistribution{T::START}; + } + return align; +} + +#endif // WritingModes_h_ |