Bump version to 24.04.3.4
[LibreOffice.git] / oox / source / export / vmlexport.cxx
blob8438befa62af800bf16b85c6f20b7f48d4a546cc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_folders.h>
21 #include <rtl/bootstrap.hxx>
22 #include <svl/itemset.hxx>
23 #include <oox/export/drawingml.hxx>
24 #include <oox/export/vmlexport.hxx>
25 #include <sax/fastattribs.hxx>
27 #include <oox/token/tokens.hxx>
29 #include <rtl/strbuf.hxx>
30 #include <rtl/ustring.hxx>
31 #include <sal/log.hxx>
33 #include <tools/stream.hxx>
34 #include <comphelper/sequenceashashmap.hxx>
35 #include <svx/msdffdef.hxx>
36 #include <svx/svdotext.hxx>
37 #include <svx/svdograf.hxx>
38 #include <svx/sdmetitm.hxx>
39 #include <utility>
40 #include <vcl/cvtgrf.hxx>
41 #include <filter/msfilter/msdffimp.hxx>
42 #include <filter/msfilter/util.hxx>
43 #include <filter/msfilter/escherex.hxx>
44 #include <o3tl/string_view.hxx>
45 #include <drawingml/fontworkhelpers.hxx>
47 #include <com/sun/star/beans/XPropertySet.hpp>
48 #include <com/sun/star/beans/XPropertySetInfo.hpp>
49 #include <com/sun/star/drawing/XShape.hpp>
50 #include <com/sun/star/text/HoriOrientation.hpp>
51 #include <com/sun/star/text/VertOrientation.hpp>
52 #include <com/sun/star/text/RelOrientation.hpp>
53 #include <com/sun/star/text/WritingMode2.hpp>
54 #include <com/sun/star/text/XTextFrame.hpp>
56 #include <cstdio>
58 using namespace sax_fastparser;
59 using namespace oox::vml;
60 using namespace com::sun::star;
62 const sal_Int32 Tag_Container = 44444;
63 const sal_Int32 Tag_Commit = 44445;
65 VMLExport::VMLExport( ::sax_fastparser::FSHelperPtr pSerializer, VMLTextExport* pTextExport )
66 : EscherEx( std::make_shared<EscherExGlobal>(), nullptr, /*bOOXML=*/true )
67 , m_pSerializer(std::move( pSerializer ))
68 , m_pTextExport( pTextExport )
69 , m_eHOri( 0 )
70 , m_eVOri( 0 )
71 , m_eHRel( 0 )
72 , m_eVRel( 0 )
73 , m_bInline( false )
74 , m_pSdrObject( nullptr )
75 , m_nShapeType( ESCHER_ShpInst_Nil )
76 , m_nShapeFlags(ShapeFlag::NONE)
77 , m_ShapeStyle( 200 )
78 , m_aShapeTypeWritten( ESCHER_ShpInst_COUNT )
79 , m_bSkipwzName( false )
80 , m_bUseHashMarkForType( false )
81 , m_bOverrideShapeIdGeneration( false )
82 , m_nShapeIDCounter( 0 )
84 mnGroupLevel = 1;
87 void VMLExport::SetFS( const ::sax_fastparser::FSHelperPtr& pSerializer )
89 m_pSerializer = pSerializer;
92 VMLExport::~VMLExport()
96 void VMLExport::OpenContainer( sal_uInt16 nEscherContainer, int nRecInstance )
98 EscherEx::OpenContainer( nEscherContainer, nRecInstance );
100 if ( nEscherContainer != ESCHER_SpContainer )
101 return;
103 // opening a shape container
104 SAL_WARN_IF(m_nShapeType != ESCHER_ShpInst_Nil, "oox.vml", "opening shape inside of a shape!");
105 m_nShapeType = ESCHER_ShpInst_Nil;
106 m_pShapeAttrList = FastSerializerHelper::createAttrList();
108 m_ShapeStyle.setLength(0);
109 m_ShapeStyle.ensureCapacity(200);
111 // postpone the output so that we are able to write even the elements
112 // that we learn inside Commit()
113 m_pSerializer->mark(Tag_Container);
116 void VMLExport::CloseContainer()
118 if ( mRecTypes.back() == ESCHER_SpContainer )
120 // write the shape now when we have all the info
121 sal_Int32 nShapeElement = StartShape();
123 m_pSerializer->mergeTopMarks(Tag_Container);
125 EndShape( nShapeElement );
127 // cleanup
128 m_nShapeType = ESCHER_ShpInst_Nil;
129 m_pShapeAttrList = nullptr;
132 EscherEx::CloseContainer();
135 sal_uInt32 VMLExport::EnterGroup( const OUString& rShapeName, const tools::Rectangle* pRect )
137 sal_uInt32 nShapeId = GenerateShapeId();
139 OStringBuffer aStyle( 200 );
140 rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
142 pAttrList->add( XML_id, ShapeIdString( nShapeId ) );
144 if ( rShapeName.getLength() )
145 pAttrList->add( XML_alt, rShapeName );
147 bool rbAbsolutePos = true;
148 //editAs
149 OUString rEditAs = EscherEx::GetEditAs();
150 if (!rEditAs.isEmpty())
152 pAttrList->add(XML_editas, rEditAs);
153 rbAbsolutePos = false;
156 // style
157 if ( pRect )
158 AddRectangleDimensions( aStyle, *pRect, rbAbsolutePos );
160 if ( !aStyle.isEmpty() )
161 pAttrList->add( XML_style, aStyle );
163 // coordorigin/coordsize
164 if ( pRect && ( mnGroupLevel == 1 ) )
166 pAttrList->add( XML_coordorigin,
167 OString::number( pRect->Left() ) + "," + OString::number( pRect->Top() ) );
169 pAttrList->add( XML_coordsize,
170 OString::number( pRect->Right() - pRect->Left() ) + "," +
171 OString::number( pRect->Bottom() - pRect->Top() ) );
174 m_pSerializer->startElementNS( XML_v, XML_group, pAttrList );
176 mnGroupLevel++;
177 return nShapeId;
180 void VMLExport::LeaveGroup()
182 --mnGroupLevel;
183 m_pSerializer->endElementNS( XML_v, XML_group );
186 void VMLExport::AddShape( sal_uInt32 nShapeType, ShapeFlag nShapeFlags, sal_uInt32 nShapeId )
188 m_nShapeType = nShapeType;
189 m_nShapeFlags = nShapeFlags;
191 m_sShapeId = ShapeIdString( nShapeId );
192 if (m_sShapeId.startsWith("_x0000_"))
194 // xml_id must be set elsewhere. The id is critical for matching VBA macros etc,
195 // and the spid is critical to link to the shape number elsewhere.
196 m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId );
198 else if (IsWaterMarkShape(m_pSdrObject->GetName()))
200 // Shape is a watermark object - keep the original shape's name
201 // because Microsoft detects if it is a watermark by the actual name
202 m_pShapeAttrList->add( XML_id, m_pSdrObject->GetName() );
203 // also ('o:spid')
204 m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId );
206 else
208 m_pShapeAttrList->add(XML_id, m_sShapeId);
212 bool VMLExport::IsWaterMarkShape(std::u16string_view rStr)
214 if (rStr.empty() ) return false;
216 return o3tl::starts_with(rStr, u"PowerPlusWaterMarkObject") || o3tl::starts_with(rStr, u"WordPictureWatermark");
219 void VMLExport::OverrideShapeIDGen(bool bOverrideShapeIdGen, const OString& sShapeIDPrefix)
221 m_bOverrideShapeIdGeneration = bOverrideShapeIdGen;
222 if(bOverrideShapeIdGen)
224 assert(!sShapeIDPrefix.isEmpty());
225 m_sShapeIDPrefix = sShapeIDPrefix;
227 else
228 m_sShapeIDPrefix.clear();
231 static void impl_AddArrowHead( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
233 if ( !pAttrList )
234 return;
236 const char *pArrowHead = nullptr;
237 switch ( nValue )
239 case ESCHER_LineNoEnd: pArrowHead = "none"; break;
240 case ESCHER_LineArrowEnd: pArrowHead = "block"; break;
241 case ESCHER_LineArrowStealthEnd: pArrowHead = "classic"; break;
242 case ESCHER_LineArrowDiamondEnd: pArrowHead = "diamond"; break;
243 case ESCHER_LineArrowOvalEnd: pArrowHead = "oval"; break;
244 case ESCHER_LineArrowOpenEnd: pArrowHead = "open"; break;
247 if ( pArrowHead )
248 pAttrList->add( nElement, pArrowHead );
251 static void impl_AddArrowLength( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
253 if ( !pAttrList )
254 return;
256 const char *pArrowLength = nullptr;
257 switch ( nValue )
259 case ESCHER_LineShortArrow: pArrowLength = "short"; break;
260 case ESCHER_LineMediumLenArrow: pArrowLength = "medium"; break;
261 case ESCHER_LineLongArrow: pArrowLength = "long"; break;
264 if ( pArrowLength )
265 pAttrList->add( nElement, pArrowLength );
268 static void impl_AddArrowWidth( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
270 if ( !pAttrList )
271 return;
273 const char *pArrowWidth = nullptr;
274 switch ( nValue )
276 case ESCHER_LineNarrowArrow: pArrowWidth = "narrow"; break;
277 case ESCHER_LineMediumWidthArrow: pArrowWidth = "medium"; break;
278 case ESCHER_LineWideArrow: pArrowWidth = "wide"; break;
281 if ( pArrowWidth )
282 pAttrList->add( nElement, pArrowWidth );
285 static void impl_AddBool( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, bool bValue )
287 if ( !pAttrList )
288 return;
290 pAttrList->add( nElement, bValue? "t": "f" );
293 static void impl_AddColor( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nColor )
295 SAL_WARN_IF( nColor & 0xFF000000 , "oox.vml" , "TODO: this is not a RGB value!");
297 if ( !pAttrList || ( nColor & 0xFF000000 ) )
298 return;
300 nColor = ( ( nColor & 0xFF ) << 16 ) + ( nColor & 0xFF00 ) + ( ( nColor & 0xFF0000 ) >> 16 );
302 const char *pColor = nullptr;
303 char pRgbColor[10];
304 switch ( nColor )
306 case 0x000000: pColor = "black"; break;
307 case 0xC0C0C0: pColor = "silver"; break;
308 case 0x808080: pColor = "gray"; break;
309 case 0xFFFFFF: pColor = "white"; break;
310 case 0x800000: pColor = "maroon"; break;
311 case 0xFF0000: pColor = "red"; break;
312 case 0x800080: pColor = "purple"; break;
313 case 0xFF00FF: pColor = "fuchsia"; break;
314 case 0x008000: pColor = "green"; break;
315 case 0x00FF00: pColor = "lime"; break;
316 case 0x808000: pColor = "olive"; break;
317 case 0xFFFF00: pColor = "yellow"; break;
318 case 0x000080: pColor = "navy"; break;
319 case 0x0000FF: pColor = "blue"; break;
320 case 0x008080: pColor = "teal"; break;
321 case 0x00FFFF: pColor = "aqua"; break;
322 default:
324 snprintf( pRgbColor, sizeof( pRgbColor ), "#%06x", static_cast< unsigned int >( nColor ) ); // not too handy to use OString::valueOf() here :-(
325 pColor = pRgbColor;
327 break;
330 pAttrList->add( nElement, pColor );
333 static void impl_AddInt( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
335 if ( !pAttrList )
336 return;
338 pAttrList->add( nElement, OString::number( nValue ) );
341 static sal_uInt16 impl_GetUInt16( const sal_uInt8* &pVal )
343 sal_uInt16 nRet = *pVal++;
344 nRet += ( *pVal++ ) << 8;
345 return nRet;
348 static sal_Int32 impl_GetPointComponent( const sal_uInt8* &pVal, sal_uInt16 nPointSize )
350 sal_Int32 nRet = 0;
351 if ( ( nPointSize == 0xfff0 ) || ( nPointSize == 4 ) )
353 sal_uInt16 nUnsigned = *pVal++;
354 nUnsigned += ( *pVal++ ) << 8;
356 nRet = sal_Int16( nUnsigned );
358 else if ( nPointSize == 8 )
360 sal_uInt32 nUnsigned = *pVal++;
361 nUnsigned += ( *pVal++ ) << 8;
362 nUnsigned += ( *pVal++ ) << 16;
363 nUnsigned += ( *pVal++ ) << 24;
365 nRet = nUnsigned;
368 return nRet;
371 void VMLExport::AddSdrObjectVMLObject( const SdrObject& rObj)
373 m_pSdrObject = &rObj;
375 void VMLExport::Commit( EscherPropertyContainer& rProps, const tools::Rectangle& rRect )
377 if ( m_nShapeType == ESCHER_ShpInst_Nil )
378 return;
380 // postpone the output of the embedded elements so that they are written
381 // inside the shapes
382 m_pSerializer->mark(Tag_Commit);
384 // dimensions
385 if ( m_nShapeType == ESCHER_ShpInst_Line )
386 AddLineDimensions( rRect );
387 else
389 if ( IsWaterMarkShape( m_pSdrObject->GetName() ) )
391 // Watermark need some padding to be compatible with MSO
392 tools::Long nPaddingY = 0;
393 const SfxItemSet& rSet = m_pSdrObject->GetMergedItemSet();
394 if ( const SdrMetricItem* pItem = rSet.GetItem( SDRATTR_TEXT_UPPERDIST ) )
395 nPaddingY += pItem->GetValue();
397 tools::Rectangle aRect( rRect );
398 aRect.setHeight( aRect.getOpenHeight() + nPaddingY );
399 AddRectangleDimensions( m_ShapeStyle, aRect );
401 else
402 AddRectangleDimensions( m_ShapeStyle, rRect );
405 // properties
406 // The numbers of defines ESCHER_Prop_foo and DFF_Prop_foo correspond to the PIDs in
407 // 'Microsoft Office Drawing 97-2007 Binary Format Specification'.
408 // The property values are set by EscherPropertyContainer::CreateCustomShapeProperties() method.
409 bool bAlreadyWritten[ 0xFFF ] = {};
410 const EscherProperties &rOpts = rProps.GetOpts();
411 for (auto const& opt : rOpts)
413 sal_uInt16 nId = ( opt.nPropId & 0x0FFF );
415 if ( bAlreadyWritten[ nId ] )
416 continue;
418 switch ( nId )
420 case ESCHER_Prop_WrapText: // 133
422 const char *pWrapType = nullptr;
423 switch ( opt.nPropValue )
425 case ESCHER_WrapSquare:
426 case ESCHER_WrapByPoints: pWrapType = "square"; break; // these two are equivalent according to the docu
427 case ESCHER_WrapNone: pWrapType = "none"; break;
428 case ESCHER_WrapTopBottom:
429 case ESCHER_WrapThrough:
430 break; // last two are *undefined* in MS-ODRAW, don't exist in VML
432 if ( pWrapType )
434 m_ShapeStyle.append(";mso-wrap-style:");
435 m_ShapeStyle.append(pWrapType);
438 bAlreadyWritten[ ESCHER_Prop_WrapText ] = true;
439 break;
441 case ESCHER_Prop_AnchorText: // 135
443 char const* pValue(nullptr);
444 switch (opt.nPropValue)
446 case ESCHER_AnchorTop:
447 pValue = "top";
448 break;
449 case ESCHER_AnchorMiddle:
450 pValue = "middle";
451 break;
452 case ESCHER_AnchorBottom:
453 pValue = "bottom";
454 break;
455 case ESCHER_AnchorTopCentered:
456 pValue = "top-center";
457 break;
458 case ESCHER_AnchorMiddleCentered:
459 pValue = "middle-center";
460 break;
461 case ESCHER_AnchorBottomCentered:
462 pValue = "bottom-center";
463 break;
464 case ESCHER_AnchorTopBaseline:
465 pValue = "top-baseline";
466 break;
467 case ESCHER_AnchorBottomBaseline:
468 pValue = "bottom-baseline";
469 break;
470 case ESCHER_AnchorTopCenteredBaseline:
471 pValue = "top-center-baseline";
472 break;
473 case ESCHER_AnchorBottomCenteredBaseline:
474 pValue = "bottom-center-baseline";
475 break;
477 m_ShapeStyle.append(";v-text-anchor:");
478 m_ShapeStyle.append(pValue);
480 break;
482 case ESCHER_Prop_txflTextFlow: // 136
484 // at least "bottom-to-top" only has an effect when it's on the v:textbox element, not on v:shape
485 assert(m_TextboxStyle.isEmpty());
486 switch (opt.nPropValue)
488 case ESCHER_txflHorzN:
489 m_TextboxStyle.append("layout-flow:horizontal");
490 break;
491 case ESCHER_txflTtoBA:
492 m_TextboxStyle.append("layout-flow:vertical");
493 break;
494 case ESCHER_txflBtoT:
495 m_TextboxStyle.append("mso-layout-flow-alt:bottom-to-top");
496 break;
497 default:
498 assert(false); // unimplemented in escher export
499 break;
502 break;
504 // coordorigin
505 case ESCHER_Prop_geoLeft: // 320
506 case ESCHER_Prop_geoTop: // 321
508 sal_uInt32 nLeft = 0, nTop = 0;
510 if ( nId == ESCHER_Prop_geoLeft )
512 nLeft = opt.nPropValue;
513 rProps.GetOpt( ESCHER_Prop_geoTop, nTop );
515 else
517 nTop = opt.nPropValue;
518 rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft );
520 if(nTop!=0 && nLeft!=0)
521 m_pShapeAttrList->add( XML_coordorigin,
522 OString::number( nLeft ) + "," + OString::number( nTop ) );
524 bAlreadyWritten[ ESCHER_Prop_geoLeft ] = true;
525 bAlreadyWritten[ ESCHER_Prop_geoTop ] = true;
526 break;
528 // coordsize
529 case ESCHER_Prop_geoRight: // 322
530 case ESCHER_Prop_geoBottom: // 323
532 sal_uInt32 nLeft = 0, nRight = 0, nTop = 0, nBottom = 0;
533 rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft );
534 rProps.GetOpt( ESCHER_Prop_geoTop, nTop );
536 if ( nId == ESCHER_Prop_geoRight )
538 nRight = opt.nPropValue;
539 rProps.GetOpt( ESCHER_Prop_geoBottom, nBottom );
541 else
543 nBottom = opt.nPropValue;
544 rProps.GetOpt( ESCHER_Prop_geoRight, nRight );
547 if(nBottom!=0 && nRight!=0 )
548 m_pShapeAttrList->add( XML_coordsize,
549 OString::number( nRight - nLeft ) + "," + OString::number( nBottom - nTop ) );
551 bAlreadyWritten[ ESCHER_Prop_geoRight ] = true;
552 bAlreadyWritten[ ESCHER_Prop_geoBottom ] = true;
553 break;
555 case ESCHER_Prop_pVertices: // 325
556 case ESCHER_Prop_pSegmentInfo: // 326
558 EscherPropSortStruct aVertices;
559 EscherPropSortStruct aSegments;
561 if ( rProps.GetOpt( ESCHER_Prop_pVertices, aVertices ) &&
562 rProps.GetOpt( ESCHER_Prop_pSegmentInfo, aSegments ) )
564 const sal_uInt8 *pVerticesIt = aVertices.nProp.data() + 6;
565 const sal_uInt8 *pSegmentIt = aSegments.nProp.data();
566 OStringBuffer aPath( 512 );
568 sal_uInt16 nPointSize = aVertices.nProp[4] + ( aVertices.nProp[5] << 8 );
570 // number of segments
571 sal_uInt16 nSegments = impl_GetUInt16( pSegmentIt );
572 pSegmentIt += 4;
574 for ( ; nSegments; --nSegments )
576 sal_uInt16 nSeg = impl_GetUInt16( pSegmentIt );
578 // The segment type is stored in the upper 3 bits
579 // and segment count is stored in the lower 13
580 // bits.
581 unsigned char nSegmentType = (nSeg & 0xE000) >> 13;
582 unsigned short nSegmentCount = nSeg & 0x03FF;
584 switch (nSegmentType)
586 case msopathMoveTo:
588 sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize );
589 sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize );
590 if (nX >= 0 && nY >= 0 )
591 aPath.append( "m" + OString::number( nX ) + "," + OString::number( nY ) );
592 break;
594 case msopathClientEscape:
595 break;
596 case msopathEscape:
598 // If the segment type is msopathEscape, the lower 13 bits are
599 // divided in a 5 bit escape code and 8 bit
600 // vertex count (not segment count!)
601 unsigned char nEscapeCode = (nSegmentCount & 0x1F00) >> 8;
602 unsigned char nVertexCount = nSegmentCount & 0x00FF;
603 pVerticesIt += nVertexCount;
605 switch (nEscapeCode)
607 case 0xa: // nofill
608 aPath.append( "nf" );
609 break;
610 case 0xb: // nostroke
611 aPath.append( "ns" );
612 break;
615 break;
617 case msopathLineTo:
618 for (unsigned short i = 0; i < nSegmentCount; ++i)
620 sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize );
621 sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize );
622 aPath.append( "l" + OString::number( nX ) + "," + OString::number( nY ) );
624 break;
625 case msopathCurveTo:
626 for (unsigned short i = 0; i < nSegmentCount; ++i)
628 sal_Int32 nX1 = impl_GetPointComponent( pVerticesIt, nPointSize );
629 sal_Int32 nY1 = impl_GetPointComponent( pVerticesIt, nPointSize );
630 sal_Int32 nX2 = impl_GetPointComponent( pVerticesIt, nPointSize );
631 sal_Int32 nY2 = impl_GetPointComponent( pVerticesIt, nPointSize );
632 sal_Int32 nX3 = impl_GetPointComponent( pVerticesIt, nPointSize );
633 sal_Int32 nY3 = impl_GetPointComponent( pVerticesIt, nPointSize );
634 aPath.append( "c" + OString::number( nX1 ) + "," + OString::number( nY1 ) + "," +
635 OString::number( nX2 ) + "," + OString::number( nY2 ) + "," +
636 OString::number( nX3 ) + "," + OString::number( nY3 ) );
638 break;
639 case msopathClose:
640 aPath.append( "x" );
641 break;
642 case msopathEnd:
643 aPath.append( "e" );
644 break;
645 default:
646 SAL_WARN("oox", "Totally b0rked");
647 break;
648 case msopathInvalid:
649 SAL_WARN("oox", "Invalid - should never be found");
650 break;
653 OString pathString = aPath.makeStringAndClear();
654 if ( !pathString.isEmpty() && pathString != "xe" )
655 m_pShapeAttrList->add( XML_path, pathString );
657 else
658 SAL_WARN("oox.vml", "unhandled shape path, missing either pVertices or pSegmentInfo.");
660 bAlreadyWritten[ ESCHER_Prop_pVertices ] = true;
661 bAlreadyWritten[ ESCHER_Prop_pSegmentInfo ] = true;
662 break;
664 case ESCHER_Prop_fillType: // 384
665 case ESCHER_Prop_fillColor: // 385
666 case ESCHER_Prop_fillBackColor: // 387
667 case ESCHER_Prop_fillBlip: // 390
668 case ESCHER_Prop_fNoFillHitTest: // 447
669 case ESCHER_Prop_fillOpacity: // 386
671 sal_uInt32 nValue;
672 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList
673 = FastSerializerHelper::createAttrList();
675 bool imageData = false;
676 EscherPropSortStruct aStruct;
677 const SdrGrafObj* pSdrGrafObj = dynamic_cast<const SdrGrafObj*>(m_pSdrObject);
679 if (pSdrGrafObj && pSdrGrafObj->isSignatureLine() && m_pTextExport)
681 rtl::Reference<sax_fastparser::FastAttributeList> pAttrListSignatureLine
682 = FastSerializerHelper::createAttrList();
683 pAttrListSignatureLine->add(XML_issignatureline, "t");
684 if (!pSdrGrafObj->getSignatureLineId().isEmpty())
686 pAttrListSignatureLine->add(
687 XML_id, pSdrGrafObj->getSignatureLineId());
689 if (!pSdrGrafObj->getSignatureLineSuggestedSignerName().isEmpty())
691 pAttrListSignatureLine->add(
692 FSNS(XML_o, XML_suggestedsigner),
693 pSdrGrafObj->getSignatureLineSuggestedSignerName());
695 if (!pSdrGrafObj->getSignatureLineSuggestedSignerTitle().isEmpty())
697 pAttrListSignatureLine->add(
698 FSNS(XML_o, XML_suggestedsigner2),
699 pSdrGrafObj->getSignatureLineSuggestedSignerTitle());
701 if (!pSdrGrafObj->getSignatureLineSuggestedSignerEmail().isEmpty())
703 pAttrListSignatureLine->add(
704 FSNS(XML_o, XML_suggestedsigneremail),
705 pSdrGrafObj->getSignatureLineSuggestedSignerEmail());
707 if (!pSdrGrafObj->getSignatureLineSigningInstructions().isEmpty())
709 pAttrListSignatureLine->add(XML_signinginstructionsset, "t");
710 pAttrListSignatureLine->add(
711 FSNS(XML_o, XML_signinginstructions),
712 pSdrGrafObj->getSignatureLineSigningInstructions());
714 pAttrListSignatureLine->add(
715 XML_showsigndate,
716 pSdrGrafObj->isSignatureLineShowSignDate() ? "t" : "f");
717 pAttrListSignatureLine->add(
718 XML_allowcomments,
719 pSdrGrafObj->isSignatureLineCanAddComment() ? "t" : "f");
721 m_pSerializer->singleElementNS(
722 XML_o, XML_signatureline,
723 pAttrListSignatureLine);
725 // Get signature line graphic
726 const uno::Reference<graphic::XGraphic>& xGraphic
727 = pSdrGrafObj->getSignatureLineUnsignedGraphic();
728 Graphic aGraphic(xGraphic);
729 OUString aImageId = m_pTextExport->GetDrawingML().writeGraphicToStorage(aGraphic, false);
730 pAttrList->add(FSNS(XML_r, XML_id), aImageId);
731 imageData = true;
733 else if (rProps.GetOpt(ESCHER_Prop_fillBlip, aStruct) && m_pTextExport)
735 SvMemoryStream aStream;
736 // The first bytes are WW8-specific, we're only interested in the PNG
737 int nHeaderSize = 25;
738 aStream.WriteBytes(aStruct.nProp.data() + nHeaderSize,
739 aStruct.nProp.size() - nHeaderSize);
740 aStream.Seek(0);
741 Graphic aGraphic;
742 GraphicConverter::Import(aStream, aGraphic);
743 OUString aImageId = m_pTextExport->GetDrawingML().writeGraphicToStorage(aGraphic, false);
744 if (!aImageId.isEmpty())
746 pAttrList->add(FSNS(XML_r, XML_id), aImageId);
747 imageData = true;
751 if (rProps.GetOpt(ESCHER_Prop_fNoFillHitTest, nValue))
752 impl_AddBool(pAttrList.get(), FSNS(XML_o, XML_detectmouseclick), nValue != 0);
754 if (imageData && ((pSdrGrafObj && pSdrGrafObj->isSignatureLine())
755 || m_nShapeType == ESCHER_ShpInst_PictureFrame))
756 m_pSerializer->singleElementNS( XML_v, XML_imagedata, pAttrList );
757 else
759 if ( rProps.GetOpt( ESCHER_Prop_fillType, nValue ) )
761 const char *pFillType = nullptr;
762 switch ( nValue )
764 case ESCHER_FillSolid: pFillType = "solid"; break;
765 // TODO case ESCHER_FillPattern: pFillType = ""; break;
766 case ESCHER_FillTexture: pFillType = "tile"; break;
767 case ESCHER_FillPicture: pFillType = "frame"; break;
768 // TODO case ESCHER_FillShade: pFillType = ""; break;
769 // TODO case ESCHER_FillShadeCenter: pFillType = ""; break;
770 // TODO case ESCHER_FillShadeShape: pFillType = ""; break;
771 // TODO case ESCHER_FillShadeScale: pFillType = ""; break;
772 // TODO case ESCHER_FillShadeTitle: pFillType = ""; break;
773 // TODO case ESCHER_FillBackground: pFillType = ""; break;
774 default:
775 SAL_INFO("oox.vml", "Unhandled fill type: " << nValue);
776 break;
778 if ( pFillType )
779 pAttrList->add( XML_type, pFillType );
781 else if (!rProps.GetOpt(ESCHER_Prop_fillColor, nValue))
782 pAttrList->add( XML_on, "false" );
784 if ( rProps.GetOpt( ESCHER_Prop_fillColor, nValue ) )
785 impl_AddColor( m_pShapeAttrList.get(), XML_fillcolor, nValue );
787 if ( rProps.GetOpt( ESCHER_Prop_fillBackColor, nValue ) )
788 impl_AddColor( pAttrList.get(), XML_color2, nValue );
790 if (rProps.GetOpt(ESCHER_Prop_fillOpacity, nValue))
791 // Partly undo the transformation at the end of EscherPropertyContainer::CreateFillProperties(): VML opacity is 0..1.
792 pAttrList->add(XML_opacity, OString::number(double((nValue * 100) >> 16) / 100));
793 m_pSerializer->singleElementNS( XML_v, XML_fill, pAttrList );
797 bAlreadyWritten[ ESCHER_Prop_fillType ] = true;
798 bAlreadyWritten[ ESCHER_Prop_fillColor ] = true;
799 bAlreadyWritten[ ESCHER_Prop_fillBackColor ] = true;
800 bAlreadyWritten[ ESCHER_Prop_fillBlip ] = true;
801 bAlreadyWritten[ ESCHER_Prop_fNoFillHitTest ] = true;
802 bAlreadyWritten[ ESCHER_Prop_fillOpacity ] = true;
803 break;
805 case ESCHER_Prop_lineColor: // 448
806 case ESCHER_Prop_lineWidth: // 459
807 case ESCHER_Prop_lineDashing: // 462
808 case ESCHER_Prop_lineStartArrowhead: // 464
809 case ESCHER_Prop_lineEndArrowhead: // 465
810 case ESCHER_Prop_lineStartArrowWidth: // 466
811 case ESCHER_Prop_lineStartArrowLength: // 467
812 case ESCHER_Prop_lineEndArrowWidth: // 468
813 case ESCHER_Prop_lineEndArrowLength: // 469
814 case ESCHER_Prop_lineJoinStyle: // 470
815 case ESCHER_Prop_lineEndCapStyle: // 471
817 sal_uInt32 nValue;
818 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
820 if ( rProps.GetOpt( ESCHER_Prop_lineColor, nValue ) )
821 impl_AddColor( pAttrList.get(), XML_color, nValue );
823 if ( rProps.GetOpt( ESCHER_Prop_lineWidth, nValue ) )
824 impl_AddInt( pAttrList.get(), XML_weight, nValue );
826 if ( rProps.GetOpt( ESCHER_Prop_lineDashing, nValue ) )
828 const char *pDashStyle = nullptr;
829 switch ( nValue )
831 case ESCHER_LineSolid: pDashStyle = "solid"; break;
832 case ESCHER_LineDashSys: pDashStyle = "shortdash"; break;
833 case ESCHER_LineDotSys: pDashStyle = "shortdot"; break;
834 case ESCHER_LineDashDotSys: pDashStyle = "shortdashdot"; break;
835 case ESCHER_LineDashDotDotSys: pDashStyle = "shortdashdotdot"; break;
836 case ESCHER_LineDotGEL: pDashStyle = "dot"; break;
837 case ESCHER_LineDashGEL: pDashStyle = "dash"; break;
838 case ESCHER_LineLongDashGEL: pDashStyle = "longdash"; break;
839 case ESCHER_LineDashDotGEL: pDashStyle = "dashdot"; break;
840 case ESCHER_LineLongDashDotGEL: pDashStyle = "longdashdot"; break;
841 case ESCHER_LineLongDashDotDotGEL: pDashStyle = "longdashdotdot"; break;
843 if ( pDashStyle )
844 pAttrList->add( XML_dashstyle, pDashStyle );
847 if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowhead, nValue ) )
848 impl_AddArrowHead( pAttrList.get(), XML_startarrow, nValue );
850 if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowhead, nValue ) )
851 impl_AddArrowHead( pAttrList.get(), XML_endarrow, nValue );
853 if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowWidth, nValue ) )
854 impl_AddArrowWidth( pAttrList.get(), XML_startarrowwidth, nValue );
856 if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowLength, nValue ) )
857 impl_AddArrowLength( pAttrList.get(), XML_startarrowlength, nValue );
859 if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowWidth, nValue ) )
860 impl_AddArrowWidth( pAttrList.get(), XML_endarrowwidth, nValue );
862 if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowLength, nValue ) )
863 impl_AddArrowLength( pAttrList.get(), XML_endarrowlength, nValue );
865 if ( rProps.GetOpt( ESCHER_Prop_lineJoinStyle, nValue ) )
867 const char *pJoinStyle = nullptr;
868 switch ( nValue )
870 case ESCHER_LineJoinBevel: pJoinStyle = "bevel"; break;
871 case ESCHER_LineJoinMiter: pJoinStyle = "miter"; break;
872 case ESCHER_LineJoinRound: pJoinStyle = "round"; break;
874 if ( pJoinStyle )
875 pAttrList->add( XML_joinstyle, pJoinStyle );
878 if ( rProps.GetOpt( ESCHER_Prop_lineEndCapStyle, nValue ) )
880 const char *pEndCap = nullptr;
881 switch ( nValue )
883 case ESCHER_LineEndCapRound: pEndCap = "round"; break;
884 case ESCHER_LineEndCapSquare: pEndCap = "square"; break;
885 case ESCHER_LineEndCapFlat: pEndCap = "flat"; break;
887 if ( pEndCap )
888 pAttrList->add( XML_endcap, pEndCap );
891 m_pSerializer->singleElementNS( XML_v, XML_stroke, pAttrList );
893 bAlreadyWritten[ ESCHER_Prop_lineColor ] = true;
894 bAlreadyWritten[ ESCHER_Prop_lineWidth ] = true;
895 bAlreadyWritten[ ESCHER_Prop_lineDashing ] = true;
896 bAlreadyWritten[ ESCHER_Prop_lineStartArrowhead ] = true;
897 bAlreadyWritten[ ESCHER_Prop_lineEndArrowhead ] = true;
898 bAlreadyWritten[ ESCHER_Prop_lineStartArrowWidth ] = true;
899 bAlreadyWritten[ ESCHER_Prop_lineStartArrowLength ] = true;
900 bAlreadyWritten[ ESCHER_Prop_lineEndArrowWidth ] = true;
901 bAlreadyWritten[ ESCHER_Prop_lineEndArrowLength ] = true;
902 bAlreadyWritten[ ESCHER_Prop_lineJoinStyle ] = true;
903 bAlreadyWritten[ ESCHER_Prop_lineEndCapStyle ] = true;
904 break;
906 case ESCHER_Prop_fHidden:
907 if ( !opt.nPropValue )
908 m_ShapeStyle.append( ";visibility:hidden" );
909 break;
910 case ESCHER_Prop_shadowColor:
911 case ESCHER_Prop_fshadowObscured:
913 sal_uInt32 nValue = 0;
914 bool bShadow = false;
915 bool bObscured = false;
916 if ( rProps.GetOpt( ESCHER_Prop_fshadowObscured, nValue ) )
918 bShadow = (( nValue & 0x20002 ) == 0x20002 );
919 bObscured = (( nValue & 0x10001 ) == 0x10001 );
921 if ( bShadow )
923 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
924 impl_AddBool( pAttrList.get(), XML_on, bShadow );
925 impl_AddBool( pAttrList.get(), XML_obscured, bObscured );
927 if ( rProps.GetOpt( ESCHER_Prop_shadowColor, nValue ) )
928 impl_AddColor( pAttrList.get(), XML_color, nValue );
930 m_pSerializer->singleElementNS( XML_v, XML_shadow, pAttrList );
931 bAlreadyWritten[ ESCHER_Prop_fshadowObscured ] = true;
932 bAlreadyWritten[ ESCHER_Prop_shadowColor ] = true;
935 break;
936 case ESCHER_Prop_gtextUNICODE:
937 case ESCHER_Prop_gtextFont:
939 EscherPropSortStruct aUnicode;
940 if (rProps.GetOpt(ESCHER_Prop_gtextUNICODE, aUnicode))
942 SvMemoryStream aStream;
944 if(!opt.nProp.empty())
946 aStream.WriteBytes(opt.nProp.data(), opt.nProp.size());
949 aStream.Seek(0);
950 OUString aTextPathString = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true);
951 aStream.Seek(0);
953 m_pSerializer->singleElementNS(XML_v, XML_path, XML_textpathok, "t");
955 rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
956 pAttrList->add(XML_on, "t");
957 pAttrList->add(XML_fitshape, "t");
958 pAttrList->add(XML_string, aTextPathString);
959 EscherPropSortStruct aFont;
960 OUString aStyle;
961 if (rProps.GetOpt(ESCHER_Prop_gtextFont, aFont))
963 aStream.WriteBytes(aFont.nProp.data(), aFont.nProp.size());
964 aStream.Seek(0);
965 OUString aTextPathFont = SvxMSDffManager::MSDFFReadZString(aStream, aFont.nProp.size(), true);
966 aStyle += "font-family:\"" + aTextPathFont + "\"";
968 sal_uInt32 nSize;
969 if (rProps.GetOpt(ESCHER_Prop_gtextSize, nSize))
971 float nSizeF = static_cast<sal_Int32>(nSize) / 65536.0;
972 OUString aSize = OUString::number(nSizeF);
973 aStyle += ";font-size:" + aSize + "pt";
976 sal_uInt32 nGtextFlags;
977 if (rProps.GetOpt(DFF_Prop_gtextFStrikethrough /*255*/, nGtextFlags))
979 // The property is in fact a collection of flags. Two bytes contain the
980 // fUsegtextF* flags and the other two bytes at same place the associated
981 // On/Off flags. See '2.3.22.10 Geometry Text Boolean Properties' section
982 // in [MS-ODRAW].
983 if ((nGtextFlags & 0x00200020) == 0x00200020) // DFF_Prop_gtextFBold = 250
984 aStyle += ";font-weight:bold";
985 if ((nGtextFlags & 0x00100010) == 0x00100010) // DFF_Prop_gtextFItalic = 251
986 aStyle += ";font-style:italic";
987 if ((nGtextFlags & 0x00800080) == 0x00800080) // no DFF, PID gtextFNormalize = 248
988 aStyle += ";v-same-letter-heights:t";
990 // The value 'Fontwork character spacing' in LO is bound to field 'Scaling'
991 // not to 'Spacing' in character properties. In fact the characters are
992 // rendered with changed distance and width. The method in escherex.cxx has
993 // put a rounded value of 'CharScaleWidth' API property to
994 // DFF_Prop_gtextSpacing (=196) as integer part of 16.16 fixed point format.
995 // fUsegtextFTight and gtextFTight (244) of MS binary format are not used.
996 sal_uInt32 nGtextSpacing;
997 if (rProps.GetOpt(DFF_Prop_gtextSpacing, nGtextSpacing))
998 aStyle += ";v-text-spacing:" + OUString::number(nGtextSpacing) + "f";
1001 if (!aStyle.isEmpty())
1002 pAttrList->add(XML_style, aStyle);
1004 // tdf#153260. LO renders all Fontwork shapes as if trim="t" is set. Default
1005 // value is "f". So always write out "t", otherwise import will reduce the
1006 // shape height as workaround for "f".
1007 pAttrList->add(XML_trim, "t");
1009 m_pSerializer->singleElementNS(XML_v, XML_textpath, pAttrList);
1012 bAlreadyWritten[ESCHER_Prop_gtextUNICODE] = true;
1013 bAlreadyWritten[ESCHER_Prop_gtextFont] = true;
1015 break;
1016 case DFF_Prop_adjustValue:
1017 case DFF_Prop_adjust2Value:
1019 // FIXME: tdf#153296: The currently exported markup for <v:shapetype> is based on
1020 // OOXML presets and unusable in regard to handles. Fontwork shapes use dedicated
1021 // own markup, see FontworkHelpers::GetVMLFontworkShapetypeMarkup.
1022 // Thus this is restricted to preset Fontwork shapes. Such have maximal two
1023 // adjustment values.
1024 if ((mso_sptTextSimple <= m_nShapeType && m_nShapeType <= mso_sptTextOnRing)
1025 || (mso_sptTextPlainText <= m_nShapeType && m_nShapeType <= mso_sptTextCanDown))
1027 sal_uInt32 nValue;
1028 OString sAdj;
1029 if (rProps.GetOpt(DFF_Prop_adjustValue, nValue))
1031 sAdj = OString::number(static_cast<sal_Int32>(nValue));
1032 if (rProps.GetOpt(DFF_Prop_adjust2Value, nValue))
1033 sAdj += "," + OString::number(static_cast<sal_Int32>(nValue));
1035 if (!sAdj.isEmpty())
1036 m_pShapeAttrList->add(XML_adj, sAdj);
1037 bAlreadyWritten[DFF_Prop_adjustValue] = true;
1038 bAlreadyWritten[DFF_Prop_adjust2Value] = true;
1041 break;
1042 case ESCHER_Prop_Rotation:
1044 // The higher half of the variable contains the angle.
1045 m_ShapeStyle.append(";rotation:" + OString::number(double(opt.nPropValue >> 16)));
1046 bAlreadyWritten[ESCHER_Prop_Rotation] = true;
1048 break;
1049 case ESCHER_Prop_fNoLineDrawDash:
1051 // See DffPropertyReader::ApplyLineAttributes().
1052 impl_AddBool( m_pShapeAttrList.get(), XML_stroked, (opt.nPropValue & 8) != 0 );
1053 bAlreadyWritten[ESCHER_Prop_fNoLineDrawDash] = true;
1055 break;
1056 case ESCHER_Prop_wzName:
1058 SvMemoryStream aStream;
1060 if(!opt.nProp.empty())
1062 aStream.WriteBytes(opt.nProp.data(), opt.nProp.size());
1065 aStream.Seek(0);
1066 OUString idStr = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true);
1067 aStream.Seek(0);
1068 if (!IsWaterMarkShape(m_pSdrObject->GetName()) && !m_bSkipwzName)
1069 m_pShapeAttrList->add(XML_ID, idStr);
1071 // note that XML_ID is different from XML_id (although it looks like a LO
1072 // implementation distinction without valid justification to me).
1073 // FIXME: XML_ID produces invalid file, see tdf#153183
1074 bAlreadyWritten[ESCHER_Prop_wzName] = true;
1076 break;
1077 default:
1078 #if OSL_DEBUG_LEVEL > 0
1079 const size_t opt_nProp_size(opt.nProp.size());
1080 SAL_WARN( "oox.vml", "TODO VMLExport::Commit(), unimplemented id: " << nId
1081 << ", value: " << opt.nPropValue
1082 << ", data: [" << opt_nProp_size << "]");
1083 if ( opt.nProp.size() )
1085 const sal_uInt8 *pIt = opt.nProp.data();
1086 OStringBuffer buf( " ( " );
1087 for ( int nCount = opt.nProp.size(); nCount; --nCount )
1089 buf.append( OString::number(static_cast<sal_Int32>(*pIt), 16) + " ");
1090 ++pIt;
1092 buf.append( ")" );
1093 SAL_WARN("oox.vml", std::string_view(buf));
1095 #endif
1096 break;
1100 m_pSerializer->mergeTopMarks(Tag_Commit, sax_fastparser::MergeMarks::POSTPONE );
1103 OString VMLExport::ShapeIdString( sal_uInt32 nId )
1105 if(m_bOverrideShapeIdGeneration)
1106 return m_sShapeIDPrefix + OString::number( nId );
1107 else
1108 return "shape_" + OString::number( nId );
1111 void VMLExport::AddFlipXY( )
1113 if (m_nShapeFlags & (ShapeFlag::FlipH | ShapeFlag::FlipV))
1115 m_ShapeStyle.append( ";flip:" );
1117 if (m_nShapeFlags & ShapeFlag::FlipH)
1118 m_ShapeStyle.append( "x" );
1120 if (m_nShapeFlags & ShapeFlag::FlipV)
1121 m_ShapeStyle.append( "y" );
1125 void VMLExport::AddLineDimensions( const tools::Rectangle& rRectangle )
1127 // style
1128 if (!m_ShapeStyle.isEmpty())
1129 m_ShapeStyle.append( ";" );
1131 m_ShapeStyle.append( "position:absolute" );
1133 AddFlipXY();
1135 // the actual dimensions
1136 OString aLeft, aTop, aRight, aBottom;
1138 if ( mnGroupLevel == 1 )
1140 static constexpr OString aPt( "pt"_ostr );
1141 aLeft = OString::number( double( rRectangle.Left() ) / 20 ) + aPt;
1142 aTop = OString::number( double( rRectangle.Top() ) / 20 ) + aPt;
1143 aRight = OString::number( double( rRectangle.Right() ) / 20 ) + aPt;
1144 aBottom = OString::number( double( rRectangle.Bottom() ) / 20 ) + aPt;
1146 else
1148 aLeft = OString::number( rRectangle.Left() );
1149 aTop = OString::number( rRectangle.Top() );
1150 aRight = OString::number( rRectangle.Right() );
1151 aBottom = OString::number( rRectangle.Bottom() );
1154 m_pShapeAttrList->add( XML_from, aLeft + "," + aTop );
1156 m_pShapeAttrList->add( XML_to, aRight + "," + aBottom );
1159 void VMLExport::AddRectangleDimensions( OStringBuffer& rBuffer, const tools::Rectangle& rRectangle, bool rbAbsolutePos)
1161 if ( !rBuffer.isEmpty() )
1162 rBuffer.append( ";" );
1164 if (rbAbsolutePos && !m_bInline)
1166 rBuffer.append( "position:absolute;" );
1169 if(m_bInline)
1171 rBuffer.append( "width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) +
1172 "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) +
1173 "pt" );
1175 else if ( mnGroupLevel == 1 )
1177 rBuffer.append( "margin-left:" + OString::number( double( rRectangle.Left() ) / 20 ) +
1178 "pt;margin-top:" + OString::number( double( rRectangle.Top() ) / 20 ) +
1179 "pt;width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) +
1180 "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) +
1181 "pt" );
1183 else
1185 rBuffer.append( "left:" + OString::number( rRectangle.Left() ) +
1186 ";top:" + OString::number( rRectangle.Top() ) +
1187 ";width:" + OString::number( rRectangle.Right() - rRectangle.Left() ) +
1188 ";height:" + OString::number( rRectangle.Bottom() - rRectangle.Top() ) );
1191 AddFlipXY();
1194 void VMLExport::AddShapeAttribute( sal_Int32 nAttribute, std::string_view rValue )
1196 m_pShapeAttrList->add( nAttribute, rValue );
1199 static std::vector<OString> lcl_getShapeTypes()
1201 std::vector<OString> aRet;
1203 OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/vml-shape-types");
1204 rtl::Bootstrap::expandMacros(aPath);
1205 SvFileStream aStream(aPath, StreamMode::READ);
1206 if (aStream.GetError() != ERRCODE_NONE)
1207 SAL_WARN("oox", "failed to open vml-shape-types");
1208 OStringBuffer aLine;
1209 bool bNotDone = aStream.ReadLine(aLine);
1210 while (bNotDone)
1212 // Filter out comments.
1213 if (!o3tl::starts_with(aLine, "/"))
1214 aRet.push_back(OString(aLine));
1215 bNotDone = aStream.ReadLine(aLine);
1217 return aRet;
1220 static bool lcl_isTextBox(const SdrObject* pSdrObject)
1222 uno::Reference<beans::XPropertySet> xPropertySet(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY);
1223 if (!xPropertySet.is())
1224 return false;
1225 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
1226 if (!xPropertySetInfo->hasPropertyByName("TextBox"))
1227 return false;
1228 css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox"));
1229 if (!aTextBox.hasValue())
1230 return false;
1231 return aTextBox.get<bool>();
1234 static OUString lcl_getAnchorIdFromGrabBag(const SdrObject* pSdrObject)
1236 OUString aResult;
1238 uno::Reference<beans::XPropertySet> xShape(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY);
1239 if (xShape->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
1241 comphelper::SequenceAsHashMap aInteropGrabBag(xShape->getPropertyValue("InteropGrabBag"));
1242 auto it = aInteropGrabBag.find("AnchorId");
1243 if (it != aInteropGrabBag.end())
1244 it->second >>= aResult;
1247 return aResult;
1250 sal_uInt32 VMLExport::GenerateShapeId()
1252 if(!m_bOverrideShapeIdGeneration)
1253 return EscherEx::GenerateShapeId();
1254 else
1255 return m_nShapeIDCounter++;
1258 OString VMLExport::GetVMLShapeTypeDefinition(
1259 std::string_view sShapeID, const bool bIsPictureFrame )
1261 OString sShapeType;
1262 if ( !bIsPictureFrame )
1263 // We don't have a shape definition for host control in presetShapeDefinitions.xml
1264 // So use a definition copied from DOCX file created with MSO
1265 sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID +
1266 "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID +
1267 "\" path=\"m,l,21600l21600,21600l21600,xe\">\n"
1268 "<v:stroke joinstyle=\"miter\"/>\n"
1269 "<v:path shadowok=\"f\" o:extrusionok=\"f\" strokeok=\"f\" fillok=\"f\" o:connecttype=\"rect\"/>\n"
1270 "<o:lock v:ext=\"edit\" shapetype=\"t\"/>\n"
1271 "</v:shapetype>";
1272 else
1273 // We don't have a shape definition for picture frame in presetShapeDefinitions.xml
1274 // So use a definition copied from DOCX file created with MSO
1275 sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID +
1276 "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID +
1277 "\" o:preferrelative=\"t\" path=\"m@4@5l@4@11@9@11@9@5xe\" filled=\"f\" stroked=\"f\">\n"
1278 "<v:stroke joinstyle=\"miter\"/>\n"
1279 "<v:formulas>\n"
1280 "<v:f eqn=\"if lineDrawn pixelLineWidth 0\"/>\n"
1281 "<v:f eqn=\"sum @0 1 0\"/>\n"
1282 "<v:f eqn=\"sum 0 0 @1\"/>\n"
1283 "<v:f eqn=\"prod @2 1 2\"/>\n"
1284 "<v:f eqn=\"prod @3 21600 pixelWidth\"/>\n"
1285 "<v:f eqn=\"prod @3 21600 pixelHeight\"/>\n"
1286 "<v:f eqn=\"sum @0 0 1\"/>\n"
1287 "<v:f eqn=\"prod @6 1 2\"/>\n"
1288 "<v:f eqn=\"prod @7 21600 pixelWidth\"/>\n"
1289 "<v:f eqn=\"sum @8 21600 0\"/>\n"
1290 "<v:f eqn=\"prod @7 21600 pixelHeight\"/>\n"
1291 "<v:f eqn=\"sum @10 21600 0\"/>\n"
1292 "</v:formulas>\n"
1293 "<v:path o:extrusionok=\"f\" gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n"
1294 "<o:lock v:ext=\"edit\" aspectratio=\"t\"/>\n"
1295 "</v:shapetype>";
1296 return sShapeType;
1299 sal_Int32 VMLExport::StartShape()
1301 if ( m_nShapeType == ESCHER_ShpInst_Nil )
1302 return -1;
1304 // some of the shapes have their own name ;-)
1305 sal_Int32 nShapeElement = -1;
1306 bool bReferToShapeType = false;
1307 switch ( m_nShapeType )
1309 case ESCHER_ShpInst_NotPrimitive: nShapeElement = XML_shape; break;
1310 case ESCHER_ShpInst_Rectangle: nShapeElement = XML_rect; break;
1311 case ESCHER_ShpInst_RoundRectangle: nShapeElement = XML_roundrect; break;
1312 case ESCHER_ShpInst_Ellipse: nShapeElement = XML_oval; break;
1313 case ESCHER_ShpInst_Arc: nShapeElement = XML_arc; break;
1314 case ESCHER_ShpInst_Line: nShapeElement = XML_line; break;
1315 case ESCHER_ShpInst_HostControl:
1317 bReferToShapeType = true;
1318 nShapeElement = XML_shape;
1319 if ( !m_aShapeTypeWritten[ m_nShapeType ] )
1321 m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), false));
1322 m_aShapeTypeWritten[ m_nShapeType ] = true;
1324 break;
1326 case ESCHER_ShpInst_PictureFrame:
1328 bReferToShapeType = true;
1329 nShapeElement = XML_shape;
1330 if ( !m_aShapeTypeWritten[ m_nShapeType ] )
1332 m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), true));
1333 m_aShapeTypeWritten[ m_nShapeType ] = true;
1335 break;
1337 default:
1338 nShapeElement = XML_shape;
1339 if (m_pSdrObject->IsTextPath())
1341 bReferToShapeType = m_aShapeTypeWritten[m_nShapeType];
1342 if (!bReferToShapeType)
1344 // Does a predefined markup exist at all?
1345 OString sMarkup = FontworkHelpers::GetVMLFontworkShapetypeMarkup(
1346 static_cast<MSO_SPT>(m_nShapeType));
1347 if (!sMarkup.isEmpty())
1349 m_pSerializer->write(sMarkup);
1350 m_aShapeTypeWritten[m_nShapeType] = true;
1351 bReferToShapeType = true;
1354 // ToDo: The case bReferToShapeType==false happens for 'non-primitive' shapes for
1355 // example. We need to get the geometry from CustomShapeGeometry in these cases.
1357 else if ( m_nShapeType < ESCHER_ShpInst_COUNT )
1359 // a predefined shape?
1360 static std::vector<OString> aShapeTypes = lcl_getShapeTypes();
1361 SAL_WARN_IF(m_nShapeType >= aShapeTypes.size(), "oox.vml", "Unknown shape type!");
1362 if (m_nShapeType < aShapeTypes.size() && aShapeTypes[m_nShapeType] != "NULL")
1364 bReferToShapeType = true;
1365 if ( !m_aShapeTypeWritten[ m_nShapeType ] )
1367 m_pSerializer->write(aShapeTypes[m_nShapeType]);
1368 m_aShapeTypeWritten[ m_nShapeType ] = true;
1371 else
1373 // rectangle is probably the best fallback...
1374 nShapeElement = XML_rect;
1377 break;
1380 // anchoring
1381 switch (m_eHOri)
1383 case text::HoriOrientation::LEFT:
1384 m_ShapeStyle.append(";mso-position-horizontal:left");
1385 break;
1386 case text::HoriOrientation::CENTER:
1387 m_ShapeStyle.append(";mso-position-horizontal:center");
1388 break;
1389 case text::HoriOrientation::RIGHT:
1390 m_ShapeStyle.append(";mso-position-horizontal:right");
1391 break;
1392 case text::HoriOrientation::INSIDE:
1393 m_ShapeStyle.append(";mso-position-horizontal:inside");
1394 break;
1395 case text::HoriOrientation::OUTSIDE:
1396 m_ShapeStyle.append(";mso-position-horizontal:outside");
1397 break;
1398 default:
1399 case text::HoriOrientation::NONE:
1400 break;
1402 switch (m_eHRel)
1404 case text::RelOrientation::PAGE_PRINT_AREA:
1405 m_ShapeStyle.append(";mso-position-horizontal-relative:margin");
1406 break;
1407 case text::RelOrientation::PAGE_FRAME:
1408 case text::RelOrientation::PAGE_LEFT:
1409 case text::RelOrientation::PAGE_RIGHT:
1410 m_ShapeStyle.append(";mso-position-horizontal-relative:page");
1411 break;
1412 case text::RelOrientation::CHAR:
1413 m_ShapeStyle.append(";mso-position-horizontal-relative:char");
1414 break;
1415 default:
1416 break;
1419 switch (m_eVOri)
1421 case text::VertOrientation::TOP:
1422 case text::VertOrientation::LINE_TOP:
1423 case text::VertOrientation::CHAR_TOP:
1424 m_ShapeStyle.append(";mso-position-vertical:top");
1425 break;
1426 case text::VertOrientation::CENTER:
1427 case text::VertOrientation::LINE_CENTER:
1428 m_ShapeStyle.append(";mso-position-vertical:center");
1429 break;
1430 case text::VertOrientation::BOTTOM:
1431 case text::VertOrientation::LINE_BOTTOM:
1432 case text::VertOrientation::CHAR_BOTTOM:
1433 m_ShapeStyle.append(";mso-position-vertical:bottom");
1434 break;
1435 default:
1436 case text::VertOrientation::NONE:
1437 break;
1439 switch (m_eVRel)
1441 case text::RelOrientation::PAGE_PRINT_AREA:
1442 m_ShapeStyle.append(";mso-position-vertical-relative:margin");
1443 break;
1444 case text::RelOrientation::PAGE_FRAME:
1445 m_ShapeStyle.append(";mso-position-vertical-relative:page");
1446 break;
1447 default:
1448 break;
1451 if (!m_pSdrObject->getHyperlink().isEmpty())
1452 m_pShapeAttrList->add(
1453 XML_href, m_pSdrObject->getHyperlink());
1455 m_pShapeAttrList->addNS(XML_o, XML_allowincell, m_IsFollowingTextFlow ? "t" : "f");
1457 // add style
1458 m_pShapeAttrList->add( XML_style, m_ShapeStyle.makeStringAndClear() );
1460 OUString sAnchorId = lcl_getAnchorIdFromGrabBag(m_pSdrObject);
1461 if (!sAnchorId.isEmpty())
1462 m_pShapeAttrList->addNS(XML_wp14, XML_anchorId, sAnchorId);
1464 if ( nShapeElement >= 0 && !m_pShapeAttrList->hasAttribute( XML_type ) && bReferToShapeType )
1466 OString sType;
1467 if (m_bUseHashMarkForType)
1468 sType = "#"_ostr;
1469 m_pShapeAttrList->add( XML_type, sType +
1470 "_x0000_t" + OString::number( m_nShapeType ) );
1473 // allow legacy id (which in form controls and textboxes
1474 // by definition seems to have this otherwise illegal name).
1475 m_pSerializer->setAllowXEscape(!m_sShapeIDPrefix.startsWith("_x0000_"));
1477 // start of the shape
1478 m_pSerializer->startElementNS( XML_v, nShapeElement, m_pShapeAttrList );
1479 m_pSerializer->setAllowXEscape(true);
1481 OString const textboxStyle(m_TextboxStyle.makeStringAndClear());
1483 // now check if we have some editeng text (not associated textbox) and we have a text exporter registered
1484 const SdrTextObj* pTxtObj = DynCastSdrTextObj( m_pSdrObject );
1485 if (pTxtObj && m_pTextExport && !m_pSdrObject->IsTextPath()
1486 && !IsWaterMarkShape(m_pSdrObject->GetName()) && !lcl_isTextBox(m_pSdrObject))
1488 std::optional<OutlinerParaObject> pParaObj;
1491 #i13885#
1492 When the object is actively being edited, that text is not set into
1493 the objects normal text object, but lives in a separate object.
1495 if (pTxtObj->IsTextEditActive())
1497 pParaObj = pTxtObj->CreateEditOutlinerParaObject();
1499 else if (pTxtObj->GetOutlinerParaObject())
1501 pParaObj = *pTxtObj->GetOutlinerParaObject();
1504 if( pParaObj )
1506 rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList();
1507 if (!textboxStyle.isEmpty())
1509 pTextboxAttrList->add(XML_style, textboxStyle);
1512 // this is reached only in case some text is attached to the shape
1513 m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList);
1514 m_pTextExport->WriteOutliner(*pParaObj);
1515 m_pSerializer->endElementNS(XML_v, XML_textbox);
1519 return nShapeElement;
1522 void VMLExport::EndShape( sal_Int32 nShapeElement )
1524 if ( nShapeElement < 0 )
1525 return;
1527 if (m_pTextExport && lcl_isTextBox(m_pSdrObject))
1529 uno::Reference<drawing::XShape> xShape {const_cast<SdrObject*>(m_pSdrObject)->getUnoShape(), uno::UNO_QUERY};
1530 uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
1531 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
1532 bool bBottomToTop = false;
1533 if (xPropertySetInfo->hasPropertyByName("CustomShapeGeometry"))
1535 // In this case a DrawingML DOCX was imported.
1536 auto aAny = xPropertySet->getPropertyValue("WritingMode");
1537 sal_Int16 nWritingMode;
1538 if ((aAny >>= nWritingMode) && nWritingMode == text::WritingMode2::BT_LR)
1539 bBottomToTop = true;
1541 else
1543 // In this case a pure VML DOCX was imported, so there is no CustomShapeGeometry.
1544 auto pTextExport = m_pTextExport->GetDrawingML().GetTextExport();
1545 // FIXME: somewhy pTextExport is always nullptr, we should find its reason
1546 if (pTextExport)
1548 auto xTextFrame = pTextExport->GetUnoTextFrame(xShape);
1549 uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
1550 auto aAny = xPropSet->getPropertyValue("WritingMode");
1551 sal_Int16 nWritingMode;
1552 if (aAny >>= nWritingMode)
1554 switch (nWritingMode)
1556 case text::WritingMode2::BT_LR:
1557 bBottomToTop = true;
1558 break;
1559 default:
1560 break;
1565 rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList();
1566 if (bBottomToTop)
1567 pTextboxAttrList->add(XML_style, "mso-layout-flow-alt:bottom-to-top");
1568 m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList);
1570 m_pTextExport->WriteVMLTextBox(uno::Reference<drawing::XShape>(xPropertySet, uno::UNO_QUERY_THROW));
1572 m_pSerializer->endElementNS(XML_v, XML_textbox);
1575 if (m_pWrapAttrList)
1577 m_pSerializer->singleElementNS(XML_w10, XML_wrap, m_pWrapAttrList);
1580 // end of the shape
1581 m_pSerializer->endElementNS( XML_v, nShapeElement );
1584 OString const & VMLExport::AddSdrObject( const SdrObject& rObj,
1585 bool const bIsFollowingTextFlow,
1586 sal_Int16 eHOri, sal_Int16 eVOri, sal_Int16 eHRel, sal_Int16 eVRel,
1587 FastAttributeList* pWrapAttrList,
1588 const bool bOOxmlExport, sal_uInt32 nId)
1590 m_pSdrObject = &rObj;
1591 m_eHOri = eHOri;
1592 m_eVOri = eVOri;
1593 m_eHRel = eHRel;
1594 m_eVRel = eVRel;
1595 m_pWrapAttrList = pWrapAttrList;
1596 m_bInline = false;
1597 m_IsFollowingTextFlow = bIsFollowingTextFlow;
1598 EscherEx::AddSdrObject(rObj, bOOxmlExport, nId);
1599 return m_sShapeId;
1602 OString const & VMLExport::AddInlineSdrObject( const SdrObject& rObj, const bool bOOxmlExport )
1604 m_pSdrObject = &rObj;
1605 m_eHOri = -1;
1606 m_eVOri = -1;
1607 m_eHRel = -1;
1608 m_eVRel = -1;
1609 m_pWrapAttrList.clear();
1610 m_bInline = true;
1611 m_IsFollowingTextFlow = true;
1612 EscherEx::AddSdrObject(rObj, bOOxmlExport);
1613 return m_sShapeId;
1616 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */