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/numerics/safe_conversions.h"
26 #include "core/fxcrt/span.h"
27 #include "core/fxcrt/stl_util.h"
28 #include "core/fxge/cfx_fillrenderoptions.h"
29 #include "core/fxge/cfx_path.h"
30 #include "fpdfsdk/cpdfsdk_helpers.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").unsigned_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 }
303
304 FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV
FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object)305 FPDFPageObj_GetClipPath(FPDF_PAGEOBJECT page_object) {
306 CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
307 if (!pPageObj)
308 return nullptr;
309
310 return FPDFClipPathFromCPDFClipPath(&pPageObj->mutable_clip_path());
311 }
312
FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path)313 FPDF_EXPORT int FPDF_CALLCONV FPDFClipPath_CountPaths(FPDF_CLIPPATH clip_path) {
314 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
315 if (!pClipPath || !pClipPath->HasRef())
316 return -1;
317
318 return pdfium::checked_cast<int>(pClipPath->GetPathCount());
319 }
320
321 FPDF_EXPORT int FPDF_CALLCONV
FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path,int path_index)322 FPDFClipPath_CountPathSegments(FPDF_CLIPPATH clip_path, int path_index) {
323 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
324 if (!pClipPath || !pClipPath->HasRef())
325 return -1;
326
327 if (path_index < 0 ||
328 static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
329 return -1;
330 }
331
332 return fxcrt::CollectionSize<int>(pClipPath->GetPath(path_index).GetPoints());
333 }
334
335 FPDF_EXPORT FPDF_PATHSEGMENT FPDF_CALLCONV
FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,int path_index,int segment_index)336 FPDFClipPath_GetPathSegment(FPDF_CLIPPATH clip_path,
337 int path_index,
338 int segment_index) {
339 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clip_path);
340 if (!pClipPath || !pClipPath->HasRef())
341 return nullptr;
342
343 if (path_index < 0 ||
344 static_cast<size_t>(path_index) >= pClipPath->GetPathCount()) {
345 return nullptr;
346 }
347
348 pdfium::span<const CFX_Path::Point> points =
349 pClipPath->GetPath(path_index).GetPoints();
350 if (!fxcrt::IndexInBounds(points, segment_index))
351 return nullptr;
352
353 return FPDFPathSegmentFromFXPathPoint(&points[segment_index]);
354 }
355
FPDF_CreateClipPath(float left,float bottom,float right,float top)356 FPDF_EXPORT FPDF_CLIPPATH FPDF_CALLCONV FPDF_CreateClipPath(float left,
357 float bottom,
358 float right,
359 float top) {
360 CPDF_Path Path;
361 Path.AppendRect(left, bottom, right, top);
362
363 auto pNewClipPath = std::make_unique<CPDF_ClipPath>();
364 pNewClipPath->AppendPath(Path, CFX_FillRenderOptions::FillType::kEvenOdd);
365
366 // Caller takes ownership.
367 return FPDFClipPathFromCPDFClipPath(pNewClipPath.release());
368 }
369
FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath)370 FPDF_EXPORT void FPDF_CALLCONV FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath) {
371 // Take ownership back from caller and destroy.
372 std::unique_ptr<CPDF_ClipPath>(CPDFClipPathFromFPDFClipPath(clipPath));
373 }
374
FPDFPage_InsertClipPath(FPDF_PAGE page,FPDF_CLIPPATH clipPath)375 FPDF_EXPORT void FPDF_CALLCONV FPDFPage_InsertClipPath(FPDF_PAGE page,
376 FPDF_CLIPPATH clipPath) {
377 CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
378 if (!pPage)
379 return;
380
381 RetainPtr<CPDF_Dictionary> pPageDict = pPage->GetMutableDict();
382 RetainPtr<CPDF_Object> pContentObj = GetPageContent(pPageDict.Get());
383 if (!pContentObj)
384 return;
385
386 fxcrt::ostringstream strClip;
387 CPDF_ClipPath* pClipPath = CPDFClipPathFromFPDFClipPath(clipPath);
388 for (size_t i = 0; i < pClipPath->GetPathCount(); ++i) {
389 CPDF_Path path = pClipPath->GetPath(i);
390 if (path.GetPoints().empty()) {
391 // Empty clipping (totally clipped out)
392 strClip << "0 0 m W n ";
393 } else {
394 OutputPath(strClip, path);
395 if (pClipPath->GetClipType(i) ==
396 CFX_FillRenderOptions::FillType::kWinding) {
397 strClip << "W n\n";
398 } else {
399 strClip << "W* n\n";
400 }
401 }
402 }
403 CPDF_Document* pDoc = pPage->GetDocument();
404 if (!pDoc)
405 return;
406
407 auto pStream = pDoc->NewIndirect<CPDF_Stream>(pDoc->New<CPDF_Dictionary>());
408 pStream->SetDataFromStringstream(&strClip);
409
410 RetainPtr<CPDF_Array> pArray = ToArray(pContentObj);
411 if (pArray) {
412 pArray->InsertNewAt<CPDF_Reference>(0, pDoc, pStream->GetObjNum());
413 } else if (pContentObj->IsStream() && !pContentObj->IsInline()) {
414 auto pContentArray = pDoc->NewIndirect<CPDF_Array>();
415 pContentArray->AppendNew<CPDF_Reference>(pDoc, pStream->GetObjNum());
416 pContentArray->AppendNew<CPDF_Reference>(pDoc, pContentObj->GetObjNum());
417 pPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents, pDoc,
418 pContentArray->GetObjNum());
419 }
420 }
421