1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "renderer/win32/win32_renderer_util.h"
31
32 #define _ATL_NO_AUTOMATIC_NAMESPACE
33 #define _WTL_NO_AUTOMATIC_NAMESPACE
34 #include <atlbase.h>
35 #include <atlapp.h>
36 #include <atlgdi.h>
37 #include <atlmisc.h>
38 #include <winuser.h>
39
40 #include <algorithm>
41 #include <limits>
42 #include <memory>
43 #include <string>
44 #include <vector>
45
46 #include "base/logging.h"
47 #include "base/system_util.h"
48 #include "base/util.h"
49 #include "base/win_util.h"
50 #include "protocol/renderer_command.pb.h"
51 #include "renderer/win32/win32_font_util.h"
52
53 namespace mozc {
54 namespace renderer {
55 namespace win32 {
56 using ATL::CWindow;
57 using WTL::CDC;
58 using WTL::CDCHandle;
59 using WTL::CSize;
60 using WTL::CRect;
61 using WTL::CPoint;
62 using WTL::CFont;
63 using WTL::CFontHandle;
64 using WTL::CLogFont;
65 using std::unique_ptr;
66
67 typedef mozc::commands::RendererCommand::CompositionForm CompositionForm;
68 typedef mozc::commands::RendererCommand::CandidateForm CandidateForm;
69
70 namespace {
71 // This template class is used to represent rendering information which may
72 // or may not be available depends on the application and operations.
73 template <typename T>
74 class Optional {
75 public:
Optional()76 Optional()
77 : value_(T()),
78 has_value_(false) {}
79
Optional(const T & src)80 explicit Optional(const T &src)
81 : value_(src),
82 has_value_(true) {}
83
value() const84 const T &value() const {
85 DCHECK(has_value_);
86 return value_;
87 }
88
mutable_value()89 T *mutable_value() {
90 has_value_ = true;
91 return &value_;
92 }
93
has_value() const94 bool has_value() const {
95 return has_value_;
96 }
97
Clear()98 void Clear() {
99 has_value_ = false;
100 }
101
102 private:
103 T value_;
104 bool has_value_;
105 };
106
107 // A set of rendering information relevant to the target application. Note
108 // that all of the positional fields are (logical) screen coordinates.
109 struct CandidateWindowLayoutParams {
110 Optional<HWND> window_handle;
111 Optional<IMECHARPOSITION> char_pos;
112 Optional<CPoint> composition_form_topleft;
113 Optional<CandidateWindowLayout> candidate_form;
114 Optional<CRect> caret_rect;
115 Optional<CLogFont> composition_font;
116 Optional<CLogFont> default_gui_font;
117 Optional<CRect> client_rect;
118 Optional<bool> vertical_writing;
119 };
120
IsValidRect(const commands::RendererCommand::Rectangle & rect)121 bool IsValidRect(const commands::RendererCommand::Rectangle &rect) {
122 return rect.has_left() && rect.has_top() &&
123 rect.has_right() && rect.has_bottom();
124 }
125
ToRect(const commands::RendererCommand::Rectangle & rect)126 CRect ToRect(const commands::RendererCommand::Rectangle &rect) {
127 DCHECK(IsValidRect(rect));
128 return CRect(rect.left(), rect.top(), rect.right(), rect.bottom());
129 }
130
IsValidPoint(const commands::RendererCommand::Point & point)131 bool IsValidPoint(const commands::RendererCommand::Point &point) {
132 return point.has_x() && point.has_y();
133 }
134
ToPoint(const commands::RendererCommand::Point & point)135 CPoint ToPoint(const commands::RendererCommand::Point &point) {
136 DCHECK(IsValidPoint(point));
137 return CPoint(point.x(), point.y());
138 }
139
140 // Returns an absolute font height of the composition font.
141 // Note that this function does not take the DPI virtualization into
142 // consideration so the caller is responsible to multiply an appropriate
143 // scaling factor.
144 // If a composition font is not available, this function try to use
145 // the default GUI font on this session.
GetAbsoluteFontHeight(const CandidateWindowLayoutParams & params)146 int GetAbsoluteFontHeight(const CandidateWindowLayoutParams ¶ms) {
147 // A negative font height is also valid in the LONGFONT structure.
148 // Use |abs| for normalization.
149 // http://msdn.microsoft.com/en-us/library/dd145037.aspx
150 if (params.composition_font.has_value()) {
151 return abs(params.composition_font.value().lfHeight);
152 }
153 if (params.default_gui_font.has_value()) {
154 return abs(params.default_gui_font.value().lfHeight);
155 }
156 return 0;
157 }
158
159 // "base_pos" is an ideal position where the candidate window is placed.
160 // Basically the ideal position depends on historical reason for each
161 // country and language.
162 // As for Japanese IME, the bottom-left (for horizontal writing) and
163 // top-left (for vertical writing) corner of the target segment have been
164 // used for many years.
GetBasePositionFromExcludeRect(const CRect & exclude_rect,bool is_vertical)165 CPoint GetBasePositionFromExcludeRect(const CRect &exclude_rect,
166 bool is_vertical) {
167 if (is_vertical) {
168 // Vertical
169 return exclude_rect.TopLeft();
170 }
171
172 // Horizontal
173 return CPoint(exclude_rect.left, exclude_rect.bottom);
174 }
175
GetBasePositionFromIMECHARPOSITION(const IMECHARPOSITION & char_pos,bool is_vertical)176 CPoint GetBasePositionFromIMECHARPOSITION(const IMECHARPOSITION &char_pos,
177 bool is_vertical) {
178 if (is_vertical) {
179 return CPoint(char_pos.pt.x - char_pos.cLineHeight, char_pos.pt.y);
180 }
181 // Horizontal
182 return CPoint(char_pos.pt.x, char_pos.pt.y + char_pos.cLineHeight);
183 }
184
185 // Returns false if given |form| should be ignored for some compatibility
186 // reason. Otherwise, returns true.
IsCompatibleCompositionForm(const CompositionForm & form,int compatibility_mode)187 bool IsCompatibleCompositionForm(const CompositionForm &form,
188 int compatibility_mode) {
189 // If IGNORE_DEFAULT_COMPOSITION_FORM flag is specified and all the fields
190 // in the CompositionForm is 0, returns false.
191 if ((compatibility_mode & IGNORE_DEFAULT_COMPOSITION_FORM) !=
192 IGNORE_DEFAULT_COMPOSITION_FORM) {
193 return true;
194 }
195
196 // Note that CompositionForm::DEFAULT is defined as 0.
197 if (form.style_bits() != CompositionForm::DEFAULT) {
198 return true;
199 }
200
201 if (!IsValidPoint(form.current_position())) {
202 return true;
203 }
204 const CPoint current_position = ToPoint(form.current_position());
205 if (current_position != CPoint(0, 0)) {
206 return true;
207 }
208
209 if (!IsValidRect(form.area())) {
210 return true;
211 }
212 const CRect area = ToRect(form.area());
213 if (!area.IsRectNull()) {
214 return true;
215 }
216
217 return false;
218 }
219
ExtractParams(LayoutManager * layout,int compatibility_mode,const commands::RendererCommand::ApplicationInfo & app_info,CandidateWindowLayoutParams * params)220 bool ExtractParams(
221 LayoutManager *layout,
222 int compatibility_mode,
223 const commands::RendererCommand::ApplicationInfo &app_info,
224 CandidateWindowLayoutParams *params) {
225 DCHECK_NE(NULL, layout);
226 DCHECK_NE(NULL, params);
227
228 params->window_handle.Clear();
229 params->char_pos.Clear();
230 params->composition_form_topleft.Clear();
231 params->candidate_form.Clear();
232 params->caret_rect.Clear();
233 params->composition_font.Clear();
234 params->default_gui_font.Clear();
235 params->client_rect.Clear();
236 params->vertical_writing.Clear();
237
238 if (!app_info.has_target_window_handle()) {
239 return false;
240 }
241 const HWND target_window = WinUtil::DecodeWindowHandle(
242 app_info.target_window_handle());
243
244 *params->window_handle.mutable_value() = target_window;
245
246 if (app_info.has_composition_target()) {
247 const commands::RendererCommand::CharacterPosition &char_pos =
248 app_info.composition_target();
249 // Check the availability of optional fields.
250 if (char_pos.has_position() &&
251 char_pos.has_top_left() &&
252 IsValidPoint(char_pos.top_left()) &&
253 char_pos.has_line_height() &&
254 char_pos.line_height() > 0 &&
255 char_pos.has_document_area() &&
256 IsValidRect(char_pos.document_area())) {
257 // Positional fields are (logical) screen coordinate.
258 IMECHARPOSITION *dest = params->char_pos.mutable_value();
259 dest->dwCharPos = char_pos.position();
260 dest->pt = ToPoint(char_pos.top_left());
261 dest->cLineHeight = char_pos.line_height();
262 dest->rcDocument = ToRect(char_pos.document_area());
263 }
264 }
265
266 if (app_info.has_composition_form()) {
267 const commands::RendererCommand::CompositionForm &form =
268 app_info.composition_form();
269
270 // Check the availability of optional fields.
271 if (form.has_current_position() &&
272 IsValidPoint(form.current_position()) &&
273 IsCompatibleCompositionForm(form, compatibility_mode)) {
274 Optional<CPoint> screen_pos;
275 if (!layout->ClientPointToScreen(
276 target_window, ToPoint(form.current_position()),
277 params->composition_form_topleft.mutable_value())) {
278 params->composition_form_topleft.Clear();
279 }
280 }
281 }
282
283 if (app_info.has_candidate_form()) {
284 const commands::RendererCommand::CandidateForm &form =
285 app_info.candidate_form();
286
287 const uint32 candidate_style_bits = form.style_bits();
288
289 const bool has_candidate_pos_style_bit =
290 ((candidate_style_bits & CandidateForm::CANDIDATEPOS) ==
291 CandidateForm::CANDIDATEPOS);
292
293 const bool has_exclude_style_bit =
294 ((candidate_style_bits & CandidateForm::EXCLUDE) ==
295 CandidateForm::EXCLUDE);
296
297 // Check the availability of optional fields.
298 if ((has_candidate_pos_style_bit || has_exclude_style_bit) &&
299 form.has_current_position() &&
300 IsValidPoint(form.current_position())) {
301 const bool use_local_coord =
302 (compatibility_mode & USE_LOCAL_COORD_FOR_CANDIDATE_FORM) ==
303 USE_LOCAL_COORD_FOR_CANDIDATE_FORM;
304 Optional<CPoint> screen_pos;
305 if (use_local_coord) {
306 if (!layout->LocalPointToScreen(
307 target_window, ToPoint(form.current_position()),
308 screen_pos.mutable_value())) {
309 screen_pos.Clear();
310 }
311 } else {
312 if (!layout->ClientPointToScreen(
313 target_window, ToPoint(form.current_position()),
314 screen_pos.mutable_value())) {
315 screen_pos.Clear();
316 }
317 }
318 if (screen_pos.has_value()) {
319 // Here, we got an appropriate position where the candidate window
320 // can be placed.
321 params->candidate_form.mutable_value()->
322 InitializeWithPosition(screen_pos.value());
323
324 // If |CandidateForm::EXCLUDE| is specified, try to use the |area|
325 // field as an exclude region.
326 if (has_exclude_style_bit &&
327 form.has_area() &&
328 IsValidRect(form.area())) {
329 Optional<CRect> screen_rect;
330 if (use_local_coord) {
331 if (!layout->LocalRectToScreen(
332 target_window, ToRect(form.area()),
333 screen_rect.mutable_value())) {
334 screen_rect.Clear();
335 }
336 } else {
337 if (!layout->ClientRectToScreen(
338 target_window, ToRect(form.area()),
339 screen_rect.mutable_value())) {
340 screen_rect.Clear();
341 }
342 }
343 if (screen_rect.has_value()) {
344 // Here we got an appropriate exclude region too.
345 // Update the |candidate_form| with them.
346 params->candidate_form.mutable_value()->
347 InitializeWithPositionAndExcludeRegion(
348 screen_pos.value(), screen_rect.value());
349 }
350 }
351 }
352 }
353 }
354
355 if (app_info.has_caret_info()) {
356 const commands::RendererCommand::CaretInfo &caret_info =
357 app_info.caret_info();
358
359 // Check the availability of optional fields.
360 if (caret_info.has_blinking() &&
361 caret_info.has_caret_rect() &&
362 IsValidRect(caret_info.caret_rect()) &&
363 caret_info.has_target_window_handle()) {
364 const HWND caret_window = WinUtil::DecodeWindowHandle(
365 caret_info.target_window_handle());
366 const CRect caret_rect_in_client_coord(ToRect(caret_info.caret_rect()));
367 // It seems (0, 0, 0, 0) represents that the application does not have a
368 // valid caret now.
369 if (!caret_rect_in_client_coord.IsRectNull()) {
370 if (!layout->ClientRectToScreen(
371 caret_window, caret_rect_in_client_coord,
372 params->caret_rect.mutable_value())) {
373 params->caret_rect.Clear();
374 }
375 }
376 }
377 }
378
379 if (app_info.has_composition_font()) {
380 if (!mozc::win32::FontUtil::ToLOGFONT(
381 app_info.composition_font(),
382 params->composition_font.mutable_value())) {
383 params->composition_font.Clear();
384 }
385 }
386
387 if (!layout->GetDefaultGuiFont(params->default_gui_font.mutable_value())) {
388 params->default_gui_font.Clear();
389 }
390
391 {
392 CRect client_rect_in_local_coord;
393 CRect client_rect_in_logical_screen_coord;
394 if (layout->GetClientRect(target_window, &client_rect_in_local_coord) &&
395 layout->ClientRectToScreen(
396 target_window, client_rect_in_local_coord,
397 &client_rect_in_logical_screen_coord)) {
398 *params->client_rect.mutable_value() =
399 client_rect_in_logical_screen_coord;
400 }
401 }
402
403 {
404 const LayoutManager::WritingDirection direction =
405 layout->GetWritingDirection(app_info);
406 if (direction == LayoutManager::VERTICAL_WRITING) {
407 *params->vertical_writing.mutable_value() = true;
408 } else if (direction == LayoutManager::HORIZONTAL_WRITING) {
409 *params->vertical_writing.mutable_value() = false;
410 }
411 }
412 return true;
413 }
414
CanUseExcludeRegionInCandidateFrom(const CandidateWindowLayoutParams & params,int compatibility_mode,bool for_suggestion)415 bool CanUseExcludeRegionInCandidateFrom(
416 const CandidateWindowLayoutParams ¶ms,
417 int compatibility_mode,
418 bool for_suggestion) {
419 if (for_suggestion &&
420 ((compatibility_mode & CAN_USE_CANDIDATE_FORM_FOR_SUGGEST) !=
421 CAN_USE_CANDIDATE_FORM_FOR_SUGGEST)) {
422 // This is suggestion and |CAN_USE_CANDIDATE_FORM_FOR_SUGGEST| is not
423 // specified. We cannot assume that |CANDIDATEFORM| is valid in this case.
424 return false;
425 }
426 if (!params.candidate_form.has_value()) {
427 return false;
428 }
429 if (!params.candidate_form.value().has_exclude_region()) {
430 return false;
431 }
432 return true;
433 }
434
ComposePreeditText(const commands::Preedit & preedit,string * preedit_utf8,std::vector<int> * segment_indices,std::vector<CharacterRange> * segment_ranges)435 std::wstring ComposePreeditText(const commands::Preedit &preedit,
436 string *preedit_utf8,
437 std::vector<int> *segment_indices,
438 std::vector<CharacterRange> *segment_ranges) {
439 if (preedit_utf8 != NULL) {
440 preedit_utf8->clear();
441 }
442 if (segment_indices != NULL) {
443 segment_indices->clear();
444 }
445 if (segment_ranges != NULL) {
446 segment_ranges->clear();
447 }
448 std::wstring value;
449 int total_characters = 0;
450 for (size_t segment_index = 0; segment_index < preedit.segment_size();
451 ++segment_index) {
452 const commands::Preedit::Segment &segment = preedit.segment(segment_index);
453 std::wstring segment_value;
454 mozc::Util::UTF8ToWide(segment.value(), &segment_value);
455 value.append(segment_value);
456 if (preedit_utf8 != NULL) {
457 preedit_utf8->append(segment.value());
458 }
459 const int text_length = segment_value.size();
460 if (segment_indices != NULL) {
461 for (size_t i = 0; i < text_length; ++i) {
462 segment_indices->push_back(segment_index);
463 }
464 }
465 if (segment_ranges != NULL) {
466 CharacterRange range;
467 range.begin = total_characters;
468 range.length = text_length;
469 segment_ranges->push_back(range);
470 }
471 total_characters += text_length;
472 }
473 return value;
474 }
475
CalcLayoutWithTextWrappingInternal(CDCHandle dc,const std::wstring & str,const int maximum_line_length,const int initial_offset,std::vector<LineLayout> * line_layouts)476 bool CalcLayoutWithTextWrappingInternal(
477 CDCHandle dc,
478 const std::wstring &str,
479 const int maximum_line_length,
480 const int initial_offset,
481 std::vector<LineLayout> *line_layouts) {
482 DCHECK(line_layouts != NULL);
483 if (initial_offset < 0 || maximum_line_length <= 0 ||
484 maximum_line_length < initial_offset) {
485 LOG(ERROR) << "(initial_offset, maximum_line_length) = ("
486 << initial_offset << ", " << maximum_line_length << ")";
487 return false;
488 }
489
490 TEXTMETRIC metrics = {0};
491 if (!dc.GetTextMetrics(&metrics)) {
492 const int error = ::GetLastError();
493 LOG(ERROR) << "GetTextMetrics failed. error = " << error;
494 return false;
495 }
496
497 int string_index = 0;
498 int current_offset = initial_offset;
499 while (true) {
500 const int remaining_chars = str.size() - string_index;
501 if (remaining_chars <= 0) {
502 return true;
503 }
504
505 // Here we can assume the following conditions are satisfied.
506 // 0 <= current_offset
507 // 0 <= maximum_line_length
508 // current_offset <= maximum_line_length
509 const int remaining_extent = maximum_line_length - current_offset;
510 DCHECK_GE(remaining_extent, 0)
511 << "(remaining_extent, maximum_line_length) = ("
512 << remaining_extent << ", " << maximum_line_length << ")";
513 DCHECK_GE(maximum_line_length, remaining_extent)
514 << "(remaining_extent, maximum_line_length) = ("
515 << remaining_extent << ", " << maximum_line_length << ")";
516
517 int allowable_chars = 0;
518 CSize dummy;
519 BOOL result = dc.GetTextExtentExPoint(
520 str.c_str() + string_index,
521 remaining_chars,
522 &dummy,
523 remaining_extent,
524 &allowable_chars,
525 NULL);
526 if (result == FALSE) {
527 const int error = ::GetLastError();
528 LOG(ERROR) << "GetTextExtentExPoint failed. error = " << error;
529 return false;
530 }
531
532 if (allowable_chars == 0 && current_offset == 0) {
533 // In this case, the region does not have enough space to display the
534 // next character.
535 return false;
536 }
537
538 // Just in case GetTextExtentExPoint returns true but the returned value
539 // is invalid. We have not seen any problem around this API though.
540 if (allowable_chars < 0 || remaining_chars < allowable_chars) {
541 // Something wrong.
542 LOG(ERROR) << "(allowable_chars, remaining_chars) = ("
543 << allowable_chars << ", " << remaining_chars << ")";
544 return false;
545 }
546
547 {
548 LineLayout layout;
549 layout.text = str.substr(string_index, allowable_chars);
550 if (allowable_chars == 0) {
551 // This case occurs only when this line does not have enough space to
552 // render the next character from |current_offset|. We will try to
553 // render text in the next line. Note that an infinite loop should
554 // never occur because we have already checked the case above. This is
555 // why |current_offset| should be positive here.
556 DCHECK_GT(current_offset, 0);
557 layout.line_width = metrics.tmHeight;
558 } else {
559 CSize line_size;
560 int allowable_chars_for_confirmation = 0;
561 std::unique_ptr<int[]> size_buffer(new int[allowable_chars]);
562 result = dc.GetTextExtentExPoint(
563 layout.text.c_str(),
564 layout.text.size(),
565 &line_size,
566 remaining_extent,
567 &allowable_chars_for_confirmation,
568 size_buffer.get());
569 if (result == FALSE) {
570 const int error = ::GetLastError();
571 LOG(ERROR) << "GetTextExtentExPoint failed. error = " << error;
572 return false;
573 }
574 if (allowable_chars != allowable_chars_for_confirmation) {
575 LOG(ERROR)
576 << "(allowable_chars, allowable_chars_for_confirmation) = ("
577 << allowable_chars << ", "
578 << allowable_chars_for_confirmation << ")";
579 return false;
580 }
581 layout.line_length = line_size.cx;
582 layout.line_width = line_size.cy;
583 layout.line_start_offset = current_offset;
584 int next_char_begin = 0;
585 for (size_t character_index = 0; character_index < allowable_chars;
586 ++character_index) {
587 CharacterRange range;
588 range.begin = next_char_begin;
589 range.length = size_buffer[character_index] - next_char_begin;
590 layout.character_positions.push_back(range);
591 next_char_begin = size_buffer[character_index];
592 }
593 }
594 DCHECK_EQ(layout.text.size(), layout.character_positions.size());
595 line_layouts->push_back(layout);
596 }
597
598 string_index += allowable_chars;
599 current_offset = 0;
600 }
601
602 return false;
603 }
604
605 class NativeSystemPreferenceAPI : public SystemPreferenceInterface {
606 public:
~NativeSystemPreferenceAPI()607 virtual ~NativeSystemPreferenceAPI() {}
608
GetDefaultGuiFont(LOGFONTW * log_font)609 virtual bool GetDefaultGuiFont(LOGFONTW *log_font) {
610 if (log_font == NULL) {
611 return false;
612 }
613
614 CLogFont message_box_font;
615 // Use message box font as a default font to be consistent with
616 // the candidate window.
617 // TODO(yukawa): make a theme layer which is responsible for
618 // the look and feel of both composition window and candidate window.
619 // TODO(yukawa): verify the font can render U+005C as a yen sign.
620 // (http://b/1992773)
621 message_box_font.SetMessageBoxFont();
622 // Use factor "3" to be consistent with the candidate window.
623 message_box_font.MakeLarger(3);
624 message_box_font.lfWeight = FW_NORMAL;
625
626 *log_font = message_box_font;
627 return true;
628 }
629 };
630
631 class NativeWorkingAreaAPI : public WorkingAreaInterface {
632 public:
NativeWorkingAreaAPI()633 NativeWorkingAreaAPI() {}
634
635 private:
GetWorkingAreaFromPoint(const POINT & point,RECT * working_area)636 virtual bool GetWorkingAreaFromPoint(const POINT &point,
637 RECT *working_area) {
638 if (working_area == nullptr) {
639 return false;
640 }
641 ::SetRect(working_area, 0, 0, 0, 0);
642
643 // Obtain the monitor's working area
644 const HMONITOR monitor = ::MonitorFromPoint(point,
645 MONITOR_DEFAULTTONEAREST);
646 if (monitor == nullptr) {
647 return false;
648 }
649
650 MONITORINFO monitor_info = {};
651 monitor_info.cbSize = CCSIZEOF_STRUCT(MONITORINFO, dwFlags);
652 if (!::GetMonitorInfo(monitor, &monitor_info)) {
653 const DWORD error = GetLastError();
654 LOG(ERROR) << "GetMonitorInfo failed. Error: " << error;
655 return false;
656 }
657
658 *working_area = monitor_info.rcWork;
659 return true;
660 }
661 };
662
663 class NativeWindowPositionAPI : public WindowPositionInterface {
664 public:
NativeWindowPositionAPI()665 NativeWindowPositionAPI()
666 : logical_to_physical_point_for_per_monitor_dpi_(
667 GetLogicalToPhysicalPointForPerMonitorDPI()) {
668 }
669
~NativeWindowPositionAPI()670 virtual ~NativeWindowPositionAPI() {}
671
LogicalToPhysicalPoint(HWND window_handle,const POINT & logical_coordinate,POINT * physical_coordinate)672 virtual bool LogicalToPhysicalPoint(
673 HWND window_handle, const POINT &logical_coordinate,
674 POINT *physical_coordinate) {
675 if (physical_coordinate == NULL) {
676 return false;
677 }
678 DCHECK_NE(NULL, physical_coordinate);
679 if (!::IsWindow(window_handle)) {
680 return false;
681 }
682
683 // The attached window is likely to be a child window but only root
684 // windows are fully supported by LogicalToPhysicalPoint API. Using
685 // root window handle instead of target window handle is likely to make
686 // this API happy.
687 const HWND root_window_handle = GetRootWindow(window_handle);
688
689 // The document of LogicalToPhysicalPoint API is somewhat ambiguous.
690 // http://msdn.microsoft.com/en-us/library/ms633533.aspx
691 // Both input coordinates and output coordinates of this API are so-called
692 // screen coordinates (offset from the upper-left corner of the screen).
693 // Note that the input coordinates are logical coordinates, which means you
694 // should pass screen coordinates obtained in a DPI-unaware process to
695 // this API. For example, coordinates returned by ClientToScreen API in a
696 // DPI-unaware process are logical coordinates. You can copy these
697 // coordinates to a DPI-aware process and convert them to physical screen
698 // coordinates by LogicalToPhysicalPoint API.
699 *physical_coordinate = logical_coordinate;
700
701 // Despite its name, LogicalToPhysicalPoint API no longer converts
702 // coordinates on Windows 8.1 and later. We must use
703 // LogicalToPhysicalPointForPerMonitorDPI API instead when it is available.
704 // See http://go.microsoft.com/fwlink/?LinkID=307061
705 if (SystemUtil::IsWindows8_1OrLater()) {
706 if (logical_to_physical_point_for_per_monitor_dpi_ == nullptr) {
707 return false;
708 }
709 return logical_to_physical_point_for_per_monitor_dpi_(
710 root_window_handle, physical_coordinate) != FALSE;
711 }
712 // On Windows 8 and prior, it's OK to rely on LogicalToPhysicalPoint API.
713 return ::LogicalToPhysicalPoint(
714 root_window_handle, physical_coordinate) != FALSE;
715 }
716
717 // This method is not const to implement Win32WindowInterface.
GetWindowRect(HWND window_handle,RECT * rect)718 virtual bool GetWindowRect(HWND window_handle, RECT *rect) {
719 return (::GetWindowRect(window_handle, rect) != FALSE);
720 }
721
722 // This method is not const to implement Win32WindowInterface.
GetClientRect(HWND window_handle,RECT * rect)723 virtual bool GetClientRect(HWND window_handle, RECT *rect) {
724 return (::GetClientRect(window_handle, rect) != FALSE);
725 }
726
727 // This method is not const to implement Win32WindowInterface.
ClientToScreen(HWND window_handle,POINT * point)728 virtual bool ClientToScreen(HWND window_handle, POINT *point) {
729 return (::ClientToScreen(window_handle, point) != FALSE);
730 }
731
732 // This method is not const to implement Win32WindowInterface.
IsWindow(HWND window_handle)733 virtual bool IsWindow(HWND window_handle) {
734 return (::IsWindow(window_handle) != FALSE);
735 }
736
737 // This method is not const to implement Win32WindowInterface.
GetRootWindow(HWND window_handle)738 virtual HWND GetRootWindow(HWND window_handle) {
739 // See the following document for Win32 window system.
740 // http://msdn.microsoft.com/en-us/library/ms997562.aspx
741 return ::GetAncestor(window_handle, GA_ROOT);
742 }
743
744 // This method is not const to implement Win32WindowInterface.
GetWindowClassName(HWND window_handle,std::wstring * class_name)745 virtual bool GetWindowClassName(HWND window_handle,
746 std::wstring *class_name) {
747 if (class_name == NULL) {
748 return false;
749 }
750 wchar_t class_name_buffer[1024] = {};
751 const size_t num_copied_without_null = ::GetClassNameW(
752 window_handle, class_name_buffer, ARRAYSIZE(class_name_buffer));
753 if (num_copied_without_null >= (ARRAYSIZE(class_name_buffer) - 1)) {
754 DLOG(ERROR) << "buffer length is insufficient.";
755 return false;
756 }
757 class_name_buffer[num_copied_without_null] = L'\0';
758 class_name->assign(class_name_buffer);
759 return (::IsWindow(window_handle) != FALSE);
760 }
761
762 private:
763 typedef BOOL (WINAPI *LogicalToPhysicalPointForPerMonitorDPIFunc)(
764 HWND window_handle, POINT *point);
765 static LogicalToPhysicalPointForPerMonitorDPIFunc
GetLogicalToPhysicalPointForPerMonitorDPI()766 GetLogicalToPhysicalPointForPerMonitorDPI() {
767 // LogicalToPhysicalPointForPerMonitorDPI API is available on Windows 8.1
768 // and later.
769 if (!SystemUtil::IsWindows8_1OrLater()) {
770 return nullptr;
771 }
772
773 const HMODULE module = WinUtil::GetSystemModuleHandle(L"user32.dll");
774 if (module == nullptr) {
775 return nullptr;
776 }
777 return reinterpret_cast<LogicalToPhysicalPointForPerMonitorDPIFunc>(
778 ::GetProcAddress(module, "LogicalToPhysicalPointForPerMonitorDPI"));
779 }
780
781 const LogicalToPhysicalPointForPerMonitorDPIFunc
782 logical_to_physical_point_for_per_monitor_dpi_;
783
784 DISALLOW_COPY_AND_ASSIGN(NativeWindowPositionAPI);
785 };
786
787 struct WindowInfo {
788 std::wstring class_name;
789 CRect window_rect;
790 CPoint client_area_offset;
791 CSize client_area_size;
792 double scale_factor;
WindowInfomozc::renderer::win32::__anon93df42c90111::WindowInfo793 WindowInfo()
794 : scale_factor(1.0) {}
795 };
796
797 class SystemPreferenceEmulatorImpl : public SystemPreferenceInterface {
798 public:
SystemPreferenceEmulatorImpl(const LOGFONTW & gui_font)799 explicit SystemPreferenceEmulatorImpl(const LOGFONTW &gui_font)
800 : default_gui_font_(gui_font) {}
801
~SystemPreferenceEmulatorImpl()802 virtual ~SystemPreferenceEmulatorImpl() {}
803
GetDefaultGuiFont(LOGFONTW * log_font)804 virtual bool GetDefaultGuiFont(LOGFONTW *log_font) {
805 if (log_font == NULL) {
806 return false;
807 }
808 *log_font = default_gui_font_;
809 return true;
810 }
811
812 private:
813 CLogFont default_gui_font_;
814 };
815
816 class WorkingAreaEmulatorImpl : public WorkingAreaInterface {
817 public:
WorkingAreaEmulatorImpl(const CRect & area)818 explicit WorkingAreaEmulatorImpl(const CRect &area)
819 : area_(area) {}
820
821 private:
GetWorkingAreaFromPoint(const POINT & point,RECT * working_area)822 virtual bool GetWorkingAreaFromPoint(const POINT &point,
823 RECT *working_area) {
824 if (working_area == nullptr) {
825 return false;
826 }
827 *working_area = area_;
828 return true;
829 }
830 const CRect area_;
831 };
832
833 class WindowPositionEmulatorImpl : public WindowPositionEmulator {
834 public:
WindowPositionEmulatorImpl()835 WindowPositionEmulatorImpl() {}
~WindowPositionEmulatorImpl()836 virtual ~WindowPositionEmulatorImpl() {}
837
838 // This method is not const to implement Win32WindowInterface.
GetWindowRect(HWND window_handle,RECT * rect)839 virtual bool GetWindowRect(HWND window_handle, RECT *rect) {
840 if (rect == NULL) {
841 return false;
842 }
843 const WindowInfo *info = GetWindowInfomation(window_handle);
844 if (info == NULL) {
845 return false;
846 }
847 *rect = info->window_rect;
848 return true;
849 }
850
851 // This method is not const to implement Win32WindowInterface.
GetClientRect(HWND window_handle,RECT * rect)852 virtual bool GetClientRect(HWND window_handle, RECT *rect) {
853 if (rect == NULL) {
854 return false;
855 }
856 const WindowInfo *info = GetWindowInfomation(window_handle);
857 if (info == NULL) {
858 return false;
859 }
860 *rect = CRect(CPoint(0, 0), info->client_area_size);
861 return true;
862 }
863
864 // This method is not const to implement Win32WindowInterface.
ClientToScreen(HWND window_handle,POINT * point)865 virtual bool ClientToScreen(HWND window_handle, POINT *point) {
866 if (point == NULL) {
867 return false;
868 }
869 const WindowInfo *info = GetWindowInfomation(window_handle);
870 if (info == NULL) {
871 return false;
872 }
873 *point = (info->window_rect.TopLeft() + info->client_area_offset +
874 *point);
875 return true;
876 }
877
878 // This method is not const to implement Win32WindowInterface.
IsWindow(HWND window_handle)879 virtual bool IsWindow(HWND window_handle) {
880 const WindowInfo *info = GetWindowInfomation(window_handle);
881 if (info == NULL) {
882 return false;
883 }
884 return true;
885 }
886
887 // This method wraps API call of GetAncestor/GA_ROOT.
GetRootWindow(HWND window_handle)888 virtual HWND GetRootWindow(HWND window_handle) {
889 const std::map<HWND, HWND>::const_iterator it =
890 root_map_.find(window_handle);
891 if (it == root_map_.end()) {
892 return window_handle;
893 }
894 return it->second;
895 }
896
897 // This method is not const to implement Win32WindowInterface.
GetWindowClassName(HWND window_handle,std::wstring * class_name)898 virtual bool GetWindowClassName(HWND window_handle,
899 std::wstring *class_name) {
900 if (class_name == NULL) {
901 return false;
902 }
903 const WindowInfo *info = GetWindowInfomation(window_handle);
904 if (info == NULL) {
905 return false;
906 }
907 *class_name = info->class_name;
908 return true;
909 }
910
911 // This method is not const to implement Win32WindowInterface.
LogicalToPhysicalPoint(HWND window_handle,const POINT & logical_coordinate,POINT * physical_coordinate)912 virtual bool LogicalToPhysicalPoint(
913 HWND window_handle, const POINT &logical_coordinate,
914 POINT *physical_coordinate) {
915 if (physical_coordinate == NULL) {
916 return false;
917 }
918
919 DCHECK_NE(NULL, physical_coordinate);
920 const WindowInfo *root_info =
921 GetWindowInfomation(GetRootWindow(window_handle));
922 if (root_info == NULL) {
923 return false;
924 }
925
926 // BottomRight is treated inside of the rect in this scenario.
927 const CRect &bottom_right_inflated_rect = CRect(
928 root_info->window_rect.left,
929 root_info->window_rect.top,
930 root_info->window_rect.right + 1,
931 root_info->window_rect.bottom + 1);
932 if (bottom_right_inflated_rect.PtInRect(logical_coordinate) == FALSE) {
933 return false;
934 }
935 physical_coordinate->x = logical_coordinate.x * root_info->scale_factor;
936 physical_coordinate->y = logical_coordinate.y * root_info->scale_factor;
937 return true;
938 }
939
RegisterWindow(const std::wstring & class_name,const RECT & window_rect,const POINT & client_area_offset,const SIZE & client_area_size,double scale_factor)940 virtual HWND RegisterWindow(
941 const std::wstring &class_name, const RECT &window_rect,
942 const POINT &client_area_offset, const SIZE &client_area_size,
943 double scale_factor) {
944 const HWND hwnd = GetNextWindowHandle();
945 window_map_[hwnd].class_name = class_name;
946 window_map_[hwnd].window_rect = window_rect;
947 window_map_[hwnd].client_area_offset = client_area_offset;
948 window_map_[hwnd].client_area_size = client_area_size;
949 window_map_[hwnd].scale_factor = scale_factor;
950 return hwnd;
951 }
952
SetRoot(HWND child_window,HWND root_window)953 virtual void SetRoot(HWND child_window, HWND root_window) {
954 root_map_[child_window] = root_window;
955 }
956
957 private:
GetNextWindowHandle() const958 HWND GetNextWindowHandle() const {
959 if (window_map_.size() > 0) {
960 const HWND last_hwnd = window_map_.rbegin()->first;
961 return WinUtil::DecodeWindowHandle(
962 WinUtil::EncodeWindowHandle(last_hwnd) + 7);
963 }
964 return WinUtil::DecodeWindowHandle(0x12345678);
965 }
966
967 // This method is not const to implement Win32WindowInterface.
GetWindowInfomation(HWND hwnd)968 const WindowInfo *GetWindowInfomation(HWND hwnd) {
969 if (window_map_.find(hwnd) == window_map_.end()) {
970 return NULL;
971 }
972 return &(window_map_.find(hwnd)->second);
973 }
974
975 std::map<HWND, WindowInfo> window_map_;
976 std::map<HWND, HWND> root_map_;
977
978 DISALLOW_COPY_AND_ASSIGN(WindowPositionEmulatorImpl);
979 };
980
IsVerticalWriting(const CandidateWindowLayoutParams & params)981 bool IsVerticalWriting(const CandidateWindowLayoutParams ¶ms) {
982 return params.vertical_writing.has_value() &&
983 params.vertical_writing.value();
984 }
985
986 // This is a helper function for LayoutCandidateWindowByCandidateForm.
987 // Some applications give us only the base position of candidate window.
988 // However, the exclude region is definitely important in terms of UX around
989 // candidate/suggest window. As a workaround, we try to use font height to
990 // compose a virtual exclude region from the base position.
991 // Expected applications and controls are:
992 // - Candidate Window on Pidgin 2.6.1
993 // - Candidate Window on V2C 2.1.6 on JRE 1.6.0.21
994 // - Candidate Window on Fudemame 21
995 // See also relevant unit tests.
996 // Returns true if the |candidate_layout| is determined in successful.
UpdateCandidateWindowFromBasePosAndFontHeight(const CandidateWindowLayoutParams & params,int compatibility_mode,const CPoint & base_pos_in_logical_coord,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)997 bool UpdateCandidateWindowFromBasePosAndFontHeight(
998 const CandidateWindowLayoutParams ¶ms,
999 int compatibility_mode,
1000 const CPoint &base_pos_in_logical_coord,
1001 LayoutManager *layout_manager,
1002 CandidateWindowLayout *candidate_layout) {
1003 DCHECK(candidate_layout);
1004 candidate_layout->Clear();
1005
1006 if (!params.window_handle.has_value()) {
1007 return false;
1008 }
1009 const HWND target_window = params.window_handle.value();
1010
1011 const int font_height = GetAbsoluteFontHeight(params);
1012 if (font_height == 0) {
1013 return false;
1014 }
1015 DCHECK_LT(0, font_height);
1016
1017 const bool is_vertical = IsVerticalWriting(params);
1018 CRect exclude_region_in_logical_coord;
1019 if (is_vertical) {
1020 // Vertical
1021 exclude_region_in_logical_coord.SetRect(
1022 base_pos_in_logical_coord.x,
1023 base_pos_in_logical_coord.y,
1024 base_pos_in_logical_coord.x + font_height,
1025 base_pos_in_logical_coord.y + 1);
1026 } else {
1027 // Horizontal
1028 exclude_region_in_logical_coord.SetRect(
1029 base_pos_in_logical_coord.x,
1030 base_pos_in_logical_coord.y - font_height,
1031 base_pos_in_logical_coord.x + 1,
1032 base_pos_in_logical_coord.y);
1033 }
1034
1035 CRect exclude_region_in_physical_coord;
1036 layout_manager->GetRectInPhysicalCoords(
1037 target_window, exclude_region_in_logical_coord,
1038 &exclude_region_in_physical_coord);
1039
1040 const CPoint base_pos_in_physical_coord =
1041 GetBasePositionFromExcludeRect(exclude_region_in_physical_coord,
1042 is_vertical);
1043 candidate_layout->InitializeWithPositionAndExcludeRegion(
1044 base_pos_in_physical_coord, exclude_region_in_physical_coord);
1045 return true;
1046 }
1047
1048 // CANDIDATEFORM is most standard way to specify the position of a candidate
1049 // window. There are two major cases; one has an exclude region and the other
1050 // has no exclude region. The second case is virtually handled by
1051 // UpdateCandidateWindowFromBasePosAndFontHeight function.
1052 // Expected applications and controls (for the first case) are:
1053 // - Suggest Window on apps with CAN_USE_CANDIDATE_FORM_FOR_SUGGEST
1054 // -- Qt-related windows whose class name is "QWidget"
1055 // -- Google Chrome-related windows whose class name is
1056 // "Chrome_RenderWidgetHostHWND"
1057 // - Candidate Window on windows which do not support IMECHARPOSITION
1058 // -- Internet Explorer
1059 // -- Open Office Writer
1060 // -- Qt-based applications
1061 // See also relevant unit tests.
1062 // Returns true if the |candidate_layout| is determined in successful.
LayoutCandidateWindowByCandidateForm(const CandidateWindowLayoutParams & params,int compatibility_mode,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)1063 bool LayoutCandidateWindowByCandidateForm(
1064 const CandidateWindowLayoutParams ¶ms,
1065 int compatibility_mode,
1066 LayoutManager *layout_manager,
1067 CandidateWindowLayout *candidate_layout) {
1068 DCHECK(candidate_layout);
1069 candidate_layout->Clear();
1070
1071 if (!params.window_handle.has_value()) {
1072 return false;
1073 }
1074 if (!params.candidate_form.has_value()) {
1075 return false;
1076 }
1077
1078 const HWND target_window = params.window_handle.value();
1079 const CandidateWindowLayout &form = params.candidate_form.value();
1080
1081 CPoint base_pos_in_physical_coord;
1082 layout_manager->GetPointInPhysicalCoords(
1083 target_window, form.position(), &base_pos_in_physical_coord);
1084
1085 if (!form.has_exclude_region()) {
1086 // If the candidate form does not have the exclude region, try to compose
1087 // supplemental exclude region by using font height information.
1088 CandidateWindowLayout layout;
1089 if (UpdateCandidateWindowFromBasePosAndFontHeight(
1090 params, compatibility_mode, form.position(), layout_manager,
1091 &layout)) {
1092 // succeeded to compose exclude region.
1093 DCHECK(layout.initialized());
1094 *candidate_layout = layout;
1095 return true;
1096 }
1097 candidate_layout->InitializeWithPosition(base_pos_in_physical_coord);
1098 return true;
1099 }
1100
1101 DCHECK(form.has_exclude_region());
1102
1103 CRect exclude_region_in_physical_coord;
1104 layout_manager->GetRectInPhysicalCoords(
1105 target_window, form.exclude_region(),
1106 &exclude_region_in_physical_coord);
1107
1108 candidate_layout->InitializeWithPositionAndExcludeRegion(
1109 base_pos_in_physical_coord, exclude_region_in_physical_coord);
1110 return true;
1111 }
1112
1113 // This function tries to use IMECHARPOSITION structure, which gives us
1114 // sufficient information around the focused segment to use EXCLUDE-style
1115 // positioning. However, relatively small number of applications support
1116 // this structure.
1117 // Expected applications and controls are:
1118 // - Microsoft Word
1119 // - Built-in RichEdit control
1120 // -- Chrome's Omni-box
1121 // - Built-in Edit control
1122 // -- Internet Explorer's address bar
1123 // - Firefox
1124 // See also relevant unit tests.
1125 // Returns true if the |candidate_layout| is determined in successful.
LayoutCandidateWindowByCompositionTarget(const CandidateWindowLayoutParams & params,int compatibility_mode,bool for_suggestion,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)1126 bool LayoutCandidateWindowByCompositionTarget(
1127 const CandidateWindowLayoutParams ¶ms,
1128 int compatibility_mode,
1129 bool for_suggestion,
1130 LayoutManager *layout_manager,
1131 CandidateWindowLayout *candidate_layout) {
1132 DCHECK(candidate_layout);
1133 candidate_layout->Clear();
1134
1135 if (!params.window_handle.has_value()) {
1136 return false;
1137 }
1138 if (!params.char_pos.has_value()) {
1139 return false;
1140 }
1141
1142 const HWND target_window = params.window_handle.value();
1143 const IMECHARPOSITION &char_pos = params.char_pos.value();
1144
1145 // From the behavior of MS Office, we assume that an application fills
1146 // members in IMECHARPOSITION as follows, even though other interpretations
1147 // might be possible from the document especially for the vertical writing.
1148 // http://msdn.microsoft.com/en-us/library/dd318162.aspx
1149 //
1150 // [Horizontal Writing]
1151 //
1152 // (pt)
1153 // v_____
1154 // | |
1155 // | | (cLineHeight)
1156 // | |
1157 // --+-----+----------> (Base Line)
1158 //
1159 // [Vertical Writing]
1160 //
1161 // |
1162 // +-----< (pt)
1163 // | |
1164 // |-----+
1165 // | (cLineHeight)
1166 // |
1167 // |
1168 // v
1169 // (Base Line)
1170
1171 const bool can_use_candidate_form_exclude_region =
1172 CanUseExcludeRegionInCandidateFrom(
1173 params, compatibility_mode, for_suggestion);
1174 const bool is_vertical = IsVerticalWriting(params);
1175 CRect exclude_region_in_logical_coord;
1176 if (can_use_candidate_form_exclude_region) {
1177 exclude_region_in_logical_coord =
1178 params.candidate_form.value().exclude_region();
1179 } else if (is_vertical) {
1180 // Vertical
1181 exclude_region_in_logical_coord.left =
1182 char_pos.pt.x - char_pos.cLineHeight;
1183 exclude_region_in_logical_coord.top = char_pos.pt.y;
1184 exclude_region_in_logical_coord.right = char_pos.pt.x;
1185 exclude_region_in_logical_coord.bottom = char_pos.pt.y + 1;
1186 } else {
1187 // Horizontal
1188 exclude_region_in_logical_coord.left = char_pos.pt.x;
1189 exclude_region_in_logical_coord.top = char_pos.pt.y;
1190 exclude_region_in_logical_coord.right = char_pos.pt.x + 1;
1191 exclude_region_in_logical_coord.bottom =
1192 char_pos.pt.y + char_pos.cLineHeight;
1193 }
1194
1195 const CPoint base_pos_in_logical_coord =
1196 GetBasePositionFromIMECHARPOSITION(char_pos, is_vertical);
1197
1198 CPoint base_pos_in_physical_coord;
1199 layout_manager->GetPointInPhysicalCoords(
1200 target_window, base_pos_in_logical_coord,
1201 &base_pos_in_physical_coord);
1202
1203 CRect exclude_region_in_physical_coord;
1204 layout_manager->GetRectInPhysicalCoords(
1205 target_window, exclude_region_in_logical_coord,
1206 &exclude_region_in_physical_coord);
1207
1208 candidate_layout->InitializeWithPositionAndExcludeRegion(
1209 base_pos_in_physical_coord, exclude_region_in_physical_coord);
1210 return true;
1211 }
1212
1213 // COMPOSITIONFORM contains the expected position of top-left corner of the
1214 // composition window, which might be used to determine the position of the
1215 // candidate window if no other relevant information is available.
1216 // Actually the top left corner might be good enough for vertical writing.
1217 // As for horizontal writing, the base position can be calculated if the height
1218 // of the composition string is available. This function supposes that the
1219 // font height approximates to the height of the composition string.
1220 // In both cases, this function also tries to compose an exclude region to
1221 // improve the UX around suggest/candidate window if font height is available.
1222 // Expected applications and controls are:
1223 // - Suggest window on Pidgin 2.6.1
1224 // See also relevant unit tests.
1225 // Returns true if the |candidate_layout| is determined in successful.
LayoutCandidateWindowByCompositionForm(const CandidateWindowLayoutParams & params,int compatibility_mode,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)1226 bool LayoutCandidateWindowByCompositionForm(
1227 const CandidateWindowLayoutParams ¶ms,
1228 int compatibility_mode,
1229 LayoutManager *layout_manager,
1230 CandidateWindowLayout *candidate_layout) {
1231 DCHECK(candidate_layout);
1232 candidate_layout->Clear();
1233
1234 if (!params.window_handle.has_value()) {
1235 return false;
1236 }
1237 if (!params.composition_form_topleft.has_value()) {
1238 return false;
1239 }
1240
1241 const HWND target_window = params.window_handle.value();
1242 const CPoint &topleft_in_logical_coord =
1243 params.composition_form_topleft.value();
1244 const bool is_vertical = IsVerticalWriting(params);
1245 const int font_height = GetAbsoluteFontHeight(params);
1246
1247 if (!is_vertical) {
1248 // For horizontal writing, a valid |font_height| is necessary to calculate
1249 // an appropriate position of suggest/candidate window.
1250 if (font_height == 0) {
1251 return false;
1252 }
1253 DCHECK_LT(0, font_height);
1254
1255 const CRect rect_in_logical_coord(
1256 topleft_in_logical_coord.x,
1257 topleft_in_logical_coord.y,
1258 topleft_in_logical_coord.x + 1,
1259 topleft_in_logical_coord.y + font_height);
1260
1261 CRect rect_in_physical_coord;
1262 layout_manager->GetRectInPhysicalCoords(
1263 target_window, rect_in_logical_coord, &rect_in_physical_coord);
1264
1265 const CPoint bottom_left_in_physical_coord(
1266 rect_in_physical_coord.left, rect_in_physical_coord.bottom);
1267 candidate_layout->InitializeWithPositionAndExcludeRegion(
1268 bottom_left_in_physical_coord, rect_in_physical_coord);
1269 return true;
1270 }
1271
1272 // Vertical
1273 DCHECK(is_vertical);
1274 CPoint topleft_in_physical_coord;
1275 layout_manager->GetPointInPhysicalCoords(
1276 target_window, topleft_in_logical_coord,
1277 &topleft_in_physical_coord);
1278
1279 if (font_height == 0) {
1280 // For vertical writing, top-left cornier is acceptable.
1281 // Use CANDIDATEPOS-style by compromise.
1282 candidate_layout->InitializeWithPosition(topleft_in_physical_coord);
1283 return true;
1284 }
1285 DCHECK_LT(0, font_height);
1286
1287 const CRect rect_in_logical_coord(
1288 topleft_in_logical_coord.x,
1289 topleft_in_logical_coord.y,
1290 topleft_in_logical_coord.x + font_height,
1291 topleft_in_logical_coord.y + 1);
1292
1293 CRect rect_in_physical_coord;
1294 layout_manager->GetRectInPhysicalCoords(
1295 target_window, rect_in_logical_coord, &rect_in_physical_coord);
1296
1297 candidate_layout->InitializeWithPositionAndExcludeRegion(
1298 topleft_in_physical_coord, rect_in_physical_coord);
1299 return true;
1300 }
1301
1302 // This function calculates the candidate window position by using caret
1303 // information, which is generally unreliable but sometimes becomes a good
1304 // alternative even when no other positional information is available.
1305 // In fact, the position of suggest window sometimes relies on the caret
1306 // position because it is not guaranteed that the CANDIDATEFORM is valid before
1307 // the application receives IMN_OPENCANDIDATE message.
1308 // Another important consideration is how to calculate the exclude region.
1309 // One may consider that the caret rect seems to be used but very small number
1310 // of applications always use 1x1 rect regardless of the actual caret size.
1311 // To improve the positional accuracy of the exclude region, this function
1312 // adopt larger one between the caret height and font height when the exclude
1313 // region is calculated.
1314 // Relevant applications and controls are:
1315 // - Workaround against Google Chrome (b/3104035)
1316 // - Suggest window on Hidemaru
1317 // - Suggest window on Open Office Writer
1318 // - Suggest window on Internet Explorer 8
1319 // See also relevant unit tests.
1320 // Returns true if the |candidate_layout| is determined in successful.
LayoutCandidateWindowByCaretInfo(const CandidateWindowLayoutParams & params,int compatibility_mode,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)1321 bool LayoutCandidateWindowByCaretInfo(
1322 const CandidateWindowLayoutParams ¶ms,
1323 int compatibility_mode,
1324 LayoutManager *layout_manager,
1325 CandidateWindowLayout *candidate_layout) {
1326 DCHECK(candidate_layout);
1327 candidate_layout->Clear();
1328
1329 if (!params.window_handle.has_value()) {
1330 return false;
1331 }
1332 if (!params.caret_rect.has_value()) {
1333 return false;
1334 }
1335
1336 const HWND target_window = params.window_handle.value();
1337 CRect exclude_region_in_logical_coord = params.caret_rect.value();
1338
1339 // Use font height if available to improve the accuracy of exclude region.
1340 const int font_height = GetAbsoluteFontHeight(params);
1341 const bool is_vertical = IsVerticalWriting(params);
1342
1343 if (font_height > 0) {
1344 if (is_vertical &&
1345 (exclude_region_in_logical_coord.Width() < font_height)) {
1346 // Vertical
1347 exclude_region_in_logical_coord.right =
1348 exclude_region_in_logical_coord.left + font_height;
1349 } else if (!is_vertical &&
1350 (exclude_region_in_logical_coord.Height() < font_height)) {
1351 // Horizontal
1352 exclude_region_in_logical_coord.bottom =
1353 exclude_region_in_logical_coord.top + font_height;
1354 }
1355 }
1356
1357 CRect exclude_region_in_physical_coord;
1358 layout_manager->GetRectInPhysicalCoords(
1359 target_window, exclude_region_in_logical_coord,
1360 &exclude_region_in_physical_coord);
1361
1362 const CPoint base_pos_in_physical_coord =
1363 GetBasePositionFromExcludeRect(exclude_region_in_physical_coord,
1364 is_vertical);
1365
1366 candidate_layout->InitializeWithPositionAndExcludeRegion(
1367 base_pos_in_physical_coord, exclude_region_in_physical_coord);
1368 return true;
1369 }
1370
1371 // On some applications, no positional information is available especially when
1372 // the client want to show the suggest window. In this case, we might want to
1373 // show the (suggest) window next to the target window so that the candidate
1374 // window will not cover the target window.
1375 // Expected applications and controls are:
1376 // - Suggest window on Fudemame 21
1377 // See also relevant unit tests.
1378 // Returns true if the |candidate_layout| is determined in successful.
LayoutCandidateWindowByClientRect(const CandidateWindowLayoutParams & params,int compatibility_mode,LayoutManager * layout_manager,CandidateWindowLayout * candidate_layout)1379 bool LayoutCandidateWindowByClientRect(
1380 const CandidateWindowLayoutParams ¶ms,
1381 int compatibility_mode,
1382 LayoutManager *layout_manager,
1383 CandidateWindowLayout *candidate_layout) {
1384 DCHECK(candidate_layout);
1385 candidate_layout->Clear();
1386
1387 if (!params.window_handle.has_value()) {
1388 return false;
1389 }
1390 if (!params.client_rect.has_value()) {
1391 return false;
1392 }
1393
1394 const HWND target_window = params.window_handle.value();
1395 const CRect &client_rect_in_logical_coord =
1396 params.client_rect.value();
1397 const bool is_vertical = IsVerticalWriting(params);
1398
1399 CRect client_rect_in_physical_coord;
1400 layout_manager->GetRectInPhysicalCoords(
1401 target_window, client_rect_in_logical_coord,
1402 &client_rect_in_physical_coord);
1403
1404 if (is_vertical) {
1405 // Vertical
1406 // Current candidate window has not fully supported vertical writing yet so
1407 // it would be rather better to show the candidate window at the right side
1408 // of the target window.
1409 // This is why we do not use GetBasePositionFromExcludeRect here.
1410 // TODO(yukawa): use GetBasePositionFromExcludeRect once the vertical
1411 // writing is fully supported by the candidate window.
1412 candidate_layout->InitializeWithPosition(CPoint(
1413 client_rect_in_physical_coord.right,
1414 client_rect_in_physical_coord.top));
1415 } else {
1416 // Horizontal
1417 candidate_layout->InitializeWithPosition(CPoint(
1418 client_rect_in_physical_coord.left,
1419 client_rect_in_physical_coord.bottom));
1420 }
1421 return true;
1422 }
1423
LayoutIndicatorWindowByCompositionTarget(const CandidateWindowLayoutParams & params,const LayoutManager & layout_manager,CRect * target_rect)1424 bool LayoutIndicatorWindowByCompositionTarget(
1425 const CandidateWindowLayoutParams ¶ms,
1426 const LayoutManager &layout_manager,
1427 CRect *target_rect) {
1428 DCHECK(target_rect);
1429 *target_rect = CRect();
1430
1431 if (!params.window_handle.has_value()) {
1432 return false;
1433 }
1434 if (!params.char_pos.has_value()) {
1435 return false;
1436 }
1437
1438 const HWND target_window = params.window_handle.value();
1439 const IMECHARPOSITION &char_pos = params.char_pos.value();
1440
1441 // From the behavior of MS Office, we assume that an application fills
1442 // members in IMECHARPOSITION as follows, even though other interpretations
1443 // might be possible from the document especially for the vertical writing.
1444 // http://msdn.microsoft.com/en-us/library/dd318162.aspx
1445
1446 const bool is_vertical = IsVerticalWriting(params);
1447 CRect rect_in_logical_coord;
1448 if (is_vertical) {
1449 // [Vertical Writing]
1450 //
1451 // |
1452 // +-----< (pt)
1453 // | |
1454 // |-----+
1455 // | (cLineHeight)
1456 // |
1457 // |
1458 // v
1459 // (Base Line)
1460 rect_in_logical_coord = CRect(
1461 char_pos.pt.x - char_pos.cLineHeight,
1462 char_pos.pt.y,
1463 char_pos.pt.x,
1464 char_pos.pt.y + 1);
1465 } else {
1466 // [Horizontal Writing]
1467 //
1468 // (pt)
1469 // v_____
1470 // | |
1471 // | | (cLineHeight)
1472 // | |
1473 // --+-----+----------> (Base Line)
1474 rect_in_logical_coord = CRect(
1475 char_pos.pt.x,
1476 char_pos.pt.y,
1477 char_pos.pt.x + 1,
1478 char_pos.pt.y + char_pos.cLineHeight);
1479 }
1480
1481 layout_manager.GetRectInPhysicalCoords(
1482 target_window, rect_in_logical_coord, target_rect);
1483 return true;
1484 }
1485
LayoutIndicatorWindowByCompositionForm(const CandidateWindowLayoutParams & params,const LayoutManager & layout_manager,CRect * target_rect)1486 bool LayoutIndicatorWindowByCompositionForm(
1487 const CandidateWindowLayoutParams ¶ms,
1488 const LayoutManager &layout_manager,
1489 CRect *target_rect) {
1490 DCHECK(target_rect);
1491 *target_rect = CRect();
1492 if (!params.window_handle.has_value()) {
1493 return false;
1494 }
1495 if (!params.composition_form_topleft.has_value()) {
1496 return false;
1497 }
1498
1499 const HWND target_window = params.window_handle.value();
1500 const CPoint &topleft_in_logical_coord =
1501 params.composition_form_topleft.value();
1502 const bool is_vertical = IsVerticalWriting(params);
1503 const int font_height = GetAbsoluteFontHeight(params);
1504 if (font_height <= 0) {
1505 return false;
1506 }
1507
1508 const CRect rect_in_logical_coord(
1509 topleft_in_logical_coord,
1510 is_vertical ? CSize(font_height, 1) : CSize(1, font_height));
1511
1512 layout_manager.GetRectInPhysicalCoords(
1513 target_window, rect_in_logical_coord, target_rect);
1514 return true;
1515 }
1516
LayoutIndicatorWindowByCaretInfo(const CandidateWindowLayoutParams & params,const LayoutManager & layout_manager,CRect * target_rect)1517 bool LayoutIndicatorWindowByCaretInfo(
1518 const CandidateWindowLayoutParams ¶ms,
1519 const LayoutManager &layout_manager,
1520 CRect *target_rect) {
1521 DCHECK(target_rect);
1522 *target_rect = CRect();
1523 if (!params.window_handle.has_value()) {
1524 return false;
1525 }
1526 if (!params.caret_rect.has_value()) {
1527 return false;
1528 }
1529
1530 const HWND target_window = params.window_handle.value();
1531 CRect rect_in_logical_coord = params.caret_rect.value();
1532
1533 // Use font height if available to improve the accuracy of exlude region.
1534 const int font_height = GetAbsoluteFontHeight(params);
1535 const bool is_vertical = IsVerticalWriting(params);
1536
1537 if (font_height > 0) {
1538 if (is_vertical &&
1539 (rect_in_logical_coord.Width() < font_height)) {
1540 // Vertical
1541 rect_in_logical_coord.right =
1542 rect_in_logical_coord.left + font_height;
1543 } else if (!is_vertical &&
1544 (rect_in_logical_coord.Height() < font_height)) {
1545 // Horizontal
1546 rect_in_logical_coord.bottom =
1547 rect_in_logical_coord.top + font_height;
1548 }
1549 }
1550
1551 layout_manager.GetRectInPhysicalCoords(
1552 target_window, rect_in_logical_coord, target_rect);
1553 return true;
1554 }
1555
GetTargetRectForIndicator(const CandidateWindowLayoutParams & params,const LayoutManager & layout_manager,CRect * focus_rect)1556 bool GetTargetRectForIndicator(
1557 const CandidateWindowLayoutParams ¶ms,
1558 const LayoutManager &layout_manager,
1559 CRect *focus_rect) {
1560 if (focus_rect == nullptr) {
1561 return false;
1562 }
1563
1564 if (LayoutIndicatorWindowByCompositionTarget(params, layout_manager,
1565 focus_rect)) {
1566 return true;
1567 }
1568 if (LayoutIndicatorWindowByCompositionForm(params,
1569 layout_manager,
1570 focus_rect)) {
1571 return true;
1572 }
1573 if (LayoutIndicatorWindowByCaretInfo(params,
1574 layout_manager,
1575 focus_rect)) {
1576 return true;
1577 }
1578
1579 // Clear the data just in case.
1580 *focus_rect = CRect();
1581 return false;
1582 }
1583
1584 } // namespace
1585
CreateMock(const LOGFONTW & gui_font)1586 SystemPreferenceInterface *SystemPreferenceFactory::CreateMock(
1587 const LOGFONTW &gui_font) {
1588 return new SystemPreferenceEmulatorImpl(gui_font);
1589 }
1590
Create()1591 WorkingAreaInterface *WorkingAreaFactory::Create() {
1592 return new NativeWorkingAreaAPI();
1593 }
1594
CreateMock(const RECT & working_area)1595 WorkingAreaInterface *WorkingAreaFactory::CreateMock(
1596 const RECT &working_area) {
1597 return new WorkingAreaEmulatorImpl(working_area);
1598 }
1599
Create()1600 WindowPositionEmulator *WindowPositionEmulator::Create() {
1601 return new WindowPositionEmulatorImpl();
1602 }
1603
CharacterRange()1604 CharacterRange::CharacterRange()
1605 : begin(0),
1606 length(0) {}
1607
LineLayout()1608 LineLayout::LineLayout()
1609 : line_length(0),
1610 line_width(0),
1611 line_start_offset(0) {}
1612
CandidateWindowLayout()1613 CandidateWindowLayout::CandidateWindowLayout()
1614 : position_(CPoint()),
1615 exclude_region_(CRect()),
1616 has_exclude_region_(false),
1617 initialized_(false) {}
1618
~CandidateWindowLayout()1619 CandidateWindowLayout::~CandidateWindowLayout() {}
1620
Clear()1621 void CandidateWindowLayout::Clear() {
1622 position_ = CPoint();
1623 exclude_region_ = CRect();
1624 has_exclude_region_ = false;
1625 initialized_ = false;
1626 }
1627
InitializeWithPosition(const POINT & position)1628 void CandidateWindowLayout::InitializeWithPosition(const POINT &position) {
1629 position_ = position;
1630 exclude_region_ = CRect();
1631 has_exclude_region_ = false;
1632 initialized_ = true;
1633 }
1634
InitializeWithPositionAndExcludeRegion(const POINT & position,const RECT & exclude_region)1635 void CandidateWindowLayout::InitializeWithPositionAndExcludeRegion(
1636 const POINT &position, const RECT &exclude_region) {
1637 position_ = position;
1638 exclude_region_ = exclude_region;
1639 has_exclude_region_ = true;
1640 initialized_ = true;
1641 }
1642
position() const1643 const POINT &CandidateWindowLayout::position() const {
1644 return position_;
1645 }
1646
exclude_region() const1647 const RECT &CandidateWindowLayout::exclude_region() const {
1648 DCHECK(has_exclude_region_);
1649 return exclude_region_;
1650 }
1651
has_exclude_region() const1652 bool CandidateWindowLayout::has_exclude_region() const {
1653 return has_exclude_region_;
1654 }
1655
initialized() const1656 bool CandidateWindowLayout::initialized() const {
1657 return initialized_;
1658 }
1659
IndicatorWindowLayout()1660 IndicatorWindowLayout::IndicatorWindowLayout()
1661 : is_vertical(false) {
1662 ::SetRect(&window_rect, 0, 0, 0, 0);
1663 }
1664
Clear()1665 void IndicatorWindowLayout::Clear() {
1666 is_vertical = false;
1667 ::SetRect(&window_rect, 0, 0, 0, 0);
1668 }
1669
CalcLayoutWithTextWrapping(const LOGFONTW & font,const std::wstring & text,int maximum_line_length,int initial_offset,std::vector<LineLayout> * line_layouts)1670 bool LayoutManager::CalcLayoutWithTextWrapping(
1671 const LOGFONTW &font,
1672 const std::wstring &text,
1673 int maximum_line_length,
1674 int initial_offset,
1675 std::vector<LineLayout> *line_layouts) {
1676 if (line_layouts == NULL) {
1677 return false;
1678 }
1679 line_layouts->clear();
1680
1681 CFont new_font(CLogFont(font).CreateFontIndirectW());
1682 if (new_font.IsNull()) {
1683 LOG(ERROR) << "CreateFont failed.";
1684 return false;
1685 }
1686
1687 CDC dc;
1688 // Create a memory DC compatible with desktop DC.
1689 dc.CreateCompatibleDC(CDC(::GetDC(NULL)));
1690 CFontHandle old_font = dc.SelectFont(new_font);
1691
1692 const bool result = CalcLayoutWithTextWrappingInternal(
1693 dc.m_hDC, text, maximum_line_length, initial_offset,
1694 line_layouts);
1695 dc.SelectFont(old_font);
1696
1697 return result;
1698 }
1699
GetPointInPhysicalCoords(HWND window_handle,const POINT & point,POINT * result) const1700 void LayoutManager::GetPointInPhysicalCoords(
1701 HWND window_handle, const POINT &point, POINT *result) const {
1702 if (result == NULL) {
1703 return;
1704 }
1705
1706 DCHECK_NE(nullptr, window_position_.get());
1707 if (window_position_->LogicalToPhysicalPoint(
1708 window_handle, point, result)) {
1709 return;
1710 }
1711
1712 // LogicalToPhysicalPoint API failed for some reason.
1713 // Emulate the result based on the scaling factor.
1714 const HWND root_window_handle =
1715 window_position_->GetRootWindow(window_handle);
1716 const double scale_factor = GetScalingFactor(root_window_handle);
1717 result->x = point.x * scale_factor;
1718 result->y = point.y * scale_factor;
1719 return;
1720 }
1721
GetRectInPhysicalCoords(HWND window_handle,const RECT & rect,RECT * result) const1722 void LayoutManager::GetRectInPhysicalCoords(
1723 HWND window_handle, const RECT &rect, RECT *result) const {
1724 if (result == NULL) {
1725 return;
1726 }
1727
1728 DCHECK_NE(nullptr, window_position_.get());
1729
1730 CPoint top_left;
1731 GetPointInPhysicalCoords(
1732 window_handle, CPoint(rect.left, rect.top), &top_left);
1733 CPoint bottom_right;
1734 GetPointInPhysicalCoords(
1735 window_handle, CPoint(rect.right, rect.bottom), &bottom_right);
1736 *result = CRect(top_left, bottom_right);
1737 return;
1738 }
1739
SegmentMarkerLayout()1740 SegmentMarkerLayout::SegmentMarkerLayout()
1741 : from(CPoint()),
1742 to(CPoint()),
1743 highlighted(false) {}
1744
CompositionWindowLayout()1745 CompositionWindowLayout::CompositionWindowLayout()
1746 : window_position_in_screen_coordinate(CRect()),
1747 caret_rect(CRect()),
1748 text_area(CRect()),
1749 base_position(CPoint()),
1750 log_font(CLogFont()) {}
1751
LayoutManager()1752 LayoutManager::LayoutManager()
1753 : system_preference_(new NativeSystemPreferenceAPI),
1754 window_position_(new NativeWindowPositionAPI) {}
1755
LayoutManager(SystemPreferenceInterface * mock_system_preference,WindowPositionInterface * mock_window_position)1756 LayoutManager::LayoutManager(SystemPreferenceInterface *mock_system_preference,
1757 WindowPositionInterface *mock_window_position)
1758 : system_preference_(mock_system_preference),
1759 window_position_(mock_window_position) {}
1760
~LayoutManager()1761 LayoutManager::~LayoutManager() {}
1762
1763 // TODO(yukawa): Refactor this function into smaller functions as soon as
1764 // possible so that you can update the functionality and add new unit tests
1765 // more easily.
LayoutCompositionWindow(const commands::RendererCommand & command,std::vector<CompositionWindowLayout> * composition_window_layouts,CandidateWindowLayout * candidate_layout) const1766 bool LayoutManager::LayoutCompositionWindow(
1767 const commands::RendererCommand &command,
1768 std::vector<CompositionWindowLayout> *composition_window_layouts,
1769 CandidateWindowLayout *candidate_layout) const {
1770 if (composition_window_layouts != NULL) {
1771 composition_window_layouts->clear();
1772 }
1773 if (candidate_layout != NULL) {
1774 candidate_layout->Clear();
1775 }
1776
1777 if (!command.has_output() ||
1778 !command.output().has_preedit() ||
1779 command.output().preedit().segment_size() <= 0 ||
1780 !command.output().preedit().has_cursor() ||
1781 !command.has_application_info() ||
1782 !command.application_info().has_target_window_handle()) {
1783 LOG(INFO) << "do nothing because of the lack of parameter(s)";
1784 return true;
1785 }
1786 const mozc::commands::Output &output = command.output();
1787 const HWND target_window_handle = WinUtil::DecodeWindowHandle(
1788 command.application_info().target_window_handle());
1789
1790 const mozc::commands::RendererCommand::ApplicationInfo &app =
1791 command.application_info();
1792 CLogFont logfont;
1793 if (!app.has_composition_font() ||
1794 !mozc::win32::FontUtil::ToLOGFONT(app.composition_font(), &logfont)) {
1795 // If the composition font is not available, use default GUI font as a
1796 // fall back.
1797 if (!system_preference_->GetDefaultGuiFont(&logfont)) {
1798 LOG(ERROR) << "GetDefaultGuiFont failed.";
1799 return false;
1800 }
1801 }
1802
1803 // Remove underline attribute. See b/2935480 for details.
1804 logfont.lfUnderline = 0;
1805
1806 // We only support lfEscapement == 0 or 2700.
1807 if (logfont.lfEscapement != 0 && logfont.lfEscapement != 2700) {
1808 LOG(ERROR) << "Unsupported escapement: " << logfont.lfEscapement;
1809 return false;
1810 }
1811
1812 const mozc::commands::Preedit &preedit = output.preedit();
1813
1814 const bool is_vertical = (GetWritingDirection(app) == VERTICAL_WRITING);
1815
1816 CompositionForm composition_form;
1817 if (command.application_info().has_composition_form()) {
1818 composition_form.CopyFrom(command.application_info().composition_form());
1819 } else {
1820 // No composition form is available. Use client rect instead.
1821 CRect client_rect;
1822 if (!window_position_->GetClientRect(target_window_handle, &client_rect)) {
1823 return false;
1824 }
1825 // We need not to use CompositionForm::RECT. The client area will be used
1826 // for character wrapping anyway.
1827 composition_form.set_style_bits(CompositionForm::POINT);
1828 if (is_vertical) {
1829 composition_form.mutable_current_position()->set_x(client_rect.left);
1830 composition_form.mutable_current_position()->set_y(client_rect.top);
1831 } else {
1832 composition_form.mutable_current_position()->set_x(client_rect.left);
1833 composition_form.mutable_current_position()->set_y(client_rect.bottom);
1834 }
1835 }
1836
1837 const uint32 style_bits = composition_form.style_bits();
1838
1839 // Check the availability of optional fields.
1840 // Note that currently we always use |current_position| field even when
1841 // |style_bits| does not contain CompositionForm::POINT bit.
1842 if (!composition_form.has_current_position() ||
1843 !composition_form.current_position().has_x() ||
1844 !composition_form.current_position().has_y()) {
1845 return false;
1846 }
1847
1848 const HWND root_window_handle =
1849 window_position_->GetRootWindow(target_window_handle);
1850 if (root_window_handle == NULL) {
1851 LOG(ERROR) << "GetRootWindow failed.";
1852 return false;
1853 }
1854
1855 const double scale = GetScalingFactor(root_window_handle);
1856 const bool no_dpi_virtualization = (scale == 1.0);
1857
1858 const CPoint current_pos_in_client_coord(
1859 composition_form.current_position().x(),
1860 composition_form.current_position().y());
1861
1862 CPoint current_pos_in_logical_coord;
1863 if (!ClientPointToScreen(target_window_handle, current_pos_in_client_coord,
1864 ¤t_pos_in_logical_coord)) {
1865 LOG(ERROR) << "ClientPointToScreen failed.";
1866 return false;
1867 }
1868
1869 CPoint current_pos;
1870 GetPointInPhysicalCoords(target_window_handle,
1871 current_pos_in_logical_coord, ¤t_pos);
1872
1873 // Check the availability of optional fields.
1874 // Note that some applications may set |CompositionForm::RECT| and other
1875 // style bits like |CompositionForm::POINT| at the same time.
1876 // See b/3200425 for details.
1877 bool use_area_in_composition_form = false;
1878 if (((style_bits & CompositionForm::RECT) == CompositionForm::RECT) &&
1879 composition_form.has_area() &&
1880 composition_form.area().has_left() &&
1881 composition_form.area().has_top() &&
1882 composition_form.area().has_right() &&
1883 composition_form.area().has_bottom()) {
1884 use_area_in_composition_form = true;
1885 }
1886
1887 CRect area_in_client_coord;
1888 if (use_area_in_composition_form) {
1889 area_in_client_coord.SetRect(
1890 composition_form.area().left(),
1891 composition_form.area().top(),
1892 composition_form.area().right(),
1893 composition_form.area().bottom());
1894 } else {
1895 if (window_position_->GetClientRect(
1896 target_window_handle, &area_in_client_coord) == FALSE) {
1897 const int error = ::GetLastError();
1898 DLOG(ERROR) << "GetClientRect failed. error = " << error;
1899 return false;
1900 }
1901 }
1902
1903 CRect area_in_logical_coord;
1904 if (!ClientRectToScreen(target_window_handle, area_in_client_coord,
1905 &area_in_logical_coord)) {
1906 return false;
1907 }
1908
1909 CPoint current_pos_in_physical_coord;
1910 GetPointInPhysicalCoords(
1911 target_window_handle, current_pos_in_logical_coord,
1912 ¤t_pos_in_physical_coord);
1913
1914 CRect area_in_physical_coord;
1915 GetRectInPhysicalCoords(
1916 target_window_handle, area_in_logical_coord,
1917 &area_in_physical_coord);
1918
1919 // Adjust the font size to be equal to that in the target process with
1920 // taking DPI virtualization into account.
1921 if (!no_dpi_virtualization) {
1922 logfont.lfHeight = static_cast<int>(logfont.lfHeight * scale);
1923 }
1924
1925 // Ensure the escapement and orientation are consistent with writing
1926 // direction. Note that some applications always set 0 to |lfOrientation|.
1927 if (is_vertical) {
1928 logfont.lfEscapement = 2700;
1929 logfont.lfOrientation = 2700;
1930 } else {
1931 logfont.lfEscapement = 0;
1932 logfont.lfOrientation = 0;
1933 }
1934
1935 string preedit_utf8;
1936 std::vector<int> segment_indices;
1937 std::vector<CharacterRange> segment_lengths;
1938 const std::wstring composition_text = ComposePreeditText(
1939 preedit, &preedit_utf8, &segment_indices, &segment_lengths);
1940 DCHECK_EQ(composition_text.size(), segment_indices.size());
1941 DCHECK_EQ(preedit.segment_size(), segment_lengths.size());
1942 std::vector<mozc::renderer::win32::LineLayout> layouts;
1943 bool result = false;
1944 {
1945 const int offset = is_vertical
1946 ? current_pos_in_physical_coord.y - area_in_physical_coord.top
1947 : current_pos_in_physical_coord.x - area_in_physical_coord.left;
1948 const int limit = is_vertical ? area_in_physical_coord.Height()
1949 : area_in_physical_coord.Width();
1950 result = CalcLayoutWithTextWrapping(
1951 logfont, composition_text, limit, offset, &layouts);
1952 }
1953 if (!result) {
1954 LOG(ERROR) << "CalcLayoutWithTextWrapping failed.";
1955 return false;
1956 }
1957
1958 if (composition_window_layouts != NULL) {
1959 composition_window_layouts->clear();
1960 }
1961
1962 int cursor_index = -1;
1963 if (output.has_candidates() && output.candidates().has_position()) {
1964 // |cursor_index| is supposed to be wide character index but
1965 // |output.candidates().position()| is the number of Unicode characters.
1966 // In case surrogate pair appears, use Util::WideCharsLen to calculate the
1967 // cursor position as wide character index. See b/4163234 for details.
1968 cursor_index = Util::WideCharsLen(
1969 Util::SubString(preedit_utf8, 0, output.candidates().position()));
1970 }
1971
1972 const bool is_suggest =
1973 output.candidates().has_category() &&
1974 (output.candidates().category() == commands::SUGGESTION);
1975
1976 // When this flag is true, suggest window must not hide preedit text.
1977 // TODO(yukawa): remove |!is_vertical| when vertical candidate window is
1978 // implemented.
1979 const bool suggest_window_never_hides_preedit = (!is_vertical && is_suggest);
1980
1981 int total_line_offset = 0;
1982 int total_characters = 0;
1983 for (size_t layout_index = 0; layout_index < layouts.size();
1984 ++layout_index) {
1985 const mozc::renderer::win32::LineLayout &layout = layouts[layout_index];
1986 if (layout.text.size() < 0 ||
1987 layout.line_length < 0 ||
1988 layout.character_positions.size() < 0) {
1989 // unexpected values found.
1990 return false;
1991 }
1992
1993 if (layout.text.size() == 0 ||
1994 layout.line_length == 0 ||
1995 layout.character_positions.size() == 0) {
1996 // This line is full. Go to next line.
1997 total_line_offset += layout.line_width;
1998 total_characters += layout.text.size();
1999 continue;
2000 }
2001
2002 DCHECK_GT(layout.text.size(), 0);
2003 DCHECK_GT(layout.line_length, 0);
2004 DCHECK_GT(layout.character_positions.size(), 0);
2005
2006 CompositionWindowLayout window_layout;
2007 window_layout.text = layout.text;
2008 window_layout.log_font = logfont;
2009 CRect window_rect;
2010 CRect text_rect;
2011 CPoint base_point;
2012 if (is_vertical) {
2013 window_rect.top = area_in_physical_coord.top + layout.line_start_offset;
2014 window_rect.right = current_pos.x - total_line_offset;
2015 window_rect.left =
2016 window_rect.right - layout.line_width;
2017 window_rect.bottom =
2018 window_rect.top + layout.line_length;
2019 text_rect.SetRect(0, 0, layout.line_width, layout.line_length);
2020 base_point.SetPoint(layout.line_width, 0);
2021 } else {
2022 window_rect.left =
2023 area_in_physical_coord.left + layout.line_start_offset;
2024 window_rect.top = current_pos.y + total_line_offset;
2025 window_rect.right = window_rect.left + layout.line_length;
2026 window_rect.bottom = window_rect.top + layout.line_width;
2027 text_rect.SetRect(0, 0, layout.line_length, layout.line_width);
2028 base_point.SetPoint(0, 0);
2029 }
2030 window_layout.window_position_in_screen_coordinate = window_rect;
2031 window_layout.text_area = text_rect;
2032 window_layout.base_position = base_point;
2033
2034 const int next_total_characters = total_characters + layout.text.size();
2035
2036 // Calculate caret rect assuming its width is 1 pixel.
2037 // Note that |caret_index| is supposed to be wide character index but
2038 // |output.preedit().cursor()| is the number of Unicode characters.
2039 // In case surrogate pair appears, use Util::WideCharsLen to calculate the
2040 // cursor position as wide character index. See b/4163234 for details.
2041 // TODO(yukawa): We should use the actual caret size, which can be
2042 // obtained by GetGUIThreadInfo API.
2043 const int caret_index = Util::WideCharsLen(
2044 Util::SubString(preedit_utf8, 0, output.preedit().cursor()));
2045
2046 if (total_characters <= caret_index &&
2047 caret_index < next_total_characters) {
2048 // In this case, caret points existing character. We use the left edge
2049 // of the pointed character.
2050 const int local_caret_index = caret_index - total_characters;
2051
2052 const int caret_begin =
2053 layout.character_positions[local_caret_index].begin;
2054 // Add 1 because Win32 RECTs are endpoint-exclusive.
2055 // http://weblogs.asp.net/oldnewthing/archive/2004/02/18/75652.aspx
2056 // http://www.radiumsoftware.com/0402.html#040222
2057 const int caret_end = caret_begin + 1;
2058 if (is_vertical) {
2059 window_layout.caret_rect =
2060 CRect(0, caret_begin, layout.line_width, caret_end);
2061 } else {
2062 window_layout.caret_rect =
2063 CRect(caret_begin, 0, caret_end, layout.line_width);
2064 }
2065 } else if (((layout_index + 1) == layouts.size()) &&
2066 (caret_index == next_total_characters)) {
2067 // In this case, caret points the next to the last character.
2068 // The composition window should have an extra space to draw the caret if
2069 // the window can be extended.
2070 CRect extended_rect(window_layout.window_position_in_screen_coordinate);
2071 if (is_vertical) {
2072 if (extended_rect.bottom < area_in_physical_coord.bottom) {
2073 // Still inside of the |area| if we extend the window.
2074 extended_rect.InflateRect(0, 0, 0, 1);
2075 }
2076 const int caret_begin = extended_rect.Height() - 1;
2077 // Add 1 because Win32 RECTs are endpoint-exclusive.
2078 // http://weblogs.asp.net/oldnewthing/archive/2004/02/18/75652.aspx
2079 // http://www.radiumsoftware.com/0402.html#040222
2080 const int caret_end = caret_begin + 1;
2081 window_layout.caret_rect =
2082 CRect(0, caret_begin, layout.line_width, caret_end);
2083 } else {
2084 if (extended_rect.right < area_in_physical_coord.right) {
2085 // Still inside of the |area| if we extend the window.
2086 extended_rect.InflateRect(0, 0, 1, 0);
2087 }
2088 const int caret_begin = extended_rect.Width() - 1;
2089 // Add 1 because Win32 RECTs are endpoint-exclusive.
2090 // http://weblogs.asp.net/oldnewthing/archive/2004/02/18/75652.aspx
2091 // http://www.radiumsoftware.com/0402.html#040222
2092 const int caret_end = caret_begin + 1;
2093 window_layout.caret_rect =
2094 CRect(caret_begin, 0, caret_end, layout.line_width);
2095 }
2096 window_layout.window_position_in_screen_coordinate =
2097 extended_rect;
2098 }
2099
2100 if (total_characters <= cursor_index &&
2101 cursor_index < next_total_characters &&
2102 candidate_layout != NULL &&
2103 !suggest_window_never_hides_preedit) {
2104 const int local_cursor_index = cursor_index - total_characters;
2105 CPoint cursor_pos;
2106 CRect exclusion_area;
2107 if (is_vertical) {
2108 cursor_pos.SetPoint(
2109 0, layout.character_positions[local_cursor_index].begin);
2110 exclusion_area.SetRect(
2111 cursor_pos, CPoint(window_rect.Width(), window_rect.Height()));
2112 } else {
2113 cursor_pos.SetPoint(
2114 layout.character_positions[local_cursor_index].begin,
2115 layout.line_width);
2116 exclusion_area.SetRect(
2117 CPoint(layout.character_positions[local_cursor_index].begin, 0),
2118 CPoint(window_rect.Width(), window_rect.Height()));
2119 }
2120 cursor_pos.Offset(window_rect.left, window_rect.top);
2121 exclusion_area.OffsetRect(window_rect.left, window_rect.top);
2122 candidate_layout->InitializeWithPositionAndExcludeRegion(
2123 cursor_pos, exclusion_area);
2124 }
2125
2126 const size_t min_segment_index = segment_indices[total_characters];
2127 const size_t max_segment_index =
2128 segment_indices[next_total_characters - 1];
2129 for (size_t segment_index = min_segment_index;
2130 segment_index <= max_segment_index; ++segment_index) {
2131 const commands::Preedit::Segment &segment =
2132 preedit.segment(segment_index);
2133 if ((segment.annotation() & commands::Preedit::Segment::UNDERLINE) !=
2134 commands::Preedit::Segment::UNDERLINE &&
2135 (segment.annotation() & commands::Preedit::Segment::HIGHLIGHT) !=
2136 commands::Preedit::Segment::HIGHLIGHT) {
2137 continue;
2138 }
2139 const int segment_begin =
2140 std::max(segment_lengths[segment_index].begin, total_characters) -
2141 total_characters;
2142 const int segment_end =
2143 std::min(segment_lengths[segment_index].begin +
2144 segment_lengths[segment_index].length,
2145 next_total_characters) - total_characters;
2146 if (segment_begin >= segment_end) {
2147 continue;
2148 }
2149 DCHECK_GT(segment_end, segment_begin);
2150 bool show_segment_gap = true;
2151 if ((segment_index + 1) >= preedit.segment_size()) {
2152 // If this segment is the last segment, we do not show the gap.
2153 show_segment_gap = false;
2154 } else if (segment_lengths[segment_index].begin +
2155 segment_lengths[segment_index].length !=
2156 segment_end + total_characters) {
2157 // If this segment continues to the next line, we do not show the
2158 // gap. This behavior is different from the composition window
2159 // drawn by CUAS.
2160 show_segment_gap = false;
2161 }
2162
2163 // As CUAS does, we make a gap in underline between segments.
2164 // The length of underline will be shortened 20% of the width of
2165 // the last character.
2166 const int begin_pos =
2167 layout.character_positions[segment_begin].begin;
2168 const int end_pos =
2169 layout.character_positions[segment_end - 1].begin +
2170 80 * layout.character_positions[segment_end - 1].length /
2171 (show_segment_gap ? 100 : 80);
2172
2173 SegmentMarkerLayout marker;
2174 if ((segment.annotation() & commands::Preedit::Segment::HIGHLIGHT) ==
2175 commands::Preedit::Segment::HIGHLIGHT) {
2176 marker.highlighted = true;
2177 }
2178
2179 if (is_vertical) {
2180 // |CPoint(layout.line_width, begin_pos)| is outside of the
2181 // window.
2182 marker.from = CPoint(layout.line_width - 1, begin_pos);
2183 marker.to = CPoint(layout.line_width - 1, end_pos);
2184 } else {
2185 // |CPoint(begin_pos, layout.line_width)| is outside of the
2186 // window.
2187 marker.from = CPoint(begin_pos, layout.line_width - 1);
2188 marker.to = CPoint(end_pos, layout.line_width - 1);
2189 }
2190 window_layout.marker_layouts.push_back(marker);
2191 }
2192 if (composition_window_layouts != NULL) {
2193 composition_window_layouts->push_back(window_layout);
2194 }
2195 total_line_offset += layout.line_width;
2196 total_characters += layout.text.size();
2197 }
2198
2199 // In this case, suggest window moves to the next line of the preedit text
2200 // so that suggest window never hides the preedit.
2201 if (suggest_window_never_hides_preedit &&
2202 (composition_window_layouts->size() > 0) &&
2203 (candidate_layout != NULL)) {
2204 // Initialize the |exclusion_area| with invalid data. These values will
2205 // be updated to be valid at the first turn of the next for-loop.
2206 // For example, |exclusion_area.left| will be updated as follows.
2207 // exclusion_area.left = std::min(exclusion_area.left,
2208 // std::numeric_limits<int>::max());
2209 CRect exclusion_area(std::numeric_limits<int>::max(),
2210 std::numeric_limits<int>::max(),
2211 std::numeric_limits<int>::min(),
2212 std::numeric_limits<int>::min());
2213
2214 for (size_t i = 0; i < composition_window_layouts->size(); ++i) {
2215 const CompositionWindowLayout &layout = composition_window_layouts->at(i);
2216 CRect text_area_in_screen_coord = layout.text_area;
2217 text_area_in_screen_coord.OffsetRect(
2218 layout.window_position_in_screen_coordinate.left,
2219 layout.window_position_in_screen_coordinate.top);
2220 exclusion_area.left = std::min(exclusion_area.left,
2221 text_area_in_screen_coord.left);
2222 exclusion_area.top = std::min(exclusion_area.top,
2223 text_area_in_screen_coord.top);
2224 exclusion_area.right = std::max(exclusion_area.right,
2225 text_area_in_screen_coord.right);
2226 exclusion_area.bottom = std::max(exclusion_area.bottom,
2227 text_area_in_screen_coord.bottom);
2228 }
2229
2230 CPoint cursor_pos;
2231 if (is_vertical) {
2232 cursor_pos.SetPoint(exclusion_area.left, exclusion_area.top);
2233 } else {
2234 cursor_pos.SetPoint(exclusion_area.left, exclusion_area.bottom);
2235 }
2236 candidate_layout->InitializeWithPositionAndExcludeRegion(
2237 cursor_pos, exclusion_area);
2238 }
2239 return true;
2240 }
2241
ClientPointToScreen(HWND src_window_handle,const POINT & src_point,POINT * dest_point) const2242 bool LayoutManager::ClientPointToScreen(
2243 HWND src_window_handle,
2244 const POINT &src_point,
2245 POINT *dest_point) const {
2246 if (dest_point == NULL) {
2247 return false;
2248 }
2249
2250 if (!window_position_->IsWindow(src_window_handle)) {
2251 DLOG(ERROR) << "Invalid window handle.";
2252 return false;
2253 }
2254
2255 CPoint converted = src_point;
2256 if (window_position_->ClientToScreen(src_window_handle, &converted) ==
2257 FALSE) {
2258 DLOG(ERROR) << "ClientToScreen failed.";
2259 return false;
2260 }
2261
2262 *dest_point = converted;
2263 return true;
2264 }
2265
ClientRectToScreen(HWND src_window_handle,const RECT & src_rect,RECT * dest_rect) const2266 bool LayoutManager::ClientRectToScreen(
2267 HWND src_window_handle,
2268 const RECT &src_rect,
2269 RECT *dest_rect) const {
2270 if (dest_rect == NULL) {
2271 return false;
2272 }
2273
2274 if (!window_position_->IsWindow(src_window_handle)) {
2275 DLOG(ERROR) << "Invalid window handle.";
2276 return false;
2277 }
2278
2279 CPoint top_left(src_rect.left, src_rect.top);
2280 if (window_position_->ClientToScreen(src_window_handle, &top_left) == FALSE) {
2281 DLOG(ERROR) << "ClientToScreen failed.";
2282 return false;
2283 }
2284
2285 CPoint bottom_right(src_rect.right, src_rect.bottom);
2286 if (window_position_->ClientToScreen(src_window_handle, &bottom_right) ==
2287 FALSE) {
2288 DLOG(ERROR) << "ClientToScreen failed.";
2289 return false;
2290 }
2291
2292 dest_rect->left = top_left.x;
2293 dest_rect->top = top_left.y;
2294 dest_rect->right = bottom_right.x;
2295 dest_rect->bottom = bottom_right.y;
2296 return true;
2297 }
2298
LocalPointToScreen(HWND src_window_handle,const POINT & src_point,POINT * dest_point) const2299 bool LayoutManager::LocalPointToScreen(
2300 HWND src_window_handle,
2301 const POINT &src_point,
2302 POINT *dest_point) const {
2303 if (dest_point == NULL) {
2304 return false;
2305 }
2306
2307 if (!window_position_->IsWindow(src_window_handle)) {
2308 DLOG(ERROR) << "Invalid window handle.";
2309 return false;
2310 }
2311
2312 CRect window_rect;
2313 if (window_position_->GetWindowRect(src_window_handle, &window_rect) ==
2314 FALSE) {
2315 return false;
2316 }
2317
2318 const CPoint offset(window_rect.TopLeft());
2319 dest_point->x = src_point.x + offset.x;
2320 dest_point->y = src_point.y + offset.y;
2321
2322 return true;
2323 }
2324
LocalRectToScreen(HWND src_window_handle,const RECT & src_rect,RECT * dest_rect) const2325 bool LayoutManager::LocalRectToScreen(
2326 HWND src_window_handle,
2327 const RECT &src_rect,
2328 RECT *dest_rect) const {
2329 if (dest_rect == NULL) {
2330 return false;
2331 }
2332
2333 if (!window_position_->IsWindow(src_window_handle)) {
2334 DLOG(ERROR) << "Invalid window handle.";
2335 return false;
2336 }
2337
2338 CRect window_rect;
2339 if (window_position_->GetWindowRect(src_window_handle, &window_rect) ==
2340 FALSE) {
2341 return false;
2342 }
2343
2344 const CPoint offset(window_rect.TopLeft());
2345 dest_rect->left = src_rect.left + offset.x;
2346 dest_rect->top = src_rect.top + offset.y;
2347 dest_rect->right = src_rect.right + offset.x;
2348 dest_rect->bottom = src_rect.bottom + offset.y;
2349
2350 return true;
2351 }
2352
GetClientRect(HWND window_handle,RECT * client_rect) const2353 bool LayoutManager::GetClientRect(
2354 HWND window_handle, RECT *client_rect) const {
2355 return window_position_->GetClientRect(window_handle, client_rect);
2356 }
2357
GetScalingFactor(HWND window_handle) const2358 double LayoutManager::GetScalingFactor(HWND window_handle) const {
2359 const double kDefaultValue = 1.0;
2360 CRect window_rect_in_logical_coord;
2361 if (!window_position_->GetWindowRect(window_handle,
2362 &window_rect_in_logical_coord)) {
2363 return kDefaultValue;
2364 }
2365
2366 CPoint top_left_in_physical_coord;
2367 if (!window_position_->LogicalToPhysicalPoint(
2368 window_handle, window_rect_in_logical_coord.TopLeft(),
2369 &top_left_in_physical_coord)) {
2370 return kDefaultValue;
2371 }
2372 CPoint bottom_right_in_physical_coord;
2373 if (!window_position_->LogicalToPhysicalPoint(
2374 window_handle, window_rect_in_logical_coord.BottomRight(),
2375 &bottom_right_in_physical_coord)) {
2376 return kDefaultValue;
2377 }
2378 const CRect window_rect_in_physical_coord(
2379 top_left_in_physical_coord, bottom_right_in_physical_coord);
2380
2381 if (window_rect_in_physical_coord == window_rect_in_logical_coord) {
2382 // No scaling.
2383 return 1.0;
2384 }
2385
2386 // use larger edge to calculate the scaling factor more accurately.
2387 if (window_rect_in_logical_coord.Width() >
2388 window_rect_in_logical_coord.Height()) {
2389 // Use width.
2390 if (window_rect_in_physical_coord.Width() <= 0 ||
2391 window_rect_in_logical_coord.Width() <= 0) {
2392 return kDefaultValue;
2393 }
2394 DCHECK_NE(0, window_rect_in_logical_coord.Width()) << "divided-by-zero";
2395 return (static_cast<double>(window_rect_in_physical_coord.Width()) /
2396 window_rect_in_logical_coord.Width());
2397 } else {
2398 // Use Height.
2399 if (window_rect_in_physical_coord.Height() <= 0 ||
2400 window_rect_in_logical_coord.Height() <= 0) {
2401 return kDefaultValue;
2402 }
2403 DCHECK_NE(0, window_rect_in_logical_coord.Height()) << "divided-by-zero";
2404 return (static_cast<double>(window_rect_in_physical_coord.Height()) /
2405 window_rect_in_logical_coord.Height());
2406 }
2407 }
2408
GetDefaultGuiFont(LOGFONTW * logfont) const2409 bool LayoutManager::GetDefaultGuiFont(LOGFONTW *logfont) const {
2410 return system_preference_->GetDefaultGuiFont(logfont);
2411 }
2412
GetWritingDirection(const commands::RendererCommand_ApplicationInfo & app_info)2413 LayoutManager::WritingDirection LayoutManager::GetWritingDirection(
2414 const commands::RendererCommand_ApplicationInfo &app_info) {
2415 // |escapement| is the angle between the escapement vector and the x-axis
2416 // of the device, in tenths of degrees. In Windows, (Japanese) vertical
2417 // writing is usually implemented by setting 2700 to the |escapement|,
2418 // which means the escapement vector is parallel to |Rot(270 deg) * (1, 0)|
2419 // in the display coordinate. Note that |escapement| and |orientation| are
2420 // different concept. But we only check |escapement| here for application
2421 // compatibility.
2422 // Any |escapement| except for 2700 is treated as horizontal, on the
2423 // strength of IMEINFO::fdwUICaps has only UI_CAP_2700 as for Mozc.
2424 // See the document of LOGFONT structure and ImmGetProperty API for
2425 // details.
2426 // http://msdn.microsoft.com/en-us/library/dd145037.aspx
2427 // http://msdn.microsoft.com/en-us/library/dd318567.aspx
2428 // TODO(yukawa): Support arbitrary angle.
2429 if (!app_info.has_composition_font() ||
2430 !app_info.composition_font().has_escapement()) {
2431 return WRITING_DIRECTION_UNSPECIFIED;
2432 }
2433
2434 if (app_info.composition_font().escapement() == 2700) {
2435 return VERTICAL_WRITING;
2436 }
2437
2438 return HORIZONTAL_WRITING;
2439 }
2440
LayoutCandidateWindowForSuggestion(const commands::RendererCommand::ApplicationInfo & app_info,CandidateWindowLayout * candidate_layout)2441 bool LayoutManager::LayoutCandidateWindowForSuggestion(
2442 const commands::RendererCommand::ApplicationInfo &app_info,
2443 CandidateWindowLayout *candidate_layout) {
2444 const int compatibility_mode = GetCompatibilityMode(app_info);
2445
2446 CandidateWindowLayoutParams params;
2447 if (!ExtractParams(this, compatibility_mode, app_info, ¶ms)) {
2448 return false;
2449 }
2450
2451 const bool is_suggestion = true;
2452 if (LayoutCandidateWindowByCompositionTarget(
2453 params, compatibility_mode, is_suggestion, this, candidate_layout)) {
2454 DCHECK(candidate_layout->initialized());
2455 return true;
2456 }
2457
2458 if ((compatibility_mode & CAN_USE_CANDIDATE_FORM_FOR_SUGGEST) ==
2459 CAN_USE_CANDIDATE_FORM_FOR_SUGGEST) {
2460 if (LayoutCandidateWindowByCandidateForm(
2461 params, compatibility_mode, this, candidate_layout)) {
2462 DCHECK(candidate_layout->initialized());
2463 return true;
2464 }
2465 }
2466
2467 if (LayoutCandidateWindowByCaretInfo(
2468 params, compatibility_mode, this, candidate_layout)) {
2469 DCHECK(candidate_layout->initialized());
2470 return true;
2471 }
2472
2473 if (LayoutCandidateWindowByCompositionForm(
2474 params, compatibility_mode, this, candidate_layout)) {
2475 DCHECK(candidate_layout->initialized());
2476 return true;
2477 }
2478
2479 if (LayoutCandidateWindowByClientRect(
2480 params, compatibility_mode, this, candidate_layout)) {
2481 DCHECK(candidate_layout->initialized());
2482 return true;
2483 }
2484
2485 return false;
2486 }
2487
LayoutCandidateWindowForConversion(const commands::RendererCommand::ApplicationInfo & app_info,CandidateWindowLayout * candidate_layout)2488 bool LayoutManager::LayoutCandidateWindowForConversion(
2489 const commands::RendererCommand::ApplicationInfo &app_info,
2490 CandidateWindowLayout *candidate_layout) {
2491 const int compatibility_mode = GetCompatibilityMode(app_info);
2492
2493 CandidateWindowLayoutParams params;
2494 if (!ExtractParams(this, compatibility_mode, app_info, ¶ms)) {
2495 return false;
2496 }
2497
2498 const bool is_suggestion = false;
2499 if (LayoutCandidateWindowByCompositionTarget(
2500 params, compatibility_mode, is_suggestion, this, candidate_layout)) {
2501 DCHECK(candidate_layout->initialized());
2502 return true;
2503 }
2504
2505 if (LayoutCandidateWindowByCandidateForm(
2506 params, compatibility_mode, this, candidate_layout)) {
2507 DCHECK(candidate_layout->initialized());
2508 return true;
2509 }
2510
2511 if (LayoutCandidateWindowByCaretInfo(
2512 params, compatibility_mode, this, candidate_layout)) {
2513 DCHECK(candidate_layout->initialized());
2514 return true;
2515 }
2516
2517 if (LayoutCandidateWindowByCompositionForm(
2518 params, compatibility_mode, this, candidate_layout)) {
2519 DCHECK(candidate_layout->initialized());
2520 return true;
2521 }
2522
2523 if (LayoutCandidateWindowByClientRect(
2524 params, compatibility_mode, this, candidate_layout)) {
2525 DCHECK(candidate_layout->initialized());
2526 return true;
2527 }
2528
2529 return false;
2530 }
2531
GetCompatibilityMode(const commands::RendererCommand_ApplicationInfo & app_info)2532 int LayoutManager::GetCompatibilityMode(
2533 const commands::RendererCommand_ApplicationInfo &app_info) {
2534 if (!app_info.has_target_window_handle()) {
2535 return COMPATIBILITY_MODE_NONE;
2536 }
2537 const HWND target_window = WinUtil::DecodeWindowHandle(
2538 app_info.target_window_handle());
2539
2540 if (!window_position_->IsWindow(target_window)) {
2541 return COMPATIBILITY_MODE_NONE;
2542 }
2543
2544 std::wstring class_name;
2545 if (!window_position_->GetWindowClassName(target_window, &class_name)) {
2546 return COMPATIBILITY_MODE_NONE;
2547 }
2548
2549 int mode = COMPATIBILITY_MODE_NONE;
2550 {
2551 {
2552 const wchar_t *kUseCandidateFormForSuggest[] = {
2553 L"Chrome_RenderWidgetHostHWND",
2554 L"JsTaroCtrl",
2555 L"MozillaWindowClass",
2556 L"OperaWindowClass",
2557 L"QWidget",
2558 };
2559 for (size_t i = 0; i < ARRAYSIZE(kUseCandidateFormForSuggest); ++i) {
2560 if (kUseCandidateFormForSuggest[i] == class_name) {
2561 mode |= CAN_USE_CANDIDATE_FORM_FOR_SUGGEST;
2562 break;
2563 }
2564 }
2565 }
2566 }
2567
2568 {
2569 const wchar_t *kUseLocalCoord[] = {
2570 L"gdkWindowToplevel",
2571 L"SunAwtDialog",
2572 L"SunAwtFrame",
2573 };
2574 for (size_t i = 0; i < ARRAYSIZE(kUseLocalCoord); ++i) {
2575 if (kUseLocalCoord[i] == class_name) {
2576 mode |= USE_LOCAL_COORD_FOR_CANDIDATE_FORM;
2577 break;
2578 }
2579 }
2580 }
2581
2582 {
2583 const wchar_t *kIgnoreDefaultCompositionForm[] = {
2584 L"SunAwtDialog",
2585 L"SunAwtFrame",
2586 };
2587 for (size_t i = 0; i < ARRAYSIZE(kIgnoreDefaultCompositionForm); ++i) {
2588 if (kIgnoreDefaultCompositionForm[i] == class_name) {
2589 mode |= IGNORE_DEFAULT_COMPOSITION_FORM;
2590 break;
2591 }
2592 }
2593 }
2594
2595 {
2596 const wchar_t *kShowInfolistImmediately[] = {
2597 L"Emacs",
2598 L"MEADOW",
2599 };
2600 for (size_t i = 0; i < ARRAYSIZE(kShowInfolistImmediately); ++i) {
2601 if (kShowInfolistImmediately[i] == class_name) {
2602 mode |= SHOW_INFOLIST_IMMEDIATELY;
2603 break;
2604 }
2605 }
2606 }
2607
2608 return mode;
2609 }
2610
LayoutIndicatorWindow(const commands::RendererCommand_ApplicationInfo & app_info,IndicatorWindowLayout * indicator_layout)2611 bool LayoutManager::LayoutIndicatorWindow(
2612 const commands::RendererCommand_ApplicationInfo &app_info,
2613 IndicatorWindowLayout *indicator_layout) {
2614 if (indicator_layout == nullptr) {
2615 return false;
2616 }
2617 indicator_layout->Clear();
2618
2619 CandidateWindowLayoutParams params;
2620 if (!ExtractParams(this,
2621 GetCompatibilityMode(app_info),
2622 app_info,
2623 ¶ms)) {
2624 return false;
2625 }
2626
2627 CRect target_rect;
2628 if (!GetTargetRectForIndicator(params, *this, &target_rect)) {
2629 return false;
2630 }
2631
2632 indicator_layout->is_vertical = IsVerticalWriting(params);
2633 indicator_layout->window_rect = target_rect;
2634 return true;
2635 }
2636
2637 } // namespace win32
2638 } // namespace renderer
2639 } // namespace mozc
2640