[Libreoffice-commits] core.git: Branch 'feature/accessibilitycheck' - 4 commits - include/svx include/tools svx/source svx/uiconfig sw/qa sw/source tools/source vcl/inc vcl/Library_vcl.mk vcl/source

Tomaž Vajngerl (via logerrit) logerrit at kemper.freedesktop.org
Fri Dec 20 16:46:59 UTC 2019


 include/svx/AccessibilityCheck.hxx                       |   23 +
 include/svx/AccessibilityCheckDialog.hxx                 |   14 -
 include/tools/XmlWriter.hxx                              |    2 
 svx/source/dialog/AccessibilityCheckDialog.cxx           |   18 +
 svx/uiconfig/ui/accessibilitycheckentry.ui               |   14 +
 sw/qa/core/accessibilitycheck/AccessibilityCheckTest.cxx |    6 
 sw/source/core/access/AccessibilityCheck.cxx             |   99 +++-----
 sw/source/core/inc/AccessibilityCheck.hxx                |   12 +
 tools/source/xml/XmlWriter.cxx                           |   11 
 vcl/Library_vcl.mk                                       |    1 
 vcl/inc/pdf/XmpMetadata.hxx                              |   48 ++++
 vcl/source/gdi/pdfwriter_impl.cxx                        |  150 ++----------
 vcl/source/gdi/pdfwriter_impl.hxx                        |    4 
 vcl/source/pdf/XmpMetadata.cxx                           |  175 +++++++++++++++
 14 files changed, 380 insertions(+), 197 deletions(-)

New commits:
commit adf3e5cb73cdd0846ad17c22cdf48b646ca51b0a
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Fri Dec 20 12:12:35 2019 +0100
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Fri Dec 20 12:12:35 2019 +0100

    acc. check: add "goto" button to see the acc. issue in document
    
    This adds the UI for the goto functionality to the UI, but
    currently no goto implementation to jump to the specific issue
    area in the document is written.
    
    Change-Id: I5b326a000e62231c491c179b1ff8accde15e1e98

diff --git a/include/svx/AccessibilityCheck.hxx b/include/svx/AccessibilityCheck.hxx
index db66ccb24fd4..ba4b01d9d6a5 100644
--- a/include/svx/AccessibilityCheck.hxx
+++ b/include/svx/AccessibilityCheck.hxx
@@ -37,6 +37,7 @@ public:
     {
     }
     virtual ~AccessibilityCheckResult() {}
+    virtual void gotoIssue() const = 0;
 };
 
 class SVX_DLLPUBLIC AccessibilityCheckResultCollection
diff --git a/include/svx/AccessibilityCheckDialog.hxx b/include/svx/AccessibilityCheckDialog.hxx
index 4b602a911a42..10a9a1803ad6 100644
--- a/include/svx/AccessibilityCheckDialog.hxx
+++ b/include/svx/AccessibilityCheckDialog.hxx
@@ -27,6 +27,7 @@ private:
     std::unique_ptr<weld::Builder> m_xBuilder;
     std::unique_ptr<weld::Container> m_xContainer;
     std::unique_ptr<weld::Label> m_xLabel;
+    std::unique_ptr<weld::Button> m_xGotoButton;
 
     std::shared_ptr<AccessibilityCheckResult> const& m_rAccessibilityCheckResult;
 
@@ -35,6 +36,8 @@ public:
         weld::Container* pParent, weld::Window* pDialog,
         std::shared_ptr<AccessibilityCheckResult> const& rAccessibilityCheckResult);
     weld::Widget* get_widget() const { return m_xContainer.get(); }
+
+    DECL_LINK(GotoButtonClicked, weld::Button&, void);
 };
 
 class SVX_DLLPUBLIC AccessibilityCheckDialog final : public weld::GenericDialogController
diff --git a/svx/source/dialog/AccessibilityCheckDialog.cxx b/svx/source/dialog/AccessibilityCheckDialog.cxx
index f6add929e351..e88ca36ebb89 100644
--- a/svx/source/dialog/AccessibilityCheckDialog.cxx
+++ b/svx/source/dialog/AccessibilityCheckDialog.cxx
@@ -20,12 +20,19 @@ AccessibilityCheckEntry::AccessibilityCheckEntry(
     , m_xBuilder(Application::CreateBuilder(pParent, "svx/ui/accessibilitycheckentry.ui"))
     , m_xContainer(m_xBuilder->weld_container("accessibilityCheckEntryBox"))
     , m_xLabel(m_xBuilder->weld_label("accessibilityCheckEntryLabel"))
+    , m_xGotoButton(m_xBuilder->weld_button("accessibilityCheckEntryGotoButton"))
     , m_rAccessibilityCheckResult(rAccessibilityCheckResult)
 {
     m_xLabel->set_label(m_rAccessibilityCheckResult->m_aIssueText);
+    m_xGotoButton->connect_clicked(LINK(this, AccessibilityCheckEntry, GotoButtonClicked));
     m_xContainer->show();
 }
 
+IMPL_LINK_NOARG(AccessibilityCheckEntry, GotoButtonClicked, weld::Button&, void)
+{
+    m_rAccessibilityCheckResult->gotoIssue();
+}
+
 AccessibilityCheckDialog::AccessibilityCheckDialog(
     weld::Window* pParent,
     AccessibilityCheckResultCollection const& rAccessibilityCheckResultCollection)
diff --git a/svx/uiconfig/ui/accessibilitycheckentry.ui b/svx/uiconfig/ui/accessibilitycheckentry.ui
index bfd013491e51..4a4633eb3f14 100644
--- a/svx/uiconfig/ui/accessibilitycheckentry.ui
+++ b/svx/uiconfig/ui/accessibilitycheckentry.ui
@@ -10,7 +10,9 @@
       <object class="GtkLabel" id="accessibilityCheckEntryLabel">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
         <property name="selectable">True</property>
+        <property name="xalign">0</property>
       </object>
       <packing>
         <property name="expand">False</property>
@@ -19,7 +21,17 @@
       </packing>
     </child>
     <child>
-      <placeholder/>
+      <object class="GtkButton" id="accessibilityCheckEntryGotoButton">
+        <property name="label" translatable="yes" context="accessibilitycheckentry|accessibilityCheckEntryGotoButton">Goto Issue</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">1</property>
+      </packing>
     </child>
   </object>
 </interface>
diff --git a/sw/source/core/inc/AccessibilityCheck.hxx b/sw/source/core/inc/AccessibilityCheck.hxx
index 0ce80bf49f45..05a88a3a4975 100644
--- a/sw/source/core/inc/AccessibilityCheck.hxx
+++ b/sw/source/core/inc/AccessibilityCheck.hxx
@@ -24,6 +24,8 @@ public:
         : svx::AccessibilityCheckResult(eIssueID)
     {
     }
+
+    void gotoIssue() const override {}
 };
 
 class SW_DLLPUBLIC AccessibilityCheck final : public svx::AccessibilityCheck
commit 8dc74a8f955f883ff6a8deb769d4210b8752d7e1
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Fri Dec 20 09:57:23 2019 +0100
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Fri Dec 20 10:04:40 2019 +0100

    acc. check: add writer specific accessibility check result
    
    This adds sw::AccessibilityCheckResult and refactors the existing
    code to work with it.
    In addition adds AccessibilityCheckResultCollection which holds
    the vector of results.
    
    Change-Id: Ide0fdbdba337bbf8c2fd18770895fb63a22528b6

diff --git a/include/svx/AccessibilityCheck.hxx b/include/svx/AccessibilityCheck.hxx
index 526082b42a4d..db66ccb24fd4 100644
--- a/include/svx/AccessibilityCheck.hxx
+++ b/include/svx/AccessibilityCheck.hxx
@@ -11,9 +11,10 @@
 #ifndef INCLUDED_SVX_ACCESSIBILITYCHECK_HXX
 #define INCLUDED_SVX_ACCESSIBILITYCHECK_HXX
 
+#include <svx/svxdllapi.h>
 #include <vector>
+#include <memory>
 #include <rtl/ustring.hxx>
-#include <svx/svxdllapi.h>
 
 namespace svx
 {
@@ -25,7 +26,7 @@ enum class AccessibilityIssueID
     STYLE_LANGUAGE
 };
 
-class SVX_DLLPUBLIC AccessibilityCheckResult final
+class SVX_DLLPUBLIC AccessibilityCheckResult
 {
 public:
     AccessibilityIssueID m_eIssueID;
@@ -35,22 +36,29 @@ public:
         : m_eIssueID(eIssueID)
     {
     }
+    virtual ~AccessibilityCheckResult() {}
+};
+
+class SVX_DLLPUBLIC AccessibilityCheckResultCollection
+{
+private:
+    std::vector<std::shared_ptr<AccessibilityCheckResult>> m_aResults;
+
+public:
+    std::vector<std::shared_ptr<AccessibilityCheckResult>>& getResults() { return m_aResults; }
 };
 
 class SVX_DLLPUBLIC AccessibilityCheck
 {
 protected:
-    std::vector<AccessibilityCheckResult> m_aResultCollection;
+    AccessibilityCheckResultCollection m_aResultCollection;
 
 public:
     virtual ~AccessibilityCheck() {}
 
     virtual void check() = 0;
 
-    std::vector<AccessibilityCheckResult> const& getResultCollecton()
-    {
-        return m_aResultCollection;
-    }
+    AccessibilityCheckResultCollection& getResultCollecton() { return m_aResultCollection; }
 };
 
 } // end svx namespace
diff --git a/include/svx/AccessibilityCheckDialog.hxx b/include/svx/AccessibilityCheckDialog.hxx
index d66bf3b887aa..4b602a911a42 100644
--- a/include/svx/AccessibilityCheckDialog.hxx
+++ b/include/svx/AccessibilityCheckDialog.hxx
@@ -28,18 +28,19 @@ private:
     std::unique_ptr<weld::Container> m_xContainer;
     std::unique_ptr<weld::Label> m_xLabel;
 
-    AccessibilityCheckResult const& m_rAccessibilityCheckResult;
+    std::shared_ptr<AccessibilityCheckResult> const& m_rAccessibilityCheckResult;
 
 public:
-    AccessibilityCheckEntry(weld::Container* pParent, weld::Window* pDialog,
-                            AccessibilityCheckResult const& rAccessibilityCheckResult);
+    AccessibilityCheckEntry(
+        weld::Container* pParent, weld::Window* pDialog,
+        std::shared_ptr<AccessibilityCheckResult> const& rAccessibilityCheckResult);
     weld::Widget* get_widget() const { return m_xContainer.get(); }
 };
 
 class SVX_DLLPUBLIC AccessibilityCheckDialog final : public weld::GenericDialogController
 {
 private:
-    std::vector<AccessibilityCheckResult> m_rAccessibilityCheckResultCollection;
+    AccessibilityCheckResultCollection m_aAccessibilityCheckResultCollection;
     std::vector<std::unique_ptr<AccessibilityCheckEntry>> m_aAccessibilityCheckEntries;
 
     // Controls
@@ -48,7 +49,7 @@ private:
 public:
     AccessibilityCheckDialog(
         weld::Window* pParent,
-        std::vector<AccessibilityCheckResult> const& rAccessibilityCheckResultCollection);
+        AccessibilityCheckResultCollection const& rAccessibilityCheckResultCollection);
     virtual ~AccessibilityCheckDialog() override;
     virtual short run() override;
 };
diff --git a/svx/source/dialog/AccessibilityCheckDialog.cxx b/svx/source/dialog/AccessibilityCheckDialog.cxx
index e40f2187f445..f6add929e351 100644
--- a/svx/source/dialog/AccessibilityCheckDialog.cxx
+++ b/svx/source/dialog/AccessibilityCheckDialog.cxx
@@ -15,23 +15,23 @@ namespace svx
 {
 AccessibilityCheckEntry::AccessibilityCheckEntry(
     weld::Container* pParent, weld::Window* pDialog,
-    AccessibilityCheckResult const& rAccessibilityCheckResult)
+    std::shared_ptr<AccessibilityCheckResult> const& rAccessibilityCheckResult)
     : m_pDialog(pDialog)
     , m_xBuilder(Application::CreateBuilder(pParent, "svx/ui/accessibilitycheckentry.ui"))
     , m_xContainer(m_xBuilder->weld_container("accessibilityCheckEntryBox"))
     , m_xLabel(m_xBuilder->weld_label("accessibilityCheckEntryLabel"))
     , m_rAccessibilityCheckResult(rAccessibilityCheckResult)
 {
-    m_xLabel->set_label(m_rAccessibilityCheckResult.m_aIssueText);
+    m_xLabel->set_label(m_rAccessibilityCheckResult->m_aIssueText);
     m_xContainer->show();
 }
 
 AccessibilityCheckDialog::AccessibilityCheckDialog(
     weld::Window* pParent,
-    std::vector<AccessibilityCheckResult> const& rAccessibilityCheckResultCollection)
+    AccessibilityCheckResultCollection const& rAccessibilityCheckResultCollection)
     : GenericDialogController(pParent, "svx/ui/accessibilitycheckdialog.ui",
                               "AccessibilityCheckDialog")
-    , m_rAccessibilityCheckResultCollection(rAccessibilityCheckResultCollection)
+    , m_aAccessibilityCheckResultCollection(rAccessibilityCheckResultCollection)
     , m_xAccessibilityCheckBox(m_xBuilder->weld_box("accessibilityCheckBox"))
 {
 }
@@ -42,7 +42,8 @@ short AccessibilityCheckDialog::run()
 {
     sal_Int32 i = 0;
 
-    for (AccessibilityCheckResult const& rResult : m_rAccessibilityCheckResultCollection)
+    for (std::shared_ptr<AccessibilityCheckResult> const& rResult :
+         m_aAccessibilityCheckResultCollection.getResults())
     {
         auto xEntry = std::make_unique<AccessibilityCheckEntry>(m_xAccessibilityCheckBox.get(),
                                                                 m_xDialog.get(), rResult);
diff --git a/sw/qa/core/accessibilitycheck/AccessibilityCheckTest.cxx b/sw/qa/core/accessibilitycheck/AccessibilityCheckTest.cxx
index eff09430e92d..48f980b505e5 100644
--- a/sw/qa/core/accessibilitycheck/AccessibilityCheckTest.cxx
+++ b/sw/qa/core/accessibilitycheck/AccessibilityCheckTest.cxx
@@ -29,10 +29,10 @@ CPPUNIT_TEST_FIXTURE(AccessibilityCheckTest, testCheck)
     CPPUNIT_ASSERT(pDoc);
     sw::AccessibilityCheck aCheck(pDoc);
     aCheck.check();
-    auto& aResults = aCheck.getResultCollecton();
+    auto const& aResults = aCheck.getResultCollecton().getResults();
     CPPUNIT_ASSERT_EQUAL(size_t(2), aResults.size());
-    CPPUNIT_ASSERT_EQUAL(svx::AccessibilityIssueID::DOCUMENT_LANGUAGE, aResults[0].m_eIssueID);
-    CPPUNIT_ASSERT_EQUAL(svx::AccessibilityIssueID::DOCUMENT_TITLE, aResults[1].m_eIssueID);
+    CPPUNIT_ASSERT_EQUAL(svx::AccessibilityIssueID::DOCUMENT_LANGUAGE, aResults[0]->m_eIssueID);
+    CPPUNIT_ASSERT_EQUAL(svx::AccessibilityIssueID::DOCUMENT_TITLE, aResults[1]->m_eIssueID);
 }
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/access/AccessibilityCheck.cxx b/sw/source/core/access/AccessibilityCheck.cxx
index c37f5975a04b..b06f9012f738 100644
--- a/sw/source/core/access/AccessibilityCheck.cxx
+++ b/sw/source/core/access/AccessibilityCheck.cxx
@@ -44,15 +44,23 @@ OUString sTextContrast("Text contrast is too low.");
 OUString sTextBlinking("Blinking text.");
 OUString sAvoidFootnotes("Avoid footnotes.");
 OUString sAvoidEndnotes("Avoid endnotes.");
+
+void lclAddIssue(svx::AccessibilityCheckResultCollection& rResultCollection, OUString const& rText,
+                 svx::AccessibilityIssueID eIssue = svx::AccessibilityIssueID::UNSPECIFIED)
+{
+    auto pResult = std::make_shared<sw::AccessibilityCheckResult>(eIssue);
+    pResult->m_aIssueText = rText;
+    rResultCollection.getResults().push_back(pResult);
+}
 }
 
 class BaseCheck
 {
 protected:
-    std::vector<svx::AccessibilityCheckResult>& m_rResultCollection;
+    svx::AccessibilityCheckResultCollection& m_rResultCollection;
 
 public:
-    BaseCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    BaseCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : m_rResultCollection(rResultCollection)
     {
     }
@@ -62,7 +70,7 @@ public:
 class NodeCheck : public BaseCheck
 {
 public:
-    NodeCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    NodeCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : BaseCheck(rResultCollection)
     {
     }
@@ -82,14 +90,13 @@ class NoTextNodeAltTextCheck : public NodeCheck
         if (sAlternative.isEmpty())
         {
             OUString sName = pNoTextNode->GetFlyFormat()->GetName();
-            svx::AccessibilityCheckResult aResult;
-            aResult.m_aIssueText = sNoAlt.replaceAll("%OBJECT_NAME%", sName);
-            m_rResultCollection.push_back(aResult);
+            OUString sIssueText = sNoAlt.replaceAll("%OBJECT_NAME%", sName);
+            lclAddIssue(m_rResultCollection, sIssueText);
         }
     }
 
 public:
-    NoTextNodeAltTextCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    NoTextNodeAltTextCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : NodeCheck(rResultCollection)
     {
     }
@@ -118,9 +125,8 @@ private:
         if (rTable.IsTableComplex())
         {
             OUString sName = rTable.GetTableStyleName();
-            svx::AccessibilityCheckResult aResult;
-            aResult.m_aIssueText = sTableMergeSplit.replaceAll("%OBJECT_NAME%", sName);
-            m_rResultCollection.push_back(aResult);
+            OUString sIssueText = sTableMergeSplit.replaceAll("%OBJECT_NAME%", sName);
+            lclAddIssue(m_rResultCollection, sIssueText);
         }
         else
         {
@@ -149,9 +155,8 @@ private:
                 if (!bAllColumnsSameSize)
                 {
                     OUString sName = rTable.GetTableStyleName();
-                    svx::AccessibilityCheckResult aResult;
-                    aResult.m_aIssueText = sTableMergeSplit.replaceAll("%OBJECT_NAME%", sName);
-                    m_rResultCollection.push_back(aResult);
+                    OUString sIssueText = sTableMergeSplit.replaceAll("%OBJECT_NAME%", sName);
+                    lclAddIssue(m_rResultCollection, sIssueText);
                 }
             }
         }
@@ -159,7 +164,7 @@ private:
 
 public:
     TableNodeMergeSplitCheck(
-        std::vector<svx::AccessibilityCheckResult>& rAccessibilityCheckResultCollection)
+        svx::AccessibilityCheckResultCollection& rAccessibilityCheckResultCollection)
         : NodeCheck(rAccessibilityCheckResultCollection)
     {
     }
@@ -186,7 +191,7 @@ private:
     };
 
 public:
-    NumberingCheck(std::vector<svx::AccessibilityCheckResult>& rAccessibilityCheckResultCollection)
+    NumberingCheck(svx::AccessibilityCheckResultCollection& rAccessibilityCheckResultCollection)
         : NodeCheck(rAccessibilityCheckResultCollection)
         , pPreviousTextNode(nullptr)
     {
@@ -203,10 +208,9 @@ public:
                     if (pCurrent->GetTextNode()->GetText().startsWith(rPair.second)
                         && pPreviousTextNode->GetText().startsWith(rPair.first))
                     {
-                        svx::AccessibilityCheckResult aResult;
                         OUString sNumbering = rPair.first + " " + rPair.second + "...";
-                        aResult.m_aIssueText = sFakeNumbering.replaceAll("%NUMBERING%", sNumbering);
-                        m_rResultCollection.push_back(aResult);
+                        OUString sIssueText = sFakeNumbering.replaceAll("%NUMBERING%", sNumbering);
+                        lclAddIssue(m_rResultCollection, sIssueText);
                     }
                 }
             }
@@ -230,16 +234,15 @@ private:
                 OUString sText = xTextRange->getString();
                 if (INetURLObject(sText) == INetURLObject(sHyperlink))
                 {
-                    svx::AccessibilityCheckResult aResult;
-                    aResult.m_aIssueText = sHyperlinkTextIsLink.replaceFirst("%LINK%", sHyperlink);
-                    m_rResultCollection.push_back(aResult);
+                    OUString sIssueText = sHyperlinkTextIsLink.replaceFirst("%LINK%", sHyperlink);
+                    lclAddIssue(m_rResultCollection, sIssueText);
                 }
             }
         }
     }
 
 public:
-    HyperlinkCheck(std::vector<svx::AccessibilityCheckResult>& rAccessibilityCheckResultCollection)
+    HyperlinkCheck(svx::AccessibilityCheckResultCollection& rAccessibilityCheckResultCollection)
         : NodeCheck(rAccessibilityCheckResultCollection)
     {
     }
@@ -366,16 +369,13 @@ private:
             double fContrastRatio = calculateContrastRatio(aForegroundColor, aBackgroundColor);
             if (fContrastRatio < 4.5)
             {
-                svx::AccessibilityCheckResult aResult;
-                aResult.m_aIssueText = sTextContrast;
-                m_rResultCollection.push_back(aResult);
+                lclAddIssue(m_rResultCollection, sTextContrast);
             }
         }
     }
 
 public:
-    TextContrastCheck(
-        std::vector<svx::AccessibilityCheckResult>& rAccessibilityCheckResultCollection)
+    TextContrastCheck(svx::AccessibilityCheckResultCollection& rAccessibilityCheckResultCollection)
         : NodeCheck(rAccessibilityCheckResultCollection)
     {
     }
@@ -417,16 +417,13 @@ private:
 
             if (bBlinking)
             {
-                svx::AccessibilityCheckResult aResult;
-                aResult.m_aIssueText = sTextBlinking;
-                m_rResultCollection.push_back(aResult);
+                lclAddIssue(m_rResultCollection, sTextBlinking);
             }
         }
     }
 
 public:
-    BlinkingTextCheck(
-        std::vector<svx::AccessibilityCheckResult>& rAccessibilityCheckResultCollection)
+    BlinkingTextCheck(svx::AccessibilityCheckResultCollection& rAccessibilityCheckResultCollection)
         : NodeCheck(rAccessibilityCheckResultCollection)
     {
     }
@@ -458,7 +455,7 @@ public:
 class DocumentCheck : public BaseCheck
 {
 public:
-    DocumentCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    DocumentCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : BaseCheck(rResultCollection)
     {
     }
@@ -470,7 +467,7 @@ public:
 class DocumentDefaultLanguageCheck : public DocumentCheck
 {
 public:
-    DocumentDefaultLanguageCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    DocumentDefaultLanguageCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : DocumentCheck(rResultCollection)
     {
     }
@@ -483,9 +480,8 @@ public:
         LanguageType eLanguage = rLang.GetLanguage();
         if (eLanguage == LANGUAGE_NONE)
         {
-            svx::AccessibilityCheckResult aResult(svx::AccessibilityIssueID::DOCUMENT_LANGUAGE);
-            aResult.m_aIssueText = sDocumentDefaultLanguage;
-            m_rResultCollection.push_back(aResult);
+            lclAddIssue(m_rResultCollection, sDocumentDefaultLanguage,
+                        svx::AccessibilityIssueID::DOCUMENT_LANGUAGE);
         }
         else
         {
@@ -494,11 +490,10 @@ public:
                 const SwAttrSet& rAttrSet = pTextFormatCollection->GetAttrSet();
                 if (rAttrSet.GetLanguage(false).GetLanguage() == LANGUAGE_NONE)
                 {
-                    svx::AccessibilityCheckResult aResult(
-                        svx::AccessibilityIssueID::STYLE_LANGUAGE);
                     OUString sName = pTextFormatCollection->GetName();
-                    aResult.m_aIssueText = sStyleNoLanguage.replaceAll("%STYLE_NAME%", sName);
-                    m_rResultCollection.push_back(aResult);
+                    OUString sIssueText = sStyleNoLanguage.replaceAll("%STYLE_NAME%", sName);
+                    lclAddIssue(m_rResultCollection, sIssueText,
+                                svx::AccessibilityIssueID::STYLE_LANGUAGE);
                 }
             }
         }
@@ -508,7 +503,7 @@ public:
 class DocumentTitleCheck : public DocumentCheck
 {
 public:
-    DocumentTitleCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    DocumentTitleCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : DocumentCheck(rResultCollection)
     {
     }
@@ -525,9 +520,8 @@ public:
             OUString sTitle = xDocumentProperties->getTitle();
             if (sTitle.isEmpty())
             {
-                svx::AccessibilityCheckResult aResult(svx::AccessibilityIssueID::DOCUMENT_TITLE);
-                aResult.m_aIssueText = sDocumentTitle;
-                m_rResultCollection.push_back(aResult);
+                lclAddIssue(m_rResultCollection, sDocumentTitle,
+                            svx::AccessibilityIssueID::DOCUMENT_TITLE);
             }
         }
     }
@@ -536,7 +530,7 @@ public:
 class FootnoteEndnoteCheck : public DocumentCheck
 {
 public:
-    FootnoteEndnoteCheck(std::vector<svx::AccessibilityCheckResult>& rResultCollection)
+    FootnoteEndnoteCheck(svx::AccessibilityCheckResultCollection& rResultCollection)
         : DocumentCheck(rResultCollection)
     {
     }
@@ -548,15 +542,11 @@ public:
             SwFormatFootnote const& rFootnote = pTextFootnote->GetFootnote();
             if (rFootnote.IsEndNote())
             {
-                svx::AccessibilityCheckResult aResult;
-                aResult.m_aIssueText = sAvoidEndnotes;
-                m_rResultCollection.push_back(aResult);
+                lclAddIssue(m_rResultCollection, sAvoidEndnotes);
             }
             else
             {
-                svx::AccessibilityCheckResult aResult;
-                aResult.m_aIssueText = sAvoidFootnotes;
-                m_rResultCollection.push_back(aResult);
+                lclAddIssue(m_rResultCollection, sAvoidFootnotes);
             }
         }
     }
@@ -574,9 +564,8 @@ void AccessibilityCheck::checkObject(SdrObject* pObject)
         if (sAlternative.isEmpty())
         {
             OUString sName = pObject->GetName();
-            svx::AccessibilityCheckResult aResult;
-            aResult.m_aIssueText = sNoAlt.replaceAll("%OBJECT_NAME%", sName);
-            m_aResultCollection.push_back(aResult);
+            OUString sIssueText = sNoAlt.replaceAll("%OBJECT_NAME%", sName);
+            lclAddIssue(m_aResultCollection, sIssueText);
         }
     }
 }
diff --git a/sw/source/core/inc/AccessibilityCheck.hxx b/sw/source/core/inc/AccessibilityCheck.hxx
index 30bcb90e00a1..0ce80bf49f45 100644
--- a/sw/source/core/inc/AccessibilityCheck.hxx
+++ b/sw/source/core/inc/AccessibilityCheck.hxx
@@ -16,6 +16,16 @@
 
 namespace sw
 {
+class SW_DLLPUBLIC AccessibilityCheckResult final : public svx::AccessibilityCheckResult
+{
+public:
+    AccessibilityCheckResult(svx::AccessibilityIssueID eIssueID
+                             = svx::AccessibilityIssueID::UNSPECIFIED)
+        : svx::AccessibilityCheckResult(eIssueID)
+    {
+    }
+};
+
 class SW_DLLPUBLIC AccessibilityCheck final : public svx::AccessibilityCheck
 {
 private:
commit 59e2c2d628877cabcc9e5eb223d7657c6c2e2ba9
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Thu Dec 19 21:09:24 2019 +0100
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Thu Dec 19 21:09:24 2019 +0100

    pdf: support for PDF/UA in PDFWriter, write flag to the metadata
    
    This adds support for PDF/UA to the PDFWriter in form of a bool
    flag and writes into the XmpMetadata that the document is supposed
    to be PDF/UA compliant (even if it may not be).
    
    Change-Id: If187152d3860397fc629e272c5b3888fca34e790

diff --git a/vcl/inc/pdf/XmpMetadata.hxx b/vcl/inc/pdf/XmpMetadata.hxx
index d9f9cacc45b4..cc3f8da1a34c 100644
--- a/vcl/inc/pdf/XmpMetadata.hxx
+++ b/vcl/inc/pdf/XmpMetadata.hxx
@@ -31,6 +31,7 @@ public:
     OString msProducer;
     OString msKeywords;
     sal_Int32 mnPDF_A;
+    bool mbPDF_UA;
 
 public:
     XmpMetadata();
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index 9206d71942d9..b7e59c1d87f1 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -1311,6 +1311,7 @@ void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal
         m_nAccessPermissions(0),
         m_bIsPDF_A1( false ),
         m_bIsPDF_A2( false ),
+        m_bIsPDF_UA( false ),
         m_rOuterFace( i_rOuterFace )
 {
     m_aStructure.emplace_back( );
@@ -5229,7 +5230,7 @@ static void escapeStringXML( const OUString& rStr, OUString &rValue)
 // emits the document metadata
 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
 {
-    if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
+    if (!m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_UA)
         return 0;
 
     //get the object number for all the destinations
@@ -5244,6 +5245,8 @@ sal_Int32 PDFWriterImpl::emitDocumentMetadata()
         else if (m_bIsPDF_A2)
             aMetadata.mnPDF_A = 2;
 
+        aMetadata.mbPDF_UA = m_bIsPDF_UA;
+
         if (!m_aContext.DocumentInfo.Title.isEmpty())
         {
             OUString aTempString;
diff --git a/vcl/source/gdi/pdfwriter_impl.hxx b/vcl/source/gdi/pdfwriter_impl.hxx
index 7e07d9e0d472..abb1e12f4e6f 100644
--- a/vcl/source/gdi/pdfwriter_impl.hxx
+++ b/vcl/source/gdi/pdfwriter_impl.hxx
@@ -983,6 +983,10 @@ i12626
     bool            m_bIsPDF_A1;
     /* true if PDF/A-2a is output */
     bool            m_bIsPDF_A2;
+
+    /* PDF/UA support enabled */
+    bool m_bIsPDF_UA;
+
     PDFWriter&      m_rOuterFace;
 
     /*
diff --git a/vcl/source/pdf/XmpMetadata.cxx b/vcl/source/pdf/XmpMetadata.cxx
index d9033f4875ae..281183c205e8 100644
--- a/vcl/source/pdf/XmpMetadata.cxx
+++ b/vcl/source/pdf/XmpMetadata.cxx
@@ -26,6 +26,7 @@ constexpr const char* constPadding = "                                        "
 XmpMetadata::XmpMetadata()
     : mbWritten(false)
     , mnPDF_A(0)
+    , mbPDF_UA(false)
 {
 }
 
@@ -107,6 +108,21 @@ void XmpMetadata::write()
             aXmlWriter.endElement();
         }
 
+        // PDF/UA
+        if (mbPDF_UA)
+        {
+            OString sPdfUaVersion = OString::number(1);
+            aXmlWriter.startElement("rdf:Description");
+            aXmlWriter.attribute("rdf:about", OString(""));
+            aXmlWriter.attribute("xmlns:pdfuaid", OString("http://www.aiim.org/pdfua/ns/id/"));
+
+            aXmlWriter.startElement("pdfuaid:part");
+            aXmlWriter.content(sPdfUaVersion);
+            aXmlWriter.endElement();
+
+            aXmlWriter.endElement();
+        }
+
         // PDF properties
         if (!msProducer.isEmpty() || !msKeywords.isEmpty())
         {
commit d9341d9b56dde355a05cb03bcbcaf8aabda80e4a
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Thu Dec 19 20:55:16 2019 +0100
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Thu Dec 19 20:55:16 2019 +0100

    pdf: extract XMP metadata writing and use XmlWriter
    
    Instead of writing XMP metadata with a string buffer, change to
    use XmlWriter instead. Extract XMP metadata writing into its own
    class vcl::pdf::XmpMetadata.
    
    This also needs a change to the XmlWriter to not write a classic
    XML header: '<?xml version="1.0" ... ?>'
    
    Change-Id: I95ea0e7ba58e7c43a0c707bf9c676994210ff104

diff --git a/include/tools/XmlWriter.hxx b/include/tools/XmlWriter.hxx
index da056c68a596..7efe3a57353a 100644
--- a/include/tools/XmlWriter.hxx
+++ b/include/tools/XmlWriter.hxx
@@ -40,7 +40,7 @@ public:
 
     ~XmlWriter();
 
-    bool startDocument(sal_Int32 nIndent = 2);
+    bool startDocument(sal_Int32 nIndent = 2, bool bWriteXmlHeader = true);
     void endDocument();
 
     void startElement(const OString& sName);
diff --git a/tools/source/xml/XmlWriter.cxx b/tools/source/xml/XmlWriter.cxx
index 3400a6e9d94b..a314eed6e940 100644
--- a/tools/source/xml/XmlWriter.cxx
+++ b/tools/source/xml/XmlWriter.cxx
@@ -36,11 +36,13 @@ struct XmlWriterImpl
     XmlWriterImpl(SvStream* pStream)
         : mpStream(pStream)
         , mpWriter(nullptr)
+        , mbWriteXmlHeader(true)
     {
     }
 
     SvStream* const mpStream;
     xmlTextWriterPtr mpWriter;
+    bool mbWriteXmlHeader;
 };
 
 XmlWriter::XmlWriter(SvStream* pStream)
@@ -54,21 +56,24 @@ XmlWriter::~XmlWriter()
         endDocument();
 }
 
-bool XmlWriter::startDocument(sal_Int32 nIndent)
+bool XmlWriter::startDocument(sal_Int32 nIndent, bool bWriteXmlHeader)
 {
+    mpImpl->mbWriteXmlHeader = bWriteXmlHeader;
     xmlOutputBufferPtr xmlOutBuffer
         = xmlOutputBufferCreateIO(funcWriteCallback, funcCloseCallback, mpImpl->mpStream, nullptr);
     mpImpl->mpWriter = xmlNewTextWriter(xmlOutBuffer);
     if (mpImpl->mpWriter == nullptr)
         return false;
     xmlTextWriterSetIndent(mpImpl->mpWriter, nIndent);
-    xmlTextWriterStartDocument(mpImpl->mpWriter, nullptr, "UTF-8", nullptr);
+    if (mpImpl->mbWriteXmlHeader)
+        xmlTextWriterStartDocument(mpImpl->mpWriter, nullptr, "UTF-8", nullptr);
     return true;
 }
 
 void XmlWriter::endDocument()
 {
-    xmlTextWriterEndDocument(mpImpl->mpWriter);
+    if (mpImpl->mbWriteXmlHeader)
+        xmlTextWriterEndDocument(mpImpl->mpWriter);
     xmlFreeTextWriter(mpImpl->mpWriter);
     mpImpl->mpWriter = nullptr;
 }
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 38bce206f72f..3115f645b5c8 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -446,6 +446,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
     vcl/source/fontsubset/sft \
     vcl/source/fontsubset/ttcr \
     vcl/source/fontsubset/xlat \
+    vcl/source/pdf/XmpMetadata \
     vcl/source/uitest/logger \
     vcl/source/uitest/uiobject \
     vcl/source/uitest/uitest \
diff --git a/vcl/inc/pdf/XmpMetadata.hxx b/vcl/inc/pdf/XmpMetadata.hxx
new file mode 100644
index 000000000000..d9f9cacc45b4
--- /dev/null
+++ b/vcl/inc/pdf/XmpMetadata.hxx
@@ -0,0 +1,47 @@
+/* -*- 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/.
+ *
+ */
+
+#ifndef INCLUDED_VCL_INC_PDF_XMPMETADATA_HXX
+#define INCLUDED_VCL_INC_PDF_XMPMETADATA_HXX
+
+#include <vcl/dllapi.h>
+#include <rtl/string.hxx>
+#include <tools/stream.hxx>
+#include <memory>
+
+namespace vcl::pdf
+{
+class XmpMetadata
+{
+private:
+    bool mbWritten;
+    std::unique_ptr<SvMemoryStream> mpMemoryStream;
+
+public:
+    OString msTitle;
+    OString msAuthor;
+    OString msSubject;
+    OString msProducer;
+    OString msKeywords;
+    sal_Int32 mnPDF_A;
+
+public:
+    XmpMetadata();
+    sal_uInt64 getSize();
+    const void* getData();
+
+private:
+    void write();
+};
+}
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx
index eefdc53b4db7..9206d71942d9 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -74,6 +74,7 @@
 #include <textlineinfo.hxx>
 #include <bitmapwriteaccess.hxx>
 #include <impglyphitem.hxx>
+#include <pdf/XmpMetadata.hxx>
 
 #include "pdfwriter_impl.hxx"
 
@@ -5236,132 +5237,44 @@ sal_Int32 PDFWriterImpl::emitDocumentMetadata()
 
     if( updateObject( nObject ) )
     {
-        // the following string are written in UTF-8 unicode
-        OStringBuffer aMetadataStream( 8192 );
+        pdf::XmpMetadata aMetadata;
 
-        aMetadataStream.append( "<?xpacket begin=\"" );
-        // these lines write Unicode "zero width non-breaking space character" (U+FEFF)
-        // (aka byte-order mark ) used as a byte-order marker.
-        aMetadataStream.append( OUStringToOString( OUString( u'\xFEFF' ), RTL_TEXTENCODING_UTF8 ) );
-        aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" );
-        aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" );
-        aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" );
-        //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" );
-        if( m_bIsPDF_A2 )
-        {
-            aMetadataStream.append( "   <pdfaid:part>2</pdfaid:part>\n" );
-            aMetadataStream.append( "   <pdfaid:conformance>B</pdfaid:conformance>\n" );
-        }
-        else
+        if (m_bIsPDF_A1)
+            aMetadata.mnPDF_A = 1;
+        else if (m_bIsPDF_A2)
+            aMetadata.mnPDF_A = 2;
+
+        if (!m_aContext.DocumentInfo.Title.isEmpty())
         {
-            aMetadataStream.append( "   <pdfaid:part>1</pdfaid:part>\n" );
-            aMetadataStream.append( "   <pdfaid:conformance>A</pdfaid:conformance>\n" );
+            OUString aTempString;
+            escapeStringXML(m_aContext.DocumentInfo.Title, aTempString);
+            aMetadata.msTitle = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
         }
-        aMetadataStream.append( "  </rdf:Description>\n" );
-        //... Dublin Core properties go here
-        if( !m_aContext.DocumentInfo.Title.isEmpty() ||
-            !m_aContext.DocumentInfo.Author.isEmpty() ||
-            !m_aContext.DocumentInfo.Subject.isEmpty() )
+        if (!m_aContext.DocumentInfo.Author.isEmpty())
         {
-            aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
-            aMetadataStream.append( "      xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" );
-            if( !m_aContext.DocumentInfo.Title.isEmpty() )
-            {
-                // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
-                aMetadataStream.append( "   <dc:title>\n" );
-                aMetadataStream.append( "    <rdf:Alt>\n" );
-                aMetadataStream.append( "     <rdf:li xml:lang=\"x-default\">" );
-                OUString aTitle;
-                escapeStringXML( m_aContext.DocumentInfo.Title, aTitle );
-                aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 )  );
-                aMetadataStream.append( "</rdf:li>\n" );
-                aMetadataStream.append( "    </rdf:Alt>\n" );
-                aMetadataStream.append( "   </dc:title>\n" );
-            }
-            if( !m_aContext.DocumentInfo.Author.isEmpty() )
-            {
-                aMetadataStream.append( "   <dc:creator>\n" );
-                aMetadataStream.append( "    <rdf:Seq>\n" );
-                aMetadataStream.append( "     <rdf:li>" );
-                OUString aAuthor;
-                escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor );
-                aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 )  );
-                aMetadataStream.append( "</rdf:li>\n" );
-                aMetadataStream.append( "    </rdf:Seq>\n" );
-                aMetadataStream.append( "   </dc:creator>\n" );
-            }
-            if( !m_aContext.DocumentInfo.Subject.isEmpty() )
-            {
-                // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
-                aMetadataStream.append( "   <dc:description>\n" );
-                aMetadataStream.append( "    <rdf:Alt>\n" );
-                aMetadataStream.append( "     <rdf:li xml:lang=\"x-default\">" );
-                OUString aSubject;
-                escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject );
-                aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 )  );
-                aMetadataStream.append( "</rdf:li>\n" );
-                aMetadataStream.append( "    </rdf:Alt>\n" );
-                aMetadataStream.append( "   </dc:description>\n" );
-            }
-            aMetadataStream.append( "  </rdf:Description>\n" );
+            OUString aTempString;
+            escapeStringXML(m_aContext.DocumentInfo.Author, aTempString);
+            aMetadata.msAuthor = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
         }
-
-        //... PDF properties go here
-        if( !m_aContext.DocumentInfo.Producer.isEmpty() ||
-            !m_aContext.DocumentInfo.Keywords.isEmpty() )
+        if (!m_aContext.DocumentInfo.Subject.isEmpty())
         {
-            aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
-            aMetadataStream.append( "     xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" );
-            if( !m_aContext.DocumentInfo.Producer.isEmpty() )
-            {
-                aMetadataStream.append( "   <pdf:Producer>" );
-                OUString aProducer;
-                escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer );
-                aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 )  );
-                aMetadataStream.append( "</pdf:Producer>\n" );
-            }
-            if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
-            {
-                aMetadataStream.append( "   <pdf:Keywords>" );
-                OUString aKeywords;
-                escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords );
-                aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 )  );
-                aMetadataStream.append( "</pdf:Keywords>\n" );
-            }
-            aMetadataStream.append( "  </rdf:Description>\n" );
+            OUString aTempString;
+            escapeStringXML(m_aContext.DocumentInfo.Subject, aTempString);
+            aMetadata.msSubject = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
         }
-
-        aMetadataStream.append( "  <rdf:Description rdf:about=\"\"\n" );
-        aMetadataStream.append( "    xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" );
-        if( !m_aContext.DocumentInfo.Creator.isEmpty() )
+        if (!m_aContext.DocumentInfo.Producer.isEmpty())
         {
-            aMetadataStream.append( "   <xmp:CreatorTool>" );
-            OUString aCreator;
-            escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator );
-            aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 )  );
-            aMetadataStream.append( "</xmp:CreatorTool>\n" );
+            OUString aTempString;
+            escapeStringXML(m_aContext.DocumentInfo.Producer, aTempString);
+            aMetadata.msProducer = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
         }
-        //creation date
-        aMetadataStream.append( "   <xmp:CreateDate>" );
-        aMetadataStream.append( m_aCreationMetaDateString );
-        aMetadataStream.append( "</xmp:CreateDate>\n" );
-
-        aMetadataStream.append( "  </rdf:Description>\n" );
-        aMetadataStream.append( " </rdf:RDF>\n" );
-        aMetadataStream.append( "</x:xmpmeta>\n" );
-
-        //add the padding
-        for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ )
+        if (!m_aContext.DocumentInfo.Keywords.isEmpty())
         {
-            aMetadataStream.append( " " );
-            if( nSpaces % 100 == 0 )
-                aMetadataStream.append( "\n" );
+            OUString aTempString;
+            escapeStringXML(m_aContext.DocumentInfo.Keywords, aTempString);
+            aMetadata.msKeywords = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
         }
 
-        aMetadataStream.append( "<?xpacket end=\"w\"?>\n" );
-
         OStringBuffer aMetadataObj( 1024 );
 
         aMetadataObj.append( nObject );
@@ -5369,12 +5282,12 @@ sal_Int32 PDFWriterImpl::emitDocumentMetadata()
 
         aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
 
-        aMetadataObj.append( aMetadataStream.getLength() );
+        aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
         aMetadataObj.append( ">>\nstream\n" );
         if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
             return 0;
         //emit the stream
-        if ( !writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) )
+        if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) )
             return 0;
 
         aMetadataObj.setLength( 0 );
diff --git a/vcl/source/pdf/XmpMetadata.cxx b/vcl/source/pdf/XmpMetadata.cxx
new file mode 100644
index 000000000000..d9033f4875ae
--- /dev/null
+++ b/vcl/source/pdf/XmpMetadata.cxx
@@ -0,0 +1,159 @@
+/* -*- 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 <pdf/XmpMetadata.hxx>
+#include <tools/XmlWriter.hxx>
+
+namespace vcl::pdf
+{
+namespace
+{
+constexpr const char* constPadding = "                                        "
+                                     "                                        "
+                                     "                                        "
+                                     "                                        "
+                                     "                                        "
+                                     "\n";
+}
+
+XmpMetadata::XmpMetadata()
+    : mbWritten(false)
+    , mnPDF_A(0)
+{
+}
+
+void XmpMetadata::write()
+{
+    mpMemoryStream = std::make_unique<SvMemoryStream>(4096 /*Initial*/, 64 /*Resize*/);
+
+    // Header
+    mpMemoryStream->WriteOString("<?xpacket begin=\"");
+    mpMemoryStream->WriteOString(OUStringToOString(OUString(u'\xFEFF'), RTL_TEXTENCODING_UTF8));
+    mpMemoryStream->WriteOString("\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
+
+    {
+        tools::XmlWriter aXmlWriter(mpMemoryStream.get());
+        aXmlWriter.startDocument(2, false);
+        aXmlWriter.startElement("x", "xmpmeta", "adobe:ns:meta/");
+        aXmlWriter.startElement("rdf", "RDF", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+
+        // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
+        if (mnPDF_A > 0)
+        {
+            OString sPdfVersion = OString::number(mnPDF_A);
+            OString sPdfConformance = (mnPDF_A == 1) ? "A" : "B";
+
+            aXmlWriter.startElement("rdf:Description");
+            aXmlWriter.attribute("rdf:about", OString(""));
+            aXmlWriter.attribute("xmlns:pdfaid", OString("http://www.aiim.org/pdfa/ns/id/"));
+
+            aXmlWriter.startElement("pdfaid:part");
+            aXmlWriter.content(sPdfVersion);
+            aXmlWriter.endElement();
+
+            aXmlWriter.startElement("pdfaid:conformance");
+            aXmlWriter.content(sPdfConformance);
+            aXmlWriter.endElement();
+
+            aXmlWriter.endElement();
+        }
+
+        // Dublin Core properties
+        if (!msTitle.isEmpty() || !msAuthor.isEmpty() || !msSubject.isEmpty())
+        {
+            aXmlWriter.startElement("rdf:Description");
+            aXmlWriter.attribute("rdf:about", OString(""));
+            aXmlWriter.attribute("xmlns:dc", OString("http://purl.org/dc/elements/1.1/"));
+            if (!msTitle.isEmpty())
+            {
+                // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
+                aXmlWriter.startElement("dc:title");
+                aXmlWriter.startElement("rdf:Alt");
+                aXmlWriter.startElement("rdf:li");
+                aXmlWriter.attribute("xml:lang", OString("x-default"));
+                aXmlWriter.content(msTitle);
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+            }
+            if (!msAuthor.isEmpty())
+            {
+                aXmlWriter.startElement("dc:creator");
+                aXmlWriter.startElement("rdf:Seq");
+                aXmlWriter.startElement("rdf:li");
+                aXmlWriter.content(msAuthor);
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+            }
+            if (!msSubject.isEmpty())
+            {
+                aXmlWriter.startElement("dc:description");
+                aXmlWriter.startElement("rdf:Alt");
+                aXmlWriter.startElement("rdf:li");
+                aXmlWriter.attribute("xml:lang", OString("x-default"));
+                aXmlWriter.content(msSubject);
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+                aXmlWriter.endElement();
+            }
+            aXmlWriter.endElement();
+        }
+
+        // PDF properties
+        if (!msProducer.isEmpty() || !msKeywords.isEmpty())
+        {
+            aXmlWriter.startElement("rdf:Description");
+            aXmlWriter.attribute("rdf:about", OString(""));
+            aXmlWriter.attribute("xmlns:pdf", OString("http://ns.adobe.com/pdf/1.3/"));
+            if (!msProducer.isEmpty())
+            {
+                aXmlWriter.startElement("pdf:Producer");
+                aXmlWriter.content(msProducer);
+                aXmlWriter.endElement();
+            }
+            if (!msKeywords.isEmpty())
+            {
+                aXmlWriter.startElement("pdf:Keywords");
+                aXmlWriter.content(msKeywords);
+                aXmlWriter.endElement();
+            }
+            aXmlWriter.endElement();
+        }
+        aXmlWriter.endElement();
+        aXmlWriter.endElement();
+        aXmlWriter.endDocument();
+    }
+
+    // add padding (needed so the metadata can be changed in-place"
+    for (sal_Int32 nSpaces = 1; nSpaces <= 21; nSpaces++)
+        mpMemoryStream->WriteOString(constPadding);
+
+    mpMemoryStream->WriteOString("<?xpacket end=\"w\"?>\n");
+    mbWritten = true;
+}
+
+sal_uInt64 XmpMetadata::getSize()
+{
+    if (!mbWritten)
+        write();
+    return mpMemoryStream->GetSize();
+}
+
+const void* XmpMetadata::getData()
+{
+    if (!mbWritten)
+        write();
+    return mpMemoryStream->GetData();
+}
+
+} // end vcl::pdf
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list