tdf#62728 add PDF/A-2 support, change UI default to use that

There was not much missing to make LibreOffice export valid PDF/A-2,
so let's add that, and switch the UI to use that instead of A-1.

The old PDF/A-1 feature is still accessible via UNO / filter
parameter 'SelectPdfVersion':

- 1 gives you PDF/A-1a
- 2 gives you PDF/A-2b
- 16 gives you PDF 1.6

Change-Id: Iea4262b119bcf33b75f3d1406cc793bdcaec65d1
Reviewed-on: https://gerrit.libreoffice.org/69294
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
diff --git a/filter/source/pdf/impdialog.cxx b/filter/source/pdf/impdialog.cxx
index 5183f6d..50d8469 100644
--- a/filter/source/pdf/impdialog.cxx
+++ b/filter/source/pdf/impdialog.cxx
@@ -462,7 +462,7 @@
    , mxNfQuality(m_xBuilder->weld_metric_spin_button("quality", FieldUnit::PERCENT))
    , mxCbReduceImageResolution(m_xBuilder->weld_check_button("reduceresolution"))
    , mxCoReduceImageResolution(m_xBuilder->weld_combo_box("resolution"))
    , mxCbPDFA1b(m_xBuilder->weld_check_button("pdfa"))
    , mxCbPDFA2b(m_xBuilder->weld_check_button("pdfa"))
    , mxCbTaggedPDF(m_xBuilder->weld_check_button("tagged"))
    , mxCbExportFormFields(m_xBuilder->weld_check_button("forms"))
    , mxFormsFrame(m_xBuilder->weld_widget("formsframe"))
@@ -530,16 +530,16 @@
    mxCbWatermark->connect_toggled( LINK( this, ImpPDFTabGeneralPage, ToggleWatermarkHdl ) );
    mxFtWatermark->set_sensitive(false );
    mxEdWatermark->set_sensitive( false );
    mxCbPDFA1b->connect_toggled(LINK(this, ImpPDFTabGeneralPage, ToggleExportPDFAHdl));
    mxCbPDFA2b->connect_toggled(LINK(this, ImpPDFTabGeneralPage, ToggleExportPDFAHdl));
    switch( pParent->mnPDFTypeSelection )
    {
    default:
    case 0: mxCbPDFA1b->set_active( false ); // PDF 1.5
        mxCbPDFA2b->set_active( false ); // PDF 1.5
        break;
    case 1: mxCbPDFA1b->set_active(true); // PDF/A-1a
    case 2: mxCbPDFA2b->set_active(true); // PDF/A-2a
        break;
    }
    ToggleExportPDFAHdl( *mxCbPDFA1b );
    ToggleExportPDFAHdl( *mxCbPDFA2b );

    mxCbExportFormFields->connect_toggled( LINK( this, ImpPDFTabGeneralPage, ToggleExportFormFieldsHdl ) );

@@ -547,7 +547,7 @@
    mbTaggedPDFUserSelection = pParent->mbUseTaggedPDF;
    mbExportFormFieldsUserSelection = pParent->mbExportFormFields;

    if( !mxCbPDFA1b->get_active() )
    if( !mxCbPDFA2b->get_active() )
    {
        // the value for PDF/A set by the ToggleExportPDFAHdl method called before
        mxCbTaggedPDF->set_active( mbTaggedPDFUserSelection  );
@@ -643,9 +643,9 @@
    }

    pParent->mnPDFTypeSelection = 0;
    if( mxCbPDFA1b->get_active() )
    if( mxCbPDFA2b->get_active() )
    {
        pParent->mnPDFTypeSelection = 1;
        pParent->mnPDFTypeSelection = 2;
        pParent->mbUseTaggedPDF =  mbTaggedPDFUserSelection;
        pParent->mbExportFormFields = mbExportFormFieldsUserSelection;
    }
@@ -759,11 +759,11 @@
    ImpPDFTabSecurityPage* pSecPage = mpParent ? mpParent->getSecurityPage() : nullptr;
    if (pSecPage)
    {
        pSecPage->ImplPDFASecurityControl(!mxCbPDFA1b->get_active());
        pSecPage->ImplPDFASecurityControl(!mxCbPDFA2b->get_active());
    }

    // PDF/A-1 needs tagged PDF, so force disable the control, will be forced in pdfexport.
    bool bPDFA1Sel = mxCbPDFA1b->get_active();
    bool bPDFA1Sel = mxCbPDFA2b->get_active();
    mxFormsFrame->set_sensitive(bPDFA1Sel);
    if(bPDFA1Sel)
    {
@@ -784,14 +784,14 @@
        mxCbExportFormFields->set_sensitive(true);
    }

    // PDF/A-1 doesn't allow launch action, so enable/disable the selection on
    // PDF/A-2 doesn't allow launch action, so enable/disable the selection on
    // Link page
    ImpPDFTabLinksPage* pLinksPage = mpParent ? mpParent->getLinksPage() : nullptr;
    if (pLinksPage)
        pLinksPage->ImplPDFALinkControl(!mxCbPDFA1b->get_active());
        pLinksPage->ImplPDFALinkControl(!mxCbPDFA2b->get_active());

    // if a password was set, inform the user that this will not be used in PDF/A case
    if( mxCbPDFA1b->get_active() && pSecPage && pSecPage->hasPassword() )
    if( mxCbPDFA2b->get_active() && pSecPage && pSecPage->hasPassword() )
    {
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xContainer.get(),
                                                  VclMessageType::Warning, VclButtonsType::Ok,
@@ -1347,11 +1347,11 @@

    // now check the status of PDF/A selection
    // and set the link action accordingly
    // PDF/A-1 doesn't allow launch action on links
    // PDF/A-2 doesn't allow launch action on links

    ImpPDFTabGeneralPage* pGeneralPage = pParent->getGeneralPage();
    if (pGeneralPage)
        ImplPDFALinkControl(!pGeneralPage->mxCbPDFA1b->get_active());
        ImplPDFALinkControl(!pGeneralPage->mxCbPDFA2b->get_active());
}


diff --git a/filter/source/pdf/impdialog.hxx b/filter/source/pdf/impdialog.hxx
index f54d714..73b86e77 100644
--- a/filter/source/pdf/impdialog.hxx
+++ b/filter/source/pdf/impdialog.hxx
@@ -182,7 +182,7 @@
    std::unique_ptr<weld::MetricSpinButton> mxNfQuality;
    std::unique_ptr<weld::CheckButton> mxCbReduceImageResolution;
    std::unique_ptr<weld::ComboBox> mxCoReduceImageResolution;
    std::unique_ptr<weld::CheckButton> mxCbPDFA1b;
    std::unique_ptr<weld::CheckButton> mxCbPDFA2b;
    std::unique_ptr<weld::CheckButton> mxCbTaggedPDF;
    std::unique_ptr<weld::CheckButton> mxCbExportFormFields;
    std::unique_ptr<weld::Widget> mxFormsFrame;
@@ -227,7 +227,7 @@

    void                        GetFilterConfigItem(ImpPDFTabDialog* paParent);
    void                        SetFilterConfigItem(ImpPDFTabDialog* paParent);
    bool                        IsPdfaSelected() const { return mxCbPDFA1b->get_active(); }
    bool                        IsPdfaSelected() const { return mxCbPDFA2b->get_active(); }
};

/// Class tab page viewer
diff --git a/filter/source/pdf/pdfexport.cxx b/filter/source/pdf/pdfexport.cxx
index 8710146..2ca3829 100644
--- a/filter/source/pdf/pdfexport.cxx
+++ b/filter/source/pdf/pdfexport.cxx
@@ -601,6 +601,13 @@
                mbEncrypt = false;              // no encryption
                xEnc.clear();
                break;
            case 2:
                aContext.Version    = vcl::PDFWriter::PDFVersion::PDF_A_2;
                mbUseTaggedPDF = true;          // force the tagged PDF as well
                mbRemoveTransparencies = false; // PDF/A-2 does allow transparencies
                mbEncrypt = false;              // no encryption
                xEnc.clear();
                break;
            case 16:
                aContext.Version = vcl::PDFWriter::PDFVersion::PDF_1_6;
                break;
diff --git a/filter/uiconfig/ui/pdfgeneralpage.ui b/filter/uiconfig/ui/pdfgeneralpage.ui
index d7d9ff0..2443e68 100644
--- a/filter/uiconfig/ui/pdfgeneralpage.ui
+++ b/filter/uiconfig/ui/pdfgeneralpage.ui
@@ -463,11 +463,11 @@
                </child>
                <child>
                  <object class="GtkCheckButton" id="pdfa">
                    <property name="label" translatable="yes" context="pdfgeneralpage|pdfa">Archive P_DF/A-1a (ISO 19005-1)</property>
                    <property name="label" translatable="yes" context="pdfgeneralpage|pdfa">Archive P_DF/A-2b (ISO 19005-2)</property>
                    <property name="visible">True</property>
                    <property name="can_focus">True</property>
                    <property name="receives_default">False</property>
                    <property name="tooltip_text" translatable="yes" context="pdfgeneralpage|pdfa|tooltip_text">Creates an ISO 19005-1 compliant PDF file, ideal for long-term document preservation</property>
                    <property name="tooltip_text" translatable="yes" context="pdfgeneralpage|pdfa|tooltip_text">Creates an ISO 19005-2 compliant PDF file, ideal for long-term document preservation</property>
                    <property name="use_underline">True</property>
                    <property name="xalign">0</property>
                    <property name="draw_indicator">True</property>
diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx
index c8cd7ec..949cdc7 100644
--- a/include/vcl/pdfwriter.hxx
+++ b/include/vcl/pdfwriter.hxx
@@ -103,7 +103,7 @@
    enum class Orientation { Portrait, Inherit };

    // in case the below enum is added PDF_1_6 PDF_1_7, please add them just after PDF_1_5
    enum class PDFVersion { PDF_1_2, PDF_1_3, PDF_1_4, PDF_1_5, PDF_1_6, PDF_A_1 };//i59651, PDF/A-1b & -1a, only -1b implemented for now
    enum class PDFVersion { PDF_1_2, PDF_1_3, PDF_1_4, PDF_1_5, PDF_1_6, PDF_A_1, PDF_A_2 };//i59651, PDF/A-1b & -1a, only -1b implemented for now
    // for the meaning of DestAreaType please look at PDF Reference Manual
    // version 1.4 section 8.2.1, page 475
    enum class DestAreaType { XYZ, FitRectangle };
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index b6e4629..ba3ae2a 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -1699,6 +1699,7 @@
        m_bEncryptThisStream( false ),
        m_nAccessPermissions(0),
        m_bIsPDF_A1( false ),
        m_bIsPDF_A2( false ),
        m_rOuterFace( i_rOuterFace )
{
#ifdef DO_TEST_PDF
@@ -1793,6 +1794,10 @@
    if( m_bIsPDF_A1 )
        m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour

    m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
    if( m_bIsPDF_A2 )
        m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features

    if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
        SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
    else
@@ -3734,7 +3739,7 @@
// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
        aLine.append( "<</Type/Annot" );
        if( m_bIsPDF_A1 )
        if( m_bIsPDF_A1 || m_bIsPDF_A2 )
            aLine.append( "/F 4" );
        aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );

@@ -3956,7 +3961,7 @@
// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
        aLine.append( "<</Type/Annot" );
        if( m_bIsPDF_A1 )
        if( m_bIsPDF_A1 || m_bIsPDF_A2 )
            aLine.append( "/F 4" );
        aLine.append( "/Subtype/Text/Rect[" );

@@ -4756,8 +4761,11 @@
                }
                else if( rWidget.m_aListEntries.empty() )
                {
                    // create a reset form action
                    aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
                    if( !m_bIsPDF_A2 )
                    {
                        // create a reset form action
                        aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
                    }
                }
                else if( rWidget.m_bSubmit )
                {
@@ -5176,7 +5184,7 @@
        aLine.append( getResourceDictObj() );
        aLine.append( " 0 R" );
        // NeedAppearances must not be used if PDF is signed
        if( m_bIsPDF_A1
        if( m_bIsPDF_A1 || m_bIsPDF_A2
#if HAVE_FEATURE_NSS
            || ( m_nSignatureObject != -1 )
#endif
@@ -5490,7 +5498,7 @@
// emits the output intent dictionary
sal_Int32 PDFWriterImpl::emitOutputIntent()
{
    if( !m_bIsPDF_A1 )
    if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
        return 0;

    //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
@@ -5597,7 +5605,7 @@
// emits the document metadata
sal_Int32 PDFWriterImpl::emitDocumentMetadata()
{
    if( !m_bIsPDF_A1 )
    if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
        return 0;

    //get the object number for all the destinations
@@ -5618,8 +5626,14 @@
        //PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
        aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
        aMetadataStream.append( "      xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" );
        aMetadataStream.append( "   <pdfaid:part>1</pdfaid:part>\n" );
        aMetadataStream.append( "   <pdfaid:conformance>A</pdfaid:conformance>\n" );
        if( m_bIsPDF_A2 )
            aMetadataStream.append( "   <pdfaid:part>2</pdfaid:part>\n" );
        else
            aMetadataStream.append( "   <pdfaid:part>1</pdfaid:part>\n" );
        if( m_bIsPDF_A2 )
            aMetadataStream.append( "   <pdfaid:conformance>B</pdfaid:conformance>\n" );
        else
            aMetadataStream.append( "   <pdfaid:conformance>A</pdfaid:conformance>\n" );
        aMetadataStream.append( "  </rdf:Description>\n" );
        //... Dublin Core properties go here
        if( !m_aContext.DocumentInfo.Title.isEmpty() ||
diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx
index a6f2f73..923454d 100644
--- a/vcl/source/gdi/pdfwriter_impl.hxx
+++ b/vcl/source/gdi/pdfwriter_impl.hxx
@@ -976,6 +976,8 @@

    /* true if PDF/A-1a or PDF/A-1b is output */
    bool            m_bIsPDF_A1;
    /* true if PDF/A-2a is output */
    bool            m_bIsPDF_A2;
    PDFWriter&      m_rOuterFace;

    /*