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