1 // Copyright 2016 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 "third_party/blink/renderer/platform/graphics/placeholder_image.h"
6 
7 #include <utility>
8 
9 #include "base/stl_util.h"
10 #include "third_party/blink/public/resources/grit/blink_image_resources.h"
11 #include "third_party/blink/public/strings/grit/blink_strings.h"
12 #include "third_party/blink/renderer/platform/fonts/font.h"
13 #include "third_party/blink/renderer/platform/fonts/font_description.h"
14 #include "third_party/blink/renderer/platform/fonts/font_family.h"
15 #include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
16 #include "third_party/blink/renderer/platform/fonts/text_run_paint_info.h"
17 #include "third_party/blink/renderer/platform/geometry/float_point.h"
18 #include "third_party/blink/renderer/platform/geometry/float_rect.h"
19 #include "third_party/blink/renderer/platform/geometry/int_point.h"
20 #include "third_party/blink/renderer/platform/geometry/int_rect.h"
21 #include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
22 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
23 #include "third_party/blink/renderer/platform/graphics/image_observer.h"
24 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
25 #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
26 #include "third_party/blink/renderer/platform/graphics/paint/paint_record.h"
27 #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
28 #include "third_party/blink/renderer/platform/text/platform_locale.h"
29 #include "third_party/blink/renderer/platform/text/text_run.h"
30 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
31 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
32 #include "third_party/skia/include/core/SkColor.h"
33 #include "third_party/skia/include/core/SkRect.h"
34 #include "third_party/skia/include/core/SkSize.h"
35 
36 namespace blink {
37 
38 namespace {
39 
40 // Placeholder image visual specifications:
41 // https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit
42 
43 constexpr int kIconWidth = 24;
44 constexpr int kIconHeight = 24;
45 constexpr int kFeaturePaddingX = 8;
46 constexpr int kIconPaddingY = 5;
47 constexpr int kPaddingBetweenIconAndText = 2;
48 constexpr int kTextPaddingY = 9;
49 
50 constexpr int kFontSize = 14;
51 
DrawIcon(cc::PaintCanvas * canvas,const PaintFlags & flags,float x,float y,float scale_factor)52 void DrawIcon(cc::PaintCanvas* canvas,
53               const PaintFlags& flags,
54               float x,
55               float y,
56               float scale_factor) {
57   // Note that |icon_image| will be a 0x0 image when running
58   // blink_platform_unittests.
59   DEFINE_STATIC_REF(Image, icon_image,
60                     (Image::LoadPlatformResource(IDR_PLACEHOLDER_ICON)));
61 
62   // Note that the |icon_image| is not scaled according to dest_rect / src_rect,
63   // and is always drawn at the same size. This is so that placeholder icons are
64   // visible (e.g. when replacing a large image that's scaled down to a small
65   // area) and so that all placeholder images on the same page look consistent.
66   canvas->drawImageRect(
67       icon_image->PaintImageForCurrentFrame(),
68       IntRect(IntPoint::Zero(), icon_image->Size()),
69       FloatRect(x, y, scale_factor * kIconWidth, scale_factor * kIconHeight),
70       &flags, cc::PaintCanvas::kFast_SrcRectConstraint);
71 }
72 
DrawCenteredIcon(cc::PaintCanvas * canvas,const PaintFlags & flags,const FloatRect & dest_rect,float scale_factor)73 void DrawCenteredIcon(cc::PaintCanvas* canvas,
74                       const PaintFlags& flags,
75                       const FloatRect& dest_rect,
76                       float scale_factor) {
77   DrawIcon(
78       canvas, flags,
79       dest_rect.X() + (dest_rect.Width() - scale_factor * kIconWidth) / 2.0f,
80       dest_rect.Y() + (dest_rect.Height() - scale_factor * kIconHeight) / 2.0f,
81       scale_factor);
82 }
83 
CreatePlaceholderFontDescription(float scale_factor)84 FontDescription CreatePlaceholderFontDescription(float scale_factor) {
85   FontDescription description;
86   description.FirstFamily().SetFamily("Roboto");
87 
88   scoped_refptr<SharedFontFamily> helvetica_neue = SharedFontFamily::Create();
89   helvetica_neue->SetFamily("Helvetica Neue");
90   scoped_refptr<SharedFontFamily> helvetica = SharedFontFamily::Create();
91   helvetica->SetFamily("Helvetica");
92   scoped_refptr<SharedFontFamily> arial = SharedFontFamily::Create();
93   arial->SetFamily("Arial");
94 
95   helvetica->AppendFamily(std::move(arial));
96   helvetica_neue->AppendFamily(std::move(helvetica));
97   description.FirstFamily().AppendFamily(std::move(helvetica_neue));
98 
99   description.SetGenericFamily(FontDescription::kSansSerifFamily);
100   description.SetComputedSize(scale_factor * kFontSize);
101   description.SetWeight(FontSelectionValue(500));
102 
103   return description;
104 }
105 
106 // Return a byte quantity as a string in a localized human-readable format,
107 // suitable for being shown on a placeholder image to indicate the full original
108 // size of the resource.
109 //
110 // Ex: FormatOriginalResourceSizeBytes(100) => "1 KB"
111 // Ex: FormatOriginalResourceSizeBytes(102401) => "100 KB"
112 // Ex: FormatOriginalResourceSizeBytes(1740800) => "1.7 MB"
113 //
114 // See the placeholder image number format specifications for more info:
115 // https://docs.google.com/document/d/1BHeA1azbgCdZgCnr16VN2g7A9MHPQ_dwKn5szh8evMQ/edit#heading=h.d135l9z7tn0a
FormatOriginalResourceSizeBytes(int64_t bytes)116 String FormatOriginalResourceSizeBytes(int64_t bytes) {
117   DCHECK_LT(0, bytes);
118 
119   static constexpr int kUnitsResourceIds[] = {
120       IDS_UNITS_KIBIBYTES, IDS_UNITS_MEBIBYTES, IDS_UNITS_GIBIBYTES,
121       IDS_UNITS_TEBIBYTES, IDS_UNITS_PEBIBYTES};
122 
123   // Start with KB. The formatted text will be at least "1 KB", with any smaller
124   // amounts being rounded up to "1 KB".
125   const int* units = kUnitsResourceIds;
126   int64_t denomenator = 1024;
127 
128   // Find the smallest unit that can represent |bytes| in 3 digits or less.
129   // Round up to the next higher unit if possible when it would take 4 digits to
130   // display the amount, e.g. 1000 KB will be rounded up to 1 MB.
131   for (; units < kUnitsResourceIds + (base::size(kUnitsResourceIds) - 1) &&
132          bytes >= denomenator * 1000;
133        ++units, denomenator *= 1024) {
134   }
135 
136   String numeric_string;
137   if (bytes < denomenator) {
138     // Round up to 1.
139     numeric_string = String::Number(1);
140   } else if (units != kUnitsResourceIds && bytes < denomenator * 10) {
141     // For amounts between 1 and 10 units and larger than 1 MB, allow up to one
142     // fractional digit.
143     numeric_string = String::Number(
144         static_cast<double>(bytes) / static_cast<double>(denomenator), 2);
145   } else {
146     numeric_string = String::Number(bytes / denomenator);
147   }
148 
149   Locale& locale = Locale::DefaultLocale();
150   // Locale::QueryString() will return an empty string if the embedder hasn't
151   // defined the string resources for the units, which will cause the
152   // PlaceholderImage to not show any text.
153   return locale.QueryString(*units,
154                             locale.ConvertToLocalizedNumber(numeric_string));
155 }
156 
157 }  // namespace
158 
159 // A simple RefCounted wrapper around a Font, so that multiple PlaceholderImages
160 // can share the same Font.
161 class PlaceholderImage::SharedFont : public RefCounted<SharedFont> {
162  public:
GetOrCreateInstance(float scale_factor)163   static scoped_refptr<SharedFont> GetOrCreateInstance(float scale_factor) {
164     if (g_instance_) {
165       scoped_refptr<SharedFont> shared_font(g_instance_);
166       shared_font->MaybeUpdateForScaleFactor(scale_factor);
167       return shared_font;
168     }
169 
170     scoped_refptr<SharedFont> shared_font =
171         base::MakeRefCounted<SharedFont>(scale_factor);
172     g_instance_ = shared_font.get();
173     return shared_font;
174   }
175 
176   // This constructor is public so that base::MakeRefCounted() can call it.
SharedFont(float scale_factor)177   explicit SharedFont(float scale_factor)
178       : font_(CreatePlaceholderFontDescription(scale_factor)),
179         scale_factor_(scale_factor) {
180   }
181 
~SharedFont()182   ~SharedFont() {
183     DCHECK_EQ(this, g_instance_);
184     g_instance_ = nullptr;
185   }
186 
MaybeUpdateForScaleFactor(float scale_factor)187   void MaybeUpdateForScaleFactor(float scale_factor) {
188     if (scale_factor_ == scale_factor)
189       return;
190 
191     scale_factor_ = scale_factor;
192     font_ = Font(CreatePlaceholderFontDescription(scale_factor_));
193   }
194 
font() const195   const Font& font() const { return font_; }
196 
197  private:
198   static SharedFont* g_instance_;
199 
200   Font font_;
201   float scale_factor_;
202 };
203 
204 // static
205 PlaceholderImage::SharedFont* PlaceholderImage::SharedFont::g_instance_ =
206     nullptr;
207 
PlaceholderImage(ImageObserver * observer,const IntSize & size,int64_t original_resource_size,bool is_lazy_image)208 PlaceholderImage::PlaceholderImage(ImageObserver* observer,
209                                    const IntSize& size,
210                                    int64_t original_resource_size,
211                                    bool is_lazy_image)
212     : Image(observer),
213       size_(size),
214       text_(original_resource_size <= 0
215                 ? String()
216                 : FormatOriginalResourceSizeBytes(original_resource_size)),
217       is_lazy_image_(is_lazy_image),
218       paint_record_content_id_(-1) {}
219 
220 PlaceholderImage::~PlaceholderImage() = default;
221 
Size() const222 IntSize PlaceholderImage::Size() const {
223   return size_;
224 }
225 
IsPlaceholderImage() const226 bool PlaceholderImage::IsPlaceholderImage() const {
227   return true;
228 }
229 
CurrentFrameHasSingleSecurityOrigin() const230 bool PlaceholderImage::CurrentFrameHasSingleSecurityOrigin() const {
231   return true;
232 }
233 
CurrentFrameKnownToBeOpaque()234 bool PlaceholderImage::CurrentFrameKnownToBeOpaque() {
235   // Placeholder images are translucent.
236   return false;
237 }
238 
PaintImageForCurrentFrame()239 PaintImage PlaceholderImage::PaintImageForCurrentFrame() {
240   auto builder = CreatePaintImageBuilder().set_completion_state(
241       PaintImage::CompletionState::DONE);
242 
243   const IntRect dest_rect(0, 0, size_.Width(), size_.Height());
244   if (paint_record_for_current_frame_) {
245     return builder
246         .set_paint_record(paint_record_for_current_frame_, dest_rect,
247                           paint_record_content_id_)
248         .TakePaintImage();
249   }
250 
251   PaintRecorder paint_recorder;
252   Draw(paint_recorder.beginRecording(FloatRect(dest_rect)), PaintFlags(),
253        FloatRect(dest_rect), FloatRect(dest_rect), kRespectImageOrientation,
254        kClampImageToSourceRect, kSyncDecode);
255 
256   paint_record_for_current_frame_ = paint_recorder.finishRecordingAsPicture();
257   paint_record_content_id_ = PaintImage::GetNextContentId();
258   return builder
259       .set_paint_record(paint_record_for_current_frame_, dest_rect,
260                         paint_record_content_id_)
261       .TakePaintImage();
262 }
263 
SetIconAndTextScaleFactor(float icon_and_text_scale_factor)264 void PlaceholderImage::SetIconAndTextScaleFactor(
265     float icon_and_text_scale_factor) {
266   if (icon_and_text_scale_factor_ == icon_and_text_scale_factor)
267     return;
268   icon_and_text_scale_factor_ = icon_and_text_scale_factor;
269   cached_text_width_.reset();
270   paint_record_for_current_frame_.reset();
271 }
272 
Draw(cc::PaintCanvas * canvas,const PaintFlags & base_flags,const FloatRect & dest_rect,const FloatRect & src_rect,RespectImageOrientationEnum respect_orientation,ImageClampingMode image_clamping_mode,ImageDecodingMode decode_mode)273 void PlaceholderImage::Draw(cc::PaintCanvas* canvas,
274                             const PaintFlags& base_flags,
275                             const FloatRect& dest_rect,
276                             const FloatRect& src_rect,
277                             RespectImageOrientationEnum respect_orientation,
278                             ImageClampingMode image_clamping_mode,
279                             ImageDecodingMode decode_mode) {
280   if (!src_rect.Intersects(FloatRect(0.0f, 0.0f,
281                                      static_cast<float>(size_.Width()),
282                                      static_cast<float>(size_.Height())))) {
283     return;
284   }
285   if (is_lazy_image_) {
286     // Keep the image without any color and text decorations.
287     return;
288   }
289 
290   PaintFlags flags(base_flags);
291   flags.setStyle(PaintFlags::kFill_Style);
292   flags.setColor(SkColorSetARGB(0x80, 0xD9, 0xD9, 0xD9));
293   canvas->drawRect(dest_rect, flags);
294 
295   if (dest_rect.Width() <
296           icon_and_text_scale_factor_ * (kIconWidth + 2 * kFeaturePaddingX) ||
297       dest_rect.Height() <
298           icon_and_text_scale_factor_ * (kIconHeight + 2 * kIconPaddingY)) {
299     return;
300   }
301 
302   if (text_.IsEmpty()) {
303     DrawCenteredIcon(canvas, base_flags, dest_rect,
304                      icon_and_text_scale_factor_);
305     return;
306   }
307 
308   if (!shared_font_)
309     shared_font_ = SharedFont::GetOrCreateInstance(icon_and_text_scale_factor_);
310   else
311     shared_font_->MaybeUpdateForScaleFactor(icon_and_text_scale_factor_);
312 
313   if (!cached_text_width_.has_value())
314     cached_text_width_ = shared_font_->font().Width(TextRun(text_));
315 
316   const float icon_and_text_width =
317       cached_text_width_.value() +
318       icon_and_text_scale_factor_ *
319           (kIconWidth + 2 * kFeaturePaddingX + kPaddingBetweenIconAndText);
320 
321   if (dest_rect.Width() < icon_and_text_width) {
322     DrawCenteredIcon(canvas, base_flags, dest_rect,
323                      icon_and_text_scale_factor_);
324     return;
325   }
326 
327   const float feature_x =
328       dest_rect.X() + (dest_rect.Width() - icon_and_text_width) / 2.0f;
329   const float feature_y =
330       dest_rect.Y() +
331       (dest_rect.Height() -
332        icon_and_text_scale_factor_ * (kIconHeight + 2 * kIconPaddingY)) /
333           2.0f;
334 
335   float icon_x, text_x;
336   if (Locale::DefaultLocale().IsRTL()) {
337     icon_x = feature_x + cached_text_width_.value() +
338              icon_and_text_scale_factor_ *
339                  (kFeaturePaddingX + kPaddingBetweenIconAndText);
340     text_x = feature_x + icon_and_text_scale_factor_ * kFeaturePaddingX;
341   } else {
342     icon_x = feature_x + icon_and_text_scale_factor_ * kFeaturePaddingX;
343     text_x = feature_x +
344              icon_and_text_scale_factor_ *
345                  (kFeaturePaddingX + kIconWidth + kPaddingBetweenIconAndText);
346   }
347 
348   DrawIcon(canvas, base_flags, icon_x,
349            feature_y + icon_and_text_scale_factor_ * kIconPaddingY,
350            icon_and_text_scale_factor_);
351 
352   flags.setColor(SkColorSetARGB(0xAB, 0, 0, 0));
353   shared_font_->font().DrawBidiText(
354       canvas, TextRunPaintInfo(TextRun(text_)),
355       FloatPoint(text_x, feature_y + icon_and_text_scale_factor_ *
356                                          (kTextPaddingY + kFontSize)),
357       Font::kUseFallbackIfFontNotReady, 1.0f, flags);
358 }
359 
DrawPattern(GraphicsContext & context,const FloatRect & src_rect,const FloatSize & scale,const FloatPoint & phase,SkBlendMode mode,const FloatRect & dest_rect,const FloatSize & repeat_spacing,RespectImageOrientationEnum respect_orientation)360 void PlaceholderImage::DrawPattern(
361     GraphicsContext& context,
362     const FloatRect& src_rect,
363     const FloatSize& scale,
364     const FloatPoint& phase,
365     SkBlendMode mode,
366     const FloatRect& dest_rect,
367     const FloatSize& repeat_spacing,
368     RespectImageOrientationEnum respect_orientation) {
369   DCHECK(context.Canvas());
370 
371   PaintFlags flags = context.FillFlags();
372   flags.setBlendMode(mode);
373 
374   // Ignore the pattern specifications and just draw a single placeholder image
375   // over the whole |dest_rect|. This is done in order to prevent repeated icons
376   // from cluttering tiled background images.
377   Draw(context.Canvas(), flags, dest_rect, src_rect, respect_orientation,
378        kClampImageToSourceRect, kUnspecifiedDecode);
379 }
380 
DestroyDecodedData()381 void PlaceholderImage::DestroyDecodedData() {
382   paint_record_for_current_frame_.reset();
383   shared_font_ = scoped_refptr<SharedFont>();
384 }
385 
SetData(scoped_refptr<SharedBuffer>,bool)386 Image::SizeAvailability PlaceholderImage::SetData(scoped_refptr<SharedBuffer>,
387                                                   bool) {
388   return Image::kSizeAvailable;
389 }
390 
GetFontForTesting() const391 const Font* PlaceholderImage::GetFontForTesting() const {
392   return shared_font_ ? &shared_font_->font() : nullptr;
393 }
394 
395 }  // namespace blink
396