android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / filter / ww8 / wrtw8nds.cxx
blobd2e57406f8b3e14312706df874a36b17f4d723e3
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 <vector>
21 #include <utility>
22 #include <algorithm>
23 #include <iostream>
25 #include "docxexport.hxx"
27 #include <officecfg/Office/Common.hxx>
28 #include <i18nlangtag/mslangid.hxx>
29 #include <hintids.hxx>
30 #include <tools/urlobj.hxx>
31 #include <editeng/cmapitem.hxx>
32 #include <editeng/langitem.hxx>
33 #include <editeng/svxfont.hxx>
34 #include <editeng/lrspitem.hxx>
35 #include <editeng/brushitem.hxx>
36 #include <editeng/fontitem.hxx>
37 #include <editeng/keepitem.hxx>
38 #include <editeng/fhgtitem.hxx>
39 #include <editeng/ulspitem.hxx>
40 #include <editeng/formatbreakitem.hxx>
41 #include <editeng/frmdiritem.hxx>
42 #include <editeng/tstpitem.hxx>
43 #include <editeng/wghtitem.hxx>
44 #include <svl/grabbagitem.hxx>
45 #include <svl/urihelper.hxx>
46 #include <svl/whiter.hxx>
47 #include <fmtpdsc.hxx>
48 #include <fmtlsplt.hxx>
49 #include <fmtanchr.hxx>
50 #include <fmtcntnt.hxx>
51 #include <frmatr.hxx>
52 #include <paratr.hxx>
53 #include <txatbase.hxx>
54 #include <fmtinfmt.hxx>
55 #include <fmtrfmrk.hxx>
56 #include <fchrfmt.hxx>
57 #include <fmtautofmt.hxx>
58 #include <charfmt.hxx>
59 #include <tox.hxx>
60 #include <ndtxt.hxx>
61 #include <pam.hxx>
62 #include <doc.hxx>
63 #include <IDocumentSettingAccess.hxx>
64 #include <IDocumentMarkAccess.hxx>
65 #include <docary.hxx>
66 #include <swtable.hxx>
67 #include <swtblfmt.hxx>
68 #include <section.hxx>
69 #include <pagedesc.hxx>
70 #include <swrect.hxx>
71 #include <reffld.hxx>
72 #include <redline.hxx>
73 #include <txttxmrk.hxx>
74 #include <fmtline.hxx>
75 #include <fmtruby.hxx>
76 #include <breakit.hxx>
77 #include <txtatr.hxx>
78 #include <cellatr.hxx>
79 #include <fmtrowsplt.hxx>
80 #include <com/sun/star/drawing/XShape.hpp>
81 #include <com/sun/star/i18n/BreakIterator.hpp>
82 #include <com/sun/star/i18n/ScriptType.hpp>
83 #include <com/sun/star/i18n/WordType.hpp>
84 #include <com/sun/star/text/RubyPosition.hpp>
85 #include <oox/export/vmlexport.hxx>
86 #include <sal/log.hxx>
87 #include <comphelper/propertysequence.hxx>
88 #include <comphelper/string.hxx>
90 #include "sprmids.hxx"
92 #include "writerhelper.hxx"
93 #include "writerwordglue.hxx"
94 #include <numrule.hxx>
95 #include "wrtww8.hxx"
96 #include "ww8par.hxx"
97 #include <IMark.hxx>
98 #include "ww8attributeoutput.hxx"
100 #include <ndgrf.hxx>
101 #include <ndole.hxx>
102 #include <formatflysplit.hxx>
104 #include <cstdio>
106 using namespace ::com::sun::star;
107 using namespace ::com::sun::star::i18n;
108 using namespace sw::util;
109 using namespace sw::types;
110 using namespace sw::mark;
111 using namespace ::oox::vml;
113 static OUString lcl_getFieldCode( const IFieldmark* pFieldmark )
115 assert(pFieldmark);
117 if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
118 return " FORMTEXT ";
119 if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
120 return " FORMDROPDOWN ";
121 if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
122 return " FORMCHECKBOX ";
123 if ( pFieldmark->GetFieldname( ) == ODF_FORMDATE )
124 return " ODFFORMDATE ";
125 if ( pFieldmark->GetFieldname( ) == ODF_TOC )
126 return " TOC ";
127 if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
128 return " HYPERLINK ";
129 if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
130 return " PAGEREF ";
131 return pFieldmark->GetFieldname();
134 static ww::eField lcl_getFieldId(const IFieldmark*const pFieldmark)
136 assert(pFieldmark);
138 if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
139 return ww::eFORMTEXT;
140 if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
141 return ww::eFORMDROPDOWN;
142 if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
143 return ww::eFORMCHECKBOX;
144 if ( pFieldmark->GetFieldname( ) == ODF_FORMDATE )
145 return ww::eFORMDATE;
146 if ( pFieldmark->GetFieldname( ) == ODF_TOC )
147 return ww::eTOC;
148 if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
149 return ww::eHYPERLINK;
150 if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
151 return ww::ePAGEREF;
152 return ww::eUNKNOWN;
155 static OUString
156 lcl_getLinkChainName(const uno::Reference<beans::XPropertySet>& rPropertySet,
157 const uno::Reference<beans::XPropertySetInfo>& rPropertySetInfo)
159 OUString sLinkChainName;
160 if (rPropertySetInfo->hasPropertyByName("LinkDisplayName"))
162 rPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
163 if (!sLinkChainName.isEmpty())
164 return sLinkChainName;
166 if (rPropertySetInfo->hasPropertyByName("ChainName"))
167 rPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
168 return sLinkChainName;
171 MSWordAttrIter::MSWordAttrIter( MSWordExportBase& rExport )
172 : m_pOld( rExport.m_pChpIter ), m_rExport( rExport )
174 m_rExport.m_pChpIter = this;
177 MSWordAttrIter::~MSWordAttrIter()
179 m_rExport.m_pChpIter = m_pOld;
182 namespace {
184 class sortswflys
186 public:
187 bool operator()(const ww8::Frame &rOne, const ww8::Frame &rTwo) const
189 return rOne.GetPosition() < rTwo.GetPosition();
195 void SwWW8AttrIter::IterToCurrent()
197 OSL_ENSURE(maCharRuns.begin() != maCharRuns.end(), "Impossible");
198 mnScript = maCharRunIter->mnScript;
199 meChrSet = maCharRunIter->meCharSet;
200 mbCharIsRTL = maCharRunIter->mbRTL;
203 SwWW8AttrIter::SwWW8AttrIter(MSWordExportBase& rWr, const SwTextNode& rTextNd) :
204 MSWordAttrIter(rWr),
205 m_rNode(rTextNd),
206 maCharRuns(GetPseudoCharRuns(rTextNd)),
207 m_pCurRedline(nullptr),
208 m_nCurrentSwPos(0),
209 m_nCurRedlinePos(SwRedlineTable::npos),
210 mrSwFormatDrop(rTextNd.GetSwAttrSet().GetDrop())
213 SwPosition aPos(rTextNd);
214 mbParaIsRTL = SvxFrameDirection::Horizontal_RL_TB == rWr.m_rDoc.GetTextDirection(aPos);
216 maCharRunIter = maCharRuns.begin();
217 IterToCurrent();
220 #i2916#
221 Get list of any graphics which may be anchored from this paragraph.
223 maFlyFrames = GetFramesInNode(rWr.m_aFrames, m_rNode);
224 std::stable_sort(maFlyFrames.begin(), maFlyFrames.end(), sortswflys());
227 #i18480#
228 If we are inside a frame then anything anchored inside this frame can
229 only be supported by word anchored inline ("as character"), so force
230 this in the supportable case.
232 if (rWr.m_bInWriteEscher)
234 for ( auto& aFlyFrame : maFlyFrames )
235 aFlyFrame.ForceTreatAsInline();
238 maFlyIter = maFlyFrames.begin();
240 if ( !m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty() )
242 SwPosition aPosition( m_rNode );
243 m_pCurRedline = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedline( aPosition, &m_nCurRedlinePos );
246 m_nCurrentSwPos = SearchNext(1);
249 static sal_Int32 lcl_getMinPos( sal_Int32 pos1, sal_Int32 pos2 )
251 if ( pos1 >= 0 && pos2 >= 0 )
253 // both valid: return minimum one
254 return std::min(pos1, pos2);
257 // return the valid one, if any, or -1
258 return std::max(pos1, pos2);
261 sal_Int32 SwWW8AttrIter::SearchNext( sal_Int32 nStartPos )
263 const OUString aText = m_rNode.GetText();
264 sal_Int32 fieldEndPos = aText.indexOf(CH_TXT_ATR_FIELDEND, nStartPos - 1);
265 // HACK: for (so far) mysterious reasons the sdtContent element closes
266 // too late in testDateFormField() unless an empty run is exported at
267 // the end of the fieldmark; hence find *also* the position after the
268 // CH_TXT_ATR_FIELDEND here
269 if (0 <= fieldEndPos && fieldEndPos < nStartPos)
271 ++fieldEndPos;
273 sal_Int32 fieldSepPos = aText.indexOf(CH_TXT_ATR_FIELDSEP, nStartPos);
274 sal_Int32 fieldStartPos = aText.indexOf(CH_TXT_ATR_FIELDSTART, nStartPos);
275 sal_Int32 formElementPos = aText.indexOf(CH_TXT_ATR_FORMELEMENT, nStartPos - 1);
276 if (0 <= formElementPos && formElementPos < nStartPos)
278 ++formElementPos; // tdf#133604 put this in its own run
281 const sal_Int32 pos = lcl_getMinPos(
282 lcl_getMinPos(lcl_getMinPos(fieldEndPos, fieldSepPos), fieldStartPos),
283 formElementPos );
285 sal_Int32 nMinPos = (pos>=0) ? pos : SAL_MAX_INT32;
287 // first the redline, then the attributes
288 if( m_pCurRedline )
290 const SwPosition* pEnd = m_pCurRedline->End();
291 if (pEnd->GetNode() == m_rNode)
293 const sal_Int32 i = pEnd->GetContentIndex();
294 if ( i >= nStartPos && i < nMinPos )
296 nMinPos = i;
301 if ( m_nCurRedlinePos < m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size() )
303 // nCurRedlinePos point to the next redline
304 SwRedlineTable::size_type nRedLinePos = m_nCurRedlinePos;
305 if( m_pCurRedline )
306 ++nRedLinePos;
308 for ( ; nRedLinePos < m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedLinePos )
310 const SwRangeRedline* pRedl = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nRedLinePos ];
312 auto [pStt, pEnd] = pRedl->StartEnd(); // SwPosition*
314 if( pStt->GetNode() == m_rNode )
316 const sal_Int32 i = pStt->GetContentIndex();
317 if( i >= nStartPos && i < nMinPos )
318 nMinPos = i;
320 else
321 break;
323 if( pEnd->GetNode() == m_rNode )
325 const sal_Int32 i = pEnd->GetContentIndex();
326 if( i >= nStartPos && i < nMinPos )
328 nMinPos = i;
334 if (mrSwFormatDrop.GetWholeWord() && nStartPos <= m_rNode.GetDropLen(0))
335 nMinPos = m_rNode.GetDropLen(0);
336 else if(nStartPos <= mrSwFormatDrop.GetChars())
337 nMinPos = mrSwFormatDrop.GetChars();
339 if(const SwpHints* pTextAttrs = m_rNode.GetpSwpHints())
342 // can be optimized if we consider that the TextAttrs are sorted by start position.
343 // but then we'd have to save 2 indices
344 for( size_t i = 0; i < pTextAttrs->Count(); ++i )
346 const SwTextAttr* pHt = pTextAttrs->Get(i);
347 sal_Int32 nPos = pHt->GetStart(); // first Attr characters
348 if( nPos >= nStartPos && nPos <= nMinPos )
349 nMinPos = nPos;
351 if( pHt->End() ) // Attr with end
353 nPos = *pHt->End(); // last Attr character + 1
354 if( nPos >= nStartPos && nPos <= nMinPos )
355 nMinPos = nPos;
357 if (pHt->HasDummyChar())
359 // pos + 1 because of CH_TXTATR in Text
360 nPos = pHt->GetStart() + 1;
361 if( nPos >= nStartPos && nPos <= nMinPos )
362 nMinPos = nPos;
367 if (maCharRunIter != maCharRuns.end())
369 if (maCharRunIter->mnEndPos < nMinPos)
370 nMinPos = maCharRunIter->mnEndPos;
371 IterToCurrent();
374 // #i2916# Check to see if there are any graphics anchored to characters in this paragraph's text.
375 sal_Int32 nNextFlyPos = 0;
376 ww8::FrameIter aTmpFlyIter = maFlyIter;
377 while (aTmpFlyIter != maFlyFrames.end() && nNextFlyPos < nStartPos)
379 const SwPosition &rAnchor = aTmpFlyIter->GetPosition();
380 nNextFlyPos = rAnchor.GetContentIndex();
382 ++aTmpFlyIter;
384 if (nNextFlyPos >= nStartPos && nNextFlyPos < nMinPos)
385 nMinPos = nNextFlyPos;
387 //nMinPos found and not going to change at this point
389 if (maCharRunIter != maCharRuns.end())
391 if (maCharRunIter->mnEndPos == nMinPos)
392 ++maCharRunIter;
395 return nMinPos;
398 void SwWW8AttrIter::OutAttr(sal_Int32 nSwPos, bool bWriteCombChars)
400 m_rExport.AttrOutput().RTLAndCJKState( mbCharIsRTL, GetScript() );
403 Depending on whether text is in CTL/CJK or Western, get the id of that
404 script, the idea is that the font that is actually in use to render this
405 range of text ends up in pFont
407 TypedWhichId<SvxFontItem> nFontId = GetWhichOfScript( RES_CHRATR_FONT, GetScript() );
409 const SvxFontItem &rParentFont =
410 static_cast<const SwTextFormatColl&>(m_rNode.GetAnyFormatColl()).GetFormatAttr(nFontId);
411 const SvxFontItem *pFont = &rParentFont;
412 const SfxPoolItem *pGrabBag = nullptr;
414 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_TXTATR_END - 1> aExportSet(*m_rNode.GetSwAttrSet().GetPool());
416 //The hard formatting properties that affect the entire paragraph
417 if (m_rNode.HasSwAttrSet())
419 // only copy hard attributes - bDeep = false
420 aExportSet.Set(m_rNode.GetSwAttrSet(), false/*bDeep*/);
421 // get the current font item. Use rNd.GetSwAttrSet instead of aExportSet:
422 const SvxFontItem &rNdFont = m_rNode.GetSwAttrSet().Get(nFontId);
423 pFont = &rNdFont;
424 aExportSet.ClearItem(nFontId);
427 //The additional hard formatting properties that affect this range in the
428 //paragraph
429 ww8::PoolItems aRangeItems;
430 if (const SwpHints* pTextAttrs = m_rNode.GetpSwpHints())
432 for( size_t i = 0; i < pTextAttrs->Count(); ++i )
434 const SwTextAttr* pHt = pTextAttrs->Get(i);
435 const sal_Int32* pEnd = pHt->End();
437 if (pEnd ? ( nSwPos >= pHt->GetStart() && nSwPos < *pEnd)
438 : nSwPos == pHt->GetStart() )
440 sal_uInt16 nWhich = pHt->GetAttr().Which();
441 if (nWhich == RES_TXTATR_AUTOFMT)
443 const SwFormatAutoFormat& rAutoFormat = static_cast<const SwFormatAutoFormat&>(pHt->GetAttr());
444 const std::shared_ptr<SfxItemSet>& pSet = rAutoFormat.GetStyleHandle();
445 SfxWhichIter aIter( *pSet );
446 const SfxPoolItem* pItem;
447 sal_uInt16 nWhichId = aIter.FirstWhich();
448 while( nWhichId )
450 if( SfxItemState::SET == aIter.GetItemState( false, &pItem ))
452 if (nWhichId == nFontId)
453 pFont = &(item_cast<SvxFontItem>(*pItem));
454 else if (nWhichId == RES_CHRATR_GRABBAG)
455 pGrabBag = pItem;
456 else
457 aRangeItems[nWhichId] = pItem;
459 nWhichId = aIter.NextWhich();
462 else
463 aRangeItems[nWhich] = (&(pHt->GetAttr()));
465 else if (nSwPos < pHt->GetStart())
466 break;
469 // DeduplicateItems(aRangeItems);
472 For #i24291# we need to explicitly remove any properties from the
473 aExportSet which a SwCharFormat would override, we can't rely on word doing
474 this for us like writer does
476 const SwFormatCharFormat *pCharFormatItem =
477 HasItem< SwFormatCharFormat >( aRangeItems, RES_TXTATR_CHARFMT );
478 if ( pCharFormatItem )
479 ClearOverridesFromSet( *pCharFormatItem, aExportSet );
481 // check toggle properties in DOCX output
483 SvxWeightItem aBoldProperty(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
484 handleToggleProperty(aExportSet, pCharFormatItem, RES_CHRATR_WEIGHT, &aBoldProperty);
487 // tdf#113790: AutoFormat style overwrites char style, so remove all
488 // elements from CHARFMT grab bag which are set in AUTOFMT grab bag
489 if (const SfxGrabBagItem *pAutoFmtGrabBag = dynamic_cast<const SfxGrabBagItem*>(pGrabBag))
491 if (const SfxGrabBagItem *pCharFmtGrabBag = aExportSet.GetItem<SfxGrabBagItem>(RES_CHRATR_GRABBAG, false))
493 std::unique_ptr<SfxGrabBagItem> pNewCharFmtGrabBag(pCharFmtGrabBag->Clone());
494 assert(pNewCharFmtGrabBag);
495 auto & rNewFmtMap = pNewCharFmtGrabBag->GetGrabBag();
496 for (auto const & item : pAutoFmtGrabBag->GetGrabBag())
498 if (item.second.hasValue())
499 rNewFmtMap.erase(item.first);
501 aExportSet.Put(std::move(pNewCharFmtGrabBag));
505 ww8::PoolItems aExportItems;
506 GetPoolItems( aExportSet, aExportItems, false );
508 if( m_rNode.GetpSwpHints() == nullptr )
509 m_rExport.SetCurItemSet(&aExportSet);
511 for ( const auto& aRangeItem : aRangeItems )
513 aExportItems[aRangeItem.first] = aRangeItem.second;
516 if ( !aExportItems.empty() )
518 const sw::BroadcastingModify* pOldMod = m_rExport.m_pOutFormatNode;
519 m_rExport.m_pOutFormatNode = &m_rNode;
520 m_rExport.m_aCurrentCharPropStarts.push( nSwPos );
522 // tdf#38778 Fix output of the font in DOC run for fields
523 const SvxFontItem * pFontToOutput = ( rParentFont != *pFont )? pFont : nullptr;
525 m_rExport.ExportPoolItemsToCHP( aExportItems, GetScript(), pFontToOutput, bWriteCombChars );
527 // HasTextItem only allowed in the above range
528 m_rExport.m_aCurrentCharPropStarts.pop();
529 m_rExport.m_pOutFormatNode = pOldMod;
532 if( m_rNode.GetpSwpHints() == nullptr )
533 m_rExport.SetCurItemSet(nullptr);
535 OSL_ENSURE( pFont, "must be *some* font associated with this txtnode" );
536 if ( pFont )
538 SvxFontItem aFont( *pFont );
540 if ( rParentFont != aFont )
541 m_rExport.AttrOutput().OutputItem( aFont );
544 // Output grab bag attributes
545 if (pGrabBag)
546 m_rExport.AttrOutput().OutputItem( *pGrabBag );
549 // Toggle Properties
551 // If the value of the toggle property appears at multiple levels of the style hierarchy (17.7.2), their
552 // effective values shall be combined as follows:
554 // value_{effective} = val_{table} XOR val_{paragraph} XOR val_{character}
556 // If the value specified by the document defaults is true, the effective value is true.
557 // Otherwise, the values are combined by a Boolean XOR as follows:
558 // i.e., the effective value to be applied to the content shall be true if its effective value is true for
559 // an odd number of levels of the style hierarchy.
561 // To prevent such logic inside output, it is required to write inline w:b token on content level.
562 void SwWW8AttrIter::handleToggleProperty(SfxItemSet& rExportSet, const SwFormatCharFormat* pCharFormatItem,
563 sal_uInt16 nWhich, const SfxPoolItem* pValue)
565 if (rExportSet.HasItem(nWhich) || !pValue)
566 return;
568 bool hasPropertyInCharStyle = false;
569 bool hasPropertyInParaStyle = false;
571 // get bold flag from specified character style
572 if (pCharFormatItem)
574 if (const SwCharFormat* pCharFormat = pCharFormatItem->GetCharFormat())
576 if (const SfxPoolItem* pItem = pCharFormat->GetAttrSet().GetItem(nWhich))
578 hasPropertyInCharStyle = (*pItem == *pValue);
583 // get bold flag from specified paragraph style
585 SwTextFormatColl& rTextColl = static_cast<SwTextFormatColl&>( m_rNode.GetAnyFormatColl() );
586 sal_uInt16 nStyle = m_rExport.m_pStyles->GetSlot( &rTextColl );
587 nStyle = ( nStyle != 0xfff ) ? nStyle : 0;
588 const SwFormat* pFormat = m_rExport.m_pStyles->GetSwFormat(nStyle);
589 if (pFormat)
591 if (const SfxPoolItem* pItem = pFormat->GetAttrSet().GetItem(nWhich))
593 hasPropertyInParaStyle = (*pItem == *pValue);
598 // add inline property
599 if (hasPropertyInCharStyle && hasPropertyInParaStyle)
601 rExportSet.Put(*pValue);
605 bool SwWW8AttrIter::IsWatermarkFrame()
607 if (maFlyFrames.size() != 1)
608 return false;
610 while ( maFlyIter != maFlyFrames.end() )
612 const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
614 if (pSdrObj)
616 if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
617 return true;
619 ++maFlyIter;
622 return false;
625 bool SwWW8AttrIter::IsAnchorLinkedToThisNode( SwNodeOffset nNodePos )
627 if ( maFlyIter == maFlyFrames.end() )
628 return false;
630 /* if current node position and the anchor position are the same
631 then the frame anchor is linked to this node
633 return nNodePos == maFlyIter->GetPosition().GetNodeIndex();
636 bool SwWW8AttrIter::HasFlysAt(sal_Int32 nSwPos) const
638 for (const auto& rFly : maFlyFrames)
640 const SwPosition& rAnchor = rFly.GetPosition();
641 const sal_Int32 nPos = rAnchor.GetContentIndex();
642 if (nPos == nSwPos)
644 return true;
648 return false;
651 FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
653 // collection point to first gather info about all of the potentially linked textboxes: to be analyzed later.
654 ww8::FrameIter linkedTextboxesIter = maFlyIter;
655 while ( linkedTextboxesIter != maFlyFrames.end() )
657 uno::Reference< drawing::XShape > xShape;
658 ww8::Frame aFrame = *linkedTextboxesIter;
659 const SdrObject* pSdrObj = aFrame.GetFrameFormat().FindRealSdrObject();
660 if( pSdrObj )
661 xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
662 uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
663 uno::Reference< beans::XPropertySetInfo > xPropertySetInfo;
664 if( xPropertySet.is() )
665 xPropertySetInfo = xPropertySet->getPropertySetInfo();
666 if( xPropertySetInfo.is() )
668 MSWordExportBase::LinkedTextboxInfo aLinkedTextboxInfo;
670 const OUString sLinkChainName = lcl_getLinkChainName(xPropertySet, xPropertySetInfo);
672 if( xPropertySetInfo->hasPropertyByName("ChainNextName") )
673 xPropertySet->getPropertyValue("ChainNextName") >>= aLinkedTextboxInfo.sNextChain;
674 if( xPropertySetInfo->hasPropertyByName("ChainPrevName") )
675 xPropertySet->getPropertyValue("ChainPrevName") >>= aLinkedTextboxInfo.sPrevChain;
677 //collect a list of linked textboxes: those with a NEXT or PREVIOUS link
678 if( !aLinkedTextboxInfo.sNextChain.isEmpty() || !aLinkedTextboxInfo.sPrevChain.isEmpty() )
680 assert( !sLinkChainName.isEmpty() );
682 //there are many discarded duplicates in documents - no duplicates allowed in the list, so try to find the real one.
683 //if this LinkDisplayName/ChainName already exists on a different shape...
684 // the earlier processed duplicates are thrown out unless this one can be proved as bad. (last processed duplicate usually is stored)
685 auto linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
686 if( linkFinder != m_rExport.m_aLinkedTextboxesHelper.end() )
688 //If my NEXT/PREV targets have already been discovered, but don't match me, then assume I'm an abandoned remnant
689 // (this logic fails if both me and one of my links are duplicated, and the remnants were added first.)
690 linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sNextChain);
691 if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sPrevChain != sLinkChainName) )
693 ++linkedTextboxesIter;
694 break;
697 linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sPrevChain);
698 if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sNextChain != sLinkChainName) )
700 ++linkedTextboxesIter;
701 break;
704 m_rExport.m_bLinkedTextboxesHelperInitialized = false;
705 m_rExport.m_aLinkedTextboxesHelper[sLinkChainName] = aLinkedTextboxInfo;
708 ++linkedTextboxesIter;
711 if (maFlyIter == maFlyFrames.end())
713 // tdf#143039 postponed prevents fly duplication at end of paragraph
714 return m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_NONE;
718 #i2916#
719 May have an anchored graphic to be placed, loop through sorted array
720 and output all at this position
722 while ( maFlyIter != maFlyFrames.end() )
724 const SwPosition &rAnchor = maFlyIter->GetPosition();
725 const sal_Int32 nPos = rAnchor.GetContentIndex();
727 assert(nPos >= nSwPos && "a fly must get flagged as a nextAttr/CurrentPos");
728 if ( nPos != nSwPos )
729 return FLY_NOT_PROCESSED ; // We haven't processed the fly
731 const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
733 if (pSdrObj)
735 if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
737 // This is a watermark object. Should be written ONLY in the header
738 if(m_rExport.m_nTextTyp == TXT_HDFT)
740 // Should write a watermark in the header
741 m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
743 else
745 // Should not write watermark object in the main body text
748 else
750 // This is not a watermark object - write normally
751 m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
754 else
756 // This is not a watermark object - write normally
757 m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
759 ++maFlyIter;
761 return ( m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_PROCESSED ) ;
764 bool SwWW8AttrIter::IsTextAttr( sal_Int32 nSwPos ) const
766 // search for attrs with dummy character or content
767 if (const SwpHints* pTextAttrs = m_rNode.GetpSwpHints())
769 for (size_t i = 0; i < pTextAttrs->Count(); ++i)
771 const SwTextAttr* pHt = pTextAttrs->Get(i);
772 if (nSwPos == pHt->GetStart())
774 if (pHt->HasDummyChar() || pHt->HasContent() )
776 return true;
779 else if (nSwPos < pHt->GetStart())
781 break; // sorted by start
786 return false;
789 bool SwWW8AttrIter::IsExportableAttr(sal_Int32 nSwPos) const
791 if (const SwpHints* pTextAttrs = m_rNode.GetpSwpHints())
793 for (size_t i = 0; i < pTextAttrs->Count(); ++i)
795 const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
796 const sal_Int32 nStart = pHt->GetStart();
797 const sal_Int32 nEnd = pHt->End() ? *pHt->End() : INT_MAX;
798 if (nSwPos >= nStart && nSwPos < nEnd)
800 switch (pHt->GetAttr().Which())
802 // Metadata fields should be dynamically generated, not dumped as text.
803 case RES_TXTATR_METAFIELD:
804 return false;
810 return true;
813 bool SwWW8AttrIter::IsDropCap( int nSwPos )
815 // see if the current position falls on a DropCap
816 int nDropChars = mrSwFormatDrop.GetChars();
817 bool bWholeWord = mrSwFormatDrop.GetWholeWord();
818 if (bWholeWord)
820 const sal_Int32 nWordLen = m_rNode.GetDropLen(0);
821 if(nSwPos == nWordLen && nSwPos != 0)
822 return true;
824 else
826 if (nSwPos == nDropChars && nSwPos != 0)
827 return true;
829 return false;
832 bool SwWW8AttrIter::RequiresImplicitBookmark()
834 return std::any_of(m_rExport.m_aImplicitBookmarks.begin(), m_rExport.m_aImplicitBookmarks.end(),
835 [this](const aBookmarkPair& rBookmarkPair) { return rBookmarkPair.second == m_rNode.GetIndex(); });
838 //HasItem is for the summary of the double attributes: Underline and WordlineMode as TextItems.
839 // OutAttr () calls the output function, which can call HasItem() for other items at the attribute's start position.
840 // Only attributes with end can be queried.
841 // It searches with bDeep
842 const SfxPoolItem* SwWW8AttrIter::HasTextItem( sal_uInt16 nWhich ) const
844 const SfxPoolItem* pRet = nullptr;
845 const SwpHints* pTextAttrs = m_rNode.GetpSwpHints();
846 if (pTextAttrs && !m_rExport.m_aCurrentCharPropStarts.empty())
848 const sal_Int32 nTmpSwPos = m_rExport.m_aCurrentCharPropStarts.top();
849 for (size_t i = 0; i < pTextAttrs->Count(); ++i)
851 const SwTextAttr* pHt = pTextAttrs->Get(i);
852 const SfxPoolItem* pItem = &pHt->GetAttr();
853 const sal_Int32 * pAtrEnd = nullptr;
854 if( nullptr != ( pAtrEnd = pHt->End() ) && // only Attr with an end
855 nTmpSwPos >= pHt->GetStart() && nTmpSwPos < *pAtrEnd )
857 if ( nWhich == pItem->Which() )
859 pRet = pItem; // found it
860 break;
862 else if( RES_TXTATR_INETFMT == pHt->Which() ||
863 RES_TXTATR_CHARFMT == pHt->Which() ||
864 RES_TXTATR_AUTOFMT == pHt->Which() )
866 const SfxItemSet* pSet = CharFormat::GetItemSet( pHt->GetAttr() );
867 const SfxPoolItem* pCharItem;
868 if ( pSet &&
869 SfxItemState::SET == pSet->GetItemState( nWhich, pHt->Which() != RES_TXTATR_AUTOFMT, &pCharItem ) )
871 pRet = pCharItem; // found it
872 break;
876 else if (nTmpSwPos < pHt->GetStart())
877 break; // nothing more to come
880 return pRet;
883 void WW8Export::GetCurrentItems(ww::bytes &rItems) const
885 rItems.insert(rItems.end(), m_pO->begin(), m_pO->end());
888 const SfxPoolItem& SwWW8AttrIter::GetItem(sal_uInt16 nWhich) const
890 const SfxPoolItem* pRet = HasTextItem(nWhich);
891 return pRet ? *pRet : m_rNode.SwContentNode::GetAttr(nWhich);
894 void WW8AttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 /*nPos*/, const SwFormatRuby& rRuby )
896 WW8Ruby aWW8Ruby(rNode, rRuby, GetExport());
897 OUString aStr =
898 FieldString( ww::eEQ )
899 + "\\* jc"
900 + OUString::number(aWW8Ruby.GetJC())
901 + " \\* \"Font:"
902 + aWW8Ruby.GetFontFamily()
903 + "\" \\* hps"
904 + OUString::number((aWW8Ruby.GetRubyHeight() + 5) / 10)
905 + " \\o";
906 if (aWW8Ruby.GetDirective())
908 aStr += OUString::Concat(u"\\a") + OUStringChar(aWW8Ruby.GetDirective());
910 aStr += "(\\s\\up " + OUString::number((aWW8Ruby.GetBaseHeight() + 10) / 20 - 1) + "(";
911 aStr += rRuby.GetText() + ")";
913 // The parameter separator depends on the FIB.lid
914 if ( m_rWW8Export.m_pFib->getNumDecimalSep() == '.' )
915 aStr += ",";
916 else
917 aStr += ";";
919 m_rWW8Export.OutputField( nullptr, ww::eEQ, aStr,
920 FieldFlags::Start | FieldFlags::CmdStart );
923 void WW8AttributeOutput::EndRuby(const SwTextNode& /*rNode*/, sal_Int32 /*nPos*/)
925 m_rWW8Export.WriteChar( ')' );
926 m_rWW8Export.OutputField( nullptr, ww::eEQ, OUString(), FieldFlags::End | FieldFlags::Close );
929 OUString AttributeOutputBase::ConvertURL( const OUString& rUrl, bool bAbsoluteOut )
931 OUString sURL = rUrl;
933 INetURLObject anAbsoluteParent(m_sBaseURL);
934 OUString sConvertedParent = INetURLObject::GetScheme( anAbsoluteParent.GetProtocol() ) + anAbsoluteParent.GetURLPath();
935 OUString sParentPath = sConvertedParent.isEmpty() ? m_sBaseURL : sConvertedParent;
937 if ( bAbsoluteOut )
939 INetURLObject anAbsoluteNew;
941 if ( anAbsoluteParent.GetNewAbsURL( rUrl, &anAbsoluteNew ) )
942 sURL = anAbsoluteNew.GetMainURL( INetURLObject::DecodeMechanism::NONE );
944 else
946 OUString sToConvert = rUrl.replaceAll( "\\", "/" );
947 INetURLObject aURL( sToConvert );
948 sToConvert = INetURLObject::GetScheme( aURL.GetProtocol() ) + aURL.GetURLPath();
949 OUString sRelative = INetURLObject::GetRelURL( sParentPath, sToConvert, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::NONE );
950 if ( !sRelative.isEmpty() )
951 sURL = sRelative;
954 return sURL;
957 bool AttributeOutputBase::AnalyzeURL( const OUString& rUrl, const OUString& /*rTarget*/, OUString* pLinkURL, OUString* pMark )
959 bool bBookMarkOnly = false;
961 OUString sMark;
962 OUString sURL;
964 if ( rUrl.getLength() > 1 && rUrl[0] == '#' )
966 sMark = BookmarkToWriter( rUrl.subView(1) );
968 const sal_Int32 nPos = sMark.lastIndexOf( cMarkSeparator );
970 const OUString sRefType(nPos>=0 && nPos+1<sMark.getLength() ?
971 sMark.copy(nPos+1).replaceAll(" ", "") :
972 OUString());
974 // #i21465# Only interested in outline references
975 if ( !sRefType.isEmpty() &&
976 (sRefType == "outline" || sRefType == "graphic" || sRefType == "frame" || sRefType == "ole" || sRefType == "region" || sRefType == "table") )
978 for ( const auto& rBookmarkPair : GetExport().m_aImplicitBookmarks )
980 if ( rBookmarkPair.first == sMark )
982 sMark = "_toc" + OUString::number( sal_Int32(rBookmarkPair.second) );
983 break;
988 else
990 INetURLObject aURL( rUrl, INetProtocol::NotValid );
991 sURL = aURL.GetURLNoMark( INetURLObject::DecodeMechanism::Unambiguous );
992 sMark = aURL.GetMark( INetURLObject::DecodeMechanism::Unambiguous );
993 INetProtocol aProtocol = aURL.GetProtocol();
995 if ( aProtocol == INetProtocol::File || aProtocol == INetProtocol::NotValid )
997 // INetProtocol::NotValid - may be a relative link
998 bool bExportRelative = officecfg::Office::Common::Save::URL::FileSystem::get();
999 sURL = ConvertURL( rUrl, !bExportRelative );
1003 if ( !sMark.isEmpty() && sURL.isEmpty() )
1004 bBookMarkOnly = true;
1006 *pMark = sMark;
1007 *pLinkURL = sURL;
1008 return bBookMarkOnly;
1011 bool WW8AttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
1013 bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
1015 OUString sURL = *pLinkURL;
1017 if ( !sURL.isEmpty() )
1018 sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
1020 if (bBookMarkOnly)
1022 sURL = FieldString(ww::eHYPERLINK);
1023 *pMark = GetExport().BookmarkToWord(*pMark);
1025 else
1026 sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
1028 if ( !pMark->isEmpty() )
1029 sURL += " \\l \"" + *pMark + "\"";
1031 if ( !rTarget.isEmpty() )
1032 sURL += " \\n " + rTarget;
1034 *pLinkURL = sURL;
1036 return bBookMarkOnly;
1039 void WW8AttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
1041 m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
1042 m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
1045 bool WW8AttributeOutput::StartURL( const OUString &rUrl, const OUString &rTarget )
1047 INetURLObject aURL( rUrl );
1048 OUString sURL;
1049 OUString sMark;
1051 bool bBookMarkOnly = AnalyzeURL( rUrl, rTarget, &sURL, &sMark );
1053 m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::Start | FieldFlags::CmdStart );
1055 // write the reference to the "picture" structure
1056 sal_uInt64 nDataStt = m_rWW8Export.m_pDataStrm->Tell();
1057 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell() );
1059 // WinWord 2000 doesn't write this - so it's a temp solution by W97 ?
1060 m_rWW8Export.WriteChar( 0x01 );
1062 static sal_uInt8 aArr1[] = {
1063 0x03, 0x6a, 0,0,0,0, // sprmCPicLocation
1065 0x06, 0x08, 0x01, // sprmCFData
1066 0x55, 0x08, 0x01, // sprmCFSpec
1067 0x02, 0x08, 0x01 // sprmCFFieldVanish
1069 sal_uInt8* pDataAdr = aArr1 + 2;
1070 Set_UInt32( pDataAdr, nDataStt );
1072 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), sizeof( aArr1 ), aArr1 );
1074 m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::CmdEnd );
1076 // now write the picture structure
1077 sURL = aURL.GetURLNoMark();
1079 // Compare the URL written by AnalyzeURL with the original one to see if
1080 // the output URL is absolute or relative.
1081 OUString sRelativeURL;
1082 if ( !rUrl.isEmpty() )
1083 sRelativeURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), rUrl );
1084 bool bAbsolute = sRelativeURL == rUrl;
1086 static sal_uInt8 aURLData1[] = {
1087 0,0,0,0, // len of struct
1088 0x44,0, // the start of "next" data
1089 0,0,0,0,0,0,0,0,0,0, // PIC-Structure!
1090 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
1091 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
1092 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
1093 0,0,0,0, // /
1095 static sal_uInt8 MAGIC_A[] = {
1096 // start of "next" data
1097 0xD0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
1098 0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
1101 m_rWW8Export.m_pDataStrm->WriteBytes(aURLData1, sizeof(aURLData1));
1102 /* Write HFD Structure */
1103 sal_uInt8 nAnchor = 0x00;
1104 if ( !sMark.isEmpty() )
1105 nAnchor = 0x08;
1106 m_rWW8Export.m_pDataStrm->WriteUChar(nAnchor); // HFDBits
1107 m_rWW8Export.m_pDataStrm->WriteBytes(MAGIC_A, sizeof(MAGIC_A)); //clsid
1109 /* Write Hyperlink Object see [MS-OSHARED] spec*/
1110 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, 0x00000002);
1111 sal_uInt32 nFlag = bBookMarkOnly ? 0 : 0x01;
1112 if ( bAbsolute )
1113 nFlag |= 0x02;
1114 if ( !sMark.isEmpty() )
1115 nFlag |= 0x08;
1116 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, nFlag );
1118 INetProtocol eProto = aURL.GetProtocol();
1119 if ( eProto == INetProtocol::File || eProto == INetProtocol::Smb )
1121 // version 1 (for a document)
1123 static sal_uInt8 MAGIC_C[] = {
1124 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1125 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
1126 0x00, 0x00
1129 static sal_uInt8 MAGIC_D[] = {
1130 0xFF, 0xFF, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00,
1131 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1132 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1135 // save the links to files as relative
1136 sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
1137 if ( eProto == INetProtocol::File && sURL.startsWith( "/" ) )
1138 sURL = aURL.PathToFileName();
1140 // special case for the absolute windows names
1141 // (convert '/c:/foo/bar.doc' into 'c:\foo\bar.doc')
1142 if (sURL.getLength()>=3)
1144 const sal_Unicode aDrive = sURL[1];
1145 if ( sURL[0]=='/' && sURL[2]==':' &&
1146 ( (aDrive>='A' && aDrive<='Z' ) || (aDrive>='a' && aDrive<='z') ) )
1148 sURL = sURL.copy(1).replaceAll("/", "\\");
1152 // n#261623 convert smb notation to '\\'
1153 const char pSmb[] = "smb://";
1154 if ( eProto == INetProtocol::Smb && sURL.startsWith( pSmb ) )
1156 sURL = sURL.copy( sizeof(pSmb)-3 ).replaceAll( "/", "\\" );
1159 m_rWW8Export.m_pDataStrm->WriteBytes(MAGIC_C, sizeof(MAGIC_C));
1160 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, sURL.getLength()+1 );
1161 SwWW8Writer::WriteString8( *m_rWW8Export.m_pDataStrm, sURL, true,
1162 RTL_TEXTENCODING_MS_1252 );
1163 m_rWW8Export.m_pDataStrm->WriteBytes(MAGIC_D, sizeof(MAGIC_D));
1165 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, 2*sURL.getLength() + 6 );
1166 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, 2*sURL.getLength() );
1167 SwWW8Writer::WriteShort( *m_rWW8Export.m_pDataStrm, 3 );
1168 SwWW8Writer::WriteString16( *m_rWW8Export.m_pDataStrm, sURL, false );
1170 else if ( eProto != INetProtocol::NotValid )
1172 // version 2 (simple url)
1173 // and write some data to the data stream, but don't ask
1174 // what the data mean, except for the URL.
1175 // The First piece is the WW8_PIC structure.
1176 static sal_uInt8 MAGIC_B[] = {
1177 0xE0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
1178 0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
1181 m_rWW8Export.m_pDataStrm->WriteBytes(MAGIC_B, sizeof(MAGIC_B));
1182 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, 2 * ( sURL.getLength() + 1 ) );
1183 SwWW8Writer::WriteString16( *m_rWW8Export.m_pDataStrm, sURL, true );
1186 if ( !sMark.isEmpty() )
1188 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, sMark.getLength()+1 );
1189 SwWW8Writer::WriteString16( *m_rWW8Export.m_pDataStrm, sMark, true );
1191 SwWW8Writer::WriteLong( *m_rWW8Export.m_pDataStrm, nDataStt,
1192 m_rWW8Export.m_pDataStrm->Tell() - nDataStt );
1194 return true;
1197 bool WW8AttributeOutput::EndURL(bool const)
1199 m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, OUString(), FieldFlags::Close );
1201 return true;
1204 OUString BookmarkToWriter(std::u16string_view rBookmark)
1206 return INetURLObject::decode(rBookmark,
1207 INetURLObject::DecodeMechanism::Unambiguous, RTL_TEXTENCODING_ASCII_US);
1210 void SwWW8AttrIter::OutSwFormatRefMark(const SwFormatRefMark& rAttr)
1212 if(m_rExport.HasRefToAttr(rAttr.GetRefName()))
1213 m_rExport.AppendBookmark( m_rExport.GetBookmarkName( REF_SETREFATTR,
1214 &rAttr.GetRefName(), 0 ));
1217 void SwWW8AttrIter::SplitRun( sal_Int32 nSplitEndPos )
1219 auto aIter = std::find_if(maCharRuns.begin(), maCharRuns.end(),
1220 [nSplitEndPos](const CharRunEntry& rCharRun) { return rCharRun.mnEndPos >= nSplitEndPos; });
1221 if (aIter == maCharRuns.end() || aIter->mnEndPos == nSplitEndPos)
1222 return;
1224 CharRunEntry aNewEntry = *aIter;
1225 aIter->mnEndPos = nSplitEndPos;
1226 maCharRuns.insert( ++aIter, aNewEntry);
1227 maCharRunIter = maCharRuns.begin();
1228 IterToCurrent();
1229 m_nCurrentSwPos = SearchNext(1);
1232 void WW8AttributeOutput::FieldVanish(const OUString& rText, ww::eField /*eType*/, OUString const*const /*pBookmarkName*/)
1234 ww::bytes aItems;
1235 m_rWW8Export.GetCurrentItems( aItems );
1237 // sprmCFFieldVanish
1238 SwWW8Writer::InsUInt16( aItems, NS_sprm::CFFldVanish::val );
1239 aItems.push_back( 1 );
1241 sal_uInt16 nStt_sprmCFSpec = aItems.size();
1243 // sprmCFSpec -- fSpec-Attribute true
1244 SwWW8Writer::InsUInt16( aItems, 0x855 );
1245 aItems.push_back( 1 );
1247 m_rWW8Export.WriteChar( '\x13' );
1248 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
1249 aItems.data() );
1250 m_rWW8Export.OutSwString(rText, 0, rText.getLength());
1251 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), nStt_sprmCFSpec,
1252 aItems.data() );
1253 m_rWW8Export.WriteChar( '\x15' );
1254 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
1255 aItems.data() );
1258 void AttributeOutputBase::TOXMark( const SwTextNode& rNode, const SwTOXMark& rAttr )
1260 // it's a field; so get the Text from the Node and build the field
1261 OUString sText;
1262 ww::eField eType = ww::eNONE;
1264 const SwTextTOXMark& rTextTOXMark = *rAttr.GetTextTOXMark();
1265 const sal_Int32* pTextEnd = rTextTOXMark.End();
1266 if ( pTextEnd ) // has range?
1268 sText = rNode.GetExpandText(nullptr, rTextTOXMark.GetStart(),
1269 *pTextEnd - rTextTOXMark.GetStart() );
1271 else
1272 sText = rAttr.GetAlternativeText();
1274 OUString sUserTypeName;
1275 auto aType = rAttr.GetTOXType()->GetType();
1276 // user index mark, it needs XE with \f
1277 if ( TOX_USER == aType )
1279 sUserTypeName = rAttr.GetTOXType()->GetTypeName();
1280 if ( !sUserTypeName.isEmpty() )
1281 aType = TOX_INDEX;
1284 switch ( aType )
1286 case TOX_INDEX:
1287 eType = ww::eXE;
1288 if ( !rAttr.GetPrimaryKey().isEmpty() )
1290 if ( !rAttr.GetSecondaryKey().isEmpty() )
1292 sText = rAttr.GetSecondaryKey() + ":" + sText;
1295 sText = rAttr.GetPrimaryKey() + ":" + sText;
1297 sText = " XE \"" + sText + "\" ";
1299 if (!sUserTypeName.isEmpty())
1301 sText += "\\f \"" + sUserTypeName + "\" ";
1303 break;
1305 case TOX_USER:
1306 sText += "\" \\f \"" + OUStringChar(static_cast<char>( 'A' + GetExport( ).GetId( *rAttr.GetTOXType() ) ));
1307 [[fallthrough]];
1308 case TOX_CONTENT:
1310 eType = ww::eTC;
1311 sText = " TC \"" + sText;
1312 sal_uInt16 nLvl = rAttr.GetLevel();
1313 if (nLvl > WW8ListManager::nMaxLevel)
1314 nLvl = WW8ListManager::nMaxLevel;
1316 sText += "\" \\l " + OUString::number(nLvl) + " ";
1318 break;
1319 default:
1320 OSL_ENSURE( false, "Unhandled option for toc export" );
1321 break;
1324 if (!sText.isEmpty())
1326 OUString const* pBookmarkName(nullptr);
1327 if (auto const it = GetExport().m_TOXMarkBookmarksByTOXMark.find(&rAttr);
1328 it != GetExport().m_TOXMarkBookmarksByTOXMark.end())
1330 pBookmarkName = &it->second;
1332 FieldVanish(sText, eType, pBookmarkName);
1336 int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos)
1338 int nRet = 0;
1339 if ( const SwpHints* pTextAttrs = m_rNode.GetpSwpHints() )
1341 m_rExport.m_aCurrentCharPropStarts.push( nPos );
1342 const sal_Int32* pEnd;
1343 // first process ends of attributes with extent
1344 for (size_t i = 0; i < pTextAttrs->Count(); ++i)
1346 const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
1347 const SfxPoolItem* pItem = &pHt->GetAttr();
1348 switch ( pItem->Which() )
1350 case RES_TXTATR_INETFMT:
1351 pEnd = pHt->End();
1352 if (nPos == *pEnd && nPos != pHt->GetStart())
1354 if (m_rExport.AttrOutput().EndURL(nPos == m_rNode.Len()))
1355 --nRet;
1357 break;
1358 case RES_TXTATR_REFMARK:
1359 pEnd = pHt->End();
1360 if (nullptr != pEnd && nPos == *pEnd && nPos != pHt->GetStart())
1362 OutSwFormatRefMark(*static_cast<const SwFormatRefMark*>(pItem));
1363 --nRet;
1365 break;
1366 case RES_TXTATR_CJK_RUBY:
1367 pEnd = pHt->End();
1368 if (nPos == *pEnd && nPos != pHt->GetStart())
1370 m_rExport.AttrOutput().EndRuby(rNode, nPos);
1371 --nRet;
1373 break;
1375 if (nPos < pHt->GetAnyEnd())
1376 break; // sorted by end
1378 for ( size_t i = 0; i < pTextAttrs->Count(); ++i )
1380 const SwTextAttr* pHt = pTextAttrs->Get(i);
1381 const SfxPoolItem* pItem = &pHt->GetAttr();
1382 switch ( pItem->Which() )
1384 case RES_TXTATR_INETFMT:
1385 if ( nPos == pHt->GetStart() )
1387 const SwFormatINetFormat *rINet = static_cast< const SwFormatINetFormat* >( pItem );
1388 if ( m_rExport.AttrOutput().StartURL( rINet->GetValue(), rINet->GetTargetFrame() ) )
1389 ++nRet;
1391 pEnd = pHt->End();
1392 if (nPos == *pEnd && nPos == pHt->GetStart())
1393 { // special case: empty must be handled here
1394 if (m_rExport.AttrOutput().EndURL(nPos == m_rNode.Len()))
1395 --nRet;
1397 break;
1398 case RES_TXTATR_REFMARK:
1399 if ( nPos == pHt->GetStart() )
1401 OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
1402 ++nRet;
1404 pEnd = pHt->End();
1405 if (nullptr != pEnd && nPos == *pEnd && nPos == pHt->GetStart())
1406 { // special case: empty TODO: is this possible or would empty one have pEnd null?
1407 OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
1408 --nRet;
1410 break;
1411 case RES_TXTATR_TOXMARK:
1412 if ( nPos == pHt->GetStart() )
1413 m_rExport.AttrOutput().TOXMark( m_rNode, *static_cast< const SwTOXMark* >( pItem ) );
1414 break;
1415 case RES_TXTATR_CJK_RUBY:
1416 if ( nPos == pHt->GetStart() )
1418 m_rExport.AttrOutput().StartRuby( m_rNode, nPos, *static_cast< const SwFormatRuby* >( pItem ) );
1419 ++nRet;
1421 pEnd = pHt->End();
1422 if (nPos == *pEnd && nPos == pHt->GetStart())
1423 { // special case: empty must be handled here
1424 m_rExport.AttrOutput().EndRuby( m_rNode, nPos );
1425 --nRet;
1427 break;
1429 if (nPos < pHt->GetStart())
1430 break; // sorted by start
1432 m_rExport.m_aCurrentCharPropStarts.pop(); // HasTextItem only allowed in the above range
1434 return nRet;
1437 bool SwWW8AttrIter::IncludeEndOfParaCRInRedlineProperties( sal_Int32 nEnd ) const
1439 // search next Redline
1440 for( SwRedlineTable::size_type nPos = m_nCurRedlinePos;
1441 nPos < m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size(); ++nPos )
1443 const SwRangeRedline *pRange = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[nPos];
1444 const SwPosition* pEnd = pRange->End();
1445 const SwPosition* pStart = pRange->Start();
1446 bool bBreak = true;
1447 // In word the paragraph end marker is a real character, in writer it is not.
1448 // Here we find out if the para end marker we will emit is affected by
1449 // redlining, in which case it must be included by the range of character
1450 // attributes that contains the redlining information.
1451 if (pEnd->GetNode() == m_rNode)
1453 if (pEnd->GetContentIndex() == nEnd)
1455 // This condition detects if the pseudo-char we will export
1456 // should be explicitly included by the redlining char
1457 // properties on this node because the redlining ends right
1458 // after it
1459 return true;
1461 bBreak = false;
1463 if (pStart->GetNode() == m_rNode)
1465 if (pStart->GetContentIndex() == nEnd)
1467 // This condition detects if the pseudo-char we will export
1468 // should be explicitly included by the redlining char
1469 // properties on this node because the redlining starts right
1470 // before it
1471 return true;
1473 bBreak = false;
1475 if (pStart->GetNodeIndex()-1 == m_rNode.GetIndex())
1477 if (pStart->GetContentIndex() == 0)
1479 // This condition detects if the pseudo-char we will export
1480 // should be implicitly excluded by the redlining char
1481 // properties starting on the next node.
1482 return true;
1484 bBreak = false;
1487 if (bBreak)
1488 break;
1490 return false;
1493 const SwRedlineData* SwWW8AttrIter::GetParagraphLevelRedline( )
1495 m_pCurRedline = nullptr;
1497 // ToDo : this is not the most ideal ... should start maybe from 'nCurRedlinePos'
1498 for(SwRangeRedline* pRedl : m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable())
1500 const SwPosition* pCheckedStt = pRedl->Start();
1502 if( pCheckedStt->GetNode() == m_rNode )
1504 // Maybe add here a check that also the start & end of the redline is the entire paragraph
1506 // Only return if this is a paragraph formatting redline
1507 if (pRedl->GetType() == RedlineType::ParagraphFormat)
1509 // write data of this redline
1510 m_pCurRedline = pRedl;
1511 return &( m_pCurRedline->GetRedlineData() );
1515 return nullptr;
1518 const SwRedlineData* SwWW8AttrIter::GetRunLevelRedline( sal_Int32 nPos )
1520 if( m_pCurRedline )
1522 const SwPosition* pEnd = m_pCurRedline->End();
1523 if (pEnd->GetNode() != m_rNode || pEnd->GetContentIndex() > nPos)
1525 switch( m_pCurRedline->GetType() )
1527 case RedlineType::Insert:
1528 case RedlineType::Delete:
1529 case RedlineType::Format:
1530 // write data of this redline
1531 return &( m_pCurRedline->GetRedlineData() );
1532 default:
1533 break;
1536 m_pCurRedline = nullptr;
1537 ++m_nCurRedlinePos;
1540 assert(!m_pCurRedline);
1541 // search next Redline
1542 for( ; m_nCurRedlinePos < m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size();
1543 ++m_nCurRedlinePos )
1545 const SwRangeRedline* pRedl = m_rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ m_nCurRedlinePos ];
1547 auto [pStt, pEnd] = pRedl->StartEnd(); // SwPosition*
1549 if( pStt->GetNode() == m_rNode )
1551 if( pStt->GetContentIndex() >= nPos )
1553 if( pStt->GetContentIndex() == nPos )
1555 switch( pRedl->GetType() )
1557 case RedlineType::Insert:
1558 case RedlineType::Delete:
1559 case RedlineType::Format:
1560 // write data of this redline
1561 m_pCurRedline = pRedl;
1562 return &( m_pCurRedline->GetRedlineData() );
1563 default:
1564 break;
1567 break;
1570 else
1572 break;
1575 if( pEnd->GetNode() == m_rNode &&
1576 pEnd->GetContentIndex() < nPos )
1578 m_pCurRedline = pRedl;
1579 break;
1582 return nullptr;
1585 SvxFrameDirection MSWordExportBase::GetCurrentPageDirection() const
1587 const SwFrameFormat &rFormat = m_pCurrentPageDesc
1588 ? m_pCurrentPageDesc->GetMaster()
1589 : m_rDoc.GetPageDesc( 0 ).GetMaster();
1590 return rFormat.GetFrameDir().GetValue();
1593 SvxFrameDirection MSWordExportBase::GetDefaultFrameDirection( ) const
1595 SvxFrameDirection nDir = SvxFrameDirection::Environment;
1597 if ( m_bOutPageDescs )
1598 nDir = GetCurrentPageDirection( );
1599 else if ( m_pOutFormatNode )
1601 if ( m_bOutFlyFrameAttrs ) //frame
1603 nDir = TrueFrameDirection( *static_cast< const SwFrameFormat * >(m_pOutFormatNode) );
1605 else if ( auto pNd = dynamic_cast< const SwContentNode *>( m_pOutFormatNode ) ) //paragraph
1607 SwPosition aPos( *pNd );
1608 nDir = m_rDoc.GetTextDirection( aPos );
1610 else if ( dynamic_cast< const SwTextFormatColl *>( m_pOutFormatNode ) != nullptr )
1612 if ( MsLangId::isRightToLeft( GetAppLanguage()) )
1613 nDir = SvxFrameDirection::Horizontal_RL_TB;
1614 else
1615 nDir = SvxFrameDirection::Horizontal_LR_TB; //what else can we do :-(
1619 if ( nDir == SvxFrameDirection::Environment )
1621 // fdo#44029 put direction right when the locale are RTL.
1622 if( MsLangId::isRightToLeft( GetAppLanguage()) )
1623 nDir = SvxFrameDirection::Horizontal_RL_TB;
1624 else
1625 nDir = SvxFrameDirection::Horizontal_LR_TB; //Set something
1628 return nDir;
1631 SvxFrameDirection MSWordExportBase::TrueFrameDirection( const SwFrameFormat &rFlyFormat ) const
1633 const SwFrameFormat *pFlyFormat = &rFlyFormat;
1634 const SvxFrameDirectionItem* pItem = nullptr;
1635 while ( pFlyFormat )
1637 pItem = &pFlyFormat->GetFrameDir();
1638 if ( SvxFrameDirection::Environment == pItem->GetValue() )
1640 pItem = nullptr;
1641 const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
1642 if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
1643 pAnchor->GetAnchorNode() )
1645 pFlyFormat = pAnchor->GetAnchorNode()->GetFlyFormat();
1647 else
1648 pFlyFormat = nullptr;
1650 else
1651 pFlyFormat = nullptr;
1654 SvxFrameDirection nRet;
1655 if ( pItem )
1656 nRet = pItem->GetValue();
1657 else
1658 nRet = GetCurrentPageDirection();
1660 OSL_ENSURE( nRet != SvxFrameDirection::Environment, "leaving with environment direction" );
1661 return nRet;
1664 const SvxBrushItem* WW8Export::GetCurrentPageBgBrush() const
1666 const SwFrameFormat &rFormat = m_pCurrentPageDesc
1667 ? m_pCurrentPageDesc->GetMaster()
1668 : m_rDoc.GetPageDesc(0).GetMaster();
1670 //If not set, or "no fill", get real bg
1671 const SvxBrushItem* pRet = rFormat.GetItemIfSet(RES_BACKGROUND);
1673 if (!pRet ||
1674 (!pRet->GetGraphic() && pRet->GetColor() == COL_TRANSPARENT))
1676 pRet = &m_rDoc.GetAttrPool().GetDefaultItem(RES_BACKGROUND);
1678 return pRet;
1681 std::shared_ptr<SvxBrushItem> WW8Export::TrueFrameBgBrush(const SwFrameFormat &rFlyFormat) const
1683 const SwFrameFormat *pFlyFormat = &rFlyFormat;
1684 const SvxBrushItem* pRet = nullptr;
1686 while (pFlyFormat)
1688 //If not set, or "no fill", get real bg
1689 pRet = pFlyFormat->GetItemIfSet(RES_BACKGROUND);
1690 if (!pRet || (!pRet->GetGraphic() &&
1691 pRet->GetColor() == COL_TRANSPARENT))
1693 pRet = nullptr;
1694 const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
1695 if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
1696 pAnchor->GetAnchorNode())
1698 pFlyFormat =
1699 pAnchor->GetAnchorNode()->GetFlyFormat();
1701 else
1702 pFlyFormat = nullptr;
1704 else
1705 pFlyFormat = nullptr;
1708 if (!pRet)
1709 pRet = GetCurrentPageBgBrush();
1711 const Color aTmpColor( COL_WHITE );
1712 std::shared_ptr<SvxBrushItem> aRet(std::make_shared<SvxBrushItem>(aTmpColor, RES_BACKGROUND));
1714 if (pRet && (pRet->GetGraphic() ||( pRet->GetColor() != COL_TRANSPARENT)))
1716 aRet.reset(pRet->Clone());
1719 return aRet;
1723 Convert characters that need to be converted, the basic replacements and the
1724 ridiculously complicated title case attribute mapping to hardcoded upper case
1725 because word doesn't have the feature
1727 OUString SwWW8AttrIter::GetSnippet(const OUString &rStr, sal_Int32 nCurrentPos,
1728 sal_Int32 nLen) const
1730 if (!nLen)
1731 return OUString();
1733 OUString aSnippet(rStr.copy(nCurrentPos, nLen));
1734 // 0x0a ( Hard Line Break ) -> 0x0b
1735 // 0xad ( soft hyphen ) -> 0x1f
1736 // 0x2011 ( hard hyphen ) -> 0x1e
1737 aSnippet = aSnippet.replace(0x0A, 0x0B);
1738 aSnippet = aSnippet.replace(CHAR_HARDHYPHEN, 0x1e);
1739 aSnippet = aSnippet.replace(CHAR_SOFTHYPHEN, 0x1f);
1740 // Ignore the dummy character at the end of content controls.
1741 static sal_Unicode const aForbidden[] = {
1742 CH_TXTATR_BREAKWORD,
1745 aSnippet = comphelper::string::removeAny(aSnippet, aForbidden);
1747 m_rExport.m_aCurrentCharPropStarts.push( nCurrentPos );
1748 const SfxPoolItem &rItem = GetItem(RES_CHRATR_CASEMAP);
1750 if (SvxCaseMap::Capitalize == static_cast<const SvxCaseMapItem&>(rItem).GetValue())
1752 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1753 sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType(aSnippet, 0);
1755 LanguageType nLanguage;
1756 switch (nScriptType)
1758 case i18n::ScriptType::ASIAN:
1759 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CJK_LANGUAGE)).GetLanguage();
1760 break;
1761 case i18n::ScriptType::COMPLEX:
1762 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CTL_LANGUAGE)).GetLanguage();
1763 break;
1764 case i18n::ScriptType::LATIN:
1765 default:
1766 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_LANGUAGE)).GetLanguage();
1767 break;
1770 SvxFont aFontHelper;
1771 aFontHelper.SetCaseMap(SvxCaseMap::Capitalize);
1772 aFontHelper.SetLanguage(nLanguage);
1773 aSnippet = aFontHelper.CalcCaseMap(aSnippet);
1775 //If we weren't at the begin of a word undo the case change.
1776 //not done before doing the casemap because the sequence might start
1777 //with whitespace
1778 if (!g_pBreakIt->GetBreakIter()->isBeginWord(
1779 rStr, nCurrentPos, g_pBreakIt->GetLocale(nLanguage),
1780 i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
1782 aSnippet = OUStringChar(rStr[nCurrentPos]) + aSnippet.subView(1);
1785 m_rExport.m_aCurrentCharPropStarts.pop();
1787 return aSnippet;
1790 /** Delivers the right paragraph style
1792 Because of the different style handling for delete operations,
1793 the track changes have to be analysed. A deletion, starting in paragraph A
1794 with style A, ending in paragraph B with style B, needs a hack.
1796 static SwTextFormatColl& lcl_getFormatCollection( MSWordExportBase& rExport, const SwTextNode* pTextNode )
1798 SwRedlineTable::size_type nPos = 0;
1799 SwRedlineTable::size_type nMax = rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().size();
1800 while( nPos < nMax )
1802 const SwRangeRedline* pRedl = rExport.m_rDoc.getIDocumentRedlineAccess().GetRedlineTable()[ nPos++ ];
1803 auto [pStt, pEnd] = pRedl->StartEnd(); // SwPosition*
1804 // Looking for deletions, which ends in current pTextNode
1805 if( RedlineType::Delete == pRedl->GetRedlineData().GetType() &&
1806 pEnd->GetNode() == *pTextNode && pStt->GetNode() != *pTextNode &&
1807 pStt->GetNode().IsTextNode() )
1809 pTextNode = pStt->GetNode().GetTextNode();
1810 nMax = nPos;
1811 nPos = 0;
1814 return static_cast<SwTextFormatColl&>( pTextNode->GetAnyFormatColl() );
1817 void WW8AttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop &rSwFormatDrop, sal_uInt16 nStyle,
1818 ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
1820 short nDropLines = rSwFormatDrop.GetLines();
1821 short nDistance = rSwFormatDrop.GetDistance();
1822 int rFontHeight, rDropHeight, rDropDescent;
1824 SVBT16 nSty;
1825 ShortToSVBT16( nStyle, nSty );
1826 m_rWW8Export.m_pO->insert( m_rWW8Export.m_pO->end(), nSty, nSty+2 ); // Style #
1828 m_rWW8Export.InsUInt16( NS_sprm::PPc::val ); // Alignment (sprmPPc)
1829 m_rWW8Export.m_pO->push_back( 0x20 );
1831 m_rWW8Export.InsUInt16( NS_sprm::PWr::val ); // Wrapping (sprmPWr)
1832 m_rWW8Export.m_pO->push_back( 0x02 );
1834 m_rWW8Export.InsUInt16( NS_sprm::PDcs::val ); // Dropcap (sprmPDcs)
1835 int nDCS = ( nDropLines << 3 ) | 0x01;
1836 m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( nDCS ) );
1838 m_rWW8Export.InsUInt16( NS_sprm::PDxaFromText::val ); // Distance from text (sprmPDxaFromText)
1839 m_rWW8Export.InsUInt16( nDistance );
1841 if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
1843 m_rWW8Export.InsUInt16( NS_sprm::PDyaLine::val ); // Line spacing
1844 m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -rDropHeight ) );
1845 m_rWW8Export.InsUInt16( 0 );
1848 m_rWW8Export.WriteCR( pTextNodeInfoInner );
1850 if ( pTextNodeInfo )
1852 #ifdef DBG_UTIL
1853 SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
1854 #endif
1855 TableInfoCell( pTextNodeInfoInner );
1858 m_rWW8Export.m_pPapPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.m_pO->size(), m_rWW8Export.m_pO->data() );
1859 m_rWW8Export.m_pO->clear();
1861 if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
1863 const SwCharFormat *pSwCharFormat = rSwFormatDrop.GetCharFormat();
1864 if ( pSwCharFormat )
1866 m_rWW8Export.InsUInt16( NS_sprm::CIstd::val );
1867 m_rWW8Export.InsUInt16( m_rWW8Export.GetId( pSwCharFormat ) );
1870 m_rWW8Export.InsUInt16( NS_sprm::CHpsPos::val ); // Lower the chars
1871 m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -((nDropLines - 1)*rDropDescent) / 10 ) );
1873 m_rWW8Export.InsUInt16( NS_sprm::CHps::val ); // Font Size
1874 m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( rFontHeight / 10 ) );
1877 m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.m_pO->size(), m_rWW8Export.m_pO->data() );
1878 m_rWW8Export.m_pO->clear();
1881 sal_Int32 MSWordExportBase::GetNextPos( SwWW8AttrIter const * aAttrIter, const SwTextNode& rNode, sal_Int32 nCurrentPos )
1883 // Get the bookmarks for the normal run
1884 const sal_Int32 nNextPos = aAttrIter->WhereNext();
1885 sal_Int32 nNextBookmark = nNextPos;
1886 sal_Int32 nNextAnnotationMark = nNextPos;
1888 if( nNextBookmark > nCurrentPos ) //no need to search for bookmarks otherwise (checked in UpdatePosition())
1890 GetSortedBookmarks( rNode, nCurrentPos, nNextBookmark - nCurrentPos );
1891 NearestBookmark( nNextBookmark, nCurrentPos, false );
1892 GetSortedAnnotationMarks(*aAttrIter, nCurrentPos, nNextAnnotationMark - nCurrentPos);
1893 NearestAnnotationMark( nNextAnnotationMark, nCurrentPos, false );
1895 return std::min( nNextPos, std::min( nNextBookmark, nNextAnnotationMark ) );
1898 void MSWordExportBase::UpdatePosition( SwWW8AttrIter* aAttrIter, sal_Int32 nCurrentPos )
1900 sal_Int32 nNextPos;
1902 // go to next attribute if no bookmark is found or if the bookmark is after the next attribute position
1903 // It may happened that the WhereNext() wasn't used in the previous increment because there was a
1904 // bookmark before it. Use that position before trying to find another one.
1905 bool bNextBookmark = NearestBookmark( nNextPos, nCurrentPos, true );
1906 if( nCurrentPos == aAttrIter->WhereNext() && ( !bNextBookmark || nNextPos > aAttrIter->WhereNext() ) )
1907 aAttrIter->NextPos();
1910 bool MSWordExportBase::GetBookmarks( const SwTextNode& rNd, sal_Int32 nStt,
1911 sal_Int32 nEnd, IMarkVector& rArr )
1913 IDocumentMarkAccess* const pMarkAccess = m_rDoc.getIDocumentMarkAccess();
1915 const sal_Int32 nMarks = pMarkAccess->getAllMarksCount();
1916 for ( sal_Int32 i = 0; i < nMarks; i++ )
1918 IMark* pMark = pMarkAccess->getAllMarksBegin()[i];
1920 switch (IDocumentMarkAccess::GetType( *pMark ))
1922 case IDocumentMarkAccess::MarkType::UNO_BOOKMARK:
1923 case IDocumentMarkAccess::MarkType::DDE_BOOKMARK:
1924 case IDocumentMarkAccess::MarkType::ANNOTATIONMARK:
1925 case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK:
1926 case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK:
1927 case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK:
1928 case IDocumentMarkAccess::MarkType::DATE_FIELDMARK:
1929 case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER:
1930 continue; // ignore irrelevant marks
1931 case IDocumentMarkAccess::MarkType::BOOKMARK:
1932 case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK:
1933 case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK:
1934 break;
1937 // Only keep the bookmarks starting or ending in this node
1938 if ( pMark->GetMarkStart().GetNode() == rNd ||
1939 pMark->GetMarkEnd().GetNode() == rNd )
1941 const sal_Int32 nBStart = pMark->GetMarkStart().GetContentIndex();
1942 const sal_Int32 nBEnd = pMark->GetMarkEnd().GetContentIndex();
1944 // Keep only the bookmarks starting or ending in the snippet
1945 bool bIsStartOk = ( pMark->GetMarkStart().GetNode() == rNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
1946 bool bIsEndOk = ( pMark->GetMarkEnd().GetNode() == rNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
1948 if ( bIsStartOk || bIsEndOk )
1950 rArr.push_back( pMark );
1954 return ( !rArr.empty() );
1957 bool MSWordExportBase::GetAnnotationMarks( const SwWW8AttrIter& rAttrs, sal_Int32 nStt,
1958 sal_Int32 nEnd, IMarkVector& rArr )
1960 IDocumentMarkAccess* const pMarkAccess = m_rDoc.getIDocumentMarkAccess();
1961 const SwNode& rNd = rAttrs.GetNode();
1963 const sal_Int32 nMarks = pMarkAccess->getAnnotationMarksCount();
1964 for ( sal_Int32 i = 0; i < nMarks; i++ )
1966 IMark* pMark = pMarkAccess->getAnnotationMarksBegin()[i];
1968 // Only keep the bookmarks starting or ending in this node
1969 if ( pMark->GetMarkStart().GetNode() == rNd ||
1970 pMark->GetMarkEnd().GetNode() == rNd )
1972 const sal_Int32 nBStart = pMark->GetMarkStart().GetContentIndex();
1973 const sal_Int32 nBEnd = pMark->GetMarkEnd().GetContentIndex();
1975 // Keep only the bookmarks starting or ending in the snippet
1976 bool bIsStartOk = ( pMark->GetMarkStart().GetNode() == rNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
1977 bool bIsEndOk = ( pMark->GetMarkEnd().GetNode() == rNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
1979 // Annotation marks always have at least one character: the anchor
1980 // point of the comment field. In this case Word wants only the
1981 // comment field, so ignore the annotation mark itself.
1982 bool bSingleChar = pMark->GetMarkStart().GetNode() == pMark->GetMarkEnd().GetNode() && nBStart + 1 == nBEnd;
1984 if (bSingleChar)
1986 if (rAttrs.HasFlysAt(nBStart))
1988 // There is content (an at-char anchored frame) between the annotation mark
1989 // start/end, so still emit range start/end.
1990 bSingleChar = false;
1994 if ( ( bIsStartOk || bIsEndOk ) && !bSingleChar )
1996 rArr.push_back( pMark );
2000 return ( !rArr.empty() );
2003 namespace {
2005 class CompareMarksEnd
2007 public:
2008 bool operator() ( const IMark * pOneB, const IMark * pTwoB ) const
2010 const sal_Int32 nOEnd = pOneB->GetMarkEnd().GetContentIndex();
2011 const sal_Int32 nTEnd = pTwoB->GetMarkEnd().GetContentIndex();
2013 return nOEnd < nTEnd;
2019 bool MSWordExportBase::NearestBookmark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
2021 bool bHasBookmark = false;
2023 if ( !m_rSortedBookmarksStart.empty() )
2025 IMark* pMarkStart = m_rSortedBookmarksStart.front();
2026 const sal_Int32 nNext = pMarkStart->GetMarkStart().GetContentIndex();
2027 if( !bNextPositionOnly || (nNext > nCurrentPos ))
2029 rNearest = nNext;
2030 bHasBookmark = true;
2034 if ( !m_rSortedBookmarksEnd.empty() )
2036 IMark* pMarkEnd = m_rSortedBookmarksEnd[0];
2037 const sal_Int32 nNext = pMarkEnd->GetMarkEnd().GetContentIndex();
2038 if( !bNextPositionOnly || nNext > nCurrentPos )
2040 if ( !bHasBookmark )
2041 rNearest = nNext;
2042 else
2043 rNearest = std::min( rNearest, nNext );
2044 bHasBookmark = true;
2048 return bHasBookmark;
2051 void MSWordExportBase::NearestAnnotationMark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
2053 bool bHasAnnotationMark = false;
2055 if ( !m_rSortedAnnotationMarksStart.empty() )
2057 IMark* pMarkStart = m_rSortedAnnotationMarksStart.front();
2058 const sal_Int32 nNext = pMarkStart->GetMarkStart().GetContentIndex();
2059 if( !bNextPositionOnly || (nNext > nCurrentPos ))
2061 rNearest = nNext;
2062 bHasAnnotationMark = true;
2066 if ( !m_rSortedAnnotationMarksEnd.empty() )
2068 IMark* pMarkEnd = m_rSortedAnnotationMarksEnd[0];
2069 const sal_Int32 nNext = pMarkEnd->GetMarkEnd().GetContentIndex();
2070 if( !bNextPositionOnly || nNext > nCurrentPos )
2072 if ( !bHasAnnotationMark )
2073 rNearest = nNext;
2074 else
2075 rNearest = std::min( rNearest, nNext );
2080 void MSWordExportBase::GetSortedAnnotationMarks( const SwWW8AttrIter& rAttrs, sal_Int32 nCurrentPos, sal_Int32 nLen )
2082 IMarkVector aMarksStart;
2083 if (GetAnnotationMarks(rAttrs, nCurrentPos, nCurrentPos + nLen, aMarksStart))
2085 IMarkVector aSortedEnd;
2086 IMarkVector aSortedStart;
2087 for ( IMark* pMark : aMarksStart )
2089 // Remove the positions equal to the current pos
2090 const sal_Int32 nStart = pMark->GetMarkStart().GetContentIndex();
2091 const sal_Int32 nEnd = pMark->GetMarkEnd().GetContentIndex();
2093 const SwTextNode& rNode = rAttrs.GetNode();
2094 if ( nStart > nCurrentPos && ( pMark->GetMarkStart().GetNode() == rNode) )
2095 aSortedStart.push_back( pMark );
2097 if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().GetNode() == rNode) )
2098 aSortedEnd.push_back( pMark );
2101 // Sort the bookmarks by end position
2102 std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
2104 m_rSortedAnnotationMarksStart.swap( aSortedStart );
2105 m_rSortedAnnotationMarksEnd.swap( aSortedEnd );
2107 else
2109 m_rSortedAnnotationMarksStart.clear( );
2110 m_rSortedAnnotationMarksEnd.clear( );
2114 void MSWordExportBase::GetSortedBookmarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
2116 IMarkVector aMarksStart;
2117 if ( GetBookmarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
2119 IMarkVector aSortedEnd;
2120 IMarkVector aSortedStart;
2121 for ( IMark* pMark : aMarksStart )
2123 // Remove the positions equal to the current pos
2124 const sal_Int32 nStart = pMark->GetMarkStart().GetContentIndex();
2125 const sal_Int32 nEnd = pMark->GetMarkEnd().GetContentIndex();
2127 if ( nStart > nCurrentPos && (pMark->GetMarkStart().GetNode() == rNode) )
2128 aSortedStart.push_back( pMark );
2130 if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().GetNode() == rNode) )
2131 aSortedEnd.push_back( pMark );
2134 // Sort the bookmarks by end position
2135 std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
2137 m_rSortedBookmarksStart.swap( aSortedStart );
2138 m_rSortedBookmarksEnd.swap( aSortedEnd );
2140 else
2142 m_rSortedBookmarksStart.clear( );
2143 m_rSortedBookmarksEnd.clear( );
2147 bool MSWordExportBase::NeedSectionBreak( const SwNode& rNd ) const
2149 if ( m_bStyDef || m_bOutKF || m_bInWriteEscher || m_bOutPageDescs || m_pCurrentPageDesc == nullptr )
2150 return false;
2152 const SwPageDesc * pPageDesc = rNd.FindPageDesc()->GetFollow();
2154 if (m_pCurrentPageDesc != pPageDesc)
2156 if (!sw::util::IsPlausableSingleWordSection(m_pCurrentPageDesc->GetFirstMaster(), pPageDesc->GetMaster()))
2158 return true;
2162 return false;
2165 bool MSWordExportBase::NeedTextNodeSplit( const SwTextNode& rNd, SwSoftPageBreakList& pList ) const
2167 SwSoftPageBreakList tmp;
2168 rNd.fillSoftPageBreakList(tmp);
2169 // hack: move the break behind any field marks; currently we can't hide the
2170 // field mark instruction so the layout position is quite meaningless
2171 IDocumentMarkAccess const& rIDMA(*rNd.GetDoc().getIDocumentMarkAccess());
2172 sal_Int32 pos(-1);
2173 for (auto const& it : tmp)
2175 if (pos < it) // previous one might have skipped over it
2177 pos = it;
2178 while (auto const*const pMark = rIDMA.getInnerFieldmarkFor(SwPosition(rNd, pos)))
2180 if (pMark->GetMarkEnd().GetNode() != rNd)
2182 pos = rNd.Len(); // skip everything
2183 break;
2185 pos = pMark->GetMarkEnd().GetContentIndex(); // no +1, it's behind the char
2187 pList.insert(pos);
2190 pList.insert(0);
2191 pList.insert( rNd.GetText().getLength() );
2192 return pList.size() > 2 && NeedSectionBreak( rNd );
2195 namespace {
2196 OUString lcl_GetSymbolFont(SwAttrPool& rPool, const SwTextNode* pTextNode, int nStart, int nEnd)
2198 SfxItemSetFixed<RES_CHRATR_FONT, RES_CHRATR_FONT> aSet( rPool );
2199 if ( pTextNode && pTextNode->GetParaAttr(aSet, nStart, nEnd) )
2201 SfxPoolItem const* pPoolItem = aSet.GetItem(RES_CHRATR_FONT);
2202 if (pPoolItem)
2204 const SvxFontItem* pFontItem = static_cast<const SvxFontItem*>(pPoolItem);
2205 if (pFontItem->GetCharSet() == RTL_TEXTENCODING_SYMBOL)
2206 return pFontItem->GetFamilyName();
2210 return OUString();
2214 void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
2216 SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" );
2218 ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo( m_pTableInfo->getTableNodeInfo( &rNode ) );
2220 //For i120928,identify the last node
2221 bool bLastCR = false;
2222 bool bExported = false;
2224 SwNodeIndex aNextIdx(rNode,1);
2225 SwNodeIndex aLastIdx(rNode.GetNodes().GetEndOfContent());
2226 if (aNextIdx == aLastIdx)
2227 bLastCR = true;
2230 // In order to make sure watermark is stored in 'header.xml', check nTextTyp.
2231 // if it is document.xml, don't write the tags (watermark should be only in the 'header')
2232 SwWW8AttrIter aWatermarkAttrIter( *this, rNode );
2233 if (( TXT_HDFT != m_nTextTyp) && aWatermarkAttrIter.IsWatermarkFrame())
2235 return;
2238 bool bFlyInTable = m_pParentFrame && IsInTable();
2240 SwTextFormatColl& rTextColl = lcl_getFormatCollection( *this, &rNode );
2241 if ( !bFlyInTable )
2242 m_nStyleBeforeFly = GetId( rTextColl );
2244 // nStyleBeforeFly may change when we recurse into another node, so we
2245 // have to remember it in nStyle
2246 sal_uInt16 nStyle = m_nStyleBeforeFly;
2248 SwWW8AttrIter aAttrIter( *this, rNode );
2249 rtl_TextEncoding eChrSet = aAttrIter.GetCharSet();
2251 if ( m_bStartTOX )
2253 // ignore TOX header section
2254 const SwSectionNode* pSectNd = rNode.FindSectionNode();
2255 if ( pSectNd && SectionType::ToxContent == pSectNd->GetSection().GetType() )
2257 AttrOutput().StartTOX( pSectNd->GetSection() );
2258 m_aCurrentCharPropStarts.push( 0 );
2262 // Emulate: If 1-row table is marked as don't split, then set the row as don't split.
2263 if ( IsInTable() )
2265 const SwTableNode* pTableNode = rNode.FindTableNode();
2266 if ( pTableNode )
2268 const SwTable& rTable = pTableNode->GetTable();
2269 const SwTableBox* pBox = rNode.GetTableBox();
2271 // export formula cell as formula field instead of only its cell content in DOCX
2272 if ( pBox->IsFormulaOrValueBox() == RES_BOXATR_FORMULA &&
2273 GetExportFormat() == MSWordExportBase::ExportFormat::DOCX )
2275 std::unique_ptr<SwTableBoxFormula> pFormula(pBox->GetFrameFormat()->GetTableBoxFormula().Clone());
2276 pFormula->PtrToBoxNm( &pTableNode->GetTable() );
2277 OutputField( nullptr, ww::eEquals, " =" + pFormula->GetFormula(),
2278 FieldFlags::Start | FieldFlags::CmdStart | FieldFlags::CmdEnd | FieldFlags::Close );
2281 const bool bKeep = rTable.GetFrameFormat()->GetKeep().GetValue();
2282 const bool bDontSplit = !rTable.GetFrameFormat()->GetLayoutSplit().GetValue();
2283 // bKeep handles this a different way later on, so ignore now
2284 if ( !bKeep && bDontSplit && rTable.GetTabLines().size() == 1 )
2286 // bDontSplit : set don't split once for the row
2287 // but only for non-complex tables
2288 const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
2289 if ( pLine && !pLine->GetUpper() )
2291 // check if box is first in that line:
2292 if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
2294 // check if paragraph is first in that line:
2295 if ( SwNodeOffset(1) == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
2296 pLine->GetFrameFormat()->SetFormatAttr(SwFormatRowSplit(!bDontSplit));
2303 SwSoftPageBreakList softBreakList;
2304 // Let's decide if we need to split the paragraph because of a section break
2305 bool bNeedParaSplit = NeedTextNodeSplit( rNode, softBreakList )
2306 && !IsInTable();
2307 const SwPageDesc* pNextSplitParaPageDesc = m_pCurrentPageDesc;
2309 auto aBreakIt = softBreakList.begin();
2310 // iterate through portions on different pages
2313 sal_Int32 nCurrentPos = *aBreakIt;
2315 if( softBreakList.size() > 1 ) // not for empty paragraph
2317 // no need to split again if the page style won't change anymore
2318 if ( pNextSplitParaPageDesc == pNextSplitParaPageDesc->GetFollow() )
2319 aBreakIt = --softBreakList.end();
2320 else
2321 ++aBreakIt;
2324 AttrOutput().StartParagraph(pTextNodeInfo, false);
2326 const SwSection* pTOXSect = nullptr;
2327 if( m_bInWriteTOX )
2329 // check for end of TOX
2330 SwNodeIndex aIdx( rNode, 1 );
2331 if( !aIdx.GetNode().IsTextNode() )
2333 const SwSectionNode* pTOXSectNd = rNode.FindSectionNode();
2334 if ( pTOXSectNd )
2336 pTOXSect = &pTOXSectNd->GetSection();
2338 const SwNode* pNxt = rNode.GetNodes().GoNext( &aIdx );
2339 if( pNxt && pNxt->FindSectionNode() == pTOXSectNd )
2340 pTOXSect = nullptr;
2345 if ( aAttrIter.RequiresImplicitBookmark() )
2347 OUString sBkmkName = "_toc" + OUString::number( sal_Int32(rNode.GetIndex()) );
2348 // Add a bookmark converted to a Word name.
2349 AppendBookmark( BookmarkToWord( sBkmkName ) );
2352 // Call this before write out fields and runs
2353 AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);
2355 const OUString& aStr( rNode.GetText() );
2357 sal_Int32 const nEnd = bNeedParaSplit ? *aBreakIt : aStr.getLength();
2358 bool bIsEndOfCell = false;
2359 bool bIncludeEndOfParaCRInRedlineProperties = false;
2360 sal_Int32 nOpenAttrWithRange = 0;
2362 ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner;
2363 if ( pTextNodeInfo )
2365 pTextNodeInfoInner = pTextNodeInfo->getFirstInner();
2366 if (pTextNodeInfoInner && pTextNodeInfoInner->isEndOfCell())
2367 bIsEndOfCell = true;
2370 do {
2372 const SwRedlineData* pRedlineData = aAttrIter.GetRunLevelRedline( nCurrentPos );
2373 bool bPostponeWritingText = false ;
2374 bool bStartedPostponedRunProperties = false;
2375 OUString aSavedSnippet ;
2377 sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos );
2379 // Skip un-exportable attributes.
2380 if (!aAttrIter.IsExportableAttr(nCurrentPos))
2382 nCurrentPos = nNextAttr;
2383 UpdatePosition(&aAttrIter, nCurrentPos);
2384 eChrSet = aAttrIter.GetCharSet();
2385 continue;
2388 // Is this the only run in this paragraph and it's empty?
2389 bool bSingleEmptyRun = nCurrentPos == 0 && nNextAttr == 0;
2390 AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2392 if( nNextAttr > nEnd )
2393 nNextAttr = nEnd;
2395 if( m_nTextTyp == TXT_FTN || m_nTextTyp == TXT_EDN )
2397 if( AttrOutput().FootnoteEndnoteRefTag() )
2399 AttrOutput().EndRun( &rNode, nCurrentPos, -1, nNextAttr == nEnd );
2400 AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2405 1) If there is a text node and an overlapping anchor, then write them in two different
2406 runs and not as part of the same run.
2407 2) Ensure that it is a text node and not in a fly.
2408 3) If the anchor is associated with a text node with empty text then we ignore.
2410 if( rNode.IsTextNode()
2411 && GetExportFormat() == MSWordExportBase::ExportFormat::DOCX
2412 && aStr != OUStringChar(CH_TXTATR_BREAKWORD) && !aStr.isEmpty()
2413 && !rNode.GetFlyFormat()
2414 && aAttrIter.IsAnchorLinkedToThisNode(rNode.GetIndex()) )
2416 bPostponeWritingText = true ;
2419 FlyProcessingState nStateOfFlyFrame = aAttrIter.OutFlys( nCurrentPos );
2420 AttrOutput().SetStateOfFlyFrame( nStateOfFlyFrame );
2421 AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && (FLY_POSTPONED != nStateOfFlyFrame) );
2422 // Append bookmarks in this range after flys, exclusive of final
2423 // position of this range
2424 AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos, pRedlineData );
2425 // Sadly only possible for main or glossary document parts: ECMA-376 Part 1 sect. 11.3.2
2426 if ( m_nTextTyp == TXT_MAINTEXT )
2427 AppendAnnotationMarks(aAttrIter, nCurrentPos, nNextAttr - nCurrentPos);
2429 // At the moment smarttags are only written for paragraphs, at the
2430 // beginning of the paragraph.
2431 if (nCurrentPos == 0)
2432 AppendSmartTags(rNode);
2434 bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
2435 nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nCurrentPos );
2437 OUString aSymbolFont;
2438 sal_Int32 nLen = nNextAttr - nCurrentPos;
2439 if ( !bTextAtr && nLen )
2441 sal_Unicode ch = aStr[nCurrentPos];
2443 const sal_Int32 ofs = (ch == CH_TXT_ATR_FIELDSTART
2444 || ch == CH_TXT_ATR_FIELDSEP
2445 || ch == CH_TXT_ATR_FIELDEND
2446 || ch == CH_TXT_ATR_FORMELEMENT)
2447 ? 1 : 0;
2448 if (ofs == 1
2449 && GetExportFormat() == MSWordExportBase::ExportFormat::DOCX
2450 // FLY_PROCESSED: there's at least 1 fly already written
2451 && nStateOfFlyFrame == FLY_PROCESSED)
2453 // write flys in a separate run before field character
2454 AttrOutput().EndRun(&rNode, nCurrentPos, -1, nNextAttr == nEnd);
2455 AttrOutput().StartRun(pRedlineData, nCurrentPos, bSingleEmptyRun);
2458 IDocumentMarkAccess* const pMarkAccess = m_rDoc.getIDocumentMarkAccess();
2459 if ( ch == CH_TXT_ATR_FIELDSTART )
2461 SwPosition aPosition( rNode, nCurrentPos );
2462 ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2463 assert(pFieldmark);
2465 // Date field is exported as content control, not as a simple field
2466 if (pFieldmark->GetFieldname() == ODF_FORMDATE)
2468 if(GetExportFormat() == MSWordExportBase::ExportFormat::DOCX) // supported by DOCX only
2470 OutputField( nullptr, lcl_getFieldId( pFieldmark ),
2471 lcl_getFieldCode( pFieldmark ),
2472 FieldFlags::Start | FieldFlags::CmdStart );
2473 WriteFormData( *pFieldmark );
2476 else
2479 if (pFieldmark->GetFieldname() == ODF_FORMTEXT
2480 && GetExportFormat() != MSWordExportBase::ExportFormat::DOCX )
2482 AppendBookmark( pFieldmark->GetName() );
2484 ww::eField eFieldId = lcl_getFieldId( pFieldmark );
2485 OUString sCode = lcl_getFieldCode( pFieldmark );
2486 if (pFieldmark->GetFieldname() == ODF_UNHANDLED )
2488 IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
2489 if ( it != pFieldmark->GetParameters()->end() )
2491 OUString sFieldId;
2492 it->second >>= sFieldId;
2493 eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
2496 it = pFieldmark->GetParameters()->find( ODF_CODE_PARAM );
2497 if ( it != pFieldmark->GetParameters()->end() )
2499 it->second >>= sCode;
2503 OutputField( nullptr, eFieldId, sCode, FieldFlags::Start | FieldFlags::CmdStart );
2505 if (pFieldmark->GetFieldname() == ODF_FORMTEXT)
2506 WriteFormData( *pFieldmark );
2507 else if (pFieldmark->GetFieldname() == ODF_HYPERLINK)
2508 WriteHyperlinkData( *pFieldmark );
2511 else if (ch == CH_TXT_ATR_FIELDSEP)
2513 SwPosition aPosition(rNode, nCurrentPos);
2514 // the innermost field is the correct one
2515 sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getInnerFieldmarkFor(aPosition);
2516 assert(pFieldmark);
2517 // DateFieldmark / ODF_FORMDATE is not a field...
2518 if (pFieldmark->GetFieldname() != ODF_FORMDATE)
2520 OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::CmdEnd );
2522 if (pFieldmark->GetFieldname() == ODF_UNHANDLED)
2524 // Check for the presence of a linked OLE object
2525 IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_OLE_PARAM );
2526 if ( it != pFieldmark->GetParameters()->end() )
2528 OUString sOleId;
2529 uno::Any aValue = it->second;
2530 aValue >>= sOleId;
2531 if ( !sOleId.isEmpty() )
2532 OutputLinkedOLE( sOleId );
2537 else if ( ch == CH_TXT_ATR_FIELDEND )
2539 SwPosition aPosition( rNode, nCurrentPos );
2540 ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2542 assert(pFieldmark);
2544 if (pFieldmark->GetFieldname() == ODF_FORMDATE)
2546 if(GetExportFormat() == MSWordExportBase::ExportFormat::DOCX) // supported by DOCX only
2548 OutputField( nullptr, ww::eFORMDATE, OUString(), FieldFlags::Close );
2551 else
2553 ww::eField eFieldId = lcl_getFieldId( pFieldmark );
2554 if (pFieldmark->GetFieldname() == ODF_UNHANDLED)
2556 IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
2557 if ( it != pFieldmark->GetParameters()->end() )
2559 OUString sFieldId;
2560 it->second >>= sFieldId;
2561 eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
2565 OutputField( nullptr, eFieldId, OUString(), FieldFlags::Close );
2567 if (pFieldmark->GetFieldname() == ODF_FORMTEXT
2568 && GetExportFormat() != MSWordExportBase::ExportFormat::DOCX )
2570 AppendBookmark( pFieldmark->GetName() );
2574 else if ( ch == CH_TXT_ATR_FORMELEMENT )
2576 SwPosition aPosition( rNode, nCurrentPos );
2577 ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2578 assert(pFieldmark);
2580 bool const isDropdownOrCheckbox(pFieldmark->GetFieldname() == ODF_FORMDROPDOWN ||
2581 pFieldmark->GetFieldname() == ODF_FORMCHECKBOX);
2582 if ( isDropdownOrCheckbox )
2583 AppendBookmark( pFieldmark->GetName() );
2584 OutputField( nullptr, lcl_getFieldId( pFieldmark ),
2585 lcl_getFieldCode( pFieldmark ),
2586 FieldFlags::Start | FieldFlags::CmdStart );
2587 if ( isDropdownOrCheckbox )
2588 WriteFormData( *pFieldmark );
2589 // tdf#129514 need CmdEnd for docx
2590 OutputField(nullptr, lcl_getFieldId(pFieldmark), OUString(),
2591 FieldFlags::CmdEnd | FieldFlags::Close);
2592 if ( isDropdownOrCheckbox )
2593 AppendBookmark( pFieldmark->GetName() );
2595 nLen -= ofs;
2597 // if paragraph needs to be split, write only until split position
2598 assert(!bNeedParaSplit || nCurrentPos <= *aBreakIt);
2599 if( bNeedParaSplit && nCurrentPos + ofs + nLen > *aBreakIt)
2600 nLen = *aBreakIt - nCurrentPos - ofs;
2601 assert(0 <= nLen);
2603 OUString aSnippet( aAttrIter.GetSnippet( aStr, nCurrentPos + ofs, nLen ) );
2604 const SwTextNode* pTextNode( rNode.GetTextNode() );
2605 if ( ( m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN ) && nCurrentPos == 0 && nLen > 0 )
2607 // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent
2608 sal_Int32 nFirstLineIndent=0;
2609 SfxItemSetFixed<RES_MARGIN_FIRSTLINE, RES_MARGIN_FIRSTLINE> aSet( m_rDoc.GetAttrPool() );
2611 if ( pTextNode && pTextNode->GetAttr(aSet) )
2613 const SvxFirstLineIndentItem *const pFirstLine(aSet.GetItem<SvxFirstLineIndentItem>(RES_MARGIN_FIRSTLINE));
2614 if (pFirstLine)
2615 nFirstLineIndent = pFirstLine->GetTextFirstLineOffset();
2618 // Insert tab for aesthetic purposes #i24762#
2619 if ( m_bAddFootnoteTab && nFirstLineIndent < 0 && aSnippet[0] != 0x09 )
2620 aSnippet = "\x09" + aSnippet;
2621 m_bAddFootnoteTab = false;
2624 aSymbolFont = lcl_GetSymbolFont(m_rDoc.GetAttrPool(), pTextNode, nCurrentPos + ofs, nCurrentPos + ofs + nLen);
2626 if ( bPostponeWritingText && ( FLY_POSTPONED != nStateOfFlyFrame ) )
2628 aSavedSnippet = aSnippet ;
2630 else
2632 bPostponeWritingText = false ;
2633 AttrOutput().RunText( aSnippet, eChrSet, aSymbolFont );
2636 if (ofs == 1 && nNextAttr == nEnd)
2638 // tdf#152200: There could be flys anchored after the last position; make sure
2639 // to provide a separate run after field character to write them
2640 AttrOutput().EndRun(&rNode, nCurrentPos, -1, nNextAttr == nEnd);
2641 AttrOutput().StartRun(pRedlineData, nCurrentPos, bSingleEmptyRun);
2645 if ( aAttrIter.IsDropCap( nNextAttr ) )
2646 AttrOutput().FormatDrop( rNode, aAttrIter.GetSwFormatDrop(), nStyle, pTextNodeInfo, pTextNodeInfoInner );
2648 // Only output character attributes if this is not a postponed text run.
2649 if (0 != nEnd && !(bPostponeWritingText
2650 && (FLY_PROCESSED == nStateOfFlyFrame || FLY_NONE == nStateOfFlyFrame)))
2652 // Output the character attributes
2653 // #i51277# do this before writing flys at end of paragraph
2654 bStartedPostponedRunProperties = true;
2655 AttrOutput().StartRunProperties();
2656 aAttrIter.OutAttr(nCurrentPos, false);
2657 AttrOutput().EndRunProperties( pRedlineData );
2660 // At the end of line, output the attributes until the CR.
2661 // Exception: footnotes at the end of line
2662 if ( nNextAttr == nEnd )
2664 OSL_ENSURE( nOpenAttrWithRange >= 0, "odd to see this happening, expected >= 0" );
2665 if ( !bTextAtr && nOpenAttrWithRange <= 0 )
2667 if ( aAttrIter.IncludeEndOfParaCRInRedlineProperties( nEnd ) )
2668 bIncludeEndOfParaCRInRedlineProperties = true;
2669 else
2671 // insert final graphic anchors if any before CR
2672 nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
2673 // insert final bookmarks if any before CR and after flys
2674 AppendBookmarks( rNode, nEnd, 1 );
2675 AppendAnnotationMarks(aAttrIter, nEnd, 1);
2676 if ( pTOXSect )
2678 m_aCurrentCharPropStarts.pop();
2679 AttrOutput().EndTOX( *pTOXSect ,false);
2681 //For i120928,the position of the bullet's graphic is at end of doc
2682 if (bLastCR && (!bExported))
2684 ExportGrfBullet(rNode);
2685 bExported = true;
2688 WriteCR( pTextNodeInfoInner );
2690 if (0 != nEnd && bIsEndOfCell)
2691 AttrOutput().OutputFKP(/*bforce=*/true);
2696 if (0 == nEnd)
2698 // Output the character attributes
2699 // do it after WriteCR for an empty paragraph (otherwise
2700 // WW8_WrFkp::Append throws SPRMs away...)
2701 AttrOutput().StartRunProperties();
2702 aAttrIter.OutAttr( nCurrentPos, false );
2703 AttrOutput().EndRunProperties( pRedlineData );
2706 // Exception: footnotes at the end of line
2707 if ( nNextAttr == nEnd )
2709 OSL_ENSURE(nOpenAttrWithRange >= 0,
2710 "odd to see this happening, expected >= 0");
2711 bool bAttrWithRange = (nOpenAttrWithRange > 0);
2712 if ( nCurrentPos != nEnd )
2714 nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nEnd );
2715 OSL_ENSURE(nOpenAttrWithRange == 0,
2716 "odd to see this happening, expected 0");
2719 AttrOutput().OutputFKP(/*bForce=*/false);
2721 if (bTextAtr || bAttrWithRange || bIncludeEndOfParaCRInRedlineProperties)
2723 AttrOutput().WritePostitFieldReference();
2725 // insert final graphic anchors if any before CR
2726 nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
2727 // insert final bookmarks if any before CR and after flys
2728 AppendBookmarks( rNode, nEnd, 1 );
2729 AppendAnnotationMarks(aAttrIter, nEnd, 1);
2730 WriteCR( pTextNodeInfoInner );
2731 // #i120928 - position of the bullet's graphic is at end of doc
2732 if (bLastCR && (!bExported))
2734 ExportGrfBullet(rNode);
2735 bExported = true;
2738 if ( pTOXSect )
2740 m_aCurrentCharPropStarts.pop();
2741 AttrOutput().EndTOX( *pTOXSect );
2744 if (bIncludeEndOfParaCRInRedlineProperties)
2746 AttrOutput().Redline( aAttrIter.GetRunLevelRedline( nEnd ) );
2747 //If there was no redline property emitted, force adding
2748 //another entry for the CR so that in the case that this
2749 //has no redline, but the next para does, then this one is
2750 //not merged with the next
2751 AttrOutput().OutputFKP(true);
2756 AttrOutput().WritePostitFieldReference();
2758 aSymbolFont = lcl_GetSymbolFont(m_rDoc.GetAttrPool(), &rNode, nCurrentPos, nCurrentPos + nLen);
2760 if (bPostponeWritingText)
2762 if (FLY_PROCESSED == nStateOfFlyFrame || FLY_NONE == nStateOfFlyFrame)
2764 AttrOutput().EndRun(&rNode, nCurrentPos, -1, /*bLastRun=*/false);
2765 if (!aSavedSnippet.isEmpty())
2766 bStartedPostponedRunProperties = false;
2768 AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2769 AttrOutput().SetAnchorIsLinkedToNode( false );
2770 AttrOutput().ResetFlyProcessingFlag();
2772 if (0 != nEnd && !bStartedPostponedRunProperties)
2774 AttrOutput().StartRunProperties();
2775 aAttrIter.OutAttr( nCurrentPos, false );
2776 AttrOutput().EndRunProperties( pRedlineData );
2778 // OutAttr may have introduced new comments, so write them out now
2779 AttrOutput().WritePostitFieldReference();
2781 AttrOutput().RunText( aSavedSnippet, eChrSet, aSymbolFont );
2782 AttrOutput().EndRun(&rNode, nCurrentPos, nLen, nNextAttr == nEnd);
2784 else
2785 AttrOutput().EndRun(&rNode, nCurrentPos, nLen, nNextAttr == nEnd);
2787 nCurrentPos = nNextAttr;
2788 UpdatePosition( &aAttrIter, nCurrentPos );
2789 eChrSet = aAttrIter.GetCharSet();
2791 while ( nCurrentPos < nEnd );
2793 // if paragraph is split, put the section break between the parts
2794 if( bNeedParaSplit && *aBreakIt != rNode.GetText().getLength() )
2796 pNextSplitParaPageDesc = pNextSplitParaPageDesc->GetFollow();
2797 assert(pNextSplitParaPageDesc);
2798 PrepareNewPageDesc( rNode.GetpSwAttrSet(), rNode, nullptr , pNextSplitParaPageDesc);
2800 else
2802 // else check if section break needed after the paragraph
2803 bool bCheckSectionBreak = true;
2804 // only try to sectionBreak after a split para if the next node specifies a break
2805 if ( bNeedParaSplit )
2807 m_pCurrentPageDesc = pNextSplitParaPageDesc;
2808 SwNodeIndex aNextIndex( rNode, 1 );
2809 const SwTextNode* pNextNode = aNextIndex.GetNode().GetTextNode();
2810 bCheckSectionBreak = pNextNode && !NoPageBreakSection( pNextNode->GetpSwAttrSet() );
2812 if ( !bCheckSectionBreak )
2814 const SvxFormatBreakItem& rBreak = rNode.GetSwAttrSet().Get(RES_BREAK);
2815 if ( rBreak.GetBreak() == SvxBreak::PageAfter )
2817 if ( pNextNode && pNextNode->FindPageDesc() != pNextSplitParaPageDesc )
2818 bCheckSectionBreak = true;
2819 else
2820 AttrOutput().SectionBreak(msword::PageBreak, /*bBreakAfter=*/true);
2825 if ( bCheckSectionBreak )
2826 AttrOutput().SectionBreaks(rNode);
2829 AttrOutput().StartParagraphProperties();
2831 AttrOutput().ParagraphStyle( nStyle );
2833 if ( m_pParentFrame && IsInTable() ) // Fly-Attrs
2834 OutputFormat( m_pParentFrame->GetFrameFormat(), false, false, true );
2836 if ( pTextNodeInfo )
2838 #ifdef DBG_UTIL
2839 SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
2840 #endif
2842 AttrOutput().TableInfoCell( pTextNodeInfoInner );
2843 if (pTextNodeInfoInner && pTextNodeInfoInner->isFirstInTable())
2845 const SwTable * pTable = pTextNodeInfoInner->getTable();
2847 const SwTableFormat* pTabFormat = pTable->GetFrameFormat();
2848 if (pTabFormat != nullptr)
2850 if (pTabFormat->GetBreak().GetBreak() == SvxBreak::PageBefore)
2851 AttrOutput().PageBreakBefore(true);
2856 if ( !bFlyInTable )
2858 std::optional<SfxItemSet> oTmpSet;
2859 const sal_uInt8 nPrvNxtNd = rNode.HasPrevNextLayNode();
2861 if( (ND_HAS_PREV_LAYNODE|ND_HAS_NEXT_LAYNODE ) != nPrvNxtNd )
2863 const SvxULSpaceItem* pSpaceItem = rNode.GetSwAttrSet().GetItemIfSet(
2864 RES_UL_SPACE );
2865 if( pSpaceItem &&
2866 ( ( !( ND_HAS_PREV_LAYNODE & nPrvNxtNd ) && pSpaceItem->GetUpper()) ||
2867 ( !( ND_HAS_NEXT_LAYNODE & nPrvNxtNd ) && pSpaceItem->GetLower()) ))
2869 oTmpSet.emplace( rNode.GetSwAttrSet() );
2870 SvxULSpaceItem aUL( *pSpaceItem );
2871 // #i25901#- consider compatibility option
2872 if (!m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES))
2874 if( !(ND_HAS_PREV_LAYNODE & nPrvNxtNd ))
2875 aUL.SetUpper( 0 );
2877 // #i25901# - consider compatibility option
2878 if (!m_rDoc.getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS))
2880 if( !(ND_HAS_NEXT_LAYNODE & nPrvNxtNd ))
2881 aUL.SetLower( 0 );
2883 oTmpSet->Put( aUL );
2887 const bool bParaRTL = aAttrIter.IsParaRTL();
2889 int nNumberLevel = -1;
2890 if (rNode.IsNumbered())
2891 nNumberLevel = rNode.GetActualListLevel();
2892 if (nNumberLevel >= 0 && nNumberLevel < MAXLEVEL)
2894 const SwNumRule* pRule = rNode.GetNumRule();
2895 sal_uInt8 nLvl = static_cast< sal_uInt8 >(nNumberLevel);
2896 const SwNumFormat* pFormat = pRule->GetNumFormat( nLvl );
2897 if( !pFormat )
2898 pFormat = &pRule->Get( nLvl );
2900 if( !oTmpSet )
2901 oTmpSet.emplace( rNode.GetSwAttrSet() );
2903 SvxFirstLineIndentItem firstLine(oTmpSet->Get(RES_MARGIN_FIRSTLINE));
2904 SvxTextLeftMarginItem leftMargin(oTmpSet->Get(RES_MARGIN_TEXTLEFT));
2905 // #i86652#
2906 if ( pFormat->GetPositionAndSpaceMode() ==
2907 SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2909 leftMargin.SetTextLeft(leftMargin.GetTextLeft() + pFormat->GetAbsLSpace());
2912 if( rNode.IsNumbered() && rNode.IsCountedInList() )
2914 // #i86652#
2915 if ( pFormat->GetPositionAndSpaceMode() ==
2916 SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2918 if (bParaRTL)
2920 firstLine.SetTextFirstLineOffsetValue(firstLine.GetTextFirstLineOffset() + pFormat->GetAbsLSpace() - pFormat->GetFirstLineOffset()); //TODO: overflow
2922 else
2924 firstLine.SetTextFirstLineOffset(firstLine.GetTextFirstLineOffset() + GetWordFirstLineOffset(*pFormat));
2928 // correct fix for issue i94187
2929 if (SfxItemState::SET !=
2930 oTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
2932 // List style set via paragraph style - then put it into the itemset.
2933 // This is needed to get list level and list id exported for
2934 // the paragraph.
2935 oTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
2937 // Put indent values into the itemset in case that the list
2938 // style is applied via paragraph style and the list level
2939 // indent values are not applicable.
2940 if ( pFormat->GetPositionAndSpaceMode() ==
2941 SvxNumberFormat::LABEL_ALIGNMENT)
2943 ::sw::ListLevelIndents const indents(rNode.AreListLevelIndentsApplicable());
2944 if (indents & ::sw::ListLevelIndents::FirstLine)
2946 oTmpSet->Put(firstLine);
2948 if (indents & ::sw::ListLevelIndents::LeftMargin)
2950 oTmpSet->Put(leftMargin);
2955 else
2956 oTmpSet->ClearItem(RES_PARATR_NUMRULE);
2958 // #i86652#
2959 if ( pFormat->GetPositionAndSpaceMode() ==
2960 SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2962 oTmpSet->Put(firstLine);
2963 oTmpSet->Put(leftMargin);
2965 //#i21847#
2966 SvxTabStopItem aItem(oTmpSet->Get(RES_PARATR_TABSTOP));
2967 SvxTabStop aTabStop(pFormat->GetAbsLSpace());
2968 aItem.Insert(aTabStop);
2969 oTmpSet->Put(aItem);
2971 MSWordExportBase::CorrectTabStopInSet(*oTmpSet, pFormat->GetAbsLSpace());
2976 If a given para is using the SvxFrameDirection::Environment direction we
2977 cannot export that, if it's ltr then that's ok as that is word's
2978 default. Otherwise we must add a RTL attribute to our export list
2979 Only necessary if the ParaStyle doesn't define the direction.
2981 const SvxFrameDirectionItem* pItem =
2982 rNode.GetSwAttrSet().GetItem(RES_FRAMEDIR);
2983 if (
2984 (!pItem || pItem->GetValue() == SvxFrameDirection::Environment) &&
2985 rTextColl.GetFrameDir().GetValue() == SvxFrameDirection::Environment
2988 if ( !oTmpSet )
2989 oTmpSet.emplace(rNode.GetSwAttrSet());
2991 if ( bParaRTL )
2992 oTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, RES_FRAMEDIR));
2993 else
2994 oTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR));
2996 const SvxAdjustItem* pAdjust = rNode.GetSwAttrSet().GetItem(RES_PARATR_ADJUST);
2997 if ( pAdjust && (pAdjust->GetAdjust() == SvxAdjust::Left || pAdjust->GetAdjust() == SvxAdjust::Right ) )
2998 oTmpSet->Put( *pAdjust, RES_PARATR_ADJUST );
3000 // move code for handling of numbered,
3001 // but not counted paragraphs to this place. Otherwise, the paragraph
3002 // isn't exported as numbered, but not counted, if no other attribute
3003 // is found in <pTmpSet>
3004 // #i44815# adjust numbering/indents for numbered paragraphs
3005 // without number
3006 // #i47013# need to check rNode.GetNumRule()!=NULL as well.
3007 if ( ! rNode.IsCountedInList() && rNode.GetNumRule()!=nullptr )
3009 // WW8 does not know numbered paragraphs without number
3010 // In WW8AttributeOutput::ParaNumRule(), we will export
3011 // the RES_PARATR_NUMRULE as list-id 0, which in WW8 means
3012 // no numbering. Here, we will adjust the indents to match
3013 // visually.
3015 if ( !oTmpSet )
3016 oTmpSet.emplace(rNode.GetSwAttrSet());
3018 // create new LRSpace item, based on the current (if present)
3019 const SvxFirstLineIndentItem *const pFirstLineIndent(oTmpSet->GetItemIfSet(RES_MARGIN_FIRSTLINE));
3020 const SvxTextLeftMarginItem *const pTextLeftMargin(oTmpSet->GetItemIfSet(RES_MARGIN_TEXTLEFT));
3021 SvxFirstLineIndentItem firstLine(pFirstLineIndent
3022 ? *pFirstLineIndent
3023 : SvxFirstLineIndentItem(0, RES_MARGIN_FIRSTLINE));
3024 SvxTextLeftMarginItem leftMargin(pTextLeftMargin
3025 ? *pTextLeftMargin
3026 : SvxTextLeftMarginItem(0, RES_MARGIN_TEXTLEFT));
3028 // new left margin = old left + label space
3029 const SwNumRule* pRule = rNode.GetNumRule();
3030 int nLevel = rNode.GetActualListLevel();
3032 if (nLevel < 0)
3033 nLevel = 0;
3035 if (nLevel >= MAXLEVEL)
3036 nLevel = MAXLEVEL - 1;
3038 const SwNumFormat& rNumFormat = pRule->Get( static_cast< sal_uInt16 >(nLevel) );
3040 // #i86652#
3041 if ( rNumFormat.GetPositionAndSpaceMode() ==
3042 SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
3044 leftMargin.SetTextLeft(leftMargin.GetLeft(firstLine) + rNumFormat.GetAbsLSpace());
3046 else
3048 leftMargin.SetTextLeft(leftMargin.GetLeft(firstLine) + rNumFormat.GetIndentAt());
3051 // new first line indent = 0
3052 // (first line indent is ignored)
3053 if (!bParaRTL)
3055 firstLine.SetTextFirstLineOffset(0);
3058 // put back the new item
3059 oTmpSet->Put(firstLine);
3060 oTmpSet->Put(leftMargin);
3062 // assure that numbering rule is in <oTmpSet>
3063 if (SfxItemState::SET != oTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
3065 oTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
3069 // #i75457#
3070 // Export page break after attribute from paragraph style.
3071 // If page break attribute at the text node exist, an existing page
3072 // break after at the paragraph style hasn't got to be considered.
3073 if ( !rNode.GetpSwAttrSet() ||
3074 SfxItemState::SET != rNode.GetpSwAttrSet()->GetItemState(RES_BREAK, false) )
3076 const SvxFormatBreakItem& rBreakAtParaStyle
3077 = rNode.GetSwAttrSet().Get(RES_BREAK);
3078 if (rBreakAtParaStyle.GetBreak() == SvxBreak::PageAfter)
3080 if ( !oTmpSet )
3081 oTmpSet.emplace(rNode.GetSwAttrSet());
3082 oTmpSet->Put(rBreakAtParaStyle);
3084 else if( oTmpSet )
3085 { // Even a pagedesc item is set, the break item can be set 'NONE',
3086 // this has to be overruled.
3087 const SwFormatPageDesc& rPageDescAtParaStyle =
3088 rNode.GetAttr( RES_PAGEDESC );
3089 if( rPageDescAtParaStyle.KnowsPageDesc() )
3090 oTmpSet->ClearItem( RES_BREAK );
3094 // #i76520# Emulate non-splitting tables
3095 if ( IsInTable() )
3097 const SwTableNode* pTableNode = rNode.FindTableNode();
3099 if ( pTableNode )
3101 const SwTable& rTable = pTableNode->GetTable();
3102 const SvxFormatKeepItem& rKeep = rTable.GetFrameFormat()->GetKeep();
3103 const bool bKeep = rKeep.GetValue();
3104 const bool bDontSplit = !(bKeep ||
3105 rTable.GetFrameFormat()->GetLayoutSplit().GetValue());
3107 if ( bKeep || bDontSplit )
3109 // bKeep: set keep at first paragraphs in all lines
3110 // bDontSplit : set keep at first paragraphs in all lines except from last line
3111 // but only for non-complex tables
3112 const SwTableBox* pBox = rNode.GetTableBox();
3113 const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
3115 if ( pLine && !pLine->GetUpper() )
3117 // check if box is first in that line:
3118 if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
3120 // check if paragraph is first in that line:
3121 if ( SwNodeOffset(1) == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
3123 bool bSetAtPara = false;
3124 if ( bKeep )
3125 bSetAtPara = true;
3126 else if ( bDontSplit )
3128 // check if pLine isn't last line in table
3129 if ( rTable.GetTabLines().size() - rTable.GetTabLines().GetPos( pLine ) != 1 )
3130 bSetAtPara = true;
3133 if ( bSetAtPara )
3135 if ( !oTmpSet )
3136 oTmpSet.emplace(rNode.GetSwAttrSet());
3138 const SvxFormatKeepItem aKeepItem( true, RES_KEEP );
3139 oTmpSet->Put( aKeepItem );
3148 const SfxItemSet* pNewSet = oTmpSet ? &*oTmpSet : rNode.GetpSwAttrSet();
3149 if( pNewSet )
3150 { // Para-Attrs
3151 m_pStyAttr = &rNode.GetAnyFormatColl().GetAttrSet();
3153 const sw::BroadcastingModify* pOldMod = m_pOutFormatNode;
3154 m_pOutFormatNode = &rNode;
3156 // Pap-Attrs, so script is not necessary
3157 OutputItemSet( *pNewSet, true, false, i18n::ScriptType::LATIN, false);
3159 m_pStyAttr = nullptr;
3160 m_pOutFormatNode = pOldMod;
3164 // The formatting of the paragraph marker has two sources:
3165 // 0) If there is a RES_PARATR_LIST_AUTOFMT, then use that.
3166 // 1) If there are hints at the end of the paragraph, then use that.
3167 // 2) Else use the RES_CHRATR_BEGIN..RES_TXTATR_END range of the paragraph
3168 // properties.
3170 // Exception: if there is a character style hint at the end of the
3171 // paragraph only, then still go with 2), as RES_TXTATR_CHARFMT is always
3172 // set as a hint.
3173 SfxItemSetFixed<RES_CHRATR_BEGIN, RES_TXTATR_END> aParagraphMarkerProperties(m_rDoc.GetAttrPool());
3174 bool bCharFormatOnly = true;
3176 SwFormatAutoFormat const& rListAutoFormat(rNode.GetAttr(RES_PARATR_LIST_AUTOFMT));
3177 if (std::shared_ptr<SfxItemSet> const& pSet = rListAutoFormat.GetStyleHandle())
3179 aParagraphMarkerProperties.Put(*pSet);
3180 bCharFormatOnly = false;
3182 else if (const SwpHints* pTextAttrs = rNode.GetpSwpHints())
3184 for( size_t i = 0; i < pTextAttrs->Count(); ++i )
3186 const SwTextAttr* pHt = pTextAttrs->Get(i);
3187 const sal_Int32 startPos = pHt->GetStart(); // first Attr characters
3188 const sal_Int32* endPos = pHt->End(); // end Attr characters
3189 // Check if these attributes are for the last character in the paragraph
3190 // - which means the paragraph marker. If a paragraph has 7 characters,
3191 // then properties on character 8 are for the paragraph marker
3192 if( endPos && (startPos == *endPos ) && (*endPos == rNode.GetText().getLength()) )
3194 SAL_INFO( "sw.ww8", startPos << "startPos == endPos" << *endPos);
3195 sal_uInt16 nWhich = pHt->GetAttr().Which();
3196 SAL_INFO( "sw.ww8", "nWhich" << nWhich);
3197 if ((nWhich == RES_TXTATR_AUTOFMT && bCharFormatOnly)
3198 || nWhich == RES_TXTATR_CHARFMT)
3200 aParagraphMarkerProperties.Put(pHt->GetAttr());
3202 if (nWhich != RES_TXTATR_CHARFMT)
3203 bCharFormatOnly = false;
3207 if (rNode.GetpSwAttrSet() && bCharFormatOnly)
3209 aParagraphMarkerProperties.Put(*rNode.GetpSwAttrSet());
3211 const SwRedlineData* pRedlineParagraphMarkerDelete = AttrOutput().GetParagraphMarkerRedline( rNode, RedlineType::Delete );
3212 const SwRedlineData* pRedlineParagraphMarkerInsert = AttrOutput().GetParagraphMarkerRedline( rNode, RedlineType::Insert );
3213 const SwRedlineData* pParagraphRedlineData = aAttrIter.GetParagraphLevelRedline( );
3214 AttrOutput().EndParagraphProperties(aParagraphMarkerProperties, pParagraphRedlineData, pRedlineParagraphMarkerDelete, pRedlineParagraphMarkerInsert);
3216 AttrOutput().EndParagraph( pTextNodeInfoInner );
3217 }while(*aBreakIt != rNode.GetText().getLength() && bNeedParaSplit );
3219 SAL_INFO( "sw.ww8", "</OutWW8_SwTextNode>" );
3222 // Tables
3224 void WW8AttributeOutput::EmptyParagraph()
3226 m_rWW8Export.WriteStringAsPara( OUString() );
3229 bool MSWordExportBase::NoPageBreakSection( const SfxItemSet* pSet )
3231 bool bRet = false;
3232 if( pSet)
3234 bool bNoPageBreak = false;
3235 const SwFormatPageDesc* pDescItem = pSet->GetItemIfSet(RES_PAGEDESC);
3236 if ( !pDescItem || nullptr == pDescItem->GetPageDesc() )
3238 bNoPageBreak = true;
3241 if (bNoPageBreak)
3243 if (const SvxFormatBreakItem* pBreakItem = pSet->GetItemIfSet(RES_BREAK))
3245 SvxBreak eBreak = pBreakItem->GetBreak();
3246 switch (eBreak)
3248 case SvxBreak::PageBefore:
3249 case SvxBreak::PageAfter:
3250 bNoPageBreak = false;
3251 break;
3252 default:
3253 break;
3257 bRet = bNoPageBreak;
3259 return bRet;
3262 void MSWordExportBase::OutputSectionNode( const SwSectionNode& rSectionNode )
3264 const SwSection& rSection = rSectionNode.GetSection();
3266 SwNodeIndex aIdx( rSectionNode, 1 );
3267 const SwNode& rNd = aIdx.GetNode();
3268 if ( !rNd.IsSectionNode() && !IsInTable() ) //No sections in table
3270 // if the first Node inside the section has an own
3271 // PageDesc or PageBreak attribute, then don't write
3272 // here the section break
3273 sal_uLong nRstLnNum = 0;
3274 const SfxItemSet* pSet;
3275 if ( rNd.IsContentNode() )
3277 pSet = &rNd.GetContentNode()->GetSwAttrSet();
3278 nRstLnNum = pSet->Get( RES_LINENUMBER ).GetStartValue();
3280 else
3281 pSet = nullptr;
3283 if ( pSet && NoPageBreakSection( pSet ) )
3284 pSet = nullptr;
3285 else
3286 AttrOutput().SectionBreaks( rSectionNode );
3288 const bool bInTOX = rSection.GetType() == SectionType::ToxContent || rSection.GetType() == SectionType::ToxHeader;
3289 if ( !pSet && !bInTOX )
3291 // new Section with no own PageDesc/-Break
3292 // -> write follow section break;
3293 const SwSectionFormat* pFormat = rSection.GetFormat();
3294 ReplaceCr( msword::PageBreak ); // Indicator for Page/Section-Break
3296 // Get the page in use at the top of this section
3297 const SwPageDesc *pCurrent = SwPageDesc::GetPageDescOfNode(rNd);
3298 if (!pCurrent)
3299 pCurrent = m_pCurrentPageDesc;
3301 AppendSection( pCurrent, pFormat, nRstLnNum );
3304 if ( SectionType::ToxContent == rSection.GetType() )
3306 m_bStartTOX = true;
3307 UpdateTocSectionNodeProperties(rSectionNode);
3311 // tdf#121561: During export of the ODT file with TOC inside into DOCX format,
3312 // the TOC title is being exported as regular paragraph. We should surround it
3313 // with <w:sdt><w:sdtPr><w:sdtContent> to make it (TOC title) recognizable
3314 // by MS Word as part of the TOC.
3315 void MSWordExportBase::UpdateTocSectionNodeProperties(const SwSectionNode& rSectionNode)
3317 // check section type
3319 const SwSection& rSection = rSectionNode.GetSection();
3320 if (SectionType::ToxContent != rSection.GetType())
3321 return;
3323 const SwTOXBase* pTOX = rSection.GetTOXBase();
3324 if (pTOX)
3326 TOXTypes type = pTOX->GetType();
3327 if (type != TOXTypes::TOX_CONTENT)
3328 return;
3332 // get section node, skip toc-header node
3333 const SwSectionNode* pSectNd = &rSectionNode;
3335 SwNodeIndex aIdxNext( *pSectNd, 1 );
3336 const SwNode& rNdNext = aIdxNext.GetNode();
3338 if (rNdNext.IsSectionNode())
3340 const SwSectionNode* pSectNdNext = static_cast<const SwSectionNode*>(&rNdNext);
3341 if (SectionType::ToxHeader == pSectNdNext->GetSection().GetType() &&
3342 pSectNdNext->StartOfSectionNode()->IsSectionNode())
3344 pSectNd = pSectNdNext;
3349 // get node of the first paragraph inside TOC
3350 SwNodeIndex aIdxNext( *pSectNd, 1 );
3351 const SwNode& rNdTocPara = aIdxNext.GetNode();
3352 const SwContentNode* pNode = rNdTocPara.GetContentNode();
3353 if (!pNode)
3354 return;
3356 // put required flags into grab bag of the first node in TOC
3358 uno::Sequence<beans::PropertyValue> aDocPropertyValues(comphelper::InitPropertySequence(
3360 {"ooxml:CT_SdtDocPart_docPartGallery", uno::Any(OUString("Table of Contents"))},
3361 {"ooxml:CT_SdtDocPart_docPartUnique", uno::Any(OUString("true"))},
3362 }));
3364 uno::Sequence<beans::PropertyValue> aSdtPrPropertyValues(comphelper::InitPropertySequence(
3366 {"ooxml:CT_SdtPr_docPartObj", uno::Any(aDocPropertyValues)},
3367 }));
3369 SfxGrabBagItem aGrabBag(RES_PARATR_GRABBAG);
3370 aGrabBag.GetGrabBag()["SdtPr"] <<= aSdtPrPropertyValues;
3372 // create temp attr set
3373 SwAttrSet aSet(pNode->GetSwAttrSet());
3374 aSet.Put(aGrabBag);
3376 // set new attr to node
3377 const_cast<SwContentNode*>(pNode)->SetAttr(aSet);
3380 // set flag for the next node after TOC
3381 // in order to indicate that std area has been finished
3382 // see, DomainMapper::lcl_startParagraphGroup() for the same functionality during load
3384 SwNodeIndex aEndTocNext( *rSectionNode.EndOfSectionNode(), 1 );
3385 const SwNode& rEndTocNextNode = aEndTocNext.GetNode();
3386 const SwContentNode* pNodeAfterToc = rEndTocNextNode.GetContentNode();
3387 if (pNodeAfterToc)
3389 SfxGrabBagItem aGrabBag(RES_PARATR_GRABBAG);
3390 aGrabBag.GetGrabBag()["ParaSdtEndBefore"] <<= true;
3392 // create temp attr set
3393 SwAttrSet aSet(pNodeAfterToc->GetSwAttrSet());
3394 aSet.Put(aGrabBag);
3396 // set new attr to node
3397 const_cast<SwContentNode*>(pNodeAfterToc)->SetAttr(aSet);
3402 void WW8Export::AppendSection( const SwPageDesc *pPageDesc, const SwSectionFormat* pFormat, sal_uLong nLnNum )
3404 m_pSepx->AppendSep(Fc2Cp(Strm().Tell()), pPageDesc, pFormat, nLnNum);
3407 // Flys
3409 void WW8AttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFormat, const Point& rNdTopLeft )
3411 const SwFrameFormat &rFrameFormat = rFormat.GetFrameFormat();
3412 const SwFormatAnchor& rAnch = rFrameFormat.GetAnchor();
3414 bool bUseEscher = true;
3416 if (rFormat.IsInline())
3418 ww8::Frame::WriterSource eType = rFormat.GetWriterType();
3419 bUseEscher = eType != ww8::Frame::eGraphic && eType != ww8::Frame::eOle;
3422 A special case for converting some inline form controls to form fields
3423 when in winword 8+ mode
3425 if (bUseEscher && (eType == ww8::Frame::eFormControl))
3427 if ( m_rWW8Export.MiserableFormFieldExportHack( rFrameFormat ) )
3428 return ;
3432 if (bUseEscher)
3434 // write as escher
3435 if (rFrameFormat.GetFlySplit().GetValue())
3437 // The frame can split: this was originally from a floating table, write it back as
3438 // such.
3439 const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
3440 SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex() + 1 : SwNodeOffset(0);
3441 SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
3442 m_rWW8Export.SaveData(nStt, nEnd);
3443 GetExport().WriteText();
3444 m_rWW8Export.RestoreData();
3446 else
3448 m_rWW8Export.AppendFlyInFlys(rFormat, rNdTopLeft);
3451 else
3453 bool bDone = false;
3455 // Fetch from node and last node the position in the section
3456 const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
3458 SwNodeOffset nStt = pNodeIndex ? pNodeIndex->GetIndex()+1 : SwNodeOffset(0);
3459 SwNodeOffset nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : SwNodeOffset(0);
3461 if( nStt >= nEnd ) // no range, hence no valid node
3462 return;
3464 if ( !m_rWW8Export.IsInTable() && rFormat.IsInline() )
3466 //Test to see if this textbox contains only a single graphic/ole
3467 SwTextNode* pParTextNode = rAnch.GetAnchorNode()->GetTextNode();
3468 if ( pParTextNode && !m_rWW8Export.m_rDoc.GetNodes()[ nStt ]->IsNoTextNode() )
3469 bDone = true;
3471 if( !bDone )
3474 m_rWW8Export.SaveData( nStt, nEnd );
3476 Point aOffset;
3477 if ( m_rWW8Export.m_pParentFrame )
3479 /* Munge flys in fly into absolutely positioned elements for word 6 */
3480 const SwTextNode* pParTextNode = rAnch.GetAnchorNode()->GetTextNode();
3481 const SwRect aPageRect = pParTextNode->FindPageFrameRect();
3483 aOffset = rFrameFormat.FindLayoutRect().Pos();
3484 aOffset -= aPageRect.Pos();
3486 m_rWW8Export.m_pFlyOffset = &aOffset;
3487 m_rWW8Export.m_eNewAnchorType = RndStdIds::FLY_AT_PAGE;
3490 m_rWW8Export.m_pParentFrame = &rFormat;
3491 if (
3492 m_rWW8Export.IsInTable() &&
3493 (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) &&
3494 !m_rWW8Export.m_rDoc.GetNodes()[ nStt ]->IsNoTextNode()
3497 // note: set Flag bOutTable again,
3498 // because we deliver the normal content of the table cell, and no border
3499 // ( Flag was deleted above in aSaveData() )
3500 m_rWW8Export.m_bOutTable = true;
3501 const OUString& aName = rFrameFormat.GetName();
3502 m_rWW8Export.StartCommentOutput(aName);
3503 m_rWW8Export.WriteText();
3504 m_rWW8Export.EndCommentOutput(aName);
3506 else
3507 m_rWW8Export.WriteText();
3509 m_rWW8Export.RestoreData();
3514 void AttributeOutputBase::OutputFlyFrame( const ww8::Frame& rFormat )
3516 if ( !rFormat.GetContentNode() )
3517 return;
3519 const SwContentNode &rNode = *rFormat.GetContentNode();
3520 Point aLayPos;
3522 // get the Layout Node-Position
3523 if (RndStdIds::FLY_AT_PAGE == rFormat.GetFrameFormat().GetAnchor().GetAnchorId())
3524 aLayPos = rNode.FindPageFrameRect().Pos();
3525 else
3526 aLayPos = rNode.FindLayoutRect().Pos();
3528 OutputFlyFrame_Impl( rFormat, aLayPos );
3531 // write data of any redline
3532 void WW8AttributeOutput::Redline( const SwRedlineData* pRedline )
3534 if ( !pRedline )
3535 return;
3537 if ( pRedline->Next() )
3538 Redline( pRedline->Next() );
3540 static const sal_uInt16 insSprmIds[ 3 ] =
3542 // Ids for insert // for WW8
3543 NS_sprm::CFRMarkIns::val, NS_sprm::CIbstRMark::val, NS_sprm::CDttmRMark::val,
3545 static const sal_uInt16 delSprmIds[ 3 ] =
3547 // Ids for delete // for WW8
3548 NS_sprm::CFRMarkDel::val, NS_sprm::CIbstRMarkDel::val, NS_sprm::CDttmRMarkDel::val,
3551 const sal_uInt16* pSprmIds = nullptr;
3552 switch( pRedline->GetType() )
3554 case RedlineType::Insert:
3555 pSprmIds = insSprmIds;
3556 break;
3558 case RedlineType::Delete:
3559 pSprmIds = delSprmIds;
3560 break;
3562 case RedlineType::Format:
3563 m_rWW8Export.InsUInt16( NS_sprm::CPropRMark90::val );
3564 m_rWW8Export.m_pO->push_back( 7 ); // len
3565 m_rWW8Export.m_pO->push_back( 1 );
3566 m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
3567 m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
3568 break;
3569 default:
3570 OSL_ENSURE(false, "Unhandled redline type for export");
3571 break;
3574 if ( pSprmIds )
3576 m_rWW8Export.InsUInt16( pSprmIds[0] );
3577 m_rWW8Export.m_pO->push_back( 1 );
3579 m_rWW8Export.InsUInt16( pSprmIds[1] );
3580 m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
3582 m_rWW8Export.InsUInt16( pSprmIds[2] );
3583 m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
3587 void MSWordExportBase::OutputContentNode( SwContentNode& rNode )
3589 switch ( rNode.GetNodeType() )
3591 case SwNodeType::Text:
3592 OutputTextNode( *rNode.GetTextNode() );
3593 break;
3594 case SwNodeType::Grf:
3595 OutputGrfNode( *rNode.GetGrfNode() );
3596 break;
3597 case SwNodeType::Ole:
3598 OutputOLENode( *rNode.GetOLENode() );
3599 break;
3600 default:
3601 SAL_WARN("sw.ww8", "Unhandled node, type == " << static_cast<int>(rNode.GetNodeType()) );
3602 break;
3607 WW8Ruby::WW8Ruby(const SwTextNode& rNode, const SwFormatRuby& rRuby, const MSWordExportBase& rExport ):
3608 m_nJC(0),
3609 m_cDirective(0),
3610 m_nRubyHeight(0),
3611 m_nBaseHeight(0)
3613 switch ( rRuby.GetAdjustment() )
3615 case css::text::RubyAdjust_LEFT:
3616 m_nJC = 3;
3617 m_cDirective = 'l';
3618 break;
3619 case css::text::RubyAdjust_CENTER:
3620 //defaults to 0
3621 break;
3622 case css::text::RubyAdjust_RIGHT:
3623 m_nJC = 4;
3624 m_cDirective = 'r';
3625 break;
3626 case css::text::RubyAdjust_BLOCK:
3627 m_nJC = 1;
3628 m_cDirective = 'd';
3629 break;
3630 case css::text::RubyAdjust_INDENT_BLOCK:
3631 m_nJC = 2;
3632 m_cDirective = 'd';
3633 break;
3634 default:
3635 OSL_ENSURE( false,"Unhandled Ruby justification code" );
3636 break;
3639 if ( rRuby.GetPosition() == css::text::RubyPosition::INTER_CHARACTER )
3641 m_nJC = 5;
3642 m_cDirective = 0;
3646 MS needs to know the name and size of the font used in the ruby item,
3647 but we could have written it in a mixture of asian and western
3648 scripts, and each of these can be a different font and size than the
3649 other, so we make a guess based upon the first character of the text,
3650 defaulting to asian.
3652 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
3653 sal_uInt16 nRubyScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3655 const SwTextRuby* pRubyText = rRuby.GetTextRuby();
3656 const SwCharFormat* pFormat = pRubyText ? pRubyText->GetCharFormat() : nullptr;
3658 if (pFormat)
3660 const auto& rFont
3661 = pFormat->GetFormatAttr( GetWhichOfScript(RES_CHRATR_FONT, nRubyScript) );
3662 m_sFontFamily = rFont.GetFamilyName();
3664 const auto& rHeight =
3665 pFormat->GetFormatAttr( GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript) );
3666 m_nRubyHeight = rHeight.GetHeight();
3668 else
3670 /*Get defaults if no formatting on ruby text*/
3672 const SfxItemPool* pPool = rNode.GetSwAttrSet().GetPool();
3673 pPool = pPool ? pPool : &rExport.m_rDoc.GetAttrPool();
3676 const SvxFontItem& rFont
3677 = pPool->GetDefaultItem( GetWhichOfScript(RES_CHRATR_FONT, nRubyScript) );
3678 m_sFontFamily = rFont.GetFamilyName();
3680 const SvxFontHeightItem& rHeight =
3681 pPool->GetDefaultItem( GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
3682 m_nRubyHeight = rHeight.GetHeight();
3685 const OUString &rText = rNode.GetText();
3686 sal_uInt16 nScript = i18n::ScriptType::LATIN;
3688 if (!rText.isEmpty())
3689 nScript = g_pBreakIt->GetBreakIter()->getScriptType(rText, 0);
3691 sal_uInt16 nWhich = GetWhichOfScript(RES_CHRATR_FONTSIZE, nScript);
3692 auto& rHeightItem = static_cast<const SvxFontHeightItem&>(rExport.GetItem(nWhich));
3693 m_nBaseHeight = rHeightItem.GetHeight();
3695 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */