Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
fx_skia_device_embeddertest.cpp
Go to the documentation of this file.
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
35namespace {
36
37using ::testing::NiceMock;
38using ::testing::SizeIs;
39using ::testing::WithArg;
40
41struct 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
54void EmptyTest(CFX_SkiaDeviceDriver* driver, const State&) {
55 driver->SaveState();
56 driver->RestoreState(true);
57 driver->RestoreState(false);
58}
59
60void 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(
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,
96 } else if (state.m_graphic == State::Graphic::kText) {
97 driver->DrawDeviceText(charPos, &font, matrix, fontSize, 0xFF445566,
98 kTextOptions);
99 }
100 if (state.m_save == State::Save::kYes)
101 driver->RestoreState(true);
102 CFX_Path path2;
103 path2.AppendRect(0, 0, 2, 2);
104 if (state.m_change == State::Change::kYes) {
105 if (state.m_graphic == State::Graphic::kPath)
107 else if (state.m_graphic == State::Graphic::kText)
108 fontSize = 2;
109 }
110 if (state.m_clip == State::Clip::kSame)
111 driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
112 else if (state.m_clip == State::Clip::kDifferentPath)
113 driver->SetClip_PathFill(clipPath2, &clipMatrix, CFX_FillRenderOptions());
114 else if (state.m_clip == State::Clip::kDifferentMatrix)
115 driver->SetClip_PathFill(clipPath, &clipMatrix2, CFX_FillRenderOptions());
116 if (state.m_graphic == State::Graphic::kPath) {
117 driver->DrawPath(path2, &matrix2, &graphState, 0xFF112233, 0,
119 } else if (state.m_graphic == State::Graphic::kText) {
120 driver->DrawDeviceText(charPos, &font, matrix2, fontSize, 0xFF445566,
121 kTextOptions);
122 }
123 if (state.m_save == State::Save::kYes)
124 driver->RestoreState(false);
125 driver->RestoreState(false);
126}
127
128void OutOfSequenceClipTest(CFX_SkiaDeviceDriver* driver, const State&) {
129 CFX_Path clipPath;
130 clipPath.AppendRect(1, 0, 3, 1);
131 CFX_Matrix clipMatrix;
132 driver->SaveState();
133 driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
134 driver->RestoreState(true);
135 driver->SaveState();
136 driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
137 driver->RestoreState(false);
138 driver->RestoreState(false);
139
140 driver->SaveState();
141 driver->SaveState();
142 driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
143 driver->RestoreState(true);
144 driver->SetClip_PathFill(clipPath, &clipMatrix, CFX_FillRenderOptions());
145 driver->RestoreState(false);
146 driver->RestoreState(false);
147}
148
149void Harness(void (*Test)(CFX_SkiaDeviceDriver*, const State&),
150 const State& state) {
151 constexpr int kWidth = 4;
152 constexpr int kHeight = 1;
153 ScopedFPDFBitmap bitmap(FPDFBitmap_Create(kWidth, kHeight, 1));
154 ASSERT_TRUE(bitmap);
155 ASSERT_TRUE(
156 FPDFBitmap_FillRect(bitmap.get(), 0, 0, kWidth, kHeight, 0x00000000));
157 RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap.get()));
158 auto driver = CFX_SkiaDeviceDriver::Create(pBitmap, false, nullptr, false);
159 ASSERT_TRUE(driver);
160 (*Test)(driver.get(), state);
161 uint32_t pixel = pBitmap->GetPixelForTesting(0, 0);
162 EXPECT_EQ(state.m_pixel, pixel);
163}
164
165void RenderPageToSkCanvas(FPDF_PAGE page,
166 int start_x,
167 int start_y,
168 int size_x,
169 int size_y,
170 SkCanvas& canvas) {
171 CPDF_Page* cpdf_page = CPDFPageFromFPDFPage(page);
172
173 auto context = std::make_unique<CPDF_PageRenderContext>();
174 CPDF_PageRenderContext* unowned_context = context.get();
175
176 CPDF_Page::RenderContextClearer clearer(cpdf_page);
177 cpdf_page->SetRenderContext(std::move(context));
178
179 auto default_device = std::make_unique<CFX_DefaultRenderDevice>();
180 CHECK(default_device->AttachCanvas(canvas));
181 unowned_context->m_pDevice = std::move(default_device);
182
183 CPDFSDK_RenderPageWithContext(unowned_context, cpdf_page, start_x, start_y,
184 size_x, size_y, /*rotate=*/0, /*flags=*/0,
185 /*color_scheme=*/nullptr,
186 /*need_to_restore=*/true, /*pause=*/nullptr);
187}
188
189class MockCanvas : public SkNoDrawCanvas {
190 public:
191 MockCanvas(int width, int height) : SkNoDrawCanvas(width, height) {}
192
193 MOCK_METHOD(void,
194 onDrawImageRect2,
195 (const SkImage*,
196 const SkRect&,
197 const SkRect&,
198 const SkSamplingOptions&,
199 const SkPaint*,
200 SrcRectConstraint),
201 (override));
202};
203
204using FxgeSkiaEmbedderTest = EmbedderTest;
205
206} // namespace
207
208TEST(fxge, SkiaStateEmpty) {
209 if (!CFX_DefaultRenderDevice::UseSkiaRenderer()) {
210 return;
211 }
212 Harness(&EmptyTest, {});
213}
214
215TEST(fxge, SkiaStatePath) {
216 if (!CFX_DefaultRenderDevice::UseSkiaRenderer()) {
217 return;
218 }
219 Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
220 State::Clip::kSame, State::Graphic::kPath, 0xFF112233});
221 Harness(&CommonTest,
222 {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentPath,
223 State::Graphic::kPath, 0xFF112233});
224 Harness(&CommonTest, {State::Change::kNo, State::Save::kYes, State::Clip::kNo,
225 State::Graphic::kPath, 0xFF112233});
226 Harness(&CommonTest, {State::Change::kYes, State::Save::kNo, State::Clip::kNo,
227 State::Graphic::kPath, 0xFF112233});
228 Harness(&CommonTest, {State::Change::kNo, State::Save::kNo, State::Clip::kNo,
229 State::Graphic::kPath, 0xFF112233});
230}
231
232TEST(fxge, SkiaStateText) {
233 if (!CFX_DefaultRenderDevice::UseSkiaRenderer()) {
234 return;
235 }
236
237 Harness(&CommonTest,
238 {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentMatrix,
239 State::Graphic::kText, 0xFF445566});
240 Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
241 State::Clip::kSame, State::Graphic::kText, 0xFF445566});
242}
243
244TEST(fxge, SkiaStateOOSClip) {
245 if (!CFX_DefaultRenderDevice::UseSkiaRenderer()) {
246 return;
247 }
248 Harness(&OutOfSequenceClipTest, {});
249}
250
251TEST_F(FxgeSkiaEmbedderTest, RenderBigImageTwice) {
252 static constexpr int kImageWidth = 5100;
253 static constexpr int kImageHeight = 6600;
254
255 // Page size that renders 20 image pixels per output pixel. This value evenly
256 // divides both the image width and half the image height.
257 static constexpr int kPageToImageFactor = 20;
258 static constexpr int kPageWidth = kImageWidth / kPageToImageFactor;
259 static constexpr int kPageHeight = kImageHeight / kPageToImageFactor;
260
261 if (!CFX_DefaultRenderDevice::UseSkiaRenderer()) {
262 GTEST_SKIP() << "Skia is not the default renderer";
263 }
264
265 ASSERT_TRUE(OpenDocument("bug_2034.pdf"));
266 FPDF_PAGE page = LoadPage(0);
267 ASSERT_TRUE(page);
268
269 std::set<int> image_ids;
270 NiceMock<MockCanvas> canvas(kPageWidth, kPageHeight / 2);
271 EXPECT_CALL(canvas, onDrawImageRect2)
272 .WillRepeatedly(WithArg<0>([&image_ids](const SkImage* image) {
273 ASSERT_TRUE(image);
274 image_ids.insert(image->uniqueID());
275
276 // TODO(crbug.com/pdfium/2026): Image dimensions should be clipped to
277 // 5100x3320. The extra `kPageToImageFactor` accounts for anti-aliasing.
278 EXPECT_EQ(SkISize::Make(kImageWidth, kImageHeight), image->dimensions())
279 << "Actual image dimensions: " << image->width() << "x"
280 << image->height();
281 }));
282
283 // Render top half.
284 RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/0,
285 /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, canvas);
286
287 // Render bottom half.
288 RenderPageToSkCanvas(page, /*start_x=*/0, /*start_y=*/-kPageHeight / 2,
289 /*size_x=*/kPageWidth, /*size_y=*/kPageHeight, canvas);
290
291 EXPECT_THAT(image_ids, SizeIs(1));
292
293 UnloadPage(page);
294}
void LoadSubst(const ByteString &face_name, bool bTrueType, uint32_t flags, int weight, int italic_angle, FX_CodePage code_page, bool bVertical)
Definition cfx_font.cpp:233
void Translate(int32_t x, int32_t y)
constexpr CFX_Matrix(float a1, float b1, float c1, float d1, float e1, float f1)
void AppendRect(float left, float bottom, float right, float top)
Definition cfx_path.cpp:310
void SaveState() override
bool SetClip_PathFill(const CFX_Path &path, const CFX_Matrix *pObject2Device, const CFX_FillRenderOptions &fill_options) override
bool DrawPath(const CFX_Path &path, const CFX_Matrix *pObject2Device, const CFX_GraphStateData *pGraphState, uint32_t fill_color, uint32_t stroke_color, const CFX_FillRenderOptions &fill_options) override
void RestoreState(bool bKeepSaved) override
RenderContextClearer(CPDF_Page *pPage)
uint32_t m_GlyphIndex
CPDF_Page * CPDFPageFromFPDFPage(FPDF_PAGE page)
void CPDFSDK_RenderPageWithContext(CPDF_PageRenderContext *pContext, CPDF_Page *pPage, int start_x, int start_y, int size_x, int size_y, int rotate, int flags, const FPDF_COLORSCHEME *color_scheme, bool need_to_restore, CPDFSDK_PauseAdapter *pause)
TEST_F(FPDFParserDecodeEmbedderTest, Bug552046)
FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV FPDFBitmap_Create(int width, int height, int alpha)
FX_CodePage
Definition fx_codepage.h:19
TEST(FXCRYPT, CryptToBase16)
#define CHECK(cvref)
static constexpr CFX_FillRenderOptions WindingOptions()
constexpr CFX_TextRenderOptions(AliasingType type)