1// Copyright 2016 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 "core/fpdfdoc/cpdf_occontext.h"
8
9#include "core/fpdfapi/page/cpdf_pageobject.h"
10#include "core/fpdfapi/parser/cpdf_array.h"
11#include "core/fpdfapi/parser/cpdf_document.h"
12
13namespace {
14
15int32_t FindGroup(const CPDF_Array* pArray, const CPDF_Dictionary* pGroupDict) {
16  if (!pArray || !pGroupDict)
17    return -1;
18
19  for (size_t i = 0; i < pArray->GetCount(); i++) {
20    if (pArray->GetDictAt(i) == pGroupDict)
21      return i;
22  }
23  return -1;
24}
25
26bool HasIntent(const CPDF_Dictionary* pDict,
27               const CFX_ByteStringC& csElement,
28               const CFX_ByteStringC& csDef) {
29  CPDF_Object* pIntent = pDict->GetDirectObjectFor("Intent");
30  if (!pIntent)
31    return csElement == csDef;
32
33  CFX_ByteString bsIntent;
34  if (CPDF_Array* pArray = pIntent->AsArray()) {
35    for (size_t i = 0; i < pArray->GetCount(); i++) {
36      bsIntent = pArray->GetStringAt(i);
37      if (bsIntent == "All" || bsIntent == csElement)
38        return true;
39    }
40    return false;
41  }
42  bsIntent = pIntent->GetString();
43  return bsIntent == "All" || bsIntent == csElement;
44}
45
46CPDF_Dictionary* GetConfig(CPDF_Document* pDoc,
47                           const CPDF_Dictionary* pOCGDict) {
48  ASSERT(pOCGDict);
49  CPDF_Dictionary* pOCProperties = pDoc->GetRoot()->GetDictFor("OCProperties");
50  if (!pOCProperties)
51    return nullptr;
52
53  CPDF_Array* pOCGs = pOCProperties->GetArrayFor("OCGs");
54  if (!pOCGs)
55    return nullptr;
56
57  if (FindGroup(pOCGs, pOCGDict) < 0)
58    return nullptr;
59
60  CPDF_Dictionary* pConfig = pOCProperties->GetDictFor("D");
61  CPDF_Array* pConfigs = pOCProperties->GetArrayFor("Configs");
62  if (!pConfigs)
63    return pConfig;
64
65  for (size_t i = 0; i < pConfigs->GetCount(); i++) {
66    CPDF_Dictionary* pFind = pConfigs->GetDictAt(i);
67    if (pFind && HasIntent(pFind, "View", "View"))
68      return pFind;
69  }
70  return pConfig;
71}
72
73CFX_ByteString GetUsageTypeString(CPDF_OCContext::UsageType eType) {
74  CFX_ByteString csState;
75  switch (eType) {
76    case CPDF_OCContext::Design:
77      csState = "Design";
78      break;
79    case CPDF_OCContext::Print:
80      csState = "Print";
81      break;
82    case CPDF_OCContext::Export:
83      csState = "Export";
84      break;
85    default:
86      csState = "View";
87      break;
88  }
89  return csState;
90}
91
92}  // namespace
93
94CPDF_OCContext::CPDF_OCContext(CPDF_Document* pDoc, UsageType eUsageType)
95    : m_pDocument(pDoc), m_eUsageType(eUsageType) {
96  ASSERT(pDoc);
97}
98
99CPDF_OCContext::~CPDF_OCContext() {}
100
101bool CPDF_OCContext::LoadOCGStateFromConfig(
102    const CFX_ByteString& csConfig,
103    const CPDF_Dictionary* pOCGDict) const {
104  CPDF_Dictionary* pConfig = GetConfig(m_pDocument, pOCGDict);
105  if (!pConfig)
106    return true;
107
108  bool bState = pConfig->GetStringFor("BaseState", "ON") != "OFF";
109  CPDF_Array* pArray = pConfig->GetArrayFor("ON");
110  if (pArray) {
111    if (FindGroup(pArray, pOCGDict) >= 0)
112      bState = true;
113  }
114  pArray = pConfig->GetArrayFor("OFF");
115  if (pArray) {
116    if (FindGroup(pArray, pOCGDict) >= 0)
117      bState = false;
118  }
119  pArray = pConfig->GetArrayFor("AS");
120  if (!pArray)
121    return bState;
122
123  CFX_ByteString csFind = csConfig + "State";
124  for (size_t i = 0; i < pArray->GetCount(); i++) {
125    CPDF_Dictionary* pUsage = pArray->GetDictAt(i);
126    if (!pUsage)
127      continue;
128
129    if (pUsage->GetStringFor("Event", "View") != csConfig)
130      continue;
131
132    CPDF_Array* pOCGs = pUsage->GetArrayFor("OCGs");
133    if (!pOCGs)
134      continue;
135
136    if (FindGroup(pOCGs, pOCGDict) < 0)
137      continue;
138
139    CPDF_Dictionary* pState = pUsage->GetDictFor(csConfig);
140    if (!pState)
141      continue;
142
143    bState = pState->GetStringFor(csFind) != "OFF";
144  }
145  return bState;
146}
147
148bool CPDF_OCContext::LoadOCGState(const CPDF_Dictionary* pOCGDict) const {
149  if (!HasIntent(pOCGDict, "View", "View"))
150    return true;
151
152  CFX_ByteString csState = GetUsageTypeString(m_eUsageType);
153  CPDF_Dictionary* pUsage = pOCGDict->GetDictFor("Usage");
154  if (pUsage) {
155    CPDF_Dictionary* pState = pUsage->GetDictFor(csState);
156    if (pState) {
157      CFX_ByteString csFind = csState + "State";
158      if (pState->KeyExist(csFind))
159        return pState->GetStringFor(csFind) != "OFF";
160    }
161    if (csState != "View") {
162      pState = pUsage->GetDictFor("View");
163      if (pState && pState->KeyExist("ViewState"))
164        return pState->GetStringFor("ViewState") != "OFF";
165    }
166  }
167  return LoadOCGStateFromConfig(csState, pOCGDict);
168}
169
170bool CPDF_OCContext::GetOCGVisible(const CPDF_Dictionary* pOCGDict) {
171  if (!pOCGDict)
172    return false;
173
174  const auto it = m_OCGStates.find(pOCGDict);
175  if (it != m_OCGStates.end())
176    return it->second;
177
178  bool bState = LoadOCGState(pOCGDict);
179  m_OCGStates[pOCGDict] = bState;
180  return bState;
181}
182
183bool CPDF_OCContext::CheckObjectVisible(const CPDF_PageObject* pObj) {
184  for (int i = 0; i < pObj->m_ContentMark.CountItems(); i++) {
185    const CPDF_ContentMarkItem& item = pObj->m_ContentMark.GetItem(i);
186    if (item.GetName() == "OC" &&
187        item.GetParamType() == CPDF_ContentMarkItem::PropertiesDict &&
188        !CheckOCGVisible(item.GetParam())) {
189      return false;
190    }
191  }
192  return true;
193}
194
195bool CPDF_OCContext::GetOCGVE(CPDF_Array* pExpression, int nLevel) {
196  if (nLevel > 32 || !pExpression)
197    return false;
198
199  CFX_ByteString csOperator = pExpression->GetStringAt(0);
200  if (csOperator == "Not") {
201    CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(1);
202    if (!pOCGObj)
203      return false;
204    if (CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
205      return !GetOCGVisible(pDict);
206    if (CPDF_Array* pArray = pOCGObj->AsArray())
207      return !GetOCGVE(pArray, nLevel + 1);
208    return false;
209  }
210
211  if (csOperator != "Or" && csOperator != "And")
212    return false;
213
214  bool bValue = false;
215  for (size_t i = 1; i < pExpression->GetCount(); i++) {
216    CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(1);
217    if (!pOCGObj)
218      continue;
219
220    bool bItem = false;
221    if (CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
222      bItem = GetOCGVisible(pDict);
223    else if (CPDF_Array* pArray = pOCGObj->AsArray())
224      bItem = GetOCGVE(pArray, nLevel + 1);
225
226    if (i == 1) {
227      bValue = bItem;
228    } else {
229      if (csOperator == "Or") {
230        bValue = bValue || bItem;
231      } else {
232        bValue = bValue && bItem;
233      }
234    }
235  }
236  return bValue;
237}
238
239bool CPDF_OCContext::LoadOCMDState(const CPDF_Dictionary* pOCMDDict) {
240  CPDF_Array* pVE = pOCMDDict->GetArrayFor("VE");
241  if (pVE)
242    return GetOCGVE(pVE, 0);
243
244  CFX_ByteString csP = pOCMDDict->GetStringFor("P", "AnyOn");
245  CPDF_Object* pOCGObj = pOCMDDict->GetDirectObjectFor("OCGs");
246  if (!pOCGObj)
247    return true;
248
249  if (const CPDF_Dictionary* pDict = pOCGObj->AsDictionary())
250    return GetOCGVisible(pDict);
251
252  CPDF_Array* pArray = pOCGObj->AsArray();
253  if (!pArray)
254    return true;
255
256  bool bState = (csP == "AllOn" || csP == "AllOff");
257  for (size_t i = 0; i < pArray->GetCount(); i++) {
258    bool bItem = true;
259    CPDF_Dictionary* pItemDict = pArray->GetDictAt(i);
260    if (pItemDict)
261      bItem = GetOCGVisible(pItemDict);
262
263    if ((csP == "AnyOn" && bItem) || (csP == "AnyOff" && !bItem))
264      return true;
265    if ((csP == "AllOn" && !bItem) || (csP == "AllOff" && bItem))
266      return false;
267  }
268  return bState;
269}
270
271bool CPDF_OCContext::CheckOCGVisible(const CPDF_Dictionary* pOCGDict) {
272  if (!pOCGDict)
273    return true;
274
275  CFX_ByteString csType = pOCGDict->GetStringFor("Type", "OCG");
276  if (csType == "OCG")
277    return GetOCGVisible(pOCGDict);
278  return LoadOCMDState(pOCGDict);
279}
280