• 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 
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:
InsertOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & added_text)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 
~InsertOperation()29   ~InsertOperation() override {}
30 
Redo() const31   void Redo() const override {
32     engine_->Insert(start_idx_, added_text_,
33                     CFDE_TextEditEngine::RecordOperation::kSkipRecord);
34   }
35 
Undo() const36   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:
DeleteOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & removed_text)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 
~DeleteOperation()54   ~DeleteOperation() override {}
55 
Redo() const56   void Redo() const override {
57     engine_->Delete(start_idx_, removed_text_.GetLength(),
58                     CFDE_TextEditEngine::RecordOperation::kSkipRecord);
59   }
60 
Undo() const61   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:
ReplaceOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & removed_text,const WideString & added_text)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 
~ReplaceOperation()81   ~ReplaceOperation() override {}
82 
Redo() const83   void Redo() const override {
84     delete_op_.Redo();
85     insert_op_.Redo();
86   }
87 
Undo() const88   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 
CheckStateChangeForWordBreak(WordBreakProperty from,WordBreakProperty to)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 
GetWordBreakProperty(wchar_t wcCodePoint)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 
GetBreakFlagsFor(WordBreakProperty current,WordBreakProperty next)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 
BreakFlagsChanged(int flags,WordBreakProperty previous)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 
CFDE_TextEditEngine()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 
~CFDE_TextEditEngine()169 CFDE_TextEditEngine::~CFDE_TextEditEngine() {}
170 
Clear()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 
SetMaxEditOperationsForTesting(size_t max)183 void CFDE_TextEditEngine::SetMaxEditOperationsForTesting(size_t max) {
184   max_edit_operations_ = max;
185   operation_buffer_.resize(max);
186 
187   ClearOperationRecords();
188 }
189 
AdjustGap(size_t idx,size_t length)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 
CountCharsExceedingSize(const WideString & text,size_t num_to_check)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 
Insert(size_t idx,const WideString & text,RecordOperation add_operation)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 
AddOperationRecord(std::unique_ptr<Operation> op)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 
ClearOperationRecords()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 
GetIndexBefore(size_t pos)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 
GetIndexLeft(size_t pos) const406 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 
GetIndexRight(size_t pos) const423 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 
GetIndexUp(size_t pos) const438 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 
GetIndexDown(size_t pos) const467 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 
GetIndexAtStartOfLine(size_t pos) const493 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 
GetIndexAtEndOfLine(size_t pos) const514 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 
LimitHorizontalScroll(bool val)532 void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) {
533   ClearOperationRecords();
534   limit_horizontal_area_ = val;
535 }
536 
LimitVerticalScroll(bool val)537 void CFDE_TextEditEngine::LimitVerticalScroll(bool val) {
538   ClearOperationRecords();
539   limit_vertical_area_ = val;
540 }
541 
CanUndo() const542 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 
CanRedo() const547 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 
Redo()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 
Undo()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 
Layout()574 void CFDE_TextEditEngine::Layout() {
575   if (!is_dirty_)
576     return;
577 
578   is_dirty_ = false;
579   RebuildPieces();
580 }
581 
GetContentsBoundingBox()582 CFX_RectF CFDE_TextEditEngine::GetContentsBoundingBox() {
583   // Layout if necessary.
584   Layout();
585   return contents_bounding_box_;
586 }
587 
SetAvailableWidth(size_t width)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 
SetHasCharacterLimit(bool limit)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 
SetCharacterLimit(size_t limit)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 
SetFont(RetainPtr<CFGAS_GEFont> font)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 
GetFont() const636 RetainPtr<CFGAS_GEFont> CFDE_TextEditEngine::GetFont() const {
637   return font_;
638 }
639 
SetFontSize(float size)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 
SetTabWidth(float width)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 
GetFontAscent() const658 float CFDE_TextEditEngine::GetFontAscent() const {
659   return (static_cast<float>(font_->GetAscent()) * font_size_) / 1000;
660 }
661 
SetAlignment(uint32_t alignment)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 
SetVisibleLineCount(size_t count)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 
EnableMultiLine(bool val)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 
EnableLineWrap(bool val)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 
SetCombText(bool enable)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 
SetCombTextWidth()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 
SelectAll()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 
ClearSelection()738 void CFDE_TextEditEngine::ClearSelection() {
739   has_selection_ = false;
740   selection_.start_idx = 0;
741   selection_.count = 0;
742 }
743 
SetSelection(size_t start_idx,size_t count)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 
GetSelectedText() const760 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 
DeleteSelectedText(RecordOperation add_operation)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 
Delete(size_t start_idx,size_t length,RecordOperation add_operation)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 
ReplaceSelectedText(const WideString & rep)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 
GetText() const842 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 
GetLength() const853 size_t CFDE_TextEditEngine::GetLength() const {
854   return text_length_;
855 }
856 
GetChar(size_t idx) const857 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 
GetWidthOfChar(size_t idx)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 
GetIndexForPoint(const CFX_PointF & point)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 
GetCharRects(const FDE_TEXTEDITPIECE & piece)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 
GetDisplayPos(const FDE_TEXTEDITPIECE & piece)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 
RebuildPieces()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 
GetCharacterInfo(int32_t start_idx)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 
GetCharacterRectsInRange(int32_t start_idx,int32_t count)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 
BoundsForWordAt(size_t idx) const1114 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 
Iterator(const CFDE_TextEditEngine * engine)1127 CFDE_TextEditEngine::Iterator::Iterator(const CFDE_TextEditEngine* engine)
1128     : engine_(engine), current_position_(-1) {}
1129 
~Iterator()1130 CFDE_TextEditEngine::Iterator::~Iterator() {}
1131 
Next(bool bPrev)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 
GetChar() const1146 wchar_t CFDE_TextEditEngine::Iterator::GetChar() const {
1147   return engine_->GetChar(current_position_);
1148 }
1149 
SetAt(size_t nIndex)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 
IsEOF(bool bPrev) const1157 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 
FindNextBreakPos(bool bPrev)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