1 // Copyright 2020 PDFium Authors. All rights reserved.
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_tilingpattern.h"
14 #include "core/fpdfapi/parser/cpdf_document.h"
15 #include "core/fpdfapi/render/cpdf_pagerendercache.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_PageRenderCache * 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_PageRenderCache* 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.Attach(pBitmap, false, nullptr, false);
41   pBitmap->Clear(0);
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, &options, nullptr);
60 #if defined(_SKIA_SUPPORT_PATHS_)
61   bitmap_device.Flush(true);
62   pBitmap->UnPreMultiply();
63 #endif
64   return pBitmap;
65 }
66 
67 }  // namespace
68 
69 // 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)70 RetainPtr<CFX_DIBitmap> CPDF_RenderTiling::Draw(
71     CPDF_RenderStatus* pRenderStatus,
72     CPDF_PageObject* pPageObj,
73     CPDF_TilingPattern* pPattern,
74     CPDF_Form* pPatternForm,
75     const CFX_Matrix& mtObj2Device,
76     const FX_RECT& clip_box,
77     bool bStroke) {
78   const CFX_Matrix mtPattern2Device =
79       pPattern->pattern_to_form() * mtObj2Device;
80 
81   CFX_FloatRect cell_bbox = mtPattern2Device.TransformRect(pPattern->bbox());
82 
83   float ceil_height = std::ceil(cell_bbox.Height());
84   float ceil_width = std::ceil(cell_bbox.Width());
85 
86   // Validate the float will fit into the int when the conversion is done.
87   if (!pdfium::base::IsValueInRangeForNumericType<int>(ceil_height) ||
88       !pdfium::base::IsValueInRangeForNumericType<int>(ceil_width)) {
89     return nullptr;
90   }
91 
92   int width = static_cast<int>(ceil_width);
93   int height = static_cast<int>(ceil_height);
94   if (width <= 0)
95     width = 1;
96   if (height <= 0)
97     height = 1;
98 
99   CFX_FloatRect clip_box_p =
100       mtPattern2Device.GetInverse().TransformRect(CFX_FloatRect(clip_box));
101   int min_col = static_cast<int>(
102       ceil((clip_box_p.left - pPattern->bbox().right) / pPattern->x_step()));
103   int max_col = static_cast<int>(
104       floor((clip_box_p.right - pPattern->bbox().left) / pPattern->x_step()));
105   int min_row = static_cast<int>(
106       ceil((clip_box_p.bottom - pPattern->bbox().top) / pPattern->y_step()));
107   int max_row = static_cast<int>(
108       floor((clip_box_p.top - pPattern->bbox().bottom) / pPattern->y_step()));
109 
110   // Make sure we can fit the needed width * height into an int.
111   if (height > std::numeric_limits<int>::max() / width)
112     return nullptr;
113 
114   CFX_RenderDevice* pDevice = pRenderStatus->GetRenderDevice();
115   CPDF_RenderContext* pContext = pRenderStatus->GetContext();
116   const CPDF_RenderOptions& options = pRenderStatus->GetRenderOptions();
117   if (width > clip_box.Width() || height > clip_box.Height() ||
118       width * height > clip_box.Width() * clip_box.Height()) {
119     std::unique_ptr<CPDF_GraphicStates> pStates;
120     if (!pPattern->colored())
121       pStates = CPDF_RenderStatus::CloneObjStates(pPageObj, bStroke);
122 
123     const CPDF_Dictionary* pFormDict = pPatternForm->GetDict();
124     const CPDF_Dictionary* pFormResource = pFormDict->GetDictFor("Resources");
125     for (int col = min_col; col <= max_col; col++) {
126       for (int row = min_row; row <= max_row; row++) {
127         CFX_PointF original = mtPattern2Device.Transform(
128             CFX_PointF(col * pPattern->x_step(), row * pPattern->y_step()));
129         CFX_Matrix matrix = mtObj2Device;
130         matrix.Translate(original.x - mtPattern2Device.e,
131                          original.y - mtPattern2Device.f);
132         CFX_RenderDevice::StateRestorer restorer2(pDevice);
133         CPDF_RenderStatus status(pContext, pDevice);
134         status.SetOptions(options);
135         status.SetTransparency(pPatternForm->GetTransparency());
136         status.SetFormResource(pFormResource);
137         status.SetDropObjects(pRenderStatus->GetDropObjects());
138         status.Initialize(pRenderStatus, pStates.get());
139         status.RenderObjectList(pPatternForm, matrix);
140       }
141     }
142     return nullptr;
143   }
144 
145   bool bAligned =
146       pPattern->bbox().left == 0 && pPattern->bbox().bottom == 0 &&
147       pPattern->bbox().right == pPattern->x_step() &&
148       pPattern->bbox().top == pPattern->y_step() &&
149       (mtPattern2Device.IsScaled() || mtPattern2Device.Is90Rotated());
150   if (bAligned) {
151     int orig_x = FXSYS_roundf(mtPattern2Device.e);
152     int orig_y = FXSYS_roundf(mtPattern2Device.f);
153     min_col = (clip_box.left - orig_x) / width;
154     if (clip_box.left < orig_x)
155       min_col--;
156 
157     max_col = (clip_box.right - orig_x) / width;
158     if (clip_box.right <= orig_x)
159       max_col--;
160 
161     min_row = (clip_box.top - orig_y) / height;
162     if (clip_box.top < orig_y)
163       min_row--;
164 
165     max_row = (clip_box.bottom - orig_y) / height;
166     if (clip_box.bottom <= orig_y)
167       max_row--;
168   }
169   float left_offset = cell_bbox.left - mtPattern2Device.e;
170   float top_offset = cell_bbox.bottom - mtPattern2Device.f;
171   RetainPtr<CFX_DIBitmap> pPatternBitmap;
172   if (width * height < 16) {
173     RetainPtr<CFX_DIBitmap> pEnlargedBitmap = DrawPatternBitmap(
174         pContext->GetDocument(), pContext->GetPageCache(), pPattern,
175         pPatternForm, mtObj2Device, 8, 8, options.GetOptions());
176     pPatternBitmap = pEnlargedBitmap->StretchTo(
177         width, height, FXDIB_ResampleOptions(), nullptr);
178   } else {
179     pPatternBitmap = DrawPatternBitmap(
180         pContext->GetDocument(), pContext->GetPageCache(), pPattern,
181         pPatternForm, mtObj2Device, width, height, options.GetOptions());
182   }
183   if (!pPatternBitmap)
184     return nullptr;
185 
186   if (options.ColorModeIs(CPDF_RenderOptions::kGray))
187     pPatternBitmap->ConvertColorScale(0, 0xffffff);
188 
189   FX_ARGB fill_argb = pRenderStatus->GetFillArgb(pPageObj);
190   int clip_width = clip_box.right - clip_box.left;
191   int clip_height = clip_box.bottom - clip_box.top;
192   auto pScreen = pdfium::MakeRetain<CFX_DIBitmap>();
193   if (!pScreen->Create(clip_width, clip_height, FXDIB_Format::kArgb))
194     return nullptr;
195 
196   pScreen->Clear(0);
197   const uint8_t* const 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->GetBuffer() + pScreen->GetPitch() * start_y + start_x * 4);
229         if (pPattern->colored()) {
230           const auto* src_buf32 = reinterpret_cast<const uint32_t*>(src_buf);
231           *dest_buf = *src_buf32;
232         } else {
233           *dest_buf = (*src_buf << 24) | (fill_argb & 0xffffff);
234         }
235       } else {
236         if (pPattern->colored()) {
237           pScreen->CompositeBitmap(start_x, start_y, width, height,
238                                    pPatternBitmap, 0, 0, BlendMode::kNormal,
239                                    nullptr, false);
240         } else {
241           pScreen->CompositeMask(start_x, start_y, width, height,
242                                  pPatternBitmap, fill_argb, 0, 0,
243                                  BlendMode::kNormal, nullptr, false);
244         }
245       }
246     }
247   }
248   return pScreen;
249 }
250