Commit 080ad30c authored by Hidehiko Abe's avatar Hidehiko Abe Committed by Commit Bot

Move utilities to convert offset between UTF{8,16} to ui/.

So, the utilities can be used in ui/ozone/platform/wayland
in following CLs.
Also, along with the move, this CL introduces more error checks
and adds unittests.

Bug: 1140536
Test: Ran ui_base_unittests, exo_unittests. Quick manual tests with Lacros.
Change-Id: I5de661528f328fd0e79154f8011056d1816e0f41
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2489107
Commit-Queue: Hidehiko Abe <hidehiko@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarRobert Kroeger <rjkroege@chromium.org>
Reviewed-by: default avatarJun Mukai <mukai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#822705}
parent bb7f3763
...@@ -131,6 +131,7 @@ static_library("exo") { ...@@ -131,6 +131,7 @@ static_library("exo") {
"//chromeos/crosapi/cpp", "//chromeos/crosapi/cpp",
"//chromeos/ui/base", "//chromeos/ui/base",
"//chromeos/ui/frame", "//chromeos/ui/frame",
"//ui/base",
"//ui/events/ozone/layout", "//ui/events/ozone/layout",
] ]
sources += [ sources += [
......
...@@ -7,13 +7,14 @@ ...@@ -7,13 +7,14 @@
#include <algorithm> #include <algorithm>
#include "ash/keyboard/ui/keyboard_ui_controller.h" #include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/string_piece.h"
#include "components/exo/surface.h" #include "components/exo/surface.h"
#include "components/exo/wm_helper.h" #include "components/exo/wm_helper.h"
#include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/uchar.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h" #include "ui/aura/window_tree_host.h"
#include "ui/base/ime/input_method.h" #include "ui/base/ime/input_method.h"
#include "ui/base/ime/utf_offset.h"
#include "ui/events/event.h" #include "ui/events/event.h"
namespace exo { namespace exo {
...@@ -28,14 +29,6 @@ ui::InputMethod* GetInputMethod(aura::Window* window) { ...@@ -28,14 +29,6 @@ ui::InputMethod* GetInputMethod(aura::Window* window) {
} // namespace } // namespace
size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
return base::UTF8ToUTF16(text.substr(0, offset)).size();
}
size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
return base::UTF16ToUTF8(text.substr(0, offset)).size();
}
TextInput::TextInput(std::unique_ptr<Delegate> delegate) TextInput::TextInput(std::unique_ptr<Delegate> delegate)
: delegate_(std::move(delegate)) {} : delegate_(std::move(delegate)) {}
...@@ -222,18 +215,26 @@ bool TextInput::GetEditableSelectionRange(gfx::Range* range) const { ...@@ -222,18 +215,26 @@ bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
bool TextInput::SetEditableSelectionRange(const gfx::Range& range) { bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
if (surrounding_text_.size() < range.GetMax()) if (surrounding_text_.size() < range.GetMax())
return false; return false;
delegate_->SetCursor( auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.start());
gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()), if (!start)
OffsetFromUTF16Offset(surrounding_text_, range.end()))); return false;
auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.end());
if (!end)
return false;
delegate_->SetCursor(gfx::Range(*start, *end));
return true; return true;
} }
bool TextInput::DeleteRange(const gfx::Range& range) { bool TextInput::DeleteRange(const gfx::Range& range) {
if (surrounding_text_.size() < range.GetMax()) if (surrounding_text_.size() < range.GetMax())
return false; return false;
delegate_->DeleteSurroundingText( auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.start());
gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()), if (!start)
OffsetFromUTF16Offset(surrounding_text_, range.end()))); return false;
auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, range.end());
if (!end)
return false;
delegate_->DeleteSurroundingText(gfx::Range(*start, *end));
return true; return true;
} }
...@@ -294,13 +295,18 @@ bool TextInput::ChangeTextDirectionAndLayoutAlignment( ...@@ -294,13 +295,18 @@ bool TextInput::ChangeTextDirectionAndLayoutAlignment(
void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) { void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
if (!cursor_pos_) if (!cursor_pos_)
return; return;
uint32_t start = size_t utf16_start =
(cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before); (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
uint32_t end = size_t utf16_end =
std::min(cursor_pos_->GetMax() + after, surrounding_text_.size()); std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
delegate_->DeleteSurroundingText( auto start = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, utf16_start);
gfx::Range(OffsetFromUTF16Offset(surrounding_text_, start), if (!start)
OffsetFromUTF16Offset(surrounding_text_, end))); return;
auto end = ui::Utf8OffsetFromUtf16Offset(surrounding_text_, utf16_end);
if (!end)
return;
delegate_->DeleteSurroundingText(gfx::Range(*start, *end));
} }
void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {} void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h" #include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/string_piece.h" #include "base/strings/string16.h"
#include "ui/base/ime/text_input_client.h" #include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_flags.h" #include "ui/base/ime/text_input_flags.h"
#include "ui/base/ime/text_input_mode.h" #include "ui/base/ime/text_input_mode.h"
...@@ -26,9 +26,6 @@ class KeyboardUIController; ...@@ -26,9 +26,6 @@ class KeyboardUIController;
namespace exo { namespace exo {
class Surface; class Surface;
size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset);
size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset);
// This class bridges the ChromeOS input method and a text-input context. // This class bridges the ChromeOS input method and a text-input context.
class TextInput : public ui::TextInputClient, class TextInput : public ui::TextInputClient,
public ash::KeyboardControllerObserver { public ash::KeyboardControllerObserver {
......
...@@ -9,12 +9,14 @@ ...@@ -9,12 +9,14 @@
#include <wayland-server-protocol-core.h> #include <wayland-server-protocol-core.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/exo/display.h" #include "components/exo/display.h"
#include "components/exo/text_input.h" #include "components/exo/text_input.h"
#include "components/exo/wayland/serial_tracker.h" #include "components/exo/wayland/serial_tracker.h"
#include "components/exo/wayland/server_util.h" #include "components/exo/wayland/server_util.h"
#include "components/exo/xkb_tracker.h" #include "components/exo/xkb_tracker.h"
#include "ui/base/ime/utf_offset.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/events/keycodes/dom/keycode_converter.h"
...@@ -77,17 +79,22 @@ class WaylandTextInputDelegate : public TextInput::Delegate { ...@@ -77,17 +79,22 @@ class WaylandTextInputDelegate : public TextInput::Delegate {
style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT; style = ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_INCORRECT;
break; break;
} }
const size_t start = const auto start =
OffsetFromUTF16Offset(composition.text, span.start_offset); ui::Utf8OffsetFromUtf16Offset(composition.text, span.start_offset);
const size_t end = if (!start)
OffsetFromUTF16Offset(composition.text, span.end_offset); continue;
zwp_text_input_v1_send_preedit_styling(text_input_, start, end - start, const auto end =
ui::Utf8OffsetFromUtf16Offset(composition.text, span.end_offset);
if (!end)
continue;
zwp_text_input_v1_send_preedit_styling(text_input_, *start, *end - *start,
style); style);
} }
const size_t pos = const auto pos = ui::Utf8OffsetFromUtf16Offset(
OffsetFromUTF16Offset(composition.text, composition.selection.start()); composition.text, composition.selection.start());
zwp_text_input_v1_send_preedit_cursor(text_input_, pos); if (pos)
zwp_text_input_v1_send_preedit_cursor(text_input_, *pos);
const std::string utf8 = base::UTF16ToUTF8(composition.text); const std::string utf8 = base::UTF16ToUTF8(composition.text);
zwp_text_input_v1_send_preedit_string( zwp_text_input_v1_send_preedit_string(
...@@ -224,9 +231,14 @@ void text_input_set_surrounding_text(wl_client* client, ...@@ -224,9 +231,14 @@ void text_input_set_surrounding_text(wl_client* client,
uint32_t cursor, uint32_t cursor,
uint32_t anchor) { uint32_t anchor) {
TextInput* text_input = GetUserDataAs<TextInput>(resource); TextInput* text_input = GetUserDataAs<TextInput>(resource);
text_input->SetSurroundingText(base::UTF8ToUTF16(text), auto utf16_cursor = ui::Utf16OffsetFromUtf8Offset(text, cursor);
OffsetFromUTF8Offset(text, cursor), if (!utf16_cursor)
OffsetFromUTF8Offset(text, anchor)); return;
auto utf16_anchor = ui::Utf16OffsetFromUtf8Offset(text, anchor);
if (!utf16_anchor)
return;
text_input->SetSurroundingText(base::UTF8ToUTF16(text), *utf16_cursor,
*utf16_anchor);
} }
void text_input_set_content_type(wl_client* client, void text_input_set_content_type(wl_client* client,
......
...@@ -96,6 +96,8 @@ component("base") { ...@@ -96,6 +96,8 @@ component("base") {
"dragdrop/os_exchange_data.h", "dragdrop/os_exchange_data.h",
"dragdrop/os_exchange_data_provider_factory.cc", "dragdrop/os_exchange_data_provider_factory.cc",
"dragdrop/os_exchange_data_provider_factory.h", "dragdrop/os_exchange_data_provider_factory.h",
"ime/utf_offset.cc",
"ime/utf_offset.h",
"l10n/formatter.cc", "l10n/formatter.cc",
"l10n/formatter.h", "l10n/formatter.h",
"l10n/l10n_font_util.cc", "l10n/l10n_font_util.cc",
...@@ -854,6 +856,7 @@ test("ui_base_unittests") { ...@@ -854,6 +856,7 @@ test("ui_base_unittests") {
sources = [ sources = [
"class_property_unittest.cc", "class_property_unittest.cc",
"ime/utf_offset_unittest.cc",
"l10n/l10n_util_unittest.cc", "l10n/l10n_util_unittest.cc",
"l10n/time_format_unittest.cc", "l10n/time_format_unittest.cc",
"layout_unittest.cc", "layout_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/utf_offset.h"
#include <string>
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
namespace ui {
base::Optional<size_t> Utf16OffsetFromUtf8Offset(base::StringPiece text,
size_t utf8_offset) {
if (utf8_offset > text.length())
return base::nullopt;
// TODO(hidehiko): Update not to depend on UTF8ToUTF16 to avoid
// unnecessary memory allocation.
base::string16 converted;
if (!base::UTF8ToUTF16(text.data(), utf8_offset, &converted))
return base::nullopt;
return converted.length();
}
base::Optional<size_t> Utf8OffsetFromUtf16Offset(base::StringPiece16 text,
size_t utf16_offset) {
if (utf16_offset > text.length())
return base::nullopt;
// TODO(hidehiko): Update not to depend on UTF16ToUTF8 to avoid
// unnecessary memory allocation.
std::string converted;
if (!base::UTF16ToUTF8(text.data(), utf16_offset, &converted))
return base::nullopt;
return converted.length();
}
} // namespace ui
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_BASE_IME_UTF_OFFSET_H_
#define UI_BASE_IME_UTF_OFFSET_H_
#include "base/component_export.h"
#include "base/optional.h"
#include "base/strings/string_piece_forward.h"
namespace ui {
// Given UTF8 string and its valid offset, returns the offset in UTF16.
// Returns nullopt if the given offset is invalid (not at a valid boundary
// or out of range).
COMPONENT_EXPORT(UI_BASE)
base::Optional<size_t> Utf16OffsetFromUtf8Offset(base::StringPiece text,
size_t utf8_offset);
// Given UTF16 string and its valid offset, returns the offset in UTF8.
// Returns nullopt if the given offset is invalid (not at a valid boundary
// or out of range).
COMPONENT_EXPORT(UI_BASE)
base::Optional<size_t> Utf8OffsetFromUtf16Offset(base::StringPiece16 text,
size_t utf16_offset);
} // namespace ui
#endif // UI_BASE_IME_UTF_OFFSET_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/ime/utf_offset.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ui {
namespace {
TEST(UtfOffsetTest, Utf16OffsetFromUtf8Offset) {
constexpr struct {
const char* str;
size_t offset;
base::Optional<size_t> expect;
} kTestCases[] = {
// 1 byte letters.
{u8"ab", 0, 0},
{u8"ab", 1, 1},
{u8"ab", 2, 2},
{u8"ab", 3, base::nullopt},
// 2 byte letters. \u03A9=\xCE\xA9 is greek OMEGA.
{u8"\u03A9\u03A9", 0, 0},
{u8"\u03A9\u03A9", 1, base::nullopt},
{u8"\u03A9\u03A9", 2, 1},
{u8"\u03A9\u03A9", 3, base::nullopt},
{u8"\u03A9\u03A9", 4, 2},
{u8"\u03A9\u03A9", 5, base::nullopt},
// 3 byte letters. \u3042=\xE3\x81\x82 is Japanese "A".
{u8"\u3042\u3042", 0, 0},
{u8"\u3042\u3042", 1, base::nullopt},
{u8"\u3042\u3042", 2, base::nullopt},
{u8"\u3042\u3042", 3, 1},
{u8"\u3042\u3042", 4, base::nullopt},
{u8"\u3042\u3042", 5, base::nullopt},
{u8"\u3042\u3042", 6, 2},
{u8"\u3042\u3042", 7, base::nullopt},
// 4 byte letters. \U0001F3B7=\xF0\x9F\x8E\xB7 is "SAXOPHONE" emoji.
// Note that a surrogate pair advances by 2 in UTF16.
{u8"\U0001F3B7\U0001F3B7", 0, 0},
{u8"\U0001F3B7\U0001F3B7", 1, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 2, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 3, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 4, 2},
{u8"\U0001F3B7\U0001F3B7", 5, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 6, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 7, base::nullopt},
{u8"\U0001F3B7\U0001F3B7", 8, 4},
{u8"\U0001F3B7\U0001F3B7", 9, base::nullopt},
// Mix case.
{u8"a\u03A9b\u3042c\U0001F3B7d", 0, 0},
{u8"a\u03A9b\u3042c\U0001F3B7d", 1, 1},
{u8"a\u03A9b\u3042c\U0001F3B7d", 2, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 3, 2},
{u8"a\u03A9b\u3042c\U0001F3B7d", 4, 3},
{u8"a\u03A9b\u3042c\U0001F3B7d", 5, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 6, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 7, 4},
{u8"a\u03A9b\u3042c\U0001F3B7d", 8, 5},
{u8"a\u03A9b\u3042c\U0001F3B7d", 9, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 10, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 11, base::nullopt},
{u8"a\u03A9b\u3042c\U0001F3B7d", 12, 7},
{u8"a\u03A9b\u3042c\U0001F3B7d", 13, 8},
{u8"a\u03A9b\u3042c\U0001F3B7d", 14, base::nullopt},
};
for (const auto& test_case : kTestCases) {
EXPECT_EQ(test_case.expect,
Utf16OffsetFromUtf8Offset(test_case.str, test_case.offset))
<< " at " << test_case.str << "[" << test_case.offset << "]";
}
}
TEST(UtfOffsetTest, Utf8OffsetFromUtf16Offset) {
constexpr struct {
const char16_t* str;
size_t offset;
base::Optional<size_t> expect;
} kTestCases[] = {
// 1 byte letters.
{u"ab", 0, 0},
{u"ab", 1, 1},
{u"ab", 2, 2},
{u"ab", 3, base::nullopt},
// 2 byte letters.
{u"\u03A9\u03A9", 0, 0},
{u"\u03A9\u03A9", 1, 2},
{u"\u03A9\u03A9", 2, 4},
{u"\u03A9\u03A9", 3, base::nullopt},
// 3 byte letters.
{u"\u3042\u3042", 0, 0},
{u"\u3042\u3042", 1, 3},
{u"\u3042\u3042", 2, 6},
{u"\u3042\u3042", 3, base::nullopt},
// 4 byte letters = surrogate pairs.
{u"\U0001F3B7\U0001F3B7", 0, 0},
{u"\U0001F3B7\U0001F3B7", 1, base::nullopt},
{u"\U0001F3B7\U0001F3B7", 2, 4},
{u"\U0001F3B7\U0001F3B7", 3, base::nullopt},
{u"\U0001F3B7\U0001F3B7", 4, 8},
{u"\U0001F3B7\U0001F3B7", 5, base::nullopt},
{u"\U0001F3B7\U0001F3B7", 6, base::nullopt},
// Mix case.
{u"a\u03A9b\u3042c\U0001F3B7d", 0, 0},
{u"a\u03A9b\u3042c\U0001F3B7d", 1, 1},
{u"a\u03A9b\u3042c\U0001F3B7d", 2, 3},
{u"a\u03A9b\u3042c\U0001F3B7d", 3, 4},
{u"a\u03A9b\u3042c\U0001F3B7d", 4, 7},
{u"a\u03A9b\u3042c\U0001F3B7d", 5, 8},
{u"a\u03A9b\u3042c\U0001F3B7d", 6, base::nullopt},
{u"a\u03A9b\u3042c\U0001F3B7d", 7, 12},
{u"a\u03A9b\u3042c\U0001F3B7d", 8, 13},
{u"a\u03A9b\u3042c\U0001F3B7d", 9, base::nullopt},
};
for (const auto& test_case : kTestCases) {
// TODO(crbug.com/911896): Get rid of reinterpret_cast on switching
// to char16_t.
base::string16 text(reinterpret_cast<const base::char16*>(test_case.str));
EXPECT_EQ(test_case.expect,
Utf8OffsetFromUtf16Offset(text, test_case.offset))
<< " at " << text << "[" << test_case.offset << "]";
}
}
} // namespace
} // namespace ui
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