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