diff options
Diffstat (limited to '')
-rw-r--r-- | editeng/source/editeng/editdoc.cxx | 3031 |
1 files changed, 3031 insertions, 0 deletions
diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx new file mode 100644 index 000000000..fbcb4c51e --- /dev/null +++ b/editeng/source/editeng/editdoc.cxx @@ -0,0 +1,3031 @@ +/* -*- 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 <editeng/tstpitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/kernitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/cmapitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/autokernitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/charscaleitem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/xmlcnitm.hxx> +#include <editeng/editids.hrc> +#include <editeng/editdata.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/lspcitem.hxx> + +#include <editdoc.hxx> +#include <editeng/eerdll.hxx> +#include <eerdll2.hxx> +#include "impedit.hxx" + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <svl/grabbagitem.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <libxml/xmlwriter.h> + +#include <algorithm> +#include <cassert> +#include <limits> +#include <memory> +#include <set> +#include <string_view> + +using namespace ::com::sun::star; + + +sal_uInt16 GetScriptItemId( sal_uInt16 nItemId, SvtScriptType nScriptType ) +{ + sal_uInt16 nId = nItemId; + + if ( ( nScriptType == SvtScriptType::ASIAN ) || + ( nScriptType == SvtScriptType::COMPLEX ) ) + { + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_LANGUAGE_CJK : EE_CHAR_LANGUAGE_CTL; + break; + case EE_CHAR_FONTINFO: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTINFO_CJK : EE_CHAR_FONTINFO_CTL; + break; + case EE_CHAR_FONTHEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_FONTHEIGHT_CJK : EE_CHAR_FONTHEIGHT_CTL; + break; + case EE_CHAR_WEIGHT: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_WEIGHT_CJK : EE_CHAR_WEIGHT_CTL; + break; + case EE_CHAR_ITALIC: + nId = ( nScriptType == SvtScriptType::ASIAN ) ? EE_CHAR_ITALIC_CJK : EE_CHAR_ITALIC_CTL; + break; + } + } + + return nId; +} + +bool IsScriptItemValid( sal_uInt16 nItemId, short nScriptType ) +{ + bool bValid = true; + + switch ( nItemId ) + { + case EE_CHAR_LANGUAGE: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_LANGUAGE_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_LANGUAGE_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTINFO: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTINFO_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTINFO_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_FONTHEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_FONTHEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_FONTHEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_WEIGHT: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_WEIGHT_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_WEIGHT_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + case EE_CHAR_ITALIC: + bValid = nScriptType == i18n::ScriptType::LATIN; + break; + case EE_CHAR_ITALIC_CJK: + bValid = nScriptType == i18n::ScriptType::ASIAN; + break; + case EE_CHAR_ITALIC_CTL: + bValid = nScriptType == i18n::ScriptType::COMPLEX; + break; + } + + return bValid; +} + +const SfxItemInfo aItemInfos[EDITITEMCOUNT] = { + { SID_ATTR_FRAMEDIRECTION, true }, // EE_PARA_WRITINGDIR + { 0, true }, // EE_PARA_XMLATTRIBS + { SID_ATTR_PARA_HANGPUNCTUATION, true }, // EE_PARA_HANGINGPUNCTUATION + { SID_ATTR_PARA_FORBIDDEN_RULES, true }, // EE_PARA_FORBIDDENRULES + { SID_ATTR_PARA_SCRIPTSPACE, true }, // EE_PARA_ASIANCJKSPACING + { SID_ATTR_NUMBERING_RULE, true }, // EE_PARA_NUMBULL + { 0, true }, // EE_PARA_HYPHENATE + { 0, true }, // EE_PARA_HYPHENATE_NO_CAPS + { 0, true }, // EE_PARA_BULLETSTATE + { 0, true }, // EE_PARA_OUTLLRSPACE + { SID_ATTR_PARA_OUTLLEVEL, true }, // EE_PARA_OUTLLEVEL + { SID_ATTR_PARA_BULLET, true }, // EE_PARA_BULLET + { SID_ATTR_LRSPACE, true }, // EE_PARA_LRSPACE + { SID_ATTR_ULSPACE, true }, // EE_PARA_ULSPACE + { SID_ATTR_PARA_LINESPACE, true }, // EE_PARA_SBL + { SID_ATTR_PARA_ADJUST, true }, // EE_PARA_JUST + { SID_ATTR_TABSTOP, true }, // EE_PARA_TABS + { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, true }, // EE_PARA_JUST_METHOD + { SID_ATTR_ALIGN_VER_JUSTIFY, true }, // EE_PARA_VER_JUST + { SID_ATTR_CHAR_COLOR, true }, // EE_CHAR_COLOR + { SID_ATTR_CHAR_FONT, true }, // EE_CHAR_FONTINFO + { SID_ATTR_CHAR_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT + { SID_ATTR_CHAR_SCALEWIDTH, true }, // EE_CHAR_FONTWIDTH + { SID_ATTR_CHAR_WEIGHT, true }, // EE_CHAR_WEIGHT + { SID_ATTR_CHAR_UNDERLINE, true }, // EE_CHAR_UNDERLINE + { SID_ATTR_CHAR_STRIKEOUT, true }, // EE_CHAR_STRIKEOUT + { SID_ATTR_CHAR_POSTURE, true }, // EE_CHAR_ITALIC + { SID_ATTR_CHAR_CONTOUR, true }, // EE_CHAR_OUTLINE + { SID_ATTR_CHAR_SHADOWED, true }, // EE_CHAR_SHADOW + { SID_ATTR_CHAR_ESCAPEMENT, true }, // EE_CHAR_ESCAPEMENT + { SID_ATTR_CHAR_AUTOKERN, true }, // EE_CHAR_PAIRKERNING + { SID_ATTR_CHAR_KERNING, true }, // EE_CHAR_KERNING + { SID_ATTR_CHAR_WORDLINEMODE, true }, // EE_CHAR_WLM + { SID_ATTR_CHAR_LANGUAGE, true }, // EE_CHAR_LANGUAGE + { SID_ATTR_CHAR_CJK_LANGUAGE, true }, // EE_CHAR_LANGUAGE_CJK + { SID_ATTR_CHAR_CTL_LANGUAGE, true }, // EE_CHAR_LANGUAGE_CTL + { SID_ATTR_CHAR_CJK_FONT, true }, // EE_CHAR_FONTINFO_CJK + { SID_ATTR_CHAR_CTL_FONT, true }, // EE_CHAR_FONTINFO_CTL + { SID_ATTR_CHAR_CJK_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CJK + { SID_ATTR_CHAR_CTL_FONTHEIGHT, true }, // EE_CHAR_FONTHEIGHT_CTL + { SID_ATTR_CHAR_CJK_WEIGHT, true }, // EE_CHAR_WEIGHT_CJK + { SID_ATTR_CHAR_CTL_WEIGHT, true }, // EE_CHAR_WEIGHT_CTL + { SID_ATTR_CHAR_CJK_POSTURE, true }, // EE_CHAR_ITALIC_CJK + { SID_ATTR_CHAR_CTL_POSTURE, true }, // EE_CHAR_ITALIC_CTL + { SID_ATTR_CHAR_EMPHASISMARK, true }, // EE_CHAR_EMPHASISMARK + { SID_ATTR_CHAR_RELIEF, true }, // EE_CHAR_RELIEF + { 0, true }, // EE_CHAR_RUBI_DUMMY + { 0, true }, // EE_CHAR_XMLATTRIBS + { SID_ATTR_CHAR_OVERLINE, true }, // EE_CHAR_OVERLINE + { SID_ATTR_CHAR_CASEMAP, true }, // EE_CHAR_CASEMAP + { SID_ATTR_CHAR_GRABBAG, true }, // EE_CHAR_GRABBAG + { SID_ATTR_CHAR_BACK_COLOR, true }, // EE_CHAR_BKGCOLOR + { 0, true }, // EE_FEATURE_TAB + { 0, true }, // EE_FEATURE_LINEBR + { SID_ATTR_CHAR_CHARSETCOLOR, true }, // EE_FEATURE_NOTCONV + { SID_FIELD, false }, // EE_FEATURE_FIELD +}; + +EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE ) +{ + // Create a new attribute in the pool + const SfxPoolItem& rNew = rPool.Put( rAttr ); + + EditCharAttrib* pNew = nullptr; + switch( rNew.Which() ) + { + case EE_CHAR_LANGUAGE: + case EE_CHAR_LANGUAGE_CJK: + case EE_CHAR_LANGUAGE_CTL: + { + pNew = new EditCharAttribLanguage( static_cast<const SvxLanguageItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_COLOR: + { + pNew = new EditCharAttribColor( static_cast<const SvxColorItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTINFO: + case EE_CHAR_FONTINFO_CJK: + case EE_CHAR_FONTINFO_CTL: + { + pNew = new EditCharAttribFont( static_cast<const SvxFontItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + pNew = new EditCharAttribFontHeight( static_cast<const SvxFontHeightItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_FONTWIDTH: + { + pNew = new EditCharAttribFontWidth( static_cast<const SvxCharScaleWidthItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_WEIGHT: + case EE_CHAR_WEIGHT_CJK: + case EE_CHAR_WEIGHT_CTL: + { + pNew = new EditCharAttribWeight( static_cast<const SvxWeightItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_UNDERLINE: + { + pNew = new EditCharAttribUnderline( static_cast<const SvxUnderlineItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_OVERLINE: + { + pNew = new EditCharAttribOverline( static_cast<const SvxOverlineItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_EMPHASISMARK: + { + pNew = new EditCharAttribEmphasisMark( static_cast<const SvxEmphasisMarkItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_RELIEF: + { + pNew = new EditCharAttribRelief( static_cast<const SvxCharReliefItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_STRIKEOUT: + { + pNew = new EditCharAttribStrikeout( static_cast<const SvxCrossedOutItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_ITALIC: + case EE_CHAR_ITALIC_CJK: + case EE_CHAR_ITALIC_CTL: + { + pNew = new EditCharAttribItalic( static_cast<const SvxPostureItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_OUTLINE: + { + pNew = new EditCharAttribOutline( static_cast<const SvxContourItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_SHADOW: + { + pNew = new EditCharAttribShadow( static_cast<const SvxShadowedItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_ESCAPEMENT: + { + pNew = new EditCharAttribEscapement( static_cast<const SvxEscapementItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_PAIRKERNING: + { + pNew = new EditCharAttribPairKerning( static_cast<const SvxAutoKernItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_KERNING: + { + pNew = new EditCharAttribKerning( static_cast<const SvxKerningItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_WLM: + { + pNew = new EditCharAttribWordLineMode( static_cast<const SvxWordLineModeItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_XMLATTRIBS: + { + pNew = new EditCharAttrib( rNew, nS, nE ); // Attribute is only for holding XML information... + } + break; + case EE_CHAR_CASEMAP: + { + pNew = new EditCharAttribCaseMap( static_cast<const SvxCaseMapItem&>(rNew), nS, nE ); + } + break; + case EE_CHAR_GRABBAG: + { + pNew = new EditCharAttribGrabBag( static_cast<const SfxGrabBagItem&>(rNew), nS, nE ); + } + break; + case EE_FEATURE_TAB: + { + pNew = new EditCharAttribTab( static_cast<const SfxVoidItem&>(rNew), nS ); + } + break; + case EE_FEATURE_LINEBR: + { + pNew = new EditCharAttribLineBreak( static_cast<const SfxVoidItem&>(rNew), nS ); + } + break; + case EE_FEATURE_FIELD: + { + pNew = new EditCharAttribField( static_cast<const SvxFieldItem&>(rNew), nS ); + } + break; + case EE_CHAR_BKGCOLOR: + { + pNew = new EditCharAttribBackgroundColor( static_cast<const SvxBackgroundColorItem&>(rNew), nS, nE ); + } + break; + default: + { + OSL_FAIL( "Invalid Attribute!" ); + } + } + return pNew; +} + +TextPortionList::TextPortionList() +{ +} + +TextPortionList::~TextPortionList() +{ + Reset(); +} + +void TextPortionList::Reset() +{ + maPortions.clear(); +} + +void TextPortionList::DeleteFromPortion(sal_Int32 nDelFrom) +{ + assert((nDelFrom < static_cast<sal_Int32>(maPortions.size())) || ((nDelFrom == 0) && maPortions.empty())); + PortionsType::iterator it = maPortions.begin(); + std::advance(it, nDelFrom); + maPortions.erase(it, maPortions.end()); +} + +sal_Int32 TextPortionList::Count() const +{ + return static_cast<sal_Int32>(maPortions.size()); +} + +const TextPortion& TextPortionList::operator[](sal_Int32 nPos) const +{ + return *maPortions[nPos]; +} + +TextPortion& TextPortionList::operator[](sal_Int32 nPos) +{ + return *maPortions[nPos]; +} + +void TextPortionList::Append(TextPortion* p) +{ + maPortions.push_back(std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Insert(sal_Int32 nPos, TextPortion* p) +{ + maPortions.insert(maPortions.begin()+nPos, std::unique_ptr<TextPortion>(p)); +} + +void TextPortionList::Remove(sal_Int32 nPos) +{ + maPortions.erase(maPortions.begin()+nPos); +} + +namespace { + +class FindTextPortionByAddress +{ + const TextPortion* mp; +public: + explicit FindTextPortionByAddress(const TextPortion* p) : mp(p) {} + bool operator() (const std::unique_ptr<TextPortion>& v) const + { + return v.get() == mp; + } +}; + +} + +sal_Int32 TextPortionList::GetPos(const TextPortion* p) const +{ + PortionsType::const_iterator it = + std::find_if(maPortions.begin(), maPortions.end(), FindTextPortionByAddress(p)); + + if (it == maPortions.end()) + return std::numeric_limits<sal_Int32>::max(); // not found. + + return std::distance(maPortions.begin(), it); +} + +sal_Int32 TextPortionList::FindPortion( + sal_Int32 nCharPos, sal_Int32& nPortionStart, bool bPreferStartingPortion) const +{ + // When nCharPos at portion limit, the left portion is found + sal_Int32 nTmpPos = 0; + sal_Int32 n = maPortions.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nTmpPos = nTmpPos + rPortion.GetLen(); + if ( nTmpPos >= nCharPos ) + { + // take this one if we don't prefer the starting portion, or if it's the last one + if ( ( nTmpPos != nCharPos ) || !bPreferStartingPortion || ( i == n-1 ) ) + { + nPortionStart = nTmpPos - rPortion.GetLen(); + return i; + } + } + } + OSL_FAIL( "FindPortion: Not found!" ); + return n - 1; +} + +sal_Int32 TextPortionList::GetStartPos(sal_Int32 nPortion) +{ + sal_Int32 nPos = 0; + for (sal_Int32 i = 0; i < nPortion; ++i) + { + const TextPortion& rPortion = *maPortions[i]; + nPos = nPos + rPortion.GetLen(); + } + return nPos; +} + +ExtraPortionInfo::ExtraPortionInfo() +: nOrgWidth(0) +, nWidthFullCompression(0) +, nPortionOffsetX(0) +, nMaxCompression100thPercent(0) +, nAsianCompressionTypes(AsianCompressionFlags::Normal) +, bFirstCharIsRightPunktuation(false) +, bCompressed(false) +, lineBreaksList() +{ +} + +ExtraPortionInfo::~ExtraPortionInfo() +{ +} + +void ExtraPortionInfo::SaveOrgDXArray( const long* pDXArray, sal_Int32 nLen ) +{ + if (pDXArray) + { + pOrgDXArray.reset(new long[nLen]); + memcpy( pOrgDXArray.get(), pDXArray, nLen * sizeof(long) ); + } + else + pOrgDXArray.reset(); +} + +ParaPortion::ParaPortion( ContentNode* pN ) : + pNode(pN), + nHeight(0), + nInvalidPosStart(0), + nFirstLineOffset(0), + nBulletX(0), + nInvalidDiff(0), + bInvalid(true), + bSimple(false), + bVisible(true), + bForceRepaint(false) +{ +} + +ParaPortion::~ParaPortion() +{ +} + +void ParaPortion::MarkInvalid( sal_Int32 nStart, sal_Int32 nDiff ) +{ + if ( !bInvalid ) + { +// nInvalidPosEnd = nStart; // ??? => CreateLines + nInvalidPosStart = ( nDiff >= 0 ) ? nStart : ( nStart + nDiff ); + nInvalidDiff = nDiff; + } + else + { + // Simple tap in succession + if ( ( nDiff > 0 ) && ( nInvalidDiff > 0 ) && + ( ( nInvalidPosStart+nInvalidDiff ) == nStart ) ) + { + nInvalidDiff = nInvalidDiff + nDiff; + } + // Simple delete in succession + else if ( ( nDiff < 0 ) && ( nInvalidDiff < 0 ) && ( nInvalidPosStart == nStart ) ) + { + nInvalidPosStart = nInvalidPosStart + nDiff; + nInvalidDiff = nInvalidDiff + nDiff; + } + else + { +// nInvalidPosEnd = pNode->Len(); + DBG_ASSERT( ( nDiff >= 0 ) || ( (nStart+nDiff) >= 0 ), "MarkInvalid: Diff out of Range" ); + nInvalidPosStart = std::min( nInvalidPosStart, ( nDiff < 0 ? nStart+nDiff : nDiff ) ); + nInvalidDiff = 0; + bSimple = false; + } + } + bInvalid = true; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +void ParaPortion::MarkSelectionInvalid( sal_Int32 nStart ) +{ + if ( !bInvalid ) + { + nInvalidPosStart = nStart; + } + else + { + nInvalidPosStart = std::min( nInvalidPosStart, nStart ); + } + nInvalidDiff = 0; + bInvalid = true; + bSimple = false; + aScriptInfos.clear(); + aWritingDirectionInfos.clear(); +} + +sal_Int32 ParaPortion::GetLineNumber( sal_Int32 nIndex ) const +{ + SAL_WARN_IF( !aLineList.Count(), "editeng", "Empty ParaPortion in GetLine!" ); + DBG_ASSERT( bVisible, "Why GetLine() on an invisible paragraph?" ); + + for ( sal_Int32 nLine = 0; nLine < aLineList.Count(); nLine++ ) + { + if ( aLineList[nLine].IsIn( nIndex ) ) + return nLine; + } + + // Then it should be at the end of the last line! + DBG_ASSERT( nIndex == aLineList[ aLineList.Count() - 1 ].GetEnd(), "Index dead wrong!" ); + return (aLineList.Count()-1); +} + +void ParaPortion::SetVisible( bool bMakeVisible ) +{ + bVisible = bMakeVisible; +} + +void ParaPortion::CorrectValuesBehindLastFormattedLine( sal_Int32 nLastFormattedLine ) +{ + sal_Int32 nLines = aLineList.Count(); + DBG_ASSERT( nLines, "CorrectPortionNumbersFromLine: Empty Portion?" ); + if ( nLastFormattedLine < ( nLines - 1 ) ) + { + const EditLine& rLastFormatted = aLineList[ nLastFormattedLine ]; + const EditLine& rUnformatted = aLineList[ nLastFormattedLine+1 ]; + sal_Int32 nPortionDiff = rUnformatted.GetStartPortion() - rLastFormatted.GetEndPortion(); + sal_Int32 nTextDiff = rUnformatted.GetStart() - rLastFormatted.GetEnd(); + nTextDiff++; // LastFormatted->GetEnd() was included => 1 deducted too much! + + // The first unformatted must begin exactly one Portion behind the last + // of the formatted: + // If the modified line was split into one portion, can + // nLastEnd > nNextStart! + int nPDiff = -( nPortionDiff-1 ); + int nTDiff = -( nTextDiff-1 ); + if ( nPDiff || nTDiff ) + { + for ( sal_Int32 nL = nLastFormattedLine+1; nL < nLines; nL++ ) + { + EditLine& rLine = aLineList[ nL ]; + + rLine.GetStartPortion() = rLine.GetStartPortion() + nPDiff; + rLine.GetEndPortion() = rLine.GetEndPortion() + nPDiff; + + rLine.GetStart() = rLine.GetStart() + nTDiff; + rLine.GetEnd() = rLine.GetEnd() + nTDiff; + + rLine.SetValid(); + } + } + } + DBG_ASSERT( aLineList[ aLineList.Count()-1 ].GetEnd() == pNode->Len(), "CorrectLines: The end is not right!" ); +} + +// Shared reverse lookup acceleration pieces ... + +namespace { + +template<typename Array, typename Val> +sal_Int32 FastGetPos(const Array& rArray, const Val* p, sal_Int32& rLastPos) +{ + sal_Int32 nArrayLen = rArray.size(); + + // Through certain filter code-paths we do a lot of appends, which in + // turn call GetPos - creating some N^2 nightmares. If we have a + // non-trivially large list, do a few checks from the end first. + if (rLastPos > 16 && nArrayLen > 16) + { + sal_Int32 nEnd; + if (rLastPos > nArrayLen - 2) + nEnd = nArrayLen; + else + nEnd = rLastPos + 2; + + for (sal_Int32 nIdx = rLastPos - 2; nIdx < nEnd; ++nIdx) + { + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return nIdx; + } + } + } + // The world's lamest linear search from svarray... + for (sal_Int32 nIdx = 0; nIdx < nArrayLen; ++nIdx) + if (rArray.at(nIdx).get() == p) + { + rLastPos = nIdx; + return rLastPos; + } + + // XXX "not found" condition for sal_Int32 indexes + return EE_PARA_NOT_FOUND; +} + +} + +ParaPortionList::ParaPortionList() : nLastCache( 0 ) +{ +} + +ParaPortionList::~ParaPortionList() +{ +} + +sal_Int32 ParaPortionList::GetPos(const ParaPortion* p) const +{ + return FastGetPos(maPortions, p, nLastCache); +} + +ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr; +} + +const ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) const +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr; +} + +std::unique_ptr<ParaPortion> ParaPortionList::Release(sal_Int32 nPos) +{ + if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) <= nPos) + { + SAL_WARN( "editeng", "ParaPortionList::Release - out of bounds pos " << nPos); + return nullptr; + } + std::unique_ptr<ParaPortion> p = std::move(maPortions[nPos]); + maPortions.erase(maPortions.begin()+nPos); + return p; +} + +void ParaPortionList::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) <= nPos) + { + SAL_WARN( "editeng", "ParaPortionList::Remove - out of bounds pos " << nPos); + return; + } + maPortions.erase(maPortions.begin()+nPos); +} + +void ParaPortionList::Insert(sal_Int32 nPos, std::unique_ptr<ParaPortion> p) +{ + if (nPos < 0 || static_cast<sal_Int32>(maPortions.size()) < nPos) + { + SAL_WARN( "editeng", "ParaPortionList::Insert - out of bounds pos " << nPos); + return; + } + maPortions.insert(maPortions.begin()+nPos, std::move(p)); +} + +void ParaPortionList::Append(std::unique_ptr<ParaPortion> p) +{ + maPortions.push_back(std::move(p)); +} + +sal_Int32 ParaPortionList::Count() const +{ + size_t nSize = maPortions.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "ParaPortionList::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +void ParaPortionList::Reset() +{ + maPortions.clear(); +} + +long ParaPortionList::GetYOffset(const ParaPortion* pPPortion) const +{ + long nHeight = 0; + for (const auto & rPortion : maPortions) + { + const ParaPortion* pTmpPortion = rPortion.get(); + if ( pTmpPortion == pPPortion ) + return nHeight; + nHeight += pTmpPortion->GetHeight(); + } + OSL_FAIL( "GetYOffset: Portion not found" ); + return nHeight; +} + +sal_Int32 ParaPortionList::FindParagraph(long nYOffset) const +{ + long nY = 0; + for (size_t i = 0, n = maPortions.size(); i < n; ++i) + { + nY += maPortions[i]->GetHeight(); // should also be correct even in bVisible! + if ( nY > nYOffset ) + return i <= SAL_MAX_INT32 ? static_cast<sal_Int32>(i) : SAL_MAX_INT32; + } + return EE_PARA_NOT_FOUND; +} + +const ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr; +} + +ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos) +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maPortions.size()) ? maPortions[nPos].get() : nullptr; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void +ParaPortionList::DbgCheck(ParaPortionList const& rParas, EditDoc const& rDoc) +{ + assert(rParas.Count() == rDoc.Count()); + for (sal_Int32 i = 0; i < rParas.Count(); ++i) + { + assert(rParas.SafeGetObject(i) != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() != nullptr); + assert(rParas.SafeGetObject(i)->GetNode() == rDoc.GetObject(i)); + } +} +#endif + +ContentAttribsInfo::ContentAttribsInfo( const SfxItemSet& rParaAttribs ) : + aPrevParaAttribs( rParaAttribs) +{ +} + +void ContentAttribsInfo::RemoveAllCharAttribsFromPool(SfxItemPool& rPool) const +{ + for (const std::unique_ptr<EditCharAttrib>& rAttrib : aPrevCharAttribs) + rPool.Remove(*rAttrib->GetItem()); +} + +void ContentAttribsInfo::AppendCharAttrib(EditCharAttrib* pNew) +{ + aPrevCharAttribs.push_back(std::unique_ptr<EditCharAttrib>(pNew)); +} + +void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit ) +{ + DBG_ASSERT( eSourceUnit != eDestUnit, "ConvertItem - Why?!" ); + + switch ( rPoolItem->Which() ) + { + case EE_PARA_LRSPACE: + { + assert(dynamic_cast<const SvxLRSpaceItem *>(rPoolItem.get()) != nullptr); + SvxLRSpaceItem& rItem = static_cast<SvxLRSpaceItem&>(*rPoolItem); + rItem.SetTextFirstLineOffset( sal::static_int_cast< short >( OutputDevice::LogicToLogic( rItem.GetTextFirstLineOffset(), eSourceUnit, eDestUnit ) ) ); + rItem.SetTextLeft( OutputDevice::LogicToLogic( rItem.GetTextLeft(), eSourceUnit, eDestUnit ) ); + rItem.SetRight( OutputDevice::LogicToLogic( rItem.GetRight(), eSourceUnit, eDestUnit ) ); + } + break; + case EE_PARA_ULSPACE: + { + assert(dynamic_cast<const SvxULSpaceItem *>(rPoolItem.get()) != nullptr); + SvxULSpaceItem& rItem = static_cast<SvxULSpaceItem&>(*rPoolItem); + rItem.SetUpper( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetUpper(), eSourceUnit, eDestUnit ) ) ); + rItem.SetLower( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLower(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_SBL: + { + assert(dynamic_cast<const SvxLineSpacingItem *>(rPoolItem.get()) != nullptr); + SvxLineSpacingItem& rItem = static_cast<SvxLineSpacingItem&>(*rPoolItem); + // SetLineHeight changes also eLineSpace! + if ( rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min ) + rItem.SetLineHeight( sal::static_int_cast< sal_uInt16 >( OutputDevice::LogicToLogic( rItem.GetLineHeight(), eSourceUnit, eDestUnit ) ) ); + } + break; + case EE_PARA_TABS: + { + assert(dynamic_cast<const SvxTabStopItem *>(rPoolItem.get()) != nullptr); + SvxTabStopItem& rItem = static_cast<SvxTabStopItem&>(*rPoolItem); + SvxTabStopItem* pNewItem(new SvxTabStopItem(EE_PARA_TABS)); + for ( sal_uInt16 i = 0; i < rItem.Count(); i++ ) + { + const SvxTabStop& rTab = rItem[i]; + SvxTabStop aNewStop( OutputDevice::LogicToLogic( rTab.GetTabPos(), eSourceUnit, eDestUnit ), rTab.GetAdjustment(), rTab.GetDecimal(), rTab.GetFill() ); + pNewItem->Insert( aNewStop ); + } + rPoolItem.reset(pNewItem); + } + break; + case EE_CHAR_FONTHEIGHT: + case EE_CHAR_FONTHEIGHT_CJK: + case EE_CHAR_FONTHEIGHT_CTL: + { + assert(dynamic_cast<const SvxFontHeightItem *>(rPoolItem.get()) != nullptr); + SvxFontHeightItem& rItem = static_cast<SvxFontHeightItem&>(*rPoolItem); + rItem.SetHeight( OutputDevice::LogicToLogic( rItem.GetHeight(), eSourceUnit, eDestUnit ) ); + } + break; + } +} + +void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit, const MapUnit* pDestUnit ) +{ + const SfxItemPool* pSourcePool = rSource.GetPool(); + const SfxItemPool* pDestPool = rDest.GetPool(); + + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // If possible go through SlotID ... + + sal_uInt16 nSourceWhich = nWhich; + sal_uInt16 nSlot = pDestPool->GetTrueSlotId( nWhich ); + if ( nSlot ) + { + sal_uInt16 nW = pSourcePool->GetTrueWhich( nSlot ); + if ( nW ) + nSourceWhich = nW; + } + + if ( rSource.GetItemState( nSourceWhich, false ) == SfxItemState::SET ) + { + MapUnit eSourceUnit = pSourceUnit ? *pSourceUnit : pSourcePool->GetMetric( nSourceWhich ); + MapUnit eDestUnit = pDestUnit ? *pDestUnit : pDestPool->GetMetric( nWhich ); + if ( eSourceUnit != eDestUnit ) + { + std::unique_ptr<SfxPoolItem> pItem(rSource.Get( nSourceWhich ).Clone()); + ConvertItem( pItem, eSourceUnit, eDestUnit ); + pItem->SetWhich(nWhich); + rDest.Put( std::move(pItem) ); + } + else + { + rDest.Put( rSource.Get( nSourceWhich ).CloneSetWhich(nWhich) ); + } + } + } +} + +EditLine::EditLine() : + nTxtWidth(0), + nStartPosX(0), + nStart(0), + nEnd(0), + nStartPortion(0), // to be able to tell the difference between a line + // without Portions from one with the Portion number 0 + nEndPortion(0), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(false), + bInvalid(true) +{ +} + +EditLine::EditLine( const EditLine& r ) : + nTxtWidth(0), + nStartPosX(0), + nStart(r.nStart), + nEnd(r.nEnd), + nStartPortion(r.nStartPortion), + nEndPortion(r.nEndPortion), + nHeight(0), + nTxtHeight(0), + nMaxAscent(0), + bHangingPunctuation(r.bHangingPunctuation), + bInvalid(true) +{ +} + +EditLine::~EditLine() +{ +} + + +EditLine* EditLine::Clone() const +{ + EditLine* pL = new EditLine; + pL->aPositions = aPositions; + pL->nStartPosX = nStartPosX; + pL->nStart = nStart; + pL->nEnd = nEnd; + pL->nStartPortion = nStartPortion; + pL->nEndPortion = nEndPortion; + pL->nHeight = nHeight; + pL->nTxtWidth = nTxtWidth; + pL->nTxtHeight = nTxtHeight; + pL->nMaxAscent = nMaxAscent; + + return pL; +} + +bool operator == ( const EditLine& r1, const EditLine& r2 ) +{ + if ( r1.nStart != r2.nStart ) + return false; + + if ( r1.nEnd != r2.nEnd ) + return false; + + if ( r1.nStartPortion != r2.nStartPortion ) + return false; + + if ( r1.nEndPortion != r2.nEndPortion ) + return false; + + return true; +} + +EditLine& EditLine::operator = ( const EditLine& r ) +{ + nEnd = r.nEnd; + nStart = r.nStart; + nEndPortion = r.nEndPortion; + nStartPortion = r.nStartPortion; + return *this; +} + + +void EditLine::SetHeight( sal_uInt16 nH, sal_uInt16 nTxtH ) +{ + nHeight = nH; + nTxtHeight = ( nTxtH ? nTxtH : nH ); +} + +void EditLine::SetStartPosX( long start ) +{ + if (start > 0) + nStartPosX = start; + else + nStartPosX = 0; +} + +Size EditLine::CalcTextSize( ParaPortion& rParaPortion ) +{ + Size aSz; + Size aTmpSz; + + DBG_ASSERT( rParaPortion.GetTextPortions().Count(), "GetTextSize before CreatePortions !" ); + + for ( sal_Int32 n = nStartPortion; n <= nEndPortion; n++ ) + { + TextPortion& rPortion = rParaPortion.GetTextPortions()[n]; + switch ( rPortion.GetKind() ) + { + case PortionKind::TEXT: + case PortionKind::FIELD: + case PortionKind::HYPHENATOR: + { + aTmpSz = rPortion.GetSize(); + aSz.AdjustWidth(aTmpSz.Width() ); + if ( aSz.Height() < aTmpSz.Height() ) + aSz.setHeight( aTmpSz.Height() ); + } + break; + case PortionKind::TAB: + { + aSz.AdjustWidth(rPortion.GetSize().Width() ); + } + break; + case PortionKind::LINEBREAK: break; + } + } + + SetHeight( static_cast<sal_uInt16>(aSz.Height()) ); + return aSz; +} + +EditLineList::EditLineList() +{ +} + +EditLineList::~EditLineList() +{ + Reset(); +} + +void EditLineList::Reset() +{ + maLines.clear(); +} + +void EditLineList::DeleteFromLine(sal_Int32 nDelFrom) +{ + assert(nDelFrom <= (static_cast<sal_Int32>(maLines.size()) - 1)); + LinesType::iterator it = maLines.begin(); + std::advance(it, nDelFrom); + maLines.erase(it, maLines.end()); +} + +sal_Int32 EditLineList::FindLine(sal_Int32 nChar, bool bInclEnd) +{ + sal_Int32 n = maLines.size(); + for (sal_Int32 i = 0; i < n; ++i) + { + const EditLine& rLine = *maLines[i]; + if ( (bInclEnd && (rLine.GetEnd() >= nChar)) || + (rLine.GetEnd() > nChar) ) + { + return i; + } + } + + DBG_ASSERT( !bInclEnd, "Line not found: FindLine" ); + return n - 1; +} + +sal_Int32 EditLineList::Count() const +{ + return maLines.size(); +} + +const EditLine& EditLineList::operator[](sal_Int32 nPos) const +{ + return *maLines[nPos]; +} + +EditLine& EditLineList::operator[](sal_Int32 nPos) +{ + return *maLines[nPos]; +} + +void EditLineList::Append(EditLine* p) +{ + maLines.push_back(std::unique_ptr<EditLine>(p)); +} + +void EditLineList::Insert(sal_Int32 nPos, EditLine* p) +{ + maLines.insert(maLines.begin()+nPos, std::unique_ptr<EditLine>(p)); +} + +EditPaM::EditPaM() : pNode(nullptr), nIndex(0) {} +EditPaM::EditPaM(ContentNode* p, sal_Int32 n) : pNode(p), nIndex(n) {} + + +void EditPaM::SetNode(ContentNode* p) +{ + pNode = p; +} + +bool EditPaM::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return !pNode || + rDoc.GetPos( pNode ) >= rDoc.Count() || + nIndex > pNode->Len(); +} + +bool EditSelection::DbgIsBuggy( EditDoc const & rDoc ) const +{ + return aStartPaM.DbgIsBuggy( rDoc ) || aEndPaM.DbgIsBuggy( rDoc ); +} + +EditSelection::EditSelection() +{ +} + +EditSelection::EditSelection( const EditPaM& rStartAndAnd ) : + aStartPaM(rStartAndAnd), + aEndPaM(rStartAndAnd) +{ +} + +EditSelection::EditSelection( const EditPaM& rStart, const EditPaM& rEnd ) : + aStartPaM(rStart), + aEndPaM(rEnd) +{ +} + +EditSelection& EditSelection::operator = ( const EditPaM& rPaM ) +{ + aStartPaM = rPaM; + aEndPaM = rPaM; + return *this; +} + +void EditSelection::Adjust( const EditDoc& rNodes ) +{ + DBG_ASSERT( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in Adjust(1)" ); + DBG_ASSERT( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in Adjust(2)" ); + + const ContentNode* pStartNode = aStartPaM.GetNode(); + const ContentNode* pEndNode = aEndPaM.GetNode(); + + sal_Int32 nStartNode = rNodes.GetPos( pStartNode ); + sal_Int32 nEndNode = rNodes.GetPos( pEndNode ); + + DBG_ASSERT( nStartNode != SAL_MAX_INT32, "Node out of range in Adjust(1)" ); + DBG_ASSERT( nEndNode != SAL_MAX_INT32, "Node out of range in Adjust(2)" ); + + const bool bSwap = ( nStartNode > nEndNode ) || + ( ( nStartNode == nEndNode ) && + ( aStartPaM.GetIndex() > aEndPaM.GetIndex() ) ); + + if ( bSwap ) + { + EditPaM aTmpPaM( aStartPaM ); + aStartPaM = aEndPaM; + aEndPaM = aTmpPaM; + } +} + +bool operator == ( const EditPaM& r1, const EditPaM& r2 ) +{ + return ( r1.GetNode() == r2.GetNode() ) && + ( r1.GetIndex() == r2.GetIndex() ); +} + +bool operator != ( const EditPaM& r1, const EditPaM& r2 ) +{ + return !( r1 == r2 ); +} + +ContentNode::ContentNode( SfxItemPool& rPool ) : aContentAttribs( rPool ) +{ +} + +ContentNode::ContentNode( const OUString& rStr, const ContentAttribs& rContentAttribs ) : + maString(rStr), aContentAttribs(rContentAttribs) +{ +} + +ContentNode::~ContentNode() +{ +} + +void ContentNode::ExpandAttribs( sal_Int32 nIndex, sal_Int32 nNew, SfxItemPool& rItemPool ) +{ + if ( !nNew ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list. // In every if ..., in the next (n) opportunities due to bFeature or + // an existing special case, must (n-1) opportunities be provided with + // bResort. The most likely possibility receives no bResort, so that is + // not sorted anew when all attributes are the same. + bool bResort = false; + bool bExpandedEmptyAtIndexNull = false; + + sal_Int32 nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all attributes behind the insertion point... + if ( pAttrib->GetStart() > nIndex ) + { + pAttrib->MoveForward( nNew ); + } + // 0: Expand empty attribute, if at insertion point + else if ( pAttrib->IsEmpty() ) + { + // Do not check Index, an empty one could only be there + // When later checking it anyhow: + // Special case: Start == 0; AbsLen == 1, nNew = 1 + // => Expand, because of paragraph break! + // Start <= nIndex, End >= nIndex => Start=End=nIndex! +// if ( pAttrib->GetStart() == nIndex ) + pAttrib->Expand( nNew ); + bResort = true; + if ( pAttrib->GetStart() == 0 ) + bExpandedEmptyAtIndexNull = true; + } + // 1: Attribute starts before, goes to index ... + else if ( pAttrib->GetEnd() == nIndex ) // Start must be before + { + // Only expand when there is no feature + // and if not in exclude list! + // Otherwise, a UL will go on until a new ULDB, expanding both +// if ( !pAttrib->IsFeature() && !rExclList.FindAttrib( pAttrib->Which() ) ) + if ( !pAttrib->IsFeature() && !aCharAttribList.FindEmptyAttrib( pAttrib->Which(), nIndex ) ) + { + if ( !pAttrib->IsEdge() ) + pAttrib->Expand( nNew ); + } + else + bResort = true; + } + // 2: Attribute starts before, goes past the Index... + else if ( ( pAttrib->GetStart() < nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Large Feature?!" ); + pAttrib->Expand( nNew ); + } + // 3: Attribute starts on index... + else if ( pAttrib->GetStart() == nIndex ) + { + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveForward( nNew ); + bResort = true; + } + else + { + bool bExpand = false; + if ( nIndex == 0 ) + { + bExpand = true; + if( bExpandedEmptyAtIndexNull ) + { + // Check if this kind of attribute was empty and expanded here... + sal_uInt16 nW = pAttrib->GetItem()->Which(); + for ( sal_Int32 nA = 0; nA < nAttr; nA++ ) + { + const EditCharAttrib& r = *aCharAttribList.GetAttribs()[nA]; + if ( ( r.GetStart() == 0 ) && ( r.GetItem()->Which() == nW ) ) + { + bExpand = false; + break; + } + } + + } + } + if ( bExpand ) + { + pAttrib->Expand( nNew ); + bResort = true; + } + else + { + pAttrib->MoveForward( nNew ); + } + } + } + } + + if ( pAttrib->IsEdge() ) + pAttrib->SetEdge(false); + + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Expand: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len() ), "Expand: Attribute larger than paragraph!" ); + if ( pAttrib->IsEmpty() ) + { + OSL_FAIL( "Empty Attribute after ExpandAttribs?" ); + bResort = true; + rItemPool.Remove( *pAttrib->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + --nAttr; + } + ++nAttr; + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + { + bool bSep = ( maString[ nIndex ] == ' ' ) || IsFeature( nIndex ); + mpWrongList->TextInserted( nIndex, nNew, bSep ); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CollapseAttribs( sal_Int32 nIndex, sal_Int32 nDeleted, SfxItemPool& rItemPool ) +{ + if ( !nDeleted ) + return; + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif + + // Since features are treated differently than normal character attributes, + // but can also affect the order of the start list + bool bResort = false; + sal_Int32 nEndChanges = nIndex+nDeleted; + + sal_Int32 nAttr = 0; + CharAttribList::AttribsType& rAttribs = aCharAttribList.GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr); + while ( pAttrib ) + { + bool bDelAttr = false; + if ( pAttrib->GetEnd() >= nIndex ) + { + // Move all Attribute behind the insert point... + if ( pAttrib->GetStart() >= nEndChanges ) + { + pAttrib->MoveBackward( nDeleted ); + } + // 1. Delete Internal attributes... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() <= nEndChanges ) ) + { + // Special case: Attribute covers the area exactly + // => keep as empty Attribute. + if ( !pAttrib->IsFeature() && ( pAttrib->GetStart() == nIndex ) && ( pAttrib->GetEnd() == nEndChanges ) ) + { + pAttrib->GetEnd() = nIndex; // empty + bResort = true; + } + else + bDelAttr = true; + } + // 2. Attribute starts earlier, ends inside or behind it ... + else if ( ( pAttrib->GetStart() <= nIndex ) && ( pAttrib->GetEnd() > nIndex ) ) + { + DBG_ASSERT( !pAttrib->IsFeature(), "Collapsing Feature!" ); + if ( pAttrib->GetEnd() <= nEndChanges ) // ends inside + pAttrib->GetEnd() = nIndex; + else + pAttrib->Collaps( nDeleted ); // ends behind + } + // 3. Attribute starts inside, ending behind ... + else if ( ( pAttrib->GetStart() >= nIndex ) && ( pAttrib->GetEnd() > nEndChanges ) ) + { + // Features not allowed to expand! + if ( pAttrib->IsFeature() ) + { + pAttrib->MoveBackward( nDeleted ); + bResort = true; + } + else + { + pAttrib->GetStart() = nEndChanges; + pAttrib->MoveBackward( nDeleted ); + } + } + } + DBG_ASSERT( !pAttrib->IsFeature() || ( pAttrib->GetLen() == 1 ), "Expand: FeaturesLen != 1" ); + + DBG_ASSERT( pAttrib->GetStart() <= pAttrib->GetEnd(), "Collapse: Attribute distorted!" ); + DBG_ASSERT( ( pAttrib->GetEnd() <= Len()) || bDelAttr, "Collapse: Attribute larger than paragraph!" ); + if ( bDelAttr ) + { + bResort = true; + rItemPool.Remove( *pAttrib->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + nAttr--; + } + else if ( pAttrib->IsEmpty() ) + aCharAttribList.SetHasEmptyAttribs(true); + + nAttr++; + pAttrib = GetAttrib(rAttribs, nAttr); + } + + if ( bResort ) + aCharAttribList.ResortAttribs(); + + if (mpWrongList) + mpWrongList->TextDeleted(nIndex, nDeleted); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); +#endif +} + +void ContentNode::CopyAndCutAttribs( ContentNode* pPrevNode, SfxItemPool& rPool, bool bKeepEndingAttribs ) +{ + assert(pPrevNode); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif + + sal_Int32 nCut = pPrevNode->Len(); + + sal_Int32 nAttr = 0; + CharAttribList::AttribsType& rPrevAttribs = pPrevNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rPrevAttribs, nAttr); + while ( pAttrib ) + { + if ( pAttrib->GetEnd() < nCut ) + { + // remain unchanged... + ; + } + else if ( pAttrib->GetEnd() == nCut ) + { + // must be copied as an empty attributes. + if ( bKeepEndingAttribs && !pAttrib->IsFeature() && !aCharAttribList.FindAttrib( pAttrib->GetItem()->Which(), 0 ) ) + { + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, 0 ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + } + } + else if ( pAttrib->IsInside( nCut ) || ( !nCut && !pAttrib->GetStart() && !pAttrib->IsFeature() ) ) + { + // If cut is done right at the front then the attribute must be + // kept! Has to be copied and changed. + EditCharAttrib* pNewAttrib = MakeCharAttrib( rPool, *(pAttrib->GetItem()), 0, pAttrib->GetEnd()-nCut ); + assert(pNewAttrib); + aCharAttribList.InsertAttrib( pNewAttrib ); + pAttrib->GetEnd() = nCut; + } + else + { + // Move all attributes in the current node (this) + CharAttribList::AttribsType::iterator it = rPrevAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rPrevAttribs.erase(it); + pAttrib->MoveBackward( nCut ); + nAttr--; + } + nAttr++; + pAttrib = GetAttrib(rPrevAttribs, nAttr); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pPrevNode->aCharAttribList); +#endif +} + +void ContentNode::AppendAttribs( ContentNode* pNextNode ) +{ + assert(pNextNode); + + sal_Int32 nNewStart = maString.getLength(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif + + sal_Int32 nAttr = 0; + CharAttribList::AttribsType& rNextAttribs = pNextNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttrib = GetAttrib(rNextAttribs, nAttr); + while ( pAttrib ) + { + // Move all attributes in the current node (this) + bool bMelted = false; + if ( ( pAttrib->GetStart() == 0 ) && ( !pAttrib->IsFeature() ) ) + { + // Attributes can possibly be summarized as: + sal_Int32 nTmpAttr = 0; + EditCharAttrib* pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + while ( !bMelted && pTmpAttrib ) + { + if ( pTmpAttrib->GetEnd() == nNewStart ) + { + if (pTmpAttrib->Which() == pAttrib->Which()) + { + // prevent adding 2 0-length attributes at same position + if ((*(pTmpAttrib->GetItem()) == *(pAttrib->GetItem())) + || (0 == pAttrib->GetLen())) + { + pTmpAttrib->GetEnd() = + pTmpAttrib->GetEnd() + pAttrib->GetLen(); + rNextAttribs.erase(rNextAttribs.begin()+nAttr); + // Unsubscribe from the pool?! + bMelted = true; + } + else if (0 == pTmpAttrib->GetLen()) + { + aCharAttribList.Remove(nTmpAttr); + --nTmpAttr; // to cancel later increment... + } + } + } + ++nTmpAttr; + pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr ); + } + } + + if ( !bMelted ) + { + pAttrib->GetStart() = pAttrib->GetStart() + nNewStart; + pAttrib->GetEnd() = pAttrib->GetEnd() + nNewStart; + CharAttribList::AttribsType::iterator it = rNextAttribs.begin() + nAttr; + aCharAttribList.InsertAttrib(it->release()); + rNextAttribs.erase(it); + } + pAttrib = GetAttrib(rNextAttribs, nAttr); + } + // For the Attributes that just moved over: + rNextAttribs.clear(); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(aCharAttribList); + CharAttribList::DbgCheckAttribs(pNextNode->aCharAttribList); +#endif +} + +void ContentNode::CreateDefFont() +{ + // First use the information from the style ... + SfxStyleSheet* pS = aContentAttribs.GetStyleSheet(); + if ( pS ) + CreateFont( GetCharAttribs().GetDefFont(), pS->GetItemSet() ); + + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, const SvxFont& rFontFromStyle ) +{ + aContentAttribs.SetStyleSheet( pS ); + + + // First use the information from the style ... + GetCharAttribs().GetDefFont() = rFontFromStyle; + // ... then iron out the hard paragraph formatting... + CreateFont( GetCharAttribs().GetDefFont(), + GetContentAttribs().GetItems(), pS == nullptr ); +} + +void ContentNode::SetStyleSheet( SfxStyleSheet* pS, bool bRecalcFont ) +{ + aContentAttribs.SetStyleSheet( pS ); + if ( bRecalcFont ) + CreateDefFont(); +} + +bool ContentNode::IsFeature( sal_Int32 nPos ) const +{ + return maString[nPos] == CH_FEATURE; +} + +sal_Int32 ContentNode::Len() const +{ + return maString.getLength(); +} + +sal_uLong ContentNode::GetExpandedLen() const +{ + sal_uLong nLen = maString.getLength(); + + // Fields can be longer than the placeholder in the Node + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (sal_Int32 nAttr = rAttrs.size(); nAttr; ) + { + const EditCharAttrib& rAttr = *rAttrs[--nAttr]; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + nLen += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + --nLen; // Standalone, to avoid corner cases when previous getLength() returns 0 + } + } + + return nLen; +} + +OUString ContentNode::GetExpandedText(sal_Int32 nStartPos, sal_Int32 nEndPos) const +{ + if ( nEndPos < 0 || nEndPos > Len() ) + nEndPos = Len(); + + DBG_ASSERT( nStartPos <= nEndPos, "Start and End reversed?" ); + + sal_Int32 nIndex = nStartPos; + OUStringBuffer aStr(256); + const EditCharAttrib* pNextFeature = GetCharAttribs().FindFeature( nIndex ); + while ( nIndex < nEndPos ) + { + sal_Int32 nEnd = nEndPos; + if ( pNextFeature && ( pNextFeature->GetStart() < nEnd ) ) + nEnd = pNextFeature->GetStart(); + else + pNextFeature = nullptr; // Feature does not interest the below + + DBG_ASSERT( nEnd >= nIndex, "End in front of the index?" ); + //!! beware of sub string length of -1 + if (nEnd > nIndex) + aStr.append( std::u16string_view(GetString()).substr(nIndex, nEnd - nIndex) ); + + if ( pNextFeature ) + { + switch ( pNextFeature->GetItem()->Which() ) + { + case EE_FEATURE_TAB: aStr.append( "\t" ); + break; + case EE_FEATURE_LINEBR: aStr.append( "\x0A" ); + break; + case EE_FEATURE_FIELD: + aStr.append( static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue() ); + break; + default: OSL_FAIL( "What feature?" ); + } + pNextFeature = GetCharAttribs().FindFeature( ++nEnd ); + } + nIndex = nEnd; + } + return aStr.makeStringAndClear(); +} + +void ContentNode::UnExpandPosition( sal_Int32 &rPos, bool bBiasStart ) +{ + sal_Int32 nOffset = 0; + + const CharAttribList::AttribsType& rAttrs = GetCharAttribs().GetAttribs(); + for (size_t nAttr = 0; nAttr < rAttrs.size(); ++nAttr ) + { + const EditCharAttrib& rAttr = *rAttrs[nAttr]; + assert (!(nAttr < rAttrs.size() - 1) || + rAttrs[nAttr]->GetStart() <= rAttrs[nAttr + 1]->GetStart()); + + nOffset = rAttr.GetStart(); + + if (nOffset >= rPos) // happens after the position + return; + + sal_Int32 nChunk = 0; + if (rAttr.Which() == EE_FEATURE_FIELD) + { + nChunk += static_cast<const EditCharAttribField&>(rAttr).GetFieldValue().getLength(); + nChunk--; // Character representing the field in the string + + if (nOffset + nChunk >= rPos) // we're inside the field + { + if (bBiasStart) + rPos = rAttr.GetStart(); + else + rPos = rAttr.GetEnd(); + return; + } + // Adjust for the position + rPos -= nChunk; + } + } + assert (rPos <= Len()); +} + +/* + * Fields are represented by a single character in the underlying string + * and/or selection, however, they can be expanded to the full value of + * the field. When we're dealing with selection / offsets however we need + * to deal in character positions inside the real (unexpanded) string. + * This method maps us back to character offsets. + */ +void ContentNode::UnExpandPositions( sal_Int32 &rStartPos, sal_Int32 &rEndPos ) +{ + UnExpandPosition( rStartPos, true ); + UnExpandPosition( rEndPos, false ); +} + +void ContentNode::SetChar(sal_Int32 nPos, sal_Unicode c) +{ + maString = maString.replaceAt(nPos, 1, OUString(c)); +} + +void ContentNode::Insert(const OUString& rStr, sal_Int32 nPos) +{ + maString = maString.replaceAt(nPos, 0, rStr); +} + +void ContentNode::Append(const OUString& rStr) +{ + maString += rStr; +} + +void ContentNode::Erase(sal_Int32 nPos) +{ + maString = maString.copy(0, nPos); +} + +void ContentNode::Erase(sal_Int32 nPos, sal_Int32 nCount) +{ + maString = maString.replaceAt(nPos, nCount, ""); +} + +OUString ContentNode::Copy(sal_Int32 nPos) const +{ + return maString.copy(nPos); +} + +OUString ContentNode::Copy(sal_Int32 nPos, sal_Int32 nCount) const +{ + return maString.copy(nPos, nCount); +} + +sal_Unicode ContentNode::GetChar(sal_Int32 nPos) const +{ + return maString[nPos]; +} + +void ContentNode::EnsureWrongList() +{ + if (!mpWrongList) + CreateWrongList(); +} + +WrongList* ContentNode::GetWrongList() +{ + return mpWrongList.get(); +} + +const WrongList* ContentNode::GetWrongList() const +{ + return mpWrongList.get(); +} + +void ContentNode::SetWrongList( WrongList* p ) +{ + mpWrongList.reset(p); +} + +void ContentNode::CreateWrongList() +{ + SAL_WARN_IF( mpWrongList && !mpWrongList->empty(), "editeng", "WrongList already exist!"); + if (!mpWrongList || !mpWrongList->empty()) + mpWrongList.reset(new WrongList); +} + +void ContentNode::DestroyWrongList() +{ + mpWrongList.reset(); +} + +void ContentNode::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("ContentNode")); + xmlTextWriterWriteAttribute(pWriter, BAD_CAST("maString"), BAD_CAST(maString.toUtf8().getStr())); + aContentAttribs.dumpAsXml(pWriter); + aCharAttribList.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + + +ContentAttribs::ContentAttribs( SfxItemPool& rPool ) +: pStyle(nullptr) +, aAttribSet( rPool, svl::Items<EE_PARA_START, EE_CHAR_END>{} ) +{ +} + + +SvxTabStop ContentAttribs::FindTabStop( sal_Int32 nCurPos, sal_uInt16 nDefTab ) +{ + const SvxTabStopItem& rTabs = GetItem( EE_PARA_TABS ); + for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ ) + { + const SvxTabStop& rTab = rTabs[i]; + if ( rTab.GetTabPos() > nCurPos ) + return rTab; + } + + // Determine DefTab ... + SvxTabStop aTabStop; + const sal_Int32 x = nCurPos / nDefTab + 1; + aTabStop.GetTabPos() = nDefTab * x; + return aTabStop; +} + +void ContentAttribs::SetStyleSheet( SfxStyleSheet* pS ) +{ + bool bStyleChanged = ( pStyle != pS ); + pStyle = pS; + // Only when other style sheet, not when current style sheet modified + if ( !(pStyle && bStyleChanged) ) + return; + + // Selectively remove the attributes from the paragraph formatting + // which are specified in the style, so that the attributes of the + // style can have an affect. + const SfxItemSet& rStyleAttribs = pStyle->GetItemSet(); + for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ ) + { + // Don't change bullet on/off + if ( ( nWhich != EE_PARA_BULLETSTATE ) && ( rStyleAttribs.GetItemState( nWhich ) == SfxItemState::SET ) ) + aAttribSet.ClearItem( nWhich ); + } +} + +const SfxPoolItem& ContentAttribs::GetItem( sal_uInt16 nWhich ) const +{ + // Hard paragraph attributes take precedence! + const SfxItemSet* pTakeFrom = &aAttribSet; + if ( pStyle && ( aAttribSet.GetItemState( nWhich, false ) != SfxItemState::SET ) ) + pTakeFrom = &pStyle->GetItemSet(); + + return pTakeFrom->Get( nWhich ); +} + +bool ContentAttribs::HasItem( sal_uInt16 nWhich ) const +{ + bool bHasItem = false; + if ( aAttribSet.GetItemState( nWhich, false ) == SfxItemState::SET ) + bHasItem = true; + else if ( pStyle && pStyle->GetItemSet().GetItemState( nWhich ) == SfxItemState::SET ) + bHasItem = true; + + return bHasItem; +} + +void ContentAttribs::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("ContentAttribs")); + xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("style"), "%s", pStyle->GetName().toUtf8().getStr()); + aAttribSet.dumpAsXml(pWriter); + xmlTextWriterEndElement(pWriter); +} + + +ItemList::ItemList() : CurrentItem( 0 ) +{ +} + +const SfxPoolItem* ItemList::First() +{ + CurrentItem = 0; + return aItemPool.empty() ? nullptr : aItemPool[ 0 ]; +} + +const SfxPoolItem* ItemList::Next() +{ + if ( CurrentItem + 1 < static_cast<sal_Int32>(aItemPool.size()) ) + { + ++CurrentItem; + return aItemPool[ CurrentItem ]; + } + return nullptr; +} + +void ItemList::Insert( const SfxPoolItem* pItem ) +{ + aItemPool.push_back( pItem ); + CurrentItem = aItemPool.size() - 1; +} + + +EditDoc::EditDoc( SfxItemPool* pPool ) : + nLastCache(0), + pItemPool(pPool ? pPool : new EditEngineItemPool()), + nDefTab(DEFTAB), + bIsVertical(false), + mnRotation(TextRotation::NONE), + bIsFixedCellHeight(false), + bOwnerOfPool(pPool == nullptr), + bModified(false), + bDisableAttributeExpanding(false) +{ + // Don't create an empty node, Clear() will be called in EditEngine-CTOR +}; + +EditDoc::~EditDoc() +{ + ImplDestroyContents(); + if ( bOwnerOfPool ) + SfxItemPool::Free(pItemPool); +} + +namespace { + +class RemoveEachItemFromPool +{ + EditDoc& mrDoc; +public: + explicit RemoveEachItemFromPool(EditDoc& rDoc) : mrDoc(rDoc) {} + void operator() (const std::unique_ptr<ContentNode>& rNode) + { + mrDoc.RemoveItemsFromPool(*rNode); + } +}; + +struct ClearSpellErrorsHandler +{ + void operator() (std::unique_ptr<ContentNode> const & rNode) + { + rNode->DestroyWrongList(); + } +}; + +} + +void EditDoc::ImplDestroyContents() +{ + std::for_each(maContents.begin(), maContents.end(), RemoveEachItemFromPool(*this)); + maContents.clear(); +} + +void EditDoc::RemoveItemsFromPool(const ContentNode& rNode) +{ + for (sal_Int32 nAttr = 0; nAttr < rNode.GetCharAttribs().Count(); ++nAttr) + { + const EditCharAttrib& rAttr = *rNode.GetCharAttribs().GetAttribs()[nAttr]; + GetItemPool().Remove(*rAttr.GetItem()); + } +} + +void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType ) +{ + vcl::Font aPrevFont( rFont ); + rFont.SetAlignment( ALIGN_BASELINE ); + rFont.SetTransparent( true ); + + sal_uInt16 nWhich_FontInfo = GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ); + sal_uInt16 nWhich_Language = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ); + sal_uInt16 nWhich_FontHeight = GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ); + sal_uInt16 nWhich_Weight = GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ); + sal_uInt16 nWhich_Italic = GetScriptItemId( EE_CHAR_ITALIC, nScriptType ); + + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontInfo ) == SfxItemState::SET ) ) + { + const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(rSet.Get( nWhich_FontInfo )); + rFont.SetFamilyName( rFontItem.GetFamilyName() ); + rFont.SetFamily( rFontItem.GetFamily() ); + rFont.SetPitch( rFontItem.GetPitch() ); + rFont.SetCharSet( rFontItem.GetCharSet() ); + } + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Language ) == SfxItemState::SET ) ) + rFont.SetLanguage( static_cast<const SvxLanguageItem&>(rSet.Get( nWhich_Language )).GetLanguage() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_COLOR ) == SfxItemState::SET ) ) + rFont.SetColor( rSet.Get( EE_CHAR_COLOR ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_BKGCOLOR ) == SfxItemState::SET ) ) + rFont.SetFillColor( rSet.Get( EE_CHAR_BKGCOLOR ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_FontHeight ) == SfxItemState::SET ) ) + rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem&>(rSet.Get( nWhich_FontHeight ) ).GetHeight() ) ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Weight ) == SfxItemState::SET ) ) + rFont.SetWeight( static_cast<const SvxWeightItem&>(rSet.Get( nWhich_Weight )).GetWeight() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) ) + rFont.SetUnderline( rSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OVERLINE ) == SfxItemState::SET ) ) + rFont.SetOverline( rSet.Get( EE_CHAR_OVERLINE ).GetLineStyle() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) ) + rFont.SetStrikeout( rSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_CASEMAP ) == SfxItemState::SET ) ) + rFont.SetCaseMap( rSet.Get( EE_CHAR_CASEMAP ).GetCaseMap() ); + if ( bSearchInParent || ( rSet.GetItemState( nWhich_Italic ) == SfxItemState::SET ) ) + rFont.SetItalic( static_cast<const SvxPostureItem&>(rSet.Get( nWhich_Italic )).GetPosture() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_OUTLINE ) == SfxItemState::SET ) ) + rFont.SetOutline( rSet.Get( EE_CHAR_OUTLINE ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_SHADOW ) == SfxItemState::SET ) ) + rFont.SetShadow( rSet.Get( EE_CHAR_SHADOW ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_ESCAPEMENT ) == SfxItemState::SET ) ) + { + const SvxEscapementItem& rEsc = rSet.Get( EE_CHAR_ESCAPEMENT ); + + sal_uInt16 const nProp = rEsc.GetProportionalHeight(); + rFont.SetPropr( static_cast<sal_uInt8>(nProp) ); + + short nEsc = rEsc.GetEsc(); + rFont.SetNonAutoEscapement( nEsc ); + } + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_PAIRKERNING ) == SfxItemState::SET ) ) + rFont.SetKerning( rSet.Get( EE_CHAR_PAIRKERNING ).GetValue() ? FontKerning::FontSpecific : FontKerning::NONE ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_KERNING ) == SfxItemState::SET ) ) + rFont.SetFixKerning( rSet.Get( EE_CHAR_KERNING ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_WLM ) == SfxItemState::SET ) ) + rFont.SetWordLineMode( rSet.Get( EE_CHAR_WLM ).GetValue() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_EMPHASISMARK ) == SfxItemState::SET ) ) + rFont.SetEmphasisMark( rSet.Get( EE_CHAR_EMPHASISMARK ).GetEmphasisMark() ); + if ( bSearchInParent || ( rSet.GetItemState( EE_CHAR_RELIEF ) == SfxItemState::SET ) ) + rFont.SetRelief( rSet.Get( EE_CHAR_RELIEF ).GetValue() ); + + // Operator == compares the individual members of the font if the impl pointer is + // not equal. If all members are the same, this assignment makes + // sure that both also point to the same internal instance of the font. + // To avoid this assignment, you would need to check in + // every if statement above whether or not the new value differs from the + // old value before making an assignment. + if ( rFont == aPrevFont ) + rFont = aPrevFont; // => The same ImpPointer for IsSameInstance +} + +void EditDoc::CreateDefFont( bool bUseStyles ) +{ + SfxItemSet aTmpSet( GetItemPool(), svl::Items<EE_PARA_START, EE_CHAR_END>{} ); + CreateFont( aDefFont, aTmpSet ); + aDefFont.SetVertical( IsVertical() ); + aDefFont.SetOrientation( IsVertical() ? (IsTopToBottom() ? 2700 : 900) : 0 ); + + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + ContentNode* pNode = GetObject( nNode ); + pNode->GetCharAttribs().GetDefFont() = aDefFont; + if ( bUseStyles ) + pNode->CreateDefFont(); + } +} + +bool EditDoc::IsVertical() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation != TextRotation::NONE); +} + +bool EditDoc::IsTopToBottom() const +{ + return (bIsVertical && mnRotation == TextRotation::NONE) || + (!bIsVertical && mnRotation == TextRotation::TOPTOBOTTOM); +} + +bool EditDoc::GetDirectVertical() const +{ + return bIsVertical; +} + +sal_Int32 EditDoc::GetPos(const ContentNode* p) const +{ + return FastGetPos(maContents, p, nLastCache); +} + +const ContentNode* EditDoc::GetObject(sal_Int32 nPos) const +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maContents.size()) ? maContents[nPos].get() : nullptr; +} + +ContentNode* EditDoc::GetObject(sal_Int32 nPos) +{ + return 0 <= nPos && nPos < static_cast<sal_Int32>(maContents.size()) ? maContents[nPos].get() : nullptr; +} + +const ContentNode* EditDoc::operator[](sal_Int32 nPos) const +{ + return GetObject(nPos); +} + +ContentNode* EditDoc::operator[](sal_Int32 nPos) +{ + return GetObject(nPos); +} + +void EditDoc::Insert(sal_Int32 nPos, ContentNode* p) +{ + if (nPos < 0 || nPos == SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Insert - overflow pos " << nPos); + return; + } + maContents.insert(maContents.begin()+nPos, std::unique_ptr<ContentNode>(p)); +} + +void EditDoc::Remove(sal_Int32 nPos) +{ + if (nPos < 0 || nPos >= static_cast<sal_Int32>(maContents.size())) + { + SAL_WARN( "editeng", "EditDoc::Remove - out of bounds pos " << nPos); + return; + } + maContents.erase(maContents.begin() + nPos); +} + +void EditDoc::Release(sal_Int32 nPos) +{ + if (nPos < 0 || nPos >= static_cast<sal_Int32>(maContents.size())) + { + SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos); + return; + } + (void)maContents[nPos].release(); + maContents.erase(maContents.begin() + nPos); +} + +sal_Int32 EditDoc::Count() const +{ + size_t nSize = maContents.size(); + if (nSize > SAL_MAX_INT32) + { + SAL_WARN( "editeng", "EditDoc::Count - overflow " << nSize); + return SAL_MAX_INT32; + } + return nSize; +} + +OUString EditDoc::GetSepStr( LineEnd eEnd ) +{ + if ( eEnd == LINEEND_CR ) + return "\015"; // 0x0d + if ( eEnd == LINEEND_LF ) + return "\012"; // 0x0a + return "\015\012"; // 0x0d, 0x0a +} + +OUString EditDoc::GetText( LineEnd eEnd ) const +{ + const sal_Int32 nNodes = Count(); + if (nNodes == 0) + return OUString(); + + const OUString aSep = EditDoc::GetSepStr( eEnd ); + const sal_Int32 nSepSize = aSep.getLength(); + const sal_uInt32 nLen = GetTextLen() + (nNodes - 1)*nSepSize; + + OUStringBuffer aBuffer(nLen + 16); // leave some slack + + for ( sal_Int32 nNode = 0; nNode < nNodes; nNode++ ) + { + if ( nSepSize && nNode>0 ) + { + aBuffer.append(aSep); + } + aBuffer.append(GetParaAsString( GetObject(nNode) )); + } + + return aBuffer.makeStringAndClear(); +} + +OUString EditDoc::GetParaAsString( sal_Int32 nNode ) const +{ + return GetParaAsString( GetObject( nNode ) ); +} + +OUString EditDoc::GetParaAsString( + const ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos) +{ + return pNode->GetExpandedText(nStartPos, nEndPos); +} + +EditPaM EditDoc::GetStartPaM() const +{ + ContentNode* p = const_cast<ContentNode*>(GetObject(0)); + return EditPaM(p, 0); +} + +EditPaM EditDoc::GetEndPaM() const +{ + ContentNode* pLastNode = const_cast<ContentNode*>(GetObject(Count()-1)); + return EditPaM( pLastNode, pLastNode->Len() ); +} + +sal_uLong EditDoc::GetTextLen() const +{ + sal_uLong nLen = 0; + for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ ) + { + const ContentNode* pNode = GetObject( nNode ); + nLen += pNode->GetExpandedLen(); + } + return nLen; +} + +EditPaM EditDoc::Clear() +{ + ImplDestroyContents(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + CreateDefFont(false); + + SetModified(false); + + return EditPaM( pNode, 0 ); +} + +void EditDoc::ClearSpellErrors() +{ + std::for_each(maContents.begin(), maContents.end(), ClearSpellErrorsHandler()); +} + +void EditDoc::SetModified( bool b ) +{ + bModified = b; + if ( bModified ) + { + aModifyHdl.Call( nullptr ); + } +} + +EditPaM EditDoc::RemoveText() +{ + // Keep the old ItemSet, to keep the chart Font. + ContentNode* pPrevFirstNode = GetObject(0); + SfxStyleSheet* pPrevStyle = pPrevFirstNode->GetStyleSheet(); + SfxItemSet aPrevSet( pPrevFirstNode->GetContentAttribs().GetItems() ); + vcl::Font aPrevFont( pPrevFirstNode->GetCharAttribs().GetDefFont() ); + + ImplDestroyContents(); + + ContentNode* pNode = new ContentNode( GetItemPool() ); + Insert(0, pNode); + + pNode->SetStyleSheet(pPrevStyle, false); + pNode->GetContentAttribs().GetItems().Set( aPrevSet ); + pNode->GetCharAttribs().GetDefFont() = aPrevFont; + + SetModified(true); + + return EditPaM( pNode, 0 ); +} + +EditPaM EditDoc::InsertText( EditPaM aPaM, const OUString& rStr ) +{ + DBG_ASSERT( rStr.indexOf( 0x0A ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.indexOf( 0x0D ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + DBG_ASSERT( rStr.indexOf( '\t' ) == -1, "EditDoc::InsertText: Newlines prohibited in paragraph!" ); + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( rStr, aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), rStr.getLength(), GetItemPool() ); + aPaM.SetIndex( aPaM.GetIndex() + rStr.getLength() ); + + SetModified( true ); + + return aPaM; +} + +EditPaM EditDoc::InsertParaBreak( EditPaM aPaM, bool bKeepEndingAttribs ) +{ + assert(aPaM.GetNode()); + ContentNode* pCurNode = aPaM.GetNode(); + sal_Int32 nPos = GetPos( pCurNode ); + OUString aStr = aPaM.GetNode()->Copy( aPaM.GetIndex() ); + aPaM.GetNode()->Erase( aPaM.GetIndex() ); + + // the paragraph attributes... + ContentAttribs aContentAttribs( aPaM.GetNode()->GetContentAttribs() ); + + // for a new paragraph we like to have the bullet/numbering visible by default + aContentAttribs.GetItems().Put( SfxBoolItem( EE_PARA_BULLETSTATE, true) ); + + // ContentNode constructor copies also the paragraph attributes + ContentNode* pNode = new ContentNode( aStr, aContentAttribs ); + + // Copy the Default Font + pNode->GetCharAttribs().GetDefFont() = aPaM.GetNode()->GetCharAttribs().GetDefFont(); + SfxStyleSheet* pStyle = aPaM.GetNode()->GetStyleSheet(); + if ( pStyle ) + { + OUString aFollow( pStyle->GetFollow() ); + if ( !aFollow.isEmpty() && ( aFollow != pStyle->GetName() ) ) + { + SfxStyleSheetBase* pNext = pStyle->GetPool()->Find( aFollow, pStyle->GetFamily() ); + pNode->SetStyleSheet( static_cast<SfxStyleSheet*>(pNext) ); + } + } + + // Character attributes may need to be copied or trimmed: + pNode->CopyAndCutAttribs( aPaM.GetNode(), GetItemPool(), bKeepEndingAttribs ); + + Insert(nPos+1, pNode); + + SetModified(true); + + aPaM.SetNode( pNode ); + aPaM.SetIndex( 0 ); + return aPaM; +} + +EditPaM EditDoc::InsertFeature( EditPaM aPaM, const SfxPoolItem& rItem ) +{ + assert(aPaM.GetNode()); + + aPaM.GetNode()->Insert( OUString(CH_FEATURE), aPaM.GetIndex() ); + aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), 1, GetItemPool() ); + + // Create a feature-attribute for the feature... + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rItem, aPaM.GetIndex(), aPaM.GetIndex()+1 ); + assert(pAttrib); + aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); + + aPaM.SetIndex( aPaM.GetIndex() + 1 ); + return aPaM; +} + +EditPaM EditDoc::ConnectParagraphs( ContentNode* pLeft, ContentNode* pRight ) +{ + const EditPaM aPaM( pLeft, pLeft->Len() ); + + // First the attributes, otherwise nLen will not be correct! + pLeft->AppendAttribs( pRight ); + // then the Text... + pLeft->Append(pRight->GetString()); + + // the one to the right disappears. + RemoveItemsFromPool(*pRight); + sal_Int32 nRight = GetPos( pRight ); + Remove( nRight ); + + SetModified(true); + + return aPaM; +} + +void EditDoc::RemoveChars( EditPaM aPaM, sal_Int32 nChars ) +{ + // Maybe remove Features! + aPaM.GetNode()->Erase( aPaM.GetIndex(), nChars ); + aPaM.GetNode()->CollapseAttribs( aPaM.GetIndex(), nChars, GetItemPool() ); + + SetModified( true ); +} + +void EditDoc::InsertAttribInSelection( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // for Optimization: + // This ends at the beginning of the selection => can be expanded + EditCharAttrib* pEndingAttrib = nullptr; + // This starts at the end of the selection => can be expanded + EditCharAttrib* pStartingAttrib = nullptr; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + + RemoveAttribs( pNode, nStart, nEnd, pStartingAttrib, pEndingAttrib, rPoolItem.Which() ); + + // tdf#132288 By default inserting an attribute beside another that is of + // the same type expands the original instead of inserting another. But the + // spell check dialog doesn't want that behaviour + if (bDisableAttributeExpanding) + { + pStartingAttrib = nullptr; + pEndingAttrib = nullptr; + } + + if ( pStartingAttrib && pEndingAttrib && + ( *(pStartingAttrib->GetItem()) == rPoolItem ) && + ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + { + // Will become a large Attribute. + pEndingAttrib->GetEnd() = pStartingAttrib->GetEnd(); + GetItemPool().Remove( *(pStartingAttrib->GetItem()) ); + pNode->GetCharAttribs().Remove(pStartingAttrib); + } + else if ( pStartingAttrib && ( *(pStartingAttrib->GetItem()) == rPoolItem ) ) + pStartingAttrib->GetStart() = nStart; + else if ( pEndingAttrib && ( *(pEndingAttrib->GetItem()) == rPoolItem ) ) + pEndingAttrib->GetEnd() = nEnd; + else + InsertAttrib( rPoolItem, pNode, nStart, nEnd ); + + if ( pStartingAttrib ) + pNode->GetCharAttribs().ResortAttribs(); + + SetModified(true); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, sal_uInt16 nWhich ) +{ + EditCharAttrib* pStarting; + EditCharAttrib* pEnding; + return RemoveAttribs( pNode, nStart, nEnd, pStarting, pEnding, nWhich ); +} + +bool EditDoc::RemoveAttribs( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, EditCharAttrib*& rpStarting, EditCharAttrib*& rpEnding, sal_uInt16 nWhich ) +{ + + assert(pNode); + DBG_ASSERT( nEnd <= pNode->Len(), "InsertAttrib: Attribute too large!" ); + + // This ends at the beginning of the selection => can be expanded + rpEnding = nullptr; + // This starts at the end of the selection => can be expanded + rpStarting = nullptr; + + bool bChanged = false; + + DBG_ASSERT( nStart <= nEnd, "Small miscalculations in InsertAttribInSelection" ); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + // iterate over the attributes ... + sal_Int32 nAttr = 0; + CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs(); + EditCharAttrib* pAttr = GetAttrib(rAttribs, nAttr); + while ( pAttr ) + { + bool bRemoveAttrib = false; + sal_uInt16 nAttrWhich = pAttr->Which(); + if ( ( nAttrWhich < EE_FEATURE_START ) && ( !nWhich || ( nAttrWhich == nWhich ) ) ) + { + // Attribute starts in Selection + if ( ( pAttr->GetStart() >= nStart ) && ( pAttr->GetStart() <= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetEnd() > nEnd ) + { + pAttr->GetStart() = nEnd; // then it starts after this + rpStarting = pAttr; + if ( nWhich ) + break; // There can be no further attributes here + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + + // Attribute ends in Selection + else if ( ( pAttr->GetEnd() >= nStart ) && ( pAttr->GetEnd() <= nEnd ) ) + { + bChanged = true; + if ( ( pAttr->GetStart() < nStart ) && !pAttr->IsFeature() ) + { + pAttr->GetEnd() = nStart; // then it ends here + rpEnding = pAttr; + } + else if ( !pAttr->IsFeature() || ( pAttr->GetStart() == nStart ) ) + { + // Delete feature only if on the exact spot + bRemoveAttrib = true; + } + } + // Attribute overlaps the selection + else if ( ( pAttr->GetStart() <= nStart ) && ( pAttr->GetEnd() >= nEnd ) ) + { + bChanged = true; + if ( pAttr->GetStart() == nStart ) + { + pAttr->GetStart() = nEnd; + rpStarting = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else if ( pAttr->GetEnd() == nEnd ) + { + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + if ( nWhich ) + break; // There can be further attributes! + } + else // Attribute must be split ... + { + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + rpEnding = pAttr; + InsertAttrib( *pAttr->GetItem(), pNode, nEnd, nOldEnd ); + if ( nWhich ) + break; // There can be further attributes! + } + } + } + if ( bRemoveAttrib ) + { + DBG_ASSERT( ( pAttr != rpStarting ) && ( pAttr != rpEnding ), "Delete and retain the same attribute?" ); + DBG_ASSERT( !pAttr->IsFeature(), "RemoveAttribs: Remove a feature?!" ); + GetItemPool().Remove( *pAttr->GetItem() ); + rAttribs.erase(rAttribs.begin()+nAttr); + nAttr--; + } + nAttr++; + pAttr = GetAttrib(rAttribs, nAttr); + } + + if ( bChanged ) + { + // char attributes need to be sorted by start again + pNode->GetCharAttribs().ResortAttribs(); + SetModified(true); + } + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs()); +#endif + + return bChanged; +} + +void EditDoc::InsertAttrib( const SfxPoolItem& rPoolItem, ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd ) +{ + // This method no longer checks whether a corresponding attribute already + // exists at this place! + EditCharAttrib* pAttrib = MakeCharAttrib( GetItemPool(), rPoolItem, nStart, nEnd ); + assert(pAttrib); + pNode->GetCharAttribs().InsertAttrib( pAttrib ); + + SetModified( true ); +} + +void EditDoc::InsertAttrib( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, const SfxPoolItem& rPoolItem ) +{ + if ( nStart != nEnd ) + { + InsertAttribInSelection( pNode, nStart, nEnd, rPoolItem ); + } + else + { + // Check whether already a new attribute with WhichId exists at this place: + CharAttribList& rAttrList = pNode->GetCharAttribs(); + EditCharAttrib* pAttr = rAttrList.FindEmptyAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + // Remove attribute... + rAttrList.Remove(pAttr); + } + + // check whether 'the same' attribute exist at this place. + pAttr = rAttrList.FindAttrib( rPoolItem.Which(), nStart ); + if ( pAttr ) + { + if ( pAttr->IsInside( nStart ) ) // split + { + // check again if really splitting, or return ! + sal_Int32 nOldEnd = pAttr->GetEnd(); + pAttr->GetEnd() = nStart; + EditCharAttrib* pNew = MakeCharAttrib( GetItemPool(), *(pAttr->GetItem()), nStart, nOldEnd ); + rAttrList.InsertAttrib(pNew); + } + else if ( pAttr->GetEnd() == nStart ) + { + DBG_ASSERT( !pAttr->IsEmpty(), "Still an empty attribute?" ); + // Check if exactly the same attribute + if ( *(pAttr->GetItem()) == rPoolItem ) + return; + } + } + InsertAttrib( rPoolItem, pNode, nStart, nStart ); + } + + SetModified( true ); +} + +void EditDoc::FindAttribs( ContentNode* pNode, sal_Int32 nStartPos, sal_Int32 nEndPos, SfxItemSet& rCurSet ) +{ + assert(pNode); + DBG_ASSERT( nStartPos <= nEndPos, "Invalid region!" ); + + sal_uInt16 nAttr = 0; + EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + // No Selection... + if ( nStartPos == nEndPos ) + { + while ( pAttr && ( pAttr->GetStart() <= nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + pItem = pAttr->GetItem(); + // Attribute ending here is not empty + else if ( ( pAttr->GetStart() < nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + if ( !pNode->GetCharAttribs().FindEmptyAttrib( pAttr->GetItem()->Which(), nStartPos ) ) + pItem = pAttr->GetItem(); + } + // Attribute ending here is empty + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() == nStartPos ) ) + { + pItem = pAttr->GetItem(); + } + // Attribute starts here + else if ( ( pAttr->GetStart() == nStartPos ) && ( pAttr->GetEnd() > nStartPos ) ) + { + if ( nStartPos == 0 ) // special case + pItem = pAttr->GetItem(); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } + else // Selection + { + while ( pAttr && ( pAttr->GetStart() < nEndPos) ) + { + const SfxPoolItem* pItem = nullptr; + // Attribute is about... + if ( ( pAttr->GetStart() <= nStartPos ) && ( pAttr->GetEnd() >= nEndPos ) ) + pItem = pAttr->GetItem(); + // Attribute starts right in the middle ... + else if ( pAttr->GetStart() >= nStartPos ) + { + // !!! pItem = pAttr->GetItem(); + // PItem is simply not enough, since one for example in case + // of Shadow, would never find an unequal item, since such a + // item represents its presence by absence! + // If (...) + // It needs to be examined on exactly the same attribute at the + // break point, which is quite expensive. + // Since optimization is done when inserting the attributes + // this case does not appear so fast... + // So based on the need for speed: + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + + } + // Attribute ends in the middle of it ... + else if ( pAttr->GetEnd() > nStartPos ) + { + rCurSet.InvalidateItem( pAttr->GetItem()->Which() ); + } + + if ( pItem ) + { + sal_uInt16 nWhich = pItem->Which(); + if ( rCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT ) + { + rCurSet.Put( *pItem ); + } + else if ( rCurSet.GetItemState( nWhich ) == SfxItemState::SET ) + { + const SfxPoolItem& rItem = rCurSet.Get( nWhich ); + if ( rItem != *pItem ) + { + rCurSet.InvalidateItem( nWhich ); + } + } + } + nAttr++; + pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr ); + } + } +} + +void EditDoc::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + bool bOwns = false; + if (!pWriter) + { + pWriter = xmlNewTextWriterFilename("editdoc.xml", 0); + xmlTextWriterSetIndent(pWriter,1); + xmlTextWriterSetIndentString(pWriter, BAD_CAST(" ")); + xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr); + bOwns = true; + } + + xmlTextWriterStartElement(pWriter, BAD_CAST("EditDoc")); + for (auto const & i : maContents) + { + i->dumpAsXml(pWriter); + } + xmlTextWriterEndElement(pWriter); + + if (bOwns) + { + xmlTextWriterEndDocument(pWriter); + xmlFreeTextWriter(pWriter); + } +} + + +namespace { + +struct LessByStart +{ + bool operator() (const std::unique_ptr<EditCharAttrib>& left, const std::unique_ptr<EditCharAttrib>& right) const + { + return left->GetStart() < right->GetStart(); + } +}; + +} + +CharAttribList::CharAttribList() +: aAttribs() +, aDefFont() +, bHasEmptyAttribs(false) +{ +} + +CharAttribList::~CharAttribList() +{ +} + +void CharAttribList::InsertAttrib( EditCharAttrib* pAttrib ) +{ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// optimize: binary search? ! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Maybe just simply iterate backwards: + // The most common and critical case: Attributes are already sorted + // (InsertBinTextObject!) binary search would not be optimal here. + // => Would bring something! + + const sal_Int32 nStart = pAttrib->GetStart(); // may be better for Comp.Opt. + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + + if ( pAttrib->IsEmpty() ) + bHasEmptyAttribs = true; + + bool bInsert(true); + for (sal_Int32 i = 0, n = aAttribs.size(); i < n; ++i) + { + const EditCharAttrib& rCurAttrib = *aAttribs[i]; + if (rCurAttrib.GetStart() > nStart) + { + aAttribs.insert(aAttribs.begin()+i, std::unique_ptr<EditCharAttrib>(pAttrib)); + bInsert = false; + break; + } + } + + if (bInsert) aAttribs.push_back(std::unique_ptr<EditCharAttrib>(pAttrib)); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::ResortAttribs() +{ + std::sort(aAttribs.begin(), aAttribs.end(), LessByStart()); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +void CharAttribList::OptimizeRanges( SfxItemPool& rItemPool ) +{ +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif + for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAttribs.size()); ++i) + { + EditCharAttrib& rAttr = *aAttribs[i]; + for (sal_Int32 nNext = i+1; nNext < static_cast<sal_Int32>(aAttribs.size()); ++nNext) + { + EditCharAttrib& rNext = *aAttribs[nNext]; + if (!rAttr.IsFeature() && rNext.GetStart() == rAttr.GetEnd() && rNext.Which() == rAttr.Which()) + { + if (*rNext.GetItem() == *rAttr.GetItem()) + { + rAttr.GetEnd() = rNext.GetEnd(); + rItemPool.Remove(*rNext.GetItem()); + aAttribs.erase(aAttribs.begin()+nNext); + } + break; // only 1 attr with same which can start here. + } + else if (rNext.GetStart() > rAttr.GetEnd()) + { + break; + } + } + } +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + CharAttribList::DbgCheckAttribs(*this); +#endif +} + +sal_Int32 CharAttribList::Count() const +{ + return aAttribs.size(); +} + +const EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](const AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + const EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +EditCharAttrib* CharAttribList::FindAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::reverse_iterator it = std::find_if(aAttribs.rbegin(), aAttribs.rend(), + [&nWhich, &nPos](AttribsType::value_type& rxAttr) { + return rxAttr->Which() == nWhich && rxAttr->IsIn(nPos); }); + if (it != aAttribs.rend()) + { + EditCharAttrib& rAttr = **it; + return &rAttr; + } + return nullptr; +} + +const EditCharAttrib* CharAttribList::FindNextAttrib( sal_uInt16 nWhich, sal_Int32 nFromPos ) const +{ + assert(nWhich); + for (auto const& attrib : aAttribs) + { + const EditCharAttrib& rAttr = *attrib; + if (rAttr.GetStart() >= nFromPos && rAttr.Which() == nWhich) + return &rAttr; + } + return nullptr; +} + +bool CharAttribList::HasAttrib( sal_Int32 nStartPos, sal_Int32 nEndPos ) const +{ + return std::any_of(aAttribs.rbegin(), aAttribs.rend(), + [&nStartPos, &nEndPos](const AttribsType::value_type& rxAttr) { + return rxAttr->GetStart() < nEndPos && rxAttr->GetEnd() > nStartPos; }); +} + + +namespace { + +class FindByAddress +{ + const EditCharAttrib* mpAttr; +public: + explicit FindByAddress(const EditCharAttrib* p) : mpAttr(p) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r.get() == mpAttr; + } +}; + +} + +void CharAttribList::Remove(const EditCharAttrib* p) +{ + AttribsType::iterator it = std::find_if(aAttribs.begin(), aAttribs.end(), FindByAddress(p)); + if (it != aAttribs.end()) + aAttribs.erase(it); +} + +void CharAttribList::Remove(sal_Int32 nPos) +{ + if (nPos >= static_cast<sal_Int32>(aAttribs.size())) + return; + + aAttribs.erase(aAttribs.begin()+nPos); +} + +void CharAttribList::SetHasEmptyAttribs(bool b) +{ + bHasEmptyAttribs = b; +} + +bool CharAttribList::HasBoundingAttrib( sal_Int32 nBound ) const +{ + // Backwards, if one ends where the next starts. + // => The starting one is the valid one ... + AttribsType::const_reverse_iterator it = aAttribs.rbegin(), itEnd = aAttribs.rend(); + for (; it != itEnd; ++it) + { + const EditCharAttrib& rAttr = **it; + if (rAttr.GetEnd() < nBound) + return false; + + if (rAttr.GetStart() == nBound || rAttr.GetEnd() == nBound) + return true; + } + return false; +} + +EditCharAttrib* CharAttribList::FindEmptyAttrib( sal_uInt16 nWhich, sal_Int32 nPos ) +{ + if ( !bHasEmptyAttribs ) + return nullptr; + + for (const std::unique_ptr<EditCharAttrib>& rAttr : aAttribs) + { + if (rAttr->GetStart() == nPos && rAttr->GetEnd() == nPos && rAttr->Which() == nWhich) + return rAttr.get(); + } + return nullptr; +} + +namespace { + +class FindByStartPos +{ + sal_Int32 mnPos; +public: + explicit FindByStartPos(sal_Int32 nPos) : mnPos(nPos) {} + bool operator() (const std::unique_ptr<EditCharAttrib>& r) const + { + return r->GetStart() >= mnPos; + } +}; + +} + +const EditCharAttrib* CharAttribList::FindFeature( sal_Int32 nPos ) const +{ + // First, find the first attribute that starts at or after specified position. + AttribsType::const_iterator it = + std::find_if(aAttribs.begin(), aAttribs.end(), FindByStartPos(nPos)); + + if (it == aAttribs.end()) + // All attributes are before the specified position. + return nullptr; + + // And find the first attribute with feature. + it = std::find_if(it, aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsFeature(); } ); + return it == aAttribs.end() ? nullptr : it->get(); +} + +namespace { + +class RemoveEmptyAttrItem +{ + SfxItemPool& mrItemPool; +public: + explicit RemoveEmptyAttrItem(SfxItemPool& rPool) : mrItemPool(rPool) {} + void operator() (const std::unique_ptr<EditCharAttrib>& r) + { + if (r->IsEmpty()) + mrItemPool.Remove(*r->GetItem()); + } +}; + +} + +void CharAttribList::DeleteEmptyAttribs( SfxItemPool& rItemPool ) +{ + std::for_each(aAttribs.begin(), aAttribs.end(), RemoveEmptyAttrItem(rItemPool)); + aAttribs.erase( std::remove_if(aAttribs.begin(), aAttribs.end(), [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsEmpty(); } ), aAttribs.end() ); + bHasEmptyAttribs = false; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void CharAttribList::DbgCheckAttribs(CharAttribList const& rAttribs) +{ + std::set<std::pair<sal_Int32, sal_uInt16>> zero_set; + for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs.aAttribs) + { + assert(rAttr->GetStart() <= rAttr->GetEnd()); + assert(!rAttr->IsFeature() || rAttr->GetLen() == 1); + if (0 == rAttr->GetLen()) + { + // not sure if 0-length attributes allowed at all in non-empty para? + assert(zero_set.insert(std::make_pair(rAttr->GetStart(), rAttr->Which())).second && "duplicate 0-length attribute detected"); + } + } + CheckOrderedList(rAttribs.GetAttribs()); +} +#endif + +void CharAttribList::dumpAsXml(xmlTextWriterPtr pWriter) const +{ + xmlTextWriterStartElement(pWriter, BAD_CAST("CharAttribList")); + for (auto const & i : aAttribs) { + i->dumpAsXml(pWriter); + } + xmlTextWriterEndElement(pWriter); +} + +EditEngineItemPool::EditEngineItemPool() + : SfxItemPool( "EditEngineItemPool", EE_ITEMS_START, EE_ITEMS_END, + aItemInfos, nullptr ) +{ + m_xDefItems = EditDLL::Get().GetGlobalData()->GetDefItems(); + SetDefaults(m_xDefItems->getDefaults()); +} + +EditEngineItemPool::~EditEngineItemPool() +{ + ClearDefaults(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |