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 "content/child/browser_font_resource_trusted.h"
6
7#include "base/strings/string_util.h"
8#include "base/strings/utf_string_conversions.h"
9#include "ppapi/c/dev/ppb_font_dev.h"
10#include "ppapi/proxy/connection.h"
11#include "ppapi/shared_impl/ppapi_preferences.h"
12#include "ppapi/shared_impl/var.h"
13#include "ppapi/thunk/enter.h"
14#include "ppapi/thunk/ppb_image_data_api.h"
15#include "ppapi/thunk/thunk.h"
16#include "skia/ext/platform_canvas.h"
17#include "third_party/WebKit/public/platform/WebCanvas.h"
18#include "third_party/WebKit/public/platform/WebFloatPoint.h"
19#include "third_party/WebKit/public/platform/WebFloatRect.h"
20#include "third_party/WebKit/public/platform/WebRect.h"
21#include "third_party/WebKit/public/web/WebFont.h"
22#include "third_party/WebKit/public/web/WebFontDescription.h"
23#include "third_party/WebKit/public/web/WebTextRun.h"
24#include "third_party/icu/source/common/unicode/ubidi.h"
25#include "third_party/skia/include/core/SkRect.h"
26
27using ppapi::StringVar;
28using ppapi::thunk::EnterResourceNoLock;
29using ppapi::thunk::PPB_ImageData_API;
30using blink::WebFloatPoint;
31using blink::WebFloatRect;
32using blink::WebFont;
33using blink::WebFontDescription;
34using blink::WebRect;
35using blink::WebTextRun;
36using blink::WebCanvas;
37
38namespace content {
39
40namespace {
41
42// Same as WebPreferences::kCommonScript. I'd use that directly here, but get an
43// undefined reference linker error.
44const char kCommonScript[] = "Zyyy";
45
46base::string16 GetFontFromMap(
47    const webkit_glue::ScriptFontFamilyMap& map,
48    const std::string& script) {
49  webkit_glue::ScriptFontFamilyMap::const_iterator it =
50      map.find(script);
51  if (it != map.end())
52    return it->second;
53  return base::string16();
54}
55
56// Splits a PP_BrowserFont_Trusted_TextRun into a sequence or LTR and RTL
57// WebTextRuns that can be used for WebKit. Normally WebKit does this for us,
58// but the font drawing and measurement routines we call happen after this
59// step. So for correct rendering of RTL content, we need to do it ourselves.
60class TextRunCollection {
61 public:
62  explicit TextRunCollection(const PP_BrowserFont_Trusted_TextRun& run)
63      : bidi_(NULL),
64        num_runs_(0) {
65    StringVar* text_string = StringVar::FromPPVar(run.text);
66    if (!text_string)
67      return;  // Leave num_runs_ = 0 so we'll do nothing.
68    text_ = base::UTF8ToUTF16(text_string->value());
69
70    if (run.override_direction) {
71      // Skip autodetection.
72      num_runs_ = 1;
73      override_run_ = WebTextRun(text_, PP_ToBool(run.rtl), true);
74    } else {
75      bidi_ = ubidi_open();
76      UErrorCode uerror = U_ZERO_ERROR;
77      ubidi_setPara(bidi_, text_.data(), text_.size(), run.rtl, NULL, &uerror);
78      if (U_SUCCESS(uerror))
79        num_runs_ = ubidi_countRuns(bidi_, &uerror);
80    }
81  }
82
83  ~TextRunCollection() {
84    if (bidi_)
85      ubidi_close(bidi_);
86  }
87
88  const base::string16& text() const { return text_; }
89  int num_runs() const { return num_runs_; }
90
91  // Returns a WebTextRun with the info for the run at the given index.
92  // The range covered by the run is in the two output params.
93  WebTextRun GetRunAt(int index, int32_t* run_start, int32_t* run_len) const {
94    DCHECK(index < num_runs_);
95    if (bidi_) {
96      bool run_rtl = !!ubidi_getVisualRun(bidi_, index, run_start, run_len);
97      return WebTextRun(base::string16(&text_[*run_start], *run_len),
98                        run_rtl, true);
99    }
100
101    // Override run, return the single one.
102    DCHECK(index == 0);
103    *run_start = 0;
104    *run_len = static_cast<int32_t>(text_.size());
105    return override_run_;
106  }
107
108 private:
109  // Will be null if we skipped autodetection.
110  UBiDi* bidi_;
111
112  // Text of all the runs.
113  base::string16 text_;
114
115  int num_runs_;
116
117  // When the content specifies override_direction (bidi_ is null) then this
118  // will contain the single text run for WebKit.
119  WebTextRun override_run_;
120
121  DISALLOW_COPY_AND_ASSIGN(TextRunCollection);
122};
123
124bool PPTextRunToWebTextRun(const PP_BrowserFont_Trusted_TextRun& text,
125                           WebTextRun* run) {
126  StringVar* text_string = StringVar::FromPPVar(text.text);
127  if (!text_string)
128    return false;
129
130  *run = WebTextRun(base::UTF8ToUTF16(text_string->value()),
131                    PP_ToBool(text.rtl),
132                    PP_ToBool(text.override_direction));
133  return true;
134}
135
136// The PP_* version lacks "None", so is just one value shifted from the
137// WebFontDescription version. These values are checked in
138// PPFontDescToWebFontDesc to make sure the conversion is correct. This is a
139// macro so it can also be used in the COMPILE_ASSERTS.
140#define PP_FAMILY_TO_WEB_FAMILY(f) \
141  static_cast<WebFontDescription::GenericFamily>(f + 1)
142
143// Assumes the given PP_FontDescription has been validated.
144WebFontDescription PPFontDescToWebFontDesc(
145    const PP_BrowserFont_Trusted_Description& font,
146    const ppapi::Preferences& prefs) {
147  // Verify that the enums match so we can just static cast.
148  COMPILE_ASSERT(static_cast<int>(WebFontDescription::Weight100) ==
149                 static_cast<int>(PP_BROWSERFONT_TRUSTED_WEIGHT_100),
150                 FontWeight100);
151  COMPILE_ASSERT(static_cast<int>(WebFontDescription::Weight900) ==
152                 static_cast<int>(PP_BROWSERFONT_TRUSTED_WEIGHT_900),
153                 FontWeight900);
154  COMPILE_ASSERT(WebFontDescription::GenericFamilyStandard ==
155                 PP_FAMILY_TO_WEB_FAMILY(PP_FONTFAMILY_DEFAULT),
156                 StandardFamily);
157  COMPILE_ASSERT(WebFontDescription::GenericFamilySerif ==
158                 PP_FAMILY_TO_WEB_FAMILY(PP_FONTFAMILY_SERIF),
159                 SerifFamily);
160  COMPILE_ASSERT(WebFontDescription::GenericFamilySansSerif ==
161                 PP_FAMILY_TO_WEB_FAMILY(PP_FONTFAMILY_SANSSERIF),
162                 SansSerifFamily);
163  COMPILE_ASSERT(WebFontDescription::GenericFamilyMonospace ==
164                 PP_FAMILY_TO_WEB_FAMILY(PP_FONTFAMILY_MONOSPACE),
165                 MonospaceFamily);
166
167  StringVar* face_name = StringVar::FromPPVar(font.face);  // Possibly null.
168
169  WebFontDescription result;
170  base::string16 resolved_family;
171  if (!face_name || face_name->value().empty()) {
172    // Resolve the generic family.
173    switch (font.family) {
174      case PP_BROWSERFONT_TRUSTED_FAMILY_SERIF:
175        resolved_family = GetFontFromMap(prefs.serif_font_family_map,
176                                         kCommonScript);
177        break;
178      case PP_BROWSERFONT_TRUSTED_FAMILY_SANSSERIF:
179        resolved_family = GetFontFromMap(prefs.sans_serif_font_family_map,
180                                         kCommonScript);
181        break;
182      case PP_BROWSERFONT_TRUSTED_FAMILY_MONOSPACE:
183        resolved_family = GetFontFromMap(prefs.fixed_font_family_map,
184                                         kCommonScript);
185        break;
186      case PP_BROWSERFONT_TRUSTED_FAMILY_DEFAULT:
187      default:
188        resolved_family = GetFontFromMap(prefs.standard_font_family_map,
189                                         kCommonScript);
190        break;
191    }
192  } else {
193    // Use the exact font.
194    resolved_family = base::UTF8ToUTF16(face_name->value());
195  }
196  result.family = resolved_family;
197
198  result.genericFamily = PP_FAMILY_TO_WEB_FAMILY(font.family);
199
200  if (font.size == 0) {
201    // Resolve the default font size, using the resolved family to see if
202    // we should use the fixed or regular font size. It's difficult at this
203    // level to detect if the requested font is fixed width, so we only apply
204    // the alternate font size to the default fixed font family.
205    if (StringToLowerASCII(resolved_family) ==
206        StringToLowerASCII(GetFontFromMap(prefs.fixed_font_family_map,
207                                          kCommonScript)))
208      result.size = static_cast<float>(prefs.default_fixed_font_size);
209    else
210      result.size = static_cast<float>(prefs.default_font_size);
211  } else {
212    // Use the exact size.
213    result.size = static_cast<float>(font.size);
214  }
215
216  result.italic = font.italic != PP_FALSE;
217  result.smallCaps = font.small_caps != PP_FALSE;
218  result.weight = static_cast<WebFontDescription::Weight>(font.weight);
219  result.letterSpacing = static_cast<short>(font.letter_spacing);
220  result.wordSpacing = static_cast<short>(font.word_spacing);
221  return result;
222}
223
224}  // namespace
225
226// static
227bool BrowserFontResource_Trusted::IsPPFontDescriptionValid(
228    const PP_BrowserFont_Trusted_Description& desc) {
229  // Check validity of string. We can't check the actual text since we could
230  // be on the wrong thread and don't know if we're in the plugin or the host.
231  if (desc.face.type != PP_VARTYPE_STRING &&
232      desc.face.type != PP_VARTYPE_UNDEFINED)
233    return false;
234
235  // Check enum ranges.
236  if (static_cast<int>(desc.family) < PP_BROWSERFONT_TRUSTED_FAMILY_DEFAULT ||
237      static_cast<int>(desc.family) > PP_BROWSERFONT_TRUSTED_FAMILY_MONOSPACE)
238    return false;
239  if (static_cast<int>(desc.weight) < PP_BROWSERFONT_TRUSTED_WEIGHT_100 ||
240      static_cast<int>(desc.weight) > PP_BROWSERFONT_TRUSTED_WEIGHT_900)
241    return false;
242
243  // Check for excessive sizes which may cause layout to get confused.
244  if (desc.size > 200)
245    return false;
246
247  return true;
248}
249
250BrowserFontResource_Trusted::BrowserFontResource_Trusted(
251    ppapi::proxy::Connection connection,
252    PP_Instance instance,
253    const PP_BrowserFont_Trusted_Description& desc,
254    const ppapi::Preferences& prefs)
255    : PluginResource(connection, instance),
256      font_(WebFont::create(PPFontDescToWebFontDesc(desc, prefs))) {
257}
258
259BrowserFontResource_Trusted::~BrowserFontResource_Trusted() {
260}
261
262ppapi::thunk::PPB_BrowserFont_Trusted_API*
263BrowserFontResource_Trusted::AsPPB_BrowserFont_Trusted_API() {
264  return this;
265}
266
267PP_Bool BrowserFontResource_Trusted::Describe(
268    PP_BrowserFont_Trusted_Description* description,
269    PP_BrowserFont_Trusted_Metrics* metrics) {
270  if (description->face.type != PP_VARTYPE_UNDEFINED)
271    return PP_FALSE;
272
273  // While converting the other way in PPFontDescToWebFontDesc we validated
274  // that the enums can be casted.
275  WebFontDescription web_desc = font_->fontDescription();
276  description->face =
277      StringVar::StringToPPVar(base::UTF16ToUTF8(web_desc.family));
278  description->family =
279      static_cast<PP_BrowserFont_Trusted_Family>(web_desc.genericFamily);
280  description->size = static_cast<uint32_t>(web_desc.size);
281  description->weight = static_cast<PP_BrowserFont_Trusted_Weight>(
282      web_desc.weight);
283  description->italic = web_desc.italic ? PP_TRUE : PP_FALSE;
284  description->small_caps = web_desc.smallCaps ? PP_TRUE : PP_FALSE;
285  description->letter_spacing = static_cast<int32_t>(web_desc.letterSpacing);
286  description->word_spacing = static_cast<int32_t>(web_desc.wordSpacing);
287
288  metrics->height = font_->height();
289  metrics->ascent = font_->ascent();
290  metrics->descent = font_->descent();
291  metrics->line_spacing = font_->lineSpacing();
292  metrics->x_height = static_cast<int32_t>(font_->xHeight());
293
294  // Convert the string.
295  return PP_TRUE;
296}
297
298PP_Bool BrowserFontResource_Trusted::DrawTextAt(
299    PP_Resource image_data,
300    const PP_BrowserFont_Trusted_TextRun* text,
301    const PP_Point* position,
302    uint32_t color,
303    const PP_Rect* clip,
304    PP_Bool image_data_is_opaque) {
305  PP_Bool result = PP_FALSE;
306  // Get and map the image data we're painting to.
307  EnterResourceNoLock<PPB_ImageData_API> enter(image_data, true);
308  if (enter.failed())
309    return result;
310
311  PPB_ImageData_API* image = static_cast<PPB_ImageData_API*>(
312      enter.object());
313  SkCanvas* canvas = image->GetPlatformCanvas();
314  bool needs_unmapping = false;
315  if (!canvas) {
316    needs_unmapping = true;
317    image->Map();
318    canvas = image->GetPlatformCanvas();
319    if (!canvas)
320      return result;  // Failure mapping.
321  }
322
323  DrawTextToCanvas(canvas, *text, position, color, clip, image_data_is_opaque);
324
325  if (needs_unmapping)
326    image->Unmap();
327  return PP_TRUE;
328}
329
330int32_t BrowserFontResource_Trusted::MeasureText(
331    const PP_BrowserFont_Trusted_TextRun* text) {
332  WebTextRun run;
333  if (!PPTextRunToWebTextRun(*text, &run))
334    return -1;
335  return font_->calculateWidth(run);
336}
337
338uint32_t BrowserFontResource_Trusted::CharacterOffsetForPixel(
339    const PP_BrowserFont_Trusted_TextRun* text,
340    int32_t pixel_position) {
341  TextRunCollection runs(*text);
342  int32_t cur_pixel_offset = 0;
343  for (int i = 0; i < runs.num_runs(); i++) {
344    int32_t run_begin = 0;
345    int32_t run_len = 0;
346    WebTextRun run = runs.GetRunAt(i, &run_begin, &run_len);
347    int run_width = font_->calculateWidth(run);
348    if (pixel_position < cur_pixel_offset + run_width) {
349      // Offset is in this run.
350      return static_cast<uint32_t>(font_->offsetForPosition(
351              run, static_cast<float>(pixel_position - cur_pixel_offset))) +
352          run_begin;
353    }
354    cur_pixel_offset += run_width;
355  }
356  return runs.text().size();
357}
358
359int32_t BrowserFontResource_Trusted::PixelOffsetForCharacter(
360    const PP_BrowserFont_Trusted_TextRun* text,
361    uint32_t char_offset) {
362  TextRunCollection runs(*text);
363  int32_t cur_pixel_offset = 0;
364  for (int i = 0; i < runs.num_runs(); i++) {
365    int32_t run_begin = 0;
366    int32_t run_len = 0;
367    WebTextRun run = runs.GetRunAt(i, &run_begin, &run_len);
368    if (char_offset >= static_cast<uint32_t>(run_begin) &&
369        char_offset < static_cast<uint32_t>(run_begin + run_len)) {
370      // Character we're looking for is in this run.
371      //
372      // Here we ask WebKit to give us the rectangle around the character in
373      // question, and then return the left edge. If we asked for a range of
374      // 0 characters starting at the character in question, it would give us
375      // a 0-width rect around the insertion point. But that will be on the
376      // right side of the character for an RTL run, which would be wrong.
377      WebFloatRect rect = font_->selectionRectForText(
378          run, WebFloatPoint(0.0f, 0.0f), font_->height(),
379          char_offset - run_begin, char_offset - run_begin + 1);
380      return cur_pixel_offset + static_cast<int>(rect.x);
381    } else {
382      // Character is past this run, account for the pixels and continue
383      // looking.
384      cur_pixel_offset += font_->calculateWidth(run);
385    }
386  }
387  return -1;  // Requested a char beyond the end.
388}
389
390void BrowserFontResource_Trusted::DrawTextToCanvas(
391    SkCanvas* destination,
392    const PP_BrowserFont_Trusted_TextRun& text,
393    const PP_Point* position,
394    uint32_t color,
395    const PP_Rect* clip,
396    PP_Bool image_data_is_opaque) {
397  // Convert position and clip.
398  WebFloatPoint web_position(static_cast<float>(position->x),
399                             static_cast<float>(position->y));
400  WebRect web_clip;
401  if (!clip) {
402    // Use entire canvas. SkCanvas doesn't have a size on it, so we just use
403    // the current clip bounds.
404    SkRect skclip;
405    destination->getClipBounds(&skclip);
406    web_clip = WebRect(skclip.fLeft, skclip.fTop, skclip.fRight - skclip.fLeft,
407                       skclip.fBottom - skclip.fTop);
408  } else {
409    web_clip = WebRect(clip->point.x, clip->point.y,
410                       clip->size.width, clip->size.height);
411  }
412
413  TextRunCollection runs(text);
414  for (int i = 0; i < runs.num_runs(); i++) {
415    int32_t run_begin = 0;
416    int32_t run_len = 0;
417    WebTextRun run = runs.GetRunAt(i, &run_begin, &run_len);
418    font_->drawText(destination, run, web_position, color, web_clip,
419                    PP_ToBool(image_data_is_opaque));
420
421    // Advance to the next run. Note that we avoid doing this for the last run
422    // since it's unnecessary, measuring text is slow, and most of the time
423    // there will be only one run anyway.
424    if (i != runs.num_runs() - 1)
425      web_position.x += font_->calculateWidth(run);
426  }
427}
428
429}  // namespace content
430