Commit 0d717603 authored by msw@chromium.org's avatar msw@chromium.org

Implement Uniscribe RenderText for Windows.

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
parent 139212d9
......@@ -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;
}
......@@ -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) {
......@@ -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;
}
......@@ -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?
......@@ -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);
}
......@@ -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 {
......@@ -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;
}
......@@ -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();
......@@ -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(
......@@ -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]));
......@@ -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();
......
......@@ -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; }
......@@ -176,7 +175,7 @@ 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);
......@@ -184,16 +183,18 @@ class UI_EXPORT RenderText {
// 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 {
......@@ -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
......@@ -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;
......@@ -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);
......@@ -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);
......
......@@ -4,13 +4,639 @@
#include "ui/gfx/render_text_win.h"
#include "base/i18n/break_iterator.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/string_util.h"
#include "skia/ext/skia_utils_win.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/canvas_skia.h"
#include "third_party/skia/include/core/SkTypeface.h"
namespace {
// The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes.
// TODO(msw): Review memory use/failure? Max string length? Alternate approach?
const int kGuessItems = 100;
const int kMaxItems = 10000;
// The maximum supported number of Uniscribe glyphs; a glyph is 1 word.
// TODO(msw): Review memory use/failure? Max string length? Alternate approach?
const int kMaxGlyphs = 100000;
// TODO(msw): Solve gfx/Uniscribe/Skia text size unit conversion issues.
const float kSkiaFontScale = 1.375;
} // namespace
namespace gfx {
namespace internal {
TextRun::TextRun()
: strike(false),
width(0),
preceding_run_widths(0),
glyph_count(0) {
}
} // namespace internal
RenderTextWin::RenderTextWin()
: RenderText() {
: RenderText(),
script_control_(),
script_state_(),
script_cache_(NULL),
string_width_(0) {
// Omitting default constructors for script_* would leave POD uninitialized.
HRESULT hr = 0;
// TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message.
// TODO(msw): Use Chrome/profile locale/language settings?
hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_);
DCHECK(SUCCEEDED(hr));
hr = ScriptApplyDigitSubstitution(&digit_substitute_,
&script_control_,
&script_state_);
DCHECK(SUCCEEDED(hr));
script_control_.fMergeNeutralItems = true;
MoveCursorTo(LeftEndSelectionModel());
}
RenderTextWin::~RenderTextWin() {
ScriptFreeCache(&script_cache_);
STLDeleteContainerPointers(runs_.begin(), runs_.end());
}
void RenderTextWin::SetText(const string16& text) {
// TODO(msw): Skip complex processing if ScriptIsComplex returns false.
RenderText::SetText(text);
ItemizeLogicalText();
LayoutVisualText(CreateCompatibleDC(NULL));
}
void RenderTextWin::SetDisplayRect(const Rect& r) {
RenderText::SetDisplayRect(r);
ItemizeLogicalText();
LayoutVisualText(CreateCompatibleDC(NULL));
}
void RenderTextWin::ApplyStyleRange(StyleRange style_range) {
RenderText::ApplyStyleRange(style_range);
ItemizeLogicalText();
LayoutVisualText(CreateCompatibleDC(NULL));
}
void RenderTextWin::ApplyDefaultStyle() {
RenderText::ApplyDefaultStyle();
ItemizeLogicalText();
LayoutVisualText(CreateCompatibleDC(NULL));
}
int RenderTextWin::GetStringWidth() {
return string_width_;
}
void RenderTextWin::Draw(Canvas* canvas) {
skia::ScopedPlatformPaint scoped_platform_paint(canvas->AsCanvasSkia());
HDC hdc = scoped_platform_paint.GetPlatformSurface();
int saved_dc = SaveDC(hdc);
DrawSelection(canvas);
DrawVisualText(canvas);
DrawCursor(canvas);
RestoreDC(hdc, saved_dc);
}
SelectionModel RenderTextWin::FindCursorPosition(const Point& point) {
if (text().empty())
return SelectionModel();
// Find the run that contains the point and adjust the argument location.
Point p(ToTextPoint(point));
size_t run_index = GetRunContainingPoint(p);
if (run_index == runs_.size())
return (p.x() < 0) ? LeftEndSelectionModel() : RightEndSelectionModel();
internal::TextRun* run = runs_[run_index];
int position = 0, trailing = 0;
HRESULT hr = ScriptXtoCP(p.x() - run->preceding_run_widths,
run->range.length(),
run->glyph_count,
run->logical_clusters.get(),
run->visible_attributes.get(),
run->advance_widths.get(),
&(run->script_analysis),
&position,
&trailing);
DCHECK(SUCCEEDED(hr));
position += run->range.start();
size_t cursor = position + trailing;
DCHECK_GE(cursor, 0U);
DCHECK_LE(cursor, text().length());
return SelectionModel(cursor, position,
(trailing > 0) ? SelectionModel::TRAILING : SelectionModel::LEADING);
}
Rect RenderTextWin::GetCursorBounds(const SelectionModel& selection,
bool insert_mode) {
// Highlight the logical cursor (selection end) when not in insert mode.
size_t pos = insert_mode ? selection.caret_pos() : selection.selection_end();
size_t run_index = GetRunContainingPosition(pos);
internal::TextRun* run = run_index == runs_.size() ? NULL : runs_[run_index];
int start_x = 0, end_x = 0;
if (run) {
HRESULT hr = 0;
hr = ScriptCPtoX(pos - run->range.start(),
false,
run->range.length(),
run->glyph_count,
run->logical_clusters.get(),
run->visible_attributes.get(),
run->advance_widths.get(),
&(run->script_analysis),
&start_x);
DCHECK(SUCCEEDED(hr));
hr = ScriptCPtoX(pos - run->range.start(),
true,
run->range.length(),
run->glyph_count,
run->logical_clusters.get(),
run->visible_attributes.get(),
run->advance_widths.get(),
&(run->script_analysis),
&end_x);
DCHECK(SUCCEEDED(hr));
}
// TODO(msw): Use the last visual run's font instead of the default font?
int height = run ? run->font.GetHeight() : default_style().font.GetHeight();
Rect rect(std::min(start_x, end_x), 0, std::abs(end_x - start_x), height);
// Offset to the run start or the right/left end for an out of bounds index.
// Also center the rect vertically in the display area.
int text_end_offset = base::i18n::IsRTL() ? 0 : GetStringWidth();
rect.Offset((run ? run->preceding_run_widths : text_end_offset),
(display_rect().height() - rect.height()) / 2);
// Adjust for leading/trailing in insert mode.
if (insert_mode && run) {
bool leading = selection.caret_placement() == SelectionModel::LEADING;
// Adjust the x value for right-side placement.
if (run->script_analysis.fRTL == leading)
rect.set_x(rect.right());
rect.set_width(0);
}
rect.set_origin(ToViewPoint(rect.origin()));
return rect;
}
SelectionModel RenderTextWin::GetLeftSelectionModel(
const SelectionModel& selection,
BreakType break_type) {
if (break_type == LINE_BREAK || text().empty())
return LeftEndSelectionModel();
if (break_type == CHARACTER_BREAK)
return LeftSelectionModel(selection);
// TODO(msw): Implement word breaking.
return RenderText::GetLeftSelectionModel(selection, break_type);
}
SelectionModel RenderTextWin::GetRightSelectionModel(
const SelectionModel& selection,
BreakType break_type) {
if (break_type == LINE_BREAK || text().empty())
return RightEndSelectionModel();
if (break_type == CHARACTER_BREAK)
return RightSelectionModel(selection);
// TODO(msw): Implement word breaking.
return RenderText::GetRightSelectionModel(selection, break_type);
}
SelectionModel RenderTextWin::LeftEndSelectionModel() {
if (text().empty())
return SelectionModel(0, 0, SelectionModel::LEADING);
size_t cursor = base::i18n::IsRTL() ? text().length() : 0;
internal::TextRun* run = runs_[visual_to_logical_[0]];
bool rtl = run->script_analysis.fRTL;
size_t caret = rtl ? run->range.end() - 1 : run->range.start();
SelectionModel::CaretPlacement placement =
rtl ? SelectionModel::TRAILING : SelectionModel::LEADING;
return SelectionModel(cursor, caret, placement);
}
SelectionModel RenderTextWin::RightEndSelectionModel() {
if (text().empty())
return SelectionModel(0, 0, SelectionModel::LEADING);
size_t cursor = base::i18n::IsRTL() ? 0 : text().length();
internal::TextRun* run = runs_[visual_to_logical_[runs_.size() - 1]];
bool rtl = run->script_analysis.fRTL;
size_t caret = rtl ? run->range.start() : run->range.end() - 1;
SelectionModel::CaretPlacement placement =
rtl ? SelectionModel::LEADING : SelectionModel::TRAILING;
return SelectionModel(cursor, caret, placement);
}
size_t RenderTextWin::GetIndexOfPreviousGrapheme(size_t position) {
return IndexOfAdjacentGrapheme(position, false);
}
std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) {
ui::Range range(from, to);
DCHECK(ui::Range(0, text().length()).Contains(range));
Point display_offset(GetUpdatedDisplayOffset());
std::vector<Rect> bounds;
HRESULT hr = 0;
// Add a Rect for each run/selection intersection.
// TODO(msw): The bounds should probably not always be leading the range ends.
for (size_t i = 0; i < runs_.size(); ++i) {
internal::TextRun* run = runs_[visual_to_logical_[i]];
ui::Range intersection = run->range.Intersect(range);
if (intersection.IsValid()) {
DCHECK(!intersection.is_reversed());
int start_offset = 0;
hr = ScriptCPtoX(intersection.start() - run->range.start(),
false,
run->range.length(),
run->glyph_count,
run->logical_clusters.get(),
run->visible_attributes.get(),
run->advance_widths.get(),
&(run->script_analysis),
&start_offset);
DCHECK(SUCCEEDED(hr));
int end_offset = 0;
hr = ScriptCPtoX(intersection.end() - run->range.start(),
false,
run->range.length(),
run->glyph_count,
run->logical_clusters.get(),
run->visible_attributes.get(),
run->advance_widths.get(),
&(run->script_analysis),
&end_offset);
DCHECK(SUCCEEDED(hr));
if (start_offset > end_offset)
std::swap(start_offset, end_offset);
Rect rect(run->preceding_run_widths + start_offset, 0,
end_offset - start_offset, run->font.GetHeight());
// Center the rect vertically in the display area.
rect.Offset(0, (display_rect().height() - rect.height()) / 2);
rect.set_origin(ToViewPoint(rect.origin()));
// Union this with the last rect if they're adjacent.
if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) {
rect = rect.Union(bounds.back());
bounds.pop_back();
}
bounds.push_back(rect);
}
}
return bounds;
}
void RenderTextWin::ItemizeLogicalText() {
text_is_dirty_ = false;
STLDeleteContainerPointers(runs_.begin(), runs_.end());
runs_.clear();
if (text().empty())
return;
const wchar_t* raw_text = text().c_str();
const int text_length = text().length();
HRESULT hr = E_OUTOFMEMORY;
int script_items_count = 0;
scoped_array<SCRIPT_ITEM> script_items;
for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) {
// Derive the array of Uniscribe script items from the logical text.
// ScriptItemize always adds a terminal array item so that the length of the
// last item can be derived from the terminal SCRIPT_ITEM::iCharPos.
script_items.reset(new SCRIPT_ITEM[n]);
hr = ScriptItemize(raw_text,
text_length,
n - 1,
&script_control_,
&script_state_,
script_items.get(),
&script_items_count);
}
DCHECK(SUCCEEDED(hr));
if (script_items_count <= 0)
return;
// Build the list of runs, merge font/underline styles.
// TODO(msw): Only break for font changes, not color etc. See TextRun comment.
// TODO(msw): Apply the overriding selection and composition styles.
StyleRanges::const_iterator style = style_ranges().begin();
SCRIPT_ITEM* script_item = script_items.get();
for (int run_break = 0; run_break < text_length;) {
internal::TextRun* run = new internal::TextRun();
run->range.set_start(run_break);
run->font = !style->underline ? style->font :
style->font.DeriveFont(0, style->font.GetStyle() | Font::UNDERLINED);
run->foreground = style->foreground;
run->strike = style->strike;
run->script_analysis = script_item->a;
// Find the range end and advance the structures as needed.
int script_item_end = (script_item + 1)->iCharPos;
int style_range_end = style->range.end();
run_break = std::min(script_item_end, style_range_end);
if (script_item_end <= style_range_end)
script_item++;
if (script_item_end >= style_range_end)
style++;
run->range.set_end(run_break);
runs_.push_back(run);
}
}
void RenderTextWin::LayoutVisualText(HDC hdc) {
HRESULT hr = 0;
std::vector<internal::TextRun*>::const_iterator run_iter;
for (run_iter = runs_.begin(); run_iter < runs_.end(); ++run_iter) {
internal::TextRun* run = *run_iter;
int run_length = run->range.length();
string16 run_string(text().substr(run->range.start(), run_length));
const wchar_t* run_text = run_string.c_str();
// Select the font desired for glyph generation
SelectObject(hdc, run->font.GetNativeFont());
run->logical_clusters.reset(new WORD[run_length]);
run->glyph_count = 0;
hr = E_OUTOFMEMORY;
// kGuess comes from: http://msdn.microsoft.com/en-us/library/dd368564.aspx
const int kGuess = static_cast<int>(1.5 * run_length + 16);
for (size_t n = kGuess; hr == E_OUTOFMEMORY && n < kMaxGlyphs; n *= 2) {
run->glyphs.reset(new WORD[n]);
run->visible_attributes.reset(new SCRIPT_VISATTR[n]);
hr = ScriptShape(hdc,
&script_cache_,
run_text,
run_length,
n,
&(run->script_analysis),
run->glyphs.get(),
run->logical_clusters.get(),
run->visible_attributes.get(),
&(run->glyph_count));
}
DCHECK(SUCCEEDED(hr));
if (run->glyph_count > 0) {
run->advance_widths.reset(new int[run->glyph_count]);
run->offsets.reset(new GOFFSET[run->glyph_count]);
hr = ScriptPlace(hdc,
&script_cache_,
run->glyphs.get(),
run->glyph_count,
run->visible_attributes.get(),
&(run->script_analysis),
run->advance_widths.get(),
run->offsets.get(),
&(run->abc_widths));
DCHECK(SUCCEEDED(hr));
}
}
if (runs_.size() > 0) {
// Build the array of bidirectional embedding levels.
scoped_array<BYTE> levels(new BYTE[runs_.size()]);
for (size_t i = 0; i < runs_.size(); ++i)
levels[i] = runs_[i]->script_analysis.s.uBidiLevel;
// Get the maps between visual and logical run indices.
visual_to_logical_.reset(new int[runs_.size()]);
logical_to_visual_.reset(new int[runs_.size()]);
hr = ScriptLayout(runs_.size(),
levels.get(),
visual_to_logical_.get(),
logical_to_visual_.get());
DCHECK(SUCCEEDED(hr));
}
// Precalculate run width information.
size_t preceding_run_widths = 0;
for (size_t i = 0; i < runs_.size(); ++i) {
internal::TextRun* run = runs_[visual_to_logical_[i]];
run->preceding_run_widths = preceding_run_widths;
const ABC& abc = run->abc_widths;
run->width = abc.abcA + abc.abcB + abc.abcC;
preceding_run_widths += run->width;
}
string_width_ = preceding_run_widths;
}
size_t RenderTextWin::GetRunContainingPosition(size_t position) const {
// Find the text run containing the argument position.
size_t run = 0;
for (; run < runs_.size(); ++run)
if (runs_[run]->range.start() <= position &&
runs_[run]->range.end() > position)
break;
return run;
}
size_t RenderTextWin::GetRunContainingPoint(const Point& point) const {
// Find the text run containing the argument point (assumed already offset).
size_t run = 0;
for (; run < runs_.size(); ++run)
if (runs_[run]->preceding_run_widths <= point.x() &&
runs_[run]->preceding_run_widths + runs_[run]->width > point.x())
break;
return run;
}
size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) const {
size_t run_index = GetRunContainingPosition(index);
internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL;
long start = run ? run->range.start() : 0;
long length = run ? run->range.length() : text().length();
long ch = index - start;
WORD cluster = run ? run->logical_clusters[ch] : 0;
if (!next) {
do {
ch--;
} while (ch >= 0 && run && run->logical_clusters[ch] == cluster);
} else {
while (ch < length && run && run->logical_clusters[ch] == cluster)
ch++;
}
return std::max(static_cast<long>(std::min(ch, length) + start), 0L);
}
SelectionModel RenderTextWin::FirstSelectionModelInsideRun(
internal::TextRun* run) const {
size_t caret = run->range.start();
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
}
SelectionModel RenderTextWin::LastSelectionModelInsideRun(
internal::TextRun* run) const {
size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false);
return SelectionModel(caret, caret, SelectionModel::LEADING);
}
SelectionModel RenderTextWin::LeftSelectionModel(
const SelectionModel& selection) {
size_t caret = selection.caret_pos();
SelectionModel::CaretPlacement caret_placement = selection.caret_placement();
size_t run_index = GetRunContainingPosition(caret);
DCHECK(run_index < runs_.size());
internal::TextRun* run = runs_[run_index];
// If the caret's associated character is in a LTR run.
if (!run->script_analysis.fRTL) {
if (caret_placement == SelectionModel::TRAILING)
return SelectionModel(caret, caret, SelectionModel::LEADING);
else if (caret > run->range.start()) {
caret = IndexOfAdjacentGrapheme(caret, false);
return SelectionModel(caret, caret, SelectionModel::LEADING);
}
} else { // The caret's associated character is in a RTL run.
if (caret_placement == SelectionModel::LEADING) {
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
} else if (selection.selection_end() < run->range.end()) {
caret = IndexOfAdjacentGrapheme(caret, true);
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
}
}
// The character is at the begin of its run; go to the previous visual run.
size_t visual_index = logical_to_visual_[run_index];
if (visual_index == 0)
return LeftEndSelectionModel();
internal::TextRun* prev = runs_[visual_to_logical_[visual_index - 1]];
return prev->script_analysis.fRTL ? FirstSelectionModelInsideRun(prev) :
LastSelectionModelInsideRun(prev);
}
SelectionModel RenderTextWin::RightSelectionModel(
const SelectionModel& selection) {
size_t caret = selection.caret_pos();
SelectionModel::CaretPlacement caret_placement = selection.caret_placement();
size_t run_index = GetRunContainingPosition(caret);
DCHECK(run_index < runs_.size());
internal::TextRun* run = runs_[run_index];
// If the caret's associated character is in a LTR run.
if (!run->script_analysis.fRTL) {
if (caret_placement == SelectionModel::LEADING) {
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
} else if (selection.selection_end() < run->range.end()) {
caret = IndexOfAdjacentGrapheme(caret, true);
size_t cursor = IndexOfAdjacentGrapheme(caret, true);
return SelectionModel(cursor, caret, SelectionModel::TRAILING);
}
} else { // The caret's associated character is in a RTL run.
if (caret_placement == SelectionModel::TRAILING)
return SelectionModel(caret, caret, SelectionModel::LEADING);
else if (caret > run->range.start()) {
caret = IndexOfAdjacentGrapheme(caret, false);
return SelectionModel(caret, caret, SelectionModel::LEADING);
}
}
// The character is at the end of its run; go to the next visual run.
size_t visual_index = logical_to_visual_[run_index];
if (visual_index == runs_.size() - 1)
return RightEndSelectionModel();
internal::TextRun* next = runs_[visual_to_logical_[visual_index + 1]];
return next->script_analysis.fRTL ? LastSelectionModelInsideRun(next) :
FirstSelectionModelInsideRun(next);
}
void RenderTextWin::DrawSelection(Canvas* canvas) {
std::vector<Rect> sel(
GetSubstringBounds(GetSelectionStart(), GetCursorPosition()));
SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
canvas->FillRectInt(color, i->x(), i->y(), i->width(), i->height());
}
void RenderTextWin::DrawVisualText(Canvas* canvas) {
if (text().empty())
return;
CanvasSkia* canvas_skia = canvas->AsCanvasSkia();
skia::ScopedPlatformPaint scoped_platform_paint(canvas_skia);
Point offset(ToViewPoint(Point()));
// TODO(msw): Establish a vertical baseline for strings of mixed font heights.
size_t height = default_style().font.GetHeight();
// Center the text vertically in the display area.
offset.Offset(0, (display_rect().height() - height) / 2);
SkPaint paint;
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
paint.setStyle(SkPaint::kFill_Style);
paint.setAntiAlias(true);
paint.setSubpixelText(true);
paint.setLCDRenderText(true);
SkPoint point(SkPoint::Make(SkIntToScalar(offset.x()),
SkIntToScalar(display_rect().height() - offset.y())));
RECT rect = display_rect().ToRECT();
scoped_array<SkPoint> pos;
for (size_t i = 0; i < runs_.size(); ++i) {
// Get the run specified by the visual-to-logical map.
internal::TextRun* run = runs_[visual_to_logical_[i]];
// TODO(msw): Font default/fallback and style integration.
std::string font(UTF16ToASCII(run->font.GetFontName()));
SkTypeface::Style style = SkTypeface::kNormal;
SkTypeface* typeface = SkTypeface::CreateFromName(font.c_str(), style);
if (typeface) {
paint.setTypeface(typeface);
// |paint| adds its own ref. Release the ref from CreateFromName.
typeface->unref();
}
// TODO(msw): Skia font size units? Set OmniboxViewViews gfx::Font size?
int font_size = run->font.GetFontSize();
paint.setTextSize(SkFloatToScalar(font_size * kSkiaFontScale));
paint.setColor(run->foreground);
// Based on WebCore::skiaDrawText.
pos.reset(new SkPoint[run->glyph_count]);
for (int glyph = 0; glyph < run->glyph_count; glyph++) {
pos[glyph].set(point.x() + run->offsets[glyph].du,
point.y() + run->offsets[glyph].dv);
point.offset(SkIntToScalar(run->advance_widths[glyph]), 0);
}
size_t byte_length = run->glyph_count * sizeof(WORD);
canvas_skia->drawPosText(run->glyphs.get(), byte_length, pos.get(), paint);
// Draw the strikethrough.
if (run->strike) {
Rect bounds(offset, Size(run->width, run->font.GetHeight()));
SkPaint strike;
strike.setAntiAlias(true);
strike.setStyle(SkPaint::kFill_Style);
strike.setColor(run->foreground);
strike.setStrokeWidth(kStrikeWidth);
canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()),
SkIntToScalar(bounds.bottom()),
SkIntToScalar(bounds.right()),
SkIntToScalar(bounds.y()),
strike);
}
offset.Offset(run->width, 0);
}
}
void RenderTextWin::DrawCursor(Canvas* canvas) {
// Paint cursor. Replace cursor is drawn as rectangle for now.
// TODO(msw): Draw a better cursor with a better indication of association.
if (cursor_visible() && focused()) {
Rect r(GetUpdatedCursorBounds());
canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height());
}
}
RenderText* RenderText::CreateRenderText() {
......
......@@ -6,17 +6,121 @@
#define UI_GFX_RENDER_TEXT_WIN_H_
#pragma once
#include <usp10.h>
#include "base/memory/scoped_ptr.h"
#include "ui/gfx/render_text.h"
namespace gfx {
namespace internal {
struct TextRun {
TextRun();
ui::Range range;
Font font;
// TODO(msw): Disambiguate color, strike, etc. from TextRuns.
// Otherwise, this breaks the glyph shaping process.
// See the example at: http://www.catch22.net/tuts/neatpad/12.
SkColor foreground;
bool strike;
int width;
// The cumulative widths of preceding runs.
int preceding_run_widths;
SCRIPT_ANALYSIS script_analysis;
scoped_array<WORD> glyphs;
scoped_array<WORD> logical_clusters;
scoped_array<SCRIPT_VISATTR> visible_attributes;
int glyph_count;
scoped_array<int> advance_widths;
scoped_array<GOFFSET> offsets;
ABC abc_widths;
private:
DISALLOW_COPY_AND_ASSIGN(TextRun);
};
} // namespace internal
// RenderTextWin is the Windows implementation of RenderText using Uniscribe.
class RenderTextWin : public RenderText {
public:
RenderTextWin();
virtual ~RenderTextWin();
// Overridden from RenderText:
virtual void SetText(const string16& text) OVERRIDE;
virtual void SetDisplayRect(const Rect& r) OVERRIDE;
virtual void ApplyStyleRange(StyleRange style_range) OVERRIDE;
virtual void ApplyDefaultStyle() OVERRIDE;
virtual int GetStringWidth() OVERRIDE;
virtual void Draw(Canvas* canvas) OVERRIDE;
virtual SelectionModel FindCursorPosition(const Point& point) OVERRIDE;
virtual Rect GetCursorBounds(const SelectionModel& selection,
bool insert_mode) OVERRIDE;
protected:
// Overridden from RenderText:
virtual SelectionModel GetLeftSelectionModel(const SelectionModel& current,
BreakType break_type) OVERRIDE;
virtual SelectionModel GetRightSelectionModel(const SelectionModel& current,
BreakType break_type) OVERRIDE;
virtual SelectionModel LeftEndSelectionModel() OVERRIDE;
virtual SelectionModel RightEndSelectionModel() OVERRIDE;
virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE;
virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to) OVERRIDE;
private:
void ItemizeLogicalText();
void LayoutVisualText(HDC hdc);
// Return the run index that contains the argument; or the length of the
// |runs_| vector if argument exceeds the text length or width.
size_t GetRunContainingPosition(size_t position) const;
size_t GetRunContainingPoint(const Point& point) const;
// Return an index belonging to the |next| or previous logical grapheme.
// The return value is bounded by 0 and the text length, inclusive.
size_t IndexOfAdjacentGrapheme(size_t index, bool next) const;
// Given a |run|, returns the SelectionModel that contains the logical first
// or last caret position inside (not at a boundary of) the run.
// The returned value represents a cursor/caret position without a selection.
SelectionModel FirstSelectionModelInsideRun(internal::TextRun*) const;
SelectionModel LastSelectionModelInsideRun(internal::TextRun*) const;
// Get the selection model visually left/right of |selection| by one grapheme.
// The returned value represents a cursor/caret position without a selection.
SelectionModel LeftSelectionModel(const SelectionModel& selection);
SelectionModel RightSelectionModel(const SelectionModel& selection);
// Draw the text, cursor, and selection.
void DrawSelection(Canvas* canvas);
void DrawVisualText(Canvas* canvas);
void DrawCursor(Canvas* canvas);
bool text_is_dirty_;
bool style_is_dirty_;
// National Language Support native digit and digit substitution settings.
SCRIPT_DIGITSUBSTITUTE digit_substitute_;
SCRIPT_CONTROL script_control_;
SCRIPT_STATE script_state_;
SCRIPT_CACHE script_cache_;
std::vector<internal::TextRun*> runs_;
int string_width_;
scoped_array<int> visual_to_logical_;
scoped_array<int> logical_to_visual_;
DISALLOW_COPY_AND_ASSIGN(RenderTextWin);
};
......
......@@ -874,11 +874,12 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
cursor_changed = true;
break;
case ui::VKEY_END:
model_->MoveCursorRight(gfx::LINE_BREAK, selection);
cursor_changed = true;
break;
case ui::VKEY_HOME:
model_->MoveCursorLeft(gfx::LINE_BREAK, selection);
if ((key_code == ui::VKEY_HOME) ==
(GetRenderText()->GetTextDirection() == base::i18n::RIGHT_TO_LEFT))
model_->MoveCursorRight(gfx::LINE_BREAK, selection);
else
model_->MoveCursorLeft(gfx::LINE_BREAK, selection);
cursor_changed = true;
break;
case ui::VKEY_BACK:
......@@ -921,7 +922,7 @@ bool NativeTextfieldViews::HandleKeyEvent(const KeyEvent& key_event) {
cursor_changed = text_changed = model_->Delete();
break;
case ui::VKEY_INSERT:
GetRenderText()->toggle_insert_mode();
GetRenderText()->ToggleInsertMode();
cursor_changed = true;
break;
default:
......
......@@ -398,11 +398,6 @@ bool TextfieldViewsModel::MoveCursorTo(const gfx::Point& point, bool select) {
return render_text_->MoveCursorTo(point, select);
}
std::vector<gfx::Rect> TextfieldViewsModel::GetSelectionBounds() const {
return render_text_->GetSubstringBounds(render_text_->GetSelectionStart(),
render_text_->GetCursorPosition());
}
string16 TextfieldViewsModel::GetSelectedText() const {
return GetText().substr(render_text_->MinOfSelection(),
(render_text_->MaxOfSelection() - render_text_->MinOfSelection()));
......@@ -421,7 +416,7 @@ void TextfieldViewsModel::SelectRange(const ui::Range& range) {
void TextfieldViewsModel::SelectSelectionModel(const gfx::SelectionModel& sel) {
if (HasCompositionText())
ConfirmCompositionText();
render_text_->SetSelectionModel(sel);
render_text_->MoveCursorTo(sel);
}
void TextfieldViewsModel::SelectAll() {
......@@ -509,7 +504,7 @@ bool TextfieldViewsModel::Cut() {
render_text_->GetSelectionStart(),
render_text_->GetSelectionStart(),
gfx::SelectionModel::LEADING);
render_text_->SetSelectionModel(sel);
render_text_->MoveCursorTo(sel);
DeleteSelection();
return true;
}
......@@ -589,7 +584,7 @@ void TextfieldViewsModel::SetCompositionText(
size_t end =
std::min(range.start() + composition.selection.end(), range.end());
gfx::SelectionModel sel(start, end);
render_text_->SetSelectionModel(sel);
render_text_->MoveCursorTo(sel);
} else {
render_text_->SetCursorPosition(range.end());
}
......@@ -662,7 +657,7 @@ void TextfieldViewsModel::ReplaceTextInternal(const string16& text,
size_t cursor = GetCursorPosition();
gfx::SelectionModel sel(render_text_->selection_model());
sel.set_selection_start(cursor + text.length());
render_text_->SetSelectionModel(sel);
render_text_->MoveCursorTo(sel);
}
// Edit history is recorded in InsertText.
InsertTextInternal(text, mergeable);
......
......@@ -143,9 +143,6 @@ class VIEWS_EXPORT TextfieldViewsModel {
// Helper function to call MoveCursorTo on the TextfieldViewsModel.
bool MoveCursorTo(const gfx::Point& point, bool select);
// Returns the bounds of selected text.
std::vector<gfx::Rect> GetSelectionBounds() const;
// Selection related method
// Returns the selected text.
......
......@@ -90,22 +90,27 @@ void TextfieldExample::ButtonPressed(views::Button* sender,
} else if (sender == set_) {
name_->SetText(WideToUTF16(L"[set]"));
} else if (sender == set_style_) {
gfx::StyleRange color;
color.foreground = SK_ColorYELLOW;
color.range = ui::Range(0, 11);
name_->ApplyStyleRange(color);
if (!name_->text().empty()) {
gfx::StyleRange color;
color.foreground = SK_ColorYELLOW;
color.range = ui::Range(0, name_->text().length());
name_->ApplyStyleRange(color);
gfx::StyleRange underline;
underline.underline = true;
underline.foreground = SK_ColorBLUE;
underline.range = ui::Range(1, 7);
name_->ApplyStyleRange(underline);
if (name_->text().length() >= 5) {
size_t fifth = name_->text().length() / 5;
gfx::StyleRange underline;
underline.underline = true;
underline.foreground = SK_ColorBLUE;
underline.range = ui::Range(1 * fifth, 4 * fifth);
name_->ApplyStyleRange(underline);
gfx::StyleRange strike;
strike.strike = true;
strike.foreground = SK_ColorRED;
strike.range = ui::Range(6, 9);
name_->ApplyStyleRange(strike);
gfx::StyleRange strike;
strike.strike = true;
strike.foreground = SK_ColorRED;
strike.range = ui::Range(2 * fifth, 3 * fifth);
name_->ApplyStyleRange(strike);
}
}
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment