summaryrefslogtreecommitdiffstats
path: root/oox/source/vml
diff options
context:
space:
mode:
Diffstat (limited to 'oox/source/vml')
-rw-r--r--oox/source/vml/vmldrawing.cxx346
-rw-r--r--oox/source/vml/vmldrawingfragment.cxx82
-rw-r--r--oox/source/vml/vmlformatting.cxx1002
-rw-r--r--oox/source/vml/vmlinputstream.cxx393
-rw-r--r--oox/source/vml/vmlshape.cxx1577
-rw-r--r--oox/source/vml/vmlshapecontainer.cxx138
-rw-r--r--oox/source/vml/vmlshapecontext.cxx731
-rw-r--r--oox/source/vml/vmltextbox.cxx205
-rw-r--r--oox/source/vml/vmltextboxcontext.cxx288
9 files changed, 4762 insertions, 0 deletions
diff --git a/oox/source/vml/vmldrawing.cxx b/oox/source/vml/vmldrawing.cxx
new file mode 100644
index 0000000000..eaa0ecf1c1
--- /dev/null
+++ b/oox/source/vml/vmldrawing.cxx
@@ -0,0 +1,346 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/vml/vmldrawing.hxx>
+
+#include <algorithm>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XControlShape.hpp>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/text/HoriOrientation.hpp>
+#include <com/sun/star/text/RelOrientation.hpp>
+#include <com/sun/star/text/VertOrientation.hpp>
+#include <osl/diagnose.h>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/helper/containerhelper.hxx>
+#include <oox/ole/axcontrol.hxx>
+#include <oox/vml/vmlshape.hxx>
+#include <oox/vml/vmlshapecontainer.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/gen.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::text;
+using namespace ::com::sun::star::uno;
+
+using ::oox::core::XmlFilterBase;
+
+namespace {
+
+/** Returns the textual representation of a numeric VML shape identifier. */
+OUString lclGetShapeId( sal_Int32 nShapeId )
+{
+ // identifier consists of a literal NUL character, a lowercase 's', and the id
+ static constexpr OUStringLiteral aStr = u"\0s";
+ return aStr + OUString::number( nShapeId );
+}
+
+/** Returns the numeric VML shape identifier from its textual representation. */
+sal_Int32 lclGetShapeId( std::u16string_view rShapeId )
+{
+ // identifier consists of a literal NUL character, a lowercase 's', and the id
+ return ((rShapeId.size() >= 3) && (rShapeId[ 0 ] == '\0') && (rShapeId[ 1 ] == 's')) ? o3tl::toInt32(rShapeId.substr( 2 )) : -1;
+}
+
+} // namespace
+
+OleObjectInfo::OleObjectInfo( bool bDmlShape ) :
+ mbAutoLoad( false ),
+ mbDmlShape( bDmlShape )
+{
+}
+
+void OleObjectInfo::setShapeId( sal_Int32 nShapeId )
+{
+ maShapeId = lclGetShapeId( nShapeId );
+}
+
+ControlInfo::ControlInfo()
+ : mbTextContentShape(false)
+{
+}
+
+void ControlInfo::setShapeId( sal_Int32 nShapeId )
+{
+ maShapeId = lclGetShapeId(nShapeId);
+}
+
+Drawing::Drawing( XmlFilterBase& rFilter, const Reference< XDrawPage >& rxDrawPage, DrawingType eType ) :
+ mrFilter( rFilter ),
+ mxDrawPage( rxDrawPage ),
+ mxShapes( new ShapeContainer( *this ) ),
+ meType( eType )
+{
+ OSL_ENSURE( mxDrawPage.is(), "Drawing::Drawing - missing UNO draw page" );
+}
+
+Drawing::~Drawing()
+{
+}
+
+::oox::ole::EmbeddedForm& Drawing::getControlForm() const
+{
+ if (!mxCtrlForm)
+ mxCtrlForm.reset( new ::oox::ole::EmbeddedForm(
+ mrFilter.getModel(), mxDrawPage, mrFilter.getGraphicHelper() ) );
+ return *mxCtrlForm;
+}
+
+void Drawing::registerBlockId( sal_Int32 nBlockId )
+{
+ OSL_ENSURE( nBlockId > 0, "Drawing::registerBlockId - invalid block index" );
+ if( nBlockId > 0 )
+ {
+ // lower_bound() returns iterator pointing to element equal to nBlockId, if existing
+ BlockIdVector::iterator aIt = ::std::lower_bound( maBlockIds.begin(), maBlockIds.end(), nBlockId );
+ if( (aIt == maBlockIds.end()) || (nBlockId != *aIt) )
+ maBlockIds.insert( aIt, nBlockId );
+ }
+}
+
+void Drawing::registerOleObject( const OleObjectInfo& rOleObject )
+{
+ OSL_ENSURE( !rOleObject.maShapeId.isEmpty(), "Drawing::registerOleObject - missing OLE object shape id" );
+ OSL_ENSURE( maOleObjects.count( rOleObject.maShapeId ) == 0, "Drawing::registerOleObject - OLE object already registered" );
+ maOleObjects.emplace( rOleObject.maShapeId, rOleObject );
+}
+
+void Drawing::registerControl( const ControlInfo& rControl )
+{
+ OSL_ENSURE( !rControl.maShapeId.isEmpty(), "Drawing::registerControl - missing form control shape id" );
+ OSL_ENSURE( !rControl.maName.isEmpty(), "Drawing::registerControl - missing form control name" );
+ OSL_ENSURE( maControls.count( rControl.maShapeId ) == 0, "Drawing::registerControl - form control already registered" );
+ maControls.emplace( rControl.maShapeId, rControl );
+}
+
+void Drawing::finalizeFragmentImport()
+{
+ mxShapes->finalizeFragmentImport();
+}
+
+void Drawing::convertAndInsert() const
+{
+ Reference< XShapes > xShapes( mxDrawPage );
+ mxShapes->convertAndInsert( xShapes );
+
+ // Group together form control radio buttons that are in the same groupBox
+ std::map<OUString, tools::Rectangle> GroupBoxMap;
+ std::map<Reference< XPropertySet >, tools::Rectangle> RadioButtonMap;
+ for ( sal_Int32 i = 0; i < xShapes->getCount(); ++i )
+ {
+ try
+ {
+ Reference< XControlShape > xCtrlShape( xShapes->getByIndex(i), UNO_QUERY );
+ if (!xCtrlShape.is())
+ continue;
+ Reference< XControlModel > xCtrlModel( xCtrlShape->getControl(), UNO_SET_THROW );
+ Reference< XServiceInfo > xModelSI (xCtrlModel, UNO_QUERY_THROW );
+ Reference< XPropertySet > aProps( xCtrlModel, UNO_QUERY_THROW );
+
+ OUString sName;
+ aProps->getPropertyValue("Name") >>= sName;
+ const ::Point aPoint( xCtrlShape->getPosition().X, xCtrlShape->getPosition().Y );
+ const ::Size aSize( xCtrlShape->getSize().Width, xCtrlShape->getSize().Height );
+ const tools::Rectangle aRect( aPoint, aSize );
+ if ( !sName.isEmpty()
+ && xModelSI->supportsService("com.sun.star.awt.UnoControlGroupBoxModel") )
+ {
+ GroupBoxMap[sName] = aRect;
+ }
+ else if ( xModelSI->supportsService("com.sun.star.awt.UnoControlRadioButtonModel") )
+ {
+ OUString sGroupName;
+ aProps->getPropertyValue("GroupName") >>= sGroupName;
+ // only Form Controls are affected by Group Boxes - see drawingfragment.cxx
+ if ( sGroupName == "autoGroup_formControl" )
+ RadioButtonMap[aProps] = aRect;
+ }
+ }
+ catch (uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox.vml");
+ }
+ }
+ for ( const auto& BoxItr : GroupBoxMap )
+ {
+ const uno::Any aGroup( "autoGroup_" + BoxItr.first );
+ for ( auto RadioItr = RadioButtonMap.begin(); RadioItr != RadioButtonMap.end(); )
+ {
+ if ( BoxItr.second.Contains(RadioItr->second) )
+ {
+ RadioItr->first->setPropertyValue("GroupName", aGroup );
+ // If conflict, first created GroupBox wins
+ RadioItr = RadioButtonMap.erase(RadioItr);
+ }
+ else
+ ++RadioItr;
+ }
+ }
+
+}
+
+sal_Int32 Drawing::getLocalShapeIndex( std::u16string_view rShapeId ) const
+{
+ sal_Int32 nShapeId = lclGetShapeId( rShapeId );
+ if( nShapeId <= 0 ) return -1;
+
+ /* Shapes in a drawing are counted per registered shape identifier blocks
+ as stored in the o:idmap element. The contents of this element have
+ been stored in our member maBlockIds. Each block represents 1024 shape
+ identifiers, starting with identifier 1 for the block #0. This means,
+ block #0 represents the identifiers 1-1024, block #1 represents the
+ identifiers 1025-2048, and so on. The local shape index has to be
+ calculated according to all blocks registered for this drawing.
+
+ Example:
+ Registered for this drawing are blocks #1 and #3 (shape identifiers
+ 1025-2048 and 3073-4096).
+ Shape identifier 1025 -> local shape index 1.
+ Shape identifier 1026 -> local shape index 2.
+ ...
+ Shape identifier 2048 -> local shape index 1024.
+ Shape identifier 3073 -> local shape index 1025.
+ ...
+ Shape identifier 4096 -> local shape index 2048.
+ */
+
+ // get block id from shape id and find its index in the list of used blocks
+ sal_Int32 nBlockId = (nShapeId - 1) / 1024;
+ BlockIdVector::iterator aIt = ::std::lower_bound( maBlockIds.begin(), maBlockIds.end(), nBlockId );
+ sal_Int32 nIndex = static_cast< sal_Int32 >( aIt - maBlockIds.begin() );
+
+ // block id not found in set -> register it now (value of nIndex remains valid)
+ if( (aIt == maBlockIds.end()) || (*aIt != nBlockId) )
+ maBlockIds.insert( aIt, nBlockId );
+
+ // get one-based offset of shape id in its block
+ sal_Int32 nBlockOffset = (nShapeId - 1) % 1024 + 1;
+
+ // calculate the local shape index
+ return 1024 * nIndex + nBlockOffset;
+}
+
+const OleObjectInfo* Drawing::getOleObjectInfo( const OUString& rShapeId ) const
+{
+ return ContainerHelper::getMapElement( maOleObjects, rShapeId );
+}
+
+const ControlInfo* Drawing::getControlInfo( const OUString& rShapeId ) const
+{
+ return ContainerHelper::getMapElement( maControls, rShapeId );
+}
+
+Reference< XShape > Drawing::createAndInsertXShape( const OUString& rService,
+ const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ OSL_ENSURE( !rService.isEmpty(), "Drawing::createAndInsertXShape - missing UNO shape service name" );
+ OSL_ENSURE( rxShapes.is(), "Drawing::createAndInsertXShape - missing XShapes container" );
+ Reference< XShape > xShape;
+ if( !rService.isEmpty() && rxShapes.is() ) try
+ {
+ Reference< XMultiServiceFactory > xModelFactory( mrFilter.getModelFactory(), UNO_SET_THROW );
+ xShape.set( xModelFactory->createInstance( rService ), UNO_QUERY_THROW );
+ if ( rService != "com.sun.star.text.TextFrame" )
+ {
+ // insert shape into passed shape collection (maybe drawpage or group shape)
+ rxShapes->add( xShape );
+ xShape->setPosition( awt::Point( rShapeRect.X, rShapeRect.Y ) );
+ }
+ else
+ {
+ Reference< XPropertySet > xPropSet( xShape, UNO_QUERY_THROW );
+ xPropSet->setPropertyValue( "HoriOrient", Any( HoriOrientation::NONE ) );
+ xPropSet->setPropertyValue( "VertOrient", Any( VertOrientation::NONE ) );
+ xPropSet->setPropertyValue( "HoriOrientPosition", Any( rShapeRect.X ) );
+ xPropSet->setPropertyValue( "VertOrientPosition", Any( rShapeRect.Y ) );
+ xPropSet->setPropertyValue( "HoriOrientRelation", Any( RelOrientation::FRAME ) );
+ xPropSet->setPropertyValue( "VertOrientRelation", Any( RelOrientation::FRAME ) );
+ }
+ xShape->setSize( awt::Size( rShapeRect.Width, rShapeRect.Height ) );
+ }
+ catch( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "oox", "Drawing::createAndInsertXShape - error during shape object creation" );
+ }
+ OSL_ENSURE( xShape.is(), "Drawing::createAndInsertXShape - cannot instantiate shape object" );
+ return xShape;
+}
+
+Reference< XShape > Drawing::createAndInsertXControlShape( const ::oox::ole::EmbeddedControl& rControl,
+ const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect, sal_Int32& rnCtrlIndex ) const
+{
+ Reference< XShape > xShape;
+ try
+ {
+ // create control model and insert it into the form of the draw page
+ Reference< XControlModel > xCtrlModel( getControlForm().convertAndInsert( rControl, rnCtrlIndex ), UNO_SET_THROW );
+
+ // create the control shape
+ xShape = createAndInsertXShape( "com.sun.star.drawing.ControlShape", rxShapes, rShapeRect );
+
+ // set the control model at the shape
+ Reference< XControlShape >( xShape, UNO_QUERY_THROW )->setControl( xCtrlModel );
+ }
+ catch (Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION("oox", "exception inserting Shape");
+ }
+ return xShape;
+}
+
+bool Drawing::isShapeSupported( const ShapeBase& /*rShape*/ ) const
+{
+ return true;
+}
+
+OUString Drawing::getShapeBaseName( const ShapeBase& /*rShape*/ ) const
+{
+ return OUString();
+}
+
+bool Drawing::convertClientAnchor( awt::Rectangle& /*orShapeRect*/, const OUString& /*rShapeAnchor*/ ) const
+{
+ return false;
+}
+
+Reference< XShape > Drawing::createAndInsertClientXShape( const ShapeBase& /*rShape*/,
+ const Reference< XShapes >& /*rxShapes*/, const awt::Rectangle& /*rShapeRect*/ ) const
+{
+ return Reference< XShape >();
+}
+
+void Drawing::notifyXShapeInserted( const Reference< XShape >& /*rxShape*/,
+ const awt::Rectangle& /*rShapeRect*/, const ShapeBase& /*rShape*/, bool /*bGroupChild*/ )
+{
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmldrawingfragment.cxx b/oox/source/vml/vmldrawingfragment.cxx
new file mode 100644
index 0000000000..cb7936bafb
--- /dev/null
+++ b/oox/source/vml/vmldrawingfragment.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/vml/vmldrawingfragment.hxx>
+
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/vml/vmldrawing.hxx>
+#include <oox/vml/vmlinputstream.hxx>
+#include <oox/vml/vmlshapecontext.hxx>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::uno;
+using namespace ::oox::core;
+
+DrawingFragment::DrawingFragment( XmlFilterBase& rFilter, const OUString& rFragmentPath, Drawing& rDrawing ) :
+ FragmentHandler2( rFilter, rFragmentPath, false ), // do not trim whitespace, has been preprocessed by the input stream
+ mrDrawing( rDrawing )
+{
+}
+
+Reference< XInputStream > DrawingFragment::openFragmentStream() const
+{
+ // #i104719# create an input stream that preprocesses the VML data
+ return new InputStream( getFilter().getComponentContext(), FragmentHandler2::openFragmentStream() );
+}
+
+ContextHandlerRef DrawingFragment::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( mrDrawing.getType() )
+ {
+ // DOCX filter handles plain shape elements with this fragment handler
+ case VMLDRAWING_WORD:
+ if ( getNamespace( nElement ) == NMSP_vml
+ || nElement == W_TOKEN(control) ) // Control shape also defined as a vml shape
+ return ShapeContextBase::createShapeContext( *this, mrDrawing.getShapes(), nElement, rAttribs );
+ break;
+
+ // XLSX and PPTX filters load the entire VML fragment
+ case VMLDRAWING_EXCEL:
+ case VMLDRAWING_POWERPOINT:
+ switch( getCurrentElement() )
+ {
+ case XML_ROOT_CONTEXT:
+ if( nElement == XML_xml ) return this;
+ break;
+ case XML_xml:
+ return ShapeContextBase::createShapeContext( *this, mrDrawing.getShapes(), nElement, rAttribs );
+ }
+ break;
+ }
+ return nullptr;
+}
+
+void DrawingFragment::finalizeImport()
+{
+ // resolve shape template references for all shapes
+ mrDrawing.finalizeFragmentImport();
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmlformatting.cxx b/oox/source/vml/vmlformatting.cxx
new file mode 100644
index 0000000000..029d5429d9
--- /dev/null
+++ b/oox/source/vml/vmlformatting.cxx
@@ -0,0 +1,1002 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <sal/config.h>
+
+#include <cstdlib>
+
+#include <oox/vml/vmlformatting.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp>
+#include <com/sun/star/table/ShadowFormat.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <o3tl/float_int_conversion.hxx>
+#include <o3tl/unit_conversion.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <oox/drawingml/color.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <drawingml/fillproperties.hxx>
+#include <drawingml/lineproperties.hxx>
+#include <oox/drawingml/shapepropertymap.hxx>
+#include <oox/helper/attributelist.hxx>
+#include <oox/helper/graphichelper.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+#include <svx/svdtrans.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <o3tl/string_view.hxx>
+#include <vcl/virdev.hxx>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::geometry;
+
+using ::oox::drawingml::Color;
+using ::oox::drawingml::FillProperties;
+using ::oox::drawingml::LineArrowProperties;
+using ::oox::drawingml::LineProperties;
+using ::oox::drawingml::ShapePropertyMap;
+using ::com::sun::star::awt::Point;
+using ::com::sun::star::drawing::PolygonFlags;
+using ::com::sun::star::drawing::PolygonFlags_NORMAL;
+using ::com::sun::star::drawing::PolygonFlags_CONTROL;
+
+namespace {
+
+bool lclExtractDouble( double& orfValue, size_t& ornEndPos, std::u16string_view aValue )
+{
+ // extract the double value and find start position of unit characters
+ rtl_math_ConversionStatus eConvStatus = rtl_math_ConversionStatus_Ok;
+ sal_Int32 nEndPos;
+ orfValue = ::rtl::math::stringToDouble( aValue, '.', '\0', &eConvStatus, &nEndPos );
+ ornEndPos = nEndPos;
+ return eConvStatus == rtl_math_ConversionStatus_Ok;
+}
+
+} // namespace
+
+bool ConversionHelper::separatePair( std::u16string_view& orValue1, std::u16string_view& orValue2,
+ std::u16string_view rValue, sal_Unicode cSep )
+{
+ size_t nSepPos = rValue.find( cSep );
+ if( nSepPos != std::u16string_view::npos )
+ {
+ orValue1 = o3tl::trim(rValue.substr( 0, nSepPos ));
+ orValue2 = o3tl::trim(rValue.substr( nSepPos + 1 ));
+ }
+ else
+ {
+ orValue1 = o3tl::trim(rValue);
+ orValue2 = std::u16string_view();
+ }
+ return !orValue1.empty() && !orValue2.empty();
+}
+
+bool ConversionHelper::decodeBool( std::u16string_view rValue )
+{
+ sal_Int32 nToken = AttributeConversion::decodeToken( rValue );
+ // anything else than 't' or 'true' is considered to be false, as specified
+ return (nToken == XML_t) || (nToken == XML_true);
+}
+
+double ConversionHelper::decodePercent( std::u16string_view rValue, double fDefValue )
+{
+ if( rValue.empty() )
+ return fDefValue;
+
+ double fValue = 0.0;
+ size_t nEndPos = 0;
+ if( !lclExtractDouble( fValue, nEndPos, rValue ) )
+ return fDefValue;
+
+ if( nEndPos == rValue.size() )
+ return fValue;
+
+ if( (nEndPos + 1 == rValue.size()) && (rValue[ nEndPos ] == '%') )
+ return fValue / 100.0;
+
+ if( (nEndPos + 1 == rValue.size()) && (rValue[ nEndPos ] == 'f') )
+ return fValue / 65536.0;
+
+ OSL_FAIL( "ConversionHelper::decodePercent - unknown measure unit" );
+ return fDefValue;
+}
+
+Degree100 ConversionHelper::decodeRotation( std::u16string_view rValue )
+{
+ if( rValue.empty() )
+ return 0_deg100;
+
+ double fValue = 0.0;
+ double fRotation = 0.0;
+ size_t nEndPos = 0;
+ if( !lclExtractDouble(fValue, nEndPos, rValue) )
+ return 0_deg100;
+
+ if( nEndPos == rValue.size() )
+ fRotation = fValue;
+ else if( (nEndPos + 2 == rValue.size()) && (rValue[nEndPos] == 'f') && (rValue[nEndPos+1] == 'd') )
+ fRotation = fValue / 65536.0;
+ else
+ {
+ OSL_FAIL("ConversionHelper::decodeRotation - unknown measure unit");
+ return 0_deg100;
+ }
+
+ return NormAngle36000(Degree100(static_cast<sal_Int32>(fRotation * -100)));
+}
+
+sal_Int64 ConversionHelper::decodeMeasureToEmu( const GraphicHelper& rGraphicHelper,
+ std::u16string_view rValue, sal_Int32 nRefValue, bool bPixelX, bool bDefaultAsPixel )
+{
+ // default for missing values is 0
+ if( rValue.empty() )
+ return 0;
+
+ // TODO: according to spec, value may contain "auto"
+ if ( rValue == u"auto" )
+ {
+ OSL_FAIL( "ConversionHelper::decodeMeasureToEmu - special value 'auto' must be handled by caller" );
+ return nRefValue;
+ }
+
+ // extract the double value and find start position of unit characters
+ double fValue = 0.0;
+ size_t nEndPos = 0;
+ if( !lclExtractDouble( fValue, nEndPos, rValue ) || (fValue == 0.0) )
+ return 0;
+
+ // process trailing unit, convert to EMU
+ std::u16string_view aUnit;
+ if( (0 < nEndPos) && (nEndPos < rValue.size()) )
+ aUnit = rValue.substr( nEndPos );
+ else if( bDefaultAsPixel )
+ aUnit = u"px";
+ // else default is EMU
+
+ if( aUnit.size() == 2 )
+ {
+ sal_Unicode cChar1 = aUnit[ 0 ];
+ sal_Unicode cChar2 = aUnit[ 1 ];
+ if ((cChar1 == 'i') && (cChar2 == 'n'))
+ fValue = o3tl::convert(fValue, o3tl::Length::in, o3tl::Length::emu);
+ else if ((cChar1 == 'c') && (cChar2 == 'm'))
+ fValue = o3tl::convert(fValue, o3tl::Length::cm, o3tl::Length::emu);
+ else if ((cChar1 == 'm') && (cChar2 == 'm'))
+ fValue = o3tl::convert(fValue, o3tl::Length::mm, o3tl::Length::emu);
+ else if ((cChar1 == 'p') && (cChar2 == 't'))
+ fValue = o3tl::convert(fValue, o3tl::Length::pt, o3tl::Length::emu);
+ else if ((cChar1 == 'p') && (cChar2 == 'c'))
+ fValue = o3tl::convert(fValue, o3tl::Length::pc, o3tl::Length::emu);
+ else if( (cChar1 == 'p') && (cChar2 == 'x') ) // 1 pixel, dependent on output device
+ fValue = o3tl::convert(bPixelX ? rGraphicHelper.convertScreenPixelXToHmm(fValue)
+ : rGraphicHelper.convertScreenPixelYToHmm(fValue),
+ o3tl::Length::mm100, o3tl::Length::emu);
+ }
+ else if( (aUnit.size() == 1) && (aUnit[ 0 ] == '%') )
+ {
+ fValue *= nRefValue / 100.0;
+ }
+ else if( bDefaultAsPixel || !aUnit.empty() ) // default as EMU and no unit -> do nothing
+ {
+ OSL_FAIL( "ConversionHelper::decodeMeasureToEmu - unknown measure unit" );
+ fValue = nRefValue;
+ }
+ return o3tl::saturating_cast< sal_Int64 >( fValue + 0.5 );
+}
+
+sal_Int32 ConversionHelper::decodeMeasureToHmm( const GraphicHelper& rGraphicHelper,
+ std::u16string_view rValue, sal_Int32 nRefValue, bool bPixelX, bool bDefaultAsPixel )
+{
+ return ::oox::drawingml::convertEmuToHmm( decodeMeasureToEmu( rGraphicHelper, rValue, nRefValue, bPixelX, bDefaultAsPixel ) );
+}
+
+sal_Int32 ConversionHelper::decodeMeasureToTwip(const GraphicHelper& rGraphicHelper,
+ std::u16string_view rValue, sal_Int32 nRefValue,
+ bool bPixelX, bool bDefaultAsPixel)
+{
+ return ::o3tl::convert(
+ decodeMeasureToEmu(rGraphicHelper, rValue, nRefValue, bPixelX, bDefaultAsPixel),
+ o3tl::Length::emu, o3tl::Length::twip);
+}
+
+Color ConversionHelper::decodeColor( const GraphicHelper& rGraphicHelper,
+ const std::optional< OUString >& roVmlColor, const std::optional< double >& roVmlOpacity,
+ ::Color nDefaultRgb, ::Color nPrimaryRgb )
+{
+ Color aDmlColor;
+
+ // convert opacity
+ const sal_Int32 DML_FULL_OPAQUE = ::oox::drawingml::MAX_PERCENT;
+ double fOpacity = roVmlOpacity.value_or( 1.0 );
+ sal_Int32 nOpacity = getLimitedValue< sal_Int32, double >( fOpacity * DML_FULL_OPAQUE, 0, DML_FULL_OPAQUE );
+ if( nOpacity < DML_FULL_OPAQUE )
+ aDmlColor.addTransformation( XML_alpha, nOpacity );
+
+ // color attribute not present - set passed default color
+ if( !roVmlColor.has_value() )
+ {
+ aDmlColor.setSrgbClr( nDefaultRgb );
+ return aDmlColor;
+ }
+
+ // separate leading color name or RGB value from following palette index
+ std::u16string_view aColorName, aColorIndex;
+ separatePair( aColorName, aColorIndex, roVmlColor.value(), ' ' );
+
+ // RGB colors in the format '#RRGGBB'
+ if( (aColorName.size() == 7) && (aColorName[ 0 ] == '#') )
+ {
+ aDmlColor.setSrgbClr( o3tl::toUInt32(aColorName.substr( 1 ), 16) );
+ return aDmlColor;
+ }
+
+ // RGB colors in the format '#RGB'
+ if( (aColorName.size() == 4) && (aColorName[ 0 ] == '#') )
+ {
+ sal_Int32 nR = o3tl::toUInt32(aColorName.substr( 1, 1 ), 16 ) * 0x11;
+ sal_Int32 nG = o3tl::toUInt32(aColorName.substr( 2, 1 ), 16 ) * 0x11;
+ sal_Int32 nB = o3tl::toUInt32(aColorName.substr( 3, 1 ), 16 ) * 0x11;
+ aDmlColor.setSrgbClr( (nR << 16) | (nG << 8) | nB );
+ return aDmlColor;
+ }
+
+ /* Predefined color names or system color names (resolve to RGB to detect
+ valid color name). */
+ sal_Int32 nColorToken = AttributeConversion::decodeToken( aColorName );
+ ::Color nRgbValue = Color::getVmlPresetColor( nColorToken, API_RGB_TRANSPARENT );
+ if( nRgbValue == API_RGB_TRANSPARENT )
+ nRgbValue = rGraphicHelper.getSystemColor( nColorToken );
+ if( nRgbValue != API_RGB_TRANSPARENT )
+ {
+ aDmlColor.setSrgbClr( nRgbValue );
+ return aDmlColor;
+ }
+
+ // try palette colors enclosed in brackets
+ if( (aColorIndex.size() >= 3) && (aColorIndex[ 0 ] == '[') && (aColorIndex[ aColorIndex.size() - 1 ] == ']') )
+ {
+ aDmlColor.setPaletteClr( o3tl::toInt32(aColorIndex.substr( 1, aColorIndex.size() - 2 )) );
+ return aDmlColor;
+ }
+
+ // try fill gradient modificator 'fill <modifier>(<amount>)'
+ if( (nPrimaryRgb != API_RGB_TRANSPARENT) && (nColorToken == XML_fill) )
+ {
+ size_t nOpenParen = aColorIndex.find( '(' );
+ size_t nCloseParen = aColorIndex.find( ')' );
+ if( nOpenParen != std::u16string_view::npos && nCloseParen != std::u16string_view::npos &&
+ (2 <= nOpenParen) && (nOpenParen + 1 < nCloseParen) && (nCloseParen + 1 == aColorIndex.size()) )
+ {
+ sal_Int32 nModToken = XML_TOKEN_INVALID;
+ switch( AttributeConversion::decodeToken( aColorIndex.substr( 0, nOpenParen ) ) )
+ {
+ case XML_darken: nModToken = XML_shade;break;
+ case XML_lighten: nModToken = XML_tint;
+ }
+ sal_Int32 nValue = o3tl::toInt32(aColorIndex.substr( nOpenParen + 1, nCloseParen - nOpenParen - 1 ));
+ if( (nModToken != XML_TOKEN_INVALID) && (0 <= nValue) && (nValue < 255) )
+ {
+ /* Simulate this modifier color by a color with related transformation.
+ The modifier amount has to be converted from the range [0;255] to
+ percentage [0;100000] used by DrawingML. */
+ aDmlColor.setSrgbClr( nPrimaryRgb );
+ aDmlColor.addTransformation( nModToken, static_cast< sal_Int32 >( nValue * ::oox::drawingml::MAX_PERCENT / 255 ) );
+ return aDmlColor;
+ }
+ }
+ }
+
+ OSL_FAIL( OStringBuffer( "lclGetColor - invalid VML color name '" +
+ OUStringToOString( roVmlColor.value(), RTL_TEXTENCODING_ASCII_US ) + "'" ).getStr() );
+ aDmlColor.setSrgbClr( nDefaultRgb );
+ return aDmlColor;
+}
+
+void ConversionHelper::decodeVmlPath( ::std::vector< ::std::vector< Point > >& rPointLists, ::std::vector< ::std::vector< PolygonFlags > >& rFlagLists, std::u16string_view rPath )
+{
+ ::std::vector< sal_Int32 > aCoordList;
+ Point aCurrentPoint;
+ sal_Int32 nTokenStart = 0;
+ sal_Int32 nTokenLen = 0;
+ sal_Int32 nParamCount = 0;
+ bool bCommand = false;
+ enum VML_State { START, MOVE_REL, MOVE_ABS, BEZIER_REL, BEZIER_ABS,
+ LINE_REL, LINE_ABS, CLOSE, END, UNSUPPORTED };
+ VML_State state = START;
+
+ rPointLists.emplace_back( );
+ rFlagLists.emplace_back( );
+
+ for ( size_t i = 0; i < rPath.size(); i++ )
+ {
+ // Keep track of current integer token
+ if ( ( rPath[ i ] >= '0' && rPath[ i ] <= '9' ) || rPath[ i ] == '-' )
+ nTokenLen++;
+ else if ( rPath[ i ] != ' ' )
+ {
+ // Store coordinate from current token
+ if ( state != START && state != UNSUPPORTED )
+ {
+ if ( nTokenLen > 0 )
+ aCoordList.push_back( o3tl::toInt32(rPath.substr( nTokenStart, nTokenLen )) );
+ else
+ aCoordList.push_back( 0 );
+ nTokenLen = 0;
+ }
+
+ if (rPath[ i ] == ',' )
+ {
+ nParamCount--;
+ }
+
+ // Upon finding the next command code, deal with stored
+ // coordinates for previous command and reset parameters counter if needed.
+ // See http://www.w3.org/TR/NOTE-VML#_Toc416858382 for params count reference
+ if ( rPath[ i ] != ',' || nParamCount == 0 )
+ {
+ switch ( state )
+ {
+ case MOVE_REL:
+ aCoordList.resize(2, 0); // 2* params -> param count reset
+ if ( !rPointLists.empty() && !rPointLists.back().empty() )
+ {
+ rPointLists.emplace_back( );
+ rFlagLists.emplace_back( );
+ }
+ rPointLists.back().emplace_back( aCoordList[ 0 ], aCoordList[ 1 ] );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ nParamCount = 2;
+ break;
+
+ case MOVE_ABS:
+ aCoordList.resize(2, 0); // 2 params -> no param count reset
+ if ( !rPointLists.empty() && !rPointLists.back().empty() )
+ {
+ rPointLists.emplace_back( );
+ rFlagLists.emplace_back( );
+ }
+ rPointLists.back().emplace_back( (aCoordList[ 0 ]), aCoordList[ 1 ] );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ break;
+
+ case BEZIER_REL:
+ aCoordList.resize(6, 0); // 6* params -> param count reset
+ rPointLists.back().emplace_back( aCurrentPoint.X + aCoordList[ 0 ],
+ aCurrentPoint.Y + aCoordList[ 1 ] );
+ rPointLists.back().emplace_back( aCurrentPoint.X + aCoordList[ 2 ],
+ aCurrentPoint.Y + aCoordList[ 3 ] );
+ rPointLists.back().emplace_back( aCurrentPoint.X + aCoordList[ 4 ],
+ aCurrentPoint.Y + aCoordList[ 5 ] );
+ rFlagLists.back().push_back( PolygonFlags_CONTROL );
+ rFlagLists.back().push_back( PolygonFlags_CONTROL );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ nParamCount = 6;
+ break;
+
+ case BEZIER_ABS:
+ aCoordList.resize(6, 0); // 6* params -> param count reset
+ rPointLists.back().emplace_back( aCoordList[ 0 ], aCoordList[ 1 ] );
+ rPointLists.back().emplace_back( aCoordList[ 2 ], aCoordList[ 3 ] );
+ rPointLists.back().emplace_back( aCoordList[ 4 ], aCoordList[ 5 ] );
+ rFlagLists.back().push_back( PolygonFlags_CONTROL );
+ rFlagLists.back().push_back( PolygonFlags_CONTROL );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ nParamCount = 6;
+ break;
+
+ case LINE_REL:
+ aCoordList.resize(2, 0); // 2* params -> param count reset
+ rPointLists.back().emplace_back( aCurrentPoint.X + aCoordList[ 0 ],
+ aCurrentPoint.Y + aCoordList[ 1 ] );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ nParamCount = 2;
+ break;
+
+ case LINE_ABS:
+ aCoordList.resize(2, 0); // 2* params -> param count reset
+ rPointLists.back().emplace_back( aCoordList[ 0 ], (aCoordList.size() > 1 ? aCoordList[ 1 ] : 0) );
+ rFlagLists.back().push_back( PolygonFlags_NORMAL );
+ aCurrentPoint = rPointLists.back().back();
+ nParamCount = 2;
+ break;
+
+ case CLOSE: // 0 param
+ SAL_WARN_IF(rPointLists.back().empty() || rFlagLists.back().empty(), "oox", "empty pointlists at close");
+ if (!rPointLists.back().empty() && !rFlagLists.back().empty())
+ {
+ rPointLists.back().push_back( rPointLists.back()[ 0 ] );
+ rFlagLists.back().push_back( rFlagLists.back()[ 0 ] );
+ aCurrentPoint = rPointLists.back().back();
+ }
+ break;
+
+ case END: // 0 param
+ rPointLists.emplace_back( );
+ rFlagLists.emplace_back( );
+ break;
+
+ case START:
+ case UNSUPPORTED:
+ break;
+ }
+
+ aCoordList.clear();
+ }
+
+ // Allow two-char commands to peek ahead to the next character
+ sal_Unicode nextChar = '\0';
+ if (i+1 < rPath.size())
+ nextChar = rPath[i+1];
+
+ // Move to relevant state upon finding a command
+ bCommand = true;
+ switch ( rPath[ i ] )
+ {
+ // Single-character commands
+ case 't': // rmoveto
+ state = MOVE_REL; nParamCount = 2; break;
+ case 'm': // moveto
+ state = MOVE_ABS; nParamCount = 2; break;
+ case 'v': // rcurveto
+ state = BEZIER_REL; nParamCount = 6; break;
+ case 'c': // curveto
+ state = BEZIER_ABS; nParamCount = 6; break;
+ case 'r': // rlineto
+ state = LINE_REL; nParamCount = 2; break;
+ case 'l': // lineto
+ state = LINE_ABS; nParamCount = 2; break;
+ case 'x': // close
+ state = CLOSE; break;
+ case 'e': // end
+ state = END; break;
+
+ // Two-character commands
+ case 'n':
+ {
+ switch ( nextChar )
+ {
+ case 'f': // nf - nofill
+ case 's': // ns - nostroke
+ state = UNSUPPORTED; i++; break;
+ }
+ break;
+ }
+ case 'a': // Elliptical curves
+ {
+ switch ( nextChar )
+ {
+ case 'e': // ae - angleellipseto
+ case 'l': // al - angleellipse
+ state = UNSUPPORTED; i++; break;
+ case 't': // at - arcto
+ case 'r': // ar - arc
+ state = UNSUPPORTED; i++; break;
+ }
+ break;
+ }
+ case 'w': // Clockwise elliptical arcs
+ {
+ switch ( nextChar )
+ {
+ case 'a': // wa - clockwisearcto
+ case 'r': // wr - clockwisearc
+ state = UNSUPPORTED; i++; break;
+ }
+ break;
+ }
+ case 'q':
+ {
+ switch ( nextChar )
+ {
+ case 'x': // qx - ellipticalquadrantx
+ case 'y': // qy - ellipticalquadranty
+ state = UNSUPPORTED; i++; break;
+ case 'b': // qb - quadraticbezier
+ state = UNSUPPORTED; i++; break;
+ }
+ break;
+ }
+ case 'h': // behaviour extensions
+ {
+ switch ( nextChar )
+ {
+ case 'a': // ha - AutoLine
+ case 'b': // hb - AutoCurve
+ case 'c': // hc - CornerLine
+ case 'd': // hd - CornerCurve
+ case 'e': // he - SmoothLine
+ case 'f': // hf - SmoothCurve
+ case 'g': // hg - SymmetricLine
+ case 'h': // hh - SymmetricCurve
+ case 'i': // hi - Freeform
+ state = UNSUPPORTED; i++; break;
+ }
+ break;
+ }
+ default:
+ bCommand = false;
+ break;
+ }
+
+ if (bCommand) nTokenLen = 0;
+ nTokenStart = i+1;
+ }
+ }
+}
+
+namespace {
+
+sal_Int64 lclGetEmu( const GraphicHelper& rGraphicHelper, const std::optional< OUString >& roValue, sal_Int64 nDefValue )
+{
+ return roValue.has_value() ? ConversionHelper::decodeMeasureToEmu( rGraphicHelper, roValue.value(), 0, false, false ) : nDefValue;
+}
+
+void lclGetDmlLineDash( std::optional< sal_Int32 >& oroPresetDash, LineProperties::DashStopVector& orCustomDash, const std::optional< OUString >& roDashStyle )
+{
+ if( !roDashStyle.has_value() )
+ return;
+
+ const OUString& rDashStyle = roDashStyle.value();
+ switch( AttributeConversion::decodeToken( rDashStyle ) )
+ {
+ case XML_solid: oroPresetDash = XML_solid; return;
+ case XML_shortdot: oroPresetDash = XML_sysDot; return;
+ case XML_shortdash: oroPresetDash = XML_sysDash; return;
+ case XML_shortdashdot: oroPresetDash = XML_sysDashDot; return;
+ case XML_shortdashdotdot: oroPresetDash = XML_sysDashDotDot; return;
+ case XML_dot: oroPresetDash = XML_dot; return;
+ case XML_dash: oroPresetDash = XML_dash; return;
+ case XML_dashdot: oroPresetDash = XML_dashDot; return;
+ case XML_longdash: oroPresetDash = XML_lgDash; return;
+ case XML_longdashdot: oroPresetDash = XML_lgDashDot; return;
+ case XML_longdashdotdot: oroPresetDash = XML_lgDashDotDot; return;
+
+ // try to convert user-defined dash style
+ default:
+ {
+ ::std::vector< sal_Int32 > aValues;
+ sal_Int32 nIndex = 0;
+ while( nIndex >= 0 )
+ aValues.push_back( o3tl::toInt32(o3tl::getToken(rDashStyle, 0, ' ', nIndex )) );
+ size_t nPairs = aValues.size() / 2; // ignore last value if size is odd
+ for( size_t nPairIdx = 0; nPairIdx < nPairs; ++nPairIdx )
+ orCustomDash.emplace_back( aValues[ 2 * nPairIdx ], aValues[ 2 * nPairIdx + 1 ] );
+ }
+ }
+}
+
+sal_Int32 lclGetDmlArrowType( const std::optional< sal_Int32 >& roArrowType )
+{
+ if( roArrowType.has_value() ) switch( roArrowType.value() )
+ {
+ case XML_none: return XML_none;
+ case XML_block: return XML_triangle;
+ case XML_classic: return XML_stealth;
+ case XML_diamond: return XML_diamond;
+ case XML_oval: return XML_oval;
+ case XML_open: return XML_arrow;
+ }
+ return XML_none;
+}
+
+sal_Int32 lclGetDmlArrowWidth( const std::optional< sal_Int32 >& roArrowWidth )
+{
+ if( roArrowWidth.has_value() ) switch( roArrowWidth.value() )
+ {
+ case XML_narrow: return XML_sm;
+ case XML_medium: return XML_med;
+ case XML_wide: return XML_lg;
+ }
+ return XML_med;
+}
+
+sal_Int32 lclGetDmlArrowLength( const std::optional< sal_Int32 >& roArrowLength )
+{
+ if( roArrowLength.has_value() ) switch( roArrowLength.value() )
+ {
+ case XML_short: return XML_sm;
+ case XML_medium: return XML_med;
+ case XML_long: return XML_lg;
+ }
+ return XML_med;
+}
+
+void lclConvertArrow( LineArrowProperties& orArrowProp, const StrokeArrowModel& rStrokeArrow )
+{
+ orArrowProp.moArrowType = lclGetDmlArrowType( rStrokeArrow.moArrowType );
+ orArrowProp.moArrowWidth = lclGetDmlArrowWidth( rStrokeArrow.moArrowWidth );
+ orArrowProp.moArrowLength = lclGetDmlArrowLength( rStrokeArrow.moArrowLength );
+}
+
+sal_Int32 lclGetDmlLineCompound( const std::optional< sal_Int32 >& roLineStyle )
+{
+ if( roLineStyle.has_value() ) switch( roLineStyle.value() )
+ {
+ case XML_single: return XML_sng;
+ case XML_thinThin: return XML_dbl;
+ case XML_thinThick: return XML_thinThick;
+ case XML_thickThin: return XML_thickThin;
+ case XML_thickBetweenThin: return XML_tri;
+ }
+ return XML_sng;
+}
+
+sal_Int32 lclGetDmlLineCap( const std::optional< sal_Int32 >& roEndCap )
+{
+ if( roEndCap.has_value() ) switch( roEndCap.value() )
+ {
+ case XML_flat: return XML_flat;
+ case XML_square: return XML_sq;
+ case XML_round: return XML_rnd;
+ }
+ return XML_flat; // different defaults in VML (flat) and DrawingML (square)
+}
+
+sal_Int32 lclGetDmlLineJoint( const std::optional< sal_Int32 >& roJoinStyle )
+{
+ if( roJoinStyle.has_value() ) switch( roJoinStyle.value() )
+ {
+ case XML_round: return XML_round;
+ case XML_bevel: return XML_bevel;
+ case XML_miter: return XML_miter;
+ }
+ return XML_round;
+}
+
+} // namespace
+
+void StrokeArrowModel::assignUsed( const StrokeArrowModel& rSource )
+{
+ assignIfUsed( moArrowType, rSource.moArrowType );
+ assignIfUsed( moArrowWidth, rSource.moArrowWidth );
+ assignIfUsed( moArrowLength, rSource.moArrowLength );
+}
+
+void StrokeModel::assignUsed( const StrokeModel& rSource )
+{
+ assignIfUsed( moStroked, rSource.moStroked );
+ maStartArrow.assignUsed( rSource.maStartArrow );
+ maEndArrow.assignUsed( rSource.maEndArrow );
+ assignIfUsed( moColor, rSource.moColor );
+ assignIfUsed( moOpacity, rSource.moOpacity );
+ assignIfUsed( moWeight, rSource.moWeight );
+ assignIfUsed( moDashStyle, rSource.moDashStyle );
+ assignIfUsed( moLineStyle, rSource.moLineStyle );
+ assignIfUsed( moEndCap, rSource.moEndCap );
+ assignIfUsed( moJoinStyle, rSource.moJoinStyle );
+}
+
+void StrokeModel::pushToPropMap( ShapePropertyMap& rPropMap, const GraphicHelper& rGraphicHelper ) const
+{
+ /* Convert VML line formatting to DrawingML line formatting and let the
+ DrawingML code do the hard work. */
+ LineProperties aLineProps;
+
+ if( moStroked.value_or( true ) )
+ {
+ aLineProps.maLineFill.moFillType = XML_solidFill;
+ lclConvertArrow( aLineProps.maStartArrow, maStartArrow );
+ lclConvertArrow( aLineProps.maEndArrow, maEndArrow );
+ aLineProps.maLineFill.maFillColor = ConversionHelper::decodeColor( rGraphicHelper, moColor, moOpacity, API_RGB_BLACK );
+ aLineProps.moLineWidth = getLimitedValue< sal_Int32, sal_Int64 >( lclGetEmu( rGraphicHelper, moWeight, 1 ), 0, SAL_MAX_INT32 );
+ lclGetDmlLineDash( aLineProps.moPresetDash, aLineProps.maCustomDash, moDashStyle );
+ aLineProps.moLineCompound = lclGetDmlLineCompound( moLineStyle );
+ aLineProps.moLineCap = lclGetDmlLineCap( moEndCap );
+ aLineProps.moLineJoint = lclGetDmlLineJoint( moJoinStyle );
+ }
+ else
+ {
+ aLineProps.maLineFill.moFillType = XML_noFill;
+ }
+
+ aLineProps.pushToPropMap( rPropMap, rGraphicHelper );
+}
+
+void FillModel::assignUsed( const FillModel& rSource )
+{
+ assignIfUsed( moFilled, rSource.moFilled );
+ assignIfUsed( moColor, rSource.moColor );
+ assignIfUsed( moOpacity, rSource.moOpacity );
+ assignIfUsed( moColor2, rSource.moColor2 );
+ assignIfUsed( moOpacity2, rSource.moOpacity2 );
+ assignIfUsed( moType, rSource.moType );
+ assignIfUsed( moAngle, rSource.moAngle );
+ assignIfUsed( moFocus, rSource.moFocus );
+ assignIfUsed( moFocusPos, rSource.moFocusPos );
+ assignIfUsed( moFocusSize, rSource.moFocusSize );
+ assignIfUsed( moBitmapPath, rSource.moBitmapPath );
+ assignIfUsed( moRotate, rSource.moRotate );
+}
+
+static void lcl_setGradientStop( std::multimap< double, Color >& rMap, const double fKey, const Color& rValue ) {
+ auto aElement = rMap.find( fKey );
+
+ if (aElement != rMap.end())
+ aElement->second = rValue;
+ else
+ rMap.emplace( fKey, rValue );
+}
+
+void FillModel::pushToPropMap( ShapePropertyMap& rPropMap, const GraphicHelper& rGraphicHelper ) const
+{
+ /* Convert VML fill formatting to DrawingML fill formatting and let the
+ DrawingML code do the hard work. */
+ FillProperties aFillProps;
+
+ if( moFilled.value_or( true ) )
+ {
+ sal_Int32 nFillType = moType.value_or( XML_solid );
+ switch( nFillType )
+ {
+ case XML_gradient:
+ case XML_gradientRadial:
+ {
+ aFillProps.moFillType = XML_gradFill;
+ aFillProps.maGradientProps.moRotateWithShape = moRotate.value_or( false );
+ double fFocus = moFocus.value_or( 0.0 );
+
+ // prepare colors
+ Color aColor1 = ConversionHelper::decodeColor( rGraphicHelper, moColor, moOpacity, API_RGB_WHITE );
+ Color aColor2 = ConversionHelper::decodeColor( rGraphicHelper, moColor2, moOpacity2, API_RGB_WHITE, aColor1.getColor( rGraphicHelper ) );
+
+ // type XML_gradient is linear or axial gradient
+ if( nFillType == XML_gradient )
+ {
+ // normalize angle to range [0;360) degrees
+ sal_Int32 nVmlAngle = getIntervalValue< sal_Int32, sal_Int32 >( moAngle.value_or( 0 ), 0, 360 );
+
+ // focus of -50% or 50% is axial gradient
+ if( ((-0.75 <= fFocus) && (fFocus <= -0.25)) || ((0.25 <= fFocus) && (fFocus <= 0.75)) )
+ {
+ /* According to spec, focus of 50% is outer-to-inner,
+ and -50% is inner-to-outer (color to color2).
+ BUT: For angles >= 180 deg., the behaviour is
+ reversed... that's not spec'ed of course. So,
+ [0;180) deg. and 50%, or [180;360) deg. and -50% is
+ outer-to-inner in fact. */
+ bool bOuterToInner = (fFocus > 0.0) == (nVmlAngle < 180);
+ // simulate axial gradient by 3-step DrawingML gradient
+ const Color& rOuterColor = bOuterToInner ? aColor1 : aColor2;
+ const Color& rInnerColor = bOuterToInner ? aColor2 : aColor1;
+
+ // add in order of offset
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.0, rOuterColor);
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.5, rInnerColor);
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 1.0, rOuterColor);
+ }
+ else // focus of -100%, 0%, and 100% is linear gradient
+ {
+ /* According to spec, focus of -100% or 100% swaps the
+ start and stop colors, effectively reversing the
+ gradient. BUT: For angles >= 180 deg., the
+ behaviour is reversed. This means that in this case
+ a focus of 0% swaps the gradient. */
+ if( fFocus < -0.5 || fFocus > 0.5 )
+ nVmlAngle = (nVmlAngle + 180) % 360;
+ // set the start and stop colors
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.0, aColor1 );
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 1.0, aColor2 );
+ }
+
+ // VML counts counterclockwise from bottom, DrawingML clockwise from left
+ sal_Int32 nDmlAngle = (630 - nVmlAngle) % 360;
+ aFillProps.maGradientProps.moShadeAngle = nDmlAngle * ::oox::drawingml::PER_DEGREE;
+ }
+ else // XML_gradientRadial is rectangular gradient
+ {
+ aFillProps.maGradientProps.moGradientPath = XML_rect;
+ // convert VML focus position and size to DrawingML fill-to-rect
+ DoublePair aFocusPos = moFocusPos.value_or( DoublePair( 0.0, 0.0 ) );
+ DoublePair aFocusSize = moFocusSize.value_or( DoublePair( 0.0, 0.0 ) );
+ double fLeft = getLimitedValue< double, double >( aFocusPos.first, 0.0, 1.0 );
+ double fTop = getLimitedValue< double, double >( aFocusPos.second, 0.0, 1.0 );
+ double fRight = getLimitedValue< double, double >( fLeft + aFocusSize.first, fLeft, 1.0 );
+ double fBottom = getLimitedValue< double, double >( fTop + aFocusSize.second, fTop, 1.0 );
+ aFillProps.maGradientProps.moFillToRect = IntegerRectangle2D(
+ static_cast< sal_Int32 >( fLeft * ::oox::drawingml::MAX_PERCENT ),
+ static_cast< sal_Int32 >( fTop * ::oox::drawingml::MAX_PERCENT ),
+ static_cast< sal_Int32 >( (1.0 - fRight) * ::oox::drawingml::MAX_PERCENT ),
+ static_cast< sal_Int32 >( (1.0 - fBottom) * ::oox::drawingml::MAX_PERCENT ) );
+
+ // set the start and stop colors (focus of 0% means outer-to-inner)
+ bool bOuterToInner = (-0.5 <= fFocus) && (fFocus <= 0.5);
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.0, bOuterToInner ? aColor2 : aColor1 );
+ lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 1.0, bOuterToInner ? aColor1 : aColor2 );
+ }
+ }
+ break;
+
+ case XML_pattern:
+ case XML_tile:
+ case XML_frame:
+ {
+ if( moBitmapPath.has_value() && !moBitmapPath.value().isEmpty() )
+ {
+ aFillProps.maBlipProps.mxFillGraphic = rGraphicHelper.importEmbeddedGraphic(moBitmapPath.value());
+ if (aFillProps.maBlipProps.mxFillGraphic.is())
+ {
+ aFillProps.moFillType = XML_blipFill;
+ aFillProps.maBlipProps.moBitmapMode = (nFillType == XML_frame) ? XML_stretch : XML_tile;
+ break; // do not break if bitmap is missing, but run to XML_solid instead
+ }
+ }
+ }
+ [[fallthrough]]; // to XML_solid in case of missing bitmap path intended!
+
+ case XML_solid:
+ default:
+ {
+ aFillProps.moFillType = XML_solidFill;
+ // fill color (default is white)
+ aFillProps.maFillColor = ConversionHelper::decodeColor( rGraphicHelper, moColor, moOpacity, API_RGB_WHITE );
+ }
+ }
+ }
+ else
+ {
+ aFillProps.moFillType = XML_noFill;
+ }
+
+ aFillProps.pushToPropMap( rPropMap, rGraphicHelper );
+}
+
+ShadowModel::ShadowModel()
+ : mbHasShadow(false)
+{
+}
+
+void ShadowModel::pushToPropMap(ShapePropertyMap& rPropMap, const GraphicHelper& rGraphicHelper) const
+{
+ if (!mbHasShadow || (moShadowOn.has_value() && !moShadowOn.value()))
+ return;
+
+ drawingml::Color aColor = ConversionHelper::decodeColor(rGraphicHelper, moColor, moOpacity, API_RGB_GRAY);
+ // nOffset* is in mm100, default value is 35 twips, see DffPropertyReader::ApplyAttributes() in msfilter.
+ sal_Int32 nOffsetX = 62, nOffsetY = 62;
+ if (moOffset.has_value())
+ {
+ std::u16string_view aOffsetX, aOffsetY;
+ ConversionHelper::separatePair(aOffsetX, aOffsetY, moOffset.value(), ',');
+ if (!aOffsetX.empty())
+ nOffsetX = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aOffsetX, 0, false, false );
+ if (!aOffsetY.empty())
+ nOffsetY = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aOffsetY, 0, false, false );
+ }
+
+ table::ShadowFormat aFormat;
+ aFormat.Color = sal_Int32(aColor.getColor(rGraphicHelper));
+ aFormat.Location = nOffsetX < 0
+ ? nOffsetY < 0 ? table::ShadowLocation_TOP_LEFT : table::ShadowLocation_BOTTOM_LEFT
+ : nOffsetY < 0 ? table::ShadowLocation_TOP_RIGHT : table::ShadowLocation_BOTTOM_RIGHT;
+ // The width of the shadow is the average of the x and y values, see SwWW8ImplReader::MatchSdrItemsIntoFlySet().
+ aFormat.ShadowWidth = ((std::abs(nOffsetX) + std::abs(nOffsetY)) / 2);
+ rPropMap.setProperty(PROP_ShadowFormat, aFormat);
+}
+
+TextpathModel::TextpathModel()
+{
+}
+
+static beans::PropertyValue lcl_createTextpathProps()
+{
+ uno::Sequence<beans::PropertyValue> aTextpathPropSeq( comphelper::InitPropertySequence({
+ { "TextPath", uno::Any(true) },
+ { "TextPathMode", uno::Any(drawing::EnhancedCustomShapeTextPathMode_SHAPE) },
+ { "ScaleX", uno::Any(false) },
+ { "SameLetterHeights", uno::Any(false) }
+ }));
+
+ beans::PropertyValue aRet;
+ aRet.Name = "TextPath";
+ aRet.Value <<= aTextpathPropSeq;
+ return aRet;
+}
+
+void TextpathModel::pushToPropMap(ShapePropertyMap& rPropMap, const uno::Reference<drawing::XShape>& xShape, const GraphicHelper& rGraphicHelper) const
+{
+ OUString sFont = "";
+
+ if (moString.has_value())
+ {
+ uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
+ xTextRange->setString(moString.value());
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ uno::Sequence<beans::PropertyValue> aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence<beans::PropertyValue> >();
+ bool bFound = false;
+ for (beans::PropertyValue& rProp : asNonConstRange(aGeomPropSeq))
+ {
+ if (rProp.Name == "TextPath")
+ {
+ bFound = true;
+ rProp = lcl_createTextpathProps();
+ }
+ }
+ if (!bFound)
+ {
+ sal_Int32 nSize = aGeomPropSeq.getLength();
+ aGeomPropSeq.realloc(nSize+1);
+ aGeomPropSeq.getArray()[nSize] = lcl_createTextpathProps();
+ }
+ rPropMap.setAnyProperty(PROP_CustomShapeGeometry, uno::Any(aGeomPropSeq));
+ }
+ if (moStyle.has_value())
+ {
+ OUString aStyle = moStyle.value_or(OUString());
+
+ sal_Int32 nIndex = 0;
+ while( nIndex >= 0 )
+ {
+ std::u16string_view aName, aValue;
+ if (ConversionHelper::separatePair(aName, aValue, o3tl::getToken(aStyle, 0, ';', nIndex), ':'))
+ {
+ if (aName == u"font-family")
+ {
+ // remove " (first, and last character)
+ if (aValue.size() > 2)
+ aValue = aValue.substr(1, aValue.size() - 2);
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("CharFontName", uno::Any(OUString(aValue)));
+ sFont = aValue;
+ }
+ else if (aName == u"font-size")
+ {
+ std::optional<OUString> aOptString {OUString(aValue)};
+ float nSize = drawingml::convertEmuToPoints(lclGetEmu(rGraphicHelper, aOptString, 1));
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("CharHeight", uno::Any(nSize));
+ }
+ }
+ }
+ }
+ if (moTrim.has_value() && moTrim.value())
+ return;
+
+ OUString sText = moString.value_or("");
+ ScopedVclPtrInstance<VirtualDevice> pDevice;
+ vcl::Font aFont = pDevice->GetFont();
+ aFont.SetFamilyName(sFont);
+ aFont.SetFontSize(Size(0, 96));
+ pDevice->SetFont(aFont);
+
+ auto nTextWidth = pDevice->GetTextWidth(sText);
+ if (nTextWidth)
+ {
+ sal_Int32 nNewHeight = (static_cast<double>(pDevice->GetTextHeight()) / nTextWidth) * xShape->getSize().Width;
+ xShape->setSize(awt::Size(xShape->getSize().Width, nNewHeight));
+ }
+}
+
+} // namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmlinputstream.cxx b/oox/source/vml/vmlinputstream.cxx
new file mode 100644
index 0000000000..26371748ee
--- /dev/null
+++ b/oox/source/vml/vmlinputstream.cxx
@@ -0,0 +1,393 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/vml/vmlinputstream.hxx>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XTextInputStream2.hpp>
+#include <map>
+#include <string.h>
+#include <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+#include <oox/helper/textinputstream.hxx>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::uno;
+
+namespace {
+
+const char* lclFindCharacter( const char* pcBeg, const char* pcEnd, char cChar )
+{
+ sal_Int32 nIndex = rtl_str_indexOfChar_WithLength( pcBeg, static_cast< sal_Int32 >( pcEnd - pcBeg ), cChar );
+ return (nIndex < 0) ? pcEnd : (pcBeg + nIndex);
+}
+
+bool lclIsWhiteSpace( char cChar )
+{
+ return cChar >= 0 && cChar <= 32;
+}
+
+const char* lclFindWhiteSpace( const char* pcBeg, const char* pcEnd )
+{
+ for( ; pcBeg < pcEnd; ++pcBeg )
+ if( lclIsWhiteSpace( *pcBeg ) )
+ return pcBeg;
+ return pcEnd;
+}
+
+const char* lclFindNonWhiteSpace( const char* pcBeg, const char* pcEnd )
+{
+ for( ; pcBeg < pcEnd; ++pcBeg )
+ if( !lclIsWhiteSpace( *pcBeg ) )
+ return pcBeg;
+ return pcEnd;
+}
+
+const char* lclTrimWhiteSpaceFromEnd( const char* pcBeg, const char* pcEnd )
+{
+ while( (pcBeg < pcEnd) && lclIsWhiteSpace( pcEnd[ -1 ] ) )
+ --pcEnd;
+ return pcEnd;
+}
+
+void lclAppendToBuffer( OStringBuffer& rBuffer, const char* pcBeg, const char* pcEnd )
+{
+ rBuffer.append( pcBeg, static_cast< sal_Int32 >( pcEnd - pcBeg ) );
+}
+
+void lclProcessAttribs( OStringBuffer& rBuffer, const char* pcBeg, const char* pcEnd )
+{
+ /* Map attribute names to char-pointer of all attributes. This map is used
+ to find multiple occurrences of attributes with the same name. The
+ mapped pointers are used as map key in the next map below. */
+ typedef ::std::map< OString, const char* > AttributeNameMap;
+ AttributeNameMap aAttributeNames;
+
+ /* Map the char-pointers of all attributes to the full attribute definition
+ string. This preserves the original order of the used attributes. */
+ typedef ::std::map< const char*, OString > AttributeDataMap;
+ AttributeDataMap aAttributes;
+
+ bool bOk = true;
+ const char* pcNameBeg = pcBeg;
+ while( bOk && (pcNameBeg < pcEnd) )
+ {
+ // pcNameBeg points to begin of attribute name, find equality sign
+ const char* pcEqualSign = lclFindCharacter( pcNameBeg, pcEnd, '=' );
+ bOk = (pcEqualSign < pcEnd);
+ if (bOk)
+ {
+ // find end of attribute name (ignore whitespace between name and equality sign)
+ const char* pcNameEnd = lclTrimWhiteSpaceFromEnd( pcNameBeg, pcEqualSign );
+ bOk = (pcNameBeg < pcNameEnd);
+ if( bOk )
+ {
+ // find begin of attribute value (must be single or double quote)
+ const char* pcValueBeg = lclFindNonWhiteSpace( pcEqualSign + 1, pcEnd );
+ bOk = (pcValueBeg < pcEnd) && ((*pcValueBeg == '\'') || (*pcValueBeg == '"'));
+ if( bOk )
+ {
+ // find end of attribute value (matching quote character)
+ const char* pcValueEnd = lclFindCharacter( pcValueBeg + 1, pcEnd, *pcValueBeg );
+ bOk = (pcValueEnd < pcEnd);
+ if( bOk )
+ {
+ ++pcValueEnd;
+ OString aAttribName( pcNameBeg, static_cast< sal_Int32 >( pcNameEnd - pcNameBeg ) );
+ OString aAttribData( pcNameBeg, static_cast< sal_Int32 >( pcValueEnd - pcNameBeg ) );
+ // search for an existing attribute with the same name
+ AttributeNameMap::iterator aIt = aAttributeNames.find( aAttribName );
+ // remove its definition from the data map
+ if( aIt != aAttributeNames.end() )
+ aAttributes.erase( aIt->second );
+ // insert the attribute into both maps
+ aAttributeNames[ aAttribName ] = pcNameBeg;
+ aAttributes[ pcNameBeg ] = aAttribData;
+ // continue with next attribute (skip whitespace after this attribute)
+ pcNameBeg = pcValueEnd;
+ if( pcNameBeg < pcEnd )
+ {
+ bOk = lclIsWhiteSpace( *pcNameBeg );
+ if( bOk )
+ pcNameBeg = lclFindNonWhiteSpace( pcNameBeg + 1, pcEnd );
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // if no error has occurred, build the resulting attribute list
+ if( bOk )
+ for (auto const& attrib : aAttributes)
+ rBuffer.append( " " + attrib.second );
+ // on error, just append the complete passed string
+ else
+ lclAppendToBuffer( rBuffer, pcBeg, pcEnd );
+}
+
+void lclProcessElement( OStringBuffer& rBuffer, const OString& rElement )
+{
+ // check that passed string starts and ends with the brackets of an XML element
+ sal_Int32 nElementLen = rElement.getLength();
+ if( nElementLen == 0 )
+ return;
+
+ const char* pcOpen = rElement.getStr();
+ const char* pcClose = pcOpen + nElementLen - 1;
+
+ // no complete element found
+ if( (pcOpen >= pcClose) || (*pcOpen != '<') || (*pcClose != '>') )
+ {
+ // just append all passed characters
+ rBuffer.append( rElement );
+ }
+
+ // skip parser instructions: '<![...]>'
+ else if( (nElementLen >= 5) && (pcOpen[ 1 ] == '!') && (pcOpen[ 2 ] == '[') && (pcClose[ -1 ] == ']') )
+ {
+ // do nothing
+ }
+
+ // just append any xml prolog (text directive) or processing instructions: <?...?>
+ else if( (nElementLen >= 4) && (pcOpen[ 1 ] == '?') && (pcClose[ -1 ] == '?') )
+ {
+ rBuffer.append( rElement );
+ }
+
+ // replace '<br>' element with newline
+ else if( (nElementLen >= 4) && (pcOpen[ 1 ] == 'b') && (pcOpen[ 2 ] == 'r') && (lclFindNonWhiteSpace( pcOpen + 3, pcClose ) == pcClose) )
+ {
+ rBuffer.append( '\n' );
+ }
+
+ // check start elements and simple elements for repeated attributes
+ else if( pcOpen[ 1 ] != '/' )
+ {
+ // find positions of text content inside brackets, exclude '/' in '<simpleelement/>'
+ const char* pcContentBeg = pcOpen + 1;
+ bool bIsEmptyElement = pcClose[ -1 ] == '/';
+ const char* pcContentEnd = bIsEmptyElement ? (pcClose - 1) : pcClose;
+ // append opening bracket and element name to buffer
+ const char* pcWhiteSpace = lclFindWhiteSpace( pcContentBeg, pcContentEnd );
+ lclAppendToBuffer( rBuffer, pcOpen, pcWhiteSpace );
+ // find begin of attributes, and process all attributes
+ const char* pcAttribBeg = lclFindNonWhiteSpace( pcWhiteSpace, pcContentEnd );
+ if( pcAttribBeg < pcContentEnd )
+ lclProcessAttribs( rBuffer, pcAttribBeg, pcContentEnd );
+ // close the element
+ if( bIsEmptyElement )
+ rBuffer.append( '/' );
+ rBuffer.append( '>' );
+ }
+
+ // append end elements without further processing
+ else
+ {
+ rBuffer.append( rElement );
+ }
+}
+
+bool lclProcessCharacters( OStringBuffer& rBuffer, const OString& rChars )
+{
+ /* MSO has a very weird way to store and handle whitespaces. The stream
+ may contain lots of spaces, tabs, and newlines which have to be handled
+ as single space character. This will be done in this function.
+
+ If the element text contains a literal line break, it will be stored as
+ <br> tag (without matching </br> element). This input stream wrapper
+ will replace this element with a literal LF character (see below).
+
+ A single space character for its own is stored as is. Example: The
+ element
+ <font> </font>
+ represents a single space character. The XML parser will ignore this
+ space character completely without issuing a 'characters' event. The
+ VML import filter implementation has to react on this case manually.
+
+ A single space character following another character is stored
+ literally and must not be stripped away here. Example: The element
+ <font>abc </font>
+ contains the three letters a, b, and c, followed by a space character.
+
+ Consecutive space characters, or a leading single space character, are
+ stored in a <span> element. If there are N space characters (N > 1),
+ then the <span> element contains exactly (N-1) NBSP (non-breaking
+ space) characters, followed by a regular space character. Examples:
+ The element
+ <font><span style='mso-spacerun:yes'>\xA0\xA0\xA0 </span></font>
+ represents 4 consecutive space characters. Has to be handled by the
+ implementation. The element
+ <font><span style='mso-spacerun:yes'> abc</span></font>
+ represents a space characters followed by the letters a, b, c. These
+ strings have to be handled by the VML import filter implementation.
+ */
+
+ // passed string ends with the leading opening bracket of an XML element
+ const char* pcBeg = rChars.getStr();
+ const char* pcEnd = pcBeg + rChars.getLength();
+ bool bHasBracket = (pcBeg < pcEnd) && (pcEnd[ -1 ] == '<');
+ if( bHasBracket ) --pcEnd;
+
+ // skip leading whitespace
+ const char* pcContentsBeg = lclFindNonWhiteSpace( pcBeg, pcEnd );
+ while( pcContentsBeg < pcEnd )
+ {
+ const char* pcWhitespaceBeg = lclFindWhiteSpace( pcContentsBeg + 1, pcEnd );
+ lclAppendToBuffer( rBuffer, pcContentsBeg, pcWhitespaceBeg );
+ if( pcWhitespaceBeg < pcEnd )
+ rBuffer.append( ' ' );
+ pcContentsBeg = lclFindNonWhiteSpace( pcWhitespaceBeg, pcEnd );
+ }
+
+ return bHasBracket;
+}
+
+} // namespace
+
+constexpr OStringLiteral gaOpeningCData( "<![CDATA[" );
+constexpr OString gaClosingCData( "]]>"_ostr );
+
+InputStream::InputStream( const Reference< XComponentContext >& rxContext, const Reference< XInputStream >& rxInStrm ) :
+ // use single-byte ISO-8859-1 encoding which maps all byte characters to the first 256 Unicode characters
+ mxTextStrm( TextInputStream::createXTextInputStream( rxContext, rxInStrm, RTL_TEXTENCODING_ISO_8859_1 ) ),
+ maOpeningBracket{ '<' },
+ maClosingBracket{ '>' },
+ mnBufferPos( 0 )
+{
+ if (!mxTextStrm.is())
+ throw IOException();
+}
+
+InputStream::~InputStream()
+{
+}
+
+sal_Int32 SAL_CALL InputStream::readBytes( Sequence< sal_Int8 >& rData, sal_Int32 nBytesToRead )
+{
+ if( nBytesToRead < 0 )
+ throw IOException();
+
+ rData.realloc( nBytesToRead );
+ sal_Int8* pcDest = rData.getArray();
+ sal_Int32 nRet = 0;
+ while( (nBytesToRead > 0) && !mxTextStrm->isEOF() )
+ {
+ updateBuffer();
+ sal_Int32 nReadSize = ::std::min( nBytesToRead, maBuffer.getLength() - mnBufferPos );
+ if( nReadSize > 0 )
+ {
+ memcpy( pcDest + nRet, maBuffer.getStr() + mnBufferPos, static_cast< size_t >( nReadSize ) );
+ mnBufferPos += nReadSize;
+ nBytesToRead -= nReadSize;
+ nRet += nReadSize;
+ }
+ }
+ if( nRet < rData.getLength() )
+ rData.realloc( nRet );
+ return nRet;
+}
+
+sal_Int32 SAL_CALL InputStream::readSomeBytes( Sequence< sal_Int8 >& rData, sal_Int32 nMaxBytesToRead )
+{
+ return readBytes( rData, nMaxBytesToRead );
+}
+
+void SAL_CALL InputStream::skipBytes( sal_Int32 nBytesToSkip )
+{
+ if( nBytesToSkip < 0 )
+ throw IOException();
+
+ while( (nBytesToSkip > 0) && !mxTextStrm->isEOF() )
+ {
+ updateBuffer();
+ sal_Int32 nSkipSize = ::std::min( nBytesToSkip, maBuffer.getLength() - mnBufferPos );
+ mnBufferPos += nSkipSize;
+ nBytesToSkip -= nSkipSize;
+ }
+}
+
+sal_Int32 SAL_CALL InputStream::available()
+{
+ updateBuffer();
+ return maBuffer.getLength() - mnBufferPos;
+}
+
+void SAL_CALL InputStream::closeInput()
+{
+ mxTextStrm->closeInput();
+}
+
+// private --------------------------------------------------------------------
+
+void InputStream::updateBuffer()
+{
+ while( (mnBufferPos >= maBuffer.getLength()) && !mxTextStrm->isEOF() )
+ {
+ // collect new contents in a string buffer
+ OStringBuffer aBuffer;
+
+ // read and process characters until the opening bracket of the next XML element
+ OString aChars = readToElementBegin();
+ bool bHasOpeningBracket = lclProcessCharacters( aBuffer, aChars );
+
+ // read and process characters until (and including) closing bracket (an XML element)
+ OSL_ENSURE( bHasOpeningBracket || mxTextStrm->isEOF(), "InputStream::updateBuffer - missing opening bracket of XML element" );
+ if( bHasOpeningBracket && !mxTextStrm->isEOF() )
+ {
+ // read the element text (add the leading opening bracket manually)
+ OString aElement = "<" + readToElementEnd();
+ // check for CDATA part, starting with '<![CDATA['
+ if( aElement.match( gaOpeningCData ) )
+ {
+ // search the end tag ']]>'
+ while( ((aElement.getLength() < gaClosingCData.getLength()) || !aElement.endsWith( gaClosingCData )) && !mxTextStrm->isEOF() )
+ aElement += readToElementEnd();
+ // copy the entire CDATA part
+ aBuffer.append( aElement );
+ }
+ else
+ {
+ // no CDATA part - process the contents of the element
+ lclProcessElement( aBuffer, aElement );
+ }
+ }
+
+ maBuffer = aBuffer.makeStringAndClear();
+ mnBufferPos = 0;
+ }
+}
+
+OString InputStream::readToElementBegin()
+{
+ return OUStringToOString( mxTextStrm->readString( maOpeningBracket, false ), RTL_TEXTENCODING_ISO_8859_1 );
+}
+
+OString InputStream::readToElementEnd()
+{
+ OString aText = OUStringToOString( mxTextStrm->readString( maClosingBracket, false ), RTL_TEXTENCODING_ISO_8859_1 );
+ OSL_ENSURE( aText.endsWith(">"), "InputStream::readToElementEnd - missing closing bracket of XML element" );
+ return aText;
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmlshape.cxx b/oox/source/vml/vmlshape.cxx
new file mode 100644
index 0000000000..5284de1768
--- /dev/null
+++ b/oox/source/vml/vmlshape.cxx
@@ -0,0 +1,1577 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <algorithm>
+
+#include <optional>
+
+#include <o3tl/safeint.hxx>
+#include <oox/vml/vmlshape.hxx>
+#include <utility>
+#include <vcl/wmfexternal.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/PointSequenceSequence.hpp>
+#include <com/sun/star/drawing/PolyPolygonBezierCoords.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/drawing/XControlShape.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/table/BorderLine2.hpp>
+#include <com/sun/star/text/HoriOrientation.hpp>
+#include <com/sun/star/text/RelOrientation.hpp>
+#include <com/sun/star/text/SizeType.hpp>
+#include <com/sun/star/text/VertOrientation.hpp>
+#include <com/sun/star/text/WrapTextMode.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/text/TextContentAnchorType.hpp>
+#include <com/sun/star/text/GraphicCrop.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/security/XDocumentDigitalSignatures.hpp>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/drawing/ColorMode.hpp>
+#include <sal/log.hxx>
+#include <oox/drawingml/shapepropertymap.hxx>
+#include <oox/helper/graphichelper.hxx>
+#include <oox/helper/propertyset.hxx>
+#include <oox/ole/axcontrol.hxx>
+#include <oox/ole/axcontrolfragment.hxx>
+#include <oox/ole/oleobjecthelper.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/vml/vmldrawing.hxx>
+#include <oox/vml/vmlshapecontainer.hxx>
+#include <oox/vml/vmltextbox.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/helper/containerhelper.hxx>
+#include <svx/msdffdef.hxx>
+#include <svx/sdtagitm.hxx>
+#include <svx/svdobj.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <o3tl/string_view.hxx>
+
+using ::com::sun::star::beans::XPropertySet;
+using ::com::sun::star::uno::Any;
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::text;
+
+namespace oox::vml {
+
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::graphic;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::io;
+
+using ::oox::core::XmlFilterBase;
+
+namespace {
+
+const sal_Int32 VML_SHAPETYPE_PICTUREFRAME = 75;
+const sal_Int32 VML_SHAPETYPE_HOSTCONTROL = 201;
+
+awt::Point lclGetAbsPoint( const awt::Point& rRelPoint, const awt::Rectangle& rShapeRect, const awt::Rectangle& rCoordSys )
+{
+ double fWidthRatio = static_cast< double >( rShapeRect.Width ) / rCoordSys.Width;
+ double fHeightRatio = static_cast< double >( rShapeRect.Height ) / rCoordSys.Height;
+ awt::Point aAbsPoint;
+ aAbsPoint.X = static_cast< sal_Int32 >( rShapeRect.X + fWidthRatio * (rRelPoint.X - rCoordSys.X) + 0.5 );
+ aAbsPoint.Y = static_cast< sal_Int32 >( rShapeRect.Y + fHeightRatio * (rRelPoint.Y - rCoordSys.Y) + 0.5 );
+ return aAbsPoint;
+}
+
+awt::Rectangle lclGetAbsRect( const awt::Rectangle& rRelRect, const awt::Rectangle& rShapeRect, const awt::Rectangle& rCoordSys )
+{
+ double fWidthRatio = static_cast< double >( rShapeRect.Width ) / rCoordSys.Width;
+ double fHeightRatio = static_cast< double >( rShapeRect.Height ) / rCoordSys.Height;
+ awt::Rectangle aAbsRect;
+ aAbsRect.X = static_cast< sal_Int32 >( rShapeRect.X + fWidthRatio * (rRelRect.X - rCoordSys.X) + 0.5 );
+ aAbsRect.Y = static_cast< sal_Int32 >( rShapeRect.Y + fHeightRatio * (rRelRect.Y - rCoordSys.Y) + 0.5 );
+ aAbsRect.Width = static_cast< sal_Int32 >( fWidthRatio * rRelRect.Width + 0.5 );
+ aAbsRect.Height = static_cast< sal_Int32 >( fHeightRatio * rRelRect.Height + 0.5 );
+ return aAbsRect;
+}
+
+/// Count the crop value based on a crop fraction and a reference size.
+sal_Int32 lclConvertCrop(std::u16string_view rCrop, sal_uInt32 nSize)
+{
+ if (o3tl::ends_with(rCrop, u"f"))
+ {
+ // Numeric value is specified in 1/65536-ths.
+ sal_uInt32 nCrop = o3tl::toUInt32(rCrop.substr(0, rCrop.size() - 1));
+ return (nCrop * nSize) / 65536;
+ }
+
+ return 0;
+}
+
+} // namespace
+
+ShapeTypeModel::ShapeTypeModel():
+ mbAutoHeight( false ),
+ mbVisible( true )
+{
+}
+
+void ShapeTypeModel::assignUsed( const ShapeTypeModel& rSource )
+{
+ assignIfUsed( moShapeType, rSource.moShapeType );
+ assignIfUsed( moCoordPos, rSource.moCoordPos );
+ assignIfUsed( moCoordSize, rSource.moCoordSize );
+ /* The style properties position, left, top, width, height, margin-left,
+ margin-top are not derived from shape template to shape. */
+ maStrokeModel.assignUsed( rSource.maStrokeModel );
+ maFillModel.assignUsed( rSource.maFillModel );
+ assignIfUsed( moGraphicPath, rSource.moGraphicPath );
+ assignIfUsed( moGraphicTitle, rSource.moGraphicTitle );
+}
+
+ShapeType::ShapeType( Drawing& rDrawing ) :
+ mrDrawing( rDrawing )
+{
+}
+
+ShapeType::~ShapeType()
+{
+}
+
+sal_Int32 ShapeType::getShapeType() const
+{
+ return maTypeModel.moShapeType.value_or( 0 );
+}
+
+OUString ShapeType::getGraphicPath() const
+{
+ return maTypeModel.moGraphicPath.value_or( OUString() );
+}
+
+awt::Rectangle ShapeType::getCoordSystem() const
+{
+ Int32Pair aCoordPos = maTypeModel.moCoordPos.value_or( Int32Pair( 0, 0 ) );
+ Int32Pair aCoordSize = maTypeModel.moCoordSize.value_or( Int32Pair( 1000, 1000 ) );
+ if( aCoordSize.first == 0 )
+ aCoordSize.first = 1;
+ if( aCoordSize.second == 0 )
+ aCoordSize.second = 1;
+ return awt::Rectangle( aCoordPos.first, aCoordPos.second, aCoordSize.first, aCoordSize.second );
+}
+
+awt::Rectangle ShapeType::getRectangle( const ShapeParentAnchor* pParentAnchor ) const
+{
+ return pParentAnchor ?
+ lclGetAbsRect( getRelRectangle(), pParentAnchor->maShapeRect, pParentAnchor->maCoordSys ) :
+ getAbsRectangle();
+}
+
+awt::Rectangle ShapeType::getAbsRectangle() const
+{
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+
+ sal_Int32 nWidth = ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maWidth, 0, true, true );
+ if ( nWidth == 0 )
+ nWidth = 1;
+
+ sal_Int32 nHeight = ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maHeight, 0, false, true );
+ if ( nHeight == 0 )
+ nHeight = 1;
+
+ sal_Int32 nLeft;
+ if (o3tl::checked_add<sal_Int32>(ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maLeft, 0, true, true),
+ ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maMarginLeft, 0, true, true),
+ nLeft))
+ {
+ SAL_WARN("oox", "overflow in addition");
+ nLeft = 0;
+ }
+ if (nLeft == 0 && maTypeModel.maPosition == "absolute")
+ nLeft = 1;
+
+ return awt::Rectangle(
+ nLeft,
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maTop, 0, false, true ) + ConversionHelper::decodeMeasureToHmm( rGraphicHelper, maTypeModel.maMarginTop, 0, false, true ),
+ nWidth, nHeight );
+}
+
+awt::Rectangle ShapeType::getRelRectangle() const
+{
+ sal_Int32 nWidth = maTypeModel.maWidth.toInt32();
+ if ( nWidth == 0 )
+ nWidth = 1;
+
+ sal_Int32 nHeight = maTypeModel.maHeight.toInt32();
+ if ( nHeight == 0 )
+ nHeight = 1;
+
+ return awt::Rectangle(
+ maTypeModel.maLeft.toInt32(),
+ maTypeModel.maTop.toInt32(),
+ nWidth, nHeight );
+}
+
+ClientData::ClientData() :
+ mnObjType( XML_TOKEN_INVALID ),
+ mnTextHAlign( XML_Left ),
+ mnTextVAlign( XML_Top ),
+ mnCol( -1 ),
+ mnRow( -1 ),
+ mnChecked( VML_CLIENTDATA_UNCHECKED ),
+ mnDropStyle( XML_Combo ),
+ mnDropLines( 1 ),
+ mnVal( 0 ),
+ mnMin( 0 ),
+ mnMax( 0 ),
+ mnInc( 0 ),
+ mnPage( 0 ),
+ mnSelType( XML_Single ),
+ mnVTEdit( VML_CLIENTDATA_TEXT ),
+ mbPrintObject( true ),
+ mbVisible( false ),
+ mbDde( false ),
+ mbNo3D( false ),
+ mbNo3D2( false ),
+ mbMultiLine( false ),
+ mbVScroll( false ),
+ mbSecretEdit( false )
+{
+}
+
+ShapeModel::ShapeModel()
+ : mbIsSignatureLine(false)
+ , mbSignatureLineShowSignDate(true)
+ , mbSignatureLineCanAddComment(false)
+ , mbInGroup(false)
+{
+}
+
+ShapeModel::~ShapeModel()
+{
+}
+
+TextBox& ShapeModel::createTextBox(ShapeTypeModel& rModel)
+{
+ mxTextBox.reset( new TextBox(rModel) );
+ return *mxTextBox;
+}
+
+ClientData& ShapeModel::createClientData()
+{
+ mxClientData.reset( new ClientData );
+ return *mxClientData;
+}
+
+ShapeBase::ShapeBase( Drawing& rDrawing ) :
+ ShapeType( rDrawing )
+{
+}
+
+void ShapeBase::finalizeFragmentImport()
+{
+ if( maShapeModel.maType.getLength() <= 1 )
+ return;
+
+ OUString aType = maShapeModel.maType;
+ if (aType[ 0 ] == '#')
+ aType = aType.copy(1);
+ if( const ShapeType* pShapeType = mrDrawing.getShapes().getShapeTypeById( aType ) )
+ {
+ // Make sure that the props from maTypeModel have priority over the props from
+ // the shape type.
+ StrokeModel aMergedStrokeModel;
+ aMergedStrokeModel.assignUsed(pShapeType->getTypeModel().maStrokeModel);
+ aMergedStrokeModel.assignUsed(maTypeModel.maStrokeModel);
+ FillModel aMergedFillModel;
+ aMergedFillModel.assignUsed(pShapeType->getTypeModel().maFillModel);
+ aMergedFillModel.assignUsed(maTypeModel.maFillModel);
+
+ maTypeModel.assignUsed( pShapeType->getTypeModel() );
+ maTypeModel.maStrokeModel = aMergedStrokeModel;
+ maTypeModel.maFillModel = aMergedFillModel;
+ }
+ else {
+ // Temporary fix, shapetype not found if referenced from different substream
+ // FIXME: extend scope of ShapeContainer to store all shapetypes from the document
+ static constexpr OUString sShapeTypePrefix = u"shapetype_"_ustr;
+ OUString tmp;
+ if (aType.startsWith(sShapeTypePrefix)) {
+ maTypeModel.moShapeType = o3tl::toInt32(aType.subView(sShapeTypePrefix.getLength()));
+ }
+ else if (aType.startsWith("_x0000_t", &tmp)) {
+ maTypeModel.moShapeType = tmp.toInt32();
+ }
+ }
+}
+
+OUString ShapeBase::getShapeName() const
+{
+ if( !maTypeModel.maShapeName.isEmpty() )
+ return maTypeModel.maShapeName;
+
+ OUString aBaseName = mrDrawing.getShapeBaseName( *this );
+ if( !aBaseName.isEmpty() )
+ {
+ sal_Int32 nShapeIdx = mrDrawing.getLocalShapeIndex( getShapeId() );
+ if( nShapeIdx > 0 )
+ return aBaseName + OUStringChar(' ') + OUString::number( nShapeIdx );
+ }
+
+ return OUString();
+}
+
+const ShapeType* ShapeBase::getChildTypeById( const OUString& ) const
+{
+ return nullptr;
+}
+
+const ShapeBase* ShapeBase::getChildById( const OUString& ) const
+{
+ return nullptr;
+}
+
+Reference< XShape > ShapeBase::convertAndInsert( const Reference< XShapes >& rxShapes, const ShapeParentAnchor* pParentAnchor ) const
+{
+ Reference< XShape > xShape;
+ if( mrDrawing.isShapeSupported( *this ) )
+ {
+ /* Calculate shape rectangle. Applications may do something special
+ according to some imported shape client data (e.g. Excel cell anchor). */
+ awt::Rectangle aShapeRect = calcShapeRectangle( pParentAnchor );
+
+ if( ((aShapeRect.Width > 0) || (aShapeRect.Height > 0)) && rxShapes.is() )
+ {
+ xShape = implConvertAndInsert( rxShapes, aShapeRect );
+ if( xShape.is() )
+ {
+ // set imported or generated shape name (not supported by form controls)
+ PropertySet aShapeProp( xShape );
+ if( aShapeProp.hasProperty( PROP_Name ) )
+ aShapeProp.setProperty( PROP_Name, getShapeName() );
+ uno::Reference< lang::XServiceInfo > xSInfo( xShape, uno::UNO_QUERY_THROW );
+
+ OUString sLinkChainName = getTypeModel().maLegacyId;
+ sal_Int32 id = 0;
+ sal_Int32 idPos = sLinkChainName.indexOf("_x");
+ sal_Int32 seq = 0;
+ if (idPos >= 0)
+ {
+ sal_Int32 seqPos = sLinkChainName.indexOf("_s",idPos);
+ if (idPos < seqPos)
+ {
+ auto idPosEnd = idPos+2;
+ id = o3tl::toInt32(sLinkChainName.subView(idPosEnd, seqPos - idPosEnd));
+ seq = o3tl::toInt32(sLinkChainName.subView(seqPos+2));
+ }
+ }
+
+ OUString s_mso_next_textbox;
+ if( getTextBox() )
+ s_mso_next_textbox = getTextBox()->msNextTextbox;
+ if( s_mso_next_textbox.startsWith("#") )
+ s_mso_next_textbox = s_mso_next_textbox.copy(1);
+
+ if (xSInfo->supportsService("com.sun.star.text.TextFrame"))
+ {
+ uno::Reference<beans::XPropertySet> propertySet (xShape, uno::UNO_QUERY);
+ uno::Any aAny = propertySet->getPropertyValue("FrameInteropGrabBag");
+ auto aGrabBag = comphelper::sequenceToContainer< std::vector<beans::PropertyValue> >(aAny.get< uno::Sequence<beans::PropertyValue> >());
+
+ aGrabBag.push_back(comphelper::makePropertyValue("VML-Z-ORDER", maTypeModel.maZIndex.toInt32()));
+
+ if( !s_mso_next_textbox.isEmpty() )
+ aGrabBag.push_back(comphelper::makePropertyValue("mso-next-textbox", s_mso_next_textbox));
+
+ if( !sLinkChainName.isEmpty() )
+ {
+ aGrabBag.push_back(comphelper::makePropertyValue("TxbxHasLink", true));
+ aGrabBag.push_back(comphelper::makePropertyValue("Txbx-Id", id));
+ aGrabBag.push_back(comphelper::makePropertyValue("Txbx-Seq", seq));
+ aGrabBag.push_back(comphelper::makePropertyValue("LinkChainName", sLinkChainName));
+ }
+
+ if(!maTypeModel.maRotation.isEmpty())
+ aGrabBag.push_back(comphelper::makePropertyValue("mso-rotation-angle", ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));
+ propertySet->setPropertyValue("FrameInteropGrabBag", uno::Any(comphelper::containerToSequence(aGrabBag)));
+ sal_Int32 backColorTransparency = 0;
+ propertySet->getPropertyValue("BackColorTransparency")
+ >>= backColorTransparency;
+ if (propertySet->getPropertyValue("FillStyle") == FillStyle_NONE &&
+ backColorTransparency == 100)
+ {
+ // If there is no fill, the Word default is 100% transparency.
+ propertySet->setPropertyValue("FillTransparence", Any(sal_Int16(100)));
+ }
+ }
+ else
+ {
+ if( maTypeModel.maZIndex.toInt32() )
+ {
+ uno::Sequence<beans::PropertyValue> aGrabBag;
+ uno::Reference<beans::XPropertySet> propertySet (xShape, uno::UNO_QUERY);
+ propertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
+ sal_Int32 length;
+
+ length = aGrabBag.getLength();
+ aGrabBag.realloc( length+1 );
+ auto pGrabBag = aGrabBag.getArray();
+ pGrabBag[length].Name = "VML-Z-ORDER";
+ pGrabBag[length].Value <<= maTypeModel.maZIndex.toInt32();
+
+ if( !s_mso_next_textbox.isEmpty() )
+ {
+ length = aGrabBag.getLength();
+ aGrabBag.realloc( length+1 );
+ pGrabBag = aGrabBag.getArray();
+ pGrabBag[length].Name = "mso-next-textbox";
+ pGrabBag[length].Value <<= s_mso_next_textbox;
+ }
+
+ if( !sLinkChainName.isEmpty() )
+ {
+ length = aGrabBag.getLength();
+ aGrabBag.realloc( length+4 );
+ pGrabBag = aGrabBag.getArray();
+ pGrabBag[length].Name = "TxbxHasLink";
+ pGrabBag[length].Value <<= true;
+ pGrabBag[length+1].Name = "Txbx-Id";
+ pGrabBag[length+1].Value <<= id;
+ pGrabBag[length+2].Name = "Txbx-Seq";
+ pGrabBag[length+2].Value <<= seq;
+ pGrabBag[length+3].Name = "LinkChainName";
+ pGrabBag[length+3].Value <<= sLinkChainName;
+ }
+ propertySet->setPropertyValue( "InteropGrabBag", uno::Any(aGrabBag) );
+ }
+ }
+ Reference< XControlShape > xControlShape( xShape, uno::UNO_QUERY );
+ if ( xControlShape.is() && !getTypeModel().mbVisible )
+ {
+ PropertySet aControlShapeProp( xControlShape->getControl() );
+ aControlShapeProp.setProperty( PROP_EnableVisible, uno::Any( false ) );
+ }
+
+ xShape = finalImplConvertAndInsert(xShape);
+ /* Notify the drawing that a new shape has been inserted. For
+ convenience, pass the rectangle that contains position and
+ size of the shape. */
+ bool bGroupChild = pParentAnchor != nullptr;
+ mrDrawing.notifyXShapeInserted( xShape, aShapeRect, *this, bGroupChild );
+ }
+ }
+ else
+ SAL_WARN("oox", "not converting shape, as calculated rectangle is empty");
+ }
+ return xShape;
+}
+
+awt::Rectangle ShapeBase::getShapeRectangle() const
+{
+ /* Calculate shape rectangle. Applications may do something special
+ according to some imported shape client data (e.g. Excel cell anchor). */
+ return calcShapeRectangle(nullptr);
+}
+
+void ShapeBase::setContainer(ShapeContainer* pContainer) { mpContainer = pContainer; }
+
+ShapeContainer* ShapeBase::getContainer() const { return mpContainer; }
+
+// protected ------------------------------------------------------------------
+
+awt::Rectangle ShapeBase::calcShapeRectangle( const ShapeParentAnchor* pParentAnchor ) const
+{
+ /* Calculate shape rectangle. Applications may do something special
+ according to some imported shape client data (e.g. Excel cell anchor). */
+ awt::Rectangle aShapeRect;
+ const ClientData* pClientData = getClientData();
+ if( !pClientData || !mrDrawing.convertClientAnchor( aShapeRect, pClientData->maAnchor ) )
+ aShapeRect = getRectangle( pParentAnchor );
+ return aShapeRect;
+}
+
+::oox::drawingml::ShapePropertyMap ShapeBase::makeShapePropertyMap() const
+{
+ ::oox::drawingml::ShapePropertyMap aPropMap( mrDrawing.getFilter().getModelObjectHelper() );
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ maTypeModel.maStrokeModel.pushToPropMap( aPropMap, rGraphicHelper );
+ maTypeModel.maFillModel.pushToPropMap( aPropMap, rGraphicHelper );
+ return aPropMap;
+}
+
+void ShapeBase::convertShapeProperties( const Reference< XShape >& rxShape ) const
+{
+ ::oox::drawingml::ShapePropertyMap aPropMap(makeShapePropertyMap());
+
+ uno::Reference<lang::XServiceInfo> xSInfo(rxShape, uno::UNO_QUERY_THROW);
+ if (xSInfo->supportsService("com.sun.star.text.TextFrame"))
+ {
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ // Any other service supporting the ShadowFormat property?
+ maTypeModel.maShadowModel.pushToPropMap(aPropMap, rGraphicHelper);
+ // TextFrames have BackColor, not FillColor
+ if (aPropMap.hasProperty(PROP_FillColor))
+ {
+ aPropMap.setAnyProperty(PROP_BackColor, aPropMap.getProperty(PROP_FillColor));
+ aPropMap.erase(PROP_FillColor);
+ }
+ // TextFrames have BackColorTransparency, not FillTransparence
+ if (aPropMap.hasProperty(PROP_FillTransparence))
+ {
+ aPropMap.setAnyProperty(PROP_BackColorTransparency, aPropMap.getProperty(PROP_FillTransparence));
+ aPropMap.erase(PROP_FillTransparence);
+ }
+ // And no LineColor property; individual borders can have colors and widths
+ std::optional<sal_Int32> oLineWidth;
+ if (maTypeModel.maStrokeModel.moWeight.has_value())
+ oLineWidth = ConversionHelper::decodeMeasureToHmm(
+ rGraphicHelper, maTypeModel.maStrokeModel.moWeight.value(), 0, false, false);
+ if (aPropMap.hasProperty(PROP_LineColor))
+ {
+ uno::Reference<beans::XPropertySet> xPropertySet(rxShape, uno::UNO_QUERY);
+ static const sal_Int32 aBorders[] = {
+ PROP_TopBorder, PROP_LeftBorder, PROP_BottomBorder, PROP_RightBorder
+ };
+ for (sal_Int32 nBorder : aBorders)
+ {
+ table::BorderLine2 aBorderLine = xPropertySet->getPropertyValue(PropertyMap::getPropertyName(nBorder)).get<table::BorderLine2>();
+ aBorderLine.Color = aPropMap.getProperty(PROP_LineColor).get<sal_Int32>();
+ if (oLineWidth)
+ aBorderLine.LineWidth = *oLineWidth;
+ aPropMap.setProperty(nBorder, aBorderLine);
+ }
+ aPropMap.erase(PROP_LineColor);
+ }
+ }
+ else if (xSInfo->supportsService("com.sun.star.drawing.CustomShape"))
+ {
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ maTypeModel.maTextpathModel.pushToPropMap(aPropMap, rxShape, rGraphicHelper);
+ }
+
+ PropertySet( rxShape ).setProperties( aPropMap );
+}
+
+SimpleShape::SimpleShape( Drawing& rDrawing, OUString aService ) :
+ ShapeBase( rDrawing ),
+ maService(std::move( aService ))
+{
+}
+
+static void lcl_setSurround(PropertySet& rPropSet, const ShapeTypeModel& rTypeModel, const GraphicHelper& rGraphicHelper)
+{
+ OUString aWrapType = rTypeModel.moWrapType.value_or("");
+
+ // Extreme negative top margin? Then the shape will end up at the top of the page, it's pointless to perform any kind of wrapping.
+ sal_Int32 nMarginTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, rTypeModel.maMarginTop, 0, false, true);
+ if (nMarginTop < -35277) // Less than 1000 points.
+ aWrapType.clear();
+
+ css::text::WrapTextMode nSurround = css::text::WrapTextMode_THROUGH;
+ if ( aWrapType == "square" || aWrapType == "tight" ||
+ aWrapType == "through" )
+ {
+ nSurround = css::text::WrapTextMode_PARALLEL;
+ if ( !rTypeModel.moWrapSide.has_value() )
+ ; // leave as PARALLEL
+ else if ( rTypeModel.moWrapSide.value() == "left" )
+ nSurround = css::text::WrapTextMode_LEFT;
+ else if ( rTypeModel.moWrapSide.value() == "right" )
+ nSurround = css::text::WrapTextMode_RIGHT;
+ }
+ else if ( aWrapType == "topAndBottom" )
+ nSurround = css::text::WrapTextMode_NONE;
+
+ rPropSet.setProperty(PROP_Surround, static_cast<sal_Int32>(nSurround));
+ rPropSet.setProperty(PROP_SurroundContour, aWrapType == "tight");
+}
+
+static void lcl_SetAnchorType(PropertySet& rPropSet, const ShapeTypeModel& rTypeModel, const GraphicHelper& rGraphicHelper)
+{
+ if ( rTypeModel.maPosition == "absolute" )
+ {
+ // Word supports as-character (inline) and at-character only, absolute can't be inline.
+ rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AT_CHARACTER);
+ // anchor is set after insertion, so reset to NONE
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::NONE));
+
+ if ( rTypeModel.maPositionVerticalRelative == "page" )
+ {
+ rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_FRAME);
+ }
+ else if ( rTypeModel.maPositionVerticalRelative == "margin" )
+ {
+ rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA);
+ }
+ else if (rTypeModel.maPositionVerticalRelative == "top-margin-area")
+ {
+ rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA_TOP);
+ }
+ else if (rTypeModel.maPositionVerticalRelative == "bottom-margin-area")
+ {
+ rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::PAGE_PRINT_AREA_BOTTOM);
+ }
+ else
+ {
+ rPropSet.setProperty(PROP_VertOrientRelation, text::RelOrientation::FRAME);
+ }
+ }
+ else if( rTypeModel.maPosition == "relative" )
+ { // I'm not very sure this is correct either.
+ rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AT_PARAGRAPH);
+ // anchor is set after insertion, so reset to NONE
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::NONE));
+ }
+ else // static (is the default) means anchored inline
+ {
+ rPropSet.setProperty(PROP_AnchorType, text::TextContentAnchorType_AS_CHARACTER);
+ // Use top orientation, this one seems similar to what MSO uses as inline
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
+ }
+
+ if ( rTypeModel.maPositionHorizontal == "center" )
+ rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::CENTER));
+ else if ( rTypeModel.maPositionHorizontal == "left" )
+ rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::LEFT));
+ else if ( rTypeModel.maPositionHorizontal == "right" )
+ rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::RIGHT));
+ else if ( rTypeModel.maPositionHorizontal == "inside" )
+ {
+ rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::LEFT));
+ rPropSet.setAnyProperty(PROP_PageToggle, Any(true));
+ }
+ else if ( rTypeModel.maPositionHorizontal == "outside" )
+ {
+ rPropSet.setAnyProperty(PROP_HoriOrient, Any(text::HoriOrientation::RIGHT));
+ rPropSet.setAnyProperty(PROP_PageToggle, Any(true));
+ }
+
+ if ( rTypeModel.maPositionHorizontalRelative == "page" )
+ rPropSet.setAnyProperty(PROP_HoriOrientRelation, Any(text::RelOrientation::PAGE_FRAME));
+ else if ( rTypeModel.maPositionHorizontalRelative == "margin" )
+ rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_PRINT_AREA);
+ else if (rTypeModel.maPositionHorizontalRelative == "right-margin-area" ||
+ rTypeModel.maPositionHorizontalRelative == "inner-margin-area")
+ rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_RIGHT);
+ else if (rTypeModel.maPositionHorizontalRelative == "left-margin-area" ||
+ rTypeModel.maPositionHorizontalRelative == "outer-margin-area")
+ rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::PAGE_LEFT);
+ else if ( rTypeModel.maPositionHorizontalRelative == "text" )
+ rPropSet.setProperty(PROP_HoriOrientRelation, text::RelOrientation::FRAME);
+
+ if ( rTypeModel.maPositionVertical == "center" )
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::CENTER));
+ else if ( rTypeModel.maPositionVertical == "top" )
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
+ else if ( rTypeModel.maPositionVertical == "bottom" )
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::BOTTOM));
+ else if ( rTypeModel.maPositionVertical == "inside" )
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::TOP));
+ else if ( rTypeModel.maPositionVertical == "outside" )
+ rPropSet.setAnyProperty(PROP_VertOrient, Any(text::VertOrientation::BOTTOM));
+
+ lcl_setSurround( rPropSet, rTypeModel, rGraphicHelper );
+}
+
+Reference< XShape > SimpleShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ awt::Rectangle aShapeRect(rShapeRect);
+ std::optional<Degree100> oRotation;
+ bool bFlipX = false, bFlipY = false;
+ // tdf#137765: skip this rotation for line shapes
+ if (!maTypeModel.maRotation.isEmpty() && maService != "com.sun.star.drawing.LineShape")
+ oRotation = ConversionHelper::decodeRotation(maTypeModel.maRotation);
+ if (!maTypeModel.maFlip.isEmpty())
+ {
+ if (maTypeModel.maFlip.startsWith("x"))
+ {
+ bFlipX = true;
+ }
+ if (maTypeModel.maFlip.endsWith("y"))
+ {
+ bFlipY = true;
+ }
+ }
+
+ Reference< XShape > xShape = mrDrawing.createAndInsertXShape( maService, rxShapes, aShapeRect );
+ SdrObject* pShape = SdrObject::getSdrObjectFromXShape(xShape);
+ if( pShape && getShapeType() >= 0 )
+ {
+ //The resize autoshape to fit text attr of FontWork/Word-Art should always be false
+ //for the fallback geometry.
+ sal_Int32 nType = getShapeType();
+ if((mso_sptTextSimple <= nType && nType <= mso_sptTextOnRing)
+ || (mso_sptTextPlainText <= nType && nType <= mso_sptTextCanDown))
+ {
+ pShape->SetMergedItem(makeSdrTextAutoGrowHeightItem(false));
+ pShape->SetMergedItem(makeSdrTextAutoGrowWidthItem(false));
+ }
+ }
+ convertShapeProperties( xShape );
+
+ // Handle left/right/top/bottom wrap distance.
+ // Default value of mso-wrap-distance-left/right is supposed to be 0 (see
+ // 19.1.2.19 of the VML spec), but Word implements a non-zero value.
+ // [MS-ODRAW] says the below default value in 2.3.4.9.
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ OUString aWrapDistanceLeft = OUString::number(0x0001BE7C);
+ if (!maTypeModel.maWrapDistanceLeft.isEmpty())
+ aWrapDistanceLeft = maTypeModel.maWrapDistanceLeft;
+ sal_Int32 nWrapDistanceLeft = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aWrapDistanceLeft, 0, true, false);
+ PropertySet(xShape).setAnyProperty(PROP_LeftMargin, uno::Any(nWrapDistanceLeft));
+ OUString aWrapDistanceRight = OUString::number(0x0001BE7C);
+ if (!maTypeModel.maWrapDistanceRight.isEmpty())
+ aWrapDistanceRight = maTypeModel.maWrapDistanceRight;
+ sal_Int32 nWrapDistanceRight = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, aWrapDistanceRight, 0, true, false);
+ PropertySet(xShape).setAnyProperty(PROP_RightMargin, uno::Any(nWrapDistanceRight));
+ sal_Int32 nWrapDistanceTop = 0;
+ if (!maTypeModel.maWrapDistanceTop.isEmpty())
+ nWrapDistanceTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceTop, 0, false, true);
+ PropertySet(xShape).setAnyProperty(PROP_TopMargin, uno::Any(nWrapDistanceTop));
+ sal_Int32 nWrapDistanceBottom = 0;
+ if (!maTypeModel.maWrapDistanceBottom.isEmpty())
+ nWrapDistanceBottom = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceBottom, 0, false, true);
+ PropertySet(xShape).setAnyProperty(PROP_BottomMargin, uno::Any(nWrapDistanceBottom));
+
+ if ( maService == "com.sun.star.text.TextFrame" )
+ {
+ PropertySet( xShape ).setAnyProperty( PROP_FrameIsAutomaticHeight, Any( maTypeModel.mbAutoHeight ) );
+ PropertySet( xShape ).setAnyProperty( PROP_SizeType, Any( maTypeModel.mbAutoHeight ? SizeType::MIN : SizeType::FIX ) );
+ if( getTextBox()->borderDistanceSet )
+ {
+ PropertySet( xShape ).setAnyProperty( PROP_LeftBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceLeft )));
+ PropertySet( xShape ).setAnyProperty( PROP_TopBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceTop )));
+ PropertySet( xShape ).setAnyProperty( PROP_RightBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceRight )));
+ PropertySet( xShape ).setAnyProperty( PROP_BottomBorderDistance, Any( sal_Int32( getTextBox()->borderDistanceBottom )));
+ }
+
+ sal_Int16 nWritingMode = text::WritingMode2::LR_TB;
+ if (getTextBox()->maLayoutFlow == "vertical" && maTypeModel.maLayoutFlowAlt.isEmpty())
+ {
+ nWritingMode = text::WritingMode2::TB_RL;
+ }
+ else if (maTypeModel.maLayoutFlowAlt == "bottom-to-top")
+ {
+ nWritingMode = text::WritingMode2::BT_LR;
+ }
+ if (nWritingMode != text::WritingMode2::LR_TB)
+ {
+ PropertySet(xShape).setAnyProperty(PROP_WritingMode, uno::Any(nWritingMode));
+ }
+ // tdf#123626
+ if (!maShapeModel.maHyperlink.isEmpty())
+ PropertySet(xShape).setAnyProperty(PROP_HyperLinkURL, Any(maShapeModel.maHyperlink));
+ }
+ else
+ {
+ // FIXME Setting the relative width/height only for everything but text frames as
+ // TextFrames already have relative width/height feature... but currently not working
+ // in the way we need.
+
+ // Set the relative width / height if any
+ if ( !maTypeModel.maWidthPercent.isEmpty( ) )
+ {
+ // Only page-relative width is supported ATM
+ if ( maTypeModel.maWidthRelative.isEmpty() || maTypeModel.maWidthRelative == "page" )
+ {
+ sal_Int16 nWidth = maTypeModel.maWidthPercent.toInt32() / 10;
+ // Only apply if nWidth != 0
+ if ( nWidth )
+ PropertySet( xShape ).setAnyProperty(PROP_RelativeWidth, Any( nWidth ) );
+ }
+ }
+ if ( !maTypeModel.maHeightPercent.isEmpty( ) )
+ {
+ // Only page-relative height is supported ATM
+ if ( maTypeModel.maHeightRelative.isEmpty() || maTypeModel.maHeightRelative == "page" )
+ {
+ sal_Int16 nHeight = maTypeModel.maHeightPercent.toInt32() / 10;
+ // Only apply if nHeight != 0
+ if ( nHeight )
+ PropertySet( xShape ).setAnyProperty(PROP_RelativeHeight, Any( nHeight ) );
+ }
+ }
+
+ // drawinglayer default is center, MSO default is top.
+ drawing::TextVerticalAdjust eTextVerticalAdjust = drawing::TextVerticalAdjust_TOP;
+ if (maTypeModel.maVTextAnchor == "middle")
+ eTextVerticalAdjust = drawing::TextVerticalAdjust_CENTER;
+ else if (maTypeModel.maVTextAnchor == "bottom")
+ eTextVerticalAdjust = drawing::TextVerticalAdjust_BOTTOM;
+ PropertySet(xShape).setAnyProperty(PROP_TextVerticalAdjust, Any(eTextVerticalAdjust));
+
+ // tdf#97618
+ if(!maTypeModel.maWrapStyle.isEmpty())
+ PropertySet(xShape).setAnyProperty(PROP_TextWordWrap, Any(maTypeModel.maWrapStyle == "square"));
+
+ // tdf#123626
+ if (!maShapeModel.maHyperlink.isEmpty())
+ PropertySet(xShape).setAnyProperty(PROP_Hyperlink, Any(maShapeModel.maHyperlink));
+
+ PropertySet(xShape).setAnyProperty(PROP_TextAutoGrowHeight,
+ Any(maTypeModel.mbAutoHeight));
+
+ if (getTextBox())
+ {
+ getTextBox()->convert(xShape);
+ if (getTextBox()->borderDistanceSet)
+ {
+ awt::Size aSize = xShape->getSize();
+ PropertySet(xShape).setAnyProperty(PROP_TextLeftDistance, Any(sal_Int32(getTextBox()->borderDistanceLeft)));
+ PropertySet(xShape).setAnyProperty(PROP_TextUpperDistance, Any(sal_Int32(getTextBox()->borderDistanceTop)));
+ PropertySet(xShape).setAnyProperty(PROP_TextRightDistance, Any(sal_Int32(getTextBox()->borderDistanceRight)));
+ PropertySet(xShape).setAnyProperty(PROP_TextLowerDistance, Any(sal_Int32(getTextBox()->borderDistanceBottom)));
+ xShape->setSize(aSize);
+ }
+ }
+ }
+
+ // Import Legacy Fragments (if any)
+ if( xShape.is() && !maShapeModel.maLegacyDiagramPath.isEmpty() )
+ {
+ Reference< XInputStream > xInStrm( mrDrawing.getFilter().openInputStream( maShapeModel.maLegacyDiagramPath ), UNO_SET_THROW );
+ if( xInStrm.is() )
+ PropertySet( xShape ).setProperty( PROP_LegacyFragment, xInStrm );
+ }
+
+ PropertySet aPropertySet(xShape);
+ if (xShape.is())
+ {
+ if (oRotation)
+ {
+ aPropertySet.setAnyProperty(PROP_RotateAngle, Any((*oRotation).get()));
+ uno::Reference<lang::XServiceInfo> xServiceInfo(rxShapes, uno::UNO_QUERY);
+ if (!xServiceInfo->supportsService("com.sun.star.drawing.GroupShape"))
+ {
+ // If rotation is used, simple setPosition() is not enough.
+ aPropertySet.setAnyProperty(PROP_HoriOrientPosition, Any(aShapeRect.X));
+ aPropertySet.setAnyProperty(PROP_VertOrientPosition, Any(aShapeRect.Y));
+ }
+ }
+
+ // custom shape geometry attributes
+ std::vector<css::beans::PropertyValue> aPropVec;
+
+ // When flip has 'x' or 'y', the associated ShapeRect will be changed but direction change doesn't occur.
+ // It might occur internally in SdrObject of "sw" module, not here.
+ // The associated properties "PROP_MirroredX" and "PROP_MirroredY" have to be set here so that direction change will occur internally.
+ if (bFlipX)
+ aPropVec.push_back(comphelper::makePropertyValue("MirroredX", true));
+ if (bFlipY)
+ aPropVec.push_back(comphelper::makePropertyValue("MirroredY", true));
+
+ if (!maTypeModel.maAdjustments.isEmpty())
+ {
+ std::vector<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentValues;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ std::u16string_view aToken = o3tl::getToken(maTypeModel.maAdjustments, 0, ',', nIndex);
+ drawing::EnhancedCustomShapeAdjustmentValue aAdjustmentValue;
+ if (aToken.empty())
+ aAdjustmentValue.State = css::beans::PropertyState::PropertyState_DEFAULT_VALUE;
+ else
+ aAdjustmentValue.Value <<= o3tl::toInt32(aToken);
+ aAdjustmentValues.push_back(aAdjustmentValue);
+ } while (nIndex >= 0);
+
+ css::beans::PropertyValue aProp;
+ aProp.Name = "AdjustmentValues";
+ aProp.Value <<= comphelper::containerToSequence(aAdjustmentValues);
+ aPropVec.push_back(aProp);
+ }
+
+ if (!aPropVec.empty())
+ aPropertySet.setAnyProperty(PROP_CustomShapeGeometry, Any(comphelper::containerToSequence(aPropVec)));
+ }
+
+ lcl_SetAnchorType(aPropertySet, maTypeModel, rGraphicHelper );
+
+ return xShape;
+}
+
+Reference<XShape> SimpleShape::finalImplConvertAndInsert(const css::uno::Reference<css::drawing::XShape>& rxShape) const
+{
+ // tdf#41466 This setting must be done here, because the position of textbox will be set as an
+ // effect of the PROP_TextBox property setting, and if we do this setting earlier (setting of
+ // properties of position and size) then the position of textbox will be set with wrong data.
+ // TODO: TextShape is set if we have rect shape in group; we should use the shape-with-textbox
+ // mechanism to handle this situation
+ if (getTextBox() && maService != "com.sun.star.text.TextFrame" && maService != "com.sun.star.drawing.TextShape"
+ && !maShapeModel.mbInGroup)
+ {
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ const auto& nLeft = ConversionHelper::decodeMeasureToHmm(
+ rGraphicHelper, maTypeModel.maMarginLeft, 0, true, true);
+ PropertySet aPropertySet(rxShape);
+ aPropertySet.setProperty(PROP_HoriOrientPosition, nLeft);
+ const auto& nTop = ConversionHelper::decodeMeasureToHmm(
+ rGraphicHelper, maTypeModel.maMarginTop, 0, true, true);
+ aPropertySet.setProperty(PROP_VertOrientPosition, nTop);
+ aPropertySet.setProperty(PROP_TextBox, true);
+
+ // And these properties must be set after textbox creation (set PROP_Textbox property).
+ // Note: if you set a new property then you have to handle it in the proper
+ // SwTextBoxHelper::syncProperty function.
+ if (maTypeModel.maLayoutFlowAlt == "bottom-to-top")
+ aPropertySet.setAnyProperty(PROP_TextWritingMode, uno::Any(text::WritingMode2::BT_LR));
+ }
+ return ShapeBase::finalImplConvertAndInsert(rxShape);
+}
+Reference< XShape > SimpleShape::createEmbeddedPictureObject( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect, OUString const & rGraphicPath ) const
+{
+ Reference<XGraphic> xGraphic = mrDrawing.getFilter().getGraphicHelper().importEmbeddedGraphic(rGraphicPath);
+ return SimpleShape::createPictureObject(rxShapes, rShapeRect, xGraphic);
+}
+
+Reference< XShape > SimpleShape::createPictureObject(const Reference< XShapes >& rxShapes,
+ const awt::Rectangle& rShapeRect,
+ uno::Reference<graphic::XGraphic> const & rxGraphic) const
+{
+ Reference< XShape > xShape = mrDrawing.createAndInsertXShape( "com.sun.star.drawing.GraphicObjectShape", rxShapes, rShapeRect );
+ if( xShape.is() )
+ {
+ PropertySet aPropSet(xShape);
+ if (rxGraphic.is())
+ {
+ aPropSet.setProperty(PROP_Graphic, rxGraphic);
+ }
+ uno::Reference< lang::XServiceInfo > xServiceInfo(rxShapes, uno::UNO_QUERY);
+ // If the shape has an absolute position, set the properties accordingly, unless we're inside a group shape.
+ if ( maTypeModel.maPosition == "absolute" && !xServiceInfo->supportsService("com.sun.star.drawing.GroupShape"))
+ {
+ aPropSet.setProperty(PROP_HoriOrientPosition, rShapeRect.X);
+ aPropSet.setProperty(PROP_VertOrientPosition, rShapeRect.Y);
+ aPropSet.setProperty(PROP_Opaque, false);
+ }
+ // fdo#70457: preserve rotation information
+ if ( !maTypeModel.maRotation.isEmpty() )
+ aPropSet.setAnyProperty(PROP_RotateAngle, Any(ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));
+
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ lcl_SetAnchorType(aPropSet, maTypeModel, rGraphicHelper);
+
+ const sal_Int32 nWrapDistanceLeft = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceLeft, 0, true, true);
+ const sal_Int32 nWrapDistanceRight = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceRight, 0, true, true);
+ const sal_Int32 nWrapDistanceTop = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceTop, 0, false, true);
+ const sal_Int32 nWrapDistanceBottom = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, maTypeModel.maWrapDistanceBottom, 0, false, true);
+ aPropSet.setProperty(PROP_LeftMargin, uno::Any(nWrapDistanceLeft));
+ aPropSet.setProperty(PROP_RightMargin, uno::Any(nWrapDistanceRight));
+ aPropSet.setProperty(PROP_TopMargin, uno::Any(nWrapDistanceTop));
+ aPropSet.setProperty(PROP_BottomMargin, uno::Any(nWrapDistanceBottom));
+
+ if (maTypeModel.moCropBottom.has_value() || maTypeModel.moCropLeft.has_value() || maTypeModel.moCropRight.has_value() || maTypeModel.moCropTop.has_value())
+ {
+ text::GraphicCrop aGraphicCrop;
+ awt::Size aOriginalSize = rGraphicHelper.getOriginalSize(rxGraphic);
+
+ if (maTypeModel.moCropBottom.has_value())
+ aGraphicCrop.Bottom = lclConvertCrop(maTypeModel.moCropBottom.value(), aOriginalSize.Height);
+ if (maTypeModel.moCropLeft.has_value())
+ aGraphicCrop.Left = lclConvertCrop(maTypeModel.moCropLeft.value(), aOriginalSize.Width);
+ if (maTypeModel.moCropRight.has_value())
+ aGraphicCrop.Right = lclConvertCrop(maTypeModel.moCropRight.value(), aOriginalSize.Width);
+ if (maTypeModel.moCropTop.has_value())
+ aGraphicCrop.Top = lclConvertCrop(maTypeModel.moCropTop.value(), aOriginalSize.Height);
+
+ aPropSet.setProperty(PROP_GraphicCrop, aGraphicCrop);
+ }
+
+ if (maTypeModel.mnGain == -70 && maTypeModel.mnBlacklevel == 70)
+ {
+ // Map MSO 'washout' to our watermark colormode.
+ aPropSet.setProperty(PROP_GraphicColorMode, uno::Any(drawing::ColorMode_WATERMARK));
+ }
+ }
+ return xShape;
+}
+
+RectangleShape::RectangleShape( Drawing& rDrawing ) :
+ SimpleShape( rDrawing, "com.sun.star.drawing.RectangleShape" )
+{
+}
+
+Reference<XShape> RectangleShape::implConvertAndInsert(const Reference<XShapes>& rxShapes, const awt::Rectangle& rShapeRect) const
+{
+ OUString aGraphicPath = getGraphicPath();
+
+ // try to create a picture object
+ if(!aGraphicPath.isEmpty())
+ return SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);
+
+ // default: try to create a rectangle shape
+ Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);
+ OUString sArcsize = maTypeModel.maArcsize;
+ if ( !sArcsize.isEmpty( ) )
+ {
+ sal_Unicode cLastChar = sArcsize[sArcsize.getLength() - 1];
+ sal_Int32 nValue = o3tl::toInt32(sArcsize.subView( 0, sArcsize.getLength() - 1 ));
+ // Get the smallest half-side
+ double size = std::min( rShapeRect.Height, rShapeRect.Width ) / 2.0;
+ sal_Int32 nRadius = 0;
+ if ( cLastChar == 'f' )
+ nRadius = size * nValue / 65536;
+ else if ( cLastChar == '%' )
+ nRadius = size * nValue / 100;
+ PropertySet( xShape ).setAnyProperty( PROP_CornerRadius, Any( nRadius ) );
+ }
+ return xShape;
+}
+
+EllipseShape::EllipseShape( Drawing& rDrawing ) :
+ SimpleShape( rDrawing, "com.sun.star.drawing.EllipseShape" )
+{
+}
+
+PolyLineShape::PolyLineShape( Drawing& rDrawing ) :
+ SimpleShape( rDrawing, "com.sun.star.drawing.PolyLineShape" )
+{
+}
+
+Reference< XShape > PolyLineShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ ::std::vector<awt::Point> aAbsPoints;
+ awt::Rectangle aCoordSys = getCoordSystem();
+ if (!maShapeModel.maPoints.empty() && (aCoordSys.Width > 0) && (aCoordSys.Height > 0))
+ {
+ for (auto const& point : maShapeModel.maPoints)
+ aAbsPoints.push_back(lclGetAbsPoint(point, rShapeRect, aCoordSys));
+ // A polyline cannot be filled but only a polygon. We treat first point == last point as
+ // indicator for being closed. In that case we force to type PolyPolygonShape.
+ if (aAbsPoints.size() > 2 && aAbsPoints.front().X == aAbsPoints.back().X
+ && aAbsPoints.front().Y == aAbsPoints.back().Y)
+ {
+ const_cast<PolyLineShape*>(this)->setService("com.sun.star.drawing.PolyPolygonShape");
+ }
+ }
+
+ Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);
+
+ // polygon path
+
+ if (!aAbsPoints.empty())
+ {
+ PointSequenceSequence aPointSeq{ comphelper::containerToSequence( aAbsPoints ) };
+ PropertySet aPropSet( xShape );
+ aPropSet.setProperty( PROP_PolyPolygon, aPointSeq );
+ }
+ return xShape;
+}
+
+namespace
+{
+ void doMirrorX(SdrObject* pShape)
+ {
+ Point aCenter(pShape->GetSnapRect().Center());
+ Point aPoint2(aCenter);
+ aPoint2.setY(aPoint2.getY() + 1);
+ pShape->NbcMirror(aCenter, aPoint2);
+ }
+
+ void doMirrorY(SdrObject* pShape)
+ {
+ Point aCenter(pShape->GetSnapRect().Center());
+ Point aPoint2(aCenter);
+ aPoint2.setX(aPoint2.getX() + 1);
+ pShape->NbcMirror(aCenter, aPoint2);
+ }
+
+ void handleMirroring(const ShapeTypeModel& rTypeModel, const Reference<XShape>& rxShape)
+ {
+ if (!rTypeModel.maFlip.isEmpty())
+ {
+ if (SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rxShape))
+ {
+ if (rTypeModel.maFlip.startsWith("x"))
+ doMirrorX(pShape);
+ if (rTypeModel.maFlip.endsWith("y"))
+ doMirrorY(pShape);
+ }
+ }
+ }
+
+ void handleRotation(const ShapeTypeModel& rTypeModel, const Reference<XShape>& rxShape)
+ {
+ if (!rTypeModel.maRotation.isEmpty())
+ {
+ if (SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rxShape))
+ {
+ // The needed factor -1 for opposite direction and factor 100 for Degree100 is
+ // contained in method decodeRotation().
+ Degree100 nAngle(ConversionHelper::decodeRotation(rTypeModel.maRotation));
+ pShape->NbcRotate(pShape->GetSnapRect().Center(), nAngle);
+ }
+ }
+ }
+}
+
+LineShape::LineShape(Drawing& rDrawing)
+ : SimpleShape(rDrawing, "com.sun.star.drawing.LineShape")
+{
+}
+
+Reference<XShape> LineShape::implConvertAndInsert(const Reference<XShapes>& rxShapes, const awt::Rectangle& rShapeRect) const
+{
+ Reference<XShape> xShape = SimpleShape::implConvertAndInsert(rxShapes, rShapeRect);
+ // tdf#137765
+ handleRotation(maTypeModel, xShape);
+ // tdf#97517 tdf#137678
+ // The MirroredX and MirroredY properties (in the CustomShapeGeometry property) are not
+ // supported for the LineShape by UNO, so we have to make the mirroring here.
+ handleMirroring(maTypeModel, xShape);
+ return xShape;
+}
+
+awt::Rectangle LineShape::getAbsRectangle() const
+{
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ awt::Rectangle aShapeRect;
+ sal_Int32 nIndex = 0;
+
+ aShapeRect.X = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex), 0, true, true);
+ aShapeRect.Y = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex), 0, false, true);
+ nIndex = 0;
+ aShapeRect.Width = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex), 0, true, true) - aShapeRect.X;
+ aShapeRect.Height = ConversionHelper::decodeMeasureToHmm(rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex), 0, false, true) - aShapeRect.Y;
+ return aShapeRect;
+}
+
+awt::Rectangle LineShape::getRelRectangle() const
+{
+ awt::Rectangle aShapeRect;
+ sal_Int32 nIndex = 0;
+
+ aShapeRect.X = o3tl::toInt32(o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex));
+ aShapeRect.Y = o3tl::toInt32(o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex));
+ nIndex = 0;
+ aShapeRect.Width = o3tl::toInt32(o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex)) - aShapeRect.X;
+ aShapeRect.Height = o3tl::toInt32(o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex)) - aShapeRect.Y;
+ return aShapeRect;
+}
+
+BezierShape::BezierShape(Drawing& rDrawing)
+ : SimpleShape(rDrawing, "com.sun.star.drawing.OpenBezierShape")
+{
+}
+
+Reference< XShape > BezierShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ // If we have an 'x' in the last part of the path it means it is closed...
+ sal_Int32 nPos = maShapeModel.maVmlPath.lastIndexOf(',');
+ if ( nPos != -1 && maShapeModel.maVmlPath.indexOf('x', nPos) != -1 )
+ {
+ const_cast<BezierShape*>( this )->setService( "com.sun.star.drawing.ClosedBezierShape" );
+ }
+
+ awt::Rectangle aCoordSys = getCoordSystem();
+ PolyPolygonBezierCoords aBezierCoords;
+
+ if( (aCoordSys.Width > 0) && (aCoordSys.Height > 0) )
+ {
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+
+ // Bezier paths may consist of one or more sub-paths
+ typedef ::std::vector< ::std::vector< PolygonFlags > > FlagsList;
+ std::vector< ::std::vector< awt::Point > > aCoordLists;
+ FlagsList aFlagLists;
+
+ // Curve defined by to, from, control1 and control2 attributes
+ if ( maShapeModel.maVmlPath.isEmpty() )
+ {
+ aCoordLists.emplace_back( );
+ aFlagLists.emplace_back( );
+ sal_Int32 nIndex = 0;
+
+ // Start point
+ aCoordLists[ 0 ].emplace_back(
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex ), 0, true, true ),
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maFrom, 0, ',', nIndex ), 0, false, true ) );
+ // Control point 1
+ aCoordLists[ 0 ].emplace_back(
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl1, 0, ',', nIndex ), 0, true, true ),
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl1, 0, ',', nIndex ), 0, false, true ) );
+ // Control point 2
+ aCoordLists[ 0 ].emplace_back(
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl2, 0, ',', nIndex ), 0, true, true ),
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maControl2, 0, ',', nIndex ), 0, false, true ) );
+ // End point
+ aCoordLists[ 0 ].emplace_back(
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex ), 0, true, true ),
+ ConversionHelper::decodeMeasureToHmm( rGraphicHelper, o3tl::getToken(maShapeModel.maTo, 0, ',', nIndex ), 0, false, true ) );
+
+ // First and last points are normals, points 2 and 4 are controls
+ aFlagLists[ 0 ].resize( aCoordLists[ 0 ].size(), PolygonFlags_CONTROL );
+ aFlagLists[ 0 ][ 0 ] = PolygonFlags_NORMAL;
+ aFlagLists[ 0 ].back() = PolygonFlags_NORMAL;
+ }
+ // Curve defined by path attribute
+ else
+ {
+ // Parse VML path string and convert to absolute coordinates
+ ConversionHelper::decodeVmlPath( aCoordLists, aFlagLists, maShapeModel.maVmlPath );
+
+ for (auto & coordList : aCoordLists)
+ for (auto & point : coordList)
+ {
+ point = lclGetAbsPoint( point, rShapeRect, aCoordSys );
+ }
+ }
+
+ aBezierCoords.Coordinates.realloc( aCoordLists.size() );
+ auto pCoordinates = aBezierCoords.Coordinates.getArray();
+ for ( size_t i = 0; i < aCoordLists.size(); i++ )
+ pCoordinates[i] = comphelper::containerToSequence( aCoordLists[i] );
+
+ aBezierCoords.Flags.realloc( aFlagLists.size() );
+ auto pFlags = aBezierCoords.Flags.getArray();
+ for ( size_t i = 0; i < aFlagLists.size(); i++ )
+ pFlags[i] = comphelper::containerToSequence( aFlagLists[i] );
+
+ if( !aCoordLists.front().empty() && !aCoordLists.back().empty()
+ && aCoordLists.front().front().X == aCoordLists.back().back().X
+ && aCoordLists.front().front().Y == aCoordLists.back().back().Y )
+ { // HACK: If the shape is in fact closed, which can be found out only when the path is known,
+ // force to closed bezier shape (otherwise e.g. fill won't work).
+ const_cast< BezierShape* >( this )->setService( "com.sun.star.drawing.ClosedBezierShape" );
+ }
+ }
+
+ Reference< XShape > xShape = SimpleShape::implConvertAndInsert( rxShapes, rShapeRect );
+
+ if( aBezierCoords.Coordinates.hasElements())
+ {
+ PropertySet aPropSet( xShape );
+ aPropSet.setProperty( PROP_PolyPolygonBezier, aBezierCoords );
+ }
+
+ // tdf#105875 handle rotation
+ // Note: must rotate before flip!
+ handleRotation(maTypeModel, xShape);
+
+ // Handle horizontal and vertical flip.
+ handleMirroring(maTypeModel, xShape);
+
+ return xShape;
+}
+
+CustomShape::CustomShape( Drawing& rDrawing ) :
+ SimpleShape( rDrawing, "com.sun.star.drawing.CustomShape" )
+{
+}
+
+Reference< XShape > CustomShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ // try to create a custom shape
+ Reference< XShape > xShape = SimpleShape::implConvertAndInsert( rxShapes, rShapeRect );
+ if( xShape.is() ) try
+ {
+ // create the custom shape geometry
+ Reference< XEnhancedCustomShapeDefaulter > xDefaulter( xShape, UNO_QUERY_THROW );
+ xDefaulter->createCustomShapeDefaults( OUString::number( getShapeType() ) );
+ // convert common properties
+ convertShapeProperties( xShape );
+ }
+ catch( Exception& )
+ {
+ }
+ return xShape;
+}
+
+ComplexShape::ComplexShape( Drawing& rDrawing ) :
+ CustomShape( rDrawing )
+{
+}
+
+Reference< XShape > ComplexShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ XmlFilterBase& rFilter = mrDrawing.getFilter();
+ sal_Int32 nShapeType = getShapeType();
+ OUString aGraphicPath = getGraphicPath();
+
+ // try to find registered OLE object info
+ if( const OleObjectInfo* pOleObjectInfo = mrDrawing.getOleObjectInfo( maTypeModel.maShapeId ) )
+ {
+ SAL_WARN_IF(
+ nShapeType != VML_SHAPETYPE_PICTUREFRAME, "oox",
+ "ComplexShape::implConvertAndInsert - unexpected shape type");
+
+ // if OLE object is embedded into a DrawingML shape (PPTX), do not create it here
+ if( pOleObjectInfo->mbDmlShape )
+ return Reference< XShape >();
+
+ PropertyMap aOleProps;
+ awt::Size aOleSize( rShapeRect.Width, rShapeRect.Height );
+ if( rFilter.getOleObjectHelper().importOleObject( aOleProps, *pOleObjectInfo, aOleSize ) )
+ {
+ Reference< XShape > xShape = mrDrawing.createAndInsertXShape( "com.sun.star.drawing.OLE2Shape", rxShapes, rShapeRect );
+ if( xShape.is() )
+ {
+ // set the replacement graphic
+ if( !aGraphicPath.isEmpty() )
+ {
+ WmfExternal aExtHeader;
+ aExtHeader.mapMode = 8;
+ aExtHeader.xExt = rShapeRect.Width;
+ aExtHeader.yExt = rShapeRect.Height;
+
+ Reference< XGraphic > xGraphic = rFilter.getGraphicHelper().importEmbeddedGraphic(aGraphicPath, &aExtHeader);
+ if (xGraphic.is())
+ aOleProps.setProperty( PROP_Graphic, xGraphic);
+ }
+
+ PropertySet aPropSet( xShape );
+ aPropSet.setProperties( aOleProps );
+
+ return xShape;
+ }
+ }
+ }
+
+ // try to find registered form control info
+ const ControlInfo* pControlInfo = mrDrawing.getControlInfo( maTypeModel.maShapeId );
+ if( pControlInfo && !pControlInfo->maFragmentPath.isEmpty() )
+ {
+ if( !pControlInfo->maName.isEmpty() )
+ {
+ // load the control properties from fragment
+ ::oox::ole::EmbeddedControl aControl(pControlInfo->maName);
+ if( rFilter.importFragment( new ::oox::ole::AxControlFragment( rFilter, pControlInfo->maFragmentPath, aControl ) ) )
+ {
+ // create and return the control shape (including control model)
+ sal_Int32 nCtrlIndex = -1;
+ Reference< XShape > xShape = mrDrawing.createAndInsertXControlShape( aControl, rxShapes, rShapeRect, nCtrlIndex );
+
+ if (pControlInfo->mbTextContentShape)
+ {
+ PropertySet aPropertySet(xShape);
+ lcl_SetAnchorType(aPropertySet, maTypeModel, mrDrawing.getFilter().getGraphicHelper());
+ }
+ // on error, proceed and try to create picture from replacement image
+ if( xShape.is() )
+ return xShape;
+ }
+ }
+ }
+
+ // host application wants to create the shape (do not try failed OLE controls again)
+ if( (nShapeType == VML_SHAPETYPE_HOSTCONTROL) && !pControlInfo )
+ {
+ OSL_ENSURE( getClientData(), "ComplexShape::implConvertAndInsert - missing client data" );
+ Reference< XShape > xShape = mrDrawing.createAndInsertClientXShape( *this, rxShapes, rShapeRect );
+ if( xShape.is() )
+ return xShape;
+ }
+
+
+ if( getShapeModel().mbIsSignatureLine )
+ {
+ uno::Reference<graphic::XGraphic> xGraphic;
+ bool bIsSigned(false);
+ try
+ {
+ // Get the document signatures
+ Reference<security::XDocumentDigitalSignatures> xSignatures(
+ security::DocumentDigitalSignatures::createDefault(
+ comphelper::getProcessComponentContext()));
+
+ uno::Reference<embed::XStorage> xStorage
+ = comphelper::OStorageHelper::GetStorageOfFormatFromURL(
+ ZIP_STORAGE_FORMAT_STRING, mrDrawing.getFilter().getFileUrl(),
+ embed::ElementModes::READ);
+ SAL_WARN_IF(!xStorage.is(), "oox.vml", "No xStorage!");
+
+ const uno::Sequence<security::DocumentSignatureInformation> xSignatureInfo
+ = xSignatures->verifyScriptingContentSignatures(xStorage,
+ uno::Reference<io::XInputStream>());
+
+ // Try to find matching signature line image - if none exists that is fine,
+ // then the signature line is not digitally signed.
+ auto pSignInfo = std::find_if(xSignatureInfo.begin(), xSignatureInfo.end(),
+ [this](const security::DocumentSignatureInformation& rSigInfo) {
+ return rSigInfo.SignatureLineId == getShapeModel().maSignatureId; });
+ if (pSignInfo != xSignatureInfo.end())
+ {
+ bIsSigned = true;
+ if (pSignInfo->SignatureIsValid)
+ {
+ // Signature is valid, use the 'valid' image
+ SAL_WARN_IF(!pSignInfo->ValidSignatureLineImage.is(), "oox.vml",
+ "No ValidSignatureLineImage!");
+ xGraphic = pSignInfo->ValidSignatureLineImage;
+ }
+ else
+ {
+ // Signature is invalid, use the 'invalid' image
+ SAL_WARN_IF(!pSignInfo->InvalidSignatureLineImage.is(), "oox.vml",
+ "No InvalidSignatureLineImage!");
+ xGraphic = pSignInfo->InvalidSignatureLineImage;
+ }
+ }
+ }
+ catch (css::uno::Exception&)
+ {
+ // DocumentDigitalSignatures service not available.
+ // We continue by rendering the "unsigned" shape instead.
+ }
+
+ Reference< XShape > xShape;
+ if (xGraphic.is())
+ {
+ // If available, use the signed image from the signature
+ xShape = SimpleShape::createPictureObject(rxShapes, rShapeRect, xGraphic);
+ }
+ else
+ {
+ // Create shape with the fallback "unsigned" image
+ xShape = SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);
+ }
+
+ // Store signature line properties
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ xPropertySet->setPropertyValue("IsSignatureLine", uno::Any(true));
+ xPropertySet->setPropertyValue("SignatureLineId",
+ uno::Any(getShapeModel().maSignatureId));
+ xPropertySet->setPropertyValue(
+ "SignatureLineSuggestedSignerName",
+ uno::Any(getShapeModel().maSignatureLineSuggestedSignerName));
+ xPropertySet->setPropertyValue(
+ "SignatureLineSuggestedSignerTitle",
+ uno::Any(getShapeModel().maSignatureLineSuggestedSignerTitle));
+ xPropertySet->setPropertyValue(
+ "SignatureLineSuggestedSignerEmail",
+ uno::Any(getShapeModel().maSignatureLineSuggestedSignerEmail));
+ xPropertySet->setPropertyValue(
+ "SignatureLineSigningInstructions",
+ uno::Any(getShapeModel().maSignatureLineSigningInstructions));
+ xPropertySet->setPropertyValue(
+ "SignatureLineShowSignDate",
+ uno::Any(getShapeModel().mbSignatureLineShowSignDate));
+ xPropertySet->setPropertyValue(
+ "SignatureLineCanAddComment",
+ uno::Any(getShapeModel().mbSignatureLineCanAddComment));
+ xPropertySet->setPropertyValue("SignatureLineIsSigned", uno::Any(bIsSigned));
+
+ if (!aGraphicPath.isEmpty())
+ {
+ xGraphic = rFilter.getGraphicHelper().importEmbeddedGraphic(aGraphicPath);
+ xPropertySet->setPropertyValue("SignatureLineUnsignedImage", uno::Any(xGraphic));
+ }
+ return xShape;
+ }
+
+ // try to create a picture object
+ if( !aGraphicPath.isEmpty() )
+ {
+ Reference<XShape> xShape = SimpleShape::createEmbeddedPictureObject(rxShapes, rShapeRect, aGraphicPath);
+ // AS_CHARACTER shape: vertical orientation default is bottom, MSO default is top.
+ if ( maTypeModel.maPosition != "absolute" && maTypeModel.maPosition != "relative" )
+ PropertySet( xShape ).setAnyProperty( PROP_VertOrient, Any(text::VertOrientation::TOP));
+
+ // Apply stroke props from the type model.
+ oox::drawingml::ShapePropertyMap aPropMap(mrDrawing.getFilter().getModelObjectHelper());
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ maTypeModel.maStrokeModel.pushToPropMap(aPropMap, rGraphicHelper);
+ // And, fill-color properties as well...
+ maTypeModel.maFillModel.pushToPropMap(aPropMap, rGraphicHelper);
+ PropertySet(xShape).setProperties(aPropMap);
+
+ return xShape;
+ }
+
+ // default: try to create a custom shape
+ return CustomShape::implConvertAndInsert( rxShapes, rShapeRect );
+}
+
+GroupShape::GroupShape( Drawing& rDrawing ) :
+ ShapeBase( rDrawing ),
+ mxChildren( new ShapeContainer( rDrawing ) )
+{
+}
+
+GroupShape::~GroupShape()
+{
+}
+
+void GroupShape::finalizeFragmentImport()
+{
+ // basic shape processing
+ ShapeBase::finalizeFragmentImport();
+ // finalize all child shapes
+ mxChildren->finalizeFragmentImport();
+}
+
+const ShapeType* GroupShape::getChildTypeById( const OUString& rShapeId ) const
+{
+ return mxChildren->getShapeTypeById( rShapeId );
+}
+
+const ShapeBase* GroupShape::getChildById( const OUString& rShapeId ) const
+{
+ return mxChildren->getShapeById( rShapeId );
+}
+
+Reference< XShape > GroupShape::implConvertAndInsert( const Reference< XShapes >& rxShapes, const awt::Rectangle& rShapeRect ) const
+{
+ Reference< XShape > xGroupShape;
+ // check that this shape contains children and a valid coordinate system
+ ShapeParentAnchor aParentAnchor;
+ aParentAnchor.maShapeRect = rShapeRect;
+ aParentAnchor.maCoordSys = getCoordSystem();
+ if( !mxChildren->empty() && (aParentAnchor.maCoordSys.Width > 0) && (aParentAnchor.maCoordSys.Height > 0) ) try
+ {
+ xGroupShape = mrDrawing.createAndInsertXShape( "com.sun.star.drawing.GroupShape", rxShapes, rShapeRect );
+ Reference< XShapes > xChildShapes( xGroupShape, UNO_QUERY_THROW );
+ mxChildren->convertAndInsert( xChildShapes, &aParentAnchor );
+ if( !xChildShapes->hasElements() )
+ {
+ SAL_WARN("oox", "no child shape has been created - deleting the group shape");
+ rxShapes->remove( xGroupShape );
+ xGroupShape.clear();
+ }
+ }
+ catch( Exception& )
+ {
+ }
+
+ uno::Reference<beans::XPropertySet> xPropertySet;
+ if (!maTypeModel.maEditAs.isEmpty())
+ xPropertySet = uno::Reference<beans::XPropertySet>(xGroupShape, uno::UNO_QUERY);
+ if (xPropertySet.is())
+ {
+ uno::Sequence<beans::PropertyValue> aGrabBag;
+ xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
+ sal_Int32 nLength = aGrabBag.getLength();
+ aGrabBag.realloc(nLength + 1);
+ aGrabBag.getArray()[nLength] = comphelper::makePropertyValue("mso-edit-as", maTypeModel.maEditAs);
+ xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag));
+ }
+ // Make sure group shapes are inline as well, unless there is an explicit different style.
+ PropertySet aPropertySet(xGroupShape);
+ const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper();
+ lcl_SetAnchorType(aPropertySet, maTypeModel, rGraphicHelper);
+ if (!maTypeModel.maRotation.isEmpty())
+ aPropertySet.setAnyProperty(PROP_RotateAngle, Any(ConversionHelper::decodeRotation(maTypeModel.maRotation).get()));
+ return xGroupShape;
+}
+
+} // namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmlshapecontainer.cxx b/oox/source/vml/vmlshapecontainer.cxx
new file mode 100644
index 0000000000..d9aa7d9aab
--- /dev/null
+++ b/oox/source/vml/vmlshapecontainer.cxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/vml/vmlshapecontainer.hxx>
+
+#include <oox/vml/vmldrawing.hxx>
+#include <oox/vml/vmlshape.hxx>
+
+#include <osl/diagnose.h>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::uno;
+
+namespace {
+
+template< typename ShapeType >
+void lclMapShapesById( RefMap< OUString, ShapeType >& orMap, const RefVector< ShapeType >& rVector )
+{
+ for (auto const& elem : rVector)
+ {
+ const OUString& rShapeId = elem->getShapeId();
+ OSL_ENSURE( !rShapeId.isEmpty(), "lclMapShapesById - missing shape identifier" );
+ if( !rShapeId.isEmpty() )
+ {
+ OSL_ENSURE( orMap.find( rShapeId ) == orMap.end(), "lclMapShapesById - shape identifier already used " );
+ orMap[ rShapeId ] = elem;
+ }
+ }
+}
+
+} // namespace
+
+ShapeContainer::ShapeContainer( Drawing& rDrawing ) :
+ mrDrawing( rDrawing )
+{
+}
+
+ShapeContainer::~ShapeContainer()
+{
+}
+
+std::shared_ptr<ShapeType> ShapeContainer::createShapeType()
+{
+ auto xShape = std::make_shared<ShapeType>( mrDrawing );
+ maTypes.push_back( xShape );
+ return xShape;
+}
+
+void ShapeContainer::finalizeFragmentImport()
+{
+ // map all shape templates by shape identifier
+ lclMapShapesById( maTypesById, maTypes );
+ // map all shapes by shape identifier
+ lclMapShapesById( maShapesById, maShapes );
+ /* process all shapes (map all children templates/shapes in group shapes,
+ resolve template references in all shapes) */
+ maShapes.forEachMem( &ShapeBase::finalizeFragmentImport );
+}
+
+const ShapeType* ShapeContainer::getShapeTypeById( const OUString& rShapeId ) const
+{
+ if (maTypesById.empty() && !maTypes.empty())
+ {
+ lclMapShapesById(const_cast<ShapeTypeMap&>(maTypesById), maTypes);
+ }
+
+ // search in own shape template list
+ if( const ShapeType* pType = maTypesById.get( rShapeId ).get() )
+ return pType;
+ // search deep in child shapes
+ for (auto const& shape : maShapes)
+ if( const ShapeType* pType = shape->getChildTypeById( rShapeId ) )
+ return pType;
+ return nullptr;
+}
+
+const ShapeBase* ShapeContainer::getShapeById( const OUString& rShapeId ) const
+{
+ // search in own shape list
+ if( const ShapeBase* pShape = maShapesById.get( rShapeId ).get() )
+ return pShape;
+ // search deep in child shapes
+ for (auto const& shape : maShapes)
+ if( const ShapeBase* pShape = shape->getChildById( rShapeId ) )
+ return pShape;
+ return nullptr;
+}
+
+std::shared_ptr< ShapeBase > ShapeContainer::takeLastShape()
+{
+ OSL_ENSURE( mrDrawing.getType() == VMLDRAWING_WORD, "ShapeContainer::takeLastShape - illegal call, Word filter only" );
+ assert( !markStack.empty());
+ if( markStack.top() >= maShapes.size())
+ return std::shared_ptr< ShapeBase >();
+ std::shared_ptr< ShapeBase > ret = maShapes.back();
+ maShapes.pop_back();
+ return ret;
+}
+
+void ShapeContainer::pushMark()
+{
+ markStack.push( maShapes.size());
+}
+
+void ShapeContainer::popMark()
+{
+ assert( !markStack.empty());
+ markStack.pop();
+}
+
+void ShapeContainer::convertAndInsert( const Reference< XShapes >& rxShapes, const ShapeParentAnchor* pParentAnchor ) const
+{
+ for (auto const& shape : maShapes)
+ shape->convertAndInsert( rxShapes, pParentAnchor );
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmlshapecontext.cxx b/oox/source/vml/vmlshapecontext.cxx
new file mode 100644
index 0000000000..e3242368a3
--- /dev/null
+++ b/oox/source/vml/vmlshapecontext.cxx
@@ -0,0 +1,731 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <sal/config.h>
+
+#include <string_view>
+
+#include <oox/vml/vmlshapecontext.hxx>
+
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/helper/attributelist.hxx>
+#include <oox/helper/helper.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/vml/vmldrawing.hxx>
+#include <oox/vml/vmlshape.hxx>
+#include <oox/vml/vmlshapecontainer.hxx>
+#include <oox/vml/vmltextboxcontext.hxx>
+
+#include <osl/diagnose.h>
+#include <filter/msfilter/escherex.hxx>
+#include <o3tl/string_view.hxx>
+
+namespace oox::vml {
+
+using namespace ::com::sun::star;
+
+using ::oox::core::ContextHandler2;
+using ::oox::core::ContextHandler2Helper;
+using ::oox::core::ContextHandlerRef;
+
+namespace {
+
+/** Returns the boolean value from the specified VML attribute (if present).
+ */
+std::optional< bool > lclDecodeBool( const AttributeList& rAttribs, sal_Int32 nToken )
+{
+ std::optional< OUString > oValue = rAttribs.getString( nToken );
+ if( oValue.has_value() ) return std::optional< bool >( ConversionHelper::decodeBool( oValue.value() ) );
+ return std::optional< bool >();
+}
+
+/** Returns the percentage value from the specified VML attribute (if present).
+ The value will be normalized (1.0 is returned for 100%).
+ */
+std::optional< double > lclDecodePercent( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue )
+{
+ std::optional< OUString > oValue = rAttribs.getString( nToken );
+ if( oValue.has_value() ) return std::optional< double >( ConversionHelper::decodePercent( oValue.value(), fDefValue ) );
+ return std::optional< double >();
+}
+
+/** #119750# Special method for opacity; it *should* be a percentage value, but there are cases
+ where a value relative to 0xffff (65536) is used, ending with an 'f'
+ */
+std::optional< double > lclDecodeOpacity( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue )
+{
+ std::optional< OUString > oValue = rAttribs.getString( nToken );
+ double fRetval(fDefValue);
+
+ if( oValue.has_value() )
+ {
+ const OUString& aString(oValue.value());
+ const sal_Int32 nLength(aString.getLength());
+
+ if(nLength > 0)
+ {
+ if(aString.endsWith("f"))
+ {
+ fRetval = std::clamp(aString.toDouble() / 65536.0, 0.0, 1.0);
+ }
+ else
+ {
+ fRetval = ConversionHelper::decodePercent( aString, fDefValue );
+ }
+ }
+ }
+
+ return std::optional< double >(fRetval);
+}
+
+/** Returns the integer value pair from the specified VML attribute (if present).
+ */
+std::optional< Int32Pair > lclDecodeInt32Pair( const AttributeList& rAttribs, sal_Int32 nToken )
+{
+ std::optional< OUString > oValue = rAttribs.getString( nToken );
+ std::optional< Int32Pair > oRetValue;
+ if( oValue.has_value() )
+ {
+ std::u16string_view aValue1, aValue2;
+ ConversionHelper::separatePair( aValue1, aValue2, oValue.value(), ',' );
+ oRetValue = Int32Pair( o3tl::toInt32(aValue1), o3tl::toInt32(aValue2) );
+ }
+ return oRetValue;
+}
+
+/** Returns the percentage pair from the specified VML attribute (if present).
+ */
+std::optional< DoublePair > lclDecodePercentPair( const AttributeList& rAttribs, sal_Int32 nToken )
+{
+ std::optional< OUString > oValue = rAttribs.getString( nToken );
+ std::optional< DoublePair > oRetValue;
+ if( oValue.has_value() )
+ {
+ std::u16string_view aValue1, aValue2;
+ ConversionHelper::separatePair( aValue1, aValue2, oValue.value(), ',' );
+ oRetValue = DoublePair(
+ ConversionHelper::decodePercent( aValue1, 0.0 ),
+ ConversionHelper::decodePercent( aValue2, 0.0 ) );
+ }
+ return oRetValue;
+}
+
+/** Returns the boolean value from the passed string of an attribute in the x:
+ namespace (VML for spreadsheets). Supported values: f, t, False, True.
+ @param bDefaultForEmpty Default value for the empty string.
+ */
+bool lclDecodeVmlxBool( std::u16string_view rValue, bool bDefaultForEmpty )
+{
+ if( rValue.empty() ) return bDefaultForEmpty;
+ sal_Int32 nToken = AttributeConversion::decodeToken( rValue );
+ // anything else than 't' or 'True' is considered to be false, as specified
+ return (nToken == XML_t) || (nToken == XML_True);
+}
+
+} // namespace
+
+ShapeLayoutContext::ShapeLayoutContext( ContextHandler2Helper const & rParent, Drawing& rDrawing ) :
+ ContextHandler2( rParent ),
+ mrDrawing( rDrawing )
+{
+}
+
+ContextHandlerRef ShapeLayoutContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( nElement )
+ {
+ case O_TOKEN( idmap ):
+ {
+ OUString aBlockIds = rAttribs.getStringDefaulted( XML_data);
+ sal_Int32 nIndex = 0;
+ while( nIndex >= 0 )
+ {
+ std::u16string_view aToken = o3tl::trim(o3tl::getToken(aBlockIds, 0, ' ', nIndex ));
+ if( !aToken.empty() )
+ mrDrawing.registerBlockId( o3tl::toInt32(aToken) );
+ }
+ }
+ break;
+ }
+ return nullptr;
+}
+
+ClientDataContext::ClientDataContext( ContextHandler2Helper const & rParent,
+ ClientData& rClientData, const AttributeList& rAttribs ) :
+ ContextHandler2( rParent ),
+ mrClientData( rClientData )
+{
+ mrClientData.mnObjType = rAttribs.getToken( XML_ObjectType, XML_TOKEN_INVALID );
+}
+
+ContextHandlerRef ClientDataContext::onCreateContext( sal_Int32 /*nElement*/, const AttributeList& /*rAttribs*/ )
+{
+ if( isRootElement() )
+ {
+ maElementText.clear();
+ return this;
+ }
+ return nullptr;
+}
+
+void ClientDataContext::onCharacters( const OUString& rChars )
+{
+ /* Empty but existing elements have special meaning, e.g. 'true'. Collect
+ existing text and convert it in onEndElement(). */
+ maElementText = rChars;
+}
+
+void ClientDataContext::onEndElement()
+{
+ switch( getCurrentElement() )
+ {
+ case VMLX_TOKEN( Anchor ): mrClientData.maAnchor = maElementText; break;
+ case VMLX_TOKEN( FmlaMacro ): mrClientData.maFmlaMacro = maElementText; break;
+ case VMLX_TOKEN( FmlaPict ): mrClientData.maFmlaPict = maElementText; break;
+ case VMLX_TOKEN( FmlaLink ): mrClientData.maFmlaLink = maElementText; break;
+ case VMLX_TOKEN( FmlaRange ): mrClientData.maFmlaRange = maElementText; break;
+ case VMLX_TOKEN( FmlaGroup ): mrClientData.maFmlaGroup = maElementText; break;
+ case VMLX_TOKEN( TextHAlign ): mrClientData.mnTextHAlign = AttributeConversion::decodeToken( maElementText ); break;
+ case VMLX_TOKEN( TextVAlign ): mrClientData.mnTextVAlign = AttributeConversion::decodeToken( maElementText ); break;
+ case VMLX_TOKEN( Column ): mrClientData.mnCol = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Row ): mrClientData.mnRow = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Checked ): mrClientData.mnChecked = maElementText.toInt32(); break;
+ case VMLX_TOKEN( DropStyle ): mrClientData.mnDropStyle = AttributeConversion::decodeToken( maElementText ); break;
+ case VMLX_TOKEN( DropLines ): mrClientData.mnDropLines = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Val ): mrClientData.mnVal = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Min ): mrClientData.mnMin = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Max ): mrClientData.mnMax = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Inc ): mrClientData.mnInc = maElementText.toInt32(); break;
+ case VMLX_TOKEN( Page ): mrClientData.mnPage = maElementText.toInt32(); break;
+ case VMLX_TOKEN( SelType ): mrClientData.mnSelType = AttributeConversion::decodeToken( maElementText ); break;
+ case VMLX_TOKEN( VTEdit ): mrClientData.mnVTEdit = maElementText.toInt32(); break;
+ case VMLX_TOKEN( PrintObject ): mrClientData.mbPrintObject = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( Visible ): mrClientData.mbVisible = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( DDE ): mrClientData.mbDde = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( NoThreeD ): mrClientData.mbNo3D = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( NoThreeD2 ): mrClientData.mbNo3D2 = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( MultiLine ): mrClientData.mbMultiLine = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( VScroll ): mrClientData.mbVScroll = lclDecodeVmlxBool( maElementText, true ); break;
+ case VMLX_TOKEN( SecretEdit ): mrClientData.mbSecretEdit = lclDecodeVmlxBool( maElementText, true ); break;
+ }
+}
+
+ShapeContextBase::ShapeContextBase( ContextHandler2Helper const & rParent ) :
+ ContextHandler2( rParent )
+{
+}
+
+ContextHandlerRef ShapeContextBase::createShapeContext( ContextHandler2Helper const & rParent,
+ ShapeContainer& rShapes, sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( nElement )
+ {
+ case O_TOKEN( shapelayout ):
+ return new ShapeLayoutContext( rParent, rShapes.getDrawing() );
+
+ case VML_TOKEN( shapetype ):
+ return new ShapeTypeContext( rParent, rShapes.createShapeType(), rAttribs );
+ case VML_TOKEN( group ):
+ return new GroupShapeContext( rParent, rShapes.createShape< GroupShape >(), rAttribs );
+ case VML_TOKEN( shape ):
+ if (rAttribs.hasAttribute(XML_path) &&
+ // tdf#122563 skip in the case of empty path
+ !rAttribs.getStringDefaulted(XML_path).isEmpty())
+ return new ShapeContext( rParent, rShapes.createShape< BezierShape >(), rAttribs );
+ else
+ return new ShapeContext( rParent, rShapes.createShape< ComplexShape >(), rAttribs );
+ case VML_TOKEN( rect ):
+ return new RectangleShapeContext( rParent, rAttribs, rShapes.createShape< RectangleShape >() );
+ case VML_TOKEN( roundrect ):
+ return new ShapeContext( rParent, rShapes.createShape< RectangleShape >(), rAttribs );
+ case VML_TOKEN( oval ):
+ return new ShapeContext( rParent, rShapes.createShape< EllipseShape >(), rAttribs );
+ case VML_TOKEN( polyline ):
+ return new ShapeContext( rParent, rShapes.createShape< PolyLineShape >(), rAttribs );
+ case VML_TOKEN( line ):
+ return new ShapeContext( rParent, rShapes.createShape< LineShape >(), rAttribs );
+ case VML_TOKEN( curve ):
+ return new ShapeContext( rParent, rShapes.createShape< BezierShape >(), rAttribs );
+
+ // TODO:
+ case VML_TOKEN( arc ):
+ case VML_TOKEN( diagram ):
+ case VML_TOKEN( image ):
+ return new ShapeContext( rParent, rShapes.createShape< ComplexShape >(), rAttribs );
+
+ case W_TOKEN(control):
+ return new ControlShapeContext( rParent, rShapes, rAttribs );
+ }
+ return nullptr;
+}
+
+ShapeTypeContext::ShapeTypeContext(ContextHandler2Helper const & rParent,
+ std::shared_ptr<ShapeType> const& pShapeType,
+ const AttributeList& rAttribs)
+ : ShapeContextBase(rParent)
+ , m_pShapeType(pShapeType) // tdf#112311 keep it alive
+ , mrTypeModel( pShapeType->getTypeModel() )
+{
+ // shape identifier and shape name
+ bool bHasOspid = rAttribs.hasAttribute( O_TOKEN( spid ) );
+ mrTypeModel.maShapeId = rAttribs.getXString( bHasOspid ? O_TOKEN( spid ) : XML_id, OUString() );
+ mrTypeModel.maLegacyId = rAttribs.getStringDefaulted( XML_id);
+ OSL_ENSURE( !mrTypeModel.maShapeId.isEmpty(), "ShapeTypeContext::ShapeTypeContext - missing shape identifier" );
+ // builtin shape type identifier
+ mrTypeModel.moShapeType = rAttribs.getInteger( O_TOKEN( spt ) );
+ // if the o:spid attribute exists, the id attribute contains the user-defined shape name
+ if( bHasOspid )
+ {
+ mrTypeModel.maShapeName = rAttribs.getXString( XML_id, OUString() );
+ // get ShapeType and ShapeId from name for compatibility
+ static constexpr OUString sShapeTypePrefix = u"shapetype_"_ustr;
+ OUString tmp;
+ if( mrTypeModel.maShapeName.startsWith( sShapeTypePrefix ) )
+ {
+ mrTypeModel.maShapeId = mrTypeModel.maShapeName;
+ mrTypeModel.moShapeType = o3tl::toInt32(mrTypeModel.maShapeName.subView(sShapeTypePrefix.getLength()));
+ }
+ else if (mrTypeModel.maShapeName.startsWith("_x0000_t", &tmp))
+ {
+ mrTypeModel.maShapeId = mrTypeModel.maShapeName;
+ mrTypeModel.moShapeType = tmp.toInt32();
+ }
+ }
+
+ // coordinate system position/size, CSS style
+ mrTypeModel.moCoordPos = lclDecodeInt32Pair( rAttribs, XML_coordorigin );
+ mrTypeModel.moCoordSize = lclDecodeInt32Pair( rAttribs, XML_coordsize );
+ setStyle( rAttribs.getStringDefaulted( XML_style) );
+ if( lclDecodeBool( rAttribs, O_TOKEN( hr )).value_or( false ))
+ { // MSO's handling of o:hr width is nowhere near what the spec says:
+ // - o:hrpct is not in % but in 0.1%
+ // - if o:hrpct is not given, 100% width is assumed
+ // - given width is used only if explicit o:hrpct="0" is given
+ OUString hrpct = rAttribs.getString( O_TOKEN( hrpct ), "1000" );
+ if( hrpct != "0" )
+ mrTypeModel.maWidthPercent = OUString::number( hrpct.toInt32() );
+ mrTypeModel.maWrapDistanceLeft = "0";
+ mrTypeModel.maWrapDistanceRight = "0";
+ mrTypeModel.maPositionHorizontal = rAttribs.getString( O_TOKEN( hralign ), "left" );
+ mrTypeModel.moWrapType = "topAndBottom";
+ }
+
+ // stroke settings (may be overridden by v:stroke element later)
+ mrTypeModel.maStrokeModel.moStroked = lclDecodeBool( rAttribs, XML_stroked );
+ mrTypeModel.maStrokeModel.moColor = rAttribs.getString( XML_strokecolor );
+ mrTypeModel.maStrokeModel.moWeight = rAttribs.getString( XML_strokeweight );
+
+ // fill settings (may be overridden by v:fill element later)
+ mrTypeModel.maFillModel.moFilled = lclDecodeBool( rAttribs, XML_filled );
+ mrTypeModel.maFillModel.moColor = rAttribs.getString( XML_fillcolor );
+
+ // For roundrect we may have an arcsize attribute to read
+ mrTypeModel.maArcsize = rAttribs.getStringDefaulted(XML_arcsize);
+ // editas
+ mrTypeModel.maEditAs = rAttribs.getStringDefaulted(XML_editas);
+
+ mrTypeModel.maAdjustments = rAttribs.getStringDefaulted(XML_adj);
+}
+
+ContextHandlerRef ShapeTypeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ if( isRootElement() ) switch( nElement )
+ {
+ case VML_TOKEN( stroke ):
+ assignIfUsed( mrTypeModel.maStrokeModel.moStroked, lclDecodeBool( rAttribs, XML_on ) );
+ mrTypeModel.maStrokeModel.maStartArrow.moArrowType = rAttribs.getToken( XML_startarrow );
+ mrTypeModel.maStrokeModel.maStartArrow.moArrowWidth = rAttribs.getToken( XML_startarrowwidth );
+ mrTypeModel.maStrokeModel.maStartArrow.moArrowLength = rAttribs.getToken( XML_startarrowlength );
+ mrTypeModel.maStrokeModel.maEndArrow.moArrowType = rAttribs.getToken( XML_endarrow );
+ mrTypeModel.maStrokeModel.maEndArrow.moArrowWidth = rAttribs.getToken( XML_endarrowwidth );
+ mrTypeModel.maStrokeModel.maEndArrow.moArrowLength = rAttribs.getToken( XML_endarrowlength );
+ assignIfUsed( mrTypeModel.maStrokeModel.moColor, rAttribs.getString( XML_color ) );
+ mrTypeModel.maStrokeModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
+ assignIfUsed( mrTypeModel.maStrokeModel.moWeight, rAttribs.getString( XML_weight ) );
+ mrTypeModel.maStrokeModel.moDashStyle = rAttribs.getString( XML_dashstyle );
+ mrTypeModel.maStrokeModel.moLineStyle = rAttribs.getToken( XML_linestyle );
+ mrTypeModel.maStrokeModel.moEndCap = rAttribs.getToken( XML_endcap );
+ mrTypeModel.maStrokeModel.moJoinStyle = rAttribs.getToken( XML_joinstyle );
+ break;
+ case VML_TOKEN( fill ):
+ {
+ // in DOCX shapes use r:id for the relationship id
+ // in XLSX they use o:relid
+ bool bHasORelId = rAttribs.hasAttribute( O_TOKEN(relid) );
+ assignIfUsed( mrTypeModel.maFillModel.moFilled, lclDecodeBool( rAttribs, XML_on ) );
+ assignIfUsed( mrTypeModel.maFillModel.moColor, rAttribs.getString( XML_color ) );
+ mrTypeModel.maFillModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 );
+ mrTypeModel.maFillModel.moColor2 = rAttribs.getString( XML_color2 );
+ mrTypeModel.maFillModel.moOpacity2 = lclDecodeOpacity( rAttribs, XML_opacity2, 1.0 );
+ mrTypeModel.maFillModel.moType = rAttribs.getToken( XML_type );
+ mrTypeModel.maFillModel.moAngle = rAttribs.getInteger( XML_angle );
+ mrTypeModel.maFillModel.moFocus = lclDecodePercent( rAttribs, XML_focus, 0.0 );
+ mrTypeModel.maFillModel.moFocusPos = lclDecodePercentPair( rAttribs, XML_focusposition );
+ mrTypeModel.maFillModel.moFocusSize = lclDecodePercentPair( rAttribs, XML_focussize );
+ mrTypeModel.maFillModel.moBitmapPath = decodeFragmentPath( rAttribs, bHasORelId ? O_TOKEN(relid) : R_TOKEN(id) );
+ mrTypeModel.maFillModel.moRotate = lclDecodeBool( rAttribs, XML_rotate );
+ break;
+ }
+ case VML_TOKEN( imagedata ):
+ {
+ // shapes in docx use r:id for the relationship id
+ // in xlsx it they use o:relid
+ bool bHasORelId = rAttribs.hasAttribute( O_TOKEN( relid ) );
+ mrTypeModel.moGraphicPath = decodeFragmentPath( rAttribs, bHasORelId ? O_TOKEN( relid ) : R_TOKEN( id ) );
+ mrTypeModel.moGraphicTitle = rAttribs.getString( O_TOKEN( title ) );
+
+ // Get crop attributes.
+ mrTypeModel.moCropBottom = rAttribs.getString(XML_cropbottom);
+ mrTypeModel.moCropLeft = rAttribs.getString(XML_cropleft);
+ mrTypeModel.moCropRight = rAttribs.getString(XML_cropright);
+ mrTypeModel.moCropTop = rAttribs.getString(XML_croptop);
+
+ // Gain / contrast.
+ std::optional<OUString> oGain = rAttribs.getString(XML_gain);
+ sal_Int32 nGain = 0x10000;
+ if (oGain.has_value() && oGain.value().endsWith("f"))
+ {
+ nGain = oGain.value().toInt32();
+ }
+ if (nGain < 0x10000)
+ {
+ nGain *= 101; // 100 + 1 to round
+ nGain /= 0x10000;
+ nGain -= 100;
+ }
+ mrTypeModel.mnGain = nGain;
+
+ // Blacklevel / brightness.
+ std::optional<OUString> oBlacklevel = rAttribs.getString(XML_blacklevel);
+ sal_Int16 nBlacklevel = 0;
+ if (oBlacklevel.has_value() && oBlacklevel.value().endsWith("f"))
+ {
+ nBlacklevel = oBlacklevel.value().toInt32();
+ }
+ if (nBlacklevel != 0)
+ {
+ nBlacklevel /= 327;
+ }
+ mrTypeModel.mnBlacklevel = nBlacklevel;
+ }
+ break;
+ case NMSP_vmlWord | XML_wrap:
+ mrTypeModel.moWrapAnchorX = rAttribs.getString(XML_anchorx);
+ mrTypeModel.moWrapAnchorY = rAttribs.getString(XML_anchory);
+ mrTypeModel.moWrapType = rAttribs.getString(XML_type);
+ mrTypeModel.moWrapSide = rAttribs.getString(XML_side);
+ break;
+ case VML_TOKEN( shadow ):
+ {
+ mrTypeModel.maShadowModel.mbHasShadow = true;
+ mrTypeModel.maShadowModel.moShadowOn = lclDecodeBool(rAttribs, XML_on).value_or(false);
+ assignIfUsed(mrTypeModel.maShadowModel.moColor, rAttribs.getString(XML_color));
+ assignIfUsed(mrTypeModel.maShadowModel.moOffset, rAttribs.getString(XML_offset));
+ mrTypeModel.maShadowModel.moOpacity = lclDecodePercent(rAttribs, XML_opacity, 1.0);
+ }
+ break;
+ case VML_TOKEN( textpath ):
+ assignIfUsed(mrTypeModel.maTextpathModel.moString, rAttribs.getString(XML_string));
+ assignIfUsed(mrTypeModel.maTextpathModel.moStyle, rAttribs.getString(XML_style));
+ assignIfUsed(mrTypeModel.maTextpathModel.moTrim, lclDecodeBool(rAttribs, XML_trim));
+ break;
+ }
+ return nullptr;
+}
+
+std::optional< OUString > ShapeTypeContext::decodeFragmentPath( const AttributeList& rAttribs, sal_Int32 nToken ) const
+{
+ std::optional< OUString > oFragmentPath;
+ std::optional< OUString > oRelId = rAttribs.getString( nToken );
+ if( oRelId.has_value() )
+ oFragmentPath = getFragmentPathFromRelId( oRelId.value() );
+ return oFragmentPath;
+}
+
+void ShapeTypeContext::setStyle( std::u16string_view rStyle )
+{
+ sal_Int32 nIndex = 0;
+ while( nIndex >= 0 )
+ {
+ std::u16string_view aName, aValue;
+ if( ConversionHelper::separatePair( aName, aValue, o3tl::getToken(rStyle, 0, ';', nIndex ), ':' ) )
+ {
+ if( aName == u"position" ) mrTypeModel.maPosition = aValue;
+ else if( aName == u"z-index" ) mrTypeModel.maZIndex = aValue;
+ else if( aName == u"left" ) mrTypeModel.maLeft = aValue;
+ else if( aName == u"top" ) mrTypeModel.maTop = aValue;
+ else if( aName == u"width" ) mrTypeModel.maWidth = aValue;
+ else if( aName == u"height" ) mrTypeModel.maHeight = aValue;
+ else if( aName == u"margin-left" ) mrTypeModel.maMarginLeft = aValue;
+ else if( aName == u"margin-top" ) mrTypeModel.maMarginTop = aValue;
+ else if( aName == u"mso-position-vertical-relative" ) mrTypeModel.maPositionVerticalRelative = aValue;
+ else if( aName == u"mso-position-horizontal-relative" ) mrTypeModel.maPositionHorizontalRelative = aValue;
+ else if( aName == u"mso-position-horizontal" ) mrTypeModel.maPositionHorizontal = aValue;
+ else if( aName == u"mso-position-vertical" ) mrTypeModel.maPositionVertical = aValue;
+ else if( aName == u"mso-width-percent" ) mrTypeModel.maWidthPercent = aValue;
+ else if( aName == u"mso-width-relative" ) mrTypeModel.maWidthRelative = aValue;
+ else if( aName == u"mso-height-percent" ) mrTypeModel.maHeightPercent = aValue;
+ else if( aName == u"mso-height-relative" ) mrTypeModel.maHeightRelative = aValue;
+ else if( aName == u"mso-fit-shape-to-text" ) mrTypeModel.mbAutoHeight = true;
+ else if( aName == u"rotation" ) mrTypeModel.maRotation = aValue;
+ else if( aName == u"flip" ) mrTypeModel.maFlip = aValue;
+ else if( aName == u"visibility" )
+ mrTypeModel.mbVisible = aValue != u"hidden";
+ else if( aName == u"mso-wrap-style" ) mrTypeModel.maWrapStyle = aValue;
+ else if ( aName == u"v-text-anchor" ) mrTypeModel.maVTextAnchor = aValue;
+ else if ( aName == u"mso-wrap-distance-left" ) mrTypeModel.maWrapDistanceLeft = aValue;
+ else if ( aName == u"mso-wrap-distance-right" ) mrTypeModel.maWrapDistanceRight = aValue;
+ else if ( aName == u"mso-wrap-distance-top" ) mrTypeModel.maWrapDistanceTop = aValue;
+ else if ( aName == u"mso-wrap-distance-bottom" ) mrTypeModel.maWrapDistanceBottom = aValue;
+ }
+ }
+}
+
+ShapeContext::ShapeContext(ContextHandler2Helper const& rParent,
+ const std::shared_ptr<ShapeBase>& pShape, const AttributeList& rAttribs)
+ : ShapeTypeContext(rParent, pShape, rAttribs)
+ , mrShape(*pShape)
+ , mrShapeModel(pShape->getShapeModel())
+{
+ // collect shape specific attributes
+ mrShapeModel.maType = rAttribs.getXString( XML_type, OUString() );
+ // polyline path
+ setPoints( rAttribs.getStringDefaulted( XML_points) );
+ // line start and end positions
+ setFrom(rAttribs.getStringDefaulted(XML_from));
+ setTo(rAttribs.getStringDefaulted(XML_to));
+ setControl1(rAttribs.getStringDefaulted(XML_control1));
+ setControl2(rAttribs.getStringDefaulted(XML_control2));
+ setVmlPath(rAttribs.getStringDefaulted(XML_path));
+ setHyperlink(rAttribs.getStringDefaulted(XML_href));
+}
+
+ContextHandlerRef ShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ // Excel specific shape client data
+ if( isRootElement() ) switch( nElement )
+ {
+ case VML_TOKEN( textbox ):
+ {
+ // Calculate the shape type: map both <rect> and <v:shape> with a textbox shape type to
+ // a TextShape.
+ sal_Int32 nShapeType = 0;
+ if (ShapeContainer* pShapeContainer = mrShape.getContainer())
+ {
+ OUString aType = mrShapeModel.maType;
+ if (!aType.isEmpty() && aType[0] == '#')
+ {
+ aType = aType.copy(1);
+ }
+ if (const ShapeType* pShapeType = pShapeContainer->getShapeTypeById(aType))
+ {
+ nShapeType = pShapeType->getTypeModel().moShapeType.value();
+ }
+ }
+ mrShapeModel.mbInGroup = (getParentElement() == VML_TOKEN(group));
+
+ // FIXME: the shape with textbox should be used for the next cases
+ if (getCurrentElement() == VML_TOKEN(rect) || nShapeType == ESCHER_ShpInst_TextBox)
+ {
+ if (mrShapeModel.mbInGroup)
+ // FIXME: without this a text will be added into the group-shape instead of its
+ // parent shape
+ dynamic_cast<SimpleShape&>(mrShape).setService("com.sun.star.drawing.TextShape");
+ else
+ // FIXME: without this we does not handle some properties like shadow
+ dynamic_cast<SimpleShape&>(mrShape).setService("com.sun.star.text.TextFrame");
+ }
+ return new TextBoxContext( *this, mrShapeModel.createTextBox(mrShape.getTypeModel()), rAttribs,
+ mrShape.getDrawing().getFilter().getGraphicHelper());
+ }
+ case VMLX_TOKEN( ClientData ):
+ // tdf#41466 ActiveX control shapes with a textbox are transformed into a frame
+ // (see unit test testActiveXOptionButtonGroup)
+ dynamic_cast<SimpleShape&>(mrShape).setService("com.sun.star.text.TextFrame");
+ return new ClientDataContext( *this, mrShapeModel.createClientData(), rAttribs );
+ case VMLPPT_TOKEN( textdata ):
+ // Force RectangleShape, this is ugly :(
+ // and is there because of the lines above which change it to TextFrame
+ dynamic_cast< SimpleShape& >( mrShape ).setService(
+ "com.sun.star.drawing.RectangleShape");
+ mrShapeModel.maLegacyDiagramPath = getFragmentPathFromRelId(rAttribs.getStringDefaulted(XML_id));
+ break;
+ case O_TOKEN( signatureline ):
+ mrShapeModel.mbIsSignatureLine = true;
+ mrShapeModel.maSignatureId = rAttribs.getStringDefaulted(XML_id);
+ mrShapeModel.maSignatureLineSuggestedSignerName
+ = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner));
+ mrShapeModel.maSignatureLineSuggestedSignerTitle
+ = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigner2));
+ mrShapeModel.maSignatureLineSuggestedSignerEmail
+ = rAttribs.getStringDefaulted(O_TOKEN(suggestedsigneremail));
+ mrShapeModel.maSignatureLineSigningInstructions
+ = rAttribs.getStringDefaulted(O_TOKEN(signinginstructions));
+ mrShapeModel.mbSignatureLineShowSignDate = ConversionHelper::decodeBool(
+ rAttribs.getString(XML_showsigndate, "t")); // default is true
+ mrShapeModel.mbSignatureLineCanAddComment = ConversionHelper::decodeBool(
+ rAttribs.getString(XML_allowcomments, "f")); // default is false
+ break;
+ case O_TOKEN( lock ):
+ // TODO
+ break;
+ }
+ // handle remaining stuff in base class
+ return ShapeTypeContext::onCreateContext( nElement, rAttribs );
+}
+
+void ShapeContext::setPoints(std::u16string_view rPoints)
+{
+ mrShapeModel.maPoints.clear();
+ sal_Int32 nIndex = 0;
+
+ while (nIndex >= 0)
+ {
+ sal_Int32 nX = ConversionHelper::decodeMeasureToTwip(
+ mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
+ 0, true, true);
+ sal_Int32 nY = ConversionHelper::decodeMeasureToTwip(
+ mrShape.getDrawing().getFilter().getGraphicHelper(), o3tl::getToken(rPoints, 0, ',', nIndex),
+ 0, false, true);
+ mrShapeModel.maPoints.emplace_back(nX, nY);
+ }
+ // VML polyline has no size in its style attribute. Word writes the size to attribute
+ // coordsize with values in twip but without unit. For others we get size from points.
+ if (!mrShape.getTypeModel().maWidth.isEmpty() || !mrShape.getTypeModel().maHeight.isEmpty())
+ return;
+
+ if (mrShape.getTypeModel().moCoordSize.has_value())
+ {
+ double fWidth = mrShape.getTypeModel().moCoordSize.value().first;
+ fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::pt);
+ double fHeight = mrShape.getTypeModel().moCoordSize.value().second;
+ fHeight = o3tl::convert(fHeight, o3tl::Length::twip, o3tl::Length::pt);
+ mrShape.getTypeModel().maWidth = OUString::number(fWidth) + "pt";
+ mrShape.getTypeModel().maHeight = OUString::number(fHeight) + "pt";
+ }
+ else if (mrShapeModel.maPoints.size())
+ {
+ double fMinX = mrShapeModel.maPoints[0].X;
+ double fMaxX = mrShapeModel.maPoints[0].X;
+ double fMinY = mrShapeModel.maPoints[0].Y;
+ double fMaxY = mrShapeModel.maPoints[0].Y;
+ for (const auto& rPoint : mrShapeModel.maPoints)
+ {
+ if (rPoint.X < fMinX)
+ fMinX = rPoint.X;
+ else if (rPoint.X > fMaxX)
+ fMaxX = rPoint.X;
+ if (rPoint.Y < fMinY)
+ fMinY = rPoint.Y;
+ else if (rPoint.Y > fMaxY)
+ fMaxY = rPoint.Y;
+ }
+ mrShape.getTypeModel().maWidth
+ = OUString::number(
+ o3tl::convert(fMaxX - fMinX, o3tl::Length::twip, o3tl::Length::pt))
+ + "pt";
+ mrShape.getTypeModel().maHeight
+ = OUString::number(
+ o3tl::convert(fMaxY - fMinY, o3tl::Length::twip, o3tl::Length::pt))
+ + "pt";
+ // Set moCoordSize, otherwise default (1000,1000) is used.
+ mrShape.getTypeModel().moCoordSize =
+ Int32Pair(basegfx::fround(fMaxX - fMinX), basegfx::fround(fMaxY - fMinY));
+ }
+}
+
+void ShapeContext::setFrom( const OUString& rPoints )
+{
+ if (!rPoints.isEmpty())
+ mrShapeModel.maFrom = rPoints;
+}
+
+void ShapeContext::setTo( const OUString& rPoints )
+{
+ if (!rPoints.isEmpty())
+ mrShapeModel.maTo = rPoints;
+}
+
+void ShapeContext::setControl1( const OUString& rPoints )
+{
+ if (!rPoints.isEmpty())
+ mrShapeModel.maControl1 = rPoints;
+}
+
+void ShapeContext::setControl2( const OUString& rPoints )
+{
+ if (!rPoints.isEmpty())
+ mrShapeModel.maControl2 = rPoints;
+}
+void ShapeContext::setVmlPath( const OUString& rPath )
+{
+ if (!rPath.isEmpty())
+ mrShapeModel.maVmlPath = rPath;
+}
+
+void ShapeContext::setHyperlink( const OUString& rHyperlink )
+{
+ if (!rHyperlink.isEmpty())
+ mrShapeModel.maHyperlink = rHyperlink;
+}
+
+GroupShapeContext::GroupShapeContext(ContextHandler2Helper const& rParent,
+ const std::shared_ptr<GroupShape>& pShape,
+ const AttributeList& rAttribs)
+ : ShapeContext(rParent, pShape, rAttribs)
+ , mrShapes(pShape->getChildren())
+{
+}
+
+ContextHandlerRef GroupShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ // try to create a context of an embedded shape
+ ContextHandlerRef xContext = createShapeContext( *this, mrShapes, nElement, rAttribs );
+ // handle remaining stuff of this shape in base class
+ return xContext ? xContext : ShapeContext::onCreateContext( nElement, rAttribs );
+}
+
+RectangleShapeContext::RectangleShapeContext(ContextHandler2Helper const& rParent,
+ const AttributeList& rAttribs,
+ const std::shared_ptr<RectangleShape>& pShape)
+ : ShapeContext(rParent, pShape, rAttribs)
+{
+}
+
+ContextHandlerRef RectangleShapeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ // The parent class's context is fine
+ return ShapeContext::onCreateContext( nElement, rAttribs );
+}
+
+ControlShapeContext::ControlShapeContext( ::oox::core::ContextHandler2Helper const & rParent, ShapeContainer& rShapes, const AttributeList& rAttribs )
+ : ShapeContextBase (rParent)
+{
+ ::oox::vml::ControlInfo aInfo;
+ aInfo.maShapeId = rAttribs.getXString( W_TOKEN( shapeid ), OUString() );
+ aInfo.maFragmentPath = getFragmentPathFromRelId(rAttribs.getStringDefaulted( R_TOKEN(id)));
+ aInfo.maName = rAttribs.getStringDefaulted( W_TOKEN( name ));
+ aInfo.mbTextContentShape = true;
+ rShapes.getDrawing().registerControl(aInfo);
+}
+
+} // namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmltextbox.cxx b/oox/source/vml/vmltextbox.cxx
new file mode 100644
index 0000000000..b2c6fd407e
--- /dev/null
+++ b/oox/source/vml/vmltextbox.cxx
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/vml/vmltextbox.hxx>
+
+#include <rtl/ustrbuf.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/text/XTextAppend.hpp>
+#include <com/sun/star/text/WritingMode.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <comphelper/sequence.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <utility>
+
+namespace oox::vml {
+
+using namespace com::sun::star;
+
+TextFontModel::TextFontModel()
+{
+}
+
+TextPortionModel::TextPortionModel( TextParagraphModel aParagraph, TextFontModel aFont, OUString aText ) :
+ maParagraph(std::move( aParagraph )),
+ maFont(std::move( aFont )),
+ maText(std::move( aText ))
+{
+}
+
+TextBox::TextBox(ShapeTypeModel& rTypeModel)
+ : mrTypeModel(rTypeModel)
+ , borderDistanceSet( false )
+ , borderDistanceLeft(0)
+ , borderDistanceTop(0)
+ , borderDistanceRight(0)
+ , borderDistanceBottom(0)
+{
+}
+
+void TextBox::appendPortion( const TextParagraphModel& rParagraph, const TextFontModel& rFont, const OUString& rText )
+{
+ maPortions.emplace_back( rParagraph, rFont, rText );
+}
+
+const TextFontModel* TextBox::getFirstFont() const
+{
+ return maPortions.empty() ? nullptr : &maPortions.front().maFont;
+}
+
+OUString TextBox::getText() const
+{
+ OUStringBuffer aBuffer;
+ for (auto const& portion : maPortions)
+ aBuffer.append( portion.maText );
+ return aBuffer.makeStringAndClear();
+}
+
+void TextBox::convert(const uno::Reference<drawing::XShape>& xShape) const
+{
+ uno::Reference<text::XTextAppend> xTextAppend(xShape, uno::UNO_QUERY);
+ OUString sParaStyle;
+ bool bAmbiguousStyle = true;
+
+ for (auto const& portion : maPortions)
+ {
+ beans::PropertyValue aPropertyValue;
+ std::vector<beans::PropertyValue> aPropVec;
+ const TextParagraphModel& rParagraph = portion.maParagraph;
+ const TextFontModel& rFont = portion.maFont;
+ if (rFont.moName.has_value())
+ {
+ aPropertyValue.Name = "CharFontName";
+ aPropertyValue.Value <<= rFont.moName.value();
+ aPropVec.push_back(aPropertyValue);
+
+ aPropertyValue.Name = "CharFontNameAsian";
+ aPropertyValue.Value <<= rFont.moNameAsian.value_or("");
+ aPropVec.push_back(aPropertyValue);
+
+ aPropertyValue.Name = "CharFontNameComplex";
+ aPropertyValue.Value <<= rFont.moNameComplex.value_or("");
+ aPropVec.push_back(aPropertyValue);
+ }
+ if (rFont.mobBold.has_value())
+ {
+ aPropertyValue.Name = "CharWeight";
+ aPropertyValue.Value <<= rFont.mobBold.value() ? awt::FontWeight::BOLD : awt::FontWeight::NORMAL;
+ aPropVec.push_back(aPropertyValue);
+ }
+ if (rFont.monSize.has_value())
+ {
+ aPropertyValue.Name = "CharHeight";
+ aPropertyValue.Value <<= double(rFont.monSize.value()) / 2.;
+ aPropVec.push_back(aPropertyValue);
+ }
+ if (rFont.monSpacing.has_value())
+ {
+ aPropertyValue.Name = "CharKerning";
+ // Value is not converted to mm100: SvxKerningItem::PutValue() gets
+ // called with nMemberId = 0, so no mm100 -> twips conversion will
+ // be done there.
+ aPropertyValue.Value <<= sal_Int16(rFont.monSpacing.value());
+ aPropVec.push_back(aPropertyValue);
+ }
+ if (rParagraph.moParaAdjust.has_value())
+ {
+ style::ParagraphAdjust eAdjust = style::ParagraphAdjust_LEFT;
+ if (rParagraph.moParaAdjust.value() == "center")
+ eAdjust = style::ParagraphAdjust_CENTER;
+ else if (rParagraph.moParaAdjust.value() == "right")
+ eAdjust = style::ParagraphAdjust_RIGHT;
+
+ aPropertyValue.Name = "ParaAdjust";
+ aPropertyValue.Value <<= eAdjust;
+ aPropVec.push_back(aPropertyValue);
+ }
+
+ // All paragraphs should be either undefined (default) or the same style,
+ // because it will only be applied to the entire shape, and not per-paragraph.
+ if (sParaStyle.isEmpty() )
+ {
+ if ( rParagraph.moParaStyleName.has_value() )
+ sParaStyle = rParagraph.moParaStyleName.value();
+ if ( bAmbiguousStyle )
+ bAmbiguousStyle = false; // both empty parastyle and ambiguous can only be true at the first paragraph
+ else
+ bAmbiguousStyle = rParagraph.moParaStyleName.has_value(); // ambiguous if both default and specified style used.
+ }
+ else if ( !bAmbiguousStyle )
+ {
+ if ( !rParagraph.moParaStyleName.has_value() )
+ bAmbiguousStyle = true; // ambiguous if both specified and default style used.
+ else if ( rParagraph.moParaStyleName.value() != sParaStyle )
+ bAmbiguousStyle = true; // ambiguous if two different styles specified.
+ }
+ if (rFont.moColor.has_value())
+ {
+ aPropertyValue.Name = "CharColor";
+ aPropertyValue.Value <<= rFont.moColor.value().toUInt32(16);
+ aPropVec.push_back(aPropertyValue);
+ }
+ xTextAppend->appendTextPortion(portion.maText, comphelper::containerToSequence(aPropVec));
+ }
+
+ try
+ {
+ // Track the style in a grabBag for use later when style details are known.
+ comphelper::SequenceAsHashMap aGrabBag;
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY_THROW);
+ aGrabBag.update( xPropertySet->getPropertyValue("CharInteropGrabBag") );
+ aGrabBag["mso-pStyle"] <<= sParaStyle;
+ xPropertySet->setPropertyValue("CharInteropGrabBag", uno::Any(aGrabBag.getAsConstPropertyValueList()));
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "oox.vml","convert() grabbag exception" );
+ }
+
+ // Remove the last character of the shape text, if it would be a newline.
+ uno::Reference< text::XTextCursor > xCursor = xTextAppend->createTextCursor();
+ xCursor->gotoEnd(false);
+ xCursor->goLeft(1, true);
+ if (xCursor->getString() == "\n")
+ xCursor->setString("");
+
+ if ( maLayoutFlow != "vertical" )
+ return;
+
+ uno::Reference<beans::XPropertySet> xProperties(xShape, uno::UNO_QUERY);
+
+ // VML has the text horizontally aligned to left (all the time),
+ // v-text-anchor for vertical alignment, and vertical mode to swap the
+ // two. drawinglayer supports both horizontal and vertical alignment,
+ // but no vertical mode: we use T->B, R->L instead.
+ // As a result, we need to set horizontal adjustment here to 'right',
+ // that will result in vertical 'top' after writing mode is applied,
+ // which matches the VML behavior.
+ xProperties->setPropertyValue("TextHorizontalAdjust", uno::Any(drawing::TextHorizontalAdjust_RIGHT));
+
+ xProperties->setPropertyValue( "TextWritingMode", uno::Any( text::WritingMode_TB_RL ) );
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/vml/vmltextboxcontext.cxx b/oox/source/vml/vmltextboxcontext.cxx
new file mode 100644
index 0000000000..0a3f255d76
--- /dev/null
+++ b/oox/source/vml/vmltextboxcontext.cxx
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-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/.
+ *
+ * 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 <oox/helper/attributelist.hxx>
+#include <oox/vml/vmlformatting.hxx>
+#include <oox/vml/vmltextboxcontext.hxx>
+#include <oox/vml/vmlshape.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/tokens.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+#include <utility>
+
+namespace oox::vml {
+
+using ::oox::core::ContextHandler2;
+using ::oox::core::ContextHandler2Helper;
+using ::oox::core::ContextHandlerRef;
+
+TextPortionContext::TextPortionContext( ContextHandler2Helper const & rParent,
+ TextBox& rTextBox, TextParagraphModel aParagraph, TextFontModel aParentFont,
+ sal_Int32 nElement, const AttributeList& rAttribs ) :
+ ContextHandler2( rParent ),
+ mrTextBox( rTextBox ),
+ maParagraph(std::move( aParagraph )),
+ maFont(std::move( aParentFont )),
+ mnInitialPortions( rTextBox.getPortionCount() )
+{
+ switch( nElement )
+ {
+ case XML_font:
+ maFont.moName = rAttribs.getXString( XML_face );
+ maFont.moColor = rAttribs.getXString( XML_color );
+ maFont.monSize = rAttribs.getInteger( XML_size );
+ break;
+ case XML_u:
+ OSL_ENSURE( !maFont.monUnderline, "TextPortionContext::TextPortionContext - nested <u> elements" );
+ maFont.monUnderline = (rAttribs.getToken( XML_class, XML_TOKEN_INVALID ) == XML_font4) ? XML_double : XML_single;
+ break;
+ case XML_sub:
+ case XML_sup:
+ OSL_ENSURE( !maFont.monEscapement, "TextPortionContext::TextPortionContext - nested <sub> or <sup> elements" );
+ maFont.monEscapement = nElement;
+ break;
+ case XML_b:
+ OSL_ENSURE( !maFont.mobBold.has_value(), "TextPortionContext::TextPortionContext - nested <b> elements" );
+ maFont.mobBold = true;
+ break;
+ case XML_i:
+ OSL_ENSURE( !maFont.mobItalic.has_value(), "TextPortionContext::TextPortionContext - nested <i> elements" );
+ maFont.mobItalic = true;
+ break;
+ case XML_s:
+ OSL_ENSURE( !maFont.mobStrikeout.has_value(), "TextPortionContext::TextPortionContext - nested <s> elements" );
+ maFont.mobStrikeout = true;
+ break;
+ case OOX_TOKEN(dml, blip):
+ {
+ std::optional<OUString> oRelId = rAttribs.getString(R_TOKEN(embed));
+ if (oRelId.has_value())
+ mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.value());
+ }
+ break;
+ case VML_TOKEN(imagedata):
+ {
+ std::optional<OUString> oRelId = rAttribs.getString(R_TOKEN(id));
+ if (oRelId.has_value())
+ mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.value());
+ }
+ break;
+ case XML_span:
+ case W_TOKEN(r):
+ break;
+ default:
+ OSL_ENSURE( false, "TextPortionContext::TextPortionContext - unknown element" );
+ }
+}
+
+ContextHandlerRef TextPortionContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ OSL_ENSURE( nElement != XML_font, "TextPortionContext::onCreateContext - nested <font> elements" );
+ if (getNamespace(getCurrentElement()) == NMSP_doc)
+ return this;
+ return new TextPortionContext( *this, mrTextBox, maParagraph, maFont, nElement, rAttribs );
+}
+
+void TextPortionContext::onCharacters( const OUString& rChars )
+{
+ if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
+ return;
+
+ switch( getCurrentElement() )
+ {
+ case XML_span:
+ // replace all NBSP characters with SP
+ mrTextBox.appendPortion( maParagraph, maFont, rChars.replace( 0xA0, ' ' ) );
+ break;
+ default:
+ mrTextBox.appendPortion( maParagraph, maFont, rChars );
+ }
+}
+
+void TextPortionContext::onStartElement(const AttributeList& rAttribs)
+{
+ switch (getCurrentElement())
+ {
+ case W_TOKEN(b):
+ maFont.mobBold = true;
+ break;
+ case W_TOKEN(sz):
+ maFont.monSize = rAttribs.getInteger( W_TOKEN(val) );
+ break;
+ case W_TOKEN(br):
+ mrTextBox.appendPortion( maParagraph, maFont, "\n" );
+ break;
+ case W_TOKEN(color):
+ maFont.moColor = rAttribs.getString( W_TOKEN(val) );
+ break;
+ case W_TOKEN(spacing):
+ maFont.monSpacing = rAttribs.getInteger(W_TOKEN(val));
+ break;
+ case W_TOKEN(r):
+ case W_TOKEN(rPr):
+ case W_TOKEN(t):
+ break;
+ case W_TOKEN(rFonts):
+ // See https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.runfonts(v=office.14).aspx
+ maFont.moName = rAttribs.getString(W_TOKEN(ascii));
+ maFont.moNameAsian = rAttribs.getString(W_TOKEN(eastAsia));
+ maFont.moNameComplex = rAttribs.getString(W_TOKEN(cs));
+ break;
+ default:
+ SAL_INFO("oox", "unhandled: 0x" << std::hex<< getCurrentElement());
+ break;
+ }
+}
+
+void TextPortionContext::onEndElement()
+{
+ if (getNamespace(getCurrentElement()) == NMSP_doc && getCurrentElement() != W_TOKEN(t))
+ return;
+
+ /* A child element without own child elements may contain a single space
+ character, for example:
+
+ <div>
+ <font><i>abc</i></font>
+ <font> </font>
+ <font><b>def</b></font>
+ </div>
+
+ represents the italic text 'abc', an unformatted space character, and
+ the bold text 'def'. Unfortunately, the XML parser skips the space
+ character without issuing a 'characters' event. The class member
+ 'mnInitialPortions' contains the number of text portions existing when
+ this context has been constructed. If no text has been added in the
+ meantime, the space character has to be added manually.
+ */
+ if( mrTextBox.getPortionCount() == mnInitialPortions )
+ mrTextBox.appendPortion( maParagraph, maFont, OUString( ' ' ) );
+}
+
+TextBoxContext::TextBoxContext( ContextHandler2Helper const & rParent, TextBox& rTextBox, const AttributeList& rAttribs,
+ const GraphicHelper& graphicHelper ) :
+ ContextHandler2( rParent ),
+ mrTextBox( rTextBox )
+{
+ if( rAttribs.getStringDefaulted( XML_insetmode ) != "auto" )
+ {
+ OUString inset = rAttribs.getStringDefaulted( XML_inset );
+ std::u16string_view value;
+ std::u16string_view remainingStr;
+
+ ConversionHelper::separatePair( value, remainingStr, inset, ',' );
+ rTextBox.borderDistanceLeft = ConversionHelper::decodeMeasureToHmm( graphicHelper,
+ value.empty() ? u"0.1in" : value, 0, false, false );
+
+ inset = remainingStr;
+ ConversionHelper::separatePair( value, remainingStr, inset, ',' );
+ rTextBox.borderDistanceTop = ConversionHelper::decodeMeasureToHmm( graphicHelper,
+ value.empty() ? u"0.05in" : value, 0, false, false );
+
+ inset = remainingStr;
+ ConversionHelper::separatePair( value, remainingStr, inset, ',' );
+ rTextBox.borderDistanceRight = ConversionHelper::decodeMeasureToHmm( graphicHelper,
+ value.empty() ? u"0.1in" : value, 0, false, false );
+
+ inset = remainingStr;
+ ConversionHelper::separatePair( value, remainingStr, inset, ',' );
+ rTextBox.borderDistanceBottom = ConversionHelper::decodeMeasureToHmm( graphicHelper,
+ value.empty() ? u"0.05in" : value, 0, false, false );
+
+ rTextBox.borderDistanceSet = true;
+ }
+
+ OUString sStyle = rAttribs.getStringDefaulted( XML_style);
+ sal_Int32 nIndex = 0;
+ while( nIndex >= 0 )
+ {
+ std::u16string_view aName, aValue;
+ if( ConversionHelper::separatePair( aName, aValue, o3tl::getToken(sStyle, 0, ';', nIndex ), ':' ) )
+ {
+ if( aName == u"layout-flow" ) rTextBox.maLayoutFlow = aValue;
+ else if (aName == u"mso-fit-shape-to-text")
+ rTextBox.mrTypeModel.mbAutoHeight = true;
+ else if (aName == u"mso-layout-flow-alt")
+ rTextBox.mrTypeModel.maLayoutFlowAlt = aValue;
+ else if (aName == u"mso-next-textbox")
+ rTextBox.msNextTextbox = aValue;
+ else
+ SAL_WARN("oox", "unhandled style property: " << OUString(aName));
+ }
+ }
+}
+
+ContextHandlerRef TextBoxContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs )
+{
+ switch( getCurrentElement() )
+ {
+ case VML_TOKEN( textbox ):
+ if( nElement == XML_div ) return this;
+ else if (nElement == W_TOKEN(txbxContent)) return this;
+ break;
+ case XML_div:
+ if( nElement == XML_font ) return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
+ break;
+ case W_TOKEN(txbxContent):
+ if (nElement == W_TOKEN(p)) return this;
+ break;
+ case W_TOKEN(p):
+ case W_TOKEN(sdtContent):
+ case W_TOKEN(smartTag):
+ if (nElement == W_TOKEN(r))
+ return new TextPortionContext( *this, mrTextBox, maParagraph, TextFontModel(), nElement, rAttribs );
+ else
+ return this;
+ case W_TOKEN(pPr):
+ case W_TOKEN(sdt):
+ return this;
+ default:
+ SAL_INFO("oox", "unhandled 0x" << std::hex << getCurrentElement());
+ break;
+ }
+ return nullptr;
+}
+
+void TextBoxContext::onStartElement(const AttributeList& rAttribs)
+{
+ switch (getCurrentElement())
+ {
+ case W_TOKEN(jc):
+ maParagraph.moParaAdjust = rAttribs.getString( W_TOKEN(val) );
+ break;
+ case W_TOKEN(pStyle):
+ maParagraph.moParaStyleName = rAttribs.getString( W_TOKEN(val) );
+ break;
+ }
+}
+
+void TextBoxContext::onEndElement()
+{
+ if (getCurrentElement() == W_TOKEN(p))
+ {
+ mrTextBox.appendPortion( maParagraph, TextFontModel(), "\n" );
+ maParagraph = TextParagraphModel();
+ }
+}
+
+} // namespace oox::vml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */