external/liborcus/UnpackedTarball_liborcus.mk | 5 external/liborcus/forcepoint-83.patch.1 | 38 hwpfilter/source/hbox.cxx | 12 include/oox/export/drawingml.hxx | 13 instsetoo_native/inc_openoffice/unix/find-requires-x11.sh | 2 oox/qa/unit/data/tdf147978_endsubpath.odp |binary oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp |binary oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp |binary oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp |binary oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp |binary oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx |binary oox/qa/unit/export.cxx | 156 + oox/source/export/drawingml.cxx | 919 ++++++---- oox/source/export/shapes.cxx | 31 sc/qa/unit/data/xlsx/tdf147014.xlsx |binary sc/qa/unit/subsequent_filters_test2.cxx | 18 sc/source/filter/oox/worksheethelper.cxx | 25 sd/qa/unit/data/odp/tdf147978_enhancedPath_viewBox.odp |binary sd/qa/unit/data/xml/tdf92001_0.xml | 42 sd/qa/unit/export-tests-ooxml2.cxx | 32 sd/qa/unit/export-tests-ooxml3.cxx | 16 sw/inc/deletelistener.hxx | 92 + sw/qa/extras/layout/data/forcepoint92.doc |binary sw/qa/extras/layout/layout.cxx | 6 sw/qa/extras/ooxmlexport/data/tdf147978_enhancedPath_commandABVW.odt |binary sw/qa/extras/ooxmlexport/data/tdf148111.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport17.cxx | 44 sw/qa/extras/ooxmlexport/ooxmlexport7.cxx | 24 sw/qa/extras/uiwriter/uiwriter3.cxx | 16 sw/source/core/frmedt/tblsel.cxx | 11 sw/source/core/layout/layact.cxx | 3 sw/source/core/layout/tabfrm.cxx | 8 sw/source/core/text/itratr.cxx | 2 sw/source/core/undo/untbl.cxx | 29 sw/source/filter/ww8/docxsdrexport.cxx | 20 sw/source/filter/ww8/rtfattributeoutput.cxx | 4 sw/source/filter/ww8/ww8par2.cxx | 71 sw/uiconfig/swxform/toolbar/formdesign.xml | 2 vcl/source/helper/strhelper.cxx | 3 writerfilter/source/dmapper/SdtHelper.cxx | 3 40 files changed, 1149 insertions(+), 498 deletions(-)
New commits: commit de1a126f6b1cc6b0bbe2e3ae82744ea7f40f9b8a Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Wed Mar 23 16:49:03 2022 +0000 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:48 2022 +0200 forcepoint#83 Invalid read of size 1 Change-Id: I1576dfd8c9731d943107764aeb66bb1c2294ad5f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131996 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/external/liborcus/UnpackedTarball_liborcus.mk b/external/liborcus/UnpackedTarball_liborcus.mk index 87e462e33d91..a87da7edb88d 100644 --- a/external/liborcus/UnpackedTarball_liborcus.mk +++ b/external/liborcus/UnpackedTarball_liborcus.mk @@ -15,8 +15,8 @@ $(eval $(call gb_UnpackedTarball_set_patchlevel,liborcus,1)) $(eval $(call gb_UnpackedTarball_update_autoconf_configs,liborcus)) -# crashtesting-crash-on-passing-null-to-std-string_vie.patch.1 submitted as -# https://gitlab.com/orcus/orcus/-/merge_requests/113 +# forcepoint-83.patch.1 submitted as +# https://gitlab.com/orcus/orcus/-/merge_requests/117 $(eval $(call gb_UnpackedTarball_add_patches,liborcus,\ external/liborcus/rpath.patch.0 \ @@ -25,6 +25,7 @@ $(eval $(call gb_UnpackedTarball_add_patches,liborcus,\ external/liborcus/fix-pch.patch.0 \ external/liborcus/liborcus_newline.patch.1 \ external/liborcus/std-get-busted.patch.1 \ + external/liborcus/forcepoint-83.patch.1 \ )) ifeq ($(OS),WNT) diff --git a/external/liborcus/forcepoint-83.patch.1 b/external/liborcus/forcepoint-83.patch.1 new file mode 100644 index 000000000000..bfd3bb86fcf9 --- /dev/null +++ b/external/liborcus/forcepoint-83.patch.1 @@ -0,0 +1,38 @@ +From 283b45ba3bcb22dc28303a09a96c9b94f86d1ba2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Caol=C3=A1n=20McNamara?= <caol...@redhat.com> +Date: Wed, 23 Mar 2022 16:44:00 +0000 +Subject: [PATCH] forcepoint#83 Invalid read of size 1 + +==343916== Invalid read of size 1 +==343916== at 0x11A7B2F0: orcus::parser_base::cur_char() const (parser_base.hpp:79) +==343916== by 0x11B7B112: orcus::sax_parser<orcus::sax_ns_parser<orcus::sax_token_parser<orcus::xml_stream_handler>::handler_wrapper>::handler_wrapper, orcus::sax_parser_default_config>::element_open(long) (sax_parser.hpp:258) +==343916== by 0x11B7A2C7: orcus::sax_parser<orcus::sax_ns_parser<orcus::sax_token_parser<orcus::xml_stream_handler>::handler_wrapper>::handler_wrapper, orcus::sax_parser_default_config>::element() (sax_parser.hpp:246) +==343916== by 0x11B7A197: orcus::sax_parser<orcus::sax_ns_parser<orcus::sax_token_parser<orcus::xml_stream_handler>::handler_wrapper>::handler_wrapper, orcus::sax_parser_default_config>::body() (sax_parser.hpp:214) +==343916== by 0x11B79FD9: orcus::sax_parser<orcus::sax_ns_parser<orcus::sax_token_parser<orcus::xml_stream_handler>::handler_wrapper>::handler_wrapper, orcus::sax_parser_default_config>::parse() (sax_parser.hpp:182) +==343916== by 0x11B79F8B: orcus::sax_ns_parser<orcus::sax_token_parser<orcus::xml_stream_handler>::handler_wrapper>::parse() (sax_ns_parser.hpp:277) +==343916== by 0x11B79768: orcus::sax_token_parser<orcus::xml_stream_handler>::parse() (sax_token_parser.hpp:215) +==343916== by 0x11B79406: orcus::xml_stream_parser::parse() (xml_stream_parser.cpp:68) +==343916== by 0x11BE3805: orcus::orcus_xlsx::detect(unsigned char const*, unsigned long) (orcus_xlsx.cpp:188) +==343916== by 0x11AB2482: orcus::detect(unsigned char const*, unsigned long) (format_detection.cpp:60) +==343916== by 0x30E60945: (anonymous namespace)::OrcusFormatDetect::detect(com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue>&) (filterdetect.cxx:83) +==343916== by 0x30E60ABE: non-virtual thunk to (anonymous namespace)::OrcusFormatDetect::detect(com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue>&) (filterdetect.cxx:0) +--- + include/orcus/sax_parser.hpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/include/orcus/sax_parser.hpp b/include/orcus/sax_parser.hpp +index 15e8d917..d0fc45b5 100644 +--- a/include/orcus/sax_parser.hpp ++++ b/include/orcus/sax_parser.hpp +@@ -255,6 +255,8 @@ void sax_parser<_Handler,_Config>::element_open(std::ptrdiff_t begin_pos) + while (true) + { + skip_space_and_control(); ++ if (!has_char()) ++ return; + char c = cur_char(); + if (c == '/') + { +-- +2.35.1 + commit 1d4bc5751dc096b501b957f5b2219f7eb2b9f3ba Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Fri Mar 25 12:10:32 2022 +0000 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:42 2022 +0200 forcepoint#89 don't remove page with footnote continuation frame in browse/html/web mode Change-Id: Ic821dd7f2cc1f47305b5fe2ced16d5168aedc0b9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132045 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx index 33a990a15a5a..da6e7c938f6b 100644 --- a/sw/source/core/layout/layact.cxx +++ b/sw/source/core/layout/layact.cxx @@ -295,7 +295,8 @@ bool SwLayAction::RemoveEmptyBrowserPages() do { if ( (pPage->GetSortedObjs() && pPage->GetSortedObjs()->size()) || - pPage->ContainsContent() ) + pPage->ContainsContent() || + pPage->FindFootnoteCont() ) pPage = static_cast<SwPageFrame*>(pPage->GetNext()); else { commit 70f2ec065a97f71415812fd20953274d387a8207 Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Sun Mar 27 12:03:06 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:36 2022 +0200 forcepoint#92 fix crash on layout of specific doc Change-Id: Id40d25d05d10d641d071cddd2e1c84594ac777a6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132142 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caol...@redhat.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132148 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/qa/extras/layout/data/forcepoint92.doc b/sw/qa/extras/layout/data/forcepoint92.doc new file mode 100644 index 000000000000..49c4a7f11dfe Binary files /dev/null and b/sw/qa/extras/layout/data/forcepoint92.doc differ diff --git a/sw/qa/extras/layout/layout.cxx b/sw/qa/extras/layout/layout.cxx index 267c20770bef..94c65d7821e2 100644 --- a/sw/qa/extras/layout/layout.cxx +++ b/sw/qa/extras/layout/layout.cxx @@ -2496,6 +2496,12 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testForcepointFootnoteFrame) //FIXME: disabled after failing again with fixed layout //CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testForcepoint76) { createSwDoc(DATA_DIRECTORY, "forcepoint76-1.rtf"); } +//just care it doesn't crash/assert +CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testForcepoint92) +{ + createSwDoc(DATA_DIRECTORY, "forcepoint92.doc"); +} + CPPUNIT_TEST_FIXTURE(SwLayoutWriter, testTdf118058) { SwDoc* pDoc = createSwDoc(DATA_DIRECTORY, "tdf118058.fodt"); diff --git a/sw/source/core/layout/tabfrm.cxx b/sw/source/core/layout/tabfrm.cxx index 7786f3aec4fc..b405a2124254 100644 --- a/sw/source/core/layout/tabfrm.cxx +++ b/sw/source/core/layout/tabfrm.cxx @@ -24,6 +24,7 @@ #include <viewimp.hxx> #include <fesh.hxx> #include <swtable.hxx> +#include <deletelistener.hxx> #include <dflyobj.hxx> #include <anchoreddrawobject.hxx> #include <fmtanchr.hxx> @@ -2114,13 +2115,18 @@ void SwTabFrame::MakeAll(vcl::RenderContext* pRenderContext) } SwFootnoteBossFrame *pOldBoss = bFootnotesInDoc ? FindFootnoteBossFrame( true ) : nullptr; bool bReformat; + std::optional<SfxDeleteListener> oDeleteListener; + if (pOldBoss) + oDeleteListener.emplace(*pOldBoss); SwFrameDeleteGuard g(this); if ( MoveBwd( bReformat ) ) { + SAL_WARN_IF(oDeleteListener && oDeleteListener->WasDeleted(), "sw.layout", "SwFootnoteBossFrame unexpectedly deleted"); + aRectFnSet.Refresh(this); bMovedBwd = true; aNotify.SetLowersComplete( false ); - if ( bFootnotesInDoc ) + if (bFootnotesInDoc && !oDeleteListener->WasDeleted()) MoveLowerFootnotes( nullptr, pOldBoss, nullptr, true ); if ( bReformat || bKeep ) { commit 8166819eb10b41960b7bd59985fc970239b30856 Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Sat Mar 26 21:50:49 2022 +0000 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:31 2022 +0200 move DeleteListener contraptions to toplevel writer includes Change-Id: Ifa1e75b62da4174f27fca52eb86559cd6a381513 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132141 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caol...@redhat.com> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132147 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/inc/deletelistener.hxx b/sw/inc/deletelistener.hxx new file mode 100644 index 000000000000..2b212e418fef --- /dev/null +++ b/sw/inc/deletelistener.hxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <svl/listener.hxx> +#include <svl/lstner.hxx> +#include "calbck.hxx" + +class SwDeleteListener final : public SwClient +{ +private: + SwModify* m_pModify; + + virtual void SwClientNotify(const SwModify&, const SfxHint& rHint) override + { + if (rHint.GetId() != SfxHintId::SwLegacyModify) + return; + auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); + if (pLegacy->GetWhich() == RES_OBJECTDYING) + { + m_pModify->Remove(this); + m_pModify = nullptr; + } + } + +public: + SwDeleteListener(SwModify& rModify) + : m_pModify(&rModify) + { + m_pModify->Add(this); + } + + bool WasDeleted() const { return !m_pModify; } + + virtual ~SwDeleteListener() override + { + if (!m_pModify) + return; + m_pModify->Remove(this); + } +}; + +class SvtDeleteListener final : public SvtListener +{ +private: + bool bObjectDeleted; + +public: + explicit SvtDeleteListener(SvtBroadcaster& rNotifier) + : bObjectDeleted(false) + { + StartListening(rNotifier); + } + + virtual void Notify(const SfxHint& rHint) override + { + if (rHint.GetId() == SfxHintId::Dying) + bObjectDeleted = true; + } + + bool WasDeleted() const { return bObjectDeleted; } +}; + +class SfxDeleteListener final : public SfxListener +{ +private: + bool bObjectDeleted; + +public: + explicit SfxDeleteListener(SfxBroadcaster& rNotifier) + : bObjectDeleted(false) + { + StartListening(rNotifier); + } + + virtual void Notify(SfxBroadcaster& /*rBC*/, const SfxHint& rHint) override + { + if (rHint.GetId() == SfxHintId::Dying) + bObjectDeleted = true; + } + + bool WasDeleted() const { return bObjectDeleted; } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sw/source/filter/ww8/ww8par2.cxx b/sw/source/filter/ww8/ww8par2.cxx index 3f3c3adfb74f..540b25018743 100644 --- a/sw/source/filter/ww8/ww8par2.cxx +++ b/sw/source/filter/ww8/ww8par2.cxx @@ -39,6 +39,7 @@ #include <editeng/pgrditem.hxx> #include <msfilter.hxx> #include <pam.hxx> +#include <deletelistener.hxx> #include <doc.hxx> #include <IDocumentStylePoolAccess.hxx> #include <docary.hxx> @@ -167,32 +168,6 @@ sal_uInt32 wwSectionManager::GetWWPageTopMargin() const return !maSegments.empty() ? maSegments.back().maSep.dyaTop : 0; } -namespace -{ - class DeleteListener final : public SvtListener - { - private: - bool bObjectDeleted; - public: - explicit DeleteListener(SvtBroadcaster& rNotifier) - : bObjectDeleted(false) - { - StartListening(rNotifier); - } - - virtual void Notify(const SfxHint& rHint) override - { - if (rHint.GetId() == SfxHintId::Dying) - bObjectDeleted = true; - } - - bool WasDeleted() const - { - return bObjectDeleted; - } - }; -} - sal_uInt16 SwWW8ImplReader::End_Footnote() { /* @@ -252,7 +227,7 @@ sal_uInt16 SwWW8ImplReader::End_Footnote() SwFormatFootnote& rFormatFootnote = static_cast<SwFormatFootnote&>(pFN->GetAttr()); - DeleteListener aDeleteListener(rFormatFootnote.GetNotifier()); + SvtDeleteListener aDeleteListener(rFormatFootnote.GetNotifier()); // read content of Ft-/End-Note Read_HdFtFootnoteText( pSttIdx, rDesc.mnStartCp, rDesc.mnLen, rDesc.meType); @@ -2783,46 +2758,6 @@ void WW8TabDesc::MoveOutsideTable() *m_pIo->m_pPaM->GetPoint() = *m_xTmpPos->GetPoint(); } -namespace -{ - class SwTableNodeListener final : public SwClient - { - private: - SwModify* m_pModify; - - virtual void SwClientNotify(const SwModify&, const SfxHint& rHint) override - { - if (rHint.GetId() != SfxHintId::SwLegacyModify) - return; - auto pLegacy = static_cast<const sw::LegacyModifyHint*>(&rHint); - if (pLegacy->GetWhich() == RES_OBJECTDYING) - { - m_pModify->Remove(this); - m_pModify = nullptr; - } - } - - public: - SwTableNodeListener(SwModify* pModify) - : m_pModify(pModify) - { - m_pModify->Add(this); - } - - bool WasDeleted() const - { - return !m_pModify; - } - - virtual ~SwTableNodeListener() override - { - if (!m_pModify) - return; - m_pModify->Remove(this); - } - }; -} - void WW8TabDesc::FinishSwTable() { m_pIo->m_xRedlineStack->closeall(*m_pIo->m_pPaM->GetPoint()); @@ -2833,7 +2768,7 @@ void WW8TabDesc::FinishSwTable() m_pIo->m_pLastAnchorPos.reset(); SwTableNode* pTableNode = m_pTable->GetTableNode(); - SwTableNodeListener aListener(pTableNode); + SwDeleteListener aListener(*pTableNode); m_pIo->m_xRedlineStack = std::move(mxOldRedlineStack); if (xLastAnchorCursor) commit 4ca76f063c8112c4f1714e003b5aa31d3d7f6706 Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Mon Mar 28 11:05:34 2022 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:27 2022 +0200 tdf#148235 Restore toolbar item to switch XForm to design mode commit 45b6f096e7ae86d0692ecdfd5b7069622d8a6efa Date: Sun Oct 28 17:02:34 2018 +0100 sw toolbars sync context toolbars between different apps had replaced the "Form Design" toolbar item to switch a complete XForm to design mode with an entry that switches the form controls to design mode instead. (The former one is labelled "Design Mode On/Off", the latter one just "Design Mode" in the English UI.) This meant that the XForm could no longer be switched to design mode from there, unless the toolbar was manually customized to add the entry back. This brings the previous toolbar item back. It also removes the one to switch the form controls to design mode again, since that one is already contained in the "Form Controls" toolbar, where it fits better. (And having two different toolbar items with almost identical labels but different functionality would probably be rather confusing.) Change-Id: Ia4c98dfa6ad8372eba08a9f08920153133a7f88d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132207 Reviewed-by: Andreas Kainz <kain...@gmail.com> Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> Tested-by: Jenkins (cherry picked from commit 4f9bf4201bb706cd19142f0805cfc4c859186cd4) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132177 Reviewed-by: Adolfo Jayme Barrientos <fit...@ubuntu.com> diff --git a/sw/uiconfig/swxform/toolbar/formdesign.xml b/sw/uiconfig/swxform/toolbar/formdesign.xml index 067ddff0675f..f748bcf68e2e 100644 --- a/sw/uiconfig/swxform/toolbar/formdesign.xml +++ b/sw/uiconfig/swxform/toolbar/formdesign.xml @@ -44,7 +44,7 @@ <toolbar:toolbaritem xlink:href=".uno:LeaveGroup" toolbar:visible="false"/> <toolbar:toolbarseparator/> <toolbar:toolbaritem xlink:href=".uno:SelectObject"/> - <toolbar:toolbaritem xlink:href=".uno:SwitchControlDesignMode"/> + <toolbar:toolbaritem xlink:href=".uno:SwitchXFormsDesignMode"/> <toolbar:toolbarseparator/> <toolbar:toolbaritem xlink:href=".uno:ControlProperties"/> <toolbar:toolbaritem xlink:href=".uno:FormProperties"/> commit 2630032ac0fb3e14fc7cd9e172ff1c4e95c54da3 Author: Mark Hung <mark...@gmail.com> AuthorDate: Sat Mar 19 21:18:54 2022 +0800 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:21 2022 +0200 tdf#141671 fix destroyed pargraph style in exported RTF in MSO. Do not call MoveCharacterProperties() in RtfAttributeOutput ::EndParagraphProperties(). RtfAttributeOutput::ParagraphStyle() has emited run associated properties defined in paragraph style. Calling MoveCharacterProperties() again overwrites them. As this bug is only visible in MS Word, no unit test case is added. Change-Id: I6e5bfd12e8afa7dc286ca54448c1ff022aade31d Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131848 Tested-by: Jenkins Reviewed-by: Mark Hung <mark...@gmail.com> (cherry picked from commit 882045b934a3416cc48da2c4e30648892a419577) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132175 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx b/sw/source/filter/ww8/rtfattributeoutput.cxx index 03f2ca851920..ac45ba2b7c2c 100644 --- a/sw/source/filter/ww8/rtfattributeoutput.cxx +++ b/sw/source/filter/ww8/rtfattributeoutput.cxx @@ -384,7 +384,9 @@ void RtfAttributeOutput::EndParagraphProperties( const SwRedlineData* /*pRedlineParagraphMarkerDeleted*/, const SwRedlineData* /*pRedlineParagraphMarkerInserted*/) { - const OString aProperties = MoveCharacterProperties(true); + // Do not call MoveCharacterProperties(), + // Otherwise associate properties in the paragraph style are ruined. + const OString aProperties = m_aStyles.makeStringAndClear(); m_rExport.Strm().WriteOString(aProperties); } commit e7ff4f0e4da4be9e59f146b4cbce1f52a418fbd3 Author: László Németh <nem...@numbertext.org> AuthorDate: Mon Mar 28 11:35:35 2022 +0200 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:16 2022 +0200 tdf#148228 sw: fix Undo of tracked table deletion in Hide Changes mode In Hide Changes mode, tables didn't reappear during Undo of their tracked deletion, only by saving and reloading the document. Follow-up to commit 0c6221e1545e7b96d9df23cdc24302c28ae935b8 "tdf#148227 sw: fix Undo of tracked row deletion in Hide Changes mode". Change-Id: Ifdc25ab4ae0be25a0c7559ee05b6af2e4f1aa8cf Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132169 Tested-by: László Németh <nem...@numbertext.org> Reviewed-by: László Németh <nem...@numbertext.org> (cherry picked from commit eda1a7aeff42c08e02295e5a8353a6d86a61a118) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132178 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx b/sw/qa/extras/uiwriter/uiwriter3.cxx index 57f5e6649b70..559d47641a99 100644 --- a/sw/qa/extras/uiwriter/uiwriter3.cxx +++ b/sw/qa/extras/uiwriter/uiwriter3.cxx @@ -1988,7 +1988,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf146962) // only a single row is visible again assertXPath(pXmlDoc, "/root/page[1]/body/tab/row", 1); - // check Undo + // tdf#148227 check Undo of tracked table row deletion dispatchCommand(mxComponent, ".uno:Undo", {}); discardDumpedLayout(); @@ -2040,6 +2040,14 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf147347) pXmlDoc = parseLayoutDump(); // no visible row again assertXPath(pXmlDoc, "/root/page[1]/body/tab/row", 0); + + // tdf#148228 check Undo of tracked table deletion + + dispatchCommand(mxComponent, ".uno:Undo", {}); + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + // This was 0 + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row", 2); } CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf135014) diff --git a/sw/source/core/undo/untbl.cxx b/sw/source/core/undo/untbl.cxx index d5536b01e624..d5c15a0fbc4a 100644 --- a/sw/source/core/undo/untbl.cxx +++ b/sw/source/core/undo/untbl.cxx @@ -952,14 +952,20 @@ void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) pFormat->InvalidateInSwCache(RES_ATTRSET_CHG); + // table without table frame + bool bHiddenTable = true; + // for safety, invalidate all TableFrames SwIterator<SwTabFrame,SwFormat> aIter( *pFormat ); for( SwTabFrame* pLast = aIter.First(); pLast; pLast = aIter.Next() ) + { if( pLast->GetTable() == &rTable ) { pLast->InvalidateAll(); pLast->SetCompletePaint(); + bHiddenTable = false; } + } // fill FrameFormats with defaults (0) pFormat = nullptr; @@ -986,7 +992,19 @@ void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) m_bModifyBox = false; if ( bHideChanges ) - aTmpBox.MakeFrames( rTable ); + { + if ( bHiddenTable ) + { + SwTableNode* pTableNode = rTable.GetTableNode(); + pTableNode->DelFrames(); + SwNodeIndex aTableIdx( *pTableNode->EndOfSectionNode(), 1 ); + pTableNode->MakeOwnFrames(&aTableIdx); + } + else + { + aTmpBox.MakeFrames( rTable ); + } + } } void SaveTable::SaveContentAttrs( SwDoc* pDoc ) commit f56a6704e94169c492c5c5313b18d462143d3be0 Author: László Németh <nem...@numbertext.org> AuthorDate: Fri Mar 25 15:27:21 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:11 2022 +0200 tdf#148227 sw: fix Undo of tracked row deletion in Hide Changes mode In Hide Changes mode, table rows didn't reappear during Undo of tracked deletion of table rows, only by saving and reloading the document. Follow-up to commit a74c51025fa4519caaf461492e4ed8e68bd34885 "tdf#146962 sw: hide deleted row at deletion in Hide Changes" and commit 794fd10af7361d5a64a0f8bfbe5c8b5f308617a5 "tdf#147347 sw: hide deleted table at deletion in Hide Changes". Change-Id: I7ffe8a3687d1d385a549f7d438f7058d829ffd8c Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132123 Tested-by: László Németh <nem...@numbertext.org> Reviewed-by: László Németh <nem...@numbertext.org> (cherry picked from commit 0c6221e1545e7b96d9df23cdc24302c28ae935b8) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132049 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx b/sw/qa/extras/uiwriter/uiwriter3.cxx index b8cb857043b2..57f5e6649b70 100644 --- a/sw/qa/extras/uiwriter/uiwriter3.cxx +++ b/sw/qa/extras/uiwriter/uiwriter3.cxx @@ -1987,6 +1987,14 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf146962) pXmlDoc = parseLayoutDump(); // only a single row is visible again assertXPath(pXmlDoc, "/root/page[1]/body/tab/row", 1); + + // check Undo + + dispatchCommand(mxComponent, ".uno:Undo", {}); + discardDumpedLayout(); + pXmlDoc = parseLayoutDump(); + // This was 1 + assertXPath(pXmlDoc, "/root/page[1]/body/tab/row", 2); } CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf147347) diff --git a/sw/source/core/frmedt/tblsel.cxx b/sw/source/core/frmedt/tblsel.cxx index 80b797384859..b203281506cd 100644 --- a/sw/source/core/frmedt/tblsel.cxx +++ b/sw/source/core/frmedt/tblsel.cxx @@ -2336,6 +2336,10 @@ void FndBox_::MakeFrames( SwTable &rTable ) // And this for all instances of a table (for example in header/footer). sal_uInt16 nStPos = 0; sal_uInt16 nEndPos= rTable.GetTabLines().size() - 1; + SwRootFrame* pLayout = + rTable.GetFrameFormat()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout(); + bool bHideChanges = pLayout && pLayout->IsHideRedlines(); + if ( m_pLineBefore ) { nStPos = rTable.GetTabLines().GetPos( @@ -2389,9 +2393,14 @@ void FndBox_::MakeFrames( SwTable &rTable ) // ???? or is this the last Follow of the table ???? pUpperFrame = pTable; + SwRedlineTable::size_type nRedlinePos = 0; for ( sal_uInt16 j = nStPos; j <= nEndPos; ++j ) - ::lcl_InsertRow( *rTable.GetTabLines()[j], + { + SwTableLine * pLine = rTable.GetTabLines()[j]; + if ( !bHideChanges || !pLine->IsDeleted(nRedlinePos) ) + ::lcl_InsertRow( *pLine, static_cast<SwLayoutFrame*>(pUpperFrame), pSibling ); + } if ( pUpperFrame->IsTabFrame() ) static_cast<SwTabFrame*>(pUpperFrame)->SetCalcLowers(); } diff --git a/sw/source/core/undo/untbl.cxx b/sw/source/core/undo/untbl.cxx index d3294b5446a3..d5536b01e624 100644 --- a/sw/source/core/undo/untbl.cxx +++ b/sw/source/core/undo/untbl.cxx @@ -34,6 +34,8 @@ #include <IDocumentRedlineAccess.hxx> #include <IDocumentFieldsAccess.hxx> #include <IDocumentStylePoolAccess.hxx> +#include <IDocumentLayoutAccess.hxx> +#include <rootfrm.hxx> #include <editsh.hxx> #include <docary.hxx> #include <ndtxt.hxx> @@ -936,6 +938,12 @@ void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) { m_bModifyBox = bMdfyBox; + FndBox_ aTmpBox( nullptr, nullptr ); + bool bHideChanges = rTable.GetFrameFormat()->GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout()->IsHideRedlines(); + // TODO delete/make frames only at changing line attribute TextChangesOnly (RES_PRINT) to true again + if ( bHideChanges ) + aTmpBox.DelFrames( rTable ); + // first, get back attributes of TableFrameFormat SwFrameFormat* pFormat = rTable.GetFrameFormat(); SfxItemSet& rFormatSet = const_cast<SfxItemSet&>(static_cast<SfxItemSet const &>(pFormat->GetAttrSet())); @@ -976,6 +984,9 @@ void SaveTable::RestoreAttr( SwTable& rTable, bool bMdfyBox ) m_aFrameFormats.clear(); m_bModifyBox = false; + + if ( bHideChanges ) + aTmpBox.MakeFrames( rTable ); } void SaveTable::SaveContentAttrs( SwDoc* pDoc ) commit b73c6a8ac7e50a179dfa9e832606947a5ecbd5ee Author: Aron Budea <aron.bu...@collabora.com> AuthorDate: Sun Feb 13 06:57:16 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:40:06 2022 +0200 tdf#147014 Image missing due to integer overflow 32-bit awt::Point/Size/Rectangle cannot fit size of 1M rows with larger (eg. 5x the usual) height, and could overflow. This causes problems in 64-bit Linux builds and, since the following commit, in 64-bit Windows builds: 3d90997fb6f232d8008df4d166d7b97b869c200f For now, clamp possibly overflowing values to 32-bit. Change-Id: Ifda7265703388abdfb47f523da4f0c5822358404 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/129876 Tested-by: Jenkins Reviewed-by: Luboš Luňák <l.lu...@collabora.com> Reviewed-by: Aron Budea <aron.bu...@collabora.com> Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132168 diff --git a/sc/qa/unit/data/xlsx/tdf147014.xlsx b/sc/qa/unit/data/xlsx/tdf147014.xlsx new file mode 100644 index 000000000000..df4428795d9d Binary files /dev/null and b/sc/qa/unit/data/xlsx/tdf147014.xlsx differ diff --git a/sc/qa/unit/subsequent_filters_test2.cxx b/sc/qa/unit/subsequent_filters_test2.cxx index f5ebba61e50b..70466d7816c8 100644 --- a/sc/qa/unit/subsequent_filters_test2.cxx +++ b/sc/qa/unit/subsequent_filters_test2.cxx @@ -204,6 +204,7 @@ public: void testTdf129940(); void testTdf139612(); void testTdf144740(); + void testTdf147014(); void testTdf139763ShapeAnchor(); void testAutofilterNamedRangesXLSX(); void testInvalidBareBiff5(); @@ -312,6 +313,7 @@ public: CPPUNIT_TEST(testTdf129940); CPPUNIT_TEST(testTdf139612); CPPUNIT_TEST(testTdf144740); + CPPUNIT_TEST(testTdf147014); CPPUNIT_TEST(testTdf139763ShapeAnchor); CPPUNIT_TEST(testAutofilterNamedRangesXLSX); CPPUNIT_TEST(testInvalidBareBiff5); @@ -2882,6 +2884,22 @@ void ScFiltersTest2::testTdf144740() xDocSh->DoClose(); } +void ScFiltersTest2::testTdf147014() +{ + ScDocShellRef xDocSh = loadDoc(u"tdf147014.", FORMAT_XLSX); + CPPUNIT_ASSERT_MESSAGE("Failed to load tdf147014.xlsx", xDocSh.is()); + uno::Reference<frame::XModel> xModel = xDocSh->GetModel(); + uno::Reference<sheet::XSpreadsheetDocument> xDoc(xModel, uno::UNO_QUERY_THROW); + uno::Reference<container::XIndexAccess> xIA(xDoc->getSheets(), uno::UNO_QUERY_THROW); + uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xIA->getByIndex(0), + uno::UNO_QUERY_THROW); + xIA.set(xDrawPageSupplier->getDrawPage(), uno::UNO_QUERY_THROW); + // The sheet has a single shape, without the fix it was not imported, except in 32-bit builds + CPPUNIT_ASSERT_EQUAL_MESSAGE("Shape not imported", static_cast<sal_Int32>(1), xIA->getCount()); + + xDocSh->DoClose(); +} + void ScFiltersTest2::testTdf139763ShapeAnchor() { ScDocShellRef xDocSh = loadDoc(u"tdf139763ShapeAnchor.", FORMAT_XLSX); diff --git a/sc/source/filter/oox/worksheethelper.cxx b/sc/source/filter/oox/worksheethelper.cxx index 40a99cdb444b..95625751d4a0 100644 --- a/sc/source/filter/oox/worksheethelper.cxx +++ b/sc/source/filter/oox/worksheethelper.cxx @@ -75,7 +75,7 @@ #include <editeng/eeitem.hxx> #include <editeng/editobj.hxx> #include <editeng/flditem.hxx> -#include <tools/UnitConversion.hxx> +#include <tools/gen.hxx> namespace oox::xls { @@ -96,6 +96,18 @@ void lclUpdateProgressBar( const ISegmentProgressBarRef& rxProgressBar, double f rxProgressBar->setPosition( fPosition ); } +// TODO Needed because input might be >32-bit (in 64-bit builds), +// or a negative, already overflown value (in 32-bit builds) +sal_Int32 lclClampToNonNegativeInt32( tools::Long aVal ) +{ + if ( aVal > SAL_MAX_INT32 || aVal < 0 ) + { + SAL_WARN( "sc.filter", "Overflow detected, " << aVal << " does not fit into sal_Int32, or is negative." ); + return SAL_MAX_INT32; + } + return static_cast<sal_Int32>( aVal ); +} + } // namespace ColumnModel::ColumnModel() : @@ -538,9 +550,9 @@ const awt::Size& WorksheetGlobals::getDrawPageSize() const awt::Point WorksheetGlobals::getCellPosition( sal_Int32 nCol, sal_Int32 nRow ) const { - awt::Point aPoint; - PropertySet aCellProp( getCell( ScAddress( nCol, nRow, getSheetIndex() ) ) ); - aCellProp.getProperty( aPoint, PROP_Position ); + const tools::Rectangle aMMRect( getScDocument().GetMMRect( nCol, nRow, nCol, nRow, getSheetIndex() ) ); + awt::Point aPoint( lclClampToNonNegativeInt32( aMMRect.Left() ), + lclClampToNonNegativeInt32( aMMRect.Top() ) ); return aPoint; } @@ -1360,8 +1372,9 @@ void WorksheetGlobals::groupColumnsOrRows( sal_Int32 nFirstColRow, sal_Int32 nLa void WorksheetGlobals::finalizeDrawings() { // calculate the current drawing page size (after rows/columns are imported) - PropertySet aRangeProp( getCellRange( ScRange( 0, 0, getSheetIndex(), mrMaxApiPos.Col(), mrMaxApiPos.Row(), getSheetIndex() ) ) ); - aRangeProp.getProperty( maDrawPageSize, PROP_Size ); + const Size aPageSize( getScDocument().GetMMRect( 0, 0, mrMaxApiPos.Col(), mrMaxApiPos.Row(), getSheetIndex() ).GetSize() ); + maDrawPageSize.Width = lclClampToNonNegativeInt32( aPageSize.Width() ); + maDrawPageSize.Height = lclClampToNonNegativeInt32( aPageSize.Height() ); // import DML and VML if( !maDrawingPath.isEmpty() ) commit 08bcf18438dce22832ad55a2e8ebef044cf83bd6 Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Thu Mar 24 09:33:44 2022 +0000 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:39:59 2022 +0200 forcepoint#85 Conditional jump or move depends on uninitialised value ==398461== by 0xCDC7960: (anonymous namespace)::CopyUntil(char16_t*&, char16_t const*&, char16_t, bool) (strhelper.cxx:88) ==398461== by 0xCDC839F: psp::WhitespaceToSpace(rtl::OUString const&, bool) (strhelper.cxx:294) ==398461== by 0xCB54980: vcl::PDFWriterImpl::setOutlineItemText(int, rtl::OUString const&) (pdfwriter_impl.cxx:9875) ==398461== by 0xCB547FF: vcl::PDFWriterImpl::createOutlineItem(int, rtl::OUString const&, int) (pdfwriter_impl.cxx:9851) ==398461== by 0xCAF39B0: vcl::PDFWriter::CreateOutlineItem(int, rtl::OUString const&, int) (pdfwriter.cxx:383) ==398461== by 0xCABD7C8: vcl::GlobalSyncData::PlayGlobalActions(vcl::PDFWriter&) (pdfextoutdevdata.cxx:250) ==398461== by 0xCAC0628: vcl::PDFExtOutDevData::PlayGlobalActions(vcl::PDFWriter&) (pdfextoutdevdata.cxx:616) ==398461== by 0x3D06EA0F: PDFExport::Export(rtl::OUString const&, com::sun::star::uno::Sequence<com::sun::star::beans::PropertyValue> const&) (pdfexport.cxx:1004) Change-Id: I6bc086997851ee06531a4a9ae263e2b26edfba84 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132035 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> diff --git a/vcl/source/helper/strhelper.cxx b/vcl/source/helper/strhelper.cxx index 96e10b4865e8..782c9ce123e1 100644 --- a/vcl/source/helper/strhelper.cxx +++ b/vcl/source/helper/strhelper.cxx @@ -83,7 +83,8 @@ void CopyUntil( sal_Unicode*& pTo, const sal_Unicode*& pFrom, sal_Unicode cUntil *pTo = *pFrom; pTo++; } - pFrom++; + if( *pFrom ) + pFrom++; } while( *pFrom && *pFrom != cUntil ); // copy the terminating character unless zero or protector if( ! isProtect( *pFrom ) || bIncludeUntil ) commit 37cd417547d0e584d4131c586546ae8779a4d122 Author: Regina Henschel <rb.hensc...@t-online.de> AuthorDate: Fri Mar 18 18:31:05 2022 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Wed Mar 30 09:39:41 2022 +0200 tdf#147978 export subpaths individually in custGeom ...and implement export of all missing commands, use existing viewBox if suitable, use one EnhancedCustomShape2d move WriteCustomGeometryPoint to protected, make GetCustomGeometryPointValue local The fix solves tdf#100390 too. Without the fix the entire enhanced-path was exported as one element <a:path>. The command F was applied to the whole drawing but should affect only the subpath. The implementation is changed so that each subpath gets its own element <a:path> and command F acts only on its subpath. Support for export of handles and equations is still a long way off. Thus there is no reason to tread shapes with and without handles different. Shapes with handles had used method WritePolyPolygon, but that is not able to handle subpaths. So have desided to use method WriteCustomGeometry for all cases. To get shapes exported regardless of path commands I have added the export for the missing commands. I have removed the no longer used method WritePolyPolygon. The special treatment of shapes "moon" and "mso-spt89" (right up arrow) in export is no longer needed. Related code parts are removed. The unit test testFlipAndRotateCustomShape is adapted. In case the method WriteCustomGeometry fails, the enhanced-path is invalid. In that case an minimal custGeom is written in case of docx. Shapes whose drawing does not touch all edges of the snap rectangle were exported with wrong position and size of the drawing inside the snap rectangle. That is fixed by using an existing ViewBox for the OOXML path size. The old way of creating a path size from point coordinates is only used if the shape has no suitable ViewBox. The point values in unit test SdOOXMLExportTest2::testTdf111798 are adapted to path size 21600 x 21600 and traverse direction of the points is corrected. The resulting shape outline is still the same as before. The expected xml is updated for file tdf92001.odp in SdImportTest::testDocumentLayout. The resulting shape outline is the same, because the shape touches the edges of the snap rectangle. The case, that the shape outline does not touch a edge of the snap rectangle is tested in SdOOXMLExportTest3::testEnhancedPathViewBox. Still missing is the case, that ViewBox has other left,top than 0,0. In that case all coordinates would have to be shifted because the path size in OOXML has only width and height but not left,top. That will not be included in this patch. Change-Id: Ib1736d6a08371f4d98411d2769275f0580cd0030 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131837 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 2029b2f6dd0109c5892e5ac5640022b31fe42fd2) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132048 Reviewed-by: Bartosz Kosiorek <gan...@poczta.onet.pl> diff --git a/include/oox/export/drawingml.hxx b/include/oox/export/drawingml.hxx index 63a32225172b..7fbb015b4ce4 100644 --- a/include/oox/export/drawingml.hxx +++ b/include/oox/export/drawingml.hxx @@ -43,6 +43,7 @@ #include <vcl/checksum.hxx> #include <tools/gen.hxx> #include <vcl/mapmod.hxx> +#include <svx/EnhancedCustomShape2d.hxx> class Graphic; class SdrObjCustomShape; @@ -198,7 +199,8 @@ protected: void WriteGlowEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); void WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet); - bool HasEnhancedCustomShapeSegmentCommand(const css::uno::Reference<css::drawing::XShape>& rXShape, const sal_Int16 nCommand); + void WriteCustomGeometryPoint(const css::drawing::EnhancedCustomShapeParameterPair& rParamPair, + const EnhancedCustomShape2d& rCustomShape2d); public: DrawingML( ::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType = DOCUMENT_PPTX, DMLTextExport* pTextExport = nullptr ) @@ -306,14 +308,7 @@ public: bool WriteCustomGeometry( const css::uno::Reference<css::drawing::XShape>& rXShape, const SdrObjCustomShape& rSdrObjCustomShape); - void WriteCustomGeometryPoint( - const css::drawing::EnhancedCustomShapeParameterPair& rParamPair, - const SdrObjCustomShape& rSdrObjCustomShape); - static sal_Int32 GetCustomGeometryPointValue( - const css::drawing::EnhancedCustomShapeParameter& rParam, - const SdrObjCustomShape& rSdrObjCustomShape); - void WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, - const tools::PolyPolygon& rPolyPolygon, const bool bClosed); + void WriteEmptyCustomGeometry(); void WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, const bool bClosed); void WriteFill( const css::uno::Reference< css::beans::XPropertySet >& xPropSet ); diff --git a/oox/qa/unit/data/tdf147978_endsubpath.odp b/oox/qa/unit/data/tdf147978_endsubpath.odp new file mode 100644 index 000000000000..2dfd55de1be3 Binary files /dev/null and b/oox/qa/unit/data/tdf147978_endsubpath.odp differ diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp new file mode 100644 index 000000000000..99ddda7c132e Binary files /dev/null and b/oox/qa/unit/data/tdf147978_enhancedPath_commandA.odp differ diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp new file mode 100644 index 000000000000..49e01bc0933a Binary files /dev/null and b/oox/qa/unit/data/tdf147978_enhancedPath_commandHIJK.odp differ diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp new file mode 100644 index 000000000000..3dcd0d567545 Binary files /dev/null and b/oox/qa/unit/data/tdf147978_enhancedPath_commandT.odp differ diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp new file mode 100644 index 000000000000..6112251783e1 Binary files /dev/null and b/oox/qa/unit/data/tdf147978_enhancedPath_commandXY.odp differ diff --git a/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx new file mode 100644 index 000000000000..bbedc7ab98f5 Binary files /dev/null and b/oox/qa/unit/data/tdf147978_enhancedPath_subpath.pptx differ diff --git a/oox/qa/unit/export.cxx b/oox/qa/unit/export.cxx index 426d2623926f..b8ee3e2c6e75 100644 --- a/oox/qa/unit/export.cxx +++ b/oox/qa/unit/export.cxx @@ -376,6 +376,162 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf146690_endParagraphRunPropertiesNewLinesTextSi assertXPath(pXmlDoc, "//p:sp[1]/p:txBody/a:p[3]/a:endParaRPr", "sz", "500"); } +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_endsubpath) +{ + // Given an odp file that contains a non-primitive custom shape with command N + OUString aURL = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_endsubpath.odp"; + + // When saving that document: + loadAndSave(aURL, "Impress Office Open XML"); + + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // Then make sure the pathLst has two child elements, + // Without the accompanying fix in place, only one element a:path was exported. + assertXPathChildren(pXmlDoc, "//a:pathLst", 2); + // and make sure first path with no stroke, second with no fill + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "stroke", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "fill", "none"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandA) +{ + // Given an odp file that contains a non-primitive custom shape with command N + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandA.odp"; + + // When saving that document: + loadAndSave(aURL, "Impress Office Open XML"); + + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // Then make sure the path has a child element arcTo. Prior to the fix that part of the curve was + // not exported at all. In odp it is a command A. Such does not exist in OOXML and is therefore + // exported as a:lnTo followed by a:arcTo + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo", 2); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", 1); + // And assert its attribute values + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "wR", "7200"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "hR", "5400"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "stAng", "7719588"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "swAng", "-5799266"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandT) +{ + // The odp file contains a non-primitive custom shape with commands MTZ + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandT.odp"; + + // Export to pptx had only exported the command M and has used a wrong path size + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 216 216" + assertXPath(pXmlDoc, "//a:pathLst/a:path", "w", "216"); + assertXPath(pXmlDoc, "//a:pathLst/a:path", "h", "216"); + // Command T is exported as lnTo followed by arcTo. + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", 1); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:close", 1); + // And assert its values + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo/a:pt", "x", "108"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:moveTo/a:pt", "y", "162"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo/a:pt", "x", "138"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:lnTo/a:pt", "y", "110"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "wR", "108"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "hR", "54"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "stAng", "18000000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo", "swAng", "18000000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandXY) +{ + // The odp file contains a non-primitive custom shapes with commands XY + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandXY.odp"; + + // Export to pptx had dropped commands X and Y. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 10 10" + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "10"); + // Shape has M 0 5 Y 5 0 10 5 5 10 F Y 0 5 N M 10 10 X 0 0 + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:moveTo/a:pt", "x", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:moveTo/a:pt", "y", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "wR", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "hR", "5"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "stAng", "10800000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[1]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[2]", "stAng", "16200000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[2]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[3]", "stAng", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[3]", "swAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[4]", "stAng", "0"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]/a:arcTo[4]", "swAng", "-5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:moveTo/a:pt", "x", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:moveTo/a:pt", "y", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "wR", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "hR", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "stAng", "5400000"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]/a:arcTo", "swAng", "5400000"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_commandHIJK) +{ + // The odp file contains a non-primitive custom shapes with commands H,I,J,K + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_commandHIJK.odp"; + + // Export to pptx had dropped commands X and Y. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File has draw:viewBox="0 0 80 80" + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "80"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "80"); + // File uses from back to front J (lighten), I (lightenLess), normal fill, K (darkenLess), + // H (darken). New feature, old versions did not export these at all. + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "fill", "lighten"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "fill", "lightenLess"); + assertXPathNoAttribute(pXmlDoc, "//a:pathLst/a:path[3]", "fill"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "fill", "darkenLess"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[5]", "fill", "darken"); +} + +CPPUNIT_TEST_FIXTURE(Test, testTdf147978_subpath) +{ + // The odp file contains a non-primitive custom shapes with commands H,I,J,K + OUString aURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "tdf147978_enhancedPath_subpath.pptx"; + + // Export to pptx had dropped the subpaths. + loadAndSave(aURL, "Impress Office Open XML"); + + // Verify the markup: + std::unique_ptr<SvStream> pStream = parseExportStream(getTempFile(), "ppt/slides/slide1.xml"); + xmlDocUniquePtr pXmlDoc = parseXmlStream(pStream.get()); + // File should have four subpaths with increasing path size + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "w", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[1]", "h", "10"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "w", "20"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[2]", "h", "20"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[3]", "w", "40"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[3]", "h", "40"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "w", "80"); + assertXPath(pXmlDoc, "//a:pathLst/a:path[4]", "h", "80"); +} +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx index c457e65ac0c8..708aea6fb29a 100644 --- a/oox/source/export/drawingml.cxx +++ b/oox/source/export/drawingml.cxx @@ -62,6 +62,7 @@ #include <com/sun/star/drawing/ColorMode.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> #include <com/sun/star/drawing/Hatch.hpp> @@ -137,6 +138,7 @@ using namespace ::css::style; using namespace ::css::text; using namespace ::css::uno; using namespace ::css::container; +using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand; using ::css::io::XOutputStream; using ::sax_fastparser::FSHelperPtr; @@ -3658,6 +3660,86 @@ void DrawingML::WritePresetShape( const char* pShape, MSO_SPT eShapeType, bool b mpFS->endElementNS( XML_a, XML_prstGeom ); } +namespace // helpers for DrawingML::WriteCustomGeometry +{ +sal_Int32 +FindNextCommandEndSubpath(const sal_Int32 nStart, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + sal_Int32 i = nStart < 0 ? 0 : nStart; + while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH) + i++; + return i; +} + +bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++) + { + if (rSegments[i].Command == nCommand) + return true; + } + return false; +} + +// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP +// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. +void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, + const double fWR, const double fHR, const double fCx, + const double fCy, const double fRayPx, const double fRayPy) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation + // and get angle + double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); + // use angle for intersection point on circle and stretch back to ellipse + double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); + double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); + // get angle of intersection point on ellipse + double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); + // convert from Math to View orientation and shift ellipse back from origin + rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle); + rfSx = fPointMathEllipse_x + fCx; + rfSy = -fPointMathEllipse_y + fCy; + } +} + +void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR, + const double fCx, const double fCy, const double fViewAngleDeg) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR; + double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR; + double fRadius = 1.0 / std::hypot(fX, fY); + rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg)); + rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg)); + } +} + +sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, + const EnhancedCustomShape2d& rCustomShape2d) +{ + double fValue = 0.0; + rCustomShape2d.GetParameter(fValue, rParam, false, false); + sal_Int32 nValue(std::lround(fValue)); + + return nValue; +} +} + bool DrawingML::WriteCustomGeometry( const Reference< XShape >& rXShape, const SdrObjCustomShape& rSdrObjCustomShape) @@ -3679,349 +3761,574 @@ bool DrawingML::WriteCustomGeometry( return false; } - auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); + if (!pGeometrySeq) + return false; + + auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const PropertyValue& rProp) { return rProp.Name == "Path"; }); + if (pPathProp == std::cend(*pGeometrySeq)) + return false; - if ( pGeometrySeq ) + uno::Sequence<beans::PropertyValue> aPathProp; + pPathProp->Value >>= aPathProp; + + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; + uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; + uno::Sequence<awt::Size> aPathSize; + for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) { - for( const beans::PropertyValue& rProp : *pGeometrySeq ) - { - if ( rProp.Name == "Path" ) - { - uno::Sequence<beans::PropertyValue> aPathProp; - rProp.Value >>= aPathProp; + if (rPathProp.Name == "Coordinates") + rPathProp.Value >>= aPairs; + else if (rPathProp.Name == "Segments") + rPathProp.Value >>= aSegments; + else if (rPathProp.Name == "SubViewSize") + rPathProp.Value >>= aPathSize; + } - uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; - uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; - uno::Sequence<awt::Size> aPathSize; - for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) - { - if (rPathProp.Name == "Coordinates") - rPathProp.Value >>= aPairs; - else if (rPathProp.Name == "Segments") - rPathProp.Value >>= aSegments; - else if (rPathProp.Name == "SubViewSize") - rPathProp.Value >>= aPathSize; - } + if ( !aPairs.hasElements() ) + return false; - if ( !aPairs.hasElements() ) - return false; + if ( !aSegments.hasElements() ) + { + aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> + { + { MOVETO, 1 }, + { LINETO, + static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, + { CLOSESUBPATH, 0 }, + { ENDSUBPATH, 0 } + }; + }; - if ( !aSegments.hasElements() ) - { - aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> - { - { drawing::EnhancedCustomShapeSegmentCommand::MOVETO, 1 }, - { drawing::EnhancedCustomShapeSegmentCommand::LINETO, - static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, - { drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH, 0 }, - { drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH, 0 } - }; - }; + int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, + [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); - int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, - [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); + if ( nExpectedPairCount > aPairs.getLength() ) + { + SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); + return false; + } - if ( nExpectedPairCount > aPairs.getLength() ) - { - SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); - return false; - } + // A EnhancedCustomShape2d caches the equation results. Therefor we use only one of it for the + // entire method. + const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); - mpFS->startElementNS(XML_a, XML_custGeom); - mpFS->singleElementNS(XML_a, XML_avLst); - mpFS->singleElementNS(XML_a, XML_gdLst); - mpFS->singleElementNS(XML_a, XML_ahLst); - mpFS->singleElementNS(XML_a, XML_rect, XML_l, "l", XML_t, "t", - XML_r, "r", XML_b, "b"); - mpFS->startElementNS(XML_a, XML_pathLst); + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + // ToDO: use draw:TextAreas for <a:rect> + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "l", XML_t, "t", XML_r, "r", XML_b, "b"); + mpFS->startElementNS(XML_a, XML_pathLst); - std::optional<OString> sFill; - if (HasEnhancedCustomShapeSegmentCommand(rXShape, css::drawing::EnhancedCustomShapeSegmentCommand::NOFILL)) - sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard + // Prepare width and height for <a:path> + bool bUseGlobalViewBox(false); + + // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not + // triggered; same for height. + sal_Int32 nViewBoxWidth(0); + sal_Int32 nViewBoxHeight(0); + if (!aPathSize.hasElements()) + { + bUseGlobalViewBox = true; + // If draw:viewBox is missing in draw:enhancedGeometry, then import sets + // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated + // current file via macro. Author of macro has to fix it. + auto pProp = std::find_if( + std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); + if (pProp != std::cend(*pGeometrySeq)) + { + css::awt::Rectangle aViewBox; + if (pProp->Value >>= aViewBox) + { + nViewBoxWidth = aViewBox.Width; + nViewBoxHeight = aViewBox.Height; + css::drawing::EnhancedCustomShapeParameter aECSP; + aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; + aECSP.Value <<= nViewBoxWidth; + double fRetValue; + aCustomShape2d.GetParameter(fRetValue, aECSP, true, false); + nViewBoxWidth = basegfx::fround(fRetValue); + aECSP.Value <<= nViewBoxHeight; + aCustomShape2d.GetParameter(fRetValue, aECSP, false, true); + nViewBoxHeight = basegfx::fround(fRetValue); + } + } + // Import from oox or documents, which are imported from oox and saved to strict ODF, might + // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those + // cases. Even if that is fixed, we need the substitute for old documents. + if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq)) + { + // Generate a substitute based on point coordinates + sal_Int32 nXMin(0); + aPairs[0].First.Value >>= nXMin; + sal_Int32 nXMax = nXMin; + sal_Int32 nYMin(0); + aPairs[0].Second.Value >>= nYMin; + sal_Int32 nYMax = nYMin; - if ( aPathSize.hasElements() ) - { - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(aPathSize[0].Width), - XML_h, OString::number(aPathSize[0].Height) ); - } - else + for (const auto& rPair : std::as_const(aPairs)) + { + sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d); + if (nX < nXMin) + nXMin = nX; + if (nY < nYMin) + nYMin = nY; + if (nX > nXMax) + nXMax = nX; + if (nY > nYMax) + nYMax = nY; + } + nViewBoxWidth = std::max(nXMax, nXMax - nXMin); + nViewBoxHeight = std::max(nYMax, nYMax - nYMin); + } + // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a + // shift of the resulting path coordinates. + } + + // Iterate over subpaths + sal_Int32 nPairIndex = 0; // index over "Coordinates" + sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize" + sal_Int32 nSubpathStartIndex(0); // index over "Segments" + do + { + bool bOK(true); // catch faulty paths were commands do not correspond to points + // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment + sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments); + + // Prepare attributes for a:path start element + // NOFILL or one of the LIGHTEN commands + std::optional<OString> sFill; + if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "none"; + else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "darken"; + else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "darkenLess"; + else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lighten"; + else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lightenLess"; + // NOSTROKE + std::optional<OString> sStroke; + if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sStroke = "0"; + + // Write a:path start element + mpFS->startElementNS( + XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w, + OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width), + XML_h, + OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height)); + + // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position + // of the target point in regard to the current point. Therefore we need to track the + // current point. A current point is not defined in the beginning. + double fCurrentX(0.0); + double fCurrentY(0.0); + bool bCurrentValid(false); + // Actually write the subpath + for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex; + ++nSegmentIndex) + { + const auto& rSegment(aSegments[nSegmentIndex]); + if (rSegment.Command == CLOSESUBPATH) + { + mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter + // ODF 1.4 specifies, that the start of the subpath becomes the current point. + // But that is not implemented yet. Currently LO keeps the last current point. + } + for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) + { + switch (rSegment.Command) { - sal_Int32 nXMin(0); - aPairs[0].First.Value >>= nXMin; - sal_Int32 nXMax = nXMin; - sal_Int32 nYMin(0); - aPairs[0].Second.Value >>= nYMin; - sal_Int32 nYMax = nYMin; - - for ( const auto& rPair : std::as_const(aPairs) ) + case MOVETO: { - sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, rSdrObjCustomShape); - sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, rSdrObjCustomShape); - if (nX < nXMin) - nXMin = nX; - if (nY < nYMin) - nYMin = nY; - if (nX > nXMax) - nXMax = nX; - if (nY > nYMax) - nYMax = nY; + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_moveTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex].Second, false, + false); + bCurrentValid = true; + nPairIndex++; + } + break; } - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(nXMax - nXMin), - XML_h, OString::number(nYMax - nYMin) ); - } - - - int nPairIndex = 0; - bool bOK = true; - for (const auto& rSegment : std::as_const(aSegments)) - { - if ( rSegment.Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH ) + case LINETO: { - mpFS->singleElementNS(XML_a, XML_close); + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_lnTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_lnTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex].Second, false, + false); + bCurrentValid = true; + nPairIndex++; + } + break; } - for (int k = 0; k < rSegment.Count && bOK; ++k) + case CURVETO: { - switch( rSegment.Command ) + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else { - case drawing::EnhancedCustomShapeSegmentCommand::MOVETO : + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for (sal_uInt8 l = 0; l <= 2; ++l) { - if (nPairIndex >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_moveTo); - WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); - mpFS->endElementNS( XML_a, XML_moveTo ); - nPairIndex++; - } - break; + WriteCustomGeometryPoint(aPairs[nPairIndex + l], aCustomShape2d); } - case drawing::EnhancedCustomShapeSegmentCommand::LINETO : + mpFS->endElementNS(XML_a, XML_cubicBezTo); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex + 2].First, + false, false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex + 2].Second, + false, false); + bCurrentValid = true; + nPairIndex += 3; + } + break; + } + case ANGLEELLIPSETO: + case ANGLEELLIPSE: + { + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else + { + // Read parameters + double fCx = 0.0; + aCustomShape2d.GetParameter(fCx, aPairs[nPairIndex].First, false, + false); + double fCy = 0.0; + aCustomShape2d.GetParameter(fCy, aPairs[nPairIndex].Second, false, + false); + double fWR = 0.0; + aCustomShape2d.GetParameter(fWR, aPairs[nPairIndex + 1].First, false, + false); + double fHR = 0.0; + aCustomShape2d.GetParameter(fHR, aPairs[nPairIndex + 1].Second, false, + false); + double fStartAngle = 0.0; + aCustomShape2d.GetParameter(fStartAngle, aPairs[nPairIndex + 2].First, + false, false); + double fEndAngle = 0.0; + aCustomShape2d.GetParameter(fEndAngle, aPairs[nPairIndex + 2].Second, + false, false); + + // Prepare start and swing angle + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng = 0; + if (basegfx::fTools::equalZero(fStartAngle) + && basegfx::fTools::equalZero(fEndAngle - 360.0)) + nSwingAng = 360 * 60000; // special case full circle + else { - if (nPairIndex >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_lnTo); - WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); - mpFS->endElementNS( XML_a, XML_lnTo ); - nPairIndex++; - } - break; + nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000); + if (nSwingAng < 0) + nSwingAng += 360 * 60000; } - case drawing::EnhancedCustomShapeSegmentCommand::CURVETO : + + // calculate start point on ellipse + double fSx = 0.0; + double fSy = 0.0; + getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle); + + // write markup for going to start point + if (rSegment.Command == ANGLEELLIPSETO) { - if (nPairIndex + 2 >= aPairs.getLength()) - bOK = false; - else - { - mpFS->startElementNS(XML_a, XML_cubicBezTo); - for( sal_uInt8 l = 0; l <= 2; ++l ) - { - WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); - } - mpFS->endElementNS( XML_a, XML_cubicBezTo ); - nPairIndex += 3; - } - break; + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fSx)), XML_y, + OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_lnTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO : - case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE : + else { - nPairIndex += 3; - break; + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fSx)), XML_y, + OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_moveTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ARCTO : - case drawing::EnhancedCustomShapeSegmentCommand::ARC : - case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO : - case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC : + // write markup for arcTo + if (!basegfx::fTools::equalZero(fWR) + && !basegfx::fTools::equalZero(fHR)) + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, + OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); + + getEllipsePointFromViewAngle(fCurrentX, fCurrentY, fWR, fHR, fCx, fCy, + fEndAngle); + bCurrentValid = true; + nPairIndex += 3; + } + break; + } + case ARCTO: + case ARC: + case CLOCKWISEARCTO: + case CLOCKWISEARC: + { + if (nPairIndex + 3 >= aPairs.getLength()) + bOK = false; + else + { + // read parameters + double fX1 = 0.0; + aCustomShape2d.GetParameter(fX1, aPairs[nPairIndex].First, false, + false); + double fY1 = 0.0; + aCustomShape2d.GetParameter(fY1, aPairs[nPairIndex].Second, false, + false); + double fX2 = 0.0; + aCustomShape2d.GetParameter(fX2, aPairs[nPairIndex + 1].First, false, + false); + double fY2 = 0.0; + aCustomShape2d.GetParameter(fY2, aPairs[nPairIndex + 1].Second, false, + false); + double fX3 = 0.0; + aCustomShape2d.GetParameter(fX3, aPairs[nPairIndex + 2].First, false, + false); + double fY3 = 0.0; + aCustomShape2d.GetParameter(fY3, aPairs[nPairIndex + 2].Second, false, + false); + double fX4 = 0.0; + aCustomShape2d.GetParameter(fX4, aPairs[nPairIndex + 3].First, false, + false); + double fY4 = 0.0; + aCustomShape2d.GetParameter(fY4, aPairs[nPairIndex + 3].Second, false, + false); + // calculate ellipse parameter + const double fWR = (fX2 - fX1) / 2.0; + const double fHR = (fY2 - fY1) / 2.0; + const double fCx = (fX1 + fX2) / 2.0; + const double fCy = (fY1 + fY2) / 2.0; + // calculate start angle + double fStartAngle = 0.0; + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, + fCx, fCy, fX3, fY3); + // markup for going to start point + if (rSegment.Command == ARCTO || rSegment.Command == CLOCKWISEARCTO) { - nPairIndex += 4; - break; + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fPx)), XML_y, + OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_lnTo); } - case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX : - case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY : + else { - nPairIndex++; - break; + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(std::lround(fPx)), XML_y, + OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_moveTo); } - case drawing::EnhancedCustomShapeSegmentCommand::QUADRATICCURVETO : + // calculate swing angle + double fEndAngle = 0.0; + getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, + fCy, fX4, fY4); + double fSwingAngle(fEndAngle - fStartAngle); + const bool bIsClockwise(rSegment.Command == CLOCKWISEARCTO + || rSegment.Command == CLOCKWISEARC); + if (bIsClockwise && fSwingAngle < 0) + fSwingAngle += 360.0; + else if (!bIsClockwise && fSwingAngle > 0) + fSwingAngle -= 360.0; + // markup for arcTo + // ToDo: write markup for case zero width or height of ellipse + const sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement( + FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), + XML_hR, OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, OString::number(nSwingAng)); + fCurrentX = fPx; + fCurrentY = fPy; + bCurrentValid = true; + nPairIndex += 4; + } + break; + } + case ELLIPTICALQUADRANTX: + case ELLIPTICALQUADRANTY: + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + // read parameters + double fX = 0.0; + aCustomShape2d.GetParameter(fX, aPairs[nPairIndex].First, false, false); + double fY = 0.0; + aCustomShape2d.GetParameter(fY, aPairs[nPairIndex].Second, false, + false); + + // Prepare parameters for arcTo + if (bCurrentValid) { - if (nPairIndex + 1 >= aPairs.getLength()) - bOK = false; - else + double fWR = std::abs(fCurrentX - fX); + double fHR = std::abs(fCurrentY - fY); + double fStartAngle(0.0); + double fSwingAngle(0.0); + // The starting direction of the arc toggles beween X and Y + if ((rSegment.Command == ELLIPTICALQUADRANTX && !(k % 2)) + || (rSegment.Command == ELLIPTICALQUADRANTY && (k % 2))) { - mpFS->startElementNS(XML_a, XML_quadBezTo); - for( sal_uInt8 l = 0; l < 2; ++l ) - { - WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); - } - mpFS->endElementNS( XML_a, XML_quadBezTo ); - nPairIndex += 2; + // arc starts horizontal + fStartAngle = fY < fCurrentY ? 90.0 : 270.0; + const bool bClockwise = (fX < fCurrentX && fY < fCurrentY) + || (fX > fCurrentX && fY > fCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; } - break; - } - case drawing::EnhancedCustomShapeSegmentCommand::ARCANGLETO : - { - if (nPairIndex + 1 >= aPairs.getLength()) - bOK = false; else { - const EnhancedCustomShape2d aCustoShape2d( - const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); - double fWR = 0.0; - aCustoShape2d.GetParameter(fWR, aPairs[nPairIndex].First, false, - false); - double fHR = 0.0; - aCustoShape2d.GetParameter(fHR, aPairs[nPairIndex].Second, - false, false); - double fStartAngle = 0.0; - aCustoShape2d.GetParameter( - fStartAngle, aPairs[nPairIndex + 1].First, false, false); - sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); - double fSwingAng = 0.0; - aCustoShape2d.GetParameter( - fSwingAng, aPairs[nPairIndex + 1].Second, false, false); - sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); - mpFS->singleElement(FSNS(XML_a, XML_arcTo), - XML_wR, OString::number(fWR), - XML_hR, OString::number(fHR), - XML_stAng, OString::number(nStartAng), - XML_swAng, OString::number(nSwingAng)); - nPairIndex += 2; + // arc starts vertical + fStartAngle = fX < fCurrentX ? 0.0 : 180.0; + const bool bClockwise = (fX < fCurrentX && fY > fCurrentY) + || (fX > fCurrentX && fY < fCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; } - break; + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, + OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); } - default: - // do nothing - break; + else + { + // faulty path, but we continue with the target point + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], aCustomShape2d); + mpFS->endElementNS(XML_a, XML_moveTo); + } + fCurrentX = fX; + fCurrentY = fY; + bCurrentValid = true; + nPairIndex++; } + break; + } + case QUADRATICCURVETO: + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_quadBezTo); + for( sal_uInt8 l = 0; l < 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex + l], aCustomShape2d); + } + mpFS->endElementNS( XML_a, XML_quadBezTo ); + aCustomShape2d.GetParameter(fCurrentX, aPairs[nPairIndex + 1].First, false, + false); + aCustomShape2d.GetParameter(fCurrentY, aPairs[nPairIndex + 1].Second, false, + false); + bCurrentValid = true; + nPairIndex += 2; + } + break; } - if (!bOK) + case ARCANGLETO: + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + double fWR = 0.0; + aCustomShape2d.GetParameter(fWR, aPairs[nPairIndex].First, false, false); + double fHR = 0.0; + aCustomShape2d.GetParameter(fHR, aPairs[nPairIndex].Second, false, false); + double fStartAngle = 0.0; + aCustomShape2d.GetParameter(fStartAngle, aPairs[nPairIndex + 1].First, + false, false); + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + double fSwingAng = 0.0; + aCustomShape2d.GetParameter(fSwingAng, aPairs[nPairIndex + 1].Second, + false, false); + sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), + XML_hR, OString::number(fHR), XML_stAng, + OString::number(nStartAng), XML_swAng, + OString::number(nSwingAng)); + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle); + double fCx = fCurrentX - fPx; + double fCy = fCurrentY - fPy; + getEllipsePointFromViewAngle(fCurrentX, fCurrentY, fWR, fHR, fCx, fCy, + fStartAngle + fSwingAng); + bCurrentValid = true; + nPairIndex += 2; + } + break; + } + default: + // do nothing break; } - mpFS->endElementNS( XML_a, XML_path ); - mpFS->endElementNS( XML_a, XML_pathLst ); - mpFS->endElementNS( XML_a, XML_custGeom ); - return bOK; - } - } - } - return false; + } // end loop for commands which are repeated + } // end loop over all commands of subpath + // finish this subpath in any case + mpFS->endElementNS(XML_a, XML_path); + + if (!bOK) + break; // exit loop if not enough values in aPairs + + // step forward to next subpath + nSubpathStartIndex = nNextNcommandIndex + 1; + nPathSizeIndex++; + } while (nSubpathStartIndex < aSegments.getLength()); + + mpFS->endElementNS(XML_a, XML_pathLst); + mpFS->endElementNS(XML_a, XML_custGeom); + return true; // We have written custGeom even if path is poorly structured. } void DrawingML::WriteCustomGeometryPoint( const drawing::EnhancedCustomShapeParameterPair& rParamPair, - const SdrObjCustomShape& rSdrObjCustomShape) + const EnhancedCustomShape2d& rCustomShape2d) { - sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rSdrObjCustomShape); - sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rSdrObjCustomShape); + sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d); + sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d); mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); } -sal_Int32 DrawingML::GetCustomGeometryPointValue( - const css::drawing::EnhancedCustomShapeParameter& rParam, - const SdrObjCustomShape& rSdrObjCustomShape) +void DrawingML::WriteEmptyCustomGeometry() { - const EnhancedCustomShape2d aCustoShape2d(const_cast< SdrObjCustomShape& >(rSdrObjCustomShape)); - double fValue = 0.0; - aCustoShape2d.GetParameter(fValue, rParam, false, false); - sal_Int32 nValue(std::lround(fValue)); - - return nValue; -} - -// version for SdrObjCustomShape -void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, - const tools::PolyPolygon& rPolyPolygon, const bool bClosed) -{ - // In case of Writer, the parent element is <wps:spPr>, and there the - // <a:custGeom> element is not optional. - if (rPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) - return; - + // This method is used for export to docx in case WriteCustomGeometry fails. mpFS->startElementNS(XML_a, XML_custGeom); mpFS->singleElementNS(XML_a, XML_avLst); mpFS->singleElementNS(XML_a, XML_gdLst); mpFS->singleElementNS(XML_a, XML_ahLst); mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); - - mpFS->startElementNS( XML_a, XML_pathLst ); - - const tools::Rectangle aRect( rPolyPolygon.GetBoundRect() ); - - // tdf#101122 - std::optional<OString> sFill; - if (HasEnhancedCustomShapeSegmentCommand(rXShape, css::drawing::EnhancedCustomShapeSegmentCommand::NOFILL)) - sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard - - // Put all polygons of rPolyPolygon in the same path element - // to subtract the overlapped areas. - mpFS->startElementNS( XML_a, XML_path, - XML_fill, sFill, - XML_w, OString::number(aRect.GetWidth()), - XML_h, OString::number(aRect.GetHeight()) ); - - for( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i ++ ) - { - - const tools::Polygon& rPoly = rPolyPolygon[ i ]; - - if( rPoly.GetSize() > 0 ) - { - mpFS->startElementNS(XML_a, XML_moveTo); - - mpFS->singleElementNS( XML_a, XML_pt, - XML_x, OString::number(rPoly[0].X() - aRect.Left()), ... etc. - the rest is truncated