Home | History | Annotate | Download | only in src
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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