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 &params) {
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 &params,
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::__anon449ac1ad0111::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 &params) {
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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 &params,
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                            &current_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, &current_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       &current_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, &params)) {
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, &params)) {
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                      &params)) {
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