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