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 "public/fpdf_transformpage.h"
8
9 #include <memory>
10 #include <sstream>
11
12 #include "constants/page_object.h"
13 #include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
14 #include "core/fpdfapi/page/cpdf_clippath.h"
15 #include "core/fpdfapi/page/cpdf_page.h"
16 #include "core/fpdfapi/page/cpdf_pageobject.h"
17 #include "core/fpdfapi/page/cpdf_path.h"
18 #include "core/fpdfapi/parser/cpdf_array.h"
19 #include "core/fpdfapi/parser/cpdf_dictionary.h"
20 #include "core/fpdfapi/parser/cpdf_document.h"
21 #include "core/fpdfapi/parser/cpdf_number.h"
22 #include "core/fpdfapi/parser/cpdf_reference.h"
23 #include "core/fpdfapi/parser/cpdf_stream.h"
24 #include "core/fxcrt/fx_string_wrappers.h"
25 #include "core/fxcrt/stl_util.h"
26 #include "core/fxge/cfx_fillrenderoptions.h"
27 #include "core/fxge/cfx_path.h"
28 #include "fpdfsdk/cpdfsdk_helpers.h"
29 #include "third_party/base/containers/span.h"
30 #include "third_party/base/numerics/safe_conversions.h"
31
32 namespace {
33
SetBoundingBox(CPDF_Page * page,const ByteString & key,const CFX_FloatRect & rect)34 void SetBoundingBox(CPDF_Page* page,
35 const ByteString& key,
36 const CFX_FloatRect& rect) {
37 if (!page)
38 return;
39
40 page->GetMutableDict()->SetRectFor(key, rect);
41 page->UpdateDimensions();
42 }
43
GetBoundingBox(const CPDF_Page * page,const ByteString & key,float * left,float * bottom,float * right,float * top)44 bool GetBoundingBox(const CPDF_Page* page,
45 const ByteString& key,
46 float* left,
47 float* bottom,
48 float* right,
49 float* top) {
50 if (!page || !left || !bottom || !right || !top)
51 return false;
52
53 RetainPtr<const CPDF_Array> pArray = page->GetDict()->GetArrayFor(key);
54 if (!pArray)
55 return false;
56
57 *left = pArray->GetFloatAt(0);
58 *bottom = pArray->GetFloatAt(1);
59 *right = pArray->GetFloatAt(2);
60 *top = pArray->GetFloatAt(3);
61 return true;
62 }
63
GetPageContent(CPDF_Dictionary * pPageDict)64 RetainPtr<CPDF_Object> GetPageContent(CPDF_Dictionary* pPageDict) {
65 return pPageDict->GetMutableDirectObjectFor(pdfium::page_object::kContents);
66 }
67
OutputPath(fxcrt::ostringstream & buf,CPDF_Path path)68 void OutputPath(fxcrt::ostringstream& buf, CPDF_Path path) {
69 const CFX_Path* pPath = path.GetObject();
70 if (!pPath)
71 return;
72
73 pdfium::span<const CFX_Path::Point> points = pPath->GetPoints();
74 if (path.IsRect()) {
75 CFX_PointF diff = points[2].m_Point - points[0].m_Point;
76 buf << points[0].m_Point.x << " " << points[0].m_Point.y << " " << diff.x
77 << " " << diff.y << " re\n";
78 return;
79 }
80
81 for (size_t i = 0; i < points.size(); ++i) {
82 buf << points[i].m_Point.x << " " << points[i].m_Point.y;
83 CFX_Path::Point::Type point_type = points[i].m_Type;
84 if (point_type == CFX_Path::Point::Type::kMove) {
85 buf << " m\n";
86 } else if (point_type == CFX_Path::Point::Type::kBezier) {
87 buf << " " << points[i + 1].m_Point.x << " " << points[i + 1].m_Point.y
88 << " " << points[i + 2].m_Point.x << " " << points[i + 2].m_Point.y;
89 buf << " c";
90 if (points[i + 2].m_CloseFigure)
91 buf << " h";
92 buf << "\n";
93
94 i += 2;
95 } else if (point_type == CFX_Path::Point::Type::kLine) {
96 buf << " l";
97 if (points[i].m_CloseFigure)
98 buf << " h";
99 buf << "\n";
100 }
101 }
102 }
103
104 } // namespace
105
FPDFPage_SetMediaBox(FPDF_PAGE page,float left,float bottom,float right,float top)106 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetMediaBox(FPDF_PAGE page,
107 float left,
108 float bottom,
109 float right,
110 float top) {
111 SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kMediaBox,
112 CFX_FloatRect(left, bottom, right, top));
113 }
114
FPDFPage_SetCropBox(FPDF_PAGE page,float left,float bottom,float right,float top)115 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetCropBox(FPDF_PAGE page,
116 float left,
117 float bottom,
118 float right,
119 float top) {
120 SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kCropBox,
121 CFX_FloatRect(left, bottom, right, top));
122 }
123
FPDFPage_SetBleedBox(FPDF_PAGE page,float left,float bottom,float right,float top)124 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetBleedBox(FPDF_PAGE page,
125 float left,
126 float bottom,
127 float right,
128 float top) {
129 SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kBleedBox,
130 CFX_FloatRect(left, bottom, right, top));
131 }
132
FPDFPage_SetTrimBox(FPDF_PAGE page,float left,float bottom,float right,float top)133 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetTrimBox(FPDF_PAGE page,
134 float left,
135 float bottom,
136 float right,
137 float top) {
138 SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kTrimBox,
139 CFX_FloatRect(left, bottom, right, top));
140 }
141
FPDFPage_SetArtBox(FPDF_PAGE page,float left,float bottom,float right,float top)142 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_SetArtBox(FPDF_PAGE page,
143 float left,
144 float bottom,
145 float right,
146 float top) {
147 SetBoundingBox(CPDFPageFromFPDFPage(page), pdfium::page_object::kArtBox,
148 CFX_FloatRect(left, bottom, right, top));
149 }
150
FPDFPage_GetMediaBox(FPDF_PAGE page,float * left,float * bottom,float * right,float * top)151 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetMediaBox(FPDF_PAGE page,
152 float* left,
153 float* bottom,
154 float* right,
155 float* top) {
156 return GetBoundingBox(CPDFPageFromFPDFPage(page),
157 pdfium::page_object::kMediaBox, left, bottom, right,
158 top);
159 }
160
FPDFPage_GetCropBox(FPDF_PAGE page,float * left,float * bottom,float * right,float * top)161 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetCropBox(FPDF_PAGE page,
162 float* left,
163 float* bottom,
164 float* right,
165 float* top) {
166 return GetBoundingBox(CPDFPageFromFPDFPage(page),
167 pdfium::page_object::kCropBox, left, bottom, right,
168 top);
169 }
170
FPDFPage_GetBleedBox(FPDF_PAGE page,float * left,float * bottom,float * right,float * top)171 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetBleedBox(FPDF_PAGE page,
172 float* left,
173 float* bottom,
174 float* right,
175 float* top) {
176 return GetBoundingBox(CPDFPageFromFPDFPage(page),
177 pdfium::page_object::kBleedBox, left, bottom, right,
178 top);
179 }
180
FPDFPage_GetTrimBox(FPDF_PAGE page,float * left,float * bottom,float * right,float * top)181 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetTrimBox(FPDF_PAGE page,
182 float* left,
183 float* bottom,
184 float* right,
185 float* top) {
186 return GetBoundingBox(CPDFPageFromFPDFPage(page),
187 pdfium::page_object::kTrimBox, left, bottom, right,
188 top);
189 }
190
FPDFPage_GetArtBox(FPDF_PAGE page,float * left,float * bottom,float * right,float * top)191 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPage_GetArtBox(FPDF_PAGE page,
192 float* left,
193 float* bottom,
194 float* right,
195 float* top) {
196 return GetBoundingBox(CPDFPageFromFPDFPage(page),
197 pdfium::page_object::kArtBox, left, bottom, right, top);
198 }
199
200 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFPage_TransFormWithClip(FPDF_PAGE page,const FS_MATRIX * matrix,const FS_RECTF * clipRect)201 FPDFPage_TransFormWithClip(FPDF_PAGE page,
202 const FS_MATRIX* matrix,
203 const FS_RECTF* clipRect) {
204 if (!matrix && !clipRect)
205 return false;
206
207 CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
208 if (!pPage)
209 return false;
210
211 RetainPtr<CPDF_Dictionary> pPageDict = pPage->GetMutableDict();
212 RetainPtr<CPDF_Object> pContentObj = GetPageContent(pPageDict.Get());
213 if (!pContentObj)
214 return false;
215
216 CPDF_Document* pDoc = pPage->GetDocument();
217 if (!pDoc)
218 return false;
219
220 fxcrt::ostringstream text_buf;
221 text_buf << "q ";
222
223 if (clipRect) {
224 CFX_FloatRect rect = CFXFloatRectFromFSRectF(*clipRect);
225 rect.Normalize();
226 WriteRect(text_buf, rect) << " re W* n ";
227 }
228 if (matrix)
229 WriteMatrix(text_buf, CFXMatrixFromFSMatrix(*matrix)) << " cm ";
230
231 auto pStream = pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
232 pStream->SetDataFromStringstream(&text_buf);
233
234 auto pEndStream =
235 pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
236 pEndStream->SetData(ByteStringView(" Q").raw_span());
237
238 RetainPtr<CPDF_Array> pContentArray = ToArray(pContentObj);
239 if (pContentArray) {
240 pContentArray->InsertNewAt<CPDF_Reference>(0, pDoc, pStream->GetObjNum());
241 pContentArray->AppendNew<CPDF_Reference>(pDoc, pEndStream->GetObjNum());
242 } else if (pContentObj->IsStream() && !pContentObj->IsInline()) {
243 pContentArray = pDoc->NewIndirect<CPDF_Array>();
244 pContentArray->AppendNew<CPDF_Reference>(pDoc, pStream->GetObjNum());
245 pContentArray->AppendNew<CPDF_Reference>(pDoc, pContentObj->GetObjNum());
246 pContentArray->AppendNew<CPDF_Reference>(pDoc, pEndStream->GetObjNum());
247 pPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents, pDoc,
248 pContentArray->GetObjNum());
249 }
250
251 // Need to transform the patterns as well.
252 RetainPtr<const CPDF_Dictionary> pRes =
253 pPageDict->GetDictFor(pdfium::page_object::kResources);
254 if (!pRes)
255 return true;
256
257 RetainPtr<const CPDF_Dictionary> pPatternDict = pRes->GetDictFor("Pattern");
258 if (!pPatternDict)
259 return true;
260
261 CPDF_DictionaryLocker locker(pPatternDict);
262 for (const auto& it : locker) {
263 RetainPtr<CPDF_Object> pObj = it.second;
264 if (pObj->IsReference())
265 pObj = pObj->GetMutableDirect();
266
267 RetainPtr<CPDF_Dictionary> pDict;
268 if (pObj->IsDictionary())
269 pDict.Reset(pObj->AsMutableDictionary());
270 else if (CPDF_Stream* pObjStream = pObj->AsMutableStream())
271 pDict = pObjStream->GetMutableDict();
272 else
273 continue;
274
275 if (matrix) {
276 CFX_Matrix m = CFXMatrixFromFSMatrix(*matrix);
277 pDict->SetMatrixFor("Matrix", pDict->GetMatrixFor("Matrix") * m);
278 }
279 }
280
281 return true;
282 }
283
284 FPDF_EXPORT void FPDF_CALLCONV
FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object,double a,double b,double c,double d,double e,double f)285 FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object,
286 double a,
287 double b,
288 double c,
289 double d,
290 double e,
291 double f) {
292 CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
293 if (!pPageObj)
294 return;
295
296 CFX_Matrix matrix((float)a, (float)b, (float)c, (float)d, (float)e, (float)f);
297
298 // Special treatment to shading object, because the ClipPath for shading
299 // object is already transformed.
300 if (!pPageObj->IsShading())
301 pPageObj->TransformClipPath(matrix);
302 pPageObj->TransformGeneralState(matrix);
303 }
304
305 FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV
FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object)306 FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object) {
307 CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
308 if (!pPageObj)
309 return nullptr;
310
311 return FPDFClipPathFromCPDFClipPath(&pPageObj->m_ClipPath);
312 }
313
FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path)314 FPDF_EXPORT int FPDF_CALLCONV FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path) {
315 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
316 if (!pClipPath || !pClipPath->HasRef())
317 return -1;
318
319 return pdfium::base::checked_cast<int>(pClipPath->GetPathCount());
320 }
321
322 FPDF_EXPORT int FPDF_CALLCONV
FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path,int path_index)323 FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, int path_index) {
324 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
325 if (!pClipPath || !pClipPath->HasRef())
326 return -1;
327
328 if (path_index < 0 ||
329 static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
330 return -1;
331 }
332
333 return fxcrt::CollectionSize<int>(pClipPath->GetPath(path_index).GetPoints());
334 }
335
336 FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV
FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,int path_index,int segment_index)337 FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,
338 int path_index,
339 int segment_index) {
340 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
341 if (!pClipPath || !pClipPath->HasRef())
342 return nullptr;
343
344 if (path_index < 0 ||
345 static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
346 return nullptr;
347 }
348
349 pdfium::span<const CFX_Path::Point> points =
350 pClipPath->GetPath(path_index).GetPoints();
351 if (!fxcrt::IndexInBounds(points, segment_index))
352 return nullptr;
353
354 return FPDFPathSegmentFromFXPathPoint(&points[segment_index]);
355 }
356
FPDF_CreateClipPath(float left,float bottom,float right,float top)357 FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV FPDF_CreateClipPath(float left,
358 float bottom,
359 float right,
360 float top) {
361 CPDF_Path Path;
362 Path.AppendRect(left, bottom, right, top);
363
364 auto pNewClipPath = std::make_unique<CPDF_ClipPath>();
365 pNewClipPath->AppendPath(Path, CFX_FillRenderOptions::FillType::kEvenOdd);
366
367 // Caller takes ownership.
368 return FPDFClipPathFromCPDFClipPath(pNewClipPath.release());
369 }
370
FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath)371 FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath) {
372 // Take ownership back from caller and destroy.
373 std::unique_ptr<CPDF_ClipPath>(CPDFClipPathFromFPDFClipPath(clipPath));
374 }
375
FPDFPage_InsertClipPath(FPDF_PAGE page,FPDF_CLIPPATH clipPath)376 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertClipPath(FPDF_PAGE page,
377 FPDF_CLIPPATH clipPath) {
378 CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
379 if (!pPage)
380 return;
381
382 RetainPtr<CPDF_Dictionary> pPageDict = pPage->GetMutableDict();
383 RetainPtr<CPDF_Object> pContentObj = GetPageContent(pPageDict.Get());
384 if (!pContentObj)
385 return;
386
387 fxcrt::ostringstream strClip;
388 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clipPath);
389 for (size_t i = 0; i < pClipPath->GetPathCount(); ++i) {
390 CPDF_Path path = pClipPath->GetPath(i);
391 if (path.GetPoints().empty()) {
392 // Empty clipping (totally clipped out)
393 strClip << "0 0 m W n ";
394 } else {
395 OutputPath(strClip, path);
396 if (pClipPath->GetClipType(i) ==
397 CFX_FillRenderOptions::FillType::kWinding) {
398 strClip << "W n\n";
399 } else {
400 strClip << "W* n\n";
401 }
402 }
403 }
404 CPDF_Document* pDoc = pPage->GetDocument();
405 if (!pDoc)
406 return;
407
408 auto pStream = pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
409 pStream->SetDataFromStringstream(&strClip);
410
411 RetainPtr<CPDF_Array> pArray = ToArray(pContentObj);
412 if (pArray) {
413 pArray->InsertNewAt<CPDF_Reference>(0, pDoc, pStream->GetObjNum());
414 } else if (pContentObj->IsStream() && !pContentObj->IsInline()) {
415 auto pContentArray = pDoc->NewIndirect<CPDF_Array>();
416 pContentArray->AppendNew<CPDF_Reference>(pDoc, pStream->GetObjNum());
417 pContentArray->AppendNew<CPDF_Reference>(pDoc, pContentObj->GetObjNum());
418 pPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents, pDoc,
419 pContentArray->GetObjNum());
420 }
421 }
422