/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <vector>
#include <list>
#include <utility>
#include <algorithm>
#include <iostream>
#include <oox/core/filterbase.hxx>
#include "docxexport.hxx"
#include "docxexportfilter.hxx"
#include <i18nlangtag/mslangid.hxx>
#include <hintids.hxx>
#include <tools/urlobj.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/cmapitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/svxfont.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/keepitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/formatbreakitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/tstpitem.hxx>
#include <svl/grabbagitem.hxx>
#include <svl/urihelper.hxx>
#include <svl/whiter.hxx>
#include <fmtpdsc.hxx>
#include <fmtfsize.hxx>
#include <fmtornt.hxx>
#include <fmtlsplt.hxx>
#include <fmtflcnt.hxx>
#include <fmtanchr.hxx>
#include <fmtcntnt.hxx>
#include <frmatr.hxx>
#include <paratr.hxx>
#include <txatbase.hxx>
#include <fmtinfmt.hxx>
#include <fmtrfmrk.hxx>
#include <fchrfmt.hxx>
#include <fmtautofmt.hxx>
#include <charfmt.hxx>
#include <tox.hxx>
#include <ndtxt.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <IDocumentSettingAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <docary.hxx>
#include <swtable.hxx>
#include <swtblfmt.hxx>
#include <section.hxx>
#include <pagedesc.hxx>
#include <swrect.hxx>
#include <reffld.hxx>
#include <redline.hxx>
#include <wrtswtbl.hxx>
#include <htmltbl.hxx>
#include <txttxmrk.hxx>
#include <fmtline.hxx>
#include <fmtruby.hxx>
#include <breakit.hxx>
#include <txtatr.hxx>
#include <fmtsrnd.hxx>
#include <fmtrowsplt.hxx>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/text/RubyPosition.hpp>
#include <oox/export/vmlexport.hxx>
#include <sfx2/docfile.hxx>
#include <sal/log.hxx>
#include "sprmids.hxx"
#include "writerhelper.hxx"
#include "writerwordglue.hxx"
#include <numrule.hxx>
#include "wrtww8.hxx"
#include "ww8par.hxx"
#include <IMark.hxx>
#include "ww8attributeoutput.hxx"
#include <ndgrf.hxx>
#include <ndole.hxx>
#include <cstdio>
using namespace ::com::sun::star;
using namespace ::com::sun::star::i18n;
using namespace sw::util;
using namespace sw::types;
using namespace sw::mark;
using namespace ::oox::vml;
static OUString lcl_getFieldCode( const IFieldmark* pFieldmark )
{
OSL_ENSURE(pFieldmark!=nullptr, "where is my fieldmark???");
if ( !pFieldmark)
return OUString();
if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
return OUString(" FORMTEXT ");
if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
return OUString(" FORMDROPDOWN ");
if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
return OUString(" FORMCHECKBOX ");
if ( pFieldmark->GetFieldname( ) == ODF_TOC )
return OUString(" TOC ");
if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
return OUString(" HYPERLINK ");
if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
return OUString(" PAGEREF ");
return pFieldmark->GetFieldname();
}
static ww::eField lcl_getFieldId( const IFieldmark* pFieldmark ) {
OSL_ENSURE(pFieldmark!=nullptr, "where is my fieldmark???");
if ( !pFieldmark )
return ww::eUNKNOWN;
if ( pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
return ww::eFORMTEXT;
if ( pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN )
return ww::eFORMDROPDOWN;
if ( pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX )
return ww::eFORMCHECKBOX;
if ( pFieldmark->GetFieldname( ) == ODF_TOC )
return ww::eTOC;
if ( pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
return ww::eHYPERLINK;
if ( pFieldmark->GetFieldname( ) == ODF_PAGEREF )
return ww::ePAGEREF;
return ww::eUNKNOWN;
}
MSWordAttrIter::MSWordAttrIter( MSWordExportBase& rExport )
: pOld( rExport.m_pChpIter ), m_rExport( rExport )
{
m_rExport.m_pChpIter = this;
}
MSWordAttrIter::~MSWordAttrIter()
{
m_rExport.m_pChpIter = pOld;
}
class sortswflys
{
public:
bool operator()(const ww8::Frame &rOne, const ww8::Frame &rTwo) const
{
return rOne.GetPosition() < rTwo.GetPosition();
}
};
void SwWW8AttrIter::IterToCurrent()
{
OSL_ENSURE(maCharRuns.begin() != maCharRuns.end(), "Impossible");
mnScript = maCharRunIter->mnScript;
meChrSet = maCharRunIter->meCharSet;
mbCharIsRTL = maCharRunIter->mbRTL;
}
SwWW8AttrIter::SwWW8AttrIter(MSWordExportBase& rWr, const SwTextNode& rTextNd) :
MSWordAttrIter(rWr),
rNd(rTextNd),
maCharRuns(GetPseudoCharRuns(rTextNd)),
pCurRedline(nullptr),
nCurrentSwPos(0),
nCurRedlinePos(SwRedlineTable::npos),
mrSwFormatDrop(rTextNd.GetSwAttrSet().GetDrop())
{
SwPosition aPos(rTextNd);
if (SvxFrameDirection::Horizontal_RL_TB == rWr.m_pDoc->GetTextDirection(aPos))
mbParaIsRTL = true;
else
mbParaIsRTL = false;
maCharRunIter = maCharRuns.begin();
IterToCurrent();
/*
#i2916#
Get list of any graphics which may be anchored from this paragraph.
*/
maFlyFrames = GetFramesInNode(rWr.m_aFrames, rNd);
std::sort(maFlyFrames.begin(), maFlyFrames.end(), sortswflys());
/*
#i18480#
If we are inside a frame then anything anchored inside this frame can
only be supported by word anchored inline ("as character"), so force
this in the supportable case.
*/
if (rWr.m_bInWriteEscher)
{
for ( auto& aFlyFrame : maFlyFrames )
aFlyFrame.ForceTreatAsInline();
}
maFlyIter = maFlyFrames.begin();
if ( !m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
{
SwPosition aPosition( rNd, SwIndex( const_cast<SwTextNode*>(&rNd) ) );
pCurRedline = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedline( aPosition, &nCurRedlinePos );
}
nCurrentSwPos = SearchNext(1);
}
sal_Int32 lcl_getMinPos( sal_Int32 pos1, sal_Int32 pos2 )
{
if ( pos1 >= 0 && pos2 >= 0 )
{
// both valid: return minimum one
return std::min(pos1, pos2);
}
// return the valid one, if any, or -1
return std::max(pos1, pos2);
}
sal_Int32 SwWW8AttrIter::SearchNext( sal_Int32 nStartPos )
{
const OUString aText = rNd.GetText();
sal_Int32 fieldEndPos = aText.indexOf(CH_TXT_ATR_FIELDEND, nStartPos);
sal_Int32 fieldStartPos = aText.indexOf(CH_TXT_ATR_FIELDSTART, nStartPos);
sal_Int32 formElementPos = aText.indexOf(CH_TXT_ATR_FORMELEMENT, nStartPos);
const sal_Int32 pos = lcl_getMinPos(
lcl_getMinPos( fieldEndPos, fieldStartPos ),
formElementPos );
sal_Int32 nMinPos = (pos>=0) ? pos : SAL_MAX_INT32;
// first the redline, then the attributes
if( pCurRedline )
{
const SwPosition* pEnd = pCurRedline->End();
if (pEnd->nNode == rNd)
{
const sal_Int32 i = pEnd->nContent.GetIndex();
if ( i >= nStartPos && i < nMinPos )
{
nMinPos = i;
}
}
}
if ( nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size() )
{
// nCurRedlinePos point to the next redline
SwRedlineTable::size_type nRedLinePos = nCurRedlinePos;
if( pCurRedline )
++nRedLinePos;
for ( ; nRedLinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nRedLinePos )
{
const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nRedLinePos ];
const SwPosition* pStt = pRedl->Start();
const SwPosition* pEnd = pStt == pRedl->GetPoint()
? pRedl->GetMark()
: pRedl->GetPoint();
if( pStt->nNode == rNd )
{
const sal_Int32 i = pStt->nContent.GetIndex();
if( i >= nStartPos && i < nMinPos )
nMinPos = i;
}
else
break;
if( pEnd->nNode == rNd )
{
const sal_Int32 i = pEnd->nContent.GetIndex();
if( i >= nStartPos && i < nMinPos )
{
nMinPos = i;
}
}
}
}
if (mrSwFormatDrop.GetWholeWord() && nStartPos <= rNd.GetDropLen(0))
nMinPos = rNd.GetDropLen(0);
else if(nStartPos <= mrSwFormatDrop.GetChars())
nMinPos = mrSwFormatDrop.GetChars();
if(const SwpHints* pTextAttrs = rNd.GetpSwpHints())
{
// can be optimized if we consider that the TextAttrs are sorted by start position.
// but then we'd have to save 2 indices
for( size_t i = 0; i < pTextAttrs->Count(); ++i )
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
sal_Int32 nPos = pHt->GetStart(); // first Attr characters
if( nPos >= nStartPos && nPos <= nMinPos )
nMinPos = nPos;
if( pHt->End() ) // Attr with end
{
nPos = *pHt->End(); // last Attr character + 1
if( nPos >= nStartPos && nPos <= nMinPos )
nMinPos = nPos;
}
if (pHt->HasDummyChar())
{
// pos + 1 because of CH_TXTATR in Text
nPos = pHt->GetStart() + 1;
if( nPos >= nStartPos && nPos <= nMinPos )
nMinPos = nPos;
}
}
}
if (maCharRunIter != maCharRuns.end())
{
if (maCharRunIter->mnEndPos < nMinPos)
nMinPos = maCharRunIter->mnEndPos;
IterToCurrent();
}
/*
#i2916#
Check to see if there are any graphics anchored to characters in this
paragraph's text. Set nMinPos to 1 past the placement for anchored to
character because anchors in Word appear after the character they are
anchored to.
*/
if (maFlyIter != maFlyFrames.end())
{
const SwPosition &rAnchor = maFlyIter->GetPosition();
sal_Int32 nPos = rAnchor.nContent.GetIndex();
if (nPos >= nStartPos && nPos <= nMinPos)
nMinPos = nPos;
if (maFlyIter->GetFrameFormat().GetAnchor().GetAnchorId() == RndStdIds::FLY_AT_CHAR)
{
++nPos;
if (nPos >= nStartPos && nPos <= nMinPos)
nMinPos = nPos;
}
}
//nMinPos found and not going to change at this point
if (maCharRunIter != maCharRuns.end())
{
if (maCharRunIter->mnEndPos == nMinPos)
++maCharRunIter;
}
return nMinPos;
}
void SwWW8AttrIter::OutAttr( sal_Int32 nSwPos, bool bWriteCombChars)
{
m_rExport.AttrOutput().RTLAndCJKState( mbCharIsRTL, GetScript() );
/*
Depending on whether text is in CTL/CJK or Western, get the id of that
script, the idea is that the font that is actually in use to render this
range of text ends up in pFont
*/
sal_uInt16 nFontId = GetWhichOfScript( RES_CHRATR_FONT, GetScript() );
const SvxFontItem &rParentFont = ItemGet<SvxFontItem>(
static_cast<const SwTextFormatColl&>(rNd.GetAnyFormatColl()), nFontId);
const SvxFontItem *pFont = &rParentFont;
const SfxPoolItem *pGrabBag = nullptr;
SfxItemSet aExportSet(*rNd.GetSwAttrSet().GetPool(),
svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END - 1>{});
//The hard formatting properties that affect the entire paragraph
if (rNd.HasSwAttrSet())
{
// only copy hard attributes - bDeep = false
aExportSet.Set(rNd.GetSwAttrSet(), false/*bDeep*/);
// get the current font item. Use rNd.GetSwAttrSet instead of aExportSet:
const SvxFontItem &rNdFont = ItemGet<SvxFontItem>(rNd.GetSwAttrSet(), nFontId);
pFont = &rNdFont;
aExportSet.ClearItem(nFontId);
}
//The additional hard formatting properties that affect this range in the
//paragraph
ww8::PoolItems aRangeItems;
if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
{
for( size_t i = 0; i < pTextAttrs->Count(); ++i )
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
const sal_Int32* pEnd = pHt->End();
if (pEnd ? ( nSwPos >= pHt->GetStart() && nSwPos < *pEnd)
: nSwPos == pHt->GetStart() )
{
sal_uInt16 nWhich = pHt->GetAttr().Which();
if (nWhich == RES_TXTATR_AUTOFMT)
{
const SwFormatAutoFormat& rAutoFormat = static_cast<const SwFormatAutoFormat&>(pHt->GetAttr());
const std::shared_ptr<SfxItemSet>& pSet = rAutoFormat.GetStyleHandle();
SfxWhichIter aIter( *pSet );
const SfxPoolItem* pItem;
sal_uInt16 nWhichId = aIter.FirstWhich();
while( nWhichId )
{
if( SfxItemState::SET == pSet->GetItemState( nWhichId, false, &pItem ))
{
if (nWhichId == nFontId)
pFont = &(item_cast<SvxFontItem>(*pItem));
else if (nWhichId == RES_CHRATR_GRABBAG)
pGrabBag = pItem;
else
aRangeItems[nWhichId] = pItem;
}
nWhichId = aIter.NextWhich();
}
}
else
aRangeItems[nWhich] = (&(pHt->GetAttr()));
}
else if (nSwPos < pHt->GetStart())
break;
}
}
/*
For #i24291# we need to explicitly remove any properties from the
aExportSet which a SwCharFormat would override, we can't rely on word doing
this for us like writer does
*/
const SwFormatCharFormat *pCharFormatItem =
HasItem< SwFormatCharFormat >( aRangeItems, RES_TXTATR_CHARFMT );
if ( pCharFormatItem )
ClearOverridesFromSet( *pCharFormatItem, aExportSet );
// tdf#113790: AutoFormat style overwrites char style, so remove all
// elements from CHARFMT grab bag which are set in AUTOFMT grab bag
if (const SfxGrabBagItem *pAutoFmtGrabBag = dynamic_cast<const SfxGrabBagItem*>(pGrabBag))
{
if (const SfxGrabBagItem *pCharFmtGrabBag = aExportSet.GetItem<SfxGrabBagItem>(RES_CHRATR_GRABBAG, false))
{
std::unique_ptr<SfxPoolItem> pNewItem(pCharFmtGrabBag->Clone());
SfxGrabBagItem* pNewCharFmtGrabBag = dynamic_cast<SfxGrabBagItem*>(pNewItem.get());
assert(pNewCharFmtGrabBag);
auto & rNewFmtMap = pNewCharFmtGrabBag->GetGrabBag();
for (auto const & item : pAutoFmtGrabBag->GetGrabBag())
{
if (item.second.hasValue())
rNewFmtMap.erase(item.first);
}
aExportSet.Put(*pNewCharFmtGrabBag);
}
}
ww8::PoolItems aExportItems;
GetPoolItems( aExportSet, aExportItems, false );
if( rNd.GetpSwpHints() == nullptr )
m_rExport.SetCurItemSet(&aExportSet);
for ( const auto& aRangeItem : aRangeItems )
{
aExportItems[aRangeItem.first] = aRangeItem.second;
}
if ( !aExportItems.empty() )
{
const SwModify* pOldMod = m_rExport.m_pOutFormatNode;
m_rExport.m_pOutFormatNode = &rNd;
m_rExport.m_aCurrentCharPropStarts.push( nSwPos );
// tdf#38778 Fix output of the font in DOC run for fields
const SvxFontItem * pFontToOutput = ( rParentFont != *pFont )? pFont : nullptr;
m_rExport.ExportPoolItemsToCHP( aExportItems, GetScript(), pFontToOutput, bWriteCombChars );
// HasTextItem only allowed in the above range
m_rExport.m_aCurrentCharPropStarts.pop();
m_rExport.m_pOutFormatNode = pOldMod;
}
if( rNd.GetpSwpHints() == nullptr )
m_rExport.SetCurItemSet(nullptr);
OSL_ENSURE( pFont, "must be *some* font associated with this txtnode" );
if ( pFont )
{
SvxFontItem aFont( *pFont );
if ( rParentFont != aFont )
m_rExport.AttrOutput().OutputItem( aFont );
}
// Output grab bag attributes
if (pGrabBag)
m_rExport.AttrOutput().OutputItem( *pGrabBag );
}
bool SwWW8AttrIter::IsWatermarkFrame()
{
if (maFlyFrames.size() != 1)
return false;
while ( maFlyIter != maFlyFrames.end() )
{
const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
if (pSdrObj)
{
if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
return true;
}
++maFlyIter;
}
return false;
}
bool SwWW8AttrIter::IsAnchorLinkedToThisNode( sal_uLong nNodePos )
{
ww8::FrameIter aTmpFlyIter = maFlyIter ;
while ( aTmpFlyIter != maFlyFrames.end() )
{
const SwPosition &rAnchor = maFlyIter->GetPosition();
sal_uLong nAnchorPos = rAnchor.nNode.GetIndex();
/* if current node position and the anchor position are the same
then the frame anchor is linked to this node
*/
if ( nAnchorPos == nNodePos )
return true ;
++aTmpFlyIter;
}
return false ;
}
FlyProcessingState SwWW8AttrIter::OutFlys(sal_Int32 nSwPos)
{
// collection point to first gather info about all of the potentially linked textboxes: to be analyzed later.
OUString sLinkChainName;
ww8::FrameIter linkedTextboxesIter = maFlyIter;
while ( linkedTextboxesIter != maFlyFrames.end() )
{
uno::Reference< drawing::XShape > xShape;
ww8::Frame aFrame = *linkedTextboxesIter;
const SdrObject* pSdrObj = aFrame.GetFrameFormat().FindRealSdrObject();
if( pSdrObj )
xShape.set(const_cast<SdrObject*>(pSdrObj)->getUnoShape(), uno::UNO_QUERY);
uno::Reference< beans::XPropertySet > xPropertySet(xShape, uno::UNO_QUERY);
uno::Reference< beans::XPropertySetInfo > xPropertySetInfo;
if( xPropertySet.is() )
xPropertySetInfo = xPropertySet->getPropertySetInfo();
if( xPropertySetInfo.is() )
{
MSWordExportBase::LinkedTextboxInfo aLinkedTextboxInfo = MSWordExportBase::LinkedTextboxInfo();
if( xPropertySetInfo->hasPropertyByName("LinkDisplayName") )
xPropertySet->getPropertyValue("LinkDisplayName") >>= sLinkChainName;
else if( xPropertySetInfo->hasPropertyByName("ChainName") )
xPropertySet->getPropertyValue("ChainName") >>= sLinkChainName;
if( xPropertySetInfo->hasPropertyByName("ChainNextName") )
xPropertySet->getPropertyValue("ChainNextName") >>= aLinkedTextboxInfo.sNextChain;
if( xPropertySetInfo->hasPropertyByName("ChainPrevName") )
xPropertySet->getPropertyValue("ChainPrevName") >>= aLinkedTextboxInfo.sPrevChain;
//collect a list of linked textboxes: those with a NEXT or PREVIOUS link
if( !aLinkedTextboxInfo.sNextChain.isEmpty() || !aLinkedTextboxInfo.sPrevChain.isEmpty() )
{
assert( !sLinkChainName.isEmpty() );
//there are many discarded duplicates in documents - no duplicates allowed in the list, so try to find the real one.
//if this LinkDisplayName/ChainName already exists on a different shape...
// the earlier processed duplicates are thrown out unless this one can be proved as bad. (last processed duplicate usually is stored)
std::map<OUString,MSWordExportBase::LinkedTextboxInfo>::iterator linkFinder;
linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(sLinkChainName);
if( linkFinder != m_rExport.m_aLinkedTextboxesHelper.end() )
{
//If my NEXT/PREV targets have already been discovered, but don't match me, then assume I'm an abandoned remnant
// (this logic fails if both me and one of my links are duplicated, and the remnants were added first.)
linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sNextChain);
if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sPrevChain != sLinkChainName) )
{
++linkedTextboxesIter;
break;
}
linkFinder = m_rExport.m_aLinkedTextboxesHelper.find(aLinkedTextboxInfo.sPrevChain);
if( (linkFinder != m_rExport.m_aLinkedTextboxesHelper.end()) && (linkFinder->second.sNextChain != sLinkChainName) )
{
++linkedTextboxesIter;
break;
}
}
m_rExport.m_bLinkedTextboxesHelperInitialized = false;
m_rExport.m_aLinkedTextboxesHelper[sLinkChainName] = aLinkedTextboxInfo;
}
}
++linkedTextboxesIter;
}
/*
#i2916#
May have an anchored graphic to be placed, loop through sorted array
and output all at this position
*/
while ( maFlyIter != maFlyFrames.end() )
{
const SwPosition &rAnchor = maFlyIter->GetPosition();
const sal_Int32 nPos = rAnchor.nContent.GetIndex();
if ( nPos != nSwPos )
return FLY_NOT_PROCESSED ; // We haven't processed the fly
const SdrObject* pSdrObj = maFlyIter->GetFrameFormat().FindRealSdrObject();
if (pSdrObj)
{
if (VMLExport::IsWaterMarkShape(pSdrObj->GetName()))
{
// This is a watermark object. Should be written ONLY in the header
if(m_rExport.m_nTextTyp == TXT_HDFT)
{
// Should write a watermark in the header
m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
}
else
{
// Should not write watermark object in the main body text
}
}
else
{
// This is not a watermark object - write normally
m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
}
}
else
{
// This is not a watermark object - write normally
m_rExport.AttrOutput().OutputFlyFrame( *maFlyIter );
}
++maFlyIter;
}
return ( m_rExport.AttrOutput().IsFlyProcessingPostponed() ? FLY_POSTPONED : FLY_PROCESSED ) ;
}
bool SwWW8AttrIter::IsTextAttr( sal_Int32 nSwPos ) const
{
// search for attrs with dummy character or content
if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
{
for (size_t i = 0; i < pTextAttrs->Count(); ++i)
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
if (nSwPos == pHt->GetStart())
{
if (pHt->HasDummyChar() || pHt->HasContent() )
{
return true;
}
}
else if (nSwPos < pHt->GetStart())
{
break; // sorted by start
}
}
}
return false;
}
bool SwWW8AttrIter::IsExportableAttr(sal_Int32 nSwPos) const
{
if (const SwpHints* pTextAttrs = rNd.GetpSwpHints())
{
for (size_t i = 0; i < pTextAttrs->Count(); ++i)
{
const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
const sal_Int32 nStart = pHt->GetStart();
const sal_Int32 nEnd = pHt->End() ? *pHt->End() : INT_MAX;
if (nSwPos >= nStart && nSwPos < nEnd)
{
switch (pHt->GetAttr().Which())
{
// Metadata fields should be dynamically generated, not dumped as text.
case RES_TXTATR_METAFIELD:
return false;
}
}
}
}
return true;
}
bool SwWW8AttrIter::IsDropCap( int nSwPos )
{
// see if the current position falls on a DropCap
int nDropChars = mrSwFormatDrop.GetChars();
bool bWholeWord = mrSwFormatDrop.GetWholeWord();
if (bWholeWord)
{
const sal_Int32 nWordLen = rNd.GetDropLen(0);
if(nSwPos == nWordLen && nSwPos != 0)
return true;
}
else
{
if (nSwPos == nDropChars && nSwPos != 0)
return true;
}
return false;
}
bool SwWW8AttrIter::RequiresImplicitBookmark()
{
std::vector<aBookmarkPair>::iterator bkmkIterEnd = m_rExport.m_aImplicitBookmarks.end();
for ( std::vector<aBookmarkPair>::iterator aIter = m_rExport.m_aImplicitBookmarks.begin(); aIter != bkmkIterEnd; ++aIter )
{
sal_uLong sample = aIter->second;
if ( sample == rNd.GetIndex() )
return true;
}
return false;
}
//HasItem is for the summary of the double attributes: Underline and WordlineMode as TextItems.
// OutAttr () calls the output function, which can call HasItem() for other items at the attribute's start position.
// Only attributes with end can be queried.
// It searches with bDeep
const SfxPoolItem* SwWW8AttrIter::HasTextItem( sal_uInt16 nWhich ) const
{
const SfxPoolItem* pRet = nullptr;
const SwpHints* pTextAttrs = rNd.GetpSwpHints();
if (pTextAttrs && !m_rExport.m_aCurrentCharPropStarts.empty())
{
const sal_Int32 nTmpSwPos = m_rExport.m_aCurrentCharPropStarts.top();
for (size_t i = 0; i < pTextAttrs->Count(); ++i)
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
const SfxPoolItem* pItem = &pHt->GetAttr();
const sal_Int32 * pAtrEnd = nullptr;
if( nullptr != ( pAtrEnd = pHt->End() ) && // only Attr with an end
nTmpSwPos >= pHt->GetStart() && nTmpSwPos < *pAtrEnd )
{
if ( nWhich == pItem->Which() )
{
pRet = pItem; // found it
break;
}
else if( RES_TXTATR_INETFMT == pHt->Which() ||
RES_TXTATR_CHARFMT == pHt->Which() ||
RES_TXTATR_AUTOFMT == pHt->Which() )
{
const SfxItemSet* pSet = CharFormat::GetItemSet( pHt->GetAttr() );
const SfxPoolItem* pCharItem;
if ( pSet &&
SfxItemState::SET == pSet->GetItemState( nWhich, pHt->Which() != RES_TXTATR_AUTOFMT, &pCharItem ) )
{
pRet = pCharItem; // found it
break;
}
}
}
else if (nTmpSwPos < pHt->GetStart())
break; // nothing more to come
}
}
return pRet;
}
void WW8Export::GetCurrentItems(ww::bytes &rItems) const
{
rItems.insert(rItems.end(), pO->begin(), pO->end());
}
const SfxPoolItem& SwWW8AttrIter::GetItem(sal_uInt16 nWhich) const
{
const SfxPoolItem* pRet = HasTextItem(nWhich);
return pRet ? *pRet : rNd.SwContentNode::GetAttr(nWhich);
}
void WW8AttributeOutput::StartRuby( const SwTextNode& rNode, sal_Int32 /*nPos*/, const SwFormatRuby& rRuby )
{
WW8Ruby aWW8Ruby(rNode, rRuby, GetExport());
OUString aStr( FieldString( ww::eEQ ) );
aStr += "\\* jc";
aStr += OUString::number(aWW8Ruby.GetJC());
aStr += " \\* \"Font:";
aStr += aWW8Ruby.GetFontFamily();
aStr += "\" \\* hps";
aStr += OUString::number((aWW8Ruby.GetRubyHeight() + 5) / 10);
aStr += " \\o";
if (aWW8Ruby.GetDirective())
{
aStr += "\\a" + OUString(aWW8Ruby.GetDirective());
}
aStr += "(\\s\\up ";
aStr += OUString::number((aWW8Ruby.GetBaseHeight() + 10) / 20 - 1);
aStr += "(";
aStr += rRuby.GetText();
aStr += ")";
// The parameter separator depends on the FIB.lid
if ( m_rWW8Export.pFib->getNumDecimalSep() == '.' )
aStr += ",";
else
aStr += ";";
m_rWW8Export.OutputField( nullptr, ww::eEQ, aStr,
FieldFlags::Start | FieldFlags::CmdStart );
}
void WW8AttributeOutput::EndRuby(const SwTextNode& /*rNode*/, sal_Int32 /*nPos*/)
{
m_rWW8Export.WriteChar( ')' );
m_rWW8Export.OutputField( nullptr, ww::eEQ, OUString(), FieldFlags::End | FieldFlags::Close );
}
/*#i15387# Better ideas welcome*/
OUString &TruncateBookmark( OUString &rRet )
{
if ( rRet.getLength() > 40 )
rRet = rRet.copy( 0, 40 );
OSL_ENSURE( rRet.getLength() <= 40, "Word cannot have bookmarks longer than 40 chars" );
return rRet;
}
OUString AttributeOutputBase::ConvertURL( const OUString& rUrl, bool bAbsoluteOut )
{
OUString sURL = rUrl;
OUString sExportedDocumentURL = "";
{
DocxExport* pDocxExport = dynamic_cast<DocxExport*>(&GetExport());
if ( pDocxExport )
{
// DOCX
DocxExportFilter& rFilter = pDocxExport->GetFilter();
sExportedDocumentURL = rFilter.getFileUrl();
}
else
{
// DOC
WW8Export* pWW8Export = dynamic_cast<WW8Export*>(&GetExport());
if ( pWW8Export )
{
SwWW8Writer& rWriter = pWW8Export->GetWriter();
sExportedDocumentURL = rWriter.GetMedia()->GetURLObject().GetPath();
}
}
}
INetURLObject anAbsoluteParent( sExportedDocumentURL );
if ( anAbsoluteParent.GetURLPath().isEmpty() )
{
// DOC filter returns system path (without file:///)
anAbsoluteParent.setFSysPath( sExportedDocumentURL, FSysStyle::Detect );
anAbsoluteParent.setFinalSlash();
}
OUString sConvertedParent = INetURLObject::GetScheme( anAbsoluteParent.GetProtocol() ) + anAbsoluteParent.GetURLPath();
OUString sParentPath = sConvertedParent.isEmpty() ? sExportedDocumentURL : sConvertedParent;
if ( bAbsoluteOut )
{
INetURLObject anAbsoluteNew;
if ( anAbsoluteParent.GetNewAbsURL( rUrl, &anAbsoluteNew ) )
sURL = anAbsoluteNew.GetMainURL( INetURLObject::DecodeMechanism::NONE );
else
sURL = rUrl;
}
else
{
OUString sToConvert = rUrl.replaceAll( "\\", "/" );
INetURLObject aURL( sToConvert );
sToConvert = INetURLObject::GetScheme( aURL.GetProtocol() ) + aURL.GetURLPath();
OUString sRelative = INetURLObject::GetRelURL( sParentPath, sToConvert, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::NONE );
if ( !sRelative.isEmpty() )
sURL = sRelative;
}
return sURL;
}
bool AttributeOutputBase::AnalyzeURL( const OUString& rUrl, const OUString& /*rTarget*/, OUString* pLinkURL, OUString* pMark )
{
bool bBookMarkOnly = false;
OUString sMark;
OUString sURL;
if ( rUrl.getLength() > 1 && rUrl[0] == '#' )
{
sMark = BookmarkToWriter( rUrl.copy(1) );
const sal_Int32 nPos = sMark.lastIndexOf( cMarkSeparator );
const OUString sRefType(nPos>=0 && nPos+1<sMark.getLength() ?
sMark.copy(nPos+1).replaceAll(" ", "") :
OUString());
// #i21465# Only interested in outline references
if ( sRefType == "outline" )
{
OUString sLink = sMark.copy(0, nPos);
std::vector<aBookmarkPair>::iterator bkmkIterEnd = GetExport().m_aImplicitBookmarks.end();
for ( std::vector<aBookmarkPair>::iterator aIter = GetExport().m_aImplicitBookmarks.begin(); aIter != bkmkIterEnd; ++aIter )
{
if ( aIter->first == sLink )
{
sMark = "_toc" + OUString::number( aIter->second );
}
}
}
}
else
{
INetURLObject aURL( rUrl, INetProtocol::NotValid );
sURL = aURL.GetURLNoMark( INetURLObject::DecodeMechanism::Unambiguous );
sMark = aURL.GetMark( INetURLObject::DecodeMechanism::Unambiguous );
INetProtocol aProtocol = aURL.GetProtocol();
if ( aProtocol == INetProtocol::File || aProtocol == INetProtocol::NotValid )
{
// INetProtocol::NotValid - may be a relative link
bool bExportRelative = m_aSaveOpt.IsSaveRelFSys();
sURL = ConvertURL( rUrl, !bExportRelative );
}
}
if ( !sMark.isEmpty() && sURL.isEmpty() )
bBookMarkOnly = true;
*pMark = sMark;
*pLinkURL = sURL;
return bBookMarkOnly;
}
bool WW8AttributeOutput::AnalyzeURL( const OUString& rUrl, const OUString& rTarget, OUString* pLinkURL, OUString* pMark )
{
bool bBookMarkOnly = AttributeOutputBase::AnalyzeURL( rUrl, rTarget, pLinkURL, pMark );
OUString sURL = *pLinkURL;
if ( !sURL.isEmpty() )
sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
if ( bBookMarkOnly )
sURL = FieldString( ww::eHYPERLINK );
else
sURL = FieldString( ww::eHYPERLINK ) + "\"" + sURL + "\"";
if ( !pMark->isEmpty() )
sURL += " \\l \"" + *pMark + "\"";
if ( !rTarget.isEmpty() )
sURL += " \\n " + rTarget;
*pLinkURL = sURL;
return bBookMarkOnly;
}
void WW8AttributeOutput::WriteBookmarkInActParagraph( const OUString& rName, sal_Int32 nFirstRunPos, sal_Int32 nLastRunPos )
{
m_aBookmarksOfParagraphStart.insert(std::pair<sal_Int32, OUString>(nFirstRunPos, rName));
m_aBookmarksOfParagraphEnd.insert(std::pair<sal_Int32, OUString>(nLastRunPos, rName));
}
bool WW8AttributeOutput::StartURL( const OUString &rUrl, const OUString &rTarget )
{
INetURLObject aURL( rUrl );
OUString sURL;
OUString sMark;
bool bBookMarkOnly = AnalyzeURL( rUrl, rTarget, &sURL, &sMark );
m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::Start | FieldFlags::CmdStart );
// write the reference to the "picture" structure
sal_uLong nDataStt = m_rWW8Export.pDataStrm->Tell();
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell() );
// WinWord 2000 doesn't write this - so it's a temp solution by W97 ?
m_rWW8Export.WriteChar( 0x01 );
static sal_uInt8 aArr1[] = {
0x03, 0x6a, 0,0,0,0, // sprmCPicLocation
0x06, 0x08, 0x01, // sprmCFData
0x55, 0x08, 0x01, // sprmCFSpec
0x02, 0x08, 0x01 // sprmCFFieldVanish
};
sal_uInt8* pDataAdr = aArr1 + 2;
Set_UInt32( pDataAdr, nDataStt );
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), sizeof( aArr1 ), aArr1 );
m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, sURL, FieldFlags::CmdEnd );
// now write the picture structure
sURL = aURL.GetURLNoMark();
// Compare the URL written by AnalyzeURL with the original one to see if
// the output URL is absolute or relative.
OUString sRelativeURL;
if ( !rUrl.isEmpty() )
sRelativeURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), rUrl );
bool bAbsolute = sRelativeURL == rUrl;
static sal_uInt8 aURLData1[] = {
0,0,0,0, // len of struct
0x44,0, // the start of "next" data
0,0,0,0,0,0,0,0,0,0, // PIC-Structure!
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // |
0,0,0,0, // /
};
static sal_uInt8 MAGIC_A[] = {
// start of "next" data
0xD0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
};
m_rWW8Export.pDataStrm->WriteBytes(aURLData1, sizeof(aURLData1));
/* Write HFD Structure */
sal_uInt8 nAnchor = 0x00;
if ( !sMark.isEmpty() )
nAnchor = 0x08;
m_rWW8Export.pDataStrm->WriteUChar(nAnchor); // HFDBits
m_rWW8Export.pDataStrm->WriteBytes(MAGIC_A, sizeof(MAGIC_A)); //clsid
/* Write Hyperlink Object see [MS-OSHARED] spec*/
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 0x00000002);
sal_uInt32 nFlag = bBookMarkOnly ? 0 : 0x01;
if ( bAbsolute )
nFlag |= 0x02;
if ( !sMark.isEmpty() )
nFlag |= 0x08;
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nFlag );
INetProtocol eProto = aURL.GetProtocol();
if ( eProto == INetProtocol::File || eProto == INetProtocol::Smb )
{
// version 1 (for a document)
static sal_uInt8 MAGIC_C[] = {
0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
0x00, 0x00
};
static sal_uInt8 MAGIC_D[] = {
0xFF, 0xFF, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// save the links to files as relative
sURL = URIHelper::simpleNormalizedMakeRelative( m_rWW8Export.GetWriter().GetBaseURL(), sURL );
if ( eProto == INetProtocol::File && sURL.startsWith( "/" ) )
sURL = aURL.PathToFileName();
// special case for the absolute windows names
// (convert '/c:/foo/bar.doc' into 'c:\foo\bar.doc')
if (sURL.getLength()>=3)
{
const sal_Unicode aDrive = sURL[1];
if ( sURL[0]=='/' && sURL[2]==':' &&
( (aDrive>='A' && aDrive<='Z' ) || (aDrive>='a' && aDrive<='z') ) )
{
sURL = sURL.copy(1).replaceAll("/", "\\");
}
}
// n#261623 convert smb notation to '\\'
const char pSmb[] = "smb://";
if ( eProto == INetProtocol::Smb && sURL.startsWith( pSmb ) )
{
sURL = sURL.copy( sizeof(pSmb)-3 ).replaceAll( "/", "\\" );
}
m_rWW8Export.pDataStrm->WriteBytes(MAGIC_C, sizeof(MAGIC_C));
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sURL.getLength()+1 );
SwWW8Writer::WriteString8( *m_rWW8Export.pDataStrm, sURL, true,
RTL_TEXTENCODING_MS_1252 );
m_rWW8Export.pDataStrm->WriteBytes(MAGIC_D, sizeof(MAGIC_D));
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() + 6 );
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2*sURL.getLength() );
SwWW8Writer::WriteShort( *m_rWW8Export.pDataStrm, 3 );
SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, false );
}
else if ( eProto != INetProtocol::NotValid )
{
// version 2 (simple url)
// and write some data to the data stream, but don't ask
// what the data mean, except for the URL.
// The First piece is the WW8_PIC structure.
static sal_uInt8 MAGIC_B[] = {
0xE0,0xC9,0xEA,0x79,0xF9,0xBA,0xCE,0x11,
0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B
};
m_rWW8Export.pDataStrm->WriteBytes(MAGIC_B, sizeof(MAGIC_B));
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, 2 * ( sURL.getLength() + 1 ) );
SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sURL, true );
}
if ( !sMark.isEmpty() )
{
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, sMark.getLength()+1 );
SwWW8Writer::WriteString16( *m_rWW8Export.pDataStrm, sMark, true );
}
SwWW8Writer::WriteLong( *m_rWW8Export.pDataStrm, nDataStt,
m_rWW8Export.pDataStrm->Tell() - nDataStt );
return true;
}
bool WW8AttributeOutput::EndURL(bool const)
{
m_rWW8Export.OutputField( nullptr, ww::eHYPERLINK, OUString(), FieldFlags::Close );
return true;
}
OUString BookmarkToWord(const OUString &rBookmark)
{
OUString sRet(INetURLObject::encode(
rBookmark.replace(' ', '_'), // Spaces are prohibited in bookmark name
INetURLObject::PART_REL_SEGMENT_EXTRA,
INetURLObject::EncodeMechanism::All, RTL_TEXTENCODING_ASCII_US));
return TruncateBookmark(sRet);
}
OUString BookmarkToWriter(const OUString &rBookmark)
{
return INetURLObject::decode(rBookmark,
INetURLObject::DecodeMechanism::Unambiguous, RTL_TEXTENCODING_ASCII_US);
}
void SwWW8AttrIter::OutSwFormatRefMark(const SwFormatRefMark& rAttr)
{
if ( m_rExport.HasRefToObject( REF_SETREFATTR, &rAttr.GetRefName(), 0 ) )
m_rExport.AppendBookmark( MSWordExportBase::GetBookmarkName( REF_SETREFATTR,
&rAttr.GetRefName(), 0 ));
}
void SwWW8AttrIter::SplitRun( sal_Int32 nSplitEndPos )
{
for(auto aIter = maCharRuns.begin(); aIter != maCharRuns.end(); ++aIter)
{
if(aIter->mnEndPos == nSplitEndPos)
return;
else if (aIter->mnEndPos > nSplitEndPos)
{
CharRunEntry aNewEntry = *aIter;
aIter->mnEndPos = nSplitEndPos;
maCharRuns.insert( ++aIter, aNewEntry);
maCharRunIter = maCharRuns.begin();
IterToCurrent();
nCurrentSwPos = SearchNext(1);
break;
}
}
}
void WW8AttributeOutput::FieldVanish( const OUString& rText, ww::eField /*eType*/ )
{
ww::bytes aItems;
m_rWW8Export.GetCurrentItems( aItems );
// sprmCFFieldVanish
SwWW8Writer::InsUInt16( aItems, NS_sprm::sprmCFFldVanish );
aItems.push_back( 1 );
sal_uInt16 nStt_sprmCFSpec = aItems.size();
// sprmCFSpec -- fSpec-Attribut true
SwWW8Writer::InsUInt16( aItems, 0x855 );
aItems.push_back( 1 );
m_rWW8Export.WriteChar( '\x13' );
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
aItems.data() );
m_rWW8Export.OutSwString(rText, 0, rText.getLength());
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), nStt_sprmCFSpec,
aItems.data() );
m_rWW8Export.WriteChar( '\x15' );
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), aItems.size(),
aItems.data() );
}
void AttributeOutputBase::TOXMark( const SwTextNode& rNode, const SwTOXMark& rAttr )
{
// it's a field; so get the Text form the Node and build the field
OUString sText;
ww::eField eType = ww::eNONE;
const SwTextTOXMark& rTextTOXMark = *rAttr.GetTextTOXMark();
const sal_Int32* pTextEnd = rTextTOXMark.End();
if ( pTextEnd ) // has range?
{
sText = rNode.GetExpandText( rTextTOXMark.GetStart(),
*pTextEnd - rTextTOXMark.GetStart() );
}
else
sText = rAttr.GetAlternativeText();
switch ( rAttr.GetTOXType()->GetType() )
{
case TOX_INDEX:
eType = ww::eXE;
if ( !rAttr.GetPrimaryKey().isEmpty() )
{
if ( !rAttr.GetSecondaryKey().isEmpty() )
{
sText = rAttr.GetSecondaryKey() + ":" + sText;
}
sText = rAttr.GetPrimaryKey() + ":" + sText;
}
sText = " XE \"" + sText + "\" ";
break;
case TOX_USER:
sText += "\" \\f \"" + OUString(static_cast<sal_Char>( 'A' + GetExport( ).GetId( *rAttr.GetTOXType() ) ));
SAL_FALLTHROUGH;
case TOX_CONTENT:
{
eType = ww::eTC;
sText = " TC \"" + sText;
sal_uInt16 nLvl = rAttr.GetLevel();
if (nLvl > WW8ListManager::nMaxLevel)
nLvl = WW8ListManager::nMaxLevel;
sText += "\" \\l " + OUString::number(nLvl) + " ";
}
break;
default:
OSL_ENSURE( false, "Unhandled option for toc export" );
break;
}
if (!sText.isEmpty())
FieldVanish( sText, eType );
}
int SwWW8AttrIter::OutAttrWithRange(const SwTextNode& rNode, sal_Int32 nPos)
{
int nRet = 0;
if ( const SwpHints* pTextAttrs = rNd.GetpSwpHints() )
{
m_rExport.m_aCurrentCharPropStarts.push( nPos );
const sal_Int32* pEnd;
// first process ends of attributes with extent
for (size_t i = 0; i < pTextAttrs->Count(); ++i)
{
const SwTextAttr* pHt = pTextAttrs->GetSortedByEnd(i);
const SfxPoolItem* pItem = &pHt->GetAttr();
switch ( pItem->Which() )
{
case RES_TXTATR_INETFMT:
pEnd = pHt->End();
if (nPos == *pEnd && nPos != pHt->GetStart())
{
if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
--nRet;
}
break;
case RES_TXTATR_REFMARK:
pEnd = pHt->End();
if (nullptr != pEnd && nPos == *pEnd && nPos != pHt->GetStart())
{
OutSwFormatRefMark(*static_cast<const SwFormatRefMark*>(pItem));
--nRet;
}
break;
case RES_TXTATR_CJK_RUBY:
pEnd = pHt->End();
if (nPos == *pEnd && nPos != pHt->GetStart())
{
m_rExport.AttrOutput().EndRuby(rNode, nPos);
--nRet;
}
break;
}
if (nPos < *pHt->GetAnyEnd())
break; // sorted by end
}
for ( size_t i = 0; i < pTextAttrs->Count(); ++i )
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
const SfxPoolItem* pItem = &pHt->GetAttr();
switch ( pItem->Which() )
{
case RES_TXTATR_INETFMT:
if ( nPos == pHt->GetStart() )
{
const SwFormatINetFormat *rINet = static_cast< const SwFormatINetFormat* >( pItem );
if ( m_rExport.AttrOutput().StartURL( rINet->GetValue(), rINet->GetTargetFrame() ) )
++nRet;
}
pEnd = pHt->End();
if (nPos == *pEnd && nPos == pHt->GetStart())
{ // special case: empty must be handled here
if (m_rExport.AttrOutput().EndURL(nPos == rNd.Len()))
--nRet;
}
break;
case RES_TXTATR_REFMARK:
if ( nPos == pHt->GetStart() )
{
OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
++nRet;
}
pEnd = pHt->End();
if (nullptr != pEnd && nPos == *pEnd && nPos == pHt->GetStart())
{ // special case: empty TODO: is this possible or would empty one have pEnd null?
OutSwFormatRefMark( *static_cast< const SwFormatRefMark* >( pItem ) );
--nRet;
}
break;
case RES_TXTATR_TOXMARK:
if ( nPos == pHt->GetStart() )
m_rExport.AttrOutput().TOXMark( rNd, *static_cast< const SwTOXMark* >( pItem ) );
break;
case RES_TXTATR_CJK_RUBY:
if ( nPos == pHt->GetStart() )
{
m_rExport.AttrOutput().StartRuby( rNd, nPos, *static_cast< const SwFormatRuby* >( pItem ) );
++nRet;
}
pEnd = pHt->End();
if (nPos == *pEnd && nPos == pHt->GetStart())
{ // special case: empty must be handled here
m_rExport.AttrOutput().EndRuby( rNd, nPos );
--nRet;
}
break;
}
if (nPos < pHt->GetStart())
break; // sorted by start
}
m_rExport.m_aCurrentCharPropStarts.pop(); // HasTextItem only allowed in the above range
}
return nRet;
}
bool SwWW8AttrIter::IncludeEndOfParaCRInRedlineProperties( sal_Int32 nEnd ) const
{
// search next Redline
for( SwRedlineTable::size_type nPos = nCurRedlinePos;
nPos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size(); ++nPos )
{
const SwRangeRedline *pRange = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[nPos];
const SwPosition* pEnd = pRange->End();
const SwPosition* pStart = pRange->Start();
bool bBreak = true;
// In word the paragraph end marker is a real character, in writer it is not.
// Here we find out if the para end marker we will emit is affected by
// redlining, in which case it must be included by the range of character
// attributes that contains the redlining information.
if (pEnd->nNode == rNd)
{
if (pEnd->nContent.GetIndex() == nEnd)
{
// This condition detects if the pseudo-char we will export
// should be explicitly included by the redlining char
// properties on this node because the redlining ends right
// after it
return true;
}
bBreak = false;
}
if (pStart->nNode == rNd)
{
if (pStart->nContent.GetIndex() == nEnd)
{
// This condition detects if the pseudo-char we will export
// should be explicitly included by the redlining char
// properties on this node because the redlining starts right
// before it
return true;
}
bBreak = false;
}
if (pStart->nNode.GetIndex()-1 == rNd.GetIndex())
{
if (pStart->nContent.GetIndex() == 0)
{
// This condition detects if the pseudo-char we will export
// should be implicitly excluded by the redlining char
// properties starting on the next node.
return true;
}
bBreak = false;
}
if (bBreak)
break;
}
return false;
}
const SwRedlineData* SwWW8AttrIter::GetParagraphLevelRedline( )
{
pCurRedline = nullptr;
// ToDo : this is not the most ideal ... should start maybe from 'nCurRedlinePos'
for(SwRangeRedline* pRedl : m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable())
{
const SwPosition* pCheckedStt = pRedl->Start();
if( pCheckedStt->nNode == rNd )
{
// Maybe add here a check that also the start & end of the redline is the entire paragraph
// Only return if this is a paragraph formatting redline
if (pRedl->GetType() == nsRedlineType_t::REDLINE_PARAGRAPH_FORMAT)
{
// write data of this redline
pCurRedline = pRedl;
return &( pCurRedline->GetRedlineData() );
}
}
}
return nullptr;
}
const SwRedlineData* SwWW8AttrIter::GetRunLevelRedline( sal_Int32 nPos )
{
if( pCurRedline )
{
const SwPosition* pEnd = pCurRedline->End();
if( pEnd->nNode == rNd &&
pEnd->nContent.GetIndex() <= nPos )
{
pCurRedline = nullptr;
++nCurRedlinePos;
}
else
{
switch( pCurRedline->GetType() )
{
case nsRedlineType_t::REDLINE_INSERT:
case nsRedlineType_t::REDLINE_DELETE:
case nsRedlineType_t::REDLINE_FORMAT:
// write data of this redline
return &( pCurRedline->GetRedlineData() );
break;
default:
break;
}
pCurRedline = nullptr;
++nCurRedlinePos;
}
}
if( !pCurRedline )
{
// search next Redline
for( ; nCurRedlinePos < m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
++nCurRedlinePos )
{
const SwRangeRedline* pRedl = m_rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nCurRedlinePos ];
const SwPosition* pStt = pRedl->Start();
const SwPosition* pEnd = pStt == pRedl->GetPoint()
? pRedl->GetMark()
: pRedl->GetPoint();
if( pStt->nNode == rNd )
{
if( pStt->nContent.GetIndex() >= nPos )
{
if( pStt->nContent.GetIndex() == nPos )
{
switch( pRedl->GetType() )
{
case nsRedlineType_t::REDLINE_INSERT:
case nsRedlineType_t::REDLINE_DELETE:
case nsRedlineType_t::REDLINE_FORMAT:
// write data of this redline
pCurRedline = pRedl;
return &( pCurRedline->GetRedlineData() );
break;
default:
break;
}
}
break;
}
}
else
{
break;
}
if( pEnd->nNode == rNd &&
pEnd->nContent.GetIndex() < nPos )
{
pCurRedline = pRedl;
break;
}
}
}
return nullptr;
}
SvxFrameDirection MSWordExportBase::GetCurrentPageDirection() const
{
const SwFrameFormat &rFormat = m_pCurrentPageDesc
? m_pCurrentPageDesc->GetMaster()
: m_pDoc->GetPageDesc( 0 ).GetMaster();
return rFormat.GetFrameDir().GetValue();
}
SvxFrameDirection MSWordExportBase::GetDefaultFrameDirection( ) const
{
SvxFrameDirection nDir = SvxFrameDirection::Environment;
if ( m_bOutPageDescs )
nDir = GetCurrentPageDirection( );
else if ( m_pOutFormatNode )
{
if ( m_bOutFlyFrameAttrs ) //frame
{
nDir = TrueFrameDirection( *static_cast< const SwFrameFormat * >(m_pOutFormatNode) );
}
else if ( dynamic_cast< const SwContentNode *>( m_pOutFormatNode ) != nullptr ) //pagagraph
{
const SwContentNode *pNd = static_cast<const SwContentNode *>(m_pOutFormatNode);
SwPosition aPos( *pNd );
nDir = m_pDoc->GetTextDirection( aPos );
}
else if ( dynamic_cast< const SwTextFormatColl *>( m_pOutFormatNode ) != nullptr )
{
if ( MsLangId::isRightToLeft( GetAppLanguage()) )
nDir = SvxFrameDirection::Horizontal_RL_TB;
else
nDir = SvxFrameDirection::Horizontal_LR_TB; //what else can we do :-(
}
}
if ( nDir == SvxFrameDirection::Environment )
{
// fdo#44029 put direction right when the locale are RTL.
if( MsLangId::isRightToLeft( GetAppLanguage()) )
nDir = SvxFrameDirection::Horizontal_RL_TB;
else
nDir = SvxFrameDirection::Horizontal_LR_TB; //Set something
}
return nDir;
}
SvxFrameDirection MSWordExportBase::TrueFrameDirection( const SwFrameFormat &rFlyFormat ) const
{
const SwFrameFormat *pFlyFormat = &rFlyFormat;
const SvxFrameDirectionItem* pItem = nullptr;
while ( pFlyFormat )
{
pItem = &pFlyFormat->GetFrameDir();
if ( SvxFrameDirection::Environment == pItem->GetValue() )
{
pItem = nullptr;
const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
pAnchor->GetContentAnchor() )
{
pFlyFormat = pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
}
else
pFlyFormat = nullptr;
}
else
pFlyFormat = nullptr;
}
SvxFrameDirection nRet;
if ( pItem )
nRet = pItem->GetValue();
else
nRet = GetCurrentPageDirection();
OSL_ENSURE( nRet != SvxFrameDirection::Environment, "leaving with environment direction" );
return nRet;
}
const SvxBrushItem* WW8Export::GetCurrentPageBgBrush() const
{
const SwFrameFormat &rFormat = m_pCurrentPageDesc
? m_pCurrentPageDesc->GetMaster()
: m_pDoc->GetPageDesc(0).GetMaster();
const SfxPoolItem* pItem = nullptr;
//If not set, or "no fill", get real bg
SfxItemState eState = rFormat.GetItemState(RES_BACKGROUND, true, &pItem);
const SvxBrushItem* pRet = static_cast<const SvxBrushItem*>(pItem);
if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
pRet->GetColor() == COL_TRANSPARENT))
{
pRet = &(DefaultItemGet<SvxBrushItem>(*m_pDoc,RES_BACKGROUND));
}
return pRet;
}
SvxBrushItem WW8Export::TrueFrameBgBrush(const SwFrameFormat &rFlyFormat) const
{
const SwFrameFormat *pFlyFormat = &rFlyFormat;
const SvxBrushItem* pRet = nullptr;
while (pFlyFormat)
{
//If not set, or "no fill", get real bg
const SfxPoolItem* pItem = nullptr;
SfxItemState eState =
pFlyFormat->GetItemState(RES_BACKGROUND, true, &pItem);
pRet = static_cast<const SvxBrushItem*>(pItem);
if (SfxItemState::SET != eState || !pRet || (!pRet->GetGraphic() &&
pRet->GetColor() == COL_TRANSPARENT))
{
pRet = nullptr;
const SwFormatAnchor* pAnchor = &pFlyFormat->GetAnchor();
if ((RndStdIds::FLY_AT_PAGE != pAnchor->GetAnchorId()) &&
pAnchor->GetContentAnchor())
{
pFlyFormat =
pAnchor->GetContentAnchor()->nNode.GetNode().GetFlyFormat();
}
else
pFlyFormat = nullptr;
}
else
pFlyFormat = nullptr;
}
if (!pRet)
pRet = GetCurrentPageBgBrush();
const Color aTmpColor( COL_WHITE );
SvxBrushItem aRet( aTmpColor, RES_BACKGROUND );
if (pRet && (pRet->GetGraphic() ||( pRet->GetColor() != COL_TRANSPARENT)))
aRet = *pRet;
return aRet;
}
/*
Convert characters that need to be converted, the basic replacements and the
ridiculously complicated title case attribute mapping to hardcoded upper case
because word doesn't have the feature
*/
OUString SwWW8AttrIter::GetSnippet(const OUString &rStr, sal_Int32 nCurrentPos,
sal_Int32 nLen) const
{
if (!nLen)
return OUString();
OUString aSnippet(rStr.copy(nCurrentPos, nLen));
// 0x0a ( Hard Line Break ) -> 0x0b
// 0xad ( soft hyphen ) -> 0x1f
// 0x2011 ( hard hyphen ) -> 0x1e
aSnippet = aSnippet.replace(0x0A, 0x0B);
aSnippet = aSnippet.replace(CHAR_HARDHYPHEN, 0x1e);
aSnippet = aSnippet.replace(CHAR_SOFTHYPHEN, 0x1f);
m_rExport.m_aCurrentCharPropStarts.push( nCurrentPos );
const SfxPoolItem &rItem = GetItem(RES_CHRATR_CASEMAP);
if (SvxCaseMap::Capitalize == static_cast<const SvxCaseMapItem&>(rItem).GetValue())
{
assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
sal_uInt16 nScriptType = g_pBreakIt->GetBreakIter()->getScriptType(aSnippet, 0);
LanguageType nLanguage;
switch (nScriptType)
{
case i18n::ScriptType::ASIAN:
nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CJK_LANGUAGE)).GetLanguage();
break;
case i18n::ScriptType::COMPLEX:
nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_CTL_LANGUAGE)).GetLanguage();
break;
case i18n::ScriptType::LATIN:
default:
nLanguage = static_cast<const SvxLanguageItem&>(GetItem(RES_CHRATR_LANGUAGE)).GetLanguage();
break;
}
SvxFont aFontHelper;
aFontHelper.SetCaseMap(SvxCaseMap::Capitalize);
aFontHelper.SetLanguage(nLanguage);
aSnippet = aFontHelper.CalcCaseMap(aSnippet);
//If we weren't at the begin of a word undo the case change.
//not done before doing the casemap because the sequence might start
//with whitespace
if (!g_pBreakIt->GetBreakIter()->isBeginWord(
rStr, nCurrentPos, g_pBreakIt->GetLocale(nLanguage),
i18n::WordType::ANYWORD_IGNOREWHITESPACES ) )
{
aSnippet = OUStringLiteral1(rStr[nCurrentPos]) + aSnippet.copy(1);
}
}
m_rExport.m_aCurrentCharPropStarts.pop();
return aSnippet;
}
/** Delivers the right paragraph style
Because of the different style handling for delete operations,
the track changes have to be analysed. A deletion, starting in paragraph A
with style A, ending in paragraph B with style B, needs a hack.
*/
static SwTextFormatColl& lcl_getFormatCollection( MSWordExportBase& rExport, const SwTextNode* pTextNode )
{
SwRedlineTable::size_type nPos = 0;
SwRedlineTable::size_type nMax = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable().size();
while( nPos < nMax )
{
const SwRangeRedline* pRedl = rExport.m_pDoc->getIDocumentRedlineAccess().GetRedlineTable()[ nPos++ ];
const SwPosition* pStt = pRedl->Start();
const SwPosition* pEnd = pStt == pRedl->GetPoint()
? pRedl->GetMark()
: pRedl->GetPoint();
// Looking for deletions, which ends in current pTextNode
if( nsRedlineType_t::REDLINE_DELETE == pRedl->GetRedlineData().GetType() &&
pEnd->nNode == *pTextNode && pStt->nNode != *pTextNode &&
pStt->nNode.GetNode().IsTextNode() )
{
pTextNode = pStt->nNode.GetNode().GetTextNode();
nMax = nPos;
nPos = 0;
}
}
return static_cast<SwTextFormatColl&>( pTextNode->GetAnyFormatColl() );
}
void WW8AttributeOutput::FormatDrop( const SwTextNode& rNode, const SwFormatDrop &rSwFormatDrop, sal_uInt16 nStyle,
ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo, ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner )
{
short nDropLines = rSwFormatDrop.GetLines();
short nDistance = rSwFormatDrop.GetDistance();
int rFontHeight, rDropHeight, rDropDescent;
SVBT16 nSty;
ShortToSVBT16( nStyle, nSty );
m_rWW8Export.pO->insert( m_rWW8Export.pO->end(), nSty, nSty+2 ); // Style #
m_rWW8Export.InsUInt16( NS_sprm::sprmPPc ); // Alignment (sprmPPc)
m_rWW8Export.pO->push_back( 0x20 );
m_rWW8Export.InsUInt16( NS_sprm::sprmPWr ); // Wrapping (sprmPWr)
m_rWW8Export.pO->push_back( 0x02 );
m_rWW8Export.InsUInt16( NS_sprm::sprmPDcs ); // Dropcap (sprmPDcs)
int nDCS = ( nDropLines << 3 ) | 0x01;
m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( nDCS ) );
m_rWW8Export.InsUInt16( NS_sprm::sprmPDxaFromText ); // Distance from text (sprmPDxaFromText)
m_rWW8Export.InsUInt16( nDistance );
if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
{
m_rWW8Export.InsUInt16( NS_sprm::sprmPDyaLine ); // Line spacing
m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -rDropHeight ) );
m_rWW8Export.InsUInt16( 0 );
}
m_rWW8Export.WriteCR( pTextNodeInfoInner );
if ( pTextNodeInfo.get() != nullptr )
{
#ifdef DBG_UTIL
SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
#endif
TableInfoCell( pTextNodeInfoInner );
}
m_rWW8Export.m_pPapPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
m_rWW8Export.pO->clear();
if ( rNode.GetDropSize( rFontHeight, rDropHeight, rDropDescent ) )
{
const SwCharFormat *pSwCharFormat = rSwFormatDrop.GetCharFormat();
if ( pSwCharFormat )
{
m_rWW8Export.InsUInt16( NS_sprm::sprmCIstd );
m_rWW8Export.InsUInt16( m_rWW8Export.GetId( pSwCharFormat ) );
}
m_rWW8Export.InsUInt16( NS_sprm::sprmCHpsPos ); // Lower the chars
m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( -((nDropLines - 1)*rDropDescent) / 10 ) );
m_rWW8Export.InsUInt16( NS_sprm::sprmCHps ); // Font Size
m_rWW8Export.InsUInt16( static_cast< sal_uInt16 >( rFontHeight / 10 ) );
}
m_rWW8Export.m_pChpPlc->AppendFkpEntry( m_rWW8Export.Strm().Tell(), m_rWW8Export.pO->size(), m_rWW8Export.pO->data() );
m_rWW8Export.pO->clear();
}
sal_Int32 MSWordExportBase::GetNextPos( SwWW8AttrIter const * aAttrIter, const SwTextNode& rNode, sal_Int32 nCurrentPos )
{
// Get the bookmarks for the normal run
const sal_Int32 nNextPos = aAttrIter->WhereNext();
sal_Int32 nNextBookmark = nNextPos;
sal_Int32 nNextAnnotationMark = nNextPos;
if( nNextBookmark > nCurrentPos ) //no need to search for bookmarks otherwise (checked in UpdatePosition())
{
GetSortedBookmarks( rNode, nCurrentPos, nNextBookmark - nCurrentPos );
NearestBookmark( nNextBookmark, nCurrentPos, false );
GetSortedAnnotationMarks( rNode, nCurrentPos, nNextAnnotationMark - nCurrentPos );
NearestAnnotationMark( nNextAnnotationMark, nCurrentPos, false );
}
return std::min( nNextPos, std::min( nNextBookmark, nNextAnnotationMark ) );
}
void MSWordExportBase::UpdatePosition( SwWW8AttrIter* aAttrIter, sal_Int32 nCurrentPos )
{
sal_Int32 nNextPos;
// go to next attribute if no bookmark is found or if the bookmark is after the next attribute position
// It may happened that the WhereNext() wasn't used in the previous increment because there was a
// bookmark before it. Use that position before trying to find another one.
bool bNextBookmark = NearestBookmark( nNextPos, nCurrentPos, true );
if( nCurrentPos == aAttrIter->WhereNext() && ( !bNextBookmark || nNextPos > aAttrIter->WhereNext() ) )
aAttrIter->NextPos();
}
bool MSWordExportBase::GetBookmarks( const SwTextNode& rNd, sal_Int32 nStt,
sal_Int32 nEnd, IMarkVector& rArr )
{
IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
sal_uLong nNd = rNd.GetIndex( );
const sal_Int32 nMarks = pMarkAccess->getAllMarksCount();
for ( sal_Int32 i = 0; i < nMarks; i++ )
{
IMark* pMark = ( pMarkAccess->getAllMarksBegin() + i )->get();
if ( IDocumentMarkAccess::GetType( *pMark ) == IDocumentMarkAccess::MarkType::ANNOTATIONMARK )
{
continue;
}
// Only keep the bookmarks starting or ending in this node
if ( pMark->GetMarkStart().nNode == nNd ||
pMark->GetMarkEnd().nNode == nNd )
{
const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
// Keep only the bookmars starting or ending in the snippet
bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
if ( bIsStartOk || bIsEndOk )
{
rArr.push_back( pMark );
}
}
}
return ( rArr.size() > 0 );
}
bool MSWordExportBase::GetAnnotationMarks( const SwTextNode& rNd, sal_Int32 nStt,
sal_Int32 nEnd, IMarkVector& rArr )
{
IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
sal_uLong nNd = rNd.GetIndex( );
const sal_Int32 nMarks = pMarkAccess->getAnnotationMarksCount();
for ( sal_Int32 i = 0; i < nMarks; i++ )
{
IMark* pMark = ( pMarkAccess->getAnnotationMarksBegin() + i )->get();
// Only keep the bookmarks starting or ending in this node
if ( pMark->GetMarkStart().nNode == nNd ||
pMark->GetMarkEnd().nNode == nNd )
{
const sal_Int32 nBStart = pMark->GetMarkStart().nContent.GetIndex();
const sal_Int32 nBEnd = pMark->GetMarkEnd().nContent.GetIndex();
// Keep only the bookmars starting or ending in the snippet
bool bIsStartOk = ( pMark->GetMarkStart().nNode == nNd ) && ( nBStart >= nStt ) && ( nBStart <= nEnd );
bool bIsEndOk = ( pMark->GetMarkEnd().nNode == nNd ) && ( nBEnd >= nStt ) && ( nBEnd <= nEnd );
// Annotation marks always have at least one character: the anchor
// point of the comment field. In this case Word wants only the
// comment field, so ignore the annotation mark itself.
bool bSingleChar = pMark->GetMarkStart().nNode == pMark->GetMarkEnd().nNode && nBStart + 1 == nBEnd;
if ( ( bIsStartOk || bIsEndOk ) && !bSingleChar )
{
rArr.push_back( pMark );
}
}
}
return ( rArr.size() > 0 );
}
class CompareMarksEnd
{
public:
bool operator() ( const IMark * pOneB, const IMark * pTwoB ) const
{
const sal_Int32 nOEnd = pOneB->GetMarkEnd().nContent.GetIndex();
const sal_Int32 nTEnd = pTwoB->GetMarkEnd().nContent.GetIndex();
return nOEnd < nTEnd;
}
};
bool MSWordExportBase::NearestBookmark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
{
bool bHasBookmark = false;
if ( !m_rSortedBookmarksStart.empty() )
{
IMark* pMarkStart = m_rSortedBookmarksStart.front();
const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
if( !bNextPositionOnly || (nNext > nCurrentPos ))
{
rNearest = nNext;
bHasBookmark = true;
}
}
if ( !m_rSortedBookmarksEnd.empty() )
{
IMark* pMarkEnd = m_rSortedBookmarksEnd[0];
const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
if( !bNextPositionOnly || nNext > nCurrentPos )
{
if ( !bHasBookmark )
rNearest = nNext;
else
rNearest = std::min( rNearest, nNext );
bHasBookmark = true;
}
}
return bHasBookmark;
}
void MSWordExportBase::NearestAnnotationMark( sal_Int32& rNearest, const sal_Int32 nCurrentPos, bool bNextPositionOnly )
{
bool bHasAnnotationMark = false;
if ( !m_rSortedAnnotationMarksStart.empty() )
{
IMark* pMarkStart = m_rSortedAnnotationMarksStart.front();
const sal_Int32 nNext = pMarkStart->GetMarkStart().nContent.GetIndex();
if( !bNextPositionOnly || (nNext > nCurrentPos ))
{
rNearest = nNext;
bHasAnnotationMark = true;
}
}
if ( !m_rSortedAnnotationMarksEnd.empty() )
{
IMark* pMarkEnd = m_rSortedAnnotationMarksEnd[0];
const sal_Int32 nNext = pMarkEnd->GetMarkEnd().nContent.GetIndex();
if( !bNextPositionOnly || nNext > nCurrentPos )
{
if ( !bHasAnnotationMark )
rNearest = nNext;
else
rNearest = std::min( rNearest, nNext );
}
}
}
void MSWordExportBase::GetSortedAnnotationMarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
{
IMarkVector aMarksStart;
if ( GetAnnotationMarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
{
IMarkVector aSortedEnd;
IMarkVector aSortedStart;
for ( IMarkVector::const_iterator it = aMarksStart.begin(), end = aMarksStart.end();
it != end; ++it )
{
IMark* pMark = (*it);
// Remove the positions equal to the current pos
const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
aSortedStart.push_back( pMark );
if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
aSortedEnd.push_back( pMark );
}
// Sort the bookmarks by end position
std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
m_rSortedAnnotationMarksStart.swap( aSortedStart );
m_rSortedAnnotationMarksEnd.swap( aSortedEnd );
}
else
{
m_rSortedAnnotationMarksStart.clear( );
m_rSortedAnnotationMarksEnd.clear( );
}
}
void MSWordExportBase::GetSortedBookmarks( const SwTextNode& rNode, sal_Int32 nCurrentPos, sal_Int32 nLen )
{
IMarkVector aMarksStart;
if ( GetBookmarks( rNode, nCurrentPos, nCurrentPos + nLen, aMarksStart ) )
{
IMarkVector aSortedEnd;
IMarkVector aSortedStart;
for ( IMarkVector::const_iterator it = aMarksStart.begin(), end = aMarksStart.end();
it != end; ++it )
{
IMark* pMark = (*it);
// Remove the positions equal to the current pos
const sal_Int32 nStart = pMark->GetMarkStart().nContent.GetIndex();
const sal_Int32 nEnd = pMark->GetMarkEnd().nContent.GetIndex();
if ( nStart > nCurrentPos && ( pMark->GetMarkStart().nNode == rNode.GetIndex()) )
aSortedStart.push_back( pMark );
if ( nEnd > nCurrentPos && nEnd <= ( nCurrentPos + nLen ) && (pMark->GetMarkEnd().nNode == rNode.GetIndex()) )
aSortedEnd.push_back( pMark );
}
// Sort the bookmarks by end position
std::sort( aSortedEnd.begin(), aSortedEnd.end(), CompareMarksEnd() );
m_rSortedBookmarksStart.swap( aSortedStart );
m_rSortedBookmarksEnd.swap( aSortedEnd );
}
else
{
m_rSortedBookmarksStart.clear( );
m_rSortedBookmarksEnd.clear( );
}
}
bool MSWordExportBase::NeedSectionBreak( const SwNode& rNd ) const
{
if ( m_bStyDef || m_bOutKF || m_bInWriteEscher || m_bOutPageDescs || m_pCurrentPageDesc == nullptr )
return false;
const SwPageDesc * pPageDesc = rNd.FindPageDesc()->GetFollow();
if (m_pCurrentPageDesc != pPageDesc)
{
if (!sw::util::IsPlausableSingleWordSection(m_pCurrentPageDesc->GetFirstMaster(), pPageDesc->GetMaster()))
{
return true;
}
}
return false;
}
bool MSWordExportBase::NeedTextNodeSplit( const SwTextNode& rNd, SwSoftPageBreakList& pList ) const
{
rNd.fillSoftPageBreakList( pList );
pList.insert(0);
pList.insert( rNd.GetText().getLength() );
return pList.size() > 2 && NeedSectionBreak( rNd );
}
void MSWordExportBase::OutputTextNode( SwTextNode& rNode )
{
SAL_INFO( "sw.ww8", "<OutWW8_SwTextNode>" );
ww8::WW8TableNodeInfo::Pointer_t pTextNodeInfo( m_pTableInfo->getTableNodeInfo( &rNode ) );
//For i120928,identify the last node
bool bLastCR = false;
bool bExported = false;
{
SwNodeIndex aNextIdx(rNode,1);
SwNodeIndex aLastIdx(rNode.GetNodes().GetEndOfContent());
if (aNextIdx == aLastIdx)
bLastCR = true;
}
// In order to make sure watermark is stored in 'header.xml', check nTextTyp.
// if it is document.xml, don't write the tags (watermark should be only in the 'header')
SwWW8AttrIter aWatermarkAttrIter( *this, rNode );
if (( TXT_HDFT != m_nTextTyp) && aWatermarkAttrIter.IsWatermarkFrame())
{
return;
}
bool bFlyInTable = m_pParentFrame && IsInTable();
if ( !bFlyInTable )
m_nStyleBeforeFly = GetId( lcl_getFormatCollection( *this, &rNode ) );
// nStyleBeforeFly may change when we recurse into another node, so we
// have to remember it in nStyle
sal_uInt16 nStyle = m_nStyleBeforeFly;
SwWW8AttrIter aAttrIter( *this, rNode );
rtl_TextEncoding eChrSet = aAttrIter.GetCharSet();
if ( m_bStartTOX )
{
// ignore TOX header section
const SwSectionNode* pSectNd = rNode.FindSectionNode();
if ( pSectNd && TOX_CONTENT_SECTION == pSectNd->GetSection().GetType() )
{
AttrOutput().StartTOX( pSectNd->GetSection() );
m_aCurrentCharPropStarts.push( 0 );
}
}
// Emulate: If 1-row table is marked as don't split, then set the row as don't split.
if ( IsInTable() )
{
const SwTableNode* pTableNode = rNode.FindTableNode();
if ( pTableNode )
{
const SwTable& rTable = pTableNode->GetTable();
const bool bKeep = rTable.GetFrameFormat()->GetKeep().GetValue();
const bool bDontSplit = !rTable.GetFrameFormat()->GetLayoutSplit().GetValue();
// bKeep handles this a different way later on, so ignore now
if ( !bKeep && bDontSplit && rTable.GetTabLines().size() == 1 )
{
// bDontSplit : set don't split once for the row
// but only for non-complex tables
const SwTableBox* pBox = rNode.GetTableBox();
const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
if ( pLine && !pLine->GetUpper() )
{
// check if box is first in that line:
if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
{
// check if paragraph is first in that line:
if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
pLine->GetFrameFormat()->SetFormatAttr(SwFormatRowSplit(!bDontSplit));
}
}
}
}
}
SwSoftPageBreakList softBreakList;
// Let's decide if we need to split the paragraph because of a section break
bool bNeedParaSplit = NeedTextNodeSplit( rNode, softBreakList )
&& !IsInTable();
auto aBreakIt = softBreakList.begin();
// iterate through portions on different pages
do
{
sal_Int32 nCurrentPos = *aBreakIt;
if( softBreakList.size() > 1 ) // not for empty paragraph
++aBreakIt;
AttrOutput().StartParagraph( pTextNodeInfo );
const SwSection* pTOXSect = nullptr;
if( m_bInWriteTOX )
{
// check for end of TOX
SwNodeIndex aIdx( rNode, 1 );
if( !aIdx.GetNode().IsTextNode() )
{
const SwSectionNode* pTOXSectNd = rNode.FindSectionNode();
if ( pTOXSectNd )
{
pTOXSect = &pTOXSectNd->GetSection();
const SwNode* pNxt = rNode.GetNodes().GoNext( &aIdx );
if( pNxt && pNxt->FindSectionNode() == pTOXSectNd )
pTOXSect = nullptr;
}
}
}
if ( aAttrIter.RequiresImplicitBookmark() )
{
OUString sBkmkName = "_toc" + OUString::number( rNode.GetIndex() );
// Add a bookmark converted to a Word name.
AppendBookmark( BookmarkToWord( sBkmkName ) );
}
// Call this before write out fields and runs
AttrOutput().GenerateBookmarksForSequenceField(rNode, aAttrIter);
const OUString& aStr( rNode.GetText() );
sal_Int32 const nEnd = bNeedParaSplit ? *aBreakIt : aStr.getLength();
bool bIncludeEndOfParaCRInRedlineProperties = false;
sal_Int32 nOpenAttrWithRange = 0;
ww8::WW8TableNodeInfoInner::Pointer_t pTextNodeInfoInner;
if ( pTextNodeInfo.get() != nullptr )
{
pTextNodeInfoInner = pTextNodeInfo->getFirstInner();
}
do {
const SwRedlineData* pRedlineData = aAttrIter.GetRunLevelRedline( nCurrentPos );
FlyProcessingState nStateOfFlyFrame = FLY_PROCESSED;
bool bPostponeWritingText = false ;
OUString aSavedSnippet ;
sal_Int32 nNextAttr = GetNextPos( &aAttrIter, rNode, nCurrentPos );
// Skip un-exportable attributes.
if (!aAttrIter.IsExportableAttr(nCurrentPos))
{
nCurrentPos = nNextAttr;
UpdatePosition(&aAttrIter, nCurrentPos);
eChrSet = aAttrIter.GetCharSet();
continue;
}
// Is this the only run in this paragraph and it's empty?
bool bSingleEmptyRun = nCurrentPos == 0 && nNextAttr == 0;
AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
if( nNextAttr > nEnd )
nNextAttr = nEnd;
if( m_nTextTyp == TXT_FTN || m_nTextTyp == TXT_EDN )
{
if( AttrOutput().FootnoteEndnoteRefTag() )
{
AttrOutput().EndRun( &rNode, nCurrentPos, nNextAttr == nEnd );
AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
}
}
/*
1) If there is a text node and an overlapping anchor, then write them in two different
runs and not as part of the same run.
2) Ensure that it is a text node and not in a fly.
3) If the anchor is associated with a text node with empty text then we ignore.
*/
if( rNode.IsTextNode()
&& aStr != "\001" && !aStr.isEmpty()
&& !rNode.GetFlyFormat()
&& !(IsInTable() && !AllowPostponedTextInTable())
&& aAttrIter.IsAnchorLinkedToThisNode(rNode.GetIndex()) )
{
bPostponeWritingText = true ;
}
nStateOfFlyFrame = aAttrIter.OutFlys( nCurrentPos );
AttrOutput().SetStateOfFlyFrame( nStateOfFlyFrame );
AttrOutput().SetAnchorIsLinkedToNode( bPostponeWritingText && (FLY_POSTPONED != nStateOfFlyFrame) );
// Append bookmarks in this range after flys, exclusive of final
// position of this range
AppendBookmarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
AppendAnnotationMarks( rNode, nCurrentPos, nNextAttr - nCurrentPos );
// At the moment smarttags are only written for paragraphs, at the
// beginning of the paragraph.
if (nCurrentPos == 0)
AppendSmartTags(rNode);
bool bTextAtr = aAttrIter.IsTextAttr( nCurrentPos );
nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nCurrentPos );
sal_Int32 nLen = nNextAttr - nCurrentPos;
if ( !bTextAtr && nLen )
{
sal_Unicode ch = aStr[nCurrentPos];
const sal_Int32 ofs = ( ch == CH_TXT_ATR_FIELDSTART || ch == CH_TXT_ATR_FIELDEND || ch == CH_TXT_ATR_FORMELEMENT? 1 : 0 );
IDocumentMarkAccess* const pMarkAccess = m_pDoc->getIDocumentMarkAccess();
if ( ch == CH_TXT_ATR_FIELDSTART )
{
SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDSTART??" );
if ( pFieldmark && pFieldmark->GetFieldname() == ODF_FORMTEXT )
AppendBookmark( pFieldmark->GetName() );
ww::eField eFieldId = lcl_getFieldId( pFieldmark );
OUString sCode = lcl_getFieldCode( pFieldmark );
if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
{
IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
if ( it != pFieldmark->GetParameters()->end() )
{
OUString sFieldId;
it->second >>= sFieldId;
eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
}
it = pFieldmark->GetParameters()->find( ODF_CODE_PARAM );
if ( it != pFieldmark->GetParameters()->end() )
{
it->second >>= sCode;
}
}
OutputField( nullptr, eFieldId, sCode, FieldFlags::Start | FieldFlags::CmdStart );
if ( pFieldmark && pFieldmark->GetFieldname( ) == ODF_FORMTEXT )
WriteFormData( *pFieldmark );
else if ( pFieldmark && pFieldmark->GetFieldname( ) == ODF_HYPERLINK )
WriteHyperlinkData( *pFieldmark );
OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::CmdEnd );
if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
{
// Check for the presence of a linked OLE object
IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_OLE_PARAM );
if ( it != pFieldmark->GetParameters()->end() )
{
OUString sOleId;
uno::Any aValue = it->second;
aValue >>= sOleId;
if ( !sOleId.isEmpty() )
OutputLinkedOLE( sOleId );
}
}
}
else if ( ch == CH_TXT_ATR_FIELDEND )
{
SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDEND??" );
ww::eField eFieldId = lcl_getFieldId( pFieldmark );
if ( pFieldmark && pFieldmark->GetFieldname() == ODF_UNHANDLED )
{
IFieldmark::parameter_map_t::const_iterator it = pFieldmark->GetParameters()->find( ODF_ID_PARAM );
if ( it != pFieldmark->GetParameters()->end() )
{
OUString sFieldId;
it->second >>= sFieldId;
eFieldId = static_cast<ww::eField>(sFieldId.toInt32());
}
}
OutputField( nullptr, eFieldId, OUString(), FieldFlags::Close );
if ( pFieldmark && pFieldmark->GetFieldname() == ODF_FORMTEXT )
AppendBookmark( pFieldmark->GetName() );
}
else if ( ch == CH_TXT_ATR_FORMELEMENT )
{
SwPosition aPosition( rNode, SwIndex( &rNode, nCurrentPos ) );
::sw::mark::IFieldmark const * const pFieldmark = pMarkAccess->getFieldmarkFor( aPosition );
OSL_ENSURE( pFieldmark, "Looks like this doc is broken...; where is the Fieldmark for the FIELDSTART??" );
bool isDropdownOrCheckbox = pFieldmark && (pFieldmark->GetFieldname( ) == ODF_FORMDROPDOWN ||
pFieldmark->GetFieldname( ) == ODF_FORMCHECKBOX );
if ( isDropdownOrCheckbox )
AppendBookmark( pFieldmark->GetName() );
OutputField( nullptr, lcl_getFieldId( pFieldmark ),
lcl_getFieldCode( pFieldmark ),
FieldFlags::Start | FieldFlags::CmdStart );
if ( isDropdownOrCheckbox )
WriteFormData( *pFieldmark );
OutputField( nullptr, lcl_getFieldId( pFieldmark ), OUString(), FieldFlags::Close );
if ( isDropdownOrCheckbox )
AppendBookmark( pFieldmark->GetName() );
}
nLen -= ofs;
// if paragraph needs to be split, write only until split position
assert(!bNeedParaSplit || nCurrentPos <= *aBreakIt);
if( bNeedParaSplit && nCurrentPos + ofs + nLen > *aBreakIt)
nLen = *aBreakIt - nCurrentPos - ofs;
assert(0 <= nLen);
OUString aSnippet( aAttrIter.GetSnippet( aStr, nCurrentPos + ofs, nLen ) );
if ( ( m_nTextTyp == TXT_EDN || m_nTextTyp == TXT_FTN ) && nCurrentPos == 0 && nLen > 0 )
{
// Allow MSO to emulate LO footnote text starting at left margin - only meaningful with hanging indent
sal_Int32 nFirstLineIndent=0;
SfxItemSet aSet( m_pDoc->GetAttrPool(), svl::Items<RES_LR_SPACE, RES_LR_SPACE>{} );
const SwTextNode* pTextNode( rNode.GetTextNode() );
if ( pTextNode && pTextNode->GetAttr(aSet) )
{
const SvxLRSpaceItem* pLRSpace = aSet.GetItem<SvxLRSpaceItem>(RES_LR_SPACE);
if ( pLRSpace )
nFirstLineIndent = pLRSpace->GetTextFirstLineOfst();
}
// Insert tab for aesthetic purposes #i24762#
if ( m_bAddFootnoteTab && nFirstLineIndent < 0 && aSnippet[0] != 0x09 )
aSnippet = "\x09" + aSnippet;
m_bAddFootnoteTab = false;
}
if ( bPostponeWritingText && ( FLY_POSTPONED != nStateOfFlyFrame ) )
{
bPostponeWritingText = true ;
aSavedSnippet = aSnippet ;
}
else
{
bPostponeWritingText = false ;
AttrOutput().RunText( aSnippet, eChrSet );
}
}
if ( aAttrIter.IsDropCap( nNextAttr ) )
AttrOutput().FormatDrop( rNode, aAttrIter.GetSwFormatDrop(), nStyle, pTextNodeInfo, pTextNodeInfoInner );
// Only output character attributes if this is not a postponed text run.
if (0 != nEnd && !(bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame))
{
// Output the character attributes
// #i51277# do this before writing flys at end of paragraph
AttrOutput().StartRunProperties();
aAttrIter.OutAttr( nCurrentPos, false );
AttrOutput().EndRunProperties( pRedlineData );
}
// At the end of line, output the attributes until the CR.
// Exception: footnotes at the end of line
if ( nNextAttr == nEnd )
{
OSL_ENSURE( nOpenAttrWithRange >= 0, "odd to see this happening, expected >= 0" );
if ( !bTextAtr && nOpenAttrWithRange <= 0 )
{
if ( aAttrIter.IncludeEndOfParaCRInRedlineProperties( nEnd ) )
bIncludeEndOfParaCRInRedlineProperties = true;
else
{
// insert final graphic anchors if any before CR
nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
// insert final bookmarks if any before CR and after flys
AppendBookmarks( rNode, nEnd, 1 );
AppendAnnotationMarks( rNode, nEnd, 1 );
if ( pTOXSect )
{
m_aCurrentCharPropStarts.pop();
AttrOutput().EndTOX( *pTOXSect ,false);
}
//For i120928,the position of the bullet's graphic is at end of doc
if (bLastCR && (!bExported))
{
ExportGrfBullet(rNode);
bExported = true;
}
WriteCR( pTextNodeInfoInner );
}
}
}
if (0 == nEnd)
{
// Output the character attributes
// do it after WriteCR for an empty paragraph (otherwise
// WW8_WrFkp::Append throws SPRMs away...)
AttrOutput().StartRunProperties();
aAttrIter.OutAttr( nCurrentPos, false );
AttrOutput().EndRunProperties( pRedlineData );
}
// Exception: footnotes at the end of line
if ( nNextAttr == nEnd )
{
OSL_ENSURE(nOpenAttrWithRange >= 0,
"odd to see this happening, expected >= 0");
bool bAttrWithRange = (nOpenAttrWithRange > 0);
if ( nCurrentPos != nEnd )
{
nOpenAttrWithRange += aAttrIter.OutAttrWithRange( rNode, nEnd );
OSL_ENSURE(nOpenAttrWithRange == 0,
"odd to see this happening, expected 0");
}
// !bIncludeEndOfParaCRInRedlineProperties implies we have just
// emitted a CR, in which case we want to pass force=true to
// OutputFKP to ensure that an FKP entry for direct character
// formatting is written even if empty, so that the next one will
// start after the CR.
AttrOutput().OutputFKP(!bIncludeEndOfParaCRInRedlineProperties);
if (bTextAtr || bAttrWithRange || bIncludeEndOfParaCRInRedlineProperties)
{
// insert final graphic anchors if any before CR
nStateOfFlyFrame = aAttrIter.OutFlys( nEnd );
// insert final bookmarks if any before CR and after flys
AppendBookmarks( rNode, nEnd, 1 );
AppendAnnotationMarks( rNode, nEnd, 1 );
WriteCR( pTextNodeInfoInner );
// #i120928 - position of the bullet's graphic is at end of doc
if (bLastCR && (!bExported))
{
ExportGrfBullet(rNode);
bExported = true;
}
if ( pTOXSect )
{
m_aCurrentCharPropStarts.pop();
AttrOutput().EndTOX( *pTOXSect );
}
if (bIncludeEndOfParaCRInRedlineProperties)
{
AttrOutput().Redline( aAttrIter.GetRunLevelRedline( nEnd ) );
//If there was no redline property emitted, force adding
//another entry for the CR so that in the case that this
//has no redline, but the next para does, then this one is
//not merged with the next
AttrOutput().OutputFKP(true);
}
}
}
AttrOutput().WritePostitFieldReference();
if( bPostponeWritingText && FLY_PROCESSED == nStateOfFlyFrame )
{
AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
//write the postponed text run
AttrOutput().StartRun( pRedlineData, nCurrentPos, bSingleEmptyRun );
AttrOutput().SetAnchorIsLinkedToNode( false );
AttrOutput().ResetFlyProcessingFlag();
if (0 != nEnd)
{
AttrOutput().StartRunProperties();
aAttrIter.OutAttr( nCurrentPos, false );
AttrOutput().EndRunProperties( pRedlineData );
}
AttrOutput().RunText( aSavedSnippet, eChrSet );
AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
}
else if( bPostponeWritingText && !aSavedSnippet.isEmpty() )
{
//write the postponed text run
AttrOutput().RunText( aSavedSnippet, eChrSet );
AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
}
else
AttrOutput().EndRun(&rNode, nCurrentPos, nNextAttr == nEnd);
nCurrentPos = nNextAttr;
UpdatePosition( &aAttrIter, nCurrentPos );
eChrSet = aAttrIter.GetCharSet();
}
while ( nCurrentPos < nEnd );
// if paragraph is split, put the section break between the parts
if( bNeedParaSplit && *aBreakIt != rNode.GetText().getLength() )
{
SwNodeIndex aNextIndex( rNode, 1 );
const SwNode& pNextNode = aNextIndex.GetNode();
// if there is a next node, use its attributes to create the new
// section
if( pNextNode.IsTextNode() )
{
const SwTextNode& rNextNode = *static_cast<SwTextNode*>(
&aNextIndex.GetNode() );
OutputSectionBreaks(rNextNode.GetpSwAttrSet(), rNextNode);
}
else if (pNextNode.IsEndNode() )
{
// In this case the same paragraph holds the next page style
// too.
const SwPageDesc* pNextPageDesc = m_pCurrentPageDesc->GetFollow();
assert(pNextPageDesc);
PrepareNewPageDesc( rNode.GetpSwAttrSet(), rNode, nullptr , pNextPageDesc);
}
}
else if (!bNeedParaSplit)
{
// else check if section break needed after the paragraph
AttrOutput().SectionBreaks(rNode);
}
AttrOutput().StartParagraphProperties();
AttrOutput().ParagraphStyle( nStyle );
if ( m_pParentFrame && IsInTable() ) // Fly-Attrs
OutputFormat( m_pParentFrame->GetFrameFormat(), false, false, true );
if ( pTextNodeInfo.get() != nullptr )
{
#ifdef DBG_UTIL
SAL_INFO( "sw.ww8", pTextNodeInfo->toString());
#endif
AttrOutput().TableInfoCell( pTextNodeInfoInner );
if (pTextNodeInfoInner->isFirstInTable())
{
const SwTable * pTable = pTextNodeInfoInner->getTable();
const SwTableFormat* pTabFormat = pTable->GetFrameFormat();
if (pTabFormat != nullptr)
{
if (pTabFormat->GetBreak().GetBreak() == SvxBreak::PageBefore)
AttrOutput().PageBreakBefore(true);
}
}
}
if ( !bFlyInTable )
{
SfxItemSet* pTmpSet = nullptr;
const sal_uInt8 nPrvNxtNd = rNode.HasPrevNextLayNode();
if( (ND_HAS_PREV_LAYNODE|ND_HAS_NEXT_LAYNODE ) != nPrvNxtNd )
{
const SfxPoolItem* pItem;
if( SfxItemState::SET == rNode.GetSwAttrSet().GetItemState(
RES_UL_SPACE, true, &pItem ) &&
( ( !( ND_HAS_PREV_LAYNODE & nPrvNxtNd ) &&
static_cast<const SvxULSpaceItem*>(pItem)->GetUpper()) ||
( !( ND_HAS_NEXT_LAYNODE & nPrvNxtNd ) &&
static_cast<const SvxULSpaceItem*>(pItem)->GetLower()) ))
{
pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
SvxULSpaceItem aUL( *static_cast<const SvxULSpaceItem*>(pItem) );
// #i25901#- consider compatibility option
if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::PARA_SPACE_MAX_AT_PAGES))
{
if( !(ND_HAS_PREV_LAYNODE & nPrvNxtNd ))
aUL.SetUpper( 0 );
}
// #i25901# - consider compatibility option
if (!m_pDoc->getIDocumentSettingAccess().get(DocumentSettingId::ADD_PARA_SPACING_TO_TABLE_CELLS))
{
if( !(ND_HAS_NEXT_LAYNODE & nPrvNxtNd ))
aUL.SetLower( 0 );
}
pTmpSet->Put( aUL );
}
}
bool bParaRTL = aAttrIter.IsParaRTL();
int nNumberLevel = -1;
if (rNode.IsNumbered())
nNumberLevel = rNode.GetActualListLevel();
if (nNumberLevel >= 0 && nNumberLevel < MAXLEVEL)
{
const SwNumRule* pRule = rNode.GetNumRule();
sal_uInt8 nLvl = static_cast< sal_uInt8 >(nNumberLevel);
const SwNumFormat* pFormat = pRule->GetNumFormat( nLvl );
if( !pFormat )
pFormat = &pRule->Get( nLvl );
if( !pTmpSet )
pTmpSet = new SfxItemSet( rNode.GetSwAttrSet() );
SvxLRSpaceItem aLR(ItemGet<SvxLRSpaceItem>(*pTmpSet, RES_LR_SPACE));
// #i86652#
if ( pFormat->GetPositionAndSpaceMode() ==
SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
{
aLR.SetTextLeft( aLR.GetTextLeft() + pFormat->GetAbsLSpace() );
}
if( rNode.IsNumbered() && rNode.IsCountedInList() )
{
// #i86652#
if ( pFormat->GetPositionAndSpaceMode() ==
SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
{
if (bParaRTL)
aLR.SetTextFirstLineOfstValue(pFormat->GetAbsLSpace() - pFormat->GetFirstLineOffset()); //TODO: overflow
else
aLR.SetTextFirstLineOfst(GetWordFirstLineOffset(*pFormat));
}
// correct fix for issue i94187
if (SfxItemState::SET !=
pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
{
// List style set via paragraph style - then put it into the itemset.
// This is needed to get list level and list id exported for
// the paragraph.
pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
// Put indent values into the itemset in case that the list
// style is applied via paragraph style and the list level
// indent values are not applicable.
if ( pFormat->GetPositionAndSpaceMode() ==
SvxNumberFormat::LABEL_ALIGNMENT &&
!rNode.AreListLevelIndentsApplicable() )
{
pTmpSet->Put( aLR );
}
}
}
else
pTmpSet->ClearItem(RES_PARATR_NUMRULE);
// #i86652#
if ( pFormat->GetPositionAndSpaceMode() ==
SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
{
pTmpSet->Put(aLR);
//#i21847#
SvxTabStopItem aItem(
ItemGet<SvxTabStopItem>(*pTmpSet, RES_PARATR_TABSTOP));
SvxTabStop aTabStop(pFormat->GetAbsLSpace());
aItem.Insert(aTabStop);
pTmpSet->Put(aItem);
MSWordExportBase::CorrectTabStopInSet(*pTmpSet, pFormat->GetAbsLSpace());
}
}
/*
If a given para is using the SvxFrameDirection::Environment direction we
cannot export that, if it's ltr then that's ok as that is word's
default. Otherwise we must add a RTL attribute to our export list
*/
const SvxFrameDirectionItem* pItem =
rNode.GetSwAttrSet().GetItem(RES_FRAMEDIR);
if (
(!pItem || pItem->GetValue() == SvxFrameDirection::Environment) &&
aAttrIter.IsParaRTL()
)
{
if ( !pTmpSet )
pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
pTmpSet->Put(SvxFrameDirectionItem(SvxFrameDirection::Horizontal_RL_TB, RES_FRAMEDIR));
}
// move code for handling of numbered,
// but not counted paragraphs to this place. Otherwise, the paragraph
// isn't exported as numbered, but not counted, if no other attribute
// is found in <pTmpSet>
// #i44815# adjust numbering/indents for numbered paragraphs
// without number (NO_NUMLEVEL)
// #i47013# need to check rNode.GetNumRule()!=NULL as well.
if ( ! rNode.IsCountedInList() && rNode.GetNumRule()!=nullptr )
{
// WW8 does not know numbered paragraphs without number
// (NO_NUMLEVEL). In WW8AttributeOutput::ParaNumRule(), we will export
// the RES_PARATR_NUMRULE as list-id 0, which in WW8 means
// no numbering. Here, we will adjust the indents to match
// visually.
if ( !pTmpSet )
pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
// create new LRSpace item, based on the current (if present)
const SfxPoolItem* pPoolItem = nullptr;
pTmpSet->GetItemState(RES_LR_SPACE, true, &pPoolItem);
SvxLRSpaceItem aLRSpace(
( pPoolItem == nullptr )
? SvxLRSpaceItem(0, 0, 0, 0, RES_LR_SPACE)
: *static_cast<const SvxLRSpaceItem*>( pPoolItem ) );
// new left margin = old left + label space
const SwNumRule* pRule = rNode.GetNumRule();
int nLevel = rNode.GetActualListLevel();
if (nLevel < 0)
nLevel = 0;
if (nLevel >= MAXLEVEL)
nLevel = MAXLEVEL - 1;
const SwNumFormat& rNumFormat = pRule->Get( static_cast< sal_uInt16 >(nLevel) );
// #i86652#
if ( rNumFormat.GetPositionAndSpaceMode() ==
SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
{
aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetAbsLSpace() );
}
else
{
aLRSpace.SetTextLeft( aLRSpace.GetLeft() + rNumFormat.GetIndentAt() );
}
// new first line indent = 0
// (first line indent is ignored for NO_NUMLEVEL)
if (!bParaRTL)
aLRSpace.SetTextFirstLineOfst( 0 );
// put back the new item
pTmpSet->Put( aLRSpace );
// assure that numbering rule is in <pTmpSet>
if (SfxItemState::SET != pTmpSet->GetItemState(RES_PARATR_NUMRULE, false) )
{
pTmpSet->Put( SwNumRuleItem( pRule->GetName() ));
}
}
// #i75457#
// Export page break after attribute from paragraph style.
// If page break attribute at the text node exist, an existing page
// break after at the paragraph style hasn't got to be considered.
if ( !rNode.GetpSwAttrSet() ||
SfxItemState::SET != rNode.GetpSwAttrSet()->GetItemState(RES_BREAK, false) )
{
const SvxFormatBreakItem* pBreakAtParaStyle =
&(ItemGet<SvxFormatBreakItem>(rNode.GetSwAttrSet(), RES_BREAK));
if ( pBreakAtParaStyle &&
pBreakAtParaStyle->GetBreak() == SvxBreak::PageAfter )
{
if ( !pTmpSet )
{
pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
}
pTmpSet->Put( *pBreakAtParaStyle );
}
else if( pTmpSet )
{ // Even a pagedesc item is set, the break item can be set 'NONE',
// this has to be overruled.
const SwFormatPageDesc& rPageDescAtParaStyle =
ItemGet<SwFormatPageDesc>( rNode, RES_PAGEDESC );
if( rPageDescAtParaStyle.KnowsPageDesc() )
pTmpSet->ClearItem( RES_BREAK );
}
}
// #i76520# Emulate non-splitting tables
if ( IsInTable() )
{
const SwTableNode* pTableNode = rNode.FindTableNode();
if ( pTableNode )
{
const SwTable& rTable = pTableNode->GetTable();
const SvxFormatKeepItem& rKeep = rTable.GetFrameFormat()->GetKeep();
const bool bKeep = rKeep.GetValue();
const bool bDontSplit = !(bKeep ||
rTable.GetFrameFormat()->GetLayoutSplit().GetValue());
if ( bKeep || bDontSplit )
{
// bKeep: set keep at first paragraphs in all lines
// bDontSplit : set keep at first paragraphs in all lines except from last line
// but only for non-complex tables
const SwTableBox* pBox = rNode.GetTableBox();
const SwTableLine* pLine = pBox ? pBox->GetUpper() : nullptr;
if ( pLine && !pLine->GetUpper() )
{
// check if box is first in that line:
if ( 0 == pLine->GetBoxPos( pBox ) && pBox->GetSttNd() )
{
// check if paragraph is first in that line:
if ( 1 == ( rNode.GetIndex() - pBox->GetSttNd()->GetIndex() ) )
{
bool bSetAtPara = false;
if ( bKeep )
bSetAtPara = true;
else if ( bDontSplit )
{
// check if pLine isn't last line in table
if ( rTable.GetTabLines().size() - rTable.GetTabLines().GetPos( pLine ) != 1 )
bSetAtPara = true;
}
if ( bSetAtPara )
{
if ( !pTmpSet )
pTmpSet = new SfxItemSet(rNode.GetSwAttrSet());
const SvxFormatKeepItem aKeepItem( true, RES_KEEP );
pTmpSet->Put( aKeepItem );
}
}
}
}
}
}
}
const SfxItemSet* pNewSet = pTmpSet ? pTmpSet : rNode.GetpSwAttrSet();
if( pNewSet )
{ // Para-Attrs
m_pStyAttr = &rNode.GetAnyFormatColl().GetAttrSet();
const SwModify* pOldMod = m_pOutFormatNode;
m_pOutFormatNode = &rNode;
// Pap-Attrs, so script is not necessary
OutputItemSet( *pNewSet, true, false, i18n::ScriptType::LATIN, false);
m_pStyAttr = nullptr;
m_pOutFormatNode = pOldMod;
if( pNewSet != rNode.GetpSwAttrSet() )
delete pNewSet;
}
}
// The formatting of the paragraph marker has two sources:
// 1) If there are hints at the end of the paragraph, then use that.
// 2) Else use the RES_CHRATR_BEGIN..RES_TXTATR_END range of the paragraph
// properties.
//
// Exception: if there is a character style hint at the end of the
// paragraph only, then still go with 2), as RES_TXTATR_CHARFMT is always
// set as a hint.
SfxItemSet aParagraphMarkerProperties(m_pDoc->GetAttrPool(), svl::Items<RES_CHRATR_BEGIN, RES_TXTATR_END>{});
bool bCharFormatOnly = true;
if(const SwpHints* pTextAttrs = rNode.GetpSwpHints())
{
for( size_t i = 0; i < pTextAttrs->Count(); ++i )
{
const SwTextAttr* pHt = pTextAttrs->Get(i);
const sal_Int32 startPos = pHt->GetStart(); // first Attr characters
const sal_Int32* endPos = pHt->End(); // end Attr characters
// Check if these attributes are for the last character in the paragraph
// - which means the paragraph marker. If a paragraph has 7 characters,
// then properties on character 8 are for the paragraph marker
if( endPos && (startPos == *endPos ) && (*endPos == rNode.GetText().getLength()) )
{
SAL_INFO( "sw.ww8", startPos << "startPos == endPos" << *endPos);
sal_uInt16 nWhich = pHt->GetAttr().Which();
SAL_INFO( "sw.ww8", "nWhich" << nWhich);
if (nWhich == RES_TXTATR_AUTOFMT || nWhich == RES_TXTATR_CHARFMT)
aParagraphMarkerProperties.Put(pHt->GetAttr());
if (nWhich != RES_TXTATR_CHARFMT)
bCharFormatOnly = false;
}
}
}
if (rNode.GetpSwAttrSet() && bCharFormatOnly)
{
aParagraphMarkerProperties.Put(*rNode.GetpSwAttrSet());
}
const SwRedlineData* pRedlineParagraphMarkerDelete = AttrOutput().GetParagraphMarkerRedline( rNode, nsRedlineType_t::REDLINE_DELETE );
const SwRedlineData* pRedlineParagraphMarkerInsert = AttrOutput().GetParagraphMarkerRedline( rNode, nsRedlineType_t::REDLINE_INSERT );
const SwRedlineData* pParagraphRedlineData = aAttrIter.GetParagraphLevelRedline( );
AttrOutput().EndParagraphProperties(aParagraphMarkerProperties, pParagraphRedlineData, pRedlineParagraphMarkerDelete, pRedlineParagraphMarkerInsert);
AttrOutput().EndParagraph( pTextNodeInfoInner );
}while(*aBreakIt != rNode.GetText().getLength() && bNeedParaSplit );
SAL_INFO( "sw.ww8", "</OutWW8_SwTextNode>" );
}
// Tables
void WW8AttributeOutput::EmptyParagraph()
{
m_rWW8Export.WriteStringAsPara( OUString() );
}
bool MSWordExportBase::NoPageBreakSection( const SfxItemSet* pSet )
{
bool bRet = false;
const SfxPoolItem* pI;
if( pSet)
{
bool bNoPageBreak = false;
if ( SfxItemState::SET != pSet->GetItemState(RES_PAGEDESC, true, &pI)
|| nullptr == static_cast<const SwFormatPageDesc*>(pI)->GetPageDesc() )
{
bNoPageBreak = true;
}
if (bNoPageBreak)
{
if (SfxItemState::SET != pSet->GetItemState(RES_BREAK, true, &pI))
bNoPageBreak = true;
else
{
SvxBreak eBreak = static_cast<const SvxFormatBreakItem*>(pI)->GetBreak();
switch (eBreak)
{
case SvxBreak::PageBefore:
case SvxBreak::PageAfter:
bNoPageBreak = false;
break;
default:
break;
}
}
}
bRet = bNoPageBreak;
}
return bRet;
}
void MSWordExportBase::OutputSectionNode( const SwSectionNode& rSectionNode )
{
const SwSection& rSection = rSectionNode.GetSection();
SwNodeIndex aIdx( rSectionNode, 1 );
const SwNode& rNd = aIdx.GetNode();
if ( !rNd.IsSectionNode() && !IsInTable()
&& rSection.GetType() != TOX_CONTENT_SECTION && rSection.GetType() != TOX_HEADER_SECTION) //No sections in table
{
// if the first Node inside the section has an own
// PageDesc or PageBreak attribute, then don't write
// here the section break
sal_uLong nRstLnNum = 0;
const SfxItemSet* pSet;
if ( rNd.IsContentNode() )
{
pSet = &rNd.GetContentNode()->GetSwAttrSet();
nRstLnNum = pSet->Get( RES_LINENUMBER ).GetStartValue();
}
else
pSet = nullptr;
if ( pSet && NoPageBreakSection( pSet ) )
pSet = nullptr;
if ( !pSet )
{
// new Section with no own PageDesc/-Break
// -> write follow section break;
const SwSectionFormat& rFormat = *rSection.GetFormat();
ReplaceCr( msword::PageBreak ); // Indicator for Page/Section-Break
// Get the page in use at the top of this section
SwNodeIndex aIdxTmp(rSectionNode, 1);
const SwPageDesc *pCurrent =
SwPageDesc::GetPageDescOfNode(aIdxTmp.GetNode());
if (!pCurrent)
pCurrent = m_pCurrentPageDesc;
AppendSection( pCurrent, &rFormat, nRstLnNum );
}
}
if ( TOX_CONTENT_SECTION == rSection.GetType() )
m_bStartTOX = true;
}
void WW8Export::AppendSection( const SwPageDesc *pPageDesc, const SwSectionFormat* pFormat, sal_uLong nLnNum )
{
pSepx->AppendSep(Fc2Cp(Strm().Tell()), pPageDesc, pFormat, nLnNum);
}
// Flys
void WW8AttributeOutput::OutputFlyFrame_Impl( const ww8::Frame& rFormat, const Point& rNdTopLeft )
{
const SwFrameFormat &rFrameFormat = rFormat.GetFrameFormat();
const SwFormatAnchor& rAnch = rFrameFormat.GetAnchor();
bool bUseEscher = true;
if (rFormat.IsInline())
{
ww8::Frame::WriterSource eType = rFormat.GetWriterType();
bUseEscher = eType != ww8::Frame::eGraphic && eType != ww8::Frame::eOle;
/*
A special case for converting some inline form controls to form fields
when in winword 8+ mode
*/
if (bUseEscher && (eType == ww8::Frame::eFormControl))
{
if ( m_rWW8Export.MiserableFormFieldExportHack( rFrameFormat ) )
return ;
}
}
if (bUseEscher)
{
// write as escher
m_rWW8Export.AppendFlyInFlys(rFormat, rNdTopLeft);
}
else
{
bool bDone = false;
// Fetch from node and last node the position in the section
const SwNodeIndex* pNodeIndex = rFrameFormat.GetContent().GetContentIdx();
sal_uLong nStt = pNodeIndex ? pNodeIndex->GetIndex()+1 : 0;
sal_uLong nEnd = pNodeIndex ? pNodeIndex->GetNode().EndOfSectionIndex() : 0;
if( nStt >= nEnd ) // no range, hence no valid node
return;
if ( !m_rWW8Export.IsInTable() && rFormat.IsInline() )
{
//Test to see if this textbox contains only a single graphic/ole
SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
if ( pParTextNode && !m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode() )
bDone = true;
}
if( !bDone )
{
m_rWW8Export.SaveData( nStt, nEnd );
Point aOffset;
if ( m_rWW8Export.m_pParentFrame )
{
/* Munge flys in fly into absolutely positioned elements for word 6 */
const SwTextNode* pParTextNode = rAnch.GetContentAnchor()->nNode.GetNode().GetTextNode();
const SwRect aPageRect = pParTextNode->FindPageFrameRect();
aOffset = rFrameFormat.FindLayoutRect().Pos();
aOffset -= aPageRect.Pos();
m_rWW8Export.m_pFlyOffset = &aOffset;
m_rWW8Export.m_eNewAnchorType = RndStdIds::FLY_AT_PAGE;
}
m_rWW8Export.m_pParentFrame = &rFormat;
if (
m_rWW8Export.IsInTable() &&
(RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) &&
!m_rWW8Export.m_pDoc->GetNodes()[ nStt ]->IsNoTextNode()
)
{
// note: set Flag bOutTable again,
// because we deliver the normal content of the table cell, and no border
// ( Flag was deleted above in aSaveData() )
m_rWW8Export.m_bOutTable = true;
const OUString& aName = rFrameFormat.GetName();
m_rWW8Export.StartCommentOutput(aName);
m_rWW8Export.WriteText();
m_rWW8Export.EndCommentOutput(aName);
}
else
m_rWW8Export.WriteText();
m_rWW8Export.RestoreData();
}
}
}
void AttributeOutputBase::OutputFlyFrame( const ww8::Frame& rFormat )
{
if ( !rFormat.GetContentNode() )
return;
const SwContentNode &rNode = *rFormat.GetContentNode();
Point aLayPos;
// get the Layout Node-Position
if (RndStdIds::FLY_AT_PAGE == rFormat.GetFrameFormat().GetAnchor().GetAnchorId())
aLayPos = rNode.FindPageFrameRect().Pos();
else
aLayPos = rNode.FindLayoutRect().Pos();
OutputFlyFrame_Impl( rFormat, aLayPos );
}
// write data of any redline
void WW8AttributeOutput::Redline( const SwRedlineData* pRedline )
{
if ( !pRedline )
return;
if ( pRedline->Next() )
Redline( pRedline->Next() );
static const sal_uInt16 insSprmIds[ 3 ] =
{
// Ids for insert // for WW8
NS_sprm::sprmCFRMarkIns, NS_sprm::sprmCIbstRMark, NS_sprm::sprmCDttmRMark,
};
static const sal_uInt16 delSprmIds[ 3 ] =
{
// Ids for delete // for WW8
NS_sprm::sprmCFRMarkDel, NS_sprm::sprmCIbstRMarkDel, NS_sprm::sprmCDttmRMarkDel,
};
const sal_uInt16* pSprmIds = nullptr;
switch( pRedline->GetType() )
{
case nsRedlineType_t::REDLINE_INSERT:
pSprmIds = insSprmIds;
break;
case nsRedlineType_t::REDLINE_DELETE:
pSprmIds = delSprmIds;
break;
case nsRedlineType_t::REDLINE_FORMAT:
m_rWW8Export.InsUInt16( NS_sprm::sprmCPropRMark90 );
m_rWW8Export.pO->push_back( 7 ); // len
m_rWW8Export.pO->push_back( 1 );
m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
break;
default:
OSL_ENSURE(false, "Unhandled redline type for export");
break;
}
if ( pSprmIds )
{
m_rWW8Export.InsUInt16( pSprmIds[0] );
m_rWW8Export.pO->push_back( 1 );
m_rWW8Export.InsUInt16( pSprmIds[1] );
m_rWW8Export.InsUInt16( m_rWW8Export.AddRedlineAuthor( pRedline->GetAuthor() ) );
m_rWW8Export.InsUInt16( pSprmIds[2] );
m_rWW8Export.InsUInt32( sw::ms::DateTime2DTTM( pRedline->GetTimeStamp() ));
}
}
void MSWordExportBase::OutputContentNode( SwContentNode& rNode )
{
switch ( rNode.GetNodeType() )
{
case SwNodeType::Text:
OutputTextNode( *rNode.GetTextNode() );
break;
case SwNodeType::Grf:
OutputGrfNode( *rNode.GetGrfNode() );
break;
case SwNodeType::Ole:
OutputOLENode( *rNode.GetOLENode() );
break;
default:
SAL_WARN("sw.ww8", "Unhandled node, type == " << static_cast<int>(rNode.GetNodeType()) );
break;
}
}
WW8Ruby::WW8Ruby(const SwTextNode& rNode, const SwFormatRuby& rRuby, const MSWordExportBase& rExport ):
m_nJC(0),
m_cDirective(0),
m_nRubyHeight(0),
m_nBaseHeight(0)
{
switch ( rRuby.GetAdjustment() )
{
case css::text::RubyAdjust_LEFT:
m_nJC = 3;
m_cDirective = 'l';
break;
case css::text::RubyAdjust_CENTER:
//defaults to 0
break;
case css::text::RubyAdjust_RIGHT:
m_nJC = 4;
m_cDirective = 'r';
break;
case css::text::RubyAdjust_BLOCK:
m_nJC = 1;
m_cDirective = 'd';
break;
case css::text::RubyAdjust_INDENT_BLOCK:
m_nJC = 2;
m_cDirective = 'd';
break;
default:
OSL_ENSURE( false,"Unhandled Ruby justification code" );
break;
}
if ( rRuby.GetPosition() == css::text::RubyPosition::INTER_CHARACTER )
{
m_nJC = 5;
m_cDirective = 0;
}
/*
MS needs to know the name and size of the font used in the ruby item,
but we could have written it in a mixture of asian and western
scripts, and each of these can be a different font and size than the
other, so we make a guess based upon the first character of the text,
defaulting to asian.
*/
assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
sal_uInt16 nRubyScript = g_pBreakIt->GetBreakIter()->getScriptType(rRuby.GetText(), 0);
const SwTextRuby* pRubyText = rRuby.GetTextRuby();
const SwCharFormat* pFormat = pRubyText ? pRubyText->GetCharFormat() : nullptr;
if (pFormat)
{
const auto& rFont
= ItemGet<SvxFontItem>(*pFormat, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
m_sFontFamily = rFont.GetFamilyName();
const auto& rHeight = ItemGet<SvxFontHeightItem>(
*pFormat, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
m_nRubyHeight = rHeight.GetHeight();
}
else
{
/*Get defaults if no formatting on ruby text*/
const SfxItemPool* pPool = rNode.GetSwAttrSet().GetPool();
pPool = pPool ? pPool : &rExport.m_pDoc->GetAttrPool();
const auto& rFont
= DefaultItemGet<SvxFontItem>(*pPool, GetWhichOfScript(RES_CHRATR_FONT, nRubyScript));
m_sFontFamily = rFont.GetFamilyName();
const auto& rHeight = DefaultItemGet<SvxFontHeightItem>(
*pPool, GetWhichOfScript(RES_CHRATR_FONTSIZE, nRubyScript));
m_nRubyHeight = rHeight.GetHeight();
}
if (pRubyText)
nRubyScript
= g_pBreakIt->GetBreakIter()->getScriptType(rNode.GetText(), pRubyText->GetStart());
else
nRubyScript = i18n::ScriptType::ASIAN;
const OUString &rText = rNode.GetText();
sal_uInt16 nScript = i18n::ScriptType::LATIN;
if (!rText.isEmpty())
nScript = g_pBreakIt->GetBreakIter()->getScriptType(rText, 0);
sal_uInt16 nWhich = GetWhichOfScript(RES_CHRATR_FONTSIZE, nScript);
auto& rHeightItem = static_cast<const SvxFontHeightItem&>(rExport.GetItem(nWhich));
m_nBaseHeight = rHeightItem.GetHeight();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V595 The 'pFont' pointer was utilized before it was verified against nullptr. Check lines: 504, 517.
↑ V560 A part of conditional expression is always true: pRet.
↑ V547 Expression '!pCurRedline' is always true.
↑ V1029 Numeric Truncation Error. Result of the 'size' function is written to the 16-bit variable.
↑ V522 There might be dereferencing of a potential null pointer 'pNewCharFmtGrabBag'.
↑ V506 Pointer to local variable 'aOffset' is stored outside the scope of this variable. Such a pointer will become invalid.
↑ V560 A part of conditional expression is always true: pBreakAtParaStyle.