xmloff/qa/unit/style.cxx | 19 +++++++++++++-- xmloff/source/draw/shapeexport.cxx | 14 ++++++++++- xmloff/source/draw/ximpshap.cxx | 46 +++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-)
New commits: commit ae6ce55091b063eb8bd695ab25ea28fc053d9ece Author: Caolán McNamara <[email protected]> AuthorDate: Sat Feb 28 20:37:52 2026 +0000 Commit: Caolán McNamara <[email protected]> CommitDate: Sun Mar 1 20:15:09 2026 +0100 tdf#156707 always write fo:border for form control shapes on export On export, always write ControlBorder and ControlBorderColor to the auto style for control shapes, even when the property state is DEFAULT_VALUE. On import, when fo:border is absent, apply the control model's default border to the shape. a) New document, user sets flat (2) border: The constructor default is 2 (flat) but getPropertyDefaultByHandle returns 1 (3D). Property state is DIRECT_VALUE and the filter exports fo:border as before. On reimport fo:border is present and applied. No change in behavior. b) New document, user sets no (0) border: Same as flat, DIRECT_VALUE, exported by the filter. No change. c) New document, user sets 3D (1) border: 1 matches getPropertyDefaultByHandle so property state is DEFAULT_VALUE and the export filter would previously skip it. Now export always adds adds ControlBorder explicitly, so fo:border is written. On reimport fo:border is present so applied. d) Old document (pre tdf#152974) with no fo:border: These documents never wrote fo:border for 3D because that was the real default. Now on import the importer detects the missing fo:border and queries getPropertyDefault("Border") for the control which returns 1 (3D) for text controls, restoring the original border. On reexport the exporter writes an explicit fo:border. e) Label controls (tdf#167358): Labels default to no (0) border. Their getPropertyDefault returns 0, so the importer applies no border when fo:border is absent, so this fix effort shouldn't cause that problem again. Change-Id: I365adf84e9d26ae3231331230178381d31bfa829 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/200685 Tested-by: Jenkins Reviewed-by: Caolán McNamara <[email protected]> diff --git a/xmloff/qa/unit/style.cxx b/xmloff/qa/unit/style.cxx index 677022579a3c..b3fc52123e27 100644 --- a/xmloff/qa/unit/style.cxx +++ b/xmloff/qa/unit/style.cxx @@ -304,13 +304,28 @@ CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testTdf156707) xShape = getShape(1); xShapeProperties.set(xShape, uno::UNO_QUERY_THROW); xShapeProperties->getPropertyValue(u"ControlBorder"_ustr) >>= nBorderStyle; - // since tdf#152974, this shape SHOULD ACTUALLY have a 3d border (1), NOT a flat one(2). - CPPUNIT_ASSERT_EQUAL_MESSAGE("DID YOU FIX ME?", sal_uInt16(2), nBorderStyle); + // since tdf#152974, this shape should have a 3d border (1), not a flat one(2). + CPPUNIT_ASSERT_EQUAL(sal_uInt16(1), nBorderStyle); xShape = getShape(2); xShapeProperties.set(xShape, uno::UNO_QUERY_THROW); xShapeProperties->getPropertyValue(u"ControlBorder"_ustr) >>= nBorderStyle; CPPUNIT_ASSERT_EQUAL(sal_uInt16(0), nBorderStyle); + + // Verify export: fo:border must be written for all three control shapes + xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr); + static constexpr OString sStylePath = "/office:document-content/office:automatic-styles/" + "style:style[@style:name='"_ostr; + static constexpr OString sGfxProps = "']/style:graphic-properties[@fo:border]"_ostr; + + // Get the auto style name from each draw:control element + OUString sStyle0 = getXPath(pXmlDoc, "(//draw:control)[1]", "style-name"); + OUString sStyle1 = getXPath(pXmlDoc, "(//draw:control)[2]", "style-name"); + OUString sStyle2 = getXPath(pXmlDoc, "(//draw:control)[3]", "style-name"); + + assertXPath(pXmlDoc, sStylePath + sStyle0.toUtf8() + sGfxProps, 1); + assertXPath(pXmlDoc, sStylePath + sStyle1.toUtf8() + sGfxProps, 1); + assertXPath(pXmlDoc, sStylePath + sStyle2.toUtf8() + sGfxProps, 1); } CPPUNIT_TEST_FIXTURE(XmloffStyleTest, testTdf167358) diff --git a/xmloff/source/draw/shapeexport.cxx b/xmloff/source/draw/shapeexport.cxx index 5a17e37b0d75..7ffd051464fc 100644 --- a/xmloff/source/draw/shapeexport.cxx +++ b/xmloff/source/draw/shapeexport.cxx @@ -445,11 +445,12 @@ void XMLShapeExport::collectShapeAutoStyles(const uno::Reference< drawing::XShap { uno::Reference< beans::XPropertySet > xControlModel(xControl->getControl(), uno::UNO_QUERY); DBG_ASSERT(xControlModel.is(), "XMLShapeExport::collectShapeAutoStyles: no control model on the control shape!"); + const rtl::Reference<XMLPropertySetMapper>& rMapper = GetPropertySetMapper()->getPropertySetMapper(); OUString sNumberStyle = mrExport.GetFormExport()->getControlNumberStyle(xControlModel); if (!sNumberStyle.isEmpty()) { - sal_Int32 nIndex = GetPropertySetMapper()->getPropertySetMapper()->FindEntryIndex(CTF_SD_CONTROL_SHAPE_DATA_STYLE); + sal_Int32 nIndex = rMapper->FindEntryIndex(CTF_SD_CONTROL_SHAPE_DATA_STYLE); // TODO : this retrieval of the index could be moved into the ctor, holding the index // as member, thus saving time. DBG_ASSERT(-1 != nIndex, "XMLShapeExport::collectShapeAutoStyles: could not obtain the index for our context id!"); @@ -457,6 +458,17 @@ void XMLShapeExport::collectShapeAutoStyles(const uno::Reference< drawing::XShap XMLPropertyState aNewState(nIndex, uno::Any(sNumberStyle)); aPropStates.push_back(aNewState); } + + // tdf#156707 always export ControlBorder for control shapes, even when property state is DEFAULT_VALUE + sal_Int32 nBorderIndex = rMapper->FindEntryIndex("ControlBorder", XML_NAMESPACE_FO, GetXMLToken(XML_BORDER)); + if (nBorderIndex != -1) + { + if (!std::any_of(aPropStates.cbegin(), aPropStates.cend(), + [nBorderIndex](const XMLPropertyState& rProp) { return rProp.mnIndex == nBorderIndex; })) + { + aPropStates.emplace_back(nBorderIndex, xPropSet->getPropertyValue(u"ControlBorder"_ustr)); + } + } } } diff --git a/xmloff/source/draw/ximpshap.cxx b/xmloff/source/draw/ximpshap.cxx index 40912115fbe2..a39c1d50aa76 100644 --- a/xmloff/source/draw/ximpshap.cxx +++ b/xmloff/source/draw/ximpshap.cxx @@ -49,6 +49,7 @@ #include <com/sun/star/lang/XMultiServiceFactory.hpp> #include <com/sun/star/util/XCloneable.hpp> #include <com/sun/star/beans/XMultiPropertyStates.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> #include <xexptran.hxx> #include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp> #include <com/sun/star/beans/XPropertySetInfo.hpp> @@ -1725,6 +1726,51 @@ void SdXMLControlShapeContext::startFastElement (sal_Int32 nElement, } SetStyle(); + + // tdf#156707 if fo:border was not in the auto style, apply the control + // model's own default (which varies per control type) to the shape + if (const XMLPropStyleContext* pDocStyle = dynamic_cast<const XMLPropStyleContext*>(FindAutoStyle())) + { + SvXMLImportPropertyMapper* pImpMapper = GetImport().GetShapeImport()->GetPropertySetMapper(); + const rtl::Reference<XMLPropertySetMapper>& rMapper = pImpMapper->getPropertySetMapper(); + + bool bHasBorder = false; + for (const auto& rProp : pDocStyle->GetProperties()) + { + if (rProp.mnIndex < 0) + continue; + if (rMapper->GetEntryAPIName(rProp.mnIndex) == "ControlBorder") + { + bHasBorder = true; + break; + } + } + + if (!bHasBorder) + { + uno::Reference<drawing::XControlShape> xControl(mxShape, uno::UNO_QUERY); + if (xControl.is()) + { + uno::Reference<beans::XPropertySet> xControlModel(xControl->getControl(), uno::UNO_QUERY); + if (xControlModel.is()) + { + uno::Reference<beans::XPropertySetInfo> xInfo(xControlModel->getPropertySetInfo()); + if (xInfo.is() && xInfo->hasPropertyByName(u"Border"_ustr)) + { + uno::Reference<beans::XPropertyState> xPropState(xControlModel, uno::UNO_QUERY); + if (xPropState.is()) + { + uno::Any aDefault = xPropState->getPropertyDefault(u"Border"_ustr); + uno::Reference<beans::XPropertySet> xShapePropSet(mxShape, uno::UNO_QUERY); + if (xShapePropSet.is()) + xShapePropSet->setPropertyValue(u"ControlBorder"_ustr, aDefault); + } + } + } + } + } + } + SetLayer(); // set pos, size, shear and rotate
