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