| 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> |
| 32 | typedef struct objc_object *id; |
| 33 | #endif |
| 34 | |
| 35 | namespace 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. |
| 41 | class DocumentMarker { |
| 42 | public: |
| 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 | |
| 151 | private: |
| 152 | MarkerType m_type; |
| 153 | unsigned m_startOffset; |
| 154 | unsigned m_endOffset; |
| 155 | Data m_data; |
| 156 | }; |
| 157 | |
| 158 | constexpr 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 | |
| 186 | inline 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 | |
| 194 | inline 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 | |
| 202 | inline 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 | |
| 210 | inline void DocumentMarker::shiftOffsets(int delta) |
| 211 | { |
| 212 | m_startOffset += delta; |
| 213 | m_endOffset += delta; |
| 214 | } |
| 215 | |
| 216 | inline const String& DocumentMarker::description() const |
| 217 | { |
| 218 | return WTF::holds_alternative<String>(m_data) ? WTF::get<String>(m_data) : emptyString(); |
| 219 | } |
| 220 | |
| 221 | inline bool DocumentMarker::isActiveMatch() const |
| 222 | { |
| 223 | return WTF::holds_alternative<bool>(m_data) && WTF::get<bool>(m_data); |
| 224 | } |
| 225 | |
| 226 | inline 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 | |
| 236 | inline 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 | |
| 245 | inline bool DocumentMarker::isDictation() const |
| 246 | { |
| 247 | return m_type == DictationPhraseWithAlternatives || m_type == DictationResult; |
| 248 | } |
| 249 | |
| 250 | inline const Vector<String>& DocumentMarker::alternatives() const |
| 251 | { |
| 252 | return WTF::get<DictationAlternativesData>(m_data).alternatives; |
| 253 | } |
| 254 | |
| 255 | inline void DocumentMarker::setAlternative(const String& alternative, size_t index) |
| 256 | { |
| 257 | WTF::get<DictationAlternativesData>(m_data).alternatives[index] = alternative; |
| 258 | } |
| 259 | |
| 260 | inline id DocumentMarker::metadata() const |
| 261 | { |
| 262 | return WTF::get<DictationAlternativesData>(m_data).metadata.get(); |
| 263 | } |
| 264 | |
| 265 | inline void DocumentMarker::setMetadata(id metadata) |
| 266 | { |
| 267 | WTF::get<DictationAlternativesData>(m_data).metadata = metadata; |
| 268 | } |
| 269 | |
| 270 | #endif |
| 271 | |
| 272 | } // namespace WebCore |
| 273 | |