1/*
2 * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 *
19 */
20
21#pragma once
22
23#include "Node.h"
24
25#include <wtf/Forward.h>
26#include <wtf/OptionSet.h>
27#include <wtf/Variant.h>
28#include <wtf/text/WTFString.h>
29
30#if PLATFORM(IOS_FAMILY)
31#import <wtf/RetainPtr.h>
32typedef struct objc_object *id;
33#endif
34
35namespace WebCore {
36
37// A range of a node within a document that is "marked", such as the range of a misspelled word.
38// It optionally includes a description that could be displayed in the user interface.
39// It also optionally includes a flag specifying whether the match is active, which is ignored
40// for all types other than type TextMatch.
41class DocumentMarker {
42public:
43 enum MarkerType {
44 Spelling = 1 << 0,
45 Grammar = 1 << 1,
46 TextMatch = 1 << 2,
47 // Text has been modified by spell correction, reversion of spell correction or other type of substitution.
48 // On some platforms, this prevents the text from being autocorrected again. On post Snow Leopard Mac OS X,
49 // if a Replacement marker contains non-empty description, a reversion UI will be shown.
50 Replacement = 1 << 3,
51 // Renderer needs to add underline indicating that the text has been modified by spell
52 // correction. Text with Replacement marker doesn't necessarily has CorrectionIndicator
53 // marker. For instance, after some text has been corrected, it will have both Replacement
54 // and CorrectionIndicator. However, if user further modifies such text, we would remove
55 // CorrectionIndicator marker, but retain Replacement marker.
56 CorrectionIndicator = 1 << 4,
57 // Correction suggestion has been offered, but got rejected by user.
58 RejectedCorrection = 1 << 5,
59 // Text has been modified by autocorrection. The description of this marker is the original text before autocorrection.
60 Autocorrected = 1 << 6,
61 // On some platforms, this prevents the text from being spellchecked again.
62 SpellCheckingExemption = 1 << 7,
63 // This marker indicates user has deleted an autocorrection starting at the end of the
64 // range that bears this marker. In some platforms, if the user later inserts the same original
65 // word again at this position, it will not be autocorrected again. The description of this
66 // marker is the original word before autocorrection was applied.
67 DeletedAutocorrection = 1 << 8,
68 // This marker indicates that the range of text spanned by the marker is entered by voice dictation,
69 // and it has alternative text.
70 DictationAlternatives = 1 << 9,
71#if ENABLE(TELEPHONE_NUMBER_DETECTION)
72 TelephoneNumber = 1 << 10,
73#endif
74#if PLATFORM(IOS_FAMILY)
75 // FIXME: iOS should share the same dictation mark system with the other platforms <rdar://problem/9431249>.
76 DictationPhraseWithAlternatives = 1 << 11,
77 DictationResult = 1 << 12,
78#endif
79 // This marker indicates that the user has selected a text candidate.
80 AcceptedCandidate = 1 << 13,
81 // This marker indicates that the user has initiated a drag with this content.
82 DraggedContent = 1 << 14,
83#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
84 // This marker maintains state for the platform text checker.
85 PlatformTextChecking = 1 << 15,
86#endif
87 };
88
89 static constexpr OptionSet<MarkerType> allMarkers();
90
91 using IsActiveMatchData = bool;
92 using DescriptionData = String;
93 struct DictationData {
94 uint64_t context;
95 String originalText;
96 };
97 struct DictationAlternativesData {
98#if PLATFORM(IOS_FAMILY)
99 Vector<String> alternatives;
100 RetainPtr<id> metadata;
101#endif
102 };
103 struct DraggedContentData {
104 RefPtr<Node> targetNode;
105 };
106#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
107 struct PlatformTextCheckingData {
108 String key;
109 String value;
110 };
111#endif
112 using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData
113#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
114 , PlatformTextCheckingData
115#endif
116 >;
117
118 DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch);
119 DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description = String());
120 DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, Data&&);
121#if PLATFORM(IOS_FAMILY)
122 DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description, const Vector<String>& alternatives, RetainPtr<id> metadata);
123#endif
124
125 MarkerType type() const { return m_type; }
126 unsigned startOffset() const { return m_startOffset; }
127 unsigned endOffset() const { return m_endOffset; }
128
129 const String& description() const;
130
131 bool isActiveMatch() const;
132 void setActiveMatch(bool);
133
134 const Data& data() const { return m_data; }
135 void clearData() { m_data = false; }
136
137 // Offset modifications are done by DocumentMarkerController.
138 // Other classes should not call following setters.
139 void setStartOffset(unsigned offset) { m_startOffset = offset; }
140 void setEndOffset(unsigned offset) { m_endOffset = offset; }
141 void shiftOffsets(int delta);
142
143#if PLATFORM(IOS_FAMILY)
144 bool isDictation() const;
145 const Vector<String>& alternatives() const;
146 void setAlternative(const String&, size_t index);
147 id metadata() const;
148 void setMetadata(id);
149#endif
150
151private:
152 MarkerType m_type;
153 unsigned m_startOffset;
154 unsigned m_endOffset;
155 Data m_data;
156};
157
158constexpr auto DocumentMarker::allMarkers() -> OptionSet<MarkerType>
159{
160 return {
161 AcceptedCandidate,
162 Autocorrected,
163 CorrectionIndicator,
164 DeletedAutocorrection,
165 DictationAlternatives,
166 DraggedContent,
167 Grammar,
168 RejectedCorrection,
169 Replacement,
170 SpellCheckingExemption,
171 Spelling,
172 TextMatch,
173#if ENABLE(TELEPHONE_NUMBER_DETECTION)
174 TelephoneNumber,
175#endif
176#if PLATFORM(IOS_FAMILY)
177 DictationPhraseWithAlternatives,
178 DictationResult,
179#endif
180#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
181 PlatformTextChecking
182#endif
183 };
184}
185
186inline DocumentMarker::DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch)
187 : m_type(TextMatch)
188 , m_startOffset(startOffset)
189 , m_endOffset(endOffset)
190 , m_data(isActiveMatch)
191{
192}
193
194inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, const String& description)
195 : m_type(type)
196 , m_startOffset(startOffset)
197 , m_endOffset(endOffset)
198 , m_data(description)
199{
200}
201
202inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, Data&& data)
203 : m_type(type)
204 , m_startOffset(startOffset)
205 , m_endOffset(endOffset)
206 , m_data(WTFMove(data))
207{
208}
209
210inline void DocumentMarker::shiftOffsets(int delta)
211{
212 m_startOffset += delta;
213 m_endOffset += delta;
214}
215
216inline const String& DocumentMarker::description() const
217{
218 return WTF::holds_alternative<String>(m_data) ? WTF::get<String>(m_data) : emptyString();
219}
220
221inline bool DocumentMarker::isActiveMatch() const
222{
223 return WTF::holds_alternative<bool>(m_data) && WTF::get<bool>(m_data);
224}
225
226inline void DocumentMarker::setActiveMatch(bool isActiveMatch)
227{
228 ASSERT(m_type == TextMatch);
229 m_data = isActiveMatch;
230}
231
232#if PLATFORM(IOS_FAMILY)
233
234// FIXME: iOS should share the same dictation mark system with the other platforms <rdar://problem/9431249>.
235
236inline DocumentMarker::DocumentMarker(MarkerType type, unsigned startOffset, unsigned endOffset, const String&, const Vector<String>& alternatives, RetainPtr<id> metadata)
237 : m_type(type)
238 , m_startOffset(startOffset)
239 , m_endOffset(endOffset)
240 , m_data(DictationAlternativesData { alternatives, metadata })
241{
242 ASSERT(isDictation());
243}
244
245inline bool DocumentMarker::isDictation() const
246{
247 return m_type == DictationPhraseWithAlternatives || m_type == DictationResult;
248}
249
250inline const Vector<String>& DocumentMarker::alternatives() const
251{
252 return WTF::get<DictationAlternativesData>(m_data).alternatives;
253}
254
255inline void DocumentMarker::setAlternative(const String& alternative, size_t index)
256{
257 WTF::get<DictationAlternativesData>(m_data).alternatives[index] = alternative;
258}
259
260inline id DocumentMarker::metadata() const
261{
262 return WTF::get<DictationAlternativesData>(m_data).metadata.get();
263}
264
265inline void DocumentMarker::setMetadata(id metadata)
266{
267 WTF::get<DictationAlternativesData>(m_data).metadata = metadata;
268}
269
270#endif
271
272} // namespace WebCore
273