1312 lines
37 KiB
C++
1312 lines
37 KiB
C++
// Copyright 2017 PDFium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
|
|
|
|
#include "xfa/fde/cfde_texteditengine.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
#include "core/fxge/text_char_pos.h"
|
|
#include "third_party/base/ptr_util.h"
|
|
#include "xfa/fde/cfde_textout.h"
|
|
#include "xfa/fde/cfde_wordbreak_data.h"
|
|
#include "xfa/fgas/font/cfgas_gefont.h"
|
|
|
|
namespace {
|
|
|
|
constexpr size_t kMaxEditOperations = 128;
|
|
constexpr size_t kGapSize = 128;
|
|
constexpr size_t kPageWidthMax = 0xffff;
|
|
|
|
class InsertOperation final : public CFDE_TextEditEngine::Operation {
|
|
public:
|
|
InsertOperation(CFDE_TextEditEngine* engine,
|
|
size_t start_idx,
|
|
const WideString& added_text)
|
|
: engine_(engine), start_idx_(start_idx), added_text_(added_text) {}
|
|
|
|
~InsertOperation() override {}
|
|
|
|
void Redo() const override {
|
|
engine_->Insert(start_idx_, added_text_,
|
|
CFDE_TextEditEngine::RecordOperation::kSkipRecord);
|
|
}
|
|
|
|
void Undo() const override {
|
|
engine_->Delete(start_idx_, added_text_.GetLength(),
|
|
CFDE_TextEditEngine::RecordOperation::kSkipRecord);
|
|
}
|
|
|
|
private:
|
|
UnownedPtr<CFDE_TextEditEngine> engine_;
|
|
size_t start_idx_;
|
|
WideString added_text_;
|
|
};
|
|
|
|
class DeleteOperation final : public CFDE_TextEditEngine::Operation {
|
|
public:
|
|
DeleteOperation(CFDE_TextEditEngine* engine,
|
|
size_t start_idx,
|
|
const WideString& removed_text)
|
|
: engine_(engine), start_idx_(start_idx), removed_text_(removed_text) {}
|
|
|
|
~DeleteOperation() override {}
|
|
|
|
void Redo() const override {
|
|
engine_->Delete(start_idx_, removed_text_.GetLength(),
|
|
CFDE_TextEditEngine::RecordOperation::kSkipRecord);
|
|
}
|
|
|
|
void Undo() const override {
|
|
engine_->Insert(start_idx_, removed_text_,
|
|
CFDE_TextEditEngine::RecordOperation::kSkipRecord);
|
|
}
|
|
|
|
private:
|
|
UnownedPtr<CFDE_TextEditEngine> engine_;
|
|
size_t start_idx_;
|
|
WideString removed_text_;
|
|
};
|
|
|
|
class ReplaceOperation final : public CFDE_TextEditEngine::Operation {
|
|
public:
|
|
ReplaceOperation(CFDE_TextEditEngine* engine,
|
|
size_t start_idx,
|
|
const WideString& removed_text,
|
|
const WideString& added_text)
|
|
: insert_op_(engine, start_idx, added_text),
|
|
delete_op_(engine, start_idx, removed_text) {}
|
|
|
|
~ReplaceOperation() override {}
|
|
|
|
void Redo() const override {
|
|
delete_op_.Redo();
|
|
insert_op_.Redo();
|
|
}
|
|
|
|
void Undo() const override {
|
|
insert_op_.Undo();
|
|
delete_op_.Undo();
|
|
}
|
|
|
|
private:
|
|
InsertOperation insert_op_;
|
|
DeleteOperation delete_op_;
|
|
};
|
|
|
|
int GetBreakFlagsFor(WordBreakProperty current, WordBreakProperty next) {
|
|
if (current == WordBreakProperty::kMidLetter) {
|
|
if (next == WordBreakProperty::kALetter)
|
|
return 1;
|
|
} else if (current == WordBreakProperty::kMidNum) {
|
|
if (next == WordBreakProperty::kNumeric)
|
|
return 2;
|
|
} else if (current == WordBreakProperty::kMidNumLet) {
|
|
if (next == WordBreakProperty::kALetter)
|
|
return 1;
|
|
if (next == WordBreakProperty::kNumeric)
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool BreakFlagsChanged(int flags, WordBreakProperty previous) {
|
|
return (flags != 1 || previous != WordBreakProperty::kALetter) &&
|
|
(flags != 2 || previous != WordBreakProperty::kNumeric);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CFDE_TextEditEngine::CFDE_TextEditEngine()
|
|
: font_color_(0xff000000),
|
|
font_size_(10.0f),
|
|
line_spacing_(10.0f),
|
|
text_length_(0),
|
|
gap_position_(0),
|
|
gap_size_(kGapSize),
|
|
available_width_(kPageWidthMax),
|
|
character_limit_(std::numeric_limits<size_t>::max()),
|
|
visible_line_count_(1),
|
|
next_operation_index_to_undo_(kMaxEditOperations - 1),
|
|
next_operation_index_to_insert_(0),
|
|
max_edit_operations_(kMaxEditOperations),
|
|
character_alignment_(CFX_TxtLineAlignment_Left),
|
|
has_character_limit_(false),
|
|
is_comb_text_(false),
|
|
is_dirty_(false),
|
|
validation_enabled_(false),
|
|
is_multiline_(false),
|
|
is_linewrap_enabled_(false),
|
|
limit_horizontal_area_(false),
|
|
limit_vertical_area_(false),
|
|
password_mode_(false),
|
|
password_alias_(L'*'),
|
|
has_selection_(false),
|
|
selection_({0, 0}) {
|
|
content_.resize(gap_size_);
|
|
operation_buffer_.resize(max_edit_operations_);
|
|
|
|
text_break_.SetFontSize(font_size_);
|
|
text_break_.SetLineBreakTolerance(2.0f);
|
|
text_break_.SetTabWidth(36);
|
|
}
|
|
|
|
CFDE_TextEditEngine::~CFDE_TextEditEngine() {}
|
|
|
|
void CFDE_TextEditEngine::Clear() {
|
|
text_length_ = 0;
|
|
gap_position_ = 0;
|
|
gap_size_ = kGapSize;
|
|
|
|
content_.clear();
|
|
content_.resize(gap_size_);
|
|
|
|
ClearSelection();
|
|
ClearOperationRecords();
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetMaxEditOperationsForTesting(size_t max) {
|
|
max_edit_operations_ = max;
|
|
operation_buffer_.resize(max);
|
|
|
|
ClearOperationRecords();
|
|
}
|
|
|
|
void CFDE_TextEditEngine::AdjustGap(size_t idx, size_t length) {
|
|
static const size_t char_size = sizeof(WideString::CharType);
|
|
|
|
// Move the gap, if necessary.
|
|
if (idx < gap_position_) {
|
|
memmove(content_.data() + idx + gap_size_, content_.data() + idx,
|
|
(gap_position_ - idx) * char_size);
|
|
gap_position_ = idx;
|
|
} else if (idx > gap_position_) {
|
|
memmove(content_.data() + gap_position_,
|
|
content_.data() + gap_position_ + gap_size_,
|
|
(idx - gap_position_) * char_size);
|
|
gap_position_ = idx;
|
|
}
|
|
|
|
// If the gap is too small, make it bigger.
|
|
if (length >= gap_size_) {
|
|
size_t new_gap_size = length + kGapSize;
|
|
content_.resize(text_length_ + new_gap_size);
|
|
|
|
memmove(content_.data() + gap_position_ + new_gap_size,
|
|
content_.data() + gap_position_ + gap_size_,
|
|
(text_length_ - gap_position_) * char_size);
|
|
|
|
gap_size_ = new_gap_size;
|
|
}
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::CountCharsExceedingSize(const WideString& text,
|
|
size_t num_to_check) {
|
|
if (!limit_horizontal_area_ && !limit_vertical_area_)
|
|
return 0;
|
|
|
|
auto text_out = pdfium::MakeUnique<CFDE_TextOut>();
|
|
text_out->SetLineSpace(line_spacing_);
|
|
text_out->SetFont(font_);
|
|
text_out->SetFontSize(font_size_);
|
|
|
|
FDE_TextStyle style;
|
|
style.single_line_ = !is_multiline_;
|
|
|
|
CFX_RectF text_rect;
|
|
if (is_linewrap_enabled_) {
|
|
style.line_wrap_ = true;
|
|
text_rect.width = available_width_;
|
|
} else {
|
|
text_rect.width = kPageWidthMax;
|
|
}
|
|
text_out->SetStyles(style);
|
|
|
|
size_t length = text.GetLength();
|
|
WideStringView temp = text.AsStringView();
|
|
|
|
float vertical_height = line_spacing_ * visible_line_count_;
|
|
size_t chars_exceeding_size = 0;
|
|
// TODO(dsinclair): Can this get changed to a binary search?
|
|
for (size_t i = 0; i < num_to_check; i++) {
|
|
text_out->CalcLogicSize(temp, &text_rect);
|
|
if (limit_horizontal_area_ && text_rect.width <= available_width_)
|
|
break;
|
|
if (limit_vertical_area_ && text_rect.height <= vertical_height)
|
|
break;
|
|
|
|
++chars_exceeding_size;
|
|
|
|
--length;
|
|
temp = temp.First(length);
|
|
}
|
|
|
|
return chars_exceeding_size;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::Insert(size_t idx,
|
|
const WideString& request_text,
|
|
RecordOperation add_operation) {
|
|
WideString text = request_text;
|
|
if (text.IsEmpty())
|
|
return;
|
|
|
|
idx = std::min(idx, text_length_);
|
|
|
|
TextChange change;
|
|
change.selection_start = idx;
|
|
change.selection_end = idx;
|
|
change.text = text;
|
|
change.previous_text = GetText();
|
|
change.cancelled = false;
|
|
|
|
if (delegate_ && (add_operation != RecordOperation::kSkipRecord &&
|
|
add_operation != RecordOperation::kSkipNotify)) {
|
|
delegate_->OnTextWillChange(&change);
|
|
if (change.cancelled)
|
|
return;
|
|
|
|
text = change.text;
|
|
idx = change.selection_start;
|
|
|
|
// Delegate extended the selection, so delete it before we insert.
|
|
if (change.selection_end != change.selection_start)
|
|
DeleteSelectedText(RecordOperation::kSkipRecord);
|
|
|
|
// Delegate may have changed text entirely, recheck.
|
|
idx = std::min(idx, text_length_);
|
|
}
|
|
|
|
size_t length = text.GetLength();
|
|
if (length == 0)
|
|
return;
|
|
|
|
// If we're going to be too big we insert what we can and notify the
|
|
// delegate we've filled the text after the insert is done.
|
|
bool exceeded_limit = false;
|
|
|
|
// Currently we allow inserting a number of characters over the text limit if
|
|
// we're skipping notify. This means we're setting the formatted text into the
|
|
// engine. Otherwise, if you enter 123456789 for an SSN into a field
|
|
// with a 9 character limit and we reformat to 123-45-6789 we'll truncate
|
|
// the 89 when inserting into the text edit. See https://crbug.com/pdfium/1089
|
|
if (has_character_limit_ && text_length_ + length > character_limit_) {
|
|
if (add_operation == RecordOperation::kSkipNotify) {
|
|
// Raise the limit to allow subsequent changes to expanded text.
|
|
character_limit_ = text_length_ + length;
|
|
} else {
|
|
// Trucate the text to comply with the limit.
|
|
CHECK(text_length_ <= character_limit_);
|
|
length = character_limit_ - text_length_;
|
|
exceeded_limit = true;
|
|
}
|
|
}
|
|
AdjustGap(idx, length);
|
|
|
|
if (validation_enabled_ || limit_horizontal_area_ || limit_vertical_area_) {
|
|
WideString str;
|
|
if (gap_position_ > 0)
|
|
str += WideStringView(content_.data(), gap_position_);
|
|
|
|
str += text;
|
|
|
|
if (text_length_ - gap_position_ > 0) {
|
|
str += WideStringView(content_.data() + gap_position_ + gap_size_,
|
|
text_length_ - gap_position_);
|
|
}
|
|
|
|
if (validation_enabled_ && delegate_ && !delegate_->OnValidate(str)) {
|
|
// TODO(dsinclair): Notify delegate of validation failure?
|
|
return;
|
|
}
|
|
|
|
// Check if we've limited the horizontal/vertical area, and if so determine
|
|
// how many of our characters would be outside the area.
|
|
size_t chars_exceeding = CountCharsExceedingSize(str, length);
|
|
if (chars_exceeding > 0) {
|
|
// If none of the characters will fit, notify and exit.
|
|
if (chars_exceeding == length) {
|
|
if (delegate_)
|
|
delegate_->NotifyTextFull();
|
|
return;
|
|
}
|
|
|
|
// Some, but not all, chars will fit, insert them and then notify
|
|
// we're full.
|
|
exceeded_limit = true;
|
|
length -= chars_exceeding;
|
|
}
|
|
}
|
|
|
|
if (add_operation == RecordOperation::kInsertRecord) {
|
|
AddOperationRecord(
|
|
pdfium::MakeUnique<InsertOperation>(this, gap_position_, text));
|
|
}
|
|
|
|
WideString previous_text;
|
|
if (delegate_)
|
|
previous_text = GetText();
|
|
|
|
// Copy the new text into the gap.
|
|
static const size_t char_size = sizeof(WideString::CharType);
|
|
memcpy(content_.data() + gap_position_, text.c_str(), length * char_size);
|
|
gap_position_ += length;
|
|
gap_size_ -= length;
|
|
text_length_ += length;
|
|
|
|
is_dirty_ = true;
|
|
|
|
// Inserting text resets the selection.
|
|
ClearSelection();
|
|
|
|
if (delegate_) {
|
|
if (exceeded_limit)
|
|
delegate_->NotifyTextFull();
|
|
|
|
delegate_->OnTextChanged();
|
|
}
|
|
}
|
|
|
|
void CFDE_TextEditEngine::AddOperationRecord(std::unique_ptr<Operation> op) {
|
|
size_t last_insert_position = next_operation_index_to_insert_ == 0
|
|
? max_edit_operations_ - 1
|
|
: next_operation_index_to_insert_ - 1;
|
|
|
|
// If our undo record is not the last thing we inserted then we need to
|
|
// remove all the undo records between our insert position and the undo marker
|
|
// and make that our new insert position.
|
|
if (next_operation_index_to_undo_ != last_insert_position) {
|
|
if (next_operation_index_to_undo_ > last_insert_position) {
|
|
// Our Undo position is ahead of us, which means we need to clear out the
|
|
// head of the queue.
|
|
while (last_insert_position != 0) {
|
|
operation_buffer_[last_insert_position].reset();
|
|
--last_insert_position;
|
|
}
|
|
operation_buffer_[0].reset();
|
|
|
|
// Moving this will let us then clear out the end, setting the undo
|
|
// position to before the insert position.
|
|
last_insert_position = max_edit_operations_ - 1;
|
|
}
|
|
|
|
// Clear out the vector from undo position to our set insert position.
|
|
while (next_operation_index_to_undo_ != last_insert_position) {
|
|
operation_buffer_[last_insert_position].reset();
|
|
--last_insert_position;
|
|
}
|
|
}
|
|
|
|
// We're now pointing at the next thing we want to Undo, so insert at the
|
|
// next position in the queue.
|
|
++last_insert_position;
|
|
if (last_insert_position >= max_edit_operations_)
|
|
last_insert_position = 0;
|
|
|
|
operation_buffer_[last_insert_position] = std::move(op);
|
|
next_operation_index_to_insert_ =
|
|
(last_insert_position + 1) % max_edit_operations_;
|
|
next_operation_index_to_undo_ = last_insert_position;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::ClearOperationRecords() {
|
|
for (auto& record : operation_buffer_)
|
|
record.reset();
|
|
|
|
next_operation_index_to_undo_ = max_edit_operations_ - 1;
|
|
next_operation_index_to_insert_ = 0;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexBefore(size_t pos) {
|
|
int32_t bidi_level;
|
|
CFX_RectF rect;
|
|
// Possible |Layout| triggered by |GetCharacterInfo|.
|
|
std::tie(bidi_level, rect) = GetCharacterInfo(pos);
|
|
return FX_IsOdd(bidi_level) ? GetIndexRight(pos) : GetIndexLeft(pos);
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexLeft(size_t pos) const {
|
|
if (pos == 0)
|
|
return 0;
|
|
--pos;
|
|
|
|
while (pos != 0) {
|
|
// We want to be on the location just before the \r or \n
|
|
wchar_t ch = GetChar(pos - 1);
|
|
if (ch != '\r' && ch != '\n')
|
|
break;
|
|
|
|
--pos;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexRight(size_t pos) const {
|
|
if (pos >= text_length_)
|
|
return text_length_;
|
|
++pos;
|
|
|
|
wchar_t ch = GetChar(pos);
|
|
// We want to be on the location after the \r\n.
|
|
while (pos < text_length_ && (ch == '\r' || ch == '\n')) {
|
|
++pos;
|
|
ch = GetChar(pos);
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexUp(size_t pos) const {
|
|
size_t line_start = GetIndexAtStartOfLine(pos);
|
|
if (line_start == 0)
|
|
return pos;
|
|
|
|
// Determine how far along the line we were.
|
|
size_t dist = pos - line_start;
|
|
|
|
// Move to the end of the preceding line.
|
|
wchar_t ch;
|
|
do {
|
|
--line_start;
|
|
ch = GetChar(line_start);
|
|
} while (line_start != 0 && (ch == '\r' || ch == '\n'));
|
|
|
|
if (line_start == 0)
|
|
return dist;
|
|
|
|
// Get the start of the line prior to the current line.
|
|
size_t prior_start = GetIndexAtStartOfLine(line_start);
|
|
|
|
// Prior line is shorter then next line, and we're past the end of that line
|
|
// return the end of line.
|
|
if (prior_start + dist > line_start)
|
|
return GetIndexAtEndOfLine(line_start);
|
|
|
|
return prior_start + dist;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexDown(size_t pos) const {
|
|
size_t line_end = GetIndexAtEndOfLine(pos);
|
|
if (line_end == text_length_)
|
|
return pos;
|
|
|
|
wchar_t ch;
|
|
do {
|
|
++line_end;
|
|
ch = GetChar(line_end);
|
|
} while (line_end < text_length_ && (ch == '\r' || ch == '\n'));
|
|
|
|
if (line_end == text_length_)
|
|
return line_end;
|
|
|
|
// Determine how far along the line we are.
|
|
size_t dist = pos - GetIndexAtStartOfLine(pos);
|
|
|
|
// Check if next line is shorter then current line. If so, return end
|
|
// of next line.
|
|
size_t next_line_end = GetIndexAtEndOfLine(line_end);
|
|
if (line_end + dist > next_line_end)
|
|
return next_line_end;
|
|
|
|
return line_end + dist;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexAtStartOfLine(size_t pos) const {
|
|
if (pos == 0)
|
|
return 0;
|
|
|
|
wchar_t ch = GetChar(pos);
|
|
// What to do.
|
|
if (ch == '\r' || ch == '\n')
|
|
return pos;
|
|
|
|
do {
|
|
// We want to be on the location just after the \r\n
|
|
ch = GetChar(pos - 1);
|
|
if (ch == '\r' || ch == '\n')
|
|
break;
|
|
|
|
--pos;
|
|
} while (pos > 0);
|
|
|
|
return pos;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexAtEndOfLine(size_t pos) const {
|
|
if (pos >= text_length_)
|
|
return text_length_;
|
|
|
|
wchar_t ch = GetChar(pos);
|
|
// Not quite sure which way to go here?
|
|
if (ch == '\r' || ch == '\n')
|
|
return pos;
|
|
|
|
// We want to be on the location of the first \r or \n.
|
|
do {
|
|
++pos;
|
|
ch = GetChar(pos);
|
|
} while (pos < text_length_ && (ch != '\r' && ch != '\n'));
|
|
|
|
return pos;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) {
|
|
ClearOperationRecords();
|
|
limit_horizontal_area_ = val;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::LimitVerticalScroll(bool val) {
|
|
ClearOperationRecords();
|
|
limit_vertical_area_ = val;
|
|
}
|
|
|
|
bool CFDE_TextEditEngine::CanUndo() const {
|
|
return operation_buffer_[next_operation_index_to_undo_] != nullptr &&
|
|
next_operation_index_to_undo_ != next_operation_index_to_insert_;
|
|
}
|
|
|
|
bool CFDE_TextEditEngine::CanRedo() const {
|
|
size_t idx = (next_operation_index_to_undo_ + 1) % max_edit_operations_;
|
|
return idx != next_operation_index_to_insert_ &&
|
|
operation_buffer_[idx] != nullptr;
|
|
}
|
|
|
|
bool CFDE_TextEditEngine::Redo() {
|
|
if (!CanRedo())
|
|
return false;
|
|
|
|
next_operation_index_to_undo_ =
|
|
(next_operation_index_to_undo_ + 1) % max_edit_operations_;
|
|
operation_buffer_[next_operation_index_to_undo_]->Redo();
|
|
return true;
|
|
}
|
|
|
|
bool CFDE_TextEditEngine::Undo() {
|
|
if (!CanUndo())
|
|
return false;
|
|
|
|
operation_buffer_[next_operation_index_to_undo_]->Undo();
|
|
next_operation_index_to_undo_ = next_operation_index_to_undo_ == 0
|
|
? max_edit_operations_ - 1
|
|
: next_operation_index_to_undo_ - 1;
|
|
return true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::Layout() {
|
|
if (!is_dirty_)
|
|
return;
|
|
|
|
is_dirty_ = false;
|
|
RebuildPieces();
|
|
}
|
|
|
|
CFX_RectF CFDE_TextEditEngine::GetContentsBoundingBox() {
|
|
// Layout if necessary.
|
|
Layout();
|
|
return contents_bounding_box_;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetAvailableWidth(size_t width) {
|
|
if (width == available_width_)
|
|
return;
|
|
|
|
ClearOperationRecords();
|
|
|
|
available_width_ = width;
|
|
if (is_linewrap_enabled_)
|
|
text_break_.SetLineWidth(width);
|
|
if (is_comb_text_)
|
|
SetCombTextWidth();
|
|
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetHasCharacterLimit(bool limit) {
|
|
if (has_character_limit_ == limit)
|
|
return;
|
|
|
|
has_character_limit_ = limit;
|
|
character_limit_ = std::max(character_limit_, text_length_);
|
|
if (is_comb_text_)
|
|
SetCombTextWidth();
|
|
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetCharacterLimit(size_t limit) {
|
|
if (character_limit_ == limit)
|
|
return;
|
|
|
|
ClearOperationRecords();
|
|
|
|
character_limit_ = std::max(limit, text_length_);
|
|
if (is_comb_text_)
|
|
SetCombTextWidth();
|
|
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetFont(RetainPtr<CFGAS_GEFont> font) {
|
|
if (font_ == font)
|
|
return;
|
|
|
|
font_ = font;
|
|
text_break_.SetFont(font_);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
RetainPtr<CFGAS_GEFont> CFDE_TextEditEngine::GetFont() const {
|
|
return font_;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetFontSize(float size) {
|
|
if (font_size_ == size)
|
|
return;
|
|
|
|
font_size_ = size;
|
|
text_break_.SetFontSize(font_size_);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetTabWidth(float width) {
|
|
int32_t old_tab_width = text_break_.GetTabWidth();
|
|
text_break_.SetTabWidth(width);
|
|
if (old_tab_width == text_break_.GetTabWidth())
|
|
return;
|
|
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetAlignment(uint32_t alignment) {
|
|
if (alignment == character_alignment_)
|
|
return;
|
|
|
|
character_alignment_ = alignment;
|
|
text_break_.SetAlignment(alignment);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetVisibleLineCount(size_t count) {
|
|
if (visible_line_count_ == count)
|
|
return;
|
|
|
|
visible_line_count_ = std::max(static_cast<size_t>(1), count);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::EnableMultiLine(bool val) {
|
|
if (is_multiline_ == val)
|
|
return;
|
|
|
|
is_multiline_ = true;
|
|
|
|
uint32_t style = text_break_.GetLayoutStyles();
|
|
if (is_multiline_)
|
|
style &= ~FX_LAYOUTSTYLE_SingleLine;
|
|
else
|
|
style |= FX_LAYOUTSTYLE_SingleLine;
|
|
text_break_.SetLayoutStyles(style);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::EnableLineWrap(bool val) {
|
|
if (is_linewrap_enabled_ == val)
|
|
return;
|
|
|
|
is_linewrap_enabled_ = val;
|
|
text_break_.SetLineWidth(is_linewrap_enabled_ ? available_width_
|
|
: kPageWidthMax);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetCombText(bool enable) {
|
|
if (is_comb_text_ == enable)
|
|
return;
|
|
|
|
is_comb_text_ = enable;
|
|
|
|
uint32_t style = text_break_.GetLayoutStyles();
|
|
if (enable) {
|
|
style |= FX_LAYOUTSTYLE_CombText;
|
|
SetCombTextWidth();
|
|
} else {
|
|
style &= ~FX_LAYOUTSTYLE_CombText;
|
|
}
|
|
text_break_.SetLayoutStyles(style);
|
|
is_dirty_ = true;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetCombTextWidth() {
|
|
size_t width = available_width_;
|
|
if (has_character_limit_)
|
|
width /= character_limit_;
|
|
|
|
text_break_.SetCombWidth(width);
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SelectAll() {
|
|
if (text_length_ == 0)
|
|
return;
|
|
|
|
has_selection_ = true;
|
|
selection_.start_idx = 0;
|
|
selection_.count = text_length_;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::ClearSelection() {
|
|
has_selection_ = false;
|
|
selection_.start_idx = 0;
|
|
selection_.count = 0;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::SetSelection(size_t start_idx, size_t count) {
|
|
if (count == 0) {
|
|
ClearSelection();
|
|
return;
|
|
}
|
|
|
|
if (start_idx > text_length_)
|
|
return;
|
|
if (start_idx + count > text_length_)
|
|
count = text_length_ - start_idx;
|
|
|
|
has_selection_ = true;
|
|
selection_.start_idx = start_idx;
|
|
selection_.count = count;
|
|
}
|
|
|
|
WideString CFDE_TextEditEngine::GetSelectedText() const {
|
|
if (!has_selection_)
|
|
return WideString();
|
|
|
|
WideString text;
|
|
if (selection_.start_idx < gap_position_) {
|
|
// Fully on left of gap.
|
|
if (selection_.start_idx + selection_.count < gap_position_) {
|
|
text += WideStringView(content_.data() + selection_.start_idx,
|
|
selection_.count);
|
|
return text;
|
|
}
|
|
|
|
// Pre-gap text
|
|
text += WideStringView(content_.data() + selection_.start_idx,
|
|
gap_position_ - selection_.start_idx);
|
|
|
|
if (selection_.count - (gap_position_ - selection_.start_idx) > 0) {
|
|
// Post-gap text
|
|
text += WideStringView(
|
|
content_.data() + gap_position_ + gap_size_,
|
|
selection_.count - (gap_position_ - selection_.start_idx));
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
// Fully right of gap
|
|
text += WideStringView(content_.data() + gap_size_ + selection_.start_idx,
|
|
selection_.count);
|
|
return text;
|
|
}
|
|
|
|
WideString CFDE_TextEditEngine::DeleteSelectedText(
|
|
RecordOperation add_operation) {
|
|
if (!has_selection_)
|
|
return WideString();
|
|
|
|
return Delete(selection_.start_idx, selection_.count, add_operation);
|
|
}
|
|
|
|
WideString CFDE_TextEditEngine::Delete(size_t start_idx,
|
|
size_t length,
|
|
RecordOperation add_operation) {
|
|
if (start_idx >= text_length_)
|
|
return WideString();
|
|
|
|
TextChange change;
|
|
change.text.clear();
|
|
change.cancelled = false;
|
|
if (delegate_ && (add_operation != RecordOperation::kSkipRecord &&
|
|
add_operation != RecordOperation::kSkipNotify)) {
|
|
change.previous_text = GetText();
|
|
change.selection_start = start_idx;
|
|
change.selection_end = start_idx + length;
|
|
|
|
delegate_->OnTextWillChange(&change);
|
|
if (change.cancelled)
|
|
return WideString();
|
|
|
|
// Delegate may have changed the selection range.
|
|
start_idx = change.selection_start;
|
|
length = change.selection_end - change.selection_start;
|
|
|
|
// Delegate may have changed text entirely, recheck.
|
|
if (start_idx >= text_length_)
|
|
return WideString();
|
|
}
|
|
|
|
length = std::min(length, text_length_ - start_idx);
|
|
AdjustGap(start_idx + length, 0);
|
|
|
|
WideString ret;
|
|
ret += WideStringView(content_.data() + start_idx, length);
|
|
|
|
if (add_operation == RecordOperation::kInsertRecord) {
|
|
AddOperationRecord(
|
|
pdfium::MakeUnique<DeleteOperation>(this, start_idx, ret));
|
|
}
|
|
|
|
WideString previous_text = GetText();
|
|
|
|
gap_position_ = start_idx;
|
|
gap_size_ += length;
|
|
|
|
text_length_ -= length;
|
|
is_dirty_ = true;
|
|
ClearSelection();
|
|
|
|
// The JS requested the insertion of text instead of just a deletion.
|
|
if (!change.text.IsEmpty())
|
|
Insert(start_idx, change.text, RecordOperation::kSkipRecord);
|
|
|
|
if (delegate_)
|
|
delegate_->OnTextChanged();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::ReplaceSelectedText(const WideString& requested_rep) {
|
|
WideString rep = requested_rep;
|
|
|
|
if (delegate_) {
|
|
TextChange change;
|
|
change.selection_start = selection_.start_idx;
|
|
change.selection_end = selection_.start_idx + selection_.count;
|
|
change.text = rep;
|
|
change.previous_text = GetText();
|
|
change.cancelled = false;
|
|
|
|
delegate_->OnTextWillChange(&change);
|
|
if (change.cancelled)
|
|
return;
|
|
|
|
rep = change.text;
|
|
selection_.start_idx = change.selection_start;
|
|
selection_.count = change.selection_end - change.selection_start;
|
|
}
|
|
|
|
size_t start_idx = selection_.start_idx;
|
|
WideString txt = DeleteSelectedText(RecordOperation::kSkipRecord);
|
|
Insert(gap_position_, rep, RecordOperation::kSkipRecord);
|
|
|
|
AddOperationRecord(
|
|
pdfium::MakeUnique<ReplaceOperation>(this, start_idx, txt, rep));
|
|
}
|
|
|
|
WideString CFDE_TextEditEngine::GetText() const {
|
|
WideString str;
|
|
if (gap_position_ > 0)
|
|
str += WideStringView(content_.data(), gap_position_);
|
|
if (text_length_ - gap_position_ > 0) {
|
|
str += WideStringView(content_.data() + gap_position_ + gap_size_,
|
|
text_length_ - gap_position_);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetLength() const {
|
|
return text_length_;
|
|
}
|
|
|
|
wchar_t CFDE_TextEditEngine::GetChar(size_t idx) const {
|
|
if (idx >= text_length_)
|
|
return L'\0';
|
|
if (password_mode_)
|
|
return password_alias_;
|
|
|
|
return idx < gap_position_
|
|
? content_[idx]
|
|
: content_[gap_position_ + gap_size_ + (idx - gap_position_)];
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetWidthOfChar(size_t idx) {
|
|
// Recalculate the widths if necessary.
|
|
Layout();
|
|
return idx < char_widths_.size() ? char_widths_[idx] : 0;
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::GetIndexForPoint(const CFX_PointF& point) {
|
|
// Recalculate the widths if necessary.
|
|
Layout();
|
|
|
|
auto start_it = text_piece_info_.begin();
|
|
for (; start_it < text_piece_info_.end(); ++start_it) {
|
|
if (start_it->rtPiece.top <= point.y &&
|
|
point.y < start_it->rtPiece.bottom())
|
|
break;
|
|
}
|
|
// We didn't find the point before getting to the end of the text, return
|
|
// end of text.
|
|
if (start_it == text_piece_info_.end())
|
|
return text_length_;
|
|
|
|
auto end_it = start_it;
|
|
for (; end_it < text_piece_info_.end(); ++end_it) {
|
|
// We've moved past where the point should be and didn't find anything.
|
|
// Return the start of the current piece as the location.
|
|
if (end_it->rtPiece.bottom() <= point.y || point.y < end_it->rtPiece.top)
|
|
break;
|
|
}
|
|
// Make sure the end iterator is pointing to our text pieces.
|
|
if (end_it == text_piece_info_.end())
|
|
--end_it;
|
|
|
|
size_t start_it_idx = start_it->nStart;
|
|
for (; start_it <= end_it; ++start_it) {
|
|
bool piece_contains_point_vertically =
|
|
(point.y >= start_it->rtPiece.top &&
|
|
point.y < start_it->rtPiece.bottom());
|
|
if (!piece_contains_point_vertically)
|
|
continue;
|
|
|
|
std::vector<CFX_RectF> rects = GetCharRects(*start_it);
|
|
for (size_t i = 0; i < rects.size(); ++i) {
|
|
bool character_contains_point_horizontally =
|
|
(point.x >= rects[i].left && point.x < rects[i].right());
|
|
if (!character_contains_point_horizontally)
|
|
continue;
|
|
|
|
// When clicking on the left half of a character, the cursor should be
|
|
// moved before it. If the click was on the right half of that character,
|
|
// move the cursor after it.
|
|
bool closer_to_left =
|
|
(point.x - rects[i].left < rects[i].right() - point.x);
|
|
int caret_pos = (closer_to_left ? i : i + 1);
|
|
size_t pos = start_it->nStart + caret_pos;
|
|
if (pos >= text_length_)
|
|
return text_length_;
|
|
|
|
wchar_t wch = GetChar(pos);
|
|
if (wch == L'\n' || wch == L'\r') {
|
|
if (wch == L'\n' && pos > 0 && GetChar(pos - 1) == L'\r')
|
|
--pos;
|
|
return pos;
|
|
}
|
|
|
|
// TODO(dsinclair): Old code had a before flag set based on bidi?
|
|
return pos;
|
|
}
|
|
|
|
// Point is not within the horizontal range of any characters, it's
|
|
// afterwards. Return the position after the last character.
|
|
// The last line has nCount equal to the number of characters + 1 (sentinel
|
|
// character maybe?). Restrict to the text_length_ to account for that.
|
|
size_t pos = std::min(
|
|
static_cast<size_t>(start_it->nStart + start_it->nCount), text_length_);
|
|
|
|
// If the line is not the last one and it was broken right after a breaking
|
|
// whitespace (space or line break), the cursor should not be placed after
|
|
// the whitespace, but before it. If the cursor is moved after the
|
|
// whitespace, it goes to the beginning of the next line.
|
|
bool is_last_line = (std::next(start_it) == text_piece_info_.end());
|
|
if (!is_last_line && pos > 0) {
|
|
wchar_t previous_char = GetChar(pos - 1);
|
|
if (previous_char == L' ' || previous_char == L'\n' ||
|
|
previous_char == L'\r') {
|
|
--pos;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
if (start_it == text_piece_info_.end())
|
|
return start_it_idx;
|
|
if (start_it == end_it)
|
|
return start_it->nStart;
|
|
|
|
// We didn't find the point before going over all of the pieces, we want to
|
|
// return the start of the piece after the point.
|
|
return end_it->nStart;
|
|
}
|
|
|
|
std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharRects(
|
|
const FDE_TEXTEDITPIECE& piece) {
|
|
if (piece.nCount < 1)
|
|
return std::vector<CFX_RectF>();
|
|
|
|
CFX_TxtBreak::Run tr;
|
|
tr.pEdtEngine = this;
|
|
tr.iStart = piece.nStart;
|
|
tr.iLength = piece.nCount;
|
|
tr.pFont = font_;
|
|
tr.fFontSize = font_size_;
|
|
tr.dwStyles = text_break_.GetLayoutStyles();
|
|
tr.dwCharStyles = piece.dwCharStyles;
|
|
tr.pRect = &piece.rtPiece;
|
|
return text_break_.GetCharRects(&tr, false);
|
|
}
|
|
|
|
std::vector<TextCharPos> CFDE_TextEditEngine::GetDisplayPos(
|
|
const FDE_TEXTEDITPIECE& piece) {
|
|
if (piece.nCount < 1)
|
|
return std::vector<TextCharPos>();
|
|
|
|
CFX_TxtBreak::Run tr;
|
|
tr.pEdtEngine = this;
|
|
tr.iStart = piece.nStart;
|
|
tr.iLength = piece.nCount;
|
|
tr.pFont = font_;
|
|
tr.fFontSize = font_size_;
|
|
tr.dwStyles = text_break_.GetLayoutStyles();
|
|
tr.dwCharStyles = piece.dwCharStyles;
|
|
tr.pRect = &piece.rtPiece;
|
|
|
|
std::vector<TextCharPos> data(text_break_.GetDisplayPos(&tr, nullptr));
|
|
text_break_.GetDisplayPos(&tr, data.data());
|
|
return data;
|
|
}
|
|
|
|
void CFDE_TextEditEngine::RebuildPieces() {
|
|
text_break_.EndBreak(CFX_BreakType::Paragraph);
|
|
text_break_.ClearBreakPieces();
|
|
|
|
char_widths_.clear();
|
|
text_piece_info_.clear();
|
|
|
|
// Must have a font set in order to break the text.
|
|
if (text_length_ == 0 || !font_)
|
|
return;
|
|
|
|
bool initialized_bounding_box = false;
|
|
contents_bounding_box_ = CFX_RectF();
|
|
size_t current_piece_start = 0;
|
|
float current_line_start = 0;
|
|
|
|
auto iter = pdfium::MakeUnique<CFDE_TextEditEngine::Iterator>(this);
|
|
while (!iter->IsEOF(false)) {
|
|
iter->Next(false);
|
|
|
|
CFX_BreakType break_status = text_break_.AppendChar(
|
|
password_mode_ ? password_alias_ : iter->GetChar());
|
|
if (iter->IsEOF(false) && CFX_BreakTypeNoneOrPiece(break_status))
|
|
break_status = text_break_.EndBreak(CFX_BreakType::Paragraph);
|
|
|
|
if (CFX_BreakTypeNoneOrPiece(break_status))
|
|
continue;
|
|
int32_t piece_count = text_break_.CountBreakPieces();
|
|
for (int32_t i = 0; i < piece_count; ++i) {
|
|
const CFX_BreakPiece* piece = text_break_.GetBreakPieceUnstable(i);
|
|
|
|
FDE_TEXTEDITPIECE txtEdtPiece;
|
|
txtEdtPiece.rtPiece.left = piece->m_iStartPos / 20000.0f;
|
|
txtEdtPiece.rtPiece.top = current_line_start;
|
|
txtEdtPiece.rtPiece.width = piece->m_iWidth / 20000.0f;
|
|
txtEdtPiece.rtPiece.height = line_spacing_;
|
|
txtEdtPiece.nStart = current_piece_start;
|
|
txtEdtPiece.nCount = piece->GetLength();
|
|
txtEdtPiece.nBidiLevel = piece->m_iBidiLevel;
|
|
txtEdtPiece.dwCharStyles = piece->m_dwCharStyles;
|
|
if (FX_IsOdd(piece->m_iBidiLevel))
|
|
txtEdtPiece.dwCharStyles |= FX_TXTCHARSTYLE_OddBidiLevel;
|
|
|
|
text_piece_info_.push_back(txtEdtPiece);
|
|
|
|
if (initialized_bounding_box) {
|
|
contents_bounding_box_.Union(txtEdtPiece.rtPiece);
|
|
} else {
|
|
contents_bounding_box_ = txtEdtPiece.rtPiece;
|
|
initialized_bounding_box = true;
|
|
}
|
|
|
|
current_piece_start += txtEdtPiece.nCount;
|
|
for (int32_t k = 0; k < txtEdtPiece.nCount; ++k)
|
|
char_widths_.push_back(piece->GetChar(k)->m_iCharWidth);
|
|
}
|
|
|
|
current_line_start += line_spacing_;
|
|
text_break_.ClearBreakPieces();
|
|
}
|
|
|
|
float delta = 0.0;
|
|
bool bounds_smaller = contents_bounding_box_.width < available_width_;
|
|
if (IsAlignedRight() && bounds_smaller) {
|
|
delta = available_width_ - contents_bounding_box_.width;
|
|
} else if (IsAlignedCenter() && bounds_smaller) {
|
|
delta = (available_width_ - contents_bounding_box_.width) / 2.0f;
|
|
}
|
|
|
|
if (delta != 0.0) {
|
|
float offset = delta - contents_bounding_box_.left;
|
|
for (auto& info : text_piece_info_)
|
|
info.rtPiece.Offset(offset, 0.0f);
|
|
contents_bounding_box_.Offset(offset, 0.0f);
|
|
}
|
|
|
|
// Shrink the last piece down to the font_size.
|
|
contents_bounding_box_.height -= line_spacing_ - font_size_;
|
|
text_piece_info_.back().rtPiece.height = font_size_;
|
|
}
|
|
|
|
std::pair<int32_t, CFX_RectF> CFDE_TextEditEngine::GetCharacterInfo(
|
|
int32_t start_idx) {
|
|
ASSERT(start_idx >= 0);
|
|
ASSERT(static_cast<size_t>(start_idx) <= text_length_);
|
|
|
|
// Make sure the current available data is fresh.
|
|
Layout();
|
|
|
|
auto it = text_piece_info_.begin();
|
|
for (; it != text_piece_info_.end(); ++it) {
|
|
if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount)
|
|
break;
|
|
}
|
|
if (it == text_piece_info_.end()) {
|
|
NOTREACHED();
|
|
return {0, CFX_RectF()};
|
|
}
|
|
|
|
return {it->nBidiLevel, GetCharRects(*it)[start_idx - it->nStart]};
|
|
}
|
|
|
|
std::vector<CFX_RectF> CFDE_TextEditEngine::GetCharacterRectsInRange(
|
|
int32_t start_idx,
|
|
int32_t count) {
|
|
// Make sure the current available data is fresh.
|
|
Layout();
|
|
|
|
auto it = text_piece_info_.begin();
|
|
for (; it != text_piece_info_.end(); ++it) {
|
|
if (it->nStart <= start_idx && start_idx < it->nStart + it->nCount)
|
|
break;
|
|
}
|
|
if (it == text_piece_info_.end())
|
|
return std::vector<CFX_RectF>();
|
|
|
|
int32_t end_idx = start_idx + count - 1;
|
|
std::vector<CFX_RectF> rects;
|
|
while (it != text_piece_info_.end()) {
|
|
// If we end inside the current piece, extract what we need and we're done.
|
|
if (it->nStart <= end_idx && end_idx < it->nStart + it->nCount) {
|
|
std::vector<CFX_RectF> arr = GetCharRects(*it);
|
|
CFX_RectF piece = arr[0];
|
|
piece.Union(arr[end_idx - it->nStart]);
|
|
rects.push_back(piece);
|
|
break;
|
|
}
|
|
rects.push_back(it->rtPiece);
|
|
++it;
|
|
}
|
|
|
|
return rects;
|
|
}
|
|
|
|
std::pair<size_t, size_t> CFDE_TextEditEngine::BoundsForWordAt(
|
|
size_t idx) const {
|
|
if (idx > text_length_)
|
|
return {0, 0};
|
|
|
|
CFDE_TextEditEngine::Iterator iter(this);
|
|
iter.SetAt(idx);
|
|
|
|
size_t start_idx = iter.FindNextBreakPos(true);
|
|
size_t end_idx = iter.FindNextBreakPos(false);
|
|
return {start_idx, end_idx - start_idx + 1};
|
|
}
|
|
|
|
CFDE_TextEditEngine::Iterator::Iterator(const CFDE_TextEditEngine* engine)
|
|
: engine_(engine), current_position_(-1) {}
|
|
|
|
CFDE_TextEditEngine::Iterator::~Iterator() {}
|
|
|
|
void CFDE_TextEditEngine::Iterator::Next(bool bPrev) {
|
|
if (bPrev && current_position_ == -1)
|
|
return;
|
|
if (!bPrev && current_position_ > -1 &&
|
|
static_cast<size_t>(current_position_) == engine_->GetLength()) {
|
|
return;
|
|
}
|
|
|
|
if (bPrev)
|
|
--current_position_;
|
|
else
|
|
++current_position_;
|
|
}
|
|
|
|
wchar_t CFDE_TextEditEngine::Iterator::GetChar() const {
|
|
return engine_->GetChar(current_position_);
|
|
}
|
|
|
|
void CFDE_TextEditEngine::Iterator::SetAt(size_t nIndex) {
|
|
if (static_cast<size_t>(nIndex) >= engine_->GetLength())
|
|
current_position_ = engine_->GetLength();
|
|
else
|
|
current_position_ = nIndex;
|
|
}
|
|
|
|
bool CFDE_TextEditEngine::Iterator::IsEOF(bool bPrev) const {
|
|
return bPrev ? current_position_ == -1
|
|
: current_position_ > -1 &&
|
|
static_cast<size_t>(current_position_) ==
|
|
engine_->GetLength();
|
|
}
|
|
|
|
size_t CFDE_TextEditEngine::Iterator::FindNextBreakPos(bool bPrev) {
|
|
if (IsEOF(bPrev))
|
|
return current_position_ > -1 ? current_position_ : 0;
|
|
|
|
WordBreakProperty ePreType = WordBreakProperty::kNone;
|
|
if (!IsEOF(!bPrev)) {
|
|
Next(!bPrev);
|
|
ePreType = FX_GetWordBreakProperty(GetChar());
|
|
Next(bPrev);
|
|
}
|
|
|
|
WordBreakProperty eCurType = FX_GetWordBreakProperty(GetChar());
|
|
bool bFirst = true;
|
|
while (!IsEOF(bPrev)) {
|
|
Next(bPrev);
|
|
|
|
WordBreakProperty eNextType = FX_GetWordBreakProperty(GetChar());
|
|
bool wBreak = FX_CheckStateChangeForWordBreak(eCurType, eNextType);
|
|
if (wBreak) {
|
|
if (IsEOF(bPrev)) {
|
|
Next(!bPrev);
|
|
break;
|
|
}
|
|
if (bFirst) {
|
|
int32_t nFlags = GetBreakFlagsFor(eCurType, eNextType);
|
|
if (nFlags > 0) {
|
|
if (BreakFlagsChanged(nFlags, ePreType)) {
|
|
Next(!bPrev);
|
|
break;
|
|
}
|
|
Next(bPrev);
|
|
wBreak = false;
|
|
}
|
|
}
|
|
if (wBreak) {
|
|
int32_t nFlags = GetBreakFlagsFor(eNextType, eCurType);
|
|
if (nFlags <= 0) {
|
|
Next(!bPrev);
|
|
break;
|
|
}
|
|
|
|
Next(bPrev);
|
|
eNextType = FX_GetWordBreakProperty(GetChar());
|
|
if (BreakFlagsChanged(nFlags, eNextType)) {
|
|
Next(!bPrev);
|
|
Next(!bPrev);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
eCurType = eNextType;
|
|
bFirst = false;
|
|
}
|
|
return current_position_ > -1 ? current_position_ : 0;
|
|
}
|