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/fpdfdoc/fpdf_doc.h"
8const int nMaxRecursion = 32;
9int CPDF_Dest::GetPageIndex(CPDF_Document* pDoc)
10{
11    if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
12        return 0;
13    }
14    CPDF_Object* pPage = ((CPDF_Array*)m_pObj)->GetElementValue(0);
15    if (pPage == NULL) {
16        return 0;
17    }
18    if (pPage->GetType() == PDFOBJ_NUMBER) {
19        return pPage->GetInteger();
20    }
21    if (pPage->GetType() != PDFOBJ_DICTIONARY) {
22        return 0;
23    }
24    return pDoc->GetPageIndex(pPage->GetObjNum());
25}
26FX_DWORD CPDF_Dest::GetPageObjNum()
27{
28    if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
29        return 0;
30    }
31    CPDF_Object* pPage = ((CPDF_Array*)m_pObj)->GetElementValue(0);
32    if (pPage == NULL) {
33        return 0;
34    }
35    if (pPage->GetType() == PDFOBJ_NUMBER) {
36        return pPage->GetInteger();
37    }
38    if (pPage->GetType() == PDFOBJ_DICTIONARY) {
39        return pPage->GetObjNum();
40    }
41    return 0;
42}
43const FX_CHAR* g_sZoomModes[] = {"XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV", ""};
44int CPDF_Dest::GetZoomMode()
45{
46    if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
47        return 0;
48    }
49    CFX_ByteString mode = ((CPDF_Array*)m_pObj)->GetElementValue(1)->GetString();
50    int i = 0;
51    while (g_sZoomModes[i][0] != '\0') {
52        if (mode == g_sZoomModes[i]) {
53            return i + 1;
54        }
55        i ++;
56    }
57    return 0;
58}
59FX_FLOAT CPDF_Dest::GetParam(int index)
60{
61    if (m_pObj == NULL || m_pObj->GetType() != PDFOBJ_ARRAY) {
62        return 0;
63    }
64    return ((CPDF_Array*)m_pObj)->GetNumber(2 + index);
65}
66CFX_ByteString CPDF_Dest::GetRemoteName()
67{
68    if (m_pObj == NULL) {
69        return CFX_ByteString();
70    }
71    return m_pObj->GetString();
72}
73CPDF_NameTree::CPDF_NameTree(CPDF_Document* pDoc, FX_BSTR category)
74{
75    m_pRoot = pDoc->GetRoot()->GetDict(FX_BSTRC("Names"))->GetDict(category);
76}
77static CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode, const CFX_ByteString& csName,
78                                   int& nIndex, CPDF_Array** ppFind, int nLevel = 0)
79{
80    if (nLevel > nMaxRecursion) {
81        return NULL;
82    }
83    CPDF_Array* pLimits = pNode->GetArray(FX_BSTRC("Limits"));
84    if (pLimits != NULL) {
85        CFX_ByteString csLeft = pLimits->GetString(0);
86        CFX_ByteString csRight = pLimits->GetString(1);
87        if (csLeft.Compare(csRight) > 0) {
88            CFX_ByteString csTmp = csRight;
89            csRight = csLeft;
90            csLeft = csTmp;
91        }
92        if (csName.Compare(csLeft) < 0 || csName.Compare(csRight) > 0) {
93            return NULL;
94        }
95    }
96    CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
97    if (pNames) {
98        FX_DWORD dwCount = pNames->GetCount() / 2;
99        for (FX_DWORD i = 0; i < dwCount; i ++) {
100            CFX_ByteString csValue = pNames->GetString(i * 2);
101            FX_INT32 iCompare = csValue.Compare(csName);
102            if (iCompare <= 0) {
103                if (ppFind != NULL) {
104                    *ppFind = pNames;
105                }
106                if (iCompare < 0) {
107                    continue;
108                }
109            } else {
110                break;
111            }
112            nIndex += i;
113            return pNames->GetElementValue(i * 2 + 1);
114        }
115        nIndex += dwCount;
116        return NULL;
117    }
118    CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
119    if (pKids == NULL) {
120        return NULL;
121    }
122    for (FX_DWORD i = 0; i < pKids->GetCount(); i ++) {
123        CPDF_Dictionary* pKid = pKids->GetDict(i);
124        if (pKid == NULL) {
125            continue;
126        }
127        CPDF_Object* pFound = SearchNameNode(pKid, csName, nIndex, ppFind, nLevel + 1);
128        if (pFound) {
129            return pFound;
130        }
131    }
132    return NULL;
133}
134static CPDF_Object* SearchNameNode(CPDF_Dictionary* pNode, int nIndex, int& nCurIndex,
135                                   CFX_ByteString& csName, CPDF_Array** ppFind, int nLevel = 0)
136{
137    if (nLevel > nMaxRecursion) {
138        return NULL;
139    }
140    CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
141    if (pNames) {
142        int nCount = pNames->GetCount() / 2;
143        if (nIndex >= nCurIndex + nCount) {
144            nCurIndex += nCount;
145            return NULL;
146        } else {
147            if (ppFind != NULL) {
148                *ppFind = pNames;
149            }
150            csName = pNames->GetString((nIndex - nCurIndex) * 2);
151            return pNames->GetElementValue((nIndex - nCurIndex) * 2 + 1);
152        }
153    }
154    CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
155    if (pKids == NULL) {
156        return NULL;
157    }
158    for (FX_DWORD i = 0; i < pKids->GetCount(); i ++) {
159        CPDF_Dictionary* pKid = pKids->GetDict(i);
160        if (pKid == NULL) {
161            continue;
162        }
163        CPDF_Object* pFound = SearchNameNode(pKid, nIndex, nCurIndex, csName, ppFind, nLevel + 1);
164        if (pFound) {
165            return pFound;
166        }
167    }
168    return NULL;
169}
170static int CountNames(CPDF_Dictionary* pNode, int nLevel = 0)
171{
172    if (nLevel > nMaxRecursion) {
173        return 0;
174    }
175    CPDF_Array* pNames = pNode->GetArray(FX_BSTRC("Names"));
176    if (pNames) {
177        return pNames->GetCount() / 2;
178    }
179    CPDF_Array* pKids = pNode->GetArray(FX_BSTRC("Kids"));
180    if (pKids == NULL) {
181        return 0;
182    }
183    int nCount = 0;
184    for (FX_DWORD i = 0; i < pKids->GetCount(); i ++) {
185        CPDF_Dictionary* pKid = pKids->GetDict(i);
186        if (pKid == NULL) {
187            continue;
188        }
189        nCount += CountNames(pKid, nLevel + 1);
190    }
191    return nCount;
192}
193int CPDF_NameTree::GetCount() const
194{
195    if (m_pRoot == NULL) {
196        return 0;
197    }
198    return ::CountNames(m_pRoot);
199}
200int CPDF_NameTree::GetIndex(const CFX_ByteString& csName) const
201{
202    if (m_pRoot == NULL) {
203        return -1;
204    }
205    int nIndex = 0;
206    if (SearchNameNode(m_pRoot, csName, nIndex, NULL) == NULL) {
207        return -1;
208    }
209    return nIndex;
210}
211CPDF_Object* CPDF_NameTree::LookupValue(int nIndex, CFX_ByteString& csName) const
212{
213    if (m_pRoot == NULL) {
214        return NULL;
215    }
216    int nCurIndex = 0;
217    return SearchNameNode(m_pRoot, nIndex, nCurIndex, csName, NULL);
218}
219CPDF_Object* CPDF_NameTree::LookupValue(const CFX_ByteString& csName) const
220{
221    if (m_pRoot == NULL) {
222        return NULL;
223    }
224    int nIndex = 0;
225    return SearchNameNode(m_pRoot, csName, nIndex, NULL);
226}
227CPDF_Array*	CPDF_NameTree::LookupNamedDest(CPDF_Document* pDoc, FX_BSTR sName)
228{
229    CPDF_Object* pValue = LookupValue(sName);
230    if (pValue == NULL) {
231        CPDF_Dictionary* pDests = pDoc->GetRoot()->GetDict(FX_BSTRC("Dests"));
232        if (pDests == NULL) {
233            return NULL;
234        }
235        pValue = pDests->GetElementValue(sName);
236    }
237    if (pValue == NULL) {
238        return NULL;
239    }
240    if (pValue->GetType() == PDFOBJ_ARRAY) {
241        return (CPDF_Array*)pValue;
242    }
243    if (pValue->GetType() == PDFOBJ_DICTIONARY) {
244        return ((CPDF_Dictionary*)pValue)->GetArray(FX_BSTRC("D"));
245    }
246    return NULL;
247}
248static CFX_WideString ChangeSlashToPlatform(FX_LPCWSTR str)
249{
250    CFX_WideString result;
251    while (*str) {
252        if (*str == '/') {
253#if _FXM_PLATFORM_  == _FXM_PLATFORM_APPLE_
254            result += ':';
255#elif _FXM_PLATFORM_  == _FXM_PLATFORM_WINDOWS_
256            result += '\\';
257#else
258            result += *str;
259#endif
260        } else {
261            result += *str;
262        }
263        str++;
264    }
265    return result;
266}
267static CFX_WideString FILESPEC_DecodeFileName(FX_WSTR filepath)
268{
269    if (filepath.GetLength() <= 1) {
270        return CFX_WideString();
271    }
272#if _FXM_PLATFORM_  == _FXM_PLATFORM_APPLE_
273    if (filepath.Left(sizeof("/Mac") - 1) == CFX_WideStringC(L"/Mac")) {
274        return ChangeSlashToPlatform(filepath.GetPtr() + 1);
275    }
276    return ChangeSlashToPlatform(filepath.GetPtr());
277#elif _FXM_PLATFORM_  == _FXM_PLATFORM_WINDOWS_
278    if (filepath.GetAt(0) != '/') {
279        return ChangeSlashToPlatform(filepath.GetPtr());
280    }
281    if (filepath.GetAt(1) == '/') {
282        return ChangeSlashToPlatform(filepath.GetPtr() + 1);
283    }
284    if (filepath.GetAt(2) == '/') {
285        CFX_WideString result;
286        result += filepath.GetAt(1);
287        result += ':';
288        result += ChangeSlashToPlatform(filepath.GetPtr() + 2);
289        return result;
290    }
291    CFX_WideString result;
292    result += '\\';
293    result += ChangeSlashToPlatform(filepath.GetPtr());
294    return result;
295#else
296    return filepath;
297#endif
298}
299FX_BOOL CPDF_FileSpec::GetFileName(CFX_WideString &csFileName) const
300{
301    if (m_pObj == NULL) {
302        return FALSE;
303    }
304    if (m_pObj->GetType() == PDFOBJ_DICTIONARY) {
305        CPDF_Dictionary* pDict = (CPDF_Dictionary*)m_pObj;
306        csFileName = pDict->GetUnicodeText(FX_BSTRC("UF"));
307        if (csFileName.IsEmpty()) {
308            csFileName = CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("F")));
309        }
310        if (pDict->GetString(FX_BSTRC("FS")) == FX_BSTRC("URL")) {
311            return TRUE;
312        }
313        if (csFileName.IsEmpty()) {
314            if (pDict->KeyExist(FX_BSTRC("DOS"))) {
315                csFileName = CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("DOS")));
316            } else if (pDict->KeyExist(FX_BSTRC("Mac"))) {
317                csFileName = CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("Mac")));
318            } else if (pDict->KeyExist(FX_BSTRC("Unix"))) {
319                csFileName = CFX_WideString::FromLocal(pDict->GetString(FX_BSTRC("Unix")));
320            } else {
321                return FALSE;
322            }
323        }
324    } else {
325        csFileName = CFX_WideString::FromLocal(m_pObj->GetString());
326    }
327    csFileName = FILESPEC_DecodeFileName(csFileName);
328    return TRUE;
329}
330CPDF_FileSpec::CPDF_FileSpec()
331{
332    m_pObj = CPDF_Dictionary::Create();
333    if (m_pObj != NULL) {
334        ((CPDF_Dictionary*)m_pObj)->SetAtName(FX_BSTRC("Type"), FX_BSTRC("Filespec"));
335    }
336}
337FX_BOOL CPDF_FileSpec::IsURL() const
338{
339    if (m_pObj == NULL) {
340        return FALSE;
341    }
342    if (m_pObj->GetType() != PDFOBJ_DICTIONARY) {
343        return FALSE;
344    }
345    return ((CPDF_Dictionary*)m_pObj)->GetString(FX_BSTRC("FS")) == FX_BSTRC("URL");
346}
347static CFX_WideString ChangeSlashToPDF(FX_LPCWSTR str)
348{
349    CFX_WideString result;
350    while (*str) {
351        if (*str == '\\' || *str == ':') {
352            result += '/';
353        } else {
354            result += *str;
355        }
356        str++;
357    }
358    return result;
359}
360CFX_WideString FILESPEC_EncodeFileName(FX_WSTR filepath)
361{
362    if (filepath.GetLength() <= 1) {
363        return CFX_WideString();
364    }
365#if _FXM_PLATFORM_  == _FXM_PLATFORM_WINDOWS_
366    if (filepath.GetAt(1) == ':') {
367        CFX_WideString result;
368        result = '/';
369        result += filepath.GetAt(0);
370        if (filepath.GetAt(2) != '\\') {
371            result += '/';
372        }
373        result += ChangeSlashToPDF(filepath.GetPtr() + 2);
374        return result;
375    }
376    if (filepath.GetAt(0) == '\\' && filepath.GetAt(1) == '\\') {
377        return ChangeSlashToPDF(filepath.GetPtr() + 1);
378    }
379    if (filepath.GetAt(0) == '\\') {
380        CFX_WideString result;
381        result = '/';
382        result += ChangeSlashToPDF(filepath.GetPtr());
383        return result;
384    }
385    return ChangeSlashToPDF(filepath.GetPtr());
386#elif _FXM_PLATFORM_  == _FXM_PLATFORM_APPLE_
387    if (filepath.Left(sizeof("Mac") - 1) == FX_WSTRC(L"Mac")) {
388        CFX_WideString result;
389        result = '/';
390        result += ChangeSlashToPDF(filepath.GetPtr());
391        return result;
392    }
393    return ChangeSlashToPDF(filepath.GetPtr());
394#else
395    return filepath;
396#endif
397}
398CPDF_Stream* CPDF_FileSpec::GetFileStream() const
399{
400    if (m_pObj == NULL) {
401        return NULL;
402    }
403    FX_INT32 iType = m_pObj->GetType();
404    if (iType == PDFOBJ_STREAM) {
405        return (CPDF_Stream*)m_pObj;
406    } else if (iType == PDFOBJ_DICTIONARY) {
407        CPDF_Dictionary *pEF = ((CPDF_Dictionary*)m_pObj)->GetDict(FX_BSTRC("EF"));
408        if (pEF == NULL) {
409            return NULL;
410        }
411        return pEF->GetStream(FX_BSTRC("F"));
412    }
413    return NULL;
414}
415static void FPDFDOC_FILESPEC_SetFileName(CPDF_Object *pObj, FX_WSTR wsFileName, FX_BOOL bURL)
416{
417    ASSERT(pObj != NULL);
418    CFX_WideString wsStr;
419    if (bURL) {
420        wsStr = wsFileName;
421    } else {
422        wsStr = FILESPEC_EncodeFileName(wsFileName);
423    }
424    FX_INT32 iType = pObj->GetType();
425    if (iType == PDFOBJ_STRING) {
426        pObj->SetString(CFX_ByteString::FromUnicode(wsStr));
427    } else if (iType == PDFOBJ_DICTIONARY) {
428        CPDF_Dictionary* pDict = (CPDF_Dictionary*)pObj;
429        pDict->SetAtString(FX_BSTRC("F"), CFX_ByteString::FromUnicode(wsStr));
430        pDict->SetAtString(FX_BSTRC("UF"), PDF_EncodeText(wsStr));
431    }
432}
433void CPDF_FileSpec::SetFileName(FX_WSTR wsFileName, FX_BOOL bURL)
434{
435    ASSERT(m_pObj != NULL);
436    if (m_pObj->GetType() == PDFOBJ_DICTIONARY && bURL) {
437        ((CPDF_Dictionary*)m_pObj)->SetAtName(FX_BSTRC("FS"), "URL");
438    }
439    FPDFDOC_FILESPEC_SetFileName(m_pObj, wsFileName, bURL);
440}
441static CFX_WideString _MakeRoman(int num)
442{
443    const int arabic[] = {
444        1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1
445    };
446    const CFX_WideString roman[] = {
447        L"m", L"cm", L"d", L"cd", L"c", L"xc", L"l", L"xl", L"x", L"ix", L"v", L"iv", L"i"
448    };
449    const int nMaxNum = 1000000;
450    num %= nMaxNum;
451    int i = 0;
452    CFX_WideString wsRomanNumber;
453    while (num > 0) {
454        while (num >= arabic[i]) {
455            num = num - arabic[i];
456            wsRomanNumber += roman[i];
457        }
458        i = i + 1;
459    }
460    return wsRomanNumber;
461}
462static CFX_WideString _MakeLetters(int num)
463{
464    if (num == 0) {
465        return CFX_WideString();
466    }
467    CFX_WideString wsLetters;
468    const int nMaxCount = 1000;
469    const int nLetterCount = 26;
470    num -= 1;
471    int count = num / nLetterCount + 1;
472    count %= nMaxCount;
473    FX_WCHAR ch = L'a' + num % nLetterCount;
474    for (int i = 0; i < count; i++) {
475        wsLetters += ch;
476    }
477    return wsLetters;
478}
479static CFX_WideString _GetLabelNumPortion(int num, const CFX_ByteString& bsStyle)
480{
481    CFX_WideString wsNumPortion;
482    if		(bsStyle.IsEmpty()) {
483        return wsNumPortion;
484    }
485    if (bsStyle == "D") {
486        wsNumPortion.Format((FX_LPCWSTR)L"%d", num);
487    } else if (bsStyle == "R") {
488        wsNumPortion = _MakeRoman(num);
489        wsNumPortion.MakeUpper();
490    } else if (bsStyle == "r") {
491        wsNumPortion = _MakeRoman(num);
492    } else if (bsStyle == "A") {
493        wsNumPortion = _MakeLetters(num);
494        wsNumPortion.MakeUpper();
495    } else if (bsStyle == "a") {
496        wsNumPortion = _MakeLetters(num);
497    }
498    return wsNumPortion;
499}
500CFX_WideString CPDF_PageLabel::GetLabel(int nPage) const
501{
502    CFX_WideString wsLabel;
503    if (m_pDocument == NULL) {
504        return wsLabel;
505    }
506    CPDF_Dictionary* pPDFRoot = m_pDocument->GetRoot();
507    if (pPDFRoot == NULL) {
508        return wsLabel;
509    }
510    CPDF_Dictionary* pLabels = pPDFRoot->GetDict(FX_BSTRC("PageLabels"));
511    CPDF_NumberTree numberTree(pLabels);
512    CPDF_Object* pValue = NULL;
513    int n = nPage;
514    while (n >= 0) {
515        pValue = numberTree.LookupValue(n);
516        if (pValue != NULL) {
517            break;
518        }
519        n--;
520    }
521    if (pValue != NULL) {
522        pValue = pValue->GetDirect();
523        if (pValue->GetType() == PDFOBJ_DICTIONARY) {
524            CPDF_Dictionary* pLabel = (CPDF_Dictionary*)pValue;
525            if (pLabel->KeyExist(FX_BSTRC("P"))) {
526                wsLabel += pLabel->GetUnicodeText(FX_BSTRC("P"));
527            }
528            CFX_ByteString bsNumberingStyle = pLabel->GetString(FX_BSTRC("S"), NULL);
529            int nLabelNum = nPage - n + pLabel->GetInteger(FX_BSTRC("St"), 1);
530            CFX_WideString wsNumPortion = _GetLabelNumPortion(nLabelNum, bsNumberingStyle);
531            wsLabel += wsNumPortion;
532            return wsLabel;
533        }
534    }
535    wsLabel.Format((FX_LPCWSTR)L"%d", nPage + 1);
536    return wsLabel;
537}
538FX_INT32 CPDF_PageLabel::GetPageByLabel(FX_BSTR bsLabel) const
539{
540    if (m_pDocument == NULL) {
541        return -1;
542    }
543    CPDF_Dictionary* pPDFRoot = m_pDocument->GetRoot();
544    if (pPDFRoot == NULL) {
545        return -1;
546    }
547    int nPages = m_pDocument->GetPageCount();
548    CFX_ByteString bsLbl;
549    CFX_ByteString bsOrig = bsLabel;
550    for (int i = 0; i < nPages; i++) {
551        bsLbl = PDF_EncodeText(GetLabel(i));
552        if (!bsLbl.Compare(bsOrig)) {
553            return i;
554        }
555    }
556    bsLbl = bsOrig;
557    int nPage = FXSYS_atoi(bsLbl);
558    if (nPage > 0 && nPage <= nPages) {
559        return nPage;
560    }
561    return -1;
562}
563FX_INT32 CPDF_PageLabel::GetPageByLabel(FX_WSTR wsLabel) const
564{
565    CFX_ByteString bsLabel = PDF_EncodeText((CFX_WideString)wsLabel);
566    return GetPageByLabel(bsLabel);
567}
568