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