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_flatten.h"
8 
9 #include <algorithm>
10 
11 #include "fpdfsdk/include/fsdk_define.h"
12 
13 typedef CFX_ArrayTemplate<CPDF_Dictionary*> CPDF_ObjectArray;
14 typedef CFX_ArrayTemplate<CPDF_Rect> CPDF_RectArray;
15 
16 enum FPDF_TYPE { MAX, MIN };
17 enum FPDF_VALUE { TOP, LEFT, RIGHT, BOTTOM };
18 
IsValiableRect(CPDF_Rect rect,CPDF_Rect rcPage)19 FX_BOOL IsValiableRect(CPDF_Rect rect, CPDF_Rect rcPage) {
20   if (rect.left - rect.right > 0.000001f || rect.bottom - rect.top > 0.000001f)
21     return FALSE;
22 
23   if (rect.left == 0.0f && rect.top == 0.0f && rect.right == 0.0f &&
24       rect.bottom == 0.0f)
25     return FALSE;
26 
27   if (!rcPage.IsEmpty()) {
28     if (rect.left - rcPage.left < -10.000001f ||
29         rect.right - rcPage.right > 10.000001f ||
30         rect.top - rcPage.top > 10.000001f ||
31         rect.bottom - rcPage.bottom < -10.000001f)
32       return FALSE;
33   }
34 
35   return TRUE;
36 }
37 
GetContentsRect(CPDF_Document * pDoc,CPDF_Dictionary * pDict,CPDF_RectArray * pRectArray)38 FX_BOOL GetContentsRect(CPDF_Document* pDoc,
39                         CPDF_Dictionary* pDict,
40                         CPDF_RectArray* pRectArray) {
41   CPDF_Page* pPDFPage = new CPDF_Page;
42   pPDFPage->Load(pDoc, pDict, FALSE);
43   pPDFPage->ParseContent();
44 
45   FX_POSITION pos = pPDFPage->GetFirstObjectPosition();
46 
47   while (pos) {
48     CPDF_PageObject* pPageObject = pPDFPage->GetNextObject(pos);
49     if (!pPageObject)
50       continue;
51 
52     CPDF_Rect rc;
53     rc.left = pPageObject->m_Left;
54     rc.right = pPageObject->m_Right;
55     rc.bottom = pPageObject->m_Bottom;
56     rc.top = pPageObject->m_Top;
57 
58     if (IsValiableRect(rc, pDict->GetRect("MediaBox"))) {
59       pRectArray->Add(rc);
60     }
61   }
62 
63   delete pPDFPage;
64   return TRUE;
65 }
66 
ParserStream(CPDF_Dictionary * pPageDic,CPDF_Dictionary * pStream,CPDF_RectArray * pRectArray,CPDF_ObjectArray * pObjectArray)67 void ParserStream(CPDF_Dictionary* pPageDic,
68                   CPDF_Dictionary* pStream,
69                   CPDF_RectArray* pRectArray,
70                   CPDF_ObjectArray* pObjectArray) {
71   if (!pStream)
72     return;
73   CPDF_Rect rect;
74   if (pStream->KeyExist("Rect"))
75     rect = pStream->GetRect("Rect");
76   else if (pStream->KeyExist("BBox"))
77     rect = pStream->GetRect("BBox");
78 
79   if (IsValiableRect(rect, pPageDic->GetRect("MediaBox")))
80     pRectArray->Add(rect);
81 
82   pObjectArray->Add(pStream);
83 }
84 
ParserAnnots(CPDF_Document * pSourceDoc,CPDF_Dictionary * pPageDic,CPDF_RectArray * pRectArray,CPDF_ObjectArray * pObjectArray,int nUsage)85 int ParserAnnots(CPDF_Document* pSourceDoc,
86                  CPDF_Dictionary* pPageDic,
87                  CPDF_RectArray* pRectArray,
88                  CPDF_ObjectArray* pObjectArray,
89                  int nUsage) {
90   if (!pSourceDoc || !pPageDic)
91     return FLATTEN_FAIL;
92 
93   GetContentsRect(pSourceDoc, pPageDic, pRectArray);
94   CPDF_Array* pAnnots = pPageDic->GetArray("Annots");
95   if (!pAnnots)
96     return FLATTEN_NOTHINGTODO;
97 
98   FX_DWORD dwSize = pAnnots->GetCount();
99   for (int i = 0; i < (int)dwSize; i++) {
100     CPDF_Dictionary* pAnnotDic = ToDictionary(pAnnots->GetElementValue(i));
101     if (!pAnnotDic)
102       continue;
103 
104     CFX_ByteString sSubtype = pAnnotDic->GetString("Subtype");
105     if (sSubtype == "Popup")
106       continue;
107 
108     int nAnnotFlag = pAnnotDic->GetInteger("F");
109     if (nAnnotFlag & ANNOTFLAG_HIDDEN)
110       continue;
111 
112     if (nUsage == FLAT_NORMALDISPLAY) {
113       if (nAnnotFlag & ANNOTFLAG_INVISIBLE)
114         continue;
115 
116       ParserStream(pPageDic, pAnnotDic, pRectArray, pObjectArray);
117     } else {
118       if (nAnnotFlag & ANNOTFLAG_PRINT)
119         ParserStream(pPageDic, pAnnotDic, pRectArray, pObjectArray);
120     }
121   }
122   return FLATTEN_SUCCESS;
123 }
124 
GetMinMaxValue(CPDF_RectArray & array,FPDF_TYPE type,FPDF_VALUE value)125 FX_FLOAT GetMinMaxValue(CPDF_RectArray& array,
126                         FPDF_TYPE type,
127                         FPDF_VALUE value) {
128   int nRects = array.GetSize();
129   FX_FLOAT fRet = 0.0f;
130 
131   if (nRects <= 0)
132     return 0.0f;
133 
134   FX_FLOAT* pArray = new FX_FLOAT[nRects];
135   switch (value) {
136     case LEFT: {
137       for (int i = 0; i < nRects; i++)
138         pArray[i] = CPDF_Rect(array.GetAt(i)).left;
139 
140       break;
141     }
142     case TOP: {
143       for (int i = 0; i < nRects; i++)
144         pArray[i] = CPDF_Rect(array.GetAt(i)).top;
145 
146       break;
147     }
148     case RIGHT: {
149       for (int i = 0; i < nRects; i++)
150         pArray[i] = CPDF_Rect(array.GetAt(i)).right;
151 
152       break;
153     }
154     case BOTTOM: {
155       for (int i = 0; i < nRects; i++)
156         pArray[i] = CPDF_Rect(array.GetAt(i)).bottom;
157 
158       break;
159     }
160     default:
161       break;
162   }
163   fRet = pArray[0];
164   if (type == MAX) {
165     for (int i = 1; i < nRects; i++)
166       if (fRet <= pArray[i])
167         fRet = pArray[i];
168   } else {
169     for (int i = 1; i < nRects; i++)
170       if (fRet >= pArray[i])
171         fRet = pArray[i];
172   }
173   delete[] pArray;
174   return fRet;
175 }
176 
CalculateRect(CPDF_RectArray * pRectArray)177 CPDF_Rect CalculateRect(CPDF_RectArray* pRectArray) {
178   CPDF_Rect rcRet;
179 
180   rcRet.left = GetMinMaxValue(*pRectArray, MIN, LEFT);
181   rcRet.top = GetMinMaxValue(*pRectArray, MAX, TOP);
182   rcRet.right = GetMinMaxValue(*pRectArray, MAX, RIGHT);
183   rcRet.bottom = GetMinMaxValue(*pRectArray, MIN, BOTTOM);
184 
185   return rcRet;
186 }
187 
SetPageContents(CFX_ByteString key,CPDF_Dictionary * pPage,CPDF_Document * pDocument)188 void SetPageContents(CFX_ByteString key,
189                      CPDF_Dictionary* pPage,
190                      CPDF_Document* pDocument) {
191   CPDF_Object* pContentsObj = pPage->GetStream("Contents");
192   if (!pContentsObj) {
193     pContentsObj = pPage->GetArray("Contents");
194   }
195 
196   if (!pContentsObj) {
197     // Create a new contents dictionary
198     if (!key.IsEmpty()) {
199       CPDF_Stream* pNewContents = new CPDF_Stream(NULL, 0, new CPDF_Dictionary);
200       pPage->SetAtReference("Contents", pDocument,
201                             pDocument->AddIndirectObject(pNewContents));
202 
203       CFX_ByteString sStream;
204       sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
205       pNewContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE,
206                             FALSE);
207     }
208     return;
209   }
210 
211   int iType = pContentsObj->GetType();
212   CPDF_Array* pContentsArray = NULL;
213 
214   switch (iType) {
215     case PDFOBJ_STREAM: {
216       pContentsArray = new CPDF_Array;
217       CPDF_Stream* pContents = pContentsObj->AsStream();
218       FX_DWORD dwObjNum = pDocument->AddIndirectObject(pContents);
219       CPDF_StreamAcc acc;
220       acc.LoadAllData(pContents);
221       CFX_ByteString sStream = "q\n";
222       CFX_ByteString sBody =
223           CFX_ByteString((const FX_CHAR*)acc.GetData(), acc.GetSize());
224       sStream = sStream + sBody + "\nQ";
225       pContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE,
226                          FALSE);
227       pContentsArray->AddReference(pDocument, dwObjNum);
228       break;
229     }
230 
231     case PDFOBJ_ARRAY: {
232       pContentsArray = pContentsObj->AsArray();
233       break;
234     }
235     default:
236       break;
237   }
238 
239   if (!pContentsArray)
240     return;
241 
242   FX_DWORD dwObjNum = pDocument->AddIndirectObject(pContentsArray);
243   pPage->SetAtReference("Contents", pDocument, dwObjNum);
244 
245   if (!key.IsEmpty()) {
246     CPDF_Stream* pNewContents = new CPDF_Stream(NULL, 0, new CPDF_Dictionary);
247     dwObjNum = pDocument->AddIndirectObject(pNewContents);
248     pContentsArray->AddReference(pDocument, dwObjNum);
249 
250     CFX_ByteString sStream;
251     sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str());
252     pNewContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE,
253                           FALSE);
254   }
255 }
256 
GetMatrix(CPDF_Rect rcAnnot,CPDF_Rect rcStream,const CFX_Matrix & matrix)257 CFX_Matrix GetMatrix(CPDF_Rect rcAnnot,
258                      CPDF_Rect rcStream,
259                      const CFX_Matrix& matrix) {
260   if (rcStream.IsEmpty())
261     return CFX_Matrix();
262 
263   matrix.TransformRect(rcStream);
264   rcStream.Normalize();
265 
266   FX_FLOAT a = rcAnnot.Width() / rcStream.Width();
267   FX_FLOAT d = rcAnnot.Height() / rcStream.Height();
268 
269   FX_FLOAT e = rcAnnot.left - rcStream.left * a;
270   FX_FLOAT f = rcAnnot.bottom - rcStream.bottom * d;
271   return CFX_Matrix(a, 0, 0, d, e, f);
272 }
273 
GetOffset(FX_FLOAT & fa,FX_FLOAT & fd,FX_FLOAT & fe,FX_FLOAT & ff,CPDF_Rect rcAnnot,CPDF_Rect rcStream,const CFX_Matrix & matrix)274 void GetOffset(FX_FLOAT& fa,
275                FX_FLOAT& fd,
276                FX_FLOAT& fe,
277                FX_FLOAT& ff,
278                CPDF_Rect rcAnnot,
279                CPDF_Rect rcStream,
280                const CFX_Matrix& matrix) {
281   FX_FLOAT fStreamWidth = 0.0f;
282   FX_FLOAT fStreamHeight = 0.0f;
283 
284   if (matrix.a != 0 && matrix.d != 0) {
285     fStreamWidth = rcStream.right - rcStream.left;
286     fStreamHeight = rcStream.top - rcStream.bottom;
287   } else {
288     fStreamWidth = rcStream.top - rcStream.bottom;
289     fStreamHeight = rcStream.right - rcStream.left;
290   }
291 
292   FX_FLOAT x1 =
293       matrix.a * rcStream.left + matrix.c * rcStream.bottom + matrix.e;
294   FX_FLOAT y1 =
295       matrix.b * rcStream.left + matrix.d * rcStream.bottom + matrix.f;
296   FX_FLOAT x2 = matrix.a * rcStream.left + matrix.c * rcStream.top + matrix.e;
297   FX_FLOAT y2 = matrix.b * rcStream.left + matrix.d * rcStream.top + matrix.f;
298   FX_FLOAT x3 =
299       matrix.a * rcStream.right + matrix.c * rcStream.bottom + matrix.e;
300   FX_FLOAT y3 =
301       matrix.b * rcStream.right + matrix.d * rcStream.bottom + matrix.f;
302   FX_FLOAT x4 = matrix.a * rcStream.right + matrix.c * rcStream.top + matrix.e;
303   FX_FLOAT y4 = matrix.b * rcStream.right + matrix.d * rcStream.top + matrix.f;
304 
305   FX_FLOAT left = std::min(std::min(x1, x2), std::min(x3, x4));
306   FX_FLOAT bottom = std::min(std::min(y1, y2), std::min(y3, y4));
307 
308   fa = (rcAnnot.right - rcAnnot.left) / fStreamWidth;
309   fd = (rcAnnot.top - rcAnnot.bottom) / fStreamHeight;
310   fe = rcAnnot.left - left * fa;
311   ff = rcAnnot.bottom - bottom * fd;
312 }
313 
FPDFPage_Flatten(FPDF_PAGE page,int nFlag)314 DLLEXPORT int STDCALL FPDFPage_Flatten(FPDF_PAGE page, int nFlag) {
315   CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
316   if (!page) {
317     return FLATTEN_FAIL;
318   }
319 
320   CPDF_Document* pDocument = pPage->m_pDocument;
321   CPDF_Dictionary* pPageDict = pPage->m_pFormDict;
322 
323   if (!pDocument || !pPageDict) {
324     return FLATTEN_FAIL;
325   }
326 
327   CPDF_ObjectArray ObjectArray;
328   CPDF_RectArray RectArray;
329 
330   int iRet = FLATTEN_FAIL;
331   iRet = ParserAnnots(pDocument, pPageDict, &RectArray, &ObjectArray, nFlag);
332   if (iRet == FLATTEN_NOTHINGTODO || iRet == FLATTEN_FAIL)
333     return iRet;
334 
335   CPDF_Rect rcOriginalCB;
336   CPDF_Rect rcMerger = CalculateRect(&RectArray);
337   CPDF_Rect rcOriginalMB = pPageDict->GetRect("MediaBox");
338 
339   if (pPageDict->KeyExist("CropBox"))
340     rcOriginalMB = pPageDict->GetRect("CropBox");
341 
342   if (rcOriginalMB.IsEmpty()) {
343     rcOriginalMB = CPDF_Rect(0.0f, 0.0f, 612.0f, 792.0f);
344   }
345 
346   rcMerger.left =
347       rcMerger.left < rcOriginalMB.left ? rcOriginalMB.left : rcMerger.left;
348   rcMerger.right =
349       rcMerger.right > rcOriginalMB.right ? rcOriginalMB.right : rcMerger.right;
350   rcMerger.top =
351       rcMerger.top > rcOriginalMB.top ? rcOriginalMB.top : rcMerger.top;
352   rcMerger.bottom = rcMerger.bottom < rcOriginalMB.bottom ? rcOriginalMB.bottom
353                                                           : rcMerger.bottom;
354 
355   if (pPageDict->KeyExist("ArtBox"))
356     rcOriginalCB = pPageDict->GetRect("ArtBox");
357   else
358     rcOriginalCB = rcOriginalMB;
359 
360   if (!rcOriginalMB.IsEmpty()) {
361     CPDF_Array* pMediaBox = new CPDF_Array();
362     pMediaBox->Add(new CPDF_Number(rcOriginalMB.left));
363     pMediaBox->Add(new CPDF_Number(rcOriginalMB.bottom));
364     pMediaBox->Add(new CPDF_Number(rcOriginalMB.right));
365     pMediaBox->Add(new CPDF_Number(rcOriginalMB.top));
366     pPageDict->SetAt("MediaBox", pMediaBox);
367   }
368 
369   if (!rcOriginalCB.IsEmpty()) {
370     CPDF_Array* pCropBox = new CPDF_Array();
371     pCropBox->Add(new CPDF_Number(rcOriginalCB.left));
372     pCropBox->Add(new CPDF_Number(rcOriginalCB.bottom));
373     pCropBox->Add(new CPDF_Number(rcOriginalCB.right));
374     pCropBox->Add(new CPDF_Number(rcOriginalCB.top));
375     pPageDict->SetAt("ArtBox", pCropBox);
376   }
377 
378   CPDF_Dictionary* pRes = pPageDict->GetDict("Resources");
379   if (!pRes) {
380     pRes = new CPDF_Dictionary;
381     pPageDict->SetAt("Resources", pRes);
382   }
383 
384   CPDF_Stream* pNewXObject = new CPDF_Stream(NULL, 0, new CPDF_Dictionary);
385   FX_DWORD dwObjNum = pDocument->AddIndirectObject(pNewXObject);
386   CPDF_Dictionary* pPageXObject = pRes->GetDict("XObject");
387   if (!pPageXObject) {
388     pPageXObject = new CPDF_Dictionary;
389     pRes->SetAt("XObject", pPageXObject);
390   }
391 
392   CFX_ByteString key = "";
393   int nStreams = ObjectArray.GetSize();
394 
395   if (nStreams > 0) {
396     for (int iKey = 0; /*iKey < 100*/; iKey++) {
397       char sExtend[5] = {};
398       FXSYS_itoa(iKey, sExtend, 10);
399       key = CFX_ByteString("FFT") + CFX_ByteString(sExtend);
400 
401       if (!pPageXObject->KeyExist(key))
402         break;
403     }
404   }
405 
406   SetPageContents(key, pPageDict, pDocument);
407 
408   CPDF_Dictionary* pNewXORes = NULL;
409 
410   if (!key.IsEmpty()) {
411     pPageXObject->SetAtReference(key, pDocument, dwObjNum);
412     CPDF_Dictionary* pNewOXbjectDic = pNewXObject->GetDict();
413     pNewXORes = new CPDF_Dictionary;
414     pNewOXbjectDic->SetAt("Resources", pNewXORes);
415     pNewOXbjectDic->SetAtName("Type", "XObject");
416     pNewOXbjectDic->SetAtName("Subtype", "Form");
417     pNewOXbjectDic->SetAtInteger("FormType", 1);
418     pNewOXbjectDic->SetAtName("Name", "FRM");
419     CPDF_Rect rcBBox = pPageDict->GetRect("ArtBox");
420     pNewOXbjectDic->SetAtRect("BBox", rcBBox);
421   }
422 
423   for (int i = 0; i < nStreams; i++) {
424     CPDF_Dictionary* pAnnotDic = ObjectArray.GetAt(i);
425     if (!pAnnotDic)
426       continue;
427 
428     CPDF_Rect rcAnnot = pAnnotDic->GetRect("Rect");
429     rcAnnot.Normalize();
430 
431     CFX_ByteString sAnnotState = pAnnotDic->GetString("AS");
432     CPDF_Dictionary* pAnnotAP = pAnnotDic->GetDict("AP");
433     if (!pAnnotAP)
434       continue;
435 
436     CPDF_Stream* pAPStream = pAnnotAP->GetStream("N");
437     if (!pAPStream) {
438       CPDF_Dictionary* pAPDic = pAnnotAP->GetDict("N");
439       if (!pAPDic)
440         continue;
441 
442       if (!sAnnotState.IsEmpty()) {
443         pAPStream = pAPDic->GetStream(sAnnotState);
444       } else {
445         auto it = pAPDic->begin();
446         if (it != pAPDic->end()) {
447           CPDF_Object* pFirstObj = it->second;
448           if (pFirstObj) {
449             if (pFirstObj->IsReference())
450               pFirstObj = pFirstObj->GetDirect();
451             if (!pFirstObj->IsStream())
452               continue;
453             pAPStream = pFirstObj->AsStream();
454           }
455         }
456       }
457     }
458     if (!pAPStream)
459       continue;
460 
461     CPDF_Dictionary* pAPDic = pAPStream->GetDict();
462     CFX_Matrix matrix = pAPDic->GetMatrix("Matrix");
463 
464     CPDF_Rect rcStream;
465     if (pAPDic->KeyExist("Rect"))
466       rcStream = pAPDic->GetRect("Rect");
467     else if (pAPDic->KeyExist("BBox"))
468       rcStream = pAPDic->GetRect("BBox");
469 
470     if (rcStream.IsEmpty())
471       continue;
472 
473     CPDF_Object* pObj = pAPStream;
474 
475     if (pObj) {
476       CPDF_Dictionary* pObjDic = pObj->GetDict();
477       if (pObjDic) {
478         pObjDic->SetAtName("Type", "XObject");
479         pObjDic->SetAtName("Subtype", "Form");
480       }
481     }
482 
483     CPDF_Dictionary* pXObject = pNewXORes->GetDict("XObject");
484     if (!pXObject) {
485       pXObject = new CPDF_Dictionary;
486       pNewXORes->SetAt("XObject", pXObject);
487     }
488 
489     CFX_ByteString sFormName;
490     sFormName.Format("F%d", i);
491     FX_DWORD dwObjNum = pDocument->AddIndirectObject(pObj);
492     pXObject->SetAtReference(sFormName, pDocument, dwObjNum);
493 
494     CPDF_StreamAcc acc;
495     acc.LoadAllData(pNewXObject);
496 
497     const uint8_t* pData = acc.GetData();
498     CFX_ByteString sStream(pData, acc.GetSize());
499     CFX_ByteString sTemp;
500 
501     if (matrix.IsIdentity()) {
502       matrix.a = 1.0f;
503       matrix.b = 0.0f;
504       matrix.c = 0.0f;
505       matrix.d = 1.0f;
506       matrix.e = 0.0f;
507       matrix.f = 0.0f;
508     }
509 
510     CFX_Matrix m = GetMatrix(rcAnnot, rcStream, matrix);
511     sTemp.Format("q %f 0 0 %f %f %f cm /%s Do Q\n", m.a, m.d, m.e, m.f,
512                  sFormName.c_str());
513     sStream += sTemp;
514 
515     pNewXObject->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE,
516                          FALSE);
517   }
518   pPageDict->RemoveAt("Annots");
519 
520   ObjectArray.RemoveAll();
521   RectArray.RemoveAll();
522 
523   return FLATTEN_SUCCESS;
524 }
525