summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Stahl <mstahl@redhat.com>2013-06-15 21:25:27 +0200
committerMichael Stahl <mstahl@redhat.com>2013-06-20 00:34:38 +0200
commit6db39dbd7378351f6476f6db25eb7110c9cfb291 (patch)
tree0f9321d40740e87e80d8ed05a7c7f474d5310afd
parente012f326c1c32c053304998a6826cb322f2c7728 (diff)
fdo#52028: sw: let text formatting ignore RSID in automatic styles
A suprising regression from 062eaeffe7cb986255063bb9b0a5f3fb3fc8e34c: The RSID text attributes that are inserted for every user-inserted text cause the text formatting (SwAttrIter) to create a lot more text portions, and the portion breaks make font kerning impossible. This is the only way i can think of to fix this problem; alternatives that don't work are splitting the RSID out of the AUTOFMT hint into a separate one and combining them in the sw UNO wrappers (fails because SwXAutoStylesEnumerator actually does need to enumerate every AUTOFMT including RSID), trying to detect and ignore them just in the text formatting (the SwAttrIter cannot easily/cheaply detect when it's allowed to skip), and having an internal subdivision inside the AUTOFMT hint (one subsection for every RSID change) (which does not work because it cannot ignore RSID-only AUTOFMTs completely). Solve the problem with 2 additional flags on AUTOFMT and CHARFMT attributes: FormatIgnoreStart and FormatIgnoreEnd, which indicate to SwAttrIter::GetNextAttr() that the start or end of the hint should be ignored, so that effectively it is merged with the preceding/subsequent hint. Of course the UNO API does not respect the flags so we can store the RSIDs in automatic styles. The flags are maintained in SwpHints::MergePortions, which detects both RSID-only AUTOFMT hints (which can be ignored completely), and the situation of N CHARFMT hints + AUTOFMT hint vs. N CHARFMT hints + AUTOFMT hint where the AUTOFMT hints differ only in their RSID attribute. This means that MergePortions needs to be called more often now, in cases where the ignore flags may have been invalidated, such as: - insertion of text with possible DontExpand flag set on hints - deletion of hints - SETATTR_NOHINTADJUST mode Change-Id: I1fb95a87c654c67d114f6f7f2c43b847c50b0ffa
-rw-r--r--sw/inc/ndhints.hxx2
-rw-r--r--sw/inc/swtypes.hxx2
-rw-r--r--sw/inc/txatbase.hxx6
-rw-r--r--sw/source/core/inc/rolbck.hxx2
-rw-r--r--sw/source/core/layout/frmtool.cxx3
-rw-r--r--sw/source/core/layout/laycache.cxx5
-rw-r--r--sw/source/core/text/itratr.cxx24
-rw-r--r--sw/source/core/txtnode/ndhints.cxx88
-rw-r--r--sw/source/core/txtnode/ndtxt.cxx44
-rw-r--r--sw/source/core/txtnode/thints.cxx213
-rw-r--r--sw/source/core/txtnode/txatbase.cxx2
-rw-r--r--sw/source/core/txtnode/txtedt.cxx4
-rw-r--r--sw/source/core/undo/rolbck.cxx14
13 files changed, 371 insertions, 38 deletions
diff --git a/sw/inc/ndhints.hxx b/sw/inc/ndhints.hxx
index 0d7a158ee889..d8b611535cbc 100644
--- a/sw/inc/ndhints.hxx
+++ b/sw/inc/ndhints.hxx
@@ -116,7 +116,7 @@ public:
inline sal_uInt16 Count() const { return m_HintStarts.size(); }
#ifdef DBG_UTIL
- bool Check() const;
+ bool Check(bool) const;
#endif
};
diff --git a/sw/inc/swtypes.hxx b/sw/inc/swtypes.hxx
index 187e070e8da8..f5ba633310ab 100644
--- a/sw/inc/swtypes.hxx
+++ b/sw/inc/swtypes.hxx
@@ -192,6 +192,8 @@ namespace nsSetAttrMode
const SetAttrMode SETATTR_DONTREPLACE = 0x0002; // Don't replace another text attribute.
const SetAttrMode SETATTR_NOTXTATRCHR = 0x0004; // Don't insert 0xFF at attributes with no end.
+ /// attention: NOHINTADJUST prevents MergePortions!
+ /// when using this need to pay attention to ignore start/end flags of hint
const SetAttrMode SETATTR_NOHINTADJUST = 0x0008; // No merging of ranges.
const SetAttrMode SETATTR_NOFORMATATTR = 0x0010; // Do not change into format attribute.
const SetAttrMode SETATTR_DONTCHGNUMRULE = 0x0020; // Do not change NumRule.
diff --git a/sw/inc/txatbase.hxx b/sw/inc/txatbase.hxx
index 3a250f068885..e69b08871fd3 100644
--- a/sw/inc/txatbase.hxx
+++ b/sw/inc/txatbase.hxx
@@ -55,6 +55,8 @@ private:
bool m_bDontExpandStart : 1; // don't expand start at paragraph start (ruby)
bool m_bNesting : 1; // SwTxtAttrNesting
bool m_bHasDummyChar : 1; // without end + meta
+ bool m_bFormatIgnoreStart : 1; ///< text formatting should ignore start
+ bool m_bFormatIgnoreEnd : 1; ///< text formatting should ignore end
protected:
SwTxtAttr( SfxPoolItem& rAttr, xub_StrLen nStart );
@@ -94,6 +96,10 @@ public:
bool IsDontExpandStartAttr() const { return m_bDontExpandStart; }
bool IsNesting() const { return m_bNesting; }
bool HasDummyChar() const { return m_bHasDummyChar; }
+ bool IsFormatIgnoreStart() const { return m_bFormatIgnoreStart; }
+ bool IsFormatIgnoreEnd () const { return m_bFormatIgnoreEnd ; }
+ void SetFormatIgnoreStart(bool bFlag) { m_bFormatIgnoreStart = bFlag; }
+ void SetFormatIgnoreEnd (bool bFlag) { m_bFormatIgnoreEnd = bFlag; }
inline const SfxPoolItem& GetAttr() const;
inline SfxPoolItem& GetAttr();
diff --git a/sw/source/core/inc/rolbck.hxx b/sw/source/core/inc/rolbck.hxx
index 62b01844f43c..ddff2a21e50f 100644
--- a/sw/source/core/inc/rolbck.hxx
+++ b/sw/source/core/inc/rolbck.hxx
@@ -122,6 +122,8 @@ class SwHistorySetTxt : public SwHistoryHint
const sal_uLong m_nNodeIndex;
const xub_StrLen m_nStart;
const xub_StrLen m_nEnd;
+ bool m_bFormatIgnoreStart : 1;
+ bool m_bFormatIgnoreEnd : 1;
public:
SwHistorySetTxt( SwTxtAttr* pTxtHt, sal_uLong nNode );
diff --git a/sw/source/core/layout/frmtool.cxx b/sw/source/core/layout/frmtool.cxx
index f9337b13e268..91239873e537 100644
--- a/sw/source/core/layout/frmtool.cxx
+++ b/sw/source/core/layout/frmtool.cxx
@@ -1277,8 +1277,7 @@ void _InsertCnt( SwLayoutFrm *pLay, SwDoc *pDoc,
if ( pNd->IsCntntNode() )
{
SwCntntNode* pNode = (SwCntntNode*)pNd;
- pFrm = pNode->IsTxtNode() ? new SwTxtFrm( (SwTxtNode*)pNode, pLay ) :
- pNode->MakeFrm( pLay );
+ pFrm = pNode->MakeFrm(pLay);
if( pPageMaker )
pPageMaker->CheckInsert( nIndex );
diff --git a/sw/source/core/layout/laycache.cxx b/sw/source/core/layout/laycache.cxx
index 1753d8544e50..ebd9e722aa2e 100644
--- a/sw/source/core/layout/laycache.cxx
+++ b/sw/source/core/layout/laycache.cxx
@@ -843,8 +843,9 @@ bool SwLayHelper::CheckInsert( sal_uLong nNodeIndex )
}
else
{
- SwTxtFrm *pNew = new SwTxtFrm( ((SwTxtFrm*)rpFrm)->
- GetTxtNode(), rpFrm );
+ SwTxtFrm *const pNew = static_cast<SwTxtFrm*>(
+ static_cast<SwTxtFrm*>(rpFrm)
+ ->GetTxtNode()->MakeFrm(rpFrm));
pNew->ManipOfst( nOfst );
pNew->SetFollow( ((SwTxtFrm*)rpFrm)->GetFollow() );
((SwTxtFrm*)rpFrm)->SetFollow( pNew );
diff --git a/sw/source/core/text/itratr.cxx b/sw/source/core/text/itratr.cxx
index d95ee5500f49..fba4898bbf84 100644
--- a/sw/source/core/text/itratr.cxx
+++ b/sw/source/core/text/itratr.cxx
@@ -311,12 +311,26 @@ xub_StrLen SwAttrIter::GetNextAttr( ) const
xub_StrLen nNext = STRING_LEN;
if( pHints )
{
- if (pHints->GetStartCount() > nStartIndex) // Gibt es noch Starts?
- nNext = (*pHints->GetStart(nStartIndex)->GetStart());
- if (pHints->GetEndCount() > nEndIndex) // Gibt es noch Enden?
+ // are there attribute starts left?
+ for (sal_uInt16 i = nStartIndex; i < pHints->GetStartCount(); ++i)
{
- xub_StrLen nNextEnd = (*pHints->GetEnd(nEndIndex)->GetAnyEnd());
- if ( nNextEnd<nNext ) nNext = nNextEnd; // Wer ist naeher?
+ SwTxtAttr *const pAttr(pHints->GetStart(i));
+ if (!pAttr->IsFormatIgnoreStart())
+ {
+ nNext = *pAttr->GetStart();
+ break;
+ }
+ }
+ // are there attribute ends left?
+ for (sal_uInt16 i = nEndIndex; i < pHints->GetEndCount(); ++i)
+ {
+ SwTxtAttr *const pAttr(pHints->GetEnd(i));
+ if (!pAttr->IsFormatIgnoreEnd())
+ {
+ xub_StrLen const nNextEnd = *pAttr->GetAnyEnd();
+ nNext = std::min(nNext, nNextEnd); // pick nearest one
+ break;
+ }
}
}
if (m_pTxtNode!=NULL) {
diff --git a/sw/source/core/txtnode/ndhints.cxx b/sw/source/core/txtnode/ndhints.cxx
index 02ea3443a884..4d070e1fb57a 100644
--- a/sw/source/core/txtnode/ndhints.cxx
+++ b/sw/source/core/txtnode/ndhints.cxx
@@ -24,6 +24,8 @@
#ifdef DBG_UTIL
#include <pam.hxx>
+#include <fmtautofmt.hxx>
+#include <set>
#endif
@@ -171,7 +173,7 @@ sal_uInt16 SwpHintsArray::GetPos( const SwTxtAttr *pHt ) const
return false; \
}
-bool SwpHintsArray::Check() const
+bool SwpHintsArray::Check(bool bPortionsMerged) const
{
// 1) gleiche Anzahl in beiden Arrays
CHECK_ERR( m_HintStarts.size() == m_HintEnds.size(),
@@ -181,6 +183,23 @@ bool SwpHintsArray::Check() const
const SwTxtAttr *pLastStart = 0;
const SwTxtAttr *pLastEnd = 0;
+ std::set<SwTxtAttr const*> RsidOnlyAutoFmts;
+ if (bPortionsMerged)
+ {
+ for (sal_uInt16 i = 0; i < Count(); ++i)
+ {
+ SwTxtAttr const*const pHint(m_HintStarts[i]);
+ if (RES_TXTATR_AUTOFMT == pHint->Which())
+ {
+ boost::shared_ptr<SfxItemSet> const pSet(
+ pHint->GetAutoFmt().GetStyleHandle());
+ if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
+ {
+ RsidOnlyAutoFmts.insert(pHint);
+ }
+ }
+ }
+ }
for( sal_uInt16 i = 0; i < Count(); ++i )
{
@@ -270,6 +289,73 @@ bool SwpHintsArray::Check() const
"HintsCheck: Portion inconsistency. "
"This can be temporarily ok during undo operations" );
+ // 8 1/2) format ignore start/end flag check
+ // (problems because MergePortions buggy or not called)
+ if (bPortionsMerged)
+ {
+ if (RES_TXTATR_AUTOFMT == pHt->Which() ||
+ RES_TXTATR_CHARFMT == pHt->Which())
+ {
+ // mostly ignore the annoying no-length hints
+ // BuildPortions inserts these in the middle of an exsiting one
+ bool const bNoLength(*pHt->GetStart() == *pHt->GetEnd());
+ bool bNeedContinuation(!bNoLength && pHt->IsFormatIgnoreEnd());
+ bool bForbidContinuation(!bNoLength && !bNeedContinuation);
+ if (RES_TXTATR_AUTOFMT == pHt->Which())
+ {
+ if (RsidOnlyAutoFmts.find(pHt) != RsidOnlyAutoFmts.end())
+ {
+ assert(pHt->IsFormatIgnoreStart());
+ bNeedContinuation = false;
+ // don't forbid continuation - may be other hint here!
+ }
+ }
+ if (bNeedContinuation || bForbidContinuation)
+ {
+ bool bFound(false);
+ for (sal_uInt16 j = i + 1; j < Count(); ++j)
+ {
+ SwTxtAttr *const pOther(m_HintStarts[j]);
+ if (*pOther->GetStart() > *pHt->GetEnd())
+ {
+ break; // done
+ }
+ else if (*pOther->GetStart() == *pOther->GetAnyEnd())
+ {
+ continue; // empty hint: ignore
+ }
+ else if (*pOther->GetStart() == *pHt->GetEnd())
+ {
+ if (RES_TXTATR_AUTOFMT == pOther->Which() ||
+ RES_TXTATR_CHARFMT == pOther->Which())
+ { // multiple charfmt on same range must all match
+ if (bNeedContinuation)
+ {
+ assert(pOther->IsFormatIgnoreStart());
+ bFound = true;
+ }
+ else if (bForbidContinuation &&
+ (RsidOnlyAutoFmts.find(pOther) ==
+ RsidOnlyAutoFmts.end()))
+ {
+ assert(!pOther->IsFormatIgnoreStart());
+ }
+ }
+ }
+ }
+ if (bNeedContinuation)
+ {
+ assert(bFound); // ? can this happen temp. during undo?
+ }
+ }
+ }
+ else
+ {
+ assert(!pHt->IsFormatIgnoreStart());
+ assert(!pHt->IsFormatIgnoreEnd());
+ }
+ }
+
// 9) nesting portion check
if (pHtThis->IsNesting())
{
diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx
index 8d4cdaf2f831..7b3b98dcc3a9 100644
--- a/sw/source/core/txtnode/ndtxt.cxx
+++ b/sw/source/core/txtnode/ndtxt.cxx
@@ -95,7 +95,7 @@ TYPEINIT1( SwTxtNode, SwCntntNode )
#ifdef DBG_UTIL
#define CHECK_SWPHINTS(pNd) { if( pNd->GetpSwpHints() && \
!pNd->GetDoc()->IsInReading() ) \
- pNd->GetpSwpHints()->Check(); }
+ pNd->GetpSwpHints()->Check(true); }
#else
#define CHECK_SWPHINTS(pNd)
#endif
@@ -263,6 +263,12 @@ SwTxtNode::~SwTxtNode()
SwCntntFrm *SwTxtNode::MakeFrm( SwFrm* pSib )
{
+ // fdo#52028: ODF file import does not result in MergePortions being called
+ // for every attribute, since that would be inefficient. So call it here.
+ if (m_pSwpHints)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
SwCntntFrm *pFrm = new SwTxtFrm( this, pSib );
return pFrm;
}
@@ -883,6 +889,7 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen,
{
bool bNoExp = false;
bool bResort = false;
+ bool bMergePortionsNeeded = false;
const sal_uInt16 coArrSz = static_cast<sal_uInt16>(RES_TXTATR_WITHEND_END) -
static_cast<sal_uInt16>(RES_CHRATR_BEGIN);
@@ -929,6 +936,11 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen,
{
pHint->SetDontExpand( false );
bResort = true;
+ // could have a continuation with IgnoreStart()...
+ if (pHint->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
if ( pHint->IsCharFmtAttr() )
{
bNoExp = true;
@@ -969,7 +981,11 @@ void SwTxtNode::Update( SwIndex const & rPos, const xub_StrLen nChangeLen,
}
}
}
- if ( bResort )
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this); // does Resort too
+ }
+ else if (bResort)
{
m_pSwpHints->Resort();
}
@@ -1745,6 +1761,7 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx,
if ( HasHints() )
{
+ bool bMergePortionsNeeded(false);
for ( sal_uInt16 i = 0; i < m_pSwpHints->Count() &&
rIdx >= *(*m_pSwpHints)[i]->GetStart(); ++i )
{
@@ -1764,6 +1781,14 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx,
*pHt->GetStart() = *pHt->GetStart() - nLen;
*pEndIdx = *pEndIdx - nLen;
m_pSwpHints->DeleteAtPos(i);
+ // could be that pHt has IsFormatIgnoreEnd set, and it's
+ // not a RSID-only hint - now we have the inserted text
+ // between pHt and its continuation... which we don't know.
+ // punt the job to MergePortions below.
+ if (pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
InsertHint( pHt, nsSetAttrMode::SETATTR_NOHINTADJUST );
}
// empty hints at insert position?
@@ -1792,9 +1817,14 @@ OUString SwTxtNode::InsertText( const XubString & rStr, const SwIndex & rIdx,
// Kein Feld, am Absatzanfang, HintExpand
m_pSwpHints->DeleteAtPos(i);
*pHt->GetStart() = *pHt->GetStart() - nLen;
+ // no effect on format ignore flags here (para start)
InsertHint( pHt, nsSetAttrMode::SETATTR_NOHINTADJUST );
}
}
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
TryDeleteSwpHints();
}
@@ -2042,6 +2072,7 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart,
// 2. Attribute verschieben
// durch das Attribute-Array, bis der Anfang des Geltungsbereiches
// des Attributs hinter dem zu verschiebenden Bereich liegt
+ bool bMergePortionsNeeded(false);
sal_uInt16 nAttrCnt = 0;
while ( m_pSwpHints && (nAttrCnt < m_pSwpHints->Count()) )
{
@@ -2085,6 +2116,10 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart,
// Attribut verschieben
m_pSwpHints->Delete( pHt );
// die Start/End Indicies neu setzen
+ if (pHt->IsFormatIgnoreStart() || pHt->IsFormatIgnoreEnd())
+ {
+ bMergePortionsNeeded = true;
+ }
*pHt->GetStart() =
nDestStart + (nAttrStartIdx - nTxtStartIdx);
if( pEndIdx )
@@ -2164,6 +2199,11 @@ void SwTxtNode::CutImpl( SwTxtNode * const pDest, const SwIndex & rDestStart,
Update( rStart, nLen, sal_True, sal_True );
}
+ if (bMergePortionsNeeded)
+ {
+ m_pSwpHints->MergePortions(*this);
+ }
+
CHECK_SWPHINTS(this);
}
diff --git a/sw/source/core/txtnode/thints.cxx b/sw/source/core/txtnode/thints.cxx
index 71da72eb18c2..9bcfcf426ea8 100644
--- a/sw/source/core/txtnode/thints.cxx
+++ b/sw/source/core/txtnode/thints.cxx
@@ -72,9 +72,11 @@
#include <map>
#ifdef DBG_UTIL
-#define CHECK Check();
+#define CHECK Check(true);
+#define CHECK_NOTMERGED Check(false);
#else
#define CHECK
+#define CHECK_NOTMERGED
#endif
using namespace ::com::sun::star::i18n;
@@ -649,7 +651,7 @@ void SwpHints::BuildPortions( SwTxtNode& rNode, SwTxtAttr& rNewHint,
#ifdef DBG_UTIL
if( !rNode.GetDoc()->IsInReading() )
- CHECK;
+ CHECK_NOTMERGED; // ignore flags not set properly yet, don't check them
#endif
//
@@ -1563,6 +1565,8 @@ void SwTxtNode::DeleteAttributes( const sal_uInt16 nWhich,
const SfxPoolItem* pHiddenItem = CharFmt::GetItem( *pTxtHt, RES_CHRATR_HIDDEN );
if ( pHiddenItem )
SetCalcHiddenCharFlags();
+ // for auto styles DeleteAttributes is only called from Undo
+ // so it shouldn't need to care about ignore start/end flags
}
xub_StrLen const * const pEndIdx = pTxtHt->GetEnd();
@@ -2306,6 +2310,9 @@ SwTxtNode::impl_FmtToTxtAttr(const SfxItemSet& i_rAttrSet)
aCurRange = aRange.second;
}
+ // hints were directly inserted, so need to fix the Ignore flags now
+ m_pSwpHints->MergePortions(*this);
+
// 3. Clear items from the node
std::vector<sal_uInt16> aClearedIds;
lcl_FillWhichIds(i_rAttrSet, aClearedIds);
@@ -2497,8 +2504,9 @@ bool SwpHints::MergePortions( SwTxtNode& rNode )
SwpHintsArray::Resort();
bool bRet = false;
- typedef std::multimap< int, SwTxtAttr* > PortionMap;
+ typedef std::multimap< int, std::pair<SwTxtAttr*, bool> > PortionMap;
PortionMap aPortionMap;
+ std::map<int, bool> RsidOnlyAutoFmtFlagMap;
xub_StrLen nLastPorStart = STRING_LEN;
sal_uInt16 i = 0;
int nKey = 0;
@@ -2513,14 +2521,45 @@ bool SwpHints::MergePortions( SwTxtNode& rNode )
//RES_TXTATR_INETFMT != pHt->Which() )
continue;
+ bool isRsidOnlyAutoFmt(false);
+ // check for RSID-only AUTOFMT
+ if (RES_TXTATR_AUTOFMT == pHt->Which())
+ {
+ boost::shared_ptr<SfxItemSet> const pSet(
+ pHt->GetAutoFmt().GetStyleHandle());
+ if ((pSet->Count() == 1) && pSet->GetItem(RES_CHRATR_RSID, false))
+ {
+ // fdo#52028: this one has _only_ RSID => ignore it completely
+ if (!pHt->IsFormatIgnoreStart() || !pHt->IsFormatIgnoreEnd())
+ {
+ NoteInHistory(pHt);
+ pHt->SetFormatIgnoreStart(true);
+ pHt->SetFormatIgnoreEnd (true);
+ NoteInHistory(pHt, true);
+ }
+ isRsidOnlyAutoFmt = true;
+ }
+ }
+
+ if (*pHt->GetStart() == *pHt->GetEnd())
+ {
+ // no-length hints are a disease. ignore them here.
+ // the SwAttrIter::SeekFwd will not call Rst/Chg for them.
+ continue;
+ }
+
const xub_StrLen nPorStart = *pHt->GetStart();
- if ( nPorStart != nLastPorStart && nLastPorStart != STRING_LEN )
+ if (nPorStart != nLastPorStart)
++nKey;
nLastPorStart = nPorStart;
- aPortionMap.insert( std::pair< const int, SwTxtAttr* >( nKey, pHt ) );
+ aPortionMap.insert(std::make_pair(nKey,
+ std::make_pair(pHt, isRsidOnlyAutoFmt)));
+ RsidOnlyAutoFmtFlagMap[nKey] = isRsidOnlyAutoFmt;
}
// check if portion i can be merged with portion i+1:
+ // note: need to include i=0 to set IgnoreStart and j=nKey+1 to reset
+ // IgnoreEnd at first / last portion
i = 0;
int j = i + 1;
while ( i <= nKey )
@@ -2530,37 +2569,128 @@ bool SwpHints::MergePortions( SwTxtNode& rNode )
PortionMap::iterator aIter1 = aRange1.first;
PortionMap::iterator aIter2 = aRange2.first;
- bool bMerge = true;
- const sal_uInt16 nAttributesInPor1 = static_cast<sal_uInt16>(std::distance( aRange1.first, aRange1.second ));
- const sal_uInt16 nAttributesInPor2 = static_cast<sal_uInt16>(std::distance( aRange2.first, aRange2.second ));
-
- if ( nAttributesInPor1 == nAttributesInPor2 && nAttributesInPor1 != 0 )
+ enum { MATCH, DIFFER_ONLY_RSID, DIFFER } eMerge(MATCH);
+ size_t const nAttributesInPor1 = std::distance(aRange1.first, aRange1.second);
+ size_t const nAttributesInPor2 = std::distance(aRange2.first, aRange2.second);
+ bool const isRsidOnlyAutoFmt1(RsidOnlyAutoFmtFlagMap[i]);
+ bool const isRsidOnlyAutoFmt2(RsidOnlyAutoFmtFlagMap[j]);
+
+ // if both have one they could be equal, but not if only one has it
+ bool const bSkipRsidOnlyAutoFmt(nAttributesInPor1 != nAttributesInPor2);
+
+ // this loop needs to handle the case where one has a CHARFMT and the
+ // other CHARFMT + RSID-only AUTOFMT, so...
+ // want to skip over RSID-only AUTOFMT here, hence the -1
+ if ((nAttributesInPor1 - ((isRsidOnlyAutoFmt1) ? 1 : 0)) ==
+ (nAttributesInPor2 - ((isRsidOnlyAutoFmt2) ? 1 : 0))
+ && (nAttributesInPor1 != 0 || nAttributesInPor2 != 0))
{
- while ( aIter1 != aRange1.second )
+ // _if_ there is one element more either in aRange1 or aRange2
+ // it _must_ be an RSID-only AUTOFMT, which can be ignored here...
+ // But if both have RSID-only AUTOFMT they could be equal, no skip!
+ while (aIter1 != aRange1.second || aIter2 != aRange2.second)
{
- const SwTxtAttr* p1 = (*aIter1).second;
- const SwTxtAttr* p2 = (*aIter2).second;
- if ( *p1->GetEnd() < *p2->GetStart() || p1->Which() != p2->Which() || !(*p1 == *p2) )
+ // first of all test if there's no gap (before skipping stuff!)
+ if (aIter1 != aRange1.second && aIter2 != aRange2.second &&
+ *aIter1->second.first->GetEnd() < *aIter2->second.first->GetStart())
{
- bMerge = false;
+ eMerge = DIFFER;
break;
}
+ // skip it - cannot be equal if bSkipRsidOnlyAutoFmt is set
+ if (bSkipRsidOnlyAutoFmt
+ && aIter1 != aRange1.second && aIter1->second.second)
+ {
+ assert(DIFFER != eMerge);
+ eMerge = DIFFER_ONLY_RSID;
+ ++aIter1;
+ continue;
+ }
+ if (bSkipRsidOnlyAutoFmt
+ && aIter2 != aRange2.second && aIter2->second.second)
+ {
+ assert(DIFFER != eMerge);
+ eMerge = DIFFER_ONLY_RSID;
+ ++aIter2;
+ continue;
+ }
+ assert(aIter1 != aRange1.second && aIter2 != aRange2.second);
+ SwTxtAttr const*const p1 = aIter1->second.first;
+ SwTxtAttr const*const p2 = aIter2->second.first;
+ if (p1->Which() != p2->Which())
+ {
+ eMerge = DIFFER;
+ break;
+ }
+ if (!(*p1 == *p2))
+ {
+ // fdo#52028: for auto styles, check if they differ only
+ // in the RSID, which should have no effect on text layout
+ if (RES_TXTATR_AUTOFMT == p1->Which())
+ {
+ SfxItemSet set1(*p1->GetAutoFmt().GetStyleHandle());
+ SfxItemSet set2(*p2->GetAutoFmt().GetStyleHandle());
+
+ set1.ClearItem(RES_CHRATR_RSID);
+ set2.ClearItem(RES_CHRATR_RSID);
+
+ // sadly SfxItemSet::operator== does not seem to work?
+ SfxItemIter iter1(set1);
+ SfxItemIter iter2(set2);
+ if (set1.Count() == set2.Count())
+ {
+ for (SfxPoolItem const* pItem1 = iter1.FirstItem(),
+ * pItem2 = iter2.FirstItem();
+ pItem1 && pItem2;
+ pItem1 = iter1.NextItem(),
+ pItem2 = iter2.NextItem())
+ {
+ if (pItem1 != pItem2 ||
+ pItem1->Which() != pItem2->Which() ||
+ *pItem1 != *pItem2)
+ {
+ eMerge = DIFFER;
+ break;
+ }
+ if (iter1.IsAtEnd())
+ {
+ assert(iter2.IsAtEnd());
+ eMerge = DIFFER_ONLY_RSID;
+ }
+ }
+ if (DIFFER == eMerge)
+ break; // outer loop too
+ }
+ else
+ {
+ eMerge = DIFFER;
+ break;
+ }
+ }
+ else
+ {
+ eMerge = DIFFER;
+ break;
+ }
+ }
++aIter1;
++aIter2;
}
}
else
{
- bMerge = false;
+ eMerge = DIFFER;
}
- if ( bMerge )
+ if (MATCH == eMerge)
{
+ // important: delete second range so any IgnoreStart on the first
+ // range is still valid
// erase all elements with key i + 1
xub_StrLen nNewPortionEnd = 0;
for ( aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2 )
{
- SwTxtAttr* p2 = (*aIter2).second;
+ SwTxtAttr *const p2 = aIter2->second.first;
nNewPortionEnd = *p2->GetEnd();
const sal_uInt16 nCountBeforeDelete = Count();
@@ -2577,7 +2707,7 @@ bool SwpHints::MergePortions( SwTxtNode& rNode )
aRange1 = aPortionMap.equal_range( i );
for ( aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1 )
{
- SwTxtAttr* p1 = (*aIter1).second;
+ SwTxtAttr *const p1 = aIter1->second.first;
NoteInHistory( p1 );
*p1->GetEnd() = nNewPortionEnd;
NoteInHistory( p1, true );
@@ -2586,8 +2716,37 @@ bool SwpHints::MergePortions( SwTxtNode& rNode )
}
else
{
- ++i;
- j = i + 1;
+ // when not merging the ignore flags need to be either set or reset
+ // (reset too in case one of the autofmts was recently changed)
+ bool const bSetIgnoreFlag(DIFFER_ONLY_RSID == eMerge);
+ for (aIter1 = aRange1.first; aIter1 != aRange1.second; ++aIter1)
+ {
+ if (!aIter1->second.second) // already set above, don't change
+ {
+ SwTxtAttr *const pCurrent(aIter1->second.first);
+ if (pCurrent->IsFormatIgnoreEnd() != bSetIgnoreFlag)
+ {
+ NoteInHistory(pCurrent);
+ pCurrent->SetFormatIgnoreEnd(bSetIgnoreFlag);
+ NoteInHistory(pCurrent, true);
+ }
+ }
+ }
+ for (aIter2 = aRange2.first; aIter2 != aRange2.second; ++aIter2)
+ {
+ if (!aIter2->second.second) // already set above, don't change
+ {
+ SwTxtAttr *const pCurrent(aIter2->second.first);
+ if (pCurrent->IsFormatIgnoreStart() != bSetIgnoreFlag)
+ {
+ NoteInHistory(pCurrent);
+ pCurrent->SetFormatIgnoreStart(bSetIgnoreFlag);
+ NoteInHistory(pCurrent, true);
+ }
+ }
+ }
+ i = j; // ++i not enough: i + 1 may have been deleted (MATCH)!
+ ++j;
}
}
@@ -2676,6 +2835,16 @@ bool SwpHints::TryInsertHint( SwTxtAttr* const pHint, SwTxtNode &rNode,
// #i75430# Recalc hidden flags if necessary
case RES_TXTATR_AUTOFMT:
{
+ if (*pHint->GetStart() == *pHint->GetEnd())
+ {
+ boost::shared_ptr<SfxItemSet> const pSet(
+ pHint->GetAutoFmt().GetStyleHandle());
+ if (pSet->Count() == 1 && pSet->GetItem(RES_CHRATR_RSID, false))
+ { // empty range RSID-only hints could cause trouble, there's no
+ rNode.DestroyAttr(pHint); // need for them so don't insert
+ return false;
+ }
+ }
// Check if auto style contains hidden attribute:
const SfxPoolItem* pHiddenItem = CharFmt::GetItem( *pHint, RES_CHRATR_HIDDEN );
if ( pHiddenItem )
@@ -2974,7 +3143,7 @@ void SwpHints::DeleteAtPos( const sal_uInt16 nPos )
}
CalcFlags();
- CHECK;
+ CHECK_NOTMERGED; // called from BuildPortions
}
// Ist der Hint schon bekannt, dann suche die Position und loesche ihn.
diff --git a/sw/source/core/txtnode/txatbase.cxx b/sw/source/core/txtnode/txatbase.cxx
index e0acb29d72f2..e59e969d78b6 100644
--- a/sw/source/core/txtnode/txatbase.cxx
+++ b/sw/source/core/txtnode/txatbase.cxx
@@ -34,6 +34,8 @@ SwTxtAttr::SwTxtAttr( SfxPoolItem& rAttr, xub_StrLen nStart )
, m_bDontExpandStart( false )
, m_bNesting( false )
, m_bHasDummyChar( false )
+ , m_bFormatIgnoreStart(false)
+ , m_bFormatIgnoreEnd(false)
{
}
diff --git a/sw/source/core/txtnode/txtedt.cxx b/sw/source/core/txtnode/txtedt.cxx
index 691da301329c..016d56a5db02 100644
--- a/sw/source/core/txtnode/txtedt.cxx
+++ b/sw/source/core/txtnode/txtedt.cxx
@@ -584,8 +584,8 @@ void SwTxtNode::RstAttr(const SwIndex &rIdx, xub_StrLen nLen, sal_uInt16 nWhich,
if (bChanged)
{
if ( HasHints() )
- {
- m_pSwpHints->Resort();
+ { // possibly sometimes Resort would be sufficient, but...
+ m_pSwpHints->MergePortions(*this);
}
//TxtFrm's reagieren auf aHint, andere auf aNew
SwUpdateAttr aHint( nMin, nMax, 0 );
diff --git a/sw/source/core/undo/rolbck.cxx b/sw/source/core/undo/rolbck.cxx
index ced76e507c80..59cfec65def5 100644
--- a/sw/source/core/undo/rolbck.cxx
+++ b/sw/source/core/undo/rolbck.cxx
@@ -195,6 +195,8 @@ SwHistorySetTxt::SwHistorySetTxt( SwTxtAttr* pTxtHt, sal_uLong nNodePos )
, m_nNodeIndex( nNodePos )
, m_nStart( *pTxtHt->GetStart() )
, m_nEnd( *pTxtHt->GetAnyEnd() )
+ , m_bFormatIgnoreStart(pTxtHt->IsFormatIgnoreStart())
+ , m_bFormatIgnoreEnd (pTxtHt->IsFormatIgnoreEnd ())
{
// Caution: the following attributes generate no format attributes:
// - NoLineBreak, NoHypen, Inserted, Deleted
@@ -234,9 +236,19 @@ void SwHistorySetTxt::SetInDoc( SwDoc* pDoc, bool )
if ( pTxtNd )
{
- pTxtNd->InsertItem( *m_pAttr, m_nStart, m_nEnd,
+ SwTxtAttr *const pAttr = pTxtNd->InsertItem(*m_pAttr, m_nStart, m_nEnd,
nsSetAttrMode::SETATTR_NOTXTATRCHR |
nsSetAttrMode::SETATTR_NOHINTADJUST );
+ // shouldn't be possible to hit any error/merging path from here
+ assert(pAttr);
+ if (m_bFormatIgnoreStart)
+ {
+ pAttr->SetFormatIgnoreStart(true);
+ }
+ if (m_bFormatIgnoreEnd)
+ {
+ pAttr->SetFormatIgnoreEnd(true);
+ }
}
}