1// Copyright 2014 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 "public/fpdf_transformpage.h"
8
9#include "fpdfsdk/include/fsdk_define.h"
10
11namespace {
12
13void SetBoundingBox(CPDF_Page* page,
14                    const CFX_ByteStringC& key,
15                    float left,
16                    float bottom,
17                    float right,
18                    float top) {
19  CPDF_Dictionary* pPageDict = page->m_pFormDict;
20  CPDF_Array* pBoundingBoxArray = new CPDF_Array;
21  pBoundingBoxArray->Add(new CPDF_Number(left));
22  pBoundingBoxArray->Add(new CPDF_Number(bottom));
23  pBoundingBoxArray->Add(new CPDF_Number(right));
24  pBoundingBoxArray->Add(new CPDF_Number(top));
25  pPageDict->SetAt(key, pBoundingBoxArray);
26}
27
28FPDF_BOOL GetBoundingBox(CPDF_Page* page,
29                         const CFX_ByteStringC& key,
30                         float* left,
31                         float* bottom,
32                         float* right,
33                         float* top) {
34  CPDF_Dictionary* pPageDict = page->m_pFormDict;
35  CPDF_Array* pArray = pPageDict->GetArray(key);
36  if (!pArray)
37    return FALSE;
38
39  *left = pArray->GetFloat(0);
40  *bottom = pArray->GetFloat(1);
41  *right = pArray->GetFloat(2);
42  *top = pArray->GetFloat(3);
43  return TRUE;
44}
45
46}  // namespace
47
48DLLEXPORT void STDCALL FPDFPage_SetMediaBox(FPDF_PAGE page,
49                                            float left,
50                                            float bottom,
51                                            float right,
52                                            float top) {
53  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
54  if (!pPage)
55    return;
56
57  SetBoundingBox(pPage, "MediaBox", left, bottom, right, top);
58}
59
60DLLEXPORT void STDCALL FPDFPage_SetCropBox(FPDF_PAGE page,
61                                           float left,
62                                           float bottom,
63                                           float right,
64                                           float top) {
65  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
66  if (!pPage)
67    return;
68
69  SetBoundingBox(pPage, "CropBox", left, bottom, right, top);
70}
71
72DLLEXPORT FPDF_BOOL STDCALL FPDFPage_GetMediaBox(FPDF_PAGE page,
73                                                 float* left,
74                                                 float* bottom,
75                                                 float* right,
76                                                 float* top) {
77  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
78  return pPage && GetBoundingBox(pPage, "MediaBox", left, bottom, right, top);
79}
80
81DLLEXPORT FPDF_BOOL STDCALL FPDFPage_GetCropBox(FPDF_PAGE page,
82                                                float* left,
83                                                float* bottom,
84                                                float* right,
85                                                float* top) {
86  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
87  return pPage && GetBoundingBox(pPage, "CropBox", left, bottom, right, top);
88}
89
90DLLEXPORT FPDF_BOOL STDCALL FPDFPage_TransFormWithClip(FPDF_PAGE page,
91                                                       FS_MATRIX* matrix,
92                                                       FS_RECTF* clipRect) {
93  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
94  if (!pPage)
95    return FALSE;
96
97  CFX_ByteTextBuf textBuf;
98  textBuf << "q ";
99  CFX_FloatRect rect(clipRect->left, clipRect->bottom, clipRect->right,
100                     clipRect->top);
101  rect.Normalize();
102  CFX_ByteString bsClipping;
103  bsClipping.Format("%f %f %f %f re W* n ", rect.left, rect.bottom,
104                    rect.Width(), rect.Height());
105  textBuf << bsClipping;
106
107  CFX_ByteString bsMatix;
108  bsMatix.Format("%f %f %f %f %f %f cm ", matrix->a, matrix->b, matrix->c,
109                 matrix->d, matrix->e, matrix->f);
110  textBuf << bsMatix;
111
112  CPDF_Dictionary* pPageDic = pPage->m_pFormDict;
113  CPDF_Object* pContentObj =
114      pPageDic ? pPageDic->GetElement("Contents") : nullptr;
115  if (!pContentObj)
116    pContentObj = pPageDic ? pPageDic->GetArray("Contents") : nullptr;
117  if (!pContentObj)
118    return FALSE;
119
120  CPDF_Dictionary* pDic = new CPDF_Dictionary;
121  CPDF_Stream* pStream = new CPDF_Stream(nullptr, 0, pDic);
122  pStream->SetData(textBuf.GetBuffer(), textBuf.GetSize(), FALSE, FALSE);
123  CPDF_Document* pDoc = pPage->m_pDocument;
124  if (!pDoc)
125    return FALSE;
126  pDoc->AddIndirectObject(pStream);
127
128  pDic = new CPDF_Dictionary;
129  CPDF_Stream* pEndStream = new CPDF_Stream(nullptr, 0, pDic);
130  pEndStream->SetData((const uint8_t*)" Q", 2, FALSE, FALSE);
131  pDoc->AddIndirectObject(pEndStream);
132
133  CPDF_Array* pContentArray = nullptr;
134  if (CPDF_Array* pArray = ToArray(pContentObj)) {
135    pContentArray = pArray;
136    CPDF_Reference* pRef = new CPDF_Reference(pDoc, pStream->GetObjNum());
137    pContentArray->InsertAt(0, pRef);
138    pContentArray->AddReference(pDoc, pEndStream);
139  } else if (CPDF_Reference* pReference = ToReference(pContentObj)) {
140    CPDF_Object* pDirectObj = pReference->GetDirect();
141    if (pDirectObj) {
142      if (CPDF_Array* pArray = pDirectObj->AsArray()) {
143        pContentArray = pArray;
144        CPDF_Reference* pRef = new CPDF_Reference(pDoc, pStream->GetObjNum());
145        pContentArray->InsertAt(0, pRef);
146        pContentArray->AddReference(pDoc, pEndStream);
147      } else if (pDirectObj->IsStream()) {
148        pContentArray = new CPDF_Array();
149        pContentArray->AddReference(pDoc, pStream->GetObjNum());
150        pContentArray->AddReference(pDoc, pDirectObj->GetObjNum());
151        pContentArray->AddReference(pDoc, pEndStream);
152        pPageDic->SetAtReference("Contents", pDoc,
153                                 pDoc->AddIndirectObject(pContentArray));
154      }
155    }
156  }
157
158  // Need to transform the patterns as well.
159  CPDF_Dictionary* pRes = pPageDic->GetDict("Resources");
160  if (pRes) {
161    CPDF_Dictionary* pPattenDict = pRes->GetDict("Pattern");
162    if (pPattenDict) {
163      for (const auto& it : *pPattenDict) {
164        CPDF_Object* pObj = it.second;
165        if (pObj->IsReference())
166          pObj = pObj->GetDirect();
167
168        CPDF_Dictionary* pDict = nullptr;
169        if (pObj->IsDictionary())
170          pDict = pObj->AsDictionary();
171        else if (CPDF_Stream* pStream = pObj->AsStream())
172          pDict = pStream->GetDict();
173        else
174          continue;
175
176        CFX_Matrix m = pDict->GetMatrix("Matrix");
177        CFX_Matrix t = *(CFX_Matrix*)matrix;
178        m.Concat(t);
179        pDict->SetAtMatrix("Matrix", m);
180      }
181    }
182  }
183
184  return TRUE;
185}
186
187DLLEXPORT void STDCALL
188FPDFPageObj_TransformClipPath(FPDF_PAGEOBJECT page_object,
189                              double a,
190                              double b,
191                              double c,
192                              double d,
193                              double e,
194                              double f) {
195  CPDF_PageObject* pPageObj = (CPDF_PageObject*)page_object;
196  if (!pPageObj)
197    return;
198  CFX_Matrix matrix((FX_FLOAT)a, (FX_FLOAT)b, (FX_FLOAT)c, (FX_FLOAT)d,
199                    (FX_FLOAT)e, (FX_FLOAT)f);
200
201  // Special treatment to shading object, because the ClipPath for shading
202  // object is already transformed.
203  if (pPageObj->m_Type != PDFPAGE_SHADING)
204    pPageObj->TransformClipPath(matrix);
205  pPageObj->TransformGeneralState(matrix);
206}
207
208DLLEXPORT FPDF_CLIPPATH STDCALL FPDF_CreateClipPath(float left,
209                                                    float bottom,
210                                                    float right,
211                                                    float top) {
212  CPDF_ClipPath* pNewClipPath = new CPDF_ClipPath();
213  pNewClipPath->GetModify();
214  CPDF_Path Path;
215  Path.GetModify();
216  Path.AppendRect(left, bottom, right, top);
217  pNewClipPath->AppendPath(Path, FXFILL_ALTERNATE, FALSE);
218  return pNewClipPath;
219}
220
221DLLEXPORT void STDCALL FPDF_DestroyClipPath(FPDF_CLIPPATH clipPath) {
222  delete (CPDF_ClipPath*)clipPath;
223}
224
225void OutputPath(CFX_ByteTextBuf& buf, CPDF_Path path) {
226  const CFX_PathData* pPathData = path;
227  if (!pPathData)
228    return;
229
230  FX_PATHPOINT* pPoints = pPathData->GetPoints();
231
232  if (path.IsRect()) {
233    buf << (pPoints[0].m_PointX) << " " << (pPoints[0].m_PointY) << " "
234        << (pPoints[2].m_PointX - pPoints[0].m_PointX) << " "
235        << (pPoints[2].m_PointY - pPoints[0].m_PointY) << " re\n";
236    return;
237  }
238
239  CFX_ByteString temp;
240  for (int i = 0; i < pPathData->GetPointCount(); i++) {
241    buf << (pPoints[i].m_PointX) << " " << (pPoints[i].m_PointY);
242    int point_type = pPoints[i].m_Flag & FXPT_TYPE;
243    if (point_type == FXPT_MOVETO)
244      buf << " m\n";
245    else if (point_type == FXPT_BEZIERTO) {
246      buf << " " << (pPoints[i + 1].m_PointX) << " "
247          << (pPoints[i + 1].m_PointY) << " " << (pPoints[i + 2].m_PointX)
248          << " " << (pPoints[i + 2].m_PointY);
249      if (pPoints[i + 2].m_Flag & FXPT_CLOSEFIGURE)
250        buf << " c h\n";
251      else
252        buf << " c\n";
253      i += 2;
254    } else if (point_type == FXPT_LINETO) {
255      if (pPoints[i].m_Flag & FXPT_CLOSEFIGURE)
256        buf << " l h\n";
257      else
258        buf << " l\n";
259    }
260  }
261}
262
263DLLEXPORT void STDCALL FPDFPage_InsertClipPath(FPDF_PAGE page,
264                                               FPDF_CLIPPATH clipPath) {
265  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
266  if (!pPage)
267    return;
268
269  CPDF_Dictionary* pPageDic = pPage->m_pFormDict;
270  CPDF_Object* pContentObj =
271      pPageDic ? pPageDic->GetElement("Contents") : nullptr;
272  if (!pContentObj)
273    pContentObj = pPageDic ? pPageDic->GetArray("Contents") : nullptr;
274  if (!pContentObj)
275    return;
276
277  CFX_ByteTextBuf strClip;
278  CPDF_ClipPath* pClipPath = (CPDF_ClipPath*)clipPath;
279  FX_DWORD i;
280  for (i = 0; i < pClipPath->GetPathCount(); i++) {
281    CPDF_Path path = pClipPath->GetPath(i);
282    int iClipType = pClipPath->GetClipType(i);
283    if (path.GetPointCount() == 0) {
284      // Empty clipping (totally clipped out)
285      strClip << "0 0 m W n ";
286    } else {
287      OutputPath(strClip, path);
288      if (iClipType == FXFILL_WINDING)
289        strClip << "W n\n";
290      else
291        strClip << "W* n\n";
292    }
293  }
294  CPDF_Dictionary* pDic = new CPDF_Dictionary;
295  CPDF_Stream* pStream = new CPDF_Stream(nullptr, 0, pDic);
296  pStream->SetData(strClip.GetBuffer(), strClip.GetSize(), FALSE, FALSE);
297  CPDF_Document* pDoc = pPage->m_pDocument;
298  if (!pDoc)
299    return;
300  pDoc->AddIndirectObject(pStream);
301
302  CPDF_Array* pContentArray = nullptr;
303  if (CPDF_Array* pArray = ToArray(pContentObj)) {
304    pContentArray = pArray;
305    CPDF_Reference* pRef = new CPDF_Reference(pDoc, pStream->GetObjNum());
306    pContentArray->InsertAt(0, pRef);
307  } else if (CPDF_Reference* pReference = ToReference(pContentObj)) {
308    CPDF_Object* pDirectObj = pReference->GetDirect();
309    if (pDirectObj) {
310      if (CPDF_Array* pArray = pDirectObj->AsArray()) {
311        pContentArray = pArray;
312        CPDF_Reference* pRef = new CPDF_Reference(pDoc, pStream->GetObjNum());
313        pContentArray->InsertAt(0, pRef);
314      } else if (pDirectObj->IsStream()) {
315        pContentArray = new CPDF_Array();
316        pContentArray->AddReference(pDoc, pStream->GetObjNum());
317        pContentArray->AddReference(pDoc, pDirectObj->GetObjNum());
318        pPageDic->SetAtReference("Contents", pDoc,
319                                 pDoc->AddIndirectObject(pContentArray));
320      }
321    }
322  }
323}
324