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 #ifndef WritingModes_h_
8 #define WritingModes_h_
9 
10 #include <ostream>
11 
12 #include "mozilla/intl/BidiEmbeddingLevel.h"
13 #include "mozilla/ComputedStyle.h"
14 #include "mozilla/EnumeratedRange.h"
15 
16 #include "nsRect.h"
17 #include "nsBidiUtils.h"
18 
19 // It is the caller's responsibility to operate on logical-coordinate objects
20 // with matched writing modes. Failure to do so will be a runtime bug; the
21 // compiler can't catch it, but in debug mode, we'll throw an assertion.
22 // NOTE that in non-debug builds, a writing mode mismatch error will NOT be
23 // detected, yet the results will be nonsense (and may lead to further layout
24 // failures). Therefore, it is important to test (and fuzz-test) writing-mode
25 // support using debug builds.
26 
27 // Methods in logical-coordinate classes that take another logical-coordinate
28 // object as a parameter should call CHECK_WRITING_MODE on it to verify that
29 // the writing modes match.
30 // (In some cases, there are internal (private) methods that don't do this;
31 // such methods should only be used by other methods that have already checked
32 // the writing modes.)
33 // The check ignores the StyleWritingMode::VERTICAL_SIDEWAYS and
34 // StyleWritingMode::TEXT_SIDEWAYS bit of writing mode, because
35 // this does not affect the interpretation of logical coordinates.
36 
37 #define CHECK_WRITING_MODE(param)                                           \
38   NS_ASSERTION(param.IgnoreSideways() == GetWritingMode().IgnoreSideways(), \
39                "writing-mode mismatch")
40 
41 namespace mozilla {
42 
43 namespace widget {
44 struct IMENotification;
45 }  // namespace widget
46 
47 // Logical axis, edge, side and corner constants for use in various places.
48 enum LogicalAxis : uint8_t {
49   eLogicalAxisBlock = 0x0,
50   eLogicalAxisInline = 0x1
51 };
52 enum LogicalEdge { eLogicalEdgeStart = 0x0, eLogicalEdgeEnd = 0x1 };
53 enum LogicalSide : uint8_t {
54   eLogicalSideBStart = (eLogicalAxisBlock << 1) | eLogicalEdgeStart,   // 0x0
55   eLogicalSideBEnd = (eLogicalAxisBlock << 1) | eLogicalEdgeEnd,       // 0x1
56   eLogicalSideIStart = (eLogicalAxisInline << 1) | eLogicalEdgeStart,  // 0x2
57   eLogicalSideIEnd = (eLogicalAxisInline << 1) | eLogicalEdgeEnd       // 0x3
58 };
AllLogicalSides()59 constexpr auto AllLogicalSides() {
60   return mozilla::MakeInclusiveEnumeratedRange(eLogicalSideBStart,
61                                                eLogicalSideIEnd);
62 }
63 
64 enum LogicalCorner {
65   eLogicalCornerBStartIStart = 0,
66   eLogicalCornerBStartIEnd = 1,
67   eLogicalCornerBEndIEnd = 2,
68   eLogicalCornerBEndIStart = 3
69 };
70 
71 // Physical axis constants.
72 enum PhysicalAxis { eAxisVertical = 0x0, eAxisHorizontal = 0x1 };
73 
GetOrthogonalAxis(LogicalAxis aAxis)74 inline LogicalAxis GetOrthogonalAxis(LogicalAxis aAxis) {
75   return aAxis == eLogicalAxisBlock ? eLogicalAxisInline : eLogicalAxisBlock;
76 }
77 
IsInline(LogicalSide aSide)78 inline bool IsInline(LogicalSide aSide) { return aSide & 0x2; }
IsBlock(LogicalSide aSide)79 inline bool IsBlock(LogicalSide aSide) { return !IsInline(aSide); }
IsEnd(LogicalSide aSide)80 inline bool IsEnd(LogicalSide aSide) { return aSide & 0x1; }
IsStart(LogicalSide aSide)81 inline bool IsStart(LogicalSide aSide) { return !IsEnd(aSide); }
82 
GetAxis(LogicalSide aSide)83 inline LogicalAxis GetAxis(LogicalSide aSide) {
84   return IsInline(aSide) ? eLogicalAxisInline : eLogicalAxisBlock;
85 }
86 
GetEdge(LogicalSide aSide)87 inline LogicalEdge GetEdge(LogicalSide aSide) {
88   return IsEnd(aSide) ? eLogicalEdgeEnd : eLogicalEdgeStart;
89 }
90 
GetOppositeEdge(LogicalEdge aEdge)91 inline LogicalEdge GetOppositeEdge(LogicalEdge aEdge) {
92   // This relies on the only two LogicalEdge enum values being 0 and 1.
93   return LogicalEdge(1 - aEdge);
94 }
95 
MakeLogicalSide(LogicalAxis aAxis,LogicalEdge aEdge)96 inline LogicalSide MakeLogicalSide(LogicalAxis aAxis, LogicalEdge aEdge) {
97   return LogicalSide((aAxis << 1) | aEdge);
98 }
99 
GetOppositeSide(LogicalSide aSide)100 inline LogicalSide GetOppositeSide(LogicalSide aSide) {
101   return MakeLogicalSide(GetAxis(aSide), GetOppositeEdge(GetEdge(aSide)));
102 }
103 
104 enum LogicalSideBits {
105   eLogicalSideBitsNone = 0,
106   eLogicalSideBitsBStart = 1 << eLogicalSideBStart,
107   eLogicalSideBitsBEnd = 1 << eLogicalSideBEnd,
108   eLogicalSideBitsIEnd = 1 << eLogicalSideIEnd,
109   eLogicalSideBitsIStart = 1 << eLogicalSideIStart,
110   eLogicalSideBitsBBoth = eLogicalSideBitsBStart | eLogicalSideBitsBEnd,
111   eLogicalSideBitsIBoth = eLogicalSideBitsIStart | eLogicalSideBitsIEnd,
112   eLogicalSideBitsAll = eLogicalSideBitsBBoth | eLogicalSideBitsIBoth
113 };
114 
115 enum LineRelativeDir {
116   eLineRelativeDirOver = eLogicalSideBStart,
117   eLineRelativeDirUnder = eLogicalSideBEnd,
118   eLineRelativeDirLeft = eLogicalSideIStart,
119   eLineRelativeDirRight = eLogicalSideIEnd
120 };
121 
122 /**
123  * mozilla::WritingMode is an immutable class representing a
124  * writing mode.
125  *
126  * It efficiently stores the writing mode and can rapidly compute
127  * interesting things about it for use in layout.
128  *
129  * Writing modes are computed from the CSS 'direction',
130  * 'writing-mode', and 'text-orientation' properties.
131  * See CSS3 Writing Modes for more information
132  *   http://www.w3.org/TR/css3-writing-modes/
133  */
134 class WritingMode {
135  public:
136   /**
137    * Absolute inline flow direction
138    */
139   enum InlineDir {
140     eInlineLTR = 0x00,  // text flows horizontally left to right
141     eInlineRTL = 0x02,  // text flows horizontally right to left
142     eInlineTTB = 0x01,  // text flows vertically top to bottom
143     eInlineBTT = 0x03,  // text flows vertically bottom to top
144   };
145 
146   /**
147    * Absolute block flow direction
148    */
149   enum BlockDir {
150     eBlockTB = 0x00,  // horizontal lines stack top to bottom
151     eBlockRL = 0x01,  // vertical lines stack right to left
152     eBlockLR = 0x05,  // vertical lines stack left to right
153   };
154 
155   /**
156    * Line-relative (bidi-relative) inline flow direction
157    */
158   enum BidiDir {
159     eBidiLTR = 0x00,  // inline flow matches bidi LTR text
160     eBidiRTL = 0x10,  // inline flow matches bidi RTL text
161   };
162 
163   /**
164    * Unknown writing mode (should never actually be stored or used anywhere).
165    */
166   enum { eUnknownWritingMode = 0xff };
167 
168   /**
169    * Return the absolute inline flow direction as an InlineDir
170    */
GetInlineDir()171   InlineDir GetInlineDir() const {
172     return InlineDir(mWritingMode.bits & eInlineMask);
173   }
174 
175   /**
176    * Return the absolute block flow direction as a BlockDir
177    */
GetBlockDir()178   BlockDir GetBlockDir() const {
179     return BlockDir(mWritingMode.bits & eBlockMask);
180   }
181 
182   /**
183    * Return the line-relative inline flow direction as a BidiDir
184    */
GetBidiDir()185   BidiDir GetBidiDir() const {
186     return BidiDir((mWritingMode & StyleWritingMode::RTL).bits);
187   }
188 
189   /**
190    * Return true if the inline flow direction is against physical direction
191    * (i.e. right-to-left or bottom-to-top).
192    * This occurs when writing-mode is sideways-lr OR direction is rtl (but not
193    * if both of those are true).
194    */
IsInlineReversed()195   bool IsInlineReversed() const {
196     return !!(mWritingMode & StyleWritingMode::INLINE_REVERSED);
197   }
198 
199   /**
200    * Return true if bidi direction is LTR. (Convenience method)
201    */
IsBidiLTR()202   bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); }
203 
204   /**
205    * Return true if bidi direction is RTL. (Convenience method)
206    */
IsBidiRTL()207   bool IsBidiRTL() const { return eBidiRTL == GetBidiDir(); }
208 
209   /**
210    * True if it is vertical and vertical-lr, or is horizontal and bidi LTR.
211    */
IsPhysicalLTR()212   bool IsPhysicalLTR() const {
213     return IsVertical() ? IsVerticalLR() : IsBidiLTR();
214   }
215 
216   /**
217    * True if it is vertical and vertical-rl, or is horizontal and bidi RTL.
218    */
IsPhysicalRTL()219   bool IsPhysicalRTL() const {
220     return IsVertical() ? IsVerticalRL() : IsBidiRTL();
221   }
222 
223   /**
224    * True if vertical-mode block direction is LR (convenience method).
225    */
IsVerticalLR()226   bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); }
227 
228   /**
229    * True if vertical-mode block direction is RL (convenience method).
230    */
IsVerticalRL()231   bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); }
232 
233   /**
234    * True if vertical writing mode, i.e. when
235    * writing-mode: vertical-lr | vertical-rl.
236    */
IsVertical()237   bool IsVertical() const {
238     return !!(mWritingMode & StyleWritingMode::VERTICAL);
239   }
240 
241   /**
242    * True if line-over/line-under are inverted from block-start/block-end.
243    * This is true only when writing-mode is vertical-lr.
244    */
IsLineInverted()245   bool IsLineInverted() const {
246     return !!(mWritingMode & StyleWritingMode::LINE_INVERTED);
247   }
248 
249   /**
250    * Block-axis flow-relative to line-relative factor.
251    * May be used as a multiplication factor for block-axis coordinates
252    * to convert between flow- and line-relative coordinate systems (e.g.
253    * positioning an over- or under-line decoration).
254    */
FlowRelativeToLineRelativeFactor()255   int FlowRelativeToLineRelativeFactor() const {
256     return IsLineInverted() ? -1 : 1;
257   }
258 
259   /**
260    * True if vertical sideways writing mode, i.e. when
261    * writing-mode: sideways-lr | sideways-rl.
262    */
IsVerticalSideways()263   bool IsVerticalSideways() const {
264     return !!(mWritingMode & StyleWritingMode::VERTICAL_SIDEWAYS);
265   }
266 
267   /**
268    * True if this is writing-mode: sideways-rl (convenience method).
269    */
IsSidewaysRL()270   bool IsSidewaysRL() const { return IsVerticalRL() && IsVerticalSideways(); }
271 
272   /**
273    * True if this is writing-mode: sideways-lr (convenience method).
274    */
IsSidewaysLR()275   bool IsSidewaysLR() const { return IsVerticalLR() && IsVerticalSideways(); }
276 
277   /**
278    * True if either text-orientation or writing-mode will force all text to be
279    * rendered sideways in vertical lines, in which case we should prefer an
280    * alphabetic baseline; otherwise, the default is centered.
281    *
282    * Note that some glyph runs may be rendered sideways even if this is false,
283    * due to text-orientation:mixed resolution, but in that case the dominant
284    * baseline remains centered.
285    */
IsSideways()286   bool IsSideways() const {
287     return !!(mWritingMode & (StyleWritingMode::VERTICAL_SIDEWAYS |
288                               StyleWritingMode::TEXT_SIDEWAYS));
289   }
290 
291 #ifdef DEBUG
292   // Used by CHECK_WRITING_MODE to compare modes without regard for the
293   // StyleWritingMode::VERTICAL_SIDEWAYS or StyleWritingMode::TEXT_SIDEWAYS
294   // flags.
IgnoreSideways()295   WritingMode IgnoreSideways() const {
296     return WritingMode(
297         mWritingMode.bits &
298         ~(StyleWritingMode::VERTICAL_SIDEWAYS | StyleWritingMode::TEXT_SIDEWAYS)
299              .bits);
300   }
301 #endif
302 
303   /**
304    * Return true if boxes with this writing mode should use central baselines.
305    */
IsCentralBaseline()306   bool IsCentralBaseline() const { return IsVertical() && !IsSideways(); }
307 
308   /**
309    * Return true if boxes with this writing mode should use alphabetical
310    * baselines.
311    */
IsAlphabeticalBaseline()312   bool IsAlphabeticalBaseline() const { return !IsCentralBaseline(); }
313 
PhysicalAxisForLogicalAxis(uint8_t aWritingModeValue,LogicalAxis aAxis)314   static mozilla::PhysicalAxis PhysicalAxisForLogicalAxis(
315       uint8_t aWritingModeValue, LogicalAxis aAxis) {
316     // This relies on bit 0 of a writing-value mode indicating vertical
317     // orientation and bit 0 of a LogicalAxis value indicating the inline axis,
318     // so that it can correctly form mozilla::PhysicalAxis values using bit
319     // manipulation.
320     static_assert(uint8_t(StyleWritingModeProperty::HorizontalTb) == 0 &&
321                       uint8_t(StyleWritingModeProperty::VerticalRl) == 1 &&
322                       uint8_t(StyleWritingModeProperty::VerticalLr) == 3 &&
323                       eLogicalAxisBlock == 0 && eLogicalAxisInline == 1 &&
324                       eAxisVertical == 0 && eAxisHorizontal == 1,
325                   "unexpected writing-mode, logical axis or physical axis "
326                   "constant values");
327     return mozilla::PhysicalAxis((aWritingModeValue ^ aAxis) & 0x1);
328   }
329 
PhysicalAxis(LogicalAxis aAxis)330   mozilla::PhysicalAxis PhysicalAxis(LogicalAxis aAxis) const {
331     // This will set wm to either StyleWritingModel::HorizontalTB or
332     // StyleWritingModeProperty::VerticalRL, and not the other two (real
333     // and hypothetical) values.  But this is fine; we only need to
334     // distinguish between vertical and horizontal in
335     // PhysicalAxisForLogicalAxis.
336     const auto wm = (mWritingMode & StyleWritingMode::VERTICAL).bits;
337     return PhysicalAxisForLogicalAxis(wm, aAxis);
338   }
339 
PhysicalSideForBlockAxis(uint8_t aWritingModeValue,LogicalEdge aEdge)340   static mozilla::Side PhysicalSideForBlockAxis(uint8_t aWritingModeValue,
341                                                 LogicalEdge aEdge) {
342     // indexes are StyleWritingModeProperty values, which are the same as these
343     // two-bit values:
344     //   bit 0 = the StyleWritingMode::VERTICAL value
345     //   bit 1 = the StyleWritingMode::VERTICAL_LR value
346     static const mozilla::Side kLogicalBlockSides[][2] = {
347         {eSideTop, eSideBottom},  // horizontal-tb
348         {eSideRight, eSideLeft},  // vertical-rl
349         {eSideBottom, eSideTop},  // (horizontal-bt)
350         {eSideLeft, eSideRight},  // vertical-lr
351     };
352 
353     // Ignore the SidewaysMask bit of the writing-mode value, as this has no
354     // effect on the side mappings.
355     aWritingModeValue &= ~kWritingModeSidewaysMask;
356 
357     // What's left of the writing-mode should be in the range 0-3:
358     NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value");
359 
360     return kLogicalBlockSides[aWritingModeValue][aEdge];
361   }
362 
PhysicalSideForInlineAxis(LogicalEdge aEdge)363   mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const {
364     // indexes are four-bit values:
365     //   bit 0 = the StyleWritingMode::VERTICAL value
366     //   bit 1 = the StyleWritingMode::INLINE_REVERSED value
367     //   bit 2 = the StyleWritingMode::VERTICAL_LR value
368     //   bit 3 = the StyleWritingMode::LINE_INVERTED value
369     // Not all of these combinations can actually be specified via CSS: there
370     // is no horizontal-bt writing-mode, and no text-orientation value that
371     // produces "inverted" text. (The former 'sideways-left' value, no longer
372     // in the spec, would have produced this in vertical-rl mode.)
373     static const mozilla::Side kLogicalInlineSides[][2] = {
374         {eSideLeft, eSideRight},  // horizontal-tb               ltr
375         {eSideTop, eSideBottom},  // vertical-rl                 ltr
376         {eSideRight, eSideLeft},  // horizontal-tb               rtl
377         {eSideBottom, eSideTop},  // vertical-rl                 rtl
378         {eSideRight, eSideLeft},  // (horizontal-bt)  (inverted) ltr
379         {eSideTop, eSideBottom},  // sideways-lr                 rtl
380         {eSideLeft, eSideRight},  // (horizontal-bt)  (inverted) rtl
381         {eSideBottom, eSideTop},  // sideways-lr                 ltr
382         {eSideLeft, eSideRight},  // horizontal-tb    (inverted) rtl
383         {eSideTop, eSideBottom},  // vertical-rl      (inverted) rtl
384         {eSideRight, eSideLeft},  // horizontal-tb    (inverted) ltr
385         {eSideBottom, eSideTop},  // vertical-rl      (inverted) ltr
386         {eSideLeft, eSideRight},  // (horizontal-bt)             ltr
387         {eSideTop, eSideBottom},  // vertical-lr                 ltr
388         {eSideRight, eSideLeft},  // (horizontal-bt)             rtl
389         {eSideBottom, eSideTop},  // vertical-lr                 rtl
390     };
391 
392     // Inline axis sides depend on all three of writing-mode, text-orientation
393     // and direction, which are encoded in the StyleWritingMode::VERTICAL,
394     // StyleWritingMode::INLINE_REVERSED, StyleWritingMode::VERTICAL_LR and
395     // StyleWritingMode::LINE_INVERTED bits.  Use these four bits to index into
396     // kLogicalInlineSides.
397     MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 &&
398                    StyleWritingMode::INLINE_REVERSED.bits == 0x02 &&
399                    StyleWritingMode::VERTICAL_LR.bits == 0x04 &&
400                    StyleWritingMode::LINE_INVERTED.bits == 0x08,
401                "unexpected mask values");
402     int index = mWritingMode.bits & 0x0F;
403     return kLogicalInlineSides[index][aEdge];
404   }
405 
406   /**
407    * Returns the physical side corresponding to the specified logical side,
408    * given the current writing mode.
409    */
PhysicalSide(LogicalSide aSide)410   mozilla::Side PhysicalSide(LogicalSide aSide) const {
411     if (IsBlock(aSide)) {
412       MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 &&
413                      StyleWritingMode::VERTICAL_LR.bits == 0x04,
414                  "unexpected mask values");
415       const uint8_t wm =
416           ((mWritingMode & StyleWritingMode::VERTICAL_LR).bits >> 1) |
417           (mWritingMode & StyleWritingMode::VERTICAL).bits;
418       return PhysicalSideForBlockAxis(wm, GetEdge(aSide));
419     }
420 
421     return PhysicalSideForInlineAxis(GetEdge(aSide));
422   }
423 
424   /**
425    * Returns the logical side corresponding to the specified physical side,
426    * given the current writing mode.
427    * (This is the inverse of the PhysicalSide() method above.)
428    */
LogicalSideForPhysicalSide(mozilla::Side aSide)429   LogicalSide LogicalSideForPhysicalSide(mozilla::Side aSide) const {
430     // clang-format off
431     // indexes are four-bit values:
432     //   bit 0 = the StyleWritingMode::VERTICAL value
433     //   bit 1 = the StyleWritingMode::INLINE_REVERSED value
434     //   bit 2 = the StyleWritingMode::VERTICAL_LR value
435     //   bit 3 = the StyleWritingMode::LINE_INVERTED value
436     static const LogicalSide kPhysicalToLogicalSides[][4] = {
437       // top                right
438       // bottom             left
439       { eLogicalSideBStart, eLogicalSideIEnd,
440         eLogicalSideBEnd,   eLogicalSideIStart },  // horizontal-tb         ltr
441       { eLogicalSideIStart, eLogicalSideBStart,
442         eLogicalSideIEnd,   eLogicalSideBEnd   },  // vertical-rl           ltr
443       { eLogicalSideBStart, eLogicalSideIStart,
444         eLogicalSideBEnd,   eLogicalSideIEnd   },  // horizontal-tb         rtl
445       { eLogicalSideIEnd,   eLogicalSideBStart,
446         eLogicalSideIStart, eLogicalSideBEnd   },  // vertical-rl           rtl
447       { eLogicalSideBEnd,   eLogicalSideIStart,
448         eLogicalSideBStart, eLogicalSideIEnd   },  // (horizontal-bt) (inv) ltr
449       { eLogicalSideIStart, eLogicalSideBEnd,
450         eLogicalSideIEnd,   eLogicalSideBStart },  // vertical-lr   sw-left rtl
451       { eLogicalSideBEnd,   eLogicalSideIEnd,
452         eLogicalSideBStart, eLogicalSideIStart },  // (horizontal-bt) (inv) rtl
453       { eLogicalSideIEnd,   eLogicalSideBEnd,
454         eLogicalSideIStart, eLogicalSideBStart },  // vertical-lr   sw-left ltr
455       { eLogicalSideBStart, eLogicalSideIEnd,
456         eLogicalSideBEnd,   eLogicalSideIStart },  // horizontal-tb   (inv) rtl
457       { eLogicalSideIStart, eLogicalSideBStart,
458         eLogicalSideIEnd,   eLogicalSideBEnd   },  // vertical-rl   sw-left rtl
459       { eLogicalSideBStart, eLogicalSideIStart,
460         eLogicalSideBEnd,   eLogicalSideIEnd   },  // horizontal-tb   (inv) ltr
461       { eLogicalSideIEnd,   eLogicalSideBStart,
462         eLogicalSideIStart, eLogicalSideBEnd   },  // vertical-rl   sw-left ltr
463       { eLogicalSideBEnd,   eLogicalSideIEnd,
464         eLogicalSideBStart, eLogicalSideIStart },  // (horizontal-bt)       ltr
465       { eLogicalSideIStart, eLogicalSideBEnd,
466         eLogicalSideIEnd,   eLogicalSideBStart },  // vertical-lr           ltr
467       { eLogicalSideBEnd,   eLogicalSideIStart,
468         eLogicalSideBStart, eLogicalSideIEnd   },  // (horizontal-bt)       rtl
469       { eLogicalSideIEnd,   eLogicalSideBEnd,
470         eLogicalSideIStart, eLogicalSideBStart },  // vertical-lr           rtl
471     };
472     // clang-format on
473 
474     MOZ_ASSERT(StyleWritingMode::VERTICAL.bits == 0x01 &&
475                    StyleWritingMode::INLINE_REVERSED.bits == 0x02 &&
476                    StyleWritingMode::VERTICAL_LR.bits == 0x04 &&
477                    StyleWritingMode::LINE_INVERTED.bits == 0x08,
478                "unexpected mask values");
479     int index = mWritingMode.bits & 0x0F;
480     return kPhysicalToLogicalSides[index][aSide];
481   }
482 
483   /**
484    * Returns the logical side corresponding to the specified
485    * line-relative direction, given the current writing mode.
486    */
LogicalSideForLineRelativeDir(LineRelativeDir aDir)487   LogicalSide LogicalSideForLineRelativeDir(LineRelativeDir aDir) const {
488     auto side = static_cast<LogicalSide>(aDir);
489     if (IsInline(side)) {
490       return IsBidiLTR() ? side : GetOppositeSide(side);
491     }
492     return !IsLineInverted() ? side : GetOppositeSide(side);
493   }
494 
495   /**
496    * Default constructor gives us a horizontal, LTR writing mode.
497    * XXX We will probably eliminate this and require explicit initialization
498    *     in all cases once transition is complete.
499    */
WritingMode()500   WritingMode() : mWritingMode{0} {}
501 
502   /**
503    * Construct writing mode based on a ComputedStyle.
504    */
WritingMode(const ComputedStyle * aComputedStyle)505   explicit WritingMode(const ComputedStyle* aComputedStyle) {
506     NS_ASSERTION(aComputedStyle, "we need an ComputedStyle here");
507     mWritingMode = aComputedStyle->WritingMode();
508   }
509 
510   /**
511    * This function performs fixup for elements with 'unicode-bidi: plaintext',
512    * where inline directionality is derived from the Unicode bidi categories
513    * of the element's content, and not the CSS 'direction' property.
514    *
515    * The WritingMode constructor will have already incorporated the 'direction'
516    * property into our flag bits, so such elements need to use this method
517    * (after resolving the bidi level of their content) to update the direction
518    * bits as needed.
519    *
520    * If it turns out that our bidi direction already matches what plaintext
521    * resolution determined, there's nothing to do here. If it didn't (i.e. if
522    * the rtl-ness doesn't match), then we correct the direction by flipping the
523    * same bits that get flipped in the constructor's CSS 'direction'-based
524    * chunk.
525    *
526    * XXX change uint8_t to UBiDiLevel after bug 924851
527    */
SetDirectionFromBidiLevel(mozilla::intl::BidiEmbeddingLevel level)528   void SetDirectionFromBidiLevel(mozilla::intl::BidiEmbeddingLevel level) {
529     if (level.IsRTL() == IsBidiLTR()) {
530       mWritingMode ^= StyleWritingMode::RTL | StyleWritingMode::INLINE_REVERSED;
531     }
532   }
533 
534   /**
535    * Compare two WritingModes for equality.
536    */
537   bool operator==(const WritingMode& aOther) const {
538     return mWritingMode == aOther.mWritingMode;
539   }
540 
541   bool operator!=(const WritingMode& aOther) const {
542     return mWritingMode != aOther.mWritingMode;
543   }
544 
545   /**
546    * Check whether two modes are orthogonal to each other.
547    */
IsOrthogonalTo(const WritingMode & aOther)548   bool IsOrthogonalTo(const WritingMode& aOther) const {
549     return IsVertical() != aOther.IsVertical();
550   }
551 
552   /**
553    * Returns true if this WritingMode's aLogicalAxis has the same physical
554    * start side as the parallel axis of WritingMode |aOther|.
555    *
556    * @param aLogicalAxis The axis to compare from this WritingMode.
557    * @param aOther The other WritingMode (from which we'll choose the axis
558    *               that's parallel to this WritingMode's aLogicalAxis, for
559    *               comparison).
560    */
ParallelAxisStartsOnSameSide(LogicalAxis aLogicalAxis,const WritingMode & aOther)561   bool ParallelAxisStartsOnSameSide(LogicalAxis aLogicalAxis,
562                                     const WritingMode& aOther) const {
563     mozilla::Side myStartSide =
564         this->PhysicalSide(MakeLogicalSide(aLogicalAxis, eLogicalEdgeStart));
565 
566     // Figure out which of aOther's axes is parallel to |this| WritingMode's
567     // aLogicalAxis, and get its physical start side as well.
568     LogicalAxis otherWMAxis = aOther.IsOrthogonalTo(*this)
569                                   ? GetOrthogonalAxis(aLogicalAxis)
570                                   : aLogicalAxis;
571     mozilla::Side otherWMStartSide =
572         aOther.PhysicalSide(MakeLogicalSide(otherWMAxis, eLogicalEdgeStart));
573 
574     NS_ASSERTION(myStartSide % 2 == otherWMStartSide % 2,
575                  "Should end up with sides in the same physical axis");
576     return myStartSide == otherWMStartSide;
577   }
578 
GetBits()579   uint8_t GetBits() const { return mWritingMode.bits; }
580 
581  private:
582   friend class LogicalPoint;
583   friend class LogicalSize;
584   friend struct LogicalSides;
585   friend class LogicalMargin;
586   friend class LogicalRect;
587 
588   friend struct IPC::ParamTraits<WritingMode>;
589   // IMENotification cannot store this class directly since this has some
590   // constructors.  Therefore, it stores mWritingMode and recreate the
591   // instance from it.
592   friend struct widget::IMENotification;
593 
594   /**
595    * Return a WritingMode representing an unknown value.
596    */
597   static inline WritingMode Unknown() {
598     return WritingMode(eUnknownWritingMode);
599   }
600 
601   /**
602    * Constructing a WritingMode with an arbitrary value is a private operation
603    * currently only used by the Unknown() and IgnoreSideways() methods.
604    */
605   explicit WritingMode(uint8_t aValue) : mWritingMode{aValue} {}
606 
607   StyleWritingMode mWritingMode;
608 
609   enum Masks {
610     // Masks for output enums
611     eInlineMask = 0x03,  // VERTICAL | INLINE_REVERSED
612     eBlockMask = 0x05,   // VERTICAL | VERTICAL_LR
613   };
614 };
615 
616 inline std::ostream& operator<<(std::ostream& aStream, const WritingMode& aWM) {
617   return aStream << (aWM.IsVertical()
618                          ? aWM.IsVerticalLR() ? aWM.IsBidiLTR()
619                                                     ? aWM.IsSideways()
620                                                           ? "sw-lr-ltr"
621                                                           : "v-lr-ltr"
622                                                 : aWM.IsSideways() ? "sw-lr-rtl"
623                                                                    : "v-lr-rtl"
624                            : aWM.IsBidiLTR()
625                                ? aWM.IsSideways() ? "sw-rl-ltr" : "v-rl-ltr"
626                            : aWM.IsSideways() ? "sw-rl-rtl"
627                                               : "v-rl-rtl"
628                      : aWM.IsBidiLTR() ? "h-ltr"
629                                        : "h-rtl");
630 }
631 
632 /**
633  * Logical-coordinate classes:
634  *
635  * There are three sets of coordinate space:
636  *   - physical (top, left, bottom, right)
637  *       relative to graphics coord system
638  *   - flow-relative (block-start, inline-start, block-end, inline-end)
639  *       relative to block/inline flow directions
640  *   - line-relative (line-over, line-left, line-under, line-right)
641  *       relative to glyph orientation / inline bidi directions
642  * See CSS3 Writing Modes for more information
643  *   http://www.w3.org/TR/css3-writing-modes/#abstract-box
644  *
645  * For shorthand, B represents the block-axis
646  *                I represents the inline-axis
647  *
648  * The flow-relative geometric classes store coords in flow-relative space.
649  * They use a private ns{Point,Size,Rect,Margin} member to store the actual
650  * coordinate values, but reinterpret them as logical instead of physical.
651  * This allows us to easily perform calculations in logical space (provided
652  * writing modes of the operands match), by simply mapping to nsPoint (etc)
653  * methods.
654  *
655  * Physical-coordinate accessors/setters are responsible to translate these
656  * internal logical values as necessary.
657  *
658  * In DEBUG builds, the logical types store their WritingMode and check
659  * that the same WritingMode is passed whenever callers ask them to do a
660  * writing-mode-dependent operation. Non-DEBUG builds do NOT check this,
661  * to avoid the overhead of storing WritingMode fields.
662  *
663  * Open question: do we need a different set optimized for line-relative
664  * math, for use in nsLineLayout and the like? Or is multiplying values
665  * by FlowRelativeToLineRelativeFactor() enough?
666  */
667 
668 /**
669  * Flow-relative point
670  */
671 class LogicalPoint {
672  public:
673   explicit LogicalPoint(WritingMode aWritingMode)
674       :
675 #ifdef DEBUG
676         mWritingMode(aWritingMode),
677 #endif
678         mPoint(0, 0) {
679   }
680 
681   // Construct from a writing mode and individual coordinates (which MUST be
682   // values in that writing mode, NOT physical coordinates!)
683   LogicalPoint(WritingMode aWritingMode, nscoord aI, nscoord aB)
684       :
685 #ifdef DEBUG
686         mWritingMode(aWritingMode),
687 #endif
688         mPoint(aI, aB) {
689   }
690 
691   // Construct from a writing mode and a physical point, within a given
692   // containing rectangle's size (defining the conversion between LTR
693   // and RTL coordinates, and between TTB and BTT coordinates).
694   LogicalPoint(WritingMode aWritingMode, const nsPoint& aPoint,
695                const nsSize& aContainerSize)
696 #ifdef DEBUG
697       : mWritingMode(aWritingMode)
698 #endif
699   {
700     if (aWritingMode.IsVertical()) {
701       I() = aWritingMode.IsInlineReversed() ? aContainerSize.height - aPoint.y
702                                             : aPoint.y;
703       B() = aWritingMode.IsVerticalLR() ? aPoint.x
704                                         : aContainerSize.width - aPoint.x;
705     } else {
706       I() = aWritingMode.IsInlineReversed() ? aContainerSize.width - aPoint.x
707                                             : aPoint.x;
708       B() = aPoint.y;
709     }
710   }
711 
712   /**
713    * Read-only (const) access to the logical coordinates.
714    */
715   nscoord I(WritingMode aWritingMode) const  // inline-axis
716   {
717     CHECK_WRITING_MODE(aWritingMode);
718     return mPoint.x;
719   }
720   nscoord B(WritingMode aWritingMode) const  // block-axis
721   {
722     CHECK_WRITING_MODE(aWritingMode);
723     return mPoint.y;
724   }
725   nscoord Pos(LogicalAxis aAxis, WritingMode aWM) const {
726     return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM);
727   }
728   nscoord LineRelative(WritingMode aWritingMode,
729                        const nsSize& aContainerSize) const  // line-axis
730   {
731     CHECK_WRITING_MODE(aWritingMode);
732     if (aWritingMode.IsBidiLTR()) {
733       return I();
734     }
735     return (aWritingMode.IsVertical() ? aContainerSize.height
736                                       : aContainerSize.width) -
737            I();
738   }
739 
740   /**
741    * These non-const accessors return a reference (lvalue) that can be
742    * assigned to by callers.
743    */
744   nscoord& I(WritingMode aWritingMode)  // inline-axis
745   {
746     CHECK_WRITING_MODE(aWritingMode);
747     return mPoint.x;
748   }
749   nscoord& B(WritingMode aWritingMode)  // block-axis
750   {
751     CHECK_WRITING_MODE(aWritingMode);
752     return mPoint.y;
753   }
754   nscoord& Pos(LogicalAxis aAxis, WritingMode aWM) {
755     return aAxis == eLogicalAxisInline ? I(aWM) : B(aWM);
756   }
757 
758   /**
759    * Return a physical point corresponding to our logical coordinates,
760    * converted according to our writing mode.
761    */
762   nsPoint GetPhysicalPoint(WritingMode aWritingMode,
763                            const nsSize& aContainerSize) const {
764     CHECK_WRITING_MODE(aWritingMode);
765     if (aWritingMode.IsVertical()) {
766       return nsPoint(
767           aWritingMode.IsVerticalLR() ? B() : aContainerSize.width - B(),
768           aWritingMode.IsInlineReversed() ? aContainerSize.height - I() : I());
769     } else {
770       return nsPoint(
771           aWritingMode.IsInlineReversed() ? aContainerSize.width - I() : I(),
772           B());
773     }
774   }
775 
776   /**
777    * Return the equivalent point in a different writing mode.
778    */
779   LogicalPoint ConvertTo(WritingMode aToMode, WritingMode aFromMode,
780                          const nsSize& aContainerSize) const {
781     CHECK_WRITING_MODE(aFromMode);
782     return aToMode == aFromMode
783                ? *this
784                : LogicalPoint(aToMode,
785                               GetPhysicalPoint(aFromMode, aContainerSize),
786                               aContainerSize);
787   }
788 
789   bool operator==(const LogicalPoint& aOther) const {
790     CHECK_WRITING_MODE(aOther.GetWritingMode());
791     return mPoint == aOther.mPoint;
792   }
793 
794   bool operator!=(const LogicalPoint& aOther) const {
795     CHECK_WRITING_MODE(aOther.GetWritingMode());
796     return mPoint != aOther.mPoint;
797   }
798 
799   LogicalPoint operator+(const LogicalPoint& aOther) const {
800     CHECK_WRITING_MODE(aOther.GetWritingMode());
801     // In non-debug builds, LogicalPoint does not store the WritingMode,
802     // so the first parameter here (which will always be eUnknownWritingMode)
803     // is ignored.
804     return LogicalPoint(GetWritingMode(), mPoint.x + aOther.mPoint.x,
805                         mPoint.y + aOther.mPoint.y);
806   }
807 
808   LogicalPoint& operator+=(const LogicalPoint& aOther) {
809     CHECK_WRITING_MODE(aOther.GetWritingMode());
810     I() += aOther.I();
811     B() += aOther.B();
812     return *this;
813   }
814 
815   LogicalPoint operator-(const LogicalPoint& aOther) const {
816     CHECK_WRITING_MODE(aOther.GetWritingMode());
817     // In non-debug builds, LogicalPoint does not store the WritingMode,
818     // so the first parameter here (which will always be eUnknownWritingMode)
819     // is ignored.
820     return LogicalPoint(GetWritingMode(), mPoint.x - aOther.mPoint.x,
821                         mPoint.y - aOther.mPoint.y);
822   }
823 
824   LogicalPoint& operator-=(const LogicalPoint& aOther) {
825     CHECK_WRITING_MODE(aOther.GetWritingMode());
826     I() -= aOther.I();
827     B() -= aOther.B();
828     return *this;
829   }
830 
831   friend std::ostream& operator<<(std::ostream& aStream,
832                                   const LogicalPoint& aPoint) {
833     return aStream << aPoint.mPoint;
834   }
835 
836  private:
837   friend class LogicalRect;
838 
839   /**
840    * NOTE that in non-DEBUG builds, GetWritingMode() always returns
841    * eUnknownWritingMode, as the current mode is not stored in the logical-
842    * geometry classes. Therefore, this method is private; it is used ONLY
843    * by the DEBUG-mode checking macros in this class and its friends;
844    * other code is not allowed to ask a logical point for its writing mode,
845    * as this info will simply not be available in non-DEBUG builds.
846    *
847    * Also, in non-DEBUG builds, CHECK_WRITING_MODE does nothing, and the
848    * WritingMode parameter to logical methods will generally be optimized
849    * away altogether.
850    */
851 #ifdef DEBUG
852   WritingMode GetWritingMode() const { return mWritingMode; }
853 #else
854   WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
855 #endif
856 
857   // We don't allow construction of a LogicalPoint with no writing mode.
858   LogicalPoint() = delete;
859 
860   // Accessors that don't take or check a WritingMode value.
861   // These are for internal use only; they are called by methods that have
862   // themselves already checked the WritingMode passed by the caller.
863   nscoord I() const  // inline-axis
864   {
865     return mPoint.x;
866   }
867   nscoord B() const  // block-axis
868   {
869     return mPoint.y;
870   }
871 
872   nscoord& I()  // inline-axis
873   {
874     return mPoint.x;
875   }
876   nscoord& B()  // block-axis
877   {
878     return mPoint.y;
879   }
880 
881 #ifdef DEBUG
882   WritingMode mWritingMode;
883 #endif
884 
885   // We use an nsPoint to hold the coordinates, but reinterpret its .x and .y
886   // fields as the inline and block directions. Hence, this is not exposed
887   // directly, but only through accessors that will map them according to the
888   // writing mode.
889   nsPoint mPoint;
890 };
891 
892 /**
893  * Flow-relative size
894  */
895 class LogicalSize {
896  public:
897   explicit LogicalSize(WritingMode aWritingMode)
898       :
899 #ifdef DEBUG
900         mWritingMode(aWritingMode),
901 #endif
902         mSize(0, 0) {
903   }
904 
905   LogicalSize(WritingMode aWritingMode, nscoord aISize, nscoord aBSize)
906       :
907 #ifdef DEBUG
908         mWritingMode(aWritingMode),
909 #endif
910         mSize(aISize, aBSize) {
911   }
912 
913   LogicalSize(WritingMode aWritingMode, const nsSize& aPhysicalSize)
914 #ifdef DEBUG
915       : mWritingMode(aWritingMode)
916 #endif
917   {
918     if (aWritingMode.IsVertical()) {
919       ISize() = aPhysicalSize.height;
920       BSize() = aPhysicalSize.width;
921     } else {
922       ISize() = aPhysicalSize.width;
923       BSize() = aPhysicalSize.height;
924     }
925   }
926 
927   void SizeTo(WritingMode aWritingMode, nscoord aISize, nscoord aBSize) {
928     CHECK_WRITING_MODE(aWritingMode);
929     mSize.SizeTo(aISize, aBSize);
930   }
931 
932   /**
933    * Dimensions in logical and physical terms
934    */
935   nscoord ISize(WritingMode aWritingMode) const  // inline-size
936   {
937     CHECK_WRITING_MODE(aWritingMode);
938     return mSize.width;
939   }
940   nscoord BSize(WritingMode aWritingMode) const  // block-size
941   {
942     CHECK_WRITING_MODE(aWritingMode);
943     return mSize.height;
944   }
945   nscoord Size(LogicalAxis aAxis, WritingMode aWM) const {
946     return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
947   }
948 
949   nscoord Width(WritingMode aWritingMode) const {
950     CHECK_WRITING_MODE(aWritingMode);
951     return aWritingMode.IsVertical() ? BSize() : ISize();
952   }
953   nscoord Height(WritingMode aWritingMode) const {
954     CHECK_WRITING_MODE(aWritingMode);
955     return aWritingMode.IsVertical() ? ISize() : BSize();
956   }
957 
958   /**
959    * Writable references to the logical dimensions
960    */
961   nscoord& ISize(WritingMode aWritingMode)  // inline-size
962   {
963     CHECK_WRITING_MODE(aWritingMode);
964     return mSize.width;
965   }
966   nscoord& BSize(WritingMode aWritingMode)  // block-size
967   {
968     CHECK_WRITING_MODE(aWritingMode);
969     return mSize.height;
970   }
971   nscoord& Size(LogicalAxis aAxis, WritingMode aWM) {
972     return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
973   }
974 
975   /**
976    * Return an nsSize containing our physical dimensions
977    */
978   nsSize GetPhysicalSize(WritingMode aWritingMode) const {
979     CHECK_WRITING_MODE(aWritingMode);
980     return aWritingMode.IsVertical() ? nsSize(BSize(), ISize())
981                                      : nsSize(ISize(), BSize());
982   }
983 
984   /**
985    * Return a LogicalSize representing this size in a different writing mode
986    */
987   LogicalSize ConvertTo(WritingMode aToMode, WritingMode aFromMode) const {
988 #ifdef DEBUG
989     // In DEBUG builds make sure to return a LogicalSize with the
990     // expected writing mode
991     CHECK_WRITING_MODE(aFromMode);
992     return aToMode == aFromMode
993                ? *this
994                : LogicalSize(aToMode, GetPhysicalSize(aFromMode));
995 #else
996     // optimization for non-DEBUG builds where LogicalSize doesn't store
997     // the writing mode
998     return (aToMode == aFromMode || !aToMode.IsOrthogonalTo(aFromMode))
999                ? *this
1000                : LogicalSize(aToMode, BSize(), ISize());
1001 #endif
1002   }
1003 
1004   /**
1005    * Test if a size is (0, 0).
1006    */
1007   bool IsAllZero() const { return ISize() == 0 && BSize() == 0; }
1008 
1009   /**
1010    * Various binary operators on LogicalSize. These are valid ONLY for operands
1011    * that share the same writing mode.
1012    */
1013   bool operator==(const LogicalSize& aOther) const {
1014     CHECK_WRITING_MODE(aOther.GetWritingMode());
1015     return mSize == aOther.mSize;
1016   }
1017 
1018   bool operator!=(const LogicalSize& aOther) const {
1019     CHECK_WRITING_MODE(aOther.GetWritingMode());
1020     return mSize != aOther.mSize;
1021   }
1022 
1023   LogicalSize operator+(const LogicalSize& aOther) const {
1024     CHECK_WRITING_MODE(aOther.GetWritingMode());
1025     return LogicalSize(GetWritingMode(), ISize() + aOther.ISize(),
1026                        BSize() + aOther.BSize());
1027   }
1028   LogicalSize& operator+=(const LogicalSize& aOther) {
1029     CHECK_WRITING_MODE(aOther.GetWritingMode());
1030     ISize() += aOther.ISize();
1031     BSize() += aOther.BSize();
1032     return *this;
1033   }
1034 
1035   LogicalSize operator-(const LogicalSize& aOther) const {
1036     CHECK_WRITING_MODE(aOther.GetWritingMode());
1037     return LogicalSize(GetWritingMode(), ISize() - aOther.ISize(),
1038                        BSize() - aOther.BSize());
1039   }
1040   LogicalSize& operator-=(const LogicalSize& aOther) {
1041     CHECK_WRITING_MODE(aOther.GetWritingMode());
1042     ISize() -= aOther.ISize();
1043     BSize() -= aOther.BSize();
1044     return *this;
1045   }
1046 
1047   friend std::ostream& operator<<(std::ostream& aStream,
1048                                   const LogicalSize& aSize) {
1049     return aStream << aSize.mSize;
1050   }
1051 
1052  private:
1053   friend class LogicalRect;
1054 
1055   LogicalSize() = delete;
1056 
1057 #ifdef DEBUG
1058   WritingMode GetWritingMode() const { return mWritingMode; }
1059 #else
1060   WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
1061 #endif
1062 
1063   nscoord ISize() const  // inline-size
1064   {
1065     return mSize.width;
1066   }
1067   nscoord BSize() const  // block-size
1068   {
1069     return mSize.height;
1070   }
1071 
1072   nscoord& ISize()  // inline-size
1073   {
1074     return mSize.width;
1075   }
1076   nscoord& BSize()  // block-size
1077   {
1078     return mSize.height;
1079   }
1080 
1081 #ifdef DEBUG
1082   WritingMode mWritingMode;
1083 #endif
1084   nsSize mSize;
1085 };
1086 
1087 /**
1088  * LogicalSides represents a set of logical sides.
1089  */
1090 struct LogicalSides final {
1091   explicit LogicalSides(WritingMode aWritingMode)
1092       :
1093 #ifdef DEBUG
1094         mWritingMode(aWritingMode),
1095 #endif
1096         mBits(0) {
1097   }
1098   LogicalSides(WritingMode aWritingMode, LogicalSideBits aSideBits)
1099       :
1100 #ifdef DEBUG
1101         mWritingMode(aWritingMode),
1102 #endif
1103         mBits(aSideBits) {
1104     MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits");
1105   }
1106   bool IsEmpty() const { return mBits == 0; }
1107   bool BStart() const { return mBits & eLogicalSideBitsBStart; }
1108   bool BEnd() const { return mBits & eLogicalSideBitsBEnd; }
1109   bool IStart() const { return mBits & eLogicalSideBitsIStart; }
1110   bool IEnd() const { return mBits & eLogicalSideBitsIEnd; }
1111   bool Contains(LogicalSideBits aSideBits) const {
1112     MOZ_ASSERT((aSideBits & ~eLogicalSideBitsAll) == 0, "illegal side bits");
1113     return (mBits & aSideBits) == aSideBits;
1114   }
1115   LogicalSides operator|(LogicalSides aOther) const {
1116     CHECK_WRITING_MODE(aOther.GetWritingMode());
1117     return *this | LogicalSideBits(aOther.mBits);
1118   }
1119   LogicalSides operator|(LogicalSideBits aSideBits) const {
1120     return LogicalSides(GetWritingMode(), LogicalSideBits(mBits | aSideBits));
1121   }
1122   LogicalSides& operator|=(LogicalSides aOther) {
1123     CHECK_WRITING_MODE(aOther.GetWritingMode());
1124     return *this |= LogicalSideBits(aOther.mBits);
1125   }
1126   LogicalSides& operator|=(LogicalSideBits aSideBits) {
1127     mBits |= aSideBits;
1128     return *this;
1129   }
1130   bool operator==(LogicalSides aOther) const {
1131     CHECK_WRITING_MODE(aOther.GetWritingMode());
1132     return mBits == aOther.mBits;
1133   }
1134   bool operator!=(LogicalSides aOther) const {
1135     CHECK_WRITING_MODE(aOther.GetWritingMode());
1136     return !(*this == aOther);
1137   }
1138 
1139 #ifdef DEBUG
1140   WritingMode GetWritingMode() const { return mWritingMode; }
1141 #else
1142   WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
1143 #endif
1144 
1145  private:
1146 #ifdef DEBUG
1147   WritingMode mWritingMode;
1148 #endif
1149   uint8_t mBits;
1150 };
1151 
1152 /**
1153  * Flow-relative margin
1154  */
1155 class LogicalMargin {
1156  public:
1157   explicit LogicalMargin(WritingMode aWritingMode)
1158       :
1159 #ifdef DEBUG
1160         mWritingMode(aWritingMode),
1161 #endif
1162         mMargin(0, 0, 0, 0) {
1163   }
1164 
1165   LogicalMargin(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd,
1166                 nscoord aBEnd, nscoord aIStart)
1167       :
1168 #ifdef DEBUG
1169         mWritingMode(aWritingMode),
1170 #endif
1171         mMargin(aBStart, aIEnd, aBEnd, aIStart) {
1172   }
1173 
1174   LogicalMargin(WritingMode aWritingMode, const nsMargin& aPhysicalMargin)
1175 #ifdef DEBUG
1176       : mWritingMode(aWritingMode)
1177 #endif
1178   {
1179     if (aWritingMode.IsVertical()) {
1180       if (aWritingMode.IsVerticalLR()) {
1181         mMargin.top = aPhysicalMargin.left;
1182         mMargin.bottom = aPhysicalMargin.right;
1183       } else {
1184         mMargin.top = aPhysicalMargin.right;
1185         mMargin.bottom = aPhysicalMargin.left;
1186       }
1187       if (aWritingMode.IsInlineReversed()) {
1188         mMargin.left = aPhysicalMargin.bottom;
1189         mMargin.right = aPhysicalMargin.top;
1190       } else {
1191         mMargin.left = aPhysicalMargin.top;
1192         mMargin.right = aPhysicalMargin.bottom;
1193       }
1194     } else {
1195       mMargin.top = aPhysicalMargin.top;
1196       mMargin.bottom = aPhysicalMargin.bottom;
1197       if (aWritingMode.IsInlineReversed()) {
1198         mMargin.left = aPhysicalMargin.right;
1199         mMargin.right = aPhysicalMargin.left;
1200       } else {
1201         mMargin.left = aPhysicalMargin.left;
1202         mMargin.right = aPhysicalMargin.right;
1203       }
1204     }
1205   }
1206 
1207   nscoord IStart(WritingMode aWritingMode) const  // inline-start margin
1208   {
1209     CHECK_WRITING_MODE(aWritingMode);
1210     return mMargin.left;
1211   }
1212   nscoord IEnd(WritingMode aWritingMode) const  // inline-end margin
1213   {
1214     CHECK_WRITING_MODE(aWritingMode);
1215     return mMargin.right;
1216   }
1217   nscoord BStart(WritingMode aWritingMode) const  // block-start margin
1218   {
1219     CHECK_WRITING_MODE(aWritingMode);
1220     return mMargin.top;
1221   }
1222   nscoord BEnd(WritingMode aWritingMode) const  // block-end margin
1223   {
1224     CHECK_WRITING_MODE(aWritingMode);
1225     return mMargin.bottom;
1226   }
1227   nscoord Start(LogicalAxis aAxis, WritingMode aWM) const {
1228     return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
1229   }
1230   nscoord End(LogicalAxis aAxis, WritingMode aWM) const {
1231     return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
1232   }
1233 
1234   nscoord& IStart(WritingMode aWritingMode)  // inline-start margin
1235   {
1236     CHECK_WRITING_MODE(aWritingMode);
1237     return mMargin.left;
1238   }
1239   nscoord& IEnd(WritingMode aWritingMode)  // inline-end margin
1240   {
1241     CHECK_WRITING_MODE(aWritingMode);
1242     return mMargin.right;
1243   }
1244   nscoord& BStart(WritingMode aWritingMode)  // block-start margin
1245   {
1246     CHECK_WRITING_MODE(aWritingMode);
1247     return mMargin.top;
1248   }
1249   nscoord& BEnd(WritingMode aWritingMode)  // block-end margin
1250   {
1251     CHECK_WRITING_MODE(aWritingMode);
1252     return mMargin.bottom;
1253   }
1254   nscoord& Start(LogicalAxis aAxis, WritingMode aWM) {
1255     return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
1256   }
1257   nscoord& End(LogicalAxis aAxis, WritingMode aWM) {
1258     return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
1259   }
1260 
1261   nscoord IStartEnd(WritingMode aWritingMode) const  // inline margins
1262   {
1263     CHECK_WRITING_MODE(aWritingMode);
1264     return mMargin.LeftRight();
1265   }
1266   nscoord BStartEnd(WritingMode aWritingMode) const  // block margins
1267   {
1268     CHECK_WRITING_MODE(aWritingMode);
1269     return mMargin.TopBottom();
1270   }
1271   nscoord StartEnd(LogicalAxis aAxis, WritingMode aWM) const {
1272     return aAxis == eLogicalAxisInline ? IStartEnd(aWM) : BStartEnd(aWM);
1273   }
1274 
1275   nscoord Side(LogicalSide aSide, WritingMode aWM) const {
1276     switch (aSide) {
1277       case eLogicalSideBStart:
1278         return BStart(aWM);
1279       case eLogicalSideBEnd:
1280         return BEnd(aWM);
1281       case eLogicalSideIStart:
1282         return IStart(aWM);
1283       case eLogicalSideIEnd:
1284         return IEnd(aWM);
1285     }
1286 
1287     MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
1288     return BStart(aWM);
1289   }
1290   nscoord& Side(LogicalSide aSide, WritingMode aWM) {
1291     switch (aSide) {
1292       case eLogicalSideBStart:
1293         return BStart(aWM);
1294       case eLogicalSideBEnd:
1295         return BEnd(aWM);
1296       case eLogicalSideIStart:
1297         return IStart(aWM);
1298       case eLogicalSideIEnd:
1299         return IEnd(aWM);
1300     }
1301 
1302     MOZ_ASSERT_UNREACHABLE("We should handle all sides!");
1303     return BStart(aWM);
1304   }
1305 
1306   /*
1307    * Return margin values for line-relative sides, as defined in
1308    * http://www.w3.org/TR/css-writing-modes-3/#line-directions:
1309    *
1310    * line-left
1311    *     Nominally the side from which LTR text would start.
1312    * line-right
1313    *     Nominally the side from which RTL text would start. (Opposite of
1314    *     line-left.)
1315    */
1316   nscoord LineLeft(WritingMode aWritingMode) const {
1317     // We don't need to CHECK_WRITING_MODE here because the IStart or IEnd
1318     // accessor that we call will do it.
1319     return aWritingMode.IsBidiLTR() ? IStart(aWritingMode) : IEnd(aWritingMode);
1320   }
1321   nscoord LineRight(WritingMode aWritingMode) const {
1322     return aWritingMode.IsBidiLTR() ? IEnd(aWritingMode) : IStart(aWritingMode);
1323   }
1324 
1325   /**
1326    * Return a LogicalSize representing the total size of the inline-
1327    * and block-dimension margins.
1328    */
1329   LogicalSize Size(WritingMode aWritingMode) const {
1330     CHECK_WRITING_MODE(aWritingMode);
1331     return LogicalSize(aWritingMode, IStartEnd(), BStartEnd());
1332   }
1333 
1334   /**
1335    * Accessors for physical margins, using our writing mode to convert from
1336    * logical values.
1337    */
1338   nscoord Top(WritingMode aWritingMode) const {
1339     CHECK_WRITING_MODE(aWritingMode);
1340     return aWritingMode.IsVertical()
1341                ? (aWritingMode.IsInlineReversed() ? IEnd() : IStart())
1342                : BStart();
1343   }
1344 
1345   nscoord Bottom(WritingMode aWritingMode) const {
1346     CHECK_WRITING_MODE(aWritingMode);
1347     return aWritingMode.IsVertical()
1348                ? (aWritingMode.IsInlineReversed() ? IStart() : IEnd())
1349                : BEnd();
1350   }
1351 
1352   nscoord Left(WritingMode aWritingMode) const {
1353     CHECK_WRITING_MODE(aWritingMode);
1354     return aWritingMode.IsVertical()
1355                ? (aWritingMode.IsVerticalLR() ? BStart() : BEnd())
1356                : (aWritingMode.IsInlineReversed() ? IEnd() : IStart());
1357   }
1358 
1359   nscoord Right(WritingMode aWritingMode) const {
1360     CHECK_WRITING_MODE(aWritingMode);
1361     return aWritingMode.IsVertical()
1362                ? (aWritingMode.IsVerticalLR() ? BEnd() : BStart())
1363                : (aWritingMode.IsInlineReversed() ? IStart() : IEnd());
1364   }
1365 
1366   nscoord LeftRight(WritingMode aWritingMode) const {
1367     CHECK_WRITING_MODE(aWritingMode);
1368     return aWritingMode.IsVertical() ? BStartEnd() : IStartEnd();
1369   }
1370 
1371   nscoord TopBottom(WritingMode aWritingMode) const {
1372     CHECK_WRITING_MODE(aWritingMode);
1373     return aWritingMode.IsVertical() ? IStartEnd() : BStartEnd();
1374   }
1375 
1376   void SizeTo(WritingMode aWritingMode, nscoord aBStart, nscoord aIEnd,
1377               nscoord aBEnd, nscoord aIStart) {
1378     CHECK_WRITING_MODE(aWritingMode);
1379     mMargin.SizeTo(aBStart, aIEnd, aBEnd, aIStart);
1380   }
1381 
1382   /**
1383    * Return an nsMargin containing our physical coordinates
1384    */
1385   nsMargin GetPhysicalMargin(WritingMode aWritingMode) const {
1386     CHECK_WRITING_MODE(aWritingMode);
1387     return aWritingMode.IsVertical()
1388                ? (aWritingMode.IsVerticalLR()
1389                       ? (aWritingMode.IsInlineReversed()
1390                              ? nsMargin(IEnd(), BEnd(), IStart(), BStart())
1391                              : nsMargin(IStart(), BEnd(), IEnd(), BStart()))
1392                       : (aWritingMode.IsInlineReversed()
1393                              ? nsMargin(IEnd(), BStart(), IStart(), BEnd())
1394                              : nsMargin(IStart(), BStart(), IEnd(), BEnd())))
1395                : (aWritingMode.IsInlineReversed()
1396                       ? nsMargin(BStart(), IStart(), BEnd(), IEnd())
1397                       : nsMargin(BStart(), IEnd(), BEnd(), IStart()));
1398   }
1399 
1400   /**
1401    * Return a LogicalMargin representing this margin in a different
1402    * writing mode
1403    */
1404   LogicalMargin ConvertTo(WritingMode aToMode, WritingMode aFromMode) const {
1405     CHECK_WRITING_MODE(aFromMode);
1406     return aToMode == aFromMode
1407                ? *this
1408                : LogicalMargin(aToMode, GetPhysicalMargin(aFromMode));
1409   }
1410 
1411   LogicalMargin& ApplySkipSides(LogicalSides aSkipSides) {
1412     CHECK_WRITING_MODE(aSkipSides.GetWritingMode());
1413     if (aSkipSides.BStart()) {
1414       BStart() = 0;
1415     }
1416     if (aSkipSides.BEnd()) {
1417       BEnd() = 0;
1418     }
1419     if (aSkipSides.IStart()) {
1420       IStart() = 0;
1421     }
1422     if (aSkipSides.IEnd()) {
1423       IEnd() = 0;
1424     }
1425     return *this;
1426   }
1427 
1428   bool IsAllZero() const {
1429     return (mMargin.left == 0 && mMargin.top == 0 && mMargin.right == 0 &&
1430             mMargin.bottom == 0);
1431   }
1432 
1433   bool operator==(const LogicalMargin& aMargin) const {
1434     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1435     return mMargin == aMargin.mMargin;
1436   }
1437 
1438   bool operator!=(const LogicalMargin& aMargin) const {
1439     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1440     return mMargin != aMargin.mMargin;
1441   }
1442 
1443   LogicalMargin operator+(const LogicalMargin& aMargin) const {
1444     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1445     return LogicalMargin(GetWritingMode(), BStart() + aMargin.BStart(),
1446                          IEnd() + aMargin.IEnd(), BEnd() + aMargin.BEnd(),
1447                          IStart() + aMargin.IStart());
1448   }
1449 
1450   LogicalMargin operator+=(const LogicalMargin& aMargin) {
1451     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1452     mMargin += aMargin.mMargin;
1453     return *this;
1454   }
1455 
1456   LogicalMargin operator-(const LogicalMargin& aMargin) const {
1457     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1458     return LogicalMargin(GetWritingMode(), BStart() - aMargin.BStart(),
1459                          IEnd() - aMargin.IEnd(), BEnd() - aMargin.BEnd(),
1460                          IStart() - aMargin.IStart());
1461   }
1462 
1463   friend std::ostream& operator<<(std::ostream& aStream,
1464                                   const LogicalMargin& aMargin) {
1465     return aStream << aMargin.mMargin;
1466   }
1467 
1468  private:
1469   friend class LogicalRect;
1470 
1471   LogicalMargin() = delete;
1472 
1473 #ifdef DEBUG
1474   WritingMode GetWritingMode() const { return mWritingMode; }
1475 #else
1476   WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
1477 #endif
1478 
1479   nscoord IStart() const  // inline-start margin
1480   {
1481     return mMargin.left;
1482   }
1483   nscoord IEnd() const  // inline-end margin
1484   {
1485     return mMargin.right;
1486   }
1487   nscoord BStart() const  // block-start margin
1488   {
1489     return mMargin.top;
1490   }
1491   nscoord BEnd() const  // block-end margin
1492   {
1493     return mMargin.bottom;
1494   }
1495 
1496   nscoord& IStart()  // inline-start margin
1497   {
1498     return mMargin.left;
1499   }
1500   nscoord& IEnd()  // inline-end margin
1501   {
1502     return mMargin.right;
1503   }
1504   nscoord& BStart()  // block-start margin
1505   {
1506     return mMargin.top;
1507   }
1508   nscoord& BEnd()  // block-end margin
1509   {
1510     return mMargin.bottom;
1511   }
1512 
1513   nscoord IStartEnd() const  // inline margins
1514   {
1515     return mMargin.LeftRight();
1516   }
1517   nscoord BStartEnd() const  // block margins
1518   {
1519     return mMargin.TopBottom();
1520   }
1521 
1522 #ifdef DEBUG
1523   WritingMode mWritingMode;
1524 #endif
1525   nsMargin mMargin;
1526 };
1527 
1528 /**
1529  * Flow-relative rectangle
1530  */
1531 class LogicalRect {
1532  public:
1533   explicit LogicalRect(WritingMode aWritingMode)
1534       :
1535 #ifdef DEBUG
1536         mWritingMode(aWritingMode),
1537 #endif
1538         mIStart(0),
1539         mBStart(0),
1540         mISize(0),
1541         mBSize(0) {
1542   }
1543 
1544   LogicalRect(WritingMode aWritingMode, nscoord aIStart, nscoord aBStart,
1545               nscoord aISize, nscoord aBSize)
1546       :
1547 #ifdef DEBUG
1548         mWritingMode(aWritingMode),
1549 #endif
1550         mIStart(aIStart),
1551         mBStart(aBStart),
1552         mISize(aISize),
1553         mBSize(aBSize) {
1554   }
1555 
1556   LogicalRect(WritingMode aWritingMode, const LogicalPoint& aOrigin,
1557               const LogicalSize& aSize)
1558       :
1559 #ifdef DEBUG
1560         mWritingMode(aWritingMode),
1561 #endif
1562         mIStart(aOrigin.mPoint.x),
1563         mBStart(aOrigin.mPoint.y),
1564         mISize(aSize.mSize.width),
1565         mBSize(aSize.mSize.height) {
1566     CHECK_WRITING_MODE(aOrigin.GetWritingMode());
1567     CHECK_WRITING_MODE(aSize.GetWritingMode());
1568   }
1569 
1570   LogicalRect(WritingMode aWritingMode, const nsRect& aRect,
1571               const nsSize& aContainerSize)
1572 #ifdef DEBUG
1573       : mWritingMode(aWritingMode)
1574 #endif
1575   {
1576     if (aWritingMode.IsVertical()) {
1577       mBStart = aWritingMode.IsVerticalLR()
1578                     ? aRect.X()
1579                     : aContainerSize.width - aRect.XMost();
1580       mIStart = aWritingMode.IsInlineReversed()
1581                     ? aContainerSize.height - aRect.YMost()
1582                     : aRect.Y();
1583       mBSize = aRect.Width();
1584       mISize = aRect.Height();
1585     } else {
1586       mIStart = aWritingMode.IsInlineReversed()
1587                     ? aContainerSize.width - aRect.XMost()
1588                     : aRect.X();
1589       mBStart = aRect.Y();
1590       mISize = aRect.Width();
1591       mBSize = aRect.Height();
1592     }
1593   }
1594 
1595   /**
1596    * Inline- and block-dimension geometry.
1597    */
1598   nscoord IStart(WritingMode aWritingMode) const  // inline-start edge
1599   {
1600     CHECK_WRITING_MODE(aWritingMode);
1601     return mIStart;
1602   }
1603   nscoord IEnd(WritingMode aWritingMode) const  // inline-end edge
1604   {
1605     CHECK_WRITING_MODE(aWritingMode);
1606     return mIStart + mISize;
1607   }
1608   nscoord ISize(WritingMode aWritingMode) const  // inline-size
1609   {
1610     CHECK_WRITING_MODE(aWritingMode);
1611     return mISize;
1612   }
1613 
1614   nscoord BStart(WritingMode aWritingMode) const  // block-start edge
1615   {
1616     CHECK_WRITING_MODE(aWritingMode);
1617     return mBStart;
1618   }
1619   nscoord BEnd(WritingMode aWritingMode) const  // block-end edge
1620   {
1621     CHECK_WRITING_MODE(aWritingMode);
1622     return mBStart + mBSize;
1623   }
1624   nscoord BSize(WritingMode aWritingMode) const  // block-size
1625   {
1626     CHECK_WRITING_MODE(aWritingMode);
1627     return mBSize;
1628   }
1629 
1630   nscoord Start(LogicalAxis aAxis, WritingMode aWM) const {
1631     return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
1632   }
1633   nscoord End(LogicalAxis aAxis, WritingMode aWM) const {
1634     return aAxis == eLogicalAxisInline ? IEnd(aWM) : BEnd(aWM);
1635   }
1636   nscoord Size(LogicalAxis aAxis, WritingMode aWM) const {
1637     return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
1638   }
1639 
1640   /**
1641    * Writable (reference) accessors are only available for the basic logical
1642    * fields (Start and Size), not derivatives like End.
1643    */
1644   nscoord& IStart(WritingMode aWritingMode)  // inline-start edge
1645   {
1646     CHECK_WRITING_MODE(aWritingMode);
1647     return mIStart;
1648   }
1649   nscoord& ISize(WritingMode aWritingMode)  // inline-size
1650   {
1651     CHECK_WRITING_MODE(aWritingMode);
1652     return mISize;
1653   }
1654   nscoord& BStart(WritingMode aWritingMode)  // block-start edge
1655   {
1656     CHECK_WRITING_MODE(aWritingMode);
1657     return mBStart;
1658   }
1659   nscoord& BSize(WritingMode aWritingMode)  // block-size
1660   {
1661     CHECK_WRITING_MODE(aWritingMode);
1662     return mBSize;
1663   }
1664   nscoord& Start(LogicalAxis aAxis, WritingMode aWM) {
1665     return aAxis == eLogicalAxisInline ? IStart(aWM) : BStart(aWM);
1666   }
1667   nscoord& Size(LogicalAxis aAxis, WritingMode aWM) {
1668     return aAxis == eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
1669   }
1670 
1671   /**
1672    * Accessors for line-relative coordinates
1673    */
1674   nscoord LineLeft(WritingMode aWritingMode,
1675                    const nsSize& aContainerSize) const {
1676     CHECK_WRITING_MODE(aWritingMode);
1677     if (aWritingMode.IsBidiLTR()) {
1678       return IStart();
1679     }
1680     nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height
1681                                                        : aContainerSize.width;
1682     return containerISize - IEnd();
1683   }
1684   nscoord LineRight(WritingMode aWritingMode,
1685                     const nsSize& aContainerSize) const {
1686     CHECK_WRITING_MODE(aWritingMode);
1687     if (aWritingMode.IsBidiLTR()) {
1688       return IEnd();
1689     }
1690     nscoord containerISize = aWritingMode.IsVertical() ? aContainerSize.height
1691                                                        : aContainerSize.width;
1692     return containerISize - IStart();
1693   }
1694 
1695   /**
1696    * Physical coordinates of the rect.
1697    */
1698   nscoord X(WritingMode aWritingMode, nscoord aContainerWidth) const {
1699     CHECK_WRITING_MODE(aWritingMode);
1700     if (aWritingMode.IsVertical()) {
1701       return aWritingMode.IsVerticalLR() ? mBStart : aContainerWidth - BEnd();
1702     }
1703     return aWritingMode.IsInlineReversed() ? aContainerWidth - IEnd() : mIStart;
1704   }
1705 
1706   nscoord Y(WritingMode aWritingMode, nscoord aContainerHeight) const {
1707     CHECK_WRITING_MODE(aWritingMode);
1708     if (aWritingMode.IsVertical()) {
1709       return aWritingMode.IsInlineReversed() ? aContainerHeight - IEnd()
1710                                              : mIStart;
1711     }
1712     return mBStart;
1713   }
1714 
1715   nscoord Width(WritingMode aWritingMode) const {
1716     CHECK_WRITING_MODE(aWritingMode);
1717     return aWritingMode.IsVertical() ? mBSize : mISize;
1718   }
1719 
1720   nscoord Height(WritingMode aWritingMode) const {
1721     CHECK_WRITING_MODE(aWritingMode);
1722     return aWritingMode.IsVertical() ? mISize : mBSize;
1723   }
1724 
1725   nscoord XMost(WritingMode aWritingMode, nscoord aContainerWidth) const {
1726     CHECK_WRITING_MODE(aWritingMode);
1727     if (aWritingMode.IsVertical()) {
1728       return aWritingMode.IsVerticalLR() ? BEnd() : aContainerWidth - mBStart;
1729     }
1730     return aWritingMode.IsInlineReversed() ? aContainerWidth - mIStart : IEnd();
1731   }
1732 
1733   nscoord YMost(WritingMode aWritingMode, nscoord aContainerHeight) const {
1734     CHECK_WRITING_MODE(aWritingMode);
1735     if (aWritingMode.IsVertical()) {
1736       return aWritingMode.IsInlineReversed() ? aContainerHeight - mIStart
1737                                              : IEnd();
1738     }
1739     return BEnd();
1740   }
1741 
1742   bool IsEmpty() const { return mISize <= 0 || mBSize <= 0; }
1743 
1744   bool IsAllZero() const {
1745     return (mIStart == 0 && mBStart == 0 && mISize == 0 && mBSize == 0);
1746   }
1747 
1748   bool IsZeroSize() const { return (mISize == 0 && mBSize == 0); }
1749 
1750   void SetEmpty() { mISize = mBSize = 0; }
1751 
1752   bool IsEqualEdges(const LogicalRect aOther) const {
1753     CHECK_WRITING_MODE(aOther.GetWritingMode());
1754     bool result = mIStart == aOther.mIStart && mBStart == aOther.mBStart &&
1755                   mISize == aOther.mISize && mBSize == aOther.mBSize;
1756 
1757     // We want the same result as nsRect, so assert we get it.
1758     MOZ_ASSERT(result ==
1759                nsRect(mIStart, mBStart, mISize, mBSize)
1760                    .IsEqualEdges(nsRect(aOther.mIStart, aOther.mBStart,
1761                                         aOther.mISize, aOther.mBSize)));
1762     return result;
1763   }
1764 
1765   LogicalPoint Origin(WritingMode aWritingMode) const {
1766     CHECK_WRITING_MODE(aWritingMode);
1767     return LogicalPoint(aWritingMode, IStart(), BStart());
1768   }
1769   void SetOrigin(WritingMode aWritingMode, const LogicalPoint& aPoint) {
1770     IStart(aWritingMode) = aPoint.I(aWritingMode);
1771     BStart(aWritingMode) = aPoint.B(aWritingMode);
1772   }
1773 
1774   LogicalSize Size(WritingMode aWritingMode) const {
1775     CHECK_WRITING_MODE(aWritingMode);
1776     return LogicalSize(aWritingMode, ISize(), BSize());
1777   }
1778 
1779   LogicalRect operator+(const LogicalPoint& aPoint) const {
1780     CHECK_WRITING_MODE(aPoint.GetWritingMode());
1781     return LogicalRect(GetWritingMode(), IStart() + aPoint.I(),
1782                        BStart() + aPoint.B(), ISize(), BSize());
1783   }
1784 
1785   LogicalRect& operator+=(const LogicalPoint& aPoint) {
1786     CHECK_WRITING_MODE(aPoint.GetWritingMode());
1787     mIStart += aPoint.mPoint.x;
1788     mBStart += aPoint.mPoint.y;
1789     return *this;
1790   }
1791 
1792   LogicalRect operator-(const LogicalPoint& aPoint) const {
1793     CHECK_WRITING_MODE(aPoint.GetWritingMode());
1794     return LogicalRect(GetWritingMode(), IStart() - aPoint.I(),
1795                        BStart() - aPoint.B(), ISize(), BSize());
1796   }
1797 
1798   LogicalRect& operator-=(const LogicalPoint& aPoint) {
1799     CHECK_WRITING_MODE(aPoint.GetWritingMode());
1800     mIStart -= aPoint.mPoint.x;
1801     mBStart -= aPoint.mPoint.y;
1802     return *this;
1803   }
1804 
1805   void MoveBy(WritingMode aWritingMode, const LogicalPoint& aDelta) {
1806     CHECK_WRITING_MODE(aWritingMode);
1807     CHECK_WRITING_MODE(aDelta.GetWritingMode());
1808     IStart() += aDelta.I();
1809     BStart() += aDelta.B();
1810   }
1811 
1812   void Inflate(nscoord aD) {
1813 #ifdef DEBUG
1814     // Compute using nsRect and assert the results match
1815     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1816     rectDebug.Inflate(aD);
1817 #endif
1818     mIStart -= aD;
1819     mBStart -= aD;
1820     mISize += 2 * aD;
1821     mBSize += 2 * aD;
1822     MOZ_ASSERT(
1823         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1824   }
1825   void Inflate(nscoord aDI, nscoord aDB) {
1826 #ifdef DEBUG
1827     // Compute using nsRect and assert the results match
1828     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1829     rectDebug.Inflate(aDI, aDB);
1830 #endif
1831     mIStart -= aDI;
1832     mBStart -= aDB;
1833     mISize += 2 * aDI;
1834     mBSize += 2 * aDB;
1835     MOZ_ASSERT(
1836         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1837   }
1838   void Inflate(WritingMode aWritingMode, const LogicalMargin& aMargin) {
1839     CHECK_WRITING_MODE(aWritingMode);
1840     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1841 #ifdef DEBUG
1842     // Compute using nsRect and assert the results match
1843     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1844     rectDebug.Inflate(aMargin.mMargin);
1845 #endif
1846     mIStart -= aMargin.mMargin.left;
1847     mBStart -= aMargin.mMargin.top;
1848     mISize += aMargin.mMargin.LeftRight();
1849     mBSize += aMargin.mMargin.TopBottom();
1850     MOZ_ASSERT(
1851         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1852   }
1853 
1854   void Deflate(nscoord aD) {
1855 #ifdef DEBUG
1856     // Compute using nsRect and assert the results match
1857     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1858     rectDebug.Deflate(aD);
1859 #endif
1860     mIStart += aD;
1861     mBStart += aD;
1862     mISize = std::max(0, mISize - 2 * aD);
1863     mBSize = std::max(0, mBSize - 2 * aD);
1864     MOZ_ASSERT(
1865         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1866   }
1867   void Deflate(nscoord aDI, nscoord aDB) {
1868 #ifdef DEBUG
1869     // Compute using nsRect and assert the results match
1870     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1871     rectDebug.Deflate(aDI, aDB);
1872 #endif
1873     mIStart += aDI;
1874     mBStart += aDB;
1875     mISize = std::max(0, mISize - 2 * aDI);
1876     mBSize = std::max(0, mBSize - 2 * aDB);
1877     MOZ_ASSERT(
1878         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1879   }
1880   void Deflate(WritingMode aWritingMode, const LogicalMargin& aMargin) {
1881     CHECK_WRITING_MODE(aWritingMode);
1882     CHECK_WRITING_MODE(aMargin.GetWritingMode());
1883 #ifdef DEBUG
1884     // Compute using nsRect and assert the results match
1885     nsRect rectDebug(mIStart, mBStart, mISize, mBSize);
1886     rectDebug.Deflate(aMargin.mMargin);
1887 #endif
1888     mIStart += aMargin.mMargin.left;
1889     mBStart += aMargin.mMargin.top;
1890     mISize = std::max(0, mISize - aMargin.mMargin.LeftRight());
1891     mBSize = std::max(0, mBSize - aMargin.mMargin.TopBottom());
1892     MOZ_ASSERT(
1893         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1894   }
1895 
1896   /**
1897    * Return an nsRect containing our physical coordinates within the given
1898    * container size.
1899    */
1900   nsRect GetPhysicalRect(WritingMode aWritingMode,
1901                          const nsSize& aContainerSize) const {
1902     CHECK_WRITING_MODE(aWritingMode);
1903     if (aWritingMode.IsVertical()) {
1904       return nsRect(aWritingMode.IsVerticalLR() ? BStart()
1905                                                 : aContainerSize.width - BEnd(),
1906                     aWritingMode.IsInlineReversed()
1907                         ? aContainerSize.height - IEnd()
1908                         : IStart(),
1909                     BSize(), ISize());
1910     } else {
1911       return nsRect(aWritingMode.IsInlineReversed()
1912                         ? aContainerSize.width - IEnd()
1913                         : IStart(),
1914                     BStart(), ISize(), BSize());
1915     }
1916   }
1917 
1918   /**
1919    * Return a LogicalRect representing this rect in a different writing mode
1920    */
1921   LogicalRect ConvertTo(WritingMode aToMode, WritingMode aFromMode,
1922                         const nsSize& aContainerSize) const {
1923     CHECK_WRITING_MODE(aFromMode);
1924     return aToMode == aFromMode
1925                ? *this
1926                : LogicalRect(aToMode,
1927                              GetPhysicalRect(aFromMode, aContainerSize),
1928                              aContainerSize);
1929   }
1930 
1931   /**
1932    * Set *this to be the rectangle containing the intersection of aRect1
1933    * and aRect2, return whether the intersection is non-empty.
1934    */
1935   bool IntersectRect(const LogicalRect& aRect1, const LogicalRect& aRect2) {
1936     CHECK_WRITING_MODE(aRect1.mWritingMode);
1937     CHECK_WRITING_MODE(aRect2.mWritingMode);
1938 #ifdef DEBUG
1939     // Compute using nsRect and assert the results match
1940     nsRect rectDebug;
1941     rectDebug.IntersectRect(
1942         nsRect(aRect1.mIStart, aRect1.mBStart, aRect1.mISize, aRect1.mBSize),
1943         nsRect(aRect2.mIStart, aRect2.mBStart, aRect2.mISize, aRect2.mBSize));
1944 #endif
1945 
1946     nscoord iEnd = std::min(aRect1.IEnd(), aRect2.IEnd());
1947     mIStart = std::max(aRect1.mIStart, aRect2.mIStart);
1948     mISize = iEnd - mIStart;
1949 
1950     nscoord bEnd = std::min(aRect1.BEnd(), aRect2.BEnd());
1951     mBStart = std::max(aRect1.mBStart, aRect2.mBStart);
1952     mBSize = bEnd - mBStart;
1953 
1954     if (mISize < 0 || mBSize < 0) {
1955       mISize = 0;
1956       mBSize = 0;
1957     }
1958 
1959     MOZ_ASSERT(
1960         (rectDebug.IsEmpty() && (mISize == 0 || mBSize == 0)) ||
1961         rectDebug.IsEqualEdges(nsRect(mIStart, mBStart, mISize, mBSize)));
1962     return mISize > 0 && mBSize > 0;
1963   }
1964 
1965   friend std::ostream& operator<<(std::ostream& aStream,
1966                                   const LogicalRect& aRect) {
1967     return aStream << '(' << aRect.IStart() << ',' << aRect.BStart() << ','
1968                    << aRect.ISize() << ',' << aRect.BSize() << ')';
1969   }
1970 
1971  private:
1972   LogicalRect() = delete;
1973 
1974 #ifdef DEBUG
1975   WritingMode GetWritingMode() const { return mWritingMode; }
1976 #else
1977   WritingMode GetWritingMode() const { return WritingMode::Unknown(); }
1978 #endif
1979 
1980   nscoord IStart() const  // inline-start edge
1981   {
1982     return mIStart;
1983   }
1984   nscoord IEnd() const  // inline-end edge
1985   {
1986     return mIStart + mISize;
1987   }
1988   nscoord ISize() const  // inline-size
1989   {
1990     return mISize;
1991   }
1992 
1993   nscoord BStart() const  // block-start edge
1994   {
1995     return mBStart;
1996   }
1997   nscoord BEnd() const  // block-end edge
1998   {
1999     return mBStart + mBSize;
2000   }
2001   nscoord BSize() const  // block-size
2002   {
2003     return mBSize;
2004   }
2005 
2006   nscoord& IStart()  // inline-start edge
2007   {
2008     return mIStart;
2009   }
2010   nscoord& ISize()  // inline-size
2011   {
2012     return mISize;
2013   }
2014   nscoord& BStart()  // block-start edge
2015   {
2016     return mBStart;
2017   }
2018   nscoord& BSize()  // block-size
2019   {
2020     return mBSize;
2021   }
2022 
2023 #ifdef DEBUG
2024   WritingMode mWritingMode;
2025 #endif
2026   // Inline- and block-geometry dimension
2027   nscoord mIStart;  // inline-start edge
2028   nscoord mBStart;  // block-start edge
2029   nscoord mISize;   // inline-size
2030   nscoord mBSize;   // block-size
2031 };
2032 
2033 template <typename T>
2034 const T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) const {
2035   return Get(aWM.PhysicalSide(aSide));
2036 }
2037 
2038 template <typename T>
2039 const T& StyleRect<T>::GetIStart(WritingMode aWM) const {
2040   return Get(aWM, eLogicalSideIStart);
2041 }
2042 
2043 template <typename T>
2044 const T& StyleRect<T>::GetBStart(WritingMode aWM) const {
2045   return Get(aWM, eLogicalSideBStart);
2046 }
2047 
2048 template <typename T>
2049 const T& StyleRect<T>::GetIEnd(WritingMode aWM) const {
2050   return Get(aWM, eLogicalSideIEnd);
2051 }
2052 
2053 template <typename T>
2054 const T& StyleRect<T>::GetBEnd(WritingMode aWM) const {
2055   return Get(aWM, eLogicalSideBEnd);
2056 }
2057 
2058 template <typename T>
2059 T& StyleRect<T>::Get(WritingMode aWM, LogicalSide aSide) {
2060   return Get(aWM.PhysicalSide(aSide));
2061 }
2062 
2063 template <typename T>
2064 T& StyleRect<T>::GetIStart(WritingMode aWM) {
2065   return Get(aWM, eLogicalSideIStart);
2066 }
2067 
2068 template <typename T>
2069 T& StyleRect<T>::GetBStart(WritingMode aWM) {
2070   return Get(aWM, eLogicalSideBStart);
2071 }
2072 
2073 template <typename T>
2074 T& StyleRect<T>::GetIEnd(WritingMode aWM) {
2075   return Get(aWM, eLogicalSideIEnd);
2076 }
2077 
2078 template <typename T>
2079 T& StyleRect<T>::GetBEnd(WritingMode aWM) {
2080   return Get(aWM, eLogicalSideBEnd);
2081 }
2082 
2083 template <typename T>
2084 const T& StyleRect<T>::Start(mozilla::LogicalAxis aAxis,
2085                              mozilla::WritingMode aWM) const {
2086   return Get(aWM, aAxis == mozilla::eLogicalAxisInline
2087                       ? mozilla::eLogicalSideIStart
2088                       : mozilla::eLogicalSideBStart);
2089 }
2090 
2091 template <typename T>
2092 const T& StyleRect<T>::End(mozilla::LogicalAxis aAxis,
2093                            mozilla::WritingMode aWM) const {
2094   return Get(aWM, aAxis == mozilla::eLogicalAxisInline
2095                       ? mozilla::eLogicalSideIEnd
2096                       : mozilla::eLogicalSideBEnd);
2097 }
2098 
2099 inline AspectRatio AspectRatio::ConvertToWritingMode(
2100     const WritingMode& aWM) const {
2101   return aWM.IsVertical() ? Inverted() : *this;
2102 }
2103 
2104 }  // namespace mozilla
2105 
2106 // Definitions of inline methods for nsStylePosition, declared in
2107 // nsStyleStruct.h but not defined there because they need WritingMode.
2108 inline const mozilla::StyleSize& nsStylePosition::ISize(WritingMode aWM) const {
2109   return aWM.IsVertical() ? mHeight : mWidth;
2110 }
2111 inline const mozilla::StyleSize& nsStylePosition::MinISize(
2112     WritingMode aWM) const {
2113   return aWM.IsVertical() ? mMinHeight : mMinWidth;
2114 }
2115 inline const mozilla::StyleMaxSize& nsStylePosition::MaxISize(
2116     WritingMode aWM) const {
2117   return aWM.IsVertical() ? mMaxHeight : mMaxWidth;
2118 }
2119 inline const mozilla::StyleSize& nsStylePosition::BSize(WritingMode aWM) const {
2120   return aWM.IsVertical() ? mWidth : mHeight;
2121 }
2122 inline const mozilla::StyleSize& nsStylePosition::MinBSize(
2123     WritingMode aWM) const {
2124   return aWM.IsVertical() ? mMinWidth : mMinHeight;
2125 }
2126 inline const mozilla::StyleMaxSize& nsStylePosition::MaxBSize(
2127     WritingMode aWM) const {
2128   return aWM.IsVertical() ? mMaxWidth : mMaxHeight;
2129 }
2130 inline const mozilla::StyleSize& nsStylePosition::Size(
2131     mozilla::LogicalAxis aAxis, WritingMode aWM) const {
2132   return aAxis == mozilla::eLogicalAxisInline ? ISize(aWM) : BSize(aWM);
2133 }
2134 inline const mozilla::StyleSize& nsStylePosition::MinSize(
2135     mozilla::LogicalAxis aAxis, WritingMode aWM) const {
2136   return aAxis == mozilla::eLogicalAxisInline ? MinISize(aWM) : MinBSize(aWM);
2137 }
2138 inline const mozilla::StyleMaxSize& nsStylePosition::MaxSize(
2139     mozilla::LogicalAxis aAxis, WritingMode aWM) const {
2140   return aAxis == mozilla::eLogicalAxisInline ? MaxISize(aWM) : MaxBSize(aWM);
2141 }
2142 
2143 inline bool nsStylePosition::ISizeDependsOnContainer(WritingMode aWM) const {
2144   const auto& iSize = ISize(aWM);
2145   return iSize.IsAuto() || ISizeCoordDependsOnContainer(iSize);
2146 }
2147 inline bool nsStylePosition::MinISizeDependsOnContainer(WritingMode aWM) const {
2148   // NOTE: For a flex item, "min-inline-size:auto" is supposed to behave like
2149   // "min-content", which does depend on the container, so you might think we'd
2150   // need a special case for "flex item && min-inline-size:auto" here. However,
2151   // we don't actually need that special-case code, because flex items are
2152   // explicitly supposed to *ignore* their min-inline-size (i.e. behave like
2153   // it's 0) until the flex container explicitly considers it. So -- since the
2154   // flex container doesn't rely on this method, we don't need to worry about
2155   // special behavior for flex items' "min-inline-size:auto" values here.
2156   return ISizeCoordDependsOnContainer(MinISize(aWM));
2157 }
2158 inline bool nsStylePosition::MaxISizeDependsOnContainer(WritingMode aWM) const {
2159   // NOTE: The comment above MinISizeDependsOnContainer about flex items
2160   // applies here, too.
2161   return ISizeCoordDependsOnContainer(MaxISize(aWM));
2162 }
2163 // Note that these functions count `auto` as depending on the container
2164 // since that's the case for absolutely positioned elements.
2165 // However, some callers do not care about this case and should check
2166 // for it, since it is the most common case.
2167 // FIXME: We should probably change the assumption to be the other way
2168 // around.
2169 inline bool nsStylePosition::BSizeDependsOnContainer(WritingMode aWM) const {
2170   const auto& bSize = BSize(aWM);
2171   return bSize.BehavesLikeInitialValueOnBlockAxis() ||
2172          BSizeCoordDependsOnContainer(bSize);
2173 }
2174 inline bool nsStylePosition::MinBSizeDependsOnContainer(WritingMode aWM) const {
2175   return BSizeCoordDependsOnContainer(MinBSize(aWM));
2176 }
2177 inline bool nsStylePosition::MaxBSizeDependsOnContainer(WritingMode aWM) const {
2178   return BSizeCoordDependsOnContainer(MaxBSize(aWM));
2179 }
2180 
2181 inline bool nsStyleMargin::HasBlockAxisAuto(mozilla::WritingMode aWM) const {
2182   return mMargin.GetBStart(aWM).IsAuto() || mMargin.GetBEnd(aWM).IsAuto();
2183 }
2184 
2185 inline bool nsStyleMargin::HasInlineAxisAuto(mozilla::WritingMode aWM) const {
2186   return mMargin.GetIStart(aWM).IsAuto() || mMargin.GetIEnd(aWM).IsAuto();
2187 }
2188 inline bool nsStyleMargin::HasAuto(mozilla::LogicalAxis aAxis,
2189                                    mozilla::WritingMode aWM) const {
2190   return aAxis == mozilla::eLogicalAxisInline ? HasInlineAxisAuto(aWM)
2191                                               : HasBlockAxisAuto(aWM);
2192 }
2193 
2194 inline mozilla::StyleAlignFlags nsStylePosition::UsedSelfAlignment(
2195     mozilla::LogicalAxis aAxis, const mozilla::ComputedStyle* aParent) const {
2196   return aAxis == mozilla::eLogicalAxisBlock ? UsedAlignSelf(aParent)._0
2197                                              : UsedJustifySelf(aParent)._0;
2198 }
2199 
2200 inline mozilla::StyleContentDistribution nsStylePosition::UsedContentAlignment(
2201     mozilla::LogicalAxis aAxis) const {
2202   return aAxis == mozilla::eLogicalAxisBlock ? mAlignContent : mJustifyContent;
2203 }
2204 
2205 inline mozilla::StyleContentDistribution nsStylePosition::UsedTracksAlignment(
2206     mozilla::LogicalAxis aAxis, uint32_t aIndex) const {
2207   using T = mozilla::StyleAlignFlags;
2208   const auto& tracksAlignment =
2209       aAxis == mozilla::eLogicalAxisBlock ? mAlignTracks : mJustifyTracks;
2210   if (MOZ_LIKELY(tracksAlignment.IsEmpty())) {
2211     // An empty array encodes the initial value, 'normal', which behaves as
2212     // 'start' for Grid containers.
2213     return mozilla::StyleContentDistribution{T::START};
2214   }
2215 
2216   // If there are fewer values than tracks, then the last value is used for all
2217   // the remaining tracks.
2218   const auto& ta = tracksAlignment.AsSpan();
2219   auto align = ta[std::min<size_t>(aIndex, ta.Length() - 1)];
2220   if (align.primary == T::NORMAL) {
2221     align = mozilla::StyleContentDistribution{T::START};
2222   }
2223   return align;
2224 }
2225 
2226 #endif  // WritingModes_h_
2227