OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ui/gfx/canvas.h" | |
6 | |
7 #include <limits> | |
8 | |
9 #include "base/i18n/rtl.h" | |
10 #include "base/logging.h" | |
11 #include "base/memory/scoped_ptr.h" | |
12 #include "base/win/scoped_gdi_object.h" | |
13 #include "skia/ext/bitmap_platform_device.h" | |
14 #include "skia/ext/skia_utils_win.h" | |
15 #include "third_party/skia/include/core/SkShader.h" | |
16 #include "ui/gfx/color_utils.h" | |
17 #include "ui/gfx/font.h" | |
18 #include "ui/gfx/rect.h" | |
19 #include "ui/gfx/shadow_value.h" | |
20 | |
21 namespace { | |
22 | |
23 static inline int Round(double x) { | |
24 // Why oh why is this not in a standard header? | |
25 return static_cast<int>(floor(x + 0.5)); | |
26 } | |
27 | |
28 // We make sure that LTR text we draw in an RTL context is modified | |
29 // appropriately to make sure it maintains it LTR orientation. | |
30 void DoDrawText(HDC hdc, | |
31 const string16& text, | |
32 RECT* text_bounds, | |
33 int flags) { | |
34 // Only adjust string directionality if both of the following are true: | |
35 // 1. The current locale is RTL. | |
36 // 2. The string itself has RTL directionality. | |
37 const wchar_t* string_ptr = text.c_str(); | |
38 int string_size = static_cast<int>(text.length()); | |
39 | |
40 string16 localized_text; | |
41 if (flags & DT_RTLREADING) { | |
42 localized_text = text; | |
43 base::i18n::AdjustStringForLocaleDirection(&localized_text); | |
44 string_ptr = localized_text.c_str(); | |
45 string_size = static_cast<int>(localized_text.length()); | |
46 } | |
47 | |
48 DrawText(hdc, string_ptr, string_size, text_bounds, flags); | |
49 } | |
50 | |
51 // Compute the windows flags necessary to implement the provided text Canvas | |
52 // flags. | |
53 int ComputeFormatFlags(int flags, const string16& text) { | |
54 // Setting the text alignment explicitly in case it hasn't already been set. | |
55 // This will make sure that we don't align text to the left on RTL locales | |
56 // just because no alignment flag was passed to DrawStringInt(). | |
57 if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER | | |
58 gfx::Canvas::TEXT_ALIGN_RIGHT | | |
59 gfx::Canvas::TEXT_ALIGN_LEFT))) { | |
60 flags |= gfx::Canvas::DefaultCanvasTextAlignment(); | |
61 } | |
62 | |
63 // horizontal alignment | |
64 int f = 0; | |
65 if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) | |
66 f |= DT_CENTER; | |
67 else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) | |
68 f |= DT_RIGHT; | |
69 else | |
70 f |= DT_LEFT; | |
71 | |
72 // vertical alignment | |
73 if (flags & gfx::Canvas::TEXT_VALIGN_TOP) | |
74 f |= DT_TOP; | |
75 else if (flags & gfx::Canvas::TEXT_VALIGN_BOTTOM) | |
76 f |= DT_BOTTOM; | |
77 else | |
78 f |= DT_VCENTER; | |
79 | |
80 if (flags & gfx::Canvas::MULTI_LINE) { | |
81 f |= DT_WORDBREAK; | |
82 if (flags & gfx::Canvas::CHARACTER_BREAK) | |
83 f |= DT_EDITCONTROL; // Turns on character breaking (not documented) | |
84 else if (!(flags & gfx::Canvas::NO_ELLIPSIS)) | |
85 f |= DT_WORD_ELLIPSIS; | |
86 } else { | |
87 f |= DT_SINGLELINE; | |
88 } | |
89 | |
90 if (flags & gfx::Canvas::HIDE_PREFIX) | |
91 f |= DT_HIDEPREFIX; | |
92 else if ((flags & gfx::Canvas::SHOW_PREFIX) == 0) | |
93 f |= DT_NOPREFIX; | |
94 | |
95 if (!(flags & gfx::Canvas::NO_ELLIPSIS)) | |
96 f |= DT_END_ELLIPSIS; | |
97 | |
98 // In order to make sure RTL/BiDi strings are rendered correctly, we must | |
99 // pass the flag DT_RTLREADING to DrawText (when the locale's language is | |
100 // a right-to-left language) so that Windows does the right thing. | |
101 // | |
102 // In addition to correctly displaying text containing both RTL and LTR | |
103 // elements (for example, a string containing a telephone number within a | |
104 // sentence in Hebrew, or a sentence in Hebrew that contains a word in | |
105 // English) this flag also makes sure that if there is not enough space to | |
106 // display the entire string, the ellipsis is displayed on the left hand side | |
107 // of the truncated string and not on the right hand side. | |
108 // | |
109 // We make a distinction between Chrome UI strings and text coming from a web | |
110 // page. | |
111 // | |
112 // For text coming from a web page we determine the alignment based on the | |
113 // first character with strong directionality. If the directionality of the | |
114 // first character with strong directionality in the text is LTR, the | |
115 // alignment is set to DT_LEFT, and the directionality should not be set as | |
116 // DT_RTLREADING. If the directionality of the first character with strong | |
117 // directionality in the text is RTL, its alignment is set to DT_RIGHT, and | |
118 // its directionality is set as DT_RTLREADING through | |
119 // FORCE_RTL_DIRECTIONALITY. | |
120 // | |
121 // This heuristic doesn't work for Chrome UI strings since even in RTL | |
122 // locales, some of those might start with English text but we know they're | |
123 // localized so their directionality should be set as DT_RTLREADING if it | |
124 // contains strong RTL characters. | |
125 // | |
126 // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when | |
127 // the flag is set, LRE-PDF don't have the desired effect of rendering | |
128 // multiline English-only text as LTR. | |
129 // | |
130 // Note that if the caller is explicitly requesting displaying the text | |
131 // using RTL directionality then we respect that and pass DT_RTLREADING to | |
132 // ::DrawText even if the locale is LTR. | |
133 int force_rtl = (flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY); | |
134 int force_ltr = (flags & gfx::Canvas::FORCE_LTR_DIRECTIONALITY); | |
135 bool is_rtl = base::i18n::IsRTL(); | |
136 bool string_contains_strong_rtl_chars = | |
137 base::i18n::StringContainsStrongRTLChars(text); | |
138 if (force_rtl || (!force_ltr && is_rtl && string_contains_strong_rtl_chars)) | |
139 f |= DT_RTLREADING; | |
140 | |
141 return f; | |
142 } | |
143 | |
144 // Changes the alpha of the given bitmap. | |
145 // If |fade_to_right| is true then the rect fades from opaque to clear, | |
146 // otherwise the rect fades from clear to opaque. | |
147 void FadeBitmapRect(SkDevice& bmp_device, | |
148 const gfx::Rect& rect, | |
149 bool fade_to_right) { | |
150 SkBitmap bmp = bmp_device.accessBitmap(true); | |
151 DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config()); | |
152 SkAutoLockPixels lock(bmp); | |
153 float total_width = static_cast<float>(rect.width()); | |
154 | |
155 for (int x = rect.x(); x < rect.right(); x++) { | |
156 float cur_width = static_cast<float>(fade_to_right ? | |
157 rect.right() - x : x - rect.x()); | |
158 // We want the fade effect to go from 0.2 to 1.0. | |
159 float alpha_percent = ((cur_width / total_width) * 0.8f) + 0.2f; | |
160 | |
161 for (int y = rect.y(); y < rect.bottom(); y++) { | |
162 SkColor color = bmp.getColor(x, y); | |
163 SkAlpha alpha = static_cast<SkAlpha>(SkColorGetA(color) * alpha_percent); | |
164 *bmp.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetA(color, alpha)); | |
165 } | |
166 } | |
167 } | |
168 | |
169 // DrawText() doesn't support alpha channels. To create a transparent background | |
170 // this function draws black on white. It then uses the intensity of black | |
171 // to determine how much alpha to use. The text is drawn in |gfx_text_rect| and | |
172 // clipped to |gfx_draw_rect|. | |
173 void DrawTextAndClearBackground(SkCanvas* bmp_canvas, | |
174 HFONT font, | |
175 COLORREF text_color, | |
176 const string16& text, | |
177 int flags, | |
178 const gfx::Rect& gfx_text_rect, | |
179 const gfx::Rect& gfx_draw_rect) { | |
180 skia::ScopedPlatformPaint scoped_platform_paint(bmp_canvas); | |
181 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | |
182 | |
183 // Clear the background by filling with white. | |
184 HBRUSH fill_brush = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); | |
185 HANDLE old_brush = SelectObject(hdc, fill_brush); | |
186 RECT draw_rect = gfx_draw_rect.ToRECT(); | |
187 FillRect(hdc, &draw_rect, fill_brush); | |
188 SelectObject(hdc, old_brush); | |
189 | |
190 // Set black text with transparent background. | |
191 SetBkMode(hdc, TRANSPARENT); | |
192 SetTextColor(hdc, 0); | |
193 | |
194 // Draw the text. | |
195 int save_dc_id = SaveDC(hdc); | |
196 // Clip the text to the draw destination. | |
197 IntersectClipRect(hdc, draw_rect.left, draw_rect.top, | |
198 draw_rect.right, draw_rect.bottom); | |
199 SelectObject(hdc, font); | |
200 RECT text_rect = gfx_text_rect.ToRECT(); | |
201 DoDrawText(hdc, text, &text_rect, | |
202 ComputeFormatFlags(flags, text)); | |
203 RestoreDC(hdc, save_dc_id); | |
204 | |
205 BYTE text_color_r = GetRValue(text_color); | |
206 BYTE text_color_g = GetGValue(text_color); | |
207 BYTE text_color_b = GetBValue(text_color); | |
208 | |
209 SkBitmap bmp = bmp_canvas->getTopDevice()->accessBitmap(true); | |
210 DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config()); | |
211 SkAutoLockPixels lock(bmp); | |
212 | |
213 // At this point the bitmap has black text on white. | |
214 // The intensity of black tells us the alpha value of the text. | |
215 for (int y = draw_rect.top; y < draw_rect.bottom; y++) { | |
216 for (int x = draw_rect.left; x < draw_rect.right; x++) { | |
217 // Gets the color directly. DrawText doesn't premultiply alpha so | |
218 // using SkBitmap::getColor() won't work here. | |
219 SkColor color = *bmp.getAddr32(x, y); | |
220 // Calculate the alpha using the luminance. Since this is black text | |
221 // on a white background the luminosity must be inverted. | |
222 BYTE alpha = 0xFF - color_utils::GetLuminanceForColor(color); | |
223 *bmp.getAddr32(x, y) = SkPreMultiplyColor( | |
224 SkColorSetARGB(alpha, text_color_r, text_color_g, text_color_b)); | |
225 } | |
226 } | |
227 } | |
228 | |
229 // Draws the given text with a fade out gradient. |bmp_device| is a bitmap | |
230 // that is used to temporary drawing. The text is drawn in |text_rect| and | |
231 // clipped to |draw_rect|. | |
232 void DrawTextGradientPart(HDC hdc, | |
233 SkCanvas* bmp_canvas, | |
234 const string16& text, | |
235 SkColor color, | |
236 HFONT font, | |
237 const gfx::Rect& text_rect, | |
238 const gfx::Rect& draw_rect, | |
239 bool fade_to_right, | |
240 int flags) { | |
241 DrawTextAndClearBackground(bmp_canvas, font, skia::SkColorToCOLORREF(color), | |
242 text, flags, text_rect, draw_rect); | |
243 FadeBitmapRect(*bmp_canvas->getTopDevice(), draw_rect, fade_to_right); | |
244 BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; | |
245 | |
246 skia::ScopedPlatformPaint scoped_platform_paint(bmp_canvas); | |
247 HDC bmp_hdc = scoped_platform_paint.GetPlatformSurface(); | |
248 AlphaBlend(hdc, draw_rect.x(), draw_rect.y(), draw_rect.width(), | |
249 draw_rect.height(), bmp_hdc, draw_rect.x(), draw_rect.y(), | |
250 draw_rect.width(), draw_rect.height(), blend); | |
251 } | |
252 | |
253 enum PrimarySide { | |
254 PrimaryOnLeft, | |
255 PrimaryOnRight, | |
256 }; | |
257 | |
258 // Divides |rect| horizontally into a |primary| of width |primary_width| and a | |
259 // |secondary| taking up the remainder. | |
260 void DivideRect(const gfx::Rect& rect, | |
261 PrimarySide primary_side, | |
262 int primary_width, | |
263 gfx::Rect* primary, | |
264 gfx::Rect* secondary) { | |
265 *primary = rect; | |
266 *secondary = rect; | |
267 int remainder = rect.width() - primary_width; | |
268 | |
269 switch (primary_side) { | |
270 case PrimaryOnLeft: | |
271 primary->Inset(0, 0, remainder, 0); | |
272 secondary->Inset(primary_width, 0, 0, 0); | |
273 break; | |
274 case PrimaryOnRight: | |
275 primary->Inset(remainder, 0, 0, 0); | |
276 secondary->Inset(0, 0, primary_width, 0); | |
277 break; | |
278 } | |
279 } | |
280 | |
281 } // anonymous namespace | |
282 | |
283 namespace gfx { | |
284 | |
285 // static | |
286 void Canvas::SizeStringInt(const string16& text, | |
287 const gfx::Font& font, | |
288 int* width, int* height, | |
289 int flags) { | |
290 // Clamp the max amount of text we'll measure to 2K. When the string is | |
291 // actually drawn, it will be clipped to whatever size box is provided, and | |
292 // the time to do that doesn't depend on the length being clipped off. | |
293 const int kMaxStringLength = 2048 - 1; // So the trailing \0 fits in 2K. | |
294 string16 clamped_string(text.substr(0, kMaxStringLength)); | |
295 | |
296 if (*width == 0) { | |
297 // If multi-line + character break are on, the computed width will be one | |
298 // character wide (useless). Furthermore, if in this case the provided text | |
299 // contains very long "words" (substrings without a word-breaking point), | |
300 // DrawText() can run extremely slowly (e.g. several seconds). So in this | |
301 // case, we turn character breaking off to get a more accurate "desired" | |
302 // width and avoid the slowdown. | |
303 int multiline_charbreak = | |
304 gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK; | |
305 if ((flags & multiline_charbreak) == multiline_charbreak) | |
306 flags &= ~gfx::Canvas::CHARACTER_BREAK; | |
307 | |
308 // Weird undocumented behavior: if the width is 0, DoDrawText() won't | |
309 // calculate a size at all. So set it to 1, which it will then change. | |
310 if (!text.empty()) | |
311 *width = 1; | |
312 } | |
313 RECT r = { 0, 0, *width, *height }; | |
314 | |
315 HDC dc = GetDC(NULL); | |
316 HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.GetNativeFont())); | |
317 DoDrawText(dc, clamped_string, &r, | |
318 ComputeFormatFlags(flags, clamped_string) | DT_CALCRECT); | |
319 SelectObject(dc, old_font); | |
320 ReleaseDC(NULL, dc); | |
321 | |
322 *width = r.right; | |
323 *height = r.bottom; | |
324 } | |
325 | |
326 void Canvas::DrawStringInt(const string16& text, | |
327 HFONT font, | |
328 SkColor color, | |
329 const gfx::Rect& text_bounds, | |
330 int flags) { | |
331 SkRect fclip; | |
332 if (!canvas_->getClipBounds(&fclip)) | |
333 return; | |
334 RECT text_rect = text_bounds.ToRECT(); | |
335 SkIRect clip; | |
336 fclip.round(&clip); | |
337 if (!clip.intersect(skia::RECTToSkIRect(text_rect))) | |
338 return; | |
339 | |
340 // Clamp the max amount of text we'll draw to 32K. There seem to be bugs in | |
341 // DrawText() if you e.g. ask it to character-break a no-whitespace string of | |
342 // length > 43680 (for which it draws nothing), and since we clamped to 2K in | |
343 // SizeStringInt() we're unlikely to be able to display this much anyway. | |
344 const int kMaxStringLength = 32768 - 1; // So the trailing \0 fits in 32K. | |
345 string16 clamped_string(text.substr(0, kMaxStringLength)); | |
346 | |
347 HDC dc; | |
348 HFONT old_font; | |
349 { | |
350 skia::ScopedPlatformPaint scoped_platform_paint(canvas_); | |
351 dc = scoped_platform_paint.GetPlatformSurface(); | |
352 SetBkMode(dc, TRANSPARENT); | |
353 old_font = (HFONT)SelectObject(dc, font); | |
354 COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color), | |
355 SkColorGetB(color)); | |
356 SetTextColor(dc, brush_color); | |
357 | |
358 int f = ComputeFormatFlags(flags, clamped_string); | |
359 DoDrawText(dc, clamped_string, &text_rect, f); | |
360 } | |
361 | |
362 // Restore the old font. This way we don't have to worry if the caller | |
363 // deletes the font and the DC lives longer. | |
364 SelectObject(dc, old_font); | |
365 | |
366 // Windows will have cleared the alpha channel of the text we drew. Assume | |
367 // we're drawing to an opaque surface, or at least the text rect area is | |
368 // opaque. | |
369 skia::MakeOpaque(canvas_, clip.fLeft, clip.fTop, clip.width(), | |
370 clip.height()); | |
371 } | |
372 | |
373 void Canvas::DrawStringWithShadows(const string16& text, | |
374 const gfx::Font& font, | |
375 SkColor color, | |
376 const gfx::Rect& text_bounds, | |
377 int flags, | |
378 const ShadowValues& shadows) { | |
379 DLOG_IF(WARNING, !shadows.empty()) << "Text shadow not implemented."; | |
380 | |
381 DrawStringInt(text, font.GetNativeFont(), color, text_bounds, flags); | |
382 } | |
383 | |
384 // Checks each pixel immediately adjacent to the given pixel in the bitmap. If | |
385 // any of them are not the halo color, returns true. This defines the halo of | |
386 // pixels that will appear around the text. Note that we have to check each | |
387 // pixel against both the halo color and transparent since DrawStringWithHalo | |
388 // will modify the bitmap as it goes, and clears pixels shouldn't count as | |
389 // changed. | |
390 static bool pixelShouldGetHalo(const SkBitmap& bitmap, | |
391 int x, int y, | |
392 SkColor halo_color) { | |
393 if (x > 0 && | |
394 *bitmap.getAddr32(x - 1, y) != halo_color && | |
395 *bitmap.getAddr32(x - 1, y) != 0) | |
396 return true; // Touched pixel to the left. | |
397 if (x < bitmap.width() - 1 && | |
398 *bitmap.getAddr32(x + 1, y) != halo_color && | |
399 *bitmap.getAddr32(x + 1, y) != 0) | |
400 return true; // Touched pixel to the right. | |
401 if (y > 0 && | |
402 *bitmap.getAddr32(x, y - 1) != halo_color && | |
403 *bitmap.getAddr32(x, y - 1) != 0) | |
404 return true; // Touched pixel above. | |
405 if (y < bitmap.height() - 1 && | |
406 *bitmap.getAddr32(x, y + 1) != halo_color && | |
407 *bitmap.getAddr32(x, y + 1) != 0) | |
408 return true; // Touched pixel below. | |
409 return false; | |
410 } | |
411 | |
412 void Canvas::DrawStringWithHalo(const string16& text, | |
413 const gfx::Font& font, | |
414 SkColor text_color, | |
415 SkColor halo_color_in, | |
416 int x, int y, int w, int h, | |
417 int flags) { | |
418 // Some callers will have semitransparent halo colors, which we don't handle | |
419 // (since the resulting image can have 1-bit transparency only). | |
420 SkColor halo_color = SkColorSetA(halo_color_in, 0xFF); | |
421 | |
422 // Create a temporary buffer filled with the halo color. It must leave room | |
423 // for the 1-pixel border around the text. | |
424 Size size(w + 2, h + 2); | |
425 Canvas text_canvas(size, true); | |
426 SkPaint bkgnd_paint; | |
427 bkgnd_paint.setColor(halo_color); | |
428 text_canvas.DrawRect(gfx::Rect(size), bkgnd_paint); | |
429 | |
430 // Draw the text into the temporary buffer. This will have correct | |
431 // ClearType since the background color is the same as the halo color. | |
432 text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags); | |
433 | |
434 // Windows will have cleared the alpha channel for the pixels it drew. Make it | |
435 // opaque. We have to do this first since pixelShouldGetHalo will check for | |
436 // 0 to see if a pixel has been modified to transparent, and black text that | |
437 // Windows draw will look transparent to it! | |
438 skia::MakeOpaque(text_canvas.sk_canvas(), 0, 0, size.width(), size.height()); | |
439 | |
440 uint32_t halo_premul = SkPreMultiplyColor(halo_color); | |
441 SkBitmap& text_bitmap = const_cast<SkBitmap&>( | |
442 skia::GetTopDevice(*text_canvas.sk_canvas())->accessBitmap(true)); | |
443 for (int cur_y = 0; cur_y < h + 2; cur_y++) { | |
444 uint32_t* text_row = text_bitmap.getAddr32(0, cur_y); | |
445 for (int cur_x = 0; cur_x < w + 2; cur_x++) { | |
446 if (text_row[cur_x] == halo_premul) { | |
447 // This pixel was not touched by the text routines. See if it borders | |
448 // a touched pixel in any of the 4 directions (not diagonally). | |
449 if (!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul)) | |
450 text_row[cur_x] = 0; // Make transparent. | |
451 } else { | |
452 text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque. | |
453 } | |
454 } | |
455 } | |
456 | |
457 // Draw the halo bitmap with blur. | |
458 DrawImageInt(text_bitmap, x - 1, y - 1); | |
459 } | |
460 | |
461 void Canvas::DrawFadeTruncatingString( | |
462 const string16& text, | |
463 TruncateFadeMode truncate_mode, | |
464 size_t desired_characters_to_truncate_from_head, | |
465 const gfx::Font& font, | |
466 SkColor color, | |
467 const gfx::Rect& display_rect) { | |
468 int flags = NO_ELLIPSIS; | |
469 | |
470 // If the whole string fits in the destination then just draw it directly. | |
471 int total_string_width = 0; | |
472 int total_string_height = 0; | |
473 SizeStringInt(text, font, &total_string_width, &total_string_height, | |
474 flags | TEXT_VALIGN_TOP); | |
475 | |
476 if (total_string_width <= display_rect.width()) { | |
477 DrawStringInt(text, font, color, display_rect.x(), display_rect.y(), | |
478 display_rect.width(), display_rect.height(), 0); | |
479 return; | |
480 } | |
481 | |
482 int average_character_width = font.GetAverageCharacterWidth(); | |
483 int clipped_string_width = total_string_width - display_rect.width(); | |
484 // Clip the string by drawing it to the left by |offset_x|. | |
485 int offset_x = 0; | |
486 switch (truncate_mode) { | |
487 case TruncateFadeHead: | |
488 offset_x = clipped_string_width; | |
489 break; | |
490 case TruncateFadeHeadAndTail: | |
491 DCHECK_GT(desired_characters_to_truncate_from_head, 0u); | |
492 // Get the width of the beginning of the string we're clipping. | |
493 string16 clipped_head_string = | |
494 text.substr(0, desired_characters_to_truncate_from_head); | |
495 int clipped_width; | |
496 int clipped_height; | |
497 SizeStringInt(clipped_head_string, font, | |
498 &clipped_width, &clipped_height, flags); | |
499 | |
500 // This is the offset at which we start drawing. This causes the | |
501 // beginning of the string to get clipped. | |
502 offset_x = clipped_width; | |
503 | |
504 // Due to the fade effect the first character is hard to see. | |
505 // We want to make sure that the first character starting at | |
506 // |desired_characters_to_truncate_from_head| is readable so we reduce | |
507 // the offset by a little bit. | |
508 offset_x = std::max(0, Round(offset_x - average_character_width * 2)); | |
509 | |
510 // If the offset is so large that there's empty space at the tail | |
511 // then reduce the offset so we can use up the empty space. | |
512 offset_x = std::min(offset_x, clipped_string_width); | |
513 break; | |
514 } | |
515 bool is_truncating_head = offset_x > 0; | |
516 bool is_truncating_tail = clipped_string_width > offset_x; | |
517 | |
518 bool is_rtl = (ComputeFormatFlags(flags, text) & DT_RTLREADING) != 0; | |
519 // |is_rtl| tells us if the given text is right to left or not. |is_rtl| can | |
520 // be false even if the UI is set to a right to left language. | |
521 // Now, normally, we right align all text if the UI is set to a right to | |
522 // left language. In this case though we don't want that because we render | |
523 // the ends of the string ourselves. | |
524 if (!is_rtl) | |
525 flags |= TEXT_ALIGN_LEFT; | |
526 | |
527 // Fade in/out about 2.5 characters of the beginning/end of the string. | |
528 // The .5 here is helpful if one of the characters is a space. | |
529 // Use a quarter of the display width if the string is very short. | |
530 int gradient_width = Round(std::min(average_character_width * 2.5, | |
531 display_rect.width() / 4.0)); | |
532 | |
533 // Move the origin to |display_rect.origin()|. This simplifies all the | |
534 // drawing so that both the source and destination can be (0,0). | |
535 canvas_->save(SkCanvas::kMatrix_SaveFlag); | |
536 Translate(display_rect.origin()); | |
537 | |
538 gfx::Rect solid_part(gfx::Point(), display_rect.size()); | |
539 gfx::Rect head_part; | |
540 gfx::Rect tail_part; | |
541 if (is_truncating_head) | |
542 DivideRect(solid_part, is_rtl ? PrimaryOnRight : PrimaryOnLeft, | |
543 gradient_width, &head_part, &solid_part); | |
544 if (is_truncating_tail) | |
545 DivideRect(solid_part, is_rtl ? PrimaryOnLeft : PrimaryOnRight, | |
546 gradient_width, &tail_part, &solid_part); | |
547 | |
548 // Grow |display_rect| by |offset_x|. | |
549 gfx::Rect text_rect(gfx::Point(), display_rect.size()); | |
550 if (!is_rtl) | |
551 text_rect.set_x(text_rect.x() - offset_x); | |
552 text_rect.set_width(text_rect.width() + offset_x); | |
553 | |
554 // Create a temporary bitmap to draw the gradient to. | |
555 scoped_ptr<SkCanvas> gradient_canvas(skia::CreateBitmapCanvas( | |
556 display_rect.width(), display_rect.height(), false)); | |
557 gradient_canvas->clear(SkColorSetARGB(0, 0, 0, 0)); | |
558 | |
559 { | |
560 skia::ScopedPlatformPaint scoped_platform_paint(canvas_); | |
561 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | |
562 if (is_truncating_head) | |
563 DrawTextGradientPart(hdc, gradient_canvas.get(), text, color, | |
564 font.GetNativeFont(), text_rect, head_part, is_rtl, | |
565 flags); | |
566 if (is_truncating_tail) | |
567 DrawTextGradientPart(hdc, gradient_canvas.get(), text, color, | |
568 font.GetNativeFont(), text_rect, tail_part, !is_rtl, | |
569 flags); | |
570 } | |
571 | |
572 // Draw the solid part. | |
573 canvas_->save(SkCanvas::kClip_SaveFlag); | |
574 ClipRect(solid_part); | |
575 DrawStringInt(text, font, color, | |
576 text_rect.x(), text_rect.y(), | |
577 text_rect.width(), text_rect.height(), | |
578 flags); | |
579 canvas_->restore(); | |
580 canvas_->restore(); | |
581 } | |
582 | |
583 } // namespace gfx | |
OLD | NEW |