/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
 
#include <sal/log.hxx>
#include <sal/types.h>
#include <rtl/strbuf.hxx>
#include <tools/stream.hxx>
#include <tools/zcodec.hxx>
 
#include <vcl/filter/pdfdocument.hxx>
#include <vcl/filter/pdfobjectcontainer.hxx>
 
#include <pdf/objectcopier.hxx>
#include <pdf/pdfwriter_impl.hxx>
 
namespace vcl
{
PDFObjectCopier::PDFObjectCopier(PDFObjectContainer& rContainer)
    : m_rContainer(rContainer)
{
}
 
void PDFObjectCopier::copyRecursively(OStringBuffer& rLine, filter::PDFElement& rInputElement,
                                      SvMemoryStream& rDocBuffer,
                                      std::map<sal_Int32, sal_Int32>& rCopiedResources)
{
    if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(&rInputElement))
    {
        filter::PDFObjectElement* pReferenced = pReference->LookupObject();
        if (pReferenced)
        {
            // Copy the referenced object.
            sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
 
            // Write the updated reference.
            rLine.append(nRef);
            rLine.append(" 0 R");
        }
    }
    else if (auto pInputArray = dynamic_cast<filter::PDFArrayElement*>(&rInputElement))
    {
        rLine.append("[ ");
        for (auto const& pElement : pInputArray->GetElements())
        {
            copyRecursively(rLine, *pElement, rDocBuffer, rCopiedResources);
            rLine.append(" ");
        }
        rLine.append("] ");
    }
    else if (auto pInputDictionary = dynamic_cast<filter::PDFDictionaryElement*>(&rInputElement))
    {
        rLine.append("<< ");
        for (auto const& pPair : pInputDictionary->GetItems())
        {
            rLine.append("/");
            rLine.append(pPair.first);
            rLine.append(" ");
            copyRecursively(rLine, *pPair.second, rDocBuffer, rCopiedResources);
            rLine.append(" ");
        }
        rLine.append(">> ");
    }
    else
    {
        rInputElement.writeString(rLine);
    }
}
 
sal_Int32 PDFObjectCopier::copyExternalResource(SvMemoryStream& rDocBuffer,
                                                filter::PDFObjectElement& rObject,
                                                std::map<sal_Int32, sal_Int32>& rCopiedResources)
{
    auto it = rCopiedResources.find(rObject.GetObjectValue());
    if (it != rCopiedResources.end())
    {
        // This resource was already copied once, nothing to do.
        return it->second;
    }
 
    sal_Int32 nObject = m_rContainer.createObject();
    // Remember what is the ID of this object in our output.
    rCopiedResources[rObject.GetObjectValue()] = nObject;
    SAL_INFO("vcl.pdfwriter", "PDFObjectCopier::copyExternalResource: " << rObject.GetObjectValue()
                                                                        << " -> " << nObject);
 
    OStringBuffer aLine = OString::number(nObject) + " 0 obj\n";
 
    if (rObject.GetDictionary())
    {
        aLine.append("<< ");
        bool bFirst = true;
        for (auto const& rPair : rObject.GetDictionaryItems())
        {
            if (bFirst)
                bFirst = false;
            else
                aLine.append(" ");
 
            aLine.append("/" + rPair.first + " ");
            copyRecursively(aLine, *rPair.second, rDocBuffer, rCopiedResources);
        }
 
        aLine.append(" >>\n");
    }
 
    filter::PDFStreamElement* pStream = rObject.GetStream();
    if (pStream)
    {
        aLine.append("stream\n");
    }
 
    if (filter::PDFArrayElement* pArray = rObject.GetArray())
    {
        aLine.append("[ ");
 
        const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
 
        bool bFirst = true;
        for (auto const& pElement : rElements)
        {
            if (bFirst)
                bFirst = false;
            else
                aLine.append(" ");
            copyRecursively(aLine, *pElement, rDocBuffer, rCopiedResources);
        }
        aLine.append("]\n");
    }
 
    // If the object has a number element outside a dictionary or array, copy that.
    if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
    {
        pNumber->writeString(aLine);
        aLine.append("\n");
    }
    // If the object has a name element outside a dictionary or array, copy that.
    else if (filter::PDFNameElement* pName = rObject.GetNameElement())
    {
        // currently just handle the exact case seen in the real world
        if (pName->GetValue() == "DeviceRGB")
        {
            pName->writeString(aLine);
            aLine.append("\n");
        }
        else
        {
            SAL_INFO("vcl.pdfwriter",
                     "PDFObjectCopier::copyExternalResource: skipping: " << pName->GetValue());
        }
    }
 
    // We have the whole object, now write it to the output.
    if (!m_rContainer.updateObject(nObject))
        return -1;
    if (!m_rContainer.writeBuffer(aLine))
        return -1;
    aLine.setLength(0);
 
    if (pStream)
    {
        SvMemoryStream& rStream = pStream->GetMemory();
        m_rContainer.checkAndEnableStreamEncryption(nObject);
        aLine.append(static_cast<const char*>(rStream.GetData()), rStream.GetSize());
        if (!m_rContainer.writeBuffer(aLine))
            return -1;
        aLine.setLength(0);
        m_rContainer.disableStreamEncryption();
 
        aLine.append("\nendstream\n");
        if (!m_rContainer.writeBuffer(aLine))
            return -1;
        aLine.setLength(0);
    }
 
    aLine.append("endobj\n\n");
    if (!m_rContainer.writeBuffer(aLine))
        return -1;
 
    return nObject;
}
 
OString PDFObjectCopier::copyExternalResources(filter::PDFObjectElement& rPage,
                                               const OString& rKind,
                                               std::map<sal_Int32, sal_Int32>& rCopiedResources)
{
    // A name - object ID map, IDs as they appear in our output, not the
    // original ones.
    std::map<OString, sal_Int32> aRet;
 
    // Get the rKind subset of the resource dictionary.
    std::map<OString, filter::PDFElement*> aItems;
    filter::PDFObjectElement* pKindObject = nullptr;
    if (auto pResources
        = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources"_ostr)))
    {
        // Resources is a direct dictionary.
        filter::PDFElement* pLookup = pResources->LookupElement(rKind);
        if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
        {
            // rKind is an inline dictionary.
            aItems = pDictionary->GetItems();
        }
        else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
        {
            // rKind refers to a dictionary.
            filter::PDFObjectElement* pReferenced = pReference->LookupObject();
            if (!pReferenced)
            {
                return {};
            }
 
            pKindObject = pReferenced;
            aItems = pReferenced->GetDictionaryItems();
        }
    }
    else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"_ostr))
    {
        // Resources is an indirect object.
        filter::PDFElement* pValue = pPageResources->Lookup(rKind);
        if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
        {
            // Kind is a direct dictionary.
            aItems = pDictionary->GetItems();
        }
        else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
        {
            // Kind is an indirect object.
            aItems = pObject->GetDictionaryItems();
            pKindObject = pObject;
        }
    }
    if (aItems.empty())
        return {};
 
    SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
    bool bHasDictValue = false;
 
    for (const auto& rItem : aItems)
    {
        // For each item copy it over to our output then insert it into aRet.
        auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
        if (!pReference)
        {
            if (pKindObject && dynamic_cast<filter::PDFDictionaryElement*>(rItem.second))
            {
                bHasDictValue = true;
                break;
            }
 
            continue;
        }
 
        filter::PDFObjectElement* pValue = pReference->LookupObject();
        if (!pValue)
            continue;
 
        // Then copying over an object copy its dictionary and its stream.
        sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
        aRet[rItem.first] = nObject;
    }
 
    if (bHasDictValue && pKindObject)
    {
        sal_Int32 nObject = copyExternalResource(rDocBuffer, *pKindObject, rCopiedResources);
        return "/" + rKind + " " + OString::number(nObject) + " 0 R";
    }
 
    // Build the dictionary entry string.
    OStringBuffer sRet("/" + rKind + "<<");
    for (const auto& rPair : aRet)
    {
        sRet.append("/" + rPair.first + " " + OString::number(rPair.second) + " 0 R");
    }
    sRet.append(">>");
 
    return sRet.makeStringAndClear();
}
 
void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine)
{
    // Maps from source object id (PDF image) to target object id (export result).
    std::map<sal_Int32, sal_Int32> aCopiedResources;
    copyPageResources(pPage, rLine, aCopiedResources);
}
 
void PDFObjectCopier::copyPageResources(filter::PDFObjectElement* pPage, OStringBuffer& rLine,
                                        std::map<sal_Int32, sal_Int32>& rCopiedResources)
{
    rLine.append(" /Resources <<");
    static const std::initializer_list<OString> aKeys
        = { "ColorSpace"_ostr, "ExtGState"_ostr, "Font"_ostr,
            "XObject"_ostr,    "Shading"_ostr,   "Pattern"_ostr };
    for (const auto& rKey : aKeys)
    {
        rLine.append(copyExternalResources(*pPage, rKey, rCopiedResources));
    }
    rLine.append(">>");
}
 
sal_Int32 PDFObjectCopier::copyPageStreams(std::vector<filter::PDFObjectElement*>& rContentStreams,
                                           SvMemoryStream& rStream, bool& rCompressed)
{
    for (auto pContent : rContentStreams)
    {
        filter::PDFStreamElement* pPageStream = pContent->GetStream();
        if (!pPageStream)
        {
            SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: contents has no stream");
            continue;
        }
 
        SvMemoryStream& rPageStream = pPageStream->GetMemory();
 
        auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"_ostr));
        auto pFilterArray = dynamic_cast<filter::PDFArrayElement*>(pContent->Lookup("Filter"_ostr));
        if (!pFilter && pFilterArray)
        {
            auto& aElements = pFilterArray->GetElements();
            if (!aElements.empty())
                pFilter = dynamic_cast<filter::PDFNameElement*>(aElements[0]);
        }
 
        if (pFilter)
        {
            if (pFilter->GetValue() != "FlateDecode")
            {
                continue;
            }
 
            SvMemoryStream aMemoryStream;
            ZCodec aZCodec;
            rPageStream.Seek(0);
            aZCodec.BeginCompression();
            aZCodec.Decompress(rPageStream, aMemoryStream);
            if (!aZCodec.EndCompression())
            {
                SAL_WARN("vcl.pdfwriter", "PDFObjectCopier::copyPageStreams: decompression failed");
                continue;
            }
 
            rStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
        }
        else
        {
            rStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
        }
    }
 
    rCompressed = PDFWriterImpl::compressStream(&rStream);
 
    return rStream.Tell();
}
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.