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
12 #include "xfa/fde/cfde_textout.h"
13 #include "xfa/fde/cfde_wordbreak_data.h"
14 #include "xfa/fgas/font/cfgas_gefont.h"
15
16 namespace {
17
18 constexpr size_t kMaxEditOperations = 128;
19 constexpr size_t kGapSize = 128;
20 constexpr size_t kPageWidthMax = 0xffff;
21
22 class InsertOperation : public CFDE_TextEditEngine::Operation {
23 public:
InsertOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & added_text)24 InsertOperation(CFDE_TextEditEngine* engine,
25 size_t start_idx,
26 const WideString& added_text)
27 : engine_(engine), start_idx_(start_idx), added_text_(added_text) {}
28
~InsertOperation()29 ~InsertOperation() override {}
30
Redo() const31 void Redo() const override {
32 engine_->Insert(start_idx_, added_text_,
33 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
34 }
35
Undo() const36 void Undo() const override {
37 engine_->Delete(start_idx_, added_text_.GetLength(),
38 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
39 }
40
41 private:
42 UnownedPtr<CFDE_TextEditEngine> engine_;
43 size_t start_idx_;
44 WideString added_text_;
45 };
46
47 class DeleteOperation : public CFDE_TextEditEngine::Operation {
48 public:
DeleteOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & removed_text)49 DeleteOperation(CFDE_TextEditEngine* engine,
50 size_t start_idx,
51 const WideString& removed_text)
52 : engine_(engine), start_idx_(start_idx), removed_text_(removed_text) {}
53
~DeleteOperation()54 ~DeleteOperation() override {}
55
Redo() const56 void Redo() const override {
57 engine_->Delete(start_idx_, removed_text_.GetLength(),
58 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
59 }
60
Undo() const61 void Undo() const override {
62 engine_->Insert(start_idx_, removed_text_,
63 CFDE_TextEditEngine::RecordOperation::kSkipRecord);
64 }
65
66 private:
67 UnownedPtr<CFDE_TextEditEngine> engine_;
68 size_t start_idx_;
69 WideString removed_text_;
70 };
71
72 class ReplaceOperation : public CFDE_TextEditEngine::Operation {
73 public:
ReplaceOperation(CFDE_TextEditEngine * engine,size_t start_idx,const WideString & removed_text,const WideString & added_text)74 ReplaceOperation(CFDE_TextEditEngine* engine,
75 size_t start_idx,
76 const WideString& removed_text,
77 const WideString& added_text)
78 : insert_op_(engine, start_idx, added_text),
79 delete_op_(engine, start_idx, removed_text) {}
80
~ReplaceOperation()81 ~ReplaceOperation() override {}
82
Redo() const83 void Redo() const override {
84 delete_op_.Redo();
85 insert_op_.Redo();
86 }
87
Undo() const88 void Undo() const override {
89 insert_op_.Undo();
90 delete_op_.Undo();
91 }
92
93 private:
94 InsertOperation insert_op_;
95 DeleteOperation delete_op_;
96 };
97
CheckStateChangeForWordBreak(WordBreakProperty from,WordBreakProperty to)98 bool CheckStateChangeForWordBreak(WordBreakProperty from,
99 WordBreakProperty to) {
100 ASSERT(static_cast<int>(from) < 13);
101
102 return !!(gs_FX_WordBreak_Table[static_cast<int>(from)] &
103 static_cast<uint16_t>(1 << static_cast<int>(to)));
104 }
105
GetWordBreakProperty(wchar_t wcCodePoint)106 WordBreakProperty GetWordBreakProperty(wchar_t wcCodePoint) {
107 uint8_t dwProperty = gs_FX_WordBreak_CodePointProperties[wcCodePoint >> 1];
108 return static_cast<WordBreakProperty>((wcCodePoint & 1) ? (dwProperty & 0x0F)
109 : (dwProperty >> 4));
110 }
111
GetBreakFlagsFor(WordBreakProperty current,WordBreakProperty next)112 int GetBreakFlagsFor(WordBreakProperty current, WordBreakProperty next) {
113 if (current == WordBreakProperty::kMidLetter) {
114 if (next == WordBreakProperty::kALetter)
115 return 1;
116 } else if (current == WordBreakProperty::kMidNum) {
117 if (next == WordBreakProperty::kNumeric)
118 return 2;
119 } else if (current == WordBreakProperty::kMidNumLet) {
120 if (next == WordBreakProperty::kALetter)
121 return 1;
122 if (next == WordBreakProperty::kNumeric)
123 return 2;
124 }
125 return 0;
126 }
127
BreakFlagsChanged(int flags,WordBreakProperty previous)128 bool BreakFlagsChanged(int flags, WordBreakProperty previous) {
129 return (flags != 1 || previous != WordBreakProperty::kALetter) &&
130 (flags != 2 || previous != WordBreakProperty::kNumeric);
131 }
132
133 } // namespace
134
CFDE_TextEditEngine()135 CFDE_TextEditEngine::CFDE_TextEditEngine()
136 : font_color_(0xff000000),
137 font_size_(10.0f),
138 line_spacing_(10.0f),
139 text_length_(0),
140 gap_position_(0),
141 gap_size_(kGapSize),
142 available_width_(kPageWidthMax),
143 character_limit_(std::numeric_limits<size_t>::max()),
144 visible_line_count_(1),
145 next_operation_index_to_undo_(kMaxEditOperations - 1),
146 next_operation_index_to_insert_(0),
147 max_edit_operations_(kMaxEditOperations),
148 character_alignment_(CFX_TxtLineAlignment_Left),
149 has_character_limit_(false),
150 is_comb_text_(false),
151 is_dirty_(false),
152 validation_enabled_(false),
153 is_multiline_(false),
154 is_linewrap_enabled_(false),
155 limit_horizontal_area_(false),
156 limit_vertical_area_(false),
157 password_mode_(false),
158 password_alias_(L'*'),
159 has_selection_(false),
160 selection_({0, 0}) {
161 content_.resize(gap_size_);
162 operation_buffer_.resize(max_edit_operations_);
163
164 text_break_.SetFontSize(font_size_);
165 text_break_.SetLineBreakTolerance(2.0f);
166 text_break_.SetTabWidth(36);
167 }
168
~CFDE_TextEditEngine()169 CFDE_TextEditEngine::~CFDE_TextEditEngine() {}
170
Clear()171 void CFDE_TextEditEngine::Clear() {
172 text_length_ = 0;
173 gap_position_ = 0;
174 gap_size_ = kGapSize;
175
176 content_.clear();
177 content_.resize(gap_size_);
178
179 ClearSelection();
180 ClearOperationRecords();
181 }
182
SetMaxEditOperationsForTesting(size_t max)183 void CFDE_TextEditEngine::SetMaxEditOperationsForTesting(size_t max) {
184 max_edit_operations_ = max;
185 operation_buffer_.resize(max);
186
187 ClearOperationRecords();
188 }
189
AdjustGap(size_t idx,size_t length)190 void CFDE_TextEditEngine::AdjustGap(size_t idx, size_t length) {
191 static const size_t char_size = sizeof(WideString::CharType);
192
193 // Move the gap, if necessary.
194 if (idx < gap_position_) {
195 memmove(content_.data() + idx + gap_size_, content_.data() + idx,
196 (gap_position_ - idx) * char_size);
197 gap_position_ = idx;
198 } else if (idx > gap_position_) {
199 memmove(content_.data() + gap_position_,
200 content_.data() + gap_position_ + gap_size_,
201 (idx - gap_position_) * char_size);
202 gap_position_ = idx;
203 }
204
205 // If the gap is too small, make it bigger.
206 if (length >= gap_size_) {
207 size_t new_gap_size = length + kGapSize;
208 content_.resize(text_length_ + new_gap_size);
209
210 memmove(content_.data() + gap_position_ + new_gap_size,
211 content_.data() + gap_position_ + gap_size_,
212 (text_length_ - gap_position_) * char_size);
213
214 gap_size_ = new_gap_size;
215 }
216 }
217
CountCharsExceedingSize(const WideString & text,size_t num_to_check)218 size_t CFDE_TextEditEngine::CountCharsExceedingSize(const WideString& text,
219 size_t num_to_check) {
220 if (!limit_horizontal_area_ && !limit_vertical_area_)
221 return 0;
222
223 auto text_out = pdfium::MakeUnique<CFDE_TextOut>();
224 text_out->SetLineSpace(line_spacing_);
225 text_out->SetFont(font_);
226 text_out->SetFontSize(font_size_);
227
228 FDE_TextStyle style;
229 style.single_line_ = !is_multiline_;
230
231 CFX_RectF text_rect;
232 if (is_linewrap_enabled_) {
233 style.line_wrap_ = true;
234 text_rect.width = available_width_;
235 } else {
236 text_rect.width = kPageWidthMax;
237 }
238 text_out->SetStyles(style);
239
240 size_t length = text.GetLength();
241 WideStringView temp(text.c_str(), length);
242
243 float vertical_height = line_spacing_ * visible_line_count_;
244 size_t chars_exceeding_size = 0;
245 // TODO(dsinclair): Can this get changed to a binary search?
246 for (size_t i = 0; i < num_to_check; i++) {
247 // This does a lot of string copying ....
248 // TODO(dsinclair): make CalcLogicSize take a WideStringC instead.
249 text_out->CalcLogicSize(WideString(temp), text_rect);
250
251 if (limit_horizontal_area_ && text_rect.width <= available_width_)
252 break;
253 if (limit_vertical_area_ && text_rect.height <= vertical_height)
254 break;
255
256 --length;
257 temp = temp.Mid(0, length);
258 ++chars_exceeding_size;
259 }
260
261 return chars_exceeding_size;
262 }
263
Insert(size_t idx,const WideString & text,RecordOperation add_operation)264 void CFDE_TextEditEngine::Insert(size_t idx,
265 const WideString& text,
266 RecordOperation add_operation) {
267 if (idx > text_length_)
268 idx = text_length_;
269
270 size_t length = text.GetLength();
271 if (length == 0)
272 return;
273
274 // If we're going to be too big we insert what we can and notify the
275 // delegate we've filled the text after the insert is done.
276 bool exceeded_limit = false;
277 if (has_character_limit_ && text_length_ + length > character_limit_) {
278 exceeded_limit = true;
279 length = character_limit_ - text_length_;
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 pdfium::MakeUnique<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 static const size_t char_size = sizeof(WideString::CharType);
330 memcpy(content_.data() + gap_position_, text.c_str(), length * char_size);
331 gap_position_ += length;
332 gap_size_ -= length;
333 text_length_ += length;
334
335 is_dirty_ = true;
336
337 // Inserting text resets the selection.
338 ClearSelection();
339
340 if (delegate_) {
341 if (exceeded_limit)
342 delegate_->NotifyTextFull();
343
344 delegate_->OnTextChanged(previous_text);
345 }
346 }
347
AddOperationRecord(std::unique_ptr<Operation> op)348 void CFDE_TextEditEngine::AddOperationRecord(std::unique_ptr<Operation> op) {
349 size_t last_insert_position = next_operation_index_to_insert_ == 0
350 ? max_edit_operations_ - 1
351 : next_operation_index_to_insert_ - 1;
352
353 // If our undo record is not the last thing we inserted then we need to
354 // remove all the undo records between our insert position and the undo marker
355 // and make that our new insert position.
356 if (next_operation_index_to_undo_ != last_insert_position) {
357 if (next_operation_index_to_undo_ > last_insert_position) {
358 // Our Undo position is ahead of us, which means we need to clear out the
359 // head of the queue.
360 while (last_insert_position != 0) {
361 operation_buffer_[last_insert_position].reset();
362 --last_insert_position;
363 }
364 operation_buffer_[0].reset();
365
366 // Moving this will let us then clear out the end, setting the undo
367 // position to before the insert position.
368 last_insert_position = max_edit_operations_ - 1;
369 }
370
371 // Clear out the vector from undo position to our set insert position.
372 while (next_operation_index_to_undo_ != last_insert_position) {
373 operation_buffer_[last_insert_position].reset();
374 --last_insert_position;
375 }
376 }
377
378 // We're now pointing at the next thing we want to Undo, so insert at the
379 // next position in the queue.
380 ++last_insert_position;
381 if (last_insert_position >= max_edit_operations_)
382 last_insert_position = 0;
383
384 operation_buffer_[last_insert_position] = std::move(op);
385 next_operation_index_to_insert_ =
386 (last_insert_position + 1) % max_edit_operations_;
387 next_operation_index_to_undo_ = last_insert_position;
388 }
389
ClearOperationRecords()390 void CFDE_TextEditEngine::ClearOperationRecords() {
391 for (auto& record : operation_buffer_)
392 record.reset();
393
394 next_operation_index_to_undo_ = max_edit_operations_ - 1;
395 next_operation_index_to_insert_ = 0;
396 }
397
GetIndexBefore(size_t pos)398 size_t CFDE_TextEditEngine::GetIndexBefore(size_t pos) {
399 int32_t bidi_level;
400 CFX_RectF rect;
401 // Possible |Layout| triggered by |GetCharacterInfo|.
402 std::tie(bidi_level, rect) = GetCharacterInfo(pos);
403 return FX_IsOdd(bidi_level) ? GetIndexRight(pos) : GetIndexLeft(pos);
404 }
405
GetIndexLeft(size_t pos) const406 size_t CFDE_TextEditEngine::GetIndexLeft(size_t pos) const {
407 if (pos == 0)
408 return 0;
409 --pos;
410
411 wchar_t ch = GetChar(pos);
412 while (pos != 0) {
413 // We want to be on the location just before the \r or \n
414 ch = GetChar(pos - 1);
415 if (ch != '\r' && ch != '\n')
416 break;
417
418 --pos;
419 }
420 return pos;
421 }
422
GetIndexRight(size_t pos) const423 size_t CFDE_TextEditEngine::GetIndexRight(size_t pos) const {
424 if (pos >= text_length_)
425 return text_length_;
426 ++pos;
427
428 wchar_t ch = GetChar(pos);
429 // We want to be on the location after the \r\n.
430 while (pos < text_length_ && (ch == '\r' || ch == '\n')) {
431 ++pos;
432 ch = GetChar(pos);
433 }
434
435 return pos;
436 }
437
GetIndexUp(size_t pos) const438 size_t CFDE_TextEditEngine::GetIndexUp(size_t pos) const {
439 size_t line_start = GetIndexAtStartOfLine(pos);
440 if (line_start == 0)
441 return pos;
442
443 // Determine how far along the line we were.
444 size_t dist = pos - line_start;
445
446 // Move to the end of the preceding line.
447 wchar_t ch;
448 do {
449 --line_start;
450 ch = GetChar(line_start);
451 } while (line_start != 0 && (ch == '\r' || ch == '\n'));
452
453 if (line_start == 0)
454 return dist;
455
456 // Get the start of the line prior to the current line.
457 size_t prior_start = GetIndexAtStartOfLine(line_start);
458
459 // Prior line is shorter then next line, and we're past the end of that line
460 // return the end of line.
461 if (prior_start + dist > line_start)
462 return GetIndexAtEndOfLine(line_start);
463
464 return prior_start + dist;
465 }
466
GetIndexDown(size_t pos) const467 size_t CFDE_TextEditEngine::GetIndexDown(size_t pos) const {
468 size_t line_end = GetIndexAtEndOfLine(pos);
469 if (line_end == text_length_)
470 return pos;
471
472 wchar_t ch;
473 do {
474 ++line_end;
475 ch = GetChar(line_end);
476 } while (line_end < text_length_ && (ch == '\r' || ch == '\n'));
477
478 if (line_end == text_length_)
479 return line_end;
480
481 // Determine how far along the line we are.
482 size_t dist = pos - GetIndexAtStartOfLine(pos);
483
484 // Check if next line is shorter then current line. If so, return end
485 // of next line.
486 size_t next_line_end = GetIndexAtEndOfLine(line_end);
487 if (line_end + dist > next_line_end)
488 return next_line_end;
489
490 return line_end + dist;
491 }
492
GetIndexAtStartOfLine(size_t pos) const493 size_t CFDE_TextEditEngine::GetIndexAtStartOfLine(size_t pos) const {
494 if (pos == 0)
495 return 0;
496
497 wchar_t ch = GetChar(pos);
498 // What to do.
499 if (ch == '\r' || ch == '\n')
500 return pos;
501
502 do {
503 // We want to be on the location just after the \r\n
504 ch = GetChar(pos - 1);
505 if (ch == '\r' || ch == '\n')
506 break;
507
508 --pos;
509 } while (pos > 0);
510
511 return pos;
512 }
513
GetIndexAtEndOfLine(size_t pos) const514 size_t CFDE_TextEditEngine::GetIndexAtEndOfLine(size_t pos) const {
515 if (pos >= text_length_)
516 return text_length_;
517
518 wchar_t ch = GetChar(pos);
519 // Not quite sure which way to go here?
520 if (ch == '\r' || ch == '\n')
521 return pos;
522
523 // We want to be on the location of the first \r or \n.
524 do {
525 ++pos;
526 ch = GetChar(pos);
527 } while (pos < text_length_ && (ch != '\r' && ch != '\n'));
528
529 return pos;
530 }
531
LimitHorizontalScroll(bool val)532 void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) {
533 ClearOperationRecords();
534 limit_horizontal_area_ = val;
535 }
536
LimitVerticalScroll(bool val)537 void CFDE_TextEditEngine::LimitVerticalScroll(bool val) {
538 ClearOperationRecords();
539 limit_vertical_area_ = val;
540 }
541
CanUndo() const542 bool CFDE_TextEditEngine::CanUndo() const {
543 return operation_buffer_[next_operation_index_to_undo_] != nullptr &&
544 next_operation_index_to_undo_ != next_operation_index_to_insert_;
545 }
546
CanRedo() const547 bool CFDE_TextEditEngine::CanRedo() const {
548 size_t idx = (next_operation_index_to_undo_ + 1) % max_edit_operations_;
549 return idx != next_operation_index_to_insert_ &&
550 operation_buffer_[idx] != nullptr;
551 }
552
Redo()553 bool CFDE_TextEditEngine::Redo() {
554 if (!CanRedo())
555 return false;
556
557 next_operation_index_to_undo_ =
558 (next_operation_index_to_undo_ + 1) % max_edit_operations_;
559 operation_buffer_[next_operation_index_to_undo_]->Redo();
560 return true;
561 }
562
Undo()563 bool CFDE_TextEditEngine::Undo() {
564 if (!CanUndo())
565 return false;
566
567 operation_buffer_[next_operation_index_to_undo_]->Undo();
568 next_operation_index_to_undo_ = next_operation_index_to_undo_ == 0
569 ? max_edit_operations_ - 1
570 : next_operation_index_to_undo_ - 1;
571 return true;
572 }
573
Layout()574 void CFDE_TextEditEngine::Layout() {
575 if (!is_dirty_)
576 return;
577
578 is_dirty_ = false;
579 RebuildPieces();
580 }
581
GetContentsBoundingBox()582 CFX_RectF CFDE_TextEditEngine::GetContentsBoundingBox() {
583 // Layout if necessary.
584 Layout();
585 return contents_bounding_box_;
586 }
587
SetAvailableWidth(size_t width)588 void CFDE_TextEditEngine::SetAvailableWidth(size_t width) {
589 if (width == available_width_)
590 return;
591
592 ClearOperationRecords();
593
594 available_width_ = width;
595 if (is_linewrap_enabled_)
596 text_break_.SetLineWidth(width);
597 if (is_comb_text_)
598 SetCombTextWidth();
599
600 is_dirty_ = true;
601 }
602
SetHasCharacterLimit(bool limit)603 void CFDE_TextEditEngine::SetHasCharacterLimit(bool limit) {
604 if (has_character_limit_ == limit)
605 return;
606
607 has_character_limit_ = limit;
608 if (is_comb_text_)
609 SetCombTextWidth();
610
611 is_dirty_ = true;
612 }
613
SetCharacterLimit(size_t limit)614 void CFDE_TextEditEngine::SetCharacterLimit(size_t limit) {
615 if (character_limit_ == limit)
616 return;
617
618 ClearOperationRecords();
619
620 character_limit_ = limit;
621 if (is_comb_text_)
622 SetCombTextWidth();
623
624 is_dirty_ = true;
625 }
626
SetFont(RetainPtr<CFGAS_GEFont> font)627 void CFDE_TextEditEngine::SetFont(RetainPtr<CFGAS_GEFont> font) {
628 if (font_ == font)
629 return;
630
631 font_ = font;
632 text_break_.SetFont(font_);
633 is_dirty_ = true;
634 }
635
GetFont() const636 RetainPtr<CFGAS_GEFont> CFDE_TextEditEngine::GetFont() const {
637 return font_;
638 }
639
SetFontSize(float size)640 void CFDE_TextEditEngine::SetFontSize(float size) {
641 if (font_size_ == size)
642 return;
643
644 font_size_ = size;
645 text_break_.SetFontSize(font_size_);
646 is_dirty_ = true;
647 }
648
SetTabWidth(float width)649 void CFDE_TextEditEngine::SetTabWidth(float width) {
650 int32_t old_tab_width = text_break_.GetTabWidth();
651 text_break_.SetTabWidth(width);
652 if (old_tab_width == text_break_.GetTabWidth())
653 return;
654
655 is_dirty_ = true;
656 }
657
GetFontAscent() const658 float CFDE_TextEditEngine::GetFontAscent() const {
659 return (static_cast<float>(font_->GetAscent()) * font_size_) / 1000;
660 }
661
SetAlignment(uint32_t alignment)662 void CFDE_TextEditEngine::SetAlignment(uint32_t alignment) {
663 if (alignment == character_alignment_)
664 return;
665
666 character_alignment_ = alignment;
667 text_break_.SetAlignment(alignment);
668 is_dirty_ = true;
669 }
670
SetVisibleLineCount(size_t count)671 void CFDE_TextEditEngine::SetVisibleLineCount(size_t count) {
672 if (visible_line_count_ == count)
673 return;
674
675 visible_line_count_ = std::max(static_cast<size_t>(1), count);
676 is_dirty_ = true;
677 }
678
EnableMultiLine(bool val)679 void CFDE_TextEditEngine::EnableMultiLine(bool val) {
680 if (is_multiline_ == val)
681 return;
682
683 is_multiline_ = true;
684
685 uint32_t style = text_break_.GetLayoutStyles();
686 if (is_multiline_)
687 style &= ~FX_LAYOUTSTYLE_SingleLine;
688 else
689 style |= FX_LAYOUTSTYLE_SingleLine;
690 text_break_.SetLayoutStyles(style);
691 is_dirty_ = true;
692 }
693
EnableLineWrap(bool val)694 void CFDE_TextEditEngine::EnableLineWrap(bool val) {
695 if (is_linewrap_enabled_ == val)
696 return;
697
698 is_linewrap_enabled_ = val;
699 text_break_.SetLineWidth(is_linewrap_enabled_ ? available_width_
700 : kPageWidthMax);
701 is_dirty_ = true;
702 }
703
SetCombText(bool enable)704 void CFDE_TextEditEngine::SetCombText(bool enable) {
705 if (is_comb_text_ == enable)
706 return;
707
708 is_comb_text_ = enable;
709
710 uint32_t style = text_break_.GetLayoutStyles();
711 if (enable) {
712 style |= FX_LAYOUTSTYLE_CombText;
713 SetCombTextWidth();
714 } else {
715 style &= ~FX_LAYOUTSTYLE_CombText;
716 }
717 text_break_.SetLayoutStyles(style);
718 is_dirty_ = true;
719 }
720
SetCombTextWidth()721 void CFDE_TextEditEngine::SetCombTextWidth() {
722 size_t width = available_width_;
723 if (has_character_limit_)
724 width /= character_limit_;
725
726 text_break_.SetCombWidth(width);
727 }
728
SelectAll()729 void CFDE_TextEditEngine::SelectAll() {
730 if (text_length_ == 0)
731 return;
732
733 has_selection_ = true;
734 selection_.start_idx = 0;
735 selection_.count = text_length_;
736 }
737
ClearSelection()738 void CFDE_TextEditEngine::ClearSelection() {
739 has_selection_ = false;
740 selection_.start_idx = 0;
741 selection_.count = 0;
742 }
743
SetSelection(size_t start_idx,size_t count)744 void CFDE_TextEditEngine::SetSelection(size_t start_idx, size_t count) {
745 if (count == 0) {
746 ClearSelection();
747 return;
748 }
749
750 if (start_idx > text_length_)
751 return;
752 if (start_idx + count > text_length_)
753 count = text_length_ - start_idx;
754
755 has_selection_ = true;
756 selection_.start_idx = start_idx;
757 selection_.count = count;
758 }
759
GetSelectedText() const760 WideString CFDE_TextEditEngine::GetSelectedText() const {
761 if (!has_selection_)
762 return L"";
763
764 WideString text;
765 if (selection_.start_idx < gap_position_) {
766 // Fully on left of gap.
767 if (selection_.start_idx + selection_.count < gap_position_) {
768 text += WideStringView(content_.data() + selection_.start_idx,
769 selection_.count);
770 return text;
771 }
772
773 // Pre-gap text
774 text += WideStringView(content_.data() + selection_.start_idx,
775 gap_position_ - selection_.start_idx);
776
777 if (selection_.count - (gap_position_ - selection_.start_idx) > 0) {
778 // Post-gap text
779 text += WideStringView(
780 content_.data() + gap_position_ + gap_size_,
781 selection_.count - (gap_position_ - selection_.start_idx));
782 }
783
784 return text;
785 }
786
787 // Fully right of gap
788 text += WideStringView(content_.data() + gap_size_ + selection_.start_idx,
789 selection_.count);
790 return text;
791 }
792
DeleteSelectedText(RecordOperation add_operation)793 WideString CFDE_TextEditEngine::DeleteSelectedText(
794 RecordOperation add_operation) {
795 if (!has_selection_)
796 return L"";
797
798 return Delete(selection_.start_idx, selection_.count, add_operation);
799 }
800
Delete(size_t start_idx,size_t length,RecordOperation add_operation)801 WideString CFDE_TextEditEngine::Delete(size_t start_idx,
802 size_t length,
803 RecordOperation add_operation) {
804 if (start_idx >= text_length_)
805 return L"";
806
807 length = std::min(length, text_length_ - start_idx);
808 AdjustGap(start_idx + length, 0);
809
810 WideString ret;
811 ret += WideStringView(content_.data() + start_idx, length);
812
813 if (add_operation == RecordOperation::kInsertRecord) {
814 AddOperationRecord(
815 pdfium::MakeUnique<DeleteOperation>(this, start_idx, ret));
816 }
817
818 WideString previous_text = GetText();
819
820 gap_position_ = start_idx;
821 gap_size_ += length;
822
823 text_length_ -= length;
824 ClearSelection();
825
826 if (delegate_)
827 delegate_->OnTextChanged(previous_text);
828
829 return ret;
830 }
831
ReplaceSelectedText(const WideString & rep)832 void CFDE_TextEditEngine::ReplaceSelectedText(const WideString& rep) {
833 size_t start_idx = selection_.start_idx;
834
835 WideString txt = DeleteSelectedText(RecordOperation::kSkipRecord);
836 Insert(gap_position_, rep, RecordOperation::kSkipRecord);
837
838 AddOperationRecord(
839 pdfium::MakeUnique<ReplaceOperation>(this, start_idx, txt, rep));
840 }
841
GetText() const842 WideString CFDE_TextEditEngine::GetText() const {
843 WideString str;
844 if (gap_position_ > 0)
845 str += WideStringView(content_.data(), gap_position_);
846 if (text_length_ - gap_position_ > 0) {
847 str += WideStringView(content_.data() + gap_position_ + gap_size_,
848 text_length_ - gap_position_);
849 }
850 return str;
851 }
852
GetLength() const853 size_t CFDE_TextEditEngine::GetLength() const {
854 return text_length_;
855 }
856
GetChar(size_t idx) const857 wchar_t CFDE_TextEditEngine::GetChar(size_t idx) const {
858 if (idx >= text_length_)
859 return L'\0';
860 if (password_mode_)
861 return password_alias_;
862
863 return idx < gap_position_
864 ? content_[idx]
865 : content_[gap_position_ + gap_size_ + (idx - gap_position_)];
866 }
867
GetWidthOfChar(size_t idx)868 size_t CFDE_TextEditEngine::GetWidthOfChar(size_t idx) {
869 // Recalculate the widths if necessary.
870 Layout();
871 return idx < char_widths_.size() ? char_widths_[idx] : 0;
872 }
873
GetIndexForPoint(const CFX_PointF & point)874 size_t CFDE_TextEditEngine::GetIndexForPoint(const CFX_PointF& point) {
875 // Recalculate the widths if necessary.
876 Layout();
877
878 auto start_it = text_piece_info_.begin();
879 for (; start_it < text_piece_info_.end(); ++start_it) {
880 if (start_it->rtPiece.top <= point.y &&
881 point.y < start_it->rtPiece.bottom())
882 break;
883 }
884 // We didn't find the point before getting to the end of the text, return
885 // end of text.
886 if (start_it == text_piece_info_.end())
887 return text_length_;
888
889 auto end_it = start_it;
890 for (; end_it < text_piece_info_.end(); ++end_it) {
891 // We've moved past where the point should be and didn't find anything.
892 // Return the start of the current piece as the location.
893 if (end_it->rtPiece.bottom() <= point.y || point.y < end_it->rtPiece.top)
894 break;
895 }
896 // Make sure the end iterator is pointing to our text pieces.
897 if (end_it == text_piece_info_.end())
898 --end_it;
899
900 size_t start_it_idx = start_it->nStart;
901 for (; start_it <= end_it; ++start_it) {
902 if (!start_it->rtPiece.Contains(point))
903 continue;
904
905 std::vector<CFX_RectF> rects = GetCharRects(*start_it);
906 for (size_t i = 0; i < rects.size(); ++i) {
907 if (!rects[i].Contains(point))
908 continue;
909 size_t pos = start_it->nStart + i;
910 if (pos >= text_length_)
911 return text_length_;
912
913 wchar_t wch = GetChar(pos);
914 if (wch == L'\n' || wch == L'\r') {
915 if (wch == L'\n' && pos > 0 && GetChar(pos - 1) == L'\r')
916 --pos;
917 return pos;
918 }
919
920 // TODO(dsinclair): Old code had a before flag set based on bidi?
921 return pos;
922 }
923 }
924
925 if (start_it == text_piece_info_.end())
926 return start_it_idx;
927 if (start_it == end_it)
928 return start_it->nStart;
929
930 // We didn't find the point before going over all of the pieces, we want to
931 // return the start of the piece after the point.
932 return end_it->nStart;
933 }
934
GetCharRects(const FDE_TEXTEDITPIECE & piece)935 std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharRects(
936 const FDE_TEXTEDITPIECE& piece) {
937 if (piece.nCount < 1)
938 return std::vector<CFX_RectF>();
939
940 FX_TXTRUN tr;
941 tr.pEdtEngine = this;
942 tr.pIdentity = &piece;
943 tr.iLength = piece.nCount;
944 tr.pFont = font_;
945 tr.fFontSize = font_size_;
946 tr.dwStyles = text_break_.GetLayoutStyles();
947 tr.dwCharStyles = piece.dwCharStyles;
948 tr.pRect = &piece.rtPiece;
949 return text_break_.GetCharRects(&tr, false);
950 }
951
GetDisplayPos(const FDE_TEXTEDITPIECE & piece)952 std::vector<FXTEXT_CHARPOS> CFDE_TextEditEngine::GetDisplayPos(
953 const FDE_TEXTEDITPIECE& piece) {
954 if (piece.nCount < 1)
955 return std::vector<FXTEXT_CHARPOS>();
956
957 FX_TXTRUN tr;
958 tr.pEdtEngine = this;
959 tr.pIdentity = &piece;
960 tr.iLength = piece.nCount;
961 tr.pFont = font_;
962 tr.fFontSize = font_size_;
963 tr.dwStyles = text_break_.GetLayoutStyles();
964 tr.dwCharStyles = piece.dwCharStyles;
965 tr.pRect = &piece.rtPiece;
966
967 std::vector<FXTEXT_CHARPOS> data(text_break_.GetDisplayPos(&tr, nullptr));
968 text_break_.GetDisplayPos(&tr, data.data());
969 return data;
970 }
971
RebuildPieces()972 void CFDE_TextEditEngine::RebuildPieces() {
973 text_break_.EndBreak(CFX_BreakType::Paragraph);
974 text_break_.ClearBreakPieces();
975
976 char_widths_.clear();
977 text_piece_info_.clear();
978
979 // Must have a font set in order to break the text.
980 if (text_length_ == 0 || !font_)
981 return;
982
983 bool initialized_bounding_box = false;
984 contents_bounding_box_ = CFX_RectF();
985 size_t current_piece_start = 0;
986 float current_line_start = 0;
987
988 auto iter = pdfium::MakeUnique<CFDE_TextEditEngine::Iterator>(this);
989 while (!iter->IsEOF(false)) {
990 iter->Next(false);
991
992 CFX_BreakType break_status = text_break_.AppendChar(
993 password_mode_ ? password_alias_ : iter->GetChar());
994 if (iter->IsEOF(false) && CFX_BreakTypeNoneOrPiece(break_status))
995 break_status = text_break_.EndBreak(CFX_BreakType::Paragraph);
996
997 if (CFX_BreakTypeNoneOrPiece(break_status))
998 continue;
999 int32_t piece_count = text_break_.CountBreakPieces();
1000 for (int32_t i = 0; i < piece_count; ++i) {
1001 const CFX_BreakPiece* piece = text_break_.GetBreakPieceUnstable(i);
1002
1003 FDE_TEXTEDITPIECE txtEdtPiece;
1004 memset(&txtEdtPiece, 0, sizeof(FDE_TEXTEDITPIECE));
1005
1006 txtEdtPiece.nBidiLevel = piece->m_iBidiLevel;
1007 txtEdtPiece.nCount = piece->GetLength();
1008 txtEdtPiece.nStart = current_piece_start;
1009 txtEdtPiece.dwCharStyles = piece->m_dwCharStyles;
1010 if (FX_IsOdd(piece->m_iBidiLevel))
1011 txtEdtPiece.dwCharStyles |= FX_TXTCHARSTYLE_OddBidiLevel;
1012
1013 txtEdtPiece.rtPiece.left = piece->m_iStartPos / 20000.0f;
1014 txtEdtPiece.rtPiece.top = current_line_start;
1015 txtEdtPiece.rtPiece.width = piece->m_iWidth / 20000.0f;
1016 txtEdtPiece.rtPiece.height = line_spacing_;
1017 text_piece_info_.push_back(txtEdtPiece);
1018
1019 if (initialized_bounding_box) {
1020 contents_bounding_box_.Union(txtEdtPiece.rtPiece);
1021 } else {
1022 contents_bounding_box_ = txtEdtPiece.rtPiece;
1023 initialized_bounding_box = true;
1024 }
1025
1026 current_piece_start += txtEdtPiece.nCount;
1027 for (int32_t k = 0; k < txtEdtPiece.nCount; ++k)
1028 char_widths_.push_back(piece->GetChar(k)->m_iCharWidth);
1029 }
1030
1031 current_line_start += line_spacing_;
1032 text_break_.ClearBreakPieces();
1033 }
1034
1035 float delta = 0.0;
1036 bool bounds_smaller = contents_bounding_box_.width < available_width_;
1037 if (IsAlignedRight() && bounds_smaller) {
1038 delta = available_width_ - contents_bounding_box_.width;
1039 } else if (IsAlignedCenter() && bounds_smaller) {
1040 // TODO(dsinclair): Old code used CombText here and set the space to
1041 // something unrelated to the available width .... Figure out if this is
1042 // needed and what it should do.
1043 // if (is_comb_text_) {
1044 // } else {
1045 delta = (available_width_ - contents_bounding_box_.width) / 2.0f;
1046 // }
1047 }
1048
1049 if (delta != 0.0) {
1050 float offset = delta - contents_bounding_box_.left;
1051 for (auto& info : text_piece_info_)
1052 info.rtPiece.Offset(offset, 0.0f);
1053 contents_bounding_box_.Offset(offset, 0.0f);
1054 }
1055
1056 // Shrink the last piece down to the font_size.
1057 contents_bounding_box_.height -= line_spacing_ - font_size_;
1058 text_piece_info_.back().rtPiece.height = font_size_;
1059 }
1060
GetCharacterInfo(int32_t start_idx)1061 std::pair<int32_t, CFX_RectF> CFDE_TextEditEngine::GetCharacterInfo(
1062 int32_t start_idx) {
1063 ASSERT(start_idx >= 0);
1064 ASSERT(static_cast<size_t>(start_idx) <= text_length_);
1065
1066 // Make sure the current available data is fresh.
1067 Layout();
1068
1069 auto it = text_piece_info_.begin();
1070 for (; it != text_piece_info_.end(); ++it) {
1071 if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount)
1072 break;
1073 }
1074 if (it == text_piece_info_.end()) {
1075 NOTREACHED();
1076 return {0, CFX_RectF()};
1077 }
1078
1079 return {it->nBidiLevel, GetCharRects(*it)[start_idx - it->nStart]};
1080 }
1081
GetCharacterRectsInRange(int32_t start_idx,int32_t count)1082 std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharacterRectsInRange(
1083 int32_t start_idx,
1084 int32_t count) {
1085 // Make sure the current available data is fresh.
1086 Layout();
1087
1088 auto it = text_piece_info_.begin();
1089 for (; it != text_piece_info_.end(); ++it) {
1090 if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount)
1091 break;
1092 }
1093 if (it == text_piece_info_.end())
1094 return std::vector<CFX_RectF>();
1095
1096 int32_t end_idx = start_idx + count - 1;
1097 std::vector<CFX_RectF> rects;
1098 while (it != text_piece_info_.end()) {
1099 // If we end inside the current piece, extract what we need and we're done.
1100 if (it->nStart <= end_idx && end_idx < it->nStart + it->nCount) {
1101 std::vector<CFX_RectF> arr = GetCharRects(*it);
1102 CFX_RectF piece = arr[0];
1103 piece.Union(arr[end_idx - it->nStart]);
1104 rects.push_back(piece);
1105 break;
1106 }
1107 rects.push_back(it->rtPiece);
1108 ++it;
1109 }
1110
1111 return rects;
1112 }
1113
BoundsForWordAt(size_t idx) const1114 std::pair<size_t, size_t> CFDE_TextEditEngine::BoundsForWordAt(
1115 size_t idx) const {
1116 if (idx > text_length_)
1117 return {0, 0};
1118
1119 CFDE_TextEditEngine::Iterator iter(this);
1120 iter.SetAt(idx);
1121
1122 size_t start_idx = iter.FindNextBreakPos(true);
1123 size_t end_idx = iter.FindNextBreakPos(false);
1124 return {start_idx, end_idx - start_idx + 1};
1125 }
1126
Iterator(const CFDE_TextEditEngine * engine)1127 CFDE_TextEditEngine::Iterator::Iterator(const CFDE_TextEditEngine* engine)
1128 : engine_(engine), current_position_(-1) {}
1129
~Iterator()1130 CFDE_TextEditEngine::Iterator::~Iterator() {}
1131
Next(bool bPrev)1132 void CFDE_TextEditEngine::Iterator::Next(bool bPrev) {
1133 if (bPrev && current_position_ == -1)
1134 return;
1135 if (!bPrev && current_position_ > -1 &&
1136 static_cast<size_t>(current_position_) == engine_->GetLength()) {
1137 return;
1138 }
1139
1140 if (bPrev)
1141 --current_position_;
1142 else
1143 ++current_position_;
1144 }
1145
GetChar() const1146 wchar_t CFDE_TextEditEngine::Iterator::GetChar() const {
1147 return engine_->GetChar(current_position_);
1148 }
1149
SetAt(size_t nIndex)1150 void CFDE_TextEditEngine::Iterator::SetAt(size_t nIndex) {
1151 if (static_cast<size_t>(nIndex) >= engine_->GetLength())
1152 current_position_ = engine_->GetLength();
1153 else
1154 current_position_ = nIndex;
1155 }
1156
IsEOF(bool bPrev) const1157 bool CFDE_TextEditEngine::Iterator::IsEOF(bool bPrev) const {
1158 return bPrev ? current_position_ == -1
1159 : current_position_ > -1 &&
1160 static_cast<size_t>(current_position_) ==
1161 engine_->GetLength();
1162 }
1163
FindNextBreakPos(bool bPrev)1164 size_t CFDE_TextEditEngine::Iterator::FindNextBreakPos(bool bPrev) {
1165 if (IsEOF(bPrev))
1166 return current_position_ > -1 ? current_position_ : 0;
1167
1168 WordBreakProperty ePreType = WordBreakProperty::kNone;
1169 if (!IsEOF(!bPrev)) {
1170 Next(!bPrev);
1171 ePreType = GetWordBreakProperty(GetChar());
1172 Next(bPrev);
1173 }
1174
1175 WordBreakProperty eCurType = GetWordBreakProperty(GetChar());
1176 bool bFirst = true;
1177 while (!IsEOF(bPrev)) {
1178 Next(bPrev);
1179
1180 WordBreakProperty eNextType = GetWordBreakProperty(GetChar());
1181 bool wBreak = CheckStateChangeForWordBreak(eCurType, eNextType);
1182 if (wBreak) {
1183 if (IsEOF(bPrev)) {
1184 Next(!bPrev);
1185 break;
1186 }
1187 if (bFirst) {
1188 int32_t nFlags = GetBreakFlagsFor(eCurType, eNextType);
1189 if (nFlags > 0) {
1190 if (BreakFlagsChanged(nFlags, ePreType)) {
1191 Next(!bPrev);
1192 break;
1193 }
1194 Next(bPrev);
1195 wBreak = false;
1196 }
1197 }
1198 if (wBreak) {
1199 int32_t nFlags = GetBreakFlagsFor(eNextType, eCurType);
1200 if (nFlags <= 0) {
1201 Next(!bPrev);
1202 break;
1203 }
1204
1205 Next(bPrev);
1206 eNextType = GetWordBreakProperty(GetChar());
1207 if (BreakFlagsChanged(nFlags, eNextType)) {
1208 Next(!bPrev);
1209 Next(!bPrev);
1210 break;
1211 }
1212 }
1213 }
1214 eCurType = eNextType;
1215 bFirst = false;
1216 }
1217 return current_position_ > -1 ? current_position_ : 0;
1218 }
1219