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