Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(502)

Unified Diff: ui/gfx/render_text_win.cc

Issue 16867016: Windows implementation of multiline RenderText (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: fix ComputeLines and rects Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/gfx/render_text_win.cc
diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc
index 0469c0f5b7a61a6770457d330cb22d6e1c09401b..218b4ba31db9de896ace8e8b1adfbf7827160d80 100644
--- a/ui/gfx/render_text_win.cc
+++ b/ui/gfx/render_text_win.cc
@@ -125,6 +125,27 @@ bool IsUnicodeBidiControlCharacter(char16 c) {
c == base::i18n::kRightToLeftOverride;
}
+ui::Range CharRangeToGlyphRange(const ui::Range& range,
Alexei Svitkine (slow) 2013/07/09 15:35:13 Add a comment.
ckocagil 2013/07/13 16:05:10 Done.
+ internal::TextRun* run) {
Alexei Svitkine (slow) 2013/07/09 15:35:13 This should be const and possibly by ref to preven
ckocagil 2013/07/13 16:05:10 Done.
+ DCHECK_GE(range.start(), 0u);
+ DCHECK_LE(range.end(), run->range.length());
msw 2013/07/10 04:01:56 nit: Can you just [D]CHECK(run->range.Contains(ran
ckocagil 2013/07/13 16:05:10 Done. Done. Done.
+ DCHECK_LE(range.start(), range.end());
+ bool rtl = run->script_analysis.fRTL;
Alexei Svitkine (slow) 2013/07/09 15:35:13 Inline this, since you only use it once.
ckocagil 2013/07/13 16:05:10 Done.
+ int start_char = range.start();
+ int end_char = range.end();
Alexei Svitkine (slow) 2013/07/09 15:35:13 Nit: You don't need local vars for start_char and
ckocagil 2013/07/13 16:05:10 Done.
+ int start, end;
Alexei Svitkine (slow) 2013/07/09 15:35:13 Nit: Separate lines.
msw 2013/07/10 04:01:56 Also, explicitly init each to 0.
ckocagil 2013/07/13 16:05:10 Both done.
+ if (rtl) {
+ start = run->logical_clusters[end_char - 1];
+ end = start_char > 0 ? run->logical_clusters[start_char - 1]
+ : run->glyph_count;
+ } else {
+ start = run->logical_clusters[start_char];
+ end = end_char < run->glyph_count ? run->logical_clusters[end_char]
msw 2013/07/10 04:01:56 Shouldn't this be end_char < run->range.length() ?
ckocagil 2013/07/13 16:05:10 Shame on me, this would be a subtle bug. Thanks! D
+ : run->glyph_count;
+ }
+ return ui::Range(start, end);
+}
+
} // namespace
namespace internal {
@@ -192,7 +213,17 @@ RenderTextWin::~RenderTextWin() {
Size RenderTextWin::GetStringSize() {
EnsureLayout();
- return string_size_;
+ // TODO: is this the right place to add +1 for cursor?
+ return Size(string_size_.width() + 1, string_size_.height());
Alexei Svitkine (slow) 2013/07/09 15:35:13 Can you explain why this is needed (and why it was
msw 2013/07/10 04:01:56 I think it's already handled by RenderText::GetCon
ckocagil 2013/07/13 16:05:10 Yes, I later noticed it. Removing the +1 here.
+}
+
+Size RenderTextWin::GetMultilineTextSize() {
+ EnsureLayout();
+ if (!multiline())
+ return Size(display_rect().width(), string_size_.height());
+ ComputeLines();
msw 2013/07/10 04:01:56 Do this as needed in EnsureLayout().
ckocagil 2013/07/13 16:05:10 Done.
+ return Size(display_rect().width(),
+ string_size_.height() * lines().size());
Alexei Svitkine (slow) 2013/07/09 15:35:13 Are all lines guaranteed to have the same height?
ckocagil 2013/07/13 16:05:10 Done, we use per-line heights now.
}
int RenderTextWin::GetBaseline() {
@@ -359,9 +390,10 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) {
TextIndexToLayoutIndex(range.end()));
DCHECK(ui::Range(0, GetLayoutText().length()).Contains(layout_range));
- std::vector<Rect> bounds;
+ std::vector<ui::Range> bounds;
+ std::vector<Rect> rects;
if (layout_range.is_empty())
- return bounds;
+ return rects;
// Add a Rect for each run/selection intersection.
// TODO(msw): The bounds should probably not always be leading the range ends.
@@ -372,17 +404,20 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) {
DCHECK(!intersection.is_reversed());
ui::Range range_x(GetGlyphXBoundary(run, intersection.start(), false),
GetGlyphXBoundary(run, intersection.end(), false));
- Rect rect(range_x.GetMin(), 0, range_x.length(), run->font.GetHeight());
- rect.set_origin(ToViewPoint(rect.origin()));
- // Union this with the last rect if they're adjacent.
- if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) {
- rect.Union(bounds.back());
+ range_x = ui::Range(range_x.GetMin(), range_x.GetMax());
+ // Union this with the last range if they're adjacent.
+ if (!bounds.empty() && bounds.back().GetMax() == range_x.GetMin()) {
msw 2013/07/10 04:01:56 This will miss cases where range_x.GetMax() == bou
ckocagil 2013/07/13 16:05:10 Added a DCHECK for this case but it doesn't seem t
+ range_x = ui::Range(bounds.back().GetMin(), range_x.GetMax());
bounds.pop_back();
}
- bounds.push_back(rect);
+ bounds.push_back(range_x);
}
}
- return bounds;
+ for (size_t i = 0; i < bounds.size(); ++i) {
+ std::vector<Rect> current_rects = RangeToViewRects(bounds[i], 0);
+ rects.insert(rects.end(), current_rects.begin(), current_rects.end());
+ }
+ return rects;
}
size_t RenderTextWin::TextIndexToLayoutIndex(size_t index) const {
@@ -420,26 +455,145 @@ bool RenderTextWin::IsCursorablePosition(size_t position) {
void RenderTextWin::ResetLayout() {
// Layout is performed lazily as needed for drawing/metrics.
needs_layout_ = true;
+ set_lines_valid(false); // TODO: do we need this?
msw 2013/07/10 04:01:56 I think lines_valid_ should implicitly be part of
ckocagil 2013/07/13 16:05:10 Done.
}
void RenderTextWin::EnsureLayout() {
if (!needs_layout_)
return;
+
// TODO(msw): Skip complex processing if ScriptIsComplex returns false.
ItemizeLogicalText();
if (!runs_.empty())
LayoutVisualText();
+
needs_layout_ = false;
}
+
+void RenderTextWin::ComputeLines() {
+ if (lines_valid() || !multiline())
+ return;
+
+ EnsureLayout();
+
+ std::vector<ScopedVector<internal::LineSegment> > lines;
msw 2013/07/10 04:01:56 Should these be LineSegmentWin?
ckocagil 2013/07/13 16:05:10 We now use Line structs here.
+ lines.push_back(ScopedVector<internal::LineSegment>());
+
+ int max_width = display_rect().width();
+
+ std::vector<size_t> breaks = line_breaks();
+ for (size_t i = 0; i < runs_.size(); ++i)
+ breaks.push_back(runs_[i]->range.start());
+ breaks.push_back(0); // TODO: necessary?
msw 2013/07/10 04:01:56 Probably not necessary, resolve the TODO.
ckocagil 2013/07/13 16:05:10 We need at least one element, now I'm only adding
+ breaks.push_back(-1);
msw 2013/07/10 04:01:56 What's this unsigned wraparound max size_t used fo
ckocagil 2013/07/13 16:05:10 This was used as an anchor so we didn't go past th
+ std::sort(breaks.begin(), breaks.end());
+ std::unique(breaks.begin(), breaks.end());
Alexei Svitkine (slow) 2013/07/09 15:35:13 Is this needed? If so, perhaps add a test that wil
ckocagil 2013/07/13 16:05:10 This shouldn't be needed. Removing.
+
+ size_t* last_break = &breaks[0];
+
+ int line_x = 0; // x from the line beginning
+ int text_x = 0; // x from the text beginning
+ size_t segment_start_char = 0; // offset of current segment beginning
+ int segment_start_pos = 0; // position of current segment beginning
+ internal::TextRun* run;
+ size_t line_beginning = 0;
+
+ for (size_t i = 0; i < runs_.size(); ++i) {
msw 2013/07/10 04:01:56 This function needs comments explaining what it's
ckocagil 2013/07/13 16:05:10 Added a comment explaining what this function does
+ // TODO: might need |line_break = range.start| to break at run beginnings
msw 2013/07/10 04:01:56 nit: resolve TODO
ckocagil 2013/07/13 16:05:10 Done.
+ run = runs_[visual_to_logical_[i]];
+ bool rtl = run->script_analysis.fRTL;
+ int break_x = 0;
+
+ last_break = &breaks[0];
+
+ segment_start_char = run->range.start();
+ segment_start_pos = text_x;
+
+ int run_beginning_x = text_x;
+ typedef std::pair<internal::LineSegment*, int> RtlWidth;
msw 2013/07/10 04:01:56 Comment here, what's an RtlWidth?
ckocagil 2013/07/13 16:05:10 Done.
+ std::vector<RtlWidth> rtl_widths;
+
+ for (size_t c = 0; c < run->range.length(); ++c) {
Alexei Svitkine (slow) 2013/07/09 15:35:13 Use i, j, k for loops, so this should be j and the
msw 2013/07/10 04:01:56 There must be a better approach than fitting lines
ckocagil 2013/07/13 16:05:10 Actually I have thought about most of the stuff yo
ckocagil 2013/07/13 16:05:10 Done.
+ size_t offset = c + run->range.start();
+ while (offset >= *(last_break + 1)) {
Alexei Svitkine (slow) 2013/07/09 15:35:13 Can you you use ui/gfx/break_list.h or are the sem
ckocagil 2013/07/13 16:05:10 I can use a break list but then every time I use G
+ ++last_break;
+ break_x = 0;
+ }
+
+ ui::Range glyphs = CharRangeToGlyphRange(ui::Range(c, c + 1), run);
+ int start = glyphs.start();
+ int end = glyphs.end();
+
+ int char_width = 0;
+ for (int g = start; g < end; ++g)
+ char_width += run->advance_widths[g];
+
+ line_x += char_width;
+ text_x += char_width;
+ break_x += char_width;
+
+ if (line_x > max_width) {
+ if (*last_break > line_beginning) {
+ // Roll back to last break.
+ text_x -= break_x;
+ c = *last_break - run->range.start() - 1;
+ offset = *last_break;
+ } else if (line_x != char_width) {
+ // Roll back one character.
+ text_x -= char_width;
+ --c;
+ } // else: Assume the current character fits; continue from next line.
+ line_beginning = offset;
+ line_x = 0;
+ break_x = 0;
+
+ internal::LineSegmentWin* segment = new internal::LineSegmentWin;
+ if (rtl)
+ rtl_widths.push_back(RtlWidth(segment, text_x - segment_start_pos));
+ else
+ segment->x_pos = ui::Range(segment_start_pos, text_x);
+ segment->char_pos = ui::Range(segment_start_char, offset);
+ segment->run = run;
+ lines.back().push_back(segment);
+ lines.push_back(ScopedVector<internal::LineSegment>());
+
+ segment_start_pos = text_x;
+ segment_start_char = offset;
+ continue;
+ }
+ }
+ if (segment_start_char != run->range.end()) {
+ internal::LineSegmentWin* segment = new internal::LineSegmentWin;
+ if (rtl)
+ rtl_widths.push_back(RtlWidth(segment, text_x - segment_start_pos));
+ else
+ segment->x_pos = ui::Range(segment_start_pos, text_x);
+ segment->char_pos = ui::Range(segment_start_char, run->range.end());
+ segment->run = run;
+ lines.back().push_back(segment);
+ segment_start_char = run->range.end();
+ segment_start_pos = text_x;
+ }
+
+ int rtl_total_widths = 0;
+ while (!rtl_widths.empty()) {
+ const RtlWidth& rtl_width = rtl_widths.back();
+ rtl_width.first->x_pos = ui::Range(run_beginning_x + rtl_total_widths,
+ run_beginning_x + rtl_total_widths + rtl_width.second);
+ rtl_widths.pop_back();
+ rtl_total_widths += rtl_width.second;
+ }
+ }
+
+ set_lines(&lines);
+ set_lines_valid(true);
+}
void RenderTextWin::DrawVisualText(Canvas* canvas) {
DCHECK(!needs_layout_);
- // Skia will draw glyphs with respect to the baseline.
- Vector2d offset(GetTextOffset() + Vector2d(0, common_baseline_));
-
- SkScalar x = SkIntToScalar(offset.x());
- SkScalar y = SkIntToScalar(offset.y());
+ if (multiline())
+ ComputeLines();
msw 2013/07/10 04:01:56 I think ComputeLines should be done as a step in E
ckocagil 2013/07/13 16:05:10 Done.
std::vector<SkPoint> pos;
@@ -454,30 +608,77 @@ void RenderTextWin::DrawVisualText(Canvas* canvas) {
renderer.SetFontSmoothingSettings(
smoothing_enabled, cleartype_enabled && !background_is_transparent());
- 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]];
-
- if (run->glyph_count == 0)
- continue;
-
- // Based on WebCore::skiaDrawText.
- pos.resize(run->glyph_count);
- SkScalar glyph_x = x;
- for (int glyph = 0; glyph < run->glyph_count; glyph++) {
- pos[glyph].set(glyph_x + run->offsets[glyph].du,
- y + run->offsets[glyph].dv);
- glyph_x += SkIntToScalar(run->advance_widths[glyph]);
+ SkScalar glyph_x = SkIntToScalar(0);
+ SkScalar glyph_y = SkIntToScalar(0);
+
+ if (!multiline()) {
+ // Skia will draw glyphs with respect to the baseline.
+ Vector2d offset(0, common_baseline_);
+ offset += GetTextOffset(GetContentWidth());
+
+ 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]];
+
+ if (run->glyph_count == 0)
+ continue;
+
+ // Based on WebCore::skiaDrawText.
+ pos.resize(run->glyph_count);
+ SkScalar run_x = glyph_x;
+ for (int glyph = 0; glyph < run->glyph_count; glyph++) {
+ pos[glyph].set(offset.x() + glyph_x + run->offsets[glyph].du,
+ offset.y() + run->offsets[glyph].dv);
+ glyph_x += SkIntToScalar(run->advance_widths[glyph]);
+ }
+
+ renderer.SetTextSize(run->font.GetFontSize());
+ renderer.SetFontFamilyWithStyle(run->font.GetFontName(), run->font_style);
+ renderer.SetForegroundColor(run->foreground);
+ renderer.DrawPosText(&pos[0], run->glyphs.get(), run->glyph_count);
+ renderer.DrawDecorations(run_x, offset.y(), run->width, run->underline,
+ run->strike, run->diagonal_strike);
}
+ return;
+ }
- renderer.SetTextSize(run->font.GetFontSize());
- renderer.SetFontFamilyWithStyle(run->font.GetFontName(), run->font_style);
- renderer.SetForegroundColor(run->foreground);
- renderer.DrawPosText(&pos[0], run->glyphs.get(), run->glyph_count);
- renderer.DrawDecorations(x, y, run->width, run->underline, run->strike,
- run->diagonal_strike);
-
- x = glyph_x;
+ for (size_t line = 0; line < lines().size(); ++line) {
Alexei Svitkine (slow) 2013/07/09 15:35:13 Ideally, I'd like to see a single codepath that ha
ckocagil 2013/07/13 16:05:10 Done, added TODO.
+ Vector2d offset(0, common_baseline_);
+ offset += GetTextOffset(LineWidth(line));
+ for (size_t i = 0; i < lines()[line].size(); ++i) {
+ const internal::LineSegmentWin* segment =
+ static_cast<const internal::LineSegmentWin*>(lines()[line][i]);
+ if (segment->char_pos.is_empty())
+ continue; // TODO: maybe prevent this from happening in the first place?
+ int segment_start_pos = glyph_x;
+ internal::TextRun* run = segment->run;
+ DCHECK_GE(segment->char_pos.start(), run->range.start());
+ DCHECK_LT(segment->char_pos.start(),
+ run->range.start() + run->glyph_count);
+ int start_char = segment->char_pos.start() - run->range.start();
+ int end_char = start_char + segment->char_pos.length();
+ ui::Range glyphs = CharRangeToGlyphRange(ui::Range(start_char, end_char),
+ run);
+ int start = glyphs.start();
+ int end = glyphs.end();
+ DCHECK_LE(start, end);
+ pos.resize(end - start);
+ for (int g = start; g < end; ++g) {
+ pos[g - start].set(glyph_x + offset.x() + run->offsets[g].du,
+ glyph_y + offset.y() + run->offsets[g].dv);
+ glyph_x += SkIntToScalar(run->advance_widths[g]);
+ }
+ renderer.SetTextSize(run->font.GetFontSize());
+ renderer.SetFontFamilyWithStyle(run->font.GetFontName(), run->font_style);
+ renderer.SetForegroundColor(run->foreground);
+ renderer.DrawPosText(&pos[0], &run->glyphs[start], end - start);
+ renderer.DrawDecorations(segment_start_pos + offset.x(),
+ glyph_y + offset.y(), segment->x_pos.length(),
+ run->underline, run->strike,
+ run->diagonal_strike);
+ }
+ glyph_x = SkIntToScalar(0);
+ glyph_y += SkIntToScalar(string_size_.height());
}
}

Powered by Google App Engine
This is Rietveld 408576698