xref: /aosp_15_r20/external/pdfium/core/fxge/skia/fx_skia_device_embeddertest.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2016 The PDFium 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 "core/fxge/skia/fx_skia_device.h"
6 
7 #include <memory>
8 #include <set>
9 #include <utility>
10 
11 #include "core/fpdfapi/page/cpdf_page.h"
12 #include "core/fpdfapi/render/cpdf_pagerendercontext.h"
13 #include "core/fxcrt/fx_codepage.h"
14 #include "core/fxge/cfx_defaultrenderdevice.h"
15 #include "core/fxge/cfx_fillrenderoptions.h"
16 #include "core/fxge/cfx_font.h"
17 #include "core/fxge/cfx_graphstatedata.h"
18 #include "core/fxge/cfx_path.h"
19 #include "core/fxge/cfx_renderdevice.h"
20 #include "core/fxge/cfx_textrenderoptions.h"
21 #include "core/fxge/dib/cfx_dibitmap.h"
22 #include "core/fxge/text_char_pos.h"
23 #include "fpdfsdk/cpdfsdk_helpers.h"
24 #include "fpdfsdk/cpdfsdk_renderpage.h"
25 #include "public/cpp/fpdf_scopers.h"
26 #include "public/fpdfview.h"
27 #include "testing/embedder_test.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "third_party/skia/include/core/SkCanvas.h"
31 #include "third_party/skia/include/core/SkImage.h"
32 #include "third_party/skia/include/core/SkSize.h"
33 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
34 
35 namespace {
36 
37 using ::testing::NiceMock;
38 using ::testing::SizeIs;
39 using ::testing::WithArg;
40 
41 struct State {
42   enum class Change { kNo, kYes };
43   enum class Save { kNo, kYes };
44   enum class Clip { kNo, kSame, kDifferentPath, kDifferentMatrix };
45   enum class Graphic { kNone, kPath, kText };
46 
47   Change m_change;
48   Save m_save;
49   Clip m_clip;
50   Graphic m_graphic;
51   uint32_t m_pixel;
52 };
53 
EmptyTest(CFX_SkiaDeviceDriver * driver,const State &)54 void EmptyTest(CFX_SkiaDeviceDriver* driver, const State&) {
55   driver->SaveState();
56   driver->RestoreState(true);
57   driver->RestoreState(false);
58 }
59 
CommonTest(CFX_SkiaDeviceDriver * driver,const State & state)60 void CommonTest(CFX_SkiaDeviceDriver* driver, const State& state) {
61   TextCharPos charPos[1];
62   charPos[0].m_Origin = CFX_PointF(0, 1);
63   charPos[0].m_GlyphIndex = 0;
64   charPos[0].m_FontCharWidth = 4;
65 
66   CFX_Font font;
67   font.LoadSubst("Courier", /*bTrueType=*/true, /*flags=*/0,
68                  /*weight=*/400, /*italic_angle=*/0, FX_CodePage::kShiftJIS,
69                  /*bVertical=*/false);
70   float fontSize = 20;
71   CFX_Path clipPath;
72   CFX_Path clipPath2;
73   clipPath.AppendRect(0, 0, 3, 1);
74   clipPath2.AppendRect(0, 0, 2, 1);
75   CFX_Matrix clipMatrix;
76   CFX_Matrix clipMatrix2(1, 0, 0, 1, 0, 1);
77   driver->SaveState();
78   CFX_Path path1;
79   path1.AppendRect(0, 0, 1, 2);
80 
81   CFX_Matrix matrix;
82   CFX_Matrix matrix2;
83   matrix2.Translate(1, 0);
84   CFX_GraphStateData graphState;
85   // Turn off anti-aliasing so that pixels with transitional colors can be
86   // avoided.
87   static constexpr CFX_TextRenderOptions kTextOptions(
88       CFX_TextRenderOptions::kAliasing);
89   if (state.m_save == State::Save::kYes)
90     driver->SaveState();
91   if (state.m_clip != State::Clip::kNo)
92     driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
93   if (state.m_graphic == State::Graphic::kPath) {
94     driver->DrawPath(path1, &matrix, &graphState, 0xFF112233, 0,
95                      CFX_FillRenderOptions::WindingOptions(),
96                      BlendMode::kNormal);
97   } else if (state.m_graphic == State::Graphic::kText) {
98     driver->DrawDeviceText(charPos, &font, matrix, fontSize, 0xFF445566,
99                            kTextOptions);
100   }
101   if (state.m_save == State::Save::kYes)
102     driver->RestoreState(true);
103   CFX_Path path2;
104   path2.AppendRect(0, 0, 2, 2);
105   if (state.m_change == State::Change::kYes) {
106     if (state.m_graphic == State::Graphic::kPath)
107       graphState.m_LineCap = CFX_GraphStateData::LineCap::kRound;
108     else if (state.m_graphic == State::Graphic::kText)
109       fontSize = 2;
110   }
111   if (state.m_clip == State::Clip::kSame)
112     driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
113   else if (state.m_clip == State::Clip::kDifferentPath)
114     driver->SetClip_PathFill(clipPath2, &clipMatrix, CFX_FillRenderOptions());
115   else if (state.m_clip == State::Clip::kDifferentMatrix)
116     driver->SetClip_PathFill(clipPath, &clipMatrix2, CFX_FillRenderOptions());
117   if (state.m_graphic == State::Graphic::kPath) {
118     driver->DrawPath(path2, &matrix2, &graphState, 0xFF112233, 0,
119                      CFX_FillRenderOptions::WindingOptions(),
120                      BlendMode::kNormal);
121   } else if (state.m_graphic == State::Graphic::kText) {
122     driver->DrawDeviceText(charPos, &font, matrix2, fontSize, 0xFF445566,
123                            kTextOptions);
124   }
125   if (state.m_save == State::Save::kYes)
126     driver->RestoreState(false);
127   driver->RestoreState(false);
128 }
129 
OutOfSequenceClipTest(CFX_SkiaDeviceDriver * driver,const State &)130 void OutOfSequenceClipTest(CFX_SkiaDeviceDriver* driver, const State&) {
131   CFX_Path clipPath;
132   clipPath.AppendRect(1, 0, 3, 1);
133   CFX_Matrix clipMatrix;
134   driver->SaveState();
135   driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
136   driver->RestoreState(true);
137   driver->SaveState();
138   driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
139   driver->RestoreState(false);
140   driver->RestoreState(false);
141 
142   driver->SaveState();
143   driver->SaveState();
144   driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
145   driver->RestoreState(true);
146   driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
147   driver->RestoreState(false);
148   driver->RestoreState(false);
149 }
150 
Harness(void (* Test)(CFX_SkiaDeviceDriver *,const State &),const State & state)151 void Harness(void (*Test)(CFX_SkiaDeviceDriver*, const State&),
152              const State& state) {
153   constexpr int kWidth = 4;
154   constexpr int kHeight = 1;
155   ScopedFPDFBitmap bitmap(FPDFBitmap_Create(kWidth, kHeight, 1));
156   ASSERT_TRUE(bitmap);
157   FPDFBitmap_FillRect(bitmap.get(), 0, 0, kWidth, kHeight, 0x00000000);
158   RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap.get()));
159   auto driver = CFX_SkiaDeviceDriver::Create(pBitmap, false, nullptr, false);
160   ASSERT_TRUE(driver);
161   (*Test)(driver.get(), state);
162   uint32_t pixel = pBitmap->GetPixel(0, 0);
163   EXPECT_EQ(state.m_pixel, pixel);
164 }
165 
RenderPageToSkCanvas(FPDF_PAGE page,int start_x,int start_y,int size_x,int size_y,SkCanvas * canvas)166 void RenderPageToSkCanvas(FPDF_PAGE page,
167                           int start_x,
168                           int start_y,
169                           int size_x,
170                           int size_y,
171                           SkCanvas* canvas) {
172   CPDF_Page* cpdf_page = CPDFPageFromFPDFPage(page);
173 
174   auto context = std::make_unique<CPDF_PageRenderContext>();
175   CPDF_PageRenderContext* unowned_context = context.get();
176 
177   CPDF_Page::RenderContextClearer clearer(cpdf_page);
178   cpdf_page->SetRenderContext(std::move(context));
179 
180   auto default_device = std::make_unique<CFX_DefaultRenderDevice>();
181   default_device->AttachCanvas(canvas);
182   unowned_context->m_pDevice = std::move(default_device);
183 
184   CPDFSDK_RenderPageWithContext(unowned_context, cpdf_page, start_x, start_y,
185                                 size_x, size_y, /*rotate=*/0, /*flags=*/0,
186                                 /*color_scheme=*/nullptr,
187                                 /*need_to_restore=*/true, /*pause=*/nullptr);
188 }
189 
190 class MockCanvas : public SkNoDrawCanvas {
191  public:
MockCanvas(int width,int height)192   MockCanvas(int width, int height) : SkNoDrawCanvas(width, height) {}
193 
194   MOCK_METHOD(void,
195               onDrawImageRect2,
196               (const SkImage*,
197                const SkRect&,
198                const SkRect&,
199                const SkSamplingOptions&,
200                const SkPaint*,
201                SrcRectConstraint),
202               (override));
203 };
204 
205 using FxgeSkiaEmbedderTest = EmbedderTest;
206 
207 }  // namespace
208 
TEST(fxge,SkiaStateEmpty)209 TEST(fxge, SkiaStateEmpty) {
210   if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
211     return;
212   Harness(&EmptyTest, {});
213 }
214 
TEST(fxge,SkiaStatePath)215 TEST(fxge, SkiaStatePath) {
216   if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
217     return;
218   Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
219                         State::Clip::kSame, State::Graphic::kPath, 0xFF112233});
220   Harness(&CommonTest,
221           {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentPath,
222            State::Graphic::kPath, 0xFF112233});
223   Harness(&CommonTest, {State::Change::kNo, State::Save::kYes, State::Clip::kNo,
224                         State::Graphic::kPath, 0xFF112233});
225   Harness(&CommonTest, {State::Change::kYes, State::Save::kNo, State::Clip::kNo,
226                         State::Graphic::kPath, 0xFF112233});
227   Harness(&CommonTest, {State::Change::kNo, State::Save::kNo, State::Clip::kNo,
228                         State::Graphic::kPath, 0xFF112233});
229 }
230 
TEST(fxge,SkiaStateText)231 TEST(fxge, SkiaStateText) {
232   if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
233     return;
234 
235   Harness(&CommonTest,
236           {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentMatrix,
237            State::Graphic::kText, 0xFF445566});
238   Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
239                         State::Clip::kSame, State::Graphic::kText, 0xFF445566});
240 }
241 
TEST(fxge,SkiaStateOOSClip)242 TEST(fxge, SkiaStateOOSClip) {
243   if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
244     return;
245   Harness(&OutOfSequenceClipTest, {});
246 }
247 
TEST_F(FxgeSkiaEmbedderTest,RenderBigImageTwice)248 TEST_F(FxgeSkiaEmbedderTest, RenderBigImageTwice) {
249   static constexpr int kImageWidth = 5100;
250   static constexpr int kImageHeight = 6600;
251 
252   // Page size that renders 20 image pixels per output pixel. This value evenly
253   // divides both the image width and half the image height.
254   static constexpr int kPageToImageFactor = 20;
255   static constexpr int kPageWidth = kImageWidth / kPageToImageFactor;
256   static constexpr int kPageHeight = kImageHeight / kPageToImageFactor;
257 
258   if (!CFX_DefaultRenderDevice::SkiaIsDefaultRenderer()) {
259     GTEST_SKIP() << "Skia is not the default renderer";
260   }
261 
262   ASSERT_TRUE(OpenDocument("bug_2034.pdf"));
263   FPDF_PAGE page = LoadPage(0);
264   ASSERT_TRUE(page);
265 
266   std::set<int> image_ids;
267   NiceMock<MockCanvas> canvas(kPageWidth, kPageHeight / 2);
268   EXPECT_CALL(canvas, onDrawImageRect2)
269       .WillRepeatedly(WithArg<0>([&image_ids](const SkImage* image) {
270         ASSERT_TRUE(image);
271         image_ids.insert(image->uniqueID());
272 
273         // TODO(crbug.com/pdfium/2026): Image dimensions should be clipped to
274         // 5100x3320. The extra `kPageToImageFactor` accounts for anti-aliasing.
275         EXPECT_EQ(SkISize::Make(kImageWidth, kImageHeight), image->dimensions())
276             << "Actual image dimensions: " << image->width() << "x"
277             << image->height();
278       }));
279 
280   // Render top half.
281   RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/0,
282                        /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, &canvas);
283 
284   // Render bottom half.
285   RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/-kPageHeight / 2,
286                        /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, &canvas);
287 
288   EXPECT_THAT(image_ids, SizeIs(1));
289 
290   UnloadPage(page);
291 }
292