Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cfde_texteditengine.cpp
Go to the documentation of this file.
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
22namespace {
23
24class InsertOperation final : public CFDE_TextEditEngine::Operation {
25 public:
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
33 void Redo() const override {
34 engine_->Insert(start_idx_, added_text_,
35 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
36 }
37
38 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
49class DeleteOperation final : public CFDE_TextEditEngine::Operation {
50 public:
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
58 void Redo() const override {
59 engine_->Delete(start_idx_, removed_text_.GetLength(),
60 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
61 }
62
63 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
74class ReplaceOperation final : public CFDE_TextEditEngine::Operation {
75 public:
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
85 void Redo() const override {
86 delete_op_.Redo();
87 insert_op_.Redo();
88 }
89
90 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
100int 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
116bool BreakFlagsChanged(int flags, WordBreakProperty previous) {
117 return (flags != 1 || previous != WordBreakProperty::kALetter) &&
118 (flags != 2 || previous != WordBreakProperty::kNumeric);
119}
120
121} // namespace
122
123CFDE_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
132CFDE_TextEditEngine::~CFDE_TextEditEngine() = default;
133
134void 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
144}
145
146void CFDE_TextEditEngine::SetMaxEditOperationsForTesting(size_t max) {
147 max_edit_operations_ = max;
148 operation_buffer_.resize(max);
149
151}
152
153void 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
181size_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
225void 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)
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.
339
340 if (delegate_) {
341 if (exceeded_limit)
342 delegate_->NotifyTextFull();
343
344 delegate_->OnTextChanged();
345 }
346}
347
348void 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
390void 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
398size_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
414size_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
429size_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
458size_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
484size_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
505size_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
523void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) {
525 limit_horizontal_area_ = val;
526}
527
528void CFDE_TextEditEngine::LimitVerticalScroll(bool val) {
530 limit_vertical_area_ = val;
531}
532
533bool 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
538bool 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
544bool 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
554bool 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
565void CFDE_TextEditEngine::Layout() {
566 if (!is_dirty_)
567 return;
568
569 is_dirty_ = false;
570 RebuildPieces();
571}
572
573CFX_RectF CFDE_TextEditEngine::GetContentsBoundingBox() {
574 // Layout if necessary.
575 Layout();
576 return contents_bounding_box_;
577}
578
579void CFDE_TextEditEngine::SetAvailableWidth(size_t width) {
580 if (width == available_width_)
581 return;
582
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
594void 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
606void CFDE_TextEditEngine::SetCharacterLimit(size_t limit) {
607 if (character_limit_ == limit)
608 return;
609
611
612 character_limit_ = std::max(limit, text_length_);
613 if (is_comb_text_)
614 SetCombTextWidth();
615
616 is_dirty_ = true;
617}
618
619void 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
628RetainPtr<CFGAS_GEFont> CFDE_TextEditEngine::GetFont() const {
629 return font_;
630}
631
632void 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
641void 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
650void 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
659void 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
667void 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_)
675 else
677
678 text_break_.SetLayoutStyles(style);
679 is_dirty_ = true;
680}
681
682void 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
692void 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) {
701 SetCombTextWidth();
702 } else {
704 }
705 text_break_.SetLayoutStyles(style);
706 is_dirty_ = true;
707}
708
709void 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
717void 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
726void CFDE_TextEditEngine::ClearSelection() {
727 has_selection_ = false;
728 selection_.start_idx = 0;
729 selection_.count = 0;
730}
731
732void CFDE_TextEditEngine::SetSelection(size_t start_idx, size_t count) {
733 if (count == 0) {
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
748WideString 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
781WideString 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
789WideString 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;
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
846void 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;
868 Insert(gap_position_, rep, RecordOperation::kSkipRecord);
869
870 AddOperationRecord(
871 std::make_unique<ReplaceOperation>(this, start_idx, txt, rep));
872}
873
874WideString 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
885size_t CFDE_TextEditEngine::GetLength() const {
886 return text_length_;
887}
888
889wchar_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
900int32_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
906size_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
1001std::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();
1014 tr.pRect = &piece.rtPiece;
1015 return text_break_.GetCharRects(tr);
1016}
1017
1018std::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();
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
1038void 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.
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()))
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
1120std::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
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
1169std::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
1182CFDE_TextEditEngine::Iterator::Iterator(const CFDE_TextEditEngine* engine)
1183 : engine_(engine) {}
1184
1185CFDE_TextEditEngine::Iterator::~Iterator() = default;
1186
1187void 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
1201wchar_t CFDE_TextEditEngine::Iterator::GetChar() const {
1202 return engine_->GetChar(current_position_);
1203}
1204
1205void 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
1210bool 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
1217size_t CFDE_TextEditEngine::Iterator::FindNextBreakPos(bool bPrev) {
1218 if (IsEOF(bPrev))
1219 return current_position_ > -1 ? current_position_ : 0;
1220
1222 if (!IsEOF(!bPrev)) {
1223 Next(!bPrev);
1225 Next(bPrev);
1226 }
1227
1229 bool bFirst = true;
1230 while (!IsEOF(bPrev)) {
1231 Next(bPrev);
1232
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);
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}
WordBreakProperty FX_GetWordBreakProperty(wchar_t wcCodePoint)
bool FX_CheckStateChangeForWordBreak(WordBreakProperty from, WordBreakProperty to)
WordBreakProperty
#define FX_TXTCHARSTYLE_OddBidiLevel
bool CFX_BreakTypeNoneOrPiece(CFGAS_Char::BreakType type)
Iterator(const CFDE_TextEditEngine *engine)
void SetAlignment(uint32_t alignment)
void SetCombText(bool enable)
size_t GetIndexAtStartOfLine(size_t pos) const
void Insert(size_t idx, const WideString &text, RecordOperation add_operation=RecordOperation::kInsertRecord)
std::pair< size_t, size_t > BoundsForWordAt(size_t idx) const
void SetAvailableWidth(size_t width)
WideString GetSelectedText() const
WideString GetText() const
size_t GetIndexAtEndOfLine(size_t pos) const
size_t GetIndexLeft(size_t pos) const
size_t GetIndexRight(size_t pos) const
WideString Delete(size_t start_idx, size_t length, RecordOperation add_operation=RecordOperation::kInsertRecord)
~CFDE_TextEditEngine() override
std::pair< int32_t, CFX_RectF > GetCharacterInfo(int32_t start_idx)
void SetVisibleLineCount(size_t lines)
size_t GetIndexDown(size_t pos) const
void LimitHorizontalScroll(bool val)
size_t GetIndexForPoint(const CFX_PointF &point)
void SetSelection(size_t start_idx, size_t count)
void SetFont(RetainPtr< CFGAS_GEFont > font)
void LimitVerticalScroll(bool val)
void SetHasCharacterLimit(bool limit)
std::vector< CFX_RectF > GetCharacterRectsInRange(int32_t start_idx, int32_t count)
int32_t GetWidthOfChar(size_t idx) override
bool CanGenerateCharacterInfo() const
std::vector< TextCharPos > GetDisplayPos(const FDE_TEXTEDITPIECE &info)
void SetCharacterLimit(size_t limit)
RetainPtr< CFGAS_GEFont > GetFont() const
wchar_t GetChar(size_t idx) const override
size_t GetIndexUp(size_t pos) const
void SetTabWidth(float width)
void SetMaxEditOperationsForTesting(size_t max)
void ReplaceSelectedText(const WideString &str)
WideString DeleteSelectedText(RecordOperation add_operation=RecordOperation::kInsertRecord)
int32_t GetCharCount() const
int32_t GetStartPos() const
uint32_t GetCharStyles() const
int32_t GetBidiLevel() const
int32_t GetWidth() const
void Offset(float dx, float dy)
constexpr CFX_RectF()=default
CFX_RectF & operator=(const CFX_RectF &other)=default
void Union(const CFX_RectF &rt)
WideString & operator+=(const WideString &str)
WideString & operator=(WideString &&that) noexcept
bool IsEmpty() const
Definition widestring.h:118
#define FX_IsOdd(a)
#define CHECK(cvref)
bool single_line_
Definition cfde_data.h:29
bool line_wrap_
Definition cfde_data.h:30