| // Copyright 2016 PDFium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com |
| |
| #include "core/fpdfapi/page/cpdf_occontext.h" |
| |
| #include "core/fpdfapi/page/cpdf_pageobject.h" |
| #include "core/fpdfapi/parser/cpdf_array.h" |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" |
| #include "core/fpdfapi/parser/cpdf_document.h" |
| #include "third_party/base/check.h" |
| |
| namespace { |
| |
| int32_t FindGroup(const CPDF_Array* pArray, const CPDF_Dictionary* pGroupDict) { |
| if (!pArray || !pGroupDict) |
| return -1; |
| |
| for (size_t i = 0; i < pArray->size(); i++) { |
| if (pArray->GetDictAt(i) == pGroupDict) |
| return i; |
| } |
| return -1; |
| } |
| |
| bool HasIntent(const CPDF_Dictionary* pDict, |
| ByteStringView csElement, |
| ByteStringView csDef) { |
| const CPDF_Object* pIntent = pDict->GetDirectObjectFor("Intent"); |
| if (!pIntent) |
| return csElement == csDef; |
| |
| ByteString bsIntent; |
| if (const CPDF_Array* pArray = pIntent->AsArray()) { |
| for (size_t i = 0; i < pArray->size(); i++) { |
| bsIntent = pArray->GetStringAt(i); |
| if (bsIntent == "All" || bsIntent == csElement) |
| return true; |
| } |
| return false; |
| } |
| bsIntent = pIntent->GetString(); |
| return bsIntent == "All" || bsIntent == csElement; |
| } |
| |
| CPDF_Dictionary* GetConfig(CPDF_Document* pDoc, |
| const CPDF_Dictionary* pOCGDict) { |
| DCHECK(pOCGDict); |
| CPDF_Dictionary* pOCProperties = pDoc->GetRoot()->GetDictFor("OCProperties"); |
| if (!pOCProperties) |
| return nullptr; |
| |
| CPDF_Array* pOCGs = pOCProperties->GetArrayFor("OCGs"); |
| if (!pOCGs) |
| return nullptr; |
| |
| if (FindGroup(pOCGs, pOCGDict) < 0) |
| return nullptr; |
| |
| CPDF_Dictionary* pConfig = pOCProperties->GetDictFor("D"); |
| CPDF_Array* pConfigs = pOCProperties->GetArrayFor("Configs"); |
| if (!pConfigs) |
| return pConfig; |
| |
| for (size_t i = 0; i < pConfigs->size(); i++) { |
| CPDF_Dictionary* pFind = pConfigs->GetDictAt(i); |
| if (pFind && HasIntent(pFind, "View", "")) |
| return pFind; |
| } |
| return pConfig; |
| } |
| |
| ByteString GetUsageTypeString(CPDF_OCContext::UsageType eType) { |
| ByteString csState; |
| switch (eType) { |
| case CPDF_OCContext::Design: |
| csState = "Design"; |
| break; |
| case CPDF_OCContext::Print: |
| csState = "Print"; |
| break; |
| case CPDF_OCContext::Export: |
| csState = "Export"; |
| break; |
| default: |
| csState = "View"; |
| break; |
| } |
| return csState; |
| } |
| |
| } // namespace |
| |
| CPDF_OCContext::CPDF_OCContext(CPDF_Document* pDoc, UsageType eUsageType) |
| : m_pDocument(pDoc), m_eUsageType(eUsageType) { |
| DCHECK(pDoc); |
| } |
| |
| CPDF_OCContext::~CPDF_OCContext() = default; |
| |
| bool CPDF_OCContext::LoadOCGStateFromConfig( |
| const ByteString& csConfig, |
| const CPDF_Dictionary* pOCGDict) const { |
| CPDF_Dictionary* pConfig = GetConfig(m_pDocument.Get(), pOCGDict); |
| if (!pConfig) |
| return true; |
| |
| bool bState = pConfig->GetStringFor("BaseState", "ON") != "OFF"; |
| CPDF_Array* pArray = pConfig->GetArrayFor("ON"); |
| if (pArray) { |
| if (FindGroup(pArray, pOCGDict) >= 0) |
| bState = true; |
| } |
| pArray = pConfig->GetArrayFor("OFF"); |
| if (pArray) { |
| if (FindGroup(pArray, pOCGDict) >= 0) |
| bState = false; |
| } |
| pArray = pConfig->GetArrayFor("AS"); |
| if (!pArray) |
| return bState; |
| |
| ByteString csFind = csConfig + "State"; |
| for (size_t i = 0; i < pArray->size(); i++) { |
| CPDF_Dictionary* pUsage = pArray->GetDictAt(i); |
| if (!pUsage) |
| continue; |
| |
| if (pUsage->GetStringFor("Event", "View") != csConfig) |
| continue; |
| |
| CPDF_Array* pOCGs = pUsage->GetArrayFor("OCGs"); |
| if (!pOCGs) |
| continue; |
| |
| if (FindGroup(pOCGs, pOCGDict) < 0) |
| continue; |
| |
| CPDF_Dictionary* pState = pUsage->GetDictFor(csConfig); |
| if (!pState) |
| continue; |
| |
| bState = pState->GetStringFor(csFind) != "OFF"; |
| } |
| return bState; |
| } |
| |
| bool CPDF_OCContext::LoadOCGState(const CPDF_Dictionary* pOCGDict) const { |
| if (!HasIntent(pOCGDict, "View", "View")) |
| return true; |
| |
| ByteString csState = GetUsageTypeString(m_eUsageType); |
| const CPDF_Dictionary* pUsage = pOCGDict->GetDictFor("Usage"); |
| if (pUsage) { |
| const CPDF_Dictionary* pState = pUsage->GetDictFor(csState); |
| if (pState) { |
| ByteString csFind = csState + "State"; |
| if (pState->KeyExist(csFind)) |
| return pState->GetStringFor(csFind) != "OFF"; |
| } |
| if (csState != "View") { |
| pState = pUsage->GetDictFor("View"); |
| if (pState && pState->KeyExist("ViewState")) |
| return pState->GetStringFor("ViewState") != "OFF"; |
| } |
| } |
| return LoadOCGStateFromConfig(csState, pOCGDict); |
| } |
| |
| bool CPDF_OCContext::GetOCGVisible(const CPDF_Dictionary* pOCGDict) const { |
| if (!pOCGDict) |
| return false; |
| |
| const auto it = m_OGCStateCache.find(pOCGDict); |
| if (it != m_OGCStateCache.end()) |
| return it->second; |
| |
| bool bState = LoadOCGState(pOCGDict); |
| m_OGCStateCache[pOCGDict] = bState; |
| return bState; |
| } |
| |
| bool CPDF_OCContext::CheckObjectVisible(const CPDF_PageObject* pObj) const { |
| const CPDF_ContentMarks* pMarks = pObj->GetContentMarks(); |
| for (size_t i = 0; i < pMarks->CountItems(); ++i) { |
| const CPDF_ContentMarkItem* item = pMarks->GetItem(i); |
| if (item->GetName() == "OC" && |
| item->GetParamType() == CPDF_ContentMarkItem::kPropertiesDict && |
| !CheckOCGVisible(item->GetParam())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool CPDF_OCContext::GetOCGVE(const CPDF_Array* pExpression, int nLevel) const { |
| if (nLevel > 32 || !pExpression) |
| return false; |
| |
| ByteString csOperator = pExpression->GetStringAt(0); |
| if (csOperator == "Not") { |
| const CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(1); |
| if (!pOCGObj) |
| return false; |
| if (const CPDF_Dictionary* pDict = pOCGObj->AsDictionary()) |
| return !GetOCGVisible(pDict); |
| if (const CPDF_Array* pArray = pOCGObj->AsArray()) |
| return !GetOCGVE(pArray, nLevel + 1); |
| return false; |
| } |
| |
| if (csOperator != "Or" && csOperator != "And") |
| return false; |
| |
| bool bValue = false; |
| for (size_t i = 1; i < pExpression->size(); i++) { |
| const CPDF_Object* pOCGObj = pExpression->GetDirectObjectAt(i); |
| if (!pOCGObj) |
| continue; |
| |
| bool bItem = false; |
| if (const CPDF_Dictionary* pDict = pOCGObj->AsDictionary()) |
| bItem = GetOCGVisible(pDict); |
| else if (const CPDF_Array* pArray = pOCGObj->AsArray()) |
| bItem = GetOCGVE(pArray, nLevel + 1); |
| |
| if (i == 1) { |
| bValue = bItem; |
| } else { |
| if (csOperator == "Or") { |
| bValue = bValue || bItem; |
| } else { |
| bValue = bValue && bItem; |
| } |
| } |
| } |
| return bValue; |
| } |
| |
| bool CPDF_OCContext::LoadOCMDState(const CPDF_Dictionary* pOCMDDict) const { |
| const CPDF_Array* pVE = pOCMDDict->GetArrayFor("VE"); |
| if (pVE) |
| return GetOCGVE(pVE, 0); |
| |
| ByteString csP = pOCMDDict->GetStringFor("P", "AnyOn"); |
| const CPDF_Object* pOCGObj = pOCMDDict->GetDirectObjectFor("OCGs"); |
| if (!pOCGObj) |
| return true; |
| |
| if (const CPDF_Dictionary* pDict = pOCGObj->AsDictionary()) |
| return GetOCGVisible(pDict); |
| |
| const CPDF_Array* pArray = pOCGObj->AsArray(); |
| if (!pArray) |
| return true; |
| |
| bool bState = (csP == "AllOn" || csP == "AllOff"); |
| // At least one entry of OCGs needs to be a valid dictionary for it to be |
| // considered present. See "OCGs" in table 4.49 in the PDF 1.7 spec. |
| bool bValidEntrySeen = false; |
| for (size_t i = 0; i < pArray->size(); i++) { |
| bool bItem = true; |
| const CPDF_Dictionary* pItemDict = pArray->GetDictAt(i); |
| if (!pItemDict) |
| continue; |
| |
| bValidEntrySeen = true; |
| bItem = GetOCGVisible(pItemDict); |
| |
| if ((csP == "AnyOn" && bItem) || (csP == "AnyOff" && !bItem)) |
| return true; |
| if ((csP == "AllOn" && !bItem) || (csP == "AllOff" && bItem)) |
| return false; |
| } |
| |
| return !bValidEntrySeen || bState; |
| } |
| |
| bool CPDF_OCContext::CheckOCGVisible(const CPDF_Dictionary* pOCGDict) const { |
| if (!pOCGDict) |
| return true; |
| |
| ByteString csType = pOCGDict->GetStringFor("Type", "OCG"); |
| if (csType == "OCG") |
| return GetOCGVisible(pOCGDict); |
| return LoadOCMDState(pOCGDict); |
| } |