Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(59)

Side by Side Diff: third_party/WebKit/Source/core/editing/SelectionModifierWord.cpp

Issue 2908083002: Move LeftWordPosition/RightWordPosition() to SelectionModifierWord.cpp (Closed)
Patch Set: 2017-05-29T16:51:51 Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "core/editing/EditingUtilities.h"
28 #include "core/editing/RenderedPosition.h"
29 #include "core/editing/VisibleUnits.h"
30 #include "core/layout/line/InlineTextBox.h"
31 #include "core/layout/line/RootInlineBox.h"
32 #include "platform/text/TextBreakIterator.h"
33
34 namespace blink {
35
36 namespace {
37
38 // This class holds a list of |InlineBox| in logical order.
39 // TDOO(editing-dev): We should utilize |CachedLogicallyOrderedLeafBoxes| class
40 // in |CompositeEditCommand::DeleteInsignificantText()|.
41 class CachedLogicallyOrderedLeafBoxes final {
42 public:
43 CachedLogicallyOrderedLeafBoxes() = default;
44
45 const InlineTextBox* PreviousTextBox(const RootInlineBox*,
46 const InlineTextBox*);
47 const InlineTextBox* NextTextBox(const RootInlineBox*, const InlineTextBox*);
48
49 size_t size() const { return leaf_boxes_.size(); }
50 const InlineBox* FirstBox() const { return leaf_boxes_[0]; }
51
52 private:
53 const Vector<InlineBox*>& CollectBoxes(const RootInlineBox*);
54 int BoxIndexInLeaves(const InlineTextBox*) const;
55
56 const RootInlineBox* root_inline_box_ = nullptr;
57 Vector<InlineBox*> leaf_boxes_;
58 };
59
60 const InlineTextBox* CachedLogicallyOrderedLeafBoxes::PreviousTextBox(
61 const RootInlineBox* root,
62 const InlineTextBox* box) {
63 if (!root)
64 return nullptr;
65
66 CollectBoxes(root);
67
68 // If box is null, root is box's previous RootInlineBox, and previousBox is
69 // the last logical box in root.
70 int box_index = leaf_boxes_.size() - 1;
71 if (box)
72 box_index = BoxIndexInLeaves(box) - 1;
73
74 for (int i = box_index; i >= 0; --i) {
75 if (leaf_boxes_[i]->IsInlineTextBox())
76 return ToInlineTextBox(leaf_boxes_[i]);
77 }
78
79 return nullptr;
80 }
81
82 const InlineTextBox* CachedLogicallyOrderedLeafBoxes::NextTextBox(
83 const RootInlineBox* root,
84 const InlineTextBox* box) {
85 if (!root)
86 return nullptr;
87
88 CollectBoxes(root);
89
90 // If box is null, root is box's next RootInlineBox, and nextBox is the first
91 // logical box in root. Otherwise, root is box's RootInlineBox, and nextBox is
92 // the next logical box in the same line.
93 size_t next_box_index = 0;
94 if (box)
95 next_box_index = BoxIndexInLeaves(box) + 1;
96
97 for (size_t i = next_box_index; i < leaf_boxes_.size(); ++i) {
98 if (leaf_boxes_[i]->IsInlineTextBox())
99 return ToInlineTextBox(leaf_boxes_[i]);
100 }
101
102 return nullptr;
103 }
104
105 const Vector<InlineBox*>& CachedLogicallyOrderedLeafBoxes::CollectBoxes(
106 const RootInlineBox* root) {
107 if (root_inline_box_ != root) {
108 root_inline_box_ = root;
109 leaf_boxes_.clear();
110 root->CollectLeafBoxesInLogicalOrder(leaf_boxes_);
111 }
112 return leaf_boxes_;
113 }
114
115 int CachedLogicallyOrderedLeafBoxes::BoxIndexInLeaves(
116 const InlineTextBox* box) const {
117 for (size_t i = 0; i < leaf_boxes_.size(); ++i) {
118 if (box == leaf_boxes_[i])
119 return i;
120 }
121 return 0;
122 }
123
124 const InlineTextBox* LogicallyPreviousBox(
125 const VisiblePosition& visible_position,
126 const InlineTextBox* text_box,
127 bool& previous_box_in_different_block,
128 CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
129 DCHECK(visible_position.IsValid()) << visible_position;
130 const InlineBox* start_box = text_box;
131
132 const InlineTextBox* previous_box =
133 leaf_boxes.PreviousTextBox(&start_box->Root(), text_box);
134 if (previous_box)
135 return previous_box;
136
137 previous_box =
138 leaf_boxes.PreviousTextBox(start_box->Root().PrevRootBox(), nullptr);
139 if (previous_box)
140 return previous_box;
141
142 for (;;) {
143 Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
144 if (!start_node)
145 break;
146
147 Position position = PreviousRootInlineBoxCandidatePosition(
148 start_node, visible_position, kContentIsEditable);
149 if (position.IsNull())
150 break;
151
152 RenderedPosition rendered_position(position, TextAffinity::kDownstream);
153 RootInlineBox* previous_root = rendered_position.RootBox();
154 if (!previous_root)
155 break;
156
157 previous_box = leaf_boxes.PreviousTextBox(previous_root, nullptr);
158 if (previous_box) {
159 previous_box_in_different_block = true;
160 return previous_box;
161 }
162
163 if (!leaf_boxes.size())
164 break;
165 start_box = leaf_boxes.FirstBox();
166 }
167 return nullptr;
168 }
169
170 const InlineTextBox* LogicallyNextBox(
171 const VisiblePosition& visible_position,
172 const InlineTextBox* text_box,
173 bool& next_box_in_different_block,
174 CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
175 DCHECK(visible_position.IsValid()) << visible_position;
176 const InlineBox* start_box = text_box;
177
178 const InlineTextBox* next_box =
179 leaf_boxes.NextTextBox(&start_box->Root(), text_box);
180 if (next_box)
181 return next_box;
182
183 next_box = leaf_boxes.NextTextBox(start_box->Root().NextRootBox(), nullptr);
184 if (next_box)
185 return next_box;
186
187 for (;;) {
188 Node* start_node = start_box->GetLineLayoutItem().NonPseudoNode();
189 if (!start_node)
190 break;
191
192 Position position = NextRootInlineBoxCandidatePosition(
193 start_node, visible_position, kContentIsEditable);
194 if (position.IsNull())
195 break;
196
197 RenderedPosition rendered_position(position, TextAffinity::kDownstream);
198 RootInlineBox* next_root = rendered_position.RootBox();
199 if (!next_root)
200 break;
201
202 next_box = leaf_boxes.NextTextBox(next_root, nullptr);
203 if (next_box) {
204 next_box_in_different_block = true;
205 return next_box;
206 }
207
208 if (!leaf_boxes.size())
209 break;
210 start_box = leaf_boxes.FirstBox();
211 }
212 return nullptr;
213 }
214
215 TextBreakIterator* WordBreakIteratorForMinOffsetBoundary(
216 const VisiblePosition& visible_position,
217 const InlineTextBox* text_box,
218 int& previous_box_length,
219 bool& previous_box_in_different_block,
220 Vector<UChar, 1024>& string,
221 CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
222 DCHECK(visible_position.IsValid()) << visible_position;
223 previous_box_in_different_block = false;
224
225 // TODO(editing-dev) Handle the case when we don't have an inline text box.
226 const InlineTextBox* previous_box = LogicallyPreviousBox(
227 visible_position, text_box, previous_box_in_different_block, leaf_boxes);
228
229 int len = 0;
230 string.clear();
231 if (previous_box) {
232 previous_box_length = previous_box->Len();
233 previous_box->GetLineLayoutItem().GetText().AppendTo(
234 string, previous_box->Start(), previous_box_length);
235 len += previous_box_length;
236 }
237 text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
238 text_box->Len());
239 len += text_box->Len();
240
241 return WordBreakIterator(string.data(), len);
242 }
243
244 TextBreakIterator* WordBreakIteratorForMaxOffsetBoundary(
245 const VisiblePosition& visible_position,
246 const InlineTextBox* text_box,
247 bool& next_box_in_different_block,
248 Vector<UChar, 1024>& string,
249 CachedLogicallyOrderedLeafBoxes& leaf_boxes) {
250 DCHECK(visible_position.IsValid()) << visible_position;
251 next_box_in_different_block = false;
252
253 // TODO(editing-dev) Handle the case when we don't have an inline text box.
254 const InlineTextBox* next_box = LogicallyNextBox(
255 visible_position, text_box, next_box_in_different_block, leaf_boxes);
256
257 int len = 0;
258 string.clear();
259 text_box->GetLineLayoutItem().GetText().AppendTo(string, text_box->Start(),
260 text_box->Len());
261 len += text_box->Len();
262 if (next_box) {
263 next_box->GetLineLayoutItem().GetText().AppendTo(string, next_box->Start(),
264 next_box->Len());
265 len += next_box->Len();
266 }
267
268 return WordBreakIterator(string.data(), len);
269 }
270
271 bool IsLogicalStartOfWord(TextBreakIterator* iter,
272 int position,
273 bool hard_line_break) {
274 bool boundary = hard_line_break ? true : iter->isBoundary(position);
275 if (!boundary)
276 return false;
277
278 iter->following(position);
279 // isWordTextBreak returns true after moving across a word and false after
280 // moving across a punctuation/space.
281 return IsWordTextBreak(iter);
282 }
283
284 bool IslogicalEndOfWord(TextBreakIterator* iter,
285 int position,
286 bool hard_line_break) {
287 bool boundary = iter->isBoundary(position);
288 return (hard_line_break || boundary) && IsWordTextBreak(iter);
289 }
290
291 enum CursorMovementDirection { kMoveLeft, kMoveRight };
292
293 VisiblePosition VisualWordPosition(const VisiblePosition& visible_position,
294 CursorMovementDirection direction,
295 bool skips_space_when_moving_right) {
296 DCHECK(visible_position.IsValid()) << visible_position;
297 if (visible_position.IsNull())
298 return VisiblePosition();
299
300 TextDirection block_direction =
301 DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
302 InlineBox* previously_visited_box = nullptr;
303 VisiblePosition current = visible_position;
304 TextBreakIterator* iter = nullptr;
305
306 CachedLogicallyOrderedLeafBoxes leaf_boxes;
307 Vector<UChar, 1024> string;
308
309 for (;;) {
310 VisiblePosition adjacent_character_position = direction == kMoveRight
311 ? RightPositionOf(current)
312 : LeftPositionOf(current);
313 if (adjacent_character_position.DeepEquivalent() ==
314 current.DeepEquivalent() ||
315 adjacent_character_position.IsNull())
316 return VisiblePosition();
317
318 InlineBoxPosition box_position = ComputeInlineBoxPosition(
319 adjacent_character_position.DeepEquivalent(), TextAffinity::kUpstream);
320 InlineBox* box = box_position.inline_box;
321 int offset_in_box = box_position.offset_in_box;
322
323 if (!box)
324 break;
325 if (!box->IsInlineTextBox()) {
326 current = adjacent_character_position;
327 continue;
328 }
329
330 InlineTextBox* text_box = ToInlineTextBox(box);
331 int previous_box_length = 0;
332 bool previous_box_in_different_block = false;
333 bool next_box_in_different_block = false;
334 bool moving_into_new_box = previously_visited_box != box;
335
336 if (offset_in_box == box->CaretMinOffset()) {
337 iter = WordBreakIteratorForMinOffsetBoundary(
338 visible_position, text_box, previous_box_length,
339 previous_box_in_different_block, string, leaf_boxes);
340 } else if (offset_in_box == box->CaretMaxOffset()) {
341 iter = WordBreakIteratorForMaxOffsetBoundary(visible_position, text_box,
342 next_box_in_different_block,
343 string, leaf_boxes);
344 } else if (moving_into_new_box) {
345 iter = WordBreakIterator(text_box->GetLineLayoutItem().GetText(),
346 text_box->Start(), text_box->Len());
347 previously_visited_box = box;
348 }
349
350 if (!iter)
351 break;
352
353 iter->first();
354 int offset_in_iterator =
355 offset_in_box - text_box->Start() + previous_box_length;
356
357 bool is_word_break;
358 bool box_has_same_directionality_as_block =
359 box->Direction() == block_direction;
360 bool moving_backward =
361 (direction == kMoveLeft && box->Direction() == TextDirection::kLtr) ||
362 (direction == kMoveRight && box->Direction() == TextDirection::kRtl);
363 if ((skips_space_when_moving_right &&
364 box_has_same_directionality_as_block) ||
365 (!skips_space_when_moving_right && moving_backward)) {
366 bool logical_start_in_layout_object =
367 offset_in_box == static_cast<int>(text_box->Start()) &&
368 previous_box_in_different_block;
369 is_word_break = IsLogicalStartOfWord(iter, offset_in_iterator,
370 logical_start_in_layout_object);
371 } else {
372 bool logical_end_in_layout_object =
373 offset_in_box ==
374 static_cast<int>(text_box->Start() + text_box->Len()) &&
375 next_box_in_different_block;
376 is_word_break = IslogicalEndOfWord(iter, offset_in_iterator,
377 logical_end_in_layout_object);
378 }
379
380 if (is_word_break)
381 return adjacent_character_position;
382
383 current = adjacent_character_position;
384 }
385 return VisiblePosition();
386 }
387
388 } // namespace
389
390 // TODO(yosin): Once we move |SelectionModifier::ModifyMovingLeft()| in this
391 // file, we can make |LeftWordPosition()| as file local function.
392 VisiblePosition LeftWordPosition(const VisiblePosition& visible_position,
393 bool skips_space_when_moving_right) {
394 DCHECK(visible_position.IsValid()) << visible_position;
395 VisiblePosition left_word_break = VisualWordPosition(
396 visible_position, kMoveLeft, skips_space_when_moving_right);
397 left_word_break = HonorEditingBoundaryAtOrBefore(
398 left_word_break, visible_position.DeepEquivalent());
399
400 // TODO(editing-dev) How should we handle a non-editable position?
401 if (left_word_break.IsNull() &&
402 IsEditablePosition(visible_position.DeepEquivalent())) {
403 TextDirection block_direction =
404 DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
405 left_word_break = block_direction == TextDirection::kLtr
406 ? StartOfEditableContent(visible_position)
407 : EndOfEditableContent(visible_position);
408 }
409 return left_word_break;
410 }
411
412 // TODO(yosin): Once we move |SelectionModifier::ModifyMovingRight()| in this
413 // file, we can make |RightWordPosition()| as file local function.
414 VisiblePosition RightWordPosition(const VisiblePosition& visible_position,
415 bool skips_space_when_moving_right) {
416 DCHECK(visible_position.IsValid()) << visible_position;
417 VisiblePosition right_word_break = VisualWordPosition(
418 visible_position, kMoveRight, skips_space_when_moving_right);
419 right_word_break = HonorEditingBoundaryAtOrBefore(
420 right_word_break, visible_position.DeepEquivalent());
421
422 // TODO(editing-dev) How should we handle a non-editable position?
423 if (right_word_break.IsNull() &&
424 IsEditablePosition(visible_position.DeepEquivalent())) {
425 TextDirection block_direction =
426 DirectionOfEnclosingBlock(visible_position.DeepEquivalent());
427 right_word_break = block_direction == TextDirection::kLtr
428 ? EndOfEditableContent(visible_position)
429 : StartOfEditableContent(visible_position);
430 }
431 return right_word_break;
432 }
433
434 } // namespace blink
OLDNEW
« no previous file with comments | « third_party/WebKit/Source/core/editing/BUILD.gn ('k') | third_party/WebKit/Source/core/editing/VisibleUnits.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698