Commit 46781bca authored by Emily Stark's avatar Emily Stark Committed by Commit Bot

Fix GetSubstringBounds() rounding to floor x coordinate

Before this CL, RenderText::GetSubstringBounds() rounded up both the
starting x and ending x that it computed. As a result, the left edge
of the substring could be slightly outside the returned bounds, and
the right edge of the returned bounds might contain some small portion
of the next characters.

When the output of GetSubstringBounds() is used as input for
set_display_rect(), this rounding behavior could show up as small
artifacts trailing the domain and/or clipping the first character
slightly (see screenshots in linked bug).

This CL makes GetSubstringBounds() round down the starting x
coordinate.  This provides the guarantee that the returned bounds
always fully contain the substring -- that is, the bounds will be
slightly larger rather than slightly smaller when rounding occurs. An
alternative I considered was to introduce GetSubstringBoundsF and let
consumers do their own rounding. However, as far as I can tell no
other caller cares about the rounding behavior so it seemed like
unnecessary complexity to add a float version of the method.

Bug: 1108045, 1111044
Change-Id: I733326ef0544c4ecbe3e39bb9bd8f7a410bc0047
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2317820
Commit-Queue: Emily Stark <estark@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Reviewed-by: default avatarEtienne Bergeron <etienneb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#793692}
parent 48a5523b
...@@ -564,11 +564,13 @@ class GFX_EXPORT RenderText { ...@@ -564,11 +564,13 @@ class GFX_EXPORT RenderText {
const ShadowValues& shadows() const { return shadows_; } const ShadowValues& shadows() const { return shadows_; }
// Get the visual bounds containing the logical substring within the |range|. // Get the visual bounds containing the logical substring within the |range|.
// If |range| is empty, the result is empty. These bounds could be visually // If |range| is empty, the result is empty. This method rounds internally so
// discontinuous if the substring is split by a LTR/RTL level change. // the returned bounds may be slightly larger than the |range|, but are
// These bounds are in local coordinates, but may be outside the visible // guaranteed not to be smaller. These bounds could be visually discontinuous
// region if the text is longer than the textfield. Subsequent text, cursor, // if the substring is split by a LTR/RTL level change. These bounds are in
// or bounds changes may invalidate returned values. // 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(const Range& range) = 0; virtual std::vector<Rect> GetSubstringBounds(const Range& range) = 0;
// Gets the horizontal span (relative to the left of the text, not the view) // Gets the horizontal span (relative to the left of the text, not the view)
......
...@@ -1454,7 +1454,8 @@ std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) { ...@@ -1454,7 +1454,8 @@ std::vector<Rect> RenderTextHarfBuzz::GetSubstringBounds(const Range& range) {
const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run]; const internal::TextRunHarfBuzz& run = *run_list->runs()[segment.run];
RangeF selected_span = RangeF selected_span =
run.GetGraphemeSpanForCharRange(this, intersection); run.GetGraphemeSpanForCharRange(this, intersection);
int start_x = base::ClampCeil(selected_span.start() - line_start_x); DCHECK(!selected_span.is_reversed());
int start_x = base::ClampFloor(selected_span.start() - line_start_x);
int end_x = base::ClampCeil(selected_span.end() - line_start_x); int end_x = base::ClampCeil(selected_span.end() - line_start_x);
Rect rect(start_x, 0, end_x - start_x, Rect rect(start_x, 0, end_x - start_x,
base::ClampCeil(line.size.height())); base::ClampCeil(line.size.height()));
......
...@@ -7016,6 +7016,12 @@ TEST_F(RenderTextTest, SkFontEdging) { ...@@ -7016,6 +7016,12 @@ TEST_F(RenderTextTest, SkFontEdging) {
// Verify GetWordLookupDataAtPoint returns the correct baseline point and // Verify GetWordLookupDataAtPoint returns the correct baseline point and
// decorated word for an LTR string. // decorated word for an LTR string.
TEST_F(RenderTextTest, GetWordLookupDataAtPoint_LTR) { TEST_F(RenderTextTest, GetWordLookupDataAtPoint_LTR) {
// Set an integer glyph width; GetCursorBounds() and
// GetWordLookupDataAtPoint() use different rounding internally.
//
// TODO(crbug.com/1111044): this shouldn't be necessary once RenderText keeps
// float precision through GetCursorBounds().
SetGlyphWidth(5);
const base::string16 ltr = ASCIIToUTF16(" ab c "); const base::string16 ltr = ASCIIToUTF16(" ab c ");
const int kWordOneStartIndex = 2; const int kWordOneStartIndex = 2;
const int kWordTwoStartIndex = 6; const int kWordTwoStartIndex = 6;
...@@ -7095,6 +7101,12 @@ TEST_F(RenderTextTest, GetWordLookupDataAtPoint_LTR) { ...@@ -7095,6 +7101,12 @@ TEST_F(RenderTextTest, GetWordLookupDataAtPoint_LTR) {
// Verify GetWordLookupDataAtPoint returns the correct baseline point and // Verify GetWordLookupDataAtPoint returns the correct baseline point and
// decorated word for an RTL string. // decorated word for an RTL string.
TEST_F(RenderTextTest, GetWordLookupDataAtPoint_RTL) { TEST_F(RenderTextTest, GetWordLookupDataAtPoint_RTL) {
// Set an integer glyph width; GetCursorBounds() and
// GetWordLookupDataAtPoint() use different rounding internally.
//
// TODO(crbug.com/1111044): this shouldn't be necessary once RenderText keeps
// float precision through GetCursorBounds().
SetGlyphWidth(5);
const base::string16 rtl = UTF8ToUTF16(" \u0634\u0632 \u0634"); const base::string16 rtl = UTF8ToUTF16(" \u0634\u0632 \u0634");
const int kWordOneStartIndex = 1; const int kWordOneStartIndex = 1;
const int kWordTwoStartIndex = 5; const int kWordTwoStartIndex = 5;
...@@ -7401,6 +7413,33 @@ TEST_F(RenderTextTest, LineEndSelections) { ...@@ -7401,6 +7413,33 @@ TEST_F(RenderTextTest, LineEndSelections) {
} }
} }
// Tests that GetSubstringBounds rounds outward when glyphs have floating-point
// widths.
TEST_F(RenderTextTest, GetSubstringBoundsFloatingPoint) {
const float kGlyphWidth = 5.8;
SetGlyphWidth(kGlyphWidth);
RenderText* render_text = GetRenderText();
render_text->SetDisplayRect(Rect(200, 1000));
render_text->SetText(UTF8ToUTF16("abcdef"));
gfx::Rect bounds = GetSubstringBoundsUnion(Range(1, 2));
// The bounds should be rounded outwards so that the full substring is always
// contained in them.
EXPECT_EQ(base::ClampFloor(kGlyphWidth), bounds.x());
EXPECT_EQ(base::ClampCeil(2 * kGlyphWidth), bounds.right());
}
// Tests that GetSubstringBounds handles integer glypth widths correctly.
TEST_F(RenderTextTest, GetSubstringBoundsInt) {
const float kGlyphWidth = 5;
SetGlyphWidth(kGlyphWidth);
RenderText* render_text = GetRenderText();
render_text->SetDisplayRect(Rect(200, 1000));
render_text->SetText(UTF8ToUTF16("abcdef"));
gfx::Rect bounds = GetSubstringBoundsUnion(Range(1, 2));
EXPECT_EQ(kGlyphWidth, bounds.x());
EXPECT_EQ(2 * kGlyphWidth, bounds.right());
}
// Tests that GetSubstringBounds returns the correct bounds for multiline text. // Tests that GetSubstringBounds returns the correct bounds for multiline text.
TEST_F(RenderTextTest, GetSubstringBoundsMultiline) { TEST_F(RenderTextTest, GetSubstringBoundsMultiline) {
RenderText* render_text = GetRenderText(); RenderText* render_text = GetRenderText();
......
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