[M108 Migration][HBBTV] Implement ewk_context_register_jsplugin_mime_types API
[platform/framework/web/chromium-efl.git] / printing / metafile_skia.cc
1 // Copyright 2012 The Chromium Authors
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 "printing/metafile_skia.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <tuple>
10 #include <utility>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/callback_helpers.h"
15 #include "base/containers/contains.h"
16 #include "base/containers/span.h"
17 #include "base/files/file.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/numerics/safe_conversions.h"
20 #include "base/time/time.h"
21 #include "base/unguessable_token.h"
22 #include "build/build_config.h"
23 #include "cc/paint/paint_record.h"
24 #include "cc/paint/paint_recorder.h"
25 #include "cc/paint/skia_paint_canvas.h"
26 #include "printing/metafile_agent.h"
27 #include "printing/mojom/print.mojom.h"
28 #include "third_party/skia/include/core/SkCanvas.h"
29 #include "third_party/skia/include/core/SkPicture.h"
30 #include "third_party/skia/include/core/SkSerialProcs.h"
31 #include "third_party/skia/include/core/SkStream.h"
32 // Note that headers in third_party/skia/src are fragile.  This is
33 // an experimental, fragile, and diagnostic-only document type.
34 #include "third_party/skia/src/utils/SkMultiPictureDocument.h"
35 #include "ui/gfx/geometry/skia_conversions.h"
36
37 #if BUILDFLAG(IS_MAC)
38 #include "printing/pdf_metafile_cg_mac.h"
39 #endif
40
41 #if BUILDFLAG(IS_POSIX)
42 #include "base/file_descriptor_posix.h"
43 #endif
44
45 #if BUILDFLAG(IS_ANDROID)
46 #include "base/files/file_util.h"
47 #endif  // BUILDFLAG(IS_ANDROID)
48
49 namespace {
50
51 // `InitFromData()` should make a copy of data for the safety of all operations
52 // which would then operate upon that.
53 constexpr bool kInitFromDataCopyData = true;
54
55 bool WriteAssetToBuffer(const SkStreamAsset* asset, void* buffer, size_t size) {
56   // Calling duplicate() keeps original asset state unchanged.
57   std::unique_ptr<SkStreamAsset> assetCopy(asset->duplicate());
58   size_t length = assetCopy->getLength();
59   return length <= size && length == assetCopy->read(buffer, length);
60 }
61
62 }  // namespace
63
64 namespace printing {
65
66 struct Page {
67   Page(const SkSize& s, sk_sp<cc::PaintRecord> c)
68       : size(s), content(std::move(c)) {}
69   Page(Page&& that) : size(that.size), content(std::move(that.content)) {}
70   Page(const Page&) = default;
71   Page& operator=(const Page&) = default;
72   Page& operator=(Page&& that) {
73     size = that.size;
74     content = std::move(that.content);
75     return *this;
76   }
77   SkSize size;
78   sk_sp<cc::PaintRecord> content;
79 };
80
81 struct MetafileSkiaData {
82   cc::PaintRecorder recorder;  // Current recording
83
84   std::vector<Page> pages;
85   std::unique_ptr<SkStreamAsset> data_stream;
86   ContentToProxyTokenMap subframe_content_info;
87   std::map<uint32_t, sk_sp<SkPicture>> subframe_pics;
88   int document_cookie = 0;
89   raw_ptr<ContentProxySet> typeface_content_info = nullptr;
90
91   // The scale factor is used because Blink occasionally calls
92   // PaintCanvas::getTotalMatrix() even though the total matrix is not as
93   // meaningful for a vector canvas as for a raster canvas.
94   float scale_factor;
95   SkSize size;
96   mojom::SkiaDocumentType type;
97
98 #if BUILDFLAG(IS_MAC)
99   PdfMetafileCg pdf_cg;
100 #endif
101 };
102
103 MetafileSkia::MetafileSkia() : data_(std::make_unique<MetafileSkiaData>()) {
104   data_->type = mojom::SkiaDocumentType::kPDF;
105 }
106
107 MetafileSkia::MetafileSkia(mojom::SkiaDocumentType type, int document_cookie)
108     : data_(std::make_unique<MetafileSkiaData>()) {
109   data_->type = type;
110   data_->document_cookie = document_cookie;
111 }
112
113 MetafileSkia::~MetafileSkia() = default;
114
115 bool MetafileSkia::Init() {
116   return true;
117 }
118
119 void MetafileSkia::UtilizeTypefaceContext(
120     ContentProxySet* typeface_content_info) {
121   data_->typeface_content_info = typeface_content_info;
122 }
123
124 // TODO(halcanary): Create a Metafile class that only stores data.
125 // Metafile::InitFromData is orthogonal to what the rest of
126 // MetafileSkia does.
127 bool MetafileSkia::InitFromData(base::span<const uint8_t> data) {
128   data_->data_stream = std::make_unique<SkMemoryStream>(
129       data.data(), data.size(), kInitFromDataCopyData);
130   return true;
131 }
132
133 void MetafileSkia::StartPage(const gfx::Size& page_size,
134                              const gfx::Rect& content_area,
135                              float scale_factor,
136                              mojom::PageOrientation page_orientation) {
137   gfx::Size physical_page_size = page_size;
138   if (page_orientation != mojom::PageOrientation::kUpright)
139     physical_page_size.SetSize(page_size.height(), page_size.width());
140
141   DCHECK_GT(page_size.width(), 0);
142   DCHECK_GT(page_size.height(), 0);
143   DCHECK_GT(scale_factor, 0.0f);
144   if (data_->recorder.getRecordingCanvas())
145     FinishPage();
146   DCHECK(!data_->recorder.getRecordingCanvas());
147
148   float inverse_scale = 1.0 / scale_factor;
149   cc::PaintCanvas* canvas = data_->recorder.beginRecording(
150       inverse_scale * physical_page_size.width(),
151       inverse_scale * physical_page_size.height());
152   // Recording canvas is owned by the `data_->recorder`.  No ref() necessary.
153   if (content_area != gfx::Rect(page_size) ||
154       page_orientation != mojom::PageOrientation::kUpright) {
155     canvas->scale(inverse_scale, inverse_scale);
156     if (page_orientation == mojom::PageOrientation::kRotateLeft) {
157       canvas->translate(0, physical_page_size.height());
158       canvas->rotate(-90);
159     } else if (page_orientation == mojom::PageOrientation::kRotateRight) {
160       canvas->translate(physical_page_size.width(), 0);
161       canvas->rotate(90);
162     }
163     SkRect sk_content_area = gfx::RectToSkRect(content_area);
164     canvas->clipRect(sk_content_area);
165     canvas->translate(sk_content_area.x(), sk_content_area.y());
166     canvas->scale(scale_factor, scale_factor);
167   }
168
169   data_->size = gfx::SizeFToSkSize(gfx::SizeF(physical_page_size));
170   data_->scale_factor = scale_factor;
171   // We scale the recording canvas's size so that
172   // canvas->getTotalMatrix() returns a value that ignores the scale
173   // factor.  We store the scale factor and re-apply it later.
174   // http://crbug.com/469656
175 }
176
177 cc::PaintCanvas* MetafileSkia::GetVectorCanvasForNewPage(
178     const gfx::Size& page_size,
179     const gfx::Rect& content_area,
180     float scale_factor,
181     mojom::PageOrientation page_orientation) {
182   StartPage(page_size, content_area, scale_factor, page_orientation);
183   return data_->recorder.getRecordingCanvas();
184 }
185
186 bool MetafileSkia::FinishPage() {
187   if (!data_->recorder.getRecordingCanvas())
188     return false;
189
190   sk_sp<cc::PaintRecord> pic = data_->recorder.finishRecordingAsPicture();
191   if (data_->scale_factor != 1.0f) {
192     cc::PaintCanvas* canvas = data_->recorder.beginRecording(
193         data_->size.width(), data_->size.height());
194     canvas->scale(data_->scale_factor, data_->scale_factor);
195     canvas->drawPicture(pic);
196     pic = data_->recorder.finishRecordingAsPicture();
197   }
198   data_->pages.emplace_back(data_->size, std::move(pic));
199   return true;
200 }
201
202 bool MetafileSkia::FinishDocument() {
203   // If we've already set the data in InitFromData, leave it be.
204   if (data_->data_stream)
205     return false;
206
207   if (data_->recorder.getRecordingCanvas())
208     FinishPage();
209
210   SkDynamicMemoryWStream stream;
211   sk_sp<SkDocument> doc;
212   cc::PlaybackParams::CustomDataRasterCallback custom_callback;
213   switch (data_->type) {
214     case mojom::SkiaDocumentType::kPDF:
215       doc = MakePdfDocument(printing::GetAgent(), accessibility_tree_, &stream);
216       break;
217     case mojom::SkiaDocumentType::kMSKP:
218       SkSerialProcs procs = SerializationProcs(&data_->subframe_content_info,
219                                                data_->typeface_content_info);
220       doc = SkMakeMultiPictureDocument(&stream, &procs);
221       // It is safe to use base::Unretained(this) because the callback
222       // is only used by `canvas` in the following loop which has shorter
223       // lifetime than `this`.
224       custom_callback = base::BindRepeating(
225           &MetafileSkia::CustomDataToSkPictureCallback, base::Unretained(this));
226       break;
227   }
228
229   for (const Page& page : data_->pages) {
230     cc::SkiaPaintCanvas canvas(
231         doc->beginPage(page.size.width(), page.size.height()));
232     canvas.drawPicture(page.content, custom_callback);
233     doc->endPage();
234   }
235   doc->close();
236
237   data_->data_stream = stream.detachAsStream();
238   return true;
239 }
240
241 void MetafileSkia::FinishFrameContent() {
242   // Sanity check to make sure we print the entire frame as a single page
243   // content.
244   DCHECK_EQ(data_->pages.size(), 1u);
245   // Also make sure it is in skia multi-picture document format.
246   DCHECK_EQ(data_->type, mojom::SkiaDocumentType::kMSKP);
247   DCHECK(!data_->data_stream);
248
249   cc::PlaybackParams::CustomDataRasterCallback custom_callback =
250       base::BindRepeating(&MetafileSkia::CustomDataToSkPictureCallback,
251                           base::Unretained(this));
252   sk_sp<SkPicture> pic = ToSkPicture(data_->pages[0].content,
253                                      SkRect::MakeSize(data_->pages[0].size),
254                                      nullptr, custom_callback);
255   SkSerialProcs procs = SerializationProcs(&data_->subframe_content_info,
256                                            data_->typeface_content_info);
257   SkDynamicMemoryWStream stream;
258   pic->serialize(&stream, &procs);
259   data_->data_stream = stream.detachAsStream();
260 }
261
262 uint32_t MetafileSkia::GetDataSize() const {
263   if (!data_->data_stream)
264     return 0;
265   return base::checked_cast<uint32_t>(data_->data_stream->getLength());
266 }
267
268 bool MetafileSkia::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
269   if (!data_->data_stream)
270     return false;
271   return WriteAssetToBuffer(data_->data_stream.get(), dst_buffer,
272                             base::checked_cast<size_t>(dst_buffer_size));
273 }
274
275 bool MetafileSkia::ShouldCopySharedMemoryRegionData() const {
276   // When `InitFromData()` copies the data, the caller doesn't have to.
277   return !kInitFromDataCopyData;
278 }
279
280 mojom::MetafileDataType MetafileSkia::GetDataType() const {
281   return mojom::MetafileDataType::kPDF;
282 }
283
284 gfx::Rect MetafileSkia::GetPageBounds(unsigned int page_number) const {
285   if (page_number < data_->pages.size()) {
286     SkSize size = data_->pages[page_number].size;
287     return gfx::Rect(base::ClampRound(size.width()),
288                      base::ClampRound(size.height()));
289   }
290   return gfx::Rect();
291 }
292
293 unsigned int MetafileSkia::GetPageCount() const {
294   return base::checked_cast<unsigned int>(data_->pages.size());
295 }
296
297 printing::NativeDrawingContext MetafileSkia::context() const {
298   NOTREACHED();
299   return nullptr;
300 }
301
302 #if BUILDFLAG(IS_WIN)
303 bool MetafileSkia::Playback(printing::NativeDrawingContext hdc,
304                             const RECT* rect) const {
305   NOTREACHED();
306   return false;
307 }
308
309 bool MetafileSkia::SafePlayback(printing::NativeDrawingContext hdc) const {
310   NOTREACHED();
311   return false;
312 }
313
314 #elif BUILDFLAG(IS_MAC)
315 /* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in
316    rasterized output.  Even if that flow uses PdfMetafileCg::RenderPage,
317    the drawing of the PDF into the canvas may result in a rasterized output.
318    PDFMetafileSkia::RenderPage should be not implemented as shown and instead
319    should do something like the following CL in PluginInstance::PrintPDFOutput:
320 http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc
321 */
322 bool MetafileSkia::RenderPage(unsigned int page_number,
323                               CGContextRef context,
324                               const CGRect& rect,
325                               bool autorotate,
326                               bool fit_to_page) const {
327   DCHECK_GT(GetDataSize(), 0U);
328   if (data_->pdf_cg.GetDataSize() == 0) {
329     if (GetDataSize() == 0)
330       return false;
331     size_t length = data_->data_stream->getLength();
332     std::vector<uint8_t> buffer(length);
333     std::ignore =
334         WriteAssetToBuffer(data_->data_stream.get(), &buffer[0], length);
335     data_->pdf_cg.InitFromData(buffer);
336   }
337   return data_->pdf_cg.RenderPage(page_number, context, rect, autorotate,
338                                   fit_to_page);
339 }
340 #endif
341
342 #if BUILDFLAG(IS_ANDROID)
343 bool MetafileSkia::SaveToFileDescriptor(int fd) const {
344   if (GetDataSize() == 0u)
345     return false;
346
347   std::unique_ptr<SkStreamAsset> asset(data_->data_stream->duplicate());
348
349   static constexpr size_t kMaximumBufferSize = 1024 * 1024;
350   std::vector<uint8_t> buffer(std::min(kMaximumBufferSize, asset->getLength()));
351   do {
352     size_t read_size = asset->read(&buffer[0], buffer.size());
353     if (read_size == 0u)
354       break;
355     DCHECK_GE(buffer.size(), read_size);
356     buffer.resize(read_size);
357     if (!base::WriteFileDescriptor(fd, buffer))
358       return false;
359   } while (!asset->isAtEnd());
360
361   return true;
362 }
363 #else
364 bool MetafileSkia::SaveTo(base::File* file) const {
365   if (GetDataSize() == 0U)
366     return false;
367
368   // Calling duplicate() keeps original asset state unchanged.
369   std::unique_ptr<SkStreamAsset> asset(data_->data_stream->duplicate());
370
371   static constexpr size_t kMaximumBufferSize = 1024 * 1024;
372   std::vector<uint8_t> buffer(std::min(kMaximumBufferSize, asset->getLength()));
373   do {
374     size_t read_size = asset->read(&buffer[0], buffer.size());
375     if (read_size == 0)
376       break;
377     DCHECK_GE(buffer.size(), read_size);
378     if (!file->WriteAtCurrentPosAndCheck(
379             base::make_span(&buffer[0], read_size))) {
380       return false;
381     }
382   } while (!asset->isAtEnd());
383
384   return true;
385 }
386 #endif  // BUILDFLAG(IS_ANDROID)
387
388 std::unique_ptr<MetafileSkia> MetafileSkia::GetMetafileForCurrentPage(
389     mojom::SkiaDocumentType type) {
390   // If we only ever need the metafile for the last page, should we
391   // only keep a handle on one PaintRecord?
392   auto metafile = std::make_unique<MetafileSkia>(type, data_->document_cookie);
393   if (data_->pages.size() == 0)
394     return metafile;
395
396   if (data_->recorder.getRecordingCanvas())  // page outstanding
397     return metafile;
398
399   metafile->data_->pages.push_back(data_->pages.back());
400   metafile->data_->subframe_content_info = data_->subframe_content_info;
401   metafile->data_->subframe_pics = data_->subframe_pics;
402   metafile->data_->typeface_content_info = data_->typeface_content_info;
403
404   if (!metafile->FinishDocument())  // Generate PDF.
405     metafile.reset();
406
407   return metafile;
408 }
409
410 uint32_t MetafileSkia::CreateContentForRemoteFrame(
411     const gfx::Rect& rect,
412     const base::UnguessableToken& render_proxy_token) {
413   // Create a place holder picture.
414   sk_sp<SkPicture> pic = SkPicture::MakePlaceholder(
415       SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()));
416
417   // Store the map between content id and the proxy id.
418   uint32_t content_id = pic->uniqueID();
419   DCHECK(!base::Contains(data_->subframe_content_info, content_id));
420   data_->subframe_content_info[content_id] = render_proxy_token;
421
422   // Store the picture content.
423   data_->subframe_pics[content_id] = pic;
424   return content_id;
425 }
426
427 int MetafileSkia::GetDocumentCookie() const {
428   return data_->document_cookie;
429 }
430
431 const ContentToProxyTokenMap& MetafileSkia::GetSubframeContentInfo() const {
432   return data_->subframe_content_info;
433 }
434
435 void MetafileSkia::AppendPage(const SkSize& page_size,
436                               sk_sp<cc::PaintRecord> record) {
437   data_->pages.emplace_back(page_size, std::move(record));
438 }
439
440 void MetafileSkia::AppendSubframeInfo(uint32_t content_id,
441                                       const base::UnguessableToken& proxy_token,
442                                       sk_sp<SkPicture> pic_holder) {
443   data_->subframe_content_info[content_id] = proxy_token;
444   data_->subframe_pics[content_id] = pic_holder;
445 }
446
447 SkStreamAsset* MetafileSkia::GetPdfData() const {
448   return data_->data_stream.get();
449 }
450
451 void MetafileSkia::CustomDataToSkPictureCallback(SkCanvas* canvas,
452                                                  uint32_t content_id) {
453   // Check whether this is the one we need to handle.
454   if (!base::Contains(data_->subframe_content_info, content_id))
455     return;
456
457   auto it = data_->subframe_pics.find(content_id);
458   DCHECK(it != data_->subframe_pics.end());
459
460   // Found the picture, draw it on canvas.
461   sk_sp<SkPicture> pic = it->second;
462   SkRect rect = pic->cullRect();
463   SkMatrix matrix = SkMatrix::Translate(rect.x(), rect.y());
464   canvas->drawPicture(it->second, &matrix, nullptr);
465 }
466
467 }  // namespace printing