1 // Copyright 2020 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 "core/fpdfapi/render/cpdf_rendertiling.h"
8
9 #include <limits>
10 #include <memory>
11
12 #include "core/fpdfapi/page/cpdf_form.h"
13 #include "core/fpdfapi/page/cpdf_pageimagecache.h"
14 #include "core/fpdfapi/page/cpdf_tilingpattern.h"
15 #include "core/fpdfapi/parser/cpdf_document.h"
16 #include "core/fpdfapi/render/cpdf_rendercontext.h"
17 #include "core/fpdfapi/render/cpdf_renderoptions.h"
18 #include "core/fpdfapi/render/cpdf_renderstatus.h"
19 #include "core/fxcrt/fx_safe_types.h"
20 #include "core/fxge/cfx_defaultrenderdevice.h"
21
22 namespace {
23
DrawPatternBitmap(CPDF_Document * pDoc,CPDF_PageImageCache * pCache,CPDF_TilingPattern * pPattern,CPDF_Form * pPatternForm,const CFX_Matrix & mtObject2Device,int width,int height,const CPDF_RenderOptions::Options & draw_options)24 RetainPtr<CFX_DIBitmap> DrawPatternBitmap(
25 CPDF_Document* pDoc,
26 CPDF_PageImageCache* pCache,
27 CPDF_TilingPattern* pPattern,
28 CPDF_Form* pPatternForm,
29 const CFX_Matrix& mtObject2Device,
30 int width,
31 int height,
32 const CPDF_RenderOptions::Options& draw_options) {
33 auto pBitmap = pdfium::MakeRetain<CFX_DIBitmap>();
34 if (!pBitmap->Create(width, height,
35 pPattern->colored() ? FXDIB_Format::kArgb
36 : FXDIB_Format::k8bppMask)) {
37 return nullptr;
38 }
39 CFX_DefaultRenderDevice bitmap_device;
40 bitmap_device.AttachWithBackdropAndGroupKnockout(
41 pBitmap, /*pBackdropBitmap=*/nullptr, /*bGroupKnockout=*/true);
42 CFX_FloatRect cell_bbox =
43 pPattern->pattern_to_form().TransformRect(pPattern->bbox());
44 cell_bbox = mtObject2Device.TransformRect(cell_bbox);
45 CFX_FloatRect bitmap_rect(0.0f, 0.0f, width, height);
46 CFX_Matrix mtAdjust;
47 mtAdjust.MatchRect(bitmap_rect, cell_bbox);
48
49 CFX_Matrix mtPattern2Bitmap = mtObject2Device * mtAdjust;
50 CPDF_RenderOptions options;
51 if (!pPattern->colored())
52 options.SetColorMode(CPDF_RenderOptions::kAlpha);
53
54 options.GetOptions() = draw_options;
55 options.GetOptions().bForceHalftone = true;
56
57 CPDF_RenderContext context(pDoc, nullptr, pCache);
58 context.AppendLayer(pPatternForm, mtPattern2Bitmap);
59 context.Render(&bitmap_device, nullptr, &options, nullptr);
60
61 #if defined(_SKIA_SUPPORT_)
62 if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer())
63 pBitmap->UnPreMultiply();
64 #endif // defined(_SKIA_SUPPORT_)
65 return pBitmap;
66 }
67
68 } // namespace
69
70 // static
Draw(CPDF_RenderStatus * pRenderStatus,CPDF_PageObject * pPageObj,CPDF_TilingPattern * pPattern,CPDF_Form * pPatternForm,const CFX_Matrix & mtObj2Device,const FX_RECT & clip_box,bool bStroke)71 RetainPtr<CFX_DIBitmap> CPDF_RenderTiling::Draw(
72 CPDF_RenderStatus* pRenderStatus,
73 CPDF_PageObject* pPageObj,
74 CPDF_TilingPattern* pPattern,
75 CPDF_Form* pPatternForm,
76 const CFX_Matrix& mtObj2Device,
77 const FX_RECT& clip_box,
78 bool bStroke) {
79 const CFX_Matrix mtPattern2Device =
80 pPattern->pattern_to_form() * mtObj2Device;
81
82 CFX_FloatRect cell_bbox = mtPattern2Device.TransformRect(pPattern->bbox());
83
84 float ceil_height = std::ceil(cell_bbox.Height());
85 float ceil_width = std::ceil(cell_bbox.Width());
86
87 // Validate the float will fit into the int when the conversion is done.
88 if (!pdfium::base::IsValueInRangeForNumericType<int>(ceil_height) ||
89 !pdfium::base::IsValueInRangeForNumericType<int>(ceil_width)) {
90 return nullptr;
91 }
92
93 int width = static_cast<int>(ceil_width);
94 int height = static_cast<int>(ceil_height);
95 if (width <= 0)
96 width = 1;
97 if (height <= 0)
98 height = 1;
99
100 CFX_FloatRect clip_box_p =
101 mtPattern2Device.GetInverse().TransformRect(CFX_FloatRect(clip_box));
102 int min_col = static_cast<int>(
103 ceil((clip_box_p.left - pPattern->bbox().right) / pPattern->x_step()));
104 int max_col = static_cast<int>(
105 floor((clip_box_p.right - pPattern->bbox().left) / pPattern->x_step()));
106 int min_row = static_cast<int>(
107 ceil((clip_box_p.bottom - pPattern->bbox().top) / pPattern->y_step()));
108 int max_row = static_cast<int>(
109 floor((clip_box_p.top - pPattern->bbox().bottom) / pPattern->y_step()));
110
111 // Make sure we can fit the needed width * height into an int.
112 if (height > std::numeric_limits<int>::max() / width)
113 return nullptr;
114
115 CFX_RenderDevice* pDevice = pRenderStatus->GetRenderDevice();
116 CPDF_RenderContext* pContext = pRenderStatus->GetContext();
117 const CPDF_RenderOptions& options = pRenderStatus->GetRenderOptions();
118 if (width > clip_box.Width() || height > clip_box.Height() ||
119 width * height > clip_box.Width() * clip_box.Height()) {
120 std::unique_ptr<CPDF_GraphicStates> pStates;
121 if (!pPattern->colored())
122 pStates = CPDF_RenderStatus::CloneObjStates(pPageObj, bStroke);
123
124 RetainPtr<const CPDF_Dictionary> pFormResource =
125 pPatternForm->GetDict()->GetDictFor("Resources");
126 for (int col = min_col; col <= max_col; col++) {
127 for (int row = min_row; row <= max_row; row++) {
128 CFX_PointF original = mtPattern2Device.Transform(
129 CFX_PointF(col * pPattern->x_step(), row * pPattern->y_step()));
130 CFX_Matrix matrix = mtObj2Device;
131 matrix.Translate(original.x - mtPattern2Device.e,
132 original.y - mtPattern2Device.f);
133 CFX_RenderDevice::StateRestorer restorer2(pDevice);
134 CPDF_RenderStatus status(pContext, pDevice);
135 status.SetOptions(options);
136 status.SetTransparency(pPatternForm->GetTransparency());
137 status.SetFormResource(pFormResource);
138 status.SetDropObjects(pRenderStatus->GetDropObjects());
139 status.Initialize(pRenderStatus, pStates.get());
140 status.RenderObjectList(pPatternForm, matrix);
141 }
142 }
143 return nullptr;
144 }
145
146 bool bAligned =
147 pPattern->bbox().left == 0 && pPattern->bbox().bottom == 0 &&
148 pPattern->bbox().right == pPattern->x_step() &&
149 pPattern->bbox().top == pPattern->y_step() &&
150 (mtPattern2Device.IsScaled() || mtPattern2Device.Is90Rotated());
151 if (bAligned) {
152 int orig_x = FXSYS_roundf(mtPattern2Device.e);
153 int orig_y = FXSYS_roundf(mtPattern2Device.f);
154 min_col = (clip_box.left - orig_x) / width;
155 if (clip_box.left < orig_x)
156 min_col--;
157
158 max_col = (clip_box.right - orig_x) / width;
159 if (clip_box.right <= orig_x)
160 max_col--;
161
162 min_row = (clip_box.top - orig_y) / height;
163 if (clip_box.top < orig_y)
164 min_row--;
165
166 max_row = (clip_box.bottom - orig_y) / height;
167 if (clip_box.bottom <= orig_y)
168 max_row--;
169 }
170 float left_offset = cell_bbox.left - mtPattern2Device.e;
171 float top_offset = cell_bbox.bottom - mtPattern2Device.f;
172 RetainPtr<CFX_DIBitmap> pPatternBitmap;
173 if (width * height < 16) {
174 RetainPtr<CFX_DIBitmap> pEnlargedBitmap = DrawPatternBitmap(
175 pContext->GetDocument(), pContext->GetPageCache(), pPattern,
176 pPatternForm, mtObj2Device, 8, 8, options.GetOptions());
177 pPatternBitmap = pEnlargedBitmap->StretchTo(
178 width, height, FXDIB_ResampleOptions(), nullptr);
179 } else {
180 pPatternBitmap = DrawPatternBitmap(
181 pContext->GetDocument(), pContext->GetPageCache(), pPattern,
182 pPatternForm, mtObj2Device, width, height, options.GetOptions());
183 }
184 if (!pPatternBitmap)
185 return nullptr;
186
187 if (options.ColorModeIs(CPDF_RenderOptions::kGray))
188 pPatternBitmap->ConvertColorScale(0, 0xffffff);
189
190 FX_ARGB fill_argb = pRenderStatus->GetFillArgb(pPageObj);
191 int clip_width = clip_box.right - clip_box.left;
192 int clip_height = clip_box.bottom - clip_box.top;
193 auto pScreen = pdfium::MakeRetain<CFX_DIBitmap>();
194 if (!pScreen->Create(clip_width, clip_height, FXDIB_Format::kArgb))
195 return nullptr;
196
197 pdfium::span<const uint8_t> src_buf = pPatternBitmap->GetBuffer();
198 for (int col = min_col; col <= max_col; col++) {
199 for (int row = min_row; row <= max_row; row++) {
200 int start_x;
201 int start_y;
202 if (bAligned) {
203 start_x =
204 FXSYS_roundf(mtPattern2Device.e) + col * width - clip_box.left;
205 start_y =
206 FXSYS_roundf(mtPattern2Device.f) + row * height - clip_box.top;
207 } else {
208 CFX_PointF original = mtPattern2Device.Transform(
209 CFX_PointF(col * pPattern->x_step(), row * pPattern->y_step()));
210
211 FX_SAFE_INT32 safeStartX = FXSYS_roundf(original.x + left_offset);
212 FX_SAFE_INT32 safeStartY = FXSYS_roundf(original.y + top_offset);
213
214 safeStartX -= clip_box.left;
215 safeStartY -= clip_box.top;
216 if (!safeStartX.IsValid() || !safeStartY.IsValid())
217 return nullptr;
218
219 start_x = safeStartX.ValueOrDie();
220 start_y = safeStartY.ValueOrDie();
221 }
222 if (width == 1 && height == 1) {
223 if (start_x < 0 || start_x >= clip_box.Width() || start_y < 0 ||
224 start_y >= clip_box.Height()) {
225 continue;
226 }
227 uint32_t* dest_buf = reinterpret_cast<uint32_t*>(
228 pScreen->GetWritableScanline(start_y).subspan(start_x * 4).data());
229 if (pPattern->colored()) {
230 const auto* src_buf32 =
231 reinterpret_cast<const uint32_t*>(src_buf.data());
232 *dest_buf = *src_buf32;
233 } else {
234 *dest_buf = (*(src_buf.data()) << 24) | (fill_argb & 0xffffff);
235 }
236 } else {
237 if (pPattern->colored()) {
238 pScreen->CompositeBitmap(start_x, start_y, width, height,
239 pPatternBitmap, 0, 0, BlendMode::kNormal,
240 nullptr, false);
241 } else {
242 pScreen->CompositeMask(start_x, start_y, width, height,
243 pPatternBitmap, fill_argb, 0, 0,
244 BlendMode::kNormal, nullptr, false);
245 }
246 }
247 }
248 }
249 return pScreen;
250 }
251