From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- oox/source/vml/vmldrawing.cxx | 346 ++++++++ oox/source/vml/vmldrawingfragment.cxx | 82 ++ oox/source/vml/vmlformatting.cxx | 1001 +++++++++++++++++++++ oox/source/vml/vmlinputstream.cxx | 393 ++++++++ oox/source/vml/vmlshape.cxx | 1574 +++++++++++++++++++++++++++++++++ oox/source/vml/vmlshapecontainer.cxx | 138 +++ oox/source/vml/vmlshapecontext.cxx | 731 +++++++++++++++ oox/source/vml/vmltextbox.cxx | 204 +++++ oox/source/vml/vmltextboxcontext.cxx | 287 ++++++ 9 files changed, 4756 insertions(+) create mode 100644 oox/source/vml/vmldrawing.cxx create mode 100644 oox/source/vml/vmldrawingfragment.cxx create mode 100644 oox/source/vml/vmlformatting.cxx create mode 100644 oox/source/vml/vmlinputstream.cxx create mode 100644 oox/source/vml/vmlshape.cxx create mode 100644 oox/source/vml/vmlshapecontainer.cxx create mode 100644 oox/source/vml/vmlshapecontext.cxx create mode 100644 oox/source/vml/vmltextbox.cxx create mode 100644 oox/source/vml/vmltextboxcontext.cxx (limited to 'oox/source/vml') diff --git a/oox/source/vml/vmldrawing.cxx b/oox/source/vml/vmldrawing.cxx new file mode 100644 index 000000000..081322a46 --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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( const OUString& rShapeId ) +{ + // identifier consists of a literal NUL character, a lowercase 's', and the id + return ((rShapeId.getLength() >= 3) && (rShapeId[ 0 ] == '\0') && (rShapeId[ 1 ] == 's')) ? o3tl::toInt32(rShapeId.subView( 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 GroupBoxMap; + std::map, 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( const OUString& 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 000000000..cb7936baf --- /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 + +#include +#include +#include +#include +#include +#include + +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 000000000..8f00eb47b --- /dev/null +++ b/oox/source/vml/vmlformatting.cxx @@ -0,0 +1,1001 @@ +/* -*- 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 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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 OptValue< OUString >& roVmlColor, const OptValue< double >& roVmlOpacity, + ::Color nDefaultRgb, ::Color nPrimaryRgb ) +{ + Color aDmlColor; + + // convert opacity + const sal_Int32 DML_FULL_OPAQUE = ::oox::drawingml::MAX_PERCENT; + double fOpacity = roVmlOpacity.get( 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() ) + { + 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.get(), ' ' ); + + // 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 ()' + 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.get(), 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, const OUString& 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 ( sal_Int32 i = 0; i < rPath.getLength(); 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.subView( 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.getLength()) + 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 OptValue< OUString >& roValue, sal_Int64 nDefValue ) +{ + return roValue.has() ? ConversionHelper::decodeMeasureToEmu( rGraphicHelper, roValue.get(), 0, false, false ) : nDefValue; +} + +void lclGetDmlLineDash( OptValue< sal_Int32 >& oroPresetDash, LineProperties::DashStopVector& orCustomDash, const OptValue< OUString >& roDashStyle ) +{ + if( !roDashStyle.has() ) + return; + + const OUString& rDashStyle = roDashStyle.get(); + 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 OptValue< sal_Int32 >& roArrowType ) +{ + if( roArrowType.has() ) switch( roArrowType.get() ) + { + 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 OptValue< sal_Int32 >& roArrowWidth ) +{ + if( roArrowWidth.has() ) switch( roArrowWidth.get() ) + { + 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 OptValue< sal_Int32 >& roArrowLength ) +{ + if( roArrowLength.has() ) switch( roArrowLength.get() ) + { + 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 OptValue< sal_Int32 >& roLineStyle ) +{ + if( roLineStyle.has() ) switch( roLineStyle.get() ) + { + 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 OptValue< sal_Int32 >& roEndCap ) +{ + if( roEndCap.has() ) switch( roEndCap.get() ) + { + 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 OptValue< sal_Int32 >& roJoinStyle ) +{ + if( roJoinStyle.has() ) switch( roJoinStyle.get() ) + { + 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 ) +{ + moArrowType.assignIfUsed( rSource.moArrowType ); + moArrowWidth.assignIfUsed( rSource.moArrowWidth ); + moArrowLength.assignIfUsed( rSource.moArrowLength ); +} + +void StrokeModel::assignUsed( const StrokeModel& rSource ) +{ + moStroked.assignIfUsed( rSource.moStroked ); + maStartArrow.assignUsed( rSource.maStartArrow ); + maEndArrow.assignUsed( rSource.maEndArrow ); + moColor.assignIfUsed( rSource.moColor ); + moOpacity.assignIfUsed( rSource.moOpacity ); + moWeight.assignIfUsed( rSource.moWeight ); + moDashStyle.assignIfUsed( rSource.moDashStyle ); + moLineStyle.assignIfUsed( rSource.moLineStyle ); + moEndCap.assignIfUsed( rSource.moEndCap ); + moJoinStyle.assignIfUsed( 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.get( 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 ) +{ + moFilled.assignIfUsed( rSource.moFilled ); + moColor.assignIfUsed( rSource.moColor ); + moOpacity.assignIfUsed( rSource.moOpacity ); + moColor2.assignIfUsed( rSource.moColor2 ); + moOpacity2.assignIfUsed( rSource.moOpacity2 ); + moType.assignIfUsed( rSource.moType ); + moAngle.assignIfUsed( rSource.moAngle ); + moFocus.assignIfUsed( rSource.moFocus ); + moFocusPos.assignIfUsed( rSource.moFocusPos ); + moFocusSize.assignIfUsed( rSource.moFocusSize ); + moBitmapPath.assignIfUsed( rSource.moBitmapPath ); + moRotate.assignIfUsed( 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.get( true ) ) + { + sal_Int32 nFillType = moType.get( XML_solid ); + switch( nFillType ) + { + case XML_gradient: + case XML_gradientRadial: + { + aFillProps.moFillType = XML_gradFill; + aFillProps.maGradientProps.moRotateWithShape = moRotate.get( false ); + double fFocus = moFocus.get( 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.get( 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; + + lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.0, rOuterColor); + lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 1.0, rOuterColor); + lcl_setGradientStop( aFillProps.maGradientProps.maGradientStops, 0.5, rInnerColor ); + } + 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.get( DoublePair( 0.0, 0.0 ) ); + DoublePair aFocusSize = moFocusSize.get( 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() && !moBitmapPath.get().isEmpty() ) + { + aFillProps.maBlipProps.mxFillGraphic = rGraphicHelper.importEmbeddedGraphic(moBitmapPath.get()); + 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() && !moShadowOn.get())) + 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()) + { + std::u16string_view aOffsetX, aOffsetY; + ConversionHelper::separatePair(aOffsetX, aOffsetY, moOffset.get(), ','); + 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 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& xShape, const GraphicHelper& rGraphicHelper) const +{ + OUString sFont = ""; + + if (moString.has()) + { + uno::Reference xTextRange(xShape, uno::UNO_QUERY); + xTextRange->setString(moString.get()); + + uno::Reference xPropertySet(xShape, uno::UNO_QUERY); + uno::Sequence aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry").get< uno::Sequence >(); + 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()) + { + OUString aStyle = moStyle.get(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 xPropertySet(xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("CharFontName", uno::Any(OUString(aValue))); + sFont = aValue; + } + else if (aName == u"font-size") + { + oox::OptValue aOptString {OUString(aValue)}; + float nSize = drawingml::convertEmuToPoints(lclGetEmu(rGraphicHelper, aOptString, 1)); + + uno::Reference xPropertySet(xShape, uno::UNO_QUERY); + xPropertySet->setPropertyValue("CharHeight", uno::Any(nSize)); + } + } + } + } + if (moTrim.has() && moTrim.get()) + return; + + OUString sText = moString.get(); + ScopedVclPtrInstance 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(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 000000000..4f9420761 --- /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 + +#include +#include +#include +#include +#include +#include +#include + +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( ' ' ).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 '
' 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 '' + 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 +
tag (without matching
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 + + 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 + abc + 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 element. If there are N space characters (N > 1), + then the element contains exactly (N-1) NBSP (non-breaking + space) characters, followed by a regular space character. Examples: + The element + \xA0\xA0\xA0 + represents 4 consecutive space characters. Has to be handled by the + implementation. The element + abc + 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( "" ); + +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 '' + 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 000000000..31fbbade4 --- /dev/null +++ b/oox/source/vml/vmlshape.cxx @@ -0,0 +1,1574 @@ +/* -*- 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 + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(const OUString& rCrop, sal_uInt32 nSize) +{ + if (rCrop.endsWith("f")) + { + // Numeric value is specified in 1/65536-ths. + sal_uInt32 nCrop = o3tl::toUInt32(rCrop.subView(0, rCrop.getLength() - 1)); + return (nCrop * nSize) / 65536; + } + + return 0; +} + +} // namespace + +ShapeTypeModel::ShapeTypeModel(): + mbAutoHeight( false ), + mbVisible( true ) +{ +} + +void ShapeTypeModel::assignUsed( const ShapeTypeModel& rSource ) +{ + moShapeType.assignIfUsed( rSource.moShapeType ); + moCoordPos.assignIfUsed( rSource.moCoordPos ); + moCoordSize.assignIfUsed( 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 ); + moGraphicPath.assignIfUsed( rSource.moGraphicPath ); + moGraphicTitle.assignIfUsed( rSource.moGraphicTitle ); +} + +ShapeType::ShapeType( Drawing& rDrawing ) : + mrDrawing( rDrawing ) +{ +} + +ShapeType::~ShapeType() +{ +} + +sal_Int32 ShapeType::getShapeType() const +{ + return maTypeModel.moShapeType.get( 0 ); +} + +OUString ShapeType::getGraphicPath() const +{ + return maTypeModel.moGraphicPath.get( OUString() ); +} + +awt::Rectangle ShapeType::getCoordSystem() const +{ + Int32Pair aCoordPos = maTypeModel.moCoordPos.get( Int32Pair( 0, 0 ) ); + Int32Pair aCoordSize = maTypeModel.moCoordSize.get( 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(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 const OUStringLiteral sShapeTypePrefix = u"shapetype_"; + 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 propertySet (xShape, uno::UNO_QUERY); + uno::Any aAny = propertySet->getPropertyValue("FrameInteropGrabBag"); + auto aGrabBag = comphelper::sequenceToContainer< std::vector >(aAny.get< uno::Sequence >()); + + 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 aGrabBag; + uno::Reference 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; +} + +void ShapeBase::convertFormatting( const Reference< XShape >& rxShape ) const +{ + if( !rxShape.is() ) + return; + + /* Calculate shape rectangle. Applications may do something special + according to some imported shape client data (e.g. Excel cell anchor). */ + awt::Rectangle aShapeRect = calcShapeRectangle( nullptr ); + + // convert the shape, if the calculated rectangle is not empty + if( (aShapeRect.Width > 0) || (aShapeRect.Height > 0) ) + { + rxShape->setPosition( awt::Point( aShapeRect.X, aShapeRect.Y ) ); + rxShape->setSize( awt::Size( aShapeRect.Width, aShapeRect.Height ) ); + convertShapeProperties( rxShape ); + } +} + +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; +} + +void ShapeBase::convertShapeProperties( const Reference< XShape >& rxShape ) const +{ + ::oox::drawingml::ShapePropertyMap aPropMap( mrDrawing.getFilter().getModelObjectHelper() ); + const GraphicHelper& rGraphicHelper = mrDrawing.getFilter().getGraphicHelper(); + maTypeModel.maStrokeModel.pushToPropMap( aPropMap, rGraphicHelper ); + maTypeModel.maFillModel.pushToPropMap( aPropMap, rGraphicHelper ); + + uno::Reference xSInfo(rxShape, uno::UNO_QUERY_THROW); + if (xSInfo->supportsService("com.sun.star.text.TextFrame")) + { + // 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 oLineWidth; + if (maTypeModel.maStrokeModel.moWeight.has()) + oLineWidth = ConversionHelper::decodeMeasureToHmm( + rGraphicHelper, maTypeModel.maStrokeModel.moWeight.get(), 0, false, false); + if (aPropMap.hasProperty(PROP_LineColor)) + { + uno::Reference 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(); + aBorderLine.Color = aPropMap.getProperty(PROP_LineColor).get(); + if (oLineWidth) + aBorderLine.LineWidth = *oLineWidth; + aPropMap.setProperty(nBorder, aBorderLine); + } + aPropMap.erase(PROP_LineColor); + } + } + else if (xSInfo->supportsService("com.sun.star.drawing.CustomShape")) + maTypeModel.maTextpathModel.pushToPropMap(aPropMap, rxShape, rGraphicHelper); + + PropertySet( rxShape ).setProperties( aPropMap ); +} + +SimpleShape::SimpleShape( Drawing& rDrawing, const OUString& rService ) : + ShapeBase( rDrawing ), + maService( rService ) +{ +} + +static void lcl_setSurround(PropertySet& rPropSet, const ShapeTypeModel& rTypeModel, const GraphicHelper& rGraphicHelper) +{ + OUString aWrapType = rTypeModel.moWrapType.get(); + + // 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.get() == "left" ) + nSurround = css::text::WrapTextMode_LEFT; + else if ( rTypeModel.moWrapSide.get() == "right" ) + nSurround = css::text::WrapTextMode_RIGHT; + } + else if ( aWrapType == "topAndBottom" ) + nSurround = css::text::WrapTextMode_NONE; + + rPropSet.setProperty(PROP_Surround, static_cast(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 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 ) + { + OUString aShapeType = EnhancedCustomShapeTypeNames::Get( static_cast< MSO_SPT >(getShapeType()) ); + //The resize autoshape to fit text attr of FontWork/Word-Art should always be false + //for the fallback geometry. + if(aShapeType.startsWith("fontwork")) + { + 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 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 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 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 SimpleShape::finalImplConvertAndInsert(const css::uno::Reference& 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 = 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 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() || maTypeModel.moCropLeft.has() || maTypeModel.moCropRight.has() || maTypeModel.moCropTop.has()) + { + text::GraphicCrop aGraphicCrop; + awt::Size aOriginalSize = rGraphicHelper.getOriginalSize(rxGraphic); + + if (maTypeModel.moCropBottom.has()) + aGraphicCrop.Bottom = lclConvertCrop(maTypeModel.moCropBottom.get(), aOriginalSize.Height); + if (maTypeModel.moCropLeft.has()) + aGraphicCrop.Left = lclConvertCrop(maTypeModel.moCropLeft.get(), aOriginalSize.Width); + if (maTypeModel.moCropRight.has()) + aGraphicCrop.Right = lclConvertCrop(maTypeModel.moCropRight.get(), aOriginalSize.Width); + if (maTypeModel.moCropTop.has()) + aGraphicCrop.Top = lclConvertCrop(maTypeModel.moCropTop.get(), 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 RectangleShape::implConvertAndInsert(const Reference& 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 = 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 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(this)->setService("com.sun.star.drawing.PolyPolygonShape"); + } + } + + Reference 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& 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& 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 LineShape::implConvertAndInsert(const Reference& rxShapes, const awt::Rectangle& rShapeRect) const +{ + Reference 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( 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 xGraphic; + bool bIsSigned(false); + try + { + // Get the document signatures + Reference xSignatures( + security::DocumentDigitalSignatures::createDefault( + comphelper::getProcessComponentContext())); + + uno::Reference 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 xSignatureInfo + = xSignatures->verifyScriptingContentSignatures(xStorage, + uno::Reference()); + + // 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 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 = 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 xPropertySet; + if (!maTypeModel.maEditAs.isEmpty()) + xPropertySet = uno::Reference(xGroupShape, uno::UNO_QUERY); + if (xPropertySet.is()) + { + uno::Sequence 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 000000000..d9aa7d9aa --- /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 + +#include +#include + +#include + +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 ShapeContainer::createShapeType() +{ + auto xShape = std::make_shared( 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(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 000000000..3141cb3aa --- /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 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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). + */ +OptValue< bool > lclDecodeBool( const AttributeList& rAttribs, sal_Int32 nToken ) +{ + OptValue< OUString > oValue = rAttribs.getString( nToken ); + if( oValue.has() ) return OptValue< bool >( ConversionHelper::decodeBool( oValue.get() ) ); + return OptValue< bool >(); +} + +/** Returns the percentage value from the specified VML attribute (if present). + The value will be normalized (1.0 is returned for 100%). + */ +OptValue< double > lclDecodePercent( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue ) +{ + OptValue< OUString > oValue = rAttribs.getString( nToken ); + if( oValue.has() ) return OptValue< double >( ConversionHelper::decodePercent( oValue.get(), fDefValue ) ); + return OptValue< 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' + */ +OptValue< double > lclDecodeOpacity( const AttributeList& rAttribs, sal_Int32 nToken, double fDefValue ) +{ + OptValue< OUString > oValue = rAttribs.getString( nToken ); + double fRetval(fDefValue); + + if( oValue.has() ) + { + const OUString& aString(oValue.get()); + 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 OptValue< double >(fRetval); +} + +/** Returns the integer value pair from the specified VML attribute (if present). + */ +OptValue< Int32Pair > lclDecodeInt32Pair( const AttributeList& rAttribs, sal_Int32 nToken ) +{ + OptValue< OUString > oValue = rAttribs.getString( nToken ); + OptValue< Int32Pair > oRetValue; + if( oValue.has() ) + { + std::u16string_view aValue1, aValue2; + ConversionHelper::separatePair( aValue1, aValue2, oValue.get(), ',' ); + oRetValue = Int32Pair( o3tl::toInt32(aValue1), o3tl::toInt32(aValue2) ); + } + return oRetValue; +} + +/** Returns the percentage pair from the specified VML attribute (if present). + */ +OptValue< DoublePair > lclDecodePercentPair( const AttributeList& rAttribs, sal_Int32 nToken ) +{ + OptValue< OUString > oValue = rAttribs.getString( nToken ); + OptValue< DoublePair > oRetValue; + if( oValue.has() ) + { + std::u16string_view aValue1, aValue2; + ConversionHelper::separatePair( aValue1, aValue2, oValue.get(), ',' ); + 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.getString( XML_data, OUString() ); + 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.getString(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 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.getString( XML_id, OUString() ); + 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 OUStringLiteral sShapeTypePrefix = u"shapetype_"; + 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.getString( XML_style, OUString() ) ); + if( lclDecodeBool( rAttribs, O_TOKEN( hr )).get( 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.getString(XML_arcsize, OUString()); + // editas + mrTypeModel.maEditAs = rAttribs.getString(XML_editas, OUString()); + + mrTypeModel.maAdjustments = rAttribs.getString(XML_adj, OUString()); +} + +ContextHandlerRef ShapeTypeContext::onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) +{ + if( isRootElement() ) switch( nElement ) + { + case VML_TOKEN( stroke ): + mrTypeModel.maStrokeModel.moStroked.assignIfUsed( 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 ); + mrTypeModel.maStrokeModel.moColor.assignIfUsed( rAttribs.getString( XML_color ) ); + mrTypeModel.maStrokeModel.moOpacity = lclDecodeOpacity( rAttribs, XML_opacity, 1.0 ); + mrTypeModel.maStrokeModel.moWeight.assignIfUsed( 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) ); + mrTypeModel.maFillModel.moFilled.assignIfUsed( lclDecodeBool( rAttribs, XML_on ) ); + mrTypeModel.maFillModel.moColor.assignIfUsed( 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. + OptValue oGain = rAttribs.getString(XML_gain); + sal_Int32 nGain = 0x10000; + if (oGain.has() && oGain.get().endsWith("f")) + { + nGain = oGain.get().toInt32(); + } + if (nGain < 0x10000) + { + nGain *= 101; // 100 + 1 to round + nGain /= 0x10000; + nGain -= 100; + } + mrTypeModel.mnGain = nGain; + + // Blacklevel / brightness. + OptValue oBlacklevel = rAttribs.getString(XML_blacklevel); + sal_Int16 nBlacklevel = 0; + if (oBlacklevel.has() && oBlacklevel.get().endsWith("f")) + { + nBlacklevel = oBlacklevel.get().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).get(false); + mrTypeModel.maShadowModel.moColor.assignIfUsed(rAttribs.getString(XML_color)); + mrTypeModel.maShadowModel.moOffset.assignIfUsed(rAttribs.getString(XML_offset)); + mrTypeModel.maShadowModel.moOpacity = lclDecodePercent(rAttribs, XML_opacity, 1.0); + } + break; + case VML_TOKEN( textpath ): + mrTypeModel.maTextpathModel.moString.assignIfUsed(rAttribs.getString(XML_string)); + mrTypeModel.maTextpathModel.moStyle.assignIfUsed(rAttribs.getString(XML_style)); + mrTypeModel.maTextpathModel.moTrim.assignIfUsed(lclDecodeBool(rAttribs, XML_trim)); + break; + } + return nullptr; +} + +OptValue< OUString > ShapeTypeContext::decodeFragmentPath( const AttributeList& rAttribs, sal_Int32 nToken ) const +{ + OptValue< OUString > oFragmentPath; + OptValue< OUString > oRelId = rAttribs.getString( nToken ); + if( oRelId.has() ) + oFragmentPath = getFragmentPathFromRelId( oRelId.get() ); + 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& 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.getString( XML_points, OUString() ) ); + // line start and end positions + setFrom(rAttribs.getString(XML_from, OUString())); + setTo(rAttribs.getString(XML_to, OUString())); + setControl1(rAttribs.getString(XML_control1, OUString())); + setControl2(rAttribs.getString(XML_control2, OUString())); + setVmlPath(rAttribs.getString(XML_path, OUString())); + setHyperlink(rAttribs.getString(XML_href, OUString())); +} + +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 and 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.get(); + } + } + 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(mrShape).setService("com.sun.star.drawing.TextShape"); + else + // FIXME: without this we does not handle some properties like shadow + dynamic_cast(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(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.getString(XML_id, OUString())); + break; + case O_TOKEN( signatureline ): + mrShapeModel.mbIsSignatureLine = true; + mrShapeModel.maSignatureId = rAttribs.getString(XML_id, OUString()); + mrShapeModel.maSignatureLineSuggestedSignerName + = rAttribs.getString(O_TOKEN(suggestedsigner), OUString()); + mrShapeModel.maSignatureLineSuggestedSignerTitle + = rAttribs.getString(O_TOKEN(suggestedsigner2), OUString()); + mrShapeModel.maSignatureLineSuggestedSignerEmail + = rAttribs.getString(O_TOKEN(suggestedsigneremail), OUString()); + mrShapeModel.maSignatureLineSigningInstructions + = rAttribs.getString(O_TOKEN(signinginstructions), OUString()); + 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()) + { + double fWidth = mrShape.getTypeModel().moCoordSize.get().first; + fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::pt); + double fHeight = mrShape.getTypeModel().moCoordSize.get().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.set( + 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& 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& 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.getString( R_TOKEN(id), OUString() )); + aInfo.maName = rAttribs.getString( W_TOKEN( name ), OUString() ); + 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 000000000..1162eb2aa --- /dev/null +++ b/oox/source/vml/vmltextbox.cxx @@ -0,0 +1,204 @@ +/* -*- 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace oox::vml { + +using namespace com::sun::star; + +TextFontModel::TextFontModel() +{ +} + +TextPortionModel::TextPortionModel( const TextParagraphModel& rParagraph, const TextFontModel& rFont, const OUString& rText ) : + maParagraph( rParagraph ), + maFont( rFont ), + maText( rText ) +{ +} + +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& xShape) const +{ + uno::Reference xTextAppend(xShape, uno::UNO_QUERY); + OUString sParaStyle; + bool bAmbiguousStyle = true; + + for (auto const& portion : maPortions) + { + beans::PropertyValue aPropertyValue; + std::vector aPropVec; + const TextParagraphModel& rParagraph = portion.maParagraph; + const TextFontModel& rFont = portion.maFont; + if (rFont.moName.has()) + { + aPropertyValue.Name = "CharFontName"; + aPropertyValue.Value <<= rFont.moName.get(); + aPropVec.push_back(aPropertyValue); + + aPropertyValue.Name = "CharFontNameAsian"; + aPropertyValue.Value <<= rFont.moNameAsian.get(); + aPropVec.push_back(aPropertyValue); + + aPropertyValue.Name = "CharFontNameComplex"; + aPropertyValue.Value <<= rFont.moNameComplex.get(); + aPropVec.push_back(aPropertyValue); + } + if (rFont.mobBold.has()) + { + aPropertyValue.Name = "CharWeight"; + aPropertyValue.Value <<= rFont.mobBold.get() ? awt::FontWeight::BOLD : awt::FontWeight::NORMAL; + aPropVec.push_back(aPropertyValue); + } + if (rFont.monSize.has()) + { + aPropertyValue.Name = "CharHeight"; + aPropertyValue.Value <<= double(rFont.monSize.get()) / 2.; + aPropVec.push_back(aPropertyValue); + } + if (rFont.monSpacing.has()) + { + 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.get()); + aPropVec.push_back(aPropertyValue); + } + if (rParagraph.moParaAdjust.has()) + { + style::ParagraphAdjust eAdjust = style::ParagraphAdjust_LEFT; + if (rParagraph.moParaAdjust.get() == "center") + eAdjust = style::ParagraphAdjust_CENTER; + else if (rParagraph.moParaAdjust.get() == "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() ) + sParaStyle = rParagraph.moParaStyleName.get(); + if ( bAmbiguousStyle ) + bAmbiguousStyle = false; // both empty parastyle and ambiguous can only be true at the first paragraph + else + bAmbiguousStyle = rParagraph.moParaStyleName.has(); // ambiguous if both default and specified style used. + } + else if ( !bAmbiguousStyle ) + { + if ( !rParagraph.moParaStyleName.has() ) + bAmbiguousStyle = true; // ambiguous if both specified and default style used. + else if ( rParagraph.moParaStyleName.get() != sParaStyle ) + bAmbiguousStyle = true; // ambiguous if two different styles specified. + } + if (rFont.moColor.has()) + { + aPropertyValue.Name = "CharColor"; + aPropertyValue.Value <<= rFont.moColor.get().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 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 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 000000000..d043ebc87 --- /dev/null +++ b/oox/source/vml/vmltextboxcontext.cxx @@ -0,0 +1,287 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace oox::vml { + +using ::oox::core::ContextHandler2; +using ::oox::core::ContextHandler2Helper; +using ::oox::core::ContextHandlerRef; + +TextPortionContext::TextPortionContext( ContextHandler2Helper const & rParent, + TextBox& rTextBox, TextParagraphModel const & rParagraph, const TextFontModel& rParentFont, + sal_Int32 nElement, const AttributeList& rAttribs ) : + ContextHandler2( rParent ), + mrTextBox( rTextBox ), + maParagraph( rParagraph ), + maFont( rParentFont ), + 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 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 or elements" ); + maFont.monEscapement = nElement; + break; + case XML_b: + OSL_ENSURE( !maFont.mobBold, "TextPortionContext::TextPortionContext - nested elements" ); + maFont.mobBold = true; + break; + case XML_i: + OSL_ENSURE( !maFont.mobItalic, "TextPortionContext::TextPortionContext - nested elements" ); + maFont.mobItalic = true; + break; + case XML_s: + OSL_ENSURE( !maFont.mobStrikeout, "TextPortionContext::TextPortionContext - nested elements" ); + maFont.mobStrikeout = true; + break; + case OOX_TOKEN(dml, blip): + { + OptValue oRelId = rAttribs.getString(R_TOKEN(embed)); + if (oRelId.has()) + mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.get()); + } + break; + case VML_TOKEN(imagedata): + { + OptValue oRelId = rAttribs.getString(R_TOKEN(id)); + if (oRelId.has()) + mrTextBox.mrTypeModel.moGraphicPath = getFragmentPathFromRelId(oRelId.get()); + } + 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 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: + +
+ abc + + def +
+ + 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.getString( XML_insetmode ).get() != "auto" ) + { + OUString inset = rAttribs.getString( XML_inset ).get(); + 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.getString( XML_style, OUString() ); + 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: */ -- cgit v1.2.3