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