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