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