diff options
-rw-r--r-- | vcl/inc/pdf/pdfwriter_impl.hxx | 14 | ||||
-rw-r--r-- | vcl/qa/cppunit/pdfexport/data/FilledUpForm.pdf | bin | 0 -> 21204 bytes | |||
-rw-r--r-- | vcl/qa/cppunit/pdfexport/data/FilledUpForm_Source.odt | bin | 0 -> 11580 bytes | |||
-rw-r--r-- | vcl/qa/cppunit/pdfexport/pdfexport2.cxx | 52 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 203 |
5 files changed, 196 insertions, 73 deletions
diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index e7644d7c4884..3f09038068b8 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -483,6 +483,11 @@ struct PDFScreen : public PDFAnnotation } }; +struct PDFWidgetCopy +{ + sal_Int32 m_nObject = -1; +}; + struct PDFWidget : public PDFAnnotation { PDFWriter::WidgetType m_eType; @@ -769,9 +774,10 @@ private: /* structure elements (object ids) that should have ID */ std::unordered_set<sal_Int32> m_StructElemObjsWithID; - /* contains all widgets used in the PDF - */ - std::vector<PDFWidget> m_aWidgets; + /* contains all widgets used in the PDF */ + std::vector<PDFWidget> m_aWidgets; + std::vector<PDFWidgetCopy> m_aCopiedWidgets; + /* maps radio group id to index of radio group control in m_aWidgets */ std::map< sal_Int32, sal_Int32 > m_aRadioGroupWidgets; /* unordered_map for field names, used to ensure unique field names */ @@ -896,6 +902,8 @@ private: /// Writes the form XObject proxy for the image. void writeReferenceXObject(const ReferenceXObjectEmit& rEmit); + void mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage, std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap); + /* tries to find the bitmap by its id and returns its emit data if exists, else creates a new emit data block */ const BitmapEmit& createBitmapEmit( const BitmapEx& rBitmapEx, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams ); diff --git a/vcl/qa/cppunit/pdfexport/data/FilledUpForm.pdf b/vcl/qa/cppunit/pdfexport/data/FilledUpForm.pdf Binary files differnew file mode 100644 index 000000000000..7032f8099411 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/FilledUpForm.pdf diff --git a/vcl/qa/cppunit/pdfexport/data/FilledUpForm_Source.odt b/vcl/qa/cppunit/pdfexport/data/FilledUpForm_Source.odt Binary files differnew file mode 100644 index 000000000000..42efe7f45dd7 --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/FilledUpForm_Source.odt diff --git a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx index 214b03b861d9..f1b8d1bb2219 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport2.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport2.cxx @@ -5989,6 +5989,58 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf163913LeftRightMarginEm) CPPUNIT_ASSERT_DOUBLES_EQUAL(123.750, aRect.at(8).getMinX(), /*delta*/ 2.0); } +CPPUNIT_TEST_FIXTURE(PdfExportTest2, testFormRoundtrip) +{ + // We need to enable PDFium import (and make sure to disable after the test) + bool bResetEnvVar = false; + if (getenv("LO_IMPORT_USE_PDFIUM") == nullptr) + { + bResetEnvVar = true; + osl_setEnvironment(u"LO_IMPORT_USE_PDFIUM"_ustr.pData, u"1"_ustr.pData); + } + comphelper::ScopeGuard aPDFiumEnvVarGuard([&]() { + if (bResetEnvVar) + osl_clearEnvironment(u"LO_IMPORT_USE_PDFIUM"_ustr.pData); + }); + + saveAsPDF(u"FilledUpForm.pdf"); + auto pPdfDocument = parsePDFExport(); + CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount()); + std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0); + std::unique_ptr<vcl::pdf::PDFiumPageObject> pPageObject = pPage->getObject(1); + CPPUNIT_ASSERT_EQUAL(5, pPage->getAnnotationCount()); + + { + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::CheckBox, + pAnnotation->getFormFieldType(pPdfDocument.get())); + } + + { + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(1); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::ComboBox, + pAnnotation->getFormFieldType(pPdfDocument.get())); + } + + { + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(2); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField, + pAnnotation->getFormFieldType(pPdfDocument.get())); + } + + { + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(3); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField, + pAnnotation->getFormFieldType(pPdfDocument.get())); + } + + { + std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(4); + CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField, + pAnnotation->getFormFieldType(pPdfDocument.get())); + } +} + } // end anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index b7ccf81df469..f41309685cc1 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -5640,39 +5640,46 @@ bool PDFWriterImpl::emitCatalog() { aLine.append( "/MarkInfo<</Marked true>>\n" ); } - if( !m_aWidgets.empty() ) + + if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty()) { - aLine.append( "/AcroForm<</Fields[\n" ); - int nWidgets = m_aWidgets.size(); - int nOut = 0; - for( int j = 0; j < nWidgets; j++ ) + aLine.append("/AcroForm"); + aLine.append("<<"); + aLine.append("/Fields"); + aLine.append("[ "); + + for (auto const& rWidget : m_aWidgets) { // output only root fields - if( m_aWidgets[j].m_nParent < 1 ) + if (rWidget.m_nParent < 1) { - aLine.append( m_aWidgets[j].m_nObject ); - aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " ); + appendObjectReference(rWidget.m_nObject, aLine); } } - aLine.append( "\n]" ); + // Add widgets that were copied from an external PDF + for (auto const& rCopiedWidget : m_aCopiedWidgets) + { + appendObjectReference(rCopiedWidget.m_nObject, aLine); + } + aLine.append(" ]\n"); + bool bSigned = false; #if HAVE_FEATURE_NSS if (m_nSignatureObject != -1) - aLine.append( "/SigFlags 3"); + { + aLine.append("/SigFlags 3 "); + bSigned = true; + } #endif + aLine.append("/DR "); + appendObjectReference(getResourceDictObj(), aLine); - aLine.append( "/DR " ); - aLine.append( getResourceDictObj() ); - aLine.append( " 0 R" ); - // NeedAppearances must not be used if PDF is signed - if (m_nPDFA_Version > 0 -#if HAVE_FEATURE_NSS - || ( m_nSignatureObject != -1 ) -#endif - ) - aLine.append( ">>\n" ); - else - aLine.append( "/NeedAppearances true>>\n" ); + // NeedAppearances must not be used if PDF is signed, PDF/A is used or + // we have copied widgets (can't guarantee we have appearance streams in this case) + if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty()) + aLine.append("/NeedAppearances true "); + + aLine.append(">>\n"); } //check if there is a Metadata object @@ -9343,6 +9350,14 @@ void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) return; } + // Get the copied resource map, so we can use that to skip objects we already copied + auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources(); + + // Add page mapping to the copied resources map. + // Needed if we reference the current page and we want to prevent copying the page + // if it is referenced. + rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject); + double aOrigin[2] = { 0.0, 0.0 }; // tdf#160714 use crop box for bounds of embedded PDF object @@ -9389,52 +9404,8 @@ void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) return; } - // Merge link annotations from pPage to our page. - std::vector<filter::PDFObjectElement*> aAnnots; - if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots"_ostr))) - { - for (const auto pElement : pArray->GetElements()) - { - auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); - if (!pReference) - { - continue; - } - - filter::PDFObjectElement* pObject = pReference->LookupObject(); - if (!pObject) - { - continue; - } - - auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr)); - if (!pType || pType->GetValue() != "Annot") - { - continue; - } - - auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr)); - if (!pSubtype || pSubtype->GetValue() != "Link") - { - continue; - } - - // Reference to a link annotation object, remember it. - aAnnots.push_back(pObject); - } - } - if (!aAnnots.empty()) - { - PDFObjectCopier aCopier(*this); - SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer(); - std::map<sal_Int32, sal_Int32> aMap; - for (const auto& pAnnot : aAnnots) - { - // Copy over the annotation and refer to its new id. - sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap); - m_aPages.back().m_aAnnotations.push_back(nNewId); - } - } + // Merge link and widget annotations from pPage to our page. + mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap); nWrappedFormObject = createObject(); // Write the form XObject wrapped below. This is a separate object from @@ -9485,8 +9456,7 @@ void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) } PDFObjectCopier aCopier(*this); - auto & rResources = rExternalPDFStream.getCopiedResources(); - aCopier.copyPageResources(pPage, aLine, rResources); + aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap); aLine.append(" /BBox [ "); aLine.append(aOrigin[0]); @@ -9639,6 +9609,99 @@ void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) return; } +namespace +{ + +sal_Int32 getRootParent(filter::PDFObjectElement* pObject) +{ + auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr)); + if (!pReference) + return pObject->GetObjectValue(); + + auto* pParent = pReference->LookupObject(); + return getRootParent(pParent); +} + +} // end anonymous + +void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage, std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap) +{ + auto* pResult = pPage->Lookup("Annots"_ostr); + filter::PDFArrayElement* pArray = nullptr; + // If the Annots array is a reference - get the array from the referenced object + auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult); + if (pAnnotsReference) + { + filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject(); + pArray = pObject->GetArray(); + } + else + { + // Not a reference so is it an array + pArray = dynamic_cast<filter::PDFArrayElement*>(pResult); + } + + // Have we found our /Annots array? + if (!pArray) + return; + + std::unordered_set<sal_Int32> aAlreadyCopied; + PDFObjectCopier aCopier(*this); + SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer(); + + for (const auto pElement : pArray->GetElements()) + { + auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); + if (!pReference) + continue; + + filter::PDFObjectElement* pObject = pReference->LookupObject(); + if (!pObject) + continue; + + // Get the /Type and the /Subtype + auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr)); + auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr)); + + // Is it a /Annot we want to copy? + if (pType && pType->GetValue() == "Annot" && pSubtype) + { + bool bIsLink = pSubtype->GetValue() == "Link"; + bool bIsWidget = pSubtype->GetValue() == "Widget"; + + // is link or widget + if (!bIsLink && !bIsWidget) + continue; + + // Copy over the annotation and refer to its new id. + sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap); + m_aPages.back().m_aAnnotations.push_back(nNewId); + + if (!bIsWidget) + continue; + + // Find the root + sal_Int32 nRootID = getRootParent(pObject); + + auto aIterator = rCopiedResourcesMap.find(nRootID); + if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ? + continue; + + nNewId = aIterator->second; + + // Ignore if we added the ID already + if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end()) + { + // Add new entry into copied widgets vector + auto& rCopiedWidget = m_aCopiedWidgets.emplace_back(); + rCopiedWidget.m_nObject = nNewId; + aAlreadyCopied.emplace(nNewId); + } + } + } + +} + bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask ) { if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) |