1 // Copyright 2014 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 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "xfa/fgas/graphics/cfgas_gegraphics.h"
8
9 #include <math.h>
10
11 #include <array>
12 #include <iterator>
13 #include <memory>
14 #include <utility>
15
16 #include "core/fxcrt/check.h"
17 #include "core/fxcrt/fx_system.h"
18 #include "core/fxcrt/span_util.h"
19 #include "core/fxcrt/stl_util.h"
20 #include "core/fxge/cfx_defaultrenderdevice.h"
21 #include "core/fxge/cfx_renderdevice.h"
22 #include "core/fxge/cfx_unicodeencoding.h"
23 #include "core/fxge/dib/cfx_dibitmap.h"
24 #include "xfa/fgas/graphics/cfgas_gecolor.h"
25 #include "xfa/fgas/graphics/cfgas_gepath.h"
26 #include "xfa/fgas/graphics/cfgas_gepattern.h"
27 #include "xfa/fgas/graphics/cfgas_geshading.h"
28
29 namespace {
30
31 struct FX_HATCHDATA {
32 int32_t width;
33 int32_t height;
34 uint8_t maskBits[64];
35 };
36
37 constexpr auto kHatchBitmapData = fxcrt::ToArray<const FX_HATCHDATA>({
38 {16, // Horizontal
39 16,
40 {
41 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
44 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
47 }},
48 {16, // Vertical
49 16,
50 {
51 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
52 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80,
53 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80,
54 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
55 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
56 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
57 }},
58 {16, // ForwardDiagonal
59 16,
60 {
61 0x80, 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00,
62 0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04,
63 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x80,
64 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00,
65 0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04, 0x00,
66 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
67 }},
68 {16, // BackwardDiagonal
69 16,
70 {
71 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00,
72 0x00, 0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20,
73 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01,
74 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00,
75 0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20, 0x00,
76 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
77 }},
78 {16, // Cross
79 16,
80 {
81 0xff, 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
82 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80,
83 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0xff,
84 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
85 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00,
86 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
87 }},
88 {16, // DiagonalCross
89 16,
90 {
91 0x81, 0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00,
92 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24,
93 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00, 0x81,
94 0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00,
95 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24, 0x00,
96 0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00,
97 }},
98 });
99
100 const FX_HATCHDATA kHatchPlaceHolder = {
101 0,
102 0,
103 {
104 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
105 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
110 }};
111
GetHatchBitmapData(size_t index)112 const FX_HATCHDATA& GetHatchBitmapData(size_t index) {
113 return index < std::size(kHatchBitmapData) ? kHatchBitmapData[index]
114 : kHatchPlaceHolder;
115 }
116
117 } // namespace
118
CFGAS_GEGraphics(CFX_RenderDevice * renderDevice)119 CFGAS_GEGraphics::CFGAS_GEGraphics(CFX_RenderDevice* renderDevice)
120 : m_renderDevice(renderDevice) {
121 DCHECK(m_renderDevice);
122 }
123
124 CFGAS_GEGraphics::~CFGAS_GEGraphics() = default;
125
SaveGraphState()126 void CFGAS_GEGraphics::SaveGraphState() {
127 m_renderDevice->SaveState();
128 m_infoStack.push_back(std::make_unique<TInfo>(m_info));
129 }
130
RestoreGraphState()131 void CFGAS_GEGraphics::RestoreGraphState() {
132 m_renderDevice->RestoreState(false);
133 CHECK(!m_infoStack.empty());
134 m_info = *m_infoStack.back();
135 m_infoStack.pop_back();
136 return;
137 }
138
SetLineCap(CFX_GraphStateData::LineCap lineCap)139 void CFGAS_GEGraphics::SetLineCap(CFX_GraphStateData::LineCap lineCap) {
140 m_info.graphState.set_line_cap(lineCap);
141 }
142
SetLineDash(std::vector<float> dash_array)143 void CFGAS_GEGraphics::SetLineDash(std::vector<float> dash_array) {
144 // For `dash_array` to be empty, call SetSolidLineDash() instead.
145 CHECK(!dash_array.empty());
146 const float scale = m_info.isActOnDash ? m_info.graphState.line_width() : 1.0;
147 for (float& f : dash_array) {
148 f *= scale;
149 }
150 m_info.graphState.set_dash_array(std::move(dash_array));
151 m_info.graphState.set_dash_phase(0);
152 }
153
SetSolidLineDash()154 void CFGAS_GEGraphics::SetSolidLineDash() {
155 m_info.graphState.set_dash_array({});
156 }
157
SetLineWidth(float lineWidth)158 void CFGAS_GEGraphics::SetLineWidth(float lineWidth) {
159 m_info.graphState.set_line_width(lineWidth);
160 }
161
EnableActOnDash()162 void CFGAS_GEGraphics::EnableActOnDash() {
163 m_info.isActOnDash = true;
164 }
165
SetStrokeColor(const CFGAS_GEColor & color)166 void CFGAS_GEGraphics::SetStrokeColor(const CFGAS_GEColor& color) {
167 m_info.strokeColor = color;
168 }
169
SetFillColor(const CFGAS_GEColor & color)170 void CFGAS_GEGraphics::SetFillColor(const CFGAS_GEColor& color) {
171 m_info.fillColor = color;
172 }
173
StrokePath(const CFGAS_GEPath & path,const CFX_Matrix & matrix)174 void CFGAS_GEGraphics::StrokePath(const CFGAS_GEPath& path,
175 const CFX_Matrix& matrix) {
176 RenderDeviceStrokePath(path, matrix);
177 }
178
FillPath(const CFGAS_GEPath & path,CFX_FillRenderOptions::FillType fill_type,const CFX_Matrix & matrix)179 void CFGAS_GEGraphics::FillPath(const CFGAS_GEPath& path,
180 CFX_FillRenderOptions::FillType fill_type,
181 const CFX_Matrix& matrix) {
182 RenderDeviceFillPath(path, fill_type, matrix);
183 }
184
ConcatMatrix(const CFX_Matrix & matrix)185 void CFGAS_GEGraphics::ConcatMatrix(const CFX_Matrix& matrix) {
186 m_info.CTM.Concat(matrix);
187 }
188
GetMatrix() const189 const CFX_Matrix* CFGAS_GEGraphics::GetMatrix() const {
190 return &m_info.CTM;
191 }
192
GetClipRect() const193 CFX_RectF CFGAS_GEGraphics::GetClipRect() const {
194 FX_RECT r = m_renderDevice->GetClipBox();
195 return CFX_RectF(r.left, r.top, r.Width(), r.Height());
196 }
197
SetClipRect(const CFX_RectF & rect)198 void CFGAS_GEGraphics::SetClipRect(const CFX_RectF& rect) {
199 m_renderDevice->SetClip_Rect(
200 FX_RECT(FXSYS_roundf(rect.left), FXSYS_roundf(rect.top),
201 FXSYS_roundf(rect.right()), FXSYS_roundf(rect.bottom())));
202 }
203
GetRenderDevice()204 CFX_RenderDevice* CFGAS_GEGraphics::GetRenderDevice() {
205 return m_renderDevice;
206 }
207
RenderDeviceStrokePath(const CFGAS_GEPath & path,const CFX_Matrix & matrix)208 void CFGAS_GEGraphics::RenderDeviceStrokePath(const CFGAS_GEPath& path,
209 const CFX_Matrix& matrix) {
210 if (m_info.strokeColor.GetType() != CFGAS_GEColor::Solid)
211 return;
212
213 CFX_Matrix m = m_info.CTM;
214 m.Concat(matrix);
215 m_renderDevice->DrawPath(path.GetPath(), &m, &m_info.graphState, 0x0,
216 m_info.strokeColor.GetArgb(),
217 CFX_FillRenderOptions());
218 }
219
RenderDeviceFillPath(const CFGAS_GEPath & path,CFX_FillRenderOptions::FillType fill_type,const CFX_Matrix & matrix)220 void CFGAS_GEGraphics::RenderDeviceFillPath(
221 const CFGAS_GEPath& path,
222 CFX_FillRenderOptions::FillType fill_type,
223 const CFX_Matrix& matrix) {
224 CFX_Matrix m = m_info.CTM;
225 m.Concat(matrix);
226
227 const CFX_FillRenderOptions fill_options(fill_type);
228 switch (m_info.fillColor.GetType()) {
229 case CFGAS_GEColor::Solid:
230 m_renderDevice->DrawPath(path.GetPath(), &m, &m_info.graphState,
231 m_info.fillColor.GetArgb(), 0x0, fill_options);
232 return;
233 case CFGAS_GEColor::Pattern:
234 FillPathWithPattern(path, fill_options, m);
235 return;
236 case CFGAS_GEColor::Shading:
237 FillPathWithShading(path, fill_options, m);
238 return;
239 default:
240 return;
241 }
242 }
243
FillPathWithPattern(const CFGAS_GEPath & path,const CFX_FillRenderOptions & fill_options,const CFX_Matrix & matrix)244 void CFGAS_GEGraphics::FillPathWithPattern(
245 const CFGAS_GEPath& path,
246 const CFX_FillRenderOptions& fill_options,
247 const CFX_Matrix& matrix) {
248 RetainPtr<const CFX_DIBitmap> bitmap = m_renderDevice->GetBitmap();
249 int32_t width = bitmap->GetWidth();
250 int32_t height = bitmap->GetHeight();
251 auto bmp = pdfium::MakeRetain<CFX_DIBitmap>();
252 // TODO(crbug.com/355630556): Consider adding support for
253 // `FXDIB_Format::kBgraPremul`
254 CHECK(bmp->Create(width, height, FXDIB_Format::kBgra));
255 m_renderDevice->GetDIBits(bmp, 0, 0);
256
257 CFGAS_GEPattern::HatchStyle hatchStyle =
258 m_info.fillColor.GetPattern()->GetHatchStyle();
259 const FX_HATCHDATA& data =
260 GetHatchBitmapData(static_cast<size_t>(hatchStyle));
261
262 auto mask = pdfium::MakeRetain<CFX_DIBitmap>();
263 CHECK(mask->Create(data.width, data.height, FXDIB_Format::k1bppMask));
264 fxcrt::Copy(
265 pdfium::make_span(data.maskBits).first(mask->GetPitch() * data.height),
266 mask->GetWritableBuffer());
267 const CFX_FloatRect rectf =
268 matrix.TransformRect(path.GetPath().GetBoundingBox());
269 const FX_RECT rect = rectf.ToRoundedFxRect();
270
271 CFX_DefaultRenderDevice device;
272 device.Attach(bmp);
273 device.FillRect(rect, m_info.fillColor.GetPattern()->GetBackArgb());
274 for (int32_t j = rect.bottom; j < rect.top; j += mask->GetHeight()) {
275 for (int32_t i = rect.left; i < rect.right; i += mask->GetWidth()) {
276 device.SetBitMask(mask, i, j,
277 m_info.fillColor.GetPattern()->GetForeArgb());
278 }
279 }
280 CFX_RenderDevice::StateRestorer restorer(m_renderDevice);
281 m_renderDevice->SetClip_PathFill(path.GetPath(), &matrix, fill_options);
282 SetDIBitsWithMatrix(std::move(bmp), CFX_Matrix());
283 }
284
FillPathWithShading(const CFGAS_GEPath & path,const CFX_FillRenderOptions & fill_options,const CFX_Matrix & matrix)285 void CFGAS_GEGraphics::FillPathWithShading(
286 const CFGAS_GEPath& path,
287 const CFX_FillRenderOptions& fill_options,
288 const CFX_Matrix& matrix) {
289 RetainPtr<const CFX_DIBitmap> bitmap = m_renderDevice->GetBitmap();
290 int32_t width = bitmap->GetWidth();
291 int32_t height = bitmap->GetHeight();
292 float start_x = m_info.fillColor.GetShading()->GetBeginPoint().x;
293 float start_y = m_info.fillColor.GetShading()->GetBeginPoint().y;
294 float end_x = m_info.fillColor.GetShading()->GetEndPoint().x;
295 float end_y = m_info.fillColor.GetShading()->GetEndPoint().y;
296 auto bmp = pdfium::MakeRetain<CFX_DIBitmap>();
297 // TODO(crbug.com/355630556): Consider adding support for
298 // `FXDIB_Format::kBgraPremul`
299 CHECK(bmp->Create(width, height, FXDIB_Format::kBgra));
300 m_renderDevice->GetDIBits(bmp, 0, 0);
301 bool result = false;
302 switch (m_info.fillColor.GetShading()->GetType()) {
303 case CFGAS_GEShading::Type::kAxial: {
304 float x_span = end_x - start_x;
305 float y_span = end_y - start_y;
306 float axis_len_square = (x_span * x_span) + (y_span * y_span);
307 for (int32_t row = 0; row < height; row++) {
308 auto dib_buf = bmp->GetWritableScanlineAs<uint32_t>(row);
309 for (int32_t column = 0; column < width; column++) {
310 float scale = 0.0f;
311 if (axis_len_square) {
312 float y = static_cast<float>(row);
313 float x = static_cast<float>(column);
314 scale = (((x - start_x) * x_span) + ((y - start_y) * y_span)) /
315 axis_len_square;
316 if (isnan(scale) || scale < 0.0f) {
317 if (!m_info.fillColor.GetShading()->IsExtendedBegin())
318 continue;
319 scale = 0.0f;
320 } else if (scale > 1.0f) {
321 if (!m_info.fillColor.GetShading()->IsExtendedEnd())
322 continue;
323 scale = 1.0f;
324 }
325 }
326 dib_buf[column] = m_info.fillColor.GetShading()->GetArgb(scale);
327 }
328 }
329 result = true;
330 break;
331 }
332 case CFGAS_GEShading::Type::kRadial: {
333 float start_r = m_info.fillColor.GetShading()->GetBeginRadius();
334 float end_r = m_info.fillColor.GetShading()->GetEndRadius();
335 float a = ((start_x - end_x) * (start_x - end_x)) +
336 ((start_y - end_y) * (start_y - end_y)) -
337 ((start_r - end_r) * (start_r - end_r));
338 for (int32_t row = 0; row < height; row++) {
339 auto dib_buf = bmp->GetWritableScanlineAs<uint32_t>(row);
340 for (int32_t column = 0; column < width; column++) {
341 float x = (float)(column);
342 float y = (float)(row);
343 float b = -2 * (((x - start_x) * (end_x - start_x)) +
344 ((y - start_y) * (end_y - start_y)) +
345 (start_r * (end_r - start_r)));
346 float c = ((x - start_x) * (x - start_x)) +
347 ((y - start_y) * (y - start_y)) - (start_r * start_r);
348 float s;
349 if (a == 0) {
350 s = -c / b;
351 } else {
352 float b2_4ac = (b * b) - 4 * (a * c);
353 if (b2_4ac < 0) {
354 continue;
355 }
356 float root = (sqrt(b2_4ac));
357 float s1;
358 float s2;
359 if (a > 0) {
360 s1 = (-b - root) / (2 * a);
361 s2 = (-b + root) / (2 * a);
362 } else {
363 s2 = (-b - root) / (2 * a);
364 s1 = (-b + root) / (2 * a);
365 }
366 if (s2 <= 1.0f || m_info.fillColor.GetShading()->IsExtendedEnd()) {
367 s = (s2);
368 } else {
369 s = (s1);
370 }
371 if ((start_r) + s * (end_r - start_r) < 0) {
372 continue;
373 }
374 }
375 if (isnan(s) || s < 0.0f) {
376 if (!m_info.fillColor.GetShading()->IsExtendedBegin())
377 continue;
378 s = 0.0f;
379 }
380 if (s > 1.0f) {
381 if (!m_info.fillColor.GetShading()->IsExtendedEnd())
382 continue;
383 s = 1.0f;
384 }
385 dib_buf[column] = m_info.fillColor.GetShading()->GetArgb(s);
386 }
387 }
388 result = true;
389 break;
390 }
391 }
392 if (result) {
393 CFX_RenderDevice::StateRestorer restorer(m_renderDevice);
394 m_renderDevice->SetClip_PathFill(path.GetPath(), &matrix, fill_options);
395 SetDIBitsWithMatrix(std::move(bmp), matrix);
396 }
397 }
398
SetDIBitsWithMatrix(RetainPtr<CFX_DIBBase> source,const CFX_Matrix & matrix)399 void CFGAS_GEGraphics::SetDIBitsWithMatrix(RetainPtr<CFX_DIBBase> source,
400 const CFX_Matrix& matrix) {
401 if (matrix.IsIdentity()) {
402 m_renderDevice->SetDIBits(source, 0, 0);
403 } else {
404 CFX_Matrix m((float)source->GetWidth(), 0, 0, (float)source->GetHeight(), 0,
405 0);
406 m.Concat(matrix);
407 int32_t left;
408 int32_t top;
409 RetainPtr<CFX_DIBitmap> bmp1 = source->FlipImage(false, true);
410 RetainPtr<CFX_DIBitmap> bmp2 = bmp1->TransformTo(m, &left, &top);
411 m_renderDevice->SetDIBits(bmp2, left, top);
412 }
413 }
414
415 CFGAS_GEGraphics::TInfo::TInfo() = default;
416
417 CFGAS_GEGraphics::TInfo::TInfo(const TInfo& info) = default;
418
419 CFGAS_GEGraphics::TInfo& CFGAS_GEGraphics::TInfo::operator=(
420 const TInfo& other) = default;
421
StateRestorer(CFGAS_GEGraphics * graphics)422 CFGAS_GEGraphics::StateRestorer::StateRestorer(CFGAS_GEGraphics* graphics)
423 : graphics_(graphics) {
424 graphics_->SaveGraphState();
425 }
426
~StateRestorer()427 CFGAS_GEGraphics::StateRestorer::~StateRestorer() {
428 graphics_->RestoreGraphState();
429 }
430