include/vcl/filter/PDFiumLibrary.hxx            |    1 
 sw/qa/extras/uiwriter/data/tdf131728.docx       |binary
 sw/qa/extras/uiwriter/data/tdf95239.fodt        |  214 ++++++++++++++++++++++++
 sw/qa/extras/uiwriter/uiwriter8.cxx             |  101 +++++++++++
 sw/source/core/text/EnhancedPDFExportHelper.cxx |   71 +++++++
 vcl/source/pdf/PDFiumLibrary.cxx                |   52 +++++
 6 files changed, 439 insertions(+)

New commits:
commit 3056c75db5b2a8a945621349fea9ee1183872989
Author:     László Németh <nem...@numbertext.org>
AuthorDate: Tue Nov 5 12:40:40 2024 +0100
Commit:     László Németh <nem...@numbertext.org>
CommitDate: Wed Nov 6 17:46:59 2024 +0100

    tdf#95239 sw: fix wrong order of PDF ToC, if headings put in text frames
    
    PDF outlines (called also as PDF bookmarks or ToC) contained headings in
    the wrong order if they were placed in a text frame:
    
    Heading 2 (frame) ... 2
    Heading 3 (frame) ... 2
    Heading 1 ........... 1
    
    Now PDF export didn't list text frame headings only at the start of the
    ToC, but in their correct position and hierarchy, based on the page
    and vertical position of the headings:
    
    Heading 1 ................ 1
      Heading 2 (frame) ...... 2
         Heading 3 (frame) ... 2
    
    This is useful for the recently implemented inline headings, where
    e.g. APA Style Heading 4 and Heading 5 are there in text frames
    anchored as characters, see tdf#48459.
    
    Extend PDFium test environment for bookmarks, and add tdf#131728 DOCX
    and an APA Style .fodt unit tests.
    
    Note: if the higher headings are only in text frames, but not the
    lower ones, only the order corrected, but not the full hierarchy, yet.
    
    This is a follow-up to commit d87cf67f8f3346a1e380383917a3a4552fd9248e
    "tdf#131728 sw inline heading: fix missing/broken DOCX export",
    commit a1dcbd1d1ce6071d48bb5df26d7839aeb21b75a8
    "tdf#48459 sw inline heading: add Inline Heading frame style" and
    commit 49765a9e7be41d4908729ff7d838755276b244cb
    "tdf#48459 tdf#131728 sw inline heading: new frame style: fix DOCX
    export".
    
    Change-Id: I87dffb9244d8aea553c98bf16c70955bb9b732d3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176050
    Reviewed-by: László Németh <nem...@numbertext.org>
    Tested-by: Jenkins

diff --git a/include/vcl/filter/PDFiumLibrary.hxx 
b/include/vcl/filter/PDFiumLibrary.hxx
index 6d2a65a8fa38..16253efda442 100644
--- a/include/vcl/filter/PDFiumLibrary.hxx
+++ b/include/vcl/filter/PDFiumLibrary.hxx
@@ -240,6 +240,7 @@ public:
     virtual std::unique_ptr<PDFiumPage> openPage(int nIndex) = 0;
     virtual std::unique_ptr<PDFiumSignature> getSignature(int nIndex) = 0;
     virtual std::vector<unsigned int> getTrailerEnds() = 0;
+    virtual OUString getBookmarks() = 0;
 };
 
 struct VCL_DLLPUBLIC PDFiumLibrary final
diff --git a/sw/qa/extras/uiwriter/data/tdf131728.docx 
b/sw/qa/extras/uiwriter/data/tdf131728.docx
new file mode 100644
index 000000000000..4f3399919db8
Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf131728.docx differ
diff --git a/sw/qa/extras/uiwriter/data/tdf95239.fodt 
b/sw/qa/extras/uiwriter/data/tdf95239.fodt
new file mode 100644
index 000000000000..4993ec44395d
--- /dev/null
+++ b/sw/qa/extras/uiwriter/data/tdf95239.fodt
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/"; 
xmlns:grddl="http://www.w3.org/2003/g/data-view#"; 
xmlns:xhtml="http://www.w3.org/1999/xhtml"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema"; 
xmlns:xforms="http://www.w3.org/2002/xforms"; 
xmlns:dom="http://www.w3.org/2001/xml-events"; 
xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" 
xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" 
xmlns:math="http://www.w3.org/1998/Math/MathML"; 
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" 
xmlns:ooo="http://openoffice.org/2004/office"; 
xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" 
xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" 
xmlns:ooow="http://openoffice.org/2004/writer"; 
xmlns:xlink="http://www.w3.org/1999/xlink"; 
xmlns:drawooo="http://openoffice.org/2010/draw"; 
xmlns:oooc="http://openoffice.org/2004/calc"; 
xmlns:dc="http://purl.org/dc/elements/1.1/"; xmlns:c
 alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" 
xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" 
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" 
xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" 
xmlns:tableooo="http://openoffice.org/2009/table"; 
xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 
xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" 
xmlns:rpt="http://openoffice.org/2005/report"; 
xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0"
 xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" 
xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" 
xmlns:officeooo="http://openoffice.org/2009/office"; 
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" 
xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" 
xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" 
xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:
 meta:1.0" 
xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0"
 office:version="1.4" office:mimetype="application/vnd.oasis.opendocument.text">
+ <office:font-face-decls>
+  <style:font-face style:name="Liberation Sans" 
svg:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" 
style:font-pitch="variable"/>
+  <style:font-face style:name="Liberation Serif" 
svg:font-family="&apos;Liberation Serif&apos;" 
style:font-family-generic="roman" style:font-pitch="variable"/>
+ </office:font-face-decls>
+ <office:styles>
+  <style:default-style style:family="graphic">
+   <style:graphic-properties svg:stroke-color="#3465a4" 
draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" 
draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" 
draw:start-line-spacing-vertical="0.283cm" 
draw:end-line-spacing-horizontal="0.283cm" 
draw:end-line-spacing-vertical="0.283cm" style:writing-mode="lr-tb" 
style:flow-with-text="false"/>
+   <style:paragraph-properties style:text-autospace="ideograph-alpha" 
style:line-break="strict" loext:tab-stop-distance="0cm" 
style:font-independent-line-spacing="false">
+    <style:tab-stops/>
+   </style:paragraph-properties>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true"/>
+  </style:default-style>
+  <style:default-style style:family="paragraph">
+   <style:paragraph-properties fo:orphans="2" fo:widows="2" 
fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" 
loext:hyphenation-keep-type="column" style:text-autospace="ideograph-alpha" 
style:punctuation-wrap="hanging" style:line-break="strict" 
style:tab-stop-distance="1.251cm" style:writing-mode="page"/>
+   <style:text-properties style:use-window-font-color="true" 
loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" 
fo:language="en" fo:country="US" style:letter-kerning="true" 
fo:hyphenate="false" fo:hyphenation-remain-char-count="2" 
fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" 
loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" 
loext:hyphenation-zone="no-limit"/>
+  </style:default-style>
+  <style:style style:name="Standard" style:family="paragraph" 
style:class="text"/>
+  <style:style style:name="Heading" style:family="paragraph" 
style:parent-style-name="Standard" style:next-style-name="Text_20_body" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.423cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false" 
fo:keep-with-next="always"/>
+   <style:text-properties style:font-name="Liberation Sans" 
fo:font-family="&apos;Liberation Sans&apos;" style:font-family-generic="swiss" 
style:font-pitch="variable" fo:font-size="14pt"/>
+  </style:style>
+  <style:style style:name="Text_20_body" style:display-name="Text body" 
style:family="paragraph" style:parent-style-name="Standard" style:class="text">
+   <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" 
style:contextual-spacing="false" fo:line-height="115%"/>
+  </style:style>
+  <style:style style:name="Caption" style:family="paragraph" 
style:parent-style-name="Standard" style:class="extra">
+   <style:paragraph-properties fo:margin-top="0.212cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false" 
text:number-lines="false" text:line-number="0"/>
+   <style:text-properties fo:font-size="12pt" fo:font-style="italic"/>
+  </style:style>
+  <style:style style:name="Index" style:family="paragraph" 
style:parent-style-name="Standard" style:class="index">
+   <style:paragraph-properties text:number-lines="false" text:line-number="0"/>
+   <style:text-properties/>
+  </style:style>
+  <style:style style:name="Heading_20_1" style:display-name="Heading 1" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="1" 
style:class="chapter" style:master-page-name="">
+   <style:paragraph-properties fo:margin-top="0.423cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false" 
style:page-number="auto" fo:break-before="page"/>
+   <style:text-properties fo:font-size="18pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_2" style:display-name="Heading 2" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="2" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.353cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="16pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_3" style:display-name="Heading 3" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="3" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.247cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="14pt" fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Heading_20_4" style:display-name="Heading 4" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="4" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.212cm" 
fo:margin-bottom="0.212cm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="13pt" fo:font-style="normal" 
fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Frame_20_contents" style:display-name="Frame 
contents" style:family="paragraph" style:parent-style-name="Standard" 
style:class="extra"/>
+  <style:style style:name="Heading_20_5" style:display-name="Heading 5" 
style:family="paragraph" style:parent-style-name="Heading" 
style:next-style-name="Text_20_body" style:default-outline-level="5" 
style:class="chapter">
+   <style:paragraph-properties fo:margin-top="0.212cm" 
fo:margin-bottom="0.106cm" style:contextual-spacing="false"/>
+   <style:text-properties fo:font-size="12pt" fo:font-style="italic" 
fo:font-weight="bold"/>
+  </style:style>
+  <style:style style:name="Frame" style:family="graphic">
+   <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" 
svg:y="0cm" fo:margin-left="0.201cm" fo:margin-right="0.201cm" 
fo:margin-top="0.201cm" fo:margin-bottom="0.201cm" style:wrap="parallel" 
style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" 
style:vertical-pos="top" style:vertical-rel="paragraph-content" 
style:horizontal-pos="center" style:horizontal-rel="paragraph-content" 
fo:background-color="transparent" draw:fill="none" draw:fill-color="#729fcf" 
fo:padding="0.15cm" fo:border="0.06pt solid #000000"/>
+  </style:style>
+  <style:style style:name="Inline_20_Heading" style:display-name="Inline 
Heading" style:family="graphic">
+   <style:graphic-properties fo:min-width="0.041cm" fo:min-height="0.041cm" 
text:anchor-type="as-char" svg:y="0cm" fo:margin-left="0cm" 
fo:margin-right="0cm" style:vertical-pos="middle" style:vertical-rel="text" 
fo:background-color="transparent" draw:fill="none" draw:fill-color="#729fcf"/>
+  </style:style>
+  <text:linenumbering-configuration text:number-lines="false" 
text:offset="0.499cm" style:num-format="1" text:number-position="left" 
text:increment="5"/>
+ </office:styles>
+ <office:automatic-styles>
+  <style:style style:name="P1" style:family="paragraph" 
style:parent-style-name="Heading_20_1">
+   <style:text-properties officeooo:rsid="00155f68" 
officeooo:paragraph-rsid="00155f68"/>
+  </style:style>
+  <style:style style:name="P2" style:family="paragraph" 
style:parent-style-name="Text_20_body">
+   <style:text-properties officeooo:rsid="00155f68" 
officeooo:paragraph-rsid="00155f68"/>
+  </style:style>
+  <style:style style:name="P3" style:family="paragraph" 
style:parent-style-name="Heading_20_4">
+   <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" 
style:contextual-spacing="false"/>
+  </style:style>
+  <style:style style:name="P4" style:family="paragraph" 
style:parent-style-name="Heading_20_5">
+   <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" 
style:contextual-spacing="false"/>
+  </style:style>
+  <style:style style:name="P5" style:family="paragraph" 
style:parent-style-name="Text_20_body">
+   <style:text-properties officeooo:rsid="00155f68" 
officeooo:paragraph-rsid="00163552"/>
+  </style:style>
+  <style:style style:name="P6" style:family="paragraph" 
style:parent-style-name="Heading_20_3">
+   <style:text-properties officeooo:paragraph-rsid="00163552"/>
+  </style:style>
+  <style:style style:name="P7" style:family="paragraph" 
style:parent-style-name="Heading_20_2">
+   <style:text-properties officeooo:paragraph-rsid="00155f68"/>
+  </style:style>
+  <style:style style:name="P8" style:family="paragraph" 
style:parent-style-name="Heading_20_3">
+   <style:text-properties officeooo:paragraph-rsid="00155f68"/>
+  </style:style>
+  <style:style style:name="P9" style:family="paragraph" 
style:parent-style-name="Text_20_body">
+   <style:text-properties officeooo:paragraph-rsid="00163552"/>
+  </style:style>
+  <style:style style:name="T1" style:family="text">
+   <style:text-properties officeooo:rsid="00155f68"/>
+  </style:style>
+  <style:style style:name="fr1" style:family="graphic" 
style:parent-style-name="Inline_20_Heading">
+   <style:graphic-properties style:vertical-pos="middle" 
style:vertical-rel="text" style:horizontal-pos="from-left" 
style:horizontal-rel="paragraph-content"/>
+  </style:style>
+  <style:page-layout style:name="pm1">
+   <style:page-layout-properties fo:page-width="21.001cm" 
fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" 
fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" 
fo:margin-right="2cm" style:writing-mode="lr-tb" 
style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" 
style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" 
style:layout-grid-mode="none" style:layout-grid-ruby-below="false" 
style:layout-grid-print="false" style:layout-grid-display="false" 
style:footnote-max-height="0cm" loext:margin-gutter="0cm">
+    <style:footnote-sep style:width="0.018cm" 
style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" 
style:line-style="solid" style:adjustment="left" style:rel-width="25%" 
style:color="#000000"/>
+   </style:page-layout-properties>
+   <style:header-style/>
+   <style:footer-style/>
+  </style:page-layout>
+  <style:style style:name="dp1" style:family="drawing-page">
+   <style:drawing-page-properties draw:background-size="full"/>
+  </style:style>
+ </office:automatic-styles>
+ <office:master-styles>
+  <style:master-page style:name="Standard" style:page-layout-name="pm1" 
draw:style-name="dp1"/>
+ </office:master-styles>
+ <office:body>
+  <office:text text:use-soft-page-breaks="true">
+   <text:h text:style-name="P1" text:outline-level="1">H1</text:h>
+   <text:h text:style-name="Heading_20_2" text:outline-level="2">H<text:span 
text:style-name="T1">2</text:span></text:h>
+   <text:h text:style-name="Heading_20_3" text:outline-level="3">H3</text:h>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame1" text:anchor-type="as-char" draw:z-index="0">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Lorem</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing 
elit.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame3" text:anchor-type="as-char" draw:z-index="1">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Vestibulum</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat mi quis pretium semper.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame2" text:anchor-type="as-char" draw:z-index="2">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Integer</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>sodales tincidunt tristique.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame4" text:anchor-type="as-char" draw:z-index="3">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Aliquam</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis 
eros.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame5" text:anchor-type="as-char" draw:z-index="4">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Donec</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame11" text:anchor-type="as-char" draw:z-index="5">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Praesent</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>vitae lacus vel leo sodales pharetra a a 
nibh.</text:p>
+   <text:h text:style-name="P6" text:outline-level="3">H3</text:h>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame14" text:anchor-type="as-char" draw:z-index="6">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Lorem</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing 
elit.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame15" text:anchor-type="as-char" draw:z-index="7">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Vestibulum</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat mi quis pretium semper.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame16" text:anchor-type="as-char" draw:z-index="8">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Integer</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>sodales tincidunt tristique.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame17" text:anchor-type="as-char" draw:z-index="9">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Aliquam</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis 
eros.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame18" text:anchor-type="as-char" draw:z-index="10">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Donec</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame13" text:anchor-type="as-char" draw:z-index="11">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Praesent</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>vitae lacus vel leo sodales pharetra a a 
nibh.</text:p>
+   <text:h text:style-name="P1" text:outline-level="1">H1</text:h>
+   <text:h text:style-name="P7" text:outline-level="2">H<text:span 
text:style-name="T1">2</text:span></text:h>
+   <text:h text:style-name="P8" text:outline-level="3">H3</text:h>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame6" text:anchor-type="as-char" draw:z-index="12">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Lorem</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing 
elit.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame7" text:anchor-type="as-char" draw:z-index="13">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Vestibulum</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat mi quis pretium semper.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame8" text:anchor-type="as-char" draw:z-index="14">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Integer</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>sodales tincidunt tristique.</text:p>
+   <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" 
draw:name="Frame9" text:anchor-type="as-char" draw:z-index="15">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P3" text:outline-level="4">Aliquam</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis 
eros.</text:p>
+   <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" 
draw:name="Frame10" text:anchor-type="as-char" draw:z-index="16">
+     <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+      <text:h text:style-name="P4" text:outline-level="5">Donec</text:h>
+     </draw:text-box>
+    </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p>
+   <text:p text:style-name="P9"><text:span text:style-name="T1"><draw:frame 
draw:style-name="fr1" draw:name="Frame12" text:anchor-type="as-char" 
draw:z-index="17">
+      <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm">
+       <text:h text:style-name="P4" text:outline-level="5">Praesent</text:h>
+      </draw:text-box>
+     </draw:frame></text:span><text:span text:style-name="T1"><text:s/>vitae 
lacus vel leo sodales pharetra a a nibh.</text:span></text:p>
+  </office:text>
+ </office:body>
+</office:document>
diff --git a/sw/qa/extras/uiwriter/uiwriter8.cxx 
b/sw/qa/extras/uiwriter/uiwriter8.cxx
index 92ef307ba1d8..109970c1cf6e 100644
--- a/sw/qa/extras/uiwriter/uiwriter8.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter8.cxx
@@ -701,6 +701,107 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest8, testTdf145584)
     CPPUNIT_ASSERT_EQUAL(u"World"_ustr, sText);
 }
 
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest8, testTdf131728)
+{
+    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
+    if (!pPDFium)
+    {
+        return;
+    }
+    createSwDoc("tdf131728.docx");
+    SwDoc* const pDoc = getSwDoc();
+    SwWrtShell* const pWrtSh = pDoc->GetDocShell()->GetWrtShell();
+    CPPUNIT_ASSERT(pWrtSh);
+
+    // Save as PDF.
+    uno::Sequence<beans::PropertyValue> 
aFilterData(comphelper::InitPropertySequence(
+        { { "ExportBookmarksToPDFDestination", uno::Any(true) } }));
+
+    uno::Sequence<beans::PropertyValue> aDescriptor(
+        comphelper::InitPropertySequence({ { "FilterName", 
uno::Any(u"writer_pdf_Export"_ustr) },
+                                           { "FilterData", 
uno::Any(aFilterData) },
+                                           { "URL", 
uno::Any(maTempFile.GetURL()) } }));
+
+    dispatchCommand(mxComponent, u".uno:ExportToPDF"_ustr, aDescriptor);
+
+    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+    CPPUNIT_ASSERT_EQUAL(1, pPdfDocument->getPageCount());
+
+    std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = 
pPdfDocument->openPage(/*nIndex=*/0);
+    CPPUNIT_ASSERT(pPdfPage);
+
+    // Without the fix in place, this test would have bad order
+    // (starting with the outlines of text frames)
+    CPPUNIT_ASSERT_EQUAL(u"Article 1. Definitions
"
+                         " Apple
"
+                         " Bread
"
+                         " Cable
"
+                         " Cable
" // ???
+                         "Article 2. Three style separators in one line!
"
+                         " Heading 2
"
+                         " Heading 2 Again
"_ustr,
+                         pPdfDocument->getBookmarks());
+}
+
+CPPUNIT_TEST_FIXTURE(SwUiWriterTest8, testTdf95239)
+{
+    std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
+    if (!pPDFium)
+    {
+        return;
+    }
+    createSwDoc("tdf95239.fodt");
+    SwDoc* const pDoc = getSwDoc();
+    SwWrtShell* const pWrtSh = pDoc->GetDocShell()->GetWrtShell();
+    CPPUNIT_ASSERT(pWrtSh);
+
+    // Save as PDF.
+    uno::Sequence<beans::PropertyValue> 
aFilterData(comphelper::InitPropertySequence(
+        { { "ExportBookmarksToPDFDestination", uno::Any(true) } }));
+
+    uno::Sequence<beans::PropertyValue> aDescriptor(
+        comphelper::InitPropertySequence({ { "FilterName", 
uno::Any(u"writer_pdf_Export"_ustr) },
+                                           { "FilterData", 
uno::Any(aFilterData) },
+                                           { "URL", 
uno::Any(maTempFile.GetURL()) } }));
+
+    dispatchCommand(mxComponent, u".uno:ExportToPDF"_ustr, aDescriptor);
+
+    std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
+    CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
+
+    std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = 
pPdfDocument->openPage(/*nIndex=*/0);
+    CPPUNIT_ASSERT(pPdfPage);
+
+    // Without the fix in place, this test would have bad order
+    // (starting with the outlines of text frames)
+    CPPUNIT_ASSERT_EQUAL(u"H1
"
+                         " H2
"
+                         "  H3
"
+                         "   Lorem
"
+                         "    Vestibulum
"
+                         "    Integer
"
+                         "   Aliquam
"
+                         "    Donec
"
+                         "    Praesent
"
+                         "  H3
"
+                         "   Lorem
"
+                         "    Vestibulum
"
+                         "    Integer
"
+                         "   Aliquam
"
+                         "    Donec
"
+                         "    Praesent
"
+                         "H1
"
+                         " H2
"
+                         "  H3
"
+                         "   Lorem
"
+                         "    Vestibulum
"
+                         "    Integer
"
+                         "   Aliquam
"
+                         "    Donec
"
+                         "    Praesent
"_ustr,
+                         pPdfDocument->getBookmarks());
+}
+
 CPPUNIT_TEST_FIXTURE(SwUiWriterTest8, testTdf152575)
 {
     // FIXME: the DPI check should be removed when either (1) the test is 
fixed to work with
diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx 
b/sw/source/core/text/EnhancedPDFExportHelper.cxx
index 4fcc8d5252ef..2d71b13be8e8 100644
--- a/sw/source/core/text/EnhancedPDFExportHelper.cxx
+++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx
@@ -2703,6 +2703,14 @@ void 
SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
             std::stack< StackEntry > aOutlineStack;
             aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value
 
+            // outlines inside flys (text frames) collected before the normal
+            // outlines by GetOutLineNds(), so store them with page/position 
data
+            // to insert later on the right page and position:
+            // tuple< nDestPageNum, rDestRect, nLevel, rEntry, nDestId >
+            typedef std::tuple< sal_Int32, SwRect, sal_Int32, const OUString, 
sal_Int32 > FlyEntry;
+            std::vector< FlyEntry > aFlyVector;
+            sal_Int32 nStartFly = 0; // first not processed item in aFlyVector
+
             const SwOutlineNodes::size_type nOutlineCount =
                 mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount();
             for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
@@ -2717,6 +2725,14 @@ void 
SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
                      pTNd->GetText().isEmpty())
                     continue;
 
+                // Check if outline is inside a text frame
+                bool bFlyOutline = pTNd->GetFlyFormat();
+
+                // save outline stack to use for postponed fly outlines
+                std::stack< StackEntry > aSavedOutlineStack;
+                if ( !aFlyVector.empty() && !bFlyOutline )
+                    aSavedOutlineStack = aOutlineStack;
+
                 // Get parent id from stack:
                 const sal_Int8 nLevel = 
static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i 
));
                 sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
@@ -2749,6 +2765,42 @@ void 
SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
                     const OUString& rEntry = 
mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
                         i, mrSh.GetLayout(), true, false, false );
 
+                    // postpone fly outlines
+                    if ( bFlyOutline )
+                    {
+                        aFlyVector.push_back(
+                                    FlyEntry(nDestPageNum, rDestRect, nLevel, 
rEntry, nDestId) );
+                        continue;
+                    }
+
+                    // create new outline items from postponed fly outlines, 
if they are before
+                    // the recent not fly outline (and after the already 
created fly outlines)
+                    for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
+                    {
+                        if ( std::get<0>(aFlyVector[j]) < nDestPageNum ||
+                             ( std::get<0>(aFlyVector[j]) == nDestPageNum &&
+                               std::get<1>(aFlyVector[j]).Pos().Y() < 
rDestRect.Pos().Y() ) )
+                        {
+                            sal_Int32 nFlyLevel = std::get<2>(aFlyVector[j]);
+                            sal_Int8 nLevelOnTopOfSavedStack = 
aSavedOutlineStack.top().first;
+                            while ( nLevelOnTopOfSavedStack >= nFlyLevel &&
+                                    nLevelOnTopOfSavedStack != -1 )
+                            {
+                                aSavedOutlineStack.pop();
+                                nLevelOnTopOfSavedStack = 
aSavedOutlineStack.top().first;
+                            }
+                            const sal_Int32 nFlyParent = 
aSavedOutlineStack.top().second;
+                            const sal_Int32 nId = 
pPDFExtOutDevData->CreateOutlineItem( nFlyParent,
+                                                      
std::get<3>(aFlyVector[j]),
+                                                      
std::get<4>(aFlyVector[j]) );
+                            // Push current level and outline id on saved 
stack:
+                            aSavedOutlineStack.push( StackEntry( nFlyLevel, 
nId ) );
+                            ++nStartFly;
+                        }
+                        else
+                            break;
+                    }
+
                     // Create a new outline item:
                     const sal_Int32 nOutlineId =
                         pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, 
nDestId );
@@ -2757,6 +2809,25 @@ void 
SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
                     aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
                 }
             }
+
+            // create remaining fly outlines
+            for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
+            {
+                sal_Int32 nLevel = std::get<2>(aFlyVector[j]);
+                sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
+                while ( nLevelOnTopOfStack >= nLevel &&
+                        nLevelOnTopOfStack != -1 )
+                {
+                    aOutlineStack.pop();
+                    nLevelOnTopOfStack = aOutlineStack.top().first;
+                }
+                const sal_Int32 nParent = aOutlineStack.top().second;
+
+                const sal_Int32 nOutlineId = 
pPDFExtOutDevData->CreateOutlineItem( nParent,
+                                          std::get<3>(aFlyVector[j]),
+                                          std::get<4>(aFlyVector[j]) );
+                aOutlineStack.push( StackEntry( std::get<2>(aFlyVector[j]), 
nOutlineId ) );
+            }
         }
 
         if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
diff --git a/vcl/source/pdf/PDFiumLibrary.cxx b/vcl/source/pdf/PDFiumLibrary.cxx
index dd7abd5defd5..f48d02595453 100644
--- a/vcl/source/pdf/PDFiumLibrary.cxx
+++ b/vcl/source/pdf/PDFiumLibrary.cxx
@@ -26,6 +26,7 @@
 #include <tools/stream.hxx>
 #include <tools/UnitConversion.hxx>
 #include <o3tl/string_view.hxx>
+#include <rtl/ustrbuf.hxx>
 
 #include <vcl/BitmapWriteAccess.hxx>
 #include <vcl/bitmapex.hxx>
@@ -484,6 +485,7 @@ public:
     std::unique_ptr<PDFiumPage> openPage(int nIndex) override;
     std::unique_ptr<PDFiumSignature> getSignature(int nIndex) override;
     std::vector<unsigned int> getTrailerEnds() override;
+    OUString getBookmarks() override;
 };
 
 class PDFiumImpl : public PDFium
@@ -746,6 +748,56 @@ std::vector<unsigned int> 
PDFiumDocumentImpl::getTrailerEnds()
     return aTrailerEnds;
 }
 
+static void lcl_getBookmarks(int nLevel, OUStringBuffer& rBuf, FPDF_DOCUMENT 
pDoc,
+                             FPDF_BOOKMARK pBookmark)
+{
+    // no first child or too much levels
+    if (!pBookmark || nLevel > 10)
+        return;
+
+    OUString aString;
+    int nBytes = FPDFBookmark_GetTitle(pBookmark, nullptr, 0);
+    assert(nBytes % 2 == 0);
+    nBytes /= 2;
+
+    std::unique_ptr<sal_Unicode[]> pText(new sal_Unicode[nBytes]);
+
+    int nActualBytes = FPDFBookmark_GetTitle(pBookmark, pText.get(), nBytes * 
2);
+    assert(nActualBytes % 2 == 0);
+    nActualBytes /= 2;
+    if (nActualBytes > 1)
+    {
+#if defined OSL_BIGENDIAN
+        // The data returned by FPDFTextObj_GetText is documented to always be 
UTF-16LE:
+        for (int i = 0; i != nActualBytes; ++i)
+        {
+            pText[i] = OSL_SWAPWORD(pText[i]);
+        }
+#endif
+        // insert nLevel spaces before the title
+        rBuf.append(OUString("          ").subView(0, nLevel));
+        aString = OUString(pText.get());
+    }
+
+    rBuf.append(aString);
+    rBuf.append("
");
+
+    // get children
+    lcl_getBookmarks(nLevel + 1, rBuf, pDoc, FPDFBookmark_GetFirstChild(pDoc, 
pBookmark));
+
+    // get siblings
+    while (nullptr != (pBookmark = FPDFBookmark_GetNextSibling(pDoc, 
pBookmark)))
+        lcl_getBookmarks(nLevel, rBuf, pDoc, pBookmark);
+}
+
+OUString PDFiumDocumentImpl::getBookmarks()
+{
+    OUStringBuffer aBuf;
+    FPDF_BOOKMARK pBookmark = FPDFBookmark_GetFirstChild(mpPdfDocument, 
nullptr);
+    lcl_getBookmarks(0, aBuf, mpPdfDocument, pBookmark);
+    return aBuf.makeStringAndClear();
+}
+
 basegfx::B2DSize PDFiumDocumentImpl::getPageSize(int nIndex)
 {
     basegfx::B2DSize aSize;

Reply via email to