| OLD | NEW |
| (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 |
| OLD | NEW |