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 <algorithm> | |
8 | |
9 #include <cairo/cairo.h> | |
10 #include <pango/pango.h> | |
11 #include <pango/pangocairo.h> | |
12 | |
13 #include "base/i18n/rtl.h" | |
14 #include "base/logging.h" | |
15 #include "ui/gfx/font.h" | |
16 #include "ui/gfx/pango_util.h" | |
17 #include "ui/gfx/platform_font_pango.h" | |
18 #include "ui/gfx/rect.h" | |
19 #include "ui/gfx/skia_util.h" | |
20 | |
21 namespace { | |
22 | |
23 // Multiply by the text height to determine how much text should be faded | |
24 // when elliding. | |
25 const double kFadeWidthFactor = 1.5; | |
26 | |
27 // End state of the elliding fade. | |
28 const double kFadeFinalAlpha = 0.15; | |
29 | |
30 // Width of the border drawn around haloed text. | |
31 const double kTextHaloWidth = 1.0; | |
32 | |
33 // A class to encapsulate string drawing params and operations. | |
34 class DrawStringContext { | |
35 public: | |
36 DrawStringContext(gfx::Canvas* canvas, | |
37 const string16& text, | |
38 const gfx::Font& font, | |
39 const gfx::Rect& bounds, | |
40 const gfx::Rect& clip, | |
41 int flags); | |
42 ~DrawStringContext(); | |
43 | |
44 void Draw(SkColor text_color); | |
45 void DrawWithHalo(SkColor text_color, SkColor halo_color); | |
46 | |
47 private: | |
48 // Draw an underline under the text using |cr|, which must already be | |
49 // initialized with the correct source. |extra_edge_width| is added to the | |
50 // outer edge of the line. Helper method for Draw() and DrawWithHalo(). | |
51 void DrawUnderline(cairo_t* cr, double extra_edge_width); | |
52 | |
53 const gfx::Rect& bounds_; | |
54 int flags_; | |
55 const gfx::Font& font_; | |
56 | |
57 gfx::Canvas* canvas_; | |
58 cairo_t* cr_; | |
59 PangoLayout* layout_; | |
60 | |
61 gfx::Rect text_rect_; | |
62 | |
63 base::i18n::TextDirection text_direction_; | |
64 | |
65 DISALLOW_COPY_AND_ASSIGN(DrawStringContext); | |
66 }; | |
67 | |
68 DrawStringContext::DrawStringContext(gfx::Canvas* canvas, | |
69 const string16& text, | |
70 const gfx::Font& font, | |
71 const gfx::Rect& bounds, | |
72 const gfx::Rect& clip, | |
73 int flags) | |
74 : bounds_(bounds), | |
75 flags_(flags), | |
76 font_(font), | |
77 canvas_(canvas), | |
78 cr_(NULL), | |
79 layout_(NULL), | |
80 text_rect_(bounds.x(), bounds.y(), 0, 0), | |
81 text_direction_(base::i18n::GetFirstStrongCharacterDirection(text)) { | |
82 DCHECK(!bounds_.IsEmpty()); | |
83 | |
84 cr_ = skia::BeginPlatformPaint(canvas_->sk_canvas()); | |
85 layout_ = pango_cairo_create_layout(cr_); | |
86 | |
87 gfx::SetupPangoLayout( | |
88 layout_, text, font, bounds_.width(), text_direction_, flags_); | |
89 | |
90 pango_layout_set_height(layout_, bounds_.height() * PANGO_SCALE); | |
91 | |
92 cairo_save(cr_); | |
93 | |
94 cairo_rectangle(cr_, clip.x(), clip.y(), clip.width(), clip.height()); | |
95 cairo_clip(cr_); | |
96 | |
97 AdjustTextRectBasedOnLayout(layout_, bounds_, flags_, &text_rect_); | |
98 } | |
99 | |
100 DrawStringContext::~DrawStringContext() { | |
101 cairo_restore(cr_); | |
102 skia::EndPlatformPaint(canvas_->sk_canvas()); | |
103 g_object_unref(layout_); | |
104 // NOTE: BeginPlatformPaint returned its surface, we shouldn't destroy it. | |
105 } | |
106 | |
107 void DrawStringContext::Draw(SkColor text_color) { | |
108 DrawPangoLayout(cr_, layout_, font_, bounds_, text_rect_, text_color, | |
109 text_direction_, flags_); | |
110 } | |
111 | |
112 void DrawStringContext::DrawWithHalo(SkColor text_color, | |
113 SkColor halo_color) { | |
114 gfx::Size size(bounds_.width() + 2, bounds_.height() + 2); | |
115 gfx::Canvas text_canvas(size, scale_factor(), false); | |
116 text_canvas.FillRect(gfx::Rect(size), static_cast<SkColor>(0)); | |
117 | |
118 { | |
119 skia::ScopedPlatformPaint scoped_platform_paint(text_canvas.sk_canvas()); | |
120 cairo_t* text_cr = scoped_platform_paint.GetPlatformSurface(); | |
121 | |
122 // TODO: The current approach (stroking the text path to generate the halo | |
123 // and then filling it for the main text) won't work if |text_color| is | |
124 // non-opaque. If we need to do this at some later point, | |
125 // http://lists.freedesktop.org/archives/cairo/2004-September/001829.html | |
126 // suggests "do[ing] the stroke and fill with opaque paint onto an | |
127 // intermediate surface, and then us[ing] cairo_show_surface to composite | |
128 // that intermediate result with the desired compositing operator." | |
129 cairo_set_source_rgba(text_cr, | |
130 SkColorGetR(halo_color) / 255.0, | |
131 SkColorGetG(halo_color) / 255.0, | |
132 SkColorGetB(halo_color) / 255.0, | |
133 SkColorGetA(halo_color) / 255.0); | |
134 | |
135 // Draw the halo underline first so that we can use the same path for the | |
136 // outer/halo text and inner text. | |
137 if (font_.GetStyle() & gfx::Font::UNDERLINED) | |
138 DrawUnderline(text_cr, kTextHaloWidth); | |
139 | |
140 cairo_move_to(text_cr, 2, 1); | |
141 pango_cairo_layout_path(text_cr, layout_); | |
142 cairo_set_line_width(text_cr, 2 * kTextHaloWidth); | |
143 cairo_set_line_join(text_cr, CAIRO_LINE_JOIN_ROUND); | |
144 cairo_stroke_preserve(text_cr); | |
145 | |
146 cairo_set_operator(text_cr, CAIRO_OPERATOR_SOURCE); | |
147 cairo_set_source_rgba(text_cr, | |
148 SkColorGetR(text_color) / 255.0, | |
149 SkColorGetG(text_color) / 255.0, | |
150 SkColorGetB(text_color) / 255.0, | |
151 SkColorGetA(text_color) / 255.0); | |
152 cairo_fill(text_cr); | |
153 | |
154 if (font_.GetStyle() & gfx::Font::UNDERLINED) | |
155 DrawUnderline(text_cr, 0.0); | |
156 } | |
157 | |
158 const SkBitmap& text_bitmap = const_cast<SkBitmap&>( | |
159 skia::GetTopDevice(*text_canvas.sk_canvas())->accessBitmap(false)); | |
160 const gfx::ImageSkia text_image = gfx::ImageSkia(gfx::ImageSkiaRep( | |
161 text_bitmap, text_canvas.scale_factor())); | |
162 canvas_->DrawImageInt(text_image, text_rect_.x() - 1, text_rect_.y() - 1); | |
163 } | |
164 | |
165 void DrawStringContext::DrawUnderline(cairo_t* cr, double extra_edge_width) { | |
166 gfx::PlatformFontPango* platform_font = | |
167 static_cast<gfx::PlatformFontPango*>(font_.platform_font()); | |
168 gfx::DrawPangoTextUnderline(cr, | |
169 platform_font, | |
170 extra_edge_width, | |
171 text_rect_); | |
172 } | |
173 | |
174 } // namespace | |
175 | |
176 namespace gfx { | |
177 | |
178 // static | |
179 void Canvas::SizeStringInt(const string16& text, | |
180 const gfx::Font& font, | |
181 int* width, int* height, | |
182 int flags) { | |
183 int org_width = *width; | |
184 cairo_surface_t* surface = | |
185 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); | |
186 cairo_t* cr = cairo_create(surface); | |
187 PangoLayout* layout = pango_cairo_create_layout(cr); | |
188 | |
189 SetupPangoLayout( | |
190 layout, | |
191 text, | |
192 font, | |
193 *width, | |
194 base::i18n::GetFirstStrongCharacterDirection(text), | |
195 flags); | |
196 | |
197 pango_layout_get_pixel_size(layout, width, height); | |
198 | |
199 if (font.GetStyle() & gfx::Font::UNDERLINED) { | |
200 gfx::PlatformFontPango* platform_font = | |
201 static_cast<gfx::PlatformFontPango*>(font.platform_font()); | |
202 *height += std::max(platform_font->underline_position() + | |
203 platform_font->underline_thickness(), 0.0); | |
204 } | |
205 | |
206 // TODO: If the text is being drawn with a halo, we should also pad each of | |
207 // the edges by |kTextHaloWidth|... except haloing is currently a drawing-time | |
208 // thing, and we don't know how the text will be drawn here. :-( This only | |
209 // seems to come into play at present if the text is both haloed and | |
210 // underlined; otherwise, the size returned by Pango is (at least sometimes) | |
211 // large enough to include the halo. | |
212 | |
213 if (org_width > 0 && flags & Canvas::MULTI_LINE && | |
214 pango_layout_is_wrapped(layout)) { | |
215 // The text wrapped. There seems to be a bug in Pango when this happens | |
216 // such that the width returned from pango_layout_get_pixel_size is too | |
217 // small. Using the width from pango_layout_get_pixel_size in this case | |
218 // results in wrapping across more lines, which requires a bigger height. | |
219 // As a workaround we use the original width, which is not necessarily | |
220 // exactly correct, but isn't wrong by much. | |
221 // | |
222 // It looks like Pango uses the size of whitespace in calculating wrapping | |
223 // but doesn't include the size of the whitespace when the extents are | |
224 // asked for. See the loop in pango-layout.c process_item that determines | |
225 // where to wrap. | |
226 *width = org_width; | |
227 } | |
228 | |
229 g_object_unref(layout); | |
230 cairo_destroy(cr); | |
231 cairo_surface_destroy(surface); | |
232 } | |
233 | |
234 void Canvas::DrawStringWithHalo(const string16& text, | |
235 const gfx::Font& font, | |
236 SkColor text_color, | |
237 SkColor halo_color, | |
238 int x, int y, int w, int h, | |
239 int flags) { | |
240 if (!IntersectsClipRectInt(x, y, w, h)) | |
241 return; | |
242 | |
243 gfx::Rect bounds(x, y, w, h); | |
244 gfx::Rect clip(x - 1, y - 1, w + 2, h + 2); // Bigger clip for halo | |
245 DrawStringContext context(this, text, font, bounds, clip,flags); | |
246 context.DrawWithHalo(text_color, halo_color); | |
247 } | |
248 | |
249 void Canvas::DrawStringWithShadows(const string16& text, | |
250 const gfx::Font& font, | |
251 SkColor color, | |
252 const gfx::Rect& text_bounds, | |
253 int flags, | |
254 const ShadowValues& shadows) { | |
255 DLOG_IF(WARNING, !shadows.empty()) << "Text shadow not implemented."; | |
256 | |
257 if (!IntersectsClipRect(text_bounds)) | |
258 return; | |
259 | |
260 DrawStringContext context(this, text, font, text_bounds, text_bounds, flags); | |
261 context.Draw(color); | |
262 } | |
263 | |
264 } // namespace gfx | |
OLD | NEW |