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 #include "RubyUtils.h"
8 #include "nsRubyFrame.h"
9 #include "nsRubyBaseFrame.h"
10 #include "nsRubyTextFrame.h"
11 #include "nsRubyBaseContainerFrame.h"
12 #include "nsRubyTextContainerFrame.h"
13 
14 using namespace mozilla;
15 
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ReservedISize,nscoord)16 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ReservedISize, nscoord)
17 
18 /* static */
19 void RubyUtils::SetReservedISize(nsIFrame* aFrame, nscoord aISize) {
20   MOZ_ASSERT(IsExpandableRubyBox(aFrame));
21   aFrame->SetProperty(ReservedISize(), aISize);
22 }
23 
24 /* static */
ClearReservedISize(nsIFrame * aFrame)25 void RubyUtils::ClearReservedISize(nsIFrame* aFrame) {
26   MOZ_ASSERT(IsExpandableRubyBox(aFrame));
27   aFrame->RemoveProperty(ReservedISize());
28 }
29 
30 /* static */
GetReservedISize(nsIFrame * aFrame)31 nscoord RubyUtils::GetReservedISize(nsIFrame* aFrame) {
32   MOZ_ASSERT(IsExpandableRubyBox(aFrame));
33   return aFrame->GetProperty(ReservedISize());
34 }
35 
AutoRubyTextContainerArray(nsRubyBaseContainerFrame * aBaseContainer)36 AutoRubyTextContainerArray::AutoRubyTextContainerArray(
37     nsRubyBaseContainerFrame* aBaseContainer) {
38   for (nsIFrame* frame = aBaseContainer->GetNextSibling();
39        frame && frame->IsRubyTextContainerFrame();
40        frame = frame->GetNextSibling()) {
41     AppendElement(static_cast<nsRubyTextContainerFrame*>(frame));
42   }
43 }
44 
operator *() const45 nsIFrame* RubyColumn::Iterator::operator*() const {
46   nsIFrame* frame;
47   if (mIndex == -1) {
48     frame = mColumn.mBaseFrame;
49   } else {
50     frame = mColumn.mTextFrames[mIndex];
51   }
52   MOZ_ASSERT(frame, "Frame here cannot be null");
53   return frame;
54 }
55 
SkipUntilExistingFrame()56 void RubyColumn::Iterator::SkipUntilExistingFrame() {
57   if (mIndex == -1) {
58     if (mColumn.mBaseFrame) {
59       return;
60     }
61     ++mIndex;
62   }
63   int32_t numTextFrames = mColumn.mTextFrames.Length();
64   for (; mIndex < numTextFrames; ++mIndex) {
65     if (mColumn.mTextFrames[mIndex]) {
66       break;
67     }
68   }
69 }
70 
RubySegmentEnumerator(nsRubyFrame * aRubyFrame)71 RubySegmentEnumerator::RubySegmentEnumerator(nsRubyFrame* aRubyFrame) {
72   nsIFrame* frame = aRubyFrame->PrincipalChildList().FirstChild();
73   MOZ_ASSERT(!frame || frame->IsRubyBaseContainerFrame());
74   mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
75 }
76 
Next()77 void RubySegmentEnumerator::Next() {
78   MOZ_ASSERT(mBaseContainer);
79   nsIFrame* frame = mBaseContainer->GetNextSibling();
80   while (frame && !frame->IsRubyBaseContainerFrame()) {
81     frame = frame->GetNextSibling();
82   }
83   mBaseContainer = static_cast<nsRubyBaseContainerFrame*>(frame);
84 }
85 
RubyColumnEnumerator(nsRubyBaseContainerFrame * aBaseContainer,const AutoRubyTextContainerArray & aTextContainers)86 RubyColumnEnumerator::RubyColumnEnumerator(
87     nsRubyBaseContainerFrame* aBaseContainer,
88     const AutoRubyTextContainerArray& aTextContainers)
89     : mAtIntraLevelWhitespace(false) {
90   const uint32_t rtcCount = aTextContainers.Length();
91   mFrames.SetCapacity(rtcCount + 1);
92 
93   nsIFrame* rbFrame = aBaseContainer->PrincipalChildList().FirstChild();
94   MOZ_ASSERT(!rbFrame || rbFrame->IsRubyBaseFrame());
95   mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rbFrame));
96   for (uint32_t i = 0; i < rtcCount; i++) {
97     nsRubyTextContainerFrame* container = aTextContainers[i];
98     // If the container is for span, leave a nullptr here.
99     // Spans do not take part in pairing.
100     nsIFrame* rtFrame = !container->IsSpanContainer()
101                             ? container->PrincipalChildList().FirstChild()
102                             : nullptr;
103     MOZ_ASSERT(!rtFrame || rtFrame->IsRubyTextFrame());
104     mFrames.AppendElement(static_cast<nsRubyContentFrame*>(rtFrame));
105   }
106 
107   // We have to init mAtIntraLevelWhitespace to be correct for the
108   // first column. There are two ways we could end up with intra-level
109   // whitespace in our first colum:
110   // 1. The current segment itself is an inter-segment whitespace;
111   // 2. If our ruby segment is split across multiple lines, and some
112   //    intra-level whitespace happens to fall right after a line-break.
113   //    Each line will get its own nsRubyBaseContainerFrame, and the
114   //    container right after the line-break will end up with its first
115   //    column containing that intra-level whitespace.
116   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
117     nsRubyContentFrame* frame = mFrames[i];
118     if (frame && frame->IsIntraLevelWhitespace()) {
119       mAtIntraLevelWhitespace = true;
120       break;
121     }
122   }
123 }
124 
Next()125 void RubyColumnEnumerator::Next() {
126   bool advancingToIntraLevelWhitespace = false;
127   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
128     nsRubyContentFrame* frame = mFrames[i];
129     // If we've got intra-level whitespace frames at some levels in the
130     // current ruby column, we "faked" an anonymous box for all other
131     // levels for this column. So when we advance off this column, we
132     // don't advance any of the frames in those levels, because we're
133     // just advancing across the "fake" frames.
134     if (frame &&
135         (!mAtIntraLevelWhitespace || frame->IsIntraLevelWhitespace())) {
136       nsIFrame* nextSibling = frame->GetNextSibling();
137       MOZ_ASSERT(!nextSibling || nextSibling->Type() == frame->Type(),
138                  "Frame type should be identical among a level");
139       mFrames[i] = frame = static_cast<nsRubyContentFrame*>(nextSibling);
140       if (!advancingToIntraLevelWhitespace && frame &&
141           frame->IsIntraLevelWhitespace()) {
142         advancingToIntraLevelWhitespace = true;
143       }
144     }
145   }
146   MOZ_ASSERT(!advancingToIntraLevelWhitespace || !mAtIntraLevelWhitespace,
147              "Should never have adjacent intra-level whitespace columns");
148   mAtIntraLevelWhitespace = advancingToIntraLevelWhitespace;
149 }
150 
AtEnd() const151 bool RubyColumnEnumerator::AtEnd() const {
152   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
153     if (mFrames[i]) {
154       return false;
155     }
156   }
157   return true;
158 }
159 
GetFrameAtLevel(uint32_t aIndex) const160 nsRubyContentFrame* RubyColumnEnumerator::GetFrameAtLevel(
161     uint32_t aIndex) const {
162   // If the current ruby column is for intra-level whitespaces, we
163   // return nullptr for any levels that do not have an actual intra-
164   // level whitespace frame in this column.  This nullptr represents
165   // an anonymous empty intra-level whitespace box.  (In this case,
166   // it's important that we NOT return mFrames[aIndex], because it's
167   // really part of the next column, not the current one.)
168   nsRubyContentFrame* frame = mFrames[aIndex];
169   return !mAtIntraLevelWhitespace || (frame && frame->IsIntraLevelWhitespace())
170              ? frame
171              : nullptr;
172 }
173 
GetColumn(RubyColumn & aColumn) const174 void RubyColumnEnumerator::GetColumn(RubyColumn& aColumn) const {
175   nsRubyContentFrame* rbFrame = GetFrameAtLevel(0);
176   MOZ_ASSERT(!rbFrame || rbFrame->IsRubyBaseFrame());
177   aColumn.mBaseFrame = static_cast<nsRubyBaseFrame*>(rbFrame);
178   aColumn.mTextFrames.ClearAndRetainStorage();
179   for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
180     nsRubyContentFrame* rtFrame = GetFrameAtLevel(i);
181     MOZ_ASSERT(!rtFrame || rtFrame->IsRubyTextFrame());
182     aColumn.mTextFrames.AppendElement(static_cast<nsRubyTextFrame*>(rtFrame));
183   }
184   aColumn.mIsIntraLevelWhitespace = mAtIntraLevelWhitespace;
185 }
186