From f367b54ef615ec72a84a9a62b00746f4ce16be126ddd5fea36b5b94e7edb8198 Mon Sep 17 00:00:00 2001 From: Fridrich Strba Date: Tue, 15 Jun 2021 13:18:53 +0000 Subject: [PATCH] Accepting request 900126 from LibreOffice:7.1 - Fix bsc#1182969: LO-L3: PPTX: one column becomes two within one text frame (two occurrences) * bsc1182969.patch OBS-URL: https://build.opensuse.org/request/show/900126 OBS-URL: https://build.opensuse.org/package/show/LibreOffice:Factory/libreoffice?expand=0&rev=969 --- bsc1182969.patch | 6228 +++++++++++++++++++++++++++++++++++++++++++ libreoffice.changes | 6 + libreoffice.spec | 3 + 3 files changed, 6237 insertions(+) create mode 100644 bsc1182969.patch diff --git a/bsc1182969.patch b/bsc1182969.patch new file mode 100644 index 0000000..19e500c --- /dev/null +++ b/bsc1182969.patch @@ -0,0 +1,6228 @@ +From 0fa895203fc42b506ea0c5e230821cf8f4804c29 Mon Sep 17 00:00:00 2001 +From: Mike Kaganski +Date: Sun, 6 Jun 2021 21:50:53 +0300 +Subject: [PATCH] Fix and unify the two methods that get scaled text size + +GetTextFitToSizeScale and SdrTextObj::GetFontScaleY both didn't +initialize outliners properly, and thus returned wrong results. + +Change-Id: I6fe63c51ed838a0d0fafdfa03597cac97ce29831 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116765 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +(cherry picked from commit a1ae30166e92a0a40dff06740f0bb8e9ee63f70a) +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116704 +Tested-by: Jenkins CollaboraOffice + +Unify some code managing coordinates depending on text direction + +Change-Id: I12163e83a6a4d4e7cb85eed690b787c47ee2e7e5 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115107 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116861 +Tested-by: Mike Kaganski + +UITest: introduce guarded context managers + +They should be used wherever we need to make sure to execute +some action in tests regardless of the assertion success. They +follow their plain counterparts, and execute finalizing code +at all outcomes. This e.g. allows to assert when a dialog is +open, and have it properly closed on failure, so that the test +is not left in hung state. + +Only two wrappers are implemented now: load_file and +execute_dialog_through_action. + +sc/qa/uitest/chart/chartLegend.py is updated to use them. + +Change-Id: Ib9cf44304f0d3ab8fa3377922ed36d2d866031b0 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116692 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116867 +Tested-by: Jenkins CollaboraOffice + +editengine-columns: Create document model and dialog page + +Change-Id: I056aad9474ca18134d1f1686a53618cc9ab3d525 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116038 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116868 +Tested-by: Jenkins CollaboraOffice +Reviewed-by: Miklos Vajna + +editengine-columns: ODF support [API CHANGE] + +This uses existing ODF markup, as used by Writer's text frame: +style::columns child element of style:graphic-properties, its +fo:column-count and fo:column-gap attributes. No ODF extension +is required. + +Since currently only columns with same width and spacing are +implemented, without additional settings, style:column child +elements are exported, but ignored on import. + +This adds new property to css::drawing::TextProperties service: +TextColumns (of type css::text::XTextColumns). + +Change-Id: I7e63293e5814b281ceec8a9632e696322d3629e8 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116035 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116871 +Tested-by: Jenkins CollaboraOffice +Reviewed-by: Miklos Vajna + +editengine-columns: Implement layout + +This changes the way how different parts access positions of lines and +paragraphs. Now there is ImpEditEngine::IterateLineAreas, which performs +uniform iteration over all ParaPortions and lines in order, calling a +user-provided callback function for each portion and line; it passes +all information about current portion, line, area, and column to the +callback, and checks the return from the callback, to decide if it needs +to continue iteration (in case when callback indicated that if doesn't +need further data), and if it needs calling the callback for the rest of +current portion's lines. + +This allows to have the code that calculates and iterates dimensions of +lines in one central place, without the need to have duplicating logic +in several places. + +One important exception is ImpEditEngine::Paint, which iterates without +ImpEditEngine::IterateLineAreas, because it does many atomic paint +operations in different points of iteration process, and implementing +ImpEditEngine::IterateLineAreas to call callback in the required places +would require increased complexity, which is left for a future change. +To make that possible, ImpEditEngine::IterFlag should be extended to +indicate additional requirements. + +Note that in fact, ImpEditEngine::Paint was taken as the model for +implementation of ImpEditEngine::IterateLineAreas, with its detailed +handling of all the vertical offsets like additional line spacing and +interparagraph spacings that depend on context. + +The notable result of the centralization of the iteration code is slight +change of heights reported by ImpEditEngine::CalcTextHeight. Previously +it simply added all pre-calculated heights of portions, and not taking +into account all the spacing handling that ImpEditEngine::Paint did, +which was inconsistent (calculated height was different from painted +height). Now ImpEditEngine::CalcTextHeight should provide more accurate +results, which required small changes in the unit tests. + +Change-Id: I33cbb978deb974b314d36fda8674186a03991107 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116034 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116876 +Tested-by: Jenkins CollaboraOffice +Reviewed-by: Miklos Vajna + +editengine-columns: PPTX support (tdf#118458) + +The unit tests that used to check the workaround using tables to +emulate columns (implemented in tdf#120028) are changed to test +import of the columns. + +This reverts some commits related to the mentioned workaround, +namely aef569ed83a3ccc02639e5b2a1c7cc131ba262fc, + c50ae6a282ed83762bf634fed5c91033eb305c88, + 7b64bd90637a6722438bf873b1ded74ab3424c46, + 33696b2820ce3c8b21b753d2c2bf92345ecb9276, + 99dff69b561a8fe2d9437e6aa67a9581a6666f41. + +Change-Id: I97693ad4a981780e822070938992f274920df5a8 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116738 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116881 +Tested-by: Jenkins CollaboraOffice +Reviewed-by: Miklos Vajna + +This API change is since 7-2. + +Change-Id: I5ca672a9c67f9a7082ef72c63b388c953d77c22c +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116843 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +(cherry picked from commit 25bd0c7ddf0b45c8628f0c7b1b57d3eb428bf1e5) +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116710 +Tested-by: Jenkins CollaboraOffice + +svxcore: provide UNO constructor for com.sun.star.text.TextColumns + +This allows to create it e.g. in Basic macros using CreateUnoService + +Change-Id: I949d3b92c83cd9e763244f70b22f0f367b93cb48 +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116970 +Tested-by: Jenkins +Reviewed-by: Mike Kaganski +(cherry picked from commit 01379acedcdad4fc08c61b73b8500e758b88d5ae) +Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116903 +Tested-by: Jenkins CollaboraOffice +--- + .../xshape/data/reference/tdf90839-1.xml | 74 +-- + .../xshape/data/reference/tdf90839-2.xml | 64 +- + .../xshape/data/reference/tdf90839-3.xml | 64 +- + .../xshape/data/reference/tdf90839-4.xml | 64 +- + cui/Library_cui.mk | 1 + + cui/UIConfig_cui.mk | 1 + + cui/source/factory/dlgfact.cxx | 5 + + cui/source/inc/TextColumnsPage.hxx | 40 ++ + cui/source/tabpages/TextColumnsPage.cxx | 82 +++ + cui/source/tabpages/textanim.cxx | 2 + + cui/uiconfig/ui/textcolumnstabpage.ui | 111 ++++ + cui/uiconfig/ui/textdialog.ui | 48 ++ + editeng/source/editeng/editeng.cxx | 31 +- + editeng/source/editeng/impedit.cxx | 131 ++-- + editeng/source/editeng/impedit.hxx | 72 ++- + editeng/source/editeng/impedit2.cxx | 573 +++++++++++------- + editeng/source/editeng/impedit3.cxx | 516 ++++++++-------- + editeng/source/editeng/impedit4.cxx | 4 +- + editeng/source/outliner/outlin2.cxx | 10 + + include/editeng/editeng.hxx | 4 + + include/editeng/outliner.hxx | 4 + + include/oox/ppt/pptimport.hxx | 2 - + include/oox/ppt/pptshape.hxx | 3 +- + include/svl/solar.hrc | 2 +- + include/svx/SvxXTextColumns.hxx | 22 + + include/svx/dialogs.hrc | 1 + + include/svx/strings.hrc | 2 + + include/svx/svddef.hxx | 8 +- + include/svx/svdotext.hxx | 9 +- + include/svx/unoshprp.hxx | 6 +- + .../com/sun/star/drawing/TextProperties.idl | 8 + + oox/inc/drawingml/table/tableproperties.hxx | 3 - + oox/inc/drawingml/textbodyproperties.hxx | 2 - + .../drawingml/table/tableproperties.cxx | 61 +- + .../drawingml/textbodypropertiescontext.cxx | 14 +- + oox/source/export/drawingml.cxx | 24 +- + oox/source/ppt/pptimport.cxx | 1 - + oox/source/ppt/pptshape.cxx | 33 +- + oox/source/ppt/slidepersist.cxx | 33 +- + oox/source/token/properties.txt | 1 + + sc/qa/uitest/chart/chartLegend.py | 183 +++--- + sc/qa/unit/filters-test.cxx | 2 +- + sd/qa/uitest/impress_tests/tdf91762.py | 8 +- + .../uitest/impress_tests/textColumnsDialog.py | 53 ++ + sd/qa/unit/data/pptx/tdf120028b.pptx | Bin 29838 -> 0 bytes + sd/qa/unit/data/xml/n593612_0.xml | 4 +- + sd/qa/unit/data/xml/n758621_1.xml | 4 +- + sd/qa/unit/export-tests-ooxml2.cxx | 125 +++- + sd/qa/unit/export-tests.cxx | 69 +++ + sd/qa/unit/import-tests.cxx | 96 +-- + sd/qa/unit/layout-tests.cxx | 107 +++- + sd/qa/unit/misc-tests.cxx | 49 ++ + sd/qa/unit/tiledrendering/LOKitSearchTest.cxx | 4 +- + sd/source/core/stlsheet.cxx | 13 + + svx/Library_svxcore.mk | 1 + + .../sdr/properties/attributeproperties.cxx | 3 +- + .../sdr/properties/captionproperties.cxx | 1 + + .../sdr/properties/circleproperties.cxx | 1 + + .../sdr/properties/connectorproperties.cxx | 1 + + .../sdr/properties/customshapeproperties.cxx | 2 +- + .../sdr/properties/graphicproperties.cxx | 2 +- + .../sdr/properties/measureproperties.cxx | 1 + + svx/source/sdr/properties/textproperties.cxx | 7 + + svx/source/svdraw/svdattr.cxx | 9 + + svx/source/svdraw/svdotext.cxx | 97 ++- + svx/source/svdraw/svdotextdecomposition.cxx | 4 + + svx/source/svdraw/svdotxed.cxx | 1 + + svx/source/svdraw/svdoutl.cxx | 1 + + svx/source/unodraw/SvxXTextColumns.cxx | 334 ++++++++++ + svx/source/unodraw/unomod.cxx | 5 + + svx/source/unodraw/unopool.cxx | 7 + + svx/source/unodraw/unoshape.cxx | 48 +- + svx/util/svxcore.component | 4 + + sw/inc/unomap.hxx | 11 - + sw/inc/unosett.hxx | 65 -- + sw/source/core/layout/atrfrm.cxx | 116 +++- + sw/source/core/unocore/unocoll.cxx | 3 +- + sw/source/core/unocore/unomap.cxx | 17 - + sw/source/core/unocore/unomap1.cxx | 6 - + sw/source/core/unocore/unosett.cxx | 291 --------- + .../sheet/xsheetannotationshapesupplier.cxx | 2 +- + uitest/uitest/uihelper/guarded.py | 51 ++ + .../draw/XMLShapePropertySetContext.cxx | 3 + + xmloff/source/draw/sdpropls.cxx | 6 + + xmloff/source/text/XMLTextColumnsExport.cxx | 2 + + xmloff/source/text/txtprhdl.cxx | 3 + + 89 files changed, 2462 insertions(+), 1496 deletions(-) + create mode 100644 cui/source/inc/TextColumnsPage.hxx + create mode 100644 cui/source/tabpages/TextColumnsPage.cxx + create mode 100644 cui/uiconfig/ui/textcolumnstabpage.ui + create mode 100644 include/svx/SvxXTextColumns.hxx + create mode 100644 sd/qa/uitest/impress_tests/textColumnsDialog.py + create mode 100644 svx/source/unodraw/SvxXTextColumns.cxx + create mode 100644 uitest/uitest/uihelper/guarded.py + +diff --git a/chart2/qa/extras/xshape/data/reference/tdf90839-1.xml b/chart2/qa/extras/xshape/data/reference/tdf90839-1.xml +index 2af62c344940..3586969602c1 100644 +--- a/chart2/qa/extras/xshape/data/reference/tdf90839-1.xml ++++ b/chart2/qa/extras/xshape/data/reference/tdf90839-1.xml +@@ -175,15 +175,15 @@ + + + +- ++ + +- ++ + +- ++ + +- ++ + +- ++ + + + +@@ -192,21 +192,21 @@ + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + +- ++ + +- ++ + + + +@@ -215,21 +215,21 @@ + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + +- ++ + +- ++ + + + +@@ -238,21 +238,21 @@ + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + +- ++ + +- ++ + + + +@@ -262,20 +262,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -284,36 +284,36 @@ + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + + + +- +- ++ ++ + + + +diff --git a/chart2/qa/extras/xshape/data/reference/tdf90839-2.xml b/chart2/qa/extras/xshape/data/reference/tdf90839-2.xml +index c8afb487a4ae..0c1c1eba61b1 100644 +--- a/chart2/qa/extras/xshape/data/reference/tdf90839-2.xml ++++ b/chart2/qa/extras/xshape/data/reference/tdf90839-2.xml +@@ -14,7 +14,7 @@ + + + +- ++ + + + +@@ -30,7 +30,7 @@ + + + +- ++ + + + +@@ -40,11 +40,11 @@ + + + +- ++ + + + +- ++ + + + +@@ -175,15 +175,15 @@ + + + +- ++ + +- ++ + +- ++ + +- ++ + +- ++ + + + +@@ -193,20 +193,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -216,20 +216,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -239,20 +239,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -262,20 +262,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -285,49 +285,49 @@ + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + +diff --git a/chart2/qa/extras/xshape/data/reference/tdf90839-3.xml b/chart2/qa/extras/xshape/data/reference/tdf90839-3.xml +index 7b67bd226da6..390d3e1615b8 100644 +--- a/chart2/qa/extras/xshape/data/reference/tdf90839-3.xml ++++ b/chart2/qa/extras/xshape/data/reference/tdf90839-3.xml +@@ -14,7 +14,7 @@ + + + +- ++ + + + +@@ -30,7 +30,7 @@ + + + +- ++ + + + +@@ -40,11 +40,11 @@ + + + +- ++ + + + +- ++ + + + +@@ -175,15 +175,15 @@ + + + +- ++ + +- ++ + +- ++ + +- ++ + +- ++ + + + +@@ -193,20 +193,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -216,20 +216,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -239,20 +239,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -262,20 +262,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -285,49 +285,49 @@ + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + +diff --git a/chart2/qa/extras/xshape/data/reference/tdf90839-4.xml b/chart2/qa/extras/xshape/data/reference/tdf90839-4.xml +index 7fabc697c751..2b107e9f81cc 100644 +--- a/chart2/qa/extras/xshape/data/reference/tdf90839-4.xml ++++ b/chart2/qa/extras/xshape/data/reference/tdf90839-4.xml +@@ -14,7 +14,7 @@ + + + +- ++ + + + +@@ -30,7 +30,7 @@ + + + +- ++ + + + +@@ -40,11 +40,11 @@ + + + +- ++ + + + +- ++ + + + +@@ -175,15 +175,15 @@ + + + +- ++ + +- ++ + +- ++ + +- ++ + +- ++ + + + +@@ -193,20 +193,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -216,20 +216,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -239,20 +239,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -262,20 +262,20 @@ + + + +- ++ + + + + + + +- ++ + + + +- ++ + +- ++ + + + +@@ -285,49 +285,49 @@ + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + + + + +- ++ + + + +diff --git a/cui/Library_cui.mk b/cui/Library_cui.mk +index ff221a9cc55a..e702d6ae94ff 100644 +--- a/cui/Library_cui.mk ++++ b/cui/Library_cui.mk +@@ -212,6 +212,7 @@ $(eval $(call gb_Library_add_exception_objects,cui,\ + cui/source/tabpages/tabstpge \ + cui/source/tabpages/textanim \ + cui/source/tabpages/textattr \ ++ cui/source/tabpages/TextColumnsPage \ + cui/source/tabpages/tparea \ + cui/source/tabpages/tpbitmap \ + cui/source/tabpages/tpcolor \ +diff --git a/cui/UIConfig_cui.mk b/cui/UIConfig_cui.mk +index 153d6fe98fda..58fd66525310 100644 +--- a/cui/UIConfig_cui.mk ++++ b/cui/UIConfig_cui.mk +@@ -200,6 +200,7 @@ $(eval $(call gb_UIConfig_add_uifiles,cui,\ + cui/uiconfig/ui/swpossizepage \ + cui/uiconfig/ui/textattrtabpage \ + cui/uiconfig/ui/textanimtabpage \ ++ cui/uiconfig/ui/textcolumnstabpage \ + cui/uiconfig/ui/textdialog \ + cui/uiconfig/ui/textflowpage \ + cui/uiconfig/ui/thesaurus \ +diff --git a/cui/source/factory/dlgfact.cxx b/cui/source/factory/dlgfact.cxx +index e43b027602c4..3ed88fbe4995 100644 +--- a/cui/source/factory/dlgfact.cxx ++++ b/cui/source/factory/dlgfact.cxx +@@ -90,6 +90,7 @@ + #include + #include + #include ++#include + + using namespace ::com::sun::star; + using namespace ::com::sun::star::frame; +@@ -1483,6 +1484,8 @@ CreateTabPage AbstractDialogFactory_Impl::GetTabPageCreatorFunc( sal_uInt16 nId + return SvxGrfCropPage::Create; + case RID_SVXPAGE_MACROASSIGN : + return SfxMacroTabPage::Create; ++ case RID_SVXPAGE_TEXTCOLUMNS: ++ return SvxTextColumnsPage::Create; + default: + break; + } +@@ -1546,6 +1549,8 @@ GetTabPageRanges AbstractDialogFactory_Impl::GetTabPageRangesFunc( sal_uInt16 nI + return SvxPageDescPage::GetRanges; + case RID_SVXPAGE_ASIAN_LAYOUT: + return SvxAsianLayoutPage::GetRanges; ++ case RID_SVXPAGE_TEXTCOLUMNS: ++ return SvxTextColumnsPage::GetRanges; + default: + break; + } +diff --git a/cui/source/inc/TextColumnsPage.hxx b/cui/source/inc/TextColumnsPage.hxx +new file mode 100644 +index 000000000000..6153cd27a520 +--- /dev/null ++++ b/cui/source/inc/TextColumnsPage.hxx +@@ -0,0 +1,40 @@ ++/* -*- 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 ++ ++#include ++ ++#include ++ ++/// Tab page for EditEngine columns properties ++class SvxTextColumnsPage : public SfxTabPage ++{ ++private: ++ static const sal_uInt16 pRanges[]; ++ ++ std::unique_ptr m_xColumnsNumber; ++ std::unique_ptr m_xColumnsSpacing; ++ ++public: ++ SvxTextColumnsPage(weld::Container* pPage, weld::DialogController* pController, ++ const SfxItemSet& rInAttrs); ++ virtual ~SvxTextColumnsPage() override; ++ ++ static std::unique_ptr ++ Create(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet*); ++ static const sal_uInt16* GetRanges() { return pRanges; } ++ ++ virtual bool FillItemSet(SfxItemSet*) override; ++ virtual void Reset(const SfxItemSet*) override; ++}; ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ +diff --git a/cui/source/tabpages/TextColumnsPage.cxx b/cui/source/tabpages/TextColumnsPage.cxx +new file mode 100644 +index 000000000000..db83722e6be1 +--- /dev/null ++++ b/cui/source/tabpages/TextColumnsPage.cxx +@@ -0,0 +1,82 @@ ++/* -*- 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/. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++const sal_uInt16 SvxTextColumnsPage::pRanges[] ++ = { SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, 0 }; ++ ++SvxTextColumnsPage::SvxTextColumnsPage(weld::Container* pPage, weld::DialogController* pController, ++ const SfxItemSet& rInAttrs) ++ : SfxTabPage(pPage, pController, "cui/ui/textcolumnstabpage.ui", "TextColumnsPage", &rInAttrs) ++ , m_xColumnsNumber(m_xBuilder->weld_spin_button("FLD_COL_NUMBER")) ++ , m_xColumnsSpacing( ++ m_xBuilder->weld_metric_spin_button("MTR_FLD_COL_SPACING", GetModuleFieldUnit(rInAttrs))) ++{ ++} ++ ++SvxTextColumnsPage::~SvxTextColumnsPage() = default; ++ ++// read the passed item set ++void SvxTextColumnsPage::Reset(const SfxItemSet* rAttrs) ++{ ++ SfxItemPool* pPool = rAttrs->GetPool(); ++ assert(pPool); ++ ++ { ++ auto pItem = GetItem(*rAttrs, SDRATTR_TEXTCOLUMNS_NUMBER); ++ if (!pItem) ++ pItem = &pPool->GetDefaultItem(SDRATTR_TEXTCOLUMNS_NUMBER); ++ m_xColumnsNumber->set_value(pItem->GetValue()); ++ m_xColumnsNumber->save_value(); ++ } ++ ++ { ++ MapUnit eUnit = pPool->GetMetric(SDRATTR_TEXTCOLUMNS_SPACING); ++ auto pItem = GetItem(*rAttrs, SDRATTR_TEXTCOLUMNS_SPACING); ++ if (!pItem) ++ pItem = &pPool->GetDefaultItem(SDRATTR_TEXTCOLUMNS_SPACING); ++ SetMetricValue(*m_xColumnsSpacing, pItem->GetValue(), eUnit); ++ m_xColumnsSpacing->save_value(); ++ } ++} ++ ++// fill the passed item set with dialog box attributes ++bool SvxTextColumnsPage::FillItemSet(SfxItemSet* rAttrs) ++{ ++ if (m_xColumnsNumber->get_value_changed_from_saved()) ++ rAttrs->Put(SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, m_xColumnsNumber->get_value())); ++ ++ if (m_xColumnsSpacing->get_value_changed_from_saved()) ++ { ++ SfxItemPool* pPool = rAttrs->GetPool(); ++ assert(pPool); ++ MapUnit eUnit = pPool->GetMetric(SDRATTR_TEXTCOLUMNS_SPACING); ++ sal_Int32 nValue = GetCoreValue(*m_xColumnsSpacing, eUnit); ++ rAttrs->Put(SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, nValue)); ++ } ++ ++ return true; ++} ++ ++std::unique_ptr SvxTextColumnsPage::Create(weld::Container* pPage, ++ weld::DialogController* pController, ++ const SfxItemSet* rAttrs) ++{ ++ return std::make_unique(pPage, pController, *rAttrs); ++} ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ +diff --git a/cui/source/tabpages/textanim.cxx b/cui/source/tabpages/textanim.cxx +index a7ee4b3aade9..b4660fc19b8d 100644 +--- a/cui/source/tabpages/textanim.cxx ++++ b/cui/source/tabpages/textanim.cxx +@@ -19,6 +19,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -47,6 +48,7 @@ SvxTextTabDialog::SvxTextTabDialog(weld::Window* pParent, const SfxItemSet* pAtt + { + AddTabPage("RID_SVXPAGE_TEXTATTR", SvxTextAttrPage::Create, nullptr); + AddTabPage("RID_SVXPAGE_TEXTANIMATION", SvxTextAnimationPage::Create, nullptr); ++ AddTabPage("RID_SVXPAGE_TEXTCOLUMNS", SvxTextColumnsPage::Create, nullptr); + } + + /************************************************************************* +diff --git a/cui/uiconfig/ui/textcolumnstabpage.ui b/cui/uiconfig/ui/textcolumnstabpage.ui +new file mode 100644 +index 000000000000..596b155dbe1b +--- /dev/null ++++ b/cui/uiconfig/ui/textcolumnstabpage.ui +@@ -0,0 +1,111 @@ ++ ++ ++ ++ ++ ++ 1 ++ 16 ++ 1 ++ 10 ++ ++ ++ 0 ++ 2147483647 ++ 1 ++ 10 ++ ++ ++ True ++ False ++ 6 ++ vertical ++ 12 ++ ++ ++ ++ True ++ False ++ 12 ++ 6 ++ 6 ++ 12 ++ ++ ++ True ++ False ++ _Number of columns: ++ True ++ FLD_COL_NUMBER ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ _Spacing: ++ True ++ MTR_FLD_COL_SPACING ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ True ++ True ++ adjustmentColNumber ++ 1 ++ ++ ++ Enter the number of columns to use for the text. ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ True ++ True ++ adjustmentColSpacing ++ 2 ++ ++ ++ Enter the amount of space to leave between the columns. ++ ++ ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Sets the columns layout properties for text in the selected drawing or text object. ++ ++ ++ ++ +diff --git a/cui/uiconfig/ui/textdialog.ui b/cui/uiconfig/ui/textdialog.ui +index a7cddf8500fd..4fcf813c71ba 100644 +--- a/cui/uiconfig/ui/textdialog.ui ++++ b/cui/uiconfig/ui/textdialog.ui +@@ -187,6 +187,54 @@ + False + + ++ ++ ++ ++ True ++ False ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 2 ++ ++ ++ ++ ++ True ++ False ++ Text Columns ++ ++ ++ 2 ++ False ++ ++ + + + False +diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx +index 3c06cbf96f51..7592a3f2b964 100644 +--- a/editeng/source/editeng/editeng.cxx ++++ b/editeng/source/editeng/editeng.cxx +@@ -448,6 +448,11 @@ bool EditEngine::GetDirectVertical() const + return pImpEditEngine->GetDirectVertical(); + } + ++void EditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) ++{ ++ pImpEditEngine->SetTextColumns(nColumns, nSpacing); ++} ++ + void EditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) + { + pImpEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +@@ -555,6 +560,11 @@ void EditEngine::SetMaxAutoPaperSize( const Size& rSz ) + pImpEditEngine->SetMaxAutoPaperSize( rSz ); + } + ++void EditEngine::SetMinColumnWrapHeight(tools::Long nVal) ++{ ++ pImpEditEngine->SetMinColumnWrapHeight(nVal); ++} ++ + OUString EditEngine::GetText( LineEnd eEnd ) const + { + return pImpEditEngine->GetEditDoc().GetText( eEnd ); +@@ -2013,29 +2023,12 @@ bool EditEngine::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder ) + if ( !pImpEditEngine->IsFormatted() ) + pImpEditEngine->FormatDoc(); + +- bool bTextPos = false; + // take unrotated positions for calculation here + Point aDocPos = GetDocPos( rPaperPos ); + + if ( ( aDocPos.Y() > 0 ) && ( aDocPos.Y() < static_cast(pImpEditEngine->GetTextHeight()) ) ) +- { +- EditPaM aPaM = pImpEditEngine->GetPaM( aDocPos, false ); +- if ( aPaM.GetNode() ) +- { +- const ParaPortion* pParaPortion = pImpEditEngine->FindParaPortion( aPaM.GetNode() ); +- DBG_ASSERT( pParaPortion, "ParaPortion?" ); +- +- sal_Int32 nLine = pParaPortion->GetLineNumber( aPaM.GetIndex() ); +- const EditLine& rLine = pParaPortion->GetLines()[nLine]; +- Range aLineXPosStartEnd = pImpEditEngine->GetLineXPosStartEnd( pParaPortion, &rLine ); +- if ( ( aDocPos.X() >= aLineXPosStartEnd.Min() - nBorder ) && +- ( aDocPos.X() <= aLineXPosStartEnd.Max() + nBorder ) ) +- { +- bTextPos = true; +- } +- } +- } +- return bTextPos; ++ return pImpEditEngine->IsTextPos(aDocPos, nBorder); ++ return false; + } + + void EditEngine::SetEditTextObjectPool( SfxItemPool* pPool ) +diff --git a/editeng/source/editeng/impedit.cxx b/editeng/source/editeng/impedit.cxx +index f226f983f593..138d73825b55 100644 +--- a/editeng/source/editeng/impedit.cxx ++++ b/editeng/source/editeng/impedit.cxx +@@ -518,107 +518,116 @@ void ImpEditView::DrawSelectionXOR( EditSelection aTmpSel, vcl::Region* pRegion, + bool bStartHandleVisible = false; + bool bEndHandleVisible = false; + +- for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ ) +- { +- ParaPortion* pTmpPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara ); +- if (!pTmpPortion) ++ auto DrawHighlight = [&, nStartLine = sal_Int32(0), nEndLine = sal_Int32(0)]( ++ const ImpEditEngine::LineAreaInfo& rInfo) mutable { ++ if (!rInfo.pLine) // Begin of ParaPortion + { +- SAL_WARN( "editeng", "Portion in Selection not found!" ); +- continue; +- } +- +- DBG_ASSERT( !pTmpPortion->IsInvalid(), "Portion in Selection not formatted!" ); +- +- if ( !pTmpPortion->IsVisible() || pTmpPortion->IsInvalid() ) +- continue; +- +- tools::Long nParaStart = pEditEngine->GetParaPortions().GetYOffset( pTmpPortion ); +- if ( ( nParaStart + pTmpPortion->GetHeight() ) < GetVisDocTop() ) +- continue; +- if ( nParaStart > GetVisDocBottom() ) +- break; +- +- sal_uInt16 nStartLine = 0; +- sal_uInt16 nEndLine = pTmpPortion->GetLines().Count() -1; +- if ( nPara == nStartPara ) +- nStartLine = pTmpPortion->GetLines().FindLine( aTmpSel.Min().GetIndex(), false ); +- if ( nPara == nEndPara ) +- nEndLine = pTmpPortion->GetLines().FindLine( aTmpSel.Max().GetIndex(), true ); ++ if (rInfo.nPortion < nStartPara) ++ return ImpEditEngine::CallbackResult::SkipThisPortion; ++ if (rInfo.nPortion > nEndPara) ++ return ImpEditEngine::CallbackResult::Stop; ++ DBG_ASSERT(!rInfo.rPortion.IsInvalid(), "Portion in Selection not formatted!"); ++ if (rInfo.rPortion.IsInvalid()) ++ return ImpEditEngine::CallbackResult::SkipThisPortion; ++ ++ if (rInfo.nPortion == nStartPara) ++ nStartLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Min().GetIndex(), false); ++ else ++ nStartLine = 0; + +- for ( sal_uInt16 nLine = nStartLine; nLine <= nEndLine; nLine++ ) ++ if (rInfo.nPortion == nEndPara) ++ nEndLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Max().GetIndex(), true); ++ else ++ nEndLine = rInfo.rPortion.GetLines().Count() - 1; ++ } ++ else // This is a correct ParaPortion + { +- const EditLine& rLine = pTmpPortion->GetLines()[nLine]; ++ if (rInfo.nLine < nStartLine) ++ return ImpEditEngine::CallbackResult::Continue; ++ if (rInfo.nLine > nEndLine) ++ return ImpEditEngine::CallbackResult::SkipThisPortion; + + bool bPartOfLine = false; +- sal_Int32 nStartIndex = rLine.GetStart(); +- sal_Int32 nEndIndex = rLine.GetEnd(); +- if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) && ( nStartIndex != aTmpSel.Min().GetIndex() ) ) ++ sal_Int32 nStartIndex = rInfo.pLine->GetStart(); ++ sal_Int32 nEndIndex = rInfo.pLine->GetEnd(); ++ if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine) ++ && (nStartIndex != aTmpSel.Min().GetIndex())) + { + nStartIndex = aTmpSel.Min().GetIndex(); + bPartOfLine = true; + } +- if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) && ( nEndIndex != aTmpSel.Max().GetIndex() ) ) ++ if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine) ++ && (nEndIndex != aTmpSel.Max().GetIndex())) + { + nEndIndex = aTmpSel.Max().GetIndex(); + bPartOfLine = true; + } + + // Can happen if at the beginning of a wrapped line. +- if ( nEndIndex < nStartIndex ) ++ if (nEndIndex < nStartIndex) + nEndIndex = nStartIndex; + +- tools::Rectangle aTmpRect( pEditEngine->pImpEditEngine->GetEditCursor( pTmpPortion, nStartIndex ) ); +- Point aTopLeft( aTmpRect.TopLeft() ); +- Point aBottomRight( aTmpRect.BottomRight() ); +- +- aTopLeft.AdjustY(nParaStart ); +- aBottomRight.AdjustY(nParaStart ); ++ tools::Rectangle aTmpRect(pEditEngine->pImpEditEngine->GetEditCursor( ++ &rInfo.rPortion, rInfo.pLine, nStartIndex, GetCursorFlags::NONE)); ++ aTmpRect.Move(0, pEditEngine->pImpEditEngine->getTopDirectionAware(rInfo.aArea)); + + // Only paint if in the visible range ... +- if ( aTopLeft.Y() > GetVisDocBottom() ) +- break; ++ if (aTmpRect.Top() > GetVisDocBottom()) ++ return ImpEditEngine::CallbackResult::Continue; + +- if ( aBottomRight.Y() < GetVisDocTop() ) +- continue; ++ if (aTmpRect.Bottom() < GetVisDocTop()) ++ return ImpEditEngine::CallbackResult::Continue; + +- if ( ( nPara == nStartPara ) && ( nLine == nStartLine ) ) ++ if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine)) + bStartHandleVisible = true; +- if ( ( nPara == nEndPara ) && ( nLine == nEndLine ) ) ++ if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine)) + bEndHandleVisible = true; + + // Now that we have Bidi, the first/last index doesn't have to be the 'most outside' position +- if ( !bPartOfLine ) ++ if (!bPartOfLine) + { +- Range aLineXPosStartEnd = pEditEngine->GetLineXPosStartEnd(pTmpPortion, &rLine); +- aTopLeft.setX( aLineXPosStartEnd.Min() ); +- aBottomRight.setX( aLineXPosStartEnd.Max() ); +- ImplDrawHighlightRect( pTarget, aTopLeft, aBottomRight, pPolyPoly.get() ); ++ Range aLineXPosStartEnd ++ = pEditEngine->GetLineXPosStartEnd(&rInfo.rPortion, rInfo.pLine); ++ aTmpRect.SetLeft(aLineXPosStartEnd.Min()); ++ aTmpRect.SetRight(aLineXPosStartEnd.Max()); ++ aTmpRect.Move(pEditEngine->pImpEditEngine->getLeftDirectionAware(rInfo.aArea), 0); ++ ImplDrawHighlightRect(pTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), ++ pPolyPoly.get()); + } + else + { + sal_Int32 nTmpStartIndex = nStartIndex; + sal_Int32 nWritingDirStart, nTmpEndIndex; ++ const sal_Int32 nLeftOffset ++ = pEditEngine->pImpEditEngine->getLeftDirectionAware(rInfo.aArea); + +- while ( nTmpStartIndex < nEndIndex ) ++ while (nTmpStartIndex < nEndIndex) + { +- pEditEngine->pImpEditEngine->GetRightToLeft( nPara, nTmpStartIndex+1, &nWritingDirStart, &nTmpEndIndex ); +- if ( nTmpEndIndex > nEndIndex ) ++ pEditEngine->pImpEditEngine->GetRightToLeft(rInfo.nPortion, nTmpStartIndex + 1, ++ &nWritingDirStart, &nTmpEndIndex); ++ if (nTmpEndIndex > nEndIndex) + nTmpEndIndex = nEndIndex; + +- DBG_ASSERT( nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?" ); ++ DBG_ASSERT(nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?"); + +- tools::Long nX1 = pEditEngine->GetXPos(pTmpPortion, &rLine, nTmpStartIndex, true); +- tools::Long nX2 = pEditEngine->GetXPos(pTmpPortion, &rLine, nTmpEndIndex); ++ tools::Long nX1 ++ = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpStartIndex, true); ++ tools::Long nX2 ++ = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpEndIndex); + +- Point aPt1( std::min( nX1, nX2 ), aTopLeft.Y() ); +- Point aPt2( std::max( nX1, nX2 ), aBottomRight.Y() ); ++ aTmpRect.SetLeft(std::min(nX1, nX2)); ++ aTmpRect.SetRight(std::max(nX1, nX2)); ++ aTmpRect.Move(nLeftOffset, 0); + +- ImplDrawHighlightRect( pTarget, aPt1, aPt2, pPolyPoly.get() ); ++ ImplDrawHighlightRect(pTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(), ++ pPolyPoly.get()); + nTmpStartIndex = nTmpEndIndex; + } + } + } +- } ++ return ImpEditEngine::CallbackResult::Continue; ++ }; ++ pEditEngine->pImpEditEngine->IterateLineAreas(DrawHighlight, ImpEditEngine::IterFlag::none); + + if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin) + lokSelectionCallback(pPolyPoly, bStartHandleVisible, bEndHandleVisible); +@@ -1791,6 +1800,8 @@ const SvxFieldItem* ImpEditView::GetField( const Point& rPos, sal_Int32* pPara, + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); ++ if (!aPaM) ++ return nullptr; + + if ( aPaM.GetIndex() == aPaM.GetNode()->Len() ) + { +@@ -1829,6 +1840,8 @@ bool ImpEditView::IsBulletArea( const Point& rPos, sal_Int32* pPara ) + + Point aDocPos( GetDocPos( rPos ) ); + EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false); ++ if (!aPaM) ++ return false; + + if ( aPaM.GetIndex() == 0 ) + { +diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx +index 3adffe49b1eb..dd797469a81f 100644 +--- a/editeng/source/editeng/impedit.hxx ++++ b/editeng/source/editeng/impedit.hxx +@@ -54,8 +54,10 @@ + #include + #include + ++#include + #include + #include ++#include + #include + + class EditView; +@@ -490,6 +492,7 @@ private: + Size aPaperSize; // Layout + Size aMinAutoPaperSize; // Layout ? + Size aMaxAutoPaperSize; // Layout ? ++ tools::Long mnMinColumnWrapHeight = 0; // Corresponds to graphic object height + EditDoc aEditDoc; // Document content + + // Engine Specific data ... +@@ -549,8 +552,8 @@ private: + // For Formatting / Update... + std::vector > aDeletedNodes; + tools::Rectangle aInvalidRect; +- sal_uInt32 nCurTextHeight; +- sal_uInt32 nCurTextHeightNTP; // without trailing empty paragraphs ++ tools::Long nCurTextHeight; ++ tools::Long nCurTextHeightNTP; // without trailing empty paragraphs + sal_uInt16 nOnePixelInRef; + + IdleFormattter aIdleFormatter; +@@ -562,6 +565,9 @@ private: + sal_Int32 mnOverflowingLine = -1; + bool mbNeedsChainingHandling = false; + ++ sal_Int16 mnColumns = 1; ++ sal_Int32 mnColumnSpacing = 0; ++ + // If it is detected at one point that the StatusHdl has to be called, but + // this should not happen immediately (critical section): + Timer aStatusTimer; +@@ -614,8 +620,9 @@ private: + + std::unique_ptr GetEmptyTextObject(); + ++ std::tuple GetPortionAndLine(Point aDocPos); + EditPaM GetPaM( Point aDocPos, bool bSmart = true ); +- EditPaM GetPaM( ParaPortion* pPortion, Point aPos, bool bSmart ); ++ bool IsTextPos(const Point& rDocPos, sal_uInt16 nBorder); + tools::Long GetXPos(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart = false) const; + tools::Long GetPortionXOffset(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const; + sal_Int32 GetChar(const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nX, bool bSmart = true); +@@ -765,8 +772,8 @@ private: + css::uno::Reference < css::i18n::XBreakIterator > const & ImplGetBreakIterator() const; + css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > const & ImplGetInputSequenceChecker() const; + +- void ImplUpdateOverflowingParaNum( sal_uInt32 ); +- void ImplUpdateOverflowingLineNum( sal_uInt32, sal_uInt32, sal_uInt32 ); ++ void ImplUpdateOverflowingParaNum(tools::Long); ++ void ImplUpdateOverflowingLineNum(tools::Long, sal_uInt32, tools::Long); + + void CreateSpellInfo( bool bMultipleDocs ); + /// Obtains a view shell ID from the active EditView. +@@ -780,6 +787,8 @@ private: + const ParaPortionList& GetParaPortions() const { return aParaPortionList; } + ParaPortionList& GetParaPortions() { return aParaPortionList; } + ++ tools::Long Calc1ColumnTextHeight(tools::Long* pHeightNTP); ++ + protected: + virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; + +@@ -807,6 +816,8 @@ public: + void SetRotation( TextRotation nRotation); + TextRotation GetRotation() const { return GetEditDoc().GetRotation(); } + ++ void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing); ++ + bool IsPageOverflow( ) const; + + void SetFixedCellHeight( bool bUseFixedCellHeight ); +@@ -830,6 +841,8 @@ public: + const Size& GetMaxAutoPaperSize() const { return aMaxAutoPaperSize; } + void SetMaxAutoPaperSize( const Size& rSz ) { aMaxAutoPaperSize = rSz; } + ++ void SetMinColumnWrapHeight(tools::Long nVal) { mnMinColumnWrapHeight = nVal; } ++ + void FormatDoc(); + void FormatFullDoc(); + void UpdateViews( EditView* pCurView = nullptr ); +@@ -884,7 +897,7 @@ public: + + EditSelection MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos, EditView* pCurView ); + +- sal_uInt32 CalcTextHeight( sal_uInt32* pHeightNTP ); ++ tools::Long CalcTextHeight( tools::Long* pHeightNTP ); + sal_uInt32 GetTextHeight() const; + sal_uInt32 GetTextHeightNTP() const; + sal_uInt32 CalcTextWidth( bool bIgnoreExtraSpace); +@@ -916,7 +929,8 @@ public: + } + + tools::Rectangle PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags = GetCursorFlags::NONE ); +- tools::Rectangle GetEditCursor( ParaPortion* pPortion, sal_Int32 nIndex, GetCursorFlags nFlags = GetCursorFlags::NONE ); ++ tools::Rectangle GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, ++ sal_Int32 nIndex, GetCursorFlags nFlags); + + bool IsModified() const { return aEditDoc.IsModified(); } + void SetModifyFlag( bool b ) { aEditDoc.SetModified( b ); } +@@ -1116,6 +1130,50 @@ public: + void Dispose(); + void SetLOKSpecialPaperSize(const Size& rSize) { aLOKSpecialPaperSize = rSize; } + const Size& GetLOKSpecialPaperSize() const { return aLOKSpecialPaperSize; } ++ ++ enum class CallbackResult ++ { ++ Continue, ++ SkipThisPortion, // Do not call callback until next portion ++ Stop, // Stop iteration ++ }; ++ struct LineAreaInfo ++ { ++ sal_Int32 nColumn; // Column number; when overflowing, equal to total number of columns ++ ParaPortion& rPortion; // Current ParaPortion ++ sal_Int32 nPortion; ++ EditLine* pLine; // Current line, or nullptr for paragraph start ++ sal_Int32 nLine; ++ tools::Rectangle aArea; // The area for the line (or for rPortion's first line offset) ++ tools::Long nHeightNeededToNotWrap; ++ }; ++ using IterateLinesAreasFunc = std::function; ++ enum IterFlag // bitmask ++ { ++ none = 0, ++ inclILS = 1, // rArea includes interline space ++ }; ++ ++ void IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions); ++ ++ tools::Long GetColumnWidth(const Size& rPaperSize) const; ++ Point MoveToNextLine(Point& rMovePos, tools::Long nLineHeight, sal_Int32& nColumn, ++ Point aOrigin, tools::Long* pnHeightNeededToNotWrap = nullptr) const; ++ ++ tools::Long getXDirectionAware(const Point& pt) const; ++ tools::Long getYDirectionAware(const Point& pt) const; ++ tools::Long getWidthDirectionAware(const Size& sz) const; ++ tools::Long getHeightDirectionAware(const Size& sz) const; ++ void adjustXDirectionAware(Point& pt, tools::Long x) const; ++ void adjustYDirectionAware(Point& pt, tools::Long y) const; ++ void setXDirectionAware(Point& pt, tools::Long x) const; ++ void setYDirectionAware(Point& pt, tools::Long y) const; ++ tools::Long getYOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; ++ bool isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const; ++ tools::Long getLeftDirectionAware(const tools::Rectangle& rect) const; ++ tools::Long getRightDirectionAware(const tools::Rectangle& rect) const; ++ tools::Long getTopDirectionAware(const tools::Rectangle& rect) const; ++ tools::Long getBottomDirectionAware(const tools::Rectangle& rect) const; + }; + + inline EPaM ImpEditEngine::CreateEPaM( const EditPaM& rPaM ) +diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx +index 2c83c1165856..4980c5730de8 100644 +--- a/editeng/source/editeng/impedit2.cxx ++++ b/editeng/source/editeng/impedit2.cxx +@@ -59,12 +59,14 @@ + #include + #include + #include ++#include + #include + #include + #include + + #include + #include ++#include + #include + #include + #include +@@ -536,22 +538,47 @@ void ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView ) + if ( !IsFormatted() ) + FormatDoc(); + +- ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) ); ++ sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode()); ++ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos); + if (pParaPortion) + { +- sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true ); +- const EditLine& rLine = pParaPortion->GetLines()[nLine]; +- std::unique_ptr aRects(new tools::Rectangle[ mpIMEInfos->nLen ]); +- for (sal_Int32 i = 0; i < mpIMEInfos->nLen; ++i) +- { +- sal_Int32 nInputPos = mpIMEInfos->aPos.GetIndex() + i; +- if ( nInputPos > rLine.GetEnd() ) +- nInputPos = rLine.GetEnd(); +- tools::Rectangle aR2 = GetEditCursor( pParaPortion, nInputPos ); +- aRects[ i ] = pView->GetImpEditView()->GetWindowPos( aR2 ); +- } ++ const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex(); ++ const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1; ++ std::vector aRects(mpIMEInfos->nLen); ++ ++ auto CollectCharPositions = [&](const LineAreaInfo& rInfo) { ++ if (!rInfo.pLine) // Start of ParaPortion ++ { ++ if (rInfo.nPortion < nPortionPos) ++ return CallbackResult::SkipThisPortion; ++ if (rInfo.nPortion > nPortionPos) ++ return CallbackResult::Stop; ++ assert(&rInfo.rPortion == pParaPortion); ++ } ++ else // This is the needed ParaPortion ++ { ++ if (rInfo.pLine->GetStart() > nMaxPos) ++ return CallbackResult::Stop; ++ if (rInfo.pLine->GetEnd() < nMinPos) ++ return CallbackResult::Continue; ++ for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n) ++ { ++ if (rInfo.pLine->IsIn(n)) ++ { ++ tools::Rectangle aR = GetEditCursor(pParaPortion, rInfo.pLine, n, ++ GetCursorFlags::NONE); ++ aR.Move(getLeftDirectionAware(rInfo.aArea), ++ getTopDirectionAware(rInfo.aArea)); ++ aRects[n - nMinPos] = pView->GetImpEditView()->GetWindowPos(aR); ++ } ++ } ++ } ++ return CallbackResult::Continue; ++ }; ++ IterateLineAreas(CollectCharPositions, IterFlag::none); ++ + if (vcl::Window* pWindow = pView->GetWindow()) +- pWindow->SetCompositionCharRect( aRects.get(), mpIMEInfos->nLen ); ++ pWindow->SetCompositionCharRect(aRects.data(), aRects.size()); + } + } + } +@@ -3027,71 +3054,242 @@ EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel) + + // Helper functions + ++tools::Rectangle ImpEditEngine::GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine, ++ sal_Int32 nIndex, GetCursorFlags nFlags) ++{ ++ assert(pPortion && pLine); ++ // nIndex might be not in the line ++ // Search within the line... ++ tools::Long nX; ++ ++ if ((nIndex == pLine->GetStart()) && (nFlags & GetCursorFlags::StartOfLine)) ++ { ++ Range aXRange = GetLineXPosStartEnd(pPortion, pLine); ++ nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Min() ++ : aXRange.Max(); ++ } ++ else if ((nIndex == pLine->GetEnd()) && (nFlags & GetCursorFlags::EndOfLine)) ++ { ++ Range aXRange = GetLineXPosStartEnd(pPortion, pLine); ++ nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Max() ++ : aXRange.Min(); ++ } ++ else ++ { ++ nX = GetXPos(pPortion, pLine, nIndex, bool(nFlags & GetCursorFlags::PreferPortionStart)); ++ } ++ ++ tools::Rectangle aEditCursor; ++ aEditCursor.SetLeft(nX); ++ aEditCursor.SetRight(nX); ++ ++ aEditCursor.SetBottom(pLine->GetHeight() - 1); ++ if (nFlags & GetCursorFlags::TextOnly) ++ aEditCursor.SetTop(aEditCursor.Bottom() - pLine->GetTxtHeight() + 1); ++ else ++ aEditCursor.SetTop(aEditCursor.Bottom() ++ - std::min(pLine->GetTxtHeight(), pLine->GetHeight()) + 1); ++ return aEditCursor; ++} ++ + tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags ) + { + OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: PaMtoEditCursor" ); + + tools::Rectangle aEditCursor; +- tools::Long nY = 0; +- for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) +- { +- ParaPortion* pPortion = GetParaPortions()[nPortion]; +- ContentNode* pNode = pPortion->GetNode(); +- OSL_ENSURE( pNode, "Invalid Node in Portion!" ); +- if ( pNode != aPaM.GetNode() ) ++ const sal_Int32 nIndex = aPaM.GetIndex(); ++ const ParaPortion* pPortion = nullptr; ++ const EditLine* pLastLine = nullptr; ++ tools::Rectangle aLineArea; ++ ++ auto FindPortionLineAndArea ++ = [&, bEOL(bool(nFlags & GetCursorFlags::EndOfLine))](const LineAreaInfo& rInfo) { ++ if (!rInfo.pLine) // start of ParaPortion + { +- nY += pPortion->GetHeight(); ++ ContentNode* pNode = rInfo.rPortion.GetNode(); ++ OSL_ENSURE(pNode, "Invalid Node in Portion!"); ++ if (pNode != aPaM.GetNode()) ++ return CallbackResult::SkipThisPortion; ++ pPortion = &rInfo.rPortion; + } +- else ++ else // guaranteed that this is the correct ParaPortion + { +- aEditCursor = GetEditCursor( pPortion, aPaM.GetIndex(), nFlags ); +- aEditCursor.AdjustTop(nY ); +- aEditCursor.AdjustBottom(nY ); +- return aEditCursor; ++ pLastLine = rInfo.pLine; ++ aLineArea = rInfo.aArea; ++ if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL))) ++ return CallbackResult::Stop; + } ++ return CallbackResult::Continue; ++ }; ++ IterateLineAreas(FindPortionLineAndArea, IterFlag::none); ++ ++ if (pLastLine) ++ { ++ aEditCursor = GetEditCursor(pPortion, pLastLine, nIndex, nFlags); ++ aEditCursor.Move(getLeftDirectionAware(aLineArea), getTopDirectionAware(aLineArea)); + } +- OSL_FAIL( "Portion not found!" ); ++ else ++ OSL_FAIL("Line not found!"); ++ + return aEditCursor; + } + ++void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions) ++{ ++ const Point aOrigin(0, 0); ++ Point aLineStart(aOrigin); ++ const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart); ++ const tools::Long nColumnWidth = GetColumnWidth(aPaperSize); ++ sal_Int32 nColumn = 0; ++ for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n) ++ { ++ ParaPortion& rPortion = *GetParaPortions()[n]; ++ bool bSkipThis = true; ++ if (rPortion.IsVisible()) ++ { ++ // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid. ++ if (rPortion.IsInvalid()) ++ return; ++ ++ LineAreaInfo aInfo{ ++ nColumn, // nColumn ++ rPortion, // rPortion ++ n, // nPortion ++ nullptr, // pLine ++ 0, // nLine ++ { aLineStart, Size{ nColumnWidth, rPortion.GetFirstLineOffset() } }, // aArea ++ 0 // nHeightNeededToNotWrap ++ }; ++ auto eResult = f(aInfo); ++ if (eResult == CallbackResult::Stop) ++ return; ++ bSkipThis = eResult == CallbackResult::SkipThisPortion; ++ ++ sal_uInt16 nSBL = 0; ++ if (!aStatus.IsOutliner()) ++ { ++ const SvxLineSpacingItem& rLSItem ++ = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL); ++ nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix) ++ ? GetYValue(rLSItem.GetInterLineSpace()) ++ : 0; ++ } ++ ++ adjustYDirectionAware(aLineStart, rPortion.GetFirstLineOffset()); ++ for (sal_Int32 nLine = 0, nLines = rPortion.GetLines().Count(); nLine < nLines; nLine++) ++ { ++ EditLine& rLine = rPortion.GetLines()[nLine]; ++ tools::Long nLineHeight = rLine.GetHeight(); ++ if (nLine != nLines - 1) ++ nLineHeight += nVertLineSpacing; ++ MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin, ++ &aInfo.nHeightNeededToNotWrap); ++ const bool bInclILS = eOptions & IterFlag::inclILS; ++ if (bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) ++ { ++ adjustYDirectionAware(aLineStart, nSBL); ++ nLineHeight += nSBL; ++ } ++ ++ if (!bSkipThis) ++ { ++ Point aOtherCorner(aLineStart); ++ adjustXDirectionAware(aOtherCorner, nColumnWidth); ++ adjustYDirectionAware(aOtherCorner, -nLineHeight); ++ ++ // Calls to f() for each line ++ aInfo.nColumn = nColumn; ++ aInfo.pLine = &rLine; ++ aInfo.nLine = nLine; ++ aInfo.aArea = tools::Rectangle::Justify(aLineStart, aOtherCorner); ++ eResult = f(aInfo); ++ if (eResult == CallbackResult::Stop) ++ return; ++ bSkipThis = eResult == CallbackResult::SkipThisPortion; ++ } ++ ++ if (!bInclILS && (nLine != nLines - 1) && !aStatus.IsOutliner()) ++ adjustYDirectionAware(aLineStart, nSBL); ++ } ++ if (!aStatus.IsOutliner()) ++ { ++ const SvxULSpaceItem& rULItem ++ = rPortion.GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE); ++ tools::Long nUL = GetYValue(rULItem.GetLower()); ++ adjustYDirectionAware(aLineStart, nUL); ++ } ++ } ++ // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it ++ } ++} ++ ++std::tuple ++ImpEditEngine::GetPortionAndLine(Point aDocPos) ++{ ++ // First find the column from the point ++ sal_Int32 nClickColumn = 0; ++ for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(aPaperSize);; ++ nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn) ++ { ++ if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2) ++ break; ++ if (nClickColumn >= mnColumns - 1) ++ break; ++ } ++ ++ const ParaPortion* pLastPortion = nullptr; ++ const EditLine* pLastLine = nullptr; ++ tools::Long nLineStartX = 0; ++ ++ auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) { ++ if (rInfo.pLine) // Only handle lines, not ParaPortion starts ++ { ++ if (rInfo.nColumn > nClickColumn) ++ return CallbackResult::Stop; ++ pLastPortion = &rInfo.rPortion; // Candidate paragraph ++ pLastLine = rInfo.pLine; // Last visible line not later than click position ++ nLineStartX = getLeftDirectionAware(rInfo.aArea); ++ if (rInfo.nColumn == nClickColumn && getBottomDirectionAware(rInfo.aArea) > aDocPos.Y()) ++ return CallbackResult::Stop; // Found it ++ } ++ return CallbackResult::Continue; ++ }; ++ IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS); ++ ++ return { pLastPortion, pLastLine, nLineStartX }; ++} ++ + EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart ) + { + OSL_ENSURE( GetUpdateMode(), "Must not be reached when Update=FALSE: GetPaM" ); + +- tools::Long nY = 0; +- EditPaM aPaM; +- sal_Int32 nPortion; +- for ( nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) ++ if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion) + { +- ParaPortion* pPortion = GetParaPortions()[nPortion]; +- const tools::Long nTmpHeight = pPortion->GetHeight(); // should also be correct for !bVisible! +- nY += nTmpHeight; +- if ( nY > aDocPos.Y() ) +- { +- nY -= nTmpHeight; +- aDocPos.AdjustY( -nY ); +- // Skip invisible Portions: +- while ( pPortion && !pPortion->IsVisible() ) +- { +- nPortion++; +- pPortion = GetParaPortions().SafeGetObject( nPortion ); +- } +- SAL_WARN_IF(!pPortion, "editeng", "worrying lack of any visible paragraph"); +- if (!pPortion) +- return aPaM; +- return GetPaM(pPortion, aDocPos, bSmart); ++ sal_Int32 nCurIndex ++ = GetChar(pPortion, pLine, aDocPos.X() - nLineStartX, bSmart); ++ EditPaM aPaM(pPortion->GetNode(), nCurIndex); + ++ if (nCurIndex && (nCurIndex == pLine->GetEnd()) ++ && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1])) ++ { ++ aPaM = CursorLeft(aPaM); + } ++ ++ return aPaM; + } +- // Then search for the last visible: +- nPortion = GetParaPortions().Count()-1; +- while ( nPortion && !GetParaPortions()[nPortion]->IsVisible() ) +- nPortion--; ++ return {}; ++} + +- OSL_ENSURE( GetParaPortions()[nPortion]->IsVisible(), "No visible paragraph found: GetPaM" ); +- aPaM.SetNode( GetParaPortions()[nPortion]->GetNode() ); +- aPaM.SetIndex( GetParaPortions()[nPortion]->GetNode()->Len() ); +- return aPaM; ++bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder) ++{ ++ if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion) ++ { ++ Range aLineXPosStartEnd = GetLineXPosStartEnd(pPortion, pLine); ++ if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder) ++ && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder)) ++ return true; ++ } ++ return false; + } + + sal_uInt32 ImpEditEngine::GetTextHeight() const +@@ -3236,28 +3434,114 @@ sal_uInt32 ImpEditEngine::GetTextHeightNTP() const + return nCurTextHeightNTP; + } + +-sal_uInt32 ImpEditEngine::CalcTextHeight( sal_uInt32* pHeightNTP ) ++tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP) + { +- OSL_ENSURE( GetUpdateMode(), "Should not be used when Update=FALSE: CalcTextHeight" ); +- sal_uInt32 nY = 0; +- sal_uInt32 nPH; +- sal_uInt32 nEmptyHeight = 0; +- for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ ) { +- ParaPortion* pPortion = GetParaPortions()[nPortion]; +- nPH = pPortion->GetHeight(); +- nY += nPH; +- if( pHeightNTP ) { +- if ( pPortion->IsEmpty() ) +- nEmptyHeight += nPH; +- else +- nEmptyHeight = 0; ++ tools::Long nHeight = 0; ++ // Pretend that we have ~infinite height to get total height ++ comphelper::ValueRestorationGuard aGuard(nCurTextHeight, ++ std::numeric_limits::max()); ++ ++ auto FindLastLineBottom = [&](const LineAreaInfo& rInfo) { ++ if (rInfo.pLine) ++ { ++ nHeight = getBottomDirectionAware(rInfo.aArea) + 1; ++ if (pHeightNTP && !rInfo.rPortion.IsEmpty()) ++ *pHeightNTP = nHeight; + } +- } ++ return CallbackResult::Continue; ++ }; ++ IterateLineAreas(FindLastLineBottom, IterFlag::none); ++ return nHeight; ++} + +- if ( pHeightNTP ) +- *pHeightNTP = nY - nEmptyHeight; ++tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP) ++{ ++ OSL_ENSURE( GetUpdateMode(), "Should not be used when Update=FALSE: CalcTextHeight" ); + +- return nY; ++ if (mnColumns <= 1) ++ return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done! ++ ++ // The final column height can be smaller than total height divided by number of columns (taking ++ // into account first line offset and interline spacing, that aren't considered in positioning ++ // after the wrap). The wrap should only happen after the minimal height is exceeded. ++ tools::Long nTentativeColHeight = mnMinColumnWrapHeight; ++ tools::Long nWantedIncrease = 0; ++ tools::Long nCurrentTextHeight; ++ ++ // This does the necessary column balancing for the case when the text does not fit min height. ++ // When the height of column (taken from nCurTextHeight) is too small, the last column will ++ // overflow, so the resulting height of the text will exceed the set column height. Increasing ++ // the column height step by step by the minimal value that allows one of columns to accomodate ++ // one line more, we finally get to the point where all the text fits. At each iteration, the ++ // height is only increased, so it's impossible to have infinite layout loops. The found value ++ // is the global minimum. ++ // ++ // E.g., given the following four line heights: ++ // Line 1: 10; ++ // Line 2: 12; ++ // Line 3: 10; ++ // Line 4: 10; ++ // number of columns 3, and the minimal paper height of 5, the iterations would be: ++ // * Tentative column height is set to 5 ++ // ++ // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1. ++ // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2. ++ // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2. ++ // * Line 4 goes to column 2 after Line 3. ++ // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4} ++ // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION ++ // * Minimal height increase that allows at least one column to accomodate one more line is ++ // min({5, 17, 17}) = 5. ++ // * Tentative column height is set to 5 + 5 = 10. ++ // ++ // * Line 1 goes to column 0, no overflow. ++ // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1. ++ // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2. ++ // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2. ++ // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} ++ // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION ++ // * Minimal height increase that allows at least one column to accomodate one more line is ++ // min({12, 12, 10}) = 10. ++ // * Tentative column height is set to 10 + 10 == 20. ++ // ++ // * Line 1 goes to column 0, no overflow. ++ // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1. ++ // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2. ++ // * Line 4 is attempted to go to column 2 after Line 3; no overflow. ++ // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4} ++ // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END. ++ do ++ { ++ nTentativeColHeight += nWantedIncrease; ++ nWantedIncrease = std::numeric_limits::max(); ++ nCurrentTextHeight = 0; ++ auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int32(0)]( ++ const LineAreaInfo& rInfo) mutable { ++ if (rInfo.pLine) ++ { ++ if (lastCol != rInfo.nColumn) ++ { ++ minHeight = std::max(nCurrentTextHeight, ++ minHeight); // total height can't be less than previous columns ++ nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease); ++ } ++ lastCol = rInfo.nColumn; ++ nCurrentTextHeight = std::max(getBottomDirectionAware(rInfo.aArea) + 1, minHeight); ++ if (pHeightNTP) ++ { ++ if (rInfo.rPortion.IsEmpty()) ++ ++ *pHeightNTP = std::max(*pHeightNTP, minHeight); ++ else ++ *pHeightNTP = nCurrentTextHeight; ++ } ++ } ++ return CallbackResult::Continue; ++ }; ++ comphelper::ValueRestorationGuard aGuard(nCurTextHeight, nTentativeColHeight); ++ IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none); ++ } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0); ++ return nCurrentTextHeight; + } + + sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const +@@ -3683,62 +3967,6 @@ Range ImpEditEngine::GetInvalidYOffsets( ParaPortion* pPortion ) + return aRange; + } + +-EditPaM ImpEditEngine::GetPaM( ParaPortion* pPortion, Point aDocPos, bool bSmart ) +-{ +- OSL_ENSURE( pPortion->IsVisible(), "Why GetPaM() for an invisible paragraph?" ); +- OSL_ENSURE( IsFormatted(), "GetPaM: Not formatted" ); +- +- sal_Int32 nCurIndex = 0; +- EditPaM aPaM; +- aPaM.SetNode( pPortion->GetNode() ); +- +- const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); +- sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) +- ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; +- +- tools::Long nY = pPortion->GetFirstLineOffset(); +- +- OSL_ENSURE( pPortion->GetLines().Count(), "Empty ParaPortion in GetPaM!" ); +- +- const EditLine* pLine = nullptr; +- for ( sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); nLine++ ) +- { +- const EditLine& rTmpLine = pPortion->GetLines()[nLine]; +- nY += rTmpLine.GetHeight(); +- if ( !aStatus.IsOutliner() ) +- nY += nSBL; +- if ( nY > aDocPos.Y() ) +- { +- pLine = &rTmpLine; +- break; // correct Y-position is not of interest +- } +- +- nCurIndex = nCurIndex + rTmpLine.GetLen(); +- } +- +- if ( !pLine ) // may happen only in the range of SA! +- { +-#if OSL_DEBUG_LEVEL > 0 +- const SvxULSpaceItem& rULSpace = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); +- OSL_ENSURE( nY+GetYValue( rULSpace.GetLower() ) >= aDocPos.Y() , "Index in no line, GetPaM ?" ); +-#endif +- aPaM.SetIndex( pPortion->GetNode()->Len() ); +- return aPaM; +- } +- +- // If no line found, only just X-Position => Index +- nCurIndex = GetChar( pPortion, pLine, aDocPos.X(), bSmart ); +- aPaM.SetIndex( nCurIndex ); +- +- if ( nCurIndex && ( nCurIndex == pLine->GetEnd() ) && +- ( pLine != &pPortion->GetLines()[pPortion->GetLines().Count()-1] ) ) +- { +- aPaM = CursorLeft( aPaM ); +- } +- +- return aPaM; +-} +- + sal_Int32 ImpEditEngine::GetChar( + const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart) + { +@@ -4202,91 +4430,6 @@ void ImpEditEngine::CalcHeight( ParaPortion* pPortion ) + } + } + +-tools::Rectangle ImpEditEngine::GetEditCursor( ParaPortion* pPortion, sal_Int32 nIndex, GetCursorFlags nFlags ) +-{ +- OSL_ENSURE( pPortion->IsVisible(), "Why GetEditCursor() for an invisible paragraph?" ); +- OSL_ENSURE( IsFormatted() || GetTextRanger(), "GetEditCursor: Not formatted" ); +- +- /* +- GetCursorFlags::EndOfLine: If after the last character of a wrapped line, remaining +- at the end of the line, not the beginning of the next one. +- Purpose: - END => really after the last character +- - Selection... +- */ +- +- tools::Long nY = pPortion->GetFirstLineOffset(); +- +- const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); +- sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) +- ? GetYValue( rLSItem.GetInterLineSpace() ) : 0; +- +- sal_Int32 nCurIndex = 0; +- sal_Int32 nLineCount = pPortion->GetLines().Count(); +- OSL_ENSURE( nLineCount, "Empty ParaPortion in GetEditCursor!" ); +- if (nLineCount == 0) +- return tools::Rectangle(); +- const EditLine* pLine = nullptr; +- bool bEOL( nFlags & GetCursorFlags::EndOfLine ); +- for (sal_Int32 nLine = 0; nLine < nLineCount; ++nLine) +- { +- const EditLine& rTmpLine = pPortion->GetLines()[nLine]; +- if ( ( rTmpLine.GetStart() == nIndex ) || ( rTmpLine.IsIn( nIndex, bEOL ) ) ) +- { +- pLine = &rTmpLine; +- break; +- } +- +- nCurIndex = nCurIndex + rTmpLine.GetLen(); +- nY += rTmpLine.GetHeight(); +- if ( !aStatus.IsOutliner() ) +- nY += nSBL; +- } +- if ( !pLine ) +- { +- // Cursor at the End of the paragraph. +- OSL_ENSURE( nIndex == nCurIndex, "Index dead wrong in GetEditCursor!" ); +- +- pLine = &pPortion->GetLines()[nLineCount-1]; +- nY -= pLine->GetHeight(); +- if ( !aStatus.IsOutliner() ) +- nY -= nSBL; +- } +- +- tools::Rectangle aEditCursor; +- +- aEditCursor.SetTop( nY ); +- nY += pLine->GetHeight(); +- aEditCursor.SetBottom( nY-1 ); +- +- // Search within the line... +- tools::Long nX; +- +- if ( ( nIndex == pLine->GetStart() ) && ( nFlags & GetCursorFlags::StartOfLine ) ) +- { +- Range aXRange = GetLineXPosStartEnd( pPortion, pLine ); +- nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Min() : aXRange.Max(); +- } +- else if ( ( nIndex == pLine->GetEnd() ) && ( nFlags & GetCursorFlags::EndOfLine ) ) +- { +- Range aXRange = GetLineXPosStartEnd( pPortion, pLine ); +- nX = !IsRightToLeft( GetEditDoc().GetPos( pPortion->GetNode() ) ) ? aXRange.Max() : aXRange.Min(); +- } +- else +- { +- nX = GetXPos( pPortion, pLine, nIndex, bool( nFlags & GetCursorFlags::PreferPortionStart ) ); +- } +- +- aEditCursor.SetLeft(nX); +- aEditCursor.SetRight(nX); +- +- if ( nFlags & GetCursorFlags::TextOnly ) +- aEditCursor.SetTop( aEditCursor.Bottom() - pLine->GetTxtHeight() + 1 ); +- else +- aEditCursor.SetTop( aEditCursor.Bottom() - std::min( pLine->GetTxtHeight(), pLine->GetHeight() ) + 1 ); +- +- return aEditCursor; +-} +- + void ImpEditEngine::SetValidPaperSize( const Size& rNewSz ) + { + aPaperSize = rNewSz; +diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx +index 7df8aaa92bf4..e74958031a96 100644 +--- a/editeng/source/editeng/impedit3.cxx ++++ b/editeng/source/editeng/impedit3.cxx +@@ -69,6 +69,7 @@ + #include + #include + #include ++#include + + #include + +@@ -374,8 +375,8 @@ void ImpEditEngine::FormatDoc() + + // Here already, so that not always in CreateLines... + bool bMapChanged = ImpCheckRefMapMode(); ++ std::set aRepaintParas; + +- aInvalidRect = tools::Rectangle(); // make empty + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; +@@ -410,46 +411,21 @@ void ImpEditEngine::FormatDoc() + pParaPortion->SetMustRepaint( false ); + } + +- // InvalidRect set only once... +- if ( aInvalidRect.IsEmpty() ) +- { +- // For Paperwidth 0 (AutoPageSize) it would otherwise be Empty()... +- tools::Long nWidth = std::max( tools::Long(1), ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) ); +- Range aInvRange( GetInvalidYOffsets( pParaPortion ) ); +- aInvalidRect = tools::Rectangle( Point( 0, nY+aInvRange.Min() ), +- Size( nWidth, aInvRange.Len() ) ); +- } +- else +- { +- aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() ); +- } +- } +- else if ( bGrow ) +- { +- aInvalidRect.SetBottom( nY + pParaPortion->GetHeight() ); ++ aRepaintParas.insert(nPara); + } + nY += pParaPortion->GetHeight(); + } + ++ aInvalidRect = tools::Rectangle(); // make empty ++ + // One can also get into the formatting through UpdateMode ON=>OFF=>ON... + // enable optimization first after Vobis delivery... + { +- sal_uInt32 nNewHeightNTP; +- sal_uInt32 nNewHeight = CalcTextHeight( &nNewHeightNTP ); ++ tools::Long nNewHeightNTP; ++ tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP); + tools::Long nDiff = nNewHeight - nCurTextHeight; + if ( nDiff ) + aStatus.GetStatusWord() |= !IsVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED; +- if ( nNewHeight < nCurTextHeight ) +- { +- aInvalidRect.SetBottom( static_cast(std::max( nNewHeight, nCurTextHeight )) ); +- if ( aInvalidRect.IsEmpty() ) +- { +- aInvalidRect.SetTop( 0 ); +- // Left and Right are not evaluated, are however set due to IsEmpty. +- aInvalidRect.SetLeft( 0 ); +- aInvalidRect.SetRight( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ); +- } +- } + + nCurTextHeight = nNewHeight; + nCurTextHeightNTP = nNewHeightNTP; +@@ -473,6 +449,20 @@ void ImpEditEngine::FormatDoc() + } + } + } ++ ++ if (nDiff) ++ aInvalidRect.Union(tools::Rectangle::Justify( ++ { 0, nNewHeight }, { getWidthDirectionAware(aPaperSize), nCurTextHeight })); ++ ++ if (!aRepaintParas.empty()) ++ { ++ auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) { ++ if (aRepaintParas.count(rInfo.nPortion)) ++ aInvalidRect.Union(rInfo.aArea); ++ return CallbackResult::Continue; ++ }; ++ IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS); ++ } + } + + bIsFormatting = false; +@@ -566,10 +556,10 @@ void ImpEditEngine::CheckPageOverflow() + { + SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( aStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") ); + +- sal_uInt32 nBoxHeight = GetMaxAutoPaperSize().Height(); ++ tools::Long nBoxHeight = GetMaxAutoPaperSize().Height(); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight); + +- sal_uInt32 nTxtHeight = CalcTextHeight(nullptr); ++ tools::Long nTxtHeight = CalcTextHeight(nullptr); + SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight); + + sal_uInt32 nParaCount = GetParaPortions().Count(); +@@ -598,6 +588,13 @@ static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontH + return ( nFontHeight * 12 ) / 10; // + 20% + } + ++tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const ++{ ++ assert(mnColumns >= 1); ++ tools::Long nWidth = IsVertical() ? rPaperSize.Height() : rPaperSize.Width(); ++ return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns; ++} ++ + bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + { + ParaPortion* pParaPortion = GetParaPortions()[nPara]; +@@ -785,11 +782,8 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + } + } + +- tools::Long nMaxLineWidth; +- if ( !IsVertical() ) +- nMaxLineWidth = aStatus.AutoPageWidth() ? aMaxAutoPaperSize.Width() : aPaperSize.Width(); +- else +- nMaxLineWidth = aStatus.AutoPageHeight() ? aMaxAutoPaperSize.Height() : aPaperSize.Height(); ++ const bool bAutoSize = IsVertical() ? aStatus.AutoPageHeight() : aStatus.AutoPageWidth(); ++ tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? aMaxAutoPaperSize : aPaperSize); + + nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); + nMaxLineWidth -= nStartX; +@@ -797,7 +791,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + // If PaperSize == long_max, one cannot take away any negative + // first line indent. (Overflow) + if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) ) +- nMaxLineWidth = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) - GetXValue( rLRItem.GetRight() ); ++ nMaxLineWidth = GetColumnWidth(aPaperSize) - GetXValue(rLRItem.GetRight()); + + // If still less than 0, it may be just the right edge. + if ( nMaxLineWidth <= 0 ) +@@ -824,7 +818,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + { + GetTextRanger()->SetVertical( IsVertical() ); + +- tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine->GetStart() ).Top(); ++ tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top(); + if ( !bSameLineAgain ) + { + SeekCursor( pNode, nTmpPos+1, aTmpFont ); +@@ -885,7 +879,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + nTextExtraYOffset += std::max( static_cast(nTextLineHeight / 10), tools::Long(1) ); + if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() ) + { +- nXWidth = !IsVertical() ? GetPaperSize().Width() : GetPaperSize().Height(); ++ nXWidth = getWidthDirectionAware(GetPaperSize()); + if ( !nXWidth ) // AutoPaperSize + nXWidth = 0x7FFFFFFF; + } +@@ -1468,7 +1462,7 @@ bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY ) + // has to be used for the Alignment. If it does not fit or if it + // will change the paper width, it will be formatted again for + // Justification! = LEFT anyway. +- tools::Long nMaxLineWidthFix = ( !IsVertical() ? aPaperSize.Width() : aPaperSize.Height() ) ++ tools::Long nMaxLineWidthFix = GetColumnWidth(aPaperSize) + - GetXValue( rLRItem.GetRight() ) - nStartX; + if ( aTextSize.Width() < nMaxLineWidthFix ) + nMaxLineWidth = nMaxLineWidthFix; +@@ -1725,7 +1719,7 @@ void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion ) + { + sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion ); + SvxAdjust eJustification = GetJustification( nPara ); +- tools::Long nMaxLineWidth = !IsVertical() ? aPaperSize.Width() : aPaperSize.Height(); ++ tools::Long nMaxLineWidth = GetColumnWidth(aPaperSize); + nMaxLineWidth -= GetXValue( rLRItem.GetRight() ); + if ( nMaxLineWidth < 0 ) + nMaxLineWidth = 1; +@@ -2636,6 +2630,20 @@ void ImpEditEngine::SetRotation(TextRotation nRotation) + } + } + ++void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) ++{ ++ if (mnColumns != nColumns || mnColumnSpacing != nSpacing) ++ { ++ mnColumns = nColumns; ++ mnColumnSpacing = nSpacing; ++ if (IsFormatted()) ++ { ++ FormatFullDoc(); ++ UpdateViews(GetActiveView()); ++ } ++ } ++} ++ + void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight ) + { + if ( IsFixedCellHeight() != bUseFixedCellHeight ) +@@ -2949,6 +2957,177 @@ void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics + } + } + ++tools::Long ImpEditEngine::getXDirectionAware(const Point& pt) const ++{ ++ if (!IsVertical()) ++ return pt.X(); ++ else ++ return pt.Y(); ++} ++ ++tools::Long ImpEditEngine::getYDirectionAware(const Point& pt) const ++{ ++ if (!IsVertical()) ++ return pt.Y(); ++ else ++ return pt.X(); ++} ++ ++tools::Long ImpEditEngine::getWidthDirectionAware(const Size& sz) const ++{ ++ return !IsVertical() ? sz.Width() : sz.Height(); ++} ++ ++tools::Long ImpEditEngine::getHeightDirectionAware(const Size& sz) const ++{ ++ return !IsVertical() ? sz.Height() : sz.Width(); ++} ++ ++void ImpEditEngine::adjustXDirectionAware(Point& pt, tools::Long x) const ++{ ++ if (!IsVertical()) ++ pt.AdjustX(x); ++ else ++ pt.AdjustY(IsTopToBottom() ? x : -x); ++} ++ ++void ImpEditEngine::adjustYDirectionAware(Point& pt, tools::Long y) const ++{ ++ if (!IsVertical()) ++ pt.AdjustY(y); ++ else ++ pt.AdjustX(IsTopToBottom() ? -y : y); ++} ++ ++void ImpEditEngine::setXDirectionAware(Point& pt, tools::Long x) const ++{ ++ if (!IsVertical()) ++ pt.setX(x); ++ else ++ pt.setY(x); ++} ++ ++void ImpEditEngine::setYDirectionAware(Point& pt, tools::Long y) const ++{ ++ if (!IsVertical()) ++ pt.setY(y); ++ else ++ pt.setX(y); ++} ++ ++tools::Long ImpEditEngine::getYOverflowDirectionAware(const Point& pt, ++ const tools::Rectangle& rectMax) const ++{ ++ tools::Long nRes; ++ if (!IsVertical()) ++ nRes = pt.Y() - rectMax.Bottom(); ++ else if (IsTopToBottom()) ++ nRes = rectMax.Left() - pt.X(); ++ else ++ nRes = pt.X() - rectMax.Right(); ++ return std::max(nRes, tools::Long(0)); ++} ++ ++bool ImpEditEngine::isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const ++{ ++ if (!IsVertical()) ++ return pt.X() > rectMax.Right(); ++ ++ if (IsTopToBottom()) ++ return pt.Y() > rectMax.Bottom(); ++ else ++ return pt.Y() < rectMax.Top(); ++} ++ ++tools::Long ImpEditEngine::getLeftDirectionAware(const tools::Rectangle& rect) const ++{ ++ if (!IsVertical()) ++ return rect.Left(); ++ ++ if (IsTopToBottom()) ++ return rect.Top(); ++ else ++ return rect.Bottom(); ++} ++ ++tools::Long ImpEditEngine::getRightDirectionAware(const tools::Rectangle& rect) const ++{ ++ if (!IsVertical()) ++ return rect.Right(); ++ ++ if (IsTopToBottom()) ++ return rect.Bottom(); ++ else ++ return rect.Top(); ++} ++ ++tools::Long ImpEditEngine::getTopDirectionAware(const tools::Rectangle& rect) const ++{ ++ if (!IsVertical()) ++ return rect.Top(); ++ ++ if (IsTopToBottom()) ++ return rect.Right(); ++ else ++ return rect.Left(); ++} ++ ++tools::Long ImpEditEngine::getBottomDirectionAware(const tools::Rectangle& rect) const ++{ ++ if (!IsVertical()) ++ return rect.Bottom(); ++ ++ if (IsTopToBottom()) ++ return rect.Left(); ++ else ++ return rect.Right(); ++} ++ ++// Returns the resulting shift for the point; allows to apply the same shift to other points ++Point ImpEditEngine::MoveToNextLine( ++ Point& rMovePos, // [in, out] Point that will move to the next line ++ tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware) ++ sal_Int32& rColumn, // [in, out] current column number ++ Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column ++ tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed ++) const ++{ ++ const Point aOld = rMovePos; ++ ++ // Move the point by the requested distance in Y direction ++ adjustYDirectionAware(rMovePos, nLineHeight); ++ // Check if the resulting position has moved beyond the limits, and more columns left. ++ // The limits are defined by a rectangle starting from aOrigin with width of aPaperSize ++ // and height of nCurTextHeight ++ Size aActPaperSize(aPaperSize); ++ if (IsVertical()) ++ aActPaperSize.setWidth(nCurTextHeight); ++ else ++ aActPaperSize.setHeight(nCurTextHeight); ++ tools::Long nNeeded = getYOverflowDirectionAware(rMovePos, { aOrigin, aActPaperSize }); ++ if (pnHeightNeededToNotWrap) ++ *pnHeightNeededToNotWrap = nNeeded; ++ if (nNeeded && rColumn < mnColumns) ++ { ++ ++rColumn; ++ // If we didn't fit into the last column, indicate that only by setting the column number ++ // to the total number of columns; do not adjust ++ if (rColumn < mnColumns) ++ { ++ // Set Y position of the point to that of aOrigin ++ setYDirectionAware(rMovePos, getYDirectionAware(aOrigin)); ++ // Move the point by the requested distance in Y direction ++ adjustYDirectionAware(rMovePos, nLineHeight); ++ // Move the point by the column+spacing distance in X direction ++ adjustXDirectionAware(rMovePos, GetColumnWidth(aPaperSize) + mnColumnSpacing); ++ } ++ } ++ ++ return rMovePos - aOld; ++} ++ ++// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication ++ + void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation ) + { + if ( !GetUpdateMode() && !bStripOnly ) +@@ -2987,6 +3166,7 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + + tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos); + ++ sal_Int32 nColumn = 0; + + // Over all the paragraphs... + +@@ -3018,17 +3198,7 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + + bool bEndOfParagraphWritten(false); + +- if ( !IsVertical() ) +- aStartPos.AdjustY(pPortion->GetFirstLineOffset() ); +- else +- { +- if( IsTopToBottom() ) +- aStartPos.AdjustX( -(pPortion->GetFirstLineOffset()) ); +- else +- aStartPos.AdjustX(pPortion->GetFirstLineOffset() ); +- } +- +- Point aParaStart( aStartPos ); ++ adjustYDirectionAware(aStartPos, pPortion->GetFirstLineOffset()); + + const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL ); + sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) +@@ -3040,34 +3210,13 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + pLine = &pPortion->GetLines()[nLine]; + nIndex = pLine->GetStart(); + DBG_ASSERT( pLine, "NULL-Pointer in the line iterator in UpdateViews" ); ++ tools::Long nLineHeight = pLine->GetHeight(); ++ if (nLine != nLastLine) ++ nLineHeight += nVertLineSpacing; ++ MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin); + aTmpPos = aStartPos; +- if ( !IsVertical() ) +- { +- aTmpPos.AdjustX(pLine->GetStartPosX() ); +- aTmpPos.AdjustY(pLine->GetMaxAscent() ); +- aStartPos.AdjustY(pLine->GetHeight() ); +- if (nLine != nLastLine) +- aStartPos.AdjustY(nVertLineSpacing ); +- } +- else +- { +- if ( IsTopToBottom() ) +- { +- aTmpPos.AdjustY(pLine->GetStartPosX() ); +- aTmpPos.AdjustX( -(pLine->GetMaxAscent()) ); +- aStartPos.AdjustX( -(pLine->GetHeight()) ); +- if (nLine != nLastLine) +- aStartPos.AdjustX( -nVertLineSpacing ); +- } +- else +- { +- aTmpPos.AdjustY( -(pLine->GetStartPosX()) ); +- aTmpPos.AdjustX(pLine->GetMaxAscent() ); +- aStartPos.AdjustX(pLine->GetHeight() ); +- if (nLine != nLastLine) +- aStartPos.AdjustX(nVertLineSpacing ); +- } +- } ++ adjustXDirectionAware(aTmpPos, pLine->GetStartPosX()); ++ adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight); + + if ( ( !IsVertical() && ( aStartPos.Y() > aClipRect.Top() ) ) + || ( IsVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() ) +@@ -3081,7 +3230,9 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + // does, too. No change for not-layouting (painting). + if(0 == nLine) // && !bStripOnly) + { +- GetEditEnginePtr()->PaintingFirstLine( n, aParaStart, aTmpPos.Y(), aOrigin, nOrientation, pOutDev ); ++ Point aLineStart(aStartPos); ++ adjustYDirectionAware(aLineStart, -nLineHeight); ++ GetEditEnginePtr()->PaintingFirstLine( n, aLineStart, aTmpPos.Y(), aOrigin, nOrientation, pOutDev ); + + // Remember whether a bullet was painted. + const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE); +@@ -3100,27 +3251,10 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion]; + + tools::Long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion ); +- if ( !IsVertical() ) +- { +- aTmpPos.setX( aStartPos.X() + nPortionXOffset ); +- if ( aTmpPos.X() > aClipRect.Right() ) +- break; // No further output in line necessary +- } +- else +- { +- if( IsTopToBottom() ) +- { +- aTmpPos.setY( aStartPos.Y() + nPortionXOffset ); +- if ( aTmpPos.Y() > aClipRect.Bottom() ) +- break; // No further output in line necessary +- } +- else +- { +- aTmpPos.setY( aStartPos.Y() - nPortionXOffset ); +- if (aTmpPos.Y() < aClipRect.Top()) +- break; // No further output in line necessary +- } +- } ++ setXDirectionAware(aTmpPos, getXDirectionAware(aStartPos)); ++ adjustXDirectionAware(aTmpPos, nPortionXOffset); ++ if (isXOverflowDirectionAware(aTmpPos, aClipRect)) ++ break; // No further output in line necessary + + switch ( rTextPortion.GetKind() ) + { +@@ -3208,44 +3342,12 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + const tools::Long nAdvanceY = -pLine->GetMaxAscent(); + + Point aTopLeftRectPos( aTmpPos ); +- if ( !IsVertical() ) +- { +- aTopLeftRectPos.AdjustX(nAdvanceX ); +- aTopLeftRectPos.AdjustY(nAdvanceY ); +- } +- else +- { +- if( IsTopToBottom() ) +- { +- aTopLeftRectPos.AdjustY( -nAdvanceX ); +- aTopLeftRectPos.AdjustX(nAdvanceY ); +- } +- else +- { +- aTopLeftRectPos.AdjustY(nAdvanceX ); +- aTopLeftRectPos.AdjustX( -nAdvanceY ); +- } +- } ++ adjustXDirectionAware(aTopLeftRectPos, nAdvanceX); ++ adjustYDirectionAware(aTopLeftRectPos, nAdvanceY); + + Point aBottomRightRectPos( aTopLeftRectPos ); +- if ( !IsVertical() ) +- { +- aBottomRightRectPos.AdjustX(2 * nHalfBlankWidth ); +- aBottomRightRectPos.AdjustY(pLine->GetHeight() ); +- } +- else +- { +- if (IsTopToBottom()) +- { +- aBottomRightRectPos.AdjustX(pLine->GetHeight() ); +- aBottomRightRectPos.AdjustY( -(2 * nHalfBlankWidth) ); +- } +- else +- { +- aBottomRightRectPos.AdjustX( -(pLine->GetHeight()) ); +- aBottomRightRectPos.AdjustY(2 * nHalfBlankWidth ); +- } +- } ++ adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth); ++ adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight()); + + pOutDev->Push( PushFlags::FILLCOLOR ); + pOutDev->Push( PushFlags::LINECOLOR ); +@@ -3271,17 +3373,8 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + const Size aSlashSize = aTmpFont.QuickGetTextSize( pOutDev, aSlash, 0, 1 ); + Point aSlashPos( aTmpPos ); + const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2; +- if ( !IsVertical() ) +- { +- aSlashPos.setX( aTopLeftRectPos.X() + nAddX ); +- } +- else +- { +- if (IsTopToBottom()) +- aSlashPos.setY( aTopLeftRectPos.Y() + nAddX ); +- else +- aSlashPos.setY( aTopLeftRectPos.Y() - nAddX ); +- } ++ setXDirectionAware(aSlashPos, getXDirectionAware(aTopLeftRectPos)); ++ adjustXDirectionAware(aSlashPos, nAddX); + + aTmpFont.QuickDrawText( pOutDev, aSlashPos, aSlash, 0, 1 ); + +@@ -3325,24 +3418,8 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + // what will lead to a compressed look with multiple lines + const sal_uInt16 nMaxAscent(pLine->GetMaxAscent()); + +- if ( !IsVertical() ) +- { +- aStartPos.AdjustY(nMaxAscent ); +- aTmpPos.AdjustY(nMaxAscent ); +- } +- else +- { +- if (IsTopToBottom()) +- { +- aTmpPos.AdjustX( -nMaxAscent ); +- aStartPos.AdjustX( -nMaxAscent ); +- } +- else +- { +- aTmpPos.AdjustX(nMaxAscent ); +- aStartPos.AdjustX(nMaxAscent ); +- } +- } ++ aTmpPos += MoveToNextLine(aStartPos, nMaxAscent, ++ nColumn, aOrigin); + } + std::vector< sal_Int32 >::iterator curIt = itSubLines; + ++itSubLines; +@@ -3509,15 +3586,7 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + if ( aTmpFont.GetEscapement() ) + { + tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L; +- if ( !IsVertical() ) +- aOutPos.AdjustY( -nDiff ); +- else +- { +- if (IsTopToBottom()) +- aOutPos.AdjustX(nDiff ); +- else +- aOutPos.AdjustX( -nDiff ); +- } ++ adjustYDirectionAware(aOutPos, -nDiff); + aRedLineTmpPos = aOutPos; + aTmpFont.SetEscapement( 0 ); + } +@@ -3644,13 +3713,7 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + if( _nEsc ) + { + tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L; +- if( !IsVertical() ) +- aRedLineTmpPos.AdjustY( -nShift ); +- else +- if (IsTopToBottom()) +- aRedLineTmpPos.AdjustX(nShift ); +- else +- aRedLineTmpPos.AdjustX( -nShift ); ++ adjustYDirectionAware(aRedLineTmpPos, -nShift); + } + } + Color aOldColor( pOutDev->GetLineColor() ); +@@ -3764,23 +3827,11 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + + if ( ( nLine != nLastLine ) && !aStatus.IsOutliner() ) + { +- if ( !IsVertical() ) +- aStartPos.AdjustY(nSBL ); +- else +- { +- if( IsTopToBottom() ) +- aStartPos.AdjustX( -nSBL ); +- else +- aStartPos.AdjustX(nSBL ); +- } ++ adjustYDirectionAware(aStartPos, nSBL); + } + + // no more visible actions? +- if ( !IsVertical() && ( aStartPos.Y() >= aClipRect.Bottom() ) ) +- break; +- else if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() <= aClipRect.Left() ) ) +- break; +- else if (IsVertical() && !IsTopToBottom() && (aStartPos.X() >= aClipRect.Right())) ++ if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } + +@@ -3788,15 +3839,7 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + { + const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE ); + tools::Long nUL = GetYValue( rULItem.GetLower() ); +- if ( !IsVertical() ) +- aStartPos.AdjustY(nUL ); +- else +- { +- if (IsTopToBottom()) +- aStartPos.AdjustX( -nUL ); +- else +- aStartPos.AdjustX(nUL ); +- } ++ adjustYDirectionAware(aStartPos, nUL); + } + + // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion +@@ -3822,26 +3865,14 @@ void ImpEditEngine::Paint( OutputDevice* pOutDev, tools::Rectangle aClipRect, Po + } + else + { +- if ( !IsVertical() ) +- aStartPos.AdjustY(nParaHeight ); +- else +- { +- if (IsTopToBottom()) +- aStartPos.AdjustX( -nParaHeight ); +- else +- aStartPos.AdjustX(nParaHeight ); +- } ++ adjustYDirectionAware(aStartPos, nParaHeight); + } + + if ( pPDFExtOutDevData ) + pPDFExtOutDevData->EndStructureElement(); + + // no more visible actions? +- if ( !IsVertical() && ( aStartPos.Y() > aClipRect.Bottom() ) ) +- break; +- if ( IsVertical() && IsTopToBottom() && ( aStartPos.X() < aClipRect.Left() ) ) +- break; +- if (IsVertical() && !IsTopToBottom() && ( aStartPos.X() > aClipRect.Right() ) ) ++ if (getYOverflowDirectionAware(aStartPos, aClipRect)) + break; + } + } +@@ -3861,26 +3892,16 @@ void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, Ou + + Point aStartPos; + if ( !IsVertical() ) +- { + aStartPos = pView->GetOutputArea().TopLeft(); +- aStartPos.AdjustX( -(pView->GetVisDocLeft()) ); +- aStartPos.AdjustY( -(pView->GetVisDocTop()) ); +- } + else + { + if( IsTopToBottom() ) +- { + aStartPos = pView->GetOutputArea().TopRight(); +- aStartPos.AdjustX(pView->GetVisDocTop() ); +- aStartPos.AdjustY( -(pView->GetVisDocLeft()) ); +- } + else +- { + aStartPos = pView->GetOutputArea().BottomLeft(); +- aStartPos.AdjustX( -(pView->GetVisDocTop()) ); +- aStartPos.AdjustY(pView->GetVisDocLeft() ); +- } + } ++ adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft())); ++ adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop())); + + // If Doc-width < Output Area,Width and not wrapped fields, + // the fields usually protrude if > line. +@@ -4029,7 +4050,7 @@ EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNew + { + aInvalidRect = tools::Rectangle(); // make empty + aInvalidRect.SetLeft( 0 ); +- aInvalidRect.SetRight( aPaperSize.Width() ); ++ aInvalidRect.SetRight(GetColumnWidth(aPaperSize)); + aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) ); + aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() ); + +@@ -4159,19 +4180,14 @@ tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const + } + } + +- tools::Long nTotalSpace = IsVertical() ? aPaperSize.Width() : aPaperSize.Height(); ++ tools::Long nTotalSpace = getHeightDirectionAware(aPaperSize); + nTotalSpace -= nTotalOccupiedHeight; + if (nTotalSpace <= 0 || nTotalLineCount <= 1) + return 0; + ++ // Shift the text to the right for the asian layout mode. + if (IsVertical()) +- { +- if( IsTopToBottom() ) +- // Shift the text to the right for the asian layout mode. +- rStartPos.AdjustX(nTotalSpace ); +- else +- rStartPos.AdjustX( -nTotalSpace ); +- } ++ adjustYDirectionAware(rStartPos, -nTotalSpace); + + return nTotalSpace / (nTotalLineCount-1); + } +@@ -4248,7 +4264,7 @@ void ImpEditEngine::SetFlatMode( bool bFlat ) + + void ImpEditEngine::SetCharStretching( sal_uInt16 nX, sal_uInt16 nY ) + { +- bool bChanged(false); ++ bool bChanged; + if ( !IsVertical() ) + { + bChanged = nStretchX!=nX || nStretchY!=nY; +@@ -4632,10 +4648,10 @@ void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* + } + } + +-void ImpEditEngine::ImplUpdateOverflowingParaNum(sal_uInt32 nPaperHeight) ++void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight) + { +- sal_uInt32 nY = 0; +- sal_uInt32 nPH; ++ tools::Long nY = 0; ++ tools::Long nPH; + + for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) { + ParaPortion* pPara = GetParaPortions()[nPara]; +@@ -4651,12 +4667,12 @@ void ImpEditEngine::ImplUpdateOverflowingParaNum(sal_uInt32 nPaperHeight) + } + } + +-void ImpEditEngine::ImplUpdateOverflowingLineNum(sal_uInt32 nPaperHeight, ++void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight, + sal_uInt32 nOverflowingPara, +- sal_uInt32 nHeightBeforeOverflowingPara) ++ tools::Long nHeightBeforeOverflowingPara) + { +- sal_uInt32 nY = nHeightBeforeOverflowingPara; +- sal_uInt32 nLH; ++ tools::Long nY = nHeightBeforeOverflowingPara; ++ tools::Long nLH; + + ParaPortion *pPara = GetParaPortions()[nOverflowingPara]; + +diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx +index e42a36c5e826..c41b8169a4cb 100644 +--- a/editeng/source/editeng/impedit4.cxx ++++ b/editeng/source/editeng/impedit4.cxx +@@ -1094,7 +1094,7 @@ std::unique_ptr ImpEditEngine::CreateTextObject( EditSelection a + // sleeper set up when Olli paragraphs not hacked! + if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && GetUpdateMode() && ( nTextPortions >= nBigObjectStart ) ) + { +- XParaPortionList* pXList = new XParaPortionList( GetRefDevice(), aPaperSize.Width(), nStretchX, nStretchY ); ++ XParaPortionList* pXList = new XParaPortionList( GetRefDevice(), GetColumnWidth(aPaperSize), nStretchX, nStretchY ); + pTxtObj->mpImpl->SetPortionInfo(std::unique_ptr(pXList)); + for ( nNode = nStartNode; nNode <= nEndNode; nNode++ ) + { +@@ -1177,7 +1177,7 @@ EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject + bool bUsePortionInfo = false; + XParaPortionList* pPortionInfo = rTextObject.mpImpl->GetPortionInfo(); + +- if ( pPortionInfo && ( static_cast(pPortionInfo->GetPaperWidth()) == aPaperSize.Width() ) ++ if ( pPortionInfo && ( static_cast(pPortionInfo->GetPaperWidth()) == GetColumnWidth(aPaperSize) ) + && ( pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode() ) + && ( pPortionInfo->GetStretchX() == nStretchX ) + && ( pPortionInfo->GetStretchY() == nStretchY ) ) +diff --git a/editeng/source/outliner/outlin2.cxx b/editeng/source/outliner/outlin2.cxx +index ff3d1583a5f1..ba556de35c7a 100644 +--- a/editeng/source/outliner/outlin2.cxx ++++ b/editeng/source/outliner/outlin2.cxx +@@ -223,6 +223,11 @@ void Outliner::SetMaxAutoPaperSize( const Size& rSz ) + pEditEngine->SetMaxAutoPaperSize( rSz ); + } + ++void Outliner::SetMinColumnWrapHeight(tools::Long nVal) ++{ ++ pEditEngine->SetMinColumnWrapHeight(nVal); ++} ++ + bool Outliner::IsExpanded( Paragraph const * pPara ) const + { + return pParaList->HasVisibleChildren( pPara ); +@@ -532,6 +537,11 @@ bool Outliner::IsTopToBottom() const + return pEditEngine->IsTopToBottom(); + } + ++void Outliner::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing) ++{ ++ pEditEngine->SetTextColumns(nColumns, nSpacing); ++} ++ + void Outliner::SetFixedCellHeight( bool bUseFixedCellHeight ) + { + pEditEngine->SetFixedCellHeight( bUseFixedCellHeight ); +diff --git a/include/editeng/editeng.hxx b/include/editeng/editeng.hxx +index 575e43d49e3f..18b136037acb 100644 +--- a/include/editeng/editeng.hxx ++++ b/include/editeng/editeng.hxx +@@ -242,6 +242,8 @@ public: + void SetRotation(TextRotation nRotation); + TextRotation GetRotation() const; + ++ void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing); ++ + void SetFixedCellHeight( bool bUseFixedCellHeight ); + + void SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ); +@@ -270,6 +272,8 @@ public: + const Size& GetMaxAutoPaperSize() const; + void SetMaxAutoPaperSize( const Size& rSz ); + ++ void SetMinColumnWrapHeight(tools::Long nVal); ++ + OUString GetText( LineEnd eEnd = LINEEND_LF ) const; + OUString GetText( const ESelection& rSelection ) const; + sal_uInt32 GetTextLen() const; +diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx +index a6bc9fbd7ff2..d063040e4bc9 100644 +--- a/include/editeng/outliner.hxx ++++ b/include/editeng/outliner.hxx +@@ -657,6 +657,8 @@ public: + bool IsVertical() const; + bool IsTopToBottom() const; + ++ void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing); ++ + void SetFixedCellHeight( bool bUseFixedCellHeight ); + + void SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ); +@@ -786,6 +788,8 @@ public: + const Size& GetMaxAutoPaperSize() const; + void SetMaxAutoPaperSize( const Size& rSz ); + ++ void SetMinColumnWrapHeight(tools::Long nVal); ++ + void SetDefTab( sal_uInt16 nTab ); + + bool IsFlatMode() const; +diff --git a/include/oox/ppt/pptimport.hxx b/include/oox/ppt/pptimport.hxx +index e96f04b70374..f04cb632e574 100644 +--- a/include/oox/ppt/pptimport.hxx ++++ b/include/oox/ppt/pptimport.hxx +@@ -77,8 +77,6 @@ public: + + ::Color getSchemeColor( sal_Int32 nToken ) const; + +- static std::vector< PPTShape* > maPPTShapes; +- + #if OSL_DEBUG_LEVEL > 0 + static XmlFilterBase* mpDebugFilterBase; + #endif +diff --git a/include/oox/ppt/pptshape.hxx b/include/oox/ppt/pptshape.hxx +index e67a77635111..f452e585abcf 100644 +--- a/include/oox/ppt/pptshape.hxx ++++ b/include/oox/ppt/pptshape.hxx +@@ -66,8 +66,7 @@ public: + const oox::drawingml::Theme* pTheme, + const css::uno::Reference< css::drawing::XShapes >& rxShapes, + basegfx::B2DHomMatrix& aTransformation, +- ::oox::drawingml::ShapeIdMap* pShapeMap, +- bool bhasSameSubTypeIndex = false ); ++ ::oox::drawingml::ShapeIdMap* pShapeMap ); + + ShapeLocation getShapeLocation() const { return meShapeLocation; }; + void setReferenced( bool bReferenced ){ mbReferenced = bReferenced; }; +diff --git a/include/svl/solar.hrc b/include/svl/solar.hrc +index 317d45a84bc1..521e24365f9f 100644 +--- a/include/svl/solar.hrc ++++ b/include/svl/solar.hrc +@@ -23,7 +23,7 @@ + // defines ------------------------------------------------------------------ + + #define OWN_ATTR_VALUE_START 3900 +-#define OWN_ATTR_VALUE_END 4005 ++#define OWN_ATTR_VALUE_END 4006 + + #define RID_LIB_START 10000 + #define RID_LIB_END 19999 +diff --git a/include/svx/SvxXTextColumns.hxx b/include/svx/SvxXTextColumns.hxx +new file mode 100644 +index 000000000000..0dbc92ba9611 +--- /dev/null ++++ b/include/svx/SvxXTextColumns.hxx +@@ -0,0 +1,22 @@ ++/* -*- 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 ++ ++#include ++#include ++ ++#include ++ ++SVXCORE_DLLPUBLIC css::uno::Reference ++SvxXTextColumns_createInstance() noexcept; ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ +diff --git a/include/svx/dialogs.hrc b/include/svx/dialogs.hrc +index 5dc3fc6faa58..f03600a74fe0 100644 +--- a/include/svx/dialogs.hrc ++++ b/include/svx/dialogs.hrc +@@ -45,6 +45,7 @@ + #define RID_SVXPAGE_TRANSPARENCE (RID_SVX_START + 54) + #define RID_SVXPAGE_TEXTATTR (RID_SVX_START + 153) + #define RID_SVXPAGE_TEXTANIMATION (RID_SVX_START + 184) ++#define RID_SVXPAGE_TEXTCOLUMNS (RID_SVX_START + 154) + #define RID_SVXPAGE_MEASURE (RID_SVX_START + 161) + #define RID_SVXPAGE_CONNECTION (RID_SVX_START + 191) + #define RID_SVXPAGE_LINE_DEF (RID_SVX_START + 52) +diff --git a/include/svx/strings.hrc b/include/svx/strings.hrc +index 95e9690b0b9e..50231f03bcc4 100644 +--- a/include/svx/strings.hrc ++++ b/include/svx/strings.hrc +@@ -401,6 +401,8 @@ + #define SIP_SA_TEXT_ANIDELAY NC_("SIP_SA_TEXT_ANIDELAY", "Speed of ticker") + #define SIP_SA_TEXT_ANIAMOUNT NC_("SIP_SA_TEXT_ANIAMOUNT", "Ticker step size") + #define SIP_SA_TEXT_CONTOURFRAME NC_("SIP_SA_TEXT_CONTOURFRAME", "Outline text flow") ++#define SIP_SA_TEXTCOLUMNS_NUMBER NC_("SIP_SA_TEXTCOLUMNS_NUMBER", "Columns number") ++#define SIP_SA_TEXTCOLUMNS_SPACING NC_("SIP_SA_TEXTCOLUMNS_SPACING", "Columns spacing") + #define SIP_SA_XMLATTRIBUTES NC_("SIP_SA_XMLATTRIBUTES", "User-defined attributes") + #define SIP_SA_TEXT_USEFIXEDCELLHEIGHT NC_("SIP_SA_TEXT_USEFIXEDCELLHEIGHT", "Use font-independent line spacing") + #define SIP_SA_WORDWRAP NC_("SIP_SA_WORDWRAP", "Word wrap text in shape") +diff --git a/include/svx/svddef.hxx b/include/svx/svddef.hxx +index 30031e20dde2..f4b5c0986d7c 100644 +--- a/include/svx/svddef.hxx ++++ b/include/svx/svddef.hxx +@@ -138,6 +138,7 @@ class SdrVertShearAllItem; + class SdrVertShearOneItem; + class SdrYesNoItem; + class SfxBoolItem; ++class SfxInt16Item; + class SfxUInt16Item; + class SfxUInt32Item; + class SfxStringItem; +@@ -426,7 +427,12 @@ constexpr sal_uInt16 SDRATTR_SOFTEDGE_FIRST(SDRATTR_GLOW_LAST + + constexpr TypedWhichId SDRATTR_SOFTEDGE_RADIUS(SDRATTR_SOFTEDGE_FIRST + 0); + constexpr sal_uInt16 SDRATTR_SOFTEDGE_LAST(SDRATTR_SOFTEDGE_RADIUS); + +-constexpr sal_uInt16 SDRATTR_END (SDRATTR_SOFTEDGE_LAST); /* 1357 */ /* 1333 V4+++*/ /* 1243 V4+++*/ /*1213*/ /*1085*/ /*1040*/ /*Pool V2: 1123,V1: 1065 */ ++constexpr sal_uInt16 SDRATTR_TEXTCOLUMNS_FIRST(SDRATTR_SOFTEDGE_LAST + 1); ++constexpr TypedWhichId SDRATTR_TEXTCOLUMNS_NUMBER(SDRATTR_TEXTCOLUMNS_FIRST + 0); ++constexpr TypedWhichId SDRATTR_TEXTCOLUMNS_SPACING(SDRATTR_TEXTCOLUMNS_FIRST + 1); ++constexpr sal_uInt16 SDRATTR_TEXTCOLUMNS_LAST(SDRATTR_TEXTCOLUMNS_SPACING); ++ ++constexpr sal_uInt16 SDRATTR_END (SDRATTR_TEXTCOLUMNS_LAST); /* 1357 */ /* 1333 V4+++*/ /* 1243 V4+++*/ /*1213*/ /*1085*/ /*1040*/ /*Pool V2: 1123,V1: 1065 */ + + #endif // INCLUDED_SVX_SVDDEF_HXX + +diff --git a/include/svx/svdotext.hxx b/include/svx/svdotext.hxx +index 9d74bffec93e..26ed2d4c6e2c 100644 +--- a/include/svx/svdotext.hxx ++++ b/include/svx/svdotext.hxx +@@ -389,7 +389,7 @@ public: + // FitToSize and Fontwork are not taken into account in GetTextSize()! + virtual const Size& GetTextSize() const; + void FitFrameToTextSize(); +- double GetFontScaleY() const; ++ sal_uInt16 GetFontScaleY() const; + + // Simultaneously sets the text into the Outliner (possibly + // the one of the EditOutliner) and sets the PaperSize. +@@ -436,6 +436,13 @@ public: + SdrTextAniKind GetTextAniKind() const; + SdrTextAniDirection GetTextAniDirection() const; + ++ bool HasTextColumnsNumber() const; ++ sal_Int16 GetTextColumnsNumber() const; ++ void SetTextColumnsNumber(sal_Int16 nColumns); ++ bool HasTextColumnsSpacing() const; ++ sal_Int32 GetTextColumnsSpacing() const; ++ void SetTextColumnsSpacing(sal_Int32 nSpacing); ++ + // react on model/page change + virtual void handlePageChange(SdrPage* pOldPage, SdrPage* pNewPage) override; + +diff --git a/include/svx/unoshprp.hxx b/include/svx/unoshprp.hxx +index 20a49f1f4d62..98bea3cd1c09 100644 +--- a/include/svx/unoshprp.hxx ++++ b/include/svx/unoshprp.hxx +@@ -60,6 +60,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -193,7 +194,9 @@ + #define OWN_ATTR_SIGNATURELINE_IS_SIGNED (OWN_ATTR_VALUE_START+103) + #define OWN_ATTR_QRCODE (OWN_ATTR_VALUE_START+104) + #define OWN_ATTR_TEXTFITTOSIZESCALE (OWN_ATTR_VALUE_START+105) +-// ATTENTION: maximum is OWN_ATTR_VALUE_START+105 svx, see include/svl/solar.hrc ++#define OWN_ATTR_TEXTCOLUMNS (OWN_ATTR_VALUE_START+106) ++// ATTENTION: current maximum is OWN_ATTR_VALUE_START+106 svx; wnen adding values, update ++// OWN_ATTR_VALUE_END in include/svl/solar.hrc accordingly + + // #FontWork# + #define FONTWORK_PROPERTIES \ +@@ -316,6 +319,7 @@ + { u"" UNO_NAME_TEXT_VERTADJUST, SDRATTR_TEXT_VERTADJUST, cppu::UnoType::get(), 0, 0},\ + { u"" UNO_NAME_TEXT_WORDWRAP, SDRATTR_TEXT_WORDWRAP, cppu::UnoType::get(), 0, 0}, \ + { u"" UNO_NAME_TEXT_CHAINNEXTNAME, SDRATTR_TEXT_CHAINNEXTNAME, ::cppu::UnoType::get(), 0, 0}, \ ++ { u"TextColumns", OWN_ATTR_TEXTCOLUMNS, cppu::UnoType::get(), 0, 0 }, \ + SVX_UNOEDIT_CHAR_PROPERTIES, \ + SVX_UNOEDIT_PARA_PROPERTIES, + +diff --git a/offapi/com/sun/star/drawing/TextProperties.idl b/offapi/com/sun/star/drawing/TextProperties.idl +index 4516c9829781..eb045624aba0 100644 +--- a/offapi/com/sun/star/drawing/TextProperties.idl ++++ b/offapi/com/sun/star/drawing/TextProperties.idl +@@ -40,6 +40,7 @@ + #include + #include + #include ++#include + + + module com { module sun { module star { module drawing { +@@ -249,6 +250,13 @@ published service TextProperties + /** This value selects the writing mode for the text. + */ + [property] ::com::sun::star::text::WritingMode TextWritingMode; ++ ++ ++ /** Column layout properties for the text. ++ ++ @since LibreOffice 7.2 ++ */ ++ [optional, property] ::com::sun::star::text::XTextColumns TextColumns; + }; + + +diff --git a/oox/inc/drawingml/table/tableproperties.hxx b/oox/inc/drawingml/table/tableproperties.hxx +index ec8b3c4c5b60..34e361b18add 100644 +--- a/oox/inc/drawingml/table/tableproperties.hxx ++++ b/oox/inc/drawingml/table/tableproperties.hxx +@@ -58,9 +58,6 @@ public: + const css::uno::Reference < css::beans::XPropertySet > & xPropSet, + const ::oox::drawingml::TextListStylePtr& pMasterTextListStyle ); + +- /// Distributes text body with multiple columns in table cells. +- void pullFromTextBody(oox::drawingml::TextBodyPtr pTextBody, sal_Int32 nShapeWidth, bool bhasSameSubTypeIndex, bool bMaster); +- + private: + + const TableStyle& getUsedTableStyle(const ::oox::core::XmlFilterBase& rFilterBase, std::unique_ptr& rTableStyleToDelete); +diff --git a/oox/inc/drawingml/textbodyproperties.hxx b/oox/inc/drawingml/textbodyproperties.hxx +index 8a51c2bb906e..41fbb832a5d8 100644 +--- a/oox/inc/drawingml/textbodyproperties.hxx ++++ b/oox/inc/drawingml/textbodyproperties.hxx +@@ -42,8 +42,6 @@ struct TextBodyProperties + std::optional< sal_Int32 > moTextOffRight; + css::drawing::TextVerticalAdjust meVA; + OUString msPrst; +- /// Number of requested columns. +- sal_Int32 mnNumCol = 1; + /// Normal autofit: font scale (default: 100%). + sal_Int32 mnFontScale = 100000; + OUString msHorzOverflow; +diff --git a/oox/source/drawingml/table/tableproperties.cxx b/oox/source/drawingml/table/tableproperties.cxx +index 1c12c10eda47..69117123dca8 100644 +--- a/oox/source/drawingml/table/tableproperties.cxx ++++ b/oox/source/drawingml/table/tableproperties.cxx +@@ -145,7 +145,7 @@ void TableProperties::pushToPropSet(const ::oox::core::XmlFilterBase& rFilterBas + for (auto& tableRow : mvTableRows) + { + sal_Int32 nColumn = 0; +- sal_Int32 nColumnSize = mvTableGrid.size(); ++ sal_Int32 nColumnSize = tableRow.getTableCells().size(); + sal_Int32 nRemovedColumn = 0; // + + for (sal_Int32 nColIndex = 0; nColIndex < nColumnSize; nColIndex++) +@@ -206,65 +206,6 @@ void TableProperties::pushToPropSet(const ::oox::core::XmlFilterBase& rFilterBas + + xTableStyleToDelete.reset(); + } +- +-void TableProperties::pullFromTextBody(oox::drawingml::TextBodyPtr pTextBody, sal_Int32 nShapeWidth, bool bhasSameSubTypeIndex, bool bMaster) +-{ +- // Create table grid and a single row. +- sal_Int32 nNumCol = pTextBody->getTextProperties().mnNumCol; +- std::vector& rTableGrid(getTableGrid()); +- std::vector& rTableRows(getTableRows()); +- sal_Int32 nColWidth = nShapeWidth / nNumCol; +- +- if(!bhasSameSubTypeIndex) +- { +- for (sal_Int32 nCol = 0; nCol < nNumCol; ++nCol) +- rTableGrid.push_back(nColWidth); +- +- rTableRows.emplace_back(); +- } +- +- if(rTableRows.empty()) +- rTableRows.emplace_back(); +- +- oox::drawingml::table::TableRow& rTableRow = rTableRows.back(); +- std::vector& rTableCells = rTableRow.getTableCells(); +- +- // Create the cells and distribute the paragraphs from pTextBody. +- sal_Int32 nNumPara = pTextBody->getParagraphs().size(); +- sal_Int32 nParaPerCol = std::ceil(double(nNumPara) / nNumCol); +- // Font scale of text body will be applied at a text run level. +- sal_Int32 nFontScale = pTextBody->getTextProperties().mnFontScale; +- size_t nPara = 0; +- for (sal_Int32 nCol = 0; nCol < nNumCol; ++nCol) +- { +- rTableCells.emplace_back(); +- oox::drawingml::table::TableCell& rTableCell = rTableCells.at(nCol); +- TextBodyPtr pCellTextBody = std::make_shared(); +- rTableCell.setTextBody(pCellTextBody); +- +- // Copy properties provided by . +- pCellTextBody->getTextListStyle() = pTextBody->getTextListStyle(); +- +- if (bMaster) +- continue; +- +- for (sal_Int32 nParaInCol = 0; nParaInCol < nParaPerCol; ++nParaInCol) +- { +- if (nPara < pTextBody->getParagraphs().size()) +- { +- std::shared_ptr pParagraph +- = pTextBody->getParagraphs()[nPara]; +- if (nFontScale != 100000) +- { +- for (auto& pRun : pParagraph->getRuns()) +- pRun->getTextCharacterProperties().moFontScale = nFontScale; +- } +- pCellTextBody->appendParagraph(pParagraph); +- } +- ++nPara; +- } +- } +-} + } + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/oox/source/drawingml/textbodypropertiescontext.cxx b/oox/source/drawingml/textbodypropertiescontext.cxx +index 4d94191dcfc7..e2811d6667ba 100644 +--- a/oox/source/drawingml/textbodypropertiescontext.cxx ++++ b/oox/source/drawingml/textbodypropertiescontext.cxx +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -32,6 +33,7 @@ + #include + #include + #include ++#include + + using namespace ::oox::core; + using namespace ::com::sun::star; +@@ -85,7 +87,17 @@ TextBodyPropertiesContext::TextBodyPropertiesContext( ContextHandler2Helper cons + mrTextBodyProp.msVertOverflow = rAttribs.getString(XML_vertOverflow, ""); + + // ST_TextColumnCount +- mrTextBodyProp.mnNumCol = rAttribs.getInteger( XML_numCol, 1 ); ++ if (const sal_Int32 nColumns = rAttribs.getInteger(XML_numCol, 0); nColumns > 0) ++ { ++ css::uno::Reference xCols(SvxXTextColumns_createInstance(), ++ css::uno::UNO_QUERY_THROW); ++ xCols->setColumnCount(nColumns); ++ css::uno::Reference xProps(xCols, css::uno::UNO_QUERY_THROW); ++ // ST_PositiveCoordinate32 ++ const sal_Int32 nSpacing = convertEmuToHmm(rAttribs.getInteger(XML_spcCol, 0)); ++ xProps->setPropertyValue("AutomaticDistance", css::uno::Any(nSpacing)); ++ mrTextBodyProp.maPropertyMap.setAnyProperty(PROP_TextColumns, css::uno::Any(xCols)); ++ } + + // ST_Angle + mrTextBodyProp.moRotation = rAttribs.getInteger( XML_rot ); +diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx +index 8cfd4a6dabc5..263cd5c06b67 100644 +--- a/oox/source/export/drawingml.cxx ++++ b/oox/source/export/drawingml.cxx +@@ -85,6 +85,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3060,6 +3061,22 @@ void DrawingML::WriteText(const Reference& rXIface, bool bBodyPr, bo + sal_Int32 nShapeTextRotateAngle = 0; + if (GetProperty(xTextSet, "RotateAngle")) + nShapeTextRotateAngle = rXPropSet->getPropertyValue("RotateAngle").get() / 300; ++ sal_Int16 nCols = 0; ++ sal_Int32 nColSpacing = -1; ++ if (GetProperty(rXPropSet, "TextColumns")) ++ { ++ if (css::uno::Reference xCols{ mAny, css::uno::UNO_QUERY }) ++ { ++ nCols = xCols->getColumnCount(); ++ if (css::uno::Reference xProps{ mAny, ++ css::uno::UNO_QUERY }) ++ { ++ if (GetProperty(xProps, "AutomaticDistance")) ++ mAny >>= nColSpacing; ++ } ++ } ++ } ++ + std::optional isUpright; + if (GetProperty(rXPropSet, "InteropGrabBag")) + { +@@ -3114,6 +3131,8 @@ void DrawingML::WriteText(const Reference& rXIface, bool bBodyPr, bo + } + + mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, ++ XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0), ++ XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0), + XML_wrap, pWrap, + XML_horzOverflow, sHorzOverflow, + XML_vertOverflow, sVertOverflow, +@@ -3229,10 +3248,7 @@ void DrawingML::WriteText(const Reference& rXIface, bool bBodyPr, bo + { + SdrTextObj* pTextObject = dynamic_cast(pTextShape->GetSdrObject()); + if (pTextObject) +- { +- double fScaleY = pTextObject->GetFontScaleY(); +- nFontScale = static_cast(fScaleY * 100) * 1000; +- } ++ nFontScale = pTextObject->GetFontScaleY() * 1000; + } + + mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale, +diff --git a/oox/source/ppt/pptimport.cxx b/oox/source/ppt/pptimport.cxx +index f7d00920705e..dea04a98e9a3 100644 +--- a/oox/source/ppt/pptimport.cxx ++++ b/oox/source/ppt/pptimport.cxx +@@ -70,7 +70,6 @@ PowerPointImport::PowerPointImport( const Reference< XComponentContext >& rxCont + + PowerPointImport::~PowerPointImport() + { +- maPPTShapes.clear(); + } + + /// Visits the relations from pRelations which are of type rType. +diff --git a/oox/source/ppt/pptshape.cxx b/oox/source/ppt/pptshape.cxx +index 96ca319f2a8e..e326cba50464 100644 +--- a/oox/source/ppt/pptshape.cxx ++++ b/oox/source/ppt/pptshape.cxx +@@ -114,8 +114,7 @@ void PPTShape::addShape( + const oox::drawingml::Theme* pTheme, + const Reference< XShapes >& rxShapes, + basegfx::B2DHomMatrix& aTransformation, +- ::oox::drawingml::ShapeIdMap* pShapeMap, +- bool bhasSameSubTypeIndex ) ++ ::oox::drawingml::ShapeIdMap* pShapeMap ) + { + SAL_INFO("oox.ppt","add shape id: " << msId << " location: " << ((meShapeLocation == Master) ? "master" : ((meShapeLocation == Slide) ? "slide" : ((meShapeLocation == Layout) ? "layout" : "other"))) << " subtype: " << mnSubType << " service: " << msServiceName); + // only placeholder from layout are being inserted +@@ -225,36 +224,6 @@ void PPTShape::addShape( + } + } + +- if (sServiceName != "com.sun.star.drawing.TableShape") +- { +- if (TextBodyPtr pTextBody = getTextBody()) +- { +- // If slide shape has not numCol but placeholder has we should inherit from placeholder. +- if (pTextBody->getTextProperties().mnNumCol == 1 && +- mnSubType && +- getSubTypeIndex().has() && +- rSlidePersist.getMasterPersist()) +- { +- oox::drawingml::ShapePtr pPlaceholder = PPTShape::findPlaceholderByIndex( +- getSubTypeIndex().get(), +- rSlidePersist.getMasterPersist()->getShapes()->getChildren()); +- if (pPlaceholder && pPlaceholder->getTableProperties()) +- pTextBody->getTextProperties().mnNumCol = pPlaceholder->getTableProperties()->getTableGrid().size(); +- } +- +- sal_Int32 nNumCol = pTextBody->getTextProperties().mnNumCol; +- if (nNumCol > 1) +- { +- // This shape is not a table, but has multiple columns, +- // represent that as a table. +- sServiceName = "com.sun.star.drawing.TableShape"; +- oox::drawingml::table::TablePropertiesPtr pTableProperties = getTableProperties(); +- pTableProperties->pullFromTextBody(pTextBody, maSize.Width, bhasSameSubTypeIndex, meShapeLocation == Layout); +- setTextBody(nullptr); +- } +- } +- } +- + SAL_INFO("oox.ppt","shape service: " << sServiceName); + + if (mnSubType && getSubTypeIndex().has() && meShapeLocation == Layout) +diff --git a/oox/source/ppt/slidepersist.cxx b/oox/source/ppt/slidepersist.cxx +index dc18ec06e128..74bd8165bc7a 100644 +--- a/oox/source/ppt/slidepersist.cxx ++++ b/oox/source/ppt/slidepersist.cxx +@@ -23,7 +23,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -35,7 +34,6 @@ + #include + #include + #include +-#include + + #include + +@@ -54,8 +52,6 @@ using namespace ::com::sun::star::animations; + + namespace oox::ppt { + +-std::vector< PPTShape* > PowerPointImport::maPPTShapes; +- + SlidePersist::SlidePersist( XmlFilterBase& rFilter, bool bMaster, bool bNotes, + const css::uno::Reference< css::drawing::XDrawPage >& rxPage, + oox::drawingml::ShapePtr const & pShapesPtr, const drawingml::TextListStylePtr & pDefaultTextStyle ) +@@ -132,29 +128,12 @@ sal_Int16 SlidePersist::getLayoutFromValueToken() const + return nLayout; + } + +-static bool hasSameSubTypeIndex(sal_Int32 checkSubTypeIndex) +-{ +- sal_Int32 nSubTypeIndex = -1; +- for(PPTShape* pPPTShape : PowerPointImport::maPPTShapes) +- { +- if(!pPPTShape->getSubTypeIndex().has()) +- continue; +- +- nSubTypeIndex = pPPTShape->getSubTypeIndex().get(); +- +- if( nSubTypeIndex == checkSubTypeIndex ) +- return true; +- } +- return false; +-} + void SlidePersist::createXShapes( XmlFilterBase& rFilterBase ) + { + applyTextStyles( rFilterBase ); + + Reference< XShapes > xShapes( getPage() ); + std::vector< oox::drawingml::ShapePtr >& rShapes( maShapesPtr->getChildren() ); +- bool bhasSameSubTypeIndex = false; +- sal_Int32 nNumCol = 1; + + for (auto const& shape : rShapes) + { +@@ -164,17 +143,7 @@ void SlidePersist::createXShapes( XmlFilterBase& rFilterBase ) + PPTShape* pPPTShape = dynamic_cast< PPTShape* >( child.get() ); + basegfx::B2DHomMatrix aTransformation; + if ( pPPTShape ) +- { +- bhasSameSubTypeIndex = hasSameSubTypeIndex( pPPTShape->getSubTypeIndex().get()); +- +- if(pPPTShape->getTextBody()) +- nNumCol = pPPTShape->getTextBody()->getTextProperties().mnNumCol; +- +- if(pPPTShape->getSubTypeIndex().has() && nNumCol > 1 ) +- PowerPointImport::maPPTShapes.push_back(pPPTShape); +- +- pPPTShape->addShape( rFilterBase, *this, getTheme().get(), xShapes, aTransformation, &getShapeMap(), bhasSameSubTypeIndex ); +- } ++ pPPTShape->addShape( rFilterBase, *this, getTheme().get(), xShapes, aTransformation, &getShapeMap() ); + else + child->addShape( rFilterBase, getTheme().get(), xShapes, aTransformation, maShapesPtr->getFillProperties(), &getShapeMap() ); + } +diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt +index d50cd4bb124d..980c1bb8c0f2 100644 +--- a/oox/source/token/properties.txt ++++ b/oox/source/token/properties.txt +@@ -528,6 +528,7 @@ TextBox + TextBreak + TextCameraZRotateAngle + TextColor ++TextColumns + TextFitToSize + TextFrames + TextHorizontalAdjust +diff --git a/sc/qa/uitest/chart/chartLegend.py b/sc/qa/uitest/chart/chartLegend.py +index 7e2e085d135b..7ee844008d40 100644 +--- a/sc/qa/uitest/chart/chartLegend.py ++++ b/sc/qa/uitest/chart/chartLegend.py +@@ -13,6 +13,7 @@ from libreoffice.calc.document import get_cell_by_position + from libreoffice.uno.propertyvalue import mkPropertyValues + from uitest.uihelper.common import get_state_as_dict, type_text + from uitest.debug import sleep ++from uitest.uihelper import guarded + import org.libreoffice.unotest + import pathlib + +@@ -23,119 +24,91 @@ def get_url_for_data_file(file_name): + + class chartLegend(UITestCase): + def test_chart_display_legend_dialog(self): +- calc_doc = self.ui_test.load_file(get_url_for_data_file("tdf98390.ods")) +- xCalcDoc = self.xUITest.getTopFocusWindow() +- gridwin = xCalcDoc.getChild("grid_window") +- document = self.ui_test.get_component() +- +- gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) +- gridwin.executeAction("ACTIVATE", tuple()) +- xChartMainTop = self.xUITest.getTopFocusWindow() +- xChartMain = xChartMainTop.getChild("chart_window") +- xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") +- self.ui_test.execute_dialog_through_action(xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) +- xDialog = self.xUITest.getTopFocusWindow() +- +- left = xDialog.getChild("left") +- right = xDialog.getChild("right") +- top = xDialog.getChild("top") +- bottom = xDialog.getChild("bottom") +- +- left.executeAction("CLICK", tuple()) +- +- xOKBtn = xDialog.getChild("ok") +- self.ui_test.close_dialog_through_button(xOKBtn) +- +- #reopen and verify InsertMenuLegend dialog +- gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) +- gridwin.executeAction("ACTIVATE", tuple()) +- xChartMainTop = self.xUITest.getTopFocusWindow() +- xChartMain = xChartMainTop.getChild("chart_window") +- xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") +- self.ui_test.execute_dialog_through_action(xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) +- xDialog = self.xUITest.getTopFocusWindow() +- +- left = xDialog.getChild("left") +- right = xDialog.getChild("right") +- top = xDialog.getChild("top") +- bottom = xDialog.getChild("bottom") +- show = xDialog.getChild("show") +- +- self.assertEqual(get_state_as_dict(left)["Checked"], "true") +- self.assertEqual(get_state_as_dict(right)["Checked"], "false") +- self.assertEqual(get_state_as_dict(top)["Checked"], "false") +- self.assertEqual(get_state_as_dict(bottom)["Checked"], "false") +- +- show.executeAction("CLICK", tuple()) +- +- xOKBtn = xDialog.getChild("ok") +- self.ui_test.close_dialog_through_button(xOKBtn) +- +- #reopen and verify InsertMenuLegend dialog +- gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) +- gridwin.executeAction("ACTIVATE", tuple()) +- xChartMainTop = self.xUITest.getTopFocusWindow() +- xChartMain = xChartMainTop.getChild("chart_window") +- xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") +- self.ui_test.execute_dialog_through_action(xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) +- xDialog = self.xUITest.getTopFocusWindow() +- +- left = xDialog.getChild("left") +- right = xDialog.getChild("right") +- top = xDialog.getChild("top") +- bottom = xDialog.getChild("bottom") +- show = xDialog.getChild("show") +- +- self.assertEqual(get_state_as_dict(left)["Checked"], "true") +- self.assertEqual(get_state_as_dict(right)["Checked"], "false") +- self.assertEqual(get_state_as_dict(top)["Checked"], "false") +- self.assertEqual(get_state_as_dict(bottom)["Checked"], "false") +- +- self.assertEqual(get_state_as_dict(show)["Selected"], "false") +- +- xOKBtn = xDialog.getChild("ok") +- self.ui_test.close_dialog_through_button(xOKBtn) +- self.ui_test.close_doc() ++ with guarded.load_file(self, get_url_for_data_file("tdf98390.ods")) as calc_doc: ++ xCalcDoc = self.xUITest.getTopFocusWindow() ++ gridwin = xCalcDoc.getChild("grid_window") ++ document = self.ui_test.get_component() ++ ++ gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) ++ gridwin.executeAction("ACTIVATE", tuple()) ++ xChartMainTop = self.xUITest.getTopFocusWindow() ++ xChartMain = xChartMainTop.getChild("chart_window") ++ xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") ++ with guarded.execute_dialog_through_action(self, xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) as xDialog: ++ left = xDialog.getChild("left") ++ right = xDialog.getChild("right") ++ top = xDialog.getChild("top") ++ bottom = xDialog.getChild("bottom") ++ ++ left.executeAction("CLICK", tuple()) ++ ++ #reopen and verify InsertMenuLegend dialog ++ gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) ++ gridwin.executeAction("ACTIVATE", tuple()) ++ xChartMainTop = self.xUITest.getTopFocusWindow() ++ xChartMain = xChartMainTop.getChild("chart_window") ++ xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") ++ with guarded.execute_dialog_through_action(self, xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) as xDialog: ++ left = xDialog.getChild("left") ++ right = xDialog.getChild("right") ++ top = xDialog.getChild("top") ++ bottom = xDialog.getChild("bottom") ++ show = xDialog.getChild("show") ++ ++ self.assertEqual(get_state_as_dict(left)["Checked"], "true") ++ self.assertEqual(get_state_as_dict(right)["Checked"], "false") ++ self.assertEqual(get_state_as_dict(top)["Checked"], "false") ++ self.assertEqual(get_state_as_dict(bottom)["Checked"], "false") ++ ++ show.executeAction("CLICK", tuple()) ++ ++ #reopen and verify InsertMenuLegend dialog ++ gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) ++ gridwin.executeAction("ACTIVATE", tuple()) ++ xChartMainTop = self.xUITest.getTopFocusWindow() ++ xChartMain = xChartMainTop.getChild("chart_window") ++ xSeriesObj = xChartMain.getChild("CID/D=0:CS=0:CT=0:Series=0") ++ with guarded.execute_dialog_through_action(self, xSeriesObj, "COMMAND", mkPropertyValues({"COMMAND": "InsertMenuLegend"})) as xDialog: ++ left = xDialog.getChild("left") ++ right = xDialog.getChild("right") ++ top = xDialog.getChild("top") ++ bottom = xDialog.getChild("bottom") ++ show = xDialog.getChild("show") ++ ++ self.assertEqual(get_state_as_dict(left)["Checked"], "true") ++ self.assertEqual(get_state_as_dict(right)["Checked"], "false") ++ self.assertEqual(get_state_as_dict(top)["Checked"], "false") ++ self.assertEqual(get_state_as_dict(bottom)["Checked"], "false") ++ ++ self.assertEqual(get_state_as_dict(show)["Selected"], "false") + + def test_legends_move_with_arrows_keys(self): + +- calc_doc = self.ui_test.load_file(get_url_for_data_file("dataLabels.ods")) +- xCalcDoc = self.xUITest.getTopFocusWindow() +- gridwin = xCalcDoc.getChild("grid_window") ++ with guarded.load_file(self, get_url_for_data_file("dataLabels.ods")) as calc_doc: ++ xCalcDoc = self.xUITest.getTopFocusWindow() ++ gridwin = xCalcDoc.getChild("grid_window") + +- change_measurement_unit(self, "Centimeter") ++ change_measurement_unit(self, "Centimeter") + +- gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) +- gridwin.executeAction("ACTIVATE", tuple()) +- xChartMainTop = self.xUITest.getTopFocusWindow() +- xChartMain = xChartMainTop.getChild("chart_window") ++ gridwin.executeAction("SELECT", mkPropertyValues({"OBJECT": "Object 1"})) ++ gridwin.executeAction("ACTIVATE", tuple()) ++ xChartMainTop = self.xUITest.getTopFocusWindow() ++ xChartMain = xChartMainTop.getChild("chart_window") + +- # Select the legends +- xLegends = xChartMain.getChild("CID/D=0:Legend=") +- xLegends.executeAction("SELECT", tuple()) ++ # Select the legends ++ xLegends = xChartMain.getChild("CID/D=0:Legend=") ++ xLegends.executeAction("SELECT", tuple()) + +- self.ui_test.execute_dialog_through_action(xLegends, "COMMAND", mkPropertyValues({"COMMAND": "TransformDialog"})) ++ with guarded.execute_dialog_through_action(self, xLegends, "COMMAND", mkPropertyValues({"COMMAND": "TransformDialog"})) as xDialog: ++ self.assertEqual("4.61", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_X"))['Value']) ++ self.assertEqual("1.53", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_Y"))['Value']) + +- xDialog = self.xUITest.getTopFocusWindow() +- self.assertEqual("4.61", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_X"))['Value']) +- self.assertEqual("1.54", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_Y"))['Value']) ++ xChartMain.executeAction("TYPE", mkPropertyValues({"KEYCODE": "UP"})) ++ xChartMain.executeAction("TYPE", mkPropertyValues({"KEYCODE": "LEFT"})) + +- xOkBtn = xDialog.getChild("ok") +- xOkBtn.executeAction("CLICK", tuple()) +- +- xChartMain.executeAction("TYPE", mkPropertyValues({"KEYCODE": "UP"})) +- xChartMain.executeAction("TYPE", mkPropertyValues({"KEYCODE": "LEFT"})) +- +- self.ui_test.execute_dialog_through_action(xLegends, "COMMAND", mkPropertyValues({"COMMAND": "TransformDialog"})) +- +- # Check the position has changed after moving the label using the arrows keys +- xDialog = self.xUITest.getTopFocusWindow() +- self.assertEqual("4.51", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_X"))['Value']) +- self.assertEqual("1.44", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_Y"))['Value']) +- +- xOkBtn = xDialog.getChild("ok") +- xOkBtn.executeAction("CLICK", tuple()) +- +- self.ui_test.close_doc() ++ # Check the position has changed after moving the label using the arrows keys ++ with guarded.execute_dialog_through_action(self, xLegends, "COMMAND", mkPropertyValues({"COMMAND": "TransformDialog"})) as xDialog: ++ self.assertEqual("4.51", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_X"))['Value']) ++ self.assertEqual("1.43", get_state_as_dict(xDialog.getChild("MTR_FLD_POS_Y"))['Value']) + + # vim: set shiftwidth=4 softtabstop=4 expandtab: +diff --git a/sc/qa/unit/filters-test.cxx b/sc/qa/unit/filters-test.cxx +index a994297ff2c0..30ef7f12aa3e 100644 +--- a/sc/qa/unit/filters-test.cxx ++++ b/sc/qa/unit/filters-test.cxx +@@ -514,7 +514,7 @@ void ScFiltersTest::testCommentSize() + + const tools::Rectangle& rNewRect = pCaption->GetLogicRect(); + CPPUNIT_ASSERT_EQUAL(rOldRect.getWidth(), rNewRect.getWidth()); +- CPPUNIT_ASSERT_EQUAL(tools::Long(1605), rNewRect.getHeight()); ++ CPPUNIT_ASSERT_EQUAL(tools::Long(1606), rNewRect.getHeight()); + + rDoc.GetUndoManager()->Undo(); + +diff --git a/sd/qa/uitest/impress_tests/tdf91762.py b/sd/qa/uitest/impress_tests/tdf91762.py +index 2487bc2a4726..b05670c4af63 100644 +--- a/sd/qa/uitest/impress_tests/tdf91762.py ++++ b/sd/qa/uitest/impress_tests/tdf91762.py +@@ -26,9 +26,9 @@ class tdf91762(UITestCase): + self.ui_test.close_dialog_through_button(xOkBtn) + + document = self.ui_test.get_component() +- self.assertEqual(1929, document.DrawPages[0].getByIndex(1).BoundRect.Height) ++ self.assertEqual(1931, document.DrawPages[0].getByIndex(1).BoundRect.Height) + self.assertEqual(25198, document.DrawPages[0].getByIndex(1).Size.Width) +- self.assertEqual(1923, document.DrawPages[0].getByIndex(1).Size.Height) ++ self.assertEqual(1925, document.DrawPages[0].getByIndex(1).Size.Height) + + self.assertEqual(1400, document.DrawPages[0].getByIndex(1).Position.X) + self.assertEqual(3685, document.DrawPages[0].getByIndex(1).Position.Y) +@@ -40,8 +40,8 @@ class tdf91762(UITestCase): + xEdit.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RETURN"})) + + # tdf#138011: Without the fix in place, this test would have failed with +- # AssertionError: 5494 != 3559 +- self.assertEqual(5494, document.DrawPages[0].getByIndex(1).BoundRect.Height) ++ # AssertionError: 5496 != 3559 ++ self.assertEqual(5496, document.DrawPages[0].getByIndex(1).BoundRect.Height) + + self.ui_test.close_doc() + +diff --git a/sd/qa/uitest/impress_tests/textColumnsDialog.py b/sd/qa/uitest/impress_tests/textColumnsDialog.py +new file mode 100644 +index 000000000000..266c55bada6b +--- /dev/null ++++ b/sd/qa/uitest/impress_tests/textColumnsDialog.py +@@ -0,0 +1,53 @@ ++# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- ++# ++# This file is part of the LibreOffice project. ++# ++# This Source Code Form is subject to the terms of the Mozilla Public ++# License, v. 2.0. If a copy of the MPL was not distributed with this ++# file, You can obtain one at http://mozilla.org/MPL/2.0/. ++# ++ ++from uitest.uihelper.common import get_state_as_dict ++from libreoffice.uno.propertyvalue import mkPropertyValues ++from uitest.uihelper.common import change_measurement_unit, select_pos ++from uitest.framework import UITestCase ++from uitest.uihelper import guarded ++ ++class textColumnsDialog(UITestCase): ++ ++ def test_textColumnsDialog(self): ++ with guarded.create_doc_in_start_center(self, "impress") as document: ++ ++ xTemplateDlg = self.xUITest.getTopFocusWindow() ++ xCancelBtn = xTemplateDlg.getChild("cancel") ++ self.ui_test.close_dialog_through_button(xCancelBtn) ++ ++ xImpressDoc = self.xUITest.getTopFocusWindow() ++ ++ xEditWin = xImpressDoc.getChild("impress_win") ++ xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) ++ self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) ++ ++ # Test defaults and set some values ++ with guarded.execute_dialog_through_command(self, ".uno:TextAttributes") as xDialog: ++ xTabs = xDialog.getChild("tabcontrol") ++ select_pos(xTabs, "2") ++ colNumber = xDialog.getChild('FLD_COL_NUMBER') ++ colSpacing = xDialog.getChild('MTR_FLD_COL_SPACING') ++ self.assertEqual('1', get_state_as_dict(colNumber)['Text']) ++ self.assertEqual('0.00″', get_state_as_dict(colSpacing)['Text']) ++ colNumber.executeAction("TYPE", mkPropertyValues({"KEYCODE": "BACKSPACE"})) ++ colNumber.executeAction("TYPE", mkPropertyValues({"TEXT": "3"})) ++ colSpacing.executeAction("TYPE", mkPropertyValues({"KEYCODE": "BACKSPACE"})) ++ colSpacing.executeAction("TYPE", mkPropertyValues({"TEXT": "1.5"})) ++ ++ # Test that settings persist ++ with guarded.execute_dialog_through_command(self, ".uno:TextAttributes") as xDialog: ++ xTabs = xDialog.getChild("tabcontrol") ++ select_pos(xTabs, "2") ++ colNumber = xDialog.getChild('FLD_COL_NUMBER') ++ colSpacing = xDialog.getChild('MTR_FLD_COL_SPACING') ++ self.assertEqual('3', get_state_as_dict(colNumber)['Text']) ++ self.assertEqual('1.50″', get_state_as_dict(colSpacing)['Text']) ++ ++# vim: set shiftwidth=4 softtabstop=4 expandtab: +diff --git a/sd/qa/unit/data/xml/n593612_0.xml b/sd/qa/unit/data/xml/n593612_0.xml +index 7c93f494e3ca..51fb75eba050 100644 +--- a/sd/qa/unit/data/xml/n593612_0.xml ++++ b/sd/qa/unit/data/xml/n593612_0.xml +@@ -1,6 +1,6 @@ + + +- ++ + + + +@@ -10,7 +10,7 @@ + + + +- ++ + + + +diff --git a/sd/qa/unit/data/xml/n758621_1.xml b/sd/qa/unit/data/xml/n758621_1.xml +index 556c18673065..92de100a09f1 100644 +--- a/sd/qa/unit/data/xml/n758621_1.xml ++++ b/sd/qa/unit/data/xml/n758621_1.xml +@@ -1,6 +1,6 @@ + + +- ++ + + + +@@ -10,7 +10,7 @@ + + + +- ++ + + + +diff --git a/sd/qa/unit/import-tests.cxx b/sd/qa/unit/import-tests.cxx +index e140c41c126d..5e09f0a2996f 100644 +--- a/sd/qa/unit/import-tests.cxx ++++ b/sd/qa/unit/import-tests.cxx +@@ -89,6 +89,7 @@ + #include + #include + #include ++#include + #include + + #include +@@ -225,7 +226,6 @@ public: + void testPatternImport(); + void testPptCrop(); + void testTdf120028(); +- void testTdf120028b(); + void testDescriptionImport(); + void testTdf83247(); + void testTdf47365(); +@@ -335,7 +335,6 @@ public: + CPPUNIT_TEST(testTdf116266); + CPPUNIT_TEST(testPptCrop); + CPPUNIT_TEST(testTdf120028); +- CPPUNIT_TEST(testTdf120028b); + CPPUNIT_TEST(testDescriptionImport); + CPPUNIT_TEST(testTdf83247); + CPPUNIT_TEST(testTdf47365); +@@ -990,26 +989,20 @@ void SdImportTest::testMultiColTexts() + sd::DrawDocShellRef xDocShRef = loadURL( m_directories.getURLFromSrc("/sd/qa/unit/data/pptx/multicol.pptx"), PPTX ); + const SdrPage *pPage = GetPage( 1, xDocShRef ); + +- sdr::table::SdrTableObj *pTableObj = dynamic_cast(pPage->GetObj(0)); +- CPPUNIT_ASSERT( pTableObj ); ++ auto pTextObj = dynamic_cast(pPage->GetObj(0)); ++ CPPUNIT_ASSERT(pTextObj); + +- CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pTableObj->getRowCount()); +- CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pTableObj->getColumnCount()); ++ CPPUNIT_ASSERT_EQUAL(sal_Int16(2), pTextObj->GetTextColumnsNumber()); ++ CPPUNIT_ASSERT_EQUAL(sal_Int32(1000), pTextObj->GetTextColumnsSpacing()); + +- sdr::table::SdrTableObj *pMasterTableObj = dynamic_cast(pPage->TRG_GetMasterPage().GetObj(0)); +- CPPUNIT_ASSERT( pMasterTableObj ); ++ auto pMasterTextObj = dynamic_cast(pPage->TRG_GetMasterPage().GetObj(0)); ++ CPPUNIT_ASSERT(pMasterTextObj); + +- CPPUNIT_ASSERT_EQUAL(sal_Int32(1), pMasterTableObj->getRowCount()); +- CPPUNIT_ASSERT_EQUAL(sal_Int32(2), pMasterTableObj->getColumnCount()); ++ CPPUNIT_ASSERT_EQUAL(sal_Int16(2), pMasterTextObj->GetTextColumnsNumber()); ++ CPPUNIT_ASSERT_EQUAL(sal_Int32(1000), pMasterTextObj->GetTextColumnsSpacing()); + +- uno::Reference< table::XCellRange > xTable(pMasterTableObj->getTable(), uno::UNO_QUERY_THROW); +- uno::Reference< beans::XPropertySet > xCell; +- xCell.set(xTable->getCellByPosition(0, 0), uno::UNO_QUERY_THROW); +- uno::Reference xParagraph(getParagraphFromShape(0, xCell)); +- uno::Reference xRun( getRunFromParagraph (0, xParagraph ) ); +- OUString sText = xRun->getString(); +- +- CPPUNIT_ASSERT_EQUAL(OUString(""), sText); //We don't import master table text for multicolumn case. ++ uno::Reference xText(pMasterTextObj->getUnoShape(), uno::UNO_QUERY_THROW); ++ CPPUNIT_ASSERT_EQUAL(OUString("mastershape1\nmastershape2"), xText->getString()); + } + + void SdImportTest::testPredefinedTableStyle() +@@ -3023,7 +3016,7 @@ void SdImportTest::testTdf116266() + + void SdImportTest::testTdf120028() + { +- // Check that the table shape has 4 columns. ++ // Check that the text shape has 4 columns. + ::sd::DrawDocShellRef xDocShRef + = loadURL(m_directories.getURLFromSrc("/sd/qa/unit/data/pptx/tdf120028.pptx"), PPTX); + uno::Reference xDoc(xDocShRef->GetDoc()->getUnoModel(), +@@ -3033,63 +3026,22 @@ void SdImportTest::testTdf120028() + uno::Reference xPage(xDoc->getDrawPages()->getByIndex(0), uno::UNO_QUERY); + CPPUNIT_ASSERT(xPage.is()); + +- // This failed, shape was not a table, all text was rendered in a single +- // column. + uno::Reference xShape(getShape(0, xPage)); +- uno::Reference xModel(xShape->getPropertyValue("Model"), +- uno::UNO_QUERY); +- CPPUNIT_ASSERT(xModel.is()); +- +- uno::Reference xColumns = xModel->getColumns(); +- CPPUNIT_ASSERT_EQUAL(static_cast(4), xColumns->getCount()); +- +- // Check font size in the A1 cell. +- uno::Reference xCells(xModel, uno::UNO_QUERY); +- uno::Reference xCell(xCells->getCellByPosition(0, 0), uno::UNO_QUERY); +- uno::Reference xParagraph(getParagraphFromShape(0, xCell)); ++ uno::Reference xCols(xShape->getPropertyValue("TextColumns"), ++ uno::UNO_QUERY_THROW); ++ CPPUNIT_ASSERT_EQUAL(static_cast(4), xCols->getColumnCount()); ++ uno::Reference xColProps(xCols, uno::UNO_QUERY_THROW); ++ CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int32(0)), xColProps->getPropertyValue("AutomaticDistance")); ++ ++ // Check font size in the shape. ++ uno::Reference xParagraph(getParagraphFromShape(0, xShape)); + uno::Reference xRun(getRunFromParagraph(0, xParagraph)); +- uno::Reference xPropSet(xRun, uno::UNO_QUERY); ++ uno::Reference xPropSet(xRun, uno::UNO_QUERY_THROW); + double fCharHeight = 0; + xPropSet->getPropertyValue("CharHeight") >>= fCharHeight; +- // This failed, non-scaled height was 13.5. +- CPPUNIT_ASSERT_DOUBLES_EQUAL(11.5, fCharHeight, 1E-12); +- +- xDocShRef->DoClose(); +-} +- +-void SdImportTest::testTdf120028b() +-{ +- // Check that the table shape has 4 columns. +- ::sd::DrawDocShellRef xDocShRef +- = loadURL(m_directories.getURLFromSrc("/sd/qa/unit/data/pptx/tdf120028b.pptx"), PPTX); +- uno::Reference xDoc(xDocShRef->GetDoc()->getUnoModel(), +- uno::UNO_QUERY); +- CPPUNIT_ASSERT(xDoc.is()); +- +- uno::Reference xPage(xDoc->getDrawPages()->getByIndex(0), uno::UNO_QUERY); +- CPPUNIT_ASSERT(xPage.is()); +- +- uno::Reference xShape(getShape(0, xPage)); +- CPPUNIT_ASSERT(xShape.is()); +- +- uno::Reference xModel(xShape->getPropertyValue("Model"), +- uno::UNO_QUERY); +- CPPUNIT_ASSERT(xModel.is()); +- +- uno::Reference xColumns = xModel->getColumns(); +- CPPUNIT_ASSERT_EQUAL(static_cast(4), xColumns->getCount()); +- +- // Check font color in the A1 cell. +- uno::Reference xCells(xModel, uno::UNO_QUERY); +- uno::Reference xCell(xCells->getCellByPosition(0, 0), uno::UNO_QUERY); +- uno::Reference xParagraph(getParagraphFromShape(0, xCell)); +- uno::Reference xRun(getRunFromParagraph(0, xParagraph)); +- uno::Reference xPropSet(xRun, uno::UNO_QUERY); +- sal_Int32 nCharColor = 0; +- xPropSet->getPropertyValue("CharColor") >>= nCharColor; +- // This was 0x1f497d, not white: text list style from placeholder shape +- // from slide layout was ignored. +- CPPUNIT_ASSERT_EQUAL(static_cast(0xffffff), nCharColor); ++ CPPUNIT_ASSERT_DOUBLES_EQUAL(13.5, fCharHeight, 1E-12); ++ // 13.5 * 86% is approx. 11.6 (the correct scaled font size) ++ CPPUNIT_ASSERT_EQUAL(uno::Any(sal_Int16(86)), xShape->getPropertyValue("TextFitToSizeScale")); + + xDocShRef->DoClose(); + } +diff --git a/sd/qa/unit/misc-tests.cxx b/sd/qa/unit/misc-tests.cxx +index 75818805295a..82734af9eb73 100644 +--- a/sd/qa/unit/misc-tests.cxx ++++ b/sd/qa/unit/misc-tests.cxx +@@ -59,6 +59,8 @@ + #include + #include + #include ++#include ++#include + + using namespace ::com::sun::star; + +@@ -81,6 +83,7 @@ public: + void testTdf67248(); + void testTdf119956(); + void testTdf120527(); ++ void testTextColumns(); + void testTdf98839_ShearVFlipH(); + void testTdf130988(); + void testTdf131033(); +@@ -103,6 +106,7 @@ public: + CPPUNIT_TEST(testTdf67248); + CPPUNIT_TEST(testTdf119956); + CPPUNIT_TEST(testTdf120527); ++ CPPUNIT_TEST(testTextColumns); + CPPUNIT_TEST(testTdf98839_ShearVFlipH); + CPPUNIT_TEST(testTdf130988); + CPPUNIT_TEST(testTdf131033); +@@ -492,6 +496,51 @@ void SdMiscTest::testTdf120527() + xDocShRef->DoClose(); + } + ++// Testing document model part of editengine-columns ++void SdMiscTest::testTextColumns() ++{ ++ ::sd::DrawDocShellRef xDocShRef ++ = new ::sd::DrawDocShell(SfxObjectCreateMode::EMBEDDED, false, DocumentType::Impress); ++ uno::Reference xDrawPagesSupplier = getDoc(xDocShRef); ++ uno::Reference xDrawPages = xDrawPagesSupplier->getDrawPages(); ++ // Insert a new page. ++ uno::Reference xDrawPage(xDrawPages->insertNewByIndex(0), ++ uno::UNO_SET_THROW); ++ uno::Reference xShapes(xDrawPage, uno::UNO_QUERY_THROW); ++ uno::Reference const xDoc(xDocShRef->GetDoc()->getUnoModel(), ++ uno::UNO_QUERY); ++ ++ { ++ // Create a text shape ++ uno::Reference xShape( ++ xDoc->createInstance("com.sun.star.drawing.TextShape"), uno::UNO_QUERY_THROW); ++ uno::Reference xPropSet(xShape, uno::UNO_QUERY_THROW); ++ ++ // Add the shape to the page. ++ xShapes->add(xShape); ++ ++ // Set up columns ++ auto pTextObj = dynamic_cast(GetSdrObjectFromXShape(xShape)); ++ CPPUNIT_ASSERT(pTextObj); ++ pTextObj->SetMergedItem(SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, 2)); ++ pTextObj->SetMergedItem(SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, 1000)); ++ } ++ ++ { ++ // Retrieve the shape and check columns ++ uno::Reference xIndexAccess(xDrawPage, uno::UNO_QUERY_THROW); ++ uno::Reference xShape(xIndexAccess->getByIndex(0), uno::UNO_QUERY_THROW); ++ ++ auto pTextObj = dynamic_cast(GetSdrObjectFromXShape(xShape)); ++ CPPUNIT_ASSERT(pTextObj); ++ ++ CPPUNIT_ASSERT_EQUAL(sal_Int16(2), pTextObj->GetTextColumnsNumber()); ++ CPPUNIT_ASSERT_EQUAL(sal_Int32(1000), pTextObj->GetTextColumnsSpacing()); ++ } ++ ++ xDocShRef->DoClose(); ++} ++ + /// Draw miscellaneous tests. + + // Since LO 6.2 the visible/printable/locked information for layers is always +diff --git a/sd/qa/unit/tiledrendering/LOKitSearchTest.cxx b/sd/qa/unit/tiledrendering/LOKitSearchTest.cxx +index beba53b67078..18b923de4461 100644 +--- a/sd/qa/unit/tiledrendering/LOKitSearchTest.cxx ++++ b/sd/qa/unit/tiledrendering/LOKitSearchTest.cxx +@@ -653,7 +653,7 @@ void LOKitSearchTest::testSearchIn2MixedObjects() + CPPUNIT_ASSERT_EQUAL(size_t(1), mpCallbackRecorder->m_aSearchResultSelection.size()); + CPPUNIT_ASSERT_EQUAL(size_t(1), mpCallbackRecorder->m_aSearchResultPart.size()); + +- CPPUNIT_ASSERT_EQUAL(OString("3546, 3174, 738, 402"), ++ CPPUNIT_ASSERT_EQUAL(OString("3546, 3173, 738, 401"), + mpCallbackRecorder->m_aSearchResultSelection[0]); + + // Search next +@@ -679,7 +679,7 @@ void LOKitSearchTest::testSearchIn2MixedObjects() + CPPUNIT_ASSERT_EQUAL(size_t(1), mpCallbackRecorder->m_aSearchResultSelection.size()); + CPPUNIT_ASSERT_EQUAL(size_t(1), mpCallbackRecorder->m_aSearchResultPart.size()); + +- CPPUNIT_ASSERT_EQUAL(OString("3546, 3174, 738, 402"), ++ CPPUNIT_ASSERT_EQUAL(OString("3546, 3173, 738, 401"), + mpCallbackRecorder->m_aSearchResultSelection[0]); + #endif + } +diff --git a/sd/source/core/stlsheet.cxx b/sd/source/core/stlsheet.cxx +index 52d05351b3b9..3f6b0b905db9 100644 +--- a/sd/source/core/stlsheet.cxx ++++ b/sd/source/core/stlsheet.cxx +@@ -1186,6 +1186,19 @@ PropertyState SAL_CALL SdStyleSheet::getPropertyState( const OUString& PropertyN + return PropertyState_AMBIGUOUS_VALUE; + } + } ++ else if (pEntry->nWID == OWN_ATTR_TEXTCOLUMNS) ++ { ++ const SfxItemSet& rSet = GetItemSet(); ++ ++ const auto eState1 = rSet.GetItemState(SDRATTR_TEXTCOLUMNS_NUMBER, false); ++ const auto eState2 = rSet.GetItemState(SDRATTR_TEXTCOLUMNS_SPACING, false); ++ if (eState1 == SfxItemState::SET || eState2 == SfxItemState::SET) ++ return PropertyState_DIRECT_VALUE; ++ else if (eState1 == SfxItemState::DEFAULT && eState2 == SfxItemState::DEFAULT) ++ return PropertyState_DEFAULT_VALUE; ++ else ++ return PropertyState_AMBIGUOUS_VALUE; ++ } + else + { + SfxItemSet &rStyleSet = GetItemSet(); +diff --git a/svx/Library_svxcore.mk b/svx/Library_svxcore.mk +index 3d4590d216a1..e8cbb606b086 100644 +--- a/svx/Library_svxcore.mk ++++ b/svx/Library_svxcore.mk +@@ -391,6 +391,7 @@ $(eval $(call gb_Library_add_exception_objects,svxcore,\ + svx/source/toolbars/fontworkbar \ + svx/source/unodraw/gluepts \ + svx/source/unodraw/shapepropertynotifier \ ++ svx/source/unodraw/SvxXTextColumns \ + svx/source/unodraw/tableshape \ + svx/source/unodraw/unobrushitemhelper \ + svx/source/unodraw/unobtabl \ +diff --git a/svx/source/sdr/properties/attributeproperties.cxx b/svx/source/sdr/properties/attributeproperties.cxx +index 55dbf646210e..c79ccbd75c52 100644 +--- a/svx/source/sdr/properties/attributeproperties.cxx ++++ b/svx/source/sdr/properties/attributeproperties.cxx +@@ -128,7 +128,8 @@ namespace sdr::properties + // ranges from SdrAttrObj + svl::Items{}); ++ SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, ++ SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST>{}); + } + + AttributeProperties::AttributeProperties(SdrObject& rObj) +diff --git a/svx/source/sdr/properties/captionproperties.cxx b/svx/source/sdr/properties/captionproperties.cxx +index 0afd70af5234..b657b1879fe9 100644 +--- a/svx/source/sdr/properties/captionproperties.cxx ++++ b/svx/source/sdr/properties/captionproperties.cxx +@@ -38,6 +38,7 @@ namespace sdr::properties + // Ranges from SdrAttrObj, SdrCaptionObj: + SDRATTR_START, SDRATTR_MISC_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, ++ SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + // Range from SdrTextObj: + EE_ITEMS_START, EE_ITEMS_END>{}); + } +diff --git a/svx/source/sdr/properties/circleproperties.cxx b/svx/source/sdr/properties/circleproperties.cxx +index 895029ca7939..c0a9e6aec14e 100644 +--- a/svx/source/sdr/properties/circleproperties.cxx ++++ b/svx/source/sdr/properties/circleproperties.cxx +@@ -42,6 +42,7 @@ namespace sdr::properties + SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, + SDRATTR_CIRC_FIRST, SDRATTR_CIRC_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, ++ SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + // Range from SdrTextObj: + EE_ITEMS_START, EE_ITEMS_END>{}); + } +diff --git a/svx/source/sdr/properties/connectorproperties.cxx b/svx/source/sdr/properties/connectorproperties.cxx +index 29a2b7edeb5f..f5e50cb53f83 100644 +--- a/svx/source/sdr/properties/connectorproperties.cxx ++++ b/svx/source/sdr/properties/connectorproperties.cxx +@@ -39,6 +39,7 @@ namespace sdr::properties + SDRATTR_START, SDRATTR_SHADOW_LAST, + SDRATTR_MISC_FIRST, SDRATTR_EDGE_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, ++ SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + // Range from SdrTextObj: + EE_ITEMS_START, EE_ITEMS_END>{}); + } +diff --git a/svx/source/sdr/properties/customshapeproperties.cxx b/svx/source/sdr/properties/customshapeproperties.cxx +index 0d1443081910..ba028bd29f8f 100644 +--- a/svx/source/sdr/properties/customshapeproperties.cxx ++++ b/svx/source/sdr/properties/customshapeproperties.cxx +@@ -73,7 +73,7 @@ namespace sdr::properties + // Graphic attributes, 3D properties, CustomShape + // properties: + SDRATTR_GRAF_FIRST, SDRATTR_CUSTOMSHAPE_LAST, +- SDRATTR_GLOW_FIRST, SDRATTR_SOFTEDGE_LAST, ++ SDRATTR_GLOW_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + // Range from SdrTextObj: + EE_ITEMS_START, EE_ITEMS_END>{}); + } +diff --git a/svx/source/sdr/properties/graphicproperties.cxx b/svx/source/sdr/properties/graphicproperties.cxx +index 2819826caad5..966cb68ab7ff 100644 +--- a/svx/source/sdr/properties/graphicproperties.cxx ++++ b/svx/source/sdr/properties/graphicproperties.cxx +@@ -67,7 +67,7 @@ namespace sdr::properties + // range from SdrGrafObj + SDRATTR_GRAF_FIRST, SDRATTR_GRAF_LAST, + +- SDRATTR_GLOW_FIRST, SDRATTR_SOFTEDGE_LAST, ++ SDRATTR_GLOW_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + + // range from SdrTextObj + EE_ITEMS_START, EE_ITEMS_END>{}); +diff --git a/svx/source/sdr/properties/measureproperties.cxx b/svx/source/sdr/properties/measureproperties.cxx +index 5519930e3dda..67b33192d890 100644 +--- a/svx/source/sdr/properties/measureproperties.cxx ++++ b/svx/source/sdr/properties/measureproperties.cxx +@@ -48,6 +48,7 @@ namespace sdr::properties + SDRATTR_MISC_FIRST, SDRATTR_MISC_LAST, + SDRATTR_MEASURE_FIRST, SDRATTR_MEASURE_LAST, + SDRATTR_TEXTDIRECTION, SDRATTR_TEXTDIRECTION, ++ SDRATTR_TEXTCOLUMNS_FIRST, SDRATTR_TEXTCOLUMNS_LAST, + // Range from SdrTextObj: + EE_ITEMS_START, EE_ITEMS_END>{}); + } +diff --git a/svx/source/sdr/properties/textproperties.cxx b/svx/source/sdr/properties/textproperties.cxx +index 8a90de5c848b..3a3335b6c0e5 100644 +--- a/svx/source/sdr/properties/textproperties.cxx ++++ b/svx/source/sdr/properties/textproperties.cxx +@@ -54,6 +54,7 @@ namespace sdr::properties + svl::Items{}); +@@ -87,6 +88,12 @@ namespace sdr::properties + // #i101556# ItemSet has changed -> new version + maVersion++; + ++ if (auto pOutliner = rObj.GetTextEditOutliner()) ++ { ++ pOutliner->SetTextColumns(rObj.GetTextColumnsNumber(), ++ rObj.GetTextColumnsSpacing()); ++ } ++ + const svx::ITextProvider& rTextProvider(getTextProvider()); + sal_Int32 nText = rTextProvider.getTextCount(); + while (nText--) +diff --git a/svx/source/svdraw/svdattr.cxx b/svx/source/svdraw/svdattr.cxx +index 28bbfe39afce..a0e8887da5a0 100644 +--- a/svx/source/svdraw/svdattr.cxx ++++ b/svx/source/svdraw/svdattr.cxx +@@ -335,6 +335,9 @@ SdrItemPool::SdrItemPool( + + rPoolDefaults[SDRATTR_SOFTEDGE_RADIUS - SDRATTR_START] = new SdrMetricItem(SDRATTR_SOFTEDGE_RADIUS, 0); + ++ rPoolDefaults[SDRATTR_TEXTCOLUMNS_NUMBER - SDRATTR_START] = new SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, 1); ++ rPoolDefaults[SDRATTR_TEXTCOLUMNS_SPACING - SDRATTR_START] = new SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, 0); ++ + // set own ItemInfos + mpLocalItemInfos[SDRATTR_SHADOW-SDRATTR_START]._nSID=SID_ATTR_FILL_SHADOW; + mpLocalItemInfos[SDRATTR_SHADOWCOLOR-SDRATTR_START]._nSID=SID_ATTR_SHADOW_COLOR; +@@ -356,6 +359,9 @@ SdrItemPool::SdrItemPool( + + mpLocalItemInfos[SDRATTR_SOFTEDGE_RADIUS - SDRATTR_START]._nSID = SID_ATTR_SOFTEDGE_RADIUS; + ++ mpLocalItemInfos[SDRATTR_TEXTCOLUMNS_NUMBER - SDRATTR_START]._nSID = 0 /*TODO*/; ++ mpLocalItemInfos[SDRATTR_TEXTCOLUMNS_SPACING - SDRATTR_START]._nSID = 0 /*TODO*/; ++ + // it's my own creation level, set Defaults and ItemInfos + SetDefaults(mpLocalPoolDefaults); + SetItemInfos(mpLocalItemInfos.get()); +@@ -622,6 +628,9 @@ OUString SdrItemPool::GetItemName(sal_uInt16 nWhich) + case EE_FEATURE_LINEBR : pResId = SIP_EE_FEATURE_LINEBR;break; + case EE_FEATURE_NOTCONV : pResId = SIP_EE_FEATURE_NOTCONV;break; + case EE_FEATURE_FIELD : pResId = SIP_EE_FEATURE_FIELD;break; ++ ++ case SDRATTR_TEXTCOLUMNS_NUMBER: pResId = SIP_SA_TEXTCOLUMNS_NUMBER; break; ++ case SDRATTR_TEXTCOLUMNS_SPACING: pResId = SIP_SA_TEXTCOLUMNS_SPACING; break; + } // switch + + return SvxResId(pResId); +diff --git a/svx/source/svdraw/svdotext.cxx b/svx/source/svdraw/svdotext.cxx +index 0daeb7cc9a5d..855e07d4c4af 100644 +--- a/svx/source/svdraw/svdotext.cxx ++++ b/svx/source/svdraw/svdotext.cxx +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + + using namespace com::sun::star; + +@@ -682,11 +683,13 @@ void SdrTextObj::TakeTextRect( SdrOutliner& rOutliner, tools::Rectangle& rTextRe + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(nAnkWdt, 0)); ++ rOutliner.SetMinColumnWrapHeight(nAnkHgt); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && IsVerticalWriting()) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnkHgt)); ++ rOutliner.SetMinColumnWrapHeight(nAnkWdt); + } + } + +@@ -1188,67 +1191,15 @@ void SdrTextObj::ImpSetupDrawOutlinerForPaint( bool bContourFrame, + } + } + +-double SdrTextObj::GetFontScaleY() const ++sal_uInt16 SdrTextObj::GetFontScaleY() const + { +- SdrText* pText = getActiveText(); +- if (pText == nullptr || !pText->GetOutlinerParaObject()) +- return 1.0; +- + SdrOutliner& rOutliner = ImpGetDrawOutliner(); +- const Size aShapeSize = GetSnapRect().GetSize(); +- const Size aSize(aShapeSize.Width() - GetTextLeftDistance() - GetTextRightDistance(), +- aShapeSize.Height() - GetTextUpperDistance() - GetTextLowerDistance()); +- +- rOutliner.SetPaperSize(aSize); +- rOutliner.SetUpdateMode(true); +- rOutliner.SetText(*pText->GetOutlinerParaObject()); +- bool bIsVerticalWriting = IsVerticalWriting(); +- +- // Algorithm from SdrTextObj::ImpAutoFitText +- +- sal_uInt16 nMinStretchX = 0, nMinStretchY = 0; +- sal_uInt16 nCurrStretchX = 100, nCurrStretchY = 100; +- sal_uInt16 aOldStretchXVals[] = { 0,0,0 }; +- const size_t aStretchArySize = SAL_N_ELEMENTS(aOldStretchXVals); +- for (unsigned int i = 0; i= 1.0) +- { +- nMinStretchX = std::max(nMinStretchX, nCurrStretchX); +- nMinStretchY = std::max(nMinStretchY, nCurrStretchY); +- } +- +- aOldStretchXVals[i] = nCurrStretchX; +- if (std::find(aOldStretchXVals, aOldStretchXVals + i, nCurrStretchX) != aOldStretchXVals + i) +- break; // same value already attained once; algo is looping, exit ++ // This eventually calls ImpAutoFitText ++ UpdateOutlinerFormatting(rOutliner, o3tl::temporary(tools::Rectangle())); + +- if (fFactor < 1.0 || nCurrStretchX != 100) +- { +- nCurrStretchX = sal::static_int_cast(nCurrStretchX*fFactor); +- nCurrStretchY = sal::static_int_cast(nCurrStretchY*fFactor); +- rOutliner.SetGlobalCharStretching(std::min(sal_uInt16(100), nCurrStretchX), +- std::min(sal_uInt16(100), nCurrStretchY)); +- } +- } +- +- return std::min(sal_uInt16(100), nCurrStretchY) / 100.0; ++ sal_uInt16 nStretchY; ++ rOutliner.GetGlobalCharStretching(o3tl::temporary(sal_uInt16()), nStretchY); ++ return nStretchY; + } + + void SdrTextObj::ImpAutoFitText( SdrOutliner& rOutliner ) const +@@ -1777,6 +1728,36 @@ SdrTextAniDirection SdrTextObj::GetTextAniDirection() const + return GetObjectItemSet().Get(SDRATTR_TEXT_ANIDIRECTION).GetValue(); + } + ++bool SdrTextObj::HasTextColumnsNumber() const ++{ ++ return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_NUMBER); ++} ++ ++sal_Int16 SdrTextObj::GetTextColumnsNumber() const ++{ ++ return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_NUMBER).GetValue(); ++} ++ ++void SdrTextObj::SetTextColumnsNumber(sal_Int16 nColumns) ++{ ++ SetObjectItem(SfxInt16Item(SDRATTR_TEXTCOLUMNS_NUMBER, nColumns)); ++} ++ ++bool SdrTextObj::HasTextColumnsSpacing() const ++{ ++ return GetObjectItemSet().HasItem(SDRATTR_TEXTCOLUMNS_SPACING); ++} ++ ++sal_Int32 SdrTextObj::GetTextColumnsSpacing() const ++{ ++ return GetObjectItemSet().Get(SDRATTR_TEXTCOLUMNS_SPACING).GetValue(); ++} ++ ++void SdrTextObj::SetTextColumnsSpacing(sal_Int32 nSpacing) ++{ ++ SetObjectItem(SdrMetricItem(SDRATTR_TEXTCOLUMNS_SPACING, nSpacing)); ++} ++ + // Get necessary data for text scroll animation. ATM base it on a Text-Metafile and a + // painting rectangle. Rotation is excluded from the returned values. + GDIMetaFile* SdrTextObj::GetTextScrollMetaFileAndRectangle( +diff --git a/svx/source/svdraw/svdotextdecomposition.cxx b/svx/source/svdraw/svdotextdecomposition.cxx +index de362466fe03..b54202c51847 100644 +--- a/svx/source/svdraw/svdotextdecomposition.cxx ++++ b/svx/source/svdraw/svdotextdecomposition.cxx +@@ -770,11 +770,13 @@ void SdrTextObj::impDecomposeAutoFitTextPrimitive( + if(SDRTEXTHORZADJUST_BLOCK == eHAdj && !bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); ++ rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + + if(SDRTEXTVERTADJUST_BLOCK == eVAdj && bVerticalWriting) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); ++ rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + rOutliner.SetPaperSize(aNullSize); +@@ -983,10 +985,12 @@ void SdrTextObj::impDecomposeBlockTextPrimitive( + if(bHorizontalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(nAnchorTextWidth, 0)); ++ rOutliner.SetMinColumnWrapHeight(nAnchorTextHeight); + } + else if(bVerticalIsBlock) + { + rOutliner.SetMinAutoPaperSize(Size(0, nAnchorTextHeight)); ++ rOutliner.SetMinColumnWrapHeight(nAnchorTextWidth); + } + + if((rSdrBlockTextPrimitive.getWordWrap() || IsTextFrame()) && !rSdrBlockTextPrimitive.getUnlimitedPage()) +diff --git a/svx/source/svdraw/svdotxed.cxx b/svx/source/svdraw/svdotxed.cxx +index 6e423029f582..f62bd8d913cc 100644 +--- a/svx/source/svdraw/svdotxed.cxx ++++ b/svx/source/svdraw/svdotxed.cxx +@@ -349,6 +349,7 @@ void SdrTextObj::ImpSetTextEditParams() const + pEdtOutl->SetMinAutoPaperSize(aPaperMin); + pEdtOutl->SetMaxAutoPaperSize(aPaperMax); + pEdtOutl->SetPaperSize(Size()); ++ pEdtOutl->SetTextColumns(GetTextColumnsNumber(), GetTextColumnsSpacing()); + if (bContourFrame) { + tools::Rectangle aAnchorRect; + TakeTextAnchorRect(aAnchorRect); +diff --git a/svx/source/svdraw/svdoutl.cxx b/svx/source/svdraw/svdoutl.cxx +index fed5e1c82f25..738309182fd7 100644 +--- a/svx/source/svdraw/svdoutl.cxx ++++ b/svx/source/svdraw/svdoutl.cxx +@@ -57,6 +57,7 @@ void SdrOutliner::SetTextObj( const SdrTextObj* pObj ) + SetMinAutoPaperSize( Size() ); + SetMaxAutoPaperSize( aMaxSize ); + SetPaperSize( aMaxSize ); ++ SetTextColumns(pObj->GetTextColumnsNumber(), pObj->GetTextColumnsSpacing()); + ClearPolygon(); + } + +diff --git a/svx/source/unodraw/SvxXTextColumns.cxx b/svx/source/unodraw/SvxXTextColumns.cxx +new file mode 100644 +index 000000000000..3d4732cc9817 +--- /dev/null ++++ b/svx/source/unodraw/SvxXTextColumns.cxx +@@ -0,0 +1,334 @@ ++/* -*- 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/. ++ * ++ * This file incorporates work covered by the following license notice: ++ * ++ * Licensed to the Apache Software Foundation (ASF) under one or more ++ * contributor license agreements. See the NOTICE file distributed ++ * with this work for additional information regarding copyright ++ * ownership. The ASF licenses this file to you under the Apache ++ * License, Version 2.0 (the "License"); you may not use this file ++ * except in compliance with the License. You may obtain a copy of ++ * the License at http://www.apache.org/licenses/LICENSE-2.0 . ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++namespace ++{ ++enum : sal_uInt16 ++{ ++ WID_TXTCOL_IS_AUTOMATIC, ++ WID_TXTCOL_AUTO_DISTANCE, ++ WID_TXTCOL_LINE_WIDTH, ++ WID_TXTCOL_LINE_COLOR, ++ WID_TXTCOL_LINE_REL_HGT, ++ WID_TXTCOL_LINE_ALIGN, ++ WID_TXTCOL_LINE_IS_ON, ++ WID_TXTCOL_LINE_STYLE, ++}; ++ ++SfxItemPropertyMapEntry const saTextColumns_Impl[] = { ++ { u"IsAutomatic", WID_TXTCOL_IS_AUTOMATIC, cppu::UnoType::get(), ++ css::beans::PropertyAttribute::READONLY, 0 }, ++ { u"AutomaticDistance", WID_TXTCOL_AUTO_DISTANCE, cppu::UnoType::get(), 0, 0 }, ++ { u"SeparatorLineWidth", WID_TXTCOL_LINE_WIDTH, cppu::UnoType::get(), 0, 0 }, ++ { u"SeparatorLineColor", WID_TXTCOL_LINE_COLOR, ++ cppu::UnoType::get(), 0, 0 }, ++ { u"SeparatorLineRelativeHeight", WID_TXTCOL_LINE_REL_HGT, cppu::UnoType::get(), 0, ++ 0 }, ++ { u"SeparatorLineVerticalAlignment", WID_TXTCOL_LINE_ALIGN, ++ cppu::UnoType::get(), 0, 0 }, ++ { u"SeparatorLineIsOn", WID_TXTCOL_LINE_IS_ON, cppu::UnoType::get(), 0, 0 }, ++ { u"SeparatorLineStyle", WID_TXTCOL_LINE_STYLE, cppu::UnoType::get(), 0, 0 }, ++ { u"", 0, css::uno::Type(), 0, 0 }, ++}; ++ ++class SvxXTextColumns final ++ : public cppu::WeakImplHelper ++{ ++public: ++ SvxXTextColumns() = default; ++ ++ // XTextColumns ++ virtual sal_Int32 SAL_CALL getReferenceValue() override; ++ virtual sal_Int16 SAL_CALL getColumnCount() override; ++ virtual void SAL_CALL setColumnCount(sal_Int16 nColumns) override; ++ virtual css::uno::Sequence SAL_CALL getColumns() override; ++ virtual void SAL_CALL ++ setColumns(const css::uno::Sequence& Columns) override; ++ ++ // XPropertySet ++ virtual css::uno::Reference ++ SAL_CALL getPropertySetInfo() override; ++ virtual void SAL_CALL setPropertyValue(const OUString& aPropertyName, ++ const css::uno::Any& aValue) override; ++ virtual css::uno::Any SAL_CALL getPropertyValue(const OUString& PropertyName) override; ++ virtual void SAL_CALL addPropertyChangeListener( ++ const OUString& aPropertyName, ++ const css::uno::Reference& xListener) override; ++ virtual void SAL_CALL removePropertyChangeListener( ++ const OUString& aPropertyName, ++ const css::uno::Reference& aListener) override; ++ virtual void SAL_CALL addVetoableChangeListener( ++ const OUString& PropertyName, ++ const css::uno::Reference& aListener) override; ++ virtual void SAL_CALL removeVetoableChangeListener( ++ const OUString& PropertyName, ++ const css::uno::Reference& aListener) override; ++ ++ // XServiceInfo ++ virtual OUString SAL_CALL getImplementationName() override; ++ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; ++ virtual css::uno::Sequence SAL_CALL getSupportedServiceNames() override; ++ ++private: ++ sal_Int32 m_nReference = USHRT_MAX; ++ css::uno::Sequence m_aTextColumns; ++ bool m_bIsAutomaticWidth = true; ++ sal_Int32 m_nAutoDistance = 0; ++ ++ const SfxItemPropertySet m_aPropSet = { saTextColumns_Impl }; ++ ++ //separator line ++ sal_Int32 m_nSepLineWidth = 0; ++ com::sun::star::util::Color m_nSepLineColor = 0; // black ++ sal_Int32 m_nSepLineHeightRelative = 100; // full height ++ css::style::VerticalAlignment m_nSepLineVertAlign = css::style::VerticalAlignment_MIDDLE; ++ bool m_bSepLineIsOn = false; ++ sal_Int16 m_nSepLineStyle = css::text::ColumnSeparatorStyle::NONE; ++}; ++ ++OUString SvxXTextColumns::getImplementationName() { return "com.sun.star.comp.svx.TextColumns"; } ++ ++sal_Bool SvxXTextColumns::supportsService(const OUString& rServiceName) ++{ ++ return cppu::supportsService(this, rServiceName); ++} ++ ++css::uno::Sequence SvxXTextColumns::getSupportedServiceNames() ++{ ++ return { "com.sun.star.text.TextColumns" }; ++} ++ ++sal_Int32 SvxXTextColumns::getReferenceValue() ++{ ++ SolarMutexGuard aGuard; ++ return m_nReference; ++} ++ ++sal_Int16 SvxXTextColumns::getColumnCount() ++{ ++ SolarMutexGuard aGuard; ++ return static_cast(m_aTextColumns.getLength()); ++} ++ ++void SvxXTextColumns::setColumnCount(sal_Int16 nColumns) ++{ ++ SolarMutexGuard aGuard; ++ if (nColumns <= 0) ++ throw css::uno::RuntimeException(); ++ m_bIsAutomaticWidth = true; ++ m_aTextColumns.realloc(nColumns); ++ css::text::TextColumn* pCols = m_aTextColumns.getArray(); ++ m_nReference = USHRT_MAX; ++ sal_Int32 nWidth = m_nReference / nColumns; ++ sal_Int32 nDiff = m_nReference - nWidth * nColumns; ++ sal_Int32 nDist = m_nAutoDistance / 2; ++ for (sal_Int16 i = 0; i < nColumns; i++) ++ { ++ pCols[i].Width = nWidth; ++ pCols[i].LeftMargin = i == 0 ? 0 : nDist; ++ pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; ++ } ++ pCols[nColumns - 1].Width += nDiff; ++} ++ ++css::uno::Sequence SvxXTextColumns::getColumns() ++{ ++ SolarMutexGuard aGuard; ++ return m_aTextColumns; ++} ++ ++void SvxXTextColumns::setColumns(const css::uno::Sequence& rColumns) ++{ ++ SolarMutexGuard aGuard; ++ sal_Int32 nReferenceTemp = std::accumulate( ++ rColumns.begin(), rColumns.end(), sal_Int32(0), ++ [](const sal_Int32 nSum, const css::text::TextColumn& rCol) { return nSum + rCol.Width; }); ++ m_bIsAutomaticWidth = false; ++ m_nReference = !nReferenceTemp ? USHRT_MAX : nReferenceTemp; ++ m_aTextColumns = rColumns; ++} ++ ++css::uno::Reference SvxXTextColumns::getPropertySetInfo() ++{ ++ return m_aPropSet.getPropertySetInfo(); ++} ++ ++void SvxXTextColumns::setPropertyValue(const OUString& rPropertyName, const css::uno::Any& aValue) ++{ ++ const SfxItemPropertySimpleEntry* pEntry = m_aPropSet.getPropertyMap().getByName(rPropertyName); ++ if (!pEntry) ++ throw css::beans::UnknownPropertyException("Unknown property: " + rPropertyName, ++ static_cast(this)); ++ if (pEntry->nFlags & css::beans::PropertyAttribute::READONLY) ++ throw css::beans::PropertyVetoException("Property is read-only: " + rPropertyName, ++ static_cast(this)); ++ ++ switch (pEntry->nWID) ++ { ++ case WID_TXTCOL_LINE_WIDTH: ++ if (sal_Int32 nTmp; !(aValue >>= nTmp) || nTmp < 0) ++ throw css::lang::IllegalArgumentException(); ++ else ++ m_nSepLineWidth = convertMm100ToTwip(nTmp); ++ break; ++ case WID_TXTCOL_LINE_COLOR: ++ if (!(aValue >>= m_nSepLineColor)) ++ throw css::lang::IllegalArgumentException(); ++ break; ++ case WID_TXTCOL_LINE_STYLE: ++ if (!(aValue >>= m_nSepLineStyle)) ++ throw css::lang::IllegalArgumentException(); ++ break; ++ case WID_TXTCOL_LINE_REL_HGT: ++ if (sal_Int32 nTmp; !(aValue >>= nTmp) || nTmp < 0) ++ throw css::lang::IllegalArgumentException(); ++ else ++ m_nSepLineHeightRelative = nTmp; ++ break; ++ case WID_TXTCOL_LINE_ALIGN: ++ if (css::style::VerticalAlignment eAlign; aValue >>= eAlign) ++ m_nSepLineVertAlign = eAlign; ++ else if (sal_Int8 nTmp; aValue >>= nTmp) ++ m_nSepLineVertAlign = static_cast(nTmp); ++ else ++ throw css::lang::IllegalArgumentException(); ++ break; ++ case WID_TXTCOL_LINE_IS_ON: ++ if (!(aValue >>= m_bSepLineIsOn)) ++ throw css::lang::IllegalArgumentException(); ++ break; ++ case WID_TXTCOL_AUTO_DISTANCE: ++ if (sal_Int32 nTmp; !(aValue >>= nTmp) || nTmp < 0 || nTmp >= m_nReference) ++ throw css::lang::IllegalArgumentException(); ++ else ++ { ++ m_nAutoDistance = nTmp; ++ sal_Int32 nColumns = m_aTextColumns.getLength(); ++ css::text::TextColumn* pCols = m_aTextColumns.getArray(); ++ sal_Int32 nDist = m_nAutoDistance / 2; ++ for (sal_Int32 i = 0; i < nColumns; i++) ++ { ++ pCols[i].LeftMargin = i == 0 ? 0 : nDist; ++ pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; ++ } ++ } ++ break; ++ } ++} ++ ++css::uno::Any SvxXTextColumns::getPropertyValue(const OUString& rPropertyName) ++{ ++ const SfxItemPropertySimpleEntry* pEntry = m_aPropSet.getPropertyMap().getByName(rPropertyName); ++ if (!pEntry) ++ throw css::beans::UnknownPropertyException("Unknown property: " + rPropertyName, ++ static_cast(this)); ++ ++ css::uno::Any aRet; ++ switch (pEntry->nWID) ++ { ++ case WID_TXTCOL_LINE_WIDTH: ++ aRet <<= static_cast(convertTwipToMm100(m_nSepLineWidth)); ++ break; ++ case WID_TXTCOL_LINE_COLOR: ++ aRet <<= m_nSepLineColor; ++ break; ++ case WID_TXTCOL_LINE_STYLE: ++ aRet <<= m_nSepLineStyle; ++ break; ++ case WID_TXTCOL_LINE_REL_HGT: ++ aRet <<= m_nSepLineHeightRelative; ++ break; ++ case WID_TXTCOL_LINE_ALIGN: ++ aRet <<= m_nSepLineVertAlign; ++ break; ++ case WID_TXTCOL_LINE_IS_ON: ++ aRet <<= m_bSepLineIsOn; ++ break; ++ case WID_TXTCOL_IS_AUTOMATIC: ++ aRet <<= m_bIsAutomaticWidth; ++ break; ++ case WID_TXTCOL_AUTO_DISTANCE: ++ aRet <<= m_nAutoDistance; ++ break; ++ } ++ return aRet; ++} ++ ++void SvxXTextColumns::addPropertyChangeListener( ++ const OUString& /*rPropertyName*/, ++ const css::uno::Reference& /*xListener*/) ++{ ++} ++ ++void SvxXTextColumns::removePropertyChangeListener( ++ const OUString& /*rPropertyName*/, ++ const css::uno::Reference& /*xListener*/) ++{ ++} ++ ++void SvxXTextColumns::addVetoableChangeListener( ++ const OUString& /*rPropertyName*/, ++ const css::uno::Reference& /*xListener*/) ++{ ++} ++ ++void SvxXTextColumns::removeVetoableChangeListener( ++ const OUString& /*rPropertyName*/, ++ const css::uno::Reference& /*xListener*/) ++{ ++} ++} ++ ++css::uno::Reference SvxXTextColumns_createInstance() noexcept ++{ ++ return static_cast(new SvxXTextColumns); ++} ++ ++extern "C" SVXCORE_DLLPUBLIC css::uno::XInterface* ++com_sun_star_comp_svx_TextColumns_get_implementation(css::uno::XComponentContext*, ++ css::uno::Sequence const&) ++{ ++ return cppu::acquire(new SvxXTextColumns); ++} ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ +diff --git a/svx/source/unodraw/unomod.cxx b/svx/source/unodraw/unomod.cxx +index eb80ab186deb..69f1b468f10d 100644 +--- a/svx/source/unodraw/unomod.cxx ++++ b/svx/source/unodraw/unomod.cxx +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -182,6 +183,10 @@ css::uno::Reference create( + uno::Reference< uno::XInterface> xRet( static_cast< ::cppu::OWeakObject* >( pGraphicHelper.get() ) ); + return xRet; + } ++ else if (rServiceSpecifier == "com.sun.star.text.TextColumns") ++ { ++ return SvxXTextColumns_createInstance(); ++ } + + uno::Reference< uno::XInterface > xRet( SvxUnoDrawMSFactory::createTextField( rServiceSpecifier ) ); + if( !xRet.is() ) +diff --git a/svx/source/unodraw/unopool.cxx b/svx/source/unodraw/unopool.cxx +index ec293ac1bf55..082841bb0827 100644 +--- a/svx/source/unodraw/unopool.cxx ++++ b/svx/source/unodraw/unopool.cxx +@@ -247,6 +247,13 @@ void SvxUnoDrawPool::_getPropertyStates( const comphelper::PropertyMapEntry** pp + } + } + break; ++ case OWN_ATTR_TEXTCOLUMNS: ++ if (IsStaticDefaultItem(&pPool->GetDefaultItem(sal_uInt16(SDRATTR_TEXTCOLUMNS_NUMBER))) ++ && IsStaticDefaultItem(&pPool->GetDefaultItem(sal_uInt16(SDRATTR_TEXTCOLUMNS_SPACING)))) ++ *pStates = beans::PropertyState_DEFAULT_VALUE; ++ else ++ *pStates = beans::PropertyState_DIRECT_VALUE; ++ break; + default: + //#i18732# - correction: + // use method instead of using probably +diff --git a/svx/source/unodraw/unoshape.cxx b/svx/source/unodraw/unoshape.cxx +index 1885348f9c64..9461bb00dafa 100644 +--- a/svx/source/unodraw/unoshape.cxx ++++ b/svx/source/unodraw/unoshape.cxx +@@ -84,6 +84,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -183,14 +184,7 @@ sal_Int16 GetTextFitToSizeScale(SdrObject* pObject) + return 0; + } + +- std::unique_ptr pOutliner +- = pTextObj->getSdrModelFromSdrObject().createOutliner(OutlinerMode::TextObject); +- tools::Rectangle aBoundRect(pTextObj->GetCurrentBoundRect()); +- pTextObj->SetupOutlinerFormatting(*pOutliner, aBoundRect); +- sal_uInt16 nX = 0; +- sal_uInt16 nY = 0; +- pOutliner->GetGlobalCharStretching(nX, nY); +- return nY; ++ return pTextObj->GetFontScaleY(); + } + } + +@@ -2519,6 +2513,27 @@ bool SvxShape::setPropertyValueImpl( const OUString&, const SfxItemPropertySimpl + return false; + } + } ++ ++ case OWN_ATTR_TEXTCOLUMNS: ++ { ++ if (auto pTextObj = dynamic_cast(GetSdrObject())) ++ { ++ css::uno::Reference xTextColumns; ++ if (rValue >>= xTextColumns) ++ { ++ pTextObj->SetTextColumnsNumber(xTextColumns->getColumnCount()); ++ if (css::uno::Reference xPropSet{ xTextColumns, ++ css::uno::UNO_QUERY }) ++ { ++ auto aVal = xPropSet->getPropertyValue("AutomaticDistance"); ++ if (sal_Int32 nSpacing; aVal >>= nSpacing) ++ pTextObj->SetTextColumnsSpacing(nSpacing); ++ } ++ } ++ } ++ return true; ++ } ++ + default: + { + return false; +@@ -2919,6 +2934,23 @@ bool SvxShape::getPropertyValueImpl( const OUString&, const SfxItemPropertySimpl + break; + } + ++ case OWN_ATTR_TEXTCOLUMNS: ++ { ++ if (auto pTextObj = dynamic_cast(GetSdrObject())) ++ { ++ if (pTextObj->HasTextColumnsNumber() || pTextObj->HasTextColumnsSpacing()) ++ { ++ auto xIf = SvxXTextColumns_createInstance(); ++ css::uno::Reference xCols(xIf, css::uno::UNO_QUERY_THROW); ++ xCols->setColumnCount(pTextObj->GetTextColumnsNumber()); ++ css::uno::Reference xProp(xIf, css::uno::UNO_QUERY_THROW); ++ xProp->setPropertyValue("AutomaticDistance", ++ css::uno::Any(pTextObj->GetTextColumnsSpacing())); ++ rValue <<= xIf; ++ } ++ } ++ break; ++ } + + default: + return false; +diff --git a/svx/util/svxcore.component b/svx/util/svxcore.component +index b36a8d71bcb4..d8989b71f4e9 100644 +--- a/svx/util/svxcore.component ++++ b/svx/util/svxcore.component +@@ -92,4 +92,8 @@ + constructor="com_sun_star_comp_svx_StylesPreviewToolBoxControl_get_implementation"> + + ++ ++ ++ + +diff --git a/sw/inc/unomap.hxx b/sw/inc/unomap.hxx +index 1cd22609f02e..d2c54d204188 100644 +--- a/sw/inc/unomap.hxx ++++ b/sw/inc/unomap.hxx +@@ -58,7 +58,6 @@ struct SfxItemPropertyMapEntry; + #define PROPERTY_MAP_AUTO_TEXT_GROUP 31 + #define PROPERTY_MAP_TEXTPORTION_EXTENSIONS 34 + #define PROPERTY_MAP_FOOTNOTE 35 +-#define PROPERTY_MAP_TEXT_COLUMS 36 + #define PROPERTY_MAP_PARAGRAPH 37 + #define PROPERTY_MAP_EMBEDDED_OBJECT 38 + #define PROPERTY_MAP_REDLINE 39 +@@ -305,16 +304,6 @@ struct SfxItemPropertyMapEntry; + #define WID_IS_OUTLINE 4 + #define WID_DEFAULT_LIST_ID 5 + +-// TextColumns +-#define WID_TXTCOL_LINE_WIDTH 0 +-#define WID_TXTCOL_LINE_COLOR 1 +-#define WID_TXTCOL_LINE_REL_HGT 2 +-#define WID_TXTCOL_LINE_ALIGN 3 +-#define WID_TXTCOL_LINE_IS_ON 4 +-#define WID_TXTCOL_IS_AUTOMATIC 5 +-#define WID_TXTCOL_AUTO_DISTANCE 6 +-#define WID_TXTCOL_LINE_STYLE 7 +- + // This define would need the include of , but this ends + // in a mess; there *are* double used symbols which are used in a #define in + // editengine and as an enum in sw; these will then collide and lead to severe +diff --git a/sw/inc/unosett.hxx b/sw/inc/unosett.hxx +index 4b3f177fb034..9a118f99cdec 100644 +--- a/sw/inc/unosett.hxx ++++ b/sw/inc/unosett.hxx +@@ -239,71 +239,6 @@ public: + + }; + +-class SwXTextColumns final : public cppu::WeakAggImplHelper4 +-< +- +- css::lang::XUnoTunnel, +- css::beans::XPropertySet, +- css::text::XTextColumns, +- css::lang::XServiceInfo +-> +-{ +- sal_Int32 m_nReference; +- css::uno::Sequence< css::text::TextColumn> m_aTextColumns; +- bool m_bIsAutomaticWidth; +- sal_Int32 m_nAutoDistance; +- +- const SfxItemPropertySet* m_pPropSet; +- +- //separator line +- sal_Int32 m_nSepLineWidth; +- Color m_nSepLineColor; +- sal_Int8 m_nSepLineHeightRelative; +- css::style::VerticalAlignment m_nSepLineVertAlign; +- bool m_bSepLineIsOn; +- sal_Int8 m_nSepLineStyle; +- +- +- virtual ~SwXTextColumns() override; +-public: +- SwXTextColumns(); +- SwXTextColumns(const SwFormatCol& rFormatCol); +- +- static const css::uno::Sequence< sal_Int8 > & getUnoTunnelId(); +- +- //XUnoTunnel +- virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override; +- +- //XTextColumns +- virtual sal_Int32 SAL_CALL getReferenceValue( ) override; +- virtual sal_Int16 SAL_CALL getColumnCount( ) override; +- virtual void SAL_CALL setColumnCount( sal_Int16 nColumns ) override; +- virtual css::uno::Sequence< css::text::TextColumn > SAL_CALL getColumns( ) override; +- virtual void SAL_CALL setColumns( const css::uno::Sequence< css::text::TextColumn >& Columns ) override; +- +- //XPropertySet +- virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; +- virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; +- virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; +- virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; +- virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; +- virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; +- virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; +- +- //XServiceInfo +- virtual OUString SAL_CALL getImplementationName() override; +- virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; +- virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; +- +- sal_Int32 GetSepLineWidth() const {return m_nSepLineWidth;} +- Color GetSepLineColor() const {return m_nSepLineColor;} +- sal_Int8 GetSepLineHeightRelative() const {return m_nSepLineHeightRelative;} +- css::style::VerticalAlignment GetSepLineVertAlign() const {return m_nSepLineVertAlign;} +- bool GetSepLineIsOn() const {return m_bSepLineIsOn;} +- sal_Int8 GetSepLineStyle() const {return m_nSepLineStyle;} +- +- bool IsAutomaticWidth() const {return m_bIsAutomaticWidth;} +-}; + #endif + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/sw/source/core/layout/atrfrm.cxx b/sw/source/core/layout/atrfrm.cxx +index 8755ee81235d..046566250a8c 100644 +--- a/sw/source/core/layout/atrfrm.cxx ++++ b/sw/source/core/layout/atrfrm.cxx +@@ -17,6 +17,9 @@ + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + ++#include ++ ++#include + #include + #include + #include +@@ -78,6 +81,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -85,6 +89,7 @@ + #include + #include + #include ++#include + + #include + +@@ -1097,7 +1102,81 @@ bool SwFormatCol::QueryValue( uno::Any& rVal, sal_uInt8 nMemberId ) const + } + else + { +- uno::Reference< text::XTextColumns > xCols = new SwXTextColumns(*this); ++ uno::Reference xCols(SvxXTextColumns_createInstance(), ++ css::uno::UNO_QUERY_THROW); ++ uno::Reference xProps(xCols, css::uno::UNO_QUERY_THROW); ++ ++ if (GetNumCols() > 0) ++ { ++ xCols->setColumnCount(GetNumCols()); ++ const sal_uInt16 nItemGutterWidth = GetGutterWidth(); ++ sal_Int32 nAutoDistance = IsOrtho() ? USHRT_MAX == nItemGutterWidth ++ ? DEF_GUTTER_WIDTH ++ : static_cast(nItemGutterWidth) ++ : 0; ++ nAutoDistance = convertTwipToMm100(nAutoDistance); ++ xProps->setPropertyValue(UNO_NAME_AUTOMATIC_DISTANCE, uno::Any(nAutoDistance)); ++ ++ if (!IsOrtho()) ++ { ++ auto aTextColumns = xCols->getColumns(); ++ text::TextColumn* pColumns = aTextColumns.getArray(); ++ const SwColumns& rCols = GetColumns(); ++ for (sal_Int32 i = 0; i < aTextColumns.getLength(); ++i) ++ { ++ const SwColumn* pCol = &rCols[i]; ++ ++ pColumns[i].Width = pCol->GetWishWidth(); ++ pColumns[i].LeftMargin = convertTwipToMm100(pCol->GetLeft()); ++ pColumns[i].RightMargin = convertTwipToMm100(pCol->GetRight()); ++ } ++ xCols->setColumns(aTextColumns); // sets "IsAutomatic" property to false ++ } ++ } ++ uno::Any aVal; ++ aVal <<= static_cast(GetLineWidth()); ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_WIDTH, aVal); ++ aVal <<= GetLineColor(); ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_COLOR, aVal); ++ aVal <<= static_cast(GetLineHeight()); ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT, aVal); ++ aVal <<= GetLineAdj() != COLADJ_NONE; ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_IS_ON, aVal); ++ sal_Int16 nStyle; ++ switch (GetLineStyle()) ++ { ++ case SvxBorderLineStyle::SOLID: ++ nStyle = css::text::ColumnSeparatorStyle::SOLID; ++ break; ++ case SvxBorderLineStyle::DOTTED: ++ nStyle = css::text::ColumnSeparatorStyle::DOTTED; ++ break; ++ case SvxBorderLineStyle::DASHED: ++ nStyle = css::text::ColumnSeparatorStyle::DASHED; ++ break; ++ case SvxBorderLineStyle::NONE: ++ default: ++ nStyle = css::text::ColumnSeparatorStyle::NONE; ++ break; ++ } ++ aVal <<= nStyle; ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_STYLE, aVal); ++ style::VerticalAlignment eAlignment; ++ switch (GetLineAdj()) ++ { ++ case COLADJ_TOP: ++ eAlignment = style::VerticalAlignment_TOP; ++ break; ++ case COLADJ_BOTTOM: ++ eAlignment = style::VerticalAlignment_BOTTOM; ++ break; ++ case COLADJ_CENTER: ++ case COLADJ_NONE: ++ default: ++ eAlignment = style::VerticalAlignment_MIDDLE; ++ } ++ aVal <<= eAlignment; ++ xProps->setPropertyValue(UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT, aVal); + rVal <<= xCols; + } + return true; +@@ -1141,24 +1220,33 @@ bool SwFormatCol::PutValue( const uno::Any& rVal, sal_uInt8 nMemberId ) + m_nWidth = nWidthSum; + m_bOrtho = false; + +- auto pSwColums = comphelper::getUnoTunnelImplementation(xCols); +- if(pSwColums) ++ if (uno::Reference xProps{ xCols, css::uno::UNO_QUERY }) + { +- m_bOrtho = pSwColums->IsAutomaticWidth(); +- m_nLineWidth = pSwColums->GetSepLineWidth(); +- m_aLineColor = pSwColums->GetSepLineColor(); +- m_nLineHeight = pSwColums->GetSepLineHeightRelative(); +- switch ( pSwColums->GetSepLineStyle() ) ++ xProps->getPropertyValue(UNO_NAME_IS_AUTOMATIC) >>= m_bOrtho; ++ xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_WIDTH) >>= m_nLineWidth; ++ xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_COLOR) >>= m_aLineColor; ++ if (sal_Int32 nHeight; ++ xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT) >>= nHeight) ++ m_nLineHeight = nHeight; ++ switch (xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_STYLE).get()) + { + default: +- case 0: m_eLineStyle = SvxBorderLineStyle::NONE; break; +- case 1: m_eLineStyle = SvxBorderLineStyle::SOLID; break; +- case 2: m_eLineStyle = SvxBorderLineStyle::DOTTED; break; +- case 3: m_eLineStyle = SvxBorderLineStyle::DASHED; break; ++ case css::text::ColumnSeparatorStyle::NONE: ++ m_eLineStyle = SvxBorderLineStyle::NONE; ++ break; ++ case css::text::ColumnSeparatorStyle::SOLID: ++ m_eLineStyle = SvxBorderLineStyle::SOLID; ++ break; ++ case css::text::ColumnSeparatorStyle::DOTTED: ++ m_eLineStyle = SvxBorderLineStyle::DOTTED; ++ break; ++ case css::text::ColumnSeparatorStyle::DASHED: ++ m_eLineStyle = SvxBorderLineStyle::DASHED; ++ break; + } +- if(!pSwColums->GetSepLineIsOn()) ++ if (!xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_IS_ON).get()) + m_eAdj = COLADJ_NONE; +- else switch(pSwColums->GetSepLineVertAlign()) ++ else switch (xProps->getPropertyValue(UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT).get()) + { + case style::VerticalAlignment_TOP: m_eAdj = COLADJ_TOP; break; + case style::VerticalAlignment_MIDDLE: m_eAdj = COLADJ_CENTER; break; +diff --git a/sw/source/core/unocore/unocoll.cxx b/sw/source/core/unocore/unocoll.cxx +index a6287cbd714d..65d10d0d96f4 100644 +--- a/sw/source/core/unocore/unocoll.cxx ++++ b/sw/source/core/unocore/unocoll.cxx +@@ -35,6 +35,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -795,7 +796,7 @@ SwXServiceProvider::MakeInstance(SwServiceType nObjectType, SwDoc & rDoc) + xRet = static_cast(new SwXNumberingRules(rDoc)); + break; + case SwServiceType::TextColumns: +- xRet = static_cast(new SwXTextColumns); ++ xRet = SvxXTextColumns_createInstance(); + break; + case SwServiceType::Defaults: + xRet = static_cast(new SwXTextDefaults(&rDoc)); +diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx +index fc8d3713c4a2..a6877c91ad35 100644 +--- a/sw/source/core/unocore/unomap.cxx ++++ b/sw/source/core/unocore/unomap.cxx +@@ -681,23 +681,6 @@ const SfxItemPropertyMapEntry* SwUnoPropertyMapProvider::GetPropertyMapEntries(s + m_aMapEntriesArr[nPropertyId] = GetFootnotePropertyMap(); + } + break; +- case PROPERTY_MAP_TEXT_COLUMS : +- { +- static SfxItemPropertyMapEntry const aTextColumns_Impl[] = +- { +- {u"" UNO_NAME_IS_AUTOMATIC, WID_TXTCOL_IS_AUTOMATIC, cppu::UnoType::get(),PropertyAttribute::READONLY, 0}, +- {u"" UNO_NAME_AUTOMATIC_DISTANCE, WID_TXTCOL_AUTO_DISTANCE, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_WIDTH, WID_TXTCOL_LINE_WIDTH, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_COLOR, WID_TXTCOL_LINE_COLOR, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_RELATIVE_HEIGHT, WID_TXTCOL_LINE_REL_HGT, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_VERTIVAL_ALIGNMENT, WID_TXTCOL_LINE_ALIGN, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_IS_ON, WID_TXTCOL_LINE_IS_ON, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- {u"" UNO_NAME_SEPARATOR_LINE_STYLE, WID_TXTCOL_LINE_STYLE, cppu::UnoType::get(),PROPERTY_NONE, 0}, +- { u"", 0, css::uno::Type(), 0, 0 } +- }; +- m_aMapEntriesArr[nPropertyId] = aTextColumns_Impl; +- } +- break; + case PROPERTY_MAP_REDLINE : + { + m_aMapEntriesArr[nPropertyId] = GetRedlinePropertyMap(); +diff --git a/sw/source/core/unocore/unomap1.cxx b/sw/source/core/unocore/unomap1.cxx +index 11508d462912..c6cff3dd7906 100644 +--- a/sw/source/core/unocore/unomap1.cxx ++++ b/sw/source/core/unocore/unomap1.cxx +@@ -1274,12 +1274,6 @@ const SfxItemPropertySet* SwUnoPropertyMapProvider::GetPropertySet( sal_uInt16 + m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_FOOTNOTE; + } + break; +- case PROPERTY_MAP_TEXT_COLUMS : +- { +- static SfxItemPropertySet aPROPERTY_MAP_TEXT_COLUMS(pEntries); +- m_aPropertySetArr[nPropertyId] = &aPROPERTY_MAP_TEXT_COLUMS; +- } +- break; + case PROPERTY_MAP_PARAGRAPH : + { + static SfxItemPropertySet aPROPERTY_MAP_PARAGRAPH(pEntries); +diff --git a/sw/source/core/unocore/unosett.cxx b/sw/source/core/unocore/unosett.cxx +index b36bc88a7bef..a4394972d889 100644 +--- a/sw/source/core/unocore/unosett.cxx ++++ b/sw/source/core/unocore/unosett.cxx +@@ -90,11 +90,6 @@ namespace + return pDoc->getIDocumentStylePoolAccess().GetPageDescFromPool(RES_POOLPAGE_STANDARD)->GetNotifier(); + } + } +-// Constants for the css::text::ColumnSeparatorStyle +-#define API_COL_LINE_NONE 0 +-#define API_COL_LINE_SOLID 1 +-#define API_COL_LINE_DOTTED 2 +-#define API_COL_LINE_DASHED 3 + + #define WID_PREFIX 0 + #define WID_SUFFIX 1 +@@ -2166,290 +2161,4 @@ SwXChapterNumbering::~SwXChapterNumbering() + { + } + +-OUString SwXTextColumns::getImplementationName() +-{ +- return "SwXTextColumns"; +-} +- +-sal_Bool SwXTextColumns::supportsService(const OUString& rServiceName) +-{ +- return cppu::supportsService(this, rServiceName); +-} +- +-Sequence< OUString > SwXTextColumns::getSupportedServiceNames() +-{ +- Sequence aRet { "com.sun.star.text.TextColumns" }; +- return aRet; +-} +- +-SwXTextColumns::SwXTextColumns() : +- m_nReference(0), +- m_bIsAutomaticWidth(true), +- m_nAutoDistance(0), +- m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_COLUMS)), +- m_nSepLineWidth(0), +- m_nSepLineColor(0), //black +- m_nSepLineHeightRelative(100),//full height +- m_nSepLineVertAlign(style::VerticalAlignment_MIDDLE), +- m_bSepLineIsOn(false), +- m_nSepLineStyle(API_COL_LINE_NONE) // None +-{ +-} +- +-SwXTextColumns::SwXTextColumns(const SwFormatCol& rFormatCol) : +- m_nReference(0), +- m_aTextColumns(rFormatCol.GetNumCols()), +- m_bIsAutomaticWidth(rFormatCol.IsOrtho()), +- m_pPropSet(aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_COLUMS)) +-{ +- const sal_uInt16 nItemGutterWidth = rFormatCol.GetGutterWidth(); +- m_nAutoDistance = m_bIsAutomaticWidth ? +- USHRT_MAX == nItemGutterWidth ? DEF_GUTTER_WIDTH : static_cast(nItemGutterWidth) +- : 0; +- m_nAutoDistance = convertTwipToMm100(m_nAutoDistance); +- +- TextColumn* pColumns = m_aTextColumns.getArray(); +- const SwColumns& rCols = rFormatCol.GetColumns(); +- for(sal_Int32 i = 0; i < m_aTextColumns.getLength(); ++i) +- { +- const SwColumn* pCol = &rCols[i]; +- +- pColumns[i].Width = pCol->GetWishWidth(); +- m_nReference += pColumns[i].Width; +- pColumns[i].LeftMargin = convertTwipToMm100(pCol->GetLeft ()); +- pColumns[i].RightMargin = convertTwipToMm100(pCol->GetRight()); +- } +- if(!m_aTextColumns.hasElements()) +- m_nReference = USHRT_MAX; +- +- m_nSepLineWidth = rFormatCol.GetLineWidth(); +- m_nSepLineColor = rFormatCol.GetLineColor(); +- m_nSepLineHeightRelative = rFormatCol.GetLineHeight(); +- m_bSepLineIsOn = rFormatCol.GetLineAdj() != COLADJ_NONE; +- sal_Int8 nStyle = API_COL_LINE_NONE; +- switch (rFormatCol.GetLineStyle()) +- { +- case SvxBorderLineStyle::SOLID: nStyle = API_COL_LINE_SOLID; break; +- case SvxBorderLineStyle::DOTTED: nStyle= API_COL_LINE_DOTTED; break; +- case SvxBorderLineStyle::DASHED: nStyle= API_COL_LINE_DASHED; break; +- default: break; +- } +- m_nSepLineStyle = nStyle; +- switch(rFormatCol.GetLineAdj()) +- { +- case COLADJ_TOP: m_nSepLineVertAlign = style::VerticalAlignment_TOP; break; +- case COLADJ_BOTTOM: m_nSepLineVertAlign = style::VerticalAlignment_BOTTOM; break; +- case COLADJ_CENTER: +- case COLADJ_NONE: m_nSepLineVertAlign = style::VerticalAlignment_MIDDLE; +- } +-} +- +-SwXTextColumns::~SwXTextColumns() +-{ +-} +- +-sal_Int32 SwXTextColumns::getReferenceValue() +-{ +- SolarMutexGuard aGuard; +- return m_nReference; +-} +- +-sal_Int16 SwXTextColumns::getColumnCount() +-{ +- SolarMutexGuard aGuard; +- return static_cast< sal_Int16>( m_aTextColumns.getLength() ); +-} +- +-void SwXTextColumns::setColumnCount(sal_Int16 nColumns) +-{ +- SolarMutexGuard aGuard; +- if(nColumns <= 0) +- throw uno::RuntimeException(); +- m_bIsAutomaticWidth = true; +- m_aTextColumns.realloc(nColumns); +- TextColumn* pCols = m_aTextColumns.getArray(); +- m_nReference = USHRT_MAX; +- sal_Int32 nWidth = m_nReference / nColumns; +- sal_Int32 nDiff = m_nReference - nWidth * nColumns; +- sal_Int32 nDist = m_nAutoDistance / 2; +- for(sal_Int16 i = 0; i < nColumns; i++) +- { +- pCols[i].Width = nWidth; +- pCols[i].LeftMargin = i == 0 ? 0 : nDist; +- pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; +- } +- pCols[nColumns - 1].Width += nDiff; +-} +- +-uno::Sequence< TextColumn > SwXTextColumns::getColumns() +-{ +- SolarMutexGuard aGuard; +- return m_aTextColumns; +-} +- +-void SwXTextColumns::setColumns(const uno::Sequence< TextColumn >& rColumns) +-{ +- SolarMutexGuard aGuard; +- sal_Int32 nReferenceTemp = std::accumulate(rColumns.begin(), rColumns.end(), sal_Int32(0), +- [](const sal_Int32 nSum, const TextColumn& rCol) { return nSum + rCol.Width; }); +- m_bIsAutomaticWidth = false; +- m_nReference = !nReferenceTemp ? USHRT_MAX : nReferenceTemp; +- m_aTextColumns = rColumns; +-} +- +-uno::Reference< XPropertySetInfo > SwXTextColumns::getPropertySetInfo( ) +-{ +- static uno::Reference< beans::XPropertySetInfo > aRef = m_pPropSet->getPropertySetInfo(); +- return aRef; +-} +- +-void SwXTextColumns::setPropertyValue( const OUString& rPropertyName, const Any& aValue ) +-{ +- const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); +- if (!pEntry) +- throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); +- if ( pEntry->nFlags & PropertyAttribute::READONLY) +- throw PropertyVetoException("Property is read-only: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); +- +- switch(pEntry->nWID) +- { +- case WID_TXTCOL_LINE_WIDTH: +- { +- sal_Int32 nTmp = 0; +- aValue >>= nTmp; +- if(nTmp < 0) +- throw IllegalArgumentException(); +- m_nSepLineWidth = convertMm100ToTwip(nTmp); +- } +- break; +- case WID_TXTCOL_LINE_COLOR: +- aValue >>= m_nSepLineColor; +- break; +- case WID_TXTCOL_LINE_STYLE: +- { +- aValue >>= m_nSepLineStyle; +- } +- break; +- case WID_TXTCOL_LINE_REL_HGT: +- { +- sal_Int8 nTmp = 0; +- aValue >>= nTmp; +- if(nTmp < 0) +- throw IllegalArgumentException(); +- m_nSepLineHeightRelative = nTmp; +- } +- break; +- case WID_TXTCOL_LINE_ALIGN: +- { +- style::VerticalAlignment eAlign; +- if(!(aValue >>= eAlign) ) +- { +- sal_Int8 nTmp = 0; +- if (! ( aValue >>= nTmp ) ) +- throw IllegalArgumentException(); +- m_nSepLineVertAlign = static_cast(nTmp); +- } +- else +- m_nSepLineVertAlign = eAlign; +- } +- break; +- case WID_TXTCOL_LINE_IS_ON: +- m_bSepLineIsOn = *o3tl::doAccess(aValue); +- break; +- case WID_TXTCOL_AUTO_DISTANCE: +- { +- sal_Int32 nTmp = 0; +- aValue >>= nTmp; +- if(nTmp < 0 || nTmp >= m_nReference) +- throw IllegalArgumentException(); +- m_nAutoDistance = nTmp; +- sal_Int32 nColumns = m_aTextColumns.getLength(); +- TextColumn* pCols = m_aTextColumns.getArray(); +- sal_Int32 nDist = m_nAutoDistance / 2; +- for(sal_Int32 i = 0; i < nColumns; i++) +- { +- pCols[i].LeftMargin = i == 0 ? 0 : nDist; +- pCols[i].RightMargin = i == nColumns - 1 ? 0 : nDist; +- } +- } +- break; +- } +-} +- +-Any SwXTextColumns::getPropertyValue( const OUString& rPropertyName ) +-{ +- const SfxItemPropertySimpleEntry* pEntry = m_pPropSet->getPropertyMap().getByName( rPropertyName ); +- if (!pEntry) +- throw UnknownPropertyException("Unknown property: " + rPropertyName, static_cast < cppu::OWeakObject * > ( this ) ); +- +- Any aRet; +- switch(pEntry->nWID) +- { +- case WID_TXTCOL_LINE_WIDTH: +- aRet <<= static_cast < sal_Int32 >(convertTwipToMm100(m_nSepLineWidth)); +- break; +- case WID_TXTCOL_LINE_COLOR: +- aRet <<= m_nSepLineColor; +- break; +- case WID_TXTCOL_LINE_STYLE: +- aRet <<= m_nSepLineStyle; +- break; +- case WID_TXTCOL_LINE_REL_HGT: +- aRet <<= m_nSepLineHeightRelative; +- break; +- case WID_TXTCOL_LINE_ALIGN: +- aRet <<= m_nSepLineVertAlign; +- break; +- case WID_TXTCOL_LINE_IS_ON: +- aRet <<= m_bSepLineIsOn; +- break; +- case WID_TXTCOL_IS_AUTOMATIC : +- aRet <<= m_bIsAutomaticWidth; +- break; +- case WID_TXTCOL_AUTO_DISTANCE: +- aRet <<= m_nAutoDistance; +- break; +- } +- return aRet; +-} +- +-void SwXTextColumns::addPropertyChangeListener( +- const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +-{ +-} +- +-void SwXTextColumns::removePropertyChangeListener( +- const OUString& /*rPropertyName*/, const uno::Reference< XPropertyChangeListener >& /*xListener*/ ) +-{ +-} +- +-void SwXTextColumns::addVetoableChangeListener( +- const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +-{ +-} +- +-void SwXTextColumns::removeVetoableChangeListener( +- const OUString& /*rPropertyName*/, const uno::Reference< XVetoableChangeListener >& /*xListener*/ ) +-{ +-} +- +-namespace +-{ +- class theSwXTextColumnsUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theSwXTextColumnsUnoTunnelId > {}; +-} +- +-const uno::Sequence< sal_Int8 > & SwXTextColumns::getUnoTunnelId() +-{ +- return theSwXTextColumnsUnoTunnelId::get().getSeq(); +-} +- +-sal_Int64 SAL_CALL SwXTextColumns::getSomething( const uno::Sequence< sal_Int8 >& rId ) +-{ +- if( isUnoTunnelId(rId) ) +- { +- return sal::static_int_cast< sal_Int64 >( reinterpret_cast< sal_IntPtr >(this) ); +- } +- return 0; +-} +- + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/test/source/sheet/xsheetannotationshapesupplier.cxx b/test/source/sheet/xsheetannotationshapesupplier.cxx +index dac446b72b52..fe392fc83494 100644 +--- a/test/source/sheet/xsheetannotationshapesupplier.cxx ++++ b/test/source/sheet/xsheetannotationshapesupplier.cxx +@@ -33,7 +33,7 @@ void XSheetAnnotationShapeSupplier::testGetAnnotationShape() + CPPUNIT_ASSERT_EQUAL_MESSAGE("getAnnotationShape() wrong width", + sal_Int32(11275), xShape->getSize().Width); + CPPUNIT_ASSERT_EQUAL_MESSAGE("getAnnotationShape() wrong height", +- sal_Int32(1386), xShape->getSize().Height); ++ sal_Int32(1387), xShape->getSize().Height); + } + + } +diff --git a/uitest/uitest/uihelper/guarded.py b/uitest/uitest/uihelper/guarded.py +new file mode 100644 +index 000000000000..b75aea332ff3 +--- /dev/null ++++ b/uitest/uitest/uihelper/guarded.py +@@ -0,0 +1,51 @@ ++# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- ++# ++# This file is part of the LibreOffice project. ++# ++# This Source Code Form is subject to the terms of the Mozilla Public ++# License, v. 2.0. If a copy of the MPL was not distributed with this ++# file, You can obtain one at http://mozilla.org/MPL/2.0/. ++# ++ ++from contextlib import contextmanager ++ ++# Calls UITest.close_doc at exit ++@contextmanager ++def load_file(testCase, url): ++ component = testCase.ui_test.load_file(url) ++ try: ++ yield component ++ finally: ++ testCase.ui_test.close_doc() ++ ++# Calls UITest.close_doc at exit ++@contextmanager ++def create_doc_in_start_center(testCase, app): ++ testCase.ui_test.create_doc_in_start_center(app) ++ component = testCase.ui_test.get_component() ++ try: ++ yield component ++ finally: ++ testCase.ui_test.close_doc() ++ ++# Calls UITest.close_dialog_through_button at exit ++@contextmanager ++def execute_dialog_through_action(testCase, ui_object, action, parameters = None, event_name = "DialogExecute", close_button = "ok"): ++ testCase.ui_test.execute_dialog_through_action(ui_object, action, parameters, event_name) ++ xDialog = testCase.xUITest.getTopFocusWindow() ++ try: ++ yield xDialog ++ finally: ++ testCase.ui_test.close_dialog_through_button(xDialog.getChild(close_button)) ++ ++# Calls UITest.close_dialog_through_button at exit ++@contextmanager ++def execute_dialog_through_command(testCase, command, printNames=False, close_button = "ok"): ++ testCase.ui_test.execute_dialog_through_command(command, printNames) ++ xDialog = testCase.xUITest.getTopFocusWindow() ++ try: ++ yield xDialog ++ finally: ++ testCase.ui_test.close_dialog_through_button(xDialog.getChild(close_button)) ++ ++# vim: set shiftwidth=4 softtabstop=4 expandtab: +diff --git a/xmloff/source/draw/XMLShapePropertySetContext.cxx b/xmloff/source/draw/XMLShapePropertySetContext.cxx +index b8bc00122044..355a67e4fa59 100644 +--- a/xmloff/source/draw/XMLShapePropertySetContext.cxx ++++ b/xmloff/source/draw/XMLShapePropertySetContext.cxx +@@ -18,6 +18,7 @@ + */ + + #include ++#include + #include + #include + #include +@@ -80,6 +81,8 @@ css::uno::Reference< css::xml::sax::XFastContextHandler > XMLShapePropertySetCon + rProp, + rProperties ); + break; ++ case CTF_TEXTCOLUMNS: ++ return new XMLTextColumnsContext(GetImport(), nElement, xAttrList, rProp, rProperties); + } + + return SvXMLPropertySetContext::createFastChildContext( nElement, +diff --git a/xmloff/source/draw/sdpropls.cxx b/xmloff/source/draw/sdpropls.cxx +index 36a24e2f0d84..d358f7ba09d9 100644 +--- a/xmloff/source/draw/sdpropls.cxx ++++ b/xmloff/source/draw/sdpropls.cxx +@@ -58,6 +58,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -145,6 +146,8 @@ const XMLPropertyMapEntry aXMLSDProperties[] = + GMAP( "TextWordWrap", XML_NAMESPACE_FO, XML_WRAP_OPTION, XML_TYPE_WRAP_OPTION, 0 ), + GMAP( "TextChainNextName", XML_NAMESPACE_DRAW, XML_CHAIN_NEXT_NAME, XML_TYPE_STRING, 0 ), + ++ GMAP( "TextColumns", XML_NAMESPACE_STYLE, XML_COLUMNS, XML_TYPE_TEXT_COLUMNS|MID_FLAG_ELEMENT_ITEM, CTF_TEXTCOLUMNS ), ++ + // shadow attributes + GMAP( "Shadow", XML_NAMESPACE_DRAW, XML_SHADOW, XML_SD_TYPE_VISIBLE_HIDDEN, 0 ), + GMAP( "ShadowXDistance", XML_NAMESPACE_DRAW, XML_SHADOW_OFFSET_X, XML_TYPE_MEASURE, 0 ), +@@ -1285,6 +1288,9 @@ const XMLPropertyHandler* XMLSdPropHdlFactory::GetPropertyHandler( sal_Int32 nTy + case XML_SD_TYPE_CELL_ROTATION_ANGLE: + pHdl = new XMLSdRotationAngleTypeHdl; + break; ++ case XML_TYPE_TEXT_COLUMNS: ++ pHdl = new XMLTextColumnsPropertyHandler; ++ break; + } + + if(pHdl) +diff --git a/xmloff/source/text/XMLTextColumnsExport.cxx b/xmloff/source/text/XMLTextColumnsExport.cxx +index 13f06fde36e6..017045d5f635 100644 +--- a/xmloff/source/text/XMLTextColumnsExport.cxx ++++ b/xmloff/source/text/XMLTextColumnsExport.cxx +@@ -61,6 +61,8 @@ void XMLTextColumnsExport::exportXML( const Any& rAny ) + { + Reference < XTextColumns > xColumns; + rAny >>= xColumns; ++ if (!xColumns) ++ return; + + const Sequence < TextColumn > aColumns = xColumns->getColumns(); + sal_Int32 nCount = aColumns.getLength(); +diff --git a/xmloff/source/text/txtprhdl.cxx b/xmloff/source/text/txtprhdl.cxx +index 77dbcb1bdd27..e3eb823349b8 100644 +--- a/xmloff/source/text/txtprhdl.cxx ++++ b/xmloff/source/text/txtprhdl.cxx +@@ -656,6 +656,9 @@ bool XMLTextColumnsPropertyHandler::equals( + Reference < XTextColumns > xColumns2; + r2 >>= xColumns2; + ++ if (!xColumns1 || !xColumns2) ++ return (!xColumns1 && !xColumns2); ++ + if( xColumns1->getColumnCount() != xColumns2->getColumnCount() || + xColumns1->getReferenceValue() != xColumns2->getReferenceValue() ) + return false; +-- +2.26.2 + diff --git a/libreoffice.changes b/libreoffice.changes index d57fe1f..3805af7 100644 --- a/libreoffice.changes +++ b/libreoffice.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Jun 14 14:46:36 UTC 2021 - Andras Timar + +- Fix bsc#1182969: LO-L3: PPTX: one column becomes two within one text frame (two occurrences) + * bsc1182969.patch + ------------------------------------------------------------------- Thu Jun 10 12:31:56 UTC 2021 - Markéta Machová diff --git a/libreoffice.spec b/libreoffice.spec index 73f21b9..408a1b2 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -107,6 +107,8 @@ Patch6: gcc11-fix-error.patch Patch7: bsc1185505.patch # bsc#1185797 Searching in PPTX document makes LibreOffice crash Patch8: bsc1185797.patch +# bsc#1182969 Multi column textbox in editengine +Patch9: bsc1182969.patch # Build with java 8 Patch101: 0001-Revert-java-9-changes.patch # try to save space by using hardlinks @@ -981,6 +983,7 @@ Provides %{langname} translations and additional resources (help files, etc.) fo %patch6 -p1 %patch7 -p1 %patch8 -p1 +%patch9 -p1 %if 0%{?suse_version} < 1500 %patch101 -p1 %endif