summaryrefslogtreecommitdiffstats
path: root/ui/gfx/canvas_skia_win.cc
blob: 67491faca6402bcca429b61bed3898c0d0017722 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/canvas_skia.h"

#include <limits>

#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/win/scoped_gdi_object.h"
#include "skia/ext/bitmap_platform_device.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkShader.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/font.h"
#include "ui/gfx/rect.h"

namespace {

static inline int Round(double x) {
  // Why oh why is this not in a standard header?
  return static_cast<int>(floor(x + 0.5));
}

// We make sure that LTR text we draw in an RTL context is modified
// appropriately to make sure it maintains it LTR orientation.
void DoDrawText(HDC hdc,
                const string16& text,
                RECT* text_bounds,
                int flags) {
  // Only adjust string directionality if both of the following are true:
  // 1. The current locale is RTL.
  // 2. The string itself has RTL directionality.
  const wchar_t* string_ptr = text.c_str();
  int string_size = static_cast<int>(text.length());

  string16 localized_text;
  if (flags & DT_RTLREADING) {
    localized_text = text;
    base::i18n::AdjustStringForLocaleDirection(&localized_text);
    string_ptr = localized_text.c_str();
    string_size = static_cast<int>(localized_text.length());
  }

  DrawText(hdc, string_ptr, string_size, text_bounds, flags);
}

// Compute the windows flags necessary to implement the provided text Canvas
// flags.
int ComputeFormatFlags(int flags, const string16& text) {
  // Setting the text alignment explicitly in case it hasn't already been set.
  // This will make sure that we don't align text to the left on RTL locales
  // just because no alignment flag was passed to DrawStringInt().
  if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER |
                 gfx::Canvas::TEXT_ALIGN_RIGHT |
                 gfx::Canvas::TEXT_ALIGN_LEFT))) {
    flags |= gfx::CanvasSkia::DefaultCanvasTextAlignment();
  }

  // horizontal alignment
  int f = 0;
  if (flags & gfx::Canvas::TEXT_ALIGN_CENTER)
    f |= DT_CENTER;
  else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT)
    f |= DT_RIGHT;
  else
    f |= DT_LEFT;

  // vertical alignment
  if (flags & gfx::Canvas::TEXT_VALIGN_TOP)
    f |= DT_TOP;
  else if (flags & gfx::Canvas::TEXT_VALIGN_BOTTOM)
    f |= DT_BOTTOM;
  else
    f |= DT_VCENTER;

  if (flags & gfx::Canvas::MULTI_LINE) {
    f |= DT_WORDBREAK;
    if (flags & gfx::Canvas::CHARACTER_BREAK)
      f |= DT_EDITCONTROL;  // Turns on character breaking (not documented)
    else if (!(flags & gfx::Canvas::NO_ELLIPSIS))
      f |= DT_WORD_ELLIPSIS;
  } else {
    f |= DT_SINGLELINE;
  }

  if (flags & gfx::Canvas::HIDE_PREFIX)
    f |= DT_HIDEPREFIX;
  else if ((flags & gfx::Canvas::SHOW_PREFIX) == 0)
    f |= DT_NOPREFIX;

  if (!(flags & gfx::Canvas::NO_ELLIPSIS))
    f |= DT_END_ELLIPSIS;

  // In order to make sure RTL/BiDi strings are rendered correctly, we must
  // pass the flag DT_RTLREADING to DrawText (when the locale's language is
  // a right-to-left language) so that Windows does the right thing.
  //
  // In addition to correctly displaying text containing both RTL and LTR
  // elements (for example, a string containing a telephone number within a
  // sentence in Hebrew, or a sentence in Hebrew that contains a word in
  // English) this flag also makes sure that if there is not enough space to
  // display the entire string, the ellipsis is displayed on the left hand side
  // of the truncated string and not on the right hand side.
  //
  // We make a distinction between Chrome UI strings and text coming from a web
  // page.
  //
  // For text coming from a web page we determine the alignment based on the
  // first character with strong directionality. If the directionality of the
  // first character with strong directionality in the text is LTR, the
  // alignment is set to DT_LEFT, and the directionality should not be set as
  // DT_RTLREADING.
  //
  // This heuristic doesn't work for Chrome UI strings since even in RTL
  // locales, some of those might start with English text but we know they're
  // localized so we always want them to be right aligned, and their
  // directionality should be set as DT_RTLREADING.
  //
  // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when
  // the flag is set, LRE-PDF don't have the desired effect of rendering
  // multiline English-only text as LTR.
  //
  // Note that if the caller is explicitly requesting displaying the text
  // using RTL directionality then we respect that and pass DT_RTLREADING to
  // ::DrawText even if the locale is LTR.
  if ((flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY) ||
      (base::i18n::IsRTL() &&
       (f & DT_RIGHT) && base::i18n::StringContainsStrongRTLChars(text))) {
    f |= DT_RTLREADING;
  }

  return f;
}

// Changes the alpha of the given bitmap.
// If |fade_to_right| is true then the rect fades from opaque to clear,
// otherwise the rect fades from clear to opaque.
void FadeBitmapRect(SkDevice& bmp_device,
                    const gfx::Rect& rect,
                    bool fade_to_right) {
  SkBitmap bmp = bmp_device.accessBitmap(true);
  DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config());
  SkAutoLockPixels lock(bmp);
  float total_width = static_cast<float>(rect.width());

  for (int x = rect.x(); x < rect.right(); x++) {
    float cur_width = static_cast<float>(fade_to_right ?
        rect.right() - x :  x - rect.x());
    // We want the fade effect to go from 0.2 to 1.0.
    float alpha_percent = ((cur_width / total_width) * 0.8f) + 0.2f;

    for (int y = rect.y(); y < rect.bottom(); y++) {
      SkColor color = bmp.getColor(x, y);
      SkAlpha alpha = static_cast<SkAlpha>(SkColorGetA(color) * alpha_percent);
      *bmp.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetA(color, alpha));
    }
  }
}

// DrawText() doesn't support alpha channels. To create a transparent background
// this function draws black on white. It then uses the intensity of black
// to determine how much alpha to use. The text is drawn in |gfx_text_rect| and
// clipped to |gfx_draw_rect|.
void DrawTextAndClearBackground(SkDevice& bmp_device,
                                HFONT font,
                                COLORREF text_color,
                                const string16& text,
                                int flags,
                                const gfx::Rect& gfx_text_rect,
                                const gfx::Rect& gfx_draw_rect) {
  HDC hdc = skia::BeginPlatformPaint(&bmp_device);

  // Clear the background by filling with white.
  HBRUSH fill_brush = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
  HANDLE old_brush = SelectObject(hdc, fill_brush);
  RECT draw_rect = gfx_draw_rect.ToRECT();
  FillRect(hdc, &draw_rect, fill_brush);
  SelectObject(hdc, old_brush);

  // Set black text with trasparent background.
  SetBkMode(hdc, TRANSPARENT);
  SetTextColor(hdc, 0);

  // Draw the text.
  int save_dc_id = SaveDC(hdc);
  // Clip the text to the draw destination.
  IntersectClipRect(hdc, draw_rect.left, draw_rect.top,
                    draw_rect.right, draw_rect.bottom);
  SelectObject(hdc, font);
  RECT text_rect = gfx_text_rect.ToRECT();
  DoDrawText(hdc, text, &text_rect,
             ComputeFormatFlags(flags, text));
  RestoreDC(hdc, save_dc_id);

  BYTE text_color_r = GetRValue(text_color);
  BYTE text_color_g = GetGValue(text_color);
  BYTE text_color_b = GetBValue(text_color);

  SkBitmap bmp = bmp_device.accessBitmap(true);
  DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config());
  SkAutoLockPixels lock(bmp);

  // At this point the bitmap has black text on white.
  // The intensity of black tells us the alpha value of the text.
  for (int y = draw_rect.top; y < draw_rect.bottom; y++) {
    for (int x = draw_rect.left; x < draw_rect.right; x++) {
      // Gets the color directly. DrawText doesn't premultiply alpha so
      // using SkBitmap::getColor() won't work here.
      SkColor color = *bmp.getAddr32(x, y);
      // Calculate the alpha using the luminance. Since this is black text
      // on a white background the luminosity must be inverted.
      BYTE alpha = 0xFF - color_utils::GetLuminanceForColor(color);
      *bmp.getAddr32(x, y) = SkPreMultiplyColor(
          SkColorSetARGB(alpha, text_color_r, text_color_g, text_color_b));
    }
  }

  skia::EndPlatformPaint(&bmp_device);
}

// Draws the given text with a fade out gradient. |bmp_device| is a bitmap
// that is used to temporary drawing. The text is drawn in |text_rect| and
// clipped to |draw_rect|.
void DrawTextGradientPart(HDC hdc,
                          SkDevice& bmp_device,
                          const string16& text,
                          const SkColor& color,
                          HFONT font,
                          const gfx::Rect& text_rect,
                          const gfx::Rect& draw_rect,
                          bool fade_to_right,
                          int flags) {
  DrawTextAndClearBackground(bmp_device, font, skia::SkColorToCOLORREF(color),
                             text, flags, text_rect, draw_rect);
  FadeBitmapRect(bmp_device, draw_rect, fade_to_right);
  BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};

  HDC bmp_hdc = skia::BeginPlatformPaint(&bmp_device);
  AlphaBlend(hdc, draw_rect.x(), draw_rect.y(), draw_rect.width(),
             draw_rect.height(), bmp_hdc, draw_rect.x(), draw_rect.y(),
             draw_rect.width(), draw_rect.height(), blend);
  skia::EndPlatformPaint(&bmp_device);
}

enum PrimarySide {
  PrimaryOnLeft,
  PrimaryOnRight,
};

// Divides |rect| horizontally into a |primary| of width |primary_width| and a
// |secondary| taking up the remainder.
void DivideRect(const gfx::Rect& rect,
                PrimarySide primary_side,
                int primary_width,
                gfx::Rect* primary,
                gfx::Rect* secondary) {
  *primary = rect;
  *secondary = rect;
  int remainder = rect.width() - primary_width;

  switch (primary_side) {
    case PrimaryOnLeft:
      primary->Inset(0, 0, remainder, 0);
      secondary->Inset(primary_width, 0, 0, 0);
      break;
    case PrimaryOnRight:
      primary->Inset(remainder, 0, 0, 0);
      secondary->Inset(0, 0, primary_width, 0);
      break;
  }
}

}  // anonymous namespace

namespace gfx {

CanvasSkia::CanvasSkia(int width, int height, bool is_opaque)
    : skia::PlatformCanvas(width, height, is_opaque) {
}

CanvasSkia::CanvasSkia() : skia::PlatformCanvas() {
}

CanvasSkia::~CanvasSkia() {
}

// static
void CanvasSkia::SizeStringInt(const string16& text,
                               const gfx::Font& font,
                               int* width, int* height,
                               int flags) {
  // Clamp the max amount of text we'll measure to 2K.  When the string is
  // actually drawn, it will be clipped to whatever size box is provided, and
  // the time to do that doesn't depend on the length being clipped off.
  const int kMaxStringLength = 2048 - 1;  // So the trailing \0 fits in 2K.
  string16 clamped_string(text.substr(0, kMaxStringLength));

  if (*width == 0) {
    // If multi-line + character break are on, the computed width will be one
    // character wide (useless).  Furthermore, if in this case the provided text
    // contains very long "words" (substrings without a word-breaking point),
    // DrawText() can run extremely slowly (e.g. several seconds).  So in this
    // case, we turn character breaking off to get a more accurate "desired"
    // width and avoid the slowdown.
    if (flags & (gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK))
      flags &= ~gfx::Canvas::CHARACTER_BREAK;

    // Weird undocumented behavior: if the width is 0, DoDrawText() won't
    // calculate a size at all.  So set it to 1, which it will then change.
    if (!text.empty())
      *width = 1;
  }
  RECT r = { 0, 0, *width, *height };

  HDC dc = GetDC(NULL);
  HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.GetNativeFont()));
  DoDrawText(dc, clamped_string, &r,
             ComputeFormatFlags(flags, clamped_string) | DT_CALCRECT);
  SelectObject(dc, old_font);
  ReleaseDC(NULL, dc);

  *width = r.right;
  *height = r.bottom;
}

void CanvasSkia::DrawStringInt(const string16& text,
                               HFONT font,
                               const SkColor& color,
                               int x, int y, int w, int h,
                               int flags) {
  SkRect fclip;
  if (!getClipBounds(&fclip))
    return;
  RECT text_bounds = { x, y, x + w, y + h };
  SkIRect clip;
  fclip.round(&clip);
  if (!clip.intersect(skia::RECTToSkIRect(text_bounds)))
    return;

  // Clamp the max amount of text we'll draw to 32K.  There seem to be bugs in
  // DrawText() if you e.g. ask it to character-break a no-whitespace string of
  // length > 43680 (for which it draws nothing), and since we clamped to 2K in
  // SizeStringInt() we're unlikely to be able to display this much anyway.
  const int kMaxStringLength = 32768 - 1;  // So the trailing \0 fits in 32K.
  string16 clamped_string(text.substr(0, kMaxStringLength));

  HDC dc;
  HFONT old_font;
  {
    skia::ScopedPlatformPaint scoped_platform_paint(this);
    dc = scoped_platform_paint.GetPlatformSurface();
    SetBkMode(dc, TRANSPARENT);
    old_font = (HFONT)SelectObject(dc, font);
    COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color),
                               SkColorGetB(color));
    SetTextColor(dc, brush_color);

    int f = ComputeFormatFlags(flags, clamped_string);
    DoDrawText(dc, clamped_string, &text_bounds, f);
  }

  // Restore the old font. This way we don't have to worry if the caller
  // deletes the font and the DC lives longer.
  SelectObject(dc, old_font);

  // Windows will have cleared the alpha channel of the text we drew. Assume
  // we're drawing to an opaque surface, or at least the text rect area is
  // opaque.
  skia::MakeOpaque(this, clip.fLeft, clip.fTop, clip.width(),
                   clip.height());
}

void CanvasSkia::DrawStringInt(const string16& text,
                               const gfx::Font& font,
                               const SkColor& color,
                               int x, int y, int w, int h,
                               int flags) {
  DrawStringInt(text, font.GetNativeFont(), color, x, y, w, h, flags);
}

// Checks each pixel immediately adjacent to the given pixel in the bitmap. If
// any of them are not the halo color, returns true. This defines the halo of
// pixels that will appear around the text. Note that we have to check each
// pixel against both the halo color and transparent since DrawStringWithHalo
// will modify the bitmap as it goes, and clears pixels shouldn't count as
// changed.
static bool pixelShouldGetHalo(const SkBitmap& bitmap,
                               int x, int y,
                               SkColor halo_color) {
  if (x > 0 &&
      *bitmap.getAddr32(x - 1, y) != halo_color &&
      *bitmap.getAddr32(x - 1, y) != 0)
    return true;  // Touched pixel to the left.
  if (x < bitmap.width() - 1 &&
      *bitmap.getAddr32(x + 1, y) != halo_color &&
      *bitmap.getAddr32(x + 1, y) != 0)
    return true;  // Touched pixel to the right.
  if (y > 0 &&
      *bitmap.getAddr32(x, y - 1) != halo_color &&
      *bitmap.getAddr32(x, y - 1) != 0)
    return true;  // Touched pixel above.
  if (y < bitmap.height() - 1 &&
      *bitmap.getAddr32(x, y + 1) != halo_color &&
      *bitmap.getAddr32(x, y + 1) != 0)
    return true;  // Touched pixel below.
  return false;
}

void CanvasSkia::DrawStringWithHalo(const string16& text,
                                    const gfx::Font& font,
                                    const SkColor& text_color,
                                    const SkColor& halo_color_in,
                                    int x, int y, int w, int h,
                                    int flags) {
  // Some callers will have semitransparent halo colors, which we don't handle
  // (since the resulting image can have 1-bit transparency only).
  SkColor halo_color = halo_color_in | 0xFF000000;

  // Create a temporary buffer filled with the halo color. It must leave room
  // for the 1-pixel border around the text.
  CanvasSkia text_canvas(w + 2, h + 2, true);
  SkPaint bkgnd_paint;
  bkgnd_paint.setColor(halo_color);
  text_canvas.DrawRectInt(0, 0, w + 2, h + 2, bkgnd_paint);

  // Draw the text into the temporary buffer. This will have correct
  // ClearType since the background color is the same as the halo color.
  text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags);

  // Windows will have cleared the alpha channel for the pixels it drew. Make it
  // opaque. We have to do this first since pixelShouldGetHalo will check for
  // 0 to see if a pixel has been modified to transparent, and black text that
  // Windows draw will look transparent to it!
  skia::MakeOpaque(&text_canvas, 0, 0, w + 2, h + 2);

  uint32_t halo_premul = SkPreMultiplyColor(halo_color);
  SkBitmap& text_bitmap = const_cast<SkBitmap&>(
      skia::GetTopDevice(text_canvas)->accessBitmap(true));
  for (int cur_y = 0; cur_y < h + 2; cur_y++) {
    uint32_t* text_row = text_bitmap.getAddr32(0, cur_y);
    for (int cur_x = 0; cur_x < w + 2; cur_x++) {
      if (text_row[cur_x] == halo_premul) {
        // This pixel was not touched by the text routines. See if it borders
        // a touched pixel in any of the 4 directions (not diagonally).
        if (!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul))
          text_row[cur_x] = 0;  // Make transparent.
      } else {
        text_row[cur_x] |= 0xff << SK_A32_SHIFT;  // Make opaque.
      }
    }
  }

  // Draw the halo bitmap with blur.
  DrawBitmapInt(text_bitmap, x - 1, y - 1);
}

ui::TextureID CanvasSkia::GetTextureID() {
  // TODO(wjmaclean)
  return 0;
}

void CanvasSkia::DrawFadeTruncatingString(
      const string16& text,
      CanvasSkia::TruncateFadeMode truncate_mode,
      size_t desired_characters_to_truncate_from_head,
      const gfx::Font& font,
      const SkColor& color,
      const gfx::Rect& display_rect) {
  int flags = NO_ELLIPSIS;

  // If the whole string fits in the destination then just draw it directly.
  int total_string_width;
  int total_string_height;
  SizeStringInt(text, font, &total_string_width, &total_string_height,
                flags | TEXT_VALIGN_TOP);

  if (total_string_width <= display_rect.width()) {
    DrawStringInt(text, font, color, display_rect.x(), display_rect.y(),
                  display_rect.width(), display_rect.height(), 0);
    return;
  }

  int average_character_width = font.GetAverageCharacterWidth();
  int clipped_string_width = total_string_width - display_rect.width();

  // Clip the string by drawing it to the left by |offset_x|.
  int offset_x = 0;
  switch (truncate_mode) {
    case TruncateFadeHead:
      offset_x = clipped_string_width;
      break;
    case TruncateFadeHeadAndTail:
      DCHECK_GT(desired_characters_to_truncate_from_head, 0u);
      // Get the width of the beginning of the string we're clipping.
      string16 clipped_head_string =
          text.substr(0, desired_characters_to_truncate_from_head);
      int clipped_width;
      int clipped_height;
      SizeStringInt(clipped_head_string, font,
                    &clipped_width, &clipped_height, flags);

      // This is the offset at which we start drawing. This causes the
      // beginning of the string to get clipped.
      offset_x = clipped_width;

      // Due to the fade effect the first character is hard to see.
      // We want to make sure that the first character starting at
      // |desired_characters_to_truncate_from_head| is readable so we reduce
      // the offset by a little bit.
      offset_x = std::max(0, Round(offset_x - average_character_width * 2));

      // If the offset is so large that there's empty space at the tail
      // then reduce the offset so we can use up the empty space.
      offset_x = std::min(offset_x, clipped_string_width);
      break;
  }
  bool is_truncating_head = offset_x > 0;
  bool is_truncating_tail = clipped_string_width > offset_x;

  bool is_rtl = (ComputeFormatFlags(flags, text) & DT_RTLREADING) != 0;
  // |is_rtl| tells us if the given text is right to left or not. |is_rtl| can
  // be false even if the UI is set to a right to left language.
  // Now, normally, we right align all text if the UI is set to a right to
  // left language. In this case though we don't want that because we render
  // the ends of the string ourselves.
  if (!is_rtl)
    flags |= TEXT_ALIGN_LEFT;

  // Fade in/out about 2.5 characters of the beginning/end of the string.
  // The .5 here is helpful if one of the characters is a space.
  // Use a quarter of the display width if the string is very short.
  int gradient_width = Round(std::min(average_character_width * 2.5,
                                      display_rect.width() / 4.0));

  // Move the origin to |display_rect.origin()|. This simplifies all the
  // drawing so that both the source and destination can be (0,0).
  save(kMatrix_SaveFlag);
  TranslateInt(display_rect.x(), display_rect.y());

  gfx::Rect solid_part(gfx::Point(), display_rect.size());
  gfx::Rect head_part;
  gfx::Rect tail_part;
  if (is_truncating_head)
    DivideRect(solid_part, is_rtl ? PrimaryOnRight : PrimaryOnLeft,
               gradient_width, &head_part, &solid_part);
  if (is_truncating_tail)
    DivideRect(solid_part, is_rtl ? PrimaryOnLeft : PrimaryOnRight,
               gradient_width, &tail_part, &solid_part);

  // Grow |display_rect| by |offset_x|.
  gfx::Rect text_rect(gfx::Point(), display_rect.size());
  if (!is_rtl)
    text_rect.set_x(text_rect.x() - offset_x);
  text_rect.set_width(text_rect.width() + offset_x);

  // Create a temporary bitmap to draw the gradient to.
  scoped_ptr<SkDevice> gradient_bitmap(
      skia::BitmapPlatformDevice::create(
          display_rect.width(), display_rect.height(), false, NULL));
  DCHECK(gradient_bitmap.get());

  {
    skia::ScopedPlatformPaint scoped_platform_paint(this);
    HDC hdc = scoped_platform_paint.GetPlatformSurface();
    if (is_truncating_head)
      DrawTextGradientPart(hdc, *gradient_bitmap, text, color,
                           font.GetNativeFont(), text_rect, head_part, is_rtl,
                           flags);
    if (is_truncating_tail)
      DrawTextGradientPart(hdc, *gradient_bitmap, text, color,
                           font.GetNativeFont(), text_rect, tail_part, !is_rtl,
                           flags);
  }

  // Draw the solid part.
  save(kClip_SaveFlag);
  ClipRectInt(solid_part.x(), solid_part.y(),
              solid_part.width(), solid_part.height());
  DrawStringInt(text, font, color,
                text_rect.x(), text_rect.y(),
                text_rect.width(), text_rect.height(),
                flags);
  restore();
  restore();
}

}  // namespace gfx