// Copyright 2014 PDFium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "public/fpdf_flatten.h" #include <algorithm> #include "fpdfsdk/include/fsdk_define.h" typedef CFX_ArrayTemplate<CPDF_Dictionary*> CPDF_ObjectArray; typedef CFX_ArrayTemplate<CPDF_Rect> CPDF_RectArray; enum FPDF_TYPE { MAX, MIN }; enum FPDF_VALUE { TOP, LEFT, RIGHT, BOTTOM }; FX_BOOL IsValiableRect(CPDF_Rect rect, CPDF_Rect rcPage) { if (rect.left - rect.right > 0.000001f || rect.bottom - rect.top > 0.000001f) return FALSE; if (rect.left == 0.0f && rect.top == 0.0f && rect.right == 0.0f && rect.bottom == 0.0f) return FALSE; if (!rcPage.IsEmpty()) { if (rect.left - rcPage.left < -10.000001f || rect.right - rcPage.right > 10.000001f || rect.top - rcPage.top > 10.000001f || rect.bottom - rcPage.bottom < -10.000001f) return FALSE; } return TRUE; } FX_BOOL GetContentsRect(CPDF_Document* pDoc, CPDF_Dictionary* pDict, CPDF_RectArray* pRectArray) { CPDF_Page* pPDFPage = new CPDF_Page; pPDFPage->Load(pDoc, pDict, FALSE); pPDFPage->ParseContent(nullptr); FX_POSITION pos = pPDFPage->GetFirstObjectPosition(); while (pos) { CPDF_PageObject* pPageObject = pPDFPage->GetNextObject(pos); if (!pPageObject) continue; CPDF_Rect rc; rc.left = pPageObject->m_Left; rc.right = pPageObject->m_Right; rc.bottom = pPageObject->m_Bottom; rc.top = pPageObject->m_Top; if (IsValiableRect(rc, pDict->GetRectBy("MediaBox"))) { pRectArray->Add(rc); } } delete pPDFPage; return TRUE; } void ParserStream(CPDF_Dictionary* pPageDic, CPDF_Dictionary* pStream, CPDF_RectArray* pRectArray, CPDF_ObjectArray* pObjectArray) { if (!pStream) return; CPDF_Rect rect; if (pStream->KeyExist("Rect")) rect = pStream->GetRectBy("Rect"); else if (pStream->KeyExist("BBox")) rect = pStream->GetRectBy("BBox"); if (IsValiableRect(rect, pPageDic->GetRectBy("MediaBox"))) pRectArray->Add(rect); pObjectArray->Add(pStream); } int ParserAnnots(CPDF_Document* pSourceDoc, CPDF_Dictionary* pPageDic, CPDF_RectArray* pRectArray, CPDF_ObjectArray* pObjectArray, int nUsage) { if (!pSourceDoc || !pPageDic) return FLATTEN_FAIL; GetContentsRect(pSourceDoc, pPageDic, pRectArray); CPDF_Array* pAnnots = pPageDic->GetArrayBy("Annots"); if (!pAnnots) return FLATTEN_NOTHINGTODO; FX_DWORD dwSize = pAnnots->GetCount(); for (int i = 0; i < (int)dwSize; i++) { CPDF_Dictionary* pAnnotDic = ToDictionary(pAnnots->GetElementValue(i)); if (!pAnnotDic) continue; CFX_ByteString sSubtype = pAnnotDic->GetStringBy("Subtype"); if (sSubtype == "Popup") continue; int nAnnotFlag = pAnnotDic->GetIntegerBy("F"); if (nAnnotFlag & ANNOTFLAG_HIDDEN) continue; if (nUsage == FLAT_NORMALDISPLAY) { if (nAnnotFlag & ANNOTFLAG_INVISIBLE) continue; ParserStream(pPageDic, pAnnotDic, pRectArray, pObjectArray); } else { if (nAnnotFlag & ANNOTFLAG_PRINT) ParserStream(pPageDic, pAnnotDic, pRectArray, pObjectArray); } } return FLATTEN_SUCCESS; } FX_FLOAT GetMinMaxValue(CPDF_RectArray& array, FPDF_TYPE type, FPDF_VALUE value) { int nRects = array.GetSize(); FX_FLOAT fRet = 0.0f; if (nRects <= 0) return 0.0f; FX_FLOAT* pArray = new FX_FLOAT[nRects]; switch (value) { case LEFT: { for (int i = 0; i < nRects; i++) pArray[i] = CPDF_Rect(array.GetAt(i)).left; break; } case TOP: { for (int i = 0; i < nRects; i++) pArray[i] = CPDF_Rect(array.GetAt(i)).top; break; } case RIGHT: { for (int i = 0; i < nRects; i++) pArray[i] = CPDF_Rect(array.GetAt(i)).right; break; } case BOTTOM: { for (int i = 0; i < nRects; i++) pArray[i] = CPDF_Rect(array.GetAt(i)).bottom; break; } default: break; } fRet = pArray[0]; if (type == MAX) { for (int i = 1; i < nRects; i++) if (fRet <= pArray[i]) fRet = pArray[i]; } else { for (int i = 1; i < nRects; i++) if (fRet >= pArray[i]) fRet = pArray[i]; } delete[] pArray; return fRet; } CPDF_Rect CalculateRect(CPDF_RectArray* pRectArray) { CPDF_Rect rcRet; rcRet.left = GetMinMaxValue(*pRectArray, MIN, LEFT); rcRet.top = GetMinMaxValue(*pRectArray, MAX, TOP); rcRet.right = GetMinMaxValue(*pRectArray, MAX, RIGHT); rcRet.bottom = GetMinMaxValue(*pRectArray, MIN, BOTTOM); return rcRet; } void SetPageContents(CFX_ByteString key, CPDF_Dictionary* pPage, CPDF_Document* pDocument) { CPDF_Object* pContentsObj = pPage->GetStreamBy("Contents"); if (!pContentsObj) { pContentsObj = pPage->GetArrayBy("Contents"); } if (!pContentsObj) { // Create a new contents dictionary if (!key.IsEmpty()) { CPDF_Stream* pNewContents = new CPDF_Stream(NULL, 0, new CPDF_Dictionary); pPage->SetAtReference("Contents", pDocument, pDocument->AddIndirectObject(pNewContents)); CFX_ByteString sStream; sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str()); pNewContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE, FALSE); } return; } CPDF_Array* pContentsArray = NULL; switch (pContentsObj->GetType()) { case CPDF_Object::STREAM: { pContentsArray = new CPDF_Array; CPDF_Stream* pContents = pContentsObj->AsStream(); FX_DWORD dwObjNum = pDocument->AddIndirectObject(pContents); CPDF_StreamAcc acc; acc.LoadAllData(pContents); CFX_ByteString sStream = "q\n"; CFX_ByteString sBody = CFX_ByteString((const FX_CHAR*)acc.GetData(), acc.GetSize()); sStream = sStream + sBody + "\nQ"; pContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE, FALSE); pContentsArray->AddReference(pDocument, dwObjNum); break; } case CPDF_Object::ARRAY: { pContentsArray = pContentsObj->AsArray(); break; } default: break; } if (!pContentsArray) return; FX_DWORD dwObjNum = pDocument->AddIndirectObject(pContentsArray); pPage->SetAtReference("Contents", pDocument, dwObjNum); if (!key.IsEmpty()) { CPDF_Stream* pNewContents = new CPDF_Stream(NULL, 0, new CPDF_Dictionary); dwObjNum = pDocument->AddIndirectObject(pNewContents); pContentsArray->AddReference(pDocument, dwObjNum); CFX_ByteString sStream; sStream.Format("q 1 0 0 1 0 0 cm /%s Do Q", key.c_str()); pNewContents->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE, FALSE); } } CFX_Matrix GetMatrix(CPDF_Rect rcAnnot, CPDF_Rect rcStream, const CFX_Matrix& matrix) { if (rcStream.IsEmpty()) return CFX_Matrix(); matrix.TransformRect(rcStream); rcStream.Normalize(); FX_FLOAT a = rcAnnot.Width() / rcStream.Width(); FX_FLOAT d = rcAnnot.Height() / rcStream.Height(); FX_FLOAT e = rcAnnot.left - rcStream.left * a; FX_FLOAT f = rcAnnot.bottom - rcStream.bottom * d; return CFX_Matrix(a, 0, 0, d, e, f); } void GetOffset(FX_FLOAT& fa, FX_FLOAT& fd, FX_FLOAT& fe, FX_FLOAT& ff, CPDF_Rect rcAnnot, CPDF_Rect rcStream, const CFX_Matrix& matrix) { FX_FLOAT fStreamWidth = 0.0f; FX_FLOAT fStreamHeight = 0.0f; if (matrix.a != 0 && matrix.d != 0) { fStreamWidth = rcStream.right - rcStream.left; fStreamHeight = rcStream.top - rcStream.bottom; } else { fStreamWidth = rcStream.top - rcStream.bottom; fStreamHeight = rcStream.right - rcStream.left; } FX_FLOAT x1 = matrix.a * rcStream.left + matrix.c * rcStream.bottom + matrix.e; FX_FLOAT y1 = matrix.b * rcStream.left + matrix.d * rcStream.bottom + matrix.f; FX_FLOAT x2 = matrix.a * rcStream.left + matrix.c * rcStream.top + matrix.e; FX_FLOAT y2 = matrix.b * rcStream.left + matrix.d * rcStream.top + matrix.f; FX_FLOAT x3 = matrix.a * rcStream.right + matrix.c * rcStream.bottom + matrix.e; FX_FLOAT y3 = matrix.b * rcStream.right + matrix.d * rcStream.bottom + matrix.f; FX_FLOAT x4 = matrix.a * rcStream.right + matrix.c * rcStream.top + matrix.e; FX_FLOAT y4 = matrix.b * rcStream.right + matrix.d * rcStream.top + matrix.f; FX_FLOAT left = std::min(std::min(x1, x2), std::min(x3, x4)); FX_FLOAT bottom = std::min(std::min(y1, y2), std::min(y3, y4)); fa = (rcAnnot.right - rcAnnot.left) / fStreamWidth; fd = (rcAnnot.top - rcAnnot.bottom) / fStreamHeight; fe = rcAnnot.left - left * fa; ff = rcAnnot.bottom - bottom * fd; } DLLEXPORT int STDCALL FPDFPage_Flatten(FPDF_PAGE page, int nFlag) { CPDF_Page* pPage = CPDFPageFromFPDFPage(page); if (!page) { return FLATTEN_FAIL; } CPDF_Document* pDocument = pPage->m_pDocument; CPDF_Dictionary* pPageDict = pPage->m_pFormDict; if (!pDocument || !pPageDict) { return FLATTEN_FAIL; } CPDF_ObjectArray ObjectArray; CPDF_RectArray RectArray; int iRet = FLATTEN_FAIL; iRet = ParserAnnots(pDocument, pPageDict, &RectArray, &ObjectArray, nFlag); if (iRet == FLATTEN_NOTHINGTODO || iRet == FLATTEN_FAIL) return iRet; CPDF_Rect rcOriginalCB; CPDF_Rect rcMerger = CalculateRect(&RectArray); CPDF_Rect rcOriginalMB = pPageDict->GetRectBy("MediaBox"); if (pPageDict->KeyExist("CropBox")) rcOriginalMB = pPageDict->GetRectBy("CropBox"); if (rcOriginalMB.IsEmpty()) { rcOriginalMB = CPDF_Rect(0.0f, 0.0f, 612.0f, 792.0f); } rcMerger.left = rcMerger.left < rcOriginalMB.left ? rcOriginalMB.left : rcMerger.left; rcMerger.right = rcMerger.right > rcOriginalMB.right ? rcOriginalMB.right : rcMerger.right; rcMerger.top = rcMerger.top > rcOriginalMB.top ? rcOriginalMB.top : rcMerger.top; rcMerger.bottom = rcMerger.bottom < rcOriginalMB.bottom ? rcOriginalMB.bottom : rcMerger.bottom; if (pPageDict->KeyExist("ArtBox")) rcOriginalCB = pPageDict->GetRectBy("ArtBox"); else rcOriginalCB = rcOriginalMB; if (!rcOriginalMB.IsEmpty()) { CPDF_Array* pMediaBox = new CPDF_Array(); pMediaBox->Add(new CPDF_Number(rcOriginalMB.left)); pMediaBox->Add(new CPDF_Number(rcOriginalMB.bottom)); pMediaBox->Add(new CPDF_Number(rcOriginalMB.right)); pMediaBox->Add(new CPDF_Number(rcOriginalMB.top)); pPageDict->SetAt("MediaBox", pMediaBox); } if (!rcOriginalCB.IsEmpty()) { CPDF_Array* pCropBox = new CPDF_Array(); pCropBox->Add(new CPDF_Number(rcOriginalCB.left)); pCropBox->Add(new CPDF_Number(rcOriginalCB.bottom)); pCropBox->Add(new CPDF_Number(rcOriginalCB.right)); pCropBox->Add(new CPDF_Number(rcOriginalCB.top)); pPageDict->SetAt("ArtBox", pCropBox); } CPDF_Dictionary* pRes = pPageDict->GetDictBy("Resources"); if (!pRes) { pRes = new CPDF_Dictionary; pPageDict->SetAt("Resources", pRes); } CPDF_Stream* pNewXObject = new CPDF_Stream(NULL, 0, new CPDF_Dictionary); FX_DWORD dwObjNum = pDocument->AddIndirectObject(pNewXObject); CPDF_Dictionary* pPageXObject = pRes->GetDictBy("XObject"); if (!pPageXObject) { pPageXObject = new CPDF_Dictionary; pRes->SetAt("XObject", pPageXObject); } CFX_ByteString key = ""; int nStreams = ObjectArray.GetSize(); if (nStreams > 0) { for (int iKey = 0; /*iKey < 100*/; iKey++) { char sExtend[5] = {}; FXSYS_itoa(iKey, sExtend, 10); key = CFX_ByteString("FFT") + CFX_ByteString(sExtend); if (!pPageXObject->KeyExist(key)) break; } } SetPageContents(key, pPageDict, pDocument); CPDF_Dictionary* pNewXORes = NULL; if (!key.IsEmpty()) { pPageXObject->SetAtReference(key, pDocument, dwObjNum); CPDF_Dictionary* pNewOXbjectDic = pNewXObject->GetDict(); pNewXORes = new CPDF_Dictionary; pNewOXbjectDic->SetAt("Resources", pNewXORes); pNewOXbjectDic->SetAtName("Type", "XObject"); pNewOXbjectDic->SetAtName("Subtype", "Form"); pNewOXbjectDic->SetAtInteger("FormType", 1); pNewOXbjectDic->SetAtName("Name", "FRM"); CPDF_Rect rcBBox = pPageDict->GetRectBy("ArtBox"); pNewOXbjectDic->SetAtRect("BBox", rcBBox); } for (int i = 0; i < nStreams; i++) { CPDF_Dictionary* pAnnotDic = ObjectArray.GetAt(i); if (!pAnnotDic) continue; CPDF_Rect rcAnnot = pAnnotDic->GetRectBy("Rect"); rcAnnot.Normalize(); CFX_ByteString sAnnotState = pAnnotDic->GetStringBy("AS"); CPDF_Dictionary* pAnnotAP = pAnnotDic->GetDictBy("AP"); if (!pAnnotAP) continue; CPDF_Stream* pAPStream = pAnnotAP->GetStreamBy("N"); if (!pAPStream) { CPDF_Dictionary* pAPDic = pAnnotAP->GetDictBy("N"); if (!pAPDic) continue; if (!sAnnotState.IsEmpty()) { pAPStream = pAPDic->GetStreamBy(sAnnotState); } else { auto it = pAPDic->begin(); if (it != pAPDic->end()) { CPDF_Object* pFirstObj = it->second; if (pFirstObj) { if (pFirstObj->IsReference()) pFirstObj = pFirstObj->GetDirect(); if (!pFirstObj->IsStream()) continue; pAPStream = pFirstObj->AsStream(); } } } } if (!pAPStream) continue; CPDF_Dictionary* pAPDic = pAPStream->GetDict(); CFX_Matrix matrix = pAPDic->GetMatrixBy("Matrix"); CPDF_Rect rcStream; if (pAPDic->KeyExist("Rect")) rcStream = pAPDic->GetRectBy("Rect"); else if (pAPDic->KeyExist("BBox")) rcStream = pAPDic->GetRectBy("BBox"); if (rcStream.IsEmpty()) continue; CPDF_Object* pObj = pAPStream; if (pObj) { CPDF_Dictionary* pObjDic = pObj->GetDict(); if (pObjDic) { pObjDic->SetAtName("Type", "XObject"); pObjDic->SetAtName("Subtype", "Form"); } } CPDF_Dictionary* pXObject = pNewXORes->GetDictBy("XObject"); if (!pXObject) { pXObject = new CPDF_Dictionary; pNewXORes->SetAt("XObject", pXObject); } CFX_ByteString sFormName; sFormName.Format("F%d", i); FX_DWORD dwObjNum = pDocument->AddIndirectObject(pObj); pXObject->SetAtReference(sFormName, pDocument, dwObjNum); CPDF_StreamAcc acc; acc.LoadAllData(pNewXObject); const uint8_t* pData = acc.GetData(); CFX_ByteString sStream(pData, acc.GetSize()); CFX_ByteString sTemp; if (matrix.IsIdentity()) { matrix.a = 1.0f; matrix.b = 0.0f; matrix.c = 0.0f; matrix.d = 1.0f; matrix.e = 0.0f; matrix.f = 0.0f; } CFX_Matrix m = GetMatrix(rcAnnot, rcStream, matrix); sTemp.Format("q %f 0 0 %f %f %f cm /%s Do Q\n", m.a, m.d, m.e, m.f, sFormName.c_str()); sStream += sTemp; pNewXObject->SetData((const uint8_t*)sStream, sStream.GetLength(), FALSE, FALSE); } pPageDict->RemoveAt("Annots"); ObjectArray.RemoveAll(); RectArray.RemoveAll(); return FLATTEN_SUCCESS; }