1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #ifndef mozilla_TextRage_h_
7 #define mozilla_TextRage_h_
8 
9 #include <stdint.h>
10 
11 #include "mozilla/EventForwards.h"
12 
13 #include "nsColor.h"
14 #include "nsISelectionController.h"
15 #include "nsITextInputProcessor.h"
16 #include "nsTArray.h"
17 
18 namespace mozilla {
19 
20 /******************************************************************************
21  * mozilla::TextRangeStyle
22  ******************************************************************************/
23 
24 struct TextRangeStyle {
25   typedef uint8_t LineStyleType;
26   // FYI: Modify IME_RANGE_LINE_* too when you modify LineStyle.
27   enum class LineStyle : LineStyleType {
28     None,
29     Solid,
30     Dotted,
31     Dashed,
32     Double,
33     Wavy,
34   };
ToLineStyleTextRangeStyle35   inline static LineStyle ToLineStyle(RawTextRangeType aRawLineStyle) {
36     switch (static_cast<LineStyle>(aRawLineStyle)) {
37       case LineStyle::None:
38       case LineStyle::Solid:
39       case LineStyle::Dotted:
40       case LineStyle::Dashed:
41       case LineStyle::Double:
42       case LineStyle::Wavy:
43         return static_cast<LineStyle>(aRawLineStyle);
44     }
45     MOZ_ASSERT_UNREACHABLE("aRawLineStyle value is invalid");
46     return LineStyle::None;
47   }
48 
49   enum {
50     DEFINED_NONE = 0x00,
51     DEFINED_LINESTYLE = 0x01,
52     DEFINED_FOREGROUND_COLOR = 0x02,
53     DEFINED_BACKGROUND_COLOR = 0x04,
54     DEFINED_UNDERLINE_COLOR = 0x08
55   };
56 
57   // Initialize all members, because TextRange instances may be compared by
58   // memcomp.
59   //
60   // FIXME(emilio): I don't think that'd be sound, as it has padding which the
61   // compiler is not guaranteed to initialize.
TextRangeStyleTextRangeStyle62   TextRangeStyle() { Clear(); }
63 
ClearTextRangeStyle64   void Clear() {
65     mDefinedStyles = DEFINED_NONE;
66     mLineStyle = LineStyle::None;
67     mIsBoldLine = false;
68     mForegroundColor = mBackgroundColor = mUnderlineColor = NS_RGBA(0, 0, 0, 0);
69   }
70 
IsDefinedTextRangeStyle71   bool IsDefined() const { return mDefinedStyles != DEFINED_NONE; }
72 
IsLineStyleDefinedTextRangeStyle73   bool IsLineStyleDefined() const {
74     return (mDefinedStyles & DEFINED_LINESTYLE) != 0;
75   }
76 
IsForegroundColorDefinedTextRangeStyle77   bool IsForegroundColorDefined() const {
78     return (mDefinedStyles & DEFINED_FOREGROUND_COLOR) != 0;
79   }
80 
IsBackgroundColorDefinedTextRangeStyle81   bool IsBackgroundColorDefined() const {
82     return (mDefinedStyles & DEFINED_BACKGROUND_COLOR) != 0;
83   }
84 
IsUnderlineColorDefinedTextRangeStyle85   bool IsUnderlineColorDefined() const {
86     return (mDefinedStyles & DEFINED_UNDERLINE_COLOR) != 0;
87   }
88 
IsNoChangeStyleTextRangeStyle89   bool IsNoChangeStyle() const {
90     return !IsForegroundColorDefined() && !IsBackgroundColorDefined() &&
91            IsLineStyleDefined() && mLineStyle == LineStyle::None;
92   }
93 
EqualsTextRangeStyle94   bool Equals(const TextRangeStyle& aOther) const {
95     if (mDefinedStyles != aOther.mDefinedStyles) return false;
96     if (IsLineStyleDefined() && (mLineStyle != aOther.mLineStyle ||
97                                  !mIsBoldLine != !aOther.mIsBoldLine))
98       return false;
99     if (IsForegroundColorDefined() &&
100         (mForegroundColor != aOther.mForegroundColor))
101       return false;
102     if (IsBackgroundColorDefined() &&
103         (mBackgroundColor != aOther.mBackgroundColor))
104       return false;
105     if (IsUnderlineColorDefined() &&
106         (mUnderlineColor != aOther.mUnderlineColor))
107       return false;
108     return true;
109   }
110 
111   bool operator!=(const TextRangeStyle& aOther) const {
112     return !Equals(aOther);
113   }
114 
115   bool operator==(const TextRangeStyle& aOther) const { return Equals(aOther); }
116 
117   uint8_t mDefinedStyles;
118   LineStyle mLineStyle;  // DEFINED_LINESTYLE
119 
120   bool mIsBoldLine;  // DEFINED_LINESTYLE
121 
122   nscolor mForegroundColor;  // DEFINED_FOREGROUND_COLOR
123   nscolor mBackgroundColor;  // DEFINED_BACKGROUND_COLOR
124   nscolor mUnderlineColor;   // DEFINED_UNDERLINE_COLOR
125 };
126 
127 /******************************************************************************
128  * mozilla::TextRange
129  ******************************************************************************/
130 
131 enum class TextRangeType : RawTextRangeType {
132   eUninitialized = 0x00,
133   eCaret = 0x01,
134   eRawClause = nsITextInputProcessor::ATTR_RAW_CLAUSE,
135   eSelectedRawClause = nsITextInputProcessor::ATTR_SELECTED_RAW_CLAUSE,
136   eConvertedClause = nsITextInputProcessor::ATTR_CONVERTED_CLAUSE,
137   eSelectedClause = nsITextInputProcessor::ATTR_SELECTED_CLAUSE
138 };
139 
140 bool IsValidRawTextRangeValue(RawTextRangeType aRawTextRangeValue);
141 RawTextRangeType ToRawTextRangeType(TextRangeType aTextRangeType);
142 TextRangeType ToTextRangeType(RawTextRangeType aRawTextRangeType);
143 const char* ToChar(TextRangeType aTextRangeType);
144 SelectionType ToSelectionType(TextRangeType aTextRangeType);
145 
146 struct TextRange {
TextRangeTextRange147   TextRange()
148       : mStartOffset(0),
149         mEndOffset(0),
150         mRangeType(TextRangeType::eUninitialized) {}
151 
152   uint32_t mStartOffset;
153   // XXX Storing end offset makes the initializing code very complicated.
154   //     We should replace it with mLength.
155   uint32_t mEndOffset;
156 
157   TextRangeStyle mRangeStyle;
158 
159   TextRangeType mRangeType;
160 
LengthTextRange161   uint32_t Length() const { return mEndOffset - mStartOffset; }
162 
IsClauseTextRange163   bool IsClause() const { return mRangeType != TextRangeType::eCaret; }
164 
EqualsTextRange165   bool Equals(const TextRange& aOther) const {
166     return mStartOffset == aOther.mStartOffset &&
167            mEndOffset == aOther.mEndOffset && mRangeType == aOther.mRangeType &&
168            mRangeStyle == aOther.mRangeStyle;
169   }
170 
RemoveCharacterTextRange171   void RemoveCharacter(uint32_t aOffset) {
172     if (mStartOffset > aOffset) {
173       --mStartOffset;
174       --mEndOffset;
175     } else if (mEndOffset > aOffset) {
176       --mEndOffset;
177     }
178   }
179 };
180 
181 /******************************************************************************
182  * mozilla::TextRangeArray
183  ******************************************************************************/
184 class TextRangeArray final : public AutoTArray<TextRange, 10> {
185   friend class WidgetCompositionEvent;
186 
187   ~TextRangeArray() = default;
188 
NS_INLINE_DECL_REFCOUNTING(TextRangeArray)189   NS_INLINE_DECL_REFCOUNTING(TextRangeArray)
190 
191   const TextRange* GetTargetClause() const {
192     for (uint32_t i = 0; i < Length(); ++i) {
193       const TextRange& range = ElementAt(i);
194       if (range.mRangeType == TextRangeType::eSelectedRawClause ||
195           range.mRangeType == TextRangeType::eSelectedClause) {
196         return &range;
197       }
198     }
199     return nullptr;
200   }
201 
202   // Returns target clause offset.  If there are selected clauses, this returns
203   // the first selected clause offset.  Otherwise, 0.
TargetClauseOffset()204   uint32_t TargetClauseOffset() const {
205     const TextRange* range = GetTargetClause();
206     return range ? range->mStartOffset : 0;
207   }
208 
209   // Returns target clause length.  If there are selected clauses, this returns
210   // the first selected clause length.  Otherwise, UINT32_MAX.
TargetClauseLength()211   uint32_t TargetClauseLength() const {
212     const TextRange* range = GetTargetClause();
213     return range ? range->Length() : UINT32_MAX;
214   }
215 
216  public:
IsComposing()217   bool IsComposing() const {
218     for (uint32_t i = 0; i < Length(); ++i) {
219       if (ElementAt(i).IsClause()) {
220         return true;
221       }
222     }
223     return false;
224   }
225 
Equals(const TextRangeArray & aOther)226   bool Equals(const TextRangeArray& aOther) const {
227     size_t len = Length();
228     if (len != aOther.Length()) {
229       return false;
230     }
231     for (size_t i = 0; i < len; i++) {
232       if (!ElementAt(i).Equals(aOther.ElementAt(i))) {
233         return false;
234       }
235     }
236     return true;
237   }
238 
RemoveCharacter(uint32_t aOffset)239   void RemoveCharacter(uint32_t aOffset) {
240     for (size_t i = 0, len = Length(); i < len; i++) {
241       ElementAt(i).RemoveCharacter(aOffset);
242     }
243   }
244 
HasCaret()245   bool HasCaret() const {
246     for (const TextRange& range : *this) {
247       if (range.mRangeType == TextRangeType::eCaret) {
248         return true;
249       }
250     }
251     return false;
252   }
253 
HasClauses()254   bool HasClauses() const {
255     for (const TextRange& range : *this) {
256       if (range.IsClause()) {
257         return true;
258       }
259     }
260     return false;
261   }
262 
GetCaretPosition()263   uint32_t GetCaretPosition() const {
264     for (const TextRange& range : *this) {
265       if (range.mRangeType == TextRangeType::eCaret) {
266         return range.mStartOffset;
267       }
268     }
269     return UINT32_MAX;
270   }
271 
GetFirstClause()272   const TextRange* GetFirstClause() const {
273     for (const TextRange& range : *this) {
274       // Look for the range of a clause whose start offset is 0 because the
275       // first clause's start offset is always 0.
276       if (range.IsClause() && !range.mStartOffset) {
277         return &range;
278       }
279     }
280     MOZ_ASSERT(!HasClauses());
281     return nullptr;
282   }
283 };
284 
285 }  // namespace mozilla
286 
287 #endif  // mozilla_TextRage_h_
288