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="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" 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="'Liberation Sans'" 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;