1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <vector>
21 #include <list>
22 #include <utility>
23 #include <algorithm>
24 #include <iostream>
25 
26 #include <oox/core/filterbase.hxx>
27 #include "docxexport.hxx"
28 #include "docxexportfilter.hxx"
29 
30 #include <i18nlangtag/mslangid.hxx>
31 #include <hintids.hxx>
32 #include <tools/urlobj.hxx>
33 #include <editeng/boxitem.hxx>
34 #include <editeng/cmapitem.hxx>
35 #include <editeng/langitem.hxx>
36 #include <editeng/svxfont.hxx>
37 #include <editeng/lrspitem.hxx>
38 #include <editeng/brushitem.hxx>
39 #include <editeng/fontitem.hxx>
40 #include <editeng/keepitem.hxx>
41 #include <editeng/fhgtitem.hxx>
42 #include <editeng/ulspitem.hxx>
43 #include <editeng/formatbreakitem.hxx>
44 #include <editeng/frmdiritem.hxx>
45 #include <editeng/tstpitem.hxx>
46 #include <editeng/wghtitem.hxx>
47 #include <svl/grabbagitem.hxx>
48 #include <svl/urihelper.hxx>
49 #include <svl/whiter.hxx>
50 #include <fmtpdsc.hxx>
51 #include <fmtfsize.hxx>
52 #include <fmtornt.hxx>
53 #include <fmtlsplt.hxx>
54 #include <fmtflcnt.hxx>
55 #include <fmtanchr.hxx>
56 #include <fmtcntnt.hxx>
57 #include <frmatr.hxx>
58 #include <paratr.hxx>
59 #include <txatbase.hxx>
60 #include <fmtinfmt.hxx>
61 #include <fmtrfmrk.hxx>
62 #include <fchrfmt.hxx>
63 #include <fmtautofmt.hxx>
64 #include <charfmt.hxx>
65 #include <tox.hxx>
66 #include <ndtxt.hxx>
67 #include <pam.hxx>
68 #include <doc.hxx>
69 #include <IDocumentSettingAccess.hxx>
70 #include <IDocumentMarkAccess.hxx>
71 #include <docary.hxx>
72 #include <swtable.hxx>
73 #include <swtblfmt.hxx>
74 #include <section.hxx>
75 #include <pagedesc.hxx>
76 #include <swrect.hxx>
77 #include <reffld.hxx>
78 #include <redline.hxx>
79 #include <wrtswtbl.hxx>
80 #include <htmltbl.hxx>
81 #include <txttxmrk.hxx>
82 #include <fmtline.hxx>
83 #include <fmtruby.hxx>
84 #include <breakit.hxx>
85 #include <txtatr.hxx>
86 #include <fmtsrnd.hxx>
87 #include <fmtrowsplt.hxx>
88 #include <com/sun/star/drawing/XShape.hpp>
89 #include <com/sun/star/i18n/BreakIterator.hpp>
90 #include <com/sun/star/i18n/ScriptType.hpp>
91 #include <com/sun/star/i18n/WordType.hpp>
92 #include <com/sun/star/text/RubyPosition.hpp>
93 #include <oox/export/vmlexport.hxx>
94 #include <sfx2/docfile.hxx>
95 #include <sal/log.hxx>
96 #include <comphelper/propertysequence.hxx>
97 
98 #include "sprmids.hxx"
99 
100 #include "writerhelper.hxx"
101 #include "writerwordglue.hxx"
102 #include <numrule.hxx>
103 #include "wrtww8.hxx"
104 #include "ww8par.hxx"
105 #include <IMark.hxx>
106 #include "ww8attributeoutput.hxx"
107 
108 #include <ndgrf.hxx>
109 #include <ndole.hxx>
110 
111 #include <cstdio>
112 
113 using namespace ::com::sun::star;
114 using namespace ::com::sun::star::i18n;
115 using namespace sw::util;
116 using namespace sw::types;
117 using namespace sw::mark;
118 using namespace ::oox::vml;
119 
lcl_getFieldCode(const IFieldmark * pFieldmark)120 static OUString lcl_getFieldCode( const IFieldmark* pFieldmark )
121 {
122     assert(pFieldmark);
123 
124     if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
125         return " FORMTEXT ";
126     if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
127         return " FORMDROPDOWN ";
128     if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
129         return " FORMCHECKBOX ";
130     if ( pFieldmark->GetFieldname( ) == ODF_FORMDATE )
131         return " ODFFORMDATE ";
132     if ( pFieldmark->GetFieldname( ) == ODF_TOC )
133         return " TOC ";
134     if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
135         return " HYPERLINK ";
136     if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
137         return " PAGEREF ";
138     return pFieldmark->GetFieldname();
139 }
140 
lcl_getFieldId(const IFieldmark * const pFieldmark)141 static ww::eField lcl_getFieldId(const IFieldmark*const pFieldmark)
142 {
143     assert(pFieldmark);
144 
145     if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
146         return ww::eFORMTEXT;
147     if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
148         return ww::eFORMDROPDOWN;
149     if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
150         return ww::eFORMCHECKBOX;
151     if ( pFieldmark->GetFieldname( ) == ODF_FORMDATE )
152         return ww::eFORMDATE;
153     if ( pFieldmark->GetFieldname( ) == ODF_TOC )
154         return ww::eTOC;
155     if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
156         return ww::eHYPERLINK;
157     if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
158         return ww::ePAGEREF;
159     return ww::eUNKNOWN;
160 }
161 
MSWordAttrIter(MSWordExportBase & rExport)162 MSWordAttrIter::MSWordAttrIter( MSWordExportBase& rExport )
163     : pOld( rExport.m_pChpIter ), m_rExport( rExport )
164 {
165     m_rExport.m_pChpIter = this;
166 }
167 
~MSWordAttrIter()168 MSWordAttrIter::~MSWordAttrIter()
169 {
170     m_rExport.m_pChpIter = pOld;
171 }
172 
173 class sortswflys
174 {
175 public:
operator ()(const ww8::Frame & rOne,const ww8::Frame & rTwo) const176     bool operator()(const ww8::Frame &rOne, const ww8::Frame &rTwo) const
177     {
178         return rOne.GetPosition() < rTwo.GetPosition();
179     }
180 };
181 
IterToCurrent()182 void SwWW8AttrIter::IterToCurrent()
183 {
184     OSL_ENSURE(maCharRuns.begin() != maCharRuns.end(), "Impossible");
185     mnScript = maCharRunIter->mnScript;
186     meChrSet = maCharRunIter->meCharSet;
187     mbCharIsRTL = maCharRunIter->mbRTL;
188 }
189 
SwWW8AttrIter(MSWordExportBase & rWr,const SwTextNode & rTextNd)190 SwWW8AttrIter::SwWW8AttrIter(MSWordExportBase& rWr, const SwTextNode& rTextNd) :
191     MSWordAttrIter(rWr),
192     rNd(rTextNd),
193     maCharRuns(GetPseudoCharRuns(rTextNd)),
194     pCurRedline(nullptr),
195     nCurrentSwPos(0),
196     nCurRedlinePos(SwRedlineTable::npos),
197     mrSwFormatDrop(rTextNd.GetSwAttrSet().GetDrop())
198 {
199 
200     SwPosition aPos(rTextNd);
201     mbParaIsRTL = SvxFrameDirection::Horizontal_RL_TB == rWr.m_pDoc->GetTextDirection(aPos);
202 
203     maCharRunIter = maCharRuns.begin();
204     IterToCurrent();
205 
206     /*
207      #i2916#
208      Get list of any graphics which may be anchored from this paragraph.
209     */
210     maFlyFrames = GetFramesInNode(rWr.m_aFrames, rNd);
211     std::sort(maFlyFrames.begin(), maFlyFrames.end(), sortswflys());
212 
213     /*
214      #i18480#
215      If we are inside a frame then anything anchored inside this frame can
216      only be supported by word anchored inline ("as character"), so force
217      this in the supportable case.
218     */
219     if (rWr.m_bInWriteEscher)
220     {
221         for ( auto& aFlyFrame : maFlyFrames )
222             aFlyFrame.ForceTreatAsInline();
223     }
224 
225     maFlyIter = maFlyFrames.begin();
226 
227     if ( !m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
228     {
229         SwPosition aPosition( rNd, SwIndex( const_cast<SwTextNode*>(&rNd) ) );
230         pCurRedline = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedline( aPosition, &nCurRedlinePos );
231     }
232 
233     nCurrentSwPos = SearchNext(1);
234 }
235 
lcl_getMinPos(sal_Int32 pos1,sal_Int32 pos2)236 static sal_Int32 lcl_getMinPos( sal_Int32 pos1, sal_Int32 pos2 )
237 {
238     if ( pos1 >= 0 && pos2 >= 0 )
239     {
240         // both valid: return minimum one
241         return std::min(pos1, pos2);
242     }
243 
244     // return the valid one, if any, or -1
245     return std::max(pos1, pos2);
246 }
247 
SearchNext(sal_Int32 nStartPos)248 sal_Int32 SwWW8AttrIter::SearchNext( sal_Int32 nStartPos )
249 {
250     const OUString aText = rNd.GetText();
251     sal_Int32 fieldEndPos = aText.indexOf(CH_TXT_ATR_FIELDEND, nStartPos - 1);
252     // HACK: for (so far) mysterious reasons the sdtContent element closes
253     // too late in testDateFormField() unless an empty run is exported at
254     // the end of the fieldmark; hence find *also* the position after the
255     // CH_TXT_ATR_FIELDEND here
256     if (0 <= fieldEndPos && fieldEndPos < nStartPos)
257     {
258         ++fieldEndPos;
259     }
260     sal_Int32 fieldSepPos = aText.indexOf(CH_TXT_ATR_FIELDSEP, nStartPos);
261     sal_Int32 fieldStartPos = aText.indexOf(CH_TXT_ATR_FIELDSTART, nStartPos);
262     sal_Int32 formElementPos = aText.indexOf(CH_TXT_ATR_FORMELEMENT, nStartPos - 1);
263     if (0 <= formElementPos && formElementPos < nStartPos)
264     {
265         ++formElementPos; // tdf#133604 put this in its own run
266     }
267 
268     const sal_Int32 pos = lcl_getMinPos(
269         lcl_getMinPos(lcl_getMinPos(fieldEndPos, fieldSepPos), fieldStartPos),
270         formElementPos );
271 
272     sal_Int32 nMinPos = (pos>=0) ? pos : SAL_MAX_INT32;
273 
274     // first the redline, then the attributes
275     if( pCurRedline )
276     {
277         const SwPosition* pEnd = pCurRedline->End();
278         if (pEnd->nNode == rNd)
279         {
280             const sal_Int32 i = pEnd->nContent.GetIndex();
281             if ( i >= nStartPos && i < nMinPos )
282             {
283                 nMinPos = i;
284             }
285         }
286     }
287 
288     if ( nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() )
289     {
290         // nCurRedlinePos point to the next redline
291         SwRedlineTable::size_type nRedLinePos = nCurRedlinePos;
292         if( pCurRedline )
293             ++nRedLinePos;
294 
295         for ( ; nRedLinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedLinePos )
296         {
297             const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedLinePos ];
298 
299             const SwPosition* pStt = pRedl->Start();
300             const SwPosition* pEnd = pStt == pRedl->GetPoint()
301                                         ? pRedl->GetMark()
302                                         : pRedl->GetPoint();
303 
304             if( pStt->nNode == rNd )
305             {
306                 const sal_Int32 i = pStt->nContent.GetIndex();
307                 if( i >= nStartPos && i < nMinPos )
308                     nMinPos = i;
309             }
310             else
311                 break;
312 
313             if( pEnd->nNode == rNd )
314             {
315                 const sal_Int32 i = pEnd->nContent.GetIndex();
316                 if( i >= nStartPos && i < nMinPos )
317                 {
318                     nMinPos = i;
319                 }
320             }
321         }
322     }
323 
324     if (mrSwFormatDrop.GetWholeWord() && nStartPos <= rNd.GetDropLen(0))
325         nMinPos = rNd.GetDropLen(0);
326     else if(nStartPos <= mrSwFormatDrop.GetChars())
327         nMinPos = mrSwFormatDrop.GetChars();
328 
329     if(const SwpHints* pTextAttrs = rNd.GetpSwpHints())
330     {
331 
332 // can be optimized if we consider that the TextAttrs are sorted by start position.
333 // but then we'd have to save 2 indices
334         for( size_t i = 0; i < pTextAttrs->Count(); ++i )
335         {
336             const SwTextAttr* pHt = pTextAttrs->Get(i);
337             sal_Int32 nPos = pHt->GetStart();    // first Attr characters
338             if( nPos >= nStartPos && nPos <= nMinPos )
339                 nMinPos = nPos;
340 
341             if( pHt->End() )         // Attr with end
342             {
343                 nPos = *pHt->End();      // last Attr character + 1
344                 if( nPos >= nStartPos && nPos <= nMinPos )
345                     nMinPos = nPos;
346             }
347             if (pHt->HasDummyChar())
348             {
349                 // pos + 1 because of CH_TXTATR in Text
350                 nPos = pHt->GetStart() + 1;
351                 if( nPos >= nStartPos && nPos <= nMinPos )
352                     nMinPos = nPos;
353             }
354         }
355     }
356 
357     if (maCharRunIter != maCharRuns.end())
358     {
359         if (maCharRunIter->mnEndPos < nMinPos)
360             nMinPos = maCharRunIter->mnEndPos;
361         IterToCurrent();
362     }
363 
364     /*
365      #i2916#
366      Check to see if there are any graphics anchored to characters in this
367      paragraph's text. Set nMinPos to 1 past the placement for anchored to
368      character because anchors in Word appear after the character they are
369      anchored to.
370     */
371     if (maFlyIter != maFlyFrames.end())
372     {
373         const SwPosition &rAnchor = maFlyIter->GetPosition();
374 
375         sal_Int32 nPos = rAnchor.nContent.GetIndex();
376         if (nPos >= nStartPos && nPos <= nMinPos)
377             nMinPos = nPos;
378 
379         if (maFlyIter->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR)
380         {
381             ++nPos;
382             if (nPos >= nStartPos && nPos <= nMinPos)
383                 nMinPos = nPos;
384         }
385     }
386 
387     //nMinPos found and not going to change at this point
388 
389     if (maCharRunIter != maCharRuns.end())
390     {
391         if (maCharRunIter->mnEndPos == nMinPos)
392             ++maCharRunIter;
393     }
394 
395     return nMinPos;
396 }
397 
OutAttr(sal_Int32 nSwPos,bool bWriteCombChars)398 void SwWW8AttrIter::OutAttr( sal_Int32 nSwPos, bool bWriteCombChars)
399 {
400     m_rExport.AttrOutput().RTLAndCJKState( mbCharIsRTL, GetScript() );
401 
402     /*
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
406     */
407     sal_uInt16 nFontId = GetWhichOfScript( RES_CHRATR_FONT, GetScript() );
408 
409     const SvxFontItem &rParentFont = ItemGet<SvxFontItem>(
410         static_cast<const SwTextFormatColl&>(rNd.GetAnyFormatColl()), nFontId);
411     const SvxFontItem *pFont = &rParentFont;
412     const SfxPoolItem *pGrabBag = nullptr;
413 
414     SfxItemSet aExportSet(*rNd.GetSwAttrSet().GetPool(),
415         svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END - 1>{});
416 
417     //The hard formatting properties that affect the entire paragraph
418     if (rNd.HasSwAttrSet())
419     {
420         // only copy hard attributes - bDeep = false
421         aExportSet.Set(rNd.GetSwAttrSet(), false/*bDeep*/);
422         // get the current font item. Use rNd.GetSwAttrSet instead of aExportSet:
423         const SvxFontItem &rNdFont = ItemGet<SvxFontItem>(rNd.GetSwAttrSet(), nFontId);
424         pFont = &rNdFont;
425         aExportSet.ClearItem(nFontId);
426     }
427 
428     //The additional hard formatting properties that affect this range in the
429     //paragraph
430     ww8::PoolItems aRangeItems;
431     if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
432     {
433         for( size_t i = 0; i < pTextAttrs->Count(); ++i )
434         {
435             const SwTextAttr* pHt = pTextAttrs->Get(i);
436             const sal_Int32* pEnd = pHt->End();
437 
438             if (pEnd ? ( nSwPos >= pHt->GetStart() && nSwPos < *pEnd)
439                         : nSwPos == pHt->GetStart() )
440             {
441                 sal_uInt16 nWhich = pHt->GetAttr().Which();
442                 if (nWhich == RES_TXTATR_AUTOFMT)
443                 {
444                     const SwFormatAutoFormat& rAutoFormat = static_cast<const SwFormatAutoFormat&>(pHt->GetAttr());
445                     const std::shared_ptr<SfxItemSet>& pSet = rAutoFormat.GetStyleHandle();
446                     SfxWhichIter aIter( *pSet );
447                     const SfxPoolItem* pItem;
448                     sal_uInt16 nWhichId = aIter.FirstWhich();
449                     while( nWhichId )
450                     {
451                         if( SfxItemState::SET == pSet->GetItemState( nWhichId, false, &pItem ))
452                         {
453                             if (nWhichId == nFontId)
454                                 pFont = &(item_cast<SvxFontItem>(*pItem));
455                             else if (nWhichId == RES_CHRATR_GRABBAG)
456                                 pGrabBag = pItem;
457                             else
458                                 aRangeItems[nWhichId] = pItem;
459                         }
460                         nWhichId = aIter.NextWhich();
461                     }
462                 }
463                 else
464                     aRangeItems[nWhich] = (&(pHt->GetAttr()));
465             }
466             else if (nSwPos < pHt->GetStart())
467                 break;
468         }
469     }
470 
471     /*
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
475     */
476     const SwFormatCharFormat *pCharFormatItem =
477         HasItem< SwFormatCharFormat >( aRangeItems, RES_TXTATR_CHARFMT );
478     if ( pCharFormatItem )
479         ClearOverridesFromSet( *pCharFormatItem, aExportSet );
480 
481     // check toggle properties in DOCX output
482     {
483         SvxWeightItem aBoldProperty(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
484         handleToggleProperty(aExportSet, pCharFormatItem, RES_CHRATR_WEIGHT, &aBoldProperty);
485     }
486 
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))
490     {
491         if (const SfxGrabBagItem *pCharFmtGrabBag = aExportSet.GetItem<SfxGrabBagItem>(RES_CHRATR_GRABBAG, false))
492         {
493             std::unique_ptr<SfxGrabBagItem> pNewCharFmtGrabBag(static_cast<SfxGrabBagItem*>(pCharFmtGrabBag->Clone()));
494             assert(pNewCharFmtGrabBag);
495             auto & rNewFmtMap = pNewCharFmtGrabBag->GetGrabBag();
496             for (auto const & item : pAutoFmtGrabBag->GetGrabBag())
497             {
498                 if (item.second.hasValue())
499                     rNewFmtMap.erase(item.first);
500             }
501             aExportSet.Put(std::move(pNewCharFmtGrabBag));
502         }
503     }
504 
505     ww8::PoolItems aExportItems;
506     GetPoolItems( aExportSet, aExportItems, false );
507 
508     if( rNd.GetpSwpHints() == nullptr )
509         m_rExport.SetCurItemSet(&aExportSet);
510 
511     for ( const auto& aRangeItem : aRangeItems )
512     {
513         aExportItems[aRangeItem.first] = aRangeItem.second;
514     }
515 
516     if ( !aExportItems.empty() )
517     {
518         const SwModify* pOldMod = m_rExport.m_pOutFormatNode;
519         m_rExport.m_pOutFormatNode = &rNd;
520         m_rExport.m_aCurrentCharPropStarts.push( nSwPos );
521 
522         // tdf#38778 Fix output of the font in DOC run for fields
523         const SvxFontItem * pFontToOutput = ( rParentFont != *pFont )? pFont : nullptr;
524 
525         m_rExport.ExportPoolItemsToCHP( aExportItems, GetScript(), pFontToOutput, bWriteCombChars );
526 
527         // HasTextItem only allowed in the above range
528         m_rExport.m_aCurrentCharPropStarts.pop();
529         m_rExport.m_pOutFormatNode = pOldMod;
530     }
531 
532     if( rNd.GetpSwpHints() == nullptr )
533         m_rExport.SetCurItemSet(nullptr);
534 
535     OSL_ENSURE( pFont, "must be *some* font associated with this txtnode" );
536     if ( pFont )
537     {
538         SvxFontItem aFont( *pFont );
539 
540         if ( rParentFont != aFont )
541             m_rExport.AttrOutput().OutputItem( aFont );
542     }
543 
544     // Output grab bag attributes
545     if (pGrabBag)
546         m_rExport.AttrOutput().OutputItem( *pGrabBag );
547 }
548 
549 // Toggle Properties
550 //
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:
553 //
554 //     value_{effective} = val_{table} XOR val_{paragraph} XOR val_{character}
555 //
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.
560 //
561 // To prevent such logic inside output, it is required to write inline w:b token on content level.
handleToggleProperty(SfxItemSet & rExportSet,const SwFormatCharFormat * pCharFormatItem,sal_uInt16 nWhich,const SfxPoolItem * pValue)562 void SwWW8AttrIter::handleToggleProperty(SfxItemSet& rExportSet, const SwFormatCharFormat* pCharFormatItem,
563     sal_uInt16 nWhich, const SfxPoolItem* pValue)
564 {
565     if (!rExportSet.HasItem(nWhich) && pValue)
566     {
567         bool hasPropertyInCharStyle = false;
568         bool hasPropertyInParaStyle = false;
569 
570         // get bold flag from specified character style
571         if (pCharFormatItem)
572         {
573             if (const SwCharFormat* pCharFormat = pCharFormatItem->GetCharFormat())
574             {
575                 const SfxPoolItem* pItem = nullptr;
576                 if (pCharFormat->GetAttrSet().HasItem(nWhich, &pItem))
577                 {
578                     hasPropertyInCharStyle = (*pItem == *pValue);
579                 }
580             }
581         }
582 
583         // get bold flag from specified paragraph style
584         {
585             SwTextFormatColl& rTextColl = static_cast<SwTextFormatColl&>( rNd.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)
590             {
591                 const SfxPoolItem* pItem = nullptr;
592                 if (pFormat->GetAttrSet().HasItem(nWhich, &pItem))
593                 {
594                     hasPropertyInParaStyle = (*pItem == *pValue);
595                 }
596             }
597         }
598 
599         // add inline property
600         if (hasPropertyInCharStyle && hasPropertyInParaStyle)
601         {
602             rExportSet.Put(*pValue);
603         }
604     }
605 }
606 
IsWatermarkFrame()607 bool SwWW8AttrIter::IsWatermarkFrame()
608 {
609     if (maFlyFrames.size() != 1)
610         return false;
611 
612     while ( maFlyIter != maFlyFrames.end() )
613     {
614         const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
615 
616         if (pSdrObj)
617         {
618             if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
619                   return true;
620         }
621         ++maFlyIter;
622     }
623 
624     return false;
625 }
626 
IsAnchorLinkedToThisNode(sal_uLong nNodePos)627 bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
628 {
629     ww8::FrameIter aTmpFlyIter = maFlyIter ;
630 
631     while ( aTmpFlyIter != maFlyFrames.end() )
632     {
633         const SwPosition &rAnchor  = maFlyIter->GetPosition();
634         sal_uLong nAnchorPos = rAnchor.nNode.GetIndex();
635         /* if current node position and the anchor position are the same
636            then the frame anchor is linked to this node
637         */
638         if ( nAnchorPos == nNodePos )
639             return true ;
640 
641         ++aTmpFlyIter;
642     }
643     return false ;
644 }
645 
HasFlysAt(sal_Int32 nSwPos) const646 bool SwWW8AttrIter::HasFlysAt(sal_Int32 nSwPos) const
647 {
648     for (const auto& rFly : maFlyFrames)
649     {
650         const SwPosition& rAnchor = rFly.GetPosition();
651         const sal_Int32 nPos = rAnchor.nContent.GetIndex();
652         if (nPos == nSwPos)
653         {
654             return true;
655         }
656     }
657 
658     return false;
659 }
660 
OutFlys(sal_Int32 nSwPos)661 FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
662 {
663     // collection point to first gather info about all of the potentially linked textboxes: to be analyzed later.
664     OUString sLinkChainName;
665     ww8::FrameIter linkedTextboxesIter = maFlyIter;
666     while ( linkedTextboxesIter != maFlyFrames.end() )
667     {
668         uno::Reference< drawing::XShape > xShape;
669         ww8::Frame aFrame = *linkedTextboxesIter;
670         const SdrObject* pSdrObj = aFrame.GetFrameFormat().FindRealSdrObject();
671         if( pSdrObj )
672             xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
673         uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
674         uno::Reference< beans::XPropertySetInfo > xPropertySetInfo;
675         if( xPropertySet.is() )
676             xPropertySetInfo = xPropertySet->getPropertySetInfo();
677         if( xPropertySetInfo.is() )
678         {
679             MSWordExportBase::LinkedTextboxInfo aLinkedTextboxInfo;
680 
681             if( xPropertySetInfo->hasPropertyByName("LinkDisplayName") )
682                 xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
683             else if( xPropertySetInfo->hasPropertyByName("ChainName") )
684                 xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
685 
686             if( xPropertySetInfo->hasPropertyByName("ChainNextName") )
687                 xPropertySet->getPropertyValue("ChainNextName") >>= aLinkedTextboxInfo.sNextChain;
688             if( xPropertySetInfo->hasPropertyByName("ChainPrevName") )
689                 xPropertySet->getPropertyValue("ChainPrevName") >>= aLinkedTextboxInfo.sPrevChain;
690 
691             //collect a list of linked textboxes: those with a NEXT or PREVIOUS link
692             if( !aLinkedTextboxInfo.sNextChain.isEmpty() || !aLinkedTextboxInfo.sPrevChain.isEmpty() )
693             {
694                 assert( !sLinkChainName.isEmpty() );
695 
696                 //there are many discarded duplicates in documents - no duplicates allowed in the list, so try to find the real one.
697                 //if this LinkDisplayName/ChainName already exists on a different shape...
698                 //  the earlier processed duplicates are thrown out unless this one can be proved as bad. (last processed duplicate usually is stored)
699                 auto linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
700                 if( linkFinder != m_rExport.m_aLinkedTextboxesHelper.end() )
701                 {
702                     //If my NEXT/PREV targets have already been discovered, but don't match me, then assume I'm an abandoned remnant
703                     //    (this logic fails if both me and one of my links are duplicated, and the remnants were added first.)
704                     linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sNextChain);
705                     if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sPrevChain != sLinkChainName) )
706                     {
707                         ++linkedTextboxesIter;
708                         break;
709                     }
710 
711                     linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sPrevChain);
712                     if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sNextChain != sLinkChainName) )
713                     {
714                         ++linkedTextboxesIter;
715                         break;
716                     }
717                 }
718                 m_rExport.m_bLinkedTextboxesHelperInitialized = false;
719                 m_rExport.m_aLinkedTextboxesHelper[sLinkChainName] = aLinkedTextboxInfo;
720             }
721         }
722         ++linkedTextboxesIter;
723     }
724 
725     /*
726      #i2916#
727      May have an anchored graphic to be placed, loop through sorted array
728      and output all at this position
729     */
730     while ( maFlyIter != maFlyFrames.end() )
731     {
732         const SwPosition &rAnchor = maFlyIter->GetPosition();
733         const sal_Int32 nPos = rAnchor.nContent.GetIndex();
734 
735         if ( nPos != nSwPos )
736             return FLY_NOT_PROCESSED ; // We haven't processed the fly
737 
738         const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
739 
740         if (pSdrObj)
741         {
742             if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
743             {
744                  // This is a watermark object. Should be written ONLY in the header
745                  if(m_rExport.m_nTextTyp == TXT_HDFT)
746                  {
747                        // Should write a watermark in the header
748                        m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
749                  }
750                  else
751                  {
752                        // Should not write watermark object in the main body text
753                  }
754             }
755             else
756             {
757                  // This is not a watermark object - write normally
758                  m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
759             }
760         }
761         else
762         {
763             // This is not a watermark object - write normally
764             m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
765         }
766         ++maFlyIter;
767     }
768     return ( m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_PROCESSED ) ;
769 }
770 
IsTextAttr(sal_Int32 nSwPos) const771 bool SwWW8AttrIter::IsTextAttr( sal_Int32 nSwPos ) const
772 {
773     // search for attrs with dummy character or content
774     if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
775     {
776         for (size_t i = 0; i < pTextAttrs->Count(); ++i)
777         {
778             const SwTextAttr* pHt = pTextAttrs->Get(i);
779             if (nSwPos == pHt->GetStart())
780             {
781                 if (pHt->HasDummyChar() || pHt->HasContent() )
782                 {
783                     return true;
784                 }
785             }
786             else if (nSwPos < pHt->GetStart())
787             {
788                 break; // sorted by start
789             }
790         }
791     }
792 
793     return false;
794 }
795 
IsExportableAttr(sal_Int32 nSwPos) const796 bool SwWW8AttrIter::IsExportableAttr(sal_Int32 nSwPos) const
797 {
798     if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
799     {
800         for (size_t i = 0; i < pTextAttrs->Count(); ++i)
801         {
802             const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
803             const sal_Int32 nStart = pHt->GetStart();
804             const sal_Int32 nEnd = pHt->End() ? *pHt->End() : INT_MAX;
805             if (nSwPos >= nStart && nSwPos < nEnd)
806             {
807                 switch (pHt->GetAttr().Which())
808                 {
809                     // Metadata fields should be dynamically generated, not dumped as text.
810                 case RES_TXTATR_METAFIELD:
811                     return false;
812                 }
813             }
814         }
815     }
816 
817     return true;
818 }
819 
IsDropCap(int nSwPos)820 bool SwWW8AttrIter::IsDropCap( int nSwPos )
821 {
822     // see if the current position falls on a DropCap
823     int nDropChars = mrSwFormatDrop.GetChars();
824     bool bWholeWord = mrSwFormatDrop.GetWholeWord();
825     if (bWholeWord)
826     {
827         const sal_Int32 nWordLen = rNd.GetDropLen(0);
828         if(nSwPos == nWordLen && nSwPos != 0)
829             return true;
830     }
831     else
832     {
833         if (nSwPos == nDropChars && nSwPos != 0)
834             return true;
835     }
836     return false;
837 }
838 
RequiresImplicitBookmark()839 bool SwWW8AttrIter::RequiresImplicitBookmark()
840 {
841     return std::any_of(m_rExport.m_aImplicitBookmarks.begin(), m_rExport.m_aImplicitBookmarks.end(),
842         [this](const aBookmarkPair& rBookmarkPair) { return rBookmarkPair.second == rNd.GetIndex(); });
843 }
844 
845 //HasItem is for the summary of the double attributes: Underline and WordlineMode as TextItems.
846 // OutAttr () calls the output function, which can call HasItem() for other items at the attribute's start position.
847 // Only attributes with end can be queried.
848 // It searches with bDeep
HasTextItem(sal_uInt16 nWhich) const849 const SfxPoolItem* SwWW8AttrIter::HasTextItem( sal_uInt16 nWhich ) const
850 {
851     const SfxPoolItem* pRet = nullptr;
852     const SwpHints* pTextAttrs = rNd.GetpSwpHints();
853     if (pTextAttrs && !m_rExport.m_aCurrentCharPropStarts.empty())
854     {
855         const sal_Int32 nTmpSwPos = m_rExport.m_aCurrentCharPropStarts.top();
856         for (size_t i = 0; i < pTextAttrs->Count(); ++i)
857         {
858             const SwTextAttr* pHt = pTextAttrs->Get(i);
859             const SfxPoolItem* pItem = &pHt->GetAttr();
860             const sal_Int32 * pAtrEnd = nullptr;
861             if( nullptr != ( pAtrEnd = pHt->End() ) &&        // only Attr with an end
862                 nTmpSwPos >= pHt->GetStart() && nTmpSwPos < *pAtrEnd )
863             {
864                 if ( nWhich == pItem->Which() )
865                 {
866                     pRet = pItem;       // found it
867                     break;
868                 }
869                 else if( RES_TXTATR_INETFMT == pHt->Which() ||
870                          RES_TXTATR_CHARFMT == pHt->Which() ||
871                          RES_TXTATR_AUTOFMT == pHt->Which() )
872                 {
873                     const SfxItemSet* pSet = CharFormat::GetItemSet( pHt->GetAttr() );
874                     const SfxPoolItem* pCharItem;
875                     if ( pSet &&
876                          SfxItemState::SET == pSet->GetItemState( nWhich, pHt->Which() != RES_TXTATR_AUTOFMT, &pCharItem ) )
877                     {
878                         pRet = pCharItem;       // found it
879                         break;
880                     }
881                 }
882             }
883             else if (nTmpSwPos < pHt->GetStart())
884                 break;              // nothing more to come
885         }
886     }
887     return pRet;
888 }
889 
GetCurrentItems(ww::bytes & rItems) const890 void WW8Export::GetCurrentItems(ww::bytes &rItems) const
891 {
892     rItems.insert(rItems.end(), pO->begin(), pO->end());
893 }
894 
GetItem(sal_uInt16 nWhich) const895 const SfxPoolItem& SwWW8AttrIter::GetItem(sal_uInt16 nWhich) const
896 {
897     const SfxPoolItem* pRet = HasTextItem(nWhich);
898     return pRet ? *pRet : rNd.SwContentNode::GetAttr(nWhich);
899 }
900 
StartRuby(const SwTextNode & rNode,sal_Int32,const SwFormatRuby & rRuby)901 void WW8AttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 /*nPos*/, const SwFormatRuby& rRuby )
902 {
903     WW8Ruby aWW8Ruby(rNode, rRuby, GetExport());
904     OUString aStr( FieldString( ww::eEQ ) + "\\* jc" );
905     aStr += OUString::number(aWW8Ruby.GetJC()) + " \\* \"Font:";
906     aStr += aWW8Ruby.GetFontFamily() + "\" \\* hps";
907     aStr += OUString::number((aWW8Ruby.GetRubyHeight() + 5) / 10) + " \\o";
908     if (aWW8Ruby.GetDirective())
909     {
910         aStr += OUStringLiteral("\\a") + OUStringChar(aWW8Ruby.GetDirective());
911     }
912     aStr += "(\\s\\up " + OUString::number((aWW8Ruby.GetBaseHeight() + 10) / 20 - 1) + "(";
913     aStr += rRuby.GetText() + ")";
914 
915     // The parameter separator depends on the FIB.lid
916     if ( m_rWW8Export.pFib->getNumDecimalSep() == '.' )
917         aStr += ",";
918     else
919         aStr += ";";
920 
921     m_rWW8Export.OutputField( nullptr, ww::eEQ, aStr,
922             FieldFlags::Start | FieldFlags::CmdStart );
923 }
924 
EndRuby(const SwTextNode &,sal_Int32)925 void WW8AttributeOutput::EndRuby(const SwTextNode& /*rNode*/, sal_Int32 /*nPos*/)
926 {
927     m_rWW8Export.WriteChar( ')' );
928     m_rWW8Export.OutputField( nullptr, ww::eEQ, OUString(), FieldFlags::End | FieldFlags::Close );
929 }
930 
931 /*#i15387# Better ideas welcome*/
TruncateBookmark(OUString & rRet)932 static OUString &TruncateBookmark( OUString &rRet )
933 {
934     if ( rRet.getLength() > 40 )
935         rRet = rRet.copy( 0, 40 );
936     OSL_ENSURE( rRet.getLength() <= 40, "Word cannot have bookmarks longer than 40 chars" );
937     return rRet;
938 }
939 
ConvertURL(const OUString & rUrl,bool bAbsoluteOut)940 OUString AttributeOutputBase::ConvertURL( const OUString& rUrl, bool bAbsoluteOut )
941 {
942     OUString sURL = rUrl;
943 
944     INetURLObject anAbsoluteParent(m_sBaseURL);
945     OUString sConvertedParent = INetURLObject::GetScheme( anAbsoluteParent.GetProtocol() ) + anAbsoluteParent.GetURLPath();
946     OUString sParentPath = sConvertedParent.isEmpty() ? m_sBaseURL : sConvertedParent;
947 
948     if ( bAbsoluteOut )
949     {
950         INetURLObject anAbsoluteNew;
951 
952         if ( anAbsoluteParent.GetNewAbsURL( rUrl, &anAbsoluteNew ) )
953             sURL = anAbsoluteNew.GetMainURL( INetURLObject::DecodeMechanism::NONE );
954     }
955     else
956     {
957         OUString sToConvert = rUrl.replaceAll( "\\", "/" );
958         INetURLObject aURL( sToConvert );
959         sToConvert = INetURLObject::GetScheme( aURL.GetProtocol() ) + aURL.GetURLPath();
960         OUString sRelative = INetURLObject::GetRelURL( sParentPath, sToConvert, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::NONE );
961         if ( !sRelative.isEmpty() )
962             sURL = sRelative;
963     }
964 
965     return sURL;
966 }
967 
AnalyzeURL(const OUString & rUrl,const OUString &,OUString * pLinkURL,OUString * pMark)968 bool AttributeOutputBase::AnalyzeURL( const OUString& rUrl, const OUString& /*rTarget*/, OUString* pLinkURL, OUString* pMark )
969 {
970     bool bBookMarkOnly = false;
971 
972     OUString sMark;
973     OUString sURL;
974 
975     if ( rUrl.getLength() > 1 && rUrl[0] == '#' )
976     {
977         sMark = BookmarkToWriter( rUrl.copy(1) );
978 
979         const sal_Int32 nPos = sMark.lastIndexOf( cMarkSeparator );
980 
981         const OUString sRefType(nPos>=0 && nPos+1<sMark.getLength() ?
982                                 sMark.copy(nPos+1).replaceAll(" ", "") :
983                                 OUString());
984 
985         // #i21465# Only interested in outline references
986         if ( !sRefType.isEmpty() &&
987             (sRefType == "outline" || sRefType == "graphic" || sRefType == "frame" || sRefType == "ole" || sRefType == "region" || sRefType == "table") )
988         {
989             for ( const auto& rBookmarkPair : GetExport().m_aImplicitBookmarks )
990             {
991                 if ( rBookmarkPair.first == sMark )
992                 {
993                     sMark = "_toc" + OUString::number( rBookmarkPair.second );
994                     break;
995                 }
996             }
997         }
998     }
999     else
1000     {
1001         INetURLObject aURL( rUrl, INetProtocol::NotValid );
1002         sURL = aURL.GetURLNoMark( INetURLObject::DecodeMechanism::Unambiguous );
1003         sMark = aURL.GetMark( INetURLObject::DecodeMechanism::Unambiguous );
1004         INetProtocol aProtocol = aURL.GetProtocol();
1005 
1006         if ( aProtocol == INetProtocol::File || aProtocol == INetProtocol::NotValid )
1007         {
1008             // INetProtocol::NotValid - may be a relative link
1009             bool bExportRelative = m_aSaveOpt.IsSaveRelFSys();
1010             sURL = ConvertURL( rUrl, !bExportRelative );
1011         }
1012     }
1013 
1014     if ( !sMark.isEmpty() && sURL.isEmpty() )
1015         bBookMarkOnly = true;
1016 
1017     *pMark = sMark;
1018     *pLinkURL = sURL;
1019     return bBookMarkOnly;
1020 }
1021 
AnalyzeURL(const OUString & rUrl,const OUString & rTarget,OUString * pLinkURL,OUString * pMark)1022 bool WW8AttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
1023 {
1024     bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
1025 
1026     OUString sURL = *pLinkURL;
1027 
1028     if ( !sURL.isEmpty() )
1029         sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
1030 
1031     if ( bBookMarkOnly )
1032         sURL = FieldString( ww::eHYPERLINK );
1033     else
1034         sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
1035 
1036     if ( !pMark->isEmpty() )
1037         sURL += " \\l \"" + *pMark + "\"";
1038 
1039     if ( !rTarget.isEmpty() )
1040         sURL += " \\n " + rTarget;
1041 
1042     *pLinkURL = sURL;
1043 
1044     return bBookMarkOnly;
1045 }
1046 
WriteBookmarkInActParagraph(const OUString & rName,sal_Int32 nFirstRunPos,sal_Int32 nLastRunPos)1047 void WW8AttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
1048 {
1049     m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
1050     m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
1051 }
1052 
StartURL(const OUString & rUrl,const OUString & rTarget)1053 bool WW8AttributeOutput::StartURL( const OUString &rUrl, const OUString &rTarget )
1054 {
1055     INetURLObject aURL( rUrl );
1056     OUString sURL;
1057     OUString sMark;
1058 
1059     bool bBookMarkOnly = AnalyzeURL( rUrl, rTarget, &sURL, &sMark );
1060 
1061     m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::Start | FieldFlags::CmdStart );
1062 
1063     // write the reference to the "picture" structure
1064     sal_uLong nDataStt = m_rWW8Export.pDataStrm->Tell();
1065     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell() );
1066 
1067     // WinWord 2000 doesn't write this - so it's a temp solution by W97 ?
1068     m_rWW8Export.WriteChar( 0x01 );
1069 
1070     static sal_uInt8 aArr1[] = {
1071         0x03, 0x6a, 0,0,0,0,    // sprmCPicLocation
1072 
1073         0x06, 0x08, 0x01,       // sprmCFData
1074         0x55, 0x08, 0x01,       // sprmCFSpec
1075         0x02, 0x08, 0x01        // sprmCFFieldVanish
1076     };
1077     sal_uInt8* pDataAdr = aArr1 + 2;
1078     Set_UInt32( pDataAdr, nDataStt );
1079 
1080     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), sizeof( aArr1 ), aArr1 );
1081 
1082     m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::CmdEnd );
1083 
1084     // now write the picture structure
1085     sURL = aURL.GetURLNoMark();
1086 
1087     // Compare the URL written by AnalyzeURL with the original one to see if
1088     // the output URL is absolute or relative.
1089     OUString sRelativeURL;
1090     if ( !rUrl.isEmpty() )
1091         sRelativeURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), rUrl );
1092     bool bAbsolute = sRelativeURL == rUrl;
1093 
1094     static sal_uInt8 aURLData1[] = {
1095         0,0,0,0,        // len of struct
1096         0x44,0,         // the start of "next" data
1097         0,0,0,0,0,0,0,0,0,0,                // PIC-Structure!
1098         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
1099         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
1100         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,    //  |
1101         0,0,0,0,                            // /
1102     };
1103     static sal_uInt8 MAGIC_A[] = {
1104         // start of "next" data
1105         0xD0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
1106         0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
1107     };
1108 
1109     m_rWW8Export.pDataStrm->WriteBytes(aURLData1, sizeof(aURLData1));
1110     /* Write HFD Structure */
1111     sal_uInt8 nAnchor = 0x00;
1112     if ( !sMark.isEmpty() )
1113         nAnchor = 0x08;
1114     m_rWW8Export.pDataStrm->WriteUChar(nAnchor); // HFDBits
1115     m_rWW8Export.pDataStrm->WriteBytes(MAGIC_A, sizeof(MAGIC_A)); //clsid
1116 
1117     /* Write Hyperlink Object see [MS-OSHARED] spec*/
1118     SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 0x00000002);
1119     sal_uInt32 nFlag = bBookMarkOnly ? 0 : 0x01;
1120     if ( bAbsolute )
1121         nFlag |= 0x02;
1122     if ( !sMark.isEmpty() )
1123         nFlag |= 0x08;
1124     SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nFlag );
1125 
1126     INetProtocol eProto = aURL.GetProtocol();
1127     if ( eProto == INetProtocol::File || eProto == INetProtocol::Smb )
1128     {
1129         // version 1 (for a document)
1130 
1131         static sal_uInt8 MAGIC_C[] = {
1132             0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1133             0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
1134             0x00, 0x00
1135         };
1136 
1137         static sal_uInt8 MAGIC_D[] = {
1138             0xFF, 0xFF, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00,
1139             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1140             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1141         };
1142 
1143         // save the links to files as relative
1144         sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
1145         if ( eProto == INetProtocol::File && sURL.startsWith( "/" ) )
1146             sURL = aURL.PathToFileName();
1147 
1148         // special case for the absolute windows names
1149         // (convert '/c:/foo/bar.doc' into 'c:\foo\bar.doc')
1150         if (sURL.getLength()>=3)
1151         {
1152             const sal_Unicode aDrive = sURL[1];
1153             if ( sURL[0]=='/' && sURL[2]==':' &&
1154                  ( (aDrive>='A' && aDrive<='Z' ) || (aDrive>='a' && aDrive<='z') ) )
1155             {
1156                 sURL = sURL.copy(1).replaceAll("/", "\\");
1157             }
1158         }
1159 
1160         // n#261623 convert smb notation to '\\'
1161         const char pSmb[] = "smb://";
1162         if ( eProto == INetProtocol::Smb && sURL.startsWith( pSmb ) )
1163         {
1164             sURL = sURL.copy( sizeof(pSmb)-3 ).replaceAll( "/", "\\" );
1165         }
1166 
1167         m_rWW8Export.pDataStrm->WriteBytes(MAGIC_C, sizeof(MAGIC_C));
1168         SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sURL.getLength()+1 );
1169         SwWW8Writer::WriteString8( *m_rWW8Export.pDataStrm, sURL, true,
1170                                     RTL_TEXTENCODING_MS_1252 );
1171         m_rWW8Export.pDataStrm->WriteBytes(MAGIC_D, sizeof(MAGIC_D));
1172 
1173         SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() + 6 );
1174         SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() );
1175         SwWW8Writer::WriteShort( *m_rWW8Export.pDataStrm, 3 );
1176         SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, false );
1177     }
1178     else if ( eProto != INetProtocol::NotValid )
1179     {
1180         // version 2 (simple url)
1181         // and write some data to the data stream, but don't ask
1182         // what the data mean, except for the URL.
1183         // The First piece is the WW8_PIC structure.
1184         static sal_uInt8 MAGIC_B[] = {
1185             0xE0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
1186             0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
1187         };
1188 
1189         m_rWW8Export.pDataStrm->WriteBytes(MAGIC_B, sizeof(MAGIC_B));
1190         SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2 * ( sURL.getLength() + 1 ) );
1191         SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, true );
1192     }
1193 
1194     if ( !sMark.isEmpty() )
1195     {
1196         SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sMark.getLength()+1 );
1197         SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sMark, true );
1198     }
1199     SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nDataStt,
1200         m_rWW8Export.pDataStrm->Tell() - nDataStt );
1201 
1202     return true;
1203 }
1204 
EndURL(bool const)1205 bool WW8AttributeOutput::EndURL(bool const)
1206 {
1207     m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, OUString(), FieldFlags::Close );
1208 
1209     return true;
1210 }
1211 
BookmarkToWord(const OUString & rBookmark)1212 OUString BookmarkToWord(const OUString &rBookmark)
1213 {
1214     OUString sRet(INetURLObject::encode(
1215         rBookmark.replace(' ', '_'), // Spaces are prohibited in bookmark name
1216         INetURLObject::PART_REL_SEGMENT_EXTRA,
1217         INetURLObject::EncodeMechanism::All, RTL_TEXTENCODING_ASCII_US));
1218     // Unicode letters are allowed
1219     sRet = INetURLObject::decode(sRet, INetURLObject::DecodeMechanism::Unambiguous, RTL_TEXTENCODING_UTF8);
1220     return TruncateBookmark(sRet);
1221 }
1222 
BookmarkToWriter(const OUString & rBookmark)1223 OUString BookmarkToWriter(const OUString &rBookmark)
1224 {
1225     return INetURLObject::decode(rBookmark,
1226         INetURLObject::DecodeMechanism::Unambiguous, RTL_TEXTENCODING_ASCII_US);
1227 }
1228 
OutSwFormatRefMark(const SwFormatRefMark & rAttr)1229 void SwWW8AttrIter::OutSwFormatRefMark(const SwFormatRefMark& rAttr)
1230 {
1231     if ( m_rExport.HasRefToObject( REF_SETREFATTR, &rAttr.GetRefName(), 0 ) )
1232         m_rExport.AppendBookmark( MSWordExportBase::GetBookmarkName( REF_SETREFATTR,
1233                                             &rAttr.GetRefName(), 0 ));
1234 }
1235 
SplitRun(sal_Int32 nSplitEndPos)1236 void SwWW8AttrIter::SplitRun( sal_Int32 nSplitEndPos )
1237 {
1238     auto aIter = std::find_if(maCharRuns.begin(), maCharRuns.end(),
1239         [nSplitEndPos](const CharRunEntry& rCharRun) { return rCharRun.mnEndPos >= nSplitEndPos; });
1240     if (aIter == maCharRuns.end() || aIter->mnEndPos == nSplitEndPos)
1241         return;
1242 
1243     CharRunEntry aNewEntry = *aIter;
1244     aIter->mnEndPos = nSplitEndPos;
1245     maCharRuns.insert( ++aIter, aNewEntry);
1246     maCharRunIter = maCharRuns.begin();
1247     IterToCurrent();
1248     nCurrentSwPos = SearchNext(1);
1249 }
1250 
FieldVanish(const OUString & rText,ww::eField)1251 void WW8AttributeOutput::FieldVanish( const OUString& rText, ww::eField /*eType*/ )
1252 {
1253     ww::bytes aItems;
1254     m_rWW8Export.GetCurrentItems( aItems );
1255 
1256     // sprmCFFieldVanish
1257     SwWW8Writer::InsUInt16( aItems, NS_sprm::sprmCFFldVanish );
1258     aItems.push_back( 1 );
1259 
1260     sal_uInt16 nStt_sprmCFSpec = aItems.size();
1261 
1262     // sprmCFSpec --  fSpec-Attribut true
1263     SwWW8Writer::InsUInt16( aItems, 0x855 );
1264     aItems.push_back( 1 );
1265 
1266     m_rWW8Export.WriteChar( '\x13' );
1267     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
1268                                     aItems.data() );
1269     m_rWW8Export.OutSwString(rText, 0, rText.getLength());
1270     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), nStt_sprmCFSpec,
1271                                     aItems.data() );
1272     m_rWW8Export.WriteChar( '\x15' );
1273     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
1274                                     aItems.data() );
1275 }
1276 
TOXMark(const SwTextNode & rNode,const SwTOXMark & rAttr)1277 void AttributeOutputBase::TOXMark( const SwTextNode& rNode, const SwTOXMark& rAttr )
1278 {
1279     // it's a field; so get the Text from the Node and build the field
1280     OUString sText;
1281     ww::eField eType = ww::eNONE;
1282 
1283     const SwTextTOXMark& rTextTOXMark = *rAttr.GetTextTOXMark();
1284     const sal_Int32* pTextEnd = rTextTOXMark.End();
1285     if ( pTextEnd ) // has range?
1286     {
1287         sText = rNode.GetExpandText(nullptr, rTextTOXMark.GetStart(),
1288                                    *pTextEnd - rTextTOXMark.GetStart() );
1289     }
1290     else
1291         sText = rAttr.GetAlternativeText();
1292 
1293     switch ( rAttr.GetTOXType()->GetType() )
1294     {
1295         case TOX_INDEX:
1296             eType = ww::eXE;
1297             if ( !rAttr.GetPrimaryKey().isEmpty() )
1298             {
1299                 if ( !rAttr.GetSecondaryKey().isEmpty() )
1300                 {
1301                     sText = rAttr.GetSecondaryKey() + ":" + sText;
1302                 }
1303 
1304                 sText = rAttr.GetPrimaryKey() + ":" + sText;
1305             }
1306             sText = " XE \"" + sText + "\" ";
1307             break;
1308 
1309         case TOX_USER:
1310             sText += "\" \\f \"" + OUStringChar(static_cast<sal_Char>( 'A' + GetExport( ).GetId( *rAttr.GetTOXType() ) ));
1311             [[fallthrough]];
1312         case TOX_CONTENT:
1313             {
1314                 eType = ww::eTC;
1315                 sText = " TC \"" + sText;
1316                 sal_uInt16 nLvl = rAttr.GetLevel();
1317                 if (nLvl > WW8ListManager::nMaxLevel)
1318                     nLvl = WW8ListManager::nMaxLevel;
1319 
1320                 sText += "\" \\l " + OUString::number(nLvl) + " ";
1321             }
1322             break;
1323         default:
1324             OSL_ENSURE( false, "Unhandled option for toc export" );
1325             break;
1326     }
1327 
1328     if (!sText.isEmpty())
1329         FieldVanish( sText, eType );
1330 }
1331 
OutAttrWithRange(const SwTextNode & rNode,sal_Int32 nPos)1332 int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos)
1333 {
1334     int nRet = 0;
1335     if ( const SwpHints* pTextAttrs = rNd.GetpSwpHints() )
1336     {
1337         m_rExport.m_aCurrentCharPropStarts.push( nPos );
1338         const sal_Int32* pEnd;
1339         // first process ends of attributes with extent
1340         for (size_t i = 0; i < pTextAttrs->Count(); ++i)
1341         {
1342             const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
1343             const SfxPoolItem* pItem = &pHt->GetAttr();
1344             switch ( pItem->Which() )
1345             {
1346                 case RES_TXTATR_INETFMT:
1347                     pEnd = pHt->End();
1348                     if (nPos == *pEnd && nPos != pHt->GetStart())
1349                     {
1350                         if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
1351                             --nRet;
1352                     }
1353                     break;
1354                 case RES_TXTATR_REFMARK:
1355                     pEnd = pHt->End();
1356                     if (nullptr != pEnd && nPos == *pEnd && nPos != pHt->GetStart())
1357                     {
1358                         OutSwFormatRefMark(*static_cast<const SwFormatRefMark*>(pItem));
1359                         --nRet;
1360                     }
1361                     break;
1362                 case RES_TXTATR_CJK_RUBY:
1363                     pEnd = pHt->End();
1364                     if (nPos == *pEnd && nPos != pHt->GetStart())
1365                     {
1366                         m_rExport.AttrOutput().EndRuby(rNode, nPos);
1367                         --nRet;
1368                     }
1369                     break;
1370             }
1371             if (nPos < pHt->GetAnyEnd())
1372                 break; // sorted by end
1373         }
1374         for ( size_t i = 0; i < pTextAttrs->Count(); ++i )
1375         {
1376             const SwTextAttr* pHt = pTextAttrs->Get(i);
1377             const SfxPoolItem* pItem = &pHt->GetAttr();
1378             switch ( pItem->Which() )
1379             {
1380                 case RES_TXTATR_INETFMT:
1381                     if ( nPos == pHt->GetStart() )
1382                     {
1383                         const SwFormatINetFormat *rINet = static_cast< const SwFormatINetFormat* >( pItem );
1384                         if ( m_rExport.AttrOutput().StartURL( rINet->GetValue(), rINet->GetTargetFrame() ) )
1385                             ++nRet;
1386                     }
1387                     pEnd = pHt->End();
1388                     if (nPos == *pEnd && nPos == pHt->GetStart())
1389                     {   // special case: empty must be handled here
1390                         if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
1391                             --nRet;
1392                     }
1393                     break;
1394                 case RES_TXTATR_REFMARK:
1395                     if ( nPos == pHt->GetStart() )
1396                     {
1397                         OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
1398                         ++nRet;
1399                     }
1400                     pEnd = pHt->End();
1401                     if (nullptr != pEnd && nPos == *pEnd && nPos == pHt->GetStart())
1402                     {   // special case: empty TODO: is this possible or would empty one have pEnd null?
1403                         OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
1404                         --nRet;
1405                     }
1406                     break;
1407                 case RES_TXTATR_TOXMARK:
1408                     if ( nPos == pHt->GetStart() )
1409                         m_rExport.AttrOutput().TOXMark( rNd, *static_cast< const SwTOXMark* >( pItem ) );
1410                     break;
1411                 case RES_TXTATR_CJK_RUBY:
1412                     if ( nPos == pHt->GetStart() )
1413                     {
1414                         m_rExport.AttrOutput().StartRuby( rNd, nPos, *static_cast< const SwFormatRuby* >( pItem ) );
1415                         ++nRet;
1416                     }
1417                     pEnd = pHt->End();
1418                     if (nPos == *pEnd && nPos == pHt->GetStart())
1419                     {   // special case: empty must be handled here
1420                         m_rExport.AttrOutput().EndRuby( rNd, nPos );
1421                         --nRet;
1422                     }
1423                     break;
1424             }
1425             if (nPos < pHt->GetStart())
1426                 break; // sorted by start
1427         }
1428         m_rExport.m_aCurrentCharPropStarts.pop(); // HasTextItem only allowed in the above range
1429     }
1430     return nRet;
1431 }
1432 
IncludeEndOfParaCRInRedlineProperties(sal_Int32 nEnd) const1433 bool SwWW8AttrIter::IncludeEndOfParaCRInRedlineProperties( sal_Int32 nEnd ) const
1434 {
1435     // search next Redline
1436     for( SwRedlineTable::size_type nPos = nCurRedlinePos;
1437         nPos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nPos )
1438     {
1439         const SwRangeRedline *pRange = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[nPos];
1440         const SwPosition* pEnd = pRange->End();
1441         const SwPosition* pStart = pRange->Start();
1442         bool bBreak = true;
1443         // In word the paragraph end marker is a real character, in writer it is not.
1444         // Here we find out if the para end marker we will emit is affected by
1445         // redlining, in which case it must be included by the range of character
1446         // attributes that contains the redlining information.
1447         if (pEnd->nNode == rNd)
1448         {
1449             if (pEnd->nContent.GetIndex() == nEnd)
1450             {
1451                 // This condition detects if the pseudo-char we will export
1452                 // should be explicitly included by the redlining char
1453                 // properties on this node because the redlining ends right
1454                 // after it
1455                 return true;
1456             }
1457             bBreak = false;
1458         }
1459         if (pStart->nNode == rNd)
1460         {
1461             if (pStart->nContent.GetIndex() == nEnd)
1462             {
1463                 // This condition detects if the pseudo-char we will export
1464                 // should be explicitly included by the redlining char
1465                 // properties on this node because the redlining starts right
1466                 // before it
1467                 return true;
1468             }
1469             bBreak = false;
1470         }
1471         if (pStart->nNode.GetIndex()-1 == rNd.GetIndex())
1472         {
1473             if (pStart->nContent.GetIndex() == 0)
1474             {
1475                 // This condition detects if the pseudo-char we will export
1476                 // should be implicitly excluded by the redlining char
1477                 // properties starting on the next node.
1478                 return true;
1479             }
1480             bBreak = false;
1481         }
1482 
1483         if (bBreak)
1484             break;
1485     }
1486     return false;
1487 }
1488 
GetParagraphLevelRedline()1489 const SwRedlineData* SwWW8AttrIter::GetParagraphLevelRedline( )
1490 {
1491     pCurRedline = nullptr;
1492 
1493     // ToDo : this is not the most ideal ... should start maybe from 'nCurRedlinePos'
1494     for(SwRangeRedline* pRedl : m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable())
1495     {
1496         const SwPosition* pCheckedStt = pRedl->Start();
1497 
1498         if( pCheckedStt->nNode == rNd )
1499         {
1500             // Maybe add here a check that also the start & end of the redline is the entire paragraph
1501 
1502             // Only return if this is a paragraph formatting redline
1503             if (pRedl->GetType() == RedlineType::ParagraphFormat)
1504             {
1505                 // write data of this redline
1506                 pCurRedline = pRedl;
1507                 return &( pCurRedline->GetRedlineData() );
1508             }
1509         }
1510     }
1511     return nullptr;
1512 }
1513 
GetRunLevelRedline(sal_Int32 nPos)1514 const SwRedlineData* SwWW8AttrIter::GetRunLevelRedline( sal_Int32 nPos )
1515 {
1516     if( pCurRedline )
1517     {
1518         const SwPosition* pEnd = pCurRedline->End();
1519         if (!(pEnd->nNode == rNd && pEnd->nContent.GetIndex() <= nPos))
1520         {
1521             switch( pCurRedline->GetType() )
1522             {
1523                 case RedlineType::Insert:
1524                 case RedlineType::Delete:
1525                 case RedlineType::Format:
1526                     // write data of this redline
1527                     return &( pCurRedline->GetRedlineData() );
1528                     break;
1529                 default:
1530                     break;
1531             }
1532         }
1533         pCurRedline = nullptr;
1534         ++nCurRedlinePos;
1535     }
1536 
1537     assert(!pCurRedline);
1538     // search next Redline
1539     for( ; nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
1540             ++nCurRedlinePos )
1541     {
1542         const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nCurRedlinePos ];
1543 
1544         const SwPosition* pStt = pRedl->Start();
1545         const SwPosition* pEnd = pStt == pRedl->GetPoint()
1546                                     ? pRedl->GetMark()
1547                                     : pRedl->GetPoint();
1548 
1549         if( pStt->nNode == rNd )
1550         {
1551             if( pStt->nContent.GetIndex() >= nPos )
1552             {
1553                 if( pStt->nContent.GetIndex() == nPos )
1554                 {
1555                         switch( pRedl->GetType() )
1556                         {
1557                             case RedlineType::Insert:
1558                             case RedlineType::Delete:
1559                             case RedlineType::Format:
1560                                 // write data of this redline
1561                                 pCurRedline = pRedl;
1562                                 return &( pCurRedline->GetRedlineData() );
1563                                 break;
1564                             default:
1565                                 break;
1566                         }
1567                 }
1568                 break;
1569             }
1570         }
1571         else
1572         {
1573             break;
1574         }
1575 
1576         if( pEnd->nNode == rNd &&
1577             pEnd->nContent.GetIndex() < nPos )
1578         {
1579             pCurRedline = pRedl;
1580             break;
1581         }
1582     }
1583     return nullptr;
1584 }
1585 
GetCurrentPageDirection() const1586 SvxFrameDirection MSWordExportBase::GetCurrentPageDirection() const
1587 {
1588     const SwFrameFormat &rFormat = m_pCurrentPageDesc
1589                     ? m_pCurrentPageDesc->GetMaster()
1590                     : m_pDoc->GetPageDesc( 0 ).GetMaster();
1591     return rFormat.GetFrameDir().GetValue();
1592 }
1593 
GetDefaultFrameDirection() const1594 SvxFrameDirection MSWordExportBase::GetDefaultFrameDirection( ) const
1595 {
1596     SvxFrameDirection nDir = SvxFrameDirection::Environment;
1597 
1598     if ( m_bOutPageDescs )
1599         nDir = GetCurrentPageDirection(  );
1600     else if ( m_pOutFormatNode )
1601     {
1602         if ( m_bOutFlyFrameAttrs ) //frame
1603         {
1604             nDir = TrueFrameDirection( *static_cast< const SwFrameFormat * >(m_pOutFormatNode) );
1605         }
1606         else if ( dynamic_cast< const SwContentNode *>( m_pOutFormatNode ) !=  nullptr )    //paragraph
1607         {
1608             const SwContentNode *pNd = static_cast<const SwContentNode *>(m_pOutFormatNode);
1609             SwPosition aPos( *pNd );
1610             nDir = m_pDoc->GetTextDirection( aPos );
1611         }
1612         else if ( dynamic_cast< const SwTextFormatColl *>( m_pOutFormatNode ) !=  nullptr )
1613         {
1614             if ( MsLangId::isRightToLeft( GetAppLanguage()) )
1615                 nDir = SvxFrameDirection::Horizontal_RL_TB;
1616             else
1617                 nDir = SvxFrameDirection::Horizontal_LR_TB;    //what else can we do :-(
1618         }
1619     }
1620 
1621     if ( nDir == SvxFrameDirection::Environment )
1622     {
1623         // fdo#44029 put direction right when the locale are RTL.
1624         if( MsLangId::isRightToLeft( GetAppLanguage()) )
1625             nDir = SvxFrameDirection::Horizontal_RL_TB;
1626         else
1627             nDir = SvxFrameDirection::Horizontal_LR_TB;        //Set something
1628     }
1629 
1630     return nDir;
1631 }
1632 
TrueFrameDirection(const SwFrameFormat & rFlyFormat) const1633 SvxFrameDirection MSWordExportBase::TrueFrameDirection( const SwFrameFormat &rFlyFormat ) const
1634 {
1635     const SwFrameFormat *pFlyFormat = &rFlyFormat;
1636     const SvxFrameDirectionItem* pItem = nullptr;
1637     while ( pFlyFormat )
1638     {
1639         pItem = &pFlyFormat->GetFrameDir();
1640         if ( SvxFrameDirection::Environment == pItem->GetValue() )
1641         {
1642             pItem = nullptr;
1643             const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
1644             if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
1645                 pAnchor->GetContentAnchor() )
1646             {
1647                 pFlyFormat = pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
1648             }
1649             else
1650                 pFlyFormat = nullptr;
1651         }
1652         else
1653             pFlyFormat = nullptr;
1654     }
1655 
1656     SvxFrameDirection nRet;
1657     if ( pItem )
1658         nRet = pItem->GetValue();
1659     else
1660         nRet = GetCurrentPageDirection();
1661 
1662     OSL_ENSURE( nRet != SvxFrameDirection::Environment, "leaving with environment direction" );
1663     return nRet;
1664 }
1665 
GetCurrentPageBgBrush() const1666 const SvxBrushItem* WW8Export::GetCurrentPageBgBrush() const
1667 {
1668     const SwFrameFormat  &rFormat = m_pCurrentPageDesc
1669                     ? m_pCurrentPageDesc->GetMaster()
1670                     : m_pDoc->GetPageDesc(0).GetMaster();
1671 
1672     const SfxPoolItem* pItem = nullptr;
1673     //If not set, or "no fill", get real bg
1674     SfxItemState eState = rFormat.GetItemState(RES_BACKGROUND, true, &pItem);
1675 
1676     const SvxBrushItem* pRet = static_cast<const SvxBrushItem*>(pItem);
1677     if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
1678         pRet->GetColor() == COL_TRANSPARENT))
1679     {
1680         pRet = &(DefaultItemGet<SvxBrushItem>(*m_pDoc,RES_BACKGROUND));
1681     }
1682     return pRet;
1683 }
1684 
TrueFrameBgBrush(const SwFrameFormat & rFlyFormat) const1685 std::shared_ptr<SvxBrushItem> WW8Export::TrueFrameBgBrush(const SwFrameFormat &rFlyFormat) const
1686 {
1687     const SwFrameFormat *pFlyFormat = &rFlyFormat;
1688     const SvxBrushItem* pRet = nullptr;
1689 
1690     while (pFlyFormat)
1691     {
1692         //If not set, or "no fill", get real bg
1693         const SfxPoolItem* pItem = nullptr;
1694         SfxItemState eState =
1695             pFlyFormat->GetItemState(RES_BACKGROUND, true, &pItem);
1696         pRet = static_cast<const SvxBrushItem*>(pItem);
1697         if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
1698             pRet->GetColor() == COL_TRANSPARENT))
1699         {
1700             pRet = nullptr;
1701             const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
1702             if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
1703                 pAnchor->GetContentAnchor())
1704             {
1705                 pFlyFormat =
1706                     pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
1707             }
1708             else
1709                 pFlyFormat = nullptr;
1710         }
1711         else
1712             pFlyFormat = nullptr;
1713     }
1714 
1715     if (!pRet)
1716         pRet = GetCurrentPageBgBrush();
1717 
1718     const Color aTmpColor( COL_WHITE );
1719     std::shared_ptr<SvxBrushItem> aRet(std::make_shared<SvxBrushItem>(aTmpColor, RES_BACKGROUND));
1720 
1721     if (pRet && (pRet->GetGraphic() ||( pRet->GetColor() != COL_TRANSPARENT)))
1722     {
1723         aRet.reset(static_cast<SvxBrushItem*>(pRet->Clone()));
1724     }
1725 
1726     return aRet;
1727 }
1728 
1729 /*
1730 Convert characters that need to be converted, the basic replacements and the
1731 ridiculously complicated title case attribute mapping to hardcoded upper case
1732 because word doesn't have the feature
1733 */
GetSnippet(const OUString & rStr,sal_Int32 nCurrentPos,sal_Int32 nLen) const1734 OUString SwWW8AttrIter::GetSnippet(const OUString &rStr, sal_Int32 nCurrentPos,
1735     sal_Int32 nLen) const
1736 {
1737     if (!nLen)
1738         return OUString();
1739 
1740     OUString aSnippet(rStr.copy(nCurrentPos, nLen));
1741     // 0x0a     ( Hard Line Break ) -> 0x0b
1742     // 0xad     ( soft hyphen )     -> 0x1f
1743     // 0x2011   ( hard hyphen )     -> 0x1e
1744     aSnippet = aSnippet.replace(0x0A, 0x0B);
1745     aSnippet = aSnippet.replace(CHAR_HARDHYPHEN, 0x1e);
1746     aSnippet = aSnippet.replace(CHAR_SOFTHYPHEN, 0x1f);
1747 
1748     m_rExport.m_aCurrentCharPropStarts.push( nCurrentPos );
1749     const SfxPoolItem &rItem = GetItem(RES_CHRATR_CASEMAP);
1750 
1751     if (SvxCaseMap::Capitalize == static_cast<const SvxCaseMapItem&>(rItem).GetValue())
1752     {
1753         assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1754         sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType(aSnippet, 0);
1755 
1756         LanguageType nLanguage;
1757         switch (nScriptType)
1758         {
1759         case i18n::ScriptType::ASIAN:
1760                 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CJK_LANGUAGE)).GetLanguage();
1761                 break;
1762         case i18n::ScriptType::COMPLEX:
1763                 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CTL_LANGUAGE)).GetLanguage();
1764                 break;
1765         case i18n::ScriptType::LATIN:
1766             default:
1767                 nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_LANGUAGE)).GetLanguage();
1768                 break;
1769         }
1770 
1771         SvxFont aFontHelper;
1772         aFontHelper.SetCaseMap(SvxCaseMap::Capitalize);
1773         aFontHelper.SetLanguage(nLanguage);
1774         aSnippet = aFontHelper.CalcCaseMap(aSnippet);
1775 
1776         //If we weren't at the begin of a word undo the case change.
1777         //not done before doing the casemap because the sequence might start
1778         //with whitespace
1779         if (!g_pBreakIt->GetBreakIter()->isBeginWord(
1780             rStr, nCurrentPos, g_pBreakIt->GetLocale(nLanguage),
1781             i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
1782         {
1783             aSnippet = OUStringChar(rStr[nCurrentPos]) + aSnippet.copy(1);
1784         }
1785     }
1786     m_rExport.m_aCurrentCharPropStarts.pop();
1787 
1788     return aSnippet;
1789 }
1790 
1791 /** Delivers the right paragraph style
1792 
1793     Because of the different style handling for delete operations,
1794     the track changes have to be analysed. A deletion, starting in paragraph A
1795     with style A, ending in paragraph B with style B, needs a hack.
1796 */
lcl_getFormatCollection(MSWordExportBase & rExport,const SwTextNode * pTextNode)1797 static SwTextFormatColl& lcl_getFormatCollection( MSWordExportBase& rExport, const SwTextNode* pTextNode )
1798 {
1799     SwRedlineTable::size_type nPos = 0;
1800     SwRedlineTable::size_type nMax = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
1801     while( nPos < nMax )
1802     {
1803         const SwRangeRedline* pRedl = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nPos++ ];
1804         const SwPosition* pStt = pRedl->Start();
1805         const SwPosition* pEnd = pStt == pRedl->GetPoint()
1806                                     ? pRedl->GetMark()
1807                                     : pRedl->GetPoint();
1808         // Looking for deletions, which ends in current pTextNode
1809         if( RedlineType::Delete == pRedl->GetRedlineData().GetType() &&
1810             pEnd->nNode == *pTextNode && pStt->nNode != *pTextNode &&
1811             pStt->nNode.GetNode().IsTextNode() )
1812         {
1813             pTextNode = pStt->nNode.GetNode().GetTextNode();
1814             nMax = nPos;
1815             nPos = 0;
1816         }
1817     }
1818     return static_cast<SwTextFormatColl&>( pTextNode->GetAnyFormatColl() );
1819 }
1820 
FormatDrop(const SwTextNode & rNode,const SwFormatDrop & rSwFormatDrop,sal_uInt16 nStyle,ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo,ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner)1821 void WW8AttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop &rSwFormatDrop, sal_uInt16 nStyle,
1822         ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
1823 {
1824     short nDropLines = rSwFormatDrop.GetLines();
1825     short nDistance = rSwFormatDrop.GetDistance();
1826     int rFontHeight, rDropHeight, rDropDescent;
1827 
1828     SVBT16 nSty;
1829     ShortToSVBT16( nStyle, nSty );
1830     m_rWW8Export.pO->insert( m_rWW8Export.pO->end(), nSty, nSty+2 );     // Style #
1831 
1832     m_rWW8Export.InsUInt16( NS_sprm::sprmPPc );            // Alignment (sprmPPc)
1833     m_rWW8Export.pO->push_back( 0x20 );
1834 
1835     m_rWW8Export.InsUInt16( NS_sprm::sprmPWr );            // Wrapping (sprmPWr)
1836     m_rWW8Export.pO->push_back( 0x02 );
1837 
1838     m_rWW8Export.InsUInt16( NS_sprm::sprmPDcs );            // Dropcap (sprmPDcs)
1839     int nDCS = ( nDropLines << 3 ) | 0x01;
1840     m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( nDCS ) );
1841 
1842     m_rWW8Export.InsUInt16( NS_sprm::sprmPDxaFromText );            // Distance from text (sprmPDxaFromText)
1843     m_rWW8Export.InsUInt16( nDistance );
1844 
1845     if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
1846     {
1847         m_rWW8Export.InsUInt16( NS_sprm::sprmPDyaLine );            // Line spacing
1848         m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -rDropHeight ) );
1849         m_rWW8Export.InsUInt16( 0 );
1850     }
1851 
1852     m_rWW8Export.WriteCR( pTextNodeInfoInner );
1853 
1854     if ( pTextNodeInfo.get() != nullptr )
1855     {
1856 #ifdef DBG_UTIL
1857         SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
1858 #endif
1859         TableInfoCell( pTextNodeInfoInner );
1860     }
1861 
1862     m_rWW8Export.m_pPapPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
1863     m_rWW8Export.pO->clear();
1864 
1865     if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
1866     {
1867         const SwCharFormat *pSwCharFormat = rSwFormatDrop.GetCharFormat();
1868         if ( pSwCharFormat )
1869         {
1870             m_rWW8Export.InsUInt16( NS_sprm::sprmCIstd );
1871             m_rWW8Export.InsUInt16( m_rWW8Export.GetId( pSwCharFormat ) );
1872         }
1873 
1874         m_rWW8Export.InsUInt16( NS_sprm::sprmCHpsPos );            // Lower the chars
1875         m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -((nDropLines - 1)*rDropDescent) / 10 ) );
1876 
1877         m_rWW8Export.InsUInt16( NS_sprm::sprmCHps );            // Font Size
1878         m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( rFontHeight / 10 ) );
1879     }
1880 
1881     m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
1882     m_rWW8Export.pO->clear();
1883 }
1884 
GetNextPos(SwWW8AttrIter const * aAttrIter,const SwTextNode & rNode,sal_Int32 nCurrentPos)1885 sal_Int32 MSWordExportBase::GetNextPos( SwWW8AttrIter const * aAttrIter, const SwTextNode& rNode, sal_Int32 nCurrentPos )
1886 {
1887     // Get the bookmarks for the normal run
1888     const sal_Int32 nNextPos = aAttrIter->WhereNext();
1889     sal_Int32 nNextBookmark = nNextPos;
1890     sal_Int32 nNextAnnotationMark = nNextPos;
1891 
1892     if( nNextBookmark > nCurrentPos ) //no need to search for bookmarks otherwise (checked in UpdatePosition())
1893     {
1894         GetSortedBookmarks( rNode, nCurrentPos, nNextBookmark - nCurrentPos );
1895         NearestBookmark( nNextBookmark, nCurrentPos, false );
1896         GetSortedAnnotationMarks(*aAttrIter, nCurrentPos, nNextAnnotationMark - nCurrentPos);
1897         NearestAnnotationMark( nNextAnnotationMark, nCurrentPos, false );
1898     }
1899     return std::min( nNextPos, std::min( nNextBookmark, nNextAnnotationMark ) );
1900 }
1901 
UpdatePosition(SwWW8AttrIter * aAttrIter,sal_Int32 nCurrentPos)1902 void MSWordExportBase::UpdatePosition( SwWW8AttrIter* aAttrIter, sal_Int32 nCurrentPos )
1903 {
1904     sal_Int32 nNextPos;
1905 
1906     // go to next attribute if no bookmark is found or if the bookmark is after the next attribute position
1907     // It may happened that the WhereNext() wasn't used in the previous increment because there was a
1908     // bookmark before it. Use that position before trying to find another one.
1909     bool bNextBookmark = NearestBookmark( nNextPos, nCurrentPos, true );
1910     if( nCurrentPos == aAttrIter->WhereNext() && ( !bNextBookmark || nNextPos > aAttrIter->WhereNext() ) )
1911         aAttrIter->NextPos();
1912 }
1913 
GetBookmarks(const SwTextNode & rNd,sal_Int32 nStt,sal_Int32 nEnd,IMarkVector & rArr)1914 bool MSWordExportBase::GetBookmarks( const SwTextNode& rNd, sal_Int32 nStt,
1915                     sal_Int32 nEnd, IMarkVector& rArr )
1916 {
1917     IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
1918     sal_uLong nNd = rNd.GetIndex( );
1919 
1920     const sal_Int32 nMarks = pMarkAccess->getAllMarksCount();
1921     for ( sal_Int32 i = 0; i < nMarks; i++ )
1922     {
1923         IMark* pMark = pMarkAccess->getAllMarksBegin()[i];
1924 
1925         switch (IDocumentMarkAccess::GetType( *pMark ))
1926         {
1927             case IDocumentMarkAccess::MarkType::UNO_BOOKMARK:
1928             case IDocumentMarkAccess::MarkType::DDE_BOOKMARK:
1929             case IDocumentMarkAccess::MarkType::ANNOTATIONMARK:
1930             case IDocumentMarkAccess::MarkType::TEXT_FIELDMARK:
1931             case IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK:
1932             case IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK:
1933             case IDocumentMarkAccess::MarkType::DATE_FIELDMARK:
1934             case IDocumentMarkAccess::MarkType::NAVIGATOR_REMINDER:
1935                 continue; // ignore irrelevant marks
1936             case IDocumentMarkAccess::MarkType::BOOKMARK:
1937             case IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK:
1938             case IDocumentMarkAccess::MarkType::CROSSREF_NUMITEM_BOOKMARK:
1939                 break;
1940         }
1941 
1942         // Only keep the bookmarks starting or ending in this node
1943         if ( pMark->GetMarkStart().nNode == nNd ||
1944              pMark->GetMarkEnd().nNode == nNd )
1945         {
1946             const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
1947             const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
1948 
1949             // Keep only the bookmarks starting or ending in the snippet
1950             bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
1951             bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
1952 
1953             if ( bIsStartOk || bIsEndOk )
1954             {
1955                 rArr.push_back( pMark );
1956             }
1957         }
1958     }
1959     return ( !rArr.empty() );
1960 }
1961 
GetAnnotationMarks(const SwWW8AttrIter & rAttrs,sal_Int32 nStt,sal_Int32 nEnd,IMarkVector & rArr)1962 bool MSWordExportBase::GetAnnotationMarks( const SwWW8AttrIter& rAttrs, sal_Int32 nStt,
1963                     sal_Int32 nEnd, IMarkVector& rArr )
1964 {
1965     IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
1966     sal_uLong nNd = rAttrs.GetNode().GetIndex();
1967 
1968     const sal_Int32 nMarks = pMarkAccess->getAnnotationMarksCount();
1969     for ( sal_Int32 i = 0; i < nMarks; i++ )
1970     {
1971         IMark* pMark = pMarkAccess->getAnnotationMarksBegin()[i];
1972 
1973         // Only keep the bookmarks starting or ending in this node
1974         if ( pMark->GetMarkStart().nNode == nNd ||
1975              pMark->GetMarkEnd().nNode == nNd )
1976         {
1977             const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
1978             const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
1979 
1980             // Keep only the bookmarks starting or ending in the snippet
1981             bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
1982             bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
1983 
1984             // Annotation marks always have at least one character: the anchor
1985             // point of the comment field. In this case Word wants only the
1986             // comment field, so ignore the annotation mark itself.
1987             bool bSingleChar = pMark->GetMarkStart().nNode == pMark->GetMarkEnd().nNode && nBStart + 1 == nBEnd;
1988 
1989             if (bSingleChar)
1990             {
1991                 if (rAttrs.HasFlysAt(nBStart))
1992                 {
1993                     // There is content (an at-char anchored frame) between the annotation mark
1994                     // start/end, so still emit range start/end.
1995                     bSingleChar = false;
1996                 }
1997             }
1998 
1999             if ( ( bIsStartOk || bIsEndOk ) && !bSingleChar )
2000             {
2001                 rArr.push_back( pMark );
2002             }
2003         }
2004     }
2005     return ( !rArr.empty() );
2006 }
2007 
2008 class CompareMarksEnd
2009 {
2010 public:
operator ()(const IMark * pOneB,const IMark * pTwoB) const2011     bool operator() ( const IMark * pOneB, const IMark * pTwoB ) const
2012     {
2013         const sal_Int32 nOEnd = pOneB->GetMarkEnd().nContent.GetIndex();
2014         const sal_Int32 nTEnd = pTwoB->GetMarkEnd().nContent.GetIndex();
2015 
2016         return nOEnd < nTEnd;
2017     }
2018 };
2019 
NearestBookmark(sal_Int32 & rNearest,const sal_Int32 nCurrentPos,bool bNextPositionOnly)2020 bool MSWordExportBase::NearestBookmark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
2021 {
2022     bool bHasBookmark = false;
2023 
2024     if ( !m_rSortedBookmarksStart.empty() )
2025     {
2026         IMark* pMarkStart = m_rSortedBookmarksStart.front();
2027         const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
2028         if( !bNextPositionOnly || (nNext > nCurrentPos ))
2029         {
2030             rNearest = nNext;
2031             bHasBookmark = true;
2032         }
2033     }
2034 
2035     if ( !m_rSortedBookmarksEnd.empty() )
2036     {
2037         IMark* pMarkEnd = m_rSortedBookmarksEnd[0];
2038         const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
2039         if( !bNextPositionOnly || nNext > nCurrentPos )
2040         {
2041             if ( !bHasBookmark )
2042                 rNearest = nNext;
2043             else
2044                 rNearest = std::min( rNearest, nNext );
2045             bHasBookmark = true;
2046         }
2047     }
2048 
2049     return bHasBookmark;
2050 }
2051 
NearestAnnotationMark(sal_Int32 & rNearest,const sal_Int32 nCurrentPos,bool bNextPositionOnly)2052 void MSWordExportBase::NearestAnnotationMark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
2053 {
2054     bool bHasAnnotationMark = false;
2055 
2056     if ( !m_rSortedAnnotationMarksStart.empty() )
2057     {
2058         IMark* pMarkStart = m_rSortedAnnotationMarksStart.front();
2059         const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
2060         if( !bNextPositionOnly || (nNext > nCurrentPos ))
2061         {
2062             rNearest = nNext;
2063             bHasAnnotationMark = true;
2064         }
2065     }
2066 
2067     if ( !m_rSortedAnnotationMarksEnd.empty() )
2068     {
2069         IMark* pMarkEnd = m_rSortedAnnotationMarksEnd[0];
2070         const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
2071         if( !bNextPositionOnly || nNext > nCurrentPos )
2072         {
2073             if ( !bHasAnnotationMark )
2074                 rNearest = nNext;
2075             else
2076                 rNearest = std::min( rNearest, nNext );
2077         }
2078     }
2079 }
2080 
GetSortedAnnotationMarks(const SwWW8AttrIter & rAttrs,sal_Int32 nCurrentPos,sal_Int32 nLen)2081 void MSWordExportBase::GetSortedAnnotationMarks( const SwWW8AttrIter& rAttrs, sal_Int32 nCurrentPos, sal_Int32 nLen )
2082 {
2083     IMarkVector aMarksStart;
2084     if (GetAnnotationMarks(rAttrs, nCurrentPos, nCurrentPos + nLen, aMarksStart))
2085     {
2086         IMarkVector aSortedEnd;
2087         IMarkVector aSortedStart;
2088         for ( IMark* pMark : aMarksStart )
2089         {
2090             // Remove the positions equal to the current pos
2091             const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
2092             const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
2093 
2094             const SwTextNode& rNode = rAttrs.GetNode();
2095             if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
2096                 aSortedStart.push_back( pMark );
2097 
2098             if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
2099                 aSortedEnd.push_back( pMark );
2100         }
2101 
2102         // Sort the bookmarks by end position
2103         std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
2104 
2105         m_rSortedAnnotationMarksStart.swap( aSortedStart );
2106         m_rSortedAnnotationMarksEnd.swap( aSortedEnd );
2107     }
2108     else
2109     {
2110         m_rSortedAnnotationMarksStart.clear( );
2111         m_rSortedAnnotationMarksEnd.clear( );
2112     }
2113 }
2114 
GetSortedBookmarks(const SwTextNode & rNode,sal_Int32 nCurrentPos,sal_Int32 nLen)2115 void MSWordExportBase::GetSortedBookmarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
2116 {
2117     IMarkVector aMarksStart;
2118     if ( GetBookmarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
2119     {
2120         IMarkVector aSortedEnd;
2121         IMarkVector aSortedStart;
2122         for ( IMark* pMark : aMarksStart )
2123         {
2124             // Remove the positions equal to the current pos
2125             const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
2126             const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
2127 
2128             if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
2129                 aSortedStart.push_back( pMark );
2130 
2131             if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
2132                 aSortedEnd.push_back( pMark );
2133         }
2134 
2135         // Sort the bookmarks by end position
2136         std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
2137 
2138         m_rSortedBookmarksStart.swap( aSortedStart );
2139         m_rSortedBookmarksEnd.swap( aSortedEnd );
2140     }
2141     else
2142     {
2143         m_rSortedBookmarksStart.clear( );
2144         m_rSortedBookmarksEnd.clear( );
2145     }
2146 }
2147 
NeedSectionBreak(const SwNode & rNd) const2148 bool MSWordExportBase::NeedSectionBreak( const SwNode& rNd ) const
2149 {
2150     if ( m_bStyDef || m_bOutKF || m_bInWriteEscher || m_bOutPageDescs || m_pCurrentPageDesc == nullptr )
2151         return false;
2152 
2153     const SwPageDesc * pPageDesc = rNd.FindPageDesc()->GetFollow();
2154 
2155     if (m_pCurrentPageDesc != pPageDesc)
2156     {
2157         if (!sw::util::IsPlausableSingleWordSection(m_pCurrentPageDesc->GetFirstMaster(), pPageDesc->GetMaster()))
2158         {
2159             return true;
2160         }
2161     }
2162 
2163     return false;
2164 }
2165 
NeedTextNodeSplit(const SwTextNode & rNd,SwSoftPageBreakList & pList) const2166 bool MSWordExportBase::NeedTextNodeSplit( const SwTextNode& rNd, SwSoftPageBreakList& pList ) const
2167 {
2168     SwSoftPageBreakList tmp;
2169     rNd.fillSoftPageBreakList(tmp);
2170     // hack: move the break behind any field marks; currently we can't hide the
2171     // field mark instruction so the layout position is quite meaningless
2172     IDocumentMarkAccess const& rIDMA(*rNd.GetDoc()->getIDocumentMarkAccess());
2173     sal_Int32 pos(-1);
2174     for (auto const& it : tmp)
2175     {
2176         if (pos < it) // previous one might have skipped over it
2177         {
2178             pos = it;
2179             while (auto const*const pMark = rIDMA.getFieldmarkFor(SwPosition(const_cast<SwTextNode&>(rNd), pos)))
2180             {
2181                 if (pMark->GetMarkEnd().nNode != rNd)
2182                 {
2183                     pos = rNd.Len(); // skip everything
2184                     break;
2185                 }
2186                 pos = pMark->GetMarkEnd().nContent.GetIndex(); // no +1, it's behind the char
2187             }
2188             pList.insert(pos);
2189         }
2190     }
2191     pList.insert(0);
2192     pList.insert( rNd.GetText().getLength() );
2193     return pList.size() > 2 && NeedSectionBreak( rNd );
2194 }
2195 
OutputTextNode(SwTextNode & rNode)2196 void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
2197 {
2198     SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" );
2199 
2200     ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo( m_pTableInfo->getTableNodeInfo( &rNode ) );
2201 
2202     //For i120928,identify the last node
2203     bool bLastCR = false;
2204     bool bExported = false;
2205     {
2206         SwNodeIndex aNextIdx(rNode,1);
2207         SwNodeIndex aLastIdx(rNode.GetNodes().GetEndOfContent());
2208         if (aNextIdx == aLastIdx)
2209             bLastCR = true;
2210     }
2211 
2212     // In order to make sure watermark is stored in 'header.xml', check nTextTyp.
2213     // if it is document.xml, don't write the tags (watermark should be only in the 'header')
2214     SwWW8AttrIter aWatermarkAttrIter( *this, rNode );
2215     if (( TXT_HDFT != m_nTextTyp) && aWatermarkAttrIter.IsWatermarkFrame())
2216     {
2217         return;
2218     }
2219 
2220     bool bFlyInTable = m_pParentFrame && IsInTable();
2221 
2222     SwTextFormatColl& rTextColl = lcl_getFormatCollection( *this, &rNode );
2223     if ( !bFlyInTable )
2224         m_nStyleBeforeFly = GetId( rTextColl );
2225 
2226     // nStyleBeforeFly may change when we recurse into another node, so we
2227     // have to remember it in nStyle
2228     sal_uInt16 nStyle = m_nStyleBeforeFly;
2229 
2230     SwWW8AttrIter aAttrIter( *this, rNode );
2231     rtl_TextEncoding eChrSet = aAttrIter.GetCharSet();
2232 
2233     if ( m_bStartTOX )
2234     {
2235         // ignore TOX header section
2236         const SwSectionNode* pSectNd = rNode.FindSectionNode();
2237         if ( pSectNd && TOX_CONTENT_SECTION == pSectNd->GetSection().GetType() )
2238         {
2239             AttrOutput().StartTOX( pSectNd->GetSection() );
2240             m_aCurrentCharPropStarts.push( 0 );
2241         }
2242     }
2243 
2244     // Emulate: If 1-row table is marked as don't split, then set the row as don't split.
2245     if ( IsInTable() )
2246     {
2247         const SwTableNode* pTableNode = rNode.FindTableNode();
2248         if ( pTableNode )
2249         {
2250             const SwTable& rTable = pTableNode->GetTable();
2251             const bool bKeep = rTable.GetFrameFormat()->GetKeep().GetValue();
2252             const bool bDontSplit = !rTable.GetFrameFormat()->GetLayoutSplit().GetValue();
2253             // bKeep handles this a different way later on, so ignore now
2254             if ( !bKeep && bDontSplit && rTable.GetTabLines().size() == 1 )
2255             {
2256                 // bDontSplit : set don't split once for the row
2257                 // but only for non-complex tables
2258                 const SwTableBox* pBox = rNode.GetTableBox();
2259                 const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
2260                 if ( pLine && !pLine->GetUpper() )
2261                 {
2262                     // check if box is first in that line:
2263                     if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
2264                     {
2265                         // check if paragraph is first in that line:
2266                         if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
2267                             pLine->GetFrameFormat()->SetFormatAttr(SwFormatRowSplit(!bDontSplit));
2268                     }
2269                 }
2270             }
2271         }
2272     }
2273 
2274     SwSoftPageBreakList softBreakList;
2275     // Let's decide if we need to split the paragraph because of a section break
2276     bool bNeedParaSplit = NeedTextNodeSplit( rNode, softBreakList )
2277                         && !IsInTable();
2278 
2279     auto aBreakIt = softBreakList.begin();
2280     // iterate through portions on different pages
2281     do
2282     {
2283         sal_Int32 nCurrentPos = *aBreakIt;
2284 
2285         if( softBreakList.size() > 1 ) // not for empty paragraph
2286             ++aBreakIt;
2287 
2288         AttrOutput().StartParagraph( pTextNodeInfo );
2289 
2290         const SwSection* pTOXSect = nullptr;
2291         if( m_bInWriteTOX )
2292         {
2293             // check for end of TOX
2294             SwNodeIndex aIdx( rNode, 1 );
2295             if( !aIdx.GetNode().IsTextNode() )
2296             {
2297                 const SwSectionNode* pTOXSectNd = rNode.FindSectionNode();
2298                 if ( pTOXSectNd )
2299                 {
2300                     pTOXSect = &pTOXSectNd->GetSection();
2301 
2302                     const SwNode* pNxt = rNode.GetNodes().GoNext( &aIdx );
2303                     if( pNxt && pNxt->FindSectionNode() == pTOXSectNd )
2304                         pTOXSect = nullptr;
2305                 }
2306             }
2307         }
2308 
2309         if ( aAttrIter.RequiresImplicitBookmark() )
2310         {
2311             OUString sBkmkName =  "_toc" + OUString::number( rNode.GetIndex() );
2312             // Add a bookmark converted to a Word name.
2313             AppendBookmark( BookmarkToWord( sBkmkName ) );
2314         }
2315 
2316         // Call this before write out fields and runs
2317         AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);
2318 
2319         const OUString& aStr( rNode.GetText() );
2320 
2321         sal_Int32 const nEnd = bNeedParaSplit ? *aBreakIt : aStr.getLength();
2322         bool bIncludeEndOfParaCRInRedlineProperties = false;
2323         sal_Int32 nOpenAttrWithRange = 0;
2324 
2325         ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner;
2326         if ( pTextNodeInfo.get() != nullptr )
2327         {
2328             pTextNodeInfoInner = pTextNodeInfo->getFirstInner();
2329         }
2330 
2331         do {
2332 
2333             const SwRedlineData* pRedlineData = aAttrIter.GetRunLevelRedline( nCurrentPos );
2334             FlyProcessingState nStateOfFlyFrame = FLY_PROCESSED;
2335             bool bPostponeWritingText    = false ;
2336             OUString aSavedSnippet ;
2337 
2338             sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos );
2339 
2340             // Skip un-exportable attributes.
2341             if (!aAttrIter.IsExportableAttr(nCurrentPos))
2342             {
2343                 nCurrentPos = nNextAttr;
2344                 UpdatePosition(&aAttrIter, nCurrentPos);
2345                 eChrSet = aAttrIter.GetCharSet();
2346                 continue;
2347             }
2348 
2349             // Is this the only run in this paragraph and it's empty?
2350             bool bSingleEmptyRun = nCurrentPos == 0 && nNextAttr == 0;
2351             AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2352 
2353             if( nNextAttr > nEnd )
2354                 nNextAttr = nEnd;
2355 
2356             if( m_nTextTyp == TXT_FTN || m_nTextTyp == TXT_EDN )
2357             {
2358                 if( AttrOutput().FootnoteEndnoteRefTag() )
2359                 {
2360                     AttrOutput().EndRun( &rNode, nCurrentPos, nNextAttr == nEnd );
2361                     AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2362                 }
2363             }
2364 
2365             /*
2366                1) If there is a text node and an overlapping anchor, then write them in two different
2367                runs and not as part of the same run.
2368                2) Ensure that it is a text node and not in a fly.
2369                3) If the anchor is associated with a text node with empty text then we ignore.
2370                */
2371             if( rNode.IsTextNode()
2372                     && aStr != "\001" && !aStr.isEmpty()
2373                     && !rNode.GetFlyFormat()
2374                     && !(IsInTable() && !AllowPostponedTextInTable())
2375                     && aAttrIter.IsAnchorLinkedToThisNode(rNode.GetIndex()) )
2376             {
2377                 bPostponeWritingText = true ;
2378             }
2379 
2380             nStateOfFlyFrame = aAttrIter.OutFlys( nCurrentPos );
2381             AttrOutput().SetStateOfFlyFrame( nStateOfFlyFrame );
2382             AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && (FLY_POSTPONED != nStateOfFlyFrame) );
2383             // Append bookmarks in this range after flys, exclusive of final
2384             // position of this range
2385             AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
2386             AppendAnnotationMarks(aAttrIter, nCurrentPos, nNextAttr - nCurrentPos);
2387 
2388             // At the moment smarttags are only written for paragraphs, at the
2389             // beginning of the paragraph.
2390             if (nCurrentPos == 0)
2391                 AppendSmartTags(rNode);
2392 
2393             bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
2394             nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nCurrentPos );
2395 
2396             sal_Int32 nLen = nNextAttr - nCurrentPos;
2397             if ( !bTextAtr && nLen )
2398             {
2399                 sal_Unicode ch = aStr[nCurrentPos];
2400 
2401                 const sal_Int32 ofs = (ch == CH_TXT_ATR_FIELDSTART
2402                                     || ch == CH_TXT_ATR_FIELDSEP
2403                                     || ch == CH_TXT_ATR_FIELDEND
2404                                     || ch == CH_TXT_ATR_FORMELEMENT)
2405                                 ? 1 : 0;
2406 
2407                 IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
2408                 if ( ch == CH_TXT_ATR_FIELDSTART )
2409                 {
2410                     SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
2411                     ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2412                     assert(pFieldmark);
2413 
2414                     // Date field is exported as content control, not as a simple field
2415                     if (pFieldmark->GetFieldname() == ODF_FORMDATE)
2416                     {
2417                         if(GetExportFormat() == MSWordExportBase::ExportFormat::DOCX) // supported by DOCX only
2418                         {
2419                             OutputField( nullptr, lcl_getFieldId( pFieldmark ),
2420                                     lcl_getFieldCode( pFieldmark ),
2421                                     FieldFlags::Start | FieldFlags::CmdStart );
2422                             WriteFormData( *pFieldmark );
2423                         }
2424                     }
2425                     else
2426                     {
2427 
2428                         if (pFieldmark->GetFieldname() == ODF_FORMTEXT
2429                              && GetExportFormat() != MSWordExportBase::ExportFormat::DOCX )
2430                         {
2431                            AppendBookmark( pFieldmark->GetName() );
2432                         }
2433                         ww::eField eFieldId = lcl_getFieldId( pFieldmark );
2434                         OUString sCode = lcl_getFieldCode( pFieldmark );
2435                         if (pFieldmark->GetFieldname() == ODF_UNHANDLED )
2436                         {
2437                             IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
2438                             if ( it != pFieldmark->GetParameters()->end() )
2439                             {
2440                                 OUString sFieldId;
2441                                 it->second >>= sFieldId;
2442                                 eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
2443                             }
2444 
2445                             it = pFieldmark->GetParameters()->find( ODF_CODE_PARAM );
2446                             if ( it != pFieldmark->GetParameters()->end() )
2447                             {
2448                                 it->second >>= sCode;
2449                             }
2450                         }
2451 
2452                         OutputField( nullptr, eFieldId, sCode, FieldFlags::Start | FieldFlags::CmdStart );
2453 
2454                         if (pFieldmark->GetFieldname() == ODF_FORMTEXT)
2455                             WriteFormData( *pFieldmark );
2456                         else if (pFieldmark->GetFieldname() == ODF_HYPERLINK)
2457                             WriteHyperlinkData( *pFieldmark );
2458                     }
2459                 }
2460                 else if (ch == CH_TXT_ATR_FIELDSEP)
2461                 {
2462                     SwPosition aPosition(rNode, SwIndex(&rNode, nCurrentPos));
2463                     // the innermost field is the correct one
2464                     ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkFor(aPosition);
2465                     assert(pFieldmark);
2466                     // DateFieldmark / ODF_FORMDATE is not a field...
2467                     if (pFieldmark->GetFieldname() != ODF_FORMDATE)
2468                     {
2469                         OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::CmdEnd );
2470 
2471                         if (pFieldmark->GetFieldname() == ODF_UNHANDLED)
2472                         {
2473                             // Check for the presence of a linked OLE object
2474                             IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_OLE_PARAM );
2475                             if ( it != pFieldmark->GetParameters()->end() )
2476                             {
2477                                 OUString sOleId;
2478                                 uno::Any aValue = it->second;
2479                                 aValue >>= sOleId;
2480                                 if ( !sOleId.isEmpty() )
2481                                     OutputLinkedOLE( sOleId );
2482                             }
2483                         }
2484                     }
2485                 }
2486                 else if ( ch == CH_TXT_ATR_FIELDEND )
2487                 {
2488                     SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
2489                     ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2490 
2491                     assert(pFieldmark);
2492 
2493                     if (pFieldmark->GetFieldname() == ODF_FORMDATE)
2494                     {
2495                         if(GetExportFormat() == MSWordExportBase::ExportFormat::DOCX) // supported by DOCX only
2496                         {
2497                             OutputField( nullptr, ww::eFORMDATE, OUString(), FieldFlags::Close );
2498                         }
2499                     }
2500                     else
2501                     {
2502                         ww::eField eFieldId = lcl_getFieldId( pFieldmark );
2503                         if (pFieldmark->GetFieldname() == ODF_UNHANDLED)
2504                         {
2505                             IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
2506                             if ( it != pFieldmark->GetParameters()->end() )
2507                             {
2508                                 OUString sFieldId;
2509                                 it->second >>= sFieldId;
2510                                 eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
2511                             }
2512                         }
2513 
2514                         OutputField( nullptr, eFieldId, OUString(), FieldFlags::Close );
2515 
2516                         if (pFieldmark->GetFieldname() == ODF_FORMTEXT
2517                              && GetExportFormat() != MSWordExportBase::ExportFormat::DOCX )
2518                         {
2519                             AppendBookmark( pFieldmark->GetName() );
2520                         }
2521                     }
2522                 }
2523                 else if ( ch == CH_TXT_ATR_FORMELEMENT )
2524                 {
2525                     SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
2526                     ::sw::mark::IFieldmark const*const pFieldmark = pMarkAccess->getFieldmarkAt(aPosition);
2527                     assert(pFieldmark);
2528 
2529                     bool const isDropdownOrCheckbox(pFieldmark->GetFieldname() == ODF_FORMDROPDOWN ||
2530                                                     pFieldmark->GetFieldname() == ODF_FORMCHECKBOX);
2531                     if ( isDropdownOrCheckbox )
2532                         AppendBookmark( pFieldmark->GetName() );
2533                     OutputField( nullptr, lcl_getFieldId( pFieldmark ),
2534                             lcl_getFieldCode( pFieldmark ),
2535                             FieldFlags::Start | FieldFlags::CmdStart );
2536                     if ( isDropdownOrCheckbox )
2537                         WriteFormData( *pFieldmark );
2538                     // tdf#129514 need CmdEnd for docx
2539                     OutputField(nullptr, lcl_getFieldId(pFieldmark), OUString(),
2540                             FieldFlags::CmdEnd | FieldFlags::Close);
2541                     if ( isDropdownOrCheckbox )
2542                         AppendBookmark( pFieldmark->GetName() );
2543                 }
2544                 nLen -= ofs;
2545 
2546                 // if paragraph needs to be split, write only until split position
2547                 assert(!bNeedParaSplit || nCurrentPos <= *aBreakIt);
2548                 if( bNeedParaSplit && nCurrentPos + ofs + nLen > *aBreakIt)
2549                     nLen = *aBreakIt - nCurrentPos - ofs;
2550                 assert(0 <= nLen);
2551 
2552                 OUString aSnippet( aAttrIter.GetSnippet( aStr, nCurrentPos + ofs, nLen ) );
2553                 if ( ( m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN ) && nCurrentPos == 0 && nLen > 0 )
2554                 {
2555                     // Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent
2556                     sal_Int32 nFirstLineIndent=0;
2557                     SfxItemSet aSet( m_pDoc->GetAttrPool(), svl::Items<RES_LR_SPACE, RES_LR_SPACE>{} );
2558                     const SwTextNode* pTextNode( rNode.GetTextNode() );
2559                     if ( pTextNode && pTextNode->GetAttr(aSet) )
2560                     {
2561                         const SvxLRSpaceItem* pLRSpace = aSet.GetItem<SvxLRSpaceItem>(RES_LR_SPACE);
2562                         if ( pLRSpace )
2563                             nFirstLineIndent = pLRSpace->GetTextFirstLineOfst();
2564                     }
2565 
2566                     // Insert tab for aesthetic purposes #i24762#
2567                     if ( m_bAddFootnoteTab && nFirstLineIndent < 0 && aSnippet[0] != 0x09 )
2568                         aSnippet = "\x09" + aSnippet;
2569                     m_bAddFootnoteTab = false;
2570                 }
2571 
2572                 if ( bPostponeWritingText && ( FLY_POSTPONED != nStateOfFlyFrame ) )
2573                 {
2574                     bPostponeWritingText = true ;
2575                     aSavedSnippet = aSnippet ;
2576                 }
2577                 else
2578                 {
2579                     bPostponeWritingText = false ;
2580                     AttrOutput().RunText( aSnippet, eChrSet );
2581                 }
2582             }
2583 
2584             if ( aAttrIter.IsDropCap( nNextAttr ) )
2585                 AttrOutput().FormatDrop( rNode, aAttrIter.GetSwFormatDrop(), nStyle, pTextNodeInfo, pTextNodeInfoInner );
2586 
2587             // Only output character attributes if this is not a postponed text run.
2588             if (0 != nEnd && !(bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame))
2589             {
2590                 // Output the character attributes
2591                 // #i51277# do this before writing flys at end of paragraph
2592                 AttrOutput().StartRunProperties();
2593                 aAttrIter.OutAttr( nCurrentPos, false );
2594                 AttrOutput().EndRunProperties( pRedlineData );
2595             }
2596 
2597             // At the end of line, output the attributes until the CR.
2598             // Exception: footnotes at the end of line
2599             if ( nNextAttr == nEnd )
2600             {
2601                 OSL_ENSURE( nOpenAttrWithRange >= 0, "odd to see this happening, expected >= 0" );
2602                 if ( !bTextAtr && nOpenAttrWithRange <= 0 )
2603                 {
2604                     if ( aAttrIter.IncludeEndOfParaCRInRedlineProperties( nEnd ) )
2605                         bIncludeEndOfParaCRInRedlineProperties = true;
2606                     else
2607                     {
2608                         // insert final graphic anchors if any before CR
2609                         nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
2610                         // insert final bookmarks if any before CR and after flys
2611                         AppendBookmarks( rNode, nEnd, 1 );
2612                         AppendAnnotationMarks(aAttrIter, nEnd, 1);
2613                         if ( pTOXSect )
2614                         {
2615                             m_aCurrentCharPropStarts.pop();
2616                             AttrOutput().EndTOX( *pTOXSect ,false);
2617                         }
2618                         //For i120928,the position of the bullet's graphic is at end of doc
2619                         if (bLastCR && (!bExported))
2620                         {
2621                             ExportGrfBullet(rNode);
2622                             bExported = true;
2623                         }
2624 
2625                         WriteCR( pTextNodeInfoInner );
2626                     }
2627                 }
2628             }
2629 
2630             if (0 == nEnd)
2631             {
2632                 // Output the character attributes
2633                 // do it after WriteCR for an empty paragraph (otherwise
2634                 // WW8_WrFkp::Append throws SPRMs away...)
2635                 AttrOutput().StartRunProperties();
2636                 aAttrIter.OutAttr( nCurrentPos, false );
2637                 AttrOutput().EndRunProperties( pRedlineData );
2638             }
2639 
2640             // Exception: footnotes at the end of line
2641             if ( nNextAttr == nEnd )
2642             {
2643                 OSL_ENSURE(nOpenAttrWithRange >= 0,
2644                         "odd to see this happening, expected >= 0");
2645                 bool bAttrWithRange = (nOpenAttrWithRange > 0);
2646                 if ( nCurrentPos != nEnd )
2647                 {
2648                     nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nEnd );
2649                     OSL_ENSURE(nOpenAttrWithRange == 0,
2650                             "odd to see this happening, expected 0");
2651                 }
2652 
2653                 // !bIncludeEndOfParaCRInRedlineProperties implies we have just
2654                 // emitted a CR, in which case we want to pass force=true to
2655                 // OutputFKP to ensure that an FKP entry for direct character
2656                 // formatting is written even if empty, so that the next one will
2657                 // start after the CR.
2658                 AttrOutput().OutputFKP(!bIncludeEndOfParaCRInRedlineProperties);
2659 
2660                 if (bTextAtr || bAttrWithRange || bIncludeEndOfParaCRInRedlineProperties)
2661                 {
2662                     // insert final graphic anchors if any before CR
2663                     nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
2664                     // insert final bookmarks if any before CR and after flys
2665                     AppendBookmarks( rNode, nEnd, 1 );
2666                     AppendAnnotationMarks(aAttrIter, nEnd, 1);
2667                     WriteCR( pTextNodeInfoInner );
2668                     // #i120928 - position of the bullet's graphic is at end of doc
2669                     if (bLastCR && (!bExported))
2670                     {
2671                         ExportGrfBullet(rNode);
2672                         bExported = true;
2673                     }
2674 
2675                     if ( pTOXSect )
2676                     {
2677                         m_aCurrentCharPropStarts.pop();
2678                         AttrOutput().EndTOX( *pTOXSect );
2679                     }
2680 
2681                     if (bIncludeEndOfParaCRInRedlineProperties)
2682                     {
2683                         AttrOutput().Redline( aAttrIter.GetRunLevelRedline( nEnd ) );
2684                         //If there was no redline property emitted, force adding
2685                         //another entry for the CR so that in the case that this
2686                         //has no redline, but the next para does, then this one is
2687                         //not merged with the next
2688                         AttrOutput().OutputFKP(true);
2689                     }
2690                 }
2691             }
2692 
2693             AttrOutput().WritePostitFieldReference();
2694 
2695             if( bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame )
2696             {
2697                 AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
2698                 //write the postponed text run
2699                 AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
2700                 AttrOutput().SetAnchorIsLinkedToNode( false );
2701                 AttrOutput().ResetFlyProcessingFlag();
2702                 if (0 != nEnd)
2703                 {
2704                     AttrOutput().StartRunProperties();
2705                     aAttrIter.OutAttr( nCurrentPos, false );
2706                     AttrOutput().EndRunProperties( pRedlineData );
2707                 }
2708                 AttrOutput().RunText( aSavedSnippet, eChrSet );
2709                 AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
2710             }
2711             else if( bPostponeWritingText && !aSavedSnippet.isEmpty() )
2712             {
2713                 //write the postponed text run
2714                 AttrOutput().RunText( aSavedSnippet, eChrSet );
2715                 AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
2716             }
2717             else
2718                 AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
2719 
2720             nCurrentPos = nNextAttr;
2721             UpdatePosition( &aAttrIter, nCurrentPos );
2722             eChrSet = aAttrIter.GetCharSet();
2723         }
2724         while ( nCurrentPos < nEnd );
2725 
2726         // if paragraph is split, put the section break between the parts
2727         if( bNeedParaSplit && *aBreakIt != rNode.GetText().getLength() )
2728         {
2729             SwNodeIndex aNextIndex( rNode, 1 );
2730             const SwNode& pNextNode = aNextIndex.GetNode();
2731             // if there is a next node, use its attributes to create the new
2732             // section
2733             if( pNextNode.IsTextNode() )
2734             {
2735                 const SwTextNode& rNextNode = *static_cast<SwTextNode*>(
2736                         &aNextIndex.GetNode() );
2737                 OutputSectionBreaks(rNextNode.GetpSwAttrSet(), rNextNode);
2738             }
2739             else if (pNextNode.IsEndNode() )
2740             {
2741                 // In this case the same paragraph holds the next page style
2742                 // too.
2743                 const SwPageDesc* pNextPageDesc = m_pCurrentPageDesc->GetFollow();
2744                 assert(pNextPageDesc);
2745                 PrepareNewPageDesc( rNode.GetpSwAttrSet(), rNode, nullptr , pNextPageDesc);
2746             }
2747         }
2748         else if (!bNeedParaSplit)
2749         {
2750             // else check if section break needed after the paragraph
2751             AttrOutput().SectionBreaks(rNode);
2752         }
2753 
2754         AttrOutput().StartParagraphProperties();
2755 
2756         AttrOutput().ParagraphStyle( nStyle );
2757 
2758         if ( m_pParentFrame && IsInTable() )    // Fly-Attrs
2759             OutputFormat( m_pParentFrame->GetFrameFormat(), false, false, true );
2760 
2761         if ( pTextNodeInfo.get() != nullptr )
2762         {
2763 #ifdef DBG_UTIL
2764             SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
2765 #endif
2766 
2767             AttrOutput().TableInfoCell( pTextNodeInfoInner );
2768             if (pTextNodeInfoInner->isFirstInTable())
2769             {
2770                 const SwTable * pTable = pTextNodeInfoInner->getTable();
2771 
2772                 const SwTableFormat* pTabFormat = pTable->GetFrameFormat();
2773                 if (pTabFormat != nullptr)
2774                 {
2775                     if (pTabFormat->GetBreak().GetBreak() == SvxBreak::PageBefore)
2776                         AttrOutput().PageBreakBefore(true);
2777                 }
2778             }
2779         }
2780 
2781         if ( !bFlyInTable )
2782         {
2783             SfxItemSet* pTmpSet = nullptr;
2784             const sal_uInt8 nPrvNxtNd = rNode.HasPrevNextLayNode();
2785 
2786             if( (ND_HAS_PREV_LAYNODE|ND_HAS_NEXT_LAYNODE ) != nPrvNxtNd )
2787             {
2788                 const SfxPoolItem* pItem;
2789                 if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState(
2790                         RES_UL_SPACE, true, &pItem ) &&
2791                     ( ( !( ND_HAS_PREV_LAYNODE & nPrvNxtNd ) &&
2792                        static_cast<const SvxULSpaceItem*>(pItem)->GetUpper()) ||
2793                       ( !( ND_HAS_NEXT_LAYNODE & nPrvNxtNd ) &&
2794                        static_cast<const SvxULSpaceItem*>(pItem)->GetLower()) ))
2795                 {
2796                     pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
2797                     SvxULSpaceItem aUL( *static_cast<const SvxULSpaceItem*>(pItem) );
2798                     // #i25901#- consider compatibility option
2799                     if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES))
2800                     {
2801                         if( !(ND_HAS_PREV_LAYNODE & nPrvNxtNd ))
2802                             aUL.SetUpper( 0 );
2803                     }
2804                     // #i25901# - consider compatibility option
2805                     if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS))
2806                     {
2807                         if( !(ND_HAS_NEXT_LAYNODE & nPrvNxtNd ))
2808                             aUL.SetLower( 0 );
2809                     }
2810                     pTmpSet->Put( aUL );
2811                 }
2812             }
2813 
2814             const bool bParaRTL = aAttrIter.IsParaRTL();
2815 
2816             int nNumberLevel = -1;
2817             if (rNode.IsNumbered())
2818                 nNumberLevel = rNode.GetActualListLevel();
2819             if (nNumberLevel >= 0 && nNumberLevel < MAXLEVEL)
2820             {
2821                 const SwNumRule* pRule = rNode.GetNumRule();
2822                 sal_uInt8 nLvl = static_cast< sal_uInt8 >(nNumberLevel);
2823                 const SwNumFormat* pFormat = pRule->GetNumFormat( nLvl );
2824                 if( !pFormat )
2825                     pFormat = &pRule->Get( nLvl );
2826 
2827                 if( !pTmpSet )
2828                     pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
2829 
2830                 SvxLRSpaceItem aLR(ItemGet<SvxLRSpaceItem>(*pTmpSet, RES_LR_SPACE));
2831                 // #i86652#
2832                 if ( pFormat->GetPositionAndSpaceMode() ==
2833                                         SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2834                 {
2835                     aLR.SetTextLeft( aLR.GetTextLeft() + pFormat->GetAbsLSpace() );
2836                 }
2837 
2838                 if( rNode.IsNumbered() && rNode.IsCountedInList() )
2839                 {
2840                     // #i86652#
2841                     if ( pFormat->GetPositionAndSpaceMode() ==
2842                                             SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2843                     {
2844                         if (bParaRTL)
2845                         {
2846                             aLR.SetTextFirstLineOfstValue(aLR.GetTextFirstLineOfst() + pFormat->GetAbsLSpace() - pFormat->GetFirstLineOffset()); //TODO: overflow
2847                         }
2848                         else
2849                         {
2850                             aLR.SetTextFirstLineOfst(aLR.GetTextFirstLineOfst() + GetWordFirstLineOffset(*pFormat));
2851                         }
2852                     }
2853 
2854                     // correct fix for issue i94187
2855                     if (SfxItemState::SET !=
2856                         pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
2857                     {
2858                         // List style set via paragraph style - then put it into the itemset.
2859                         // This is needed to get list level and list id exported for
2860                         // the paragraph.
2861                         pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
2862 
2863                         // Put indent values into the itemset in case that the list
2864                         // style is applied via paragraph style and the list level
2865                         // indent values are not applicable.
2866                         if ( pFormat->GetPositionAndSpaceMode() ==
2867                                                 SvxNumberFormat::LABEL_ALIGNMENT &&
2868                              !rNode.AreListLevelIndentsApplicable() )
2869                         {
2870                             pTmpSet->Put( aLR );
2871                         }
2872                     }
2873                 }
2874                 else
2875                     pTmpSet->ClearItem(RES_PARATR_NUMRULE);
2876 
2877                 // #i86652#
2878                 if ( pFormat->GetPositionAndSpaceMode() ==
2879                                         SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2880                 {
2881                     pTmpSet->Put(aLR);
2882 
2883                     //#i21847#
2884                     SvxTabStopItem aItem(
2885                         ItemGet<SvxTabStopItem>(*pTmpSet, RES_PARATR_TABSTOP));
2886                     SvxTabStop aTabStop(pFormat->GetAbsLSpace());
2887                     aItem.Insert(aTabStop);
2888                     pTmpSet->Put(aItem);
2889 
2890                     MSWordExportBase::CorrectTabStopInSet(*pTmpSet, pFormat->GetAbsLSpace());
2891                 }
2892             }
2893 
2894             /*
2895             If a given para is using the SvxFrameDirection::Environment direction we
2896             cannot export that, if it's ltr then that's ok as that is word's
2897             default. Otherwise we must add a RTL attribute to our export list
2898             Only necessary if the ParaStyle doesn't define the direction.
2899             */
2900             const SvxFrameDirectionItem* pItem =
2901                 rNode.GetSwAttrSet().GetItem(RES_FRAMEDIR);
2902             if (
2903                 (!pItem || pItem->GetValue() == SvxFrameDirection::Environment) &&
2904                 rTextColl.GetFrameDir().GetValue() == SvxFrameDirection::Environment
2905                )
2906             {
2907                 if ( !pTmpSet )
2908                     pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
2909 
2910                 if ( bParaRTL )
2911                     pTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, RES_FRAMEDIR));
2912                 else
2913                     pTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_LR_TB, RES_FRAMEDIR));
2914 
2915                 const SvxAdjustItem* pAdjust = rNode.GetSwAttrSet().GetItem(RES_PARATR_ADJUST);
2916                 if ( pAdjust && (pAdjust->GetAdjust() == SvxAdjust::Left || pAdjust->GetAdjust() == SvxAdjust::Right ) )
2917                     pTmpSet->Put( *pAdjust, RES_PARATR_ADJUST );
2918             }
2919             // move code for handling of numbered,
2920             // but not counted paragraphs to this place. Otherwise, the paragraph
2921             // isn't exported as numbered, but not counted, if no other attribute
2922             // is found in <pTmpSet>
2923             // #i44815# adjust numbering/indents for numbered paragraphs
2924             //          without number (NO_NUMLEVEL)
2925             // #i47013# need to check rNode.GetNumRule()!=NULL as well.
2926             if ( ! rNode.IsCountedInList() && rNode.GetNumRule()!=nullptr )
2927             {
2928                 // WW8 does not know numbered paragraphs without number
2929                 // (NO_NUMLEVEL). In WW8AttributeOutput::ParaNumRule(), we will export
2930                 // the RES_PARATR_NUMRULE as list-id 0, which in WW8 means
2931                 // no numbering. Here, we will adjust the indents to match
2932                 // visually.
2933 
2934                 if ( !pTmpSet )
2935                     pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
2936 
2937                 // create new LRSpace item, based on the current (if present)
2938                 const SfxPoolItem* pPoolItem = nullptr;
2939                 pTmpSet->GetItemState(RES_LR_SPACE, true, &pPoolItem);
2940                 SvxLRSpaceItem aLRSpace(
2941                     ( pPoolItem == nullptr )
2942                         ? SvxLRSpaceItem(0, 0, 0, 0, RES_LR_SPACE)
2943                         : *static_cast<const SvxLRSpaceItem*>( pPoolItem ) );
2944 
2945                 // new left margin = old left + label space
2946                 const SwNumRule* pRule = rNode.GetNumRule();
2947                 int nLevel = rNode.GetActualListLevel();
2948 
2949                 if (nLevel < 0)
2950                     nLevel = 0;
2951 
2952                 if (nLevel >= MAXLEVEL)
2953                     nLevel = MAXLEVEL - 1;
2954 
2955                 const SwNumFormat& rNumFormat = pRule->Get( static_cast< sal_uInt16 >(nLevel) );
2956 
2957                 // #i86652#
2958                 if ( rNumFormat.GetPositionAndSpaceMode() ==
2959                                         SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
2960                 {
2961                     aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetAbsLSpace() );
2962                 }
2963                 else
2964                 {
2965                     aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetIndentAt() );
2966                 }
2967 
2968                 // new first line indent = 0
2969                 // (first line indent is ignored for NO_NUMLEVEL)
2970                 if (!bParaRTL)
2971                     aLRSpace.SetTextFirstLineOfst( 0 );
2972 
2973                 // put back the new item
2974                 pTmpSet->Put( aLRSpace );
2975 
2976                 // assure that numbering rule is in <pTmpSet>
2977                 if (SfxItemState::SET != pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
2978                 {
2979                     pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
2980                 }
2981             }
2982 
2983             // #i75457#
2984             // Export page break after attribute from paragraph style.
2985             // If page break attribute at the text node exist, an existing page
2986             // break after at the paragraph style hasn't got to be considered.
2987             if ( !rNode.GetpSwAttrSet() ||
2988                  SfxItemState::SET != rNode.GetpSwAttrSet()->GetItemState(RES_BREAK, false) )
2989             {
2990                 const SvxFormatBreakItem& rBreakAtParaStyle
2991                     = ItemGet<SvxFormatBreakItem>(rNode.GetSwAttrSet(), RES_BREAK);
2992                 if (rBreakAtParaStyle.GetBreak() == SvxBreak::PageAfter)
2993                 {
2994                     if ( !pTmpSet )
2995                     {
2996                         pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
2997                     }
2998                     pTmpSet->Put(rBreakAtParaStyle);
2999                 }
3000                 else if( pTmpSet )
3001                 {   // Even a pagedesc item is set, the break item can be set 'NONE',
3002                     // this has to be overruled.
3003                     const SwFormatPageDesc& rPageDescAtParaStyle =
3004                         ItemGet<SwFormatPageDesc>( rNode, RES_PAGEDESC );
3005                     if( rPageDescAtParaStyle.KnowsPageDesc() )
3006                         pTmpSet->ClearItem( RES_BREAK );
3007                 }
3008             }
3009 
3010             // #i76520# Emulate non-splitting tables
3011             if ( IsInTable() )
3012             {
3013                 const SwTableNode* pTableNode = rNode.FindTableNode();
3014 
3015                 if ( pTableNode )
3016                 {
3017                     const SwTable& rTable = pTableNode->GetTable();
3018                     const SvxFormatKeepItem& rKeep = rTable.GetFrameFormat()->GetKeep();
3019                     const bool bKeep = rKeep.GetValue();
3020                     const bool bDontSplit = !(bKeep ||
3021                                               rTable.GetFrameFormat()->GetLayoutSplit().GetValue());
3022 
3023                     if ( bKeep || bDontSplit )
3024                     {
3025                         // bKeep: set keep at first paragraphs in all lines
3026                         // bDontSplit : set keep at first paragraphs in all lines except from last line
3027                         // but only for non-complex tables
3028                         const SwTableBox* pBox = rNode.GetTableBox();
3029                         const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
3030 
3031                         if ( pLine && !pLine->GetUpper() )
3032                         {
3033                             // check if box is first in that line:
3034                             if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
3035                             {
3036                                 // check if paragraph is first in that line:
3037                                 if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
3038                                 {
3039                                     bool bSetAtPara = false;
3040                                     if ( bKeep )
3041                                         bSetAtPara = true;
3042                                     else if ( bDontSplit )
3043                                     {
3044                                         // check if pLine isn't last line in table
3045                                         if ( rTable.GetTabLines().size() - rTable.GetTabLines().GetPos( pLine ) != 1 )
3046                                             bSetAtPara = true;
3047                                     }
3048 
3049                                     if ( bSetAtPara )
3050                                     {
3051                                         if ( !pTmpSet )
3052                                             pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
3053 
3054                                         const SvxFormatKeepItem aKeepItem( true, RES_KEEP );
3055                                         pTmpSet->Put( aKeepItem );
3056                                     }
3057                                 }
3058                             }
3059                         }
3060                     }
3061                 }
3062             }
3063 
3064             const SfxItemSet* pNewSet = pTmpSet ? pTmpSet : rNode.GetpSwAttrSet();
3065             if( pNewSet )
3066             {                                               // Para-Attrs
3067                 m_pStyAttr = &rNode.GetAnyFormatColl().GetAttrSet();
3068 
3069                 const SwModify* pOldMod = m_pOutFormatNode;
3070                 m_pOutFormatNode = &rNode;
3071 
3072                 // Pap-Attrs, so script is not necessary
3073                 OutputItemSet( *pNewSet, true, false, i18n::ScriptType::LATIN, false);
3074 
3075                 m_pStyAttr = nullptr;
3076                 m_pOutFormatNode = pOldMod;
3077 
3078                 if( pNewSet != rNode.GetpSwAttrSet() )
3079                     delete pNewSet;
3080             }
3081         }
3082 
3083         // The formatting of the paragraph marker has two sources:
3084         // 0) If there is a RES_PARATR_LIST_AUTOFMT, then use that.
3085         // 1) If there are hints at the end of the paragraph, then use that.
3086         // 2) Else use the RES_CHRATR_BEGIN..RES_TXTATR_END range of the paragraph
3087         // properties.
3088         //
3089         // Exception: if there is a character style hint at the end of the
3090         // paragraph only, then still go with 2), as RES_TXTATR_CHARFMT is always
3091         // set as a hint.
3092         SfxItemSet aParagraphMarkerProperties(m_pDoc->GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END>{});
3093         bool bCharFormatOnly = true;
3094 
3095         SwFormatAutoFormat const& rListAutoFormat(static_cast<SwFormatAutoFormat const&>(rNode.GetAttr(RES_PARATR_LIST_AUTOFMT)));
3096         if (std::shared_ptr<SfxItemSet> const& pSet = rListAutoFormat.GetStyleHandle())
3097         {
3098             aParagraphMarkerProperties.Put(*pSet);
3099             bCharFormatOnly = false;
3100         }
3101         else if (const SwpHints* pTextAttrs = rNode.GetpSwpHints())
3102         {
3103             for( size_t i = 0; i < pTextAttrs->Count(); ++i )
3104             {
3105                 const SwTextAttr* pHt = pTextAttrs->Get(i);
3106                 const sal_Int32 startPos = pHt->GetStart();    // first Attr characters
3107                 const sal_Int32* endPos = pHt->End();    // end Attr characters
3108                 // Check if these attributes are for the last character in the paragraph
3109                 // - which means the paragraph marker. If a paragraph has 7 characters,
3110                 // then properties on character 8 are for the paragraph marker
3111                 if( endPos && (startPos == *endPos ) && (*endPos == rNode.GetText().getLength()) )
3112                 {
3113                     SAL_INFO( "sw.ww8", startPos << "startPos == endPos" << *endPos);
3114                     sal_uInt16 nWhich = pHt->GetAttr().Which();
3115                     SAL_INFO( "sw.ww8", "nWhich" << nWhich);
3116                     if ((nWhich == RES_TXTATR_AUTOFMT && bCharFormatOnly)
3117                         || nWhich == RES_TXTATR_CHARFMT)
3118                     {
3119                         aParagraphMarkerProperties.Put(pHt->GetAttr());
3120                     }
3121                     if (nWhich != RES_TXTATR_CHARFMT)
3122                         bCharFormatOnly = false;
3123                 }
3124             }
3125         }
3126         if (rNode.GetpSwAttrSet() && bCharFormatOnly)
3127         {
3128             aParagraphMarkerProperties.Put(*rNode.GetpSwAttrSet());
3129         }
3130         const SwRedlineData* pRedlineParagraphMarkerDelete = AttrOutput().GetParagraphMarkerRedline( rNode, RedlineType::Delete );
3131         const SwRedlineData* pRedlineParagraphMarkerInsert = AttrOutput().GetParagraphMarkerRedline( rNode, RedlineType::Insert );
3132         const SwRedlineData* pParagraphRedlineData = aAttrIter.GetParagraphLevelRedline( );
3133         AttrOutput().EndParagraphProperties(aParagraphMarkerProperties, pParagraphRedlineData, pRedlineParagraphMarkerDelete, pRedlineParagraphMarkerInsert);
3134 
3135         AttrOutput().EndParagraph( pTextNodeInfoInner );
3136     }while(*aBreakIt != rNode.GetText().getLength() && bNeedParaSplit );
3137 
3138     SAL_INFO( "sw.ww8", "</OutWW8_SwTextNode>" );
3139 }
3140 
3141 // Tables
3142 
EmptyParagraph()3143 void WW8AttributeOutput::EmptyParagraph()
3144 {
3145     m_rWW8Export.WriteStringAsPara( OUString() );
3146 }
3147 
NoPageBreakSection(const SfxItemSet * pSet)3148 bool MSWordExportBase::NoPageBreakSection( const SfxItemSet* pSet )
3149 {
3150     bool bRet = false;
3151     const SfxPoolItem* pI;
3152     if( pSet)
3153     {
3154         bool bNoPageBreak = false;
3155         if ( SfxItemState::SET != pSet->GetItemState(RES_PAGEDESC, true, &pI)
3156             || nullptr == static_cast<const SwFormatPageDesc*>(pI)->GetPageDesc() )
3157         {
3158             bNoPageBreak = true;
3159         }
3160 
3161         if (bNoPageBreak)
3162         {
3163             if (SfxItemState::SET == pSet->GetItemState(RES_BREAK, true, &pI))
3164             {
3165                 SvxBreak eBreak = static_cast<const SvxFormatBreakItem*>(pI)->GetBreak();
3166                 switch (eBreak)
3167                 {
3168                     case SvxBreak::PageBefore:
3169                     case SvxBreak::PageAfter:
3170                         bNoPageBreak = false;
3171                         break;
3172                     default:
3173                         break;
3174                 }
3175             }
3176         }
3177         bRet = bNoPageBreak;
3178     }
3179     return bRet;
3180 }
3181 
OutputSectionNode(const SwSectionNode & rSectionNode)3182 void MSWordExportBase::OutputSectionNode( const SwSectionNode& rSectionNode )
3183 {
3184     const SwSection& rSection = rSectionNode.GetSection();
3185 
3186     SwNodeIndex aIdx( rSectionNode, 1 );
3187     const SwNode& rNd = aIdx.GetNode();
3188     if ( !rNd.IsSectionNode() && !IsInTable() ) //No sections in table
3189     {
3190         // if the first Node inside the section has an own
3191         // PageDesc or PageBreak attribute, then don't write
3192         // here the section break
3193         sal_uLong nRstLnNum = 0;
3194         const SfxItemSet* pSet;
3195         if ( rNd.IsContentNode() )
3196         {
3197             pSet = &rNd.GetContentNode()->GetSwAttrSet();
3198             nRstLnNum = pSet->Get( RES_LINENUMBER ).GetStartValue();
3199         }
3200         else
3201             pSet = nullptr;
3202 
3203         if ( pSet && NoPageBreakSection( pSet ) )
3204             pSet = nullptr;
3205         else
3206             AttrOutput().SectionBreaks( rSectionNode );
3207 
3208         const bool bInTOX = rSection.GetType() == TOX_CONTENT_SECTION || rSection.GetType() == TOX_HEADER_SECTION;
3209         if ( !pSet && !bInTOX )
3210         {
3211             // new Section with no own PageDesc/-Break
3212             //  -> write follow section break;
3213             const SwSectionFormat* pFormat = rSection.GetFormat();
3214             ReplaceCr( msword::PageBreak ); // Indicator for Page/Section-Break
3215 
3216             // Get the page in use at the top of this section
3217             const SwPageDesc *pCurrent = SwPageDesc::GetPageDescOfNode(rNd);
3218             if (!pCurrent)
3219                 pCurrent = m_pCurrentPageDesc;
3220 
3221             AppendSection( pCurrent, pFormat, nRstLnNum );
3222         }
3223     }
3224     if ( TOX_CONTENT_SECTION == rSection.GetType() )
3225     {
3226         m_bStartTOX = true;
3227         UpdateTocSectionNodeProperties(rSectionNode);
3228     }
3229 }
3230 
3231 // tdf#121561: During export of the ODT file with TOC inside into DOCX format,
3232 // the TOC title is being exported as regular paragraph. We should surround it
3233 // with <w:sdt><w:sdtPr><w:sdtContent> to make it (TOC title) recognizable
3234 // by MS Word as part of the TOC.
UpdateTocSectionNodeProperties(const SwSectionNode & rSectionNode)3235 void MSWordExportBase::UpdateTocSectionNodeProperties(const SwSectionNode& rSectionNode)
3236 {
3237     // check section type
3238     {
3239         const SwSection& rSection = rSectionNode.GetSection();
3240         if (TOX_CONTENT_SECTION != rSection.GetType())
3241             return;
3242 
3243         const SwTOXBase* pTOX = rSection.GetTOXBase();
3244         if (pTOX)
3245         {
3246             TOXTypes type = pTOX->GetType();
3247             if (type != TOXTypes::TOX_CONTENT)
3248                 return;
3249         }
3250     }
3251 
3252     // get section node, skip toc-header node
3253     const SwSectionNode* pSectNd = &rSectionNode;
3254     {
3255         SwNodeIndex aIdxNext( *pSectNd, 1 );
3256         const SwNode& rNdNext = aIdxNext.GetNode();
3257 
3258         if (rNdNext.IsSectionNode())
3259         {
3260             const SwSectionNode* pSectNdNext = static_cast<const SwSectionNode*>(&rNdNext);
3261             if (TOX_HEADER_SECTION == pSectNdNext->GetSection().GetType() &&
3262                 pSectNdNext->StartOfSectionNode()->IsSectionNode())
3263             {
3264                 pSectNd = pSectNdNext;
3265             }
3266         }
3267     }
3268 
3269     // get node of the first paragraph inside TOC
3270     SwNodeIndex aIdxNext( *pSectNd, 1 );
3271     const SwNode& rNdTocPara = aIdxNext.GetNode();
3272     const SwContentNode* pNode = rNdTocPara.GetContentNode();
3273     if (!pNode)
3274         return;
3275 
3276     // put required flags into grab bag of the first node in TOC
3277     {
3278         uno::Sequence<beans::PropertyValue> aDocPropertyValues(comphelper::InitPropertySequence(
3279         {
3280             {"ooxml:CT_SdtDocPart_docPartGallery", uno::makeAny(OUString("Table of Contents"))},
3281             {"ooxml:CT_SdtDocPart_docPartUnique",  uno::makeAny(OUString("true"))},
3282         }));
3283 
3284         uno::Sequence<beans::PropertyValue> aSdtPrPropertyValues(comphelper::InitPropertySequence(
3285         {
3286             {"ooxml:CT_SdtPr_docPartObj", uno::makeAny(aDocPropertyValues)},
3287         }));
3288 
3289         SfxGrabBagItem aGrabBag(RES_PARATR_GRABBAG);
3290         aGrabBag.GetGrabBag()["SdtPr"] <<= aSdtPrPropertyValues;
3291 
3292         // create temp attr set
3293         SwAttrSet aSet(pNode->GetSwAttrSet());
3294         aSet.Put(aGrabBag);
3295 
3296         // set new attr to node
3297         const_cast<SwContentNode*>(pNode)->SetAttr(aSet);
3298     }
3299 
3300     // set flag for the next node after TOC
3301     // in order to indicate that std area has been finished
3302     // see, DomainMapper::lcl_startParagraphGroup() for the same functionality during load
3303     {
3304         SwNodeIndex aEndTocNext( *rSectionNode.EndOfSectionNode(), 1 );
3305         const SwNode& rEndTocNextNode = aEndTocNext.GetNode();
3306         const SwContentNode* pNodeAfterToc = rEndTocNextNode.GetContentNode();
3307         if (pNodeAfterToc)
3308         {
3309             SfxGrabBagItem aGrabBag(RES_PARATR_GRABBAG);
3310             aGrabBag.GetGrabBag()["ParaSdtEndBefore"] <<= true;
3311 
3312             // create temp attr set
3313             SwAttrSet aSet(pNodeAfterToc->GetSwAttrSet());
3314             aSet.Put(aGrabBag);
3315 
3316             // set new attr to node
3317             const_cast<SwContentNode*>(pNodeAfterToc)->SetAttr(aSet);
3318         }
3319     }
3320 }
3321 
AppendSection(const SwPageDesc * pPageDesc,const SwSectionFormat * pFormat,sal_uLong nLnNum)3322 void WW8Export::AppendSection( const SwPageDesc *pPageDesc, const SwSectionFormat* pFormat, sal_uLong nLnNum )
3323 {
3324     pSepx->AppendSep(Fc2Cp(Strm().Tell()), pPageDesc, pFormat, nLnNum);
3325 }
3326 
3327 // Flys
3328 
OutputFlyFrame_Impl(const ww8::Frame & rFormat,const Point & rNdTopLeft)3329 void WW8AttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFormat, const Point& rNdTopLeft )
3330 {
3331     const SwFrameFormat &rFrameFormat = rFormat.GetFrameFormat();
3332     const SwFormatAnchor& rAnch = rFrameFormat.GetAnchor();
3333 
3334     bool bUseEscher = true;
3335 
3336     if (rFormat.IsInline())
3337     {
3338         ww8::Frame::WriterSource eType = rFormat.GetWriterType();
3339         bUseEscher = eType != ww8::Frame::eGraphic && eType != ww8::Frame::eOle;
3340 
3341         /*
3342          A special case for converting some inline form controls to form fields
3343          when in winword 8+ mode
3344         */
3345         if (bUseEscher && (eType == ww8::Frame::eFormControl))
3346         {
3347             if ( m_rWW8Export.MiserableFormFieldExportHack( rFrameFormat ) )
3348                 return ;
3349         }
3350     }
3351 
3352     if (bUseEscher)
3353     {
3354         // write as escher
3355         m_rWW8Export.AppendFlyInFlys(rFormat, rNdTopLeft);
3356     }
3357     else
3358     {
3359         bool bDone = false;
3360 
3361         // Fetch from node and last node the position in the section
3362         const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
3363 
3364         sal_uLong nStt = pNodeIndex ? pNodeIndex->GetIndex()+1                  : 0;
3365         sal_uLong nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : 0;
3366 
3367         if( nStt >= nEnd )      // no range, hence no valid node
3368             return;
3369 
3370         if ( !m_rWW8Export.IsInTable() && rFormat.IsInline() )
3371         {
3372             //Test to see if this textbox contains only a single graphic/ole
3373             SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
3374             if ( pParTextNode && !m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode() )
3375                 bDone = true;
3376         }
3377         if( !bDone )
3378         {
3379 
3380             m_rWW8Export.SaveData( nStt, nEnd );
3381 
3382             Point aOffset;
3383             if ( m_rWW8Export.m_pParentFrame )
3384             {
3385                 /* Munge flys in fly into absolutely positioned elements for word 6 */
3386                 const SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
3387                 const SwRect aPageRect = pParTextNode->FindPageFrameRect();
3388 
3389                 aOffset = rFrameFormat.FindLayoutRect().Pos();
3390                 aOffset -= aPageRect.Pos();
3391 
3392                 m_rWW8Export.m_pFlyOffset = &aOffset;
3393                 m_rWW8Export.m_eNewAnchorType = RndStdIds::FLY_AT_PAGE;
3394             }
3395 
3396             m_rWW8Export.m_pParentFrame = &rFormat;
3397             if (
3398                 m_rWW8Export.IsInTable() &&
3399                  (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) &&
3400                  !m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode()
3401                )
3402             {
3403                 // note: set Flag  bOutTable again,
3404                 // because we deliver the normal content of the table cell, and no border
3405                 // ( Flag was deleted above in aSaveData() )
3406                 m_rWW8Export.m_bOutTable = true;
3407                 const OUString& aName = rFrameFormat.GetName();
3408                 m_rWW8Export.StartCommentOutput(aName);
3409                 m_rWW8Export.WriteText();
3410                 m_rWW8Export.EndCommentOutput(aName);
3411             }
3412             else
3413                 m_rWW8Export.WriteText();
3414 
3415             m_rWW8Export.RestoreData();
3416         }
3417     }
3418 }
3419 
OutputFlyFrame(const ww8::Frame & rFormat)3420 void AttributeOutputBase::OutputFlyFrame( const ww8::Frame& rFormat )
3421 {
3422     if ( !rFormat.GetContentNode() )
3423         return;
3424 
3425     const SwContentNode &rNode = *rFormat.GetContentNode();
3426     Point aLayPos;
3427 
3428     // get the Layout Node-Position
3429     if (RndStdIds::FLY_AT_PAGE == rFormat.GetFrameFormat().GetAnchor().GetAnchorId())
3430         aLayPos = rNode.FindPageFrameRect().Pos();
3431     else
3432         aLayPos = rNode.FindLayoutRect().Pos();
3433 
3434     OutputFlyFrame_Impl( rFormat, aLayPos );
3435 }
3436 
3437 // write data of any redline
Redline(const SwRedlineData * pRedline)3438 void WW8AttributeOutput::Redline( const SwRedlineData* pRedline )
3439 {
3440     if ( !pRedline )
3441         return;
3442 
3443     if ( pRedline->Next() )
3444         Redline( pRedline->Next() );
3445 
3446     static const sal_uInt16 insSprmIds[ 3 ] =
3447     {
3448         // Ids for insert // for WW8
3449         NS_sprm::sprmCFRMarkIns, NS_sprm::sprmCIbstRMark, NS_sprm::sprmCDttmRMark,
3450     };
3451     static const sal_uInt16 delSprmIds[ 3 ] =
3452     {
3453         // Ids for delete // for WW8
3454         NS_sprm::sprmCFRMarkDel, NS_sprm::sprmCIbstRMarkDel, NS_sprm::sprmCDttmRMarkDel,
3455     };
3456 
3457     const sal_uInt16* pSprmIds = nullptr;
3458     switch( pRedline->GetType() )
3459     {
3460     case RedlineType::Insert:
3461         pSprmIds = insSprmIds;
3462         break;
3463 
3464     case RedlineType::Delete:
3465         pSprmIds = delSprmIds;
3466         break;
3467 
3468     case RedlineType::Format:
3469         m_rWW8Export.InsUInt16( NS_sprm::sprmCPropRMark90 );
3470         m_rWW8Export.pO->push_back( 7 );       // len
3471         m_rWW8Export.pO->push_back( 1 );
3472         m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
3473         m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
3474         break;
3475     default:
3476         OSL_ENSURE(false, "Unhandled redline type for export");
3477         break;
3478     }
3479 
3480     if ( pSprmIds )
3481     {
3482         m_rWW8Export.InsUInt16( pSprmIds[0] );
3483         m_rWW8Export.pO->push_back( 1 );
3484 
3485         m_rWW8Export.InsUInt16( pSprmIds[1] );
3486         m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
3487 
3488         m_rWW8Export.InsUInt16( pSprmIds[2] );
3489         m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
3490     }
3491 }
3492 
OutputContentNode(SwContentNode & rNode)3493 void MSWordExportBase::OutputContentNode( SwContentNode& rNode )
3494 {
3495     switch ( rNode.GetNodeType() )
3496     {
3497         case SwNodeType::Text:
3498             OutputTextNode( *rNode.GetTextNode() );
3499             break;
3500         case SwNodeType::Grf:
3501             OutputGrfNode( *rNode.GetGrfNode() );
3502             break;
3503         case SwNodeType::Ole:
3504             OutputOLENode( *rNode.GetOLENode() );
3505             break;
3506         default:
3507             SAL_WARN("sw.ww8", "Unhandled node, type == " << static_cast<int>(rNode.GetNodeType()) );
3508             break;
3509     }
3510 }
3511 
3512 
WW8Ruby(const SwTextNode & rNode,const SwFormatRuby & rRuby,const MSWordExportBase & rExport)3513 WW8Ruby::WW8Ruby(const SwTextNode& rNode, const SwFormatRuby& rRuby, const MSWordExportBase& rExport ):
3514     m_nJC(0),
3515     m_cDirective(0),
3516     m_nRubyHeight(0),
3517     m_nBaseHeight(0)
3518 {
3519     switch ( rRuby.GetAdjustment() )
3520     {
3521         case css::text::RubyAdjust_LEFT:
3522             m_nJC = 3;
3523             m_cDirective = 'l';
3524             break;
3525         case css::text::RubyAdjust_CENTER:
3526             //defaults to 0
3527             break;
3528         case css::text::RubyAdjust_RIGHT:
3529             m_nJC = 4;
3530             m_cDirective = 'r';
3531             break;
3532         case css::text::RubyAdjust_BLOCK:
3533             m_nJC = 1;
3534             m_cDirective = 'd';
3535             break;
3536         case css::text::RubyAdjust_INDENT_BLOCK:
3537             m_nJC = 2;
3538             m_cDirective = 'd';
3539             break;
3540         default:
3541             OSL_ENSURE( false,"Unhandled Ruby justification code" );
3542             break;
3543     }
3544 
3545     if ( rRuby.GetPosition() == css::text::RubyPosition::INTER_CHARACTER )
3546     {
3547         m_nJC = 5;
3548         m_cDirective = 0;
3549     }
3550 
3551     /*
3552      MS needs to know the name and size of the font used in the ruby item,
3553      but we could have written it in a mixture of asian and western
3554      scripts, and each of these can be a different font and size than the
3555      other, so we make a guess based upon the first character of the text,
3556      defaulting to asian.
3557      */
3558     assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
3559     sal_uInt16 nRubyScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
3560 
3561     const SwTextRuby* pRubyText = rRuby.GetTextRuby();
3562     const SwCharFormat* pFormat = pRubyText ? pRubyText->GetCharFormat() : nullptr;
3563 
3564     if (pFormat)
3565     {
3566         const auto& rFont
3567             = ItemGet<SvxFontItem>(*pFormat, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
3568         m_sFontFamily = rFont.GetFamilyName();
3569 
3570         const auto& rHeight = ItemGet<SvxFontHeightItem>(
3571             *pFormat, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
3572         m_nRubyHeight = rHeight.GetHeight();
3573     }
3574     else
3575     {
3576         /*Get defaults if no formatting on ruby text*/
3577 
3578         const SfxItemPool* pPool = rNode.GetSwAttrSet().GetPool();
3579         pPool = pPool ? pPool : &rExport.m_pDoc->GetAttrPool();
3580 
3581 
3582         const auto& rFont
3583             = DefaultItemGet<SvxFontItem>(*pPool, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
3584         m_sFontFamily = rFont.GetFamilyName();
3585 
3586         const auto& rHeight = DefaultItemGet<SvxFontHeightItem>(
3587             *pPool, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
3588         m_nRubyHeight = rHeight.GetHeight();
3589     }
3590 
3591     if (pRubyText)
3592         nRubyScript
3593             = g_pBreakIt->GetBreakIter()->getScriptType(rNode.GetText(), pRubyText->GetStart());
3594     else
3595         nRubyScript = i18n::ScriptType::ASIAN;
3596 
3597     const OUString &rText = rNode.GetText();
3598     sal_uInt16 nScript = i18n::ScriptType::LATIN;
3599 
3600     if (!rText.isEmpty())
3601         nScript = g_pBreakIt->GetBreakIter()->getScriptType(rText, 0);
3602 
3603     sal_uInt16 nWhich = GetWhichOfScript(RES_CHRATR_FONTSIZE, nScript);
3604     auto& rHeightItem = static_cast<const SvxFontHeightItem&>(rExport.GetItem(nWhich));
3605     m_nBaseHeight = rHeightItem.GetHeight();
3606 }
3607 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3608