1 // Copyright 2018 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.
5 #include "printing/metafile_skia.h"
9 #include "build/build_config.h"
10 #include "cc/paint/paint_op.h"
11 #include "cc/paint/paint_record.h"
12 #include "printing/common/metafile_utils.h"
13 #include "printing/mojom/print.mojom.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "third_party/skia/include/core/SkBitmap.h"
16 #include "third_party/skia/include/core/SkCanvas.h"
17 #include "third_party/skia/include/core/SkPaint.h"
18 #include "third_party/skia/include/core/SkPicture.h"
19 #include "third_party/skia/include/core/SkPictureRecorder.h"
20 #include "third_party/skia/include/core/SkRect.h"
21 #include "third_party/skia/include/core/SkRefCnt.h"
22 #include "third_party/skia/include/core/SkSerialProcs.h"
23 #include "third_party/skia/include/core/SkSize.h"
24 #include "third_party/skia/include/core/SkStream.h"
25 #include "third_party/skia/include/core/SkSurfaceProps.h"
26 #include "third_party/skia/include/core/SkTextBlob.h"
30 TEST(MetafileSkiaTest, FrameContent) {
31 constexpr int kPictureSideLen = 100;
32 constexpr int kPageSideLen = 150;
34 // Create a placeholder picture.
35 sk_sp<SkPicture> pic_holder = SkPicture::MakePlaceholder(
36 SkRect::MakeXYWH(0, 0, kPictureSideLen, kPictureSideLen));
38 // Create the page with nested content which is the placeholder and will be
40 cc::PaintOpBuffer buffer;
42 flags.setColor(SK_ColorWHITE);
43 const SkRect page_rect = SkRect::MakeXYWH(0, 0, kPageSideLen, kPageSideLen);
44 buffer.push<cc::DrawRectOp>(page_rect, flags);
45 const uint32_t content_id = pic_holder->uniqueID();
46 buffer.push<cc::CustomDataOp>(content_id);
47 SkSize page_size = SkSize::Make(kPageSideLen, kPageSideLen);
49 // Finish creating the entire metafile.
50 MetafileSkia metafile(mojom::SkiaDocumentType::kMSKP, 1);
51 metafile.AppendPage(page_size, buffer.ReleaseAsRecord());
52 metafile.AppendSubframeInfo(content_id, base::UnguessableToken::Create(),
53 std::move(pic_holder));
54 metafile.FinishFrameContent();
55 SkStreamAsset* metafile_stream = metafile.GetPdfData();
56 ASSERT_TRUE(metafile_stream);
58 // Draw a 100 by 100 red square which will be the actual content of
60 SkPictureRecorder recorder;
61 SkCanvas* canvas = recorder.beginRecording(kPictureSideLen, kPictureSideLen);
63 paint.setStyle(SkPaint::kFill_Style);
64 paint.setColor(SK_ColorRED);
65 paint.setAlpha(SK_AlphaOPAQUE);
66 canvas->drawRect(SkRect::MakeXYWH(0, 0, kPictureSideLen, kPictureSideLen),
68 sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
71 // Get the complete picture by replacing the placeholder.
72 PictureDeserializationContext subframes;
73 subframes[content_id] = picture;
74 SkDeserialProcs procs = DeserializationProcs(&subframes, nullptr);
75 sk_sp<SkPicture> pic = SkPicture::MakeFromStream(metafile_stream, &procs);
78 // Verify the resultant picture is as expected by comparing the sizes and
79 // detecting the color inside and outside of the square area.
80 EXPECT_TRUE(pic->cullRect() == page_rect);
82 bitmap.allocN32Pixels(kPageSideLen, kPageSideLen);
83 SkCanvas bitmap_canvas(bitmap, SkSurfaceProps{});
84 pic->playback(&bitmap_canvas);
85 // Check top left pixel color of the red square.
86 EXPECT_EQ(bitmap.getColor(0, 0), SK_ColorRED);
87 // Check bottom right pixel of the red square.
88 EXPECT_EQ(bitmap.getColor(kPictureSideLen - 1, kPictureSideLen - 1),
90 // Check inside of the red square.
91 EXPECT_EQ(bitmap.getColor(kPictureSideLen / 2, kPictureSideLen / 2),
93 // Check outside of the red square.
94 EXPECT_EQ(bitmap.getColor(kPictureSideLen, kPictureSideLen), SK_ColorWHITE);
97 TEST(MetafileSkiaTest, GetPageBounds) {
98 constexpr int kPictureSideLen = 100;
99 constexpr int kPageSideWidth = 150;
100 constexpr int kPageSideHeight = 120;
102 // Create a placeholder picture.
103 sk_sp<SkPicture> pic_holder = SkPicture::MakePlaceholder(
104 SkRect::MakeXYWH(0, 0, kPictureSideLen, kPictureSideLen));
106 // Create the page with nested content which is the placeholder and will be
108 cc::PaintOpBuffer buffer;
109 cc::PaintFlags flags;
110 flags.setColor(SK_ColorWHITE);
111 const SkRect page_rect =
112 SkRect::MakeXYWH(0, 0, kPageSideWidth, kPageSideHeight);
113 buffer.push<cc::DrawRectOp>(page_rect, flags);
114 const uint32_t content_id = pic_holder->uniqueID();
115 buffer.push<cc::CustomDataOp>(content_id);
116 SkSize page_size = SkSize::Make(kPageSideWidth, kPageSideHeight);
118 // Finish creating the entire metafile.
119 MetafileSkia metafile(mojom::SkiaDocumentType::kMSKP, 1);
120 metafile.AppendPage(page_size, buffer.ReleaseAsRecord());
121 metafile.AppendSubframeInfo(content_id, base::UnguessableToken::Create(),
122 std::move(pic_holder));
123 metafile.FinishFrameContent();
125 // Confirm there is 1 page in the doc.
126 EXPECT_EQ(1u, metafile.GetPageCount());
128 // Test in bound case.
129 EXPECT_EQ(gfx::Rect(kPageSideWidth, kPageSideHeight),
130 metafile.GetPageBounds(/*page_number=*/1));
132 // Test out of bounds cases.
133 EXPECT_EQ(gfx::Rect(), metafile.GetPageBounds(/*page_number=*/0));
134 EXPECT_EQ(gfx::Rect(), metafile.GetPageBounds(/*page_number=*/2));
137 TEST(MetafileSkiaTest, MultiPictureDocumentTypefaces) {
138 constexpr int kPictureSideLen = 100;
139 constexpr int kPageSideLen = 150;
140 constexpr int kDocumentCookie = 1;
141 constexpr int kNumDocumentPages = 2;
143 // The content tracking for serialization/deserialization.
144 ContentProxySet serialize_typeface_ctx;
145 PictureDeserializationContext subframes;
146 TypefaceDeserializationContext typefaces;
147 SkDeserialProcs procs = DeserializationProcs(&subframes, &typefaces);
149 // The typefaces which will be reused across the multiple (duplicate) pages.
150 constexpr char kTypefaceName1[] = "sans-serif";
151 #if BUILDFLAG(IS_WIN)
152 constexpr char kTypefaceName2[] = "Courier New";
154 constexpr char kTypefaceName2[] = "monospace";
156 constexpr size_t kNumTypefaces = 2;
157 sk_sp<SkTypeface> typeface1 =
158 SkTypeface::MakeFromName(kTypefaceName1, SkFontStyle());
159 sk_sp<SkTypeface> typeface2 =
160 SkTypeface::MakeFromName(kTypefaceName2, SkFontStyle());
161 const SkFont font1 = SkFont(typeface1, 10);
162 const SkFont font2 = SkFont(typeface2, 12);
164 // Node IDs for the text, which will increase for each text blob added.
165 cc::NodeId node_id = 7;
167 // All text can just be black.
168 cc::PaintFlags flags_text;
169 flags_text.setColor(SK_ColorBLACK);
171 // Mark the text on white pages, each of the same size.
172 cc::PaintFlags flags;
173 flags.setColor(SK_ColorWHITE);
174 const SkRect page_rect = SkRect::MakeXYWH(0, 0, kPageSideLen, kPageSideLen);
175 SkSize page_size = SkSize::Make(kPageSideLen, kPageSideLen);
177 for (int i = 0; i < kNumDocumentPages; i++) {
178 MetafileSkia metafile(mojom::SkiaDocumentType::kMSKP, kDocumentCookie);
180 // When the stream is serialized inside FinishFrameContent(), any typeface
181 // which is used on any page will be serialized only once by the first
182 // page's metafile which needed it. Any subsequent page that reuses the
183 // same typeface will rely upon `serialize_typeface_ctx` which is used by
184 // printing::SerializeOopTypeface() to optimize away the need to resend.
185 metafile.UtilizeTypefaceContext(&serialize_typeface_ctx);
187 sk_sp<SkPicture> pic_holder = SkPicture::MakePlaceholder(
188 SkRect::MakeXYWH(0, 0, kPictureSideLen, kPictureSideLen));
190 // Create the page for the text content.
191 cc::PaintOpBuffer buffer;
192 buffer.push<cc::DrawRectOp>(page_rect, flags);
193 const uint32_t content_id = pic_holder->uniqueID();
194 buffer.push<cc::CustomDataOp>(content_id);
196 // Mark the page with some text using multiple fonts.
197 // Use the first font.
198 sk_sp<SkTextBlob> text_blob1 = SkTextBlob::MakeFromString("foo", font1);
199 buffer.push<cc::DrawTextBlobOp>(text_blob1, 0.0f, 0.0f, ++node_id,
202 // Use the second font.
203 sk_sp<SkTextBlob> text_blob2 = SkTextBlob::MakeFromString("bar", font2);
204 buffer.push<cc::DrawTextBlobOp>(text_blob2, 0.0f, 0.0f, ++node_id,
207 // Reuse the first font again on same page.
208 sk_sp<SkTextBlob> text_blob3 = SkTextBlob::MakeFromString("bar", font2);
209 buffer.push<cc::DrawTextBlobOp>(text_blob3, 0.0f, 0.0f, ++node_id,
212 metafile.AppendPage(page_size, buffer.ReleaseAsRecord());
213 metafile.AppendSubframeInfo(content_id, base::UnguessableToken::Create(),
214 std::move(pic_holder));
215 metafile.FinishFrameContent();
216 SkStreamAsset* metafile_stream = metafile.GetPdfData();
217 ASSERT_TRUE(metafile_stream);
219 // Deserialize the stream. Any given typeface is expected to appear only
220 // once in the stream, so the deserialization context of `typefaces` bundled
221 // with `procs` should be empty the first time through, and afterwards
222 // there should never be more than the number of unique typefaces we used,
223 // regardless of number of pages.
224 EXPECT_EQ(typefaces.size(), i ? kNumTypefaces : 0);
225 ASSERT_TRUE(SkPicture::MakeFromStream(metafile_stream, &procs));
226 EXPECT_EQ(typefaces.size(), kNumTypefaces);
230 } // namespace printing