/* -*- 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.