OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "gfx/canvas_skia.h" | |
6 | |
7 #include <limits> | |
8 | |
9 #include "base/i18n/rtl.h" | |
10 #include "gfx/font.h" | |
11 #include "gfx/rect.h" | |
12 #include "third_party/skia/include/core/SkShader.h" | |
13 | |
14 namespace { | |
15 | |
16 // We make sure that LTR text we draw in an RTL context is modified | |
17 // appropriately to make sure it maintains it LTR orientation. | |
18 void DoDrawText(HDC hdc, | |
19 const string16& text, | |
20 RECT* text_bounds, | |
21 int flags) { | |
22 // Only adjust string directionality if both of the following are true: | |
23 // 1. The current locale is RTL. | |
24 // 2. The string itself has RTL directionality. | |
25 const wchar_t* string_ptr = text.c_str(); | |
26 int string_size = static_cast<int>(text.length()); | |
27 | |
28 string16 localized_text; | |
29 if (flags & DT_RTLREADING) { | |
30 localized_text = text; | |
31 base::i18n::AdjustStringForLocaleDirection(&localized_text); | |
32 string_ptr = localized_text.c_str(); | |
33 string_size = static_cast<int>(localized_text.length()); | |
34 } | |
35 | |
36 DrawText(hdc, string_ptr, string_size, text_bounds, flags); | |
37 } | |
38 | |
39 // Compute the windows flags necessary to implement the provided text Canvas | |
40 // flags. | |
41 int ComputeFormatFlags(int flags, const string16& text) { | |
42 // Setting the text alignment explicitly in case it hasn't already been set. | |
43 // This will make sure that we don't align text to the left on RTL locales | |
44 // just because no alignment flag was passed to DrawStringInt(). | |
45 if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER | | |
46 gfx::Canvas::TEXT_ALIGN_RIGHT | | |
47 gfx::Canvas::TEXT_ALIGN_LEFT))) { | |
48 flags |= gfx::CanvasSkia::DefaultCanvasTextAlignment(); | |
49 } | |
50 | |
51 // horizontal alignment | |
52 int f = 0; | |
53 if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) | |
54 f |= DT_CENTER; | |
55 else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) | |
56 f |= DT_RIGHT; | |
57 else | |
58 f |= DT_LEFT; | |
59 | |
60 // vertical alignment | |
61 if (flags & gfx::Canvas::TEXT_VALIGN_TOP) | |
62 f |= DT_TOP; | |
63 else if (flags & gfx::Canvas::TEXT_VALIGN_BOTTOM) | |
64 f |= DT_BOTTOM; | |
65 else | |
66 f |= DT_VCENTER; | |
67 | |
68 if (flags & gfx::Canvas::MULTI_LINE) { | |
69 f |= DT_WORDBREAK; | |
70 if (flags & gfx::Canvas::CHARACTER_BREAK) | |
71 f |= DT_EDITCONTROL; // Turns on character breaking (not documented) | |
72 else if (!(flags & gfx::Canvas::NO_ELLIPSIS)) | |
73 f |= DT_WORD_ELLIPSIS; | |
74 } else { | |
75 f |= DT_SINGLELINE; | |
76 } | |
77 | |
78 if (flags & gfx::Canvas::HIDE_PREFIX) | |
79 f |= DT_HIDEPREFIX; | |
80 else if ((flags & gfx::Canvas::SHOW_PREFIX) == 0) | |
81 f |= DT_NOPREFIX; | |
82 | |
83 if (!(flags & gfx::Canvas::NO_ELLIPSIS)) | |
84 f |= DT_END_ELLIPSIS; | |
85 | |
86 // In order to make sure RTL/BiDi strings are rendered correctly, we must | |
87 // pass the flag DT_RTLREADING to DrawText (when the locale's language is | |
88 // a right-to-left language) so that Windows does the right thing. | |
89 // | |
90 // In addition to correctly displaying text containing both RTL and LTR | |
91 // elements (for example, a string containing a telephone number within a | |
92 // sentence in Hebrew, or a sentence in Hebrew that contains a word in | |
93 // English) this flag also makes sure that if there is not enough space to | |
94 // display the entire string, the ellipsis is displayed on the left hand side | |
95 // of the truncated string and not on the right hand side. | |
96 // | |
97 // We make a distinction between Chrome UI strings and text coming from a web | |
98 // page. | |
99 // | |
100 // For text coming from a web page we determine the alignment based on the | |
101 // first character with strong directionality. If the directionality of the | |
102 // first character with strong directionality in the text is LTR, the | |
103 // alignment is set to DT_LEFT, and the directionality should not be set as | |
104 // DT_RTLREADING. | |
105 // | |
106 // This heuristic doesn't work for Chrome UI strings since even in RTL | |
107 // locales, some of those might start with English text but we know they're | |
108 // localized so we always want them to be right aligned, and their | |
109 // directionality should be set as DT_RTLREADING. | |
110 // | |
111 // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when | |
112 // the flag is set, LRE-PDF don't have the desired effect of rendering | |
113 // multiline English-only text as LTR. | |
114 // | |
115 // Note that if the caller is explicitly requesting displaying the text | |
116 // using RTL directionality then we respect that and pass DT_RTLREADING to | |
117 // ::DrawText even if the locale is LTR. | |
118 if ((flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY) || | |
119 (base::i18n::IsRTL() && | |
120 (f & DT_RIGHT) && base::i18n::StringContainsStrongRTLChars(text))) { | |
121 f |= DT_RTLREADING; | |
122 } | |
123 | |
124 return f; | |
125 } | |
126 | |
127 } // anonymous namespace | |
128 | |
129 namespace gfx { | |
130 | |
131 CanvasSkia::CanvasSkia(int width, int height, bool is_opaque) | |
132 : skia::PlatformCanvas(width, height, is_opaque) { | |
133 } | |
134 | |
135 CanvasSkia::CanvasSkia() : skia::PlatformCanvas() { | |
136 } | |
137 | |
138 CanvasSkia::~CanvasSkia() { | |
139 } | |
140 | |
141 // static | |
142 void CanvasSkia::SizeStringInt(const string16& text, | |
143 const gfx::Font& font, | |
144 int* width, int* height, | |
145 int flags) { | |
146 // Clamp the max amount of text we'll measure to 2K. When the string is | |
147 // actually drawn, it will be clipped to whatever size box is provided, and | |
148 // the time to do that doesn't depend on the length being clipped off. | |
149 const int kMaxStringLength = 2048 - 1; // So the trailing \0 fits in 2K. | |
150 string16 clamped_string(text.substr(0, kMaxStringLength)); | |
151 | |
152 if (*width == 0) { | |
153 // If multi-line + character break are on, the computed width will be one | |
154 // character wide (useless). Furthermore, if in this case the provided text | |
155 // contains very long "words" (substrings without a word-breaking point), | |
156 // DrawText() can run extremely slowly (e.g. several seconds). So in this | |
157 // case, we turn character breaking off to get a more accurate "desired" | |
158 // width and avoid the slowdown. | |
159 if (flags & (gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK)) | |
160 flags &= ~gfx::Canvas::CHARACTER_BREAK; | |
161 | |
162 // Weird undocumented behavior: if the width is 0, DoDrawText() won't | |
163 // calculate a size at all. So set it to 1, which it will then change. | |
164 if (!text.empty()) | |
165 *width = 1; | |
166 } | |
167 RECT r = { 0, 0, *width, *height }; | |
168 | |
169 HDC dc = GetDC(NULL); | |
170 HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.GetNativeFont())); | |
171 DoDrawText(dc, clamped_string, &r, | |
172 ComputeFormatFlags(flags, clamped_string) | DT_CALCRECT); | |
173 SelectObject(dc, old_font); | |
174 ReleaseDC(NULL, dc); | |
175 | |
176 *width = r.right; | |
177 *height = r.bottom; | |
178 } | |
179 | |
180 void CanvasSkia::DrawStringInt(const string16& text, | |
181 HFONT font, | |
182 const SkColor& color, | |
183 int x, int y, int w, int h, | |
184 int flags) { | |
185 if (!IntersectsClipRectInt(x, y, w, h)) | |
186 return; | |
187 | |
188 // Clamp the max amount of text we'll draw to 32K. There seem to be bugs in | |
189 // DrawText() if you e.g. ask it to character-break a no-whitespace string of | |
190 // length > 43680 (for which it draws nothing), and since we clamped to 2K in | |
191 // SizeStringInt() we're unlikely to be able to display this much anyway. | |
192 const int kMaxStringLength = 32768 - 1; // So the trailing \0 fits in 32K. | |
193 string16 clamped_string(text.substr(0, kMaxStringLength)); | |
194 | |
195 RECT text_bounds = { x, y, x + w, y + h }; | |
196 HDC dc = beginPlatformPaint(); | |
197 SetBkMode(dc, TRANSPARENT); | |
198 HFONT old_font = (HFONT)SelectObject(dc, font); | |
199 COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color), | |
200 SkColorGetB(color)); | |
201 SetTextColor(dc, brush_color); | |
202 | |
203 int f = ComputeFormatFlags(flags, clamped_string); | |
204 DoDrawText(dc, clamped_string, &text_bounds, f); | |
205 endPlatformPaint(); | |
206 | |
207 // Restore the old font. This way we don't have to worry if the caller | |
208 // deletes the font and the DC lives longer. | |
209 SelectObject(dc, old_font); | |
210 | |
211 // Windows will have cleared the alpha channel of the text we drew. Assume | |
212 // we're drawing to an opaque surface, or at least the text rect area is | |
213 // opaque. | |
214 getTopPlatformDevice().makeOpaque(x, y, w, h); | |
215 } | |
216 | |
217 void CanvasSkia::DrawStringInt(const string16& text, | |
218 const gfx::Font& font, | |
219 const SkColor& color, | |
220 int x, int y, int w, int h, | |
221 int flags) { | |
222 DrawStringInt(text, font.GetNativeFont(), color, x, y, w, h, flags); | |
223 } | |
224 | |
225 // Checks each pixel immediately adjacent to the given pixel in the bitmap. If | |
226 // any of them are not the halo color, returns true. This defines the halo of | |
227 // pixels that will appear around the text. Note that we have to check each | |
228 // pixel against both the halo color and transparent since DrawStringWithHalo | |
229 // will modify the bitmap as it goes, and clears pixels shouldn't count as | |
230 // changed. | |
231 static bool pixelShouldGetHalo(const SkBitmap& bitmap, | |
232 int x, int y, | |
233 SkColor halo_color) { | |
234 if (x > 0 && | |
235 *bitmap.getAddr32(x - 1, y) != halo_color && | |
236 *bitmap.getAddr32(x - 1, y) != 0) | |
237 return true; // Touched pixel to the left. | |
238 if (x < bitmap.width() - 1 && | |
239 *bitmap.getAddr32(x + 1, y) != halo_color && | |
240 *bitmap.getAddr32(x + 1, y) != 0) | |
241 return true; // Touched pixel to the right. | |
242 if (y > 0 && | |
243 *bitmap.getAddr32(x, y - 1) != halo_color && | |
244 *bitmap.getAddr32(x, y - 1) != 0) | |
245 return true; // Touched pixel above. | |
246 if (y < bitmap.height() - 1 && | |
247 *bitmap.getAddr32(x, y + 1) != halo_color && | |
248 *bitmap.getAddr32(x, y + 1) != 0) | |
249 return true; // Touched pixel below. | |
250 return false; | |
251 } | |
252 | |
253 void CanvasSkia::DrawStringWithHalo(const string16& text, | |
254 const gfx::Font& font, | |
255 const SkColor& text_color, | |
256 const SkColor& halo_color_in, | |
257 int x, int y, int w, int h, | |
258 int flags) { | |
259 // Some callers will have semitransparent halo colors, which we don't handle | |
260 // (since the resulting image can have 1-bit transparency only). | |
261 SkColor halo_color = halo_color_in | 0xFF000000; | |
262 | |
263 // Create a temporary buffer filled with the halo color. It must leave room | |
264 // for the 1-pixel border around the text. | |
265 CanvasSkia text_canvas(w + 2, h + 2, true); | |
266 SkPaint bkgnd_paint; | |
267 bkgnd_paint.setColor(halo_color); | |
268 text_canvas.DrawRectInt(0, 0, w + 2, h + 2, bkgnd_paint); | |
269 | |
270 // Draw the text into the temporary buffer. This will have correct | |
271 // ClearType since the background color is the same as the halo color. | |
272 text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags); | |
273 | |
274 // Windows will have cleared the alpha channel for the pixels it drew. Make it | |
275 // opaque. We have to do this first since pixelShouldGetHalo will check for | |
276 // 0 to see if a pixel has been modified to transparent, and black text that | |
277 // Windows draw will look transparent to it! | |
278 text_canvas.getTopPlatformDevice().makeOpaque(0, 0, w + 2, h + 2); | |
279 | |
280 uint32_t halo_premul = SkPreMultiplyColor(halo_color); | |
281 SkBitmap& text_bitmap = const_cast<SkBitmap&>( | |
282 text_canvas.getTopPlatformDevice().accessBitmap(true)); | |
283 for (int cur_y = 0; cur_y < h + 2; cur_y++) { | |
284 uint32_t* text_row = text_bitmap.getAddr32(0, cur_y); | |
285 for (int cur_x = 0; cur_x < w + 2; cur_x++) { | |
286 if (text_row[cur_x] == halo_premul) { | |
287 // This pixel was not touched by the text routines. See if it borders | |
288 // a touched pixel in any of the 4 directions (not diagonally). | |
289 if (!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul)) | |
290 text_row[cur_x] = 0; // Make transparent. | |
291 } else { | |
292 text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque. | |
293 } | |
294 } | |
295 } | |
296 | |
297 // Draw the halo bitmap with blur. | |
298 DrawBitmapInt(text_bitmap, x - 1, y - 1); | |
299 } | |
300 | |
301 } // namespace gfx | |
OLD | NEW |