Skip to content

Commit

Permalink
Implement Uniscribe RenderText for Windows.
Browse files Browse the repository at this point in the history
Follow the I18N recommendations for BiDi text editing.
Visual cursor movement and logical selection over BiDi text.
Cleanup some common RenderText code and interfaces.
Fixup TextfieldExample for views_examples.

Known issues:
Word breaking is not well implemented.
Font sizes and vertical alignments are slightly off.
Text styles break runs (colors can affect glyph shaping).
Composition/selection ranges aren't stylized.

BUG=90426
TEST=--use-pure-views text editing

Review URL: http://codereview.chromium.org/7458014

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@98785 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
msw@chromium.org committed Aug 30, 2011
1 parent 139212d commit 0d71760
Show file tree
Hide file tree
Showing 8 changed files with 892 additions and 94 deletions.
142 changes: 98 additions & 44 deletions ui/gfx/render_text.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,14 +164,8 @@ void RenderText::SetText(const string16& text) {
cached_bounds_and_offset_valid_ = false;
}

void RenderText::SetSelectionModel(const SelectionModel& sel) {
size_t start = sel.selection_start();
size_t end = sel.selection_end();
selection_model_.set_selection_start(std::min(start, text().length()));
selection_model_.set_selection_end(std::min(end, text().length()));
selection_model_.set_caret_pos(std::min(sel.caret_pos(), text().length()));
selection_model_.set_caret_placement(sel.caret_placement());

void RenderText::ToggleInsertMode() {
insert_mode_ = !insert_mode_;
cached_bounds_and_offset_valid_ = false;
}

Expand All @@ -184,13 +178,8 @@ size_t RenderText::GetCursorPosition() const {
return selection_model_.selection_end();
}

void RenderText::SetCursorPosition(const size_t position) {
SelectionModel sel(selection_model());
sel.set_selection_start(position);
sel.set_selection_end(position);
sel.set_caret_pos(GetIndexOfPreviousGrapheme(position));
sel.set_caret_placement(SelectionModel::TRAILING);
SetSelectionModel(sel);
void RenderText::SetCursorPosition(size_t position) {
MoveCursorTo(position, false);
}

void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
Expand Down Expand Up @@ -237,9 +226,26 @@ void RenderText::MoveCursorRight(BreakType break_type, bool select) {
MoveCursorTo(position);
}

bool RenderText::MoveCursorTo(const SelectionModel& selection) {
bool changed = !selection.Equals(selection_model_);
SetSelectionModel(selection);
bool RenderText::MoveCursorTo(const SelectionModel& selection_model) {
SelectionModel sel(selection_model);
size_t text_length = text().length();
// Enforce valid selection model components.
if (sel.selection_start() > text_length)
sel.set_selection_start(text_length);
if (sel.selection_end() > text_length)
sel.set_selection_end(text_length);
// The current model only supports caret positions at valid character indices.
if (text_length == 0) {
sel.set_caret_pos(0);
sel.set_caret_placement(SelectionModel::LEADING);
} else if (sel.caret_pos() >= text_length) {
SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
LeftEndSelectionModel() : RightEndSelectionModel();
sel.set_caret_pos(end.caret_pos());
sel.set_caret_placement(end.caret_placement());
}
bool changed = !sel.Equals(selection_model_);
SetSelectionModel(sel);
return changed;
}

Expand All @@ -251,6 +257,8 @@ bool RenderText::MoveCursorTo(const Point& point, bool select) {
}

bool RenderText::IsPointInSelection(const Point& point) {
if (EmptySelection())
return false;
// TODO(xji): should this check whether the point is inside the visual
// selection bounds? In case of "abcFED", if "ED" is selected, |point| points
// to the right half of 'c', is the point in selection?
Expand All @@ -265,8 +273,8 @@ void RenderText::ClearSelection() {
}

void RenderText::SelectAll() {
SelectionModel sel(0, text().length(),
text().length(), SelectionModel::LEADING);
SelectionModel sel(RightEndSelectionModel());
sel.set_selection_start(LeftEndSelectionModel().selection_start());
SetSelectionModel(sel);
}

Expand Down Expand Up @@ -308,12 +316,8 @@ void RenderText::SelectWord() {
break;
}

SelectionModel sel(selection_model());
sel.set_selection_start(selection_start);
sel.set_selection_end(cursor_position);
sel.set_caret_pos(GetIndexOfPreviousGrapheme(cursor_position));
sel.set_caret_placement(SelectionModel::TRAILING);
SetSelectionModel(sel);
MoveCursorTo(selection_start, false);
MoveCursorTo(cursor_position, true);
}

const ui::Range& RenderText::GetCompositionRange() const {
Expand Down Expand Up @@ -350,8 +354,8 @@ void RenderText::ApplyDefaultStyle() {
}

base::i18n::TextDirection RenderText::GetTextDirection() const {
// TODO(msw): Bidi implementation, intended to replace the functionality added
// in crrev.com/91881 (discussed in codereview.chromium.org/7324011).
if (base::i18n::IsRTL())
return base::i18n::RIGHT_TO_LEFT;
return base::i18n::LEFT_TO_RIGHT;
}

Expand Down Expand Up @@ -443,20 +447,6 @@ SelectionModel RenderText::FindCursorPosition(const Point& point) {
return SelectionModel(left_pos);
}

std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) {
size_t start = std::min(from, to);
size_t end = std::max(from, to);
const Font& font = default_style_.font;
int start_x = font.GetStringWidth(text().substr(0, start));
int end_x = font.GetStringWidth(text().substr(0, end));
Rect rect(start_x, 0, end_x - start_x, font.GetHeight());
rect.Offset(display_rect_.origin());
rect.Offset(GetUpdatedDisplayOffset());
// Center the rect vertically in |display_rect_|.
rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2));
return std::vector<Rect>(1, rect);
}

Rect RenderText::GetCursorBounds(const SelectionModel& selection,
bool insert_mode) {
size_t from = selection.selection_end();
Expand Down Expand Up @@ -549,9 +539,35 @@ SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current,
return SelectionModel(pos, pos, SelectionModel::LEADING);
}

SelectionModel RenderText::LeftEndSelectionModel() {
return SelectionModel(0, 0, SelectionModel::LEADING);
}

SelectionModel RenderText::RightEndSelectionModel() {
size_t cursor = text().length();
size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
SelectionModel::LEADING : SelectionModel::TRAILING;
return SelectionModel(cursor, caret_pos, placement);
}

size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
// TODO(msw): Handle complex script.
return std::max(static_cast<int>(position - 1), static_cast<int>(0));
return std::max(static_cast<long>(position - 1), static_cast<long>(0));
}

std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) {
size_t start = std::min(from, to);
size_t end = std::max(from, to);
const Font& font = default_style_.font;
int start_x = font.GetStringWidth(text().substr(0, start));
int end_x = font.GetStringWidth(text().substr(0, end));
Rect rect(start_x, 0, end_x - start_x, font.GetHeight());
rect.Offset(display_rect_.origin());
rect.Offset(GetUpdatedDisplayOffset());
// Center the rect vertically in |display_rect_|.
rect.Offset(Point(0, (display_rect_.height() - rect.height()) / 2));
return std::vector<Rect>(1, rect);
}

void RenderText::ApplyCompositionAndSelectionStyles(
Expand All @@ -576,6 +592,45 @@ void RenderText::ApplyCompositionAndSelectionStyles(
}
}

Point RenderText::ToTextPoint(const Point& point) {
Point p(point.Subtract(display_rect().origin()));
p = p.Subtract(GetUpdatedDisplayOffset());
if (base::i18n::IsRTL())
p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
return p;
}

Point RenderText::ToViewPoint(const Point& point) {
Point p(point.Add(display_rect().origin()));
p = p.Add(GetUpdatedDisplayOffset());
if (base::i18n::IsRTL())
p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
return p;
}

void RenderText::SetSelectionModel(const SelectionModel& selection_model) {
DCHECK_LE(selection_model.selection_start(), text().length());
selection_model_.set_selection_start(selection_model.selection_start());
DCHECK_LE(selection_model.selection_end(), text().length());
selection_model_.set_selection_end(selection_model.selection_end());
DCHECK_LT(selection_model.caret_pos(),
std::max(text().length(), static_cast<size_t>(1)));
selection_model_.set_caret_pos(selection_model.caret_pos());
selection_model_.set_caret_placement(selection_model.caret_placement());

cached_bounds_and_offset_valid_ = false;
}

void RenderText::MoveCursorTo(size_t position, bool select) {
size_t cursor = std::min(position, text().length());
size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
SelectionModel::LEADING : SelectionModel::TRAILING;
size_t selection_start = select ? GetSelectionStart() : cursor;
SelectionModel sel(selection_start, cursor, caret_pos, placement);
SetSelectionModel(sel);
}

bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) {
return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) ||
(!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos]));
Expand All @@ -589,7 +644,6 @@ void RenderText::UpdateCachedBoundsAndOffset() {
// function will set |cursor_bounds_| and |display_offset_| to correct values.
cached_bounds_and_offset_valid_ = true;
cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
cursor_bounds_.set_width(std::max(cursor_bounds_.width(), 1));
// Update |display_offset_| to ensure the current cursor is visible.
int display_width = display_rect_.width();
int string_width = GetStringWidth();
Expand Down
52 changes: 34 additions & 18 deletions ui/gfx/render_text.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,12 @@ class UI_EXPORT RenderText {
virtual void SetText(const string16& text);

const SelectionModel& selection_model() const { return selection_model_; }
void SetSelectionModel(const SelectionModel& sel);

bool cursor_visible() const { return cursor_visible_; }
void set_cursor_visible(bool visible) { cursor_visible_ = visible; }

bool insert_mode() const { return insert_mode_; }
void toggle_insert_mode() { insert_mode_ = !insert_mode_; }
void ToggleInsertMode();

bool focused() const { return focused_; }
void set_focused(bool focused) { focused_ = focused; }
Expand All @@ -176,24 +175,26 @@ class UI_EXPORT RenderText {
// edits take place, and doesn't necessarily correspond to
// SelectionModel::caret_pos.
size_t GetCursorPosition() const;
void SetCursorPosition(const size_t position);
void SetCursorPosition(size_t position);

void SetCaretPlacement(SelectionModel::CaretPlacement placement) {
selection_model_.set_caret_placement(placement);
}

// Moves the cursor left or right. Cursor movement is visual, meaning that
// left and right are relative to screen, not the directionality of the text.
// If |select| is false, the selection range is emptied at the new position.
// If |select| is false, the selection start is moved to the same position.
void MoveCursorLeft(BreakType break_type, bool select);
void MoveCursorRight(BreakType break_type, bool select);

// Set the selection_model_ to the value of |selection|.
// The selection model components are modified if invalid.
// Returns true if the cursor position or selection range changed.
bool MoveCursorTo(const SelectionModel& selection);
bool MoveCursorTo(const SelectionModel& selection_model);

// Move the cursor to the position associated with the clicked point.
// If |select| is false, the selection range is emptied at the new position.
// If |select| is false, the selection start is moved to the same position.
// Returns true if the cursor position or selection range changed.
bool MoveCursorTo(const Point& point, bool select);

size_t GetSelectionStart() const {
Expand Down Expand Up @@ -236,13 +237,6 @@ class UI_EXPORT RenderText {
// Gets the SelectionModel from a visual point in local coordinates.
virtual SelectionModel FindCursorPosition(const Point& point);

// Get the visual bounds containing the logical substring within |from| to
// |to|. These bounds could be visually discontinuous if the substring is
// split by a LTR/RTL level change. These bounds are in local coordinates, but
// may be outside the visible region if the text is longer than the textfield.
// Subsequent text, cursor, or bounds changes may invalidate returned values.
virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to);

// Get the visual bounds of a cursor at |selection|. These bounds typically
// represent a vertical line, but if |insert_mode| is true they contain the
// bounds of the associated glyph. These bounds are in local coordinates, but
Expand Down Expand Up @@ -274,13 +268,31 @@ class UI_EXPORT RenderText {
virtual SelectionModel GetRightSelectionModel(const SelectionModel& current,
BreakType break_type);

// Get the SelectionModels corresponding to visual text ends.
// The returned value represents a cursor/caret position without a selection.
virtual SelectionModel LeftEndSelectionModel();
virtual SelectionModel RightEndSelectionModel();

// Get the logical index of the grapheme preceeding the argument |position|.
virtual size_t GetIndexOfPreviousGrapheme(size_t position);

// Get the visual bounds containing the logical substring within |from| to
// |to|. These bounds could be visually discontinuous if the substring is
// split by a LTR/RTL level change. These bounds are in local coordinates, but
// may be outside the visible region if the text is longer than the textfield.
// Subsequent text, cursor, or bounds changes may invalidate returned values.
// TODO(msw) Re-evaluate this function's necessity and signature.
virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to);

// Apply composition style (underline) to composition range and selection
// style (foreground) to selection range.
void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const;

// Convert points from the text space to the view space and back.
// Handles the display area, display offset, and the application LTR/RTL mode.
Point ToTextPoint(const Point& point);
Point ToViewPoint(const Point& point);

private:
friend class RenderTextTest;

Expand All @@ -289,8 +301,13 @@ class UI_EXPORT RenderText {
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust);

// Clear out |style_ranges_|.
void ClearStyleRanges();
// Sets the selection model, the argument is assumed to be valid.
void SetSelectionModel(const SelectionModel& selection_model);

// Set the cursor to |position|, with the caret trailing the previous
// grapheme, or if there is no previous grapheme, leading the cursor position.
// If |select| is false, the selection start is moved to the same position.
void MoveCursorTo(size_t position, bool select);

bool IsPositionAtWordSelectionBoundary(size_t pos);

Expand Down Expand Up @@ -328,9 +345,8 @@ class UI_EXPORT RenderText {
// Get this point with GetUpdatedDisplayOffset (or risk using a stale value).
Point display_offset_;

// The cached bounds and offset are invalidated by operations such as
// SetCursorPosition, SetSelectionModel, Font related style change, and other
// operations that adjust the visible text bounds.
// The cached bounds and offset are invalidated by changes to the cursor,
// selection, font, and other operations that adjust the visible text bounds.
bool cached_bounds_and_offset_valid_;

DISALLOW_COPY_AND_ASSIGN(RenderText);
Expand Down
Loading

0 comments on commit 0d71760

Please sign in to comment.