Commit 8c349098 authored by Siye Liu's avatar Siye Liu Committed by Commit Bot

Implement interim character selection highlight during IME composition.

Interim character selection is a standard UI element of Korean and some
Chinese compositions. The interim char selection spans exactly one char
and is visually represented as a solid rectangle behind the text, which
is the visually the same as text selection highlight. In order to render
the blue background highlight during composition that has interim char
selection, we need to introduce a new flag |interim_char_selection| in
ui::ImeTextSpan and new flag |interim_char_selection_| in
blink::ImeTextSpan. If the flag is set to true, then one composition
marker is added with background color equal to active background text
selection highlight color.

Bug: 1095682
Change-Id: If06b0ce38c2993427c04efecf6f3e2e4d9f6ec7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2249051
Commit-Queue: Siye Liu <siliu@microsoft.com>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarYohei Yukawa <yukawa@chromium.org>
Reviewed-by: default avatarSiye Liu <siliu@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#780377}
parent 49323769
...@@ -20,6 +20,7 @@ ImeTextSpan::ImeTextSpan(Type type, ...@@ -20,6 +20,7 @@ ImeTextSpan::ImeTextSpan(Type type,
const Color& background_color, const Color& background_color,
const Color& suggestion_highlight_color, const Color& suggestion_highlight_color,
bool remove_on_finish_composing, bool remove_on_finish_composing,
bool interim_char_selection,
const Vector<String>& suggestions) const Vector<String>& suggestions)
: type_(type), : type_(type),
underline_color_(underline_color), underline_color_(underline_color),
...@@ -29,6 +30,7 @@ ImeTextSpan::ImeTextSpan(Type type, ...@@ -29,6 +30,7 @@ ImeTextSpan::ImeTextSpan(Type type,
background_color_(background_color), background_color_(background_color),
suggestion_highlight_color_(suggestion_highlight_color), suggestion_highlight_color_(suggestion_highlight_color),
remove_on_finish_composing_(remove_on_finish_composing), remove_on_finish_composing_(remove_on_finish_composing),
interim_char_selection_(interim_char_selection),
suggestions_(suggestions) { suggestions_(suggestions) {
// Sanitize offsets by ensuring a valid range corresponding to the last // Sanitize offsets by ensuring a valid range corresponding to the last
// possible position. // possible position.
...@@ -111,6 +113,7 @@ ImeTextSpan::ImeTextSpan(const ui::ImeTextSpan& ime_text_span) ...@@ -111,6 +113,7 @@ ImeTextSpan::ImeTextSpan(const ui::ImeTextSpan& ime_text_span)
Color(ime_text_span.background_color), Color(ime_text_span.background_color),
Color(ime_text_span.suggestion_highlight_color), Color(ime_text_span.suggestion_highlight_color),
ime_text_span.remove_on_finish_composing, ime_text_span.remove_on_finish_composing,
ime_text_span.interim_char_selection,
ConvertStdVectorOfStdStringsToVectorOfStrings( ConvertStdVectorOfStdStringsToVectorOfStrings(
ime_text_span.suggestions)) {} ime_text_span.suggestions)) {}
} // namespace blink } // namespace blink
...@@ -55,6 +55,7 @@ class CORE_EXPORT ImeTextSpan { ...@@ -55,6 +55,7 @@ class CORE_EXPORT ImeTextSpan {
const Color& background_color, const Color& background_color,
const Color& suggestion_highlight_color = Color::kTransparent, const Color& suggestion_highlight_color = Color::kTransparent,
bool remove_on_finish_composing = false, bool remove_on_finish_composing = false,
bool interim_char_selection_ = false,
const Vector<String>& suggestions = Vector<String>()); const Vector<String>& suggestions = Vector<String>());
explicit ImeTextSpan(const ui::ImeTextSpan&); explicit ImeTextSpan(const ui::ImeTextSpan&);
...@@ -75,6 +76,7 @@ class CORE_EXPORT ImeTextSpan { ...@@ -75,6 +76,7 @@ class CORE_EXPORT ImeTextSpan {
bool NeedsRemovalOnFinishComposing() const { bool NeedsRemovalOnFinishComposing() const {
return remove_on_finish_composing_; return remove_on_finish_composing_;
} }
bool InterimCharSelection() const { return interim_char_selection_; }
const Vector<String>& Suggestions() const { return suggestions_; } const Vector<String>& Suggestions() const { return suggestions_; }
private: private:
...@@ -88,6 +90,7 @@ class CORE_EXPORT ImeTextSpan { ...@@ -88,6 +90,7 @@ class CORE_EXPORT ImeTextSpan {
Color background_color_; Color background_color_;
Color suggestion_highlight_color_; Color suggestion_highlight_color_;
bool remove_on_finish_composing_; bool remove_on_finish_composing_;
bool interim_char_selection_;
Vector<String> suggestions_; Vector<String> suggestions_;
}; };
......
...@@ -28,6 +28,16 @@ ImeTextSpan CreateImeTextSpan( ...@@ -28,6 +28,16 @@ ImeTextSpan CreateImeTextSpan(
Color::kTransparent, Color::kTransparent); Color::kTransparent, Color::kTransparent);
} }
ImeTextSpan CreateImeTextSpan(unsigned start_offset,
unsigned end_offset,
bool interim_char_selection) {
return ImeTextSpan(
ImeTextSpan::Type::kComposition, start_offset, end_offset,
Color::kTransparent, ui::mojom::ImeTextSpanThickness::kNone,
ui::mojom::ImeTextSpanUnderlineStyle::kNone, Color::kTransparent,
Color::kTransparent, Color::kTransparent, false, interim_char_selection);
}
TEST(ImeTextSpanTest, OneChar) { TEST(ImeTextSpanTest, OneChar) {
ImeTextSpan ime_text_span = CreateImeTextSpan(0, 1); ImeTextSpan ime_text_span = CreateImeTextSpan(0, 1);
EXPECT_EQ(0u, ime_text_span.StartOffset()); EXPECT_EQ(0u, ime_text_span.StartOffset());
...@@ -103,5 +113,12 @@ TEST(ImeTextSpanTest, UnderlineStyles) { ...@@ -103,5 +113,12 @@ TEST(ImeTextSpanTest, UnderlineStyles) {
ime_text_span.UnderlineStyle()); ime_text_span.UnderlineStyle());
} }
TEST(ImeTextSpanTest, InterimCharSelection) {
ImeTextSpan ime_text_span = CreateImeTextSpan(0, 1, false);
EXPECT_EQ(false, ime_text_span.InterimCharSelection());
ime_text_span = CreateImeTextSpan(0, 1, true);
EXPECT_EQ(true, ime_text_span.InterimCharSelection());
}
} // namespace } // namespace
} // namespace blink } // namespace blink
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
#include "third_party/blink/renderer/core/events/composition_event.h" #include "third_party/blink/renderer/core/events/composition_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h" #include "third_party/blink/renderer/core/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
...@@ -651,12 +652,21 @@ void InputMethodController::AddImeTextSpans( ...@@ -651,12 +652,21 @@ void InputMethodController::AddImeTextSpans(
continue; continue;
switch (ime_text_span.GetType()) { switch (ime_text_span.GetType()) {
case ImeTextSpan::Type::kComposition: case ImeTextSpan::Type::kComposition: {
Color background_color =
GetDocument().GetPage() && ime_text_span.InterimCharSelection()
? LayoutTheme::GetTheme().ActiveSelectionBackgroundColor(
GetDocument()
.GetPage()
->GetVisualViewport()
.UsedColorScheme())
: ime_text_span.BackgroundColor();
GetDocument().Markers().AddCompositionMarker( GetDocument().Markers().AddCompositionMarker(
ephemeral_line_range, ime_text_span.UnderlineColor(), ephemeral_line_range, ime_text_span.UnderlineColor(),
ime_text_span.Thickness(), ime_text_span.UnderlineStyle(), ime_text_span.Thickness(), ime_text_span.UnderlineStyle(),
ime_text_span.TextColor(), ime_text_span.BackgroundColor()); ime_text_span.TextColor(), background_color);
break; break;
}
case ImeTextSpan::Type::kSuggestion: case ImeTextSpan::Type::kSuggestion:
case ImeTextSpan::Type::kMisspellingSuggestion: case ImeTextSpan::Type::kMisspellingSuggestion:
const SuggestionMarker::SuggestionType suggestion_type = const SuggestionMarker::SuggestionType suggestion_type =
......
...@@ -20,8 +20,10 @@ ...@@ -20,8 +20,10 @@
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h" #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h" #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
using ui::mojom::ImeTextSpanThickness; using ui::mojom::ImeTextSpanThickness;
using ui::mojom::ImeTextSpanUnderlineStyle; using ui::mojom::ImeTextSpanUnderlineStyle;
...@@ -1401,6 +1403,28 @@ TEST_F(InputMethodControllerTest, SetCompositionPlainTextWithIme_Text_Span) { ...@@ -1401,6 +1403,28 @@ TEST_F(InputMethodControllerTest, SetCompositionPlainTextWithIme_Text_Span) {
EXPECT_EQ(1u, GetDocument().Markers().Markers()[0]->EndOffset()); EXPECT_EQ(1u, GetDocument().Markers().Markers()[0]->EndOffset());
} }
TEST_F(InputMethodControllerTest,
SetCompositionPlainTextWithIme_Text_Span_Interim_Char_Selection) {
InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
Vector<ImeTextSpan> ime_text_spans;
ime_text_spans.push_back(ImeTextSpan(
ImeTextSpan::Type::kComposition, 0, 1, Color(255, 0, 0),
ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid, 0, 0, 0,
false, true /*interim_char_selection*/));
Controller().SetComposition("a", ime_text_spans, 0, 1);
ASSERT_EQ(1u, GetDocument().Markers().Markers().size());
auto* styleable_marker =
DynamicTo<StyleableMarker>(GetDocument().Markers().Markers()[0].Get());
Color background_color =
LayoutTheme::GetTheme().ActiveSelectionBackgroundColor(
GetFrame().GetPage()->GetVisualViewport().UsedColorScheme());
EXPECT_EQ(background_color, styleable_marker->BackgroundColor());
}
TEST_F(InputMethodControllerTest, CommitPlainTextWithIme_Text_SpanInsert) { TEST_F(InputMethodControllerTest, CommitPlainTextWithIme_Text_SpanInsert) {
InsertHTMLElement("<div id='sample' contenteditable>Initial text.</div>", InsertHTMLElement("<div id='sample' contenteditable>Initial text.</div>",
"sample"); "sample");
......
...@@ -69,6 +69,7 @@ struct COMPONENT_EXPORT(UI_BASE_IME_TYPES) ImeTextSpan { ...@@ -69,6 +69,7 @@ struct COMPONENT_EXPORT(UI_BASE_IME_TYPES) ImeTextSpan {
rhs.suggestion_highlight_color) && rhs.suggestion_highlight_color) &&
(this->remove_on_finish_composing == (this->remove_on_finish_composing ==
rhs.remove_on_finish_composing) && rhs.remove_on_finish_composing) &&
(this->interim_char_selection == rhs.interim_char_selection) &&
(this->suggestions == rhs.suggestions); (this->suggestions == rhs.suggestions);
} }
...@@ -84,6 +85,7 @@ struct COMPONENT_EXPORT(UI_BASE_IME_TYPES) ImeTextSpan { ...@@ -84,6 +85,7 @@ struct COMPONENT_EXPORT(UI_BASE_IME_TYPES) ImeTextSpan {
SkColor background_color; SkColor background_color;
SkColor suggestion_highlight_color; SkColor suggestion_highlight_color;
bool remove_on_finish_composing = false; bool remove_on_finish_composing = false;
bool interim_char_selection = false;
std::vector<std::string> suggestions; std::vector<std::string> suggestions;
}; };
......
...@@ -99,5 +99,6 @@ struct ImeTextSpan { ...@@ -99,5 +99,6 @@ struct ImeTextSpan {
uint32 background_color; uint32 background_color;
uint32 suggestion_highlight_color; uint32 suggestion_highlight_color;
bool remove_on_finish_composing; bool remove_on_finish_composing;
bool interim_char_selection;
array<string> suggestions; array<string> suggestions;
}; };
...@@ -190,6 +190,7 @@ bool StructTraits<ui::mojom::ImeTextSpanDataView, ui::ImeTextSpan>::Read( ...@@ -190,6 +190,7 @@ bool StructTraits<ui::mojom::ImeTextSpanDataView, ui::ImeTextSpan>::Read(
out->background_color = data.background_color(); out->background_color = data.background_color();
out->suggestion_highlight_color = data.suggestion_highlight_color(); out->suggestion_highlight_color = data.suggestion_highlight_color();
out->remove_on_finish_composing = data.remove_on_finish_composing(); out->remove_on_finish_composing = data.remove_on_finish_composing();
out->interim_char_selection = data.interim_char_selection();
if (!data.ReadSuggestions(&out->suggestions)) if (!data.ReadSuggestions(&out->suggestions))
return false; return false;
return true; return true;
......
...@@ -67,6 +67,9 @@ struct COMPONENT_EXPORT(IME_SHARED_MOJOM_TRAITS) ...@@ -67,6 +67,9 @@ struct COMPONENT_EXPORT(IME_SHARED_MOJOM_TRAITS)
static bool remove_on_finish_composing(const ui::ImeTextSpan& c) { static bool remove_on_finish_composing(const ui::ImeTextSpan& c) {
return c.remove_on_finish_composing; return c.remove_on_finish_composing;
} }
static bool interim_char_selection(const ui::ImeTextSpan& c) {
return c.interim_char_selection;
}
static std::vector<std::string> suggestions(const ui::ImeTextSpan& c) { static std::vector<std::string> suggestions(const ui::ImeTextSpan& c) {
return c.suggestions; return c.suggestions;
} }
......
...@@ -693,6 +693,7 @@ HRESULT TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) { ...@@ -693,6 +693,7 @@ HRESULT TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
// reset the flag since we've already inserted/replaced the text. // reset the flag since we've already inserted/replaced the text.
new_text_inserted_ = false; new_text_inserted_ = false;
is_selection_interim_char_ = false;
// reset string_buffer_ if composition is no longer active. // reset string_buffer_ if composition is no longer active.
if (!text_input_client_->HasCompositionText()) { if (!text_input_client_->HasCompositionText()) {
...@@ -759,6 +760,7 @@ HRESULT TSFTextStore::SetSelection(ULONG selection_buffer_size, ...@@ -759,6 +760,7 @@ HRESULT TSFTextStore::SetSelection(ULONG selection_buffer_size,
} }
selection_.set_start(start_pos); selection_.set_start(start_pos);
selection_.set_end(end_pos); selection_.set_end(end_pos);
is_selection_interim_char_ = selection_buffer[0].style.fInterimChar;
} }
return S_OK; return S_OK;
} }
...@@ -1051,6 +1053,10 @@ bool TSFTextStore::GetCompositionStatus( ...@@ -1051,6 +1053,10 @@ bool TSFTextStore::GetCompositionStatus(
span.end_offset = start_pos + length; span.end_offset = start_pos + length;
span.underline_color = SK_ColorBLACK; span.underline_color = SK_ColorBLACK;
span.background_color = SK_ColorTRANSPARENT; span.background_color = SK_ColorTRANSPARENT;
if (selection_.EqualsIgnoringDirection(
gfx::Range(span.start_offset, span.end_offset))) {
span.interim_char_selection = is_selection_interim_char_;
}
if (has_display_attribute) if (has_display_attribute)
GetStyle(display_attribute, &span); GetStyle(display_attribute, &span);
spans->push_back(span); spans->push_back(span);
......
...@@ -391,6 +391,10 @@ class COMPONENT_EXPORT(UI_BASE_IME_WIN) TSFTextStore ...@@ -391,6 +391,10 @@ class COMPONENT_EXPORT(UI_BASE_IME_WIN) TSFTextStore
// |selection_.end()|: 4 // |selection_.end()|: 4
gfx::Range selection_; gfx::Range selection_;
// Indicates if the selection is an interim character. Please refer to
// https://docs.microsoft.com/en-us/windows/win32/api/textstor/ns-textstor-ts_selectionstyle
bool is_selection_interim_char_ = false;
// |start_offset| and |end_offset| of |text_spans_| indicates // |start_offset| and |end_offset| of |text_spans_| indicates
// the offsets in |string_buffer_document_|. // the offsets in |string_buffer_document_|.
// Example: "aoi" is committed. There are two underlines in "umi" and "no". // Example: "aoi" is committed. There are two underlines in "umi" and "no".
......
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