[Libreoffice-commits] core.git: 2 commits - xmlsecurity/source
Miklos Vajna
vmiklos at collabora.co.uk
Wed Nov 9 19:22:02 UTC 2016
xmlsecurity/source/pdfio/pdfdocument.cxx | 380 +++++++++++++++++++++++--------
1 file changed, 289 insertions(+), 91 deletions(-)
New commits:
commit 2a7e39eac2f44ad48455c8a5c04242b1fc92c726
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Wed Nov 9 15:49:35 2016 +0100
xmlsecurity PDF sign: conditionally write xref stream
In case the input document used a PDF 1.5 xref stream, not an old xref
table, then write that as part of the incremental update. Acrobat seems
to require this.
Change-Id: I9f1f73140c26308f8720aa1ffe1b905d0e60ede0
Reviewed-on: https://gerrit.libreoffice.org/30724
Tested-by: Jenkins <ci at libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
index 3b90bf3..8bd942f 100644
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -667,80 +667,197 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
}
- // Write the xref table.
sal_uInt64 nXRefOffset = m_aEditBuffer.Tell();
- m_aEditBuffer.WriteCharPtr("xref\n");
- for (const auto& rXRef : m_aXRef)
- {
- size_t nObject = rXRef.first;
- size_t nOffset = rXRef.second.m_nOffset;
- if (!rXRef.second.m_bDirty)
- continue;
-
- m_aEditBuffer.WriteUInt32AsString(nObject);
- m_aEditBuffer.WriteCharPtr(" 1\n");
- OStringBuffer aBuffer;
- aBuffer.append(static_cast<sal_Int32>(nOffset));
- while (aBuffer.getLength() < 10)
- aBuffer.insert(0, "0");
- if (nObject == 0)
- aBuffer.append(" 65535 f \n");
- else
- aBuffer.append(" 00000 n \n");
- m_aEditBuffer.WriteOString(aBuffer.toString());
- }
-
- // Write the trailer.
- m_aEditBuffer.WriteCharPtr("trailer\n<</Size ");
- m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
- m_aEditBuffer.WriteCharPtr("/Root ");
- m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
- m_aEditBuffer.WriteCharPtr(" ");
- m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
- m_aEditBuffer.WriteCharPtr(" R\n");
- PDFReferenceElement* pInfo = nullptr;
if (m_pXRefStream)
- pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info"));
- else
- pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info"));
- if (pInfo)
{
- m_aEditBuffer.WriteCharPtr("/Info ");
- m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ // Write the xref stream.
+ // This is a bit meta: the xref stream stores its own offset.
+ sal_Int32 nXRefStreamId = m_aXRef.size();
+ XRefEntry aXRefStreamEntry;
+ aXRefStreamEntry.m_nOffset = nXRefOffset;
+ aXRefStreamEntry.m_bDirty = true;
+ m_aXRef[nXRefStreamId] = aXRefStreamEntry;
+
+ // Write stream data.
+ SvMemoryStream aXRefStream;
+ for (const auto& rXRef : m_aXRef)
+ {
+ const XRefEntry& rEntry = rXRef.second;
+
+ if (!rEntry.m_bDirty)
+ continue;
+
+ // First field.
+ unsigned char nType = 0;
+ switch (rEntry.m_eType)
+ {
+ case XRefEntryType::FREE:
+ nType = 0;
+ break;
+ case XRefEntryType::NOT_COMPRESSED:
+ nType = 1;
+ break;
+ case XRefEntryType::COMPRESSED:
+ nType = 2;
+ break;
+ }
+ aXRefStream.WriteUChar(nType);
+
+ // Second field.
+ const size_t nOffsetLen = 3;
+ for (size_t i = 0; i < nOffsetLen; ++i)
+ {
+ size_t nByte = nOffsetLen - i - 1;
+ // Fields requiring more than one byte are stored with the
+ // high-order byte first.
+ unsigned char nCh = (rEntry.m_nOffset & (0xff << (nByte * 8))) >> (nByte * 8);
+ aXRefStream.WriteUChar(nCh);
+ }
+
+ // Third field.
+ aXRefStream.WriteUChar(0);
+ }
+
+ m_aEditBuffer.WriteUInt32AsString(nXRefStreamId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n<<");
+
+ // ID.
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
+
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr("> <");
+ }
+ m_aEditBuffer.WriteCharPtr("> ] ");
+ }
+
+ // Index.
+ m_aEditBuffer.WriteCharPtr("/Index [ ");
+ for (const auto& rXRef : m_aXRef)
+ {
+ if (!rXRef.second.m_bDirty)
+ continue;
+
+ m_aEditBuffer.WriteUInt32AsString(rXRef.first);
+ m_aEditBuffer.WriteCharPtr(" 1 ");
+ }
+ m_aEditBuffer.WriteCharPtr("] ");
+
+ // Info.
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+ }
+
+ // Length.
+ m_aEditBuffer.WriteCharPtr("/Length ");
+ m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize());
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ // Root.
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
m_aEditBuffer.WriteCharPtr(" ");
- m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
- m_aEditBuffer.WriteCharPtr(" R\n");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R ");
+
+ // Size.
+ m_aEditBuffer.WriteCharPtr("/Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+
+ m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n");
+ aXRefStream.Seek(0);
+ m_aEditBuffer.WriteStream(aXRefStream);
+ m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n");
}
- PDFArrayElement* pID = nullptr;
- if (m_pXRefStream)
- pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"));
else
- pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID"));
- if (pID)
{
- const std::vector<PDFElement*>& rElements = pID->GetElements();
- m_aEditBuffer.WriteCharPtr("/ID [ <");
- for (size_t i = 0; i < rElements.size(); ++i)
+ // Write the xref table.
+ m_aEditBuffer.WriteCharPtr("xref\n");
+ for (const auto& rXRef : m_aXRef)
{
- auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
- if (!pIDString)
+ size_t nObject = rXRef.first;
+ size_t nOffset = rXRef.second.m_nOffset;
+ if (!rXRef.second.m_bDirty)
continue;
- m_aEditBuffer.WriteOString(pIDString->GetValue());
- if ((i + 1) < rElements.size())
- m_aEditBuffer.WriteCharPtr(">\n<");
+ m_aEditBuffer.WriteUInt32AsString(nObject);
+ m_aEditBuffer.WriteCharPtr(" 1\n");
+ OStringBuffer aBuffer;
+ aBuffer.append(static_cast<sal_Int32>(nOffset));
+ while (aBuffer.getLength() < 10)
+ aBuffer.insert(0, "0");
+ if (nObject == 0)
+ aBuffer.append(" 65535 f \n");
+ else
+ aBuffer.append(" 00000 n \n");
+ m_aEditBuffer.WriteOString(aBuffer.toString());
}
- m_aEditBuffer.WriteCharPtr("> ]\n");
- }
- if (!m_aStartXRefs.empty())
- {
- // Write location of the previous cross-reference section.
- m_aEditBuffer.WriteCharPtr("/Prev ");
- m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
- }
+ // Write the trailer.
+ m_aEditBuffer.WriteCharPtr("trailer\n<</Size ");
+ m_aEditBuffer.WriteUInt32AsString(m_aXRef.size());
+ m_aEditBuffer.WriteCharPtr("/Root ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info"));
+ if (pInfo)
+ {
+ m_aEditBuffer.WriteCharPtr("/Info ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue());
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue());
+ m_aEditBuffer.WriteCharPtr(" R\n");
+ }
+ auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID"));
+ if (pID)
+ {
+ const std::vector<PDFElement*>& rElements = pID->GetElements();
+ m_aEditBuffer.WriteCharPtr("/ID [ <");
+ for (size_t i = 0; i < rElements.size(); ++i)
+ {
+ auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]);
+ if (!pIDString)
+ continue;
- m_aEditBuffer.WriteCharPtr(">>\n");
+ m_aEditBuffer.WriteOString(pIDString->GetValue());
+ if ((i + 1) < rElements.size())
+ m_aEditBuffer.WriteCharPtr(">\n<");
+ }
+ m_aEditBuffer.WriteCharPtr("> ]\n");
+ }
+
+ if (!m_aStartXRefs.empty())
+ {
+ // Write location of the previous cross-reference section.
+ m_aEditBuffer.WriteCharPtr("/Prev ");
+ m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back());
+ }
+
+ m_aEditBuffer.WriteCharPtr(">>\n");
+ }
// Write startxref.
m_aEditBuffer.WriteCharPtr("startxref\n");
commit 05ad6dfd4e7201793a6350b440173e4a6335c776
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date: Wed Nov 9 14:19:05 2016 +0100
xmlsecurity PDF sign: handle when Catalog's AcroForm is an indirect dictionary
Normally it's a direct dictionary, but it's OK to have it as a reference, and
then the referenced object is a dictionary.
Change-Id: If09edaf23501883be68148e430c42e721ec68247
Reviewed-on: https://gerrit.libreoffice.org/30719
Tested-by: Jenkins <ci at libreoffice.org>
Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
index 8c01cd9..3b90bf3 100644
--- a/xmlsecurity/source/pdfio/pdfdocument.cxx
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -107,6 +107,8 @@ class PDFObjectElement : public PDFElement
std::vector< std::unique_ptr<PDFObjectElement> > m_aStoredElements;
/// Elements of an object in an object stream.
std::vector< std::unique_ptr<PDFElement> > m_aElements;
+ /// Uncompressed buffer of an object in an object stream.
+ std::unique_ptr<SvMemoryStream> m_pStreamBuffer;
public:
PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
@@ -126,6 +128,8 @@ public:
/// Parse objects stored in this object stream.
void ParseStoredObjects();
std::vector< std::unique_ptr<PDFElement> >& GetStoredElements();
+ SvMemoryStream* GetStreamBuffer() const;
+ void SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer);
};
/// Dictionary object: a set key-value pairs.
@@ -557,50 +561,111 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog obj id");
return false;
}
- m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell();
- m_aXRef[nCatalogId].m_bDirty = true;
- m_aEditBuffer.WriteUInt32AsString(nCatalogId);
- m_aEditBuffer.WriteCharPtr(" 0 obj\n");
- m_aEditBuffer.WriteCharPtr("<<");
- auto pAcroForm = dynamic_cast<PDFDictionaryElement*>(pCatalog->Lookup("AcroForm"));
- if (!pAcroForm)
+ PDFElement* pAcroForm = pCatalog->Lookup("AcroForm");
+ auto pAcroFormReference = dynamic_cast<PDFReferenceElement*>(pAcroForm);
+ if (pAcroFormReference)
{
- // No AcroForm key, assume no signatures.
- m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength());
- m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n");
- m_aEditBuffer.WriteUInt32AsString(nAnnotId);
- m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n");
- }
- else
- {
- // AcroForm key is already there, insert our reference at the Fields end.
- auto it = pAcroForm->GetItems().find("Fields");
- if (it == pAcroForm->GetItems().end())
+ // Write the updated AcroForm key of the Catalog object.
+ PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject();
+ if (!pAcroFormObject)
{
- SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm without required Fields key");
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid AcroForm reference");
return false;
}
- auto pFields = dynamic_cast<PDFArrayElement*>(it->second);
- if (!pFields)
+ sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue();
+ m_aXRef[nAcroFormId].m_eType = XRefEntryType::NOT_COMPRESSED;
+ m_aXRef[nAcroFormId].m_nOffset = m_aEditBuffer.Tell();
+ m_aXRef[nAcroFormId].m_nGenerationNumber = 0;
+ m_aXRef[nAcroFormId].m_bDirty = true;
+ m_aEditBuffer.WriteUInt32AsString(nAcroFormId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+
+ SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer();
+ if (!pStreamBuffer)
{
- SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm Fields is not an array");
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object is in an object stream");
+ return false;
+ }
+
+ if (!pAcroFormObject->Lookup("Fields"))
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object without required Fields key");
+ return false;
+ }
+
+ PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary();
+ if (!pAcroFormDictionary)
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object has no dictionary");
return false;
}
// Offset right before the end of the Fields array.
- sal_uInt64 nFieldsEndOffset = pAcroForm->GetKeyOffset("Fields") + pAcroForm->GetKeyValueLength("Fields") - 1;
- // Length of beginning of the Catalog dictionary -> Fields end.
- sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset();
- m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), nFieldsBeforeEndLength);
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - strlen("]");
+ // Length of beginning of the object dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength);
+
+ // Append our reference at the end of the Fields array.
m_aEditBuffer.WriteCharPtr(" ");
m_aEditBuffer.WriteUInt32AsString(nAnnotId);
m_aEditBuffer.WriteCharPtr(" 0 R");
- // Length of Fields end -> end of the Catalog dictionary.
- sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + pCatalog->GetDictionaryLength() - nFieldsEndOffset;
- m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength);
+
+ // Length of Fields end -> end of the object dictionary.
+ sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(pStreamBuffer->GetData()) + nFieldsEndOffset, nFieldsAfterEndLength);
+
+ m_aEditBuffer.WriteCharPtr("\nendobj\n\n");
+ }
+ else
+ {
+ // Write the updated Catalog object, references nAnnotId.
+ auto pAcroFormDictionary = dynamic_cast<PDFDictionaryElement*>(pAcroForm);
+ m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell();
+ m_aXRef[nCatalogId].m_bDirty = true;
+ m_aEditBuffer.WriteUInt32AsString(nCatalogId);
+ m_aEditBuffer.WriteCharPtr(" 0 obj\n");
+ m_aEditBuffer.WriteCharPtr("<<");
+ if (!pAcroFormDictionary)
+ {
+ // No AcroForm key, assume no signatures.
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength());
+ m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n");
+ }
+ else
+ {
+ // AcroForm key is already there, insert our reference at the Fields end.
+ auto it = pAcroFormDictionary->GetItems().find("Fields");
+ if (it == pAcroFormDictionary->GetItems().end())
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm without required Fields key");
+ return false;
+ }
+
+ auto pFields = dynamic_cast<PDFArrayElement*>(it->second);
+ if (!pFields)
+ {
+ SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm Fields is not an array");
+ return false;
+ }
+
+ // Offset right before the end of the Fields array.
+ sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - 1;
+ // Length of beginning of the Catalog dictionary -> Fields end.
+ sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset();
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), nFieldsBeforeEndLength);
+ m_aEditBuffer.WriteCharPtr(" ");
+ m_aEditBuffer.WriteUInt32AsString(nAnnotId);
+ m_aEditBuffer.WriteCharPtr(" 0 R");
+ // Length of Fields end -> end of the Catalog dictionary.
+ sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + pCatalog->GetDictionaryLength() - nFieldsEndOffset;
+ m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength);
+ }
+ m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
}
- m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n");
// Write the xref table.
sal_uInt64 nXRefOffset = m_aEditBuffer.Tell();
@@ -2736,6 +2801,12 @@ void PDFObjectElement::ParseStoredObjects()
m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(), pStored);
// This is how references know the object is stored inside this object stream.
m_rDoc.SetIDObject(nObjNum, pStored);
+
+ // Store the stream of the object in the object stream for later use.
+ std::unique_ptr<SvMemoryStream> pStreamBuffer(new SvMemoryStream());
+ aStoredStream.Seek(0);
+ pStreamBuffer->WriteStream(aStoredStream);
+ pStored->SetStreamBuffer(pStreamBuffer);
}
}
@@ -2744,6 +2815,16 @@ std::vector< std::unique_ptr<PDFElement> >& PDFObjectElement::GetStoredElements(
return m_aElements;
}
+SvMemoryStream* PDFObjectElement::GetStreamBuffer() const
+{
+ return m_pStreamBuffer.get();
+}
+
+void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer)
+{
+ m_pStreamBuffer = std::move(pStreamBuffer);
+}
+
PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue)
: m_rDoc(rDoc),
m_fObjectValue(fObjectValue),
More information about the Libreoffice-commits
mailing list