Home | History | Annotate | Download | only in fde
      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