Use a 1-byte enum class for TextDirection
[WebKit-https.git] / Source / WebKitLegacy / ios / WebCoreSupport / WebVisiblePosition.mm
1 /*
2  * Copyright (C) 2009 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #if PLATFORM(IOS)
27
28 #import "WebVisiblePosition.h"
29 #import "WebVisiblePositionInternal.h"
30
31 #import <WebCore/DocumentMarkerController.h>
32 #import <WebCore/Editing.h>
33 #import <WebCore/FrameSelection.h>
34 #import <WebCore/HTMLTextFormControlElement.h>
35 #import <WebCore/Node.h>
36 #import <WebCore/Position.h>
37 #import <WebCore/Range.h>
38 #import <WebCore/RenderTextControl.h>
39 #import <WebCore/RenderedDocumentMarker.h>
40 #import <WebCore/TextBoundaries.h>
41 #import <WebCore/TextFlags.h>
42 #import <WebCore/TextGranularity.h>
43 #import <WebCore/TextIterator.h>
44 #import <WebCore/VisiblePosition.h>
45 #import <WebCore/VisibleUnits.h>
46
47
48 #import "DOMNodeInternal.h"
49 #import "DOMRangeInternal.h"
50
51 using namespace WebCore;
52
53 //-------------------
54
55 @implementation WebVisiblePosition (Internal)
56
57 // Since VisiblePosition isn't refcounted, we have to use new and delete on a copy.
58
59 + (WebVisiblePosition *)_wrapVisiblePosition:(VisiblePosition)visiblePosition
60 {
61     WebVisiblePosition *vp = [[WebVisiblePosition alloc] init];
62     VisiblePosition *copy = new VisiblePosition(visiblePosition);
63     vp->_internal = reinterpret_cast<WebObjectInternal *>(copy);
64     return [vp autorelease];
65 }
66
67 // Returns nil if visible position is null.
68 + (WebVisiblePosition *)_wrapVisiblePositionIfValid:(VisiblePosition)visiblePosition
69 {
70     return (visiblePosition.isNotNull() ? [WebVisiblePosition _wrapVisiblePosition:visiblePosition] : nil);
71 }
72
73 - (VisiblePosition)_visiblePosition
74 {
75     VisiblePosition *vp = reinterpret_cast<VisiblePosition *>(_internal);
76     return *vp;
77 }
78
79 @end
80
81 @implementation WebVisiblePosition
82
83 - (void)dealloc
84 {
85     VisiblePosition *vp = reinterpret_cast<VisiblePosition *>(_internal);
86     delete vp;
87     _internal = nil;
88     [super dealloc];
89 }
90
91 // FIXME: Overriding isEqual: without overriding hash will cause trouble if this ever goes into an NSSet or is the key in an NSDictionary,
92 // since two equal objects could have different hashes.
93 - (BOOL)isEqual:(id)other
94 {
95     if (![other isKindOfClass:[WebVisiblePosition class]])
96         return NO;
97     return [self _visiblePosition] == [(WebVisiblePosition *)other _visiblePosition];
98 }
99
100 - (NSComparisonResult)compare:(WebVisiblePosition *)other
101 {
102     VisiblePosition myVP = [self _visiblePosition];
103     VisiblePosition otherVP = [other _visiblePosition];
104     
105     if (myVP == otherVP)
106         return NSOrderedSame;
107     else if (myVP < otherVP)
108         return NSOrderedAscending;
109     else
110         return NSOrderedDescending;
111 }
112
113 - (int)distanceFromPosition:(WebVisiblePosition *)other
114 {
115     return distanceBetweenPositions([self _visiblePosition], [other _visiblePosition]);
116 }
117
118 - (NSString *)description
119 {
120     
121     NSMutableString *description = [NSMutableString stringWithString:[super description]];
122     VisiblePosition vp = [self _visiblePosition];
123     int offset = vp.deepEquivalent().offsetInContainerNode();
124
125     [description appendFormat:@"(offset=%d, context=([%c|%c], [u+%04x|u+%04x])", offset, vp.characterBefore(), vp.characterAfter(),
126         vp.characterBefore(), vp.characterAfter()];
127     
128     return description;
129 }
130
131
132 - (TextDirection)textDirection
133 {
134     // TODO: implement
135     return TextDirection::LTR;
136 }
137
138 - (BOOL)directionIsDownstream:(WebTextAdjustmentDirection)direction
139 {
140     if (direction == WebTextAdjustmentBackward)
141         return NO;
142     
143     if (direction == WebTextAdjustmentForward)
144         return YES;
145     
146     
147     if ([self textDirection] == TextDirection::LTR)
148         return (direction == WebTextAdjustmentRight);
149     return (direction == WebTextAdjustmentLeft);
150 }
151
152 - (WebVisiblePosition *)positionByMovingInDirection:(WebTextAdjustmentDirection)direction amount:(UInt32)amount withAffinityDownstream:(BOOL)affinityDownstream
153 {
154     VisiblePosition vp = [self _visiblePosition];
155                           
156     vp.setAffinity(affinityDownstream ? DOWNSTREAM : VP_UPSTREAM_IF_POSSIBLE);
157
158     switch (direction) {
159         case WebTextAdjustmentForward: {
160             for (UInt32 i = 0; i < amount; i++)
161                 vp = vp.next();
162             break;
163         }
164         case WebTextAdjustmentBackward: {
165             for (UInt32 i = 0; i < amount; i++)
166                 vp = vp.previous();
167             break;
168         }
169         case WebTextAdjustmentRight: {
170             for (UInt32 i = 0; i < amount; i++)
171                 vp = vp.right();
172             break;
173         }
174         case WebTextAdjustmentLeft: {
175             for (UInt32 i = 0; i < amount; i++)
176                 vp = vp.left();
177             break;
178         }
179         case WebTextAdjustmentUp: {
180             int xOffset = vp.lineDirectionPointForBlockDirectionNavigation();
181             for (UInt32 i = 0; i < amount; i++)
182                 vp = previousLinePosition(vp, xOffset);            
183             break;
184         }
185         case WebTextAdjustmentDown: {
186             int xOffset = vp.lineDirectionPointForBlockDirectionNavigation();
187             for (UInt32 i = 0; i < amount; i++)
188                 vp = nextLinePosition(vp, xOffset);            
189             break;
190         }
191         default: {
192             ASSERT_NOT_REACHED();
193             break;
194         }
195     }
196     return [WebVisiblePosition _wrapVisiblePositionIfValid:vp];
197 }
198
199 - (WebVisiblePosition *)positionByMovingInDirection:(WebTextAdjustmentDirection)direction amount:(UInt32)amount
200
201 {
202     return [self positionByMovingInDirection:direction amount:amount withAffinityDownstream:YES];
203 }
204
205 static inline TextGranularity toTextGranularity(WebTextGranularity webGranularity)
206 {
207     TextGranularity granularity;
208     
209     switch (webGranularity) {
210         case WebTextGranularityCharacter:
211             granularity = CharacterGranularity;
212             break;
213
214         case WebTextGranularityWord:
215             granularity = WordGranularity;
216             break;
217
218         case WebTextGranularitySentence:
219             granularity = SentenceGranularity;
220             break;
221
222         case WebTextGranularityLine:
223             granularity = LineGranularity;
224             break;
225
226         case WebTextGranularityParagraph:
227             granularity = ParagraphGranularity;
228             break;
229
230         case WebTextGranularityAll:
231             granularity = DocumentGranularity;
232             break;
233
234         default:
235             ASSERT_NOT_REACHED();
236             break;
237     }
238             
239     return granularity;
240 }
241
242 static inline SelectionDirection toSelectionDirection(WebTextAdjustmentDirection direction)
243 {
244     SelectionDirection result;
245     
246     switch (direction) {
247         case WebTextAdjustmentForward:
248             result = DirectionForward;
249             break;
250             
251         case WebTextAdjustmentBackward:
252             result = DirectionBackward;
253             break;
254             
255         case WebTextAdjustmentRight:
256             result = DirectionRight;
257             break;
258             
259         case WebTextAdjustmentLeft:
260             result = DirectionLeft;
261             break;
262         
263         case WebTextAdjustmentUp:
264             result = DirectionLeft;
265             break;
266         
267         case WebTextAdjustmentDown:
268             result = DirectionRight;
269             break;
270     }
271
272     return result;
273 }
274
275 // Returnes YES only if a position is at a boundary of a text unit of the specified granularity in the particular direction.
276 - (BOOL)atBoundaryOfGranularity:(WebTextGranularity)granularity inDirection:(WebTextAdjustmentDirection)direction
277 {
278     return atBoundaryOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction));
279 }
280
281 // Returns the next boundary position of a text unit of the given granularity in the given direction, or nil if there is no such position.
282 - (WebVisiblePosition *)positionOfNextBoundaryOfGranularity:(WebTextGranularity)granularity inDirection:(WebTextAdjustmentDirection)direction
283 {
284     return [WebVisiblePosition _wrapVisiblePositionIfValid:positionOfNextBoundaryOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction))];
285 }
286
287 // Returns YES if position is within a text unit of the given granularity.  If the position is at a boundary, returns YES only if
288 // if the boundary is part of the text unit in the given direction.
289 - (BOOL)withinTextUnitOfGranularity:(WebTextGranularity)granularity inDirectionIfAtBoundary:(WebTextAdjustmentDirection)direction
290 {
291     return withinTextUnitOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction));
292 }
293
294 // Returns range of the enclosing text unit of the given granularity, or nil if there is no such enclosing unit.  Whether a boundary position
295 // is enclosed depends on the given direction, using the same rule as -[WebVisiblePosition withinTextUnitOfGranularity:inDirectionAtBoundary:].
296 - (DOMRange *)enclosingTextUnitOfGranularity:(WebTextGranularity)granularity inDirectionIfAtBoundary:(WebTextAdjustmentDirection)direction
297 {
298     return kit(enclosingTextUnitOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction)).get());
299 }
300
301 - (WebVisiblePosition *)positionAtStartOrEndOfWord
302 {
303     // Ripped from WebCore::Frame::moveSelectionToStartOrEndOfCurrentWord
304     
305     // Note: this is the iOS notion, not the unicode notion.
306     // Here, a word starts with non-whitespace or at the start of a line and
307     // ends at the next whitespace, or at the end of a line.
308     
309     // Selection rule: If the selection is before the first character or
310     // just after the first character of a word that is longer than one
311     // character, move to the start of the word. Otherwise, move to the
312     // end of the word.
313     
314     VisiblePosition pos = [self _visiblePosition];
315     VisiblePosition originalPos(pos);
316     
317     UChar32 ch = pos.characterAfter();
318     bool isComplex = requiresContextForWordBoundary(ch);
319     if (isComplex) {
320         // for complex layout, find word around insertion point
321         VisiblePosition visibleWordStart = startOfWord(pos);
322         Position wordStart = visibleWordStart.deepEquivalent();
323         
324         // place caret in front of word if pos is within first 2 characters of word (see Selection Rule above)
325         if (wordStart.deprecatedEditingOffset() + 1 >= pos.deepEquivalent().deprecatedEditingOffset()) {
326             pos = wordStart;
327         } else {
328             // calculate end of word to insert caret after word
329             VisiblePosition visibleWordEnd = endOfWord(pos);
330             Position wordEnd = visibleWordEnd.deepEquivalent();
331             pos = wordEnd;
332         }
333     } else {
334         UChar32 c = pos.characterAfter();
335         CFCharacterSetRef set = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline);
336         if (c == 0 || CFCharacterSetIsLongCharacterMember(set, c)) {
337             // search backward for a non-space
338             while (1) {
339                 if (pos.isNull() || isStartOfLine(pos))
340                     break;
341                 c = pos.characterBefore();
342                 if (!CFCharacterSetIsLongCharacterMember(set, c))
343                     break;
344                 pos = pos.previous(CannotCrossEditingBoundary);  // stay in editable content
345             }
346         }
347         else {
348             // See how far the selection extends into the current word.
349             // Follow the rule stated above.
350             int index = 0;
351             while (1) {
352                 if (pos.isNull() || isStartOfLine(pos))
353                     break;
354                 c = pos.characterBefore();
355                 if (CFCharacterSetIsLongCharacterMember(set, c))
356                     break;
357                 pos = pos.previous(CannotCrossEditingBoundary);  // stay in editable content
358                 index++;
359                 if (index > 1)
360                     break;
361             }
362             if (index > 1) {
363                 // search forward for a space
364                 pos = originalPos;
365                 while (1) {
366                     if (pos.isNull() || isEndOfLine(pos))
367                         break;
368                     c = pos.characterAfter();
369                     if (CFCharacterSetIsLongCharacterMember(set, c))
370                         break;
371                     pos = pos.next(CannotCrossEditingBoundary);  // stay in editable content
372                 }
373             }
374         }
375     }
376     
377     return [WebVisiblePosition _wrapVisiblePositionIfValid:pos];
378 }
379
380 - (BOOL)isEditable
381 {
382     return isEditablePosition([self _visiblePosition].deepEquivalent());
383 }
384
385 - (BOOL)requiresContextForWordBoundary
386 {
387     VisiblePosition vp = [self _visiblePosition];
388     return requiresContextForWordBoundary(vp.characterAfter()) || requiresContextForWordBoundary(vp.characterBefore());
389 }    
390
391 - (BOOL)atAlphaNumericBoundaryInDirection:(WebTextAdjustmentDirection)direction
392 {
393     static CFCharacterSetRef set = CFCharacterSetGetPredefined(kCFCharacterSetAlphaNumeric);
394     VisiblePosition pos = [self _visiblePosition];
395     UChar32 charBefore = pos.characterBefore();
396     UChar32 charAfter = pos.characterAfter();
397     bool before = CFCharacterSetIsCharacterMember(set, charBefore);
398     bool after = CFCharacterSetIsCharacterMember(set, charAfter);
399     return [self directionIsDownstream:direction] ? (before && !after) : (!before && after);
400 }
401
402 - (DOMRange *)enclosingRangeWithDictationPhraseAlternatives:(NSArray **)alternatives
403 {
404     ASSERT(alternatives);
405     if (!alternatives)
406         return nil;
407         
408     // *alternatives should not already point to an array.
409     ASSERT(!(*alternatives));
410     *alternatives = nil;
411         
412     VisiblePosition p = [self _visiblePosition];
413     if (p.isNull())
414         return nil;
415         
416     int o = p.deepEquivalent().deprecatedEditingOffset();
417     if (o < 0)
418         return nil;
419     unsigned offset = o;
420     
421     Node* node = p.deepEquivalent().anchorNode();
422     Document& document = node->document();
423     
424     const auto& markers = document.markers().markersFor(node, DocumentMarker::DictationPhraseWithAlternatives);
425     if (markers.isEmpty())
426         return nil;
427         
428     for (size_t i = 0; i < markers.size(); i++) {
429         const DocumentMarker* marker = markers[i];
430         if (marker->startOffset() <= offset && marker->endOffset() >= offset) {
431             const Vector<String>& markerAlternatives = marker->alternatives();
432             *alternatives = [NSMutableArray arrayWithCapacity:markerAlternatives.size()];
433             for (size_t j = 0; j < markerAlternatives.size(); j++)
434                 [(NSMutableArray *)*alternatives addObject:(NSString *)(markerAlternatives[j])];
435                 
436             RefPtr<Range> range = Range::create(document, node, marker->startOffset(), node, marker->endOffset());
437             return kit(range.get());
438         }
439     }
440         
441     return nil;
442 }
443
444 - (DOMRange *)enclosingRangeWithCorrectionIndicator
445 {
446     VisiblePosition p = [self _visiblePosition];
447     if (p.isNull())
448         return nil;
449     
450     int o = p.deepEquivalent().deprecatedEditingOffset();
451     if (o < 0)
452         return nil;
453     unsigned offset = o;
454     
455     Node* node = p.deepEquivalent().anchorNode();
456     Document& document = node->document();
457     
458     const auto& markers = document.markers().markersFor(node, DocumentMarker::Spelling);
459     if (markers.isEmpty())
460         return nil;
461     
462     for (size_t i = 0; i < markers.size(); i++) {
463         const DocumentMarker* marker = markers[i];
464         if (marker->startOffset() <= offset && marker->endOffset() >= offset) {
465             RefPtr<Range> range = Range::create(document, node, marker->startOffset(), node, marker->endOffset());
466             return kit(range.get());
467         }
468     }
469     
470     return nil;
471 }
472
473 - (NSSelectionAffinity)affinity
474 {
475     return (NSSelectionAffinity)[self _visiblePosition].affinity();
476 }
477
478 - (void)setAffinity:(NSSelectionAffinity)affinity
479 {
480     reinterpret_cast<VisiblePosition *>(_internal)->setAffinity((WebCore::EAffinity)affinity);
481 }
482
483 @end
484
485 @implementation DOMRange (VisiblePositionExtensions)
486
487 - (WebVisiblePosition *)startPosition
488 {
489     Range *range = core(self);
490     return [WebVisiblePosition _wrapVisiblePosition:VisiblePosition(range->startPosition())];
491 }
492
493 - (WebVisiblePosition *)endPosition
494 {
495     Range *range = core(self);
496     return [WebVisiblePosition _wrapVisiblePosition:VisiblePosition(range->endPosition())];
497 }
498
499 - (DOMRange *)enclosingWordRange
500 {
501     VisibleSelection selection([self.startPosition _visiblePosition], [self.endPosition _visiblePosition]);
502     selection = FrameSelection::wordSelectionContainingCaretSelection(selection);
503     WebVisiblePosition *start = [WebVisiblePosition _wrapVisiblePosition:selection.visibleStart()];
504     WebVisiblePosition *end = [WebVisiblePosition _wrapVisiblePosition:selection.visibleEnd()];
505     return [DOMRange rangeForFirstPosition:start second:end];
506 }
507
508 + (DOMRange *)rangeForFirstPosition:(WebVisiblePosition *)first second:(WebVisiblePosition *)second
509 {
510     VisiblePosition firstVP = [first _visiblePosition];
511     VisiblePosition secondVP = [second _visiblePosition];
512     
513     if (firstVP.isNull() || secondVP.isNull())
514         return nil;
515     
516     RefPtr<Range> range;
517     if (firstVP < secondVP) {
518         range = Range::create(firstVP.deepEquivalent().deprecatedNode()->document(),
519                                      firstVP, secondVP);
520     } else {
521         range = Range::create(firstVP.deepEquivalent().deprecatedNode()->document(),
522                                             secondVP, firstVP);
523     }
524     
525     
526     return kit(range.get());
527 }
528
529 @end
530
531 @implementation DOMNode (VisiblePositionExtensions)
532
533 - (DOMRange *)rangeOfContents
534 {
535     DOMRange *range = [[self ownerDocument] createRange];
536     [range setStart:self offset:0];
537     [range setEnd:self offset:[[self childNodes] length]];
538     return range;    
539 }
540
541 - (WebVisiblePosition *)startPosition
542 {
543 #if USE(UIKIT_EDITING)
544     // When in editable content, we need to calculate the startPosition from the beginning of the
545     // editable area.
546     Node* node = core(self);
547     if (node->isContentEditable()) {
548         VisiblePosition vp(createLegacyEditingPosition(node, 0), VP_DEFAULT_AFFINITY);
549         return [WebVisiblePosition _wrapVisiblePosition:startOfEditableContent(vp)];
550     }
551 #endif
552     return [[self rangeOfContents] startPosition];
553 }
554
555 - (WebVisiblePosition *)endPosition
556 {
557 #if USE(UIKIT_EDITING)
558     // When in editable content, we need to calculate the endPosition from the end of the
559     // editable area.
560     Node* node = core(self);
561     if (node->isContentEditable()) {
562         VisiblePosition vp(createLegacyEditingPosition(node, 0), VP_DEFAULT_AFFINITY);
563         return [WebVisiblePosition _wrapVisiblePosition:endOfEditableContent(vp)];
564     }
565 #endif
566     return [[self rangeOfContents] endPosition];
567 }
568
569 @end
570
571 @implementation DOMHTMLInputElement (VisiblePositionExtensions)
572
573 - (WebVisiblePosition *)startPosition
574 {
575     Node* node = core(self);
576     RenderObject* object = node->renderer();
577     if (!is<RenderTextControl>(object))
578         return [super startPosition];
579     
580     VisiblePosition visiblePosition = downcast<RenderTextControl>(*object).textFormControlElement().visiblePositionForIndex(0);
581     return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
582 }
583
584 - (WebVisiblePosition *)endPosition
585 {
586     Node* node = core(self);
587     RenderObject* object = node->renderer();
588     if (!is<RenderTextControl>(object))
589         return [super endPosition];
590     
591     RenderTextControl& textControl = downcast<RenderTextControl>(*object);
592     VisiblePosition visiblePosition = textControl.textFormControlElement().visiblePositionForIndex(textControl.textFormControlElement().value().length());
593     return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
594 }
595
596 @end
597
598 @implementation DOMHTMLTextAreaElement (VisiblePositionExtensions)
599
600 - (WebVisiblePosition *)startPosition
601 {
602     Node* node = core(self);
603     RenderObject* object = node->renderer();
604     if (!object) 
605         return [super startPosition];
606     
607     VisiblePosition visiblePosition = downcast<RenderTextControl>(*object).textFormControlElement().visiblePositionForIndex(0);
608     return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
609 }
610
611 - (WebVisiblePosition *)endPosition
612 {
613     Node* node = core(self);
614     RenderObject* object = node->renderer();
615     if (!object) 
616         return [super endPosition];
617     
618     RenderTextControl& textControl = downcast<RenderTextControl>(*object);
619     VisiblePosition visiblePosition = textControl.textFormControlElement().visiblePositionForIndex(textControl.textFormControlElement().value().length());
620     return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
621 }
622
623 @end
624
625 #endif // PLATFORM(IOS)