Reviewed by Ken Kocienda.
[WebKit-https.git] / WebCore / khtml / editing / visible_units.cpp
1 /*
2  * Copyright (C) 2004 Apple Computer, 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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "visible_units.h"
27
28 #include <qstring.h>
29
30 #include "misc/helper.h"
31 #include "rendering/render_text.h"
32 #include "visible_position.h"
33 #include "visible_text.h"
34 #include "xml/dom_docimpl.h"
35
36 using DOM::DocumentImpl;
37 using DOM::NodeImpl;
38 using DOM::Position;
39 using DOM::Range;
40
41 namespace khtml {
42
43 static VisiblePosition previousWordBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const QChar *, unsigned))
44 {
45     Position pos = c.deepEquivalent();
46     NodeImpl *n = pos.node();
47     if (!n)
48         return VisiblePosition();
49     DocumentImpl *d = n->getDocument();
50     if (!d)
51         return VisiblePosition();
52     NodeImpl *de = d->documentElement();
53     if (!de)
54         return VisiblePosition();
55
56     Range searchRange(d);
57     searchRange.setStartBefore(de);
58     Position end(pos.equivalentRangeCompliantPosition());
59     searchRange.setEnd(end.node(), end.offset());
60     SimplifiedBackwardsTextIterator it(searchRange);
61     QString string;
62     unsigned next = 0;
63     while (!it.atEnd() && it.length() > 0) {
64         // Keep asking the iterator for chunks until the nextWordFromIndex() function
65         // returns a non-zero value.
66         string.prepend(it.characters(), it.length());
67         next = searchFunction(string.unicode(), string.length());
68         if (next != 0)
69             break;
70         it.advance();
71     }
72     
73     if (it.atEnd() && next == 0) {
74         Range range(it.range());
75         pos = Position(range.startContainer().handle(), range.startOffset());
76     }
77     else if (!it.atEnd() && it.length() == 0) {
78         // Got a zero-length chunk.
79         // This means we have hit a replaced element.
80         // Make a check to see if the position should be before or after the replaced element
81         // by performing an additional check with a modified string which uses an "X" 
82         // character to stand in for the replaced element.
83         QChar chars[2];
84         chars[0] = 'X';
85         chars[1] = ' ';
86         string.prepend(chars, 2);
87         unsigned pastImage = searchFunction(string.unicode(), string.length());
88         Range range(it.range());
89         if (pastImage == 0)
90             pos = Position(range.startContainer().handle(), range.startOffset());
91         else
92             pos = Position(range.endContainer().handle(), range.endOffset());
93     }
94     else if (next != 0) {
95         // The simpler iterator used in this function, as compared to the one used in 
96         // nextWordPosition(), gives us results we can use directly without having to 
97         // iterate again to translate the next value into a DOM position. 
98         NodeImpl *node = it.range().startContainer().handle();
99         if (node->isTextNode() || (node->renderer() && node->renderer()->isBR())) {
100             // The next variable contains a usable index into a text node
101             pos = Position(node, next);
102         }
103         else {
104             // If we are not in a text node, we ended on a node boundary, so the
105             // range start offset should be used.
106             pos = Position(node, it.range().startOffset());
107         }
108     }
109
110     return VisiblePosition(pos, UPSTREAM);
111 }
112
113 static VisiblePosition nextWordBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const QChar *, unsigned))
114 {
115     Position pos = c.deepEquivalent();
116     NodeImpl *n = pos.node();
117     if (!n)
118         return VisiblePosition();
119     DocumentImpl *d = n->getDocument();
120     if (!d)
121         return VisiblePosition();
122     NodeImpl *de = d->documentElement();
123     if (!de)
124         return VisiblePosition();
125
126     Range searchRange(d);
127     Position start(pos.equivalentRangeCompliantPosition());
128     searchRange.setStart(start.node(), start.offset());
129     searchRange.setEndAfter(de);
130     TextIterator it(searchRange);
131     QString string;
132     unsigned next = 0;
133     while (!it.atEnd() && it.length() > 0) {
134         // Keep asking the iterator for chunks until the search function
135         // returns an end value not equal to the length of the string passed to it.
136         string.append(it.characters(), it.length());
137         next = searchFunction(string.unicode(), string.length());
138         if (next != string.length())
139             break;
140         it.advance();
141     }
142     
143     if (it.atEnd() && next == string.length()) {
144         Range range(it.range());
145         pos = Position(range.startContainer().handle(), range.startOffset());
146     }
147     else if (!it.atEnd() && it.length() == 0) {
148         // Got a zero-length chunk.
149         // This means we have hit a replaced element.
150         // Make a check to see if the position should be before or after the replaced element
151         // by performing an additional check with a modified string which uses an "X" 
152         // character to stand in for the replaced element.
153         QChar chars[2];
154         chars[0] = ' ';
155         chars[1] = 'X';
156         string.append(chars, 2);
157         unsigned pastImage = searchFunction(string.unicode(), string.length());
158         Range range(it.range());
159         if (next != pastImage)
160             pos = Position(range.endContainer().handle(), range.endOffset());
161         else
162             pos = Position(range.startContainer().handle(), range.startOffset());
163     }
164     else if (next != 0) {
165         // Use the character iterator to translate the next value into a DOM position.
166         CharacterIterator charIt(searchRange);
167         charIt.advance(next - 1);
168         pos = Position(charIt.range().endContainer().handle(), charIt.range().endOffset());
169     }
170     return VisiblePosition(pos, UPSTREAM);
171 }
172
173 static unsigned startWordBoundary(const QChar *characters, unsigned length)
174 {
175     int start, end;
176     findWordBoundary(characters, length, length, &start, &end);
177     return start;
178 }
179
180 VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side)
181 {
182     VisiblePosition p = c;
183     if (side == RightWordIfOnBoundary) {
184         p = c.next();
185         if (p.isNull())
186             return c;
187     }
188     return previousWordBoundary(p, startWordBoundary);
189 }
190
191 static unsigned endWordBoundary(const QChar *characters, unsigned length)
192 {
193     int start, end;
194     findWordBoundary(characters, length, 0, &start, &end);
195     return end;
196 }
197
198 VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side)
199 {
200     VisiblePosition p = c;
201     if (side == LeftWordIfOnBoundary) {
202         p = c.previous();
203         if (p.isNull())
204             return c;
205     }
206     return nextWordBoundary(p, endWordBoundary);
207 }
208
209 static unsigned previousWordPositionBoundary(const QChar *characters, unsigned length)
210 {
211     return nextWordFromIndex(characters, length, length, false);
212 }
213
214 VisiblePosition previousWordPosition(const VisiblePosition &c)
215 {
216     return previousWordBoundary(c, previousWordPositionBoundary);
217 }
218
219 static unsigned nextWordPositionBoundary(const QChar *characters, unsigned length)
220 {
221     return nextWordFromIndex(characters, length, 0, true);
222 }
223
224 VisiblePosition nextWordPosition(const VisiblePosition &c)
225 {
226     return nextWordBoundary(c, nextWordPositionBoundary);
227 }
228
229 VisiblePosition previousLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
230 {
231     Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
232     return VisiblePosition(pos.previousLinePosition(x, affinity));
233 }
234
235 VisiblePosition nextLinePosition(const VisiblePosition &c, EAffinity affinity, int x)
236 {
237     Position pos = affinity == UPSTREAM ? c.deepEquivalent() : c.downstreamDeepEquivalent();
238     return VisiblePosition(pos.nextLinePosition(x, affinity));
239 }
240
241 VisiblePosition startOfParagraph(const VisiblePosition &c)
242 {
243     Position p = c.deepEquivalent();
244     NodeImpl *startNode = p.node();
245     if (!startNode)
246         return VisiblePosition();
247
248     NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
249
250     NodeImpl *node = startNode;
251     long offset = p.offset();
252
253     for (NodeImpl *n = startNode; n; n = n->traversePreviousNodePostOrder(startBlock)) {
254         RenderObject *r = n->renderer();
255         if (!r)
256             continue;
257         RenderStyle *style = r->style();
258         if (style->visibility() != VISIBLE)
259             continue;
260         if (r->isBR() || r->isBlockFlow())
261             break;
262         if (r->isText()) {
263             if (style->whiteSpace() == PRE) {
264                 QChar *text = static_cast<RenderText *>(r)->text();
265                 long i = static_cast<RenderText *>(r)->length();
266                 long o = offset;
267                 if (n == startNode && o < i)
268                     i = kMax(0L, o);
269                 while (--i >= 0)
270                     if (text[i] == '\n')
271                         return VisiblePosition(n, i + 1);
272             }
273             node = n;
274             offset = 0;
275         } else if (r->isReplaced()) {
276             node = n;
277             offset = 0;
278         }
279     }
280
281     return VisiblePosition(node, offset);
282 }
283
284 VisiblePosition endOfParagraph(const VisiblePosition &c, EIncludeLineBreak includeLineBreak)
285 {
286     Position p = c.deepEquivalent();
287
288     NodeImpl *startNode = p.node();
289     if (!startNode)
290         return VisiblePosition();
291
292     NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
293
294     NodeImpl *node = startNode;
295     long offset = p.offset();
296
297     for (NodeImpl *n = startNode; n; n = n->traverseNextNode(startBlock)) {
298         RenderObject *r = n->renderer();
299         if (!r)
300             continue;
301         RenderStyle *style = r->style();
302         if (style->visibility() != VISIBLE)
303             continue;
304         if (r->isBR()) {
305             if (includeLineBreak)
306                 return VisiblePosition(n, 1);
307             break;
308         }
309         if (r->isBlockFlow()) {
310             if (includeLineBreak)
311                 return VisiblePosition(n, 0);
312             break;
313         }
314         if (r->isText()) {
315             long length = static_cast<RenderText *>(r)->length();
316             if (style->whiteSpace() == PRE) {
317                 QChar *text = static_cast<RenderText *>(r)->text();
318                 long o = 0;
319                 if (n == startNode && offset < length)
320                     o = offset;
321                 for (long i = o; i < length; ++i)
322                     if (text[i] == '\n')
323                         return VisiblePosition(n, i + includeLineBreak);
324             }
325             node = n;
326             offset = length;
327         } else if (r->isReplaced()) {
328             node = n;
329             offset = 1;
330         }
331     }
332
333     return VisiblePosition(node, offset);
334 }
335
336 bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b)
337 {
338     return a == b || startOfParagraph(a) == startOfParagraph(b);
339 }
340
341 VisiblePosition previousParagraphPosition(const VisiblePosition &p, EAffinity affinity, int x)
342 {
343     VisiblePosition pos = p;
344     do {
345         VisiblePosition n = previousLinePosition(pos, affinity, x);
346         if (n.isNull() || n == pos)
347             return p;
348         pos = n;
349     } while (inSameParagraph(p, pos));
350     return pos;
351 }
352
353 VisiblePosition nextParagraphPosition(const VisiblePosition &p, EAffinity affinity, int x)
354 {
355     VisiblePosition pos = p;
356     do {
357         VisiblePosition n = nextLinePosition(pos, affinity, x);
358         if (n.isNull() || n == pos)
359             return p;
360         pos = n;
361     } while (inSameParagraph(p, pos));
362     return pos;
363 }
364
365 } // namespace DOM