1 // Copyright 2017 PDFium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com 6 7 #include "xfa/fde/cfde_texteditengine.h" 8 9 #include <algorithm> 10 #include <limits> 11 12 #include "xfa/fde/cfde_textout.h" 13 #include "xfa/fde/cfde_wordbreak_data.h" 14 #include "xfa/fgas/font/cfgas_gefont.h" 15 16 namespace { 17 18 constexpr size_t kMaxEditOperations = 128; 19 constexpr size_t kGapSize = 128; 20 constexpr size_t kPageWidthMax = 0xffff; 21 22 class InsertOperation : public CFDE_TextEditEngine::Operation { 23 public: 24 InsertOperation(CFDE_TextEditEngine* engine, 25 size_t start_idx, 26 const WideString& added_text) 27 : engine_(engine), start_idx_(start_idx), added_text_(added_text) {} 28 29 ~InsertOperation() override {} 30 31 void Redo() const override { 32 engine_->Insert(start_idx_, added_text_, 33 CFDE_TextEditEngine::RecordOperation::kSkipRecord); 34 } 35 36 void Undo() const override { 37 engine_->Delete(start_idx_, added_text_.GetLength(), 38 CFDE_TextEditEngine::RecordOperation::kSkipRecord); 39 } 40 41 private: 42 UnownedPtr<CFDE_TextEditEngine> engine_; 43 size_t start_idx_; 44 WideString added_text_; 45 }; 46 47 class DeleteOperation : public CFDE_TextEditEngine::Operation { 48 public: 49 DeleteOperation(CFDE_TextEditEngine* engine, 50 size_t start_idx, 51 const WideString& removed_text) 52 : engine_(engine), start_idx_(start_idx), removed_text_(removed_text) {} 53 54 ~DeleteOperation() override {} 55 56 void Redo() const override { 57 engine_->Delete(start_idx_, removed_text_.GetLength(), 58 CFDE_TextEditEngine::RecordOperation::kSkipRecord); 59 } 60 61 void Undo() const override { 62 engine_->Insert(start_idx_, removed_text_, 63 CFDE_TextEditEngine::RecordOperation::kSkipRecord); 64 } 65 66 private: 67 UnownedPtr<CFDE_TextEditEngine> engine_; 68 size_t start_idx_; 69 WideString removed_text_; 70 }; 71 72 class ReplaceOperation : public CFDE_TextEditEngine::Operation { 73 public: 74 ReplaceOperation(CFDE_TextEditEngine* engine, 75 size_t start_idx, 76 const WideString& removed_text, 77 const WideString& added_text) 78 : insert_op_(engine, start_idx, added_text), 79 delete_op_(engine, start_idx, removed_text) {} 80 81 ~ReplaceOperation() override {} 82 83 void Redo() const override { 84 delete_op_.Redo(); 85 insert_op_.Redo(); 86 } 87 88 void Undo() const override { 89 insert_op_.Undo(); 90 delete_op_.Undo(); 91 } 92 93 private: 94 InsertOperation insert_op_; 95 DeleteOperation delete_op_; 96 }; 97 98 bool CheckStateChangeForWordBreak(WordBreakProperty from, 99 WordBreakProperty to) { 100 ASSERT(static_cast<int>(from) < 13); 101 102 return !!(gs_FX_WordBreak_Table[static_cast<int>(from)] & 103 static_cast<uint16_t>(1 << static_cast<int>(to))); 104 } 105 106 WordBreakProperty GetWordBreakProperty(wchar_t wcCodePoint) { 107 uint8_t dwProperty = gs_FX_WordBreak_CodePointProperties[wcCodePoint >> 1]; 108 return static_cast<WordBreakProperty>((wcCodePoint & 1) ? (dwProperty & 0x0F) 109 : (dwProperty >> 4)); 110 } 111 112 int GetBreakFlagsFor(WordBreakProperty current, WordBreakProperty next) { 113 if (current == WordBreakProperty::kMidLetter) { 114 if (next == WordBreakProperty::kALetter) 115 return 1; 116 } else if (current == WordBreakProperty::kMidNum) { 117 if (next == WordBreakProperty::kNumeric) 118 return 2; 119 } else if (current == WordBreakProperty::kMidNumLet) { 120 if (next == WordBreakProperty::kALetter) 121 return 1; 122 if (next == WordBreakProperty::kNumeric) 123 return 2; 124 } 125 return 0; 126 } 127 128 bool BreakFlagsChanged(int flags, WordBreakProperty previous) { 129 return (flags != 1 || previous != WordBreakProperty::kALetter) && 130 (flags != 2 || previous != WordBreakProperty::kNumeric); 131 } 132 133 } // namespace 134 135 CFDE_TextEditEngine::CFDE_TextEditEngine() 136 : font_color_(0xff000000), 137 font_size_(10.0f), 138 line_spacing_(10.0f), 139 text_length_(0), 140 gap_position_(0), 141 gap_size_(kGapSize), 142 available_width_(kPageWidthMax), 143 character_limit_(std::numeric_limits<size_t>::max()), 144 visible_line_count_(1), 145 next_operation_index_to_undo_(kMaxEditOperations - 1), 146 next_operation_index_to_insert_(0), 147 max_edit_operations_(kMaxEditOperations), 148 character_alignment_(CFX_TxtLineAlignment_Left), 149 has_character_limit_(false), 150 is_comb_text_(false), 151 is_dirty_(false), 152 validation_enabled_(false), 153 is_multiline_(false), 154 is_linewrap_enabled_(false), 155 limit_horizontal_area_(false), 156 limit_vertical_area_(false), 157 password_mode_(false), 158 password_alias_(L'*'), 159 has_selection_(false), 160 selection_({0, 0}) { 161 content_.resize(gap_size_); 162 operation_buffer_.resize(max_edit_operations_); 163 164 text_break_.SetFontSize(font_size_); 165 text_break_.SetLineBreakTolerance(2.0f); 166 text_break_.SetTabWidth(36); 167 } 168 169 CFDE_TextEditEngine::~CFDE_TextEditEngine() {} 170 171 void CFDE_TextEditEngine::Clear() { 172 text_length_ = 0; 173 gap_position_ = 0; 174 gap_size_ = kGapSize; 175 176 content_.clear(); 177 content_.resize(gap_size_); 178 179 ClearSelection(); 180 ClearOperationRecords(); 181 } 182 183 void CFDE_TextEditEngine::SetMaxEditOperationsForTesting(size_t max) { 184 max_edit_operations_ = max; 185 operation_buffer_.resize(max); 186 187 ClearOperationRecords(); 188 } 189 190 void CFDE_TextEditEngine::AdjustGap(size_t idx, size_t length) { 191 static const size_t char_size = sizeof(WideString::CharType); 192 193 // Move the gap, if necessary. 194 if (idx < gap_position_) { 195 memmove(content_.data() + idx + gap_size_, content_.data() + idx, 196 (gap_position_ - idx) * char_size); 197 gap_position_ = idx; 198 } else if (idx > gap_position_) { 199 memmove(content_.data() + gap_position_, 200 content_.data() + gap_position_ + gap_size_, 201 (idx - gap_position_) * char_size); 202 gap_position_ = idx; 203 } 204 205 // If the gap is too small, make it bigger. 206 if (length >= gap_size_) { 207 size_t new_gap_size = length + kGapSize; 208 content_.resize(text_length_ + new_gap_size); 209 210 memmove(content_.data() + gap_position_ + new_gap_size, 211 content_.data() + gap_position_ + gap_size_, 212 (text_length_ - gap_position_) * char_size); 213 214 gap_size_ = new_gap_size; 215 } 216 } 217 218 size_t CFDE_TextEditEngine::CountCharsExceedingSize(const WideString& text, 219 size_t num_to_check) { 220 if (!limit_horizontal_area_ && !limit_vertical_area_) 221 return 0; 222 223 auto text_out = pdfium::MakeUnique<CFDE_TextOut>(); 224 text_out->SetLineSpace(line_spacing_); 225 text_out->SetFont(font_); 226 text_out->SetFontSize(font_size_); 227 228 FDE_TextStyle style; 229 style.single_line_ = !is_multiline_; 230 231 CFX_RectF text_rect; 232 if (is_linewrap_enabled_) { 233 style.line_wrap_ = true; 234 text_rect.width = available_width_; 235 } else { 236 text_rect.width = kPageWidthMax; 237 } 238 text_out->SetStyles(style); 239 240 size_t length = text.GetLength(); 241 WideStringView temp(text.c_str(), length); 242 243 float vertical_height = line_spacing_ * visible_line_count_; 244 size_t chars_exceeding_size = 0; 245 // TODO(dsinclair): Can this get changed to a binary search? 246 for (size_t i = 0; i < num_to_check; i++) { 247 // This does a lot of string copying .... 248 // TODO(dsinclair): make CalcLogicSize take a WideStringC instead. 249 text_out->CalcLogicSize(WideString(temp), text_rect); 250 251 if (limit_horizontal_area_ && text_rect.width <= available_width_) 252 break; 253 if (limit_vertical_area_ && text_rect.height <= vertical_height) 254 break; 255 256 --length; 257 temp = temp.Mid(0, length); 258 ++chars_exceeding_size; 259 } 260 261 return chars_exceeding_size; 262 } 263 264 void CFDE_TextEditEngine::Insert(size_t idx, 265 const WideString& text, 266 RecordOperation add_operation) { 267 if (idx > text_length_) 268 idx = text_length_; 269 270 size_t length = text.GetLength(); 271 if (length == 0) 272 return; 273 274 // If we're going to be too big we insert what we can and notify the 275 // delegate we've filled the text after the insert is done. 276 bool exceeded_limit = false; 277 if (has_character_limit_ && text_length_ + length > character_limit_) { 278 exceeded_limit = true; 279 length = character_limit_ - text_length_; 280 } 281 282 AdjustGap(idx, length); 283 284 if (validation_enabled_ || limit_horizontal_area_ || limit_vertical_area_) { 285 WideString str; 286 if (gap_position_ > 0) 287 str += WideStringView(content_.data(), gap_position_); 288 289 str += text; 290 291 if (text_length_ - gap_position_ > 0) { 292 str += WideStringView(content_.data() + gap_position_ + gap_size_, 293 text_length_ - gap_position_); 294 } 295 296 if (validation_enabled_ && delegate_ && !delegate_->OnValidate(str)) { 297 // TODO(dsinclair): Notify delegate of validation failure? 298 return; 299 } 300 301 // Check if we've limited the horizontal/vertical area, and if so determine 302 // how many of our characters would be outside the area. 303 size_t chars_exceeding = CountCharsExceedingSize(str, length); 304 if (chars_exceeding > 0) { 305 // If none of the characters will fit, notify and exit. 306 if (chars_exceeding == length) { 307 if (delegate_) 308 delegate_->NotifyTextFull(); 309 return; 310 } 311 312 // Some, but not all, chars will fit, insert them and then notify 313 // we're full. 314 exceeded_limit = true; 315 length -= chars_exceeding; 316 } 317 } 318 319 if (add_operation == RecordOperation::kInsertRecord) { 320 AddOperationRecord( 321 pdfium::MakeUnique<InsertOperation>(this, gap_position_, text)); 322 } 323 324 WideString previous_text; 325 if (delegate_) 326 previous_text = GetText(); 327 328 // Copy the new text into the gap. 329 static const size_t char_size = sizeof(WideString::CharType); 330 memcpy(content_.data() + gap_position_, text.c_str(), length * char_size); 331 gap_position_ += length; 332 gap_size_ -= length; 333 text_length_ += length; 334 335 is_dirty_ = true; 336 337 // Inserting text resets the selection. 338 ClearSelection(); 339 340 if (delegate_) { 341 if (exceeded_limit) 342 delegate_->NotifyTextFull(); 343 344 delegate_->OnTextChanged(previous_text); 345 } 346 } 347 348 void CFDE_TextEditEngine::AddOperationRecord(std::unique_ptr<Operation> op) { 349 size_t last_insert_position = next_operation_index_to_insert_ == 0 350 ? max_edit_operations_ - 1 351 : next_operation_index_to_insert_ - 1; 352 353 // If our undo record is not the last thing we inserted then we need to 354 // remove all the undo records between our insert position and the undo marker 355 // and make that our new insert position. 356 if (next_operation_index_to_undo_ != last_insert_position) { 357 if (next_operation_index_to_undo_ > last_insert_position) { 358 // Our Undo position is ahead of us, which means we need to clear out the 359 // head of the queue. 360 while (last_insert_position != 0) { 361 operation_buffer_[last_insert_position].reset(); 362 --last_insert_position; 363 } 364 operation_buffer_[0].reset(); 365 366 // Moving this will let us then clear out the end, setting the undo 367 // position to before the insert position. 368 last_insert_position = max_edit_operations_ - 1; 369 } 370 371 // Clear out the vector from undo position to our set insert position. 372 while (next_operation_index_to_undo_ != last_insert_position) { 373 operation_buffer_[last_insert_position].reset(); 374 --last_insert_position; 375 } 376 } 377 378 // We're now pointing at the next thing we want to Undo, so insert at the 379 // next position in the queue. 380 ++last_insert_position; 381 if (last_insert_position >= max_edit_operations_) 382 last_insert_position = 0; 383 384 operation_buffer_[last_insert_position] = std::move(op); 385 next_operation_index_to_insert_ = 386 (last_insert_position + 1) % max_edit_operations_; 387 next_operation_index_to_undo_ = last_insert_position; 388 } 389 390 void CFDE_TextEditEngine::ClearOperationRecords() { 391 for (auto& record : operation_buffer_) 392 record.reset(); 393 394 next_operation_index_to_undo_ = max_edit_operations_ - 1; 395 next_operation_index_to_insert_ = 0; 396 } 397 398 size_t CFDE_TextEditEngine::GetIndexBefore(size_t pos) { 399 int32_t bidi_level; 400 CFX_RectF rect; 401 // Possible |Layout| triggered by |GetCharacterInfo|. 402 std::tie(bidi_level, rect) = GetCharacterInfo(pos); 403 return FX_IsOdd(bidi_level) ? GetIndexRight(pos) : GetIndexLeft(pos); 404 } 405 406 size_t CFDE_TextEditEngine::GetIndexLeft(size_t pos) const { 407 if (pos == 0) 408 return 0; 409 --pos; 410 411 wchar_t ch = GetChar(pos); 412 while (pos != 0) { 413 // We want to be on the location just before the \r or \n 414 ch = GetChar(pos - 1); 415 if (ch != '\r' && ch != '\n') 416 break; 417 418 --pos; 419 } 420 return pos; 421 } 422 423 size_t CFDE_TextEditEngine::GetIndexRight(size_t pos) const { 424 if (pos >= text_length_) 425 return text_length_; 426 ++pos; 427 428 wchar_t ch = GetChar(pos); 429 // We want to be on the location after the \r\n. 430 while (pos < text_length_ && (ch == '\r' || ch == '\n')) { 431 ++pos; 432 ch = GetChar(pos); 433 } 434 435 return pos; 436 } 437 438 size_t CFDE_TextEditEngine::GetIndexUp(size_t pos) const { 439 size_t line_start = GetIndexAtStartOfLine(pos); 440 if (line_start == 0) 441 return pos; 442 443 // Determine how far along the line we were. 444 size_t dist = pos - line_start; 445 446 // Move to the end of the preceding line. 447 wchar_t ch; 448 do { 449 --line_start; 450 ch = GetChar(line_start); 451 } while (line_start != 0 && (ch == '\r' || ch == '\n')); 452 453 if (line_start == 0) 454 return dist; 455 456 // Get the start of the line prior to the current line. 457 size_t prior_start = GetIndexAtStartOfLine(line_start); 458 459 // Prior line is shorter then next line, and we're past the end of that line 460 // return the end of line. 461 if (prior_start + dist > line_start) 462 return GetIndexAtEndOfLine(line_start); 463 464 return prior_start + dist; 465 } 466 467 size_t CFDE_TextEditEngine::GetIndexDown(size_t pos) const { 468 size_t line_end = GetIndexAtEndOfLine(pos); 469 if (line_end == text_length_) 470 return pos; 471 472 wchar_t ch; 473 do { 474 ++line_end; 475 ch = GetChar(line_end); 476 } while (line_end < text_length_ && (ch == '\r' || ch == '\n')); 477 478 if (line_end == text_length_) 479 return line_end; 480 481 // Determine how far along the line we are. 482 size_t dist = pos - GetIndexAtStartOfLine(pos); 483 484 // Check if next line is shorter then current line. If so, return end 485 // of next line. 486 size_t next_line_end = GetIndexAtEndOfLine(line_end); 487 if (line_end + dist > next_line_end) 488 return next_line_end; 489 490 return line_end + dist; 491 } 492 493 size_t CFDE_TextEditEngine::GetIndexAtStartOfLine(size_t pos) const { 494 if (pos == 0) 495 return 0; 496 497 wchar_t ch = GetChar(pos); 498 // What to do. 499 if (ch == '\r' || ch == '\n') 500 return pos; 501 502 do { 503 // We want to be on the location just after the \r\n 504 ch = GetChar(pos - 1); 505 if (ch == '\r' || ch == '\n') 506 break; 507 508 --pos; 509 } while (pos > 0); 510 511 return pos; 512 } 513 514 size_t CFDE_TextEditEngine::GetIndexAtEndOfLine(size_t pos) const { 515 if (pos >= text_length_) 516 return text_length_; 517 518 wchar_t ch = GetChar(pos); 519 // Not quite sure which way to go here? 520 if (ch == '\r' || ch == '\n') 521 return pos; 522 523 // We want to be on the location of the first \r or \n. 524 do { 525 ++pos; 526 ch = GetChar(pos); 527 } while (pos < text_length_ && (ch != '\r' && ch != '\n')); 528 529 return pos; 530 } 531 532 void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) { 533 ClearOperationRecords(); 534 limit_horizontal_area_ = val; 535 } 536 537 void CFDE_TextEditEngine::LimitVerticalScroll(bool val) { 538 ClearOperationRecords(); 539 limit_vertical_area_ = val; 540 } 541 542 bool CFDE_TextEditEngine::CanUndo() const { 543 return operation_buffer_[next_operation_index_to_undo_] != nullptr && 544 next_operation_index_to_undo_ != next_operation_index_to_insert_; 545 } 546 547 bool CFDE_TextEditEngine::CanRedo() const { 548 size_t idx = (next_operation_index_to_undo_ + 1) % max_edit_operations_; 549 return idx != next_operation_index_to_insert_ && 550 operation_buffer_[idx] != nullptr; 551 } 552 553 bool CFDE_TextEditEngine::Redo() { 554 if (!CanRedo()) 555 return false; 556 557 next_operation_index_to_undo_ = 558 (next_operation_index_to_undo_ + 1) % max_edit_operations_; 559 operation_buffer_[next_operation_index_to_undo_]->Redo(); 560 return true; 561 } 562 563 bool CFDE_TextEditEngine::Undo() { 564 if (!CanUndo()) 565 return false; 566 567 operation_buffer_[next_operation_index_to_undo_]->Undo(); 568 next_operation_index_to_undo_ = next_operation_index_to_undo_ == 0 569 ? max_edit_operations_ - 1 570 : next_operation_index_to_undo_ - 1; 571 return true; 572 } 573 574 void CFDE_TextEditEngine::Layout() { 575 if (!is_dirty_) 576 return; 577 578 is_dirty_ = false; 579 RebuildPieces(); 580 } 581 582 CFX_RectF CFDE_TextEditEngine::GetContentsBoundingBox() { 583 // Layout if necessary. 584 Layout(); 585 return contents_bounding_box_; 586 } 587 588 void CFDE_TextEditEngine::SetAvailableWidth(size_t width) { 589 if (width == available_width_) 590 return; 591 592 ClearOperationRecords(); 593 594 available_width_ = width; 595 if (is_linewrap_enabled_) 596 text_break_.SetLineWidth(width); 597 if (is_comb_text_) 598 SetCombTextWidth(); 599 600 is_dirty_ = true; 601 } 602 603 void CFDE_TextEditEngine::SetHasCharacterLimit(bool limit) { 604 if (has_character_limit_ == limit) 605 return; 606 607 has_character_limit_ = limit; 608 if (is_comb_text_) 609 SetCombTextWidth(); 610 611 is_dirty_ = true; 612 } 613 614 void CFDE_TextEditEngine::SetCharacterLimit(size_t limit) { 615 if (character_limit_ == limit) 616 return; 617 618 ClearOperationRecords(); 619 620 character_limit_ = limit; 621 if (is_comb_text_) 622 SetCombTextWidth(); 623 624 is_dirty_ = true; 625 } 626 627 void CFDE_TextEditEngine::SetFont(RetainPtr<CFGAS_GEFont> font) { 628 if (font_ == font) 629 return; 630 631 font_ = font; 632 text_break_.SetFont(font_); 633 is_dirty_ = true; 634 } 635 636 RetainPtr<CFGAS_GEFont> CFDE_TextEditEngine::GetFont() const { 637 return font_; 638 } 639 640 void CFDE_TextEditEngine::SetFontSize(float size) { 641 if (font_size_ == size) 642 return; 643 644 font_size_ = size; 645 text_break_.SetFontSize(font_size_); 646 is_dirty_ = true; 647 } 648 649 void CFDE_TextEditEngine::SetTabWidth(float width) { 650 int32_t old_tab_width = text_break_.GetTabWidth(); 651 text_break_.SetTabWidth(width); 652 if (old_tab_width == text_break_.GetTabWidth()) 653 return; 654 655 is_dirty_ = true; 656 } 657 658 float CFDE_TextEditEngine::GetFontAscent() const { 659 return (static_cast<float>(font_->GetAscent()) * font_size_) / 1000; 660 } 661 662 void CFDE_TextEditEngine::SetAlignment(uint32_t alignment) { 663 if (alignment == character_alignment_) 664 return; 665 666 character_alignment_ = alignment; 667 text_break_.SetAlignment(alignment); 668 is_dirty_ = true; 669 } 670 671 void CFDE_TextEditEngine::SetVisibleLineCount(size_t count) { 672 if (visible_line_count_ == count) 673 return; 674 675 visible_line_count_ = std::max(static_cast<size_t>(1), count); 676 is_dirty_ = true; 677 } 678 679 void CFDE_TextEditEngine::EnableMultiLine(bool val) { 680 if (is_multiline_ == val) 681 return; 682 683 is_multiline_ = true; 684 685 uint32_t style = text_break_.GetLayoutStyles(); 686 if (is_multiline_) 687 style &= ~FX_LAYOUTSTYLE_SingleLine; 688 else 689 style |= FX_LAYOUTSTYLE_SingleLine; 690 text_break_.SetLayoutStyles(style); 691 is_dirty_ = true; 692 } 693 694 void CFDE_TextEditEngine::EnableLineWrap(bool val) { 695 if (is_linewrap_enabled_ == val) 696 return; 697 698 is_linewrap_enabled_ = val; 699 text_break_.SetLineWidth(is_linewrap_enabled_ ? available_width_ 700 : kPageWidthMax); 701 is_dirty_ = true; 702 } 703 704 void CFDE_TextEditEngine::SetCombText(bool enable) { 705 if (is_comb_text_ == enable) 706 return; 707 708 is_comb_text_ = enable; 709 710 uint32_t style = text_break_.GetLayoutStyles(); 711 if (enable) { 712 style |= FX_LAYOUTSTYLE_CombText; 713 SetCombTextWidth(); 714 } else { 715 style &= ~FX_LAYOUTSTYLE_CombText; 716 } 717 text_break_.SetLayoutStyles(style); 718 is_dirty_ = true; 719 } 720 721 void CFDE_TextEditEngine::SetCombTextWidth() { 722 size_t width = available_width_; 723 if (has_character_limit_) 724 width /= character_limit_; 725 726 text_break_.SetCombWidth(width); 727 } 728 729 void CFDE_TextEditEngine::SelectAll() { 730 if (text_length_ == 0) 731 return; 732 733 has_selection_ = true; 734 selection_.start_idx = 0; 735 selection_.count = text_length_; 736 } 737 738 void CFDE_TextEditEngine::ClearSelection() { 739 has_selection_ = false; 740 selection_.start_idx = 0; 741 selection_.count = 0; 742 } 743 744 void CFDE_TextEditEngine::SetSelection(size_t start_idx, size_t count) { 745 if (count == 0) { 746 ClearSelection(); 747 return; 748 } 749 750 if (start_idx > text_length_) 751 return; 752 if (start_idx + count > text_length_) 753 count = text_length_ - start_idx; 754 755 has_selection_ = true; 756 selection_.start_idx = start_idx; 757 selection_.count = count; 758 } 759 760 WideString CFDE_TextEditEngine::GetSelectedText() const { 761 if (!has_selection_) 762 return L""; 763 764 WideString text; 765 if (selection_.start_idx < gap_position_) { 766 // Fully on left of gap. 767 if (selection_.start_idx + selection_.count < gap_position_) { 768 text += WideStringView(content_.data() + selection_.start_idx, 769 selection_.count); 770 return text; 771 } 772 773 // Pre-gap text 774 text += WideStringView(content_.data() + selection_.start_idx, 775 gap_position_ - selection_.start_idx); 776 777 if (selection_.count - (gap_position_ - selection_.start_idx) > 0) { 778 // Post-gap text 779 text += WideStringView( 780 content_.data() + gap_position_ + gap_size_, 781 selection_.count - (gap_position_ - selection_.start_idx)); 782 } 783 784 return text; 785 } 786 787 // Fully right of gap 788 text += WideStringView(content_.data() + gap_size_ + selection_.start_idx, 789 selection_.count); 790 return text; 791 } 792 793 WideString CFDE_TextEditEngine::DeleteSelectedText( 794 RecordOperation add_operation) { 795 if (!has_selection_) 796 return L""; 797 798 return Delete(selection_.start_idx, selection_.count, add_operation); 799 } 800 801 WideString CFDE_TextEditEngine::Delete(size_t start_idx, 802 size_t length, 803 RecordOperation add_operation) { 804 if (start_idx >= text_length_) 805 return L""; 806 807 length = std::min(length, text_length_ - start_idx); 808 AdjustGap(start_idx + length, 0); 809 810 WideString ret; 811 ret += WideStringView(content_.data() + start_idx, length); 812 813 if (add_operation == RecordOperation::kInsertRecord) { 814 AddOperationRecord( 815 pdfium::MakeUnique<DeleteOperation>(this, start_idx, ret)); 816 } 817 818 WideString previous_text = GetText(); 819 820 gap_position_ = start_idx; 821 gap_size_ += length; 822 823 text_length_ -= length; 824 ClearSelection(); 825 826 if (delegate_) 827 delegate_->OnTextChanged(previous_text); 828 829 return ret; 830 } 831 832 void CFDE_TextEditEngine::ReplaceSelectedText(const WideString& rep) { 833 size_t start_idx = selection_.start_idx; 834 835 WideString txt = DeleteSelectedText(RecordOperation::kSkipRecord); 836 Insert(gap_position_, rep, RecordOperation::kSkipRecord); 837 838 AddOperationRecord( 839 pdfium::MakeUnique<ReplaceOperation>(this, start_idx, txt, rep)); 840 } 841 842 WideString CFDE_TextEditEngine::GetText() const { 843 WideString str; 844 if (gap_position_ > 0) 845 str += WideStringView(content_.data(), gap_position_); 846 if (text_length_ - gap_position_ > 0) { 847 str += WideStringView(content_.data() + gap_position_ + gap_size_, 848 text_length_ - gap_position_); 849 } 850 return str; 851 } 852 853 size_t CFDE_TextEditEngine::GetLength() const { 854 return text_length_; 855 } 856 857 wchar_t CFDE_TextEditEngine::GetChar(size_t idx) const { 858 if (idx >= text_length_) 859 return L'\0'; 860 if (password_mode_) 861 return password_alias_; 862 863 return idx < gap_position_ 864 ? content_[idx] 865 : content_[gap_position_ + gap_size_ + (idx - gap_position_)]; 866 } 867 868 size_t CFDE_TextEditEngine::GetWidthOfChar(size_t idx) { 869 // Recalculate the widths if necessary. 870 Layout(); 871 return idx < char_widths_.size() ? char_widths_[idx] : 0; 872 } 873 874 size_t CFDE_TextEditEngine::GetIndexForPoint(const CFX_PointF& point) { 875 // Recalculate the widths if necessary. 876 Layout(); 877 878 auto start_it = text_piece_info_.begin(); 879 for (; start_it < text_piece_info_.end(); ++start_it) { 880 if (start_it->rtPiece.top <= point.y && 881 point.y < start_it->rtPiece.bottom()) 882 break; 883 } 884 // We didn't find the point before getting to the end of the text, return 885 // end of text. 886 if (start_it == text_piece_info_.end()) 887 return text_length_; 888 889 auto end_it = start_it; 890 for (; end_it < text_piece_info_.end(); ++end_it) { 891 // We've moved past where the point should be and didn't find anything. 892 // Return the start of the current piece as the location. 893 if (end_it->rtPiece.bottom() <= point.y || point.y < end_it->rtPiece.top) 894 break; 895 } 896 // Make sure the end iterator is pointing to our text pieces. 897 if (end_it == text_piece_info_.end()) 898 --end_it; 899 900 size_t start_it_idx = start_it->nStart; 901 for (; start_it <= end_it; ++start_it) { 902 if (!start_it->rtPiece.Contains(point)) 903 continue; 904 905 std::vector<CFX_RectF> rects = GetCharRects(*start_it); 906 for (size_t i = 0; i < rects.size(); ++i) { 907 if (!rects[i].Contains(point)) 908 continue; 909 size_t pos = start_it->nStart + i; 910 if (pos >= text_length_) 911 return text_length_; 912 913 wchar_t wch = GetChar(pos); 914 if (wch == L'\n' || wch == L'\r') { 915 if (wch == L'\n' && pos > 0 && GetChar(pos - 1) == L'\r') 916 --pos; 917 return pos; 918 } 919 920 // TODO(dsinclair): Old code had a before flag set based on bidi? 921 return pos; 922 } 923 } 924 925 if (start_it == text_piece_info_.end()) 926 return start_it_idx; 927 if (start_it == end_it) 928 return start_it->nStart; 929 930 // We didn't find the point before going over all of the pieces, we want to 931 // return the start of the piece after the point. 932 return end_it->nStart; 933 } 934 935 std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharRects( 936 const FDE_TEXTEDITPIECE& piece) { 937 if (piece.nCount < 1) 938 return std::vector<CFX_RectF>(); 939 940 FX_TXTRUN tr; 941 tr.pEdtEngine = this; 942 tr.pIdentity = &piece; 943 tr.iLength = piece.nCount; 944 tr.pFont = font_; 945 tr.fFontSize = font_size_; 946 tr.dwStyles = text_break_.GetLayoutStyles(); 947 tr.dwCharStyles = piece.dwCharStyles; 948 tr.pRect = &piece.rtPiece; 949 return text_break_.GetCharRects(&tr, false); 950 } 951 952 std::vector<FXTEXT_CHARPOS> CFDE_TextEditEngine::GetDisplayPos( 953 const FDE_TEXTEDITPIECE& piece) { 954 if (piece.nCount < 1) 955 return std::vector<FXTEXT_CHARPOS>(); 956 957 FX_TXTRUN tr; 958 tr.pEdtEngine = this; 959 tr.pIdentity = &piece; 960 tr.iLength = piece.nCount; 961 tr.pFont = font_; 962 tr.fFontSize = font_size_; 963 tr.dwStyles = text_break_.GetLayoutStyles(); 964 tr.dwCharStyles = piece.dwCharStyles; 965 tr.pRect = &piece.rtPiece; 966 967 std::vector<FXTEXT_CHARPOS> data(text_break_.GetDisplayPos(&tr, nullptr)); 968 text_break_.GetDisplayPos(&tr, data.data()); 969 return data; 970 } 971 972 void CFDE_TextEditEngine::RebuildPieces() { 973 text_break_.EndBreak(CFX_BreakType::Paragraph); 974 text_break_.ClearBreakPieces(); 975 976 char_widths_.clear(); 977 text_piece_info_.clear(); 978 979 // Must have a font set in order to break the text. 980 if (text_length_ == 0 || !font_) 981 return; 982 983 bool initialized_bounding_box = false; 984 contents_bounding_box_ = CFX_RectF(); 985 size_t current_piece_start = 0; 986 float current_line_start = 0; 987 988 auto iter = pdfium::MakeUnique<CFDE_TextEditEngine::Iterator>(this); 989 while (!iter->IsEOF(false)) { 990 iter->Next(false); 991 992 CFX_BreakType break_status = text_break_.AppendChar( 993 password_mode_ ? password_alias_ : iter->GetChar()); 994 if (iter->IsEOF(false) && CFX_BreakTypeNoneOrPiece(break_status)) 995 break_status = text_break_.EndBreak(CFX_BreakType::Paragraph); 996 997 if (CFX_BreakTypeNoneOrPiece(break_status)) 998 continue; 999 int32_t piece_count = text_break_.CountBreakPieces(); 1000 for (int32_t i = 0; i < piece_count; ++i) { 1001 const CFX_BreakPiece* piece = text_break_.GetBreakPieceUnstable(i); 1002 1003 FDE_TEXTEDITPIECE txtEdtPiece; 1004 memset(&txtEdtPiece, 0, sizeof(FDE_TEXTEDITPIECE)); 1005 1006 txtEdtPiece.nBidiLevel = piece->m_iBidiLevel; 1007 txtEdtPiece.nCount = piece->GetLength(); 1008 txtEdtPiece.nStart = current_piece_start; 1009 txtEdtPiece.dwCharStyles = piece->m_dwCharStyles; 1010 if (FX_IsOdd(piece->m_iBidiLevel)) 1011 txtEdtPiece.dwCharStyles |= FX_TXTCHARSTYLE_OddBidiLevel; 1012 1013 txtEdtPiece.rtPiece.left = piece->m_iStartPos / 20000.0f; 1014 txtEdtPiece.rtPiece.top = current_line_start; 1015 txtEdtPiece.rtPiece.width = piece->m_iWidth / 20000.0f; 1016 txtEdtPiece.rtPiece.height = line_spacing_; 1017 text_piece_info_.push_back(txtEdtPiece); 1018 1019 if (initialized_bounding_box) { 1020 contents_bounding_box_.Union(txtEdtPiece.rtPiece); 1021 } else { 1022 contents_bounding_box_ = txtEdtPiece.rtPiece; 1023 initialized_bounding_box = true; 1024 } 1025 1026 current_piece_start += txtEdtPiece.nCount; 1027 for (int32_t k = 0; k < txtEdtPiece.nCount; ++k) 1028 char_widths_.push_back(piece->GetChar(k)->m_iCharWidth); 1029 } 1030 1031 current_line_start += line_spacing_; 1032 text_break_.ClearBreakPieces(); 1033 } 1034 1035 float delta = 0.0; 1036 bool bounds_smaller = contents_bounding_box_.width < available_width_; 1037 if (IsAlignedRight() && bounds_smaller) { 1038 delta = available_width_ - contents_bounding_box_.width; 1039 } else if (IsAlignedCenter() && bounds_smaller) { 1040 // TODO(dsinclair): Old code used CombText here and set the space to 1041 // something unrelated to the available width .... Figure out if this is 1042 // needed and what it should do. 1043 // if (is_comb_text_) { 1044 // } else { 1045 delta = (available_width_ - contents_bounding_box_.width) / 2.0f; 1046 // } 1047 } 1048 1049 if (delta != 0.0) { 1050 float offset = delta - contents_bounding_box_.left; 1051 for (auto& info : text_piece_info_) 1052 info.rtPiece.Offset(offset, 0.0f); 1053 contents_bounding_box_.Offset(offset, 0.0f); 1054 } 1055 1056 // Shrink the last piece down to the font_size. 1057 contents_bounding_box_.height -= line_spacing_ - font_size_; 1058 text_piece_info_.back().rtPiece.height = font_size_; 1059 } 1060 1061 std::pair<int32_t, CFX_RectF> CFDE_TextEditEngine::GetCharacterInfo( 1062 int32_t start_idx) { 1063 ASSERT(start_idx >= 0); 1064 ASSERT(static_cast<size_t>(start_idx) <= text_length_); 1065 1066 // Make sure the current available data is fresh. 1067 Layout(); 1068 1069 auto it = text_piece_info_.begin(); 1070 for (; it != text_piece_info_.end(); ++it) { 1071 if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount) 1072 break; 1073 } 1074 if (it == text_piece_info_.end()) { 1075 NOTREACHED(); 1076 return {0, CFX_RectF()}; 1077 } 1078 1079 return {it->nBidiLevel, GetCharRects(*it)[start_idx - it->nStart]}; 1080 } 1081 1082 std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharacterRectsInRange( 1083 int32_t start_idx, 1084 int32_t count) { 1085 // Make sure the current available data is fresh. 1086 Layout(); 1087 1088 auto it = text_piece_info_.begin(); 1089 for (; it != text_piece_info_.end(); ++it) { 1090 if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount) 1091 break; 1092 } 1093 if (it == text_piece_info_.end()) 1094 return std::vector<CFX_RectF>(); 1095 1096 int32_t end_idx = start_idx + count - 1; 1097 std::vector<CFX_RectF> rects; 1098 while (it != text_piece_info_.end()) { 1099 // If we end inside the current piece, extract what we need and we're done. 1100 if (it->nStart <= end_idx && end_idx < it->nStart + it->nCount) { 1101 std::vector<CFX_RectF> arr = GetCharRects(*it); 1102 CFX_RectF piece = arr[0]; 1103 piece.Union(arr[end_idx - it->nStart]); 1104 rects.push_back(piece); 1105 break; 1106 } 1107 rects.push_back(it->rtPiece); 1108 ++it; 1109 } 1110 1111 return rects; 1112 } 1113 1114 std::pair<size_t, size_t> CFDE_TextEditEngine::BoundsForWordAt( 1115 size_t idx) const { 1116 if (idx > text_length_) 1117 return {0, 0}; 1118 1119 CFDE_TextEditEngine::Iterator iter(this); 1120 iter.SetAt(idx); 1121 1122 size_t start_idx = iter.FindNextBreakPos(true); 1123 size_t end_idx = iter.FindNextBreakPos(false); 1124 return {start_idx, end_idx - start_idx + 1}; 1125 } 1126 1127 CFDE_TextEditEngine::Iterator::Iterator(const CFDE_TextEditEngine* engine) 1128 : engine_(engine), current_position_(-1) {} 1129 1130 CFDE_TextEditEngine::Iterator::~Iterator() {} 1131 1132 void CFDE_TextEditEngine::Iterator::Next(bool bPrev) { 1133 if (bPrev && current_position_ == -1) 1134 return; 1135 if (!bPrev && current_position_ > -1 && 1136 static_cast<size_t>(current_position_) == engine_->GetLength()) { 1137 return; 1138 } 1139 1140 if (bPrev) 1141 --current_position_; 1142 else 1143 ++current_position_; 1144 } 1145 1146 wchar_t CFDE_TextEditEngine::Iterator::GetChar() const { 1147 return engine_->GetChar(current_position_); 1148 } 1149 1150 void CFDE_TextEditEngine::Iterator::SetAt(size_t nIndex) { 1151 if (static_cast<size_t>(nIndex) >= engine_->GetLength()) 1152 current_position_ = engine_->GetLength(); 1153 else 1154 current_position_ = nIndex; 1155 } 1156 1157 bool CFDE_TextEditEngine::Iterator::IsEOF(bool bPrev) const { 1158 return bPrev ? current_position_ == -1 1159 : current_position_ > -1 && 1160 static_cast<size_t>(current_position_) == 1161 engine_->GetLength(); 1162 } 1163 1164 size_t CFDE_TextEditEngine::Iterator::FindNextBreakPos(bool bPrev) { 1165 if (IsEOF(bPrev)) 1166 return current_position_ > -1 ? current_position_ : 0; 1167 1168 WordBreakProperty ePreType = WordBreakProperty::kNone; 1169 if (!IsEOF(!bPrev)) { 1170 Next(!bPrev); 1171 ePreType = GetWordBreakProperty(GetChar()); 1172 Next(bPrev); 1173 } 1174 1175 WordBreakProperty eCurType = GetWordBreakProperty(GetChar()); 1176 bool bFirst = true; 1177 while (!IsEOF(bPrev)) { 1178 Next(bPrev); 1179 1180 WordBreakProperty eNextType = GetWordBreakProperty(GetChar()); 1181 bool wBreak = CheckStateChangeForWordBreak(eCurType, eNextType); 1182 if (wBreak) { 1183 if (IsEOF(bPrev)) { 1184 Next(!bPrev); 1185 break; 1186 } 1187 if (bFirst) { 1188 int32_t nFlags = GetBreakFlagsFor(eCurType, eNextType); 1189 if (nFlags > 0) { 1190 if (BreakFlagsChanged(nFlags, ePreType)) { 1191 Next(!bPrev); 1192 break; 1193 } 1194 Next(bPrev); 1195 wBreak = false; 1196 } 1197 } 1198 if (wBreak) { 1199 int32_t nFlags = GetBreakFlagsFor(eNextType, eCurType); 1200 if (nFlags <= 0) { 1201 Next(!bPrev); 1202 break; 1203 } 1204 1205 Next(bPrev); 1206 eNextType = GetWordBreakProperty(GetChar()); 1207 if (BreakFlagsChanged(nFlags, eNextType)) { 1208 Next(!bPrev); 1209 Next(!bPrev); 1210 break; 1211 } 1212 } 1213 } 1214 eCurType = eNextType; 1215 bFirst = false; 1216 } 1217 return current_position_ > -1 ? current_position_ : 0; 1218 } 1219