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