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